From 8ed075090bf030421a5a8d848c8f261c62440e5a Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Fri, 28 Oct 2022 11:20:59 +0100 Subject: [PATCH 01/32] Implement FITS Compression algorithms in Codec classes --- astropy/io/fits/tiled_compression/__init__.py | 1 + .../fits/tiled_compression/setup_package.py | 23 + .../fits/tiled_compression/src/compression.c | 286 +++++++++ .../tiled_compression/src/fits_hcompress.h | 6 + .../tiled_compression/src/fits_hdecompress.h | 6 + .../io/fits/tiled_compression/src/fitsio2.h | 47 ++ .../io/fits/tiled_compression/src/pliocomp.h | 2 + .../io/fits/tiled_compression/src/ricecomp.h | 6 + .../fits/tiled_compression/tests/__init__.py | 0 .../tests/test_tiled_compression.py | 216 +++++++ .../tiled_compression/tiled_compression.py | 545 ++++++++++++++++++ docs/io/fits/api/tiled_compression.rst | 5 + docs/io/fits/index.rst | 1 + 13 files changed, 1144 insertions(+) create mode 100644 astropy/io/fits/tiled_compression/__init__.py create mode 100644 astropy/io/fits/tiled_compression/setup_package.py create mode 100644 astropy/io/fits/tiled_compression/src/compression.c create mode 100644 astropy/io/fits/tiled_compression/src/fits_hcompress.h create mode 100644 astropy/io/fits/tiled_compression/src/fits_hdecompress.h create mode 100644 astropy/io/fits/tiled_compression/src/fitsio2.h create mode 100644 astropy/io/fits/tiled_compression/src/pliocomp.h create mode 100644 astropy/io/fits/tiled_compression/src/ricecomp.h create mode 100644 astropy/io/fits/tiled_compression/tests/__init__.py create mode 100644 astropy/io/fits/tiled_compression/tests/test_tiled_compression.py create mode 100644 astropy/io/fits/tiled_compression/tiled_compression.py create mode 100644 docs/io/fits/api/tiled_compression.rst diff --git a/astropy/io/fits/tiled_compression/__init__.py b/astropy/io/fits/tiled_compression/__init__.py new file mode 100644 index 00000000000..daf1a3d597f --- /dev/null +++ b/astropy/io/fits/tiled_compression/__init__.py @@ -0,0 +1 @@ +from .tiled_compression import * diff --git a/astropy/io/fits/tiled_compression/setup_package.py b/astropy/io/fits/tiled_compression/setup_package.py new file mode 100644 index 00000000000..25370610555 --- /dev/null +++ b/astropy/io/fits/tiled_compression/setup_package.py @@ -0,0 +1,23 @@ +# Licensed under a 3-clause BSD style license + +import os + +from setuptools import Extension + +SRC_DIR = os.path.join(os.path.dirname(__file__), "src") + + +def get_extensions(): + return [ + Extension( + "astropy.io.fits.tiled_compression._compression", + sources=[ + os.path.join(SRC_DIR, "compression.c"), + os.path.join("cextern", "cfitsio", "lib", "pliocomp.c"), + os.path.join("cextern", "cfitsio", "lib", "ricecomp.c"), + os.path.join("cextern", "cfitsio", "lib", "fits_hcompress.c"), + os.path.join("cextern", "cfitsio", "lib", "fits_hdecompress.c"), + ], + include_dirs=[SRC_DIR], + ) + ] diff --git a/astropy/io/fits/tiled_compression/src/compression.c b/astropy/io/fits/tiled_compression/src/compression.c new file mode 100644 index 00000000000..907bcf28f13 --- /dev/null +++ b/astropy/io/fits/tiled_compression/src/compression.c @@ -0,0 +1,286 @@ +#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION +#define PY_SSIZE_T_CLEAN + +#include +#include +#include +#include +#include + +// TODO: use better estimates for compressed buffer sizes, as done in +// imcomp_calc_max_elem in cfitsio. For now we assume the +// compressed data won't be more than four times the size of the +// uncompressed data, which is safe but too generous. + +// Some of the cfitsio compression files use ffpmsg +// so we provide a dummy function to replace this. +void ffpmsg(const char *err_message) {} + +// Compatibility code because we pick up fitsio2.h from cextern. Can +// remove once we remove cextern +#ifdef _REENTRANT +pthread_mutex_t Fitsio_Lock; +int Fitsio_Pthread_Status = 0; +#endif + +/* Define docstrings */ +static char module_docstring[] = "Core compression/decompression functions"; +static char compress_plio_1_c_docstring[] = "Compress data using PLIO_1"; +static char decompress_plio_1_c_docstring[] = "Decompress data using PLIO_1"; +static char compress_rice_1_c_docstring[] = "Compress data using RICE_1"; +static char decompress_rice_1_c_docstring[] = "Decompress data using RICE_1"; +static char compress_hcompress_1_c_docstring[] = "Compress data using HCOMPRESS_1"; +static char decompress_hcompress_1_c_docstring[] = "Decompress data using HCOMPRESS_1"; + +/* Declare the C functions here. */ +static PyObject *compress_plio_1_c(PyObject *self, PyObject *args); +static PyObject *decompress_plio_1_c(PyObject *self, PyObject *args); +static PyObject *compress_rice_1_c(PyObject *self, PyObject *args); +static PyObject *decompress_rice_1_c(PyObject *self, PyObject *args); +static PyObject *compress_hcompress_1_c(PyObject *self, PyObject *args); +static PyObject *decompress_hcompress_1_c(PyObject *self, PyObject *args); + +/* Define the methods that will be available on the module. */ +static PyMethodDef module_methods[] = { + {"compress_plio_1_c", compress_plio_1_c, METH_VARARGS, compress_plio_1_c_docstring}, + {"decompress_plio_1_c", decompress_plio_1_c, METH_VARARGS, decompress_plio_1_c_docstring}, + {"compress_rice_1_c", compress_rice_1_c, METH_VARARGS, compress_rice_1_c_docstring}, + {"decompress_rice_1_c", decompress_rice_1_c, METH_VARARGS, decompress_rice_1_c_docstring}, + {"compress_hcompress_1_c", compress_hcompress_1_c, METH_VARARGS, compress_hcompress_1_c_docstring}, + {"decompress_hcompress_1_c", decompress_hcompress_1_c, METH_VARARGS, decompress_hcompress_1_c_docstring}, + {NULL, NULL, 0, NULL} +}; + +/* This is the function that is called on import. */ + +#define MOD_ERROR_VAL NULL +#define MOD_SUCCESS_VAL(val) val +#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name(void) +#define MOD_DEF(ob, name, doc, methods) \ + static struct PyModuleDef moduledef = { \ + PyModuleDef_HEAD_INIT, name, doc, -1, methods, \ + }; \ + ob = PyModule_Create(&moduledef); + +MOD_INIT(_compression) { + PyObject *m; + MOD_DEF(m, "_compression", module_docstring, module_methods); + if (m == NULL) + return MOD_ERROR_VAL; + return MOD_SUCCESS_VAL(m); +} + +/* PLIO/IRAF compression */ + +static PyObject *compress_plio_1_c(PyObject *self, PyObject *args) { + + const char *str; + char *buf; + Py_ssize_t count; + PyObject *result; + + short *compressed_values; + int compressed_length; + int *decompressed_values; + + if (!PyArg_ParseTuple(args, "y#", &str, &count)) { + return NULL; + } + + compressed_values = (short *)malloc(count * 4); + + decompressed_values = (int *)str; + count /= 4; + + // Zero the compressed values array + for (int i = 0; i < count * 2; i++) { + compressed_values[i] = 0; + } + + compressed_length = pl_p2li(decompressed_values, 1, compressed_values, (int)count); + + buf = (char *)compressed_values; + + result = Py_BuildValue("y#", buf, compressed_length * 2); + free(buf); + return result; +} + +static PyObject *decompress_plio_1_c(PyObject *self, PyObject *args) { + + const char *str; + char *buf; + Py_ssize_t count; + PyObject *result; + + int tilesize; + + short *compressed_values; + int *decompressed_values; + + if (!PyArg_ParseTuple(args, "y#i", &str, &count, &tilesize)) { + return NULL; + } + + compressed_values = (short *)str; + + // NOTE: the second *4 shouldn't be needed but ran into segfaults with + // smaller buffers. + decompressed_values = (int *)malloc(tilesize * 4 * 4); + + pl_l2pi(compressed_values, 1, decompressed_values, (int)count); + + buf = (char *)decompressed_values; + + result = Py_BuildValue("y#", buf, tilesize * 4); + free(buf); + return result; +} + +/* RICE compression */ + +static PyObject *compress_rice_1_c(PyObject *self, PyObject *args) { + + const char *str; + Py_ssize_t count; + PyObject *result; + + int blocksize, bytepix; + + unsigned char *compressed_values; + int compressed_length; + signed char *decompressed_values_byte; + short *decompressed_values_short; + int *decompressed_values_int; + + if (!PyArg_ParseTuple(args, "y#ii", &str, &count, &blocksize, &bytepix)) { + return NULL; + } + + compressed_values = (unsigned char *)malloc(count * 4); + + if (bytepix == 1) { + decompressed_values_byte = (signed char *)str; + compressed_length = fits_rcomp_byte(decompressed_values_byte, (int)count, compressed_values, count * 16, blocksize); + } else if (bytepix == 2) { + decompressed_values_short = (short *)str; + compressed_length = fits_rcomp_short(decompressed_values_short, (int)count / 2, compressed_values, count * 16, blocksize); + } else { + decompressed_values_int = (int *)str; + compressed_length = fits_rcomp(decompressed_values_int, (int)count / 4, compressed_values, count * 16, blocksize); + } + + result = Py_BuildValue("y#", compressed_values, compressed_length); + free(compressed_values); + return result; +} + +static PyObject *decompress_rice_1_c(PyObject *self, PyObject *args) { + + const char *str; + char *dbytes; + Py_ssize_t count; + PyObject *result; + + int blocksize, bytepix, tilesize; + + unsigned char *compressed_values; + unsigned char *decompressed_values_byte; + unsigned short *decompressed_values_short; + unsigned int *decompressed_values_int; + + if (!PyArg_ParseTuple(args, "y#iii", &str, &count, &blocksize, &bytepix, &tilesize)) { + return NULL; + } + + compressed_values = (unsigned char *)str; + + if (bytepix == 1) { + decompressed_values_byte = (unsigned char *)malloc(tilesize); + fits_rdecomp_byte(compressed_values, (int)count, decompressed_values_byte, tilesize, blocksize); + dbytes = (char *)decompressed_values_byte; + } else if (bytepix == 2) { + decompressed_values_short = (unsigned short *)malloc(tilesize * 2); + fits_rdecomp_short(compressed_values, (int)count, decompressed_values_short, tilesize, blocksize); + dbytes = (char *)decompressed_values_short; + } else { + decompressed_values_int = (unsigned int *)malloc(tilesize * 4); + fits_rdecomp(compressed_values, (int)count, decompressed_values_int, tilesize, blocksize); + dbytes = (char *)decompressed_values_int; + } + + result = Py_BuildValue("y#", dbytes, tilesize * bytepix); + free(dbytes); + return result; +} + +/* HCompress compression */ + +static PyObject *compress_hcompress_1_c(PyObject *self, PyObject *args) { + + const char *str; + Py_ssize_t count; + PyObject *result; + + int bytepix, nx, ny, scale; + int status=0; // Important to initialize this to zero otherwise will fail silently + + char *compressed_values; + int *decompressed_values_int; + long long *decompressed_values_longlong; + + if (!PyArg_ParseTuple(args, "y#iiii", &str, &count, &nx, &ny, &scale, &bytepix)) { + return NULL; + } + + compressed_values = (char *)malloc(count * 4); + + if (bytepix == 4) { + decompressed_values_int = (int *)str; + fits_hcompress(decompressed_values_int, ny, nx, scale, compressed_values, &count, &status); + } else { + decompressed_values_longlong = (long long *)str; + fits_hcompress64(decompressed_values_longlong, ny, nx, scale, compressed_values, &count, &status); + } + + result = Py_BuildValue("y#", compressed_values, count); + free(compressed_values); + return result; +} + +static PyObject *decompress_hcompress_1_c(PyObject *self, PyObject *args) { + + const unsigned char *str; + char *dbytes; + Py_ssize_t count; + PyObject *result; + + int bytepix, nx, ny, scale, smooth; + int status=0; // Important to initialize this to zero otherwise will fail silently + + unsigned char *compressed_values; + int *decompressed_values_int; + long long *decompressed_values_longlong; + + if (!PyArg_ParseTuple(args, "y#iiiii", &str, &count, &nx, &ny, &scale, &smooth, &bytepix)) { + return NULL; + } + + compressed_values = (unsigned char *)str; + + // TODO: raise an error if bytepix is not 4 or 8 + + dbytes = malloc(nx * ny * bytepix); + + if (bytepix == 4) { + decompressed_values_int = (int *)dbytes; + fits_hdecompress(compressed_values, smooth, decompressed_values_int, &ny, &nx, &scale, &status); + } else { + decompressed_values_longlong = (long long *)dbytes; + fits_hdecompress64(compressed_values, smooth, decompressed_values_longlong, &ny, &nx, &scale, &status); + } + + result = Py_BuildValue("y#", dbytes, nx * ny * bytepix); + free(dbytes); + return result; +} diff --git a/astropy/io/fits/tiled_compression/src/fits_hcompress.h b/astropy/io/fits/tiled_compression/src/fits_hcompress.h new file mode 100644 index 00000000000..1d5b2095189 --- /dev/null +++ b/astropy/io/fits/tiled_compression/src/fits_hcompress.h @@ -0,0 +1,6 @@ +typedef long long LONGLONG; + +int fits_hcompress(int *a, int ny, int nx, int scale, char *output, + long *nbytes, int *status); +int fits_hcompress64(LONGLONG *a, int ny, int nx, int scale, char *output, + long *nbytes, int *status); diff --git a/astropy/io/fits/tiled_compression/src/fits_hdecompress.h b/astropy/io/fits/tiled_compression/src/fits_hdecompress.h new file mode 100644 index 00000000000..002d51fecbd --- /dev/null +++ b/astropy/io/fits/tiled_compression/src/fits_hdecompress.h @@ -0,0 +1,6 @@ +typedef long long LONGLONG; + +int fits_hdecompress(unsigned char *input, int smooth, int *a, int *ny, int *nx, + int *scale, int *status); +int fits_hdecompress64(unsigned char *input, int smooth, LONGLONG *a, int *ny, int *nx, + int *scale, int *status); diff --git a/astropy/io/fits/tiled_compression/src/fitsio2.h b/astropy/io/fits/tiled_compression/src/fitsio2.h new file mode 100644 index 00000000000..16fcfff8734 --- /dev/null +++ b/astropy/io/fits/tiled_compression/src/fitsio2.h @@ -0,0 +1,47 @@ +#ifndef LONGLONG_TYPE /* this may have been previously defined */ +#if defined(_MSC_VER) /* Microsoft Visual C++ */ + +#if (_MSC_VER < 1300) /* versions earlier than V7.0 do not have 'long long' */ + typedef __int64 LONGLONG; + typedef unsigned __int64 ULONGLONG; + +#else /* newer versions do support 'long long' */ + typedef long long LONGLONG; + typedef unsigned long long ULONGLONG; + +#endif + +#elif defined( __BORLANDC__) /* for the Borland 5.5 compiler, in particular */ + typedef __int64 LONGLONG; + typedef unsigned __int64 ULONGLONG; +#else + typedef long long LONGLONG; + typedef unsigned long long ULONGLONG; +#endif + +#define LONGLONG_TYPE +#endif + +# define DATA_COMPRESSION_ERR 413 /* error in imcompress routines */ +# define DATA_DECOMPRESSION_ERR 414 /* error in imcompress routines */ +# define NO_COMPRESSED_TILE 415 /* compressed tile doesn't exist */ + +void ffpmsg(const char *err_message); + +#ifdef _REENTRANT +#include +/* #include not needed any more */ +extern pthread_mutex_t Fitsio_Lock; +extern int Fitsio_Pthread_Status; + +#define FFLOCK1(lockname) (Fitsio_Pthread_Status = pthread_mutex_lock(&lockname)) +#define FFUNLOCK1(lockname) (Fitsio_Pthread_Status = pthread_mutex_unlock(&lockname)) +#define FFLOCK FFLOCK1(Fitsio_Lock) +#define FFUNLOCK FFUNLOCK1(Fitsio_Lock) +#define ffstrtok(str, tok, save) strtok_r(str, tok, save) + +#else +#define FFLOCK +#define FFUNLOCK +#define ffstrtok(str, tok, save) strtok(str, tok) +#endif diff --git a/astropy/io/fits/tiled_compression/src/pliocomp.h b/astropy/io/fits/tiled_compression/src/pliocomp.h new file mode 100644 index 00000000000..a648da7ee70 --- /dev/null +++ b/astropy/io/fits/tiled_compression/src/pliocomp.h @@ -0,0 +1,2 @@ +int pl_p2li (int *pxsrc, int xs, short *lldst, int npix); +int pl_l2pi (short *ll_src, int xs, int *px_dst, int npix); diff --git a/astropy/io/fits/tiled_compression/src/ricecomp.h b/astropy/io/fits/tiled_compression/src/ricecomp.h new file mode 100644 index 00000000000..5669e2b965c --- /dev/null +++ b/astropy/io/fits/tiled_compression/src/ricecomp.h @@ -0,0 +1,6 @@ +int fits_rcomp_byte(signed char a[], int nx, unsigned char *c, int clen, int nblock); +int fits_rcomp_short(short a[], int nx, unsigned char *c, int clen, int nblock); +int fits_rcomp(int a[], int nx, unsigned char *c, int clen, int nblock); +int fits_rdecomp_byte(unsigned char *c, int clen, unsigned char array[], int nx, int nblock); +int fits_rdecomp_short(unsigned char *c, int clen, unsigned short array[], int nx, int nblock); +int fits_rdecomp(unsigned char *c, int clen, unsigned int array[], int nx, int nblock); diff --git a/astropy/io/fits/tiled_compression/tests/__init__.py b/astropy/io/fits/tiled_compression/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py new file mode 100644 index 00000000000..47048c7c9bc --- /dev/null +++ b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py @@ -0,0 +1,216 @@ +import numpy as np +import pytest +from numpy.testing import assert_equal + +from astropy.io import fits +from astropy.io.fits.tiled_compression import ( + compress_hdu, + compress_tile, + decompress_hdu, + decompress_tile, +) + +COMPRESSION_TYPES = [ + "GZIP_1", + "GZIP_2", + "RICE_1", + "HCOMPRESS_1", + "PLIO_1", +] + +parameters = [] +for compression_type in COMPRESSION_TYPES: + # io.fits doesn't seem able to compress 64-bit data, even though e.g. GZIP_? + # and HCOMPRESS_1 should be able to handle it. + for itemsize in [1, 2, 4]: + for endian in ["<", ">"]: + format = "u" if itemsize == 1 else "i" + parameters.append((compression_type, f"{endian}{format}{itemsize}")) + + +@pytest.mark.parametrize(("compression_type", "dtype"), parameters) +def test_basic(tmp_path, compression_type, dtype): + + # In future can pass in settings as part of the parameterization + settings = {} + + # Generate compressed file dynamically + + original_data = np.arange(144).reshape((12, 12)).astype(dtype) + + header = fits.Header() + + hdu = fits.CompImageHDU( + original_data, header, compression_type=compression_type, tile_size=(4, 4) + ) + + hdu.writeto(tmp_path / "test.fits") + + # Load in raw compressed data + hdulist = fits.open(tmp_path / "test.fits", disable_image_compression=True) + + tile_shape = (hdulist[1].header["ZTILE2"], hdulist[1].header["ZTILE1"]) + + if compression_type == "GZIP_2": + settings["itemsize"] = original_data.dtype.itemsize + elif compression_type == "PLIO_1": + settings["tilesize"] = np.product(tile_shape) + elif compression_type == "RICE_1": + settings["blocksize"] = hdulist[1].header["ZVAL1"] + settings["bytepix"] = hdulist[1].header["ZVAL2"] + settings["tilesize"] = np.product(tile_shape) + elif compression_type == "HCOMPRESS_1": + # TODO: generalize bytepix, we need to pick 4 or 8 and then cast down + # later to smaller ints if needed. + settings["bytepix"] = 4 + settings["scale"] = hdulist[1].header["ZVAL1"] + settings["smooth"] = hdulist[1].header["ZVAL2"] + settings["nx"] = hdulist[1].header["ZTILE2"] + settings["ny"] = hdulist[1].header["ZTILE1"] + + # Test decompression of the first tile + + compressed_tile_bytes = hdulist[1].data["COMPRESSED_DATA"][0].tobytes() + + tile_data_buffer = decompress_tile( + compressed_tile_bytes, algorithm=compression_type, **settings + ) + + # TODO: determine whether we are happy with having to interpret the returned bytes from + # the GZip codec or whether we want to set the dtype as a setting to the codec. + if compression_type.startswith("GZIP"): + # NOTE: It looks like the data is stored as big endian data even if it was + # originally little-endian. + tile_data = ( + np.asarray(tile_data_buffer) + .view(original_data.dtype.newbyteorder(">")) + .reshape(tile_shape) + ) + else: + tile_data = np.asarray(tile_data_buffer).reshape(tile_shape) + + assert_equal(tile_data, original_data[:4, :4]) + + # Now compress the original data and compare to compressed bytes. Since + # the exact compressed bytes might not match (e.g. for GZIP it will depend + # on the compression level) we instead put the compressed bytes into the + # original BinTableHDU, then read it in as a normal compressed HDU and make + # sure the final data match. + + if compression_type.startswith("GZIP"): + # NOTE: It looks like the data is stored as big endian data even if it was + # originally little-endian. + tile_data_buffer = ( + original_data[:4, :4].astype(original_data.dtype.newbyteorder(">")).data + ) + else: + tile_data_buffer = original_data[:4, :4].data + + compressed_tile_bytes = compress_tile( + tile_data_buffer, algorithm=compression_type, **settings + ) + + # Then check that it also round-trips if we go through fits.open + if compression_type == "PLIO_1": + hdulist[1].data["COMPRESSED_DATA"][0] = np.frombuffer( + compressed_tile_bytes, dtype=np.int16 + ) + else: + hdulist[1].data["COMPRESSED_DATA"][0] = np.frombuffer( + compressed_tile_bytes, dtype=np.uint8 + ) + hdulist[1].writeto(tmp_path / "updated.fits") + hdulist.close() + hdulist_new = fits.open(tmp_path / "updated.fits") + assert_equal(hdulist_new[1].data, original_data) + hdulist_new.close() + + +@pytest.mark.parametrize(("compression_type", "dtype"), parameters) +def test_decompress_hdu(tmp_path, compression_type, dtype): + + # NOTE: for now this test is designed to compare the Python implementation + # of decompress_hdu with the C implementation - once we get rid of the C + # implementation we should update this test. + + original_data = np.arange(144).reshape((12, 12)).astype(dtype) + + header = fits.Header() + + hdu = fits.CompImageHDU( + original_data, header, compression_type=compression_type, tile_size=(4, 4) + ) + + hdu.writeto(tmp_path / "test.fits") + + # Load in CompImageHDU + hdulist = fits.open(tmp_path / "test.fits") + hdu = hdulist[1] + + data = decompress_hdu(hdu) + + assert_equal(data, original_data) + + # NOTE: the following block can be removed once we remove the C implementation + from astropy.io.fits.compression import decompress_hdu as decompress_hdu_c + + data_c = decompress_hdu_c(hdu) + assert_equal(data, data_c) + + hdulist.close() + + +def _assert_heap_same(heap_a, heap_b, n_tiles): + # The exact length and data might not always match because they might be + # equivalent but store the tiles in a different order or with padding, so + # we need to interpret the heap data and actually check chunk by chunk + heap_header_a = heap_a[: n_tiles * 2 * 4].view(">i4") + heap_header_b = heap_b[: n_tiles * 2 * 4].view(">i4") + for itile in range(n_tiles): + len_a = heap_header_a[itile * 2] + off_a = heap_header_a[itile * 2 + 1] + n_tiles * 2 * 4 + len_b = heap_header_b[itile * 2] + off_b = heap_header_b[itile * 2 + 1] + n_tiles * 2 * 4 + data_a = heap_a[off_a : off_a + len_a] + data_b = heap_b[off_b : off_b + len_b] + if np.all(data_a[:4] == np.array([31, 139, 8, 0], dtype=np.uint8)): + # gzip compressed - need to compare decompressed bytes as compression + # is non-deterministic due to e.g. compression level and so on + from gzip import decompress + + data_a = decompress(data_a.tobytes()) + data_b = decompress(data_b.tobytes()) + assert data_a == data_b + else: + assert_equal(data_a, data_b) + + +@pytest.mark.parametrize(("compression_type", "dtype"), parameters) +def test_compress_hdu(tmp_path, compression_type, dtype): + + # NOTE: for now this test is designed to compare the Python implementation + # of compress_hdu with the C implementation - once we get rid of the C + # implementation we should update this test. + + if compression_type == "PLIO_1": + pytest.xfail() + + original_data = np.arange(144).reshape((12, 12)).astype(dtype) + + header = fits.Header() + + hdu = fits.CompImageHDU( + original_data, header, compression_type=compression_type, tile_size=(4, 4) + ) + + heap_length, heap_data = compress_hdu(hdu) + + # NOTE: the following block can be removed once we remove the C implementation + from astropy.io.fits.compression import compress_hdu as compress_hdu_c + + # Note that when CompImageHDU calls compress_hdu it converts to native byte + # order first, so we have to do this here. + hdu.data = hdu.data.astype(hdu.data.dtype.newbyteorder("=")) + heap_length_c, heap_data_c = compress_hdu_c(hdu) + + _assert_heap_same(heap_data, heap_data_c, 9) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py new file mode 100644 index 00000000000..7fe5f73238b --- /dev/null +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -0,0 +1,545 @@ +""" +This module contains low level helper functions for compressing and decompressing buffer for the Tiled Table Compression algorithms as specified in the FITS 4 standard. +""" +from abc import abstractmethod +from gzip import compress as gzip_compress +from gzip import decompress as gzip_decompress + +import numpy as np + +from astropy.io.fits.tiled_compression._compression import ( + compress_hcompress_1_c, + compress_plio_1_c, + compress_rice_1_c, + decompress_hcompress_1_c, + decompress_plio_1_c, + decompress_rice_1_c, +) + +__all__ = [ + "Gzip1", + "Gzip2", + "Rice1", + "PLIO1", + "HCompress1", + "compress_tile", + "decompress_tile", + "compress_hdu", + "decompress_hdu", +] + + +# We define our compression classes in the form of a numcodecs class. We make +# the dependency on numcodecs optional as we can use them internally without it +# (for now). +try: + from numcodecs.abc import Codec +except ImportError: + + class Codec: + codec_id = None + """Codec identifier.""" + + @abstractmethod + def encode(self, buf): # pragma: no cover + """Encode data in `buf`. + Parameters + ---------- + buf : buffer-like + Data to be encoded. May be any object supporting the new-style + buffer protocol. + Returns + ------- + enc : buffer-like + Encoded data. May be any object supporting the new-style buffer + protocol. + """ + + @abstractmethod + def decode(self, buf, out=None): # pragma: no cover + """Decode data in `buf`. + Parameters + ---------- + buf : buffer-like + Encoded data. May be any object supporting the new-style buffer + protocol. + out : buffer-like, optional + Writeable buffer to store decoded data. N.B. if provided, this buffer must + be exactly the right size to store the decoded data. + Returns + ------- + dec : buffer-like + Decoded data. May be any object supporting the new-style + buffer protocol. + """ + + +class Gzip1(Codec): + """ + The FITS GZIP 1 compression and decompression algorithm. + + The Gzip algorithm is used in the free GNU software compression utility of + the same name. It was created by J. L. Gailly and M. Adler, based on the + DEFLATE algorithm (Deutsch 1996), which is a combination of LZ77 (Ziv & + Lempel 1977) and Huffman coding. + """ + + codec_id = "FITS_GZIP1" + + def decode(self, buf): + """ + Decompress buffer using the GZIP_1 algorithm. + + {_GZIP_1_DESCRIPTION} + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + dbytes = gzip_decompress(cbytes) + return np.frombuffer(dbytes, dtype=np.uint8).data + + def encode(self, buf): + """ + Compress the data in the buffer using the GZIP_1 algorithm. + + Parameters + ---------- + buf + The buffer to compress. + + Returns + ------- + buf + A buffer with compressed data. + """ + dbytes = np.asarray(buf).tobytes() + return gzip_compress(dbytes) + + +class Gzip2(Codec): + """ + The FTIS GZIP2 compression and decompression algorithm. + + The gzip2 algorithm is a variation on 'GZIP 1'. In this case the buffer in + the array of data values are shuffled so that they are arranged in order of + decreasing significance before being compressed. + + For example, a five-element contiguous array of two-byte (16-bit) integer + values, with an original big-endian byte order of: + + .. math:: + A1 A2 B1 B2 C1 C2 D1 D2 E1 E2 + + will have the following byte order after shuffling: + + .. math:: + A1 B1 C1 D1 E1 A2 B2 C2 D2 E2, + + where A1, B1, C1, D1, and E1 are the most-significant buffer from + each of the integer values. + + Byte shuffling shall only be performed for integer or floating-point + numeric data types; logical, bit, and character types must not be shuffled. + + Parameters + ---------- + itemsize + The number of buffer per value (e.g. 2 for a 16-bit integer) + + """ + + codec_id = "FITS_GZIP2" + + def __init__(self, itemsize: int): + super().__init__() + self.itemsize = itemsize + + def decode(self, buf): + """ + Decompress buffer using the GZIP_2 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + # Start off by unshuffling buffer + unshuffled_buffer = gzip_decompress(cbytes) + array = np.frombuffer(unshuffled_buffer, dtype=np.uint8) + return array.reshape((self.itemsize, -1)).T.ravel().data + + def encode(self, buf): + """ + Compress the data in the buffer using the GZIP_2 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + # Start off by shuffling buffer + array = np.asarray(buf).ravel().view(np.uint8) + shuffled_buffer = array.reshape((-1, self.itemsize)).T.ravel().tobytes() + return gzip_compress(shuffled_buffer) + + +class Rice1(Codec): + """ + The FTIS RICE1 compression and decompression algorithm. + + The Rice algorithm [1]_ is simple and very fast It requires only enough + memory to hold a single block of 16 or 32 pixels at a time. It codes the + pixels in small blocks and so is able to adapt very quickly to changes in + the input image statistics (e.g., Rice has no problem handling cosmic rays, + bright stars, saturated pixels, etc.). + + Parameters + ---------- + blocksize + The blocksize to use, each tile is coded into blocks a number of pixels + wide. The default value in FITS headers is 32 pixels per block. + + bytepix + The number of 8-bit buffer in each original integer pixel value. + + References + ---------- + .. [1] Rice, R. F., Yeh, P.-S., and Miller, W. H. 1993, in Proc. of the 9th + AIAA Computing in Aerospace Conf., AIAA-93-4541-CP, American Institute of + Aeronautics and Astronautics [https://doi.org/10.2514/6.1993-4541] + """ + + codec_id = "FITS_RICE1" + + def __init__(self, blocksize: int, bytepix: int, tilesize: int): + self.blocksize = blocksize + self.bytepix = bytepix + self.tilesize = tilesize + + def decode(self, buf): + """ + Decompress buffer using the RICE_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + dbytes = decompress_rice_1_c( + cbytes, self.blocksize, self.bytepix, self.tilesize + ) + return np.frombuffer(dbytes, dtype=f"i{self.bytepix}").data + + def encode(self, buf): + """ + Compress the data in the buffer using the RICE_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + A buffer with decompressed data. + """ + dbytes = np.asarray(buf).astype(f"i{self.bytepix}").tobytes() + return compress_rice_1_c(dbytes, self.blocksize, self.bytepix) + + +class PLIO1(Codec): + """ + The FTIS PLIO1 compression and decompression algorithm. + + The IRAF PLIO (pixel list) algorithm was developed to store integer-valued + image masks in a compressed form. Such masks often have large regions of + constant value hence are highly compressible. The compression algorithm + used is based on run-length encoding, with the ability to dynamically + follow level changes in the image, allowing a 16-bit encoding to be used + regardless of the image depth. + """ + + codec_id = "FITS_PLIO1" + + def __init__(self, tilesize: int): + self.tilesize = tilesize + + def decode(self, buf): + """ + Decompress buffer using the PLIO_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + dbytes = decompress_plio_1_c(cbytes, self.tilesize) + return np.frombuffer(dbytes, dtype="i4").data + + def encode(self, buf): + """ + Compress the data in the buffer using the PLIO_1 algorithm. + """ + dbytes = np.asarray(buf).astype("i4").tobytes() + return compress_plio_1_c(dbytes) + + +class HCompress1(Codec): + """ + The FTIS PLIO1 compression and decompression algorithm. + + Hcompress is an the image compression package written by Richard L. White + for use at the Space Telescope Science Institute. Hcompress was used to + compress the STScI Digitized Sky Survey and has also been used to compress + the preview images in the Hubble Data Archive. + + The technique gives very good compression for astronomical images and is + relatively fast. The calculations are carried out using integer arithmetic + and are entirely reversible. Consequently, the program can be used for + either lossy or lossless compression, with no special approach needed for + the lossless case. + + Parameters + ---------- + scale + The integer scale parameter determines the amount of compression. Scale + = 0 or 1 leads to lossless compression, i.e. the decompressed image has + exactly the same pixel values as the original image. If the scale + factor is greater than 1 then the compression is lossy: the + decompressed image will not be exactly the same as the original + + smooth + At high compressions factors the decompressed image begins to appear + blocky because of the way information is discarded. This blockiness + ness is greatly reduced, producing more pleasing images, if the image + is smoothed slightly during decompression. + """ + + codec_id = "FITS_HCOMPRESS1" + + def __init__(self, scale: float, smooth: bool, bytepix: int, nx: int, ny: int): + self.scale = scale + self.smooth = smooth + self.bytepix = bytepix + # NOTE: we should probably make this less confusing, but nx is shape[0] and ny is shape[1] + self.nx = nx + self.ny = ny + + def decode(self, buf): + """ + Decompress buffer using the HCOMPRESS_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + A buffer with decompressed data. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + dbytes = decompress_hcompress_1_c( + cbytes, self.nx, self.ny, self.scale, self.smooth, self.bytepix + ) + return np.frombuffer(dbytes, dtype=f"i{self.bytepix}").data + + def encode(self, buf): + """ + Compress the data in the buffer using the HCOMPRESS_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + A buffer with decompressed data. + """ + dbytes = np.asarray(buf).astype(f"i{self.bytepix}").tobytes() + return compress_hcompress_1_c( + dbytes, self.nx, self.ny, self.scale, self.bytepix + ) + + +ALGORITHMS = { + "GZIP_1": Gzip1, + "GZIP_2": Gzip2, + "RICE_1": Rice1, + "PLIO_1": PLIO1, + "HCOMPRESS_1": HCompress1, +} + + +def decompress_tile(buf, *, algorithm: str, **kwargs): + """ + Decompress the buffer of a tile using the given compression algorithm. + + Parameters + ---------- + buf + The compressed buffer to be decompressed. + algorithm + A supported decompression algorithm. + kwargs + Any parameters for the given compression algorithm + """ + return ALGORITHMS[algorithm](**kwargs).decode(buf) + + +def compress_tile(buf, *, algorithm: str, **kwargs): + """ + Compress the buffer of a tile using the given compression algorithm. + + Parameters + ---------- + buf + The decompressed buffer to be compressed. + algorithm + A supported compression algorithm. + kwargs + Any parameters for the given compression algorithm + """ + return ALGORITHMS[algorithm](**kwargs).encode(buf) + + +def _header_to_settings(header): + + tile_shape = (header["ZTILE2"], header["ZTILE1"]) + + settings = {} + + if header["ZCMPTYPE"] == "GZIP_2": + settings["itemsize"] = header["ZBITPIX"] // 8 + elif header["ZCMPTYPE"] == "PLIO_1": + settings["tilesize"] = np.product(tile_shape) + elif header["ZCMPTYPE"] == "RICE_1": + settings["blocksize"] = header["ZVAL1"] + settings["bytepix"] = header["ZVAL2"] + settings["tilesize"] = np.product(tile_shape) + elif header["ZCMPTYPE"] == "HCOMPRESS_1": + settings["bytepix"] = 4 + settings["scale"] = header["ZVAL1"] + settings["smooth"] = header["ZVAL2"] + settings["nx"] = header["ZTILE2"] + settings["ny"] = header["ZTILE1"] + + return settings + + +def decompress_hdu(hdu): + """ + Drop-in replacement for decompress_hdu from compressionmodule.c + """ + + tile_shape = (hdu._header["ZTILE2"], hdu._header["ZTILE1"]) + data_shape = (hdu._header["ZNAXIS1"], hdu._header["ZNAXIS2"]) + + settings = _header_to_settings(hdu._header) + + data = np.zeros(data_shape, dtype="i4") + + istart = 0 + jstart = 0 + for cdata in hdu.compressed_data["COMPRESSED_DATA"]: + tile_buffer = decompress_tile( + cdata, algorithm=hdu._header["ZCMPTYPE"], **settings + ) + + if hdu._header["ZCMPTYPE"].startswith("GZIP") and hdu._header["ZBITPIX"] > 8: + # TOOD: support float types + int_size = hdu._header["ZBITPIX"] // 8 + tile_data = ( + np.asarray(tile_buffer).view(f">i{int_size}").reshape(tile_shape) + ) + else: + if tile_buffer.format == "b": + # NOTE: this feels like a Numpy bug - need to investigate + tile_data = np.asarray(tile_buffer, dtype=np.uint8).reshape(tile_shape) + else: + tile_data = np.asarray(tile_buffer).reshape(tile_shape) + + data[ + istart : istart + tile_shape[0], jstart : jstart + tile_shape[1] + ] = tile_data + jstart += tile_shape[1] + if jstart >= data_shape[1]: + jstart = 0 + istart += tile_shape[0] + + return data + + +def compress_hdu(hdu): + """ + Drop-in replacement for compress_hdu from compressionmodule.c + """ + + # For now this is very inefficient, just a proof of concept! + + settings = _header_to_settings(hdu._header) + + tile_shape = (hdu._header["ZTILE2"], hdu._header["ZTILE1"]) + data_shape = (hdu._header["ZNAXIS1"], hdu._header["ZNAXIS2"]) + + compressed_bytes = [] + + for i in range(0, data_shape[0], tile_shape[0]): + for j in range(0, data_shape[1], tile_shape[1]): + # TODO: deal with data not being integer number of tiles + data = hdu.data[i : i + tile_shape[0], j : j + tile_shape[1]] + # The original compress_hdu assumed the data was in native endian, so we + # change this here: + if hdu._header["ZCMPTYPE"].startswith("GZIP"): + # This is apparently needed so that our heap data agrees with + # the C implementation!? + data = data.astype(data.dtype.newbyteorder(">")) + else: + if not data.dtype.isnative: + data = data.astype(data.dtype.newbyteorder("=")) + cbytes = compress_tile(data, algorithm=hdu._header["ZCMPTYPE"], **settings) + compressed_bytes.append(cbytes) + + heap_header = np.zeros(len(compressed_bytes) * 2, ">i4") + for i in range(len(compressed_bytes)): + heap_header[i * 2] = len(compressed_bytes[i]) + heap_header[1 + i * 2] = heap_header[: i * 2 : 2].sum() + + heap = heap_header.tobytes() + b"".join(compressed_bytes) + + return heap_header[::2].sum(), np.frombuffer(heap, dtype=np.uint8) diff --git a/docs/io/fits/api/tiled_compression.rst b/docs/io/fits/api/tiled_compression.rst new file mode 100644 index 00000000000..16c92cc2114 --- /dev/null +++ b/docs/io/fits/api/tiled_compression.rst @@ -0,0 +1,5 @@ +***************** +Tiled Compression +***************** + +.. automodapi:: astropy.io.fits.tiled_compression diff --git a/docs/io/fits/index.rst b/docs/io/fits/index.rst index f18029c52cf..a72cd932cf6 100644 --- a/docs/io/fits/index.rst +++ b/docs/io/fits/index.rst @@ -1047,6 +1047,7 @@ Reference/API api/images.rst api/diff.rst api/verification.rst + api/tiled_compression.rst .. rubric:: Footnotes From 314f98681b7d712c5d29b54dfeab1acdb1b09058 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Mon, 14 Nov 2022 11:58:27 +0000 Subject: [PATCH 02/32] Add canonical compressed data from FITS site --- .gitattributes | 1 + .../tiled_compression/tests/data/m13.fits | Bin 0 -> 184320 bytes .../tests/data/m13_gzip.fits | 366 ++++ .../tests/data/m13_hcomp.fits | 191 +++ .../tests/data/m13_plio.fits | 1484 +++++++++++++++++ .../tests/data/m13_rice.fits | 323 ++++ .../tests/test_tiled_compression.py | 67 +- .../tiled_compression/tiled_compression.py | 43 +- 8 files changed, 2438 insertions(+), 37 deletions(-) create mode 100644 astropy/io/fits/tiled_compression/tests/data/m13.fits create mode 100644 astropy/io/fits/tiled_compression/tests/data/m13_gzip.fits create mode 100644 astropy/io/fits/tiled_compression/tests/data/m13_hcomp.fits create mode 100644 astropy/io/fits/tiled_compression/tests/data/m13_plio.fits create mode 100644 astropy/io/fits/tiled_compression/tests/data/m13_rice.fits diff --git a/.gitattributes b/.gitattributes index c9e0d8c607a..9461903e124 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ *.fits -text +astropy/io/fits/tiled_compression/tests/data/*fits binary diff --git a/astropy/io/fits/tiled_compression/tests/data/m13.fits b/astropy/io/fits/tiled_compression/tests/data/m13.fits new file mode 100644 index 0000000000000000000000000000000000000000..1f2f82a4f18e809fcad980498cb2f3b126903a37 GIT binary patch literal 184320 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

#nO<8I*45$V*nejI(>nRP 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 X?w|bkNeO&X0-uz?CnfOzr3C&r4ZA<1 literal 0 HcmV?d00001 diff --git a/astropy/io/fits/tiled_compression/tests/data/m13_gzip.fits b/astropy/io/fits/tiled_compression/tests/data/m13_gzip.fits new file mode 100644 index 00000000000..cf7acea4eac --- /dev/null +++ b/astropy/io/fits/tiled_compression/tests/data/m13_gzip.fits @@ -0,0 +1,366 @@ +SIMPLE = T / file does conform to FITS standard BITPIX = 16 / number of bits per data pixel NAXIS = 0 / number of data axes EXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H CHECKSUM= '0aE73a960aC60a96' / HDU checksum updated 2006-11-15T17:28:30 DATASUM = ' 0' / data unit checksum updated 2006-11-15T17:28:30 END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / 8-bit bytes NAXIS = 2 / 2-dimensional binary table NAXIS1 = 8 / width of table in bytes NAXIS2 = 300 / number of rows in table PCOUNT = 111820 / size of special data area GCOUNT = 1 / one data group (required keyword) TFIELDS = 1 / number of fields in each row TTYPE1 = 'COMPRESSED_DATA' / label for field 1 TFORM1 = '1PB(506)' / data format of field: variable length array ZIMAGE = T / extension contains compressed image ZTILE1 = 300 / size of tiles to be compressed ZTILE2 = 1 / size of tiles to be compressed ZCMPTYPE= 'GZIP_1 ' / compression algorithm EXTNAME = 'COMPRESSED_IMAGE' ZSIMPLE = T / file does conform to FITS standard ZBITPIX = 16 / number of bits per data pixel ZNAXIS = 2 / number of data axes ZNAXIS1 = 300 / length of data axis 1 ZNAXIS2 = 300 / length of data axis 2 ZEXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H COMMENT COMMENT This file was produced by the SkyView survey analysis system from COMMENT available astronomical surveys. The data are formatted COMMENT as a simple two-dimensional FITS image with the same units as COMMENT the orginal survey. CTYPE1 = 'RA---TAN' / X-axis type CTYPE2 = 'DEC--TAN' / Y-axis type CRVAL1 = 250.4226 / Reference pixel value CRVAL2 = 36.4602 / Reference pixel value CRPIX1 = 150.500 / Reference pixel CRPIX2 = 150.500 / Reference pixel CDELT1 = -0.00027770002 / Degrees/pixel CDELT2 = 0.00027770002 / Degrees/pixel CROTA1 = 0.00000 / Rotation in degrees. EQUINOX = 2000.00 /Equinox of coordinates ZHECKSUM= '2f4R3c4O2c4O2c4O' / HDU checksum updated 2006-11-15T17:18:55 ZDATASUM= '1803906202' / data unit checksum updated 2006-11-15T17:18:55 CHECKSUM= 'CeA6FZ03Cd73CZ73' / HDU checksum updated 2006-11-15T17:28:30 DATASUM = '3567348586' / data unit checksum updated 2006-11-15T17:28:30 END "/"6QO‡SÖP)SyKÌ7 +- N { ˜´ÊÛô& )2&[/°Ëá*ý*',Q}"› ½-!Ü;# 9$D6%}8&³-'ë)*-+? ,]$-}.¡/¼0Õ1ô3 4*5J?6gQ7¦p8÷e:gi;Ìg=5H>œ8?äA"B5BCWYD™iEòG[zHÚiJThK½nM%rN“fPaQkjRÌ{T6‚U±‡W3…XºrZ?_[±m]o^}}_ìˆai~bñ‹doŠeú”g„„iljœnl†mvžnüÏpšÁri¶t*ªuà˜wІy"”z¨}|<˜}¹•Q‘€æ‚w„›…¡¬‡<­ˆè®Š•­ŒC¬ðœž‘9Ÿ’×£”v±–¸—ÊÆ™‚á›HÔ)âžýÌ ßÄ¢«®¤o·¦ã§Ôß©·é«–Ø­ʯW±±!Ö²Òç´¨ضà¸gÚºGÙ¼!×½úî¿ÑúÁ¿òùçÅ«æÇ’êÉxØËbØÍ:åÏåÐ÷áÒÜÝÔ½àÖšרzÒÚQâÜ#ßÞÉßäÄá­§ãq·å½æÏÃ茸êOÓìåíÚæï¿ðñ¥ôó•ôõ‰ê÷}ñùgçûXÏý?ÙÿÝçæÄǪÍqÇ>Ê +Ê ÏÊ ™µcÔÈ츴³l¸¶×´®A¯ï˜ ž˜"6¢#ÎÀ%pÌ'0Ê(ü¹*Ư,˜..„/ƈ1J‘2Ò¨4cÂ6 Ù7ÍÇ9¦¾;m´=+‡>ßl@flAÒoC>qD­zF‰G˜‡I!zJ¨kL"{MfOqPntQßnSSoTÁˆV0’W¸›YJ‹Zå€\p’]ðŒ_‚xa{b†tdbeuXf×Dh/@is0j³GkãRm*Qn|]oÍbq*\rŒ^sècuF`v©Kx @yT>z”;{Ò/} (~<'d€‹£ ‚±ƒ¾ù„̅ņƇˈˉъà$‹÷).ŽD,r4ž@‘Ò9“9”K3•„ –·—×(˜õ,š6›I"œ¡ž½ ŸÚî æû¡Ô¢Ï£ß ¤ô ¦ §  ¨© ª7«U¬cù­e®^ü¯_°["±c'²… ³¬‹…”;n1†‡3pŽH´\ +²ï.«M +"%ˆ‚"}DA“ ä¹GRå7Œ•Ñ Å§õc^þwly” ™Âs^÷䆨¾g¬°_Ap?.A &úu:žø'°SЀJýB\§¾2’'7ù¼ÿ3Xƒw°/&¿ßïÀØ€•îwºWQŒSÂi¸×1œò9ƒ)À ¼bo<ìžáÜ+4uí†â¹Eï57æ%˯Ïgqjû—˦rîÚxå2Îéßñ>®àž`žÎÍk]>´žò’Õ[Vгå³÷Æñç+öónbž2Ù=©ûÎàõ°a¶¾ o0B{ý/paCÝÌ+qÆukù×}žïà.m¨M­—}¿YïÑ.Å÷ÿ݇è~û8ŒsÉ;)¬›ý¿ˆâŠq~·¿?°½i°‹…”±NA†Ð’ðLT< êÁrw cb°1†( /@G …=¾O`eÅ¿òO²o¤ør·»3;3ÿ̈dr¦Gîˆîëú6!“¿~ž½½¯gðìûd +ð>EjxÖÛ ‰÷ö¶à ÉxKúLèœúRb¼¼‚˜3~ ¾ÁÔ°·SÚ’'k­ÃÖ­è¹úZ]<}S=CBU¼@ +êÙ75ÛøvÖ6ãYI¡ÀØ€{P$þª‡ÆÏ¥ºÎKó¡yÆ;îÙóȘwþ—oÚ«“‡×§‚5ƹzGÌÁ^¤±¹náùƒÙ¼Âþx§NñçÁë×ÀÄŠµNYo™hèéfçF¹!]bÏ/õCQ½ã·ñ rþw¯÷Q4/›çïúî|Lϰ‹…”A.Q†Ë„cØ:‡p K‘ û½îi†LÌYÌŠ°´ ±`cao!n`é áü-%•ßôX|é®zýªþªzýÌ,™YvIAJâþmâv)Gã©ý7ƒ}p¾Ì^Ìz`ï[ð=‚c0'༂wð θ?uÄWýIèƒ!¸A¾eä]Ÿ`ö:ü÷` PƒŠ{jjw’hp[×Õ®H¦Îâ3h×öÀ!{P‡oË£aúô•aÔ«õk2¿mk¾dÿŸÀ5çÐØßÚ3sD¢Æ?Î=ËÌk=2d<í“ÖßÕß.fÛÄúÀœƒ7œƒo³ÅU³¥;<‘«·ÿ×ÇœK%èù‹z]gMÆÌèoÔãë9Ð>ûÿ<¯^ŸGÔ¢ùÚswj¿ÿ‡])g¤÷ŠÞ]÷ÅÜ{㞊~°‹”=NÃ@…‡Ÿ’ +Z åTˆÐq vbÇ#ˆ" A + +(hÜ.@…¸ Ÿ¥7ÒjdÅ'g&Þ™·oÇkf¹™õà@ôD&<¿'<νP'Ö‹ñ_xÝ>Œá>ÍæŽÌæWx~ùg¨ánà‹üìÂñ›þÏ;êGýN.J˜ÀõÖè½ °C¼MþN` ´¦v'<.’uƒ–÷Ó|£s_¸7øa3¸„)ŒBïRÞÔò± +}Jå +k×ýIûžÃ<•êÇ};•tœ +×ÙV?KÖÅ3©ä÷LL´‡è“ãú¾¢¿]´Ím®sn´ŸÁ5¼3x¿°j¶¸Îs“ø•<3bò)ÕçÄùKõºÎBýÆ:Û©~;öá³×ô<Ô{ÿó®ï´¯Zµðùñ~M¯föîáNgR%5»î®ûâ×{ãþ‰µ°‹”½JÄ@Ç÷lm| ñ©|c’Í}ä¼Csj#‡"ˆ‚ØY[ê#øbkcå/ðXÆËiñ#Ù™ÝùÏÌNBÈCl‹Lì8¼?wؾÌáýFk+¡]þÂ<Âg½ Xãý æ0† \Á7>Îöf°Éú ˆÒñùÎn˜½=Wà ¼s ^ Õû€3éGQºº|ü"ÙÛ‡‘ÎTk_ØžJXÜ4ÇJ½¹„k8…=&ñõ®õŸÃ¾tÒLÏ߃¯!õ[_fÊaª8¾þ¨|éßÃÞëû­6o·u¥^ME-]>£ÿ¬KDzy«îc¸ƒ/æºVÖy>Ã*¶›¤'qAüesgw3Too¸áUzÉWI¼¨Þ´³¹«=]whßç_}³š«^??鉉rÊÃïAJ×ÿÂëçi¬RZ*°‹ÔKJÄ@àšûx/â<€1Éd&ãû‚ Š]ˆ¢‚èF\xÁ…K·Š'âÚ¿™¿°ø‰ÅÇLº“êªêN̬4³¨ E¡ó¥ðû +¡ó.UHçó\ ˰ Gp gpÍß çÃ<™õf`ÿ?àV Ïu4Ÿ$ãÎÇósX…Cx€·é:vÊñ:Ü_I]ßczÜœÿ&ãŒ8Õäq=Nź·™WîÍ>¬Áñ[8€;¸‡örrˆt’Ì¥Ž¹&ä­u&Ž·Ì-çñ¯p [¡>_'ÖûX‰! ¬»ÏñœýçZãk½~OÍz×Ùû8Gx‡g¸bm¾]ùÿvîú|6ïå|â<ÏÁ<ôp}K¼¯ñ|?Æì¹ö&Öäïç_}Ë16lúnMXwöË÷¿¥Æ¾Ï©~ ¢Ÿ¾º~c}Fp|°‹}“MNÃ@ …MÁ!8 IšLÚŠZñWX‚8g`Á{®ƒx¦ÏŠe&]|J2ñß³="RȆ1Ñ÷ +L¥ôv…³©IE›ÒÅ)é›ÈÄÙ”ôñ”Χ¡OÎÀ܃;° 0G ¥ýœ‚Gð +>À3¸à?«5æ¯0틯9¯X‹ÖtÂü¾W>FÔX…¸-k^ñÙñ¬¥¶9ÏRÈayýTã5X‚C×?õb/¾Á {ØetæúS;ýC:†´šÆûô>ÁÈÎÏwpÃZ¦ÒïIúîϛ̹ß+ߟ¸W‘8ÿ”Áôû¸‰õÚ|ÝÇKpKM‹-s‹äêIœ£Îô ýÚí‚=¼«ísl«ÏîEì}˜ßî·kŒcÙìÖ’³œIΉíªÝ5«ÿóÈå‹÷Çïßý/ÒÇH°‹u”MJÄ@…{<§gPc:=™‰ÿˆ¢A\ºu-ˆ Áx—.<†¯÷˜G‘^|Tª«^UW%¥t6ì“ú|Òƒ.mýD#)ôé,N}^‚#²²Xs8½10n=w ÎÁ%¸ ÍóJc¦í\ƒžùMybþÜ äPóÒrG¿rÎÕ9Xj]'wgf/3y¤Ýû4™®LûxŸàÜZO¼N½{rÚÎA«Ž¹õ]5Vm÷àü€_ðF-UãÚXñŒP¿‡@±¾Hsìßw$޿⩟kËÛšùŒÔ=™­5G‘9=…±îÀwJ ÌþÎ.@ÜÅ#l_i³>ÿBÚ4±7zß#1¿öÛÏi&'«ó +<ƒÞë5ù¾ùÿ¢µž/î×÷ïÿiæ°‹“ËMADÛÁÙ Ýõì¬`þX²á‚o\‘àJœ Á‡A ÔHåfVâð¤ÙžžþT÷šYeÇÔ` :@C{Ís—`Á˜ˆoÅs²-y?“û†9-óLI$“‘þ­¼Ï4ŒS >ŽGû.½+õ£5Öãk |7ÝæÔÇçÑï¬a'úeÒœžÀ X3žêœŸ=Øqþ`åyf{®)rö7à¼Ø‚pKîíwŸVàÂþîƒ×½o~}󩬼ú¶£îwà,hó3õ}¶Rgþöºøz<~gÔâÃl0Ÿ`Na{ç\u.ºÿ©îsÑO}ò–þûÄÑ;wêPÏœàüÅ™ÎÙ‡ÿßü¡‡Ÿûo(ïp°‹“»nÂ@E—ÔÔù~,²Û˜G@Q ˆG(ÓD¢JOOÏq7¹#M†1¡8Z{ç¹swC1ü’‘tA´ç$}ÀÀX‚‘ò͸V`žS(J’jô铨i¯ˆŽ‰ìÍ#*? Ú^ÝÈ—tþÂÔÕ}•Š[yÒ9Çœ›œYòÈL†\K§Žüç¦nåP›\úœMó°ûvþZo_´•xÑø¼“9Xø{ðAß~¸¾¶ŽÕ­I«ŸŸú}3ðF}zަ^ÿî…×OtüìýØ‚S­ÐÆ÷lÂß÷¸ 5·ï@îafý'¢m6ÿÑOàý·Và{߬Ûuòß[¿i>?ö mD> +°‹…Ô=NÃ@àá.܃A6‰M~ˆ¨…’! + +*ꈆ«å-~#=ž¼¢ødÙ;;;;^;"æ1¸„)¯õ¾£3¸ :¾‚Wø‚<ÁFrT眷¤\#óçóžù®©çøŒrNÞkš³Ç«Â57ÔKÍ}xÉÇ[õNHãp{¸…-Ÿ™7o(7Y×ûâ²íÛBhžÚŸ«ø{Z}ñ<úÞUÇœ¹ÿGx†wøäuÏžt"ûãÆú2Æûãñy¿†] çc%ûñøÿÎ÷Éã<_«Oµ÷ðoðÀÚZïxɺ³~ý¶µýN«±sªç7÷PÏÄüDœœÁ7|Ài ÿ‚㦠¾~æ÷:¼žßñ#c›‡“°‹}Ò]JAàñjÞÀ‹¨Éκ»Æ@‚?"j@|ôpÞAR“TASôúð‘,ÛÓÓS;¥”¡u°ào#UXµ÷+x…oø‚'˜Bæ’º ö—kW¤Y–Ô%Ï­~MZSÉë£Êú-­9CV¯søñ,‹dï¯T_™ë¼Á3là*dûÄœâ9ë̾sæ¾KåÞ¢ýZ6÷œsÙÿËE}to4ooTÓz> ìà>˜É\‡^cÈÇ©o–I–×;å0ÌÔeçÎîçäuÞÏs™ÜÐj²ïܳf +gÈÏÉï©ß_¡õ¼…ø+åä Nñÿ>9c }œï¯þ>‡Ïsx¿êu2°‹”Í +Â0„÷‰ýkLm+õ_¼‰^¼z|>“…em(xø(I6“Íì¦"D$‚ IãX‚-h¹^€±‰Y€؃9× £3Πë…#òÜ)Ç#bãÓZvà ®à6Ü™[èÐ×ý X5ï¦ç‡ŸŸ:ü}ý½ý¼Í¡¤·Gz¸0šC¢:vOÅoùGvŒN$ªYÏäïÜÁE¾ýå×SëOÒ«¹¿2z^?2®e-V¬áŽçÔ&~ÖQÛ.r>äú­0úµñ4×?^'w®úåtúÞã€äúÅ£o(õmêx‚›|ÿšcß{èóËûö‰{¹“ ô°‹•ÓMnÂ0à9qšUÀ]U°èX·¨z $6ܤÏí{b:‚.>EN&cû%6³'3«ì÷š´°„78À0†Ò.õ tPs<"õeäžWì¥y +Rý˜sÎaŸp„/x…k¯]Ÿ(½ßÃŒõ7_E-5|¦û±Wéòðâ>EõZÇ”¹Î8.ùÞ#©Oi×/ÿ]G̹§6ì/×ðg8Á;løÌï#æÓ0Ó´·Ž÷ô=´n}Îâÿ'Í£ºÚÍuM.‡˜¿—Ö7ð;ôa?±6öÉÍ«œ‹ ·žš™-hÊývÿCßCçÐç*±>wèÞó«ÜR^+x-<Ûå_ºç<ÜÊ+æöS÷ KÍk°‹Ó9nAÐò8+0ݳ c±Ø’"‘CBäÀ™%¸ƒ@†}<¿¤ækÚ&xz©^ª 3[§€SXþàVÐp\yüˆÆBû +äíj +œ;ä7$ã/°…“ÙÃß3ì` -L` xb_Íó?Rþ²ç>¾F›w!Ãï£÷õ¸’*ò8¯y çëh¼*Däý§äïêëµ|ï#|Ã>áÕnk Ê˜Pöϭ3ü¾¿ÖG•¬“öë½5ÿ¾oºN#çºþžÁ³uµRõì¯yÖ÷ÔÿÁ=ùOsx}ƒ7ØÃ;l¬«ÝôœÑúë*2‡^ã}çÔ¸¿Îíÿšò1}WÝw$rùÐóåâ~Û?ñÌõ°‹}Ô;NAÐæœ»­×Þ±ù + °Csû@†Dà +$Ôà*i(v¾eÚ'ÝýÏqʾžíç±}C÷«ºx~þ.Jy Õ_ù¥=Ó{}ØÛ‡|Á‚}müwþ{Õy-w½£!Z/_g×øÊêäç­L©¥ÿŸ÷ÿê(H@°‹…ÔMNAà➀KxNCÀžéa@4*‰Q6lX`ÜjB‚a-×î8¾vªHó,pñ…a¨ôÔ#"=iô!Àžà¾à  €+ z뫞ËË拾GpS¸Aö̤T)¿Gx‡-¬`ýüiµá×{ØÀž?†*=»pê)H™ ÎïìT‚žÁqÖ@çÛwî!÷Ù›ƒ=+Ï›ã*êƒÅVÚ«gx…öûb–3×c¢Î.’ª•Åp~ÜÃùÞ3‹¯µŽ¡æžjXÂ+Ì¥Ù±:åx¬ü|žWI¼\½}JõßÊïþ¶.` ;èàÞ§4;åïžðœ-ëó©y{ÿ;/O®ÛÛçÔë®úo.^Þçxïƒp”Ïk°‹…ÔMNAà’;xWž€ÃüáOü‘°† ·Â¥·pëÆ ø:¼2åK ,¾3=]Õ¯{0³‰íM¡‚na X Ìà‚c*~¦4Idãâµ2÷5<à <@jƺ û)ãV°`¿ÅÎìä >áʘo®ázj9OQSìÅuòL-÷3CùT|ÞëVtÍPsÎöÁk¹¬¿çõüwˬîà‰¹Î™…÷kèú›06ê$O£ýiNûw1‡Y¨×sW¶?/å ¼Âl¹¶ÞòýçBë{þÞg-´×˜IÇ|[~¿‡wœÓsø2âsŒkðhÿω¾7ÚƒÏ?´ßÙ{§â9Ð÷#æPòÓ±}Éú>$ûÿøó ÷צ°‹”MnÂ@ … ê¸ +nÁaH˜LDK‹6‰â,Xp°^ oijj ,>ebÇ¿cGD +¹¥ ˜ƒ5X‚O©SÆ âslä£ ª÷ñ +Ú§˜ßà‡ñÒ{`ŒŠç’çLÉ„$»“Hg(ÒíÎðù–ß$Ÿ-Q»Öø¦žŠº/æ5¥}ù¾”ûþXy ½æ¡½ ìmåüz9¿¯â{…“kÜHÖšîÁ»¨>Èíý{jÓ#ïWk·õ>Ëßú °óL¬ÈXéîfdv`6r—ÖÕ£1m ¾Ÿ¹þçôꯑÿùÑ~¦üàs +Yçúx?Ëuæâq´ê_{ê÷ÏîšÅî½Îi4yê}Ùy/ä~¯sõçö>÷Ÿxʾv®°‹”MNA…Ë —ó02N÷Ðóh&²0aÍšñn<ƒWð_%Å£#,¾ÌtÕtýטYc—ÌA^À¼ƒ5eÏÔŸiAæ³¥.ê׫¿'>( 'YÄûî·–à`ö0à!û8Û†`çLû‰ï™>G°o¬ÇDyü^ówT^xU±£y¦Š=ï^ÿj¯µz¾^Ç1zÞKÆåz·g}¼ž AíÚ.<»þ¿øsÅN$æŸD7±g¯ÌÁsÚÚßÁžµÒ¾Õð>Fûg¹îŸï›Þwyœ³8Þ¯8o]ïuÜ›ÚëÞßú_Tùfqä9°‹…”M‚0„{b(@ð'Æ ÷žÀ›x ·Âi2/¾Œ_å½étZBX„oJÐ#ÉÏ(¤&‚†Ô®¦à÷rB[™“‚ýµ"I$R¯ZóÑ‚¸ƒ'x¸q #؃-Xƒ©VåæÌz°£nÎ`é¼xo~Ìðz‘s Ô멟8Í÷Öe©z-ûFzѺR°q¿?VŸ&°ï ×9Îi4.—=søn¨/Ó_RËr´ÜÕ·?S´Ÿ»®oͽîéËöÛz}½ï×\tå­žm\Ï«®/{Égâ®àLï6þOª£X½þgª£þÔ§æókŸŒÄœó™8…Ͻ‘þôÛ1›ðòÅm8S°‹…“ÁJÄ@Dç‹ÕLœL6F×]\Ae=xñ<ù~™5K5”e‚‡¡»§ººg’RºHÉ ‚+Ò¾{ÐYÍ%ó[p-u=óyAÛ9:ž-ÔVB«“ºèÓò˜À=xoà ÜxÏà@¯#)¢Õ‹V䛿 6â+KÍD‰Í²·ðßâ7à–ù‘õ…D¿º0‹?€wðÁùv¦¿FÍÐ(B¥Ç˜» +3g8p§{Îõî?ô7Üû–»‰½Ç;‰ú¸‡²Â( rÎç‹~êm–»ŠzŸß÷¢ïBçɆ{ޏ¿WŸOßî$ûóóÚg‰ÎðÿÌu\ͧׯùh»}Ÿà|#÷?¦ßÿq6ýæïì§ø»—e°‹…”KNÃ@D›cpIˆ±ã±@æ#>(kv!Ë%7áÔ*¥)ÅbñäÑLª{zçñ—i í7¤î-Á5¸7`¸01ŸGñrÞ%iS åÔy!=‘Ï.Áš¬ÀÞÀ;¿µŽôiÝ?HOÏØWÌ£xw`^Ù“~=ײˆï÷´W­…q>À'ø[ðÂüª[qJê÷²MZ¼®Ö|†OŒ¬µêy$ûíú»”«O¾ê—×é>~¿²Ëu–ä£:;ËWçó<ó^ž¨·Ê“5äûv]>çYGÕ©yÒ¹×é~:ÏóžñwéoÌíΈëð¸sÌÅ?VWíÇCìçò;âä`»ØÏÈš6Çþ#®»‰Ãü­âð~~Ïk°‹”MNA…˽Wð$ÞIQzf@6ˆ?‰ 7,HàÁ… âÒKðÚ¼‹JO`ñe¦§º~^Õt›Ùµ2 7ø}F àdÀm``Çóh­¸#¾+†r–s°"%wC[aL2)¶xïà…ë ÉôS©Gƒâ&GÃ-¹¯`Ö¬¯s~±.ѹz´WzJÜ%ø?à|7ú©W>Nëü}/ÕÏTa|T›´e>'Ô9'Sî“®Ø#?ù·ÑÇLJù6NWœ·ìªsÁÙïØÃ-x¤­­äñuøùǺjöäz­ÿIö¨3þ_qßùós¬ùkß9·¾ø5]™=þ4;;—àëoðl‡çì”ûE÷ÊÔþÏÏŸ}š öS°‹…”9jAEÛÐUmf׌¼ B‰@ @Êœl9têHÇÐ|ÿÆ¿ðwÑ£ ÍTW×ò§ºCWá?Óf¤ h¸F{FÌ/'™cæ0»ù—D}bްà lÁ ÷ŠD}+ú<‚;Ѓ–ÔÜ·¼©zÊ„%½ÏI̱°KÐI/FÍs cv¬©-5ïx_à^»•:4Î\ô0¼.ÞžêWëê¾­Ó -sÉã5ÈÏøµßzñ¨–­Ðó?Ü“¨Ý&üÎÊ'xkp+ñÛæFç´pyµö2Ñ¿ŸoóÓšgè^¨_j>ý9oÚºþú}ÿÔoAmO!\L°~ƒwêß…¿÷aì}ɨS| _ÁµtGº;Ú Åà™Ûã5!„‡Ð÷xÇÓ€RâS²XASÇ×°qËTŠÞà®pXCbÎPÝ‚ñZT"ʼ~=‘9 Y~¢:~fKØÃ0“½D©gëh˜ÓQͺ–—c+8À‘ò÷9ã¶[c+5´wzVCã~¿yÞgx¡éo݇7#ý,Þ¶žÈsÒž(í¥õ0çoà>á^Ù¯/ø…츷Fj뻑dÚ7WßÓJίrû6þÜ“›Çß Ncï§ÎÅÇîŸ?ú?Hìg¾›ßpf¯¯Bÿ?â?þÎÕ|6OnpÐS°‹u”ÁNÂ`„—GðEJ[(ˆ &¨'!<Þ}ãÅ3óÃN2™´‡/íÿwÿ™Ýí¶QEÄ Ü safTÉ|$Î׌_•Áså¾Ë¤ÉørÝ€øÿÉ7Øç™:ãš¼¯E·¶çÔm®5§:sè$—VX‡de{`múÔå:ø,Å·h=gðšµöÛŽà>¬È_Ÿ3ŸÒëmú¯s]Ø%[˳Ï}Æ{?ý½ûR—ï‹:¬›ûžßÂ|µÏô§‹á9b®êÁ¾ûüóŒÂü»ë¯ã5°ŸîS4§úî;•8~GåÝ—9}/q›Öèÿ‘1¨éu_ý.¦»¶7°‹…”=NÃP„®Ã18} Á‰íØI”!h8D:.@IGAË…£i4r øŠ}ïíì¼}?UÕTÕ +ÜKð@Väž(^šÏXë×$uSoòÓžxο€7ð>Á ™³±¼ŽZ®;Ñénˆb÷¤õ½åHgÃ[Ò™ÖmЄ–Æp ;0‚=Í‹üH¿ ´¦ oêÏ_^ÕÿÁöÖÓϑ쬆æô¸5êež»bÝ ×É:¹çuÔž|>ƒ÷ª‹ëªË+ð nß`ü¼‚GjÖ3ùÐ9ä¸ãuÇ™óð{•÷HùÙk½?ÝSÏqä!ßݹþ&šoyNOdÏ󒟬qî½/ˆþŠ|þ^üÿøEÍïë·î*¹êT°‹…”ÍNÃ0„¸õM¡&V +\ù¹ ÎH<W^Š ‘–Q"ŸïƳ㵈h#¢€K²Mâ-ß›”$ÏY%”Ë1éi¼4*XƒŽõ}Þöà ¼€{p•ünÀØÚüBÍ1~›òÒò_è¥ç³Nä+ñ~Ò¤5I£1ãcGðʵí¹Þ.Q“F3SOã–ój÷I'ûÉþEg ìÙŽ üN:=÷áš9iË—ûVÜçoɆcÕϾ¼î x8ZD£g'ßgðxú€ñâçȿѷö²Äßó'_î³×8ž1½|.ü|Ïáuu_<îûê÷å¿:¹^e_ŸÁ'øˆÃ=Ú¥¾ª/~ß½^ÎM­G^çþþÿ˜Cš¿ýÿľ°‹u”MNÄ0 …X‡á ìÙrJ§mÒ锸Y²á !ÁØ!ŽÅ‚ñ O&]|ÊÄvü\;3KfÖƒ3Ò‚Î~쉿;ñ; Ñs…•Pì§\Ýæùâ¾íÁþêŠq=}#˜À¬¥Þ p.ãçãïÁ×¹¢¯õ­Ìü™u©/Ñž+ýêÉ 1ž£ :+ÚÏÁ#x/¬s–܉qƒèD½Ø§Iú´aOFÉ㤀ÇÍd˜Â÷¯™ËxÿVíCM/žw=Ÿë˜¸ú¹w ÞÌvNÌvÑ·½#³ƒw³ýCì¿`?†ÿ \É,ýné½ó»§èÜéI¹cwœY¶åû½„ëzÞ¥zâ\ÛÀRþÞþ¿ŸÌ^=ƒOðÁ¾Üò»ôÍÅ÷õÔWöþ µ5•ë[s¥ïJµ¾Vö.øJaXc´Ç¹$Åu?wòJîT£ãÛ¬×gQ ßï…{ˆµZÇý¸ßF1§äŠÜjnkr’é8¯ïÛŸÏ£’-Î"ækE'œ#öЇ¼ÖÎäØÚfÀö3°ûìp>³7Ú÷é$çÒóœÒ®Ëç:ÄxÏ:ý«µzn1îQÜŸ©½ÊwØz ~¿2ð—îT)‹5µfÅùà“|'r‰ñ­äï6‡Evo.…ãæ²ØŸ¾¾ۭİ‹•”=NQ „­@ŽÀéh8JÈnö7¢Ð ¤ AÒ€èhRFœ‚ÓÐP¤ÌWìyÈÌ·°ü^ÅýuŸ%kÒýËíMôÍõP­ãûœæ¸ïàIê×ïË÷3~‡îåñ ¹&®›ü‘Ü¿ç×ÿÑ ì,ì{°‹“MNÃ0…ÎÀŠ §á*]BHhb'„ + +UaSq$6,Ør.€8Ïè=1‰Å§ØcÏóüÅÌZ3kÀ9iHK:¢½Î;ÇÒ­/I¸ Ò)wzp îÀ +dÚÛŠ^âŸÀ•#Ñ^»ÛóÉÝ[’¾²/z;ðBžç@ÿD½ìüe—Î_±«ѯè߃[¾ïÏGÆžCÍÕƒÁåQ}F’œ¶Ï[þ¥'ÏàËìàßOð +ÀìâÿÄØo\/³³½ XÓ´ýXT_ÿrÈÌqp=ÍÔ{oˆõ|˜.À1Ö§°¿ƒ­ý·§·ýù~œ ÙÕ‡sYs­âœÇ9ˆó?Ó_½Ö=Ÿgô«Íu|Gû8cÞ²3íþõÄÿ8ÆÓü“3íÒ‰ïÅ{?çßßÇ%¿°‹u”±NA DÍoð‘\’Û½KH DC…èi(‘øZ +>‚Y4#™Ñ¥x:­=g{mßED3pNf¤# ¢³üsÒ|«¤[’…!½â4M¶àìÁš¶åD¼>ù²­‚ ãlyî-O¦ÕZR¬Qü|¾Oà<³Î!éKЧzjŠãù…êëÂøÕìí<ÒW¬çšÞ]O°1FËQ¨k÷»·à|EœœâùÞÁ=Ø êuß6ù†dÛ3Þ8P3Z y>êMqJò©¯¹î+ð^Á'jÆ#¾Áxä,kÂóû<…ôù<òNöjG{«É÷Ü÷ ‹ÿ»©™ùn+Ÿ¿ç{ä{ë}œÚ!Ý=kæû~ýà¸ÿŒÓy>×ýù5›)ý°‹…”MnÂ0„sbT’’ˆPº(ªTµªZXuÛ;ô œ›0ó¤aÄÏâ±3žyÏØÉ²lšy2b>7銸O¢ X‚g°à\iº’ó‰ ßtôè9Öõ¡+ õI¿s£¢ï'ø#_`Ã:k©©âx!¸ßÌðµ×ò•†„×­~¢ŽŽ´Ü—¼‘ô¼æ»š$Í+ø{ðÁçpð˵+0p/æ4ô‹Ü%36d$ÿkíE‰~|\çýöÌ|ï`¾Á–ù-3}¼ŽÈ›Ëž‡FëéØGOï8·z7r«Uïjôœúy_'ô®»æ§uD¯þ~ú¿ï>V +ɸ÷} Ök&ä"ç¬/b°‹…ÔQkÂ0àübY5í¬µX†TÁmììÙ±÷ý¬ñއ¶{øšÜôÞ›& !dáæÁøøºâÒü¨Ã°C0¦¢ÅDŽŒÏå½×± TPòYצ¸hô=©U°ž'ØÁ ¬ø<•Z ‰_R‘¯m˯t.ïé'Õ1¥Öð¿p3,a&õ>Ã|Ã|Âð'®¹ö?‡-¼roÌS1g®y‡£ì]ØRzQÞo[ÿº.õ[±†š}¥:=ŸïסùJÑö]Æ2—άޗhõê=è»GÑòø·ŽáþükœŸ{½g~wt>ûG×ÁãF–§ïÿ¢±¾f@wyþ:ó°‹•”;nADÇgà4œ +Øg?`ƒ±! –G$ˆSX²GÄ\…„ZQ-µJ³’ ž4ÓS=ýÙž !ŒÃ‘0&DõfÏ{ȈéúöÑs»»;{t”bÎ6I9mE}äº5÷–GI:û ÌÁ x-ï)\Qð¹ZŒ'° œ5\Û¾%f÷u{Röèò^¸„ð0¬ÀŽ5Yì5؃_ðGŸ X‚7ðJý‚Úwð>À–vëSçw'pßà“š–¹Y­Ú¯JÐï4%µó«¸¯ºÖiK§·þú¹É¥‡©¼ô»Û¼è¼Ù¬Õ.¾ŸM}gúŽÌ¿”8E"¾Í ÷¿»Ô{KéûðùkžÙ?tÚwóKþ®èùX°‹ÔAJÃ@à<€—éi<„M:ImšV¥­hw"‚"¸wëÂ¥'ð‚ñþ?<~&X\|LÞÌ{y3IJiš~™©©Èïg¨Iñc÷®²Ú ±ñy« +×A+ÚÂ5\qL9çܵŒÓ{z|95w êìY®‡ú.¨±m!«–á]–°‡”Ž&p§¸ÿ‚G¸ ¹‡šžà ÞánXÊu¨®ÜÁ3<À-û§~k"×1œàú^™cÉþ)oì§úÍ)öpÍ9Ú¯6ÄyO§Þèù¢ð,îc\/Öåû^:¯ñ¼Å<ñlúwæß“rª¾lçïÿßïÎë×ú¥ÿê‘ú€8ï»æÿ??ƒôz~°‹“]jA„Çá]r ¨ÙÙ]”à B"xƒÜ wÈòêQ$5¤ +šf}øf¦«»¦·7„0ÿ<Ý #ŠÏA °äZ˜¸œh¯<3ro]剹+CáÎJ’9¬>úìÀ+Ø‚wðN`žM^«+L~_¯päŽÊôHõ;žÅ»,HM/&®¥Î×µ~j‡Îã;¿CMü°ÿå›[Æ4¬c?Á¹ïx'¢f à ì@Oß‘ õ?àŠzc¬ðEfeÅ\=W½ßöcaü©‡ò¡øÊ!ýŠ¨Ï¾/¹³ó¤8ÝË“ßûïíç< Ãóéç{îú¯¬Þ×ËÆ’šGéRþo¡7• ]Ê_*ŸÞí}©?dù&åž°‹ÓMnÂ0`_¸…-ˆþÐUp6lzˆ.P/Ò+°âE}Ož‚ÚÅ'Û3ã±MJ©›~=ü¡GZŸÃð +3(¡O9)Ny:ôߺõBî¨ ¯§yÅ7ßC˜Ã +Þáö°…öÓûñߪïqRð\†TQò ¨âÆ0¥ ãJÊònŒ7ýîàÎðGx¶¼ªûΧñKæ™óžuçk~ס¯)cð'ø„ cU³fo¯ZÎaÎMg¨q­/Œâk*Óõûй”a.ÞŸÖi^{òß~ßþÎõý}úûèš,ÔWßmñª—…˜È÷çã·öïÿ÷÷æóžÏëúzõíuuŽwt߸ü– %°‹m”KK1„ó‡}¬Îdçá2ˆˆº¸zqÔ“"þMk° +Š2‡ïNº:•éžRÊiù㌜pÙ` pGp&Æ>ò\×ÑþEu3Þ“.è­ö†± +fp öà‰ìwïXOºª+½('ëo©;“Á4åCçt·[pÃ÷UÞD²Þ`ÔˆO¦¹~£oðIÏ»ð+5~^Àx¯à<€Gð~À×WÔ’Ï…gÌ=Ðל¹^ÈÄû¶·3ÿÒOÿþ.ò?Z¾Þ;û¥ï“Ö¾êyýjßYßQ}¬uöAmà9ò—g³ßs.rDúMß9Wžëq“Zþßßg· ­ûŠžï«sÒÓÿéd埵 T°‹…”ÝNÂ@…÷…Q[iÙ¶TDMÀ/Œ7BL4ñ|.y'Nã9ar xñ%0;gþv¶)¥«ôÇ5¹1 +p{†X€ðÖ ¥} !•éJ¢ÿ•ù”ÆØ¹ë§FÔÔÈÞ{ðÀ]¨· 4Ô”!®ò¿k‰ú®‰òÏ,n1ÕGEŸ%xo`ÅùJ×åS½­©ŽL›r±Ïôý3ýžÁø?à lxÏ[ð ö`>9ËÆèÙÏ’1û0Ó¸#êOsëèÛMâzÿs’©›weø=ù>éÜó)öA÷X¤ãžÅ}‹§ûšš~¬çûèïÆßƒ÷ûß»ŠÚh=Ôé´~¯!¾‹KõjgæôU<}Ÿ&¥hœ°‹u”ÍNÃ0„ýÆ×MS(­J¥"$ QÑ[_§àÀ;1®f¤éÊ9|J¼ÙŸñî*)¥»tË=ѹ#½1€X-xP@&… ÆHjÌC£Žçézæò+ß`±Q»Ž <ò.¾»®Xci9FÓê}˜»§“™c>À8²¾â¤­ú=xµÞO¦!S›3#m>‡Bû¼Opß|V]ïà ~ÀøÆ(÷ªQ/Ööþ8…>ÞõwMÏ–ßcÏ÷Q3Õ÷xÖ,×¼WÜûèŸÓíδöÝëD=ÝL>Ýßýb\ì1Þ{Ø|·sÃîçÌ~h–Ëp—Êõ¿ôé9FÖ°‹u”½nÂ@„í3š€ ‰¢´”)"ÁðpŒÃŒ´ŒŽâ+Öû7··¾”R•îÌÈ ‘-ÿ0°_g÷8ù¶à |ƒ#xg¿ù'pà‡º«»0Ô[³i +dƬÉÀo¨áH ò­éC]ß/õ\éÐÙuÿQC.Øš£ï•ï£îT~·µg+êñ½÷ø&=îLiŸc×S=©§³Ç8Ï«ì{Ì÷þþ?>{J¶öä\Z;ËÄÿ»t*Žå°‹“MR1…ã†ó* !™düŠ…{–pÍI8€'ñ¥|]>»’*_Ít:yý“ta~¹#ÞöëFG–¤æ³#Á¬A+° ¶ÏlÓ÷ñkùíA´WB$>_­§øèI’ýÞwÁ ؃-x}cå|”¸VŸÙ=ÏLÄz“$nñïÀ'øáf†ï¼3ö û3í,ÿ5Œ^úc5=ƒ8±®׬®‰ö‘<0WÕL®÷Yúfþ$=‰.Qò-úoà œÁxåkÑkélxGGÖ5IýK×·,wÕz7­{ôsáßuçtý¹ê}©í­åá礿5oåÿ¶‚Íâµõ{]Oç¶ ï$9Ýsþwé‰3°‹}”MRÂ@…‡«xB0!$ˆFÝX.ØYvÀ8gàÀ•/Å{UmW&‹¯’ééŸ×=™„Ãñko)Y’Œ¤ÎÏÇûhÀб‰A~IÄ>¥/cÞl@ r"}Ò+ýV×°_P_ÅwÅË^“gð öà Žà›¶¬éïëÇtغµ—´ÛÚoà~C˜=àù`ËyV¬ÝÑÖ™Y¯ŒþÚéS\ žÀ éÙkË}Å6ŽXÞœ}øýÂÍÃÏYz_9ã+¸ øàùʧ4ùÆò ¾ïà˜Վ}*~lîSç%ô}z»üc߯÷ª§s÷zbq±:Þó·ëùI¤õ)½ö?05{oÓ‘üòÿwÏÿµF÷°‹Ó;NÃPÐaT¬Šg;þÅq @A@(Š(h ¦¤ `t@IÍ"h¹QîÃÈ‘(Žì7ï?›ÙȶÆtLŠ«½éË`Ÿ9cc ãi`~¢œc4?¹1~\FéŸ4~sŽLù^0ž»óúvá”ÐÃ9œ@ÍXÉ÷:öÝÀ³ÙÞ>Á! ö +·°àø:(Ý~™{WEÚWyR~Oá>áÞáæìŸrÌV|.oÂ9<ÝOzλ„+æ¤wwjH9™ÚCkÍ©µ¿yðõ4 ¹j¹ï¼Á¼ðN~¿ž¾¯Ömù½Öð\ë®]Þ”÷ø½|­«^“këܾ^‡j|dæøs(·ýþ[qÞ®ÿ$Ö¹öUlÇxüU«yPó|gpaÛšéîQ…üÅ|ì2ò~–VcC°‹…”MJA…K½Œ§ò &Ó“ÌŸ“DMˆ$ñoáBwºEÜ ^À.½Š¯õ=lŠA=ýªººªº{ÌlÛ~è“‘®y´°ö8© @hYÇúŒD{É1§Öëð $û‡œÈ_ñEÁüä7 C¢yÆ` .8Ž©‹†Úܘ­mš­¯Ì6>Á¾Ï mÁöHŸèÛ:ª$§àö¯\ÞEÒ'Õ{|®Á=¸ 0¢-ŽSæŽiŸðŒ´OáP}Bu¢>;áwšMê#2!-mû\;¥Þ¸”Œ£º»ú}nÁë%q*Ó߃–9œ‚ð>À3¸‡ì›ï»ú ÜtÒ{œ’Þ+“ÞC\×w‡ÎØ÷u—ÔöûÞºðïBùänÙ5÷uù<õ.ƒ‹§\gìã+x/œë¼£}Îïò}üÈëß|ðzý…°‹u”=NÃ@…'‰{Pp7DbÇ?¬Û$ @ ü‚†’QRP"JnAÅx–ßH“Q\|òîÌîì›™]‹ÈP:FdèP{Æ`æ` +rÚRP‚жhÍþ$ pmÁyDü9}èúvoƳSãOHJ-Þ¯öÜùsº/àѨ·Íý\ƒo‘Á—Èæ‡ÈÖ%ØÆøQdc¾°5hwÊqA݉£µ84¿±¬æ¨ãÌxNihû°Wàœ˜Þø¼•ÜäsÎ<–¬Eiö«^=¯â¸6z*Æ8ã·v¹e\×°¶…É¡àºÀ8m ÔYNGuNÌ^[׌¶†ÚÏÁxŸ}“Wpãâ©.k­4®Þ£¬Õ;c‚¬Þ[Åßk«ÙÖAïŽjñ:”˜¬{ö|û3ÓÏ„6]÷ o(°®Ïào¶Á;ØÅüå^i¥8²=ïÍÜÉ|bf}[ƒ(À¡Cç)AÍ»9ï´—|ï¿tï<€r FNCñ¥› +’tpžÀ=˜Òž-A¿KùUÔM>g`ìÎj~ÏÀ1ud‚Žç×àËlåÃlú›{f¿xGÜÕmœ}ƒSÐR+qDjR1/¡¶Ô9}åí)Š«ŒY»ìÁ»ÙÚ.æ Ï|?Ãþ ®Ø×söºrùJGÿÊ«}?ñ% ¬°‹}”=NA …Mr .“KP!®€H–l2›Ý%äB‚„Hj +ˆ"*::ºÔÜCð¬[ÆÑÚÐ:ÐsÏrV‘¿Ûø©zÒºòï·’ulÑ÷ñÇ”xVïV§~G߉½çij)ïu<;‚øRßð0‘§ÿϰ<¬=ó 6®¯È°‹”½NÃ@„ï‘CLb›sã@„@‚†Š&B‚ Q@Ïð¼sÊŒ4Z]AñI÷3³»w¾uJi‘Ž4deãÂ)Y‘5)kKpRAqZÐ\ƒ'ð ÞÁ˜¨ikYÉç=¸Ïà |€{ÆZ›^x--5¥¦3ÖÕ™®©äm,o[pöà<‚7ð +à†šŽôæÍÌëäÀ†dóöŸÖ=OñÌàŠ\€1ä‘OßQßgæ™v<ßÈõX_±jU÷hóšÇÏ ïd¾-½CÐn¸7Å,Žü®Û“²ÖÚK'ïÌûšé›m<ð¬Åû åžòë^:îKÿŸ~rÔþ.U®xbü%žú]}óÄ8þNüéÍÜð~Áw:öÁŽõúY:{g)ú1.ûƒþDóhjZCswj¨n)}©±¦9¿mú¬3Ñ;ûÎÒÛy97ûÎ×q“ýÎ aXÿ÷ëO¬=‡:ßqîx®t3ûÛhÇsm=ßY‡n—­ýª ô©9‡çŠÿ˜¬}ODúxô@kß«´Â¦°‹}ÔÍnÂ0`?q…Ä$¦ü¨•¸Tê‘C/•¸õzì¥ïÔµ:# #Äá$Yï®;)¥eú÷hàÏçà×3˜ËXÕ„:XAbZh€õ™§Eüva +r-eœc½Æêx~¿^Èøµhƒ>&¥ªó*2¦3Ùò‰ï-Ò5nžÂžq¯Ø¸b÷z©[ã·°†:§×ð.áNxæy?!G±ÉËõ`üŽb/ýl1§=~GYþnàXî/b?µö[ø +?á¾ÃgxÁ<2´F÷4×s%ñºßt¿çtò\ù9bœ÷¡ýd‰ós¤÷³à{òkÆÕõ­ç­¾ûw8ÝxƒŒ«ýúw…fÆ¿#p÷´ ó°‹•ÔMJÃPà+¸á"ÜbMLš<›¶¡**ˆˆ8‡‚ÐI·Q§Î9p žçÀÍ%|„÷{ïûÉ3³ÚÌΡ *ÖOœSRùìŸJ*c—ÐÃ$Öu,+©©…ÇõçûT¤u©­ šº1•k÷s6Œ“¿eŽ^â7Á4ðqê‘~*+Þ®ö,aE æÓ†xÊ߯%¹üçœGß[xƒo³½}|?á…1|>3ê[ç§ùU¯óê(ÇÈç 7pOð pÅþKêCyåæÓzýÙ¨>ž¯rîg_XãÁ!Ê?ðÊ9´ºñ>î¢x5÷Aû¤ù”c²á?7ö¿#1ý=ÐüɆwË‹yk\Ü;ÝõÍg÷kØÂ¼Ãcú1úrþ½ù9¦ŠïËàÝùr+QÙ°‹•”=NÃ@…'§@¢¤ä œ…€;Nbâ8€’! ¢¡¡¤à\‰–‹ðVzOp¤ô‰ß—:ÿÐé­G[*°EöÀøïàŽ¶‰Ë%‚­»`Ÿ:c§×åÏöl0çx—Ô›¬‡Ð¯”Ôˆ)OZ²‡g™‚MÖP1Oît¥%»êñýPžÚéH_q]{¢9nÀ—Y² +îÁ +ÖŸà”13ÖÝòW窩wÌÞžCΫ¥_½ªh ¾sp ^Á#óìÐßR3¬XkÃxõIó©èÛ& sænÏÔæ÷/Ôˆ{—,ƒk€Z’ØÞßõ½Ï®yuͧdþЯwÆÒæ÷6~?ZKKg(Ø—Pÿ3xÔÎ\¼ò+ÆßSõ­ï|9ûæòƒ¾,Eüÿ¶¿ûQuÄéÝé½ëÛ¯×È ‡û@/À,°‹}”MNÃ@ …ÝÀŽ%‡à‰i“4I›¦¿Ð¢ò#@¡.`à +$œ6HHHl8—àY}fŠX|Jfb{ülOD¤%"M°C"²Kl¿îÐuÄ|z;ÃüÃxÛÄÎÒ)(ÀxàÜ~o¨_Ú t6õ„yy=}õ¼pæ`À8)cüE"‹še´Ë +’:mºß=0ûà˜Œ™L©wD]-—OLbù˜._“ѧ‚yéy§¬Áßg`ÎÁ‹HmÀ¾¶õ38qz¦ÔrÈuŸÆÒ˜gÔ]Q[X³Š¾Gà<‘9ó’û6á³ãz‘²f¹ÓÖ'=®sG›þ]ƺ_иÖðþ .+œ›Ãp?þ‡T~çY²ß3Ö*“Ÿ¹ ïO8û6‡ªcÌ:=Êâþ\°ž‰ó·ž—´·þZýšÎÖßW›yµÓYxGmVÁ +Þߨ_ó÷u±{g÷Ýþáz‹Ôeù³äÿ ’ëcH°‹uÔ¹NÃ@àåax?‚ØN|Ç .!„8$A"¨!=-/AÉÑðô2Z™âS²ÞñÎìî$ι‘s.…) eZ2dSäÅ…f®‹®¥ëG$5Ô0…gx€X…ÌË'ú”RÜ‘;44^ž'PÂ&œÃB®—PjÆ1¿KL¬Ðˆk40±9cZØfÎxcn©cöáv[ñÝÏ ãš}sº¿„ó%k©ùY™zÄc™_c^9çwø‚¸=Xg½2þvnaŸ¿p[¬qN¹ Ö>6g#qŒk;ö“³æ1ïZòÞÁ'üÀ+ïFjðŽƶÜcÞAï¡¢štlçt^îAúïfp̺lÿù}ø_?úý£ûµÏsæmxöwèÿ>üõ3î¡äÙH­×pÉ3¬Ü¼gµç‡¼Ó)ïpÆ{)ܼ§´Ÿ4¯}WÎû á‰ëLLÝ–ÿÿá{ýoéyûM|ðX’r•°‹…”=NÃ@…‡š[PÓpNCIb;vœØ1ÄA!„…ÿ4HÐÑP!Jh©qN@Ã[å4Å¡ødïîü¼Ù]ŠH¤ 1‰H›ìÚÆ.qv:Ž˜v‘#ä/À\9ƒÒÅI)‰ÿÉÝ%a>—eÝ§àšœ€÷¢kbX?l*pDcÆ; ÞÒıö5s½‚_‘M|¿Á-˜ pλâžô¨9g¼Ô õ…µ>5 žŠz +¢º/Á¸oвöÀÆ_`ÁºF´}Ôþ¦\gõžÁÇ5µ”üŸ¬©§à¾×Œò~BÇ6ØÅÿ󛚇Œ7pèzé°~êk×Õ·¦Öç‚ÆU}ÜÔ‡¶_2ÖjëÕyk¯½¢wÐÞ‹¦ø¹©+ôî¹,{kNý}§;ãÜŒçôγ\p/šî¿½WzoB¾)óÆ×âß?n9ômi¹ºccýå^ư‹}Ô;NÃ@àÉ-8ôôˆã@b;vŒID /Axˆ‚†Š‰‚ PpJJî¥ÿW†UDñI^{g³³6³‰™  „2jÓ©Ý¡”ÔN׃= +ÏtŸF»œq Ô|V|Bâ4§—SE%õáf0f*öÍÜz *ŠñGŒ1~?êçÕŒ™Ã§Yk Öñü÷p +ç4qã‰ÏGáò¬}jü!…ø?àû°æ xxƒ/¬eð½µ…ö7ܱ¿öy ·ð7pÆü]Á<Ãß7ѾŽÓwûÐyh}šgÎõlÀ6ž9çýW“ö6æYN™¿cæ +óºÜè›ê'>7å[u¤ºP=ªžõ>hü¸&»‘x|Õ·Ÿ'wçr:➯yª›Ê–÷&szÌS8§wxelc˺÷wÚßuü¹Åóøvbÿ«ÚA{…øÛn°WV,º°‹“ÍJÃP…GÁOã›iÓ¦I“4i­V+•ZTE]ºp#º7¾OáVpëz\¯ +.>ÂÜù½gÌìÆÌ.Á %-²NtNH‡èì>胆ßä  øù‡¤ôñÿm’üâçy†Äóö« 9ñ{#°M¿*è¿ÕÜ üý^ÍGdH[?¼_1Ÿß›ƒða¶´Šï¸¶Å;܃ °ôÍTñKž…×±¶È&ýjâ¶1˜€cæzOàµ,ƒ5°‚ó 8bŒ†¸ï8çìÃcMi?3ÖQý«NÅ‘.ªÍgÜwðÉZæì¥Pßnße]Wœß)kh˜GóQ~é%ÿéHz¥gÙc +w¬ÉX_q|é[y2újž¾ƒëæܲgÍI1Ò€{Ÿq^'¶ÐLß~î[bßw=ÞÍMû±¶šví‘b¨¸_·µþÁ†ó”VØj°‹uÔÍ.Aàkï9<“§côt÷L·݆±‘aa„ 6Vbaã!$ÀÖ©Ì9qÕÄâˤª«êÞºU5fömf_pëPB]èÀ"-Ñr¢K=¨ ¥(ØïÇ¥óÒ~IãiœúÕΡ†5毸E¿GÌ7êșóѼX—cÕn®äníÌRœ`Žà^à>Ìfæ`fѾæ¸àöÕrË#p¿ lÁçű«ü®üb{ĵ¶á nàÎá^áNaÓ­¯c8dþ»Ì­áïÈ{åòTü@}òµï»=5\;æu \WcKWÛÀ¸ûpÅ=Ürî û[ŽUÌÀZ¨F麅ËÉÓ}*yÖþ>å 4OßÔÖ÷ô^þ×Öž+Ö(žÃ¼Ã'¼Á1÷TÚï½÷ï'suk¨²¿ïÅççß§ö©ø:{Õfh“{µg“;XÚôW|Õ¿fŸÿŸé$Ô¿ýâÌÞQ°‹…Ô;NÃ@à ‡¡át a;6~àMá©Ѐ äÑÐq(8gà_ñ˜ Å'e׳;;ëqD«"ƒù€#¨a$°A)m:•ÐÁ˜(“š8Ý'!?ïù<©Û/ç™G0áJ£Åóæäcr³gfâ ŠwR1WíÖ—f½î¯±=ÞË ÜÀ+îMdiæøßÃ\2~¸nÛäo9îYwÜó¦\ø|‹ZÎõ¼+=Üï}ç43ù:òyv9§Ï‚‰o˜¯5ëu¬çiHǵ‰±f[·îkë¬÷ ážàîáöMÞ†{s^=CE×™œVÍ8ßZ_Å9ßg:Öuÿ‡¤ëíýLXÛ ¼Ã'¼ÁëÒóùïTû²’ÅÞ/äo?k þ;‹ýxÁ\§òÛ±‡náN~z7¸{Ð=b c¾·À9û?“8:¿}§Ä纰‹mÔÝ.CAð)¯àƽwòÚêùèiOÛ=Uñ"D$¸Ã¥K/áAÜùOü'¦£¿ôììžÙ=»i­‰´VDäv¡t`ÃéÒ&umçP€´Ý ãT›b»´oý>w5U.w”³È Ær·.›?¾çù¾Œâ˜’¹4Ϙ{{ +פÏWð…ý?YúYnð»Žö*â7°;¤Ï#·¿¶^M`F‰ùAÍxÔДï[¾†ó©æÜ k»¬Ž‘oó[ÌêèÓ}u0aŽ$óuN)¹µ$Öz—pK÷ðwpÀñ–ßê´XEVWÅ~Û˳M‰ï•ŽÖ{Ì—×sPžz úýyÊ÷uÙyÒùáá`_þÎø¼‹ò[SÆ”ü6gð̽ÇõÁ3¼ÊïÙ…uÙ½*Ý7¨äÿ]ÿs÷ý"=Bì°‹m”»NA Eø*þ‘<6Én’Ý%!<’€ ^¢@¢ §E” Q#* +¾ƒ;p¯b¬G³öŒíëy¬™}šÙ+¸cÐÐ[Ž6é¸y‘ì ô@—d\ß +4I´Ûf@룎T«%PGÈAÀœ½n^ù”ßÇ Ê­uê7_AZ?;Üßð>À#¸of ä[»3[ßĸ»ÿ%˜:¶©_}Ö¤âܘ”¬)Ê1µóU¤füÄ‘òŽHÍ>ö¨§¹«€×7 +kKæžM5}Sj‘/Õ]}ÚcŽ:Ï#pN¹¯÷àÌž¨Ÿô]Øò\ g'Í»¬¥ñ˜Ì¨Iq© ð¾Á38§?Í÷‰îŒî™Þ‹æ{öÿýô©Gº½Î¡;£9u-¨+gînÀ×Í\]åÓ¹èî*FñÖK½=Ùß]Nwø½¦ñ<€3[þKTÏ¿«žÓßzü_(î×þ®ù›}°‹m”MNÃ0…qN‡aÁq  IÚ¤)nZÓ¦AË@Hˆ N€Ø°BbËUX²ãY}£Œ¬.>){Þüxby‘5h@œ‚ôÈ IÌ^bìñ~ H‰ÕQÝ$Š“FÄçÕž˜!ߌÀ˜ iÏM> +´à<ñ{åØ3热 ö\€¨iÏIA’±«ˆš¾ïàWdgìáû<€7ðÛ +ü€#¬¿Àë9‹p¼+&ijž}jƒõsÜŸPÃq¯fîC®õ̘õ¦`)›YY³'޾•ñS_Eõ­®§ÿÜä¡„8ç&~Ãõ%{²¤Ÿ7Zžµ·'SRp=ẢMÙGD›³Þ’9ºÿ.8÷àœsŸÔô™˜øÂäÇFã<xå\Kæê,už¶Ö)Ycl¾AÌØ9ç³gf«9÷°91÷ÒYnDl_©ÉÉòÜÝ€o‘Ö*ì›ÔߺyþBÒ#î90h\W–g~@ Íqïìðåïùµ„ÌÍYcÅš´ÿI]wk <‹¬¬Ã>Â÷#õy6êS´­¯oêÒ;ôßÄÞûæÝwl’æÿÀ3ZÞ/3ZoȰ‹mÔ=NÃ@†á…jz +n€8 ×€Ç$ÁIpb“ BTP€Ä_@ ‰‚‚PpnÀ·Ò;bbQMÃüÞmŸÑ׌µÓ_÷ħÞÁf‹¢=¹¸¦ëu?PµÆ4ÌŸ;Sý6ü÷ñçÞ³ó¿ê÷ q¹’_m‚w¸°‹u”;NÃ@†'ŠÐsîâÄØ&Ç‚  @ˆ +„¢¢ D¢§â +\€Á?ây²„âÓÚ³³3ÿÌ>Dd ¿ô@l‘±ÿ¾ÃÛ»Äü#ÒˆÂ<u¤$!cP€!ˆ—Ä9wnÁx'`B¦Œ£~;Äâ8?¢}›è\Æ5˜ýΩi2×9Ç(é“3¶RsnÎÑrÉØÕZ9ßcÆÖÚîÁ•Ó±nÀøßàœR›Òš9 —ÃSPoÅu3Ò°ÆKp9^P‡Å²×¾ž\û µ<ƒO‘Î:XÅ÷ í¥‹ãuÖ¤`ÌpŸRÚ¬¿Vo)íþO\Mõ@ÏÍ{vÀÚsÆ Ñ¼1›í™å­‚õvÞRiÏUâô‡Ù¬—¶ÞοùÅò÷Nø){Ù°¶=i÷[ëÆ]él€‘•MŒï` ökÖI{Mÿ À4„ú§Í¿ ±,êþï½YFô·‡Ån°‹uÔÍJÃ@ðé{yó…jÓ&Mbú‘¤­R«ˆèEQüO>€AÁƒу7/âUðú_ú‡æð£l:;3›Ýˆôd¥mØ¢é¸kØçmÒøˆºNäø:ûH)¦ üí¯ÉóùNàNa†4fŽmÈ©OÌ_™ÿ3Æ#˜QÉ\%k†¹pHsW2gņFAZc`òÖÌ¡¹á Îሽ†˜}¸…Gxƒg¸‚†úcó¼¦)Õî?]ï.á~àîùŽŒ±ë³ë™:û¾†ø…o¸ã:5‡ö«½LHß{ÁýI(g݉éyƱö”»½Ô¾Âyy€x‚Øc­Â 9¹ÌÈï_ÓØÏ×s—8='1µìÜØÄè±÷ÁçÐ÷ã÷=œ‘pž¾DZ›€ØÖÆï²:Çþ¾¤ }j¶fʹ™ü}/ôÞÇò¿ïußû}°¢%óQš°‹…Ô;NÃ@Ðal€P±VB…ägb{œØŽÂ'|Bt4QÑ Aà +XKÉå^å1ŠDqdÍïÍ{öŒsm·tø;¯eúHíi^;2ëãýZ&~zðÙ1í$ê·B_ +#h¨€| pÌ5aî5\†à!•|æ\—s1LàæpGgPCż´¿7ñ<óÉkÄþšyÍànùœ3îŒû…þgxãóžceT¯g슔óļ/å©Üœ¸‚wç66a¶Ðþ'ŽsžêѾª§¦1]À#¼Â ßUùª¿4ùN©bL}?‡”ýÚG5ª.}·Ô(ø¿PÏ6ìÁÚ¿ðÅÑ· ç¥훺¿çÉÊÜêìø(g«O]#>÷Š©ó/Zß-«gbèÜæÌ«a½Ÿð nyÎ +æ;0uÆ{+×uíÄ­îIh‡û¯ûnëlsÌÒÖxøìkýÍõ¡ê°‹m”]N1…ËN\‚[pCfgg˜?Ô`PžÔÄøóâ&ÜŒì€xΉ7_ÚÛöÞsÛÞÖ9wâv„ãÖŽÌø‘­ós]ãÆLüôA +r¶Þî±U?ðcg £ŸgFl-CÎg´Çàšø~ +CC +úfÔ’ž×(ÁÜ€gð +î¯ òðñ'¤1ù*NC¿Kæt–`Z°`ì5xoà<ÒG¹J/gÞ5sœ3ÞšÜÒ¯¦~ÅWÜÏøv®³pWØ?àq–\§³+ µ9¿sâ÷=£î‚Ú÷?b¿âÚ ®Ñ.Íý¥Dw šŒÉÄ܃ÎÄ÷W`ƒý¼€-ø{û¢^˳šr½îÝêªÞR÷W"ç=¨žTËi€õIˆjZ5¯yÕ¿8%‰!|g±YkµôÆ +žQËó-«Ý3±¬¶lé+ïÌøý÷þõtIÄ×^üÿqèvÿDô ÃÏqâ°‹…”ÍNÂ@…§àK¸÷Í(¥ +…ò#jŒŠ ݸ3Ñ…®Œk·ú">€1žI¾ ×ãâKésϽwfŠs®éœ«‰:ÄЄú?ÔL¾Ík˜µ†‰âŠuD&¦b_ÌEN¼-Z†àã'¢'ºhýs +1)ë}Cf4(ˆ ÐäÄ +ò½÷.´!E?âZÜŠbš.#1c¶‰é;Eë×K±Äïp)îÄ£x ÖÚ1=÷ñí™þüžž’û&>‹ôp¯ä/È÷½­Ä½xæù"­ÄÑ–ØÐû»¸aÆ qÆ<uæÌ0bÿ‡x[JgÎeÀû”¹WÔ™á—C}â¾æº±ée ¥ÙŸ.>4戾„ö2ÚT쉺Gâ˜}-©•˜s çè¸õ= ÷1ô–s ¤ÆÃz…;¾Áê]«¾'n}'mž§1k»h÷ó[ +>a¯«~«––©ß6^¶^lüc÷»/ÿŸ° ýGÝŽå¦üô–°‹}Ô;NÃ@àã€DÁh8-uHœØŽ‡í@$ x +DAGEE  ƒ‹ Z:þEÿˆa…(>9ÞÏÌz7‘HDhP“ZÔðè|H.¦næ"ùÉY§ÀŒkÞ¦—×Í¥0„8ƒ(9þQ׉¡]ƵyÝ„æË ¹Q0fÈkÁ¸¥Ì™QjÖ±®Övó¨`ÊþǬéæ“ÏÅm±nazî°nÎùŠ9¶©äý9ÜÃ<À5kV̳n—´¿}¸ƒ‘¹šÈü,ã÷Æ^àöà^1¾«øý 3x†7^oá&\««?âšvYkÌ~ôý ÌÚ-}÷}Êxïöî®à’½ùÏe\¯[»;+ì«âüˆ=N8–óäÌ÷ˆ5.Á:¬àþn̦Œ+¹7 Ÿ×^õÌé:±§{¢ç@÷¹gòh®„ÏëÌ?kZ#1ÚŒ ÍsN@úŸ‰L~l(¿Ïvë¡Y£ŠM=ÿ;áÓïBì7ÂÖѸ úÎûH¸Ûd°‹m“=NÃ@…(¸7àôˆÃðc'Îí$vþ@H €D¢ §@ (¹§ æ­x#=¬Ÿ¼ëyóvv×Ìr3;"âó]²OÂZRûË­3®Ú C’%9˜hÍXòG`®Á%˜€ 4$.ôâ4©3`nŸ>»;ÖqÜa^‹4‰Ï݃øzðV²ÖDôr‰mÑ[Áõ Ç©èx?»¤GÝàu(ßSp žÁ{t(uKæú9ä¬u>ÍV¶ÍVwÌÖ^ÁÆøÿîÀxGÌþ¯ƒMŒCÏ¿Á=k…³9³Êž½¯ÁË1˜rî>t?¾—±ø.dïõ/ÀYðß±nIBÍÆSwÀø™R;£Ÿ!c_ÀøìÓŒñs2fSñ×£VËþß?s?ÏBúÐ&9ÉH»B¸gáž×‰Þ9÷ŸI=Ówåo/–u‰e]ß`@ßVMP Ý_F¿õö*TõÝ£¯«Ïeþ]#xH~4ØR°‹m”KNA†ËÇÂ+x2×ÞÀ3<AT|$ÆWİrï ¼ˆ;®ü[¿ŠÅ—žîêªþ«ª{̬cf‰8€ø|ö!ØE]4EU”DM‹Kh±¯Èè$¹óüƒ­‚ßP\ˆ‘è²æç#Ö‚-ƒ”8 q„Î ­!×袷ÏXï •ܼ±Ýs¾ÏâQ "½åH[8ó^ÌÅ‹£-…Œ¹S#‡ ¹®?øÞˆ<.y;m|oĻيz¸új¶¾%¶õ½£µ ÙfhûÖüMëŸfk›úþÐÚùèÍ`Éymðšö¨yZìó\NÅY§ƒ“±æÜÙߘ’÷0Ê1|?ˆ'qEøÌaBì”Ú6Ñ1¢S|=Ç í¿ 1{Ô~Bü#úVµÅû’‘ǘØSbûŒ{ï¾ñ<¼‡xÌ”ZzÍSû¿ga_ü®ü½&Qß“Döøâ·U´Å{ïwºõ·Âþo/G>¾kt{¬s™~÷ýý?üË>P…°‹uÔ»N1Ðz:>ƒ†ï æ¨ Éæ©Í;„d!„ˆwÅK*¨h(¨¨-5%©èÜ‘î–%Š£¬½öÌØkÇ̺fV„<ÈŸs°Fëäý Ô¡ )Ô ‡pM“à½Æ”‚øŠç¹ËP!Û‰ô ÅÅ F1+|'Þ®FwƒºÔ§”óJ¤yª«ÔYãxŸwÏðopÃþz×ë?…ø/¸‡!÷ÈÇ6¸Fñþ6ÇŒ!cÐ& 8fH[¤ç}x0›Y4›]0›[U´¿Ñÿ—p‡öGgUtætŽãvƒõ†ß¶Ç~§»¡{ªûìý:×ß÷„â¶î§Î|Ê|º?MæVÞ…ÿyƉﮄõ%ÿÔå5TJª¬H°‹m“Û.aǧ}Ïàĸò:J»¥k·G¶U[%뇄„{D%âV¸÷".Ä•ÿÄbºéÅ/ûÍ|süvFDb)ƒ%R&Ê d‘”¨_5‚$`œ“Ðu¢vUßâé9à²BÛÐÅnƒ&åÀŰ:ÍϨ²>Åj˜4Iä|½¿ÅôrÈžRp¾E +Ó` +çg°Í«´ëƒkð›y0‡ó;ߨEÛ&û\#ªï€Ș+q÷Æ:é1Úï#¢96Á8oÈ? îÀ#˜nÀ%ø‚þEüÛ"ü +û…÷Ö‘2W¹;¬·•ë¡Í»e_Bÿ|OæßgÍûº·ÈxÞ Â:ºÎO{}ŸàÜÒ>rs`õ&|£!ß+c¿‡à”œþk|ä•ú.ãØœ[|{­ó +Ü€ ù› Õëìéù¹­ÉÿN…9¹Î:SWß1Ø¥^gMçÓöÃï³ß¯I÷¶G• r ãû±û¯1ïm§KÄö;ŸÇ~Ϫ¹:Æúø&#ݰ‹}Ô»NÃ0à“7Vx +&Š´%MÚZš6¡-”".ˆK$&„„ÄEÌ€XY+# ;¿Õÿˆ#1|ªãÛÇ'vE$‘ +”¨ «†{a…B*q\X‡¶)ƒêä¯ã+Sdbj›~?Ï +Çi|ÂÜbOB5ObÆXÚoÇ×¹ß-x€o‘`fÑ~…SBÛÔå +¾³ Kh¿Á6ÈÕ«à¸!Çt ÇzöùÜötɽۄ¸€;¸#ÎáæÀ ÜÃ;|À-r û„üæ9èfÐwÉ÷…‘SÆü›¤ye&¯Û™¡qM³ÿŒ±9k0âºî-çoŸ}=>ë;[¯3xFþ2!ãÖ[3ù¶˜‡ÖgvaöÉ}ÓÌ5 hÂ5c»Üƒ›75ZÌÍÕõœß¦0qz¦ý3fÏeÊøŒ5qßêÆ29ScÖ£Á9üû£÷ËÞ½Ó¡Lß?¥ñz¿ü;Ñ ˜ãCOéŸþèù´Uùýß™ÊóÀL.°‹uÔËJÃ@àqçøž¦×4^’j›ÆD õB¬.A\"]("ˆ¸ò\èVܺré?ôÿá8ââ£É9g.Í©s®åœkMhC—ü8‚eŠÌ¸ab{´J tLpP“:Ô2ü8æíšmç×Í` V˜›ºÚO8¯ZÝ€æê±n +[pOð¯ð38‡ lR wð pÅüŒr3®æ3Ÿ7€‚ã‘©'9 s×ð̽œ2_q;pÀ¸C؃ +JØ… xoz`|νȈü÷ síkÀÅ…ù}ʘoÏQqÍšwRp^µ‡œ+yž Ïá?·i +÷ðE3ÖÒ~¥oî±dîØ¨hnçw²°‹øþ7¼Ç‚üyÔ)kk¯×T$&.5söÙ:÷\òN¦t—pìæ¿»ßý»Ù ¨¿Ô7ê3õqØ¢øv üP_GÆ?ó>>v¿ûMýs¬uþìóÛçа‹uÔKJAÆñö|Â+¨™hBÌs¢31Æh$>AAD\èB7ºÑ+O7®¼„_ë¿HSàâG¦{ºjª&ÝB¨„?Kˆ×UYA&ËÉýEØ8CÕÅÙØîWœÌ±ùô¹™Ë¿Š†ôeC:RK']ir¯–ä5¶¾êr›:ÏYC3Ñ&!;r,Wr+Ïò*÷2a­=k¹‘1õ·±.#ÖDâz¬ñ¬k­ÿMØõ˜:^äIÎeÈú>k¶É5Jâmþˆ^îÈ3!¾‹œ^JÔ“;V_Iî’±éÓ[ë‚ÞJêˆïôTö‰·ç˜‚úÏäQÞåÚw‰‹=|ȧ<È!}“zò$_œßJêµþäŒÿÛWsó2•oYÐÜ'ä´¾,6}G¶/üÚÞ(ˆi¡f{"¾‹K¹àÿÙƒí“<Ìöfu¤{<åϹ?sþ\üw.ýùõcûN,3_£7ÛMêôuøïÅoüÐ:Câ°‹}Ô9NC1ÆqÈè9'áƒ$$,ÊFHòBö- ±  ¨B¢ ¡âTôto”¿%Ë$?%¶ŸgÆ~ösÎeÝÀ|ÀÚ¹@&Ë =·€<|Ûg#ùH°ÿ ñýllƒø-ö±Œ}þUÐ`Þ›b§eJÿä™sТv‹Qbv–ÿ;ë‹ïFÎý½£îeü||ßýw#Ç|[£­[¹tƒóUt£¿7áw&ÿ N@¬°‹mÔI/CQàÓ¦±åøƒ†;µªèÜ*m EÌ’"!„°!ba!6vb+,%l½_î{’ωœsÏüécÆMlÌá–’„ä)Rž† ˆ˜÷ÉÏg·í'ßY¨Ažá ΡκH â©qmì:žˆk˜TrŽiÈC *ЀÀ1ìÁc+²­¤sÐf*k)P‘J$e3,“1V`cv8Ç|À;\²M›é-rC‰y™Ö˜6i6T™¬«ªâŸ‡µ†:ëuÜö»Â¹f•ÕXŸwöQÆj1ö3¸‡~oqý-®«߯$pÉÒBÙ>ë»ì+wåNáˆc7Õ|uWUE‘y)ß„WŒgLê'6ÐèÛ—Áü?1ˆ6»Ü9« Ƈ°Íý–øË<߂ڧ2ç¬pÿ1&îem’7øþ‚a”Ÿp/§L|ï3”e™;+wXÞ£¾×ö „¬³}%ÿßûsß}/îûqß—nçsîÏå>Mü~{\shþþFHÿ/Ò¿Ef©°‹mÔ¿NTAÇñ³l „`|©è(è‰@|:k#v¹,fAþ¬+ »,Aù«!˜B 44+ _@KH,´“ïäþN2¹Ù⓽3wfÎ9sgÖÌF-5’‘í–"ŒË˜xÿ+Ì¢¬vA²ë4?ÑÜBFQ|^x~‰%ãÖ,—ã÷ +Ëz—D<¿ I¢XcѺžœO¢9“‘r Óªu‹x‡=|Æ>¶QG3˜C ©inP‘ùȬæ,`ﱉøMýOÀsîÒw¨uíJh¿Ñ:3Z3ÄÝÂNµŸaý&Ö•÷ÞbE5TTÇŠêl*΂ú=ç׊WÓû†ÆzÍžWSc¼nŸSWøG]ƒB?í_øŠìâŒþ.s[¿wx¤½oª†CÕùIýþ]ʵªêÑ·š‹„ö²âýMµõšu<6ë~nöà™Yg·Yþ)±x } ã/ñ78Ç7M—ð-ü¾ø¨*·/¬×C,Æ·¯:ò'ô}çÝOÕ7eé¹/YzfKêóuÃs8»ÙóøÝ(Ih·º~¼í÷%{Š-ÆÄ÷*ʶôœ]ã?þhO*ÿ?¼øÿbâS殟°‹…ÔK/œQÇñ¿©Öâ5tá=XXK¼‰ˆKw¦3ŒaF£JÜ-,h*,¤•¸'ÝhHl-$6"} ýžú89!ŸÌã<çù_Î…™õ˜Yw Kºßï‰ôJ?†‘Ç4²óñ;">®çý$á¼AŒc ·xÀOÖœ¾È€ô©NŸïµ~Ã~ÒÇ”aEF}ÏbÛØÁLhNVµºy_õ.¯ž¦PÄ$rš›Õ·3XÆ:Vðˬ¢‡f)jIµòÜÆøJÁ”dZqrú-c׸åê.©¶å[žé]Aµ$ú-ªîIÅuÏóªsSß/aQãóªßÇœ úN´&.ï9ý|Ä ½ñ}Š:Sü=Ä»'œà¿ñÈxˆ[áöé¯rLI¢~ç_ÔK^¹Ëª§¬^>k¿2ꫨžœ{ò\™Uþ1«aÿkÙÿªvês{R§9‰âíágÏ{fûø®õ-(ÏHËŸ#·NÇÄk1û€*Ö©º™œäiP®Vwþü¹vgÕÝ·1Õ<¡göýyïn:ß·ø~¾÷>¾¿qŽõêöü‡=Ÿ½K­×¨¾é´—ÿ‘ÿ1ÿlÚ£ °‹…”Ë.CQ†×ik&F‰Ëóx§.U½P½ÐjÑK¢¥i HDâ–‘˜!fˆ‘x ÿÒ';;ƒ/眵÷^ÿ¿ÖÞûˆHLDà“iâ;¸ñ€ÌEéæI€,XË`ÞÊ=é0EzåŸ!¶Þ,ˆƒ"8' R3~”E-ßé–®™%&oŒÄI‚úÖ]-° `¤­yyP5°É~éÚ(ƒXaÿ²ŒµÁ18dÞSo\‰„0/„>{êùš½ÉRGýlPO}øT_—à |‚G°Çùº~‹ßªw:Ì‘§?“§då-°¦3ð@.¸¶É}ªÓ›ÊVÝ Æ±ÞDMý"aœÈøÂ;zÆØ(}¿‚'pÞãùÝíÍo¾U‹{â’ç4髯¹iîUŠïiƫݚ¼xA¯"w"}Cð6Ø6ýT¬ÕXÓ>Ÿ-ö Èý]"F+Á³ :Ø[o :·ÐçйAìc/웞?s®õüë}KZu'qsGÌù,ÜûæÞóÿÆ{ýŒ®¹‡Izßa/2ô­kôàþlüv°‹mÓÍ.CAðÓJ»ð–6;/`ã!<…ªª¶´Tõ¶¥”ÖW$*¾_+K6‚ÄΖxÿ“üOœ4wñË™;sfæÌŒˆÌŠHÆ:$ÉꣴmòP‚"ËEÖµœucFÈêOM8º¶”kÏ2v*09×_ÿORž´œ!‹gü~S!Òdñ3œÏÇ×=Wa`‘_[_¦Ù/€%hAƒû(qlq¶kœ#x€¸‡ú‰ ÂÜ@ÚNܼ¯ Û° œoÚpt 룖ÙGã]p¾Æ-‘í·Nº§=x†_øaù˜s·HçÙ‚M®¥É¶8ç¾0WŸH¬[$޼Å㨋D‡ðo}ÞážàŒ9:…C®½ÊµÖÜ×ÊËoÍå£Íµ”yVFÏLßÈû¯Á-ÖÑ È]ä †ÑöÉ\ûÌMƒgigkw¢ÀØ*ðŸöÝg.›è5|A õ~îYãèÝÓ»hw;çÖ¬q¦äÿ¾Û=»ãÊÞ{Ø;gï5íæH:ÖÏÞ­¶N•e“pó&BêÉ?ÿ'̰‹}ÓÍ.QðƒòÖ<†ïà1Œê´”jkZ5­ß4B­‰vb!ÁÖʆW ñ¿ñ?ÉÉdtñËÌÜ{Ï9÷Þ¹WDæD$ 1iÒïñ׿C(Â4LñéäL¬3Fú­ù4¾@y˜d.ŸÏœgûm{ÙÌKóèX·Þ Ùõf:ð)ËZ§È: hÂkëüg`–ã\û<Ô`–«í‹Tcß\Ã;|œœëHW +ð*÷°ÎúNÛpp°iâ/áZœ»Ö9nŸcw¡Î¼Ù±!×߆Gø¢'8‚5X¥ˆ¹[†«q XË H÷ˆHjT¤÷E¤oOtõô£ï/ßp·œ›9ö¸>7¯ +T9ß%îe{Þà¼uŸÜ·ˆíeþ+å¾KòwOÆDÜ7·Ög¸‚ÖÒÿYfL…1UæqgAÏD‰óÕ³ ýUî™û×X÷ ãý‡ç¡ÉÜö~ä™·hÚõœÛ{üßùÖûžtÏ“âô¾fcuT§qö>)㙺^Âwú X·W°‹uÔÉJAà +.GßB¼ùl&™¤uLÌ6Ñ$C4‹DÁ݃E!("Š ˆËÁƒâYŸÄ¿É_Ð6ãá»§ªº«íŽˆE$G–(d)À·°"ã:ËÌ5Ì·2´@šë޳ÎúZ/¢’WW÷ºè1^~ZPãÞ +2^÷g9‡öF®SæZ=ªs¿!i\•ý¬A dã+ܧýÖlÃ>\Á;|Á'<¼Ñ3Ü´Ùs‹uváà‚ã-؃Ú>scöÒà¸Ëo]Ö\å~µ‡˜qmönë_ ¼Â5×Ò:Ö&ãÎ`çüÛöù-’ÂÙM̉L¡÷éy‘É{Œg1z©ÄÜÁÒ1 á”çó,#îWïAŸ½x6M§—&óêT¦é¸BÚ¿Öìp*éÿSé]öëÖXÇžÝ:sõNõx>ð#ã;0äZ‘ü}sªèÌù÷ÙÞa{ßÝ÷¢óî;÷åòLÂØÖÈ8üwx1Zÿ¿ïú»8ãô/Y,Ű‹uÔË.AàêÈ,l$<›— ›¾M›Ö3Í Æe\"q‰B"ˆ 6ÖbeÇ^¬<€ÿðŸ¤Tf_fªúÔ©SÕUmŒIŒ1Sæï7‡&eB@=ŒIaª1ß$ù4N:Ön1W f)gÞÄÊ-b +)rÆ/À"4X[F©56fî^ùuݺí›qžÖÙ¢‚ùRÒ¸:ãdo—`…š¬qJèÂ1\À9]üã Â`^o}ßp‡°Û´{pOp G° ë°KÒî°–6÷Í®SŸµø¬dÛ±ÊÚ¥Ž3Ö-ëØáJj;…gxG¸a_XÓ¥1Ø‹ +ÖUùÄäöÞ`ˆëÜgž.ë—5]qü k‘½œg½z:¬Eæ_³ö]÷^5øNENn»°ÞWɹêV\áäÕ³\§ÂŠ‘:–Y£ŒÍsX÷0Œàÿ;û$OqUóÿüö;ËBw=—£ç;è#´ÆÆ—XíÈÊã[Ü{ã;4¿>w¿z×ôŽÿ~7~´‹i°‹uÔKNÂPàÓj¹·âVTZWA +еj|EF&‰qèœ7àÔÄø_ùO<¹)ƒ/mïóœöÜŠHADbH`Ná6¡k´2CÄqUÒ9«3øë¨HeªÓºGÇùmºN‰ó[Ì¡aÖ,›9ºG“¹WL_Ŭ™íÞ5Ò8[”˜>ËűmèÀ6u¨ 8‡Wx§¸†{ø ÎDBÄ0·€ëÏ.–¸ƒ1<Ó±í &p »Ðçu28äuĘzäî÷8.•i} 97¥‘‘rO­!çÀÛçá ~LŽ“iÞuø)2ø–!Þ]°„¾7æ”™½3î5æû¹a£OšKÆ<:üyæ{YZ+ ¿u—Ú¯}nœ«½-êqŒÒZiz÷Z+.¶ä½Ë¢í–cÝ9sõ[‘ÿº.y´VcÒº®3ÖÛ\_!G䉹nÞY´çDÏwlÖvçÞÿoøÿû™x«Ü÷/®_Ϧ%¹°‹mÔË2Aà“ðÁÎÒByoƒd"7¹MRAÄ¥*nE¹,U”‡°µ°µP…¥…… ÿá?U'Íâ«™>Ýçt÷ÌôˆHJD"¨@Žá–!ó4°xšã +d9s¥Éò-ž¡-ųdíCƒbjòù_Á‹Hb&pÿgpö˜Hr\dÏt×>bÏè{eÞ Ü“ælÀ6À>lAZÐaßéom9‡ô`•ãlÌûv ÏØ!œðª}›°Î9½ëtÙ¯5ná >àëÖuÜÁö„õ$‘›ÄûJè3|‚ ΫuÖ¨Ëú»Ü߀{ô:nÏmæ­ÀÅÌkñÞ¾ Uã;k²¯Ê~Swël»¹böÕƒzEGë]Â'ö?Ó¸ç³Ö;yù{^ÂïÜâöÝêw_æ<Óó’’áó£þ;§‘ ŸC;û7ÞD®¶žÿðÿaÿ«ïÛi·Þ×ÿ³¾oí*Ã÷°‹mÓÍ.QðÓØØ{‰x¯¢Ó™~ë¨N)5UJERâc!V6>"V6+€ÿÿIO&³ø%sï¹÷œ{ïÜ+"žˆøP…ô©ä(käR yæóHãÇIsûœS"k;?`‘ë­A…ó‚D*•9¾`Öè„Ücê¦ßåŒ`Ìü²ÉYeÝŠ™§ñ…¤íy +Y¯ÎúÊý&܈d&¹2³hÃ\ =¿"c“ð3hß!ö +çp t1¬@ÖØnÓÃ#¼ÁÓ Ù…ǯàaß÷ðÌZW¬çÆnq~ócZ….lÃ)ë½À-×¾æúľ¦`ß_3`¾sµ˜1—û€ûÒµö̾—yªÅÿÜLü‹ˆñ>ó ùo¢Äÿ\bN_7š\k—g©bÖ˜ÃÞ)ÍÝàYº}¿Ã\sMîžåeôô^ë“ѽӻ›vÿõy†{oúÎ|Ò7ï'âöíë»Ì§Ä5w6AûulZ²Nî^Ó¡}°‹mÔÏ/Aðá"qô¿ø;üŽ.Žm·Ýní¶»­¶J‹UBS4âW\D"ˆ““Aâ wßI¾/ž‰Ã'»;óæ½ÙÙ™5Æd1  ó@NÉ(¹äÁg›/Kn‰|¶Ùø"ë¼/ªŸyB*1ÞW}Ç$©~Yƒ^%GȾ˜"š#é«R…Êθ +k6¡u²õÖáÖ˜‘1˜i<¿Ã> àmSðmÌè,Là~í7°Ã˜3êC‡kÔ`­%e †ðŸ¼^3ϤЅì1vȹØz¨=Žë¼ÀÂã›r«°Å>wÊy³æ&ëöØvðW|›kÚλtØgsÁ ¯Û̹¢b[\k±È¶¦ó-ìú\³v¬¾g]«™ßý”°­Í<}®í3ÜÃ.ëÚ¡"{DöGW}×”u Ù·²GÎ¥Êx}Þ4÷œÊ OÉ™—g÷ g[æ#ã%Nrgžª§ó¹ÿ?ÿœ/ã9†°‹ÔÍ.Qð;ÁÂÂ+Ø{/etÚaªcZ­*ª” QÂBˆˆØHØñ^ÀÿÄÿÄé$‹_fî{Ϲ=uÎeœsY !eȧÉߌÑ'0ý‰=Ó¤ãÃ?¤ã)ù–‡*,Â$Œ©så}"æŒ8§%*PÞ¬'d_ *R‰9k”˜8%Žm@êl—M>+á¸%ƪrN áÕ9oðþX}xAÿ ŒÁÚÏp«´ ;Ð…mÆl¤4¯Ëøoð=Ø‚uæÜ`<É{B½Ÿ±Þ( áå®ánàîàœñÖ¸¦ xb®SØ5ùd=Ë|v˜SrÁ1÷¢ëéðìô emÆÙãv9¾Íï-3§–R§*ïfŽk¾‚wø€[ž¯Þ»˜%é‹yßÚ®q?zgã€={ò»ü‚#æ,eŠ)¡ +ãÊ\ù=ÛúºŠ8_sÏSìúë gÈ\©O­çt½fRt^à~ëßwýõmÿ?²&‡¥ùlMK{â?¾R¸t€°‹…ÔË.ÃAÇñA"ñöžÆsx´ÕÖ¥Z´Z—ÞPE\q !DXYXXˆ•—°¿Ã÷$ÿü±ødþ3sæÌ¥3 !¤CYÉȈ$03c1©ð;>Í·IÂóxÝòÊX,>>>.“&GE²(EÚ²°ïœŒ3§•ÓÄob^&#ãR‘ÜÖ>… +2Ë|‹|Ok}e©J]¤ÄøILDXüq³¬¿L½);r)²Æ^+Ì»*{r*‡²%-©aI¶åZne_–Éíªœ…åÚ•#rn«EßmsÆz®ä%„Žné“Õßå†5?É›¼Ê#óÛ~Îiÿ’O¹§½ÍújìÏ÷àó’þ䄲Í>üÌëäYÇ +{lDbš”óœ»+Ã‹ÆØ¼wòÀy7è/ÀïHß8O½È^ìž=ëŒúCè¡K÷°«Wõcµ°N›ËïXžßï‹çµÒî²ÝW3Ú¬¿ÄÞlî +9ü øûv6ÖÞgô=Gßk"ÆÇ%‰÷ÿ÷þ¿Þs"6_ôM[}ð?ã¾j‘[´°‹mÔ¹NÃ@àáz*j:^ƒ'à-¨ ‰ã˜$8'˜„rpˆ£H$h§„„„DIÇCÐ Ä¿â_e´¢ø”x<³³»öZD–DdÒàC•8Å óÈBòª>F¶Îæg™—“¿ž>%)áð(áôµ½ËPƒ* T;¯ ”`^à¾àêjî¶>Í1BGž=ס¯sdçÓ  +çr¼´2ßä¬ÐǬS“"æ•)rúlÁ¦ªëÀ ¼Á;×y-jC¶yÝTñ6ãÆÀ1Üpÿ>DFf`0ØÜÃÜÁ+™ÿz‚OäOÃ$ï÷`—snq=œ_ãÃÜÂ<Ã%rÝ5²{fÇjq¬H=³™Ü*÷ÕXeNϡȽ6ãï±W—y%õÌ4/ð·ÂÚ>×|%2†w`bVdsCl +÷NÙ3Ïw¢@æÚ¼3 +ÕœŠŒ™3Èðü–¸»öˆuʳÌyóIÇ÷SЉé3nû¤œz›ï;5–ýÎ,ûýˆËðÜû¿{PEI°‹mÔÍ.QðÓÒ••…¥‡ð"vÞ5m‡NÛI[ý.­ªï„"ˆ¥DˆH¬<VÀÿ$ÿ“^‹_fîÇ9w#"ÉÂ"¤! ³Ž$ÍC +|Î ¡ -h@Ì‘ÅyÌ›‡"`}iæýGnÛb|®2_–}nÑ_[‘Ø8à™cšãv8/pâmå:· +M¨±#½¯p¬Éû"ãæ´¼¡óŒ%æl°–ߦ–³–­¡í:éø2÷¨¶àžà nà6há€méÂ:l3^m’λ€øBí&EâˆîjÿDŸîáƒëžÁ\³}—ðߘ; Üßñ¹öH×ìÃ*ŸCãïá^àk¼rOûÐaݬU—4OÏ©½³µY» +Õ§û]sêmµ¶<%ç}U5û¬ãÇË|'ÇÜó©ÈдHb5›BÍ&Ð7ʺ´dð­ZþßI–ì›)s®öéòyÍ1ÎÝcMþ~×SàUϼ‘¡EÇS΋wϸ­“‰ÄÛüt$ÆØf†¢ÿ¤ μÿ ¿@™°‹uÔÉJAàŠÆCÎO>€ïà iM&Ñ,fsKbÔwÄí"T½yóàAÁ¼ ‚‡<‚øù‹4ƒ>z¦»ºº§§fDdZDâÒk£¦ˆÆ¤`rP„E¨Á2¤!æÄGä¡Ì¹ž³ž?Þîcdý¶ÏÄ?âäqŸºŸ,ÌÃ&¼ˆ&àÎ ˆ¾=î+õG¾Í:ÒÌWàsdxv&9æsYŒåË8±9æZ€Z¢ +Ôa•ý%Æ–©À>[ƒuhÀÃÜÀB“Zp +mØåœ&c´ïˆ×-'Vs=áÌFDpü a,ÔzFÎ=0̵ö}Nàà¾à :pÀõÚ\k 68ïÞ¡Kßð pÉç´ÚsÕ˜CÏDë³He'¦ìSa¾sž]ý6·DöN |G ÆwØV9fïTÇïq6ã8£1‘ÁQ´z¯9_a›qV7¶ŽÖ˜ÖšÖ‹ÖsRzõ—e¿öi­Ïp,ÍyUÖA{Ð9 ¶y>‹ûýû®’ó˜Ûÿ=[l\úÿŠI¶6fsì?ó ûD|ñSd÷Qé×Þ/%£øË°‹mÔK/CAðsIÄFv¶¾‚ø¾+[ªú.Õ–VõåQ¯hh;B<"ÄJˆÇÆV"‰á?ñ?éÉÅ/73sæÌÜsg®ˆ„D$,Ï 1eD`2£*°€i3'$íÜIÆ•˜#nÖôÇÛ5¨áæÅ(â&›…9ÈBžD¼>‡´¿` ÒÜ_Ì'N J1§æM³&*ã£uú/.g,A–¡Ì¶Sî½Î7^¤<,Â*lÁ6lò¹{Ђ}hÀ:¹þC8€¦‰?‚¸€¸¥;¸‡ÔmH¤5骉ôDý"Ý"à "æŠ9Ôd¾7ø†Oxkî¡Å5OÙv{ÙàÞ^‘³FaÐ%ïðÈœ-¾o…´.ֲʶ=§Söy,±Î®.gpÌ}Ô8V`­óæ›Íó¹ºÃ\ò[¸±,óŒý£Væzchÿð}ËŒKó\è:.‡;cî\ê™×3˜`_Ôôg¹ö.׫±/Á˜”©½öÎh~=§zÇìý´sô1Á§Žë?à¿ÿˆí÷k{’‚&o”µHþ˹r°‹…ÔË/QðC"XXØH,Eü$þ$Š>ô]Eõ¡(¥‰Tš"%,+BB,HØÚZbÉßá;qM"¿ÌÌ{¿;sæÞ‘qù6F£d×z/Ó0Ãcœ¢† ²¼I‡ŽŸ… ÇÀGÖ߯Û?™Ÿ„ÄœùBÌ ’w\Ü—…:ÜÁ+‘ö´ÝãÞúœÂ>Ô`öà +ž™¡ÇKgÞC8ç¸:ë Nà ™KÈ.œ£í®™Ù¤:ß5˺ͳ.¶eØf¬nZ‹EÖªÄyk¬E…myfä(Ãïlß°Ì:6ù.E®…ÙÚÓïs 7pÛÌÓõ`{(ÎlË×ñ¶.ò{­ÛšÔó稲n >»>‡í“03Óò³ÿÜýâg?Ûk*Â|ï¾¶kÝ»îÂçaýíú¯~ÿõ·ÿÏW>ÚNU°‹…ÔK/CAð¹•H|;ßÁ‡²Jé‹V[-·«UJh‚xm¼"â V؈­ø!þ‡ÿIO„XüÒ;÷ž™9sf¦Î¹÷­ŸúHÛò-c…¶Å0E`"¿ØQHóYÞ…Ic†È¶“P€*g)’\¢dûEù-Åy3P†X‡%(ñ[ÜŒeÛÒµ&Hëå³¶}˜â0¾‡Žð,5~„+¸5Ža‘kžä®»föD÷¥Ìzè÷:k'¦ŽUÖß7ý}î_ŽJ¬}“ý +Üã åÌ>Ö8~ÕÄÉÕs%ÏiÞM½gzFõLÆçó|ì±æÇO¸ÖÙÕû—p­;kï]Œ9è9Mr.½×ú?¡m¹»ö"üƒÆkû¯¸ÿâõÿç«NŸº^°‹mÔK/CAði#"ai';;k_Á·ÑêûáQ´h{)­Ö+¡„ñHˆH$ÂÎk!숈OâøŸdrbñKîÌœ93÷ÌÜ뜋»?1ˆÂ¨!ý ÈRŠm7F’Òô_¼äÌPÊ“ƒX…:TØsP`LÆË¯su¿y’Ø"ç‰)ög 雀I÷úsl8? ÝO6` V8¦û•øÌsl v`öaZŒ/’Ä—/y·iµ¨q¬Å¾&Û5jržŒ/3wÀµŸ À0t¡ý×ðöˆsáˆsØWçtãyý_Ð}p€Ô(\Åó=ôcî;<œóÚp·ðÀ±S8‚c>_Àë°É=_!g/|cW@MCrÆŸ$yžànXKy÷%Ö¦ÍÜ—pÈZTYÏEÆÔ9GÕ¼óªP™g¦µ x–Ó4Ës’¾Þ‚7¦÷­ì)1Fî’½»zÇtLûõ¦ ‰És½:ë×àön缜ò=ê÷™24>iØïZÈ¿ä‘ÿ_ñã¢F„´móE¹Öï¾ù¡¼?°‹]ÔË.CQà…„„70—ð⌌y‰­j«E«7—R©¢.%QqI‰„¸ML$ÄL„`ä $†þÿŠe¾œ®½×ZgŸÓ½ˆ„ä×`Àà\˜†«gØË {‚dó#0B#Y˜ƒ<a‰ci渺imÌ3F õhÞ8$Ù{ÂÌ'çyÿmZåš–¡k°ó\ã<ç`á +nà ö`Jì¥Ì@µ%*²§öÛâ:*Œ© +»œ/›µ¸ñ7‘š.è‡VÄïp /ˆ{EêúD°†FÔ4öˆÔˆÔ>a®Ú : 5_ðp—pÌû®s]U>§³Cî÷œÓkÜûÛ‡gôï†W8¡fŒß²ÿ)\Æe“Ï\fß{øF~®ŸìW0ïSßeÁ˜…i˜4¦X£ÿa‘ÿQ†{$Å«¿·RÌɲOŽ2&ßî÷(¹8.{ZÇuoF=ºïÝþL›{%åÿžŽ™¾®.,çÄ?wQ“cé¹¶çÓÅzŽõ\ûßu>à$ýïƒíüû€²}°‹uÔ»JA೦H‘Â7°³ô-KÁw°ÔÜÌM£h¼„E#ï + +â¥/………"‚•¥½¥…ÿ°ÿÁã ÅÇîÎÌ93;{vD$.¡‘ŒRR„´'i$HóÚ¸¤É§ý)#Í™…<ŒÃ$”¡«Ð‚*Ûs”§,¹¶ã-§ýEæ)²¿Ä¹¦y-ÒÌAŽáàŒëY¢×6‹°°Í8óN÷° /®bâ¨Ê«¶/3ïíð¹Éõ°Ýå_ƒ:m‹HÐ}Ð>Ë%<âyP$‚w~ˆÄúa÷ÈéBö5À· † ãŸà®à®á”s·¹žuÎß"×¶‡|ÿOø‚W8ç>í¼…‚Dqÿ 'Œwï»Ïñ-¾w›ë@¾ †¡›ë«óûUÍ^èÞÖÈÝÏrÿ+¦¯ÁÜm~ÏÖ‰3!ax_25Sö¸6Wc®ö2òSó*ãÑZökÛökýÛqy/ÖÖΛCãÝü)CÿC—öÚ²Œ±ÿ½žqgIÜŒ÷ÏÿœQ¿Î“o¶`œ°‹}ÔË.CAÇñ©hÄÒ3Ø{ vÞÀÖέÕjéÍ][­kƒÖ%"""Š $±´ ^ÁZâ;ü&Æ ŸœœÿÌÿ?×sŒ1ó¥ÿE {ì{ƒW×åºvWϵÇâ’Ĉ'…1Ìcy¤½y¸~I±ý3Ç4JÊõòRz·}²ŠÛœ&0…IOUÔñ€G\b³˜ÑX“Ê/` û8Æîñ‚g\c,aAul^Ykó”4FAý7°'Û¨aUsÜñâv‹²‰+¼áOZCýëj1¦½ ·ÓÔ ú‡;‰uÑÖæêCýo´.›{ˆìjì]oŸ[š×ªæhÛ/¨Ñ ö=ÄZBmÄ^q«}¹»gçr¤z5­©*nͶíŽZè@7Z•[ÖùçµUQû_Ñþ—t³ÚóÕ=Á©æ^Öùæt_Ræû¾ÙXV2^ÌÅÝݳßÍP@"ÀÝåàÝöÛÝØ®_úYOÊüü~;þoß¡+ˆ%•ã¾k›ýGDý\à?'øŸ‰j~þ/>^ÒV°‹}Ô¹N[AÆñ1(¢B4<OÄ+¤MÍj³Û˜-!¬̾„-„ˆ""Q„„¨©(()èxþ#,DñÓÜ;÷Ì93sïÜBk(i“vñ÷It!,0„/FNÏSèò|QG™¤ø¾”jeЇ^t«t5Ó.ÎÚIë¹Å-à®p%äÝØ¼dU+£˜ESø,ÓXWÎßø¡¸íIdµG±ŒCœâØûƒ¬hŽ·ˆ‚j[åŽ +Êió˜Ä¢‘¸'\âDã•{»8™Ö}àæ¸¢ç×äi"wj¹æ½%êñAª‰yÀ_ßQî ·Ö¢önIëµ5üÃ#ãkP§ß1ޝ¡ôÍŽ8ãÊç÷° EÕ´wu#ÇêS.; 9}7ÖæÃëo*«ëA±ÿ­š.ñ÷Ööi|NâYèqχ¥_âùÐ}îñÜ;_¡töRnN’,ûË×cß:çÝ®žÅÙÿ Eü(ÆûÿÐKÿ3{-1‰°‹mÔ9KAðIcao¡ŸÀÚÖʯ#Ys×DݘÄ#1ÉÆDC<ñ@ð@DA´,¬l m±òÿØÿÀ¸¦ø‘ÉìÌ›7ovG)Õ£<½ÔGA² q…1‡ +,ÃL acž?ž?®ßòõõCŒ1a€9D ÊgQ²™›Í~=OrÍÃ|+hƒ´O!ãks Y+ eØcþÊ'Óáþ‹—g<©K²”!aV¡Jó0Çe¶+Œ›ƒi(@‰f9.Ǹ“ƘÛú¿Ëñ5®½ 'pWðŠZ4CÚ¬Ñ:lrÌ;žµêH@ú~àíFh@ûná’.àÖ`…kÒ–±ß*Ÿ_{qoð]Ôʵ;™ßì³v5îË¥ã-qÝ=¸'x;Øe]³<·4ÏJ“:Îõwy¦yÆ—šÜÇW7uÎ<Ь·ÄVÞ;g;I ÊðÜDJý_£uHÿç§ÈaŸžç¹Ê^F”÷Ž&ùßa>¶!n0soK¾±°Oˆ,ŸÅÔß}è±õ¾óˆ‘‡Œ“û ›ô=$m}o˜÷’þî8Œœ°‹mÔÛ*DQÇñµÉ¸pãxïàÒ•g`ff0Nã4ÎŒ!‡(ãØHI”"Š’p!¥p%I.¹ñ]öoe7ÍÅgöÚ{þë¿Nÿ½1uÆW/!DЈ&4#Ž^Œ`“ØÅ%N°„NÅ7 ¬<¡y¤Qܽ‹·}£ÓŠIT\¿¨æÖ¦±ÛÕnÑ5‰ÜãU€>^5÷·CZS®Ìúëò¸xEüÜcƒ'!Ýè‘>L`ç8ÃF1¤ñ¦°ŒEÍ1£çSê3,¶ÖœV‘Uœý/¥Ü.vP×qÍ9¯yãïþš¼*Ð×cý^)Ï/°é\ñ¬ë5Ngó|Я%´¿ð†\!§zÈj?¶°¢5ω]û¾ÉQƒZ)ïUjŽåþœÿòî)oFû4¥\ØÇ¡æ~ƒWsNûkÏÆÖî€Îª? ¥}ÕÞ¹3sçf×ð„|ê|uÅØw£Cõája"0Þˆb3jÛÚK¿ö,Wëù÷¶Ž»gÛoþë6¡ÜiIi ]“Ònþk<èÛª1š +p±¶]è½sï^–=ûé ¦•””žÝ´þ'üÿaô?4Ún%öëÏÿݰ‹uÔËOSAÇñ)nM„¸`玸1ᯀ…ÿƒ \š(¥öiÖbÁP´¤šøJh +TVcˆƒ‰+7ºv†Ç¿~'7añ ÷Μ9çp¸ƒsîŠûoø†$ÒGwñ-¬â1FÇPÄ ØóUŒDÄÅ¿[­œê¥bâê-®þ²¸‰’úÊ}}~Ç6~â™Ö‹ê9)Lâ½s±SèÇy™ÇmÉ+w.¨ñŽØ^çÚˆ?qݼ³æ¾aŠ{…=özp]¼EE3œÃ”jY/ øŒbÏ¡]3·õG˜E9°„ç¨áî)ö5~sþ úÀz¬ z‹foo•û >àú}"–w 8s‰ßµœp–õ]üÀŠb+ª½¨¾,WC¹[òñhv‡ßP]‹ÙÀ&öÉ}Ôr;:ûUÅÚlþ²ß¡ýOXVïuõ] æuGf4oóPk%ÍÝÜBAlŽ/Ð úm¨~Y3.É´êþ–fYSûúÂ:–ßç)=›ŒdÝÑ÷=¥þígQëiÉŠ¿'þ%äºø»ã÷íÙŸÃбºþž[Þdp>Íãù{}®ˆ¯?pŒËrø?êEæÅ̰‹uÔËJUQÇñÿ±FÄjØÀO ø½@àG=^ÒÂëñŽ÷¼ßSD‰JyAˉ(b ‰¨‰¼ D„¾K 7|Ø{¯õ_ÿußfVbw¢ˆ!ŽJTééTˆ+«GýèF ªÕ®4$*î½ åR&>¯/ƒwx‹Z$1^BqI a hPy½ê𠋘×wRq.æS¥¾Z1ƒC³Hxµ%´k¾µoQŽ&ôâ±<d›=œÅK³¬ç”Ÿ )­Ó*qyhÃ^Qö“Ñø:Ö“;#î¨)~=xAÆ|Ôz4Þ.åÿŽSr&÷ý^Kø;¬XnþQÿYÙ°‹uÔËOSAÇñ)VülÝÊ–?‚=ÿ¬ÀZLy#ZAZ(B •WÀáY£‰(!µ!Ix¬$%°ä{èo’ëMX|r;3§çÌãÎuÎÕ¸¼Zy*u“¨øvš$ˆy">O¸} Î×lD+ZTãY fLí¸bú±„MLá:ÑQ|Å%αˆýÏjÔX»Ï‘ÄVôŸ$ð¤U§[¬ýݹHµsÄ•:WxÈïbúÏ0†AÍg‡¸T‚µEÊéû‰q cHu’Z›Õ¿UüªhÿU\¯¤ÕѼßcoRÿ€-üÆäðYñoµY|Á6V± \fóó<Â;Lã1}ÿðCrzf•Óê.cOì,®q…#|Ãfk57°\è 5/Û“9å´ùŸâFÏeíÃösBFuŽ/uvƒÏh¿:ˤÚ}ŠM¨/­øÍáŸtn–ÏÞ«.ÅNbWûlÏu­Ëj½@:¤Ùåß=—Z4nÚ5—_ìq¸+‘ +íEFñþ.ÄÝÿ÷Ä÷[^ÿŽûñð½ Þë­{\kMiŽ>>ø]',Gm@8¸® ¼h(ç}Üîª/”°‹uÔÉK[QÇñ›ÐtQܸsá_áÒ¿ÁUÿ—jL'T´jL´q¨cœg,‚⬨‚…èÆ…àßÐ]¿§ù]x}ÚŇ¼ûÞ¹çÜ1ιZWT'õÒ q‰½#{§¸ûOœI  )$Ñ(ÁÖnFF°ƒ<–E/2ØÃoç"•¨àùChCKHJïM;ºÐ‡~ô(ç0Æ1¦:_d+ÔøÆe £Õ<Û|5¾ ¬kL¿û‰çW}ŸÀ¤~­NZ5¾ãžØ2T¡TsõóìÕm^_ñM}0‡™@Þ)Ìkl<›XÆ4f±…«Æ.V¿¦>ó³8¯H >j8Ä5žPÀòmá/xÆNTÏòîX¾SœãL}ójTçVµ¯¿ªyúõœ[‡Q­SV{8©wíã€ÖÞÞå—Ö÷\ ÏŠöq]í¬Î@ Ç´â~hÌÛÚÛ¯NW<»ÆÎ?;ë­®xîüy³1-²Æå`½¢5°çϪoyü]ð|;©ï–¯[µü÷ð½ Þ夿aóšÓ\ÚñþŽ&Ü¿w6¡uîíÿH\µS’Ð{þÿñb¡œãþ¼ûô°‹uÔÉ.CqÇñ?k/ 6Þ€—ðO`¡í¥†RcPcƒ´SJ¹f’bˆ6켆…ßá{ãö¦ŸäÞó?ç?œûosqWªöìIkH"’'–åxhƒ™'Q¦&¬ÉH~0nñé–´lÊ©ìIŽØ´É—s R§ç'™>顾7ÂbÆûeX¦$#³¬1$°yóò©µj~Ù³»“G¹— ñe_NØsž=›)È"ó²æ*µ6Ϲ,Èû + Êç¶ñeY§7k²DÜde…ñuÆ×ØË±œÉl‡rìyz[ä\EÎbu·ò†\‘_àŒÄ®©µ½Ê»|È ó^²Ÿ›ÿY½­’&i–ZÖ²õÉÛâ|Ä|ΗãûeèÏ2ý˜¢×ã2Gÿ³äMÒÓ,ñ½\$w†úÞ3¼[Ý<}óÙ“Û]±û×íJïZï|[ÛÏÄo¿*ꫬ–iÔ{Œþ¥\é= æìqw2ØÓ(9ö»ùïw™„å S“¢&ʱ5Û#s˜Âÿ%¹)z’fo‰(WÅò¾Ãþjí°‹mÔÉ/CQÇñ[â?ð/øOü5†RóPJ©C[óCÅLŒ!ìL‘X‘;K6Øá{ô׸^,>y¯·ç{î¹÷=ç\…û«Lì>Œ*Oe ®Bc•^Lu@8Ç ^"R#~¬ŸßbjÑŒAœáùP×gdÐiÜ0^Èõ ;èA;:Љ.‰¡U9ë¤^c ¤1‹IÍÙ¥åsýÄ;^ð Ü6ç +ñJ½Î+A)÷×(RíœkÎ-Õòˆ;͓ѺGuÍݸì¹èõz3Ž”ú=¡ÚOTϺþ÷ŸKy9SÚ‡´j·žN+ÎÎI Å?kö;ª½+÷l¶'¡1ÖË9±ÎP±ö'ª\6WRµ&ôl¯·Ç–ÃÎSLóäÞ¯Üû ¨U-MªÍÆü÷ÎÆÜï»û”‹ÿ- +G\ýHªn/ øïy?ïOÜ7üWš°‹uÔÉOAÇñjá`"à w/\µ¨Ñs•Dï±wuh8ê‘P{Lãz±ÐØÛŒsÁs\9÷à)×c´Ob³8ÂRH¢ÝÀk £_í š»N¹XÜÎpƒO˜A¯â[Ð(-jëB&°ˆ46ðo0¢9mîQLes òQÀý9Þ¢mÊÙÖÚ#’ ­§]9Ù¸ï1™U[J,‡i¬â—r¤\­ÏG¼Sn j³øm|Vü7œ`[X×ûmÕ{'”CZ–U¿)ÕÃêò¿Y{!¡¶—EšgSudžà1~:—÷/ØsÖ”ñîV±vý®µ\óî!¸uÊsNë·½Ó^ †j;„qÕi\û°‹_Œó ¥ú¾öÓ+6ΤâýþΨÕ²+ô­4‰Ý¿rÙo>¡ûf´jo‡Ôÿó–dkã¾j|ûÞº•ËœbIë[ÔXÖ´ÖV_+þ;÷Ïáÿ×øÿ×ÿ5.÷_Ä]î\ðç@DÂÏQõIªÎýêoç!å.wžDþÃw÷¤î&µ°‹uÔÍKTaÇñsµÜ$Am—®ú;Zöo¸šG§F2G3ß_20´,¡‚©|CK¢U‚¶J3¨A¸)¿Çû{ðšµøpïóvνçyî5³köou¨G¤p5!ÌKknV|M j§%ó»ŽF¹‰[hF^qêê±}îvÌ¢=³ò*¼4+Û¢]Kÿ$:ч"FpMhÓØ]ÅÔ¼våÏK÷±FÌj\ÂyÚ èWœ‚b6i­k‘NÅöüÃÊå9»Ñ«û!L(ÇYÅ_ŨÖ{ŽÛzö.é– m‡(á=Öã¸6…Çx$žïæð?Äç.áµÆb /@mm›<#µŽÎq¿Å)%x¾i­S>_¿¨%õùØSõÃ.þû‚j}‘öwÌhÍ2}•øÂ>ÿf¿/s½Bö šÿoñ™±S\÷ñ oô^Eí__¢vÒ£}Ö™™ŒcGœÓ² +ø³ùÞýT-C ¿Þ“~ǬjQ´£óçZ¥Yí|‚·[´ïzž'x…çŸÉ6áœDgT¯Ú_ñ.®Et:®ça}íè»ÊIÈwC$|gáû ß_V}9;þ_ÿ”$Û­ ï\Ðú´üŸ¤þ#Ä;œw oÉã°‹mÔ;OTAÆñY¥A„B(¤ Pz:h()ø| +¬÷†r۰˲Ü\Y`¹¬&š! áj„D† DH „€V +*þÃ>' '¿œsfÞyç=gfŽ1&dò‚Ž0¢x刪݋K³xq¯ÍN\Ô'¢~׊ˆ£ÃÉçå÷êiSÌ{cyPoÌCú q}N›ý„7èÇú”»S×”ú3Áz”7¦8;vÿÈY‹—¨âyYô"©ØN+¿Õ¥œ}>vÞ·Ä8>ã”ÜOñ„û_ø¨šÒNýi±5c9U®Õ÷Ÿ\ÏPÊý>±„5¬â+~`G8Á!vñS±Ó² ø=ò•£ /P¢ZeËø¢g[ËLaEskŽUõOi­¾«ÝÖqEî +s€uõÿÁ%í 8gÝ+¹n &ÿýnòÿVýv¾oz÷]·sÎéÛŽè{zkÒ/iõM¨vÞ#P† æÛŠòûÎÌj2Úï$«9½ß¤Ö²ÛÜî¿^Í—Òs‡Ø½—ÔþÈ8ëïíÕ1Íéõóž:ðhD!m¹ :¿̶êI(»Ä|l›=‡-æî9öÎ}Ä'êôùÙv÷¿Ð¢Ü­æî¹ŽÜ3Ö¶ùÿGAµ…®8´*̰‹mÔÛK”AÇñçõJ½öVï…‚À¿À›¼ð¿Ù¶=dkí®yÚòÔ†‡Ü˜v€h©ÄDEÜ "…„. ‚ºêVßÙ÷74,]|˜}çyžgfÞY3»f±D ‰2”úýø¤\?Î?§Õ&ƒx¡pŒk³È¡7‚xiñõÜDe|7‹Þ™5ÝÇO\åù2ý (aÃD·¤ˆ!™À˜ÆÜÖ»;¸‡ +>✸´Ç ŸMj¾ë HQ +âû‡¥¤Ø.ï –ñ+޵ñ{Ïô®¬ÚîªPnÖksx$î÷<ªøJœt£‹göÅ>`;øŒ}|Ã.>á ãýtóm Kò5ü%^£×Z¯×Í+.Ï&VðJçð8 þsžKUskªí°­¹ûZÇoœ’·Së»SÍ[Z£ÛÏ×xÔU ÚÚÛ±†3q¦ôŽ5ÚÓxNÔ +ö¬é ȹÎtFþ,*ZÇ"*Ž?«qåpß@^߯ˆòùo¨_òê€iÅÕøIÕµ ³XÕ9ôâJ|Fõ3e£v4k_ÊÊã¾ýœø»·wÃõû»—•Æ{ïïw*xÞïŒbô‰çïq&ˆŸ¶ÿÿ/4þ%|þ 4Ãóe°‹uÔÉJ#AÇñêgð(Dæ)ŸÁ“à[¨Mר‰É·ÜqCÅ™ˆ¸Ë(®(‚ƒ;zqE¼‰zô[æ×Ø4xøÐµþÿUÝUmŒ)2)…bËK™Uøú>?¥Ø'è’ §¿T¹B¾ºW9*Ñ€a<ãä>'‡útâ7Zð QÔ Ê#ŒS«1uˆ£I1úÄæÑ¡xamD«âD3¢>wLúbgc7k³xdíYH§|†qô£[±5§IõVÍíA/ºTžÀ6žˆ•4ÊXÁ:NñNûdR~Á¶p‰œc #ÃBê:ò1  ËljÌ’r¬«þ“˜Ñšîð†WåØÔÞÿêýÎáŸÚ·ô¾Wõ<Á=n´ŸkÜêyæù6IO^kJ9vãËÒ·KBõA­wM{°k;bŸÙ –ÃÚœ\ÚöЮïÑ¡÷4­ñ6†{îbõ&uÆ¢ÊçžÛV-µš×& Í+ߤòŒè›bW8Öw¶{^Ä!ö5Çž{í½)÷ؼa•+LêÎÙ»Wò ÿ]vÛÝûjsDÌ×y­WÜÆÙ§÷>ûÿ îÿÀÿ_úü¯|$í¨š°‹uÔY+„QÇñƒ×àFÞ +åJÞŠ1ÃXÇÎXÇ–‘5{ÙE"²'ûš\H¹å·Êß™ùžzzÊŧÎùŸóœÿYcLIȇ-¥ÅQúÚƒ>ú„P"F‰'^&¥äsõ*„i³Ê©7HŸ¬Ê¾¬É–eQbÒ(ÕRIî +Ƭ£­^jéccÍÒ[n•N4Ñ¿†ø°LÉý#h`,Ç~•!aÞƒ²#¿Æ$e%˜Oæ=îé×ãÉß…Úbèe¼Ù’¹c_lýRã§É¦1É’ªržâr Wò˜èßO›RÖåZ}S-¹’©ø—œÊ±‘k›sXâ 6äPnåY^äOr&»|wL~;—{y` 78%Çû¶Éø+ìÙ‚ÌÉ,lû·æš#ßþÚ=kG'ë½`é*ÿÃö=‘7yg-Ã|eœiÖ:Ï™E9ïÊQʾ˜­Û{W…Zbn^î®E8ïirعvK‡ôËycž{1J¿~Ʋ÷µÜ£ar»·áÞcÑ?Ü[u\ܽ×ó97ñ{¿vî]ûÊ!ߘþÿLÀë®L'^°‹uÔÏNSAÇñ¹u­^€¸0–²rá °õ!ÜJhiii–p‹© 4¦‚0€ÐTMLý»1›°aƒÙhXcX¿§÷wÃDÓÅ'{朙¹sgêœs‘Û’”ñ>âü;’ìcâ–;‰iÌbEdV<«ç)±öPÐó¤¤Tcñ2*¨â^â_°‡Eä¼ñÍ òª/¨×|eÕ”´Æ¢Ö° þ9­ßæÛF qWµeÕ–¼õßÃàmìâй`!¨ ˆ½ÂcÙÀ:V5F +jûªb} ljü&Þâ”±ÉKütîÂ/°¶Ä0±Aú>âÞ£ƒ§ª7ÏðIÎÈ¿‚ë¸=»ù¬q^ëÝ^H[±Žú¿Rà*®á"±oèF{ÑkÿÀoü‰ÖÝ›ûÖÒôìjÿw°¯XKíçš‹3›¸…®Þw”ø“xïÑ—ÀY /¸©wÚš¾¯ýÖ±,¶ß÷ñÈË[Q_è}º¾[(vN*îü\ÏŠ–Áyå…š«¡qKê+)¯¬¼¢âKÊ«i>‹O{f$ã±{h÷ο·ñ}‹ïdZRâÇ ZûŒàíïÚ—œW“rÿßã¬~ãþxM–cÿ½ÿ¿¶:…°‹uÔ;O”AÆñó‚•¡4ØÛ™HGcAAE‚_@¾¬€ËevWX‘…•{‚¨ +ˆ& Xy)F + c $ØXcÿÏ›lBñË23gΜ™w3k¶sM’;ˆã[$qÖÛ†ô ‹4:Ñ®þ.ýíñw5–Á}ôªÝäJª/¥\}˜Ä[üÆVSîÅwóîI*hg‚úz¤W2âk á1žaLë„ã}ªý!æ±g]CêÛÂwÚuØÅ>èû‚e¼ÀKÍÁ8&0…Q †Œ(n›øCîwfåœï¥×¨2+ã<¢›Œ}Â{|1¶¡µcë8 ö*jq õ¨ÆTà21ÁlG¹|k`=[Å{¾6õE·5ýÛgÍÿ…ŒñcÇÊõ&¨gQ|oÏ1‹}k{¥=ü'Çö܈Ÿì÷íú¿*~Z÷Åsl3vKú~×™kOuž~®ƒÐ÷ÕwŸSÌ´¾Ÿ{^1Ý‹)ýzÿåÈYñ^§¥ðDFÛ¯yq 9+ÞͬîYF¹üžw«¯Ô”µâp©ñ;ôw¿?—”¸Ý.¥ýñ[õs=áü¨/b/Q%í%Õ› ÖOí®’ÚÒ:«¼öèoýìÿÎ)6È2¥°‹uÓÉJ\AÆñªn7.Vºq—p«ÏàˆALÔØ­m·m´’ˆJB¢†8ãŒ8¢"¢& +uaˆ Ÿ@\%Ùß…âb?î½UuÏ©á”1¦ÞdÕ…¼–p{à4Hø»ñ?âhAZÜ{3š$&qµ·JBíAž ^0¾Yñz0…ï8ÆÞ*^ÜË“¤ß)on©g¸¹´!ƒ.|ŒÚÓÊ×!Ä–16ŠÏiŽ·¼W)ÿÙSÒ~€%,cãZ˨ £½z† hÌpM\Î%²iLôÊyϧí}8ÇO9–m0Þü`\jAn;tã%8»ŠET#‡~áZdžrü¡¿B1³s25Ö9Ã%~ãWØñÍ¢öÃÅZÓºf1­§Ë³";x$>눖€s‹pNÖÕî=ö‹y˜Iìfçm+A=ÙRíÁGí±Ó-î<ç4߬+Îg i¬«Ã÷:‹AO¿×ß©Éhì¸óÏC1ï7Z×€7‡^íñêïâ÷)¥>÷ß'åíP_Pûiåw\½&Õ¾'þŒyýáûçÆµkTçì¹Íå›Zyº71/2$¡¾¤Ö÷Õdë¼KíîÜ^ýgÚ`m°‹ÔÍKTQÇñ玶‘ÿ‡–ù¸¤E»Zˆ+éh“šcêLÖhMš–YJXù†QDÅ 1½ …EJ/Ú&ÐE-\ä&ýžîïÀá‚àâÃ=ç9gžsî¹Ï3k³XkB‹$ã^»œ•´œÛ§®@gðûŒt(îû~ܯëóøx§req¸ƒz”¯#Èw>ÁÇÝÜœô$öétk ¸ˆ^ÉJNýKÒ‡x‚¿fÑI4ã ý÷øE;o–Z4«8Á³‘þqâßð ñÓ2ƒû¸ëÒstc(ây€sIÕðüSÄ·±‚ÏøpýU|ÄæÅˆGŸ°Œ˜%_{p˜v1ÞÁ¶ð z—XÃ?ÆkqœUTOì'Êxw ·-á­â¥xÿöJûÚĺ斵FIë<Û {ªÆ1ÚÄ¢#ñ™Ûw­ñ“˜ÃýÑLgì È],ÆßÓv”ë¹Îù–Åõ×(¨Šú†÷0‚˪7<^ó-K¹³ý‚CÄç5ßïcTýAí§ ZsµçjÔÕm^y‡5îÇ|í÷é·×ÔÎ*ž¼'áÝKãÉû—QŽ‘ø;F•¨RýÜTît°~·„÷/­ö‹ïî¸ÞÃÅÝΙ]K¸G°‹}ÔÍK”QÇñûÌ”m\´—­Ä?£Eî"7ýmÒLÇaÌ—23ß𔢠Å"T°BAM¥¤Mº#²…Pˆ+A‘¾Çùº>P‹s_Ͻç>÷N¡&œWýéqµ)9äÑ€úÿx uš“noœÆÄããºóù>ÏöP@«4Fñ¼¯ r,'Ö÷Mšëc=~ÜkŽØº¥ ÇzI¥”`ûÔ{CÈPÏ’Oæ>õ;´oãfñ“x£ú4Fñ#ÃkÌaSQ¿±õpHü2\¥üŸ°Œ5±úg|Å.vðñ×A>ÉÐl°ço!\(ÇMÊ'´Y¾QìU•-æŽS‚+”á#æñAãWð^ùÌißì)¹„Ûàþ%Uʃ³L®ãÙY†/X+ŸÒw·”³;û ?±…·:¿Ñ9ú™cC:Ç™*Æ8;“ +ÊÇÅo{ö¬¿CßÝï@§âÛZ›Ê×â÷ã™~Y3©ä\áâ]0.ËY$‹Êþ!÷$ôi/¶§.<ÅÝ9»—vg›ð(Åï±ßqÛ_7z4ßæÄo×߃×ýýy{ºžâÚÞ^éœ,·æð÷û[õwèkúÿHòh—‚Úï™?®æ­°‹…ÔËKVAÇñç¼µk㪕ë®Z´ÑÄ•´Hð Lëx×L"¤B¤ÒîFYø¢ð‚Ý(H»b.EÅuH ¿ç78¾`->Ì™Ëyæ™93ÇÌm¯3û(w¶H ÚЊæ8'þNÕƒ0.Õ˜&Iÿ3¾UBAèí]ÒQ¤E:ÔßNÕÛ¢øqì|äú¤—0Œi³$‡ržÿb ÔkÌrGq™ç%¡ý&ðOñ “j‹qÄ÷1Ú‹ëá^ôhíýŠÝ¥~¿«M¶{gãøáÿKÚÅçðÿMƒÛšW:°‹mÔËKVAÇñç¼ÇËJQp] ]»q'X;kå¶öÕ?àÝWßT’¼Aôºð‚]DJ‰$Ȳ‚Ò…$ŠŠB`¤m\´í;½¿¡apñ᜙yæ™Ë™9fÖlf­h“i•fiQ{»´E²Ò…Ž ®S:¢ú¸¿Ï·‡óód£|¾¿/‡q~>N7z1ˆûÃ=ÜEŸÚú½Ò£|îy;¨ï{'0Á8vÍ’« >i |€%Õ×áª(¯â¦ñ³xƒ/…XÛ²êÞa›~hÄ%ʧªŸÄ„LaŸñ +ââ^ªÍ=ñ_±†ï8&o‰Æ(GŠe¸‚ ³ sÏ\ç¾ÉúüÄoÞëÁü“·Z}í6ñ ¯1¯qß«nEk]Vy·'Sc–²)í)㦕fE·ÌŠpƒòMâþà2}~iÝZ>hŸCÚkÑ„jÊ{xÇxˆ'üwþ(Íb°‹uÔËKUQÇñu¼·B!ÂAˆ4‰¦Ú,‚è/¨‰3çMíj¦^óQ™HE>%-{(IH (EQR¡”B‰d&5)šX40ü.ÏoÃá`ƒÏÝû®ý8k?Î1³Z3;‹sâõF´£-h@½J׌KÚë¤6%Äs¨Q™K´Ÿ‘Gâ^oÅ &pWSyÕ'Æ%ó šÔ¿ïÌ¢JäQÅÿèE.à +zTvhüùç‘Oh‘Vñ±—Ñ…N\Lìá5 ã)ÏŒpüµWU^¾¾!ÜP>}¸±ïô/ŒÇÚ*æð“xˆ™xÞeùù‰5¼Ç La\唯¿Æ,àîa:íC)õ”E8ŠcØKì«æuŸñK¹•ãJˆmR–ü2ä“}‰ ꧈=Á._Â[,`ë´íÇi°ßÑ dˆÿ¦<ŒûfG@[{“™7ÛýǬè¸Yá_³=ä˜]¦í }ý®ýÀ¼Ö9?+ª€÷)¦äL"J#'»©=Ð9ŒáíÐ/eÚ;Ÿë‹Ú|O>(6‚ëº]:wÿß­3öó|Œ|Âs°&»«¿1ßIpƑ׫‰-+¯vÝÁ4Wý½ ïAúÞçÕ'Äò‰»ìãêþ#¼Oá»Ð$âãýÞwj­þ-hKÌ›þaÞôór;¨IØþl¤Ýc°‹uÔMKÔQÇñsGň\eE­Üô + µÒwØ&p%63Ú¤¢ib!Y&†FI’Ø%"’=-‚¢Ð…n +Ò*$Qò{ýÿ.]†\|¸ÿ¹wÎùŸ{æÞ1³Œ™]Š4!^ôá® ‡f\F'îà.®k=+!OFr’F£Æt´~QZÐ.ù(Ÿ¿—ø„EŒ¢Kµ4Kø~.š BÍ>nÛÌõ›¥J_ãs÷•Ïïõ†4ö C5µª® M:¤Sñ}ª·Wõ<€{ǼöñO1¬^ê½þ¹_1øAgÀïáÎóùæ0‰‡ªÝïë±zóËø… |Á ×;Ù·}Ð8£\~m!Éïªpù¼ÅxåÊ÷ïÿ «Š9‰Të¹+fEfÅoñ›çúdÎ!æ;Öð+ªyµS`ߎw¸ 8¦N ô×ÑÇyH‘wÓ¬”>¨dl0+¡EgY÷½ù‹¯ê×ó¤nw·ñœG—b~V}Tß½õ†îÈi;x¦µ)¼’'x ¸ðû{7uŽÂ]™NêqÅ [R¾GÿêÛëÁa”©'£:KñÙkøûáïl¸g…ç>¯ï„9Óņ¸løÿ )Šo‘¼òôj¯þì·i-¾ÿû)|_ú?#{ÿ»Õ‘5°‹ÔÍKTQÇñsG£\.ÚøXH¸éh´ Š­ÅœQgœi¨)©i5m*ó"_Ph¡ AVDAA/+qD/~Ÿ¹¿ —Q¡Å‡{ÏsÏësιι.çë–^$1ˆ® …¸Ø·<îà¾ê¤Bíƒz½’˜Æ¹(Á¸QÕËá&Џдâ)Åžâ^a×0 z‰=Æ‹‡ôá’æûË9/‹XD±)ikBYcZ9«qR’V9£>órYÏZÇ X·QÁ¸ž6ÿÇ2©xðm ÃÁ²??¯„-ïñ*f1¡zeµ{€9¼Àwlà,h,{®á‹¼Å +汊ŸŒÑîs•só/µUñZqËe+:ä$΂ùGN;×Іó¼?"Fï8m~c›÷ƒzÿ„o”[ÀYðîá «^‹úäüyp +¬?BÎ=çÃ3Þ¿c]^?íþ`K˜ñóé>ó­GqˆòGÅ'”÷[2ª[ûþ¹©­{\g£¨œßÕ¹²ûÀ>ÕîCAßKê³¢}±õÃ&Þá„öæaèL<ÁsåÙöózè &%-vû$|ÖM¢Np7,/·ûîôì£þ^…YvGìîä4·¸ÚÅêôüg<*Á¢Sjå¯M°‹ÔÍKTQÇñ3£ØB(ZI€îD„AË-Üî‚¶Ñ&tÌg®ã4ùVö2ã ŽNÖ¼ˆ¤…É”6`ŠHQ.ÜH1ÐÆE‹pâ"è{æþ.\îBZ|àÞsÎ}žç<÷žkŒ¹o\ƒƒ‡Ç$0(q¤‘Å,i>& ‰‹#Qåé/¯—o9ä1$1ŒI¬à#Ö±€ÇZ“ÒóÁ| Ÿ!íÇÖ»kLè"ºÐÉ} e<Õ~Ÿ £œ£Ê‘VÃÊ—ÖZÛƒ’QMÏtmçæ±,/µ¿­ƒwx‹%PB‹Z_Áuöâ+¨?ÔÄØÅ,¨9åZT Û£}ù†-¼V;wˆüÁ/Û¼WÜ#7gÝì¨ï›>U·æ pL]WpÜßRh +ømL¸—¸ÞÆ \@3.óü©ÞCMãÝèC:ÐŠë¸ úº‡›¸ææ #L½ k¸ÊØ_´ï'>©×eõ`CµÆžö¼ª^ÚÎI^=®h}U½µïï¹Ìªç6ö+Ýg5—õűy¿Sßaµ6RSã-j¶{lÓû))FQòŠcÏטï;Lú Ã;ÞZïÚžg{R¾q{nþóÄñžqñ߳рÁÿ÷þCÞ""õûbÏro°‹uÔÍOAÇñ ¦ÜH 1ñbB¼xðÀŸÀ¿àÆ“GƒÖn[ZÊ‹å%µ(P[•B±¼%HQ£ÂÁŽœ¼zÕp |ÇýMºÙÄÃ'³óöÌì̳kŒ¹gBw%))¤q_R®}CP¿ïÆe$©øñõÜØ£˜Ä3•®^@NëŒá)&ð0‚¼ââ×KÇÖwãâ9Vñ õ\QÜÅ}¤õ‡5o@e>g[8Æ!ê×Þ\¬2Þa»XÀ”ú\9‡¬a/#^+Þ[c)ì5\äáQŬö1«zUïRQœ7XRüW*7ñˆ— ü‹#¼Wß¶öü{:£ø¢µ¿êì\ÛŽÞí;~ï*n ‹xGËZîÙr.–ýXÎÎv£•9g8Å/íá„öK¸¥qÜ¿ålIõÛ¸‰^ôà +®‡ý-]¸ƒê}á~ÌglDÞ¿&uYÔ½¸óšÑYúó|¡>—ëh¨½hÂ\t÷XÒ™×uŸSê{bš¹\ ÏÕ^fo̹ÐiL;¹‘X6¦{¶'zÿ†öQÖ¼¢rÉå¢Ïõ‚i棓ðm>G][Ns\ɰi~WnŒÿ>²1Ño*ˆÄóíþ?Äøvÿÿo\&¶†ÿ·øñþÿÐ/ÿêç ¡ü°‹mÔË/^AÇñçÐôèΚØÚY4¤ÝèÂÿÁÂÝ[··¨kP*ÔíEiW"¡i•n,Ø”•Ø´bA "i‹&|§ïojrbñÉ9g.ÏÌ™yf̬ÎÒêcjõôõR¼'Є† ]ØÞÕ?—ÆX¼Z©Sÿt ½zºïöÀ‹˜võk +øñ±ñ]»$:ч!¼ÂkÀKéA—ÆoSß6éPß5ü5‹ž¢÷]Œa#x‡=üÄ161§ºaŒcKHaB¦±€X• |źê&4Ö˜â¼ÁTÀÕO*þû€›ó7üaÎ|Wü5·…}üÀ.p\)Æg|Ä'l㿉W€j,‚ÿŠ*QŠî‡ø5Ìc +Oð ¯ðÓRRŸâï™Ã=WÜÇS™4BŽ÷=VðE¶q¢u·£)Ú~aŸ°Šü#v·ÀÞ£ÛhC=±]|Åg“{ßñWãn¢͸Bî°¯zý¦½uhÅ,Žœ«ªåÊZ#ö}ı|õnàR<×Ù|MèÂ\ÑúüÚÞb XÿZõ)ë]ÙÚ—õ~+&^R­CíÇUïYÕwËzGõ=Mh\%®kÔÀ¾˜£æ‚s)Ö—â¯aŸÕÄü7~×6ºŽË<ÿÄåóçÁ÷)È€„sa…sQИ)Šý®}?{.¼Þ{nÓîÿy +ç8íΟiÏþ72šÏÏÿÈ(jÍ៓üXgí§˜x°x°‹mÔÍKTQÇñç:ã*"[ºÑÆU´kØ¢•ø¸Z‹›Çæê¨)æK“âø’È¹SÑ,5¢„V  Eh¡ÿ€;í{¼¿Ë.->ÜóöœsÏsî¹fÖa‘Nt!«ú½„x\F:âqwÕïæ¹~éENëdþ3[;¯±Cx(®ÞƒPóu'ä´VVc\ì†QPL2.¯¶Pï5€A>õõˆëE e|6 jÑŽ&êß±€ÌãVð‡8ÁÞh޽ç${žzñeÕ‹?âÅ=c7Ç<Ç2Þ{~ð®q·pUïõ XÂ[ü¦¯ì§ê +Z(Ÿ¡ —èßÃ&ÖñNeç¾â/ã\ Lœãö»å$¸ŒÛxÀ×ÌR­fé4O¾‡ªfÚCåwz îà&uòÔ1Çvð§Ñšnmû¨½/Š;‡-åþ5žaZ¹-é &UžÓ9Ωã:{—ßGÊñ”r¼Ÿø„YUÌ„æ(GcâR×ͪÙk5ç‘^¥^OûK4âû#gÁntF¶¦õÈÏù=(HŸ¸roB^ +V¹{ÉØø»vãü;á„V¹kN|¿:%nïòÚ’wÚñÿY­íîfü k_¡Uþ9Éÿˆï¼ý¢«=0°‹uÔËKUQÇñuT'û‚fÍ Fý:Ókzí>4Í÷+ ¹jVAQ)e/ÒB¡ +«Šˆ¢H „ú.ïïàဃûž½×^{í}ö¹fvÍÌ’Vh£jb’±¸:‰ÎññZõ_G'îã)î¢)Gó5"ƒfÜ”´¡EýiÅ5HJ"9<ÿ0ž` ý¸¡¹éHŽÆÈ|ïËjÅ7!§šÒzöz0ªý¼Å` oðãx¨}»çø†}üÃWE‡t£W¹óÊñXm^ý]h—NŇs\ø~o«F_÷=>aJõÎã¯Y¢Å…ß¶„i¼ÂKÅï0~ëfÁØ_PÁs%c›øˆ×˜ÔÓòN>ã;V°UüÆ2~â—ê8 Î=(3+"çÉ«f§žÑ–ò\Å5'.ƒ{œÁÈ•øƒÓäXÐz^óœÎyVµLé|ÁšÎ~MµûýÑùÞÁ äuvzÃ:Ï>ñ˜!ŹGø€Eµ÷"ï¦Wïã–ò¼À.us§‚ …} -§ï.b›ç+8Çïjâg”ÿ‡ðnÆå$+ñ±VµÞŸ>F8ÏãÚt¿|½&;úNê%ÉŸŠ ¿Ãð!ü¾ë47§=„{iV_˜?SsØÿx¡¾¯°‹uÔËKUQÇñµAŠ£¤ôH‚ÓtÚá°¼WïU¯æ#E-ób¾ÐJmàc –o‰2 *+‚A¡Qh£ð»½¿M›‹ >ÎÚkï}ÎZû3k4³4ê‹Ü.âiII:R¯Xîb‡øƒÏ˜@‹râõ²hE‡æymÈ)Þ¢œÆh¯I+îsû±€ml` +½Z«Y²ægoÕ¾íÊûæóë c›ØÂž+öñ“Å®>ç+ö±ªuz¤÷‘ǴƽgÊ{¨½»¥O4æ=GÓ:~Ïײ‚EÕdßÔ—OØÑ»ø±7ø‚ßfîæÌ’jP_wŒ*ÆÞcIë-c/‹øõÞ⿘ÇÅþâ?p¤øuPã¤Æìµ/¹bVJÝ/ßäž”äã¼§£þ޹É;P{Ç3»Rí±¦çþ¨gßUÿ½øNnEáÙ]™úàçÌ©_ê}^ý×¹™RG¢>ND}×ý¬ê0«úû¾ FÂý˜jÆþî*ê@ÌÝ"vʵô-)çúµÄ_i¾ï{—Îa¬Í +g4œÓ\t߉{ÊóñæÿgÛ¿Ó +œmžû5/cÿ¾•L´~Fš$|Ÿá¿¾ïÔsܰvœÜ)r?J/~ˆ°‹uÔ?hSQÇñó"1›[W»WœºuºÕÑ­“KGÿÄæŸ©©-…Ä–$QÛ”´ŠD+¡ˆ­E—""8 ±1‚ƒB©âà÷ú~‘úîßs{ν1³´™%q¾Ï99+¾?)¤¿})Ìâ‰YÇ1¾¢…¼æDçgpYrj§$#~Mô “Ëim÷ñ +/pó(à’øýÓâ÷wcS¸"þ<ý/Wqø€wèà&*¨âºT·„¢úÜYV±„Zd¼¤ïšÆ·ä!nimYñ£Ô_ŽÄn`Q±+›úöñ;ê÷q^cïñ ß±GÍbÂdq’þ¯xŠÚ˱ÖÙÖ•›—ÚßíÛUý‡ëƒ8JÿìbŸö¾™Z3;bZ7”ÃkªÃ,klQïbEšÊµ›_W꺕H-Ú××ÊÝ•–røuÎvx¦`§0Fû·jæî‡»îNNK^ü]öoÀËk 9³µýýÏŠûvw»¨:þ óŒcö3¹yiûÿ½ø·éÏ}£þFù÷Ú¯ÞA¿+ÿÚ_á̰‹uÔËKTaÇñç8ˆÕ² ¥v-ƒ £…ÿ@»–¹4sîiÍ4]f0œÆËP£]´ b” +jQ:F’X‹­Š MP7ÒB¿¯çwð0μ·ç}ÎyÞwÌ,if}.K¯üo¼µ@ +<ź™×Îó;&0 9WÄýÎ ')õ%B±R¡5n¯~óóNk‹™G 5Lc +£¸…A\•œöKKF²/È5)(Æ]Lb?ñ /QÁÜÖþ%ývkЏ›Ò\—SUí² ©ÏåüV^à±ötîëÝF0¬|†Åõ¡Ž'ZûshbV±]={‡%¬QŸ.œÁ ݶ…UüÆ&~a9”Ûs=Šïb¾Ç>à£_sï(.€9ßÍó÷;8?°Kû¢Y[·YŒoÛÆyÚ—ÐÁßÙ£æÞ9œÅ)åóEû|Å_úãþ^¶‚y|Æ}'qZïx z׸êøF祮ڌ㡾—놼’õ×TÏŠjW µkŠWW]\î)þÅvinU{6•ÿ>™_Ê:Gá3Ü•L„à>\ך¼Ö JÖÏzQçê_Ÿ¶£·‡ãÊßÅIÛ¿÷%Ø+ÈɵÃ÷:ê?¥?Bëœ`}0´Ý}Oîïã(9°‹…ÔÍOAÇñYˆéAN`¢ÿÜ œ¼r5ñŸàh[l)µ¥_ª måÅ÷b0àKŒ)1JQ ƨ©‰1“Žø~§û›°>™ÙÙgž}fv1gM(‰xÄIHü?ܼ¤ââ2ãâ"F0‘FF­•ÒxÊãòt}çÎ#/\Ð:S˜DÖ3Ž1=5ªxnÞ„b»xW0‹:> .i¢”‹ËyyÙzÌÈUÅ´íuTÿ®jµ¬Ú9¶ÿ÷PÆ´âTpS°¤òϱ‚'º¶÷ß`ǘ`ߌi‹Ñ^C/:¹÷ [x‡·ØÄ{Å|g‘X«x‰uÍ߯Ä:êÛÖ9ôÐ?Ž]4±ßŒ ‚܃Ϩ…Ïœƒ`}èÀÑ0žù‰ïø}üa<ÐxSù|Â_Æ»S'ñ‘úi÷Âwo­ßPmna^ûº¤z?U­Ü»×µvî ©HYûh÷á6î+Ö¢öÍ>sÔ¯jKÚËšöë Ö£¤seÏ’=“öŒŽ›ƒsœÿüæ$kŸý1]ÃÜ‚cÔä„1íÇqšëtX«V òŠñ¸õÜ:6¦ý~£ÿ+áIzüûþ<÷Ÿh}ïÿÌ奰‹…Ô_KTAÇñ9»`…/À_@w]ø*º/½î"w×?kžÕÝÜWmk­ÄJƒ4UWC7e ©¬E¡‚ P½€@„úNþƒàŇ³gæ™çÌ<3³Æ˜^s&‰¹% é¹@4ÎæIaw0†ÑçéGZÏ~µ¥Î‰sstï6W£¸«gNmC¸-!²ÈK¨öÏ Æ…ârßÃ}Œ£„gx9µçµÆ¼Ö9b¤Ýͯ¨\ŽÍ7‰i<Å ¦ðOd¯±ƒ6°¤1%}߯?;¦‚:ø„=ìFÚj;0&hÅcbŒ‰wñ¼ÁûM´ã1ßõÝ-¼Å{¼Ã¶Ô•ëöqˆ¯úÆG|#ÏePƒØu¬ ƒ÷Nú~FæhÇüÂ_úšD~Ûñ͸†2Èt£ W$NüoåÚĺær¬\¬/hÁ+¾ÏœâU\å½rcçiÞà¹öÁîï¢ê_Sÿ­©ª=°qeqõ·û÷Hû° ¹4TÇÏXÃK™×¾?Й™PŽŠê½ª<ö¬t–ÜÙ#Ü9·}#Š-èüeωMK¨˜YQ jãÞÆXwprZ×ÿõp÷6íq÷eXln{{= OÒã÷ûq)söù-“·û°‹uÔËKTaÆñß;£¸heêΕ 7méopQÛþ×êè8ãXãÔ”šSŠ6N9RÑ"ucQ…P"]A¼$ši‹À…¸¨ ðû2ÏÓAμ—ó¼×3f–0³nt‰/wœ ]‚rç)‚ö ¯G’¡üîH}XWHò„~A¹ý¸!בäCý2jƲ‘ö”²ú$#¾ßîb êYP–÷ò¸…1”qwÔßת¾¬>A†Ï~Œ<ĸŒ „iÌcQæUWÄm1ªÌ±Ð;oñËz~Ä7üÁ?üÀ¾™»h»lÿjVu‰ß;Ô‘çiÿ/x‰Wxƒ÷šÇ’2W°Ž-eá/~bµRçšÁ<Ý/òÏð|†&Ú6ñIY~ƶC`[í›Êhó·€³ˆ=¡<‡VœCÖççùïðúj­w—ßuØãý6ÖÊ=³÷n Nëx¸‡G` {ª¬ ñû:«sí}1RžÒ<˜¿«ÇyœÕ^½Pƴμ¤s,èLKRy¾~@w-k•û_¯JN}†å¦U¾ßßß甤%£wü ̯죿»@Ý­)¯ü^½|7ᜌýÿ†Çñuþ{OHôÿ"qÚÿˆÏí?â2F°‹mÔ;HAÇñÙSAìlíé,‚(D;«´)ÒÄÖ>zõ¼Ó¨è9Ÿ'*‘$Ä *¢"ZHðmѼHãl#Š‚~‡ý-Œ‹Å‡»™ÿþ3³3»Æ˜jcL5bÿW?âíXH0>+1§¾m×I­„ëÅÔÎ Ú H¢Åñˆ;yqÅ:Ñ¡x£Þ©•T~\yíèúr´K›Ú6>Œ¯ø‚!¤5ö3f1¥œÁ4æå2ø€~ŒkܪÌáÑ­i­±Oý6¾€5lÉ.ñ +ñx +ö)7&ë8³ÈKú^‘{ƒSüÀ&vUÃÚÇ?œà ¸f\JPŠ|ÍÁkPÇû ž•÷Lã~©þ6þ✘ñk™CüQÎ1ýŬç˜Í¼9Ü‹,æŽÑ_‰'äÜâ7–ñÝ_»—ëǼÚG~Ž7Á¸+Ưðk×gŸÿl`FçaÏk zŽ[Ú¯Ý÷¢Î¤_g<{ÛîuÎÃÖasǨB™ö¶£º“ª•Q´ÎoCw®Cw-¸ã&4K‹r:%¥~{Æ¿çVp¿Š¥´Þ%h¯ëºGvOïÑjüwÄŽwß+—ûžÖ«~‰EÍÃïŒû½‰>GlÝä=q‹óe°‹mÔËK”QÇñóZ‚´r)AŠ ÷&a­ú ülÛ6´qg¼45£Ómfs•ÄjQ)†)Ú•ˆ.%¤´jÓ„6¡.õ{šß¯/³øpæ<çœgÎí=ι^wرzO„Åcˆ‹ÿ}Q,Ö'±H»±¸õ‹Æ­Ícùû1€†0")Å­_I¤ÅÚ!¾}P’¡ú%Œ"‡,®¨Ì*f¬O ·e˜Å#¬©œÂMä1­˜µ?Æ=-`\ý`s(£¨ñ7Tå*ʱ€%yŽ¿Î8‡ Þ8W  §©ïã<ýöð ﱎØÆO|“_Ø¥å .ƒù]Š‘;`ýu'(Ét3nñ +ÏðBõßÕœî6ðÌÑm2î$98Ë£¬©þ¬sGžPo"μÝ?liËš·ÿ¯V­÷õ/øÊïN|fl{µ Zˆ¿ÆCÌh/óÚÇ»XÁ;íÃKÜ×Ùøó»bõ«RÒ™²OA3Ná¸æú°¨³*IEùWñwp Ý»´îcØp˜ï;¦ydÕÇßg»ëýbßÝñ‚ÖVÖúmm9Í!œ#Q#_ÙˆÕﳿë)k|wÜÏÛ¿û]ŒÕôý|#½G¾d #nÌÜw#ßóð{à¿7»ý¿Z¨a°‹ÔÉ.AÇñêñŽnÂÉcxáäh_f,cI±$–Xc™HXÆ>¶˜± 'ÄÉÁøvü*Z…Äá“tÕ¿–wý«1eæwURÊ@©Øv¹£R*Äöÿw¾;Î]×Ï©µR‡DÅo‡q›µÚ ;"Ž:©G#šÐ‚±Š{<ããèF\z1†lbSèð×™ÀŽp‹kìc^c:Ñ!qíÑ«ØF1­µGeLíYI ‰Wc¼bcBmÆd ƒõBѯ>ó†C[Á:ÎðA< ù(gᵃ¸w9ð}½Öâ9ô„\ÚÈF¶1„Ö»Ãv±§÷>•cõoɦÆð½<—GNuáeÒ¡<²ˆ\áï¸Ô;¥ð ¾Wí·ª9ämFô]{ЇIl så3®XtI\ç“vµûu3ªƒ<ªnR:cÿ5½kZy¿h?ÿ¬[Ñl¾j»Ñ|×¢ Äš”Ïœj(©|ýûQë¨1?ï‘{/lýGþoÛö?¼÷U¸ûÿ(÷~ÛyaͳëãŸRêÛ£°‹•ÔI+…QÇñƒäPVvÊ  ,lYÚyvv¸æù^ cÉ=gü?çœçücL±ù]™”"ài/Wø”J‰þ9ß?Î×®©•Rƒ:4ˆ­WyúË5¯\õZ©ò©ö©;¶A4¡ÇÆÄdãΘØxž!Ú1‚v鯿°ˆY„eTÆd +[¸Á®±†~´¡YZ»Sñ0ŽL«l×0¬¸aµÛw/á’µf²æT¼àŒúÒè»À¦ÆÍc‡x¦?ƒ°sb‘ˆ\ä#N}”߉Kç™G½ HF²ÀXs‹9®)²oû<Ð9ì`_õ=áŸÑ˜æ §;ï9×Y›¿‹mý¤öu¢wÙ÷n¨mEc&tÞöûöé,<±ly]:¯µwH«¾W‹¾_»ÆØu¬+ÞQt1IH¡ü½W‘ö}íÇÆ‰½Ûõæû.U®{Ou/Vq¥xšëîµË‹ +ó3\¿Ëwÿ«ÿïêÞÿ„Ëû2O¿Ë?—×…âÏo7Ç寋çú#ÿ“/ ü°‹…ÔÉJ\AÆñêë[賸2¾€àFqá+˜6¶Ý¶¨F£Q´ÁyĹN ‰8 h 8!‚" +ÈÆ… ®ÿEׂÆÅ[·†SuÏ©ncL•Éúè©–*J®u~Ô“+Nx¯h(fLâ’@ øŒf4¢>¤µ¨Ñ3áÅsÄu±“šk÷H¡SÆD +Œ +ÉëÁ#íúÊ[D» b¿ñCVdK˜Á(†°€?¸Ä)Öчoøª½¿ Mñ»ÖÚ ±íRì)Å^Æ6ž8k%61†ôÝc šgÏ5§³?0§ÿøÎrT€÷€ýÖùŒ‘ûç ¨UÐOûE¬ý¯Ø/¼çñ$Wæçr…Ü©}¨=÷q„c`W¹¹ÀvtV›ËLèû3Êí/üT=&1ŒqÛ5ßõ}ÕÁΛÖ<ÎoF05YTü^PsÓ-iåÚå>­ºt¨^-âêÖ­ØvÏ[rR ò¡æ‘Rúþê¼ƒŠ‘’&i0oï·»ÇujÛ9˜ÅžÉÞ¹´Ö…ï´åî{­7ŽW¯¾D¨ßý>ÜïªÚóÉóÞÿˆcçÆBñcÞzû_Þh°‹…ÔËK”QÇñgÆ\æ_‘ÿC‚ÐBZ¸Ü·Q3Ç™QÓÀËàTŒfI…™ /á%Å+R˜`h«6.ÚD.¤6.¢ú>½¿/S‹çåœó<çþšY«EÚªh­êÛ¥Z\¥ö +Õò„q¼.…tâ®d$‹ "!Üǽ˜^t)¾[19I+ß ù»¤[2êß,™%Í’ŸÍ®¬˜Õ6˜ÕlP7CÛ6 ˆMüˆ$ê(¿ã‡xÌã&ðä³=ÅÎâ¹ÚÆPÐ:‡UæccMÄŒ«|ŠILÁç6‡7ûsª¡ü…3¹†U,â^k=—ômóO6£ ×p=’x‡¸‰Þâ®û G`¯ì+ÎñEczyŸôMjN±ã£æ»¥9~ãÙ:–£³ø»O3² uì(Æ×RÖ><Æ}»(iÞg:ÖVÖ^ìj¬²ösLŠ2®s+«,êL +º+ŸÕ()Ïû7ëæ¾&ëáûWÐú=§ße¿o}Ò+~Óÿ³è <´è^ùšÔîtVÒõA¨ï,Ä„9dãïòvL‡„÷”’ÿýGâÿïß)!GÈïßé?¸hô°‹uÔ9LTQÆñóÄž*Z: t$tô6TÔ¢0b˜‘A6Q”ÝÁ!²d LD¡bK¤¡5tD¨- ÿ›ù^¸yÉ¿œ9÷Ý}3k´œWÍ£š¤Y^çÖóh»|y(†Äц$:Ѓ~|À|Ç2æñŸô½_uSèÆGô*oG­x+­’Ð÷wž”Úà§YðÜìIŸÙÓ +웼 Ì•Å(ä†òZ°Ž šü8ß\_6¡6ã˜Áf‘Q\ÔÚ¦1†!qkİÚNbJ1”×ïõµŽ= +ó·]ì(nbE‘y…à|‚¬¹ %ü>EìWPƒg(F*QDÛk­óP‘ööWúvO½RÔ¡å*?Âç¶%«9n`kØÒçÚÛ¿8ÆoµYòönD1­½š‘Œê¸³ø¦3ØÒøYåãÚkߨ×WZçñYqPg4âMFó9Ñžqï‚KÜ¢^ëpw¬ âî|Rü{ëÄ•ûeIµyo¹»ëßsÛ$¿ïë³[RªïÞ§{§îm»÷ë¿Õ¸ò˜=þG¼”ƈèÿJ,7nÜ!Š(°‹…Ô_KTAÇñç^Jx¥7Ñ Å—Ð} —ŠïBËM×UÓTBV¡6µ]ñº‹V‹@ ¢±ÛÁ+Eð*A…Hoúç7t<¸xñavæ<3çÙgfŽ™µY¨]âý¸Ò!ÏKðñ¾ŸWªïu"‰^¼Â0ÒXÆ'ðÅ,  ð˜'Èã¦0IÌcsÃkô£ÝÒѧoHóÆ‘Á)ï{oV–Â*é×0¾¤÷N)+Æ[0‚Fú¿ñ [ÊVù½Q®iqco±¨¼?cEùÏ*÷œl9,¨6Kêçô¿ŠËª¿Yw›ØÅWlGú‡ä] ö/ çà?ÁÞõ('æøýOÿ€±süÂì‡kóí ‰©5 ˜_VAK~ÁCÅúŠòQܾ¯©vÅ0¯ +ÏО»ÆØÛQlVu›VŠš¿¦:gõ,£zåõ|]µLkŸîÝÇŒÖ.jWïï¸!Ï:4¡O»ÐÞŒbÐÂóîÚ Ï¿ãÎ¥;ŸþÌöÚÿ3ëÇÜIÙísíÚ.µ)¹k^RqN§Ý¾ƒCÒ¯ø„…÷ÔÝíŽHœÏ7©ù/õ<þ=ˆgü½ODÖŽrktÿËC°‹]Ô=h“AÇñ{pb#T°¸8vrÒ¥[‡nqv:ÖäI41i›¾ ÆAÅ’*šÚ&©–ÚB[TPDK‡¶ƒ/`é ƒŠ ºKÅïåù=2|xîõ÷Üÿ¹Ç“4‘ó’lãڇĖSÈHJÒ¸€‹jÛâ„’–ГÑ\_F± +(¢Œ9|ÄÏH0`Lì´1ñnžï©ÿ@œ¾e<ÆCÌã>án+–ucƈÖÊ«^”q\FI*ŠýµN Ô_ÊÜÄ}lÓ×…“8H}³¨jÜu\óöS[¾¡M<•œýX{í½Ï>g›YÅjÅëõrI.J­êÒ —‘C+2ê«ß‡Ç{=Ä5i|RhkF×Ð)ü6‹ÊqÒ,uÞ¬ì°ÙÁÓfŽ›•¬ÐÞMÌw<ÇSYÄO¼Á 0Šè ­=«93ªç4÷uÜ”Ûÿ›ÌÅ +ùÇpízŽ)î ^c½èA§âòE¹Ýôc#Ç,æðLÏOòBûW̸<¯ûû1Óx¢þIå{«uÎkÝ?°ÃþjpgPMÛíé>bëjÿKLQ^SÜtß⸈óŠîq†Gp‚òJé{©ýMêŒFµö‰„|%¾°AÞwj «¸£´·á±»øï©ðí?•ï!UIý}¯ð¬Éú@{,ý:+?£ñsË'μGãø_qîè\¼ÖÂþƒmêa}%”ß+ÿ-´I^Bù*Z$#Y m9¹bÿþ›&õ…±­z†qáÛNKóøÿ—.ây½Ïÿïp4¨/“˜Çã}]EÂØºÏã÷‚ßG~ïî¤=™9qư‹]Ô9O”Q‡ñó2#ß@Bƒ¥µ‰ÚÐb´¢²¢Ãufp˜Yd !(ÆD1ƈ,nq‹ +—€ +B¥‰¡À’ç„ÿ 7Sü2ïÝνç.cfWm× 匄òeñï,šÇ5iAºÑ¦ºŒúf£x¡œSŸ¢Æ^—B$/Å}‚el›%gÍR5fû˜)穨üZ¹úz߀=²|V»Çy&ïÔîfð È« +ç1â$ Hknõ ¿e¶JœÄiÖYø™}Å̫챫QîUrœºU“ç8¡u*ï å2®µo1æØ«„9*θ)æN±Þ +òMXSÂNŽÒ|_ í‡Yš3Ms†©‹Ô=P>¾0¢9ßãîê\:u®½RÒ½ õý:³aí÷bŸA#j´§s+˜ÃSÅò87¥[:pÃvï¥+J«íÝË’æ÷~…¨—ÛÃý.ÇïÕ5GÂû ãÃÛ íaLN2‘œúmï݆~ù²y›$S&[&þÏq—ÜÝ®R$°‹]ÔËK”QÇñç}'I\-Û nm.Ü·hé?R63ê˜÷ëH"e]ÐEQŠ)"*ÞÐÊÈ !]t•‹7¢Ñ÷q~^fñaÞsyž÷œ÷#颶çЩ¸´ YšôŽðÏ9„ l˜EfñK³’S³RÖtqÄì¢5Æ?‚1{¤˜a<Æ=Oã'¾ã îk­ZSNkhѾ|¬Kîb¦ *ß$">ª¥o]¹ò2 }’O¬Çû߬GmÏOsFÄ×=Š1<Ǥö°,óXÄ*¶p€}µ_áf5¶­uÎiÌsMaïåþ°§kà½qÈÁUúêÿ¥\»øŠoøÇûŒ‰‹kx~ŠËôÿÀ'¼Å +–àç´ƒ#üʼnæ}Æ;íéµÌhýÓZ³ÇŸ‘û†¾?g1?®ùS—øí¥oÔ\Äúí7Ž g•¢]räKmÒ·¬}xÍ<Ó;×ôæôíûuF~†ƒ’×¹†þ!ñð:=$w5®£ÌÎëö|/aþ¾'Šñø^ õâõÖ¦tá®uª®¼.Æñ01¿[ü¹C<‡×³ß¥lB¸ƒ9Íqw$ÜÃ0Þ(!6Õ w8Ì w8ćv¶H¦Hò?ÇÝtÿÇØ±x°‹mÔÏKÔAÇñç»j` xii¿Îð™Î³WÏÁ mì²Îó¹öð ZÇ‚öß™ÑXÿ½t2Áœþ{qqÓÚ¿œæÈ*ç´¾›auñîžù;åïd\êKŠÿî“GŠKãBî~º{›B‡]Þã|ñMâçkÖüþÞÇU_ƒü÷tKŸ¯¿°‹uÔÍKTQÇñç:µh×&¡…᮵ jÙÿ"¥ŽN3ãø2·±ñ5M°-ÄÄ×Þ¥u ÅL!q#šBŠ© +¡E è÷àïâMtñaî9÷œç<÷9猙•ÚÙÊ Úeˆ"†2’R_¥Þ—‡”ž3?‰Z¤Q)F§Bc«4æ1šñ;f^ –1‰;ô}C;ê·&$¥ßôÊSøÊ%¥5NK‹/Oð¬i»8ÄúР:­W'®/«1õùʵÝÆ'¼S~]ú—ç3¼@F0ƒ%™Ñ¼ilbSÄ+ô+æ=iÎ?êV€‹<ÿÆÖißĸY5Í[5‹[ä®Ñ¿»ŒÛƬâðÎ»ÏØ³|bäåÙÅ&¯—óW¹×>à#rXó¼"ã +í-üÀwÌé»>cTßþ?ñKk_Â-ÜÆ \EDk®)ÇÜqm¼{ ®·wÞöÛY<®…whÏ£S{äkßšt†Üž¹³Ø¨ß U_‡ÎÄ[¼5± Œ©=)nÚÛŸ¬öü¥ÎF‹Þ7hUWÏÕtAuÑYiÒ9 îUù)QõÇ%!ɸÜɘÜÝàÎÔ„rv÷¹Z1Î[/ŠÍO+¶»óeòßÿÑŽöž†°‹eÔÍK”QÇñ{ÑR¢•L«Ñà¦mmruÞšFÔ&ÒÂBJœ!RÓ”2¨TÔDݱEQäÒˆvA!Õ÷8¿‹ãâÃÜç<çžç¾Žs®ÝuUãqI ‰kèFy¤õ.ôùõ¬Ý¡Ö'#öÜ‘N½Ëè›9ô`e¼Á6Ö1ƒ ('¯ülä;iÅn _y9Å-ïºæôŠåUÛúÞÄLáãú"}ÂBÍ‚Þ÷KAõJU¬à5&q·å®žÇ•·‹¿ØÇ'Ìj<¯´.ö<‡¨`Z5Ÿ(ç—sþ¨ç/¡™Øg|¡Ý‚˨â‡sMçÁ›.òüçÈÛÖñ•Xï™KŒxlƒ6{çY#Ï=sóì£o%÷ƒÆ?¯q¾¯×ò'qŠö?|S|K{kßx®9Mj.³Š­*ÿ7««á­ú,â%æPU{øÖ ~bŒ×}'vgpVcy‡±È¾Û¾ ¹úù»çêç­$¿¥Ü—õ{Ø>½Šú‡3ΉÕ+k­lžT¿¨=´1/ig´{RÓ»)»;]îøý²XÊÞ”äÜáÝIGØs^ë`wÀμǧ_Qý’‘oÄ#íé’¬æ;¨5Ëj|–cÿWäàÿã?Ȱ‹]ÔÍOAÇñbL¼ràlâÅ›þ^ø7ðb´ÒÛE+´µ¼5õ#B‹& +DES„àÁ èA! ãE½˜ðúÛ0ÝÃ'Û>ÏÌóÌή1æš©w=&OH +n¡„oõòZc÷þüIÍáò:ÑáåǹñvŤ•wCk÷#rèC—jÊ(.TN‡’ÖÚ¡ã®7Õ[·Ç­3ˆ éÞ­? õ‡tÍ¢Wu8·UO4g—Æ¢ÿ]ü]ñÌâæ0aõæÜÓý#¼Á¾1ö4Nñ{“(i®'Š+yceLa«ä5‚ºìwk/2þøÃ}Îã.ý±ìµ½‚sÄlaUü¨Ùec.£™ßó Î†üûeŸû êá-–ðÛØÁz­>³‚Oú¿ªøꡬ~Ÿá>b Ô_Å‹‹ú~޼ÇP«ùŒ ê: 涇p{CÍöŒæ+êYõè¹åbç!/wt6²ÞYˆÎHÁ‹Ë+wLF·¬òFñR}}ÃkõjÝqåß׳žÓÞíá7~)wBµ»3Ÿ2õïZ›9y·Òæä= Mý»ɨ>×ÿC=—]üULíœÞ: o½¤´I øõ}K\ŒûV\•ãïǨ€ß°‹}Ô»K\AÇñs£…] +k;ë4ú'XÓø7¤®®¬®W]u}k|"Q|á‹‚E‚!¤SÁ‚H  (Úøîo`Ø"ŇYÎ93gî¹kfo,V# ©ÉS+õhD»¤Ð"‡,ú¤Wó>`32¨úÞI›ÆŒæöcëø&[ø„yŒ*¿ø=]| ¿q†süÐsAÑ>³XÀþ˜E ÑKp¦ˆw½BŠxp[ÜàÇø‹<âB{î`¿pÇÚr¼F*peVPiVø‚qÒìY)±bæî©îUlà3¾jÜÄ.~Êž|ÑÜ%Ý‘—ƒŠO«ÞI™S|]yŽT‹»·Îóÿ8ç-8äŒÔ±WT¨Úü{ôºu¿ÃzÖ§XWÀ½ãÉ(Ö#YÉiíû@V=4¦÷éî÷—Ø·¸¯ºÔGá~nÝ8VðWñû0ê±EÍq}~—¾ÿÓ÷»ëûÿ}/)íëjÒÒQIÜ7v¯˜«·Yëü‚ÿžÃÿˆd7­5õyóª‰'´Ù M°‹]ÔÉJ\AÆñê“`pãB|ˆ@6¾‚d‘¥{Áˆ¶v_牴ġÓqÂiѺµ%Æ9*& ˆA·² AÜÔ…ÿCÊ»øqëÖ­áÔ©Óíœ{ãòê¥N¢ý Òˆ$Z%ð¾…h–@ïq±ùMH!ƒ4º½uÂqIID„ý·‡ÅÑ%bí/†¦ˆÀëoñÎÓªùS/1ެLb(ÂÆôã–ðC1‚wx‹={5~_‹£U¼ßbÓZÛòó^ú0€1Ìâ3v±‡œúÓ3 vZqÙ^S˜Á2ð7ì[†xW(¥ÿþà‡Šé 6°-«XÁNñwÂüØ|s® W¨qîQ}Ï5ÿæ”§­gí¬ißÐ1ް©19oÿœÖú¨;š)ÉjÎÎpM ÏxþÇ%í—ÄUëÜcî©°’8÷yJ‰Îk÷òØ=d”ïå¸OwêQ uëÝî{XüÚÉhn¸¦ÕÚ¼rmgýŽsüÖùgUOVßm._ãÚ'¥X²Êï¡ò8®x¬ÎO³ÖéW,V3íú–ŒÓm:Õ¼ÕÒI>—±rÚ¿”ï.íeóüÿF÷ð?"áþ“÷ÆÕyâ÷<®à°‹mÔÍKTQÇñs+$fÙ`+7nÛ‹‹þ‚þ6éèÍ&u˜Êq|ÉÉÔÂp†Æ^TÒÒ ñ…,Aˆ V +Ñ¢…(„¿þž..>œsïy{î9Ϲι´;u[Zp+àÛ[Ú¥MüxkË SÚíVqC(¢'˜Ïëdþ],¶F—æ3y<@AÏÙ oŒ÷ëÜ d¥9••Ó~Ñkç.4¡‘ºÅ±ƒ7Áz#*maÅ2ƹi•eõéO°5ã‰â{÷XÃ&>á+öXû*¨c¿°UÌh¬Åü³˜ÇlေWPGý¾ã'Ï×ù®7ÁÜ‘ÿoÔô¯4wMs½Õû¬ñYß°‚wXÒþÚóœÆX\UÍi}–5ç¬Ê/Ú[buGø‹ÄTOl7œ»t ¬y‘¼ˆö‘¢}Q{gùÔ+E÷˜Ú|[¾øÌ«^йT§Å6g:ßQé°ÞfÝËh–”¾}S{TÑ|–ã]’ ê9Åèϼ¤gk³»’¼7–S“Ú£I}ƒ¿·!ÿ,?ïiý’Æm(ìì,·ïº³{ëÿ™s„wæ2GÞežå”oi~_g§Îø±´è»ðúgz÷ºf;û›Tש9<‡/ÊuDgÖ§sóóñoçqˈ_JWq…÷ꊴæiíÍÎÕcùýñï?-÷,ú~½¾Qy<°ó÷ ¥v=£}Íh-Þ7û>†÷ÏcÔk/|}ýÊ¿M±ã;þ'ð~ñÝKÚÙ¿¢ÚÎÿ;NÿC'Œ¿+¾°‹uÔÉJAàšDâø +Ä£øÞ|¯.1«Ä%Q#Š‘¸â†[p'ÆE +‚ ((ˆ z=ˆý+ù›49|ÌLõtwMwõˆH³ˆ´‚B„vÆ´­…Z­˜^|? +a>û-mÔni³)àR*î²æï„u[4±¾«”°KÄ ý0LIèc\õZÌ{ pJË0Iz¿|NÁ"ü‰8O=Tã^s~…5˜¦%ö=‚l±}Æa„sZtüQ¶O¾¿ çp °Ê<ôzðŒ<4—)ƒ*Ä~ß<¬@–9¡ÍñB TBbïìsðpÅys´MY^3ü^ý¾=ŽoìsÎ +ã;å¸þÂ-Ûá¾ÐV MPÇ\®˜Ç7bžÂºË=ûÀâ à/ô•7æ4õ°ößHºhLk°‡÷ÃÜ?]×'øàºÏBšã¦¸º¦Ÿ¨ +‘²FöÊ7$âE-8 ö[çþ­s/SœWçÓzÕºRDŠu“â955a¿4÷r—k®9wIñܸÏe˜cuqnÔG¾¶4—8Ûô¬›ÿ€égÎkÐjXy–šÏü7òÿŸ•4j"°‹…ÔËK”QÇñg¼”;AÄÿ¦ý®Ý—ŽcÎhš6"™£"j&šW4Ñò–" +A•‘µq#J‹î\Ò÷q~SäâÃûžË{žçÜ^3»kfõH£‘ARõ÷¤>’TÿvdÑ©r2êÓ ‘†Ô'%%M’ºÆ}ñ\ÛðHÚ¤Umiõkúæ-x µðùuK—btJ6šVíO0(½2„)¼Ä‚Ê¡~×,Q…:Ô¢„ºM<Ã0&°‡3ÚªÁ«½×8£@Nq{¤Oñ½} OÅsxƒŸBl{çj[PìßÄaNE·p‡wê•Ô¿Ãf1­oW•ß}näÙ)±¯\÷Zßøs ëx…eÌcFã{ŸñA9{ŸEñq~«œç9¾ª×Ãm5`o·óó±8¦|SëXFù‡ÆÔ¸;šã¶r‹ö±§à ĺ"¡®[ûâû¸‚OøŽ ík¼_¹üœ¬7ûPúÖ¬ìk¼S ïç”WX'ϯ_qý¼ú¹ÍØÕ¹o‰ø¹ŽÏ|¸ï9qŒ(¯»º7A¸>¶ß­ÇÊã‹ÖCkÕªñýñÂ8þî}ª Ï´ýýÿËÿÊN˜°‹}ÔKKBAð£…E_ }‹>CŸ£e›VAë쪙…aj³Ò´‡Zí¤÷ÊÈÚô0"ŠhSËVAô hÓÿà`iñãÞ™{æÌ™¹w®ˆxEÄA!½÷A¯Å±ø!1HÂÛ:¦‡Ì8ù©V>ÇŠ ‰7ýf^}„~€Äa”5…YOÈŠ Ô`ž…¸fôáaæ7×8E)fõG§Æ! 0¬U¯ó° 8ƒu˜…iȲý)âêĹtï>`“¹f ES”â\Ë”§U¸GŽ&hÄý” +°exÃ󌈻ß]‡v'úßáv8FómÃ>œÀ5ÜÀ%×sJfmeŽÕõÂ1ç×¾ æ,²}¯ð̼»GGp Oðȹ˜³R]—KknÚwôƒvs•î©\p¾,÷QßEŽ{—å;P“”ê·nÎIüú~“̳k|g‹ìOðšdnùÆ^·‰ÔDP¿ç ÷­¨7Íõç(Ï<¾k­eHªg!ø‡ùÆû,Ús-25iÝQŽ Xñ&Ÿž‹ç~AmíÐÁï©Äµë9µÿöÜöyv˜Óœ·ç´ÿ=&NÿSÞ_•^¸°‹uÔË.CAð9mB,lÙˆñöÞÁ‚hO[ÚR½©ª†j—XÔ-êZÄFBÂÆB$‚D¼báÿébrÂâ—9óo®gæ(¥Okð€ö­Ð‚ç8…2BÎáïÛá^)WÊ;è‚z¼»an 6`öhŸï/3v\[ÇO}í$ÿva“ý•˜#sx†/x…[¸`¼jôWa?²Û|–Ø ó®áŒó¹„¬¡å\qÜ"÷<Í=Ÿb=ú)Ío¨éïÿC’}È·˜¥œú=giÖss®6W«{Þ­”17¾µ«±F¼[6æ‘e»iö'gQîœq}ô×g¿Ïdécs“9ÉÙ–ûf>#?DrµïkµÁ;æˆsaõ öÄ9I¾¾×~ÇØæ}¶¹†Xêqÿ ùOy¾šJé°‹uÔK/ƒAàù¬lü öþ?EõªJQªuoâ¥h+u‰Š…!‘ˆÄB¬ˆ¥D"6ÞcÞ‰I“.ž43sæÌ™ùfª”r)¥Üà?ôñ×öÛÜ$cA“ë¡^r[ù½Ìë³Úž:ëø©^€ëB.àNÙŽrnÛž0g¬ç]™£8…c´?` Û´ã4NØ?m|tÓ¼÷pWOð¯ðÌúWŒýx5´ð ßð[Æ4]ó#ó5kZÇ]O5<ÄŒf1‡yãS;Æþa-]8ƒ“´â->ãËÿ5ïÖ[w]5çŠïþâ6Í=ã¼5k]´¶¸¾e÷#îó¨ÿׯü_*î_Œ{ŠU÷ö®¹¿U÷¡ásÊ|ãŽû÷Òz«æÔˆyÆœ{Ôÿ»î\-k5ße\ò9dŽt¶’tÖãýJ÷¤è»xöÓy.™«b½Ã¾Ïïcº·ycŠJ±ìmæ8Ç™IÏ_Ëu•ÃþïG®-w{þô )„½»vиÝïÎ?}z]°‹]ÔIJAÆñn¯â9\x¯ÍœhÔ`"¢qlÇÄ#qDÅ AÝ("ºA—®¿"ÿ‚2‹éîª÷^ñ<¯Å«Š &)I#)QÚL¿f„xÓåÙ¶‡8#N®5lÿ'O¸f.ûÝÖŠ£MÆäTîq"Ó’e.qæÒ.|7òR‘7ù•/¹‘Eé%wÒ©•B‡ ˵Ö$‚ô“·[2ÒêÈ›£Ÿ‰™`¬‹ŒeNFÈ5Hû2ó{[Ù–=Ïóõã«ÿ#óR§+Ä9ùr£R”yj•ˆ1ùŸåN6h[ m«R&&`ÝÇ©Q¢}_ŽX“úMñ\‚ͽäüš:ÇÌ©I´ö~ƒ^>åL®åŠÜeÖ, þ\}ë‰kdwYÓ"ýælöë‘|ÆnÖe€=éCq^È·|P{Šõ•-9 –ÿ$ënÚ7åIÞå’þ=^õü™3å=Çsu‡Ø£aÆ”§ÝÄ´;ÌÙtϤ=cöκg¶•º&&´“+íý¿—öÞÆœþ¶ÝÞ¥<ûý"¯rȘ3ô“§ö~×Þó(ãµwÓÎ+RgþoBŽetý°‹]ÔÏ.dAÇñbaaç<€µðÞƒ6újú6!þwg¦i“™¦ 3þgF"‚ĉ„ÙJ,faÇ÷ÄïÄ^|roݪ:uêÞS7„ð!ü/‰^äñ ѧ¾:%¡g)ä$ëïF„É)fVmëïRœ„î»´~26ÏÛž_w,v¤5‡ñU–±€1¤#Òúöl£Òõ.ðB—ðg()ßTLZú1…Ø@L*þµ×tͼåa9®á'¾£ª{˧ˆi­Qоlì.Ö±„mòmÄJõÍ\Р6ÿ³æY¼/˜Á„̪¿¬1KŠû[ÚOYs+ÊÏs¬ÆbÎ*OËq«ØÁolê[”cë8o{üeåmûûË>ZІ&Ú78Õ79Tž‹˜W–ûc[‘A;ígìk\Iù´‡c<àNñxaOתâõ},Æ“òxÁ¹Ö³ï~‚{Üâ+z§“z/¿p‰k­iñìlC…mSŒ'93‡lÁ Ã:ó/1ÏWc<í(¿Øgÿ¡FeÆ.0nóU9ÿlXýåûáî8Wÿ«Œ9 ó° ÷ðI2îá‚õ'h0v…ªÖÚì{çpļà +n¸þ5Ž“<–Hb6±^è„Ôßa—ùIö…1›­~¬ÍÛ‹ûçéBû-ÏåõèƒnÔߘO{óö6”߬—yÎzöYk¯ó¼ro†Lë~ÊLPÜEߊŠs\„¤õÕ¦1bœCÛ¥ó ‹¾!ÍOòN3žÄÑ÷3ß©¾{}Ï>è‡ù+ÊýYæÙȽLYñÎñ»®=îñö°‹…Ó¿/CQðû°û#Ø-l‘X¬b5ø (úãµOiU5´Vý¨6D¡ƒ&"H0 ‰Dl‰ø,¾'ï{⦆OÞ;çÞwî¹/÷c&/!ƒ q’±ÇÔ9WÆg!Åù:GêE­ù.E¬œ'­%×8þ›àÂì_z,Á +,‚/`mkg¡è®î:»îºxâ ë-^x!¢(" +ÞŠ… b'ø +‚¥ß8¿@ÄâÃL’ÿüóO&1Æô˜HH¢)I#Ô˜“@»êû./&嵊MzíD¬ßåúk®NÕ”öÚɸøú0 gAëÎIúòj‡ªß—÷ÆÝ7½ÞwY)xs bËØÃ6q†gœb£V¼}Î(ÖŽ?È5Îq€LcRÏ"汆mÙÀ¢rÙÆ1¦¹¦°„uÕ³ƒK|Ô€õ–” ž÷Ô2v¡|sXP ûxe¼œ  eô=âŽ÷r°'Áêèûıê\T7xÃ;^p¯uÛý:‰ò˜+ìj¯¦½µOªžƒèûßù*yÿVŽ¢Ö;ªXÛ^Åq ¬¯Ù˜Ò*žüË ‘þ[íÇS´Ž ¥Qûw¯ŠšÏþ›#ÕdÛ#èי薼w +:3ö|„âÎKF²1®ß¿´'Ô~¾ÐûÆÕ*>~ŸÜÍÄò¸ü®ßíŒâÝÝtw/~Ÿ]þVi‘6qãmŠOiž>±u$½|9í¯ý—‡? pÛ°‹mÔ»OTAÇñYiìè,)¨¨ üü'vž{Yöd!AÞ( ”m”„„È£MÀBj°2&„Šðöw²'7ŸÜ{î½3sfî™ !Œ…ªô¢[z ‡¼ÞeÑ•’•Dmì¹µ/ˆïÛúïqíMwJ§»·\Šè×}’Ê!q}'޵õú$ýÜÆ(I^íûDZµf¦äò*;ØÀGZ×wx­µœNå1ª]Vm ºÿ?äj¤¤éwµcµlõXpbÜ+¹P«ÃB¨Õ\Þ½+ºñý±½Ù)Yõiµnc]\p±í ;lù}uH»Xœ>7þwÎäBí èл²þÅ îfãpذ‹mÔI/ƒQÆñÛ®,-øÖ>†µm÷Öb¨Åk¨kˆ1fjXH$"H°DBb%‰ˆ…tç°ò¿ésâ¶±øå­{î=w<œsó®d´K +èFºFRÚ$-©Šö¤Æ)÷€rtHJÒ:þ‘ÖØ~ä0‰q jŽ.é–Nék³¸µg$ìgñ>­y ˆù¾²ZÇpÐ' +Æ |ßQL霗±‡[¼àJm£:§ ,•â±z|;Oðý@j'þŒ}åÌcÊû*¸Pû:e[ØÑwC¹È]…F\3/ûÑÄßÍÄŸPP_rÇêÀ=Ä6‘(­Í}iÞ3œãöjÔðûï¸Ç¶µ6ß—ùÜnp‰CÓ±Úü>v±¦qÌé´÷¼~4ÖŸÁ‰öåÏg³:×Uåðû>ŧγ¨¹tW3XQ¿í`ÎUåÓýȘî0§;7á;‰ÄÞÆô»¿óì]öê[ù¾-¹òwëÙû³uø¸ûVŸVßVWa}g´–l°æ>Wþ¦#õµúµµ¼Vÿ­þ?Ù¸d+¥~\‹b~=˺›Ã_ ©|1°‹}ÔÍ.CAðSÞÀÚKH¼ƒ×ZmÝ–V«Ú*Um±ðQ*>ã³BÅ`oecÇÖJbáÒÿIÇMXürçÎ9gfz¦"R‘y˜B4öylkß0)äcýaˆCf!1ö‡ë?6.Ê8)(Ã>\ñ9 Æã8“dÎ4ßmqÎIpŽ;o”´=ÎI¶Ý>ÝSžÏ´3&æ¬EãOP†t|–`NaŠ£X‡w‘žÝ¨‰tµð^‡èÇ÷o¸Ï¥ +{ð ðOpÍ<šo…±á <Ç Æ9àœOäèƒx¬;ÐÃs߆fû=ðŠuÝ Ú_Ћo̧9Žàîàžk¹„ØeÞ-g=M:湬ò{ƒ±´}Î=¶£Ê3+²½uîUÏ¥Ä:Ñ_d¿Æ]vÆZ® +Ǩä(;íiißû}µ=Åß7Ë÷k#-:L±Vl^Ь¾¬þ<Ÿ¨tj×_Ëžï[‚¹¬6µõîº÷ÚîvÄÉim]OŽûÏÈﶘcÒù?ðß{û¤! +þÁ½óAg¼Í‰rMºžüÇÝx°‹uÔË.ÃAÇñ)+ïá¼€­½µZUZmõæV—ЈkEQq-‰`#$؈ÄÂÂCH$ì„ÄwÚßÄä]|2Ó™3çL;35Æä1Y$EDÂÔXT};6 aqñÁñ¨rÚüãC<ï?CžFÆn µ¡ƒþö•?¥ü9Å&‘Á¤j'µ—„úcˈ› ôÓÞwÊk½«÷Œj_©@-»f‹XQ;… )¢‚w¾ç‰1-ŸÆ´×òÌç ðÛ…Èê&æ ×XE [¸Á+ˆ7W8Å1¶WÆîð„{Ô°¡ùœãoøVû€TkCì©‹½õ¢@¿‡±væ.±‡CÕ>RΊj¯«-k¿»:ÇšÖ^ªo÷»†MÕ¬*ç uhÌGã>ÔóÍb^¿Á’ÚÙ¹ÌilYy7´¶¤ùiϤÅOA2:sw¾ösNg›÷æÝ™g5žÓœ[—6wÇÞ1wÿbÿpw2ÑdÞÍ¥T/«¾½‹ö-¹w7àjº~Rût÷Û—5ïØÆÛ÷êÿ_øÿ}Ò/á&ü·öâÝšˆö]ßã/—"Và°‹uÔ»NA೩L| +_ÃW±±µ0Š\V`‘è"¯xÙx‹Z X 5övVö¶ÆDÿ‘ÿ„qÅ—Íž9sæÌîìŠHRºâ4ë˜s¸ãƒòæ! Yò!Ÿ‘p¤È·æä¡e8ñF  +¯0…Ø T ˆË:ï p®‘£€c‹TäÜ%Æ}Zpd97èS£ÀÚ‹ÏkàÈqÍeX…5¨Á +…ŒïÁ3ö8_"CÓ¸þž«‡|ï ayŸpÁ½o@-¸†#Øg½M¨3Çä?Á¼Ã=@ƒLþq÷™Ë\ ÁÖ‡oô†Ã1\Gq㺧p¸eÛì¡Á^wY÷ÚÔa/mÖ8¤ˆk·ºýzcàqk›}6a‡õëÖó­Pñ-æ6ÙSHU²ßGÈ÷eÎ`‰ï//½óÈÿsZ¶rt¼ßyQæœè9Óó§ß¤~+ö·a¤9žàx’±´ôΫÖ×ÚZCó|kYæh<ÃþKÖ3(3¦çÛýŽãý/ÌРÿ†öŸpæ¹ÿ›¿û_ c°‹…ÔË.CAÇñá$l$V¶žÅ‹Ø T[¥”œj¥—¸q‰¸%D"DD°‘ˆ6Ć­•xßI“Ž“6ŸœËüÏæ?gfŒ1S3ØÄ€ü –‰bÈ{m 4¦QÁV1scZzðjLkWÛþ†,cK˜À’2Ž,¦Ô–‘´÷>ãÅ6`ó¥<.GJù“¡þÒêËïÏö•Ã,Š˜Cy±ïʪwÔikíUíèÃ7sÐÎõÄ(W^ó± ½/ªÏ‚Ú·p‡O¼ã+Šsc¨Ê"Ö5·»ØÃ~è›ÿÚÚ†nîÇ”ËÆâ^Ï8PþªrW4†¼à·¸Âuí_›}lŠíó—xÂ>osiì7kÊ_7/y¯~;¿v]m¨>¿î²b휑@óè9«ÿëÖ‚[G9 ·g¼uàâ»VÜKˆÛ3ὓ¸©ï)·Çâ›+¼–]n÷œU=%Õ›Ò·1S߇“Š t?®¶Xƒ±…¹s¡_šnüî|Ÿ'Ρ_Ì9°™°‹uÔË.CAÇññ¬l¬¼•-/@‹ªkµÊ)­û]‰[Üoq A\±@ @""±öýM29ÒÅ'súïÌþsfæc"¦ ¶ˆ:‰Eƒ§^¢E¸ÿý11ÄÑŽ+x3¦¤%<_ãŒç2LáUÄnÑf±†<’hVÎtaÓj»4—“PÛÒª<ÍzîIˆK¡£ªiYtkm¶Þ ¨îœb£ª/¯1cXÀnp…Ö]‰]ü`åÄw”#ÐÜ}ÊkÛ Òj”w{Ê{e «Ord½ºæ±æ3ëØÇs×àߨ&öª~§ø$VJû…s,j]#jm®'¼öÚÀ6Žqg +gáPµ‹kHkÉjýCZS^5åÕϚĸÞ]dôü|AhßÝ9ꔤÇí{ ³‘RlP9mõØsÿž4¢ÉÓè‰árúgÚÖnÏÆŒÞï¤ê²wÅww/Ò’ôjóësw·Åok³w<üˆšÿß›ˆúºµ†Ç9ß¡_" M°‹uÔKKQàO!Z´‹–õ;Úôú+¥–©i¥Xi^F-)³ ‹d*ZA—MEA´kÔ&hÓ²÷sÞC‡3gæ\ß9gD$ ®á6F(ÐFF-! +’ioÊ!‹i†LAÞE|Žˆ¿×gèų3¸Ç}'ôáþ6`f¡Kà°Ÿqö‡À5¯9HÀ„EË“¦N”Wïû$¥HçQ„}øÀ<{pý…cXàÜʰ%˜'}¾Æ¹iÛuXM8…Köqoèw›oåoØenUŽ“g¿Î)Í{ÍhšKm1·‚U_3cN:ßëmCN0v\á!c?2ðá™ü¸ïäå€n”ï8G{™kÛƒxu³Òuµîáœ9èúá‰ëüâ7l°/}/ðÀŒjÌÓázK\GŽß¦ÄŒêâîŸ:ëU¬üŠÌ"à ÖwÓzY~÷8™=äÝY¶u8~šÊ̳É1“ò¿ì3&îþµÏÖ˜¸ûÚ°Ë‘6¢£ÇË3Ãf–â{Sß{&ÌY0ý„-:Î4™z:7û¬›óïýß8³fÓÎûŸiýGþ‡ãv °‹mÔËNSAÇñiÃ7.\»ã|ŸÃZK¥íÁB)-±R‹J@Â% Ü!„! Pƒ7ºq£!7†øNú›trÂ⓹œÿüçræcLÒ“ÀsIx’’òDûÚð +íHãeDòîY›¤5¾ ÆÄã'˜#öŒþMŒbu £„×(¨^FrÈH—b¿âßð y/&§vQ +ÊÛ!¶Þ©˜¼êŽïEÞa׬™2þ”òÒ7÷RC¡Æ `;²¨<{ø‹Z¿íßÂrr>æ +§Xű­\ƒx‹7:—~|ÄŽd ㊵ëªh=e ÕgŸéü'sÄã‚=žà;õßhÑúðŸöÊ[µí~¦0‹¬h/_pFì#´R¿Ñ>êz×ËøÑÈgα«÷9¨õ¬ãû˜ÑÙöIè±kW´W›c #:ÿŠÞIÍ;‹0ÂÅ•uÜýÄÝ“¢âí™}VzweXg¿¡ù osÚû[R{7Ý·Õ®º•öd=JŸ5Í;îßón݇š¿ªù¢ñ/wÎ4¿… ²¶@{+y{qÿû}»ÿ@J}þ$íïÔ8ÿ¹óº_l\z°‹mÔËJBQà­Ñ qÏP³&AÐ[ô ‘™éÑÔŽe¥©!•–$]Aº a4èQÑ Y£"hРI úþ‹6ûœ}]{Ÿµ1&hŒ À,Ò‚°%ä…1ˆ³}Ä#Ø‚¶’Œs`VàÛ߉1þtá¹u{P€,LÃ$É%Ÿ`L–)X†+ø€;(±_ÄÓ/CiΗ`}šk¦Èµ¤9f–àñ î-cÚÖPþà=׬7e˜g¿ +Ÿó[…r¯ûð„±íЉç7¶×gcà.à ^ЯåœÂ:÷)g6Ã*ÏáÞá6×"Ù7Ç¸ŠŒ³Ì˜7à~±Úý=Їç Ï·ÁØoàn3ÎÃÔàŽ9‡ìõº³¯¡ï÷°Ë1užÝ3÷[gœÛ㑺ÆW°¾£×¬uÞºŸ÷¨ß¯Â|ѳÈ{ÆK9iþsMÄ)É|ÈsŽ÷Qe抴m3fÙƒäQŒsN±ä·æ±Cš¯ö}Œ‘®ï²Œ³>j‘{šâY~cÍ·Eÿ¨µ¶Ž×¹56ÁeÌ:Ã6¹ßr×õ?¢õú/s>½sûzÿÃâôÎÊŽ°‹mÔ¹.DQÇñcí4 +o äÞ@¢ô öe˜Ë0û؃Œ b !öJ%!Ô*o ¡ÑøøþãÅ'wî¹ÿíž{ç:çšÜ·4Z¤íh tJ¿ŒHJz¤ƒkßúfX«ɨ\:—W.ÃR§óY G/}¼>ú'9ï’3˜n“M¹–æýcÔU?7Îõ!`-Íú =­/½-&#÷š¹Þ¹ü7ç +Kœ+¨Ñù¡ÖdQ–e[öeMf™oZVeCÖ‰¹•wå±gr$8—S¹‘OÅUJ©~?’ïûM1»Ÿ/'ÏŠÑÁ}Pßf˜e—¸,ñ~ß¶˜Éæ;–'Õ)–*©¦æ1r÷È_Âj0¿?ðL^•_!µR¦ó;ö'Çó:á>÷¨3<«Ö¸G¿žÂPÀïñì÷$ÒÔɰþ8ìîýíHïI2Ûœ¬È.²ôd–413Äû܈šaÿÿòïpÌý}¯;¸ÞEþu©ëktÿª3¤ÈI"ÄljµûŠèqÍjÚ –ÿgÖðûs?ÿQ[o#ÏzÙ÷£þ;Ñh¾²oß0°‹eÔÙ.CQàÍ•gpám<‡G ªƒªjuL«­SÇLÄc‚Db¸1].$$„„x×â_é¿b¥.¾ìvŸ³×Z{:ι€«ë¤€„ž!£Ê05„Ÿ…ÆÝ49ä½($  +ûð70Y>‹)H3o‚ãìAõ²ÎL¼»×ð÷pÏð°ÈñÛð‚<-h¿áæaŒûRa}²Ž§ðƵ•5žä\=þ–œ—ð¨ß}Â+ëÝUØ„#Îã®`Ïäqõ3!FM|Ÿï,±•z–á˜y~àÎ` Ö8·]Xa}U®w +f¿ÊüŸ3òT2{Zf_Ö9çÌùÏÿ?3gæc¢Æ˜ÑDCb—ŒÔ¥#MÉÉï¬âpí.ט'æÅÛ¼¹çÈ›&·•äy6>å½w÷®ž‹ŸBÒ«• &E®’´¥Ëœf¥,ÉK‘±Ü—ˆ±±K²)ׯDDsŽ éùI¶dMNåUÞåQY¿:Р}WÎäY¹"Ò/}z¾!Ÿ]–ž\É%}º^®:Œã@öd…:5Æ¿-/Ê?,š_¤"#2¨öy#ú_È=5míuÖ­A.W³Iþš7¿–gQVe_ÎÉ{B×cÆÖf?”Yÿª´WynoñmºŒ¯È·œá»ÄÎïS Þ²ûÄí¥´'C\v_T¨—C¹PǶeÍÏ=>ONš\vwäNÞäÖ|ï‰<1Ióû ¸1§0‰ s´k`÷Çk–%ÎõóÏ¢;OöŒ»ÿCø¿áÄCqã!õÿúO|K!—­°‹}Ô;NÃ@à…žÑr Î@͸$˜$¶’€“—ŠÂ£AQD< + ƒŠ“ð/ü#F+‡â“ãõîìÌzbcLƳôŒ# 9Xƒ"”!†„dŸ{°J+ä‘Ü/+Y5n×ÜÃ^óŒã©¸y%ç(¨Òæ[ïÃ&4!:TXg‘5V¡M]8„S8æ} öàᎠpoðO|>àž5%æ5aüWc&¦af¸vÀ™\„yü~€˜bÞwpÏðï0‚¾ªÁž[ƒy5¿ªj«;s$[Ï\òülÝç2É9滩¨³’ñªq~‡ïdŸ9–øžmïn+²®ìØ"éƒuóÛç¶_tIÏʼHÍ ù;rÆ}õ\æl0NZk!ϵÏs²ýö·¬9rrô+Pàäß4ý”p,PyÊ|‰'ÿó,¹ß´ïˆ&5Ž[ÿóø +|¥°‹…Ô»JAà_Àǰõ-|5»1›¬5‰¹í&ë&Dð +¢à‹Xˆ¢"VÚ‰…`%V¶þcþã±ø˜Ì™û™ˆHVDf`feX÷ÁcÝfb!´`Ö`æ©äØWeÉc›OY‹Ï6cŽòNÏ‘!oÑ}ø#æ¬ýj™3­²\‚e¨ò¼'p,ßàž`"æ§)$ŒE¬ïÁ\ÁŽ`Ú\³e®i~ס—ð!’Gù·\¯Ïµb®Õƒ.cçMX¶¬~ ë5hpÌ æŸ›„gÀ˜Ì5L íîuy„WÆ^àœãkÌ“ÎÙ¤:Ës¨ûѼČ›<3?§°O°Í³ö(á8=ƒ­ÁxÛ™?eLûÄÜwÊýUxçš÷˜´­Jæ~J2üVŒ‚#¤¢Cãú^Üq:v¥©›ïÒ~úì×d^váÎ`‹û å÷{p¬¹ìµË¼«MÎÛeÌ´-R‰u}Cî{¶ß¼÷G›¡ÿ-ÿµÿøÖv=°‹uÔË.CAÇñÁÖ»xK ;¶Þ‚ÞÛÓ›K«Ú£U´B©DD"ˆKˆ $"<‚D¼‡ßÈw’ÉI»øÄ™çÿŸÿÌœÃ7Æ,ȼÄ$-YII‚˜“‘šœË›\¡%òÒrãˆO"îIR?C ¿Ž›Ÿòb o¾“0Nxk¸|Ûg@ÏAdÝ,ñE,³çmy1fdRäDfeJ¦eL¿ŸI›¹ûr(ix¶¤'ÌÙ#Ö`-»fYŠR"¶FμË3w`kìʦ„Ô°w±ÁØæ­zí>êÌi1ÇÅlÏv_òmÌ茌ëyNñ_¹—K¹–Gîÿƒç#iJUVPe]Ç®×—cï\Öas»œß«|É“Üòü#ŸrA®=‹zv{¯yBj¶¨r&î§É¸Ëô˜[á¼+äuÑælëä†ì±€Æ¾aiÍ&›+JÒ1X¢4çŠúhŸ$ÍSŒý’mâ䲟ŷx.÷ë#νxP€ +ìëˆó,ÒƒùûGDú>p?#EûÞÁ)½¡mÆ`Ï/p5Xá¼*Gyæ[c.0i^³¬G‘×<Çê<ë´Ê÷Ö¦ÜÖ?KãX­–:¾ÎZêÚ‡`÷ïp{°ÉõïÀÁ!l1O%®Áêhë®Â1ÜÂ<@v9ž¥Kx„oøAü^@Üà8òŒz±FGsü ¬‰ŽßöåµÈ½l°­ÊucN¦aúÐvÃñuö·15æT÷³Æ9*œ¿ê‹W`>=æVkåJël¥XK¿TiòX«m…y´~‰6vΓÒ:³9®EßÛ7ÒIBþ¯!Ã=5š9vŸÀ'òŽú®ñ<…ö{æÀÎZÖǾ­hWïõ`ßä,µÿ+LÈ'úÝYë°‹uÔËNSQÆñÅ¥”‘qÌØ¡#æÄÀ 0àx¥´ÚªèÅUÂ%VI¸&Å ŽÀ`œÈÀ‘àÄȈ„ ÿߊ›“0ø%Ýûì½×:k¯3{nw†0‚ILa\ãH&ø=†iT°ŒyE)¼ˆIÉpLJ{¢3gPÓyEä××f´nL±²A>y—¼æ=çt,~: +ÎUÜ ½ãêø€?f­Í}f=fÉ#³ö3枘µðØ>a{øË܈ÝòŒñ5N±Š²Î.ùzÍ'eB¢ß³xE,¨NÑþWªUEósZ;Œ£5%í™Q _j}o±ƒüÂ>ëª:¯¬˜u­M¬i®¬˜Îë×Àöñhb[8õ´cœã7u뢶½ÔøŸYÇ.udü“ù~žÅ;¼Q~«ªOUï^“ºr¼bßöÁw~_âóÛÁž%Ýó–òª5õúùûûzŸ”tŸÞsÞ§Þ“~¯~ßÉÛýÞ-êü†òn*(N.X›“(Ö¨„1Ãÿˆ÷yÖþ÷w!ÈÅM©ߨ ñÚèëý¤ßO©[7Ï.u×UåY +Þ9‹™‘ôcÿ&Ä¿ño‡Ÿn×Üu¤Ò°‹u”;KAÇ'ÆIeoe!؉ß@ìý BÌCs^^.ž‰‰Ñ€¨Á7¾Åˆb¥¥¶‚_ÁÂÖÂÆÿèÉrhñcowggþ3;·"’”_R r`–ã4¿óEà”€² M¦þÀìeHФyVý –èße|sÆhQfˆÑªçç,òV9KŸñ•µH[þÆ-1¿*h‚mð*9‰~ˆôô‹ÄžðÝÆÚ>öžÁ:X:Ãú ˜ØË&˜§ÿ‚USÕ\¶ðH¬Ð'bË*5©ãµh£:‹`¶•eŽ•ãcœk²ËUÚûô§¾×À18[ÔPcL¥N›=pÈq‡¶Z‡6Gw®À9¸¨[B¤k u‰Ç0¦ë ëÚ7/ôÙ¦æKpÄx Æ®SüFFq¹t |ã#“Ø{¤MƒùkÎ÷à„u ˜·é+“¿Ïû)YkžtúÎ`þs¾H +$lëQ·ÖêܱFÕN¿¸D{Û!9 íålÓÛFoÀ>0šÞÍ;êEнÃ" Üu<ÉÚ÷Q[“wíKç?s-j°u„çá÷â¿÷üO?6ßÕ.k°‹mÔÍ.Qð[*ÊÖsØXxïàl|tTG;V[mi«¨ÖW|‹Hˆ„ ‰ÔÒBDll­=€ÿéüOÜL,~étîÜsîœ{æc¦L`†â–9JCrà“ p¬¹jšþ‹Ë|ò°eÈ@f)Aºž$¹äqm®s>4_ó†ãé+±RÌ/qŠP‚83&‚ŸÈ·1=oð€ë/èÃͨrí 8…ø„Wx„ Že™CéÚ+°Ì|yZ„f»k“5*òÙÇ7  ÛЂºõ\‘u)Xqõ¿ŽKîUλp[|w]ÓÉóë|fùk|ÿ2s¯Á1\à kÒf¼:5CÆa‡9eÞj‹÷éE¾h†Qó ÜÇØ;ë!¹¯ànaŸñ+ÔêÁ\Ô+ƒQ\ÿàž±×Pem/áÎYí§ å,>Ç´&r&ìýÕ9žÅîãcTY'ôÍ÷È Åu­ùBúWzÙ¡p¯»\CûÐâ>ùTbý¤¿ÑèQì_l̘AÔ«÷žQ³öF™±<ó÷ÝdBuгA¿='Ä>/kz>è¹4IÝ3ãF¢Q°‹u”Ë.CQ†W<‚‘ÄÌÌÀ3^§Ôé-GO/§7‡¢(­¢¤1(bÀL\ffb$1‘x‰©ë¿Ó•“|Ù—µ×¿Ö^û""Ë2 NVB$ÀȃÈ HGùÅGhü§©íc¸Œá4Hr>Á~’1S#Æ.ó*†üWC„õìØêd¨å©ýúdôÀ§Hd +L¢ÿ®Á.¨0¾¡ +¶Á!hƒ–Zã1ŽUàÚ.8Mú›˜%°E6û%ÚÊ àœñé€3p v@kÊô±~¾ÊÓWT•^‹Z=Æ­ƒuæ_a-`Ÿû²ö@i€ pGL¾5bÖqß'´7ØöÁê|#]óè?€9Ì¿ÒÇø^‚{pK:÷bØä>¾á÷ øDßÀ Æ‹˜až÷a4¯ØÖY3{'=ž•}YuOôöÏ7;ÂÏ%i¢ï]ÀøÏà œò ³ +ûþ’JÃŒÍ]vdø¦Êî²&Ö¬Ïóó™—‰ÑÔ$Cp·Ç§E&fEÆ0÷Û;ëT å‘a ì¹æ™›Éþ='„ý¾GûfÍß`ÿ¥ùû3~«óI¶°‹}Ô9NÃ@àqÎÂ8G  ¦á +ˆ›Ø !Æ`³ˆE,bA@Bé(¸Ôš”ÿG#Ë¡ødÏòÞ<Ç6Æ,š±:-9l{X…ˆWŸýš·\ÂssîŸ'ƒ>ïˆsT{Îûgø6Æ›‚Y˜Aû^á”ñöz\7už;e˜ïáâ-öÏ›·Í×ÉX¿ò²NíwDÚ73õë}뜨­ó 8õé{ÍOœý·çkÈ=N¸NèÄëÜN»áðKã!÷ã^ø^íùµçJç,ã;ûÂþÌS›ì‹÷ 5ôßr¾­UßW“ùU{Áu¢Šš\úG(^Ï`ÿ åÿП_Ä©Á°‹…Ô¹NÃ@ÐùdÈjÇ vŒb!@Á* + DA…@HTÑ@AG‹øîˆûäÇ(Å‘·ywmŒY0¿jÊ"Ùó4¡ > §]½B­B™ˆ!ZÏÑ&yÞ$ikÇ03f¾®û/ÏcMKP@ö~CêC¦æÐ™#Tã‘v>Ù¼œÃ ^඘8u2®ŒRêAÁ,kÀç]êS®Æ3KËX» p7p°Ã±Ma°ksŒÙöŽ˜)c+¸~{ìã naŸy#öu —ð_ôȼ‰êg̼Xvl{÷ð +ïð Wì§`홫\«”ëQBö™Ëaöa +)ç¢ ~•ùDÆ:ºÿ„ó)e”SÆù„´¯!Å×užytÍØä›šËsüÞá…ñÕ}äæ¹4çQ0«ù,Z؃¸€+¸[¸†s8†9㶸÷–dÏkAîÖÄÈü×í°Î¼Á£éÿà^á¾àî¹Vò­CôPþs ã¥öœ±G9÷M2P>{ó^ÄÈý¼žyjÖ×÷nפŒÓ¼™yϺ׊cöÛŠM½¾×;>4sö{Ò¹kç¬1çYȹOYßg¦}è=ÝïocÄ^v©q‹ï©v‹»âkûÑ<²GùŸÿ¢eêX߭Ȥî°‹]ÔIJA†áòžÃs‰Æ˜Ét'1ck¥…à„ u¥".t-îô‚—\ùµ¼µxHzø§ªJB›!„-©I]²‹š‰:Ò—BŽd&9Ïä«Gqi¾êÝ}È^R·Ê‘Iw–r#÷r)ê§ý¶ˆé‘³õšÓïc™Ê¡,˜cÄ9¶Ëý,ê+–Eù}Ýãúèrßyôã^Ž™· ÇjþyïÖ6d]ßßeÅó<êÅu<ãŒõòÜU®sêŽYƒ3¹àó\îäM>䙵?a¿çôçš0OÕLjë3j/P’ïZ®ä”š·ò$/ò*_ò#ŸòHm¯ Y‡˜ç/¨·dmKê¸nIo£Ä0á¼9||^|N¼—S¾{Għg(ÿg"êdäMù\Æ¿Qó3_§¿å6ýæìÍŠ=X±7>»îÙ½ô‰ñ¬Îã3Þ&Îûá÷«ÏyTcH\ú_Ò¤ÏmÔ°ƒ¿û¿ô×üV°‹mÔ9NÃ@àÉy8@¬qlÇŽ—Øda1« „"6AACEDn@MÉèy£¼'ý²(>ÙŽgþefçÜšsn6¡ =¨K;¤ß{TÀ5|À¼C‘Ó‡˜ÏÆÑûÐäé1Ær2î#¼Â FcëSý1ç&¼˜'… Jc ‡pûP™zl)k)yzV|åîSÂy#*L߉¹úy»p pSÓÿܹΠ+ FgÏ¿p5s'”RÉ~jÆ(ÿnéœ9*^}Î3^} oð ?°àœ)ãLëžÝò,LÌzdìµ2uŒ¸æcŽmد¯ã†ñgŒy/ðé–çkÎ1ªW{¨û¢ÅötÅØ—äs#Æ9æí™>÷Á÷üó ç<æÁÖ_à¬3OlòÛº3ÀºWàœ‚!Ï¡¤}ì€#p>À'x‡¬'g/'༃[jåÌò©utkØ|ìeÄÚÎYÛ™üÎÔˆëmÖZt­=j΂gãk|¯àŽ}? æJõýõ›éœ¸ ´¤šS‹Î„=ƒžŒÏMB_M½{ÎÄ{m?:×aÍ¿!Õ]²w30k»Êß™û¯õ³½©ÍÞÝÈô—?«p_Ï5bl “kµŒýƒ~ùSPC°‹}ÔË.AàòžÂãx ZMOééÑ—1CbˆK‰¸,\a1‘±aiá=<ÿ‘ÿÄIgÌâK«ª®s©Ò㜛qÎÍÂ{óžþØÞbÎk>]×oÊöbûѼúh?ãjµuh¾ßß Ó÷ÚŒ°‹m“O/CQů/K´}íûÓzZJiÊ‚ ‚H„‚",ÅÖÒWè7gäLÞdÒÅ/÷Ý÷Î93ïNa1Ô,;–ˆ<7A‹4¸À ˜†0·€õ‚Ä « â^bD }~M:$1ç›ÕDÜ«OýÞ&r¾`Žžy¯ñcîcCJ_B—µíƒ[ðŽÁˆµäÔ%&^Ìøu§àŠëÐÄÎŒ^}Eæl9Öè¡`>©e.Àxgï¥ß› ¤ï®É“ººVwœÓß÷c2¡çoüÓy¬¿à‹ú=°múP2·è/ÁØàR§u(k*ø< vœ€kðžÁ÷ûˆ¹úæ|N4VIrö1gl9û +>Á ¸g?À#óJÝëô[±Wzõ?k?ußvØ;¬ú‚¾¥¾-Öë}ê]ê8¢Pß ½VkgCµ:£ú¾êymÍð¬çt–¼Þ{ññm½~¢q4¾÷çg]½êœüÏû§LÍÔ°‹}ÔINÃ@Ðæ‹cpÀIO1±c†ˆ bF,˜,`Ãö¬¸÷à—òK”‡Å“Ýêªj»çÜ’ûµãv[i]¥VgÌW¾ë„¹–ìÓc]ú¼¯8fÊf¬ßŸMÂïËÍš +wD¾—1cæ~b®W¬ÅÛÇ MNº¦œì:mõÛë÷²û¡Ëø‡¬YÅùKÞ™¯Ý‡vžÜÄ“¶ì{žôœÅNäÄn¬îuÝg)K÷·²k^¤éæ´ÿÿ/јþlèZvÄlÏYD°‹}“±NÃ@ †ÄŒÄs0ñ,ˆçÒ”¤-IHÓ¦í-110 ±òl°0°ñ, ñ[ú-YVÚáÓu>ÿ¶ÏB8 !œm!=’8[è¼_ +. #°_!ì‚pû|‚'Pœþ©#3ô7äÐ…ú¥Œ}"¸3Pšå|jp^Áxaî ý/YeÀUâ·ÔhéŸ ­!'C¢qÔÖs‰[P{îH¤Vɵ"šßkÅzÖà=?!ì`•÷ÿÏà>RóÎöÁöïìÅÜí‰ÑWÍÒä¹b¯iÏÙI-}Eÿ‘¹,©3cÌÊP­Wò˜RgÁ}Ã52ïÈ"c¯™cËû5ïŒiIC4W=÷s`ßK)èß:´—KögÊx]ï®ZbËÜ艭,Û€þÌÝÕ¹Ó9±ÿÄþ);³#÷ïë»ëœÛÿÛs$¯éÿ†Öp.üÐ`°‹u”INÃ@E+\ƒ5÷#“‡Ø&vllÌ$1 !!öHìG`Ç1Øp$~)¿”JCOîTuÿº:"2”¿’ íö{´óÁD$=xà38ûXü€OðJóÜ8`BØí™ÒîQ_Æx‰³ëÙ¨À¬@C[âôô| +Ì÷†œòlá˜3çœ:3®ÕVsFMŸ‡‘ЯZGÀx'` +î Y'1šˆq%‡à l@Øîõ=L@É^ÆÌǦ^Äú^ëÀ‚Ô à~¥ fL}ƵcÞ'g½¸ƒ4¨35µF†Üärƒß;KpĵÖÑ|ÅÜ-øÙ™‹ìöÀÖ÷ˆ}sjüy!/-ÏÏÇW¬[ð{Ô¯¥¶¤úé÷×Ô¬Á¸#§Œ7¤2=zÏKãé:Î{Îû½â¯ÞMà jüú˜~k¯ÉŒ¾…©]ÒÓQë=žÁ'ø¯à5Wô(¹Wï?1èÝMx'ûhy†Kùÿ®¼Vߣ¾û®2“³³¢ï&%ÚOjÈè7â?#:k‘©¾QÇoq<óðÈ»­äoÞ†²=£úÑ5Û!êSÓÿy­†Ã°‹uÔINÃ@ÐÊŽp®ÆˆñÔà˜`‡$HÄ ,`„ƒîÁš“ä7ù%J½xŠé¸kê"Ò‘­@_Ú×7h³CWœmJ ƒ&p s8#®;H)ᾈ4VLQÀ· WðNþyÏìIYCÆç8¨/1ïf¬Çï2vgâå0`/?Ç<Ú§ãºÆ+' 8“+3ûFœgû°cøº§p7ð%Ò[u@ϽU¬=Â!t±.­¿âÚj¾3d¾Âôïë9eÞgø„¸†cTfNû˜1¶Î.§‚ýÁã=À¹©¥fÞ†ÏcöÖ˜~j“gÂ:Jž³òýø;ù†­Á+|/ç&?p —²¼¿Z«ž«Òû ç¦g¬ç5§)óé¹ë½Wö¬í<4‡½Ã±É›ËßKÍß±ü¿ëm¿¥„yK3;=Ç^|íwð÷œsjâj­¯+îÑÿQ°ÿ÷ý6}-c°‹}”Í.Q… ïeé <È0mº§ûÓÝÓHŒ‰6HüDB°‘X ±’HXI<€%O`ã\s**7®Å—S·þîˆH[DæÚò·½Eæ"Äâ,RÐéEÒ£-']ú'DcuHà}p +^À8ȨÉè—š6·ÔÄ×\ÉϲD +úåôõ¿• æSýj¢­³â:Sÿ¨®fÝÐ7ýôï«àÜ€[p.Á#ûã¿GÔÆëÐÚVÀ:ØCöÙÖïíçà|‚wpލk"=jhÓ~hŸë9oà<ƒ°Fg™h슽¨h÷gï±ö3°E]ßà}·Ç9O<ˆL~‰LÍà9‹ïiØïd¼[;ŒWr©!3óµûÞPw®9Ÿ!gæäwïcwÆÞÝkõí˜ss¾;Ö303 +w]ïP¸û%{»ÉyÖ&?/¿;ûìå.ÏÈ‚8¶–Ø9š«þw$þG÷ f + °‹…”¿JAÆ'ú ¾O!Xû +crwI.çÅh¢§Á?Eüƒ¨DQH+ZXˆ`-XYXÙÚù­÷ Ö‹{3»;óÍìî‰HEþ²D¬¯ þ¡L=l܈ûA4A‹4I "®óõT +òUAŽÀÜ}ú/¯Æ´T‰¯W5*QÛi]mŽ-~¯€ÔÔå|Žmêr56ˆÆóí:5¨þЬ‹'¡»Çiéƒkð +žÁØ[`lP§jUÔ·Jºm=®–mp`ÝèpsgàS¤4 fÀì®íš<©×=¿1s\‚7ð^À)虾¦½Õ>¹¼'à)×&ïà +df_ÂïAž£ô-29nE&ŽaÏÂÎÁ!ûØ‘ñ} IäÙ!ër¹nÀkxd®ŒçÒã:½£5³ßž{LÛÞe½ÛŒï¨‹Ý2dí™ß÷¹3^»’¿©=ö¥Á¹>ÏxÈù¢¸ú6õ¡¶ê´½r¹ç‰¾mókÿö²€h°‹…ÔÍJÃ@ðµ'_Á‹G_ÀÓjŒÍÆ4Ý?jµÔ?P<‚ ˆ‚ ¨èÅ“ xñ cã¨òz2–À¬Ãã¦\Oîe\Ï›:ËyjÜÈÌ©šÌ8 X†,2w©¡Í\êÔག‚©15ñò4ÃcyßRÖ£¼¹§û*yÃ=ÜÂs +&ŸY +†\/@‹u¬ðÜÖ#5ta¶8/ðžÔæÜÀìÂ!Œbì†órÒõ²_êVZ_Μöà +à6ÝwÏhßdÜŸÄìS` 'ð†|†açw°jö@ó’±GÌAÔT¹Æù' aüv\¿Ç´gµÍ[ë‰ Ïú/á±ppÏpļ°†»€ ¾wÛÚ Á¼›‚cÚÃ:_û]Þñ><Á+¼°šŒù×sÚ?RW‡{Ñ2{Ûå¾}ôëèåÜæs‘¡ß¦þ#Êÿ»®|{cTþ¶{×_w…Nß°‹m”¹JA†k@S_ħð5L|×=œwö˜]]DDcÁHL} cñoú/·h >ú˜êªú««GDVE¤ê` d ')h‰·Q;µ­soÅì«]ƒ´šÄÅIÀ:(À>¸OàÜ‚° : æyÆSêôŸò\Çhˆ¹v>+Ðç7õÝ0þÃücÚ&DÏ4¹.©áì2Æ3V›ç]m»Ô\pž± ëbÑ…6ºŸh~)cvLn§`FÔî|öxϤ$9Ç-Ú¨ghòîÒn@cêî™:߈Dó{QÛÏ励2sGzOVŸjì2ß¾ÉwƒpÄuEºjýl¾gà ùÌ`üw¬Ñ¹å&Î9ø„í2@M¢¬_ë2 ^­ÆW=ÚOÚûÎþ¼ø<äÁ×J^áã—ø7PÉ´ß´§KÆðN÷dúVcùûÆœžK¯3šóZäžZÝwíoÛ;a¯VÔ—s¿` ¿ás ,bþλÈ[ߎj¯Ø7kùïóËÉ£°‹u”9NCAD€€€Kr.Âlƒý7cÿÅ,± Ä" 6 DH€-$D 9w Æ®’Z#<ýÙzªºgæ;çι:h‚l‚° z +¤IV‰S|í†Yß +P\Ä GàÛ¹èÔæÁ"ÚÕdlìCZFÿ?=y‹LŒðý50`Ž^;1ël~¡ÿˆk;DqmÖÌûÝ{Ü»2:}™ø®!ebî5˜qi°.6y +í•1W_»‚^6Heô5ß')ø2¯!csÆ©Ò(IcÛ'àç9f'íñyç&^ë³ 1ûÝ)ûçô; O?·NJã1%ª»òõ9Ý‚ð ®À9¸§œ×ùípî¼'°O-³|Ô¾ÎK÷Iw߯ñïë\ƒ3ð~Q§€wàF`‹þSS¯uîÁ#8æ˜îXxwræô~À¸aýü¼îwj´S7ÕÚÞß¿ƒÏXKèÑW‡Úz;á¿#|»ê‡óê7,/öh&°‹u”ËJAEK$+Â/2q^é¼fŒ&ADDÄ'‚¸p!n|l\ú "ˆÿàÂÈs/–C\¦«º»êvuטYdfmÐ=°ÎÀ1(AR’Ìùc[Ĉ£CŸ_“MÚ9èƒ +¼š­™­î›µào}aüß s` +‚ËÕqù"3Y¢3á¼è‚ØCêHqù¥wL=SÖfàâmÅî»oàxLFÔ²L¯¯oν=’74/Ó©\Ø¡Vé*87r”ÔTr¾pûÇŒQrm ]"»O¼¿¶'à<“K0k䑞²Q»ÜÝWI”Gšu.Ù:£ê%}²µ¿ ŽpîÁ'øïàÆ}0aÕaFŸj§ûW}†.G°¿ï?u÷¨a˜»à +¼áÝ·¨ã‰¬CÆ3Ô¹Á-u×5­˜_göo*0OÝÓ×ü*®ïíàöê- ¹öÜQcÅx§àz×ÁÆ/\ì·×kü?AýÛvlÙÑ?üÌÏö –w°‹u“½JQ…G‹€¾†•o`gã3øB²ùÛMv³š¢AıQ°QÁN+±²ð|[=79‡ŒK,>Ø;;wæÜsçšYÅÌÊ  èƒ!Øh‚©“¦‹×l\£Âc>¿F´·ºàü˜Íž™•VÌæ¾Ìæ¡¥´ŠØ2þ}RG +bÖˆ\¿@Õõ‘Φ£Îœ*× û·X³áN­cúïè´kpŽèUgÙ W;æwˆ À Ùí)¾ýÕþ6‘îÿô¶¨5è9—Ô{ÁþÒ—“Mê×9t†Ôåeì¸3%…µt).¡Ö¶çiH»õëÑ“ó¥¡ÍþÒ›’ {‡¹[\çÔ¬}Þ/¯Ktxîcða6³ÖÀÖoÔ+ò@õ3“¾ÌþÞ»¿WÍ’÷MuØçÜðNéKÆ=š‰ŒÚnÁ3xt¹ò(v½5¿=zÖ·É»öo&.ò>À7|Y´Ñ[µ{ÆÃ|Ý€wðjã¹Îm2—zsÑÊŽu¢uµ€Þùèÿ/”XJذ‹u”?/aÆg"¾€ï ÷Eô*‰–»[{ûçnw­C8ÿ"…F": +5Qk$J­BáyÝ31^«øå2÷ÎÎ<óì¼+" Y$-€.‰@hXòh=o=«Ëud`œˆ“"Ã#"cs"wàCdôSdhçÏ`t@Ì:-Ó§ééÒú)ŸI<}šÍרæiì ðÞÀ=8»œ£d¿”ĤM .ç\ƒ+p +ãQÄü„Ć”~eÞ¹Õ¯ó:Ø—àþŽã÷œ5ÎSŠþöÁ:cWc™äÆG«Kcí¯±Îï´v½>•©«ÿç¤G;`‹:+ÖÈäïüõ®ÒÛnM®}Æ×­~¹ûà>Mƒy0…ø–Z +¯njèÔ'}/ºGú^5Gk©w±9ï±ç öjƒï"•Ÿ= ™ërnÀµžÒ¿’=ùÿNdR¿óªUçszä{烀»“2د’šûÔÚa ½— ùýmñ㢱ývèýz_Ï¥°‹uÔ».aðÿJ$:oâ…D£ÐyvŒÙÙ™½±ˆË*ˆB%ÑP£Pi–ÐI4ñΗœœÌοÌ|×=ßeÖÌêf¶Aá}SD¬Èë·(¡Åä}´"Z° —ðk65o6½l6ÓƒÞçÌjïh{„]È!•9#WäÉ8¦Ér\êçy[äók¿0® ð€<5X„%@•=Á×áãÏŸ0Ã\à <ÃôØîyRöm–æÊ$§·gÒ¿€>ì3ë\Ár®ÂPÁk*d\s‡2Ç)…÷®ü~FžÃ÷[syÞBäTL¨ïòœ=ȱÃ>ºnê:ÜÃŽä«¢û©9s9ß{øÁ>Íâù ·ÌS”æÊD^âg¢ßŸ_ŸëѼåsÎ¥_¸/æÓ{ß`Ý9¼Â'Ÿ7ܳvÅÙèOmò7ëí¾ÿìË +ÜÁÊoplÿwÞ×âsùwY·ñÿ-¯“—}œæ›ï‡q¿á°‹uÔ9NÃPà œ€Ñp .À(¨(8$ŽÄŽ …% „”ŠŠ¥±tZ.@KMOÏÿ¢Ähŧä-ž™·Ø"Ò‘M§I¤íEC]H¡Ïß„cA›êPr½HcIdfMdv wø¿Š¾yŒ]@Åùæ\<›'f_jt ͯ5oÁ.•ÌcׯGðŽz–á`}op;&Olž×úBÌÎá®á +SÝ—ž«9ÄÎHÇum9k2þ˜nàuâ™ÆÖÑþ`ÞŠgÕ7ëß§Sx„Oø‚W8mîOÉœ™ü­ÉÖ¥c~®§uò XÓ€}ö,ýþd.—Æóm_—ß¿÷ì®àÐí“ϯ÷½à¼’qµ.û^Ô<ŸsÏÄÕxù½/)cÖÔ7c³â™<Éô^NÇÜ/]sÅç SOì$®_óçÆ¼àþÌÁ +,¢ýÀ\©‰mã„÷Ó~Sü·ã¿vdØ:Ûfnëww=&°‹mÔ;NÄ0`ç Tô´t\cK + +λY'8ñ&l ìŠ÷£ DAƒtPP!®@Í8üÿH£!Å'9±=ž±8ç†Î¹McÃÒˆºv +lAÛùìilÈûJhà^àǹd«m÷»Pp¼çš)׈ŒáMüœý"Sí’ +ÎoáÎØŒ!s&poÈk 07YÇó7<Â!k—u¼š/Ï9×Úc­Ç¦.-οPïe\A]~SƼ…{êrûD®Ë°‹Üã9sÑókžÃ>¸÷àœMÚKj¤¢×ˆ^%¶œd!Îl î 1FÔžÒ^Jî¹øœƒWð>Á#8dîå?º+cžñº*öËýÃZ+u$ŽŸÓ·¡ï8WàšßðÞÀí=ã* ë8ïf èÑâ8Àïg¬}€ög(hÿKÉGñbÍšuÖÒ‹šñFœÑ˜yµ¤£­—Øš¿ûײî{µ—n›Ç_ýñ|}~Þƒpžf`Ï~Ÿ×é˜×”=œ±¶&š{|¾ã¹kîA7Üm° &Ò“ ç|À%×*јw'=BÐ;rL½–9Åû5nx+üз#-ï•ß‘Töªÿ@ã}©q‡°‹u”ÍNÃ0„M_H›4M“Z BüJ€€ N\y Þ€+ÏÁẌ‘Ÿâx׳㵓ÂaaŸ½g` +2 ¿ùBñÔ|*Ï™%ä`N*Ð#p +¶`ZÆæÔÔÚ‚±žëT·d~e~ Æ£æ#xåsC–µÖ`ÁZSóך·Kð Àç«ùPíœÈÇ‚4̯“ü8¿äÞZÓS¯\wÎõ1o`ïnÁ¸§ßÈ5Ø1GõjCë¯ÀW{èÏõ'obî›±ÁzÖÑãŠã†x¼ã¸¡±>¨ 5ý Ö³xF'<»>ñcÇÌol~I|ß©?ç¿þ,¨³&=‰/À9çµiè<·ìá ÷¦³õ{ê÷[èÜKÓÔ}ÜXÿå5¾ßwð^Âß]O¿Íô›«_Œô#úÉGÖ¹nü/ø?"³¸4Õ«<ÑKó3×ûWQ¡°‹u”MNÃ@ …Ím8 +Ç’¦É$“Ÿm¢bÁŽW‰7@âH¼HïIƤ‹O­=þ÷LÌlef—䊬H*ЂÖ<ö‘ß5‰²}IjÁ@”OçkR²ž†Ô$Ñ'»³ õ8‚ðÍß#m«@ÌÓ:æ8#è·bŽÚÙ¤€â6Ìד6بÏY¿ežìÎ6¤ ²â«¾;p÷Œsjž¾çÄšvàÝìì|‚pÝãf—·cŽ'ðn™¯}j~OÉíHrMy`¬ëÑæ|x{ê›à7¸|Úûèbdþß‘út¤µÿ÷JñÕÓ–uh¶ëÙS§9ËW÷b✞Ù[g÷®]ø={Ùïªe-£«;Ñ.sF¯à ¼°.ÕäïP|[Å ü›(ô’ã›_Bßõ¡ºÇÛ.Æûž-°_°‹}”ÍN1 „ýÈPº¿ÙÝn)…ñw@âÀñÎÜyN=w3ÂX,‡O›ÄŽÇv’5³™™í‘}2#%hAÇoAû”¿8 q.æ +$êH«¦~IÝ‚ã¼ÞŠ(Ç%Xãåõ\ƒð^Á-}ê ÚÅìÜ8QW{š÷Ê넌ŒëliSþ²©Î2ÌkGÞÛSkúQ'îKÜwÞÀ'Ø‚wpC[rº‡à;ÇÆ<¿`ª9å¾Î7íýÞ™»g?¶Oõeÿ+­Éwð _Ì9pF:wùÿ  ÛgLg³0ñZÏÏ”—«ÏÚ®»À»þ{NvÝùݲGîôy;MÓ°‹u”ÛJÃ@†×;ßÁwôЦicÓ¦1Vm•*hEDôÊQ/E| _Æá˜,ÍÅGw‡9þ™m¡B؇=س$ôB;ÎÛ31€ÌùzúÚ³„´^´åb"NÅ…8æ>âU<‹KQ‹B ùµ³ïÏ÷kć¢3q&nÄZ,DE®äàãs|jú|ïâMÜ‘óˆôHã=#z2Jæ6*ò­Ä‹øŸâ{Œ;ߊ»ÍQ¢ámkbFIm¯É5šÄšKîO̺"ÇØÅZ.ûÖOéòÆ^ú›b·ºÖ·Í<ÃÇ¾ëœ sœ3SÌy"®Ð~ÍŒ§AAè{vœü¬ß9³Æ]û_è<%GÚû–jh÷ØÜÙ¿¶vĶοhýÒ½J÷ÖÓOèÚIÃâ6½OÿF»üºÞp×E×Ý×Ùäת÷êBnw°‹…ÔËJÃ@àé {IÓ41é%¶©­Š‚7ÜxA|E\ŠK}®\ù‚ ÿÿÀßCƒ‹Ð™3gΜfBHB´Nkdã›N–×éx +=J%Vu)u1©ãã2È¡†C89 8v—pǰ#( Ïšº+òöœ¸GÛÌù ¿!t:x¾Â)󿔑­·ß%L`ÎàŠµÅœ»0ƒ±Ôå×›¸Ç–Sɹž5öâžàÞঌ/X“±¾Øy'ÌÓÈùtÿ35×Ìx¦;x‡¸ežRj¶~Bç-ÿˆ†26f¾†ûdÞÎÑ—ž/`Ï)Åžp¼–˜ŠÿÕ#|Á'Ü3ÞbKÖ¶`¯¯ùœó ™Ôbñù>Äúã{ß©ø†Öi¹ô½Ò{ÕvWŒ_›;mwï¿;êçýxÛ·Âç³yOV¬[ZÿÖ'i,°‹}ÔKNAÆñölžÃ+2óPy(ÆhŒo  W.L\¹ó.ti<‚~ÿšŠºø…žžžªê!„v¡…m´œØÇí8m×ßA†ŽÓE}ž3ÇÛElçÒÈB–r •2’¹œÊï'RòÏeq»ÔÒ'N-cb<†°±)ò%[?}á±ý„Õkõ׌;Á‚çXó”vCm½äÌ¡BIì!ó[ÿH.ä^^äI®eŸñ¹cuZü{ä±yÙøŠwö~@ì%yžåSÞåVfäMë.Y·â—øE’c˜ÔÓ°sŒÉ]»yÙ·cÖä\.ÙÇø|Œ1˰¾Ö1®ß›¼Ê9«dœ)&äõgµb#~­V›wÅ÷7ò@Þ«°:Óv.üý°¾…õ{’'í:Y³tïÓ;Ðý#Gä﾿ë6Þßy_—í{æâø|Ö¿öó µÃ·g°‹…”½NÃP … LKBÚ¤IúKÛP +¨º $X`a`câ€ÄÌ̱t,\«ˆá“ro}}Ž}¯+"‰ˆ´Èit¯ öí°Ÿ”$.(AEt¹x##ràÐu ÁÔ.OÁõ ,Á ˜0>ߢe93æÐóЀ+ð¾Ev^Dv_Á;¾?±÷Nßu˜_󯺇ôÒÐsEª3æ:wg·‘3Wéú×gýS° Ÿ#p Và܃ z0¯e>-¿ÝKj³s…óP3︷}’ðFíÆÕ[»üÅ?ù­?CǘµN]=º7gfìÇ xwàœ÷°äï}ù}FÅšÖà™¬©Y†8õ3¡O«Å¿ÕÏ 7rw`uÛ{Õ>]RkÅ;›3^óÄù0ø:m_WõZßl2Ùœì %ξÍtÌg>ú²^¥!OÔ³ýÿ˜D5x"°‹”¹NAD¾‚Ï„Ýõ6öÚË-ˆ„#ABBBÀG@ÊP#UIMsˆàI»==]5=‡™UfV€Å–…#sóÒX0«`ÌÿÇrÎ)óä«8¿ +qit`›Z}PÌýï3ÿkkï´O ó&®NÚ'þužäqdßϋ֛Æwé÷¼°‡ë¬ï?ÇÝ í[ò:%ò[â]“V¼ÏºëÊSOÔƒø6xÏ=®¹ü!õ¥ûåùptÖ°‹…“MJA…+[Á ¸÷2ÞÁ;èd&“I&™š ñEñA\z·‚k÷ºõnÄ׿=¨ƒ.>fº»ºª^U—™ ͬ2°õY`;;¼¿tÖØ»`ÂuÁ³.ïäÜóûm´Ùô¹nÁ=8e¬~ˆ“¹uÎ ˜Û#øßf°^Á'ØÄþ سEí*úN”¤G´_9[Ùøüe_üAÉüj0'à SïŒ:ÓyÃüfdJ] óðyyä_È.ÝRÀ%¸çàˆÌ¿fN‡´¹âj!´ŸQ“t5Œ9$µó´Ý-úÕYÅ÷ <ƒköÿ <0§QЫ¸±Ocúžð_½ó}ò=Uýû×{RMÇNCß!Íà ÖÀ:þßYÓ*øÍmù·ÍEé4ªwÒ£|}žþíʼnó¬Y—¿ïïÉίeŸÛò -ñ£Ö_?wÛÀ°‹}Ô;N1à Qq*.ƒH6ûÞì&ˆW€@/!Z +W £@4P’6-œñ[û†$Å'ye{ìÛ+"•ˆ¤A‡6 Òï.uœ#6±t¼ö‡¾\Ú5 èQD·gtgÄÑq‘“@ ð/ð#“ã¬u¼Œ{Â5<Ã>à¾à^áJæréSÉuR¶5ÏÒŒÏLTl,Ú_Æüöà„FüÞa_e4ÌgvaK~Ï;e¾¥™W˜œ +³firìsüöáŽØ:®êu÷pŽTŒ­¹Üï.ánàŒ96œS›¼‚CxYZ†uXÁ÷›´w`Ìp^2‡Ö¢p´ŽZ ¯ç¢uô÷861s«aýrw¡Nç0ÁþWa íOiïamÆ&òÿ½øµ³VcÎVï§ÆÐ¹zWýÝÔ~½—‹Þ¼åÿ!þßáçÏ{ÓZë?oåv%°‹}”=NÃ@…q‚ˆSp9DìÄqÖÆ6Ä€B QDI4 Q-¢äHœ3ðV¼!“Q’âSÖ;³oþv#"¹ˆôA×°ïØ#ú®#¥Vêt¬=’žA÷U×Ú’:ê—:¢î¸Ÿà¼‚ nˆÏÅ> @ÎÀ-x÷`n¸5ý*ЂKp Æà€¶èÓЧ Aý¶y÷]ß|~9ÏVÔSpÌuäö’¾¹ÔìIËß’uص7æœæ©ÔR4«[{mêoÙ›ð îÀ9ýmœÀz¦à¼‘'0c½oÌï×qF?"[»`ëΤf>A–ï\æÐ^øÚKÓÏÜø«ŽöQ÷uŽªgç^š|†ôQßh»ïÈt°þ’¿ûÖ8ÿ^4µNXÿÄÌUcö̹L–ïjÅ=Lœî¦7Ÿ®Ø_÷ßáýÖ½iÛÓÿ·ò Ñý›­°‹•ÔÛJÃ@à}cmS“l’6m­'<µˆ¤´R +Þ*"x¡‚ˆàûø¯ü?–õâ»èff'»uÎ%ιmÐfDã­H<ߦ8^ûZß4Þ4ouÀîàža ;™8J)‹T0„CØcÌÚèq>Ä=‡{x‚ìs^kú¬+ÄÝúƒÄÔ–qï6œñ\c1n×(!'OÏ¢z**(Œí ë®M­–güóÖQ^åSþ’´&ľ淘Áã\¯¡_p/ð ¯p Çpöü”µØ›Gø‚˜»Õ7·½U_s“w{^õ+w«û’Fqt~«·}î÷&fiêÒ= ¿Ã›ÂÏñ7<£úX1Ÿ½óö{æ³—ìiØ“öªþ.sX[ʸZ¯›Þ¤þšÞªæÿû¦•Wuü¬ÿa² ~°‹”»JA†ç5®ÙÙ[Ö—xAƒŠ +!i´±±;ÁF¬¬,|ÅäÿÉá‹aÎý2»!„Qa˱í|äðú"½—ç}NžÓïJЂpnÀ8£|lâXŸ’:K’UôKÔ òœ€ã^€{ð¾À sw¤ç7äS™c±£ËÁ7øoà ´­éGqSŒÆÔ#2˜YÍÁ”q¢£¦¼§j­]Ejã#¿pÍÚg”5´×,zÎužÀ+Iý_qÞ·`Éû!اFžÎc3ËM{Uÿ¾O» {µu–ÅQ¿’wôÂzÿ–TKÖï° šÑxŸà™{ÑŽ;Öaß½yËy¤9§w¹à½b›³4~ö ©OoŸû&õÈ}«Òë^8þûoøãWÎ&¼°‹…”ANAEÛ³p î$ÎL"†… +ÆX°/ 11ñ®<{~Çÿc¥2/é™î®ú¿ººC§K.új}/CnþÒ¡ÿŠ«ï>)HI†`nÁ šk­~í÷ùŠD0KpÀ¼ƒ/° ®+ ¹øIÛ¬Á¬À è%ŸÁOg-ÐÆøŒ¿kr*Wh4öÝÑË’ù¬vë;2Lj(_eæk‡Í?æÙÌ8Žf¿¨{A߯à¼xd­ÖÔ;çÚT·žÇœÚêóÈ¿t6i˜š˃æ}Ã×iÈy¯Cý§~–®T›ÔOŸà;üõÕÊø±:*Æ”圄ÿ¾|¢4¯ûä{Þ¡üD£ÓßEÿäî²÷¨ûZ: +·Î¿!ú>O.›Ae°‹…”MNÃ0…Í8 +Wá·uì64ýÐ.+•B$X³ìºï¾ÇhŸÅ{b4JÄâS2öøeæÙNá"tsI®ÿ/ÿºƒ®ùžCãÒU|Cú$’¨Á-‚ÊäÚú5æ¿'½Š(V~¤þxspVà ¶h[}Qj\€Oð>À’¥—1µ·!œœ‚3¼À;˜±Ï†¨_ë‡%QsÀ÷Ä5 µÎÙ5ÞߢŸIe^v$ƒöF½%§aµkzùv`6`Mß‹ÿ0¥_Ïà^MÙWnéC{ÛUovžLÛüˆüÌ-ºCÓ§¯CçIçYuØÏ+ø/á÷|y}ù[j¯éAY7gÃÖ–Ýcí“Æíñw×ãï²ïQ÷5±Æ1Ÿ}—çÿ!ŠÏ GBƒü°‹u”;N1D›ƒp εÀ~XÏÎì + „bDJ€Dˆ8Ç æ”¡J*5žàÉãvÛ].ÛÇ1#' æÆiüå‹YBqŸ»+² Zo‘PÞ’xÝSóE®3Ox\:;0’th|G¶D}å ­_¿ÏHÍY“Ü7ðÎvoµëø x_hâ<‚s°!ÛuÚ¿êŽ÷DñÅ;Ó¨qõµ­«¸÷‹áuK£~g9éèH͹Øó!8Â÷7xWô¼g{ žÀ3x#×*¦ÝÏÁÏB¨¦tl ùRèóH cò²EË'iÒ“¾kïxæõ^\SƒŸ«ûê5²çòràšSwdÊ“ü–äaþ/ä7™ãê«Fõì’{»ˆÿolê­ÿòàhÉ/°‹”]JÃP…Çõ¸—SmšŸ›&R‹Š ¢ú"E|7ã†|è‰=c +>|;™¿{&“ˆ8ˆ¹˜¡2–qð‹„ì[ƒÎXY¾U¢!5ñºs(î˜=שnWŸ¸"kК¿ú+¤':W–«¶ü5óæ”SìØ/ðî­îäsIÛ}ÞÀ }ŠåÙ{ŸôîÌg ²¯ÍÞ³fkïuÖ}tÙý\ ¯ë=J¯ŽqÒ`´þÅøŽ89gxþŸ¼û`¹®Á3x¥Nš[±Þ}š…£>¤GŸPÿSîGðÀfFkgN'iåºêÛ÷yø,óñ¹uÿ˜¾Ñbšø»kÇfÝXý‰ü_È;˜wÖw¢å¶à–úù~y\þ7üÚ÷´-ÇV°‹u”]NÃ0„‰ó–&ı›(åïOˆKpîÁ˜ˆi´M>UÞ¬wwÆv#bWd›èÀ5@ÏØÆò7+kíï¹·[pv¬Ù¥¼žq§Ot$¯×â¹îZQÁÜ“g/d$…¹uyž÷H%;¢:ê÷^Á =ªÖS}Žàß÷¬Síû#xÏœiZè;¥ø\ç†óOÔàh¿|r]^·™®j:ÇÏû5‡3{ñ ~#..ñû>ÁÉô7Î~"G®…ûX¬¯ðó, úuèéìñøïñG¤Sù®Sȷ¹å÷hs´Tcˆóû©·$}máL¤Åï´|v?†•þæ}.Ý|·×êä\÷Z´8³ù¿côÞ¨§n”°‹}”_‹Â0Äó‰=«MúÏÞõŠÈq(øìÓà“øo³0?Úl¶;›IÒÂ:„ðÖ`¶F4jŒóÜŠòW…1×ÉßöÆd|êV=f#T@Ç¥¸Ö-é8y­1‚ÜwB<â=Ó o6~ÁŒx$Îg4>@o Xÿ9¿CnöÆÅ¸7ãhì^è¶@u?‘ïô  Ëýw_k0`OgÔh¥Ï$DªWã×óFÁÄø†ßs#û–¦ò5¶×ɼگü#½<’µŽ øWð^¬Ô= éz½ªC~©æÕƒ7ðÎ|Š‘æ@¯RÇýl-f +¿Öß s,Ì'Mé:®çãìßýôuÕÑ[þ™½Á+¿ªEqÙ¯Ÿ¥ûVs¯ÎM~åùoùíwEõ¹§éEjåËù*òéž=ÿØzk÷ÞVöô_üÙ|ûØ8mi°‹…”KjÃ@Dçĉ­ècYrdolY²Ê Âð‘ÜUAQÌàÅ[Lª»gZJ)mRJ[ð&tÁôA“ÖXåø™P3çï‚1‚zÛJíÆü~&¬K¿ç·R«¤çy=èíÔá½äYÞƒ¯àüß°íÄÿ‰˜`Áìªë}Ö׺¬5&è@®ó܃[ð#õ†Â\h)ªË·Ò>”3žƒKp’œ sZ÷§¦ã½ðîϽáÒïó ’S¢ö®î¥÷5gœ¹ïcxµëî5ö~“Ü—Æ+¥ýð9h÷·tÍÚι½½Üãè ô­=û¶|¦Zþ p½ÿøÔôÀȰ‹}”½nÃ0 „õƉíXrš_d,ÚŒdì”%Sž!ÕÊK‡ÊØ´È#iÊ)¥eú¥« o`#èÒßÙÎl²4geÆ€þÖ¹ÞèP¶ÆÔ>rÔ?…=4úÐÞç`}5# ¸‚O°kp_àžàÎ1Ϲ´ÖÁp]ÎM¿KÕß„Þ)ônà|Ä{žÏ¢«ùXýSäÖï>J¼Sµ`/½q6{™Ç\?ã?~¯—v‘+öN÷­D/Yâæç\·ñ¼èã¼'É©;V$Ÿö›Mßmžóý› ïOo­Üœ‰ß¿Ë´Ù×.ð½õe\ËïwÞï²ÿKTçÕûvÃJ°‹…”KÂ0 DscúKÒ–‚`Ãç +‚ÓpNÁ +WÌ k”¨‹'9Žcݤ!„&„Ѓ1‚ V;ÑhÃ/¶í×d¸î@/Ð_‹ë„¶ëqíϰŸ£±Àî+}0^Iõ%—ûbܳ±7ãi¼ñ2ˆOÈíµê|TGü.¬Ÿá_ít\aŸ +ùTÇàr'§sHn?ü z=ÓF?±àW½$»3±àççÈÚÂzkÜ ý3´O°¹f>î«ö$õé¯Ý÷®rÎÏ…ïGc·jé[Ñ·Ì5çÁè½Õ;[CëiÚ¿Ä×ù÷þîóѰ‹“YÂ0 DsbhK’.lb‘øà\‚»p +Áš‘¬QªòñTÅŽ=¶ã†ºB4c zAZЀµ@{7Ã0ãZ؈@u7‚ÖÃ3{*½\Œ«qp½è}ÍÏøŒ¹L ÃÎ{Å·3öÐñ½Oãm|Œ—ñÀÝèðzµ9Þç{èœèc££_ˆŸÓËîƒêÖÎEóhœ0¯ïëIt“³Õú'ô'Ña<óû}Ö³ïo&¼ÿ Úx.¾²WeÇnÆ6?+}'?ï¹ÿн².틳i]?Ÿ(öÚ®Íý;Äé¬5ÏÒ.ÿ£WXõó}u|kVCÿ°‹“IÂ0 E}b(m“BK Ø ±äœ€p,éäZ‰`ñÔÚñ;"R‹H«,• 5X€ +ÌÔ×À8ôk¯¥¼Ã×C™= ÊQ9+;%âÌÛ{ìôÊôÐ3„nÀ½uø¦œ奼•§rA¼ÖÀ8¥{"´ 7{ÞBÆŸgÑÕaóÅŒ½Ï›“Sÿ[e¯Œnž¶Ž¥!¸¼Áèrý[s®.ÆgýiVkÌ«3q¸÷´§žö#Hÿåv¯Ê]¹A^Ét¶w;ßÒ»b¯Þ?Èôn2ÝúµN—ÛµÒÛ©œŸ÷÷q~íò?ù3{›ßøn +¢ƒ°‹•“Í +Â0„óÄjMÚÚÖ*Eô xó<øŽnd–%KñðAB²³?“„ªB5X;¬ Ë•‚qÔ¯œóœ³¶ 5÷yÏÛ³}ž„^Ø “0"O,Ô_ÒÉ÷hL`@mµ"ï7¸Û`{8wá!\QGƒº›fúbIékŸ +ºΟõõ S3Ño€úµz-b‰íšW4~—tí;Œ¿-œkOè ¡'xÓÏ`¯úÊ=Œð.s^ÂGx OÄܰÎ^UNεzÿJ{­ý¶sÐ>ÇP~;ž¦wîýUëÉ¿zöO-À\½Þ_øåý™¼"°‹•”MNÃ0…Ý×m¡v’Ò„þ±‰K¶Ü ê¸W鎱ôžô:Éuñ)vfæy~d§”žÒ˜uÀ£ƒþ+aJ'ƒèœÆx6Ð9úE{ŸOµ£5¶Fͺô÷9«N–ø=PR÷ø¶Xˆú‰-3ui› _&tK÷Ÿ:5·Ô“…Fj¡mÔí0¯hm-ìôѾë¼êæ|Þ®3á\H­ùÕx3NØ×ÿã˸g¬Rß zUc¾ß”ö½?Æ'4?ŒwøòLö‡=Žî•Κuþ×íg´½fdɽzþN-AôÆDùÜœûa†Z?°‹“KN1D›;p­"'`&{>DR6KÎBÄ}†KÐŽª¤¦dG,žfÚýqWÛ6³ÎÌž@vBÇõÐNΈïPñwëÈ™@Â:ã´N/~Úê/ÿû@«N„½ŒAKF_Ð7U88/ÎsÈ)±sÈË~ÔfÿÜFݲÇR©C-}ÔUòW|3rïÍ+…9 ½PWª@ݵ˜ÑþÎ8ýãœâLö•s㺞Ó­GçÍyÅZéÿì|:?ηóå¼mœ÷ɹ˜=<:[gãöÕù@½La_j×÷ Ä÷ç :kqœŸ¾Y½Sz–¼'­·£wжú[ûéÝÓ³kÍ •w‹û? Ű‹}“KnÂ@D›MnÅ8Klüƒ€ R”HQ®’ dË8 +j¤j©(ÙYÀø¯vÆ]OÿYOöUús|ŽÓÝлБdzO†¸æ©¿u¿SžCÞU[Ëw̵þwf×ÞìŸ7~@|eò°‹…“MNÃ@ …çÆ !é$mÚ@E”,X T$Ô3ô,`ÍYú,=«¯VŸ”ÉŒÿží”R‘Î̈Ÿ¯H´Ïzo<¯5z"{1УêÖ¾ö\W:WÈßúª¿`úR¹Xëg®?~=`®—SoÃŽ=önM½u­gdò}¿;úýOž)}ÊÜ›}bž/ŸŸ¯¢ø°‹“AO1…‡Äÿäß‚Ý¥Ý +ŠÐxðDÃ̓ ÿŸoÉ›8™´„×l·Ó™7oZ©Dd@úŽ£’ÿøЂF¤û’ð6àŽ`VŒÕ‘ÿÁÂ\Û/ƒËç°‹…”AJAE+â2—ñÆq&3Ý“IbE¢ Á… 7îWÀ#$ÈAü ÿCQÉàâÑSEwuÕ¯ê1³ÚÌ.¤"òWùKŒhAr,Àø2›œ‚sp{Ag‡÷7níHKš€òËOhýÚ/[÷Έâ(¿ VdN;ó;Ú7à¼s-vO´¯ :*Žüª³£IÒo·®ž†¶òy°f¯Ê÷7Ø‚_ðÊÚb¢.B÷DÔ_ç7»ïcçb<ãÈÖüeç/zÜ‚{®ƒ;'æÔâ s:5;)ìtšxVq㬩Sf^ªipýé)èègÆ÷Û÷MsççÜ¿…±™öo´¶Ã7÷iF4“) ÚÌUµŒÅõ=OÔ©üÀ5}Çò¨y×xŸàãƒ^#°‹uÓ;N1à )8' à<ìÝìnx(!‚(¨"E¢¤Á) @¢§¤¤à”üÖþ#™»ø¤ø1c{vbföß>ÕæUZ;‚-̨‡sx6›lÃì`ü ·Ü›Š@‘á±~?wšßÇAòhœžëãÅ6|G›­ûûóõ¬Ç̳¸ºl.ý>¡NÎ ÌïqzžŽóûÎx‡ <Â=ÜÀÖð?ðÜë¹¢¨½?¯o(Ôß÷yœÖ·Vïí‡R¼ŸuL½Ô§cÓÚ…ýºk¶õj6E-¦ˆŸ|cþ®¹Ïc–œ»‚SÎ{Þž:òsó¾Ì—ò,²ïÝÊ;k}]«Q­çUé?—î8'ýö¥Þ×¾È÷ù9‘õº´±Ïîlì»ÁþöŒkxþ +žà^~M÷åа‹…“;KQ…'ÿ–¿ÇGv7k‰ø*TÄJÁÂ&M E*±MÒˆ…•ÿÃÆ3pã^->–;;¯{f®™íÛoöHÉžñ‡¤5‚cð6f½m|¿À ¸ ý{Š ŠU9^äü:{Ž#0b®§ººÎƒD•¿þ7¬áZLÁ˜µÒé!Û˜´…ºŠ“uB~Ñæ~à ,Á Ü‚p^Á'xÏà,ä.ÝS÷]úª~Ö_~ŠËúþ§w¤þÃ?îdÔ9kÕr>pæØÕ]³­{³>4êïàüû;¸¡V“àï¶+ÚGsÐ=£Ž±7ÏsJ­§ö³ y–¥ýìÒ¨´ó™®7ç}j÷òì³îqî]ýÄYû=ýÝCW{¤nz{¹¯šõ¯Á¬ÁêO °‹…“ANÃ@ E wàT,¹…LÒ!)E,(ˆM Ä +„ʉcq +þ¨ÿ c&°xjÇã|Û?Ž™MlœƒÀ!ËÓYy tà¬À¼‚%@C¢¾(w-5zpBf ó®qÄ祻OÔŸtKgÔÌõ/ÚP3‘)ѽâÊëØs©uîêM)ÔŒºQ_¾ $3æ=i©qD4óx`ŽÙÛ=xÏà†½Æ>bñ¬Üdõ9b\ÏÇù}¼ ºé_j>Õ7=ßÏœO>Ͷ^̶÷Àþï"önÁ•mvZž•ó%½êí÷¾¨N¶Ÿ{«þjÍù;ð]e«ïßÍšç~÷â·Õüƒtµ/cº~®±ÐÌ3zS<»×¶Ù¿…Ó÷ý6ö½³%缃õ¢ô»°‹…“]nÂ0„͸g)ÎIÊo‘P jßxâ2=EoÓ±:#-«X<|JlïŽw'›Â"”yu,I)N륡 èÁlÀÔ ¯/"ãóÎä@=«'ò¥ûBTŸts[0ò¯£Ú©Ýi‰Îµ¯8õ¿Gö²2y¢rÔtÞPg ‰{Ö“šÚûÕóµì¹VLöãijžš¾¿†h߯[¹}›Ÿx_gòK$öÙ9ŸJ¾LùÔüm©ÝsïwðÂlfxÿ7ð>Âÿ ô1Ï‘:¶n[[˘q¢y¯o3𙜎Oï­õÜΞÿ·â¤ýL×ö55š±wp߯·=Œ¾­7†Ç™ÍóxŸ2ŽÅ7°‹“ÍNA„‡qw`p +AcŒ‰ÀwàH8úB^­UÚT؇/³óW]Ý=›R¦?Fä\pŒŒ:¸">È`Lj®KWñ=N{onÁìÁ|€5˜R·"1f͵.ŸÒžx·¦×Èq²ùQç܃ ¸7\+V¯Û9ä_ºs0c>Ùröõ¿²ÿ}LöWgô‘û“óåžÜWË„x<××¶/wà |ƒ/ðîÙ×I&¯k/ÍK©ž¥¸žWÝ>/~oÔ1&q®´o—¶ŸÝý¬À xàZîŠoâP?tŸ×_5åÞ„ïuýsj¸¥¾©èðzLéCc 3¥gnÀ3ÞiÏìoõxkÖ=Ã÷+ÖN[ƒ%uäê§z=÷®ç?ÍMÂ"°‹•“KnA D Cäp îÁ@æLB¾BbØ#Ä5Øpr‰ì³Kª“²ä”f4ÉâIc»ÛŸj™MÍlFæÄí)¹ÈIFfŒ¹ßi"c=õÇ{K°of½˜àûÁ]¨‡û©×<“ZòÎ;ð<žWãÉW‚©:ò·é› ªWAR¸§ÚS›î±ß¨Ï_thêKõñþJ¢vì3g¬"i–=8ƒ+¸€Ø2V…::gÛ^uÓ÷tÛûªY{b¥ýž'·æùb>Ý´§ûÙã}˜QõÒ9Tï¦wò:5YI¯ñ­*™õ¼‚ÇÓóîw;å_“¨““öóœð¯Á»Ùà<™õÇðPƒõ?gþÞÅ/Á@³°‹“1n1E‡{å"œØ5°à (ER$5 GHACÑs™ùþ(£ÑR<íz<ÿù¶Íl`f#R쾉 ‰Çu]œ÷œº0®XoLFBÍø +ìÀœÀ'xMДBÝvM’eÿª¯3!I˜pïG2ýZï–¿CAýq_æô` –AS—ï>¯þÜãCÔåë¼ÎXpŸt5&žÃ"|_Á|ñLߟÞð³t?"]yzޱŸ)÷^Òoï¡ ÿêg쯒X’mÍ'»Ü—zŒ~izKçäõ3qí¥7Ñî9 ¹™zÎ×…ü¨³Íß‚²±ë7è>¾ƒo³Þ8ƒÐGì^‚Î{{þ»‹¿ƒ ̬°‹“]N1 „sãÂ6ûËn[¨Ô‡‚„hOQ‰ÓðÚcà 1š¦ßC6Ž={SJ«”Ò#hÀXžýÞa}Wâ×ÀãÊ·6è‚\ÉÇzå~` ’7#†yJÎqŒ­é¦?g¼YÀˆœ¬×Kþ]0áöµý„ßÝ'ÎÀãäPg<£O½'ÌéõÕ§,gý^«­{Qj>Á þÐóVæ8È{¥g;ÌïÞmƒ×à#8"wŸn÷Ì}e=ïÃu;µ¾I‡¾fÌÖQ]÷æšïuë½öqo]·BÏ©“Þ»6Î`ï[ÄóŸñsÍföœ‚sð–þþ ï¹ìégp ¾ƒkð…7³hüï®þzñ²ÔÍí°‹“ÉÂ0 DóÇ-mÓ¥€!N,ß‹ƒf¤‘•OmÇ“qœBBX +” åŸ0î«dÞÇÕF4§[Ê^’bZÀøôƈo‹œã~!ÿܹZä;€ù¨—þ;Äl ¡>}= ÷û:3ñ¬A ÉØb¬5¡O榎×m@íh2Úê§þ͸'xç}h½G¹ =Oª×Þ8GĤ¹µ±Ã˜5ö}¨ýR‹ž÷áÏíÉù&ôÒ㾕z¹åêKØ'äˆn]},½õ­ûxvjèû¨\Ž´žúgC&ž}嵨ƒWãe¼q—|Ú[óIãb<Œ'âù^|/þêÕ¯«g‡°‹“KÂ0 D}b(MÓôƒ„Xpö¬¹$.š‘¬QJY<)MS{<™šÙÁÌ*p*á¸Ã¿çH ’ô‹õj9—ÂóúMvçæÌNï4ò¢sFj̨¹rvZÔdPÒUBçPß“ û –u=AãfW=Ú·Ä/?çἜ·ó„?Ñ“ç&h¹ÀÖÈðqïÕI ̃ú±•×­}Ö§Þpêhθ•'ÍC#={Å9y^ÿƒèGõ˜ƒ’‡ª5f¯ÁŒ¸§ÎþÏ"½`ÞîX«wÑ‹½®`,xËÿg/«_]r¦m–°‹“;Â0 „ó‹¡m}P lìÌüAt–¬S¢2|jÓÄç‹í:çVι´DC¬ø÷œÒOù¬^ÞÀ× Gá"„{ÖwGèý¼É­ëQ8 wá)Ü„=tá Ög¬ïÒyÖãxýžs'a+\…žs¥Nœ·DÍsÞÛ@ÿ-|„jO{4ÂÓ1‘tˆìScuj}ãy­}Wý„žžÁl|ÅBŸ¹ŽŒž+Å'ÔeDžÉÔÍöS×mAßC7Ç F/`Ïvö‚Ñè©K3¢º=roð(ÎÞE=O¸»ý‡x–fõw/š…¾ˆ°‹…”A +Â0Esb­mÒZµ* +¢K7Šà!<„ÇqéÂ_ùã´‹GIf2ùó3Ô97tÎ ÈлŸÊ$ÞW_ò3C<(Iaê·±1X‚ X€Jåé:¹:'ë‚HN và Þà^àjðõô~¬?‰{"÷[=HßAÅ +åÉ +œÀ éCÑ—êÛêÖ~ˆî‚ž®ÁÜÁÌ•R?0wÂwñ‘;º|“Þ½‹ûlçÃö—zo}_ëO;'{ðW°5zSúRþÙµœ—ý’žÌHHœ—w—¹–üš¾¶ß†ú§Ü+ ÁýÏ‹ö&6{Z»WSK÷¯óú<’Y­XËÆc:coûû‡|Kº·O°‹…”KNA„.ä<„@Ï ðÆq¡K\zWÞ­ð$V“ꤨô ‹/0Wÿ˜GÄ ™ âö1ÝÒ(ç—üýÂH =iÌ¢ý<€[0ˆNý$»—¨k$ÎøŒ˜]_ð®aû/ŒÕNøS{­¾$±Z‹¯ù ¬å‘äÿœ—žäšßÀ;xås-¿©º•Žèý†¶5ûs`N+‰µ4méS[‰¡z×èý¾¢÷ýhŒ±8¾/;ð¾Á{¸á¹î»Ïk¬þ¬ý(óZ1ÆFfé÷Kÿ³fŽ$÷üžä<ƒ-gÒ]œïŠö®¶{þ?Yó·äªïÓîw¬æZžµoÉéò™ÇRô°‹}”MA …C9¯¡iÓ”VÊÒÒÎÊ,]Á¤ÕKI½‹¯fD:/D¤‘/bHôACpžau¸~¤ÕÎO`¢<+cĹN¢¸}®µ§ÊQyˆt®"Ý»òÔ÷—ÒÓø þ|4§¨O»Ç¿>2zÙ(°…v¦ó5¶TvÊJi†Ïcý1ð¹ÙÍ/ÊkA‘OF÷šÙédÔgy^¾¿š7Ñýý¹ª±†Ÿê³\0ãòÿäê×'Ï…u¼¯>ßæÇû™\nßgåêû}×ïên,ЛÍÐÏÌÏý±x¾¼÷žãi~F;Ïÿ¦gñîò.p¿üúÔ}-1l°‹…“ÝmÂ@„‚㟂…"à…DŠÒ(M¤„¼Ò%¤ÆhFYîðÃ'ûÖû;{N)ÍÓ?ÆÂ}nèû“áþ¥³Û•ÏÏî_£Îô_›]ç¬À'8§4;¦Tý€ Þÿ@ûx¶ø)¼Oér¯¬Á¼“7Ön-~œ³cLŸÑ¿6d—FÑ·e®nÂ/âs—¾—â[¢:š_}h®—âGŸW°!Ë QNgյ܂pkùþš`\¼c¥:q¯Ñ?ΛÛÃ{ð ~ɉ¶ý¬¨“çôûŸ›}I\_¿{yáSû,Ýyñ@TSyzæ÷»XÒÐÿ¡[Þ+Kœ›¶°‹…“Ë 1 DM?4D|—]¾â'@ˆp¡ ÄÑ0AÉŒ6ËáÙ3'1³®™õ…“[ç‚T`ì=½@Wˆ}‰‘Ãu]®QCÕºÏú”w.ànÖjƒ'xƒö^à +¯ÏùsŸçUÍs$í)Ø€#8ƒ؃…ÏUsçæ–Cû‹@éw6z)ÏÌ}FÅ}jQ‡wØä[‡æÐüܯ<ÛÖ™x¿öÕédz•¢O +Ÿ}Ò^gôsÿ†¾üœ_œaô!É#½µxØ÷=Úì<3ÿýuVúƹfÝÄ)ÝOëb}ü˜[ÏÚô’ÇÌg·öYjVž;w×?ç1´&V°‹ÓM +Â0àœ¸Ú¦ik +*Š‚ âB<€WpãÂ…WqïÖ™qȈ‹lèdæ%Õ9—9çºJN¬gK ô ¦šŽ)\WOø95—ÿcžÜàU=÷‰³Ž`'¸Â^ð€´”¯øÑŸ×9¯îoå-iÿØÿw¸Á¦¢·ž›÷ÓçfÑõ¥PÑ9Dîp {ØÂDäOÕKðŒ¿ú¦xEÏÏë<ç4ªžë*:Ö~ó|œ•ûêûâï9î=0öOÝ?gl©®O½¬œr-öÃŽp€9íÜ÷7\ˆ,úÌRg(%î’ÿ‹ú}YS‹šTÖ™ek˜%rXg¡s|ú¾Ob°‹“ÍJA„;_Í·‰ÙÝI\MüÔ@ð˜cò +y¾•5P M9#>Ó®êšY3»°ß,ˆžÏÉ¢A#Xò·—ºÎ넞t Úïu÷Õò¯¾Uox¯à |€wžMìQÝVn:_I¤ÌÌà<ƒo³Ù98€'p†³=¸ažIhéÔî%I¾]8ýVúv_àvàŠý>׳ӽ¢^ôÑò;¸“¾‡Té÷üJ>kú^¿pÏÞeÈßukù¸–çâs³ì­y–úDOEó–w<Šî sÔï¸ã¬Ü¸÷,5Ý%Ùß»ê7TÛeöÿïÛö Ê÷³Ÿà…»åÊÿÜ_ÌZßZÿZ¹‹°‹}“kNA„8 'àVÊûà¡Á9ƒ‰^BOàOoÀ]¨Ñê¤)gøñe=ÛêZ3»·ÿ̉¾¿#ó + X´ ÝÈ׉4•>ô¼Ç½¯ZÿÚ·ÖKìuÁXƒÂZ·¦›kГ6äH!Þ2žk¾›¦fã™Ùã/<àæìÙGG‚æ×½ø¹¨¯î*’g?Op?à ìxÞó&+Ïë©?TwÏÑò›šâœ×ï‰×¨SŽ-ÁÀ3Ï ’¯¦O’xιbξ¢çÀzGðš;~×Yy_ªÅ’;XÛµJ{w•´W¯•fÕH½á5¼÷Úµ^Ïþ³w_íÏO¾+Íîý a‡Ñk¿ñ \Û'â°‹…“;Â0D¥¢‚@HÂ/|tÑqJJÀ%¸ 4+ÌÈÅ“âÍzv=k›Yf_$K0"c2<>%¨ÈDòUOשx.hž?$Ï‚þBR}kÿŠê{¼Ù³k2§®Ö*˜»w³V߬}5ëAß]Ä_`¦Ô(¹¯Öÿ|v¿ôÜê§ëÎÀÜÀ<À¬X®Õ7­ëµó ïXžŸ7ÔoöÕô°fïž7%K°aÎ:ÐP}×uô%g¹`Øÿ¦ÖœÁ‰>®ØG!õÔT}w¸žÛïÝsb³HÍDïtlF¹Åßzì¸ÿ5=÷ùTìMߺîÓ·ò™å·ä!*°‹}“1Â@  =áq„KH ‚”ˆ +QÐAü ƒv…YqSعøÖÞ³™öaŠ %¨@)0ŸœÆé@’óZOã\~!¨žŸÍA_¤²oí5X=“»—ù×?kg z§•:)ܱq®f£©ÙxâÜ›ÇÏ?œþoõ1Vª‹óÒ¾už¤†þƒsvŽÎà¬pŸÖk2yõ‰P?ßGÎòE´_Öç›à¼YKu‘Næ¤}'¹WûHð!Ö‰ßx½wN˜ß€}cq_rscL[ã¾´B©©žèävíß®ÿzSœÿ2ø½ÖzúŸîÊ»¯'Õ b°‹…“i +Â@ …SÏäµ´ítZÛºà +‚Wð@Ë^ <2úãcÈLöID¤‘ÔhˆÒ{R²²‰ôMÏⲿ-Ø€z$‰ó0;öÏú tN§%}{÷$²l-ÏQ9('e†œÉo&å¥|DªµòPô®ZéÝ[ÙCÏìrÎ#ª×Û›Ìý²Ü–¸GÔ1yGDþYàó¦\ û~fÜ8¹oQ}¾~ÿ_ޯɥù+Ùsƒ÷¡Ã_^•'ÎÙÕ`;Vš›hîzôavØ¿,³uWÎ'° zž«s=Åå^—rõ;턽Û|Dûû«÷~û/€O½¯°‹…“YÂ0 D}ah›na“ˆEàƒp >¸SjKÖ(§¨‰íØ“©ˆ”"R)ÅJâ×yµ(Þbì^®7U&J¡q$݇åÙ>ßï{³þªþ| Uå§r-¿s°Ô5ºÞkb¨½gðoð°½ÆX¼×ÁÃ}¤æõùöÍïQQoozôD›©ß)þ{ `§úøþ‰º²n<_Iù¹¹Ø¿¼ŸÓ/åöTéîŽôæÖ¿ýc¿|ãç4]#¸€½Œ~4;›Œ¹ƒ+ØÊè£Yâ Ø+öŽQa±Ö¬-×Ëy0彜v©ú¶÷=ÿ±öÝò°‹…ÔË Â0 à° öM[TÄK 8sX˜`þ[˜_±zøQÄŽ­†Šð““Lè÷R¤Ö™¡û R’JŒÝŸK|#ÊDlê¾ÚHåmq¬ÍOï­è|ýÎññŒvp„ Ìa&ß:Sï1+Ø‹%ô&¶¦{Ru¤ê¬HCxŸ×oÝëè¤ÆXÛAò$G§±©·…lÅœÞ_q~¼s<\?ϙׇÚÔ^'âÆæ§‘7ZÃð„;œá7x…0™~…7\¥ƒì·=N½m+oõ²æ8of¼Ü½™ózêõP×ÿ õˆ°‹…Ô; +Â@àÑ«xß‚‚XX ‚•… +6v6À+–^Eý30ù³kŠÜdö1³cD$‘TõH"Õ÷™ +Çæ§$#}Õ´â3Šãs™Ü ÛãX¿Ÿç×÷çññØÀ .°… tý¡*4Þö-ß‹1¹ÔëÂyrE`ÝüÏ}q¾¹æ¸‚=œákÍÁâ”Sáö/s›*« ‹Õ·©Ÿø¹OBwÂùs_Çöó9ûzÄêÉç*k3‡R;]gù–¸…ß,ÀœÁ ¬Ùnã猭­~§¼;ç5ôÈöÅö×~Ûwؼ¬ž§/hTõ35ô¹#®mJ¿}Éu‘ûÅ)8•jàNT®c]ÂöOìD•QµšØ·`›ãì +æ;W9z³š³ª¡ž©1ÏÆ¬èû?ȼ‹§ãÍøçß{ƒ“'R°‹…Ô±NAà_ƒ˜ø&¼…¥/#Üq‰!ØXIBK 6¡ò!´ãl-l¥à?™I&“-¾âvvgwvÜh™Ñq2§+2—ÃÏ·óR룸æ‰òÖzF.ºÁþÑ>šÇÏóûé94ž'ôiBKÚÑ­è^b^žÈWˆÜk¾Ž^ÀŸMó–T‰;z¤9dž¯[¿í94£ÚКždÜï_ +û6Ä<=O)çü¯_ü}øo_GªOþÝk%ï9•¹ÛBî`(ãZ‹]ß—9úÚº¾›Ò8kÐ+¨û¢à¼\\—W@óhÜ0þÉØ;-Ì=Ú>³¢»‹z'ŠÛõ…ìûŒSßïé§þxšµÆ(OôŸñûÛ;<ƒæ°‹”1NBA†"x+oÀ¸¢Oß#š¨$j¡@Á)lL¨I­· ¢£¢óßøO²NÞ²_2»;;óÏÌfô\Š+G/CÊ?·¶|©8)£ëîâF‰üׯý~N¿ùÇù·âŽŒÉ\»Šüí¾Ý‹c•ò­dÇñÍÇò_?ñ†äIšÉ3y'³Hg꾯s¨¯âþŸz}¿ å‘Å-Ô—š·'7ïTÜc}í«gõìMu÷¥¹n~ñ{)åˆ2:³ósJV@ãŒtH“ë ÙÑÞÍ/àôh€“%÷<[“ü¾Áª¦÷¹šsû~¦†éoë“ì©§M.h“HS.›‘ÿ‹þÌð²Ãݰ \ No newline at end of file diff --git a/astropy/io/fits/tiled_compression/tests/data/m13_hcomp.fits b/astropy/io/fits/tiled_compression/tests/data/m13_hcomp.fits new file mode 100644 index 00000000000..fd9184595e0 --- /dev/null +++ b/astropy/io/fits/tiled_compression/tests/data/m13_hcomp.fits @@ -0,0 +1,191 @@ +SIMPLE = T / file does conform to FITS standard BITPIX = 16 / number of bits per data pixel NAXIS = 0 / number of data axes EXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H CHECKSUM= '0dE53Z940dC40Z94' / HDU checksum updated 2006-11-15T17:28:38 DATASUM = ' 0' / data unit checksum updated 2006-11-15T17:28:38 END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / 8-bit bytes NAXIS = 2 / 2-dimensional binary table NAXIS1 = 8 / width of table in bytes NAXIS2 = 1 / number of rows in table PCOUNT = 58149 / size of special data area GCOUNT = 1 / one data group (required keyword) TFIELDS = 1 / number of fields in each row TTYPE1 = 'COMPRESSED_DATA' / label for field 1 TFORM1 = '1PB(58149)' / data format of field: variable length array ZIMAGE = T / extension contains compressed image ZTILE1 = 300 / size of tiles to be compressed ZTILE2 = 300 / size of tiles to be compressed ZCMPTYPE= 'HCOMPRESS_1' / compression algorithm ZNAME1 = 'SCALE ' / HCOMPRESS scale factor ZVAL1 = 0 / HCOMPRESS scale factor ZNAME2 = 'SMOOTH ' / HCOMPRESS smooth option ZVAL2 = 0 / HCOMPRESS smooth option EXTNAME = 'COMPRESSED_IMAGE' ZSIMPLE = T / file does conform to FITS standard ZBITPIX = 16 / number of bits per data pixel ZNAXIS = 2 / number of data axes ZNAXIS1 = 300 / length of data axis 1 ZNAXIS2 = 300 / length of data axis 2 ZEXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H COMMENT COMMENT This file was produced by the SkyView survey analysis system from COMMENT available astronomical surveys. The data are formatted COMMENT as a simple two-dimensional FITS image with the same units as COMMENT the orginal survey. CTYPE1 = 'RA---TAN' / X-axis type CTYPE2 = 'DEC--TAN' / Y-axis type CRVAL1 = 250.4226 / Reference pixel value CRVAL2 = 36.4602 / Reference pixel value CRPIX1 = 150.500 / Reference pixel CRPIX2 = 150.500 / Reference pixel CDELT1 = -0.00027770002 / Degrees/pixel CDELT2 = 0.00027770002 / Degrees/pixel CROTA1 = 0.00000 / Rotation in degrees. EQUINOX = 2000.00 /Equinox of coordinates ZHECKSUM= '2f4R3c4O2c4O2c4O' / HDU checksum updated 2006-11-15T17:18:55 ZDATASUM= '1803906202' / data unit checksum updated 2006-11-15T17:18:55 CHECKSUM= 'cGfHcDZHcDdHcDZH' / HDU checksum updated 2006-11-15T17:28:38 DATASUM = '844555891' / data unit checksum updated 2006-11-15T17:28:38 END ã%Ý™,, öÛtÚ×ÛüòÏ7sóÄlT1\B–Æüi‚ šIؤhVKE¢Á&›P‘6¶‹  Àm¦éIÁ3e°Ñ%·Lž?û¬mÆVbv|å,ÎÖcE¹‰–z1+-/1¬Ë¢M@BÙA$.UZÏ$Ë€Á '„·ÿ; O9£ÓXÈ)Se´Ò$,‚A6~d²šÑ%"¥9I4]"‹p»Jr`4Zàh¾å‘!´Xh­Ô“)€6C{[@ÉK,4H†(ž Áji  0¹¤‹@ÈMÄÀ·eÓ„kÿuŽë2³?ç ÆcZÿ՛̭ÖcI–¸ú—ë3(2¿ò»5‘™¯{Yÿþ5ŸçùœU™˜…,B%娮nRIn\A%*šQOžÐ:Í¥Ä]wÞî°‘(…„ThÈiÕfób¬ôú‰÷Aû3`ßùÜÆÑ 3]J‹´ 3i~ì¦šà ²Z¡Â -´ ‚ÊÕ›öì×Ò0[< qA2ˆv@Mq¤à$wš¬ÉGijއ'E Ði’QgÝ!ÁCÚ6ªF÷?;—Úï &v†7¤Z'#ü(2ÔÂjMI Z¤jS[,™l£cÑ[X5"Ke4Á’†`€Ó ‘’Âl$X éJ7)„8Ò!¤)©!ñ¦Òl¶Cd Q ™h4Ë(: Üüã0ˆ!뀟Ó|I²i6e q÷ WÖ‘‘A¢Z"jÐl=qÎ*Hç¦ÛI”bº¿Ö7u™Y‡ùšÎæwz̬ÌÏó3IÓ|ôj¯2c0æn\¨£ Œ¬Õù_³¹¼ÌÌßùþefEffL÷™CL‚š1oLF—(Þ'0LK?÷yú”køaÑ0C ~žÐòí9·Ïó+üÉ\ZiV`ÿ&RþçÎBƒ­iGç6¥Â)óp%® ¾MfÿÀ}Yš©öa ‘þ`ÿ31~«“?ï3H†Wɔɫ˜Ý4Î;NåÖô¼‘ 2Ñ ²& ê@ÛÑÛý*MìÉ A!´™@‰”ÃÒ&Àö‚,yCm£ÊþyøåG³W¨1ÎTgv áÚïûat^»Hò?Ài´AdÀ "ÛUӸΓ-cž½Ÿu# !ÿcñÜÁZ ½ú\­h ׫3þÂïtsÍ5ÃÍî&ý“žmFÓ^éÈçþD‹ùy€´ä¶æï+2 ÓL²Ý°@.M”eLšhT¢?*Ûb«f”Y]!È!ÔK ðѦ“š,É,”¬ÙtºN‚Lš4i̪a"í©+0ÚHU&!ÜÀ†­‚ïn ¾Ñ*›à@9KCò zÇS˜Ô©à ÌM€ƒ!„êZQE!Eå†eŠ ŠVHl‘ ’ÆÚPx‘MT¶0ÐR¿MqÒl¢E€¸ 2!¨ í==ÙvLêtõX!"È×êeÓM$í‚* $_ ´‹aŠè€Y¶Ù.k‰+ˆ Ó¦:OIJ~âƒj¢ÓE´¶9 ä€\”‹‡£?¶t]°Qäé»1+‰ö’QÉÒN9çîÈToÇHÞŒæý¿ÇuŽî³+0¬÷õ™™ìÿ=þfgù™´—õýû=íÿW™³™'3$¨£¬fj÷ž¿ffffcÌÍæfgùƒ33²'üÌÀ¦‡•1riš=üís¿ù Na·½wß­ßûû aC—ÄÕWeÊÂØ¤ºmŽ8¾sþå/Xbló3½¼Ï#Û–´k2Os%^dõKæòZÌ,çù—ê:;îoÞç=›íkÿ{¹™—ÌʬÌÈÌñÐÿ0™˜· ª÷þ˜›V”†J¤ªº%aÉñƒ¬ˆ«þ»_›®µ#Þê‘¥ž(V=ÁGùYÜiá@ñ·¯1#€AKñ<é7Í Ílð¢_Z¥J LÙ°‘,¾»«šñOE°óÔÕ>{I†žšØÏuˆ]Ã8„ ÷}×ãþæwÒD+2ÿ©Îw»íVlÀœGˆ,Úöš rHL_³€VÚ!É ‹ÝÿËþÀ(:ï¿­´ùtä^ß;ž¯ÿÜÙs±|+“¶·]À{ìØEffîL ‚6hîö³?FŽoüÈhÀk|ÿO°w3<Úïqw{ßõsæˆ,N¿Þw;ÜJ’(W¼ÎæÀ Ó² 3FA†í¢(Y¢2ÛH"h–©8,†) +t•Ùh ›"ƒþ†ÔùQFd†&xœ´´4—ðEZ­À˜üHj•À®¦ ”ÐI¢H›6ªaˆm‚e#`õ(’Á #ýŠº&ÊN‚`[áR”z•¢a¤‘©!§LØ  ¹ëRtßhR°R ­tp*f”dCNª*˜·SØ Žr„è÷\eó¼jÐ\<öÿ×*“dõØüÝáˆHÙ´ÿëËñÝc»¬ÊÌÏÿÌÿ3333?ÌÌÌÌÆÛJÑÿ5ùÿÙžÌßs3g¹›¬ÌÙ˜ÅZÌÝuffffffff<ÌÌÌ̬ÌÌÌÌ7™™™™Cù.›($ANt=?Çù^÷ªÐÓŸÊ}ÿýÙÎw= ½ÌÍ䱟÷Ýýkÿfg¤y„ý™éãÙYŸ×;Û‚@½Ç!¾J¹*T{ü:„ã¿oÜÿ3õÿü¡ëy™ÌÍäwyïj«3Õ™™ÊÌödÖ{·Ýæñœÿ39£ßó¿æg‰ÈÌÞ™y™˜ns3ð+33üÌÃÙͬÌîffkÚh=öo331¤“d[4¢ †%¦Ðlá5D¹‚t(îP-Ö5~ž5i1¸:Fçüì°éþ™ ö»êþkŒÓSØIÔœ×sq0NÎö^‘©îgø¨‰Ž*˜:¨îry¤¿º’/óœìÄÿ þ^—=ã‹M„.‚†ÛGã ìñR"È ZD +fì9ÓP‘Þ¡”¡u¾š²°?DÚ¿\†µ’ìUö{ÄŸçÖûy3ÂçÿóØ^”"æ=ÌÌÌ“Ÿÿ¾äˆ 0+IÍo=&ºó7·LöÝ÷­½ÿ¬ñK³Þï\IÚÿ[ñíáöø*\í¦Ÿkå΂I´ÎšWÆÕ{=þ¿0rwdUqw××[^îg¢ýA>Uæ{“ì•]ŽïÙ®ø^\¸9™îægžÌå‚o}ß¹¿ôóo»^¢ÚÿÌÿðûQ˜å Çg‰Æw38ÿ¿¾æ2 %fùþn;™™þfcÓÐ+»¡ýæeefaË&9u0ûþwÙ™øëKMø”]™‹ÈÎ÷y¨(ÝE¤Â%4((µ6!„'õjSW°Žÿð–‚6z 7m5ü‘ndÉ™µÚCø ÈÅh(.ÿ¸Jÿóßýݤm˜ ¸Ÿî³»ã®eûFlYÒp5Ù\¿ç˶î‘T;¡VW/˜j!ˆI“> ^º$Ïà*C•p¥2îkš}ŽëÝfVf™™™™™™™™™™™»wvþÿ?ÌÌ>ÌÌÌÍ×s2³3+Ù™Ýæew3331æfffeffffeffffeffffcä¥ãåVÙ<·çú~ÐôçùYküîdæ%Ýg³Ë‡w™™q¾÷0zOs331ffdëüÌÍà߳ГÌÌÞ6!geÖk¹™l:Ù\J÷Ë b2·<÷þÖ_ùáóŽVf{üÌ¿÷ü^e¼ÌÎæf=ßù™™¾æfffs?ÌÌÉ;ÌÞo3/¹™™™þfo3Ù†³37±þffffUï33›¬ÌÌÌÌ0;ì̓þfffaÞkÞÎff™˜šM¤i$ˆˆ¡`a¦E²öÒT-þS£R{eAb à/F •B#sszÍÓÏ6Úžò³yè–©3gÞ™.?³Ë­æ›s!ÿ¿^ç¦Iõæû™™ìñý<Îw,ê7þgùÿp@½™Rû*Ïù—™ß{÷¤ýŽ÷:ýu0×wægùüßsdßòr·3²’¨·¡Ã´–dßýŒÛh±½Eè&ÃéîÙf€HRí.ÈÖˆØüªÓ éŽ)ÿ–Qö«N®C“²ä=$“Á`™»aââ?¨ºö½ïÁ³šÛõwûÎ{4Úw|€öoÙèCs™ß÷ý]®ÏqfŒ{7¿ó‘ìÌ” ûOvÙþU_s3ü½{¹ÍŸÍi“Ýç¿þS ³ÜÎ »™™»-;Ìæó×°âo~ð½Æw?ÚsšökÓ©_ù8ÝcAíkßæÿçŸA¹’t¶9šŸýÿtÛ¬öo3~ØÂ.L~ÅW·¾çñâb§¾yŽò¥þ7»ÌöWùÜ<Ξo~AÜÌÞÍfóy™“øÛè¬Ý©}ŸgÿçwÜæoºÞm Á½{Zs¹ŸæVf½¼ÏcÔ*#™™8ægs3;$þff^ÿîo÷™ºÖl2s=ÏóY™ÑÍ4æ•~åû=ìïù˜%Z,Á –æÉLEIPßB!¹tüSþ›MÒŽH&PÚÉI¤ÍƒØ·ê y¨2¸Rý† ÅH‹ºTM0ÊXÝ2Cáã è4q w.çRjL$×jdÉ=ïOC™µn‰qÆAn†zËShJLW šlŸÇMÖH¾6™¿5¯p´\ê™8ÑÒ&‹Iý ^zÐv*ÿuÇ.íܲëS‹ž–(±ÏÕë¿ñs„J7¢ÿãj +dÏa=º¤Øxx +QÀ$†CŠß:ÎÌ(°iVÄkð¦t½4Ú÷UáY²_¹cJÚç`mJâñ9Ë/•°´Æ#aÑzKbi„8 ¸=a¬ëR†´Óì¹~Ñý€Ž)¿À3,Ò½P‘a‘­ob%&›ž´XmP¤Sú †‡dD”P©Gj‚ ŒSë „Å'I´J³ûH‹ÿÄ Êk"âÒ¤”- l‚(øÍŠg]!:dºš^d¶ª•rÛ¥®Û†ÈdO…½3!j9|°€ŽßéàãFNæP$ O†hß?Ày sDû{üÁЫµia©côm¢µ?Ä ðÿùháSRx}ÊAva{t%¢Q°*£æ«þo.5òÎk§+öx§ D¨- ,ïgÄ ; lþu,4uMÀP¥zžÄ;»à®ÑÛˆbS’ü qÄŠ Pÿ@-–”Èž 8M™¥ËeÑÔ“¢Í?ÐŽº.’Þ•‚€þÔ$âÖ¿D::C ’57Í7OZàâ¢lÌÞ³}âù‚`‰gœT ¢Ï•"h–}Ië­^Ì1ûtZòEú±–ƒuýâ:ý’’´Ðý¢B¾&L#ÿm:¾²«›G›¢4Ì_ãþO¢"ãÏÿl¶ÿÄ"R”Lzêw4JQ#ø骎&kô2ö +¬¾4ÓD¤ºÂ7#±M˜oŸ«#þÝž•Zºÿ«‰¨bUÙí8{ôÝùŽÂXö®ÂB÷m‡ÎcZ†i Ü„?‡çQ££ÎGo@êKqÿ·ðøÚm¶ ¿Àºß ´kžoû +pYáéźSÐÒ Ïšÿ¸¨2ANÀ†w¿<þJŠ ’`F†¸ô¿<7Æà¹ÿ%ÔòPL½Ëu®x-ëK×?ähEÛpKU Ãn9/ÍûW>?‰,‘¤™ç'Þê:/þ u_Ý®i ðkHÂ[0u¸‡“Íg§@Š Y '„’}¾IOçnXãj Ál„DLÉR9Ó æ #¦–¸¹Åú0ñL +ã.ÿŽëÝfVgs33333333333ݧwküÿ;™™Y™™™™]ÌÌÌ̬ÌÎï3ffff<ÌÌÌ̬ÌÌÌ̬ÌÌÌ̬ÌÌÌÌ£æ×÷ûη]ú _sO¼ÌÊÏÿÿ=™™ëŸf{8¹Üÿ32ðæg³2Ow™™™¶ó336·þffgïüÞ¹òöffò+7›ÎêßüÌÌ]zëq¾ïûÜÏN{³þO3^Ïg³g]ÈçsÕþfVfffff]ffffaÌÌÌÍæffffw333333332û™™™Ÿæfffa¬ÌÌÏfffffW³337ÜÌÌÌÌ=ŒÞgcüÌÌÌÌ©ÍûÜÿy™™™˜M¤Ò´\¢+ךh’‰V¢æ1QóMÙm¹x¯©OkpUÿÿwüO¸Ôˆ&ÞÇ(ú„oÿ÷›¸œ¯ýo¾¼v£—ßó3?Ìô6ˆ +õÜÞoú¸ƒßó7?æë"˜ž5Ü©Ï,“[¼ß#3=ÜÜ-–yýffzŒÄ3™™™›ÉˆU—Üôwü•eŸóy¼Ìî7f²27­sþe%¼Á™™îæ{'ŠêóüˆÐ;ÌÌÞÿì)©]­Þpí¤x«yŸço=m6ƒÞ‚ÿ¯÷_¼Î-÷عSÙhÐ 0æŽjm ;`„ ½tæÀh5A9f£zkSøNÈT7I1úUÌ’ì5ÇaE)½Ï…^»žÿªÑl4bË·¯Mëþ{ÿ™éÀ2{S™˜óæfwþámqÿsX|Î{;ì÷wþoü¼Ì»[ÿQ]èÏõÅÌÌÌ£Ùöd÷6ÄG"{ +]D/.×ùîæ›ÿ3y™¾ÆžfgËÚ3wÝç³yÛÿ?ÌÌÊRî ÿ›ËÍ—¿ñfz”ï;Ÿï3Úßs33¹ì«¹öøuÞìt©×r¿Îß4óüÿ?ö¿ÊÛ®c>½ÃöÕæW±Õ×ù™ßfxrÌyìæ*î™þ†³YÜÿ?ÌÎÍœÌöw5™þgs7•7›îgüÜäÖ¯7™íÿ™ÌÌÌÌÄ·ÿsf\êk^ÞžÌÌÌÌÌÏñ¤žë';“Úßù™™™¯ó3yœÌ{€µµÛ¸ïw™™ÜÍæ®æ%Áž(ÿÝæfgÿ332hl^wÉžó¹™™ÜÏfgr½ÿâзq_ÿæóýçs=ˆŸ×(SýpþCbKPÁIƒ €c³¡K Ad +IEòZ®&ª]‰Ih×%kCžm=òŸâ@JÅÃІÓCTQãéÛ ~á쎦 +U5‘NÜÛ¹pš0‘”h+Q-·Â9±6 ¤Ê½ÜzÔpÌ+€‚Wä-3£Q‰A?ž  ð§Z Qj(Ëó[mrÒ;ý¿öÚ,”W¹i›ÛÔ¯GšY½¤¯§Ç¯Ð EW èIì +î¿Ißþ!Ø-˜;²ù}|Ô²_$»©y~hroD¶í€{ ø‹ëjkSÞë:Úáãµ£Ý"š*0ªú¸þ5 ý†¡c¶OZëçéLj¤5¶ zõËÙ·P‚‰ð Ig MذÞÓêšž1¾=½UNÿï݈«ßPëï$½öÇ­I¢\Þº3›w¸}òt…ëòôA ÝÃÏ„eÌ.rø=8™`P<®_÷Y»‚PÖÉuD*í ò6ÿ÷;ÒSŽ›ÿ¼ÃïìÂ’â/Ì-;v¹ÑíÝøô¾Ê[‡7Ü— ¸•b +|›Ú0ùé[}`µ\¨b„:_ò´žM€.Méú mp¿ãQõ›©\×O$”GúépÜ»Ýð,Gé½âôuUSç0 í´õ$µ+´æe0QÏf½Zý€‚aå¸.ͽVï^.E­ÝNÖ-Çï¶åÌ›“SœUpF¯&!;gsÂì)öHwfi¡RÒ<‰Ú~ün{ŒæÎ~¿æñR÷jäŽ_ê^§ò¤={ݾÙô þ¦77·ÍsY­ŸÒÿ]Ì(yµ`ë‡FãúíûÜä´š‡nÀÜHNJ\Qcp-Z®&ßM@v7Ó½$AbAb%ºA Dx§È×û»Goÿ>¦½¤¿×YþÒ®TXçT‰)“¨×†ýß åÍ×ÿäè™]Ñ…+UÛ|0vÔ;ýËõZÝo$&ºˆ7ãŸÉ ¡x“ +”>sþ^½í/Ï)!‰zÉÒ9¼UPŠh Ð;†¶õ¯æÁÁ f,Ý“½©Qü‚R™€cp5`¼ÑÞýé×óº{p{|¨BŒ:Ô´-שvz¢‡r×gýú;ËÍo~Þ_äÒnîֻêþE%îßtœ~çxCõÛATÍꘗ NV¹ÇZ3íÍ¢Èh¯#®ëR¿ŒIgñý® åêÿÐrË y¬Vá.¸æ1u®ú«#ߘ 0RÌ=tþJÆõÓèßkßâ© €ÏôªM Ì“>#}@NsñÝc»¬ÊÌÌÌÌÌÌÌÌÌÌÌÌÌÇwwwwgüÌÌÌÊÌÌÌÌÊÌÌÌÌÊÌÌÌÌÊÌÌÌÌÊÌÌÌÌÊÌÌÌÌÊÌÌÌÌÊÌÌÌÌóšŸZ;ÌÌÿÖ#üôîdz3=›ÌÌÌÌËÌÌÏff³Ù™žÎffffdw333Y™™™™·ÜÌÌÌÌÌÌÌÌg7™‘¾æfffeffgsÕ™™™™•å[ÍÔÿ?ï3{ξfoä÷?Íæfgû̯{3üÌÌÌÌ9™™™™™™™™ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÿ333=™™™™™ÜÌÌÌÌÌÌÌÌÏ÷¾æfw3333;¯ó37ìÌÌÌÇm&ÒmZMݦ‘aë •þ¹-K]Ý"c¾$q ÞI_÷)sSÏÕÿæS~Þs.¿¯ñÉn†÷ïÒÊ•*ë¾Ìîg¿ß™ˆ¹öïv9ãäØîó3?Ë߯ŽM_³27˜â{žÑÍæeælÄKì¿{=™³¸z9šï³33.³?³[÷ûîgù¬Þb.»™ÜÉÉîfoú›îó33;“ïýÌÌßs3X¦²s¹ÍÇ„[;îfÿÌÌÍgn»Q™™¼îgRG_æfo3332űY™Üß³xœ¨9ÜÞo3;þe ûÌÌ̹êÏgþÿy¼ÝOi¥²/½ößuºÿ7Îæ{3;³Ó7ÝáÎB1›Þo»Ïó½Îóz$D=ÿ… +ˆÙçróúö»àÐu;rßâžzzðÕ„•Ê3·K9§^ßï0ïúÛ \záåfÿúÏïyžÿή9sƒ5¯Y÷ù™üÇsüÍæfïõ™èöû|­Ïùžö{/¹™™™¼“gÞÿ/=¼õ¬>Ïó7™ìÌÎæó¸¿×ûÞOsyŸä¿ó337ÌÌöó"¿ìœïfû—ßü§y™™™™™Üîf,ß}™™Ÿû¸sÛ÷k333;Ýû›ÎågsüÌÉÿ3=›ÇÌöfw·™™™™¼u9¼ÌÌÌÍç½3}ßñ™Yþffdg¨æWù™™žÌæYöñæfgw›ÌÌÏß'¿æfffffoµ¼öó2ÌÞó3yœ×?ÌÞ‘™™ÿù™™™™ŸæffaÌÏ™™™þDæ{33¹™™—™™‡vs»öo/75œÖgù™™™—žÌî~ó?¬îó3¹½žÞo3ÿó?ÌÏ™š÷?ÌÎäŽ{~ŸûÚÌÿ37™™“@ºŸfg.ÀŒÌÌÍßù™›€hhdÝGžù~ÌÎäÿ¼ÌÌ“Ÿñšæ¦ò#¹Ÿåï333ä¸m´xe‚ÓEH€ƒ­xݦaCT \Š;6ìØ‚ +e¦Z®ÊѲgNëIQÕ"§X çqI$(„Ü2Sƒ[i?Èô¶í¹–TÁ©¼¢8KÛÝÙÇü£vq+™•’nÎõvh‚.tk•7Ùÿ_Žñ¼ªÛ\P/­l‚ôëÃ@ÿ¾Àü­@|g³ µ¢ÓÞv“t^¡Þ°EÍMJUŒ[H»VrùÈk›iL?ûÝqmÍÿ9×Fãª<6"«kø¢m^`tuPÓjWwnùºäNéäI¸Ø)뵌¡³d¨ÏÄl!ÍrûÛ¨ôøé æ­tðKÛØZÿ(N‡ˆ™,’üòDUJ¯‹kµŽOŸÞ•Þ® » +t:kñR–1ªÿ?k‘Û”áGN»ÁU¸zæm¢Ñsk€YÉðÒ]¨Û¦ Øi‡3|®íghÒ[똕¥yÂ+;;ÓÖã…ÿ“>l†FûØÜþ·ÂJ…¦¼hJµµªˆÜçi$NrGkÞÚ™ŒïªYeÏò¼?ÜWvO\ž#œÙß^¹ÿq~½k[ñä ]ûþO«[¿àN[ZÿÚ·qü×€tò×"½ÏF½¸;¬‚d$”1¾é„U׺Ôb%Œ ´7žâLoZ ‰Ð|ß8à„%ЋyÌÆk›™ã4Jïï_”rDÿèQ¶µ¶lÅ/±ª§µ¯¶ïó+ûçîôù½ó?ÿóFä’9}>µˆž‡ê)£¶J½(Sw·þÀçÿîµr]†â´ÚiÊìh×»oÚÿ)»cM”¶ôˆH¯Y\ÀÓOmG=^÷]eÅM6Õplþí¦ö¨b¹DU0eÍ‹ëa¹¤ƒ%×riˆÕrwíè%}’*Dƒ¯Dq$åX|ÝŠõž!^¿@¶ ¿7fš2нËz˜t¸.¤´OéóÍaì["š{zF +}JǘF½e¾øŽ6Ùˆj”¡"xüUúõÏýo¬pisV -ÙmA= îhÄ*’ú•Ø3î3BüÕÔØ"MrW9þjªºùï8üs7Ú7á¸M·n`ÁS°¿ó]“Œrc4ýc\ŠE‡ÄhZ‰,ÈýÚpãýϬg-Vµ/Ѥ­3h»’QHÑ]å{·ê«Pÿ>“Ê;îwþÓÛvjñþ¿@9[UÆ“}ºZ\_ÕÄþ³“ ùÚÈ#wKº9Z¹ü0+ßé«ïjº|´M'p?¬×fÇñ†7úÀA¼„† ôí«Ùç'Z\\¾ÉÓ€nNô<¸‘ìJ×GQÄß/Oýþ­D_9\ÝK¥Pv½,‡ZË‹Ùþ«ú«Õ7ë»}¯ÕÀO9šÝr¡sÊÎÒ:Q®[Ó~>YÿÈOþï¼Îl{‡¹ÜeçL;ÛlrÁõÊ…ru\ã¸ÅKz?Ïùr¼Â…¯^ c¡x÷Ùæ­jÒæÃ\Öã<5l=4ûÍõ&Õ©ˆä±a·ÈõþïÞ<í~}BS¸,1¯ôkÊ ¹‰q›¿ëD×[ˆ”ŸëÜsÊÞý‘« ñ¦¸ƒñAŽ["tè»EÁz”Ï£×iÚ¾yÉp8ÈŸþûÔ==é¸ä.@ óúÑÔ˜C¾G£DÅ}c¡òAüÊŸÊ£·|ÿý€tjõÊ©<îóßëW—¿Ê¹rœ§žu•’¼1;ßMÕc'™—Çúóïoâÿ¼E«¹Ý\UoóúÕë_æöÊ×z´mYOþ¯ãs«¸Ùò8`X†ƒ1U«ªü®¯MWyÊÝbvç“Îu¶³Xj#zµ{ón÷›‹©×uãE/Ùÿ»«VVÿå¹Õ¨¯òóGÛãʇ?Ô/wþëNgsßýÿÿÄß1h™•EûI•~Ò¸× 6øOô\ƒVcËÕ™›FÅo]«ŽG·ê/¿äÕüœü‹ó÷uîw•[o˜^Mî`J¸7yʨG+ß™ØHŸpñŸÿ‰?õfȽzS¯.ïôp×§Ub?uã¼°©ÿ}2@ºŽï°¢Ru»ªï}mp«îâu-Ç»­Õòiâ‰íju—³Ïùh6„j-û;à9Z Ôí^¬¬0Bzý®ö=¸Îç·½4Õ<ÝÙ4ÑÖ´¼øÎËŸýWûHl–XƒLuh Æ÷=Ñk³xû¯Pÿßè9Í6ŠT®ù€Ó¸WüÝU8Ö¡ÖÞ‹Eïº÷š:Ã%W•æJáÖ÷U´ë?×ù®…Ï;í0+ÛÔLÛ-ÜT®]v¡[NæÎé²\v5Éç¸çÕU¥¿Wí~ÀzüÙ²Ÿ9p8ý³üÏFã[¹u¹›^ê\ߣdeËYÁ( +DÊànÅ ¨{&+Oª-{Û5ˆ*±{ÙƒËs!Ùë%Ä€ªnÙOazt"@(e¢¹ë1%vÆ­ä¦ S0hê9 -úé•ôÐa²Øm¹M +¢,ϠͣQ¸ù˜\n™X]vs®8É©õ,‹¬÷vk…5Â×h@¯€Â-û4>b¬þ…âeÄ”¼6¶ãÇ’r!R6(+Š–°­t2_`Û2ºh‰ä²ÌhÖ´ÔŠGÔ‰@¸ÿwËÅì€ßƒ)+^“¾4¶ÌÁº¯#&JK°¾±ZÊK¦^ÆfB`L€•„Y›¦ÒÅ!ÛW¨¼Ðï¢vÇˆÞÆÇ˜Þt®Rz€Ÿ“>ùW*Vþ\¾ï;úéì½ a+‚¦óO3°V ÷î83÷?+Jjp³Èt£Ó(=:îx¦\Å MÞ¨øâè¼O.´uÆkêÃà©ù¸³”¥z>‚vÁØ9ƒ(¨ °ë@#ç†ÎœMJÎ㱇¶bÿòèK5}Wݽ|Î2»‚¡}‹~EäùiÕ¬ºsqiVMSQ%X7ÿÑJvE{'óºølò’§åïïö?þPÐ¥Ë;JQ5JРÒì`…gŸcèås¬òÆGrzº¹­$8}¡Oû·yƒ>*–ŒX„5@Ò.‰Ë22%ÿu”ÐUnI_šLOQ‘¹f ãÁyU4‚E€½ ¢%¼w,€`D3'~@í {šB;‚¯ûµy%Mþt5qØ"Gã,“M)¨bY¿¶Ò:·@†{Á>ÌÆ©Q•ᬨ@O÷¯äI'¡¡_"bfÁáo[¦ë@Ç”á­4A8ˆx€JƒyÂpˆ>íBé„¢5«5Ùì¥{*k´ÌûÒ‡UìO»pË#Ƅı¾p °²t‘&C×ßm|ÞÞÒ}0¤=ž’õ |h˜)p"F‡XCÀ!…+/¢&m÷_8Yr1Pso$¡=ˆtÁˆ1ú`€ÂXl öéYù†nŸM6Ü8¶gøÉ;VC#’"½€¢¨,å¸R)J2ogíÑ@Úÿr<c" \#„€‡u€£˜@W9Lw‚&-jðQc[„ÀaCî`€àÄ€î{`[̈ˆ³\Âð@ +¬Â‰l‚ P † ˜`D,FB +9”‡`Lxú`K`B(X@ €€]†V€•  XF‹DÒT &ÖËBIA +‡€W Úì¡­%þ¾ž‚€,ƒØ€À !"€‚!Å©Ì= ЬpÀ€ r@ €Ó;x …(v)¾»{°@! ‘І˜Á€ ÂF'Hê€À>GæXBHPÀ Yˆ(`#°³I€ÎJ þ{4×Ĩ"ú!:Ž€Ð¾Ø€B"\XÚx„BÏ jpä®l%âè¶!‰rð€ +rÌ@e!BŒ`C˜gLuÇ­i,1‰"‘¨…X¤6 åï7†YóûäfSVqã&ëã‚E`%ðDGdðu¿]WñÄå=éõÖñíô­}Hâ‚@81ô¦–wA— +\½»vÛ~áÿïµ3ÉôWÿ¥i•q„@É%p ʈ¶À À8 Þ!‰LƒÃGè¶ß­~éû³D¼(2„PE 2€üwŠ„³%+š!…gx"AÌeÙ½€B;8†ìü!BuÈäËÈØV©¶£öÏá¨vûñÀi ƒ°%]°SIòÀ )n÷2„kó]RÂí³Ü²o`£…Ô ‹Ï£¸µ±¥àw¢x0‚ºÙ¾™Nïv„ˆѢ•u¹'Û"Ü€ÄĦA«4OŠ ƒJ $%‹â™.{z0oTèLïX÷Í÷–/ŠEÈÌj¬Ñ«–9?ÝáøøLã48T‘Œø ·H³®†ÍS%ãÜ=üËk €’Ál§ÀÄg¼4ù¸œZ…F»~B02±E~ eNϡݡTd6½úß©âq¹€)ùBf¸v7 ^ˆrd/§'OÊ"LBÆxpi:´þj¢5DØf­×#|z4¿õ SJ€¨X“ß0¦¯B— ŽŠ¤¢g:¡eäÂõcè&öz%Ü÷€zò#ú:”’ž\bê­>h©äýôØÀA {¹hMEÃk’o™'6}V™ ¯ ; ÛÐô9YÀÁiüŸ®$rÀw›8·fpÜ ++ðïÐÀ@>¥†p#ã¾(G]chˆ’{=øú>c~é6´ ÷ÛjŠ¾à “Ø5){ie‡ô@ßš‚ú ü—Ô*Ák­Ö/§ÂQ±‰F"à¯Þg˜f…ô3‰Ù†³7Žö3L~4âdxëM€ÍqN­¾®OÏ×dÙ°ZØ ÛyÜ0 +’øéÕà'03|:Ôæ a@,xQ=I†ÏQ(r„ K*¿¶t—Œ}é\kq†nû’¬P†|)Þ-ÀWrGKdÜÎúùüºë]‰ßÚ dêÔ›IEÀ dt¾§JBS%W Ä2ÆÂwû_¾¼[Õ×^vçRصC:ƒHM`ÏJÎ}oT2“Á›SÚ‰1– C%äó9VäsO½§À¤Q¢¼:ŽRùÙe—ÅÎBdf0`’'LÊYÚ à¼“oª(#¸!Ò4ç"F£HRC•É”*óýwšÑ­{L¾’ØmŽàÿqhÑsZ… %bÌPZæðÂ÷£f¼æÐÃ$ œ÷4Í•ÓfP±‡ú½0™p0f(‚;…—©¸DEÕ±§Lÿ~·Âúú¬ôEW•;\„˜»- +éÐɉPÁr¨­=k·ßöC{Ã$,š«˜èd·R4ée6Ð'ö5ÛTcñßÒPb»Î‡,¬Ñ€'T'òÁ×LHÓÎ"/ž‚ `¦ëO'´2{i ECŸÅ‡­ûؼˆB„\ƒÐ"Õ¿Ü‹è "¯eÙ³úP*¿ŠA™–º€‚‰”‹${r}hh{×Xæß&³Œ%9åH BH +ðL!44, œ7¼yǤWâ[þ. óËÚª “¨(p(@shBA#þ4CºÌÌÌÌÌwwj³3&³3&³3&³3$ÄD ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÞfffffffffo3333=™™™™™™™™™™ÜÌÌÌÍ·wwwwwwwwj½ÿ³3ÛÌöffû“Y›ÌÍæfw2s0Vffffó¹ìÌÿÌÞ^ffdffw&³23üÿ3333¸«ÛÎç³7žÿ330<Þf{=¼ÌÌÿ¹•þfo3Ù™™™™¹¯ó3¹Ÿæg{™™“Y™™ÜÞf{3ÙîÍfffffffw¹•™™™™™™ÿÌíffgs7™›ÌÌÉ­æffgùþf™’û™™™›ÌÌÌÌɯóúÌöó3;™É9™y™™þo33ÿMfffÿÌöfó3Ù1LD.?ìV›Ž„«óù‡åõx¿V B÷Vµ×£-ê‰'ƒ‹‹¬ÍÊ<Ü÷Cë$>îMŒÿ^ò{¢6´;å³_™3á¾Èê7JÔ[ò­:*ïc^[¨ §é]Dóßÿ´ôùv`J=6ßëªóåQ¡êתÃõ‰ŽìkŠâx»Öâ4k É:½Îs*Î_]ϳüÄS¹JœxÒî…çéíúÇkÆ=¬õûÿ!˜»ˆÜ`©ãü^[móy¯F½çŽ=³ê­ú{ÞDìûþÛäÖó{ÎÆlÏç[ö¦¹¥§5êѶ¡v}áÞò½’Ö{GQ· Ý+/Ü™¯ÿÿ˜³Ý2»Ë{ÖŒ)sJ|·{}߬{?ôÄGTÄ÷¼þ«ù×oÔü?ï§¹"LLë_ÑÏ÷³/Ó¾l5µÛñÞ˜“%ùþûà'?bb…þ¹VþWÿ¸=Œž‰Îû&EoøQ¨ïv7Qýµ}ºòÐåJ¾ØËžÂcª›r–ýSTµó–Þ¦ÜóñØÙ^î]ö´eÛ»cEG†Ûçÿ¯ý[Í×ë“s±§¡ƒÏüýj$×q{û½VmA<ÝAÝ]-^ýýëx¦Ÿ aêÍÌÖ¿ªãc_Åhs¿ÿ#zÔ»_õÐ×ÿõ>û?|ˆ.?Â6œŒå÷O4»;3Ïn=¯²¿¿èf£Ø¢´´ÿç93|“þÖœð þo9zŸ[ÿ3ë^ߺc\SÑÿÿûúìÿ¯èÅ×¹¼Ðóðu£×œk=qü×6ýµ4{e*»Ïus=‚+ S>—ÏnÆn7ÙíëK—ÞõÝò=þûÝp{/ÚTÔê27ɧèÎþÛ¾½NíóR"bÝî¦Æo×1¸‘:¬3:?Æ.DÏ÷¾^Ì®g?ð_÷`(]»ÔDFçó9gýo—¥#'½½g½@A&@‚Aˆ?÷½£²Ò† D[kÿGÿ¸ƒLbZ6Ûm¦µf8 ¶)ì4áµ 4Ce¤ †B¢&Û^ +öM˜é'ñÝcHæ<Ûàm1™cÿö “l6ÍÛkÖÖjá_0O„h±&)y4­‘øüù0Ê0…i¶œ6• 6Ý´I´e4@O\3. +H8ˆVÕIi+HŽ$.'šmwdS„Z¶n¨“-I»TTíÑÔC6AKù4j_ÿ¸×€ ’tëþëÖefs‰¿ÿšÌÉy›æ“l–[7BݺYÑ£K3Ñ']¿ÏM`QH>*`ˆͳŽý¸l¬ßàƒøÿ~„ÂTkt¢È$†Ú·ZWâ(Ú TÿiÙ ªW€HdTÚ _Pß,ÙvÕIþ2åžêöQ ïѯØPþ-§©†­:—$†¬‘Îþš0•L˲*–îÕ¦’ŸsdÉÀèWÊ!JÉÀMPf LÈ5ú Á¥STBþzœ@jøPâ c]MÊ'‚Kl–ŠpÙ,œ 2wMM/LL’)¢[e»‡)£àKfí8Çu]fVcç.»™êÌÏ<Ì徫a’™põNä[ÿÌï¿Õ{3Ù#ºÌÈÛ9ºÀàèê=H?v†ÀI‹– XN;ô¶¼õIˆUëöCM:öû0 +»ç³3I˜~¿~Œì;[©¨½Lˆ °D¯E›!4Ó·£¹“«IÇèˆÁù(„jgE=¨Døø~äA ºº˜·VÓÏom~œÅóþ´jOnrëÛYÝ$U†š8cþ@zëj€v„;1U.4Â5°ïo.ÔTµz’ ˆv­‘ u¤fx¡E¹hÞ!´t^&AƤ0 îÕM´O4,Á$‡ Û¿-7-‚À "Yô4E‰L¨$¤CüSNeÆ2Ñ ðò.Öí=×¥"/i~?DœD7mera(f¢-†âö­–¿)ºj½50G=ã¹-€–6e킵 šýáÖÛ¾HD’]$oS«Üzz!º¤ÀlŒ m”EQ.‰¦I?˜.À,ƒZjlÙIº •±à ~;¬wu™Y•êíÖfz³3ßæfWù˜­¶^Šºq{ÿœˆÌÏó×y™›ÍVf{0æfFÁçùŸäÃÿ÷¸ê«ÊŠÕÂp°àní5]×ÿ©d1’‘¯`ÇýÞJM;¼Ì¯"‚ÏfVíÓN½Y™“‹¿óüÌÏs}ðˆ—¾ç3ß4ÝœÍòäϪ‹ÐºÄÈÐä4؃OJÅ­ƒAÀü"%Ü&¿ÕÙG2ZÊ‘kq 4ŽÅ£E€òü̳ ‚ ;A§PÙƒHrY¦²mž ßþ<%š¦©KzM☪$q6Ì—t|ˆHÅŽEMq†ëiÕÙÑ,Ó Ý–…Ü‹|4‘©”ˆ&ÒêR)²JaS'ßÌ2-C±é{©d§·«¦•é)¶y–Ýšà膫 £W *’‘)6$ìjׇ@Š×–ô@‘h?礕Ýõ~.E=kHWHH©¶,iöôYâÑì‡gУ§Ex#)®·Wr‰>¢ÈICçySéÙ©©4NüwXîë2³?÷ùu™™ÜÌ÷ù™•™˜®Ý¸9ÿ¿o=|ÎæffÍÿ™™™®æffLfffo¦ó33Ûpgù*ùþ¹ŸÁÙ»‡jƒ@ÂÁýÖYß}nÿüà¬É˜iGwîÿÄœåœéŒö^d«©×¿ÿ33?¼Šß³Ùÿ3=ÿéÅfff{ýÏE¼ÌÌÌÌß$ã3y™›ÿüÉh¼ôfg®¶÷“þãüÝ”ÿ´È©ÞsHºÜulÁçïkÃ= Í»Ùþ] ´gr¿“ ]¦Ô5Rˆ¡lï m‘†¦} 9M¢ìÀ>k•Ó•æÈƒeäßf×èÅórªÀ(hVRê&Íj+dPÿÂ¥åfúøáÿû#ÿ×ù6²³PÚl‡¯g¿öf$ÿÎÎkñ‡‹ü2ÖÜ·ÿzÿòÜÿÞÍF# lÚ¬ŸfoÛæ(n]ù5þ*]ÿÝœÌ÷ýÉ1öÜÌÌß·±´šy†ï=žöw76ަÛF}[öfs¾·qùûY¾N³‘‘úÅ_ÏŸþÉ¡þg}Ú¿öVöyÍú³&µG=±·¯Z…’¿›ÿcÏOÁ–\n¤Wµx²à]É F„(Í“_‘ùEÄíi"7í1ÙpÑÖÐU#öÔVÒ©iY ŠãQ‡?$ï¹>©÷)ãi¯)&Ai¼¿+ªŠ×âÌ P#S$ïñoG;21¤Ú^~K´š-¢¬j±Â‡vl?!}Ú¸€î»rÙP@ d¿/ʤÍ2 ‚kh™tÕØoJ€OpN”òÛL¢ªhô‚GX>òhŸãÔV¸üÙštL˜N#ByÈ&:z&Dˆ†úíJiûkÚ‡šôãÙ»Ïôán{mްÒ~të–ÿH&æì9vvŒÜ*HþÁûd–‹r 1é!{|æ”NÌ©>mû÷mŽ Û‘L*LÔÿ1û0Z¤;FË×?Š$ƒ­òÔËiLç`ÀCi)A³!ò=S¡<â݈ìøOÿŽfÿ°ÝËe_<JÃ|–°$ßlŽ×9¢tLq£û"Fµ-×M„ïü¹`ºX·mŸä +VÈv?úl½¿Q(‹€Öù©™q.¹î»’ÔՀâmï(Øi´J£|j¦ n(@´ú ìq;ÕµTÒ;O»Çµ ü@a'G¤€÷.I¬s´ã?×þ4i[Dc…ƒ€%›´@ žô ¦Ül*”í5ȃcgö´]d€ 6Ø K$„¿¦ÓéâÕÅ2R"¿Ù¢•ä Õ QI®îÚ4ÓO¤ÔóL&Š pE~Èœ¤ëhâÉ ¯j{ßÊPØ ®œ2£a§Û´†Éj 鬇ÝH#õ[´aÉsøî±ÝÖef™—Y™™™™þfeff+»·o+3»ÌWŸæfzó3330æfffe™™™žŒÌÌÌÃìÌÌÌïvs3#yßsÿýŸìïñlåÃôÂÔ4 ¨Ò `01£Ûèÿ/ßç}m{ýæãY‡Ù¿'®n7Ýæf_s5äsºÌÌÿ3Ëúݲffff^G5[ÌÊÌÌÌÌÿBê³3332p]þï333327¸q™™™›ÿ3%®Þgs3ÙÓÇR÷›ÿ#3¸ªŸò«yÙÌæ÷¨¯÷«qœ×?îÖšðÌßN!­Þß½u9hú|yÉ ]¦šm>¹L¹¬mdÝj¥ä<Øgd4Nýû;kwþÍl8joùvhûÎî­¶c&¥ÿu±°—o°k¡æ¨^ùŠÌÁôMb¼ˆ"NÁ•ØwŒ—z›F»þ,îMdƒ%%Õ›©Î×ùïáPîí¥SÕk»}ɯýw‹0úÚ© ‰ÅÜÏó?ÏóÚ œ»ægÿäf{3/v½ë§Ùþuÿü¼™ÿ36w¾ß ´"¨ïW³7™™™¼ÛÈ©Ï,ô<ï²³3˃ÝÚ‹7ÌËÎæw3'}Šó»LÿýrÿÿüÌÍç²…Ü[<¶›mgc3y¼Íæfâ3bÅg‡;Ïo37™ìéɬD`ÿÙY¼Ï÷™ÊàRNØýæ{=™¾TœPõÍsSÏfc™Ë‚ k6µçYîíÿ¾þ/Í.s2{š÷òs$+wvhÁåö MÃvAÀe@«BÒ¸Š~#)ÝIŒ€Eö^ +–ÓY:©â0ÿ™².m;1…þ‘ÿn<ˆýy¨òu?ÍÇ,£$5¸ðx"¤—ú¬ÈŠ­vIÔÇÿ‚é-’OQî¯ØFÝØà!š^²Ÿò õ¾«$%Xf±*¡úYuK®%Cž) >"$°­%&ƒ'J´Bªèv•É$Å;7*I*å³_ê¸=4RziTšˆÐY4À .Qj…ù4¯Áž¯= ?òË0“æíÙ0ª\ñ6Ÿ8IMȸ<l–A$€f-ú Úm½Òçó ß8dh’ˆGízgªíßÔ;%þ×hú/ð“ š?JRæI¯r‹fš—Ád×ãºÇwY•™þfþfffffffff+»»µYÜÌÌÌÌÌÌÏcÌÌÌÌÊÌÌÌÌÊÌÌÌÌóŒÌÌÌÃìÌÌÌÇÜîffaÌÌÏó3+1YÌ—k=1쌎ÿ»ÏïJ?äÿîûyžäp;Ïgù™™™üõßù™™¬Ã™™ÿ?àÌÍæfffg¿g9™™™™™™œÌ¬Þ³33333yn»™™™™™›þóB½Ÿæfffg³Ë^åæffffó7;ÝæffffffqOÿæfffff÷çêÌÌÎæf^M?ÿö«33;›ÌŠu0=šîw3»½™ðÌßOøàGHeä?owž«©¬zzöó®óp·mZµi«QSSë`Ô½Ô»6R[bHwx+"³ðý¸þrõµ–¬ÙÐñ¸¨…Ïy«Á/Ú’«¾ÿ¨õ±Ì¹kÖ¥õèÀ©Ï-wÖ°‚§¹ž×/#2Á÷ûŠÝÍe»YîäffwÙYÿ\îm»ÉYìÞ<_û.³¹Ÿät÷Òo;ßVw½—¼Ïwvû6»&ì]¸Ö)ïúÃßý¿ýx;•9Éq™öÿ™ŸÿšÃ¬îg“ÝàÆë˜.ÿËîg«ýç3?ÞW¼ýÿb닱½ÿ™žyÜÎçr-÷28õÅyþff{7žÏg{ìîY–w“œÿ3=¬Ïfg³3¹þe®£³üÌÌÌÌÿüÌÌÏfï&‡òH¯™þóüÌÿüÏ÷™Ì1 ÿÁY¼Îg·™šÌÊÞvÇl?ÉG¹™Þó=™™™ÿwÿ¹”[·þgù™Ÿæfg³?Ä"øñYìÌÌÌîg³3½öÿÿ=›Bë™™ÌÌÌÌÍ÷3¾Î‰¯;¿^ó3?÷¿÷z¿ß¶0m2ÈÖOãøîw3¹þgûî¶ÔÖe{Ù™èÞë;™¿awù‡YÙ¯gBîfðf&ã¶Ï÷syؾÿÿ–ñQʈYÖ!QÕ .+2åÿ^ŒpÑÈ ½aÉhÆ?TW²n§ô¼bÿÍ@ÜrÀi¦ï\îü½­¶Ó³Èk‡}1û°z ™æ$\ä«kÄÚÊ Þä œš˜¥k.»ÿr;¿à&‘’$ˆõ±jyžu \ ÝxÅQ„¼‘ONñ…®~9pÂHMH¿í¡€Ï6tv*kfs Û™@®ÙÓó k&²iêŸ9{(5R€”d~Bc‰ #(q<n¥ "©(à ° ÓSÔ¶’UÛ  !DnSdd, %AN—€‰¼ly’u»µ¡ÿpwøT¯RÍl”©«_ù‡\“ç·jcP\ÉšiÝÅHuÞ—ü¾5ïR³w3 Ù›š¿ÝÛja.ÿܗ'ž´äGöNÔÕ-–cAj Z’á£×  ¹2Í"_mðÞ7¢õªœßWû¼‡"ÿ_ñ^À4˜P:ŒJÜ1]®(‘5ÿ}ø6[Ú$ÇR@Èÿ“¶â_–Lÿ²]¢z‡…i/atûqIN~ûBˆÝ‘ ë\i`XlñKaþÍ´4%åí‡Þ+]ërœ¹!%_ažêó{å‰Ìþ7ãük#xËS^+ßçP’&4;Bg^þµ‡ý$9 ÈNô)à/ˆßFûß=È׸ÀVJÞÅPâÊiÝ^ãk‹HïœÁÜ/7 Pý¶SZUYSqÞLtN«.˜­Äo“IZ ò+þ¹„WóƒG8T«\ ±Gœ´ŒâÖqWÿú. t­@ߎhDØÙŸúLÇ`©¨rºÔÄgþÁ½ÕÅì¶¼CÏÔ*_ÐýËóŠ$z<ä’N”FˆYgCCŸë¯DÀìúµüŸ:µé¡lïªÒ‡.AØt޲v¹¤)žs¼îòPPC’›ÉòŸoRfo¯– À|5¹”ÿF.ɸS +ç[¦gÐAlDÿ@Âó5:RŸ 7Qâ÷E–”ªŸTc›(òä.]b´µ|‘gØ„»è¨ÿF™ŸmÔVè2é¬ß¢3]ë§n§.[¯ùÞ¿ÇA” aÔÎ1‡h C¦¢ê­ +BlŠçèõ®!&;ä^c­J[™¢Ê/v£K +£\>çƒ6A® Jiþ„¢tçÿÃåØw¢»"cÈHßô›Þÿ4gäšMá*ØUS$Ö´ ¤ù¹3å·8?##…L@În=Ä‚‘£äLXI§Õ|³ÅF}Çþ|»­…C2Ja–”DnˆJ‡=dþªDuÂó;P}I( ùYÂÅ1lĶëýøëdŽ~á= ÓD¸‰HùCä$‰çKs$%Òì·F«:3’ÛMèÙ™h¶H;m¢«þÔêÿµJÐÄN¤ò!€p6“ ¢ŠÂ +¢ À j’y$ "X´„I pŠp(döpÐ +­à@$È@@¶ €€%_ZP€ú… Ÿ8á¡X5˜Ö ˆP@… €Mp$Nø$…c€ æ‚@Q$Šª à&° ¨È%°@=ÀL@"@K€$ ©&H Š€+ÙP( )z€„Ô€_Pð$‰p²Ù€@€€b†ÔHÎBt8à  ðÀH@ €@["Õpž ®¨U@€ð Á!ŠíƒtÒ a!J  B €ÅepÅYEÀ‚°0! @àcWæ +ÀàQ p’Un€ @ATЀ@' +©ÂD€P x•PÈ`@PÖl ¨)‚@P c¸àaU× Pb¨ ˆ PD@Þ ¡L Ð’ÒˆE¨»CPPP€Y”m +°¹€€ÀAw\îQ. +ÀJ'Õí2CD‚ €r%€L"ó` +€2t+ÂÀJ:@A|Cd˜@@€ +P óôã2‰Ëè@›è‘S@Jb00… À$%æƒ!h Já{S ¥õ0¬J2š0‚!äxw0]!Ò{€  H  ¤P€ŒÎ6T‘°ĨDÈ&_åB»`€ +à"@ˆ@Ùp›ÊqS¨×@šVæ:„š"³(±K ^àPìx;¶ànÃÊ'wÔirÄbIšð¤PYF`®ò‘ê nq Y@$ºÔi0íd ªÉäÁấ _K¢¡Ÿ‰Ã]¸l’FÔê¬5 f ’⥒dœ@z¢¸åÀÑB •ÅXds  JÀb  f€ ®@Ïõ?ŸÆ,ßá]p ÕœAÌÂU0\`!Çay(3ŠoÔˆ¦kV€A ×  Êݽ‘i_ÅNÏöóÈÊ:ö‡>¶H%HD@Ð8ì¯ 6'J4UMð´@U¬x1Ò«[-kS P¿ì5AólIcœ(0Û³ßSÍ^ö +ï\ LC„° }˜@=©㉦`†ã?’•‡æúé 2€Œ‚9aÓÍh¡çÄ}þ¢ j~~o7À²J"1"€Z Œe†¬^¤©ÔžÂk_›lø`ˆ© P®„€Ë RÁVV‰F‚£Ü¦G áágiËø ˜e"ë +fJWA|v±úŽõŸÕ´¸ŽùûVÈç€RÁ2P  +"bAê‚Pu/áâdÝr;ma8y4ä;'Êÿ£@@H¨[…†¢< +£\‡=2’ÄŒd' íÕ"Yé¸0óÐ‡ç¬Ø±÷ùú +Sm‡²`|æ  €Z±AÒ.¤óRUóC‘tK”‡r¼ÂE01¿4Ž +V" +M¡¿o¤ðª¬%Z²"€pçø“¦+‹ÙàK¹ÍÕÐû¼ç ³Rnf$²Åkzž4ÛÚ¦þíOÉ^Æûë „ ˆ‡à… bÒ°¿‹Q:q„Gi²å¥“éŒFETÂJFP*PŽ XIêä)mÑeˆ»˜Æ¦¬à„‘W)=0âA  •a/’·ÃhHÜPûÑj^h/›3±àþUÔ½‘BCQ@2„† À_Å)ŒNÖ.‘9V)œ…»ñŽ' @‘T@¢.\Lù'Z9ž±%ŠYðkr¸jzö!@ªw }²DBKœ.̘²˜ 5"˜‹”@D B&Q(J•HB@è³f-j¬ð¨st°äCÓóxƒhˆHÈ!¢8pùSÛ.UBG,ÖÑõÞ×sJ6H´¤@èP`6HGh¢"½|t DZÌŇu¨oÿ¾<ÌpF'Lî’@ à%gë£d å´ÖïõàtÞ€Áh  á…ÓvpžÔ O”œTÂ&hùÿÎ1ób$ˆLh§ ¥Ù@¸@ Ff + ÔÔ¸S ÝE_M]Ràœ¯Ñ’D’Ëð{•EÆé`sÈ–ÓD)jé„`ÅtÒ>î½ÀOH8º‹D +V˜Eiãe=‹)~éæ-V(*~2Q@_ó ˆ« —è³xW¾æ«Öa9ªiåé“…9l`«Õø²‚XH×x°b5ÈC´%pÈ‘)@I +WÀÃ}ðÐ]` … B,+¯äûáPè6Ó½&–B…FpŽD$ `Ï` @Emp~Ó3žA)á0X Ûk PUOªŒVÁvÛÖ —— å!^'ùG +÷ l ‚ H Ê·x™sä“Kd\[/Ã6"h̰TÌdZ!pF U„@Ýè3H!l K,\¶×»UPJ í[°À&‘ªEÙ‘Ô•€†¢@DFúŒ1Â0Õ¸ Ñj'‹AÐ’( *¥VÇAB[¥€¤¼ {E€Ï 0 +ÐAèTˆõ;“}@ +° /® d‡Ofz£‰`ø{…Å-C¶H’P€Q +€€:ªPP4P¦Ú6Íî Ñ"‚Wú©AP¢„–ü" •[Ñ¥p ¢Ä!Û"“D‰…"q,Õ@ e Šs,#`\(½bd$0á* ÈÐ$ô@ ÀÁÔE +¥öI Ò €Ey<¡%T€T…R/fÔ…lVÀ¤zEÖ\C€8 @D‘ €(~I§HPn$yMT&np%P B ÀÖ’¤EÿÃDT^€ÀØÀ P KN… +*„7Òòì4€ŒL8 €€  œ`A J6ò´TL'Òâ @ ] Ð›$²2®È£ P(€šÐû‘!ñ"PsT‘ >Ðì_ò‰@ž“â$ˆjÂp  Ú€¾¢ˆß  +¡è²`ÉãÀP +,Hòw€ãÔD¯¶Œ¶Ä#2pã@¨Ò²Ëà@ZH4å Q žÖ¬NrS @ +¢ œ—!AAª) +ph,m" ®à*¢rš‡A ð…à`4ŸˆÑ(à€ˆ„º +"(€ r`9±›:ê·i;L–Ù¹Š`€x€ Züæ¡V7 «%ñ@ É{BŠÝ†aŒ ˜ +  ˜!K ‹ÐÀã‘,ª·¡Ýûþ‡ñr Sð†[¤”M”Šƒ>ðœ,UÓ$FI@ìà€5à ¦Ð¢P´Ö”&X£gXþñÓE`©Œ€!³¥P)³ LD²€X€¹á•$":F÷pΘR€"É@ "Àcª ªv1þýF4Â"ÍA?Á–õ¦€0@¡@m•Z ˜ØøüD¿ÂG€ú rW}œámLÿ³ ™P }’¯£(AÔ¥ù@—˰–•&çwù·åÅ ˆU˜ Yå c’ì/%…<û±«"M©ý‚^µ)²c!ëôX%0¯( Dˆz¶¸zÔS*¡s[ +Dú©[±&«(›ÉXìÞ:]  å)ˆãP!oà÷ö…Ï£í„ù'(C=ÇÎP6Y¿¿e`ˆP ¤#vT³(vPš~ •ÜŸ5Œ˜¤Dhd7…ðH ¤”_}©ÍÔÏ”Ëdˆ±ÑYß·+ÅÚá¢M„ê´™ÀŇ*ÁnñԀ V•° ˜íIEÏÁëá^%Ÿ­ J*ô˜˜"i4À!@.œ`ScÓ)F!’ð+q!þDˆ&L%  k„’%Óz+Æ­w2)~'.ùü÷¶ÎÉÏS @ê@G`( ‰J "0-|Á„Ä^¾W†w®Ei†i|rQ +n ÚE¥eöèhFc°U~´„¬æã•žb¢ _X|ÐF €Œ¥ë °¤®¶¥l=Á9€DmD<ö6˜ê«ýì¯JCÉ…Ð×àÔ%N#Û£Qé ÒÖ£È3Ç)mF•p²,²Ä@È´) +fkb^¥;6ä\‘7?t bŠê¯Þ!íú4´%P•)Š@ ï„LèÞÉšš”T³¡AKJxéæH+)V'},FL%2U Ñ[”û[ÇÜuf ÆwUL‰OB?4OÒEÔ= ¤®{¢s´ÅÅv•¢r›îºæ>&YN·GY0³¶aAt÷2š>ÕWŒ¥ÛÛZ_*R HÆDP]- +¥œã"MdÄ×Ô-þK-®3ô×Ñü~À¨¤S.ýøA¿X DX¿x!AH°§;þ%³¿jmë^ê1@ßÄ“:Á‚ßûå –ÞY¶ˆ°cwÊ0 +NV²KŠ RÑ”Oýî"²lŠ·…ô¡*àÉÒ¬’L‚7­ë|ž,¤Ú†}§ýëŒÏÏuÎ4p0]Ñ 8A@,Ÿ_ˆ&4N2€1fÖ^Å;Å›–‡ªH±°°Ä@Ü€ ¨ÐK§ AÁ"ïñôy¬‡¥Öý$tTýÕDªá^An@©¤€/#I€uqZ^!,-ïM|™õO¾zk^4èÁ +`K!0Ï1‡¦åxxäñ^t ôËï¸ãrw›ò7ÄÑðˆØ@T'Ÿ­…ý¨nie%ê:νý´e‰p{!ŠrU ,P„w@áªmÃUìÓ À/ªs~ý¿µ{ZCð«ªN-'q.­Ó"GŠ Q) 21 ¤ ·É'xíYbì5•KWiwa–4;å(Ä(¾ú#ÿ ›ðj²ÿH÷yw#EÄj å6ÇgȸœºÁˆÎA¦mà¹1.në§t‹~&ìØÅIg˜›”޽›c|óÆ=ð†!–ö{ìštDÀV˜rñ‰N ]ú¦/dòk.|ðZLR¦@nñAຽûoO<ð/f]&E"°›R‚—E ¦D­Eí² ,ÜgwÏ÷k¹MãìYeRï—îômÝEtCBOʹ±лÃ! ¡€ip¿íȦ¥Ù¼„”"•OBÏ ‡Fö9ÏD%í5Ûž†\’*¦Ä]FÆÙÊ“s`Ý2‡ì‰? røí½íºÛûUºEØK Üo]^7c+ÕÆ¬éØ> *®üò´‡Ê{r®úøš\U;‹"Qsj¹úY´\é³Ê^nr®ùD;1¹­íÓèú¾A´MAGr±&.àk:;…Qð†é¼;˾ÙCÏŠ}Ï—]³uÙvZ>F5*8ˆ x eW¥¯«6 é$¤›¯Œz&Ðfiªj•SDVL‰]¯ojšr*µ°7îƒ÷ËÑRœ*ëXÊ÷úO¾))Îʬ¢1ÑNtzÓ·ÆE¿6©»«¯e€“\:ѤÕ[°H8ttó/7KÏòø€ @÷çvýˆæ ;©w!Ÿë£÷Å6ËN¹´ëwþw`žT•Ðò + ·;§ŽËÀ€½yö{šÞánþÏÖ÷`Oí|MWÌ-\€0êwŸ=®ûÒÔ” Ls„2ÓCÙN×í9ëø‰õ ˆSà ëpùæ+5ھ!áQ¥ÕB‚TjÅy韨£1ê§š³U;Ø^ÚÊèódÙ4_æñY們½uÇœïSPP .1þ:Î⊧cGyÕªc»g½ùOÿÓnã·Ÿt·PåïhÜ*{úÿìJŸÎ…’®œŸßøøù–çFâï'¾äLªO5&Hÿ¤ïýÍïžÿþ–\¸…:²xŒ}ãQßÿ²ed;ªuJ çóÒûöú˜;8¾D5Çë÷6Bèœ}½=Z×òzÚ@.qÐ}$W8N Ùk_ž+tõ_‰ÎBwp˜wmjPÿã"zØqe¦Ù!Kjˆ~S‚9ÖØ®Ê,cYú¬(|k‰ÄÆrÊ÷†»‹éÏD9l®Ñ}7 ·Tþ±¨ÂÈojyóirôÜÔ8ÏÇ7SÑ÷L$ýv{³w2?É× 0Bm E„öÝ–ˆг¾ÓÍ}ÞäRU4~íÛ­¢K.c7=@!rÉ(@|ðwü: \û«ÿ}£¶/Û•™ÛC“Q«ÛcKµ¸(ˆC˜h©p k¦.îæ=èPR{Öö\[Ôð¨˜ù÷O[’ïö¯YCÀAD€Q Érþͽ}ÿhG-Iyc»Rëòïçà¨ñµ®ÿï!0 *—ç~yõî³Xµÿz HmíÖúו ÛÿC¯S^xðDH ÂUݾ·õqôóTâñ‹Ÿ¬½Ê½«ÙÈÏó‹5£uR!v@BpûÚ†XLôqïPlzE™)¶ÒjÙ„q%6(À2ÙyR„§.þ[A Úi¦ €»Ò"î +‘i~;¬iÇ›| ¦3=?ï`¹6Ãlݺ^)¬× ._$þ@†ƒ™Kɯ0Abµæ)ÖÔ¨¶Ëh3†± ÙsnTíT‘e† $T½D'RÒIÁh€Úm`Š0B !DC@‚ЩFÁÂ@BDwRaô „®´¿Ö5u™YœMÿüÖfKÌØ74­†K¦Ë±n]_ýšœÏ :ïì{M ùA¢¡P`L¶¦ÛÆ" ;~Ü4éjõé&†nÂâ˜LÏkhR¨zHmÙpå¤ÙfƒDgR%Ë Zd É ™däÛ#)8¹LâSäÝjÐ.\Iêàt +¢ïüþˆMº–):óÔZ!‡_È©+ÓŒ¹ \Wœ‘*Õ~` DÔžÛ¯ÈJZ+XyuÞ†L‚--¸ÿ†ºWáSCÍ‹vÀ‚±"ZÓ 2æ“~0ˆgû$¤¨ $ˆ»3íòÒ +í1ŽëºÌ¬Êçn¿Ìõfg«39_ï©[b´U¸½Sá/"èKÿ3³þ«3=½ÝfdK9ÚÉpU\M ý6”-€Ä¦š«–H)ßvï%¿Ýf=˜0«÷ì†Ó¤w™Š ÿÏfd®jI${z¾iIÊß.f¦¢ÿ +™} 6 4Ô†á™i¦Ê3NŠ] “jS#üÚêi;ð/ùz„˦¶†Ã4Ñ$U3*nþê|æ{ªý!˜ÍIzß/Øy¶³¦e*ñ Ð×­j@ëÃ!B&ˆ +jrZ·’“-Ò´êj`‚üŠuP OôÄÑÌ–ƒ"•>ÿ2ˆ‰¤U` Ìa‡i `ÄJD´áYº'Va@‹ŽËþH<Љ)ÛI­S ڤʄ6é¶«¡ø‡çvKÿ÷h·£jgõD§t*Ï;'§°ž‰E['½*Ñ«‰h¤ªÉ¶1¨?ŽL@tA°æªµN‚·â…™³½‘‰¸’±!7 ¨6ŒöCþ¸ÛEJÖêüFEIeŒ+@ âT»JÓóiîšI ÞABÛ9Pêÿøî±ÝÖef_?íÖf{¹™êÌ̬ÌËm±Eè«þ·í÷Õ™Ÿà9þfff«ÙžÌ‘ÜÌÌÙæ·’\IÿÿöGº‡úxêS»·aðm†g¶ÚUú5ü†22R'ŸŒÞ²U†šþùÜ©‡È¡QêÌÌÎÿ¯Ó©Ì̈ +ÿ÷³33Ûçƒ/}ËÍëÜØW*7˜, žÿ?î+[¹§SÞ¹l¨\ZKÃ& âÕÉ~!9.Ìÿ32s°fdRçú¼þ+ÀÝÂW ðÁ(a*òë*í¯ÿÖ™3 ˆÝóbØ?û8læ{d¦e=_³þæfcäTfMfff{_¦kfffOE·þfff{#$ :žï¹™¿óØ+=™Ë.w“›òrËþÕ\÷=?–ÄRƒ<ýéx/o†[ØåÐÛ>‰wüÕÚmªGl†¬‡ÙƪO÷±µ©ˆ +”[­¿mi0ªËsÏÐxû-\­æe¯ÝmÏQÝr«Ì"?©>9«¤MwmPËP÷™þMþ³bÐ!½n3=ÿ§8DlšÎNj;ß.`‡wA´óŸ÷(~3Ì$›}Þ`ÌÏòäCôûÖNg7•&7¾Éžï+Ùîÿb“ršîêÞfÿÌîeÿ_¦‚)*ÝÖï=¿òý‘¿0 gšÁ™šÑàž®¼DžžžÏãÕ™øþ*]²ù›uš‘ûÃC·Yª÷”,—IÞlK]ò@ˆìa¢eioòª•ù†14±´-Aÿ`R„€Cœ‰x•M@ò †L'Plÿr•Mƒd2*l’qãÓî¸"¤@l¦Šý´üCU ¡¥sRÐå¹hÜ +Ÿä©U‰§Óq¸åÏåˆ vOKo‰”Hv› Spå³Á 5ý´G“¸@íî‡ vŒšw© B$:¦b -9¨.R£¸ü§Û>m¨iܳû/^i¢Z €'?ŸTiI ˆ¶v[Äôã@mí€ÕÚ,±°ôWòöPwi·ƒÛÔ‘EêhE*¶ü'P SnZ1§:næ×¬½Ò‘2ÔÇNІD´Ú"aTËŠ¶C>nß1T§%ÈRÔƒ :0* {zˆáê…©Ø÷hTà!`mÚ‰Ìa]†Ÿ­ß?,~;¤Ï¤ëm»]Øè"/ØÓ>êï¬N•ƒo°ƒ%2jgŽ•)<·©þ6ª’[þ—Fý5%§Â2CµÇ|±Tb§™£Ì±¥H‰ÕP vVɺÞrÉDT‡Å'\$<P*7FnßHvý¥ÂBþ÷¿î®Íh]iÙˆi¦Ñ:àï?(Y0Jö“;¡ +ü-;¡)ò&Ñ-\ÿQS[„š@K›üP ÊCEÔqô5Ÿ¥gTxÐèõÓK¾+ÓX@ÏÃðl5f'ikMPÀ´üÀ‚-Yü7ø0+¡›…2D¶íV¦‘³Ðï I²/¤[ m¼€eÀ¶Ý“ã²nÝ"Ýî¥coÍ>ÌþŠb“F“»M§âväüšÕ+5?ŽëÝfVgù™u™™™™™™™Y™Ší]ªÊÌî÷Ùö™¼Ïfw332³3331ÿ™™™ž1™™™˜}™™žÌÍœÌÍÎwþÿ³ýÖè/ÿåïBЩ涠¤5’Ï0tU{üýÖóß©wýo&ÿÎû7Ôÿäv"s3ÞÖgþ#9¼Ìÿ3˼ÿšÌÌÌÌËÈå:ÞfffffúÃØ¬ÌÌÌ̘ò–ffffg}³ù‡™þffff`hÞgs33±"¸eï3233+Ôoÿ*×s39¿~O?ˆ=ÞFÆŸ÷¤ZðÌÜ“D1¿n³×SŒëÓãËþL4í4Úl™`Ô½™¥Re¬ª à/¢²è"uïl{Oo¨ß*RØ9l™LcWœù[³Ù2’ÇP?íøûQÈÐoÇæñÕ íá8t‘AÿSéîîñ°už£Xºòk|'tó§Ù&n²³?÷ù]é÷…#}f_¯6»Ÿæú.ÕÈ1/ó33yŸï"3giW³=ë_çùýfm¬Ëq©§¬÷ðsüÁþ“]ÌØ„I€s#Ì÷»™ï{w.·^ÏSŸÿ§¼Þfg³+¾ÏLDgs¬ÖfwÞÉÿzö©y]»Íûÿoç#;5åa'%Ç·Y™ìÌÌÍæ1Ô@+1k#¿ÿ™¯f³þßÿ¿òAt+1ÿ—þíæTfzr·n,ïüÏff›Þlo+d=ßþæffdú +k!kÆÿÞ_«¹ÜM"+÷ܾïg·+x‚ºO$ØÈyK`é­Pu/AÍ£˜X4pŽÕKƒ4ØŠ·‡×Pº*b ×ê¹ Ž&ÓºŸ“6@ÿ + œ‘ž +Äš©²³ÆÜï +=X"¤ÓÄë`öI¸ƒÈJ-rH€¿À©©zO]…v@n…"«j`¨ã¤I3m G•òŸûMòDº¶=T?kÓRH"À-hµ‚NR˜š_¶›¿éÛ¥·Üü•ÿ|•Ë(Ôp5¯"À} Ò¡S\”‹(éfR§â±­#à6Ú|pœ®4£²(œÜ3¥½4MAÁ/µ%˜OþW"íÛ- %¶A…thn³n6…è˧bMd9J­¯ÂK…ºOŸBŽ6£yî®bÃÚñþ zkQãýÝÙéP‚u“i$ÿåëýÕ¼‹(‚Œ³¥WÉ ËüûRà ‘Y¦ÒO{wªç‹þêxÀž$’ܵÈIþê?Üt-Ô¢gɯù\—ÅÞ½q6víVìúy/ƒHó΄~÷Eþ8·<‚ MÙ&iµÔóý½/BÛpÒ÷úÔk‡Äü—hÅI6æ¥ròy¡±oPíóÂ(Aò×ã_òNŸpóº Z­hÜ¢.±ŽþfùÊ´R<­yGõÛ ø_„(G¹¸Ö™Ïc¬ sÑ"´Ÿ#ÜÙ}˜'¼÷ì£Ìfóv‚È—Qz_îŸõœ’Kwi¦Eã׿äóœ9ZüÈNø+®ÁgÐú›®xÛ™ó®*þêLÀZ Ùï•nÕÉì-DÃæÐ90™™=TB<ÐÉ|fŸ,ïφ ßáT +ãwéŸ÷é§ü!×'p ‡~ñ•mâµ øôà/Øâ|ièÏ•pzÍ÷û/HBº!ñ››#X +AïG|s_ëùÊ0£”™N]KÔK-nç\ŒÔÙlÁ "¨pÉIÕ5ÌÄí2@&Ø.IÔ‰m4Ùô¾‚±]vŒz“Ú±ÍwÄS «jÿ}ÆœþGþÀ+nŽÌ·||–Àfa8&©í’LѾÄe‹ZhìTž‘f{3 Ô%s6@©‘Ú®!‘bÏØÕ‡µ #¡M‹"®· ô sdà4C· Åi¾ìòíèâìz ›ª +È2*)ÚM–¸!¦Ñà7kI‘Bµu=d7Q+@‹mV€Mºš€*œn•@J‘ +¼[wɆÕ9©ÊM‘hÍM«R-o +Òö¦þ;¬wu™Y™™Ÿÿ™™™™™™™™™ŽîîÝªÎæff{3333+3333+3333+3333Ï3333+3333ffffÌÌÌÌZ5ž›¬Ï÷ig¯¾Èÿ~ÿæ#'2kÙ›Ïï;sÌÌÏóüÿ31O}ìÌögù•™™ÿu™¼ÌÌÌÏfgþ·ÜÌÿ33?ÌÌÎæVgù™™™™þg¿ªÌÌÌÌÌÌÌþ·¯ûŸæffffó?¼Ïfffffó3ùÎæffffffwÞó33337™3êòîffffgùŸëç·YÜÌÌÌÏUíå÷335™›öŒ5Îë33}Þetw­óg2wÿ³+úçûöò¹ìÞá;´•¦š´\vjy†¥XÝ¿åld»¿WSÿÏð÷0dÞzü÷¾ÚcǼ¡ý ɪÏm×ù=ÐÌÔ,Ṍ›Ú wØ(àZ-5+rz»ß~rdÞˆº©¬#Ó[ÏfMwý÷Y’|•K6½ýF{ñ^žâêΓû`WGý÷®½Š»ëåg2ðâîIñ=®èôfûÝëÕÿùÛÌåörOöíGýN¿Ïóþî³ÝŸûsŸï$G³{ºß_s¹™Éæfky +…âw˜°óÞÌÌî{?Íæfg“¼Â'ü•ØîoüÏó;žÌö·ÿ7N²ó»œÌöffó7›ÌÍdø*5±“œ¼Ìöfffg³3üÌÂ©ÊØ'7YœÌÌÎæfó™•·õ`B¬ÎfwüÌÌÌîw;˜ ö"+øÃ 7™žÌî™™ÜÌüãºz=y‹¯3333ÛÏÿ÷u¸Ìvý­‡9™™þg¿ÌÌïù™‹aõ£-ß,Þffgsg3ÚÍû6ä˜rsñìÏfg}ÌÌÌÌÞd£ú`ß2Ÿù™þffoÙ¼ï¿dãÊ@oÙ¼®çw™¾ïüÌ™÷³³¨ÿ‹‹üÿ=Y™ÜÌÌÏzr43¦»èÌÏfùéYžÎû1;Ž*Ü•;Ï ¨Êß»?ä{þóyÿþõç=ü÷Ó™üs3yÝåmça&æpUeF{;—½gº:ùf²iy éþí<œqª0A!—^¯\L˜ƒu5"½Ÿï'tdÑ)øÉ3r’áühûÒ1<’¢Ndá©·jX&·7aêß%<ñQ$ÑËLÍ²Š›/Ë–•ÍЧîÙ+þ&o€†Ú"§©¡ÿ—m\ çvµWðÐ$»²hè7Ú瓎\ÔmC×òXDGC£î´SÐ&A€j!%ù\0Óý.?«­d5þ­áD!|ß4Þ¤J¾_ÎfÀÝ¥¥vcc]*¦±’8åø·©_Â:¶Ä ïã q¨ ÒAÿ–*ù*âú¼QŽþ„‘üi¥f•ÚuR›º ÌžüÂôî}çÒ{_ëCoßʵ| ÄÅÚ)!ÔîÚGJ­ŠÕ êçðm×ê„Ïÿ* mûOIé„û©\Zc´“€íÊ´xœ*2˜$_¹†è3É{Øxå¯ÿŸæÛºäK<´ÑNä +?­azZãÃäAˆ“~ò¥Ç0pã^ô:/ƒÿ¬¥Ž›Wvì1húzÕKL,’[á0 ½-Z ÿ\­ vå8Ú#«×îܾ­gæ…ß%¦àïÁøjº >çsG¼‡Œ:Oÿß‹Ñÿõ@È}°ˆ1Ûã rt·:%‘×óÖÈ%¤ôÝ÷8«¯´ß„;-¸ìär4D°ÿ•ÛœÏ'Î/yœEÇâ%ì?ól8çü!ËþçÓïçÃkÚÿ?ÊsMWúõ¾xÁüzÓ2e>3+¸AÀï¹½F¢&êÎO÷:ïÆ¢3QÁIòߌèÛ€AŸÿÚå­uÃõ4G/œÕ¾ïÄ¢4õpï½?..ör$ÛÑÍ“o' ~ +*í~?@³¶5¢yØNœFæÇ=üoò4#·ËÏrÞž›n’ÐÞ¿P¦u¡~{}~£­tÂe-ûÄÚ¼ï‘î½|µ·Ÿû?ö¶}U¹ŽA 8ÅÕ~DÆìqýê$Q6kÜZýsh²Î)ß´ÿßzÑ•Z³ÉÛüys_æï‡NwWcÛHô<¸µu­/óþ¨õóŸ»ÿk·%h‹NG„0…žŠh]P…ŠlÆ®ÿýpœ½'E7A+3ªçèÔ–¼ùA¿ûßÖ^„Žv Á¦šmµŠ²²¢£6PÜê`G}ýk©_äh?Go†¹¿?úÏ ÌÝVØ#ʹ+œÑè‹Fý=”´|¦q|\ÿ +L4ÁÜ÷‚gðN Ä‹Úÿ±.¹8;ÓÎæîoFu¯å#îÎiú¼Í='`VÄíÍLt×¹5”0ˆ¾=ŽÊÈ€‰w"ƒd‘¸DÅHÀ‚E¡si =´<Ùü4€†¨àCéçÐÏðôQÁ²á@ø@Ä #lM0*äÞPÜ d!LA©•$,Á,ÉhÁ@_Ðd@z 0µ&` ÄÖ·a#ø,"'p3Œªìï †K"‘&KU=Å¢ÂĽð ¨@ÐÄ̾~dŠ4…øÉàØPl€àC3çÄAHØ4A® P¢@ôáÂV€¸M¡ú˜!„Úˆa ²†€@! ?BN–Ñ]‘ŸõnÑx§ïÕF(&‚ ‚-5 qbA=€•<‚ +9ð@£Y”Ï’Á§ºyÓ\&îÔÂÙÐÚàp bxÒî|Z"\Ù æ`L®ägYö$Ä ‡)ø VéH ò@)â‚k½AË)-슪0<¨ø¾‰º”€0]¹< ’Æj *uLë°Èœ3à„GQ+%uBô…”àS vÙtd€öi¢RÏÃÃr ¡>€!ú( PÕV/JÆ'‚˜ìÌy;Ð[D +‰Ó, ÄÈê.hÞÀ “ìã-båðÎ×6µ 5¨ +6W2~zL¨~…’Æöj œ¹Ù˜ÏxÄ9gª¢ r I yJ+IŠgÚùù8ÊÓ.Ìlr4RŒ ML{x>ª˜Å?Ù* MÍ=›‹²8Ƥ==šVlHÌq9©è È A<ÈvPÎ\kà0‰.Ùw“Ï21ƒAg2ç[ðð4sä´ÖEñ [$»åÛš6HJeV/°bc—³ ´ k@†âf8ÖSÓ(¢ Íf`5EaØÌr¸8ÜHÊ/wÊ‚ VÈ· $Á0 i cDÜÅFLNSB¦[ó\«Ü¤{ôÑ!¸&§-àõÖOÍ0GÔJ°aψ:ûwǶ“‰?׋#ƒïV p€A€€@ÍÒJo~Ìk.*hVgô”‡ßZ?Ãx‰¬wÙ+5’’xØÀ¹˜^Ùl¶Nÿêøn:L‰2:¾þñD¢ 4Rá:,@  Åü¬­žÁ6Ü]; ›±AI  ølÆ<éHÀ@%*’W842~ èGæ€y^ÁŒémŸÚ×ü¶¯¿«#d°†@Dwƈ2Ãèè2ðð7iPù˜ ðÝÒÄUâ ''IP@9A0@$!@Nòe"“ uøŸ¶bæð 9@;YŸ%Rq p_hf Àµ;dê³Ù B´Ôø‘Am9IwÑÞŠ HJ‹(H@‰1 é}CK—X,P8À§Ú:‹-kFxTŒi ÚHèÁ1tC gÙ´ø0P® W>3„‰ž¥|oF°2òäIA* VÔƒ8á\*ÿTEÉpÛG‘.sÕ˜Bs +@v 0ÏK‘IÚ¼lϾŽ?‰£èD Xwï`ƒÇf*¸F0„éùîœO5c%‚Ï b 鬅ô¤djà^¾”«ïî^*æSa0 ¨5ú‡ázÕâXªÂfS+°µË>Æ4†îAÅ"nGU®ksãh„TYd#¬Û)ݲ½Q<·ËXƒvö¤›H@’ ,6ŠT F£óðM©;µ MÑŸ¢ê-¬êT$OWW­¨"pD +L1ôB%uÄ<4M`ÏGÛ3F‘¼lÓh‘£èr4HÔF«‡HÁkÞÙhmPÙq'ë«¿ÚCðH ‚~Å Êà*ÁAd±(/­„à W>Ì9òOG•6ŸòÁØ» Ø. £åyÜ)x#prÈ +SšŽÚHÿÐP”lA¸-x‹mÎK~æd‚9©i×Ò2³…YËX(âøÑ¥g]µå܆(ŠSCüÙÞØ+ý + € Ó·? /ŤwA<Žýí‹—˜˜ŒB¡¡HÂè™û#µ ¡DVlvíBþ\,€ø¦õÂF©ð±ÍEÊ{íqÌÀìÂjzÌZ@^à«ÓÅõ ‰ÂnŸå×ñJ…„ËeLBPIÐ’:#áî# ¯ É÷„PÙ˜÷ïÑVX\É2ñ p²3‚0ô‚À¾ËAÇœ *Ô,ç¸`Y‡¦LËý&#„Ûáä¦ ¼Ç‚­aö0@í€KÀeã€ãèT@6š¥<Ì¡)€”áX´3‰îcKO4+Õ°sä~x„ ¦°tøËp—¸O;á ²#›ÀÀ€€0`à )m¨HÌwxL"™X6¾"„†.o ¦ €4‰ÍÐ#4v;Ø +0q©ÅDÈ0²pæŽ,Ñ<"@ QÉ,—`U W‰ Sìxû»J_VsŒF%È>MKÞ’„ó&P +€  ÈDE“ºà< %hƒ-(ÃÍ®X‰^<ÑA¢1@jB(â,HQçMWH¡Ú„ ¦ðz=LA7pÀw¸˜€€àI›kÁ@Hð0#áz! `Ó™…•…b`mÃ…#Å4äD¨6àlB¬½“ì  ƒ×5Dæ  I(p$ Ö#ƒ u¡"£›·q|†J@ƒÀ,¢A'h@ª€Ô¤˜1 \…3Y‹€Æ À ÖYU ,½0D £A O¶âElPä€ÆÌ€Ñ7ÊÈÆ0”­ÄŠpDyˆ«`à Ätƒ5) Ä€0%EçÃÓ%Ûs™D_ú ,ÊV¡€êHÕ€zq&ü.ø>D%ôü݈np3€ÂrhpÐE< @;@H!* ', ¢, €`L½àf§>“@#¹t‚ÀàÃb8ÿ0ò#!ƒ_;—À3" ÄÀ„¨»ãÀEPì¸hÌÈÆäà•"ÈÀL2¨ þ¤|0È53K©âÚ` ڨƕ€ƒd0­=ÜòóAÉó7ðo3ÒàÊûD«>œ ”ÿ„•ßÀ¼{8 +ñúˆ»îñ"Ñ' "¯èöîˆßreÅ“½£1;?#ÈçÁþ*<;ÎOFFœtÀ+­?¢iÎj}t1ÀÆl¥-د{3ì8W=b“ó5GÆ^`ºŒ}ä3ûêÚšÆ'ȯúá ¤žý Hjm‹t°ÿ¯ÎÚ¦ß (xNððX΀ë#]æÚ@i½ØJ.ð€û”m/Ûò‡>ˆž±ñæ’OSAµÈÄyÞ¬b`±ûÃç=Áê?A—ŽÜÜðX” à@k¹E0¯H<¬¡.4T ¡¡ÓæLïóY@¬HFˆ}0/l¥t-ÎÔh G³u}áñLý§‡kÒÜê ÷OpÄYÒ“áÒZ$"ƒ ›ãx¾ÕŸ_cÌô§¾ç<þË!‰#‚ +hÍuRÇ>ìµKÈb½A6ì>cE®~5é8l³ŒÞ6uÇ;2ßÔñÐ~y-塞Ԉ0Ší€Ð;÷y/ìŸ'¬Ã¼_Å…Ÿ“D’Ã^ƒ“M¥âÒѱ·*ÐØÆÎç™Sà $ +‹.û§O­z]ŠØmÁs()ãÆô QÝ’÷é´ÐTi¤ÝO“Æ-š:\Q;pÂq‹•Æ3!ÿ<Êß/½Þn¹R‡[´pVÿë¶ÆËjÓ±øÕ˜]ü÷^YÊ—¡H+P¹çØ8¬ër‹Ýñ[ëOk~þ­'âþ2Ò‹ %% ¨ÒréŒÒ|õÝS«ú¯ß©úÊ} ÷ù»ñP¤‘q„IȬÀoîQY«+y—|‘PIÓ³ä_:UÿÍù¹¤K„yÈ`§Ë%JÍRÈdfÔìÒ„õp#HS¸ÃKîÞÿ¨[ùkµ§¯ÍNâvGŽ¢T‡æ]SyÂ+3L/QjÅXÄ‹°Go²tÚ8mêQè-`E ›CQ4™0žYô4Œ.‹|¸^¯—ë5èÕ[ïcUåò|¯Q‹HÀ⪇­¡ ß0Ž„·Âﺥf™&Èfî1Pâz=¦=ý¦sxÃ/íçãpè¾Kx=}.ášÓ‰Sî7 ñÜ0ŠøÏß•[h°&î'ïJ„f¸2ã¼&œFó*i°fŒw²!±ÍŠÁØÝ[ì£à…&FñvÞÛhà'VbF´½­ÿ¾tN_„û}U÷4¯ÎYƒOæÓvõ5Ú—h¥Íø»J±Ë@Ú¥W“šð8€†ã“ä™_þ&Ö0ƒÙxòÜç ÑÓËJäçª8KÙóü™ ¯3Ë?ä|‹/Á!ÉÒkšº.ÀÒì~¢¢©«¯%KÄòÁ3xã´dú·ðž¥ ¬ W¤7·ü‘tά±=Ç×£%WضYèøêâ$ùB$[4@›ìÿ_ÛwéÑ©2`*+•_ÖøwÅrèD®B/ž¸'˜?¼°D“Õ“Øá$„JÃb­a¾È-Ü$1=Oû ÿ‹¶V¶¶ R;_ÏÍÖ>~‡ nhàê‰'¿î¸Ó¨_%:hkONºþ”L¾‹ÔÁLµújð±'˼ ”êGQ‹qZæ¬Ë ½¶p +>ì 9ò².Ô_ø Üs·!5~%C7{Ê‚__ Fù³üAPy 3‡ VY)y D†þæÄ+ýn\¶izóH¶…@Ìs¯—°d5äŶó?0#!9¸Zd ”Ì<?ÊîXºÚr¿® +òdD”Ñ“ÿ­æ>ˆ ±~´o<~™?\€Æënà^(¬DªÁï1ÑìVÍ@?À6½ïš¶svGŠ*I¶4üÄVi¤àyÚ\ÇŒ.ƒ½Ó;}(nô­œÝü>×»ç|ÏsUÃKßxc(†ÂˆUéÏzIvòYíK|·„R£OÂBóï4Ìߎ¨Ôçlmö:¡¢—>Ç2ÁuÓòÚïsä5÷½Õ³>Å&ááOÿÉÞêTyÒÆÃŒ!~*…«Ÿ“ËgåŒÚOþ¹þî¶ÉroQ’«õŒõ‰Ày¾ +`³ÿs½QëíKkÕ»Lš×ßùá³.ŠL?Y@Ük¢÷ ‘¼ YE矑ABw³†2Ç‘”ªµn/ýtñƒ_µ4/ÈÃ<½ûÖ{$%ñìjü7C¼%ëAÃ^²³Hä¾ ÀèKРüF=XZgÜ„EŠœÐ"¼óó=ÝÎR>æ½½Þ˜2Ö¦A7¸›¡è70µëÔ*žåÊXè® +•{aYc¥¯´;à.¢î,Fß_ØßŠ-Û‚¾yÒ`•}sË]ósõÿîïdžcùªI.ÝEN•¦¼û´`ˆé¼RË9³»±@ +=&,|—ÑãÅpèµZÕq/üF‹ž Ü¼´­ ÿN±LBÎ9>€œ ¤I!&Ÿ +ïûeE]%9Âlèz,Ù¯Üô(@5NÄ€öj’kegè‡ \óœçßN1dÈ:úÿ¥ô€iö"kDk‡}íqŒyÿ3öm•ªŽ(•üZɺ?¥¾¡þñ9}®:gÂô¿Ôùÿÿ¹©¬ 82†xý“<ý:4‰þ⟵sÂ..ˆ5B2Vì“sĤÀ°®E´ãؼץvÒ G~Ò“µ£¶AÎÓ”˜¿ð–·™@Á^³ð†¢4Ù½¦küø~»ïþ…ç¦k²‹£J¢}Fy¯‘åý5ûüh¿Ó7—/ói_ìì¾êOámt<‘µŸ¸->äåSÌ0j/ØBÀ~åòØà¿›[–‘Cì„Û— «?V™9´kLÏ‚Å-×$wö¿a#÷AVn÷8Ü=äû_ôRèßÂ{ÿú*j÷H“Þ.ƒJ)úDZ}0$hú +×§~UŸÉFÚÛxÛZÚÏwt°û1¹pÌ"ÁË0=lòOûùz _†–E{úÛ"ÔÔ+o†dÞ¸ Â{Ÿ‹sÀMÛÈ ÂûôÍàܼÊ[\Z9§Ðç{¿3Ë{ö+¸þïQW¯)üKfíÆÍ]»Ù©ñ'í¼3Ï,ŒÜïù¸@–eÊîÒ;K|— +ë÷> l_"[äqÓ +#TRú¿´zóìNjIY€³‰»ÕnêÌ›;8s®ªùí.׃|ÚuúÉ7ñóR2ÕO¡ÎÆ7­¿ç]~îøíÓÿ7ëîš\-‘Zy—7Çm-…O÷ÊHt+ 1ÓWi_ëUž»(ºG·íýºXpº”N1×ã´øxóÏJÈý9s(øè|:wqûägýÐñ “-°mMºR ²o¸—J7¤C¿ôµŸPï LöïþÒ;&XŽÁ°¿¿\{ÿ +×–´œ“Īþq¦âzÚ]b½çc¶"²þ·=Ûï{õo쮇C +úÈ<¿óýz ؽ}¹ùLY8[ˆ?ö3ï-0Au/9RÎjø•'ˆ†/˜o lL8|Ïÿ¿ý;ïúüÈ{3|'ã ^„þ€ceÚûXÿÕå3¨®¾_ÏÛ¥ÊÕ^F%ηçÙ?¾ñ4CY¿)þ>ç»iƒ°±&÷ÁÞkÔùóÍ•1DþÈÿWÝ«¶¸@=á¾ÈŸ÷~´õMŸZó¼ô¥Ñ”ýô¹¾'ÿ‹A¢d¾T\ˆmŽ…<øÃbàÏñ˜T÷~ó Äé9Íõ£+ú~‡7~W‡<>¿¯Ÿ®SB¶ãu66œŽæ77üþíÃkŲ+ÅÅVç_uâ{kB×6’nóF%zŸßóÞB ëv»w¬Cÿ½ ÊÜ Ú¢þŽ#}ê !fKm´ÕÙ„q0Ø£[[…•(4åÄ—üÚ ši¦¢ ÞÈ6n$‚b¤ÛoñÝcHÿ%ð4ÆfOù²ä¦v/mf„ÛÁ…‹ E/&—˜ þ+~4ësS†ÚE°ÆWí6É!¾£!«U$EšòPØ€p¡ù„Cj€$éŒJßüI5ˆ•"¨2uQÉ"$£ù6 «l’`€-ƒRKVlI·o©¦šüwXÕÖef>q7ÿóY™/2A¹¥l2]6oñl:¿ú?59ž‚ußüƒ] tƒá B…A‚(mÝM·×"­ûøiÐ^×ò@±›ð\˜ÎëmML^…CmYpÓIÞÈ,ͨLþ‚Ôèµi! Èu7r/¡8öé«ýªCeãüb¾Ã,ùÁ‰[x!„;©Œd5ûºš˜!ÚdÿÉ-B2fg­z« I7gd“FÝÚr„Œ4I„‰Œ&ƒcŒ¿Ø·åÓ sLCZ 9üà;BPä€Gṓç©6"6Á;dùÛ…h餎U.´EWô§s$ò‰ª$—Umª'b4›%»²á~;¬jë2³+ºÿ3Õ™ž¬ÍŠÿ}JÛJn/MÑ7ÞÛ¿ó;ïõY™ì;¬ÎÆÙ;ÁT§Ôƒð©jîÚþå‚¥=ŒµüÚþû™R`‚«Õ솚u'y˜ ¿æffïšìÁ˜Žû¾hZîî{J Šdu°aµ²ÜYÓM³t²ZÝ †€B¼È3ËD„ês³~$PoA¦v«£¡>ÊF­Põк“ªÞo7K$ƒ0Ï3­˜ËÖò쾸ÆÖDZ"Èi’½æùV;$o ~#¤Õzê`3ø“mc1hZwÙ0‹©Ñ€º&Fb"Ü4=SSw™;)Y—DÖå£#Ô@8›&6E0Ý€*¬¶É³wm¥~²äÔÀh‘B›$Ùžé1-šM¹ÚA{i[m¶ÅA¢I£ýwñm4…]p"úBÑýL“!¦—4B~Õ-XWc©Ø­yZ°ô‹7}ÝM¥ J¶¹Ú<%Ü–û_Ã6‹RIÅT-L M²&A—i¦ë€q–ÉÒeí˜4€wÞÕ–æ:D¸÷e¥6¤$À,ò0Êsvݺm=2ÚÒD†Ñ¦ÒJ«NTZmÇïãºÇwY•™ÿn³3Õ™ž¼Ìõff+mŠz*ÿqÍ÷Ü1™—žÿüÌÌÍVffdÆó3{<ÿ;þL8?ÎÀå!þ¯­Û´PÃàÜ34“J³¿Èdˆ†?×»9ÚÉVROüÌÎä>EÛ¬ÌÌÏõúu‡32Nä‹yžÌÌ÷¶$›Þg³ûØ&;™Ë‰s¿æ¢òn—þTžõ¦À…¸g‰éXþö8¹þFÜÀwü…v‰©CôðÐF¤,gì›pC°SÄaÒ þWS¡â?©ïòŸÿ°"Ûü+ÕnA£'½õÈÀ@ØOÿÿž5±X:¨:ÚHz®IŒDkÙ’òs`€Ø_Фo^<Ì®pô@í‘™—ïvšy¶Ï³}ήZþR*±ÿ™œÈÿ—˜+žØ¬É䞬ŽÖL9·ýº÷ù•Ü¥ø·Íoÿ cˆnÑY5ëÿj4Ȇb+ÖFa—Zé‹P‡ ã{¤*ˆÂBÆŸâ‡F™Ù Eº˜¦¢±M‹!sÅYès­R÷ÿR—Ý=of2Dº!$´¬´ºá4Û.ÕVî&?öçe†Ô4”ÙÍ6®#”GcÜ´Ó›"o\*Y&4EhðZB+‘ÙnZ€Ô€vƒÛmZSÉ@Vœãá ä†“؇ù ‡*œ±h¯çÀ†ÉL¸$·™ÈÚi'»uþ¥IM6¦(*Ê} žƒ@--Ck¢pÀ„›w:Ò=a±i'Í]aŠòý(7Z^ͱµ Æ¤žwßÙ¤û¯ä;tÚ×–ÕÃi ׽ߵ›'‡bÌ5üÁ®9'@PRZ–Ñz ÑBÏ 5ÔqÕÂð·isq'“¢íx¢IrŸÚ4ˆ#{ló)LÞÓ%²ÜÇûXK*@AVSrñ‹ Ž]à$’6ƒë`$Ù.4“dW‹  ‚H‰%äÊ2›E¦ÓfÓ~hÙ[´@âl‚6Ún¼¸r¡6C!Ú¶ŽZE¢ +m'MÂÉý†íÌ¢'ñÝc»¬ÊÌÿßåÖfgs3ßæfVfb»wXr;­ÿ¿o<»™™™˜s3335ÜÌÌÉŒÌÌ̃ìÌÌöw²s343·Ïõìþ+*î-xB Ð0€ÁÛÿùÛ·ÿ®/óÓ¸h€DkÝÿ×yùg!ç³üÏ/9óÐwÿù™™—Ø­ÿ¼ÌÌÌï³ôâ³33378.ÿÌÌÌÌÎäÀ¸Îï33û04o3¹™ïàH2÷üÿ‘þoÕ5Oÿ*.ç½æ×æ«f–Œ sý¦¼;“Ã*ˆ{öÞMÐÆ–;þL5i6‡rq¦ˆ¥`‡úÚþp…üìx|; 7=vkÀMAŒ“+÷ÌÆ×üÜM«I©ÌÁB"p¹Áþ5B¾uÀzDÈêîIÕ™nPŠ¿KþÏùÛ¼ÿ›*Ú!ÿéÿ=øÿ3Bà»Iÿ¼üb±} zý4¿Í‡™™™›¬0oÉ÷Ù¬ÿyžÙ{UÏî§ü2o33ÿÿü“™œ®æfz36—ŽÚk0þ³?î{3=šÎaž4}5¼ÌéÏ›Êî3Õ·iï'¹þgù›žåâ"øÿžÏf¿ŒÏëwº•n,ï7œÌÙ‚,5üT¾û?Õnra¬Ÿýþo˜Õ`m8»Œ­À9íÍþE»NÖÄ6¬ÌM,MEŒñ‚ëóÿ„dö-bIAw*ÄŽæ*VGøl*C^häÕJ2­R5ÙÅ$¹G3ž1 µ&s-L©wSBbÑ[P7,ô4@„O_9¥Än; rHNa;ŽHüÅ¥"4à´Û®p6òÜeÀŸ&A-H* 8™†ª}Ôÿ°ìM¤í¨õ÷uù*7QTSpXI*($a»²4‰¦Ip™b~Pݳ¦¬BP¸ÐJ_ *‘e }äâl5VA–Ý»œñå$æ(mþ<<qév­rª^D`^ÆK=í)AšïJð4‹ä½*R2^¡fýdÍ X}àtÇ{_7¸GuvÁÒvòs•æ)æ4xØF°8˜ý¸ë‡àézŽ•ÌCm‚?Þõ + n¨­Ç5ÇelôAë½êëpÙO¬Aü‡BÉZc ‡‘é×ó<ÔèîÌ‹Z¤Ç*GN¢Z¸ŒdxÎi´) 5'–‘õÞß+ÒlWB‚/..Šÿøž\î¸=}ÅN^ÍtA§BfOñ½Äãpô}u–äÐ!ÂC·?‘¨/W4f»mJª%hZp©³Ê_¨’‘o¹å@âò®oú†ÎÑ™"ºž~ލ#KDRvb+i´’h‰xµî^ºýV*hÓo®ÓyÚ&±JÃPêûé­Dû&˜ÒIU6„•jØ‹¾HçíÈIêÑ6¿î+Qh‚kÝ=Vಠö3¨`€ˆ!™•yýAiqÈ7ÌiÛ&ÕE W^ ~«NdHt -]õ!-0“¥`ë‘È ZMÖ]ÝÙÛÜ-¶­1r4-¿ÿ ‹´Ýþ¢Ý ÊVDéJ÷”ü ¢Ô{”ÓI¦Àï„¥gT9¤ml¤¨°:¿Ö;ºÌ¬Ïó2ó333333333ÝÝÝgsy™›9™™¼öVffffVffffVffff>æfffffff<Þffoffffn³ùUß ËYëï¿‹œÖÖbN"j²c~ŒÑïHyÿ½ÿ¿÷ùž°]çûÎÿþéÏiŸý½ï¹™™ìöí]ffÿÌÌÌϾgŽefffffd{÷Y™ìÌÌÌÌÿüÜ×3üÌÌÌÌÌå¿ò½™™™™™›ÊÍÿ›ÌÌÌÌÌÌiï333ÙÜÉÎTÖó3333þgç?öû¼ÌÌÍæMwýïk5™>ÌÉц‘Á™™¼Îa\دÖ÷ìÃu'oOY¿o—ÝÄ5všjÒjÌ!ì×ä;Òº’伃Æßf‡‰¡Ñ¡¤†5Q Gs߀w2ùd¥Äo+Nûíi«!~gnÎå +©Œg“y›ËEÞàŸLO ++ w×½fl'þÿɽ^6Ž¿ÞV sœÈ#³¬ÏVî3×FÔ]¶«ó±_¯ó&³„o|W¸ÁÏ÷Ûh¬ÏÿÌX⿇˜¢ï­`læë7ßóßæ™Þ[º˜cÿüŒë¼îfÏ~(÷06w¦‚uíg¯þ÷=™ÌÒ?çñT?ö;Ìïs7žÌÌŽçùëÕÆ ×}™5ìÏfgÿç¿ÏfÝäè4`ë3ùÜÿ?ÌÏw3$÷‘Gõ ?÷»ßóüÎæz3}ð†ÿ–aÆ«2½ÌÌÌÌÌ÷ûÉ×ûïøíR¯;¾gýÌÌÿ7ÜÌç{ÆŒB  +³·ìßs33¹™Ûÿ;Ì“+›ÿ{¯g³ýû¹¼¬Ì÷ø&½~¿kýFw=™ì­ïc÷^¨ÎëwÿýÌìþó~Ïk m,Ïg«Ù̬Ïó~ÃGu¬Ì÷ùß5ÜÏû¬É{ê|5úÝÿŸ|3"²ʯö`Tä÷y=Ý{ÒíÏóaÖ±U‘Kó“ßèÖJ¶{‹\¾é,ŸÕIÒf ï&»í¨¸š.ë¹½‡qzÝ–ìÁºcøvº6™2^j ܧS±>Ý×ÿŸoÉÖÆFÿb”þ^eCB‘,TÉílØ`»TÈ;‡iä»ìq´-Rkˆƒ óÎF)w!È!ܽ¶`3)][° õ AöËh¾"éž;.qê|Ò¼ÚŠ.¦¤ÀÛ6 SÓ„ÇöÿJ4Ûátmчn¹: +ç)˜{ióD™°ÛìO<½"©Õô”'à÷ 9×a³ÂM ·¤JkðÿÛüÖ“ÎGJ‚[uοpv­Íl@nˆÄ#Êx<émsRÁgÊ‘f˜‡ %T/KÚ­të’ïË‚Œ¥ Åh \ë:z;¿síêvø¨صêpùV]-V·Zq¸€·ú¹ƒÄš ò‰áÖAV?óEr˜ð˜õqK² ¢èŒ¯.ERýú—f‚4Ð:_ÉÊs“ÎYz,[MóNêí> Žø"¶ÈW÷þ9Îv#þ‡Þ9YåßÞõß¿Úïú'n’¶ÌM¦žÓÑ+týÈ•G*ìçýŒžçe­Cmƒ|ÆûP[æbXóÈ’[~¢ˆæË«ÎXå œ¬ÐQTćhr«7t ‰ +º(K¿Ej?zžÈOA3ÉÿL¿À¸Ÿ7Íuëdž™ïxÚ´ÓP ò9Zz]ö_ BáƒGó㻹ýŠ«B¶©z0+o»Öª;Í…~ü½ËZ¢R€YÊ9*¹ÍVº—;ì+µyWMé ÑwZãK²½?Ø­w³ÑXÿ„œ L ó¢[|sãÍèë‹·î´¨¿ÏY?úˆ‚b¨Ðh‡ë×´ã^ÈŽ‘±;Äëó§Ö¹Î!ËoúÙó,ø¡®w”ý™ @½;LÿÇE­û³ Èæžêî(ÔáÒŽ~^ÂeMqø"5³Þò·ýÉ^=Öðy¤ï@Ò)À2(gl¾wÙ® +“‘b+§º¸âÿH RØl~•—Ç{{ýÝ]èî ÎÿÅ‚8ç¬-¢¦®›ÖÉ¢?%2èí4÷C‹ÃÃ\°9¸ëš»ç~)ì‹âêÞ-Pé£ù¡bfÕÀ µ^zº·‚—ùsèüª¤7OšvÄ=`^»¿øÀiZþùÈìq²2Ox.G#bYîkƒmÐêöÚŽ9E£3Ý”ûšDLhHc˜t†ºÒfÁ0¿M¢ùÿ†u€-„À-°”³W$ÈÜ ,é"Ò¸Ž¢Ú;&­((…xCEpæköÄoÍ¥](L¿êj¤'Å0áT^k¡b¡Ê©Úg_ÿ¸@o.Pq +R ½ø÷òw*P €4‡Â*‰ÀIRnã„iZ’aM%`Êä˜rÔ-€TZ™?¥"n-QéA¥y-6ÓmWiZõ£ÝŠ Üƒ X6LFí+Û­˜ D+âi´wÊL½[³ -ö¤Ç.íé½ÄÌ©–¨þ!ÛhkŽ{SÿbÅwƺ¿Þ¸êï•!ëºhŸÿ ÁÅÚ ²@!£*ìŠ0ň  -o¹"!!5I³íÁEÿ(” é>"@8€7ö  !— +ݨ![ê(mñ‚@ª^‹(ž1 ¢Ä€o'x9@Húû`HËlB3'0ä + ,¾ô5 4€JÚE + íjÄà‡ 2ª ™Àér¢¨€âÀÐr Ù +îª' I¨t ;^IøR€îˆX ñ BˆÇ&›³® kqp€F“´Á€‰m›H¦  Îj`3pš²_ —°(­ÐHfÁð Ri€¢ +Y‚¤º½ 9ÂÁÊ«qºß¿è*0  ˆeºIDÙ@H¨3ï ÂÅ]2Dd$À@àY +m +!;MiAe +6uào4V +€@hÈ :U:Ä@ (…ˆ !^RB#¤ow é…(,‘P„ + |!¦: @Ú§cïÔcL),ÔüoZhÀD +Üe B €Ð/ÄKü$x ×%wÙÎÔÏû0É‘P@·¯ +ú2„J_4 |» ` Rgþwqû~\Z…Y€”±NQà°P@9.À‚ùXQqcÏ»²$ÚŸØ%é R› F2÷G‚]*ð@‚H‡«k‡­E2ª5°¤Oª•»a*²‰±S•ŽÍÂ…Ú¾R˜5 öþh\ú>ØO’pâ„3Ü|åxÕšÛþ^eÚB7eK1q²‡eɧàÉ]ÉóXÉŠA$F†@ ˆ_Gt€Ê@[ @÷ÚœØÝLùA¬¶H‹Ýûq’¼]®1@«Iœ Q¸r¬ì;HÅi[ ŽÐ4 \ü¾âC d¢ OA‰‰‚&“A €LéÆ6=1p”b)ß¶F¬· „@‚fâZ–¸I"]7 @r° j×s"—ârïŸÏqЈ+ÁÃúÀŽ,vò€¸”ª#@"×ÌPøLA5ëåqxi‚@´6Q—Ç  FὤZPöQn†„f;WëHJÎn1yYÂz …T…‡ÍjÀú^°ËJJëjVÀƒÜ˜FÔCÏci‰q:Aj­Ô1L˜P€ ~ BTâ=º5‘âÑ}-j<‰-dAɲ×k,D € @ò †f¶%à:S³nAØÕÉð#s¢[&„'ߣ@ BU Q˜¤ÎøDÎ왩©EK:´¥('ã‚aˆ3´a¤ÂS%PÁMAQ%¹O°õ¼}ÇV`Ñ \gzFdñ$ŒyîÁC=$YCÑ2*JàW± +!Ç;L\Wa)Z!×);ï[kìàdL~õY{0;` Os)¡3íUqèÊP-½£pÃY”s6|Ù´²ôP Š[Î2 $ÖLM}@‚Ðä²Úã1=8NüX¿-vJÄs”õŠç‹÷€`b‹ +s¿â[;ö¦Þ·_ûrˆçn§\my|·v màU›h‹0| ƒ¤åaû$°(ô•ÂyX¥g7Á¨À(_Jr @~ *É$È#zÞ·É ãÐ=¦]T•UJ—Í ƒÝÓ€ ÉõøˆÒcDã(–©Ë¡p¢MùE)@Ð+×ü» ÜD Èê(ÚºpÄ.ëëµ3BlÈÚE²(I´Eää +P +Hò4˜§PmªS+m.ð²ÌÁÐü +еCNŒ¦± p|ózn]bháj?Cz/Ý!™£H€ˆTEBp)úØ_ÚžUȺqL^H`KÍ¡¢•ׂ±——%RÅGt¦Ü5^Í;m (·nÚâ£ÕgJÙ÷¶Äðíɱ%* Õ³# +Hú ¸ mr¬Þ!±Å“±K9œ³jR\à+ùkE°"PbŒ@€@[ïµ@;sö)*µÑI€•Jãlç!°¤v|‹ˆ»¬ŒäfÑ0Oq=¥¹E‘ÎI†dðì„Üf»ù7Áßp `Sßbqz'ÙÁôò4âÏŸuÿŠöNLâ¤Å*dÆï;Ì„ »â Y:\_‘èq@E?rªdJÔPÛ! @œo¢È"!'Ãät…0É.Ix,)ë€l‡›] ¼2 +€˜pLЀèn¯W,øÐrÁ­h—’9öo“R]¹èeÉ"ªlEê— D²J~¢FÚÂ)ñ‰wº™sdY²…0¡Ä^´°>zб‡ Àžk¢*˜¯„wúÁ?v’!òr +Y¥Åะ‚%‘¤ÔF11•‹Œì¹î +* 팉ÑzB»q'+`ˆŒIFk¯v›¤º:«2XËu ¨)ˆ€£äcR£€À‘¶LŠÄTPâ´ì¢Ã*„‘2‘¥QL AtEdÈ•ÐHº…‰w Š"˜4Ò5?8¥y³Ž Bš!3àG"uÁ ‰¨Y`c'µU¨…½ˆøX¼/ˆ¨Äy9@TÐqXß/2¨¸LDÛH<˜;€ÈåMYs   ° É–çT"È#õ»û•%©¨ •ÂqœÁÐAÝÈ¢œLšÛ4,¸›0ˆÅm&»P("GÊ&°lT+p¯™€…>Ã$›­I¨‰£C¸`¤éH1¡3×-U‰ +¢ +0€$!ÀŒr¬¢Lhh+pk³öOƒøI3˜Èÿ€:ŸXT‘ƒ p6ð1™H €pR‰1Ž$¢ X‰›Ê‹@ +h | ¨ ¯‚@Cb!@€‚‹LW¨K…œqà 8@‚ #ª€)*€B€€š?!ŒbÄÀDüw[wu™[ÿ32³3+3#üå]ÝÝݱY™™˜ÿÌÌÌÎæfffgù™™±®æfÌȯ¸“±3333333/3333333ffffffffffffffbÿ3333333™™™™™™™3333332ÌÌÌÌÌÌØ­û333339™™™˜/3336!ÆfflÌÁK3aÿ{ BíZ»»»»»»wwwwwv­Û·þfff™þffff{3¹™íç³=™¼¬ÎûÙÿ³;™žÌÎ÷£337Ÿæ{=™Ïgs=™™Y™ÜÌÌÌÌÿ7™™™žÌÿ¹™™™™þo333u½æžîffffgù¼Ïffw3;™ìÌÌÎ{=™ÜÌÌÌÌÌÿ333;‹33+ÝÌÌÿ;ÌÎßù™™›ýö³3333y™™ÿù™™™™žÊ™™™¿FfgùìÌÌÌÌÌÌÌÌÌÎçs33¹þwüÌÌÏó2§/yÌök¹™™™ÜÌÎç}Dffff™™™ìöfgs36s3333Ù™›Íæfffl<ÌÞfæff{y™™†ƒy™™žÌÌÌîw330@1™™™™™ìÌÌÌÌØ]ÌÌÖs3Ÿæffw333=ÜÌÌÌÍŒÌÌÞfffföÎæó=™™™°CÍû7™™žÉ™?æffMçù™™“èÌÌ€ó30™›{ØäÀp…âr1FB»³ïÄG#…ïzç,—À.¸½ýrPñ †/z­QïøûÞâ;öô¿à¾ö¹=þ.‡Gµ¸³¯è’b‡N·ÜÉôñŸ×H?Ëí.V(q=Õnµè2=×:½6¡ÇæÏøkv?0+·o°þ)êˆR_Nî¿<¯/W®¡ ß`uøk·¾uÑ~}á륊óó†˜] нzðnOœ;“é¸×s—UÓ«ßj¹¸y|ŠÉÞmtö&3\' w¿Á×ü‹€–½Çî˜ö¿Î•œÿ˜ÛÜwÒ _,ó½|7dž/Ä)›_ÊtÜy€¡¸ì3Á>®F¹Â¨_OíONóPôW#*M>GR™—/Xüù/¹ÌÿÝyÿy½ Kˆ›|ïðu1‘RøÿèÃQ©äzˆ^¯o\å_œ ó8;5´ú«WÄ/ž×xü¥÷ž¼xkˆÿàõÎryÂk§QíÆ»ßïUw¯Ožm5u\ù±?ò÷ÓÞÅÿÎ÷ ÷F²yn5zÈÕÿáÂÇÁíì=¯ 䉎Ÿ~\1¾’ŽE^ŽíÒ­ByÉÛè9ÿ7ÌõG;Ccý ÷.yãØã9…êô`ÓR˜µY“Ó¤¬;ìÙ¿&AÌÞê{g‰wT¿lÌúWñycúâ²N«§qÿT(ÎG÷¾ÞñB‹%Ê«C×ê¿ÿ4:µ»<Þåÿí#Úp}:Ñ;y¸ ³o‡U<ÿ[öù·åøî¦^-×vq~ôE1Éð‰|ÕÅ¢;Á@ÿ1áÏ÷~íÉÝpóüÿóÄ**ífD,Þâú—Š9½kØ}ÿ=9ÛYÖEOð&9Q¾F½ßûzÿù»ØçPÎÎÙÜŠn=ÿgùîkž¿rv}ß;® ‹åÖõ:ê×fvåÙ¬ÿþ=Ö£kƒÎµ=¥ÍÈæ~önûä¿7ülC`_;¨|år*¯ö\ãå9ÐØ¬Uùç¶Y_§©®=ÍÈZ{ÝÕV{$B]ËìAkþTdwsÝ6EØþóúôg¼ý¯k#»±¸ÛÏüþ‡2°î?öÏ\;Ý肊ŒŸ¿«ÅÍþøÈŸç˜®3?Ÿ.t_=Ø;Ìöv7«kÀãö£ºt=ÿþ†ô?šïAâï+=*sÀsWk:«höü»ÚÉ­,àÖwT߇Oñ½ûúœ9~½…ݹ÷½ßõþùÌãQ÷á~ö?Öh×¶"}ÿBÿcP•gwŒß;Òë\³Òá­E«æ¯O{õÆú«]à½úÔËïý…6:_ŽùqìèV‡ˆ¬B#ýkù¬×c»ZÌ{ÖÍo€äuWw¦¹“¿iGHë;‘w;íþ5àÞð½Ípw¹o¹Ö‰ÌUo±_ðc\š­!{öµþó|ôïQÏûͶgeå.Nõžq#ÊøK¾mΫõßTßž-жTö_ú®5Ò½™àRÕocÙ¿à(Üè‘Ï.ÝsÜ÷ý™ýϨî»ÕÉÿÃejmƒs‘_h^ï ýóûç÷éÛ$Ú•ÈöEDNÅÿÿL\îÈïèžWqæ1u+ƒÜ©ïS¿ê»Y¾a“ÚÓìðã›ÃB¸¢`“¹­o×ø]Õ?ÂÖ‡úöË7Š_{¦¿ ãÿ;îWy¢b†Üt!YÝE‡ù¹&¼vÙ4`ñé5'ÆHh•Ø•Rúmx´ÒöZÐøòá 9Œå˜=ÄéMF±Ðôä¥U&z–¦=ì£ áÙ•0ÈFÅ#¥UÕd…¬†ÃLÇFf†&lâ±¶íì–«ªÚ¬–“ÔœØþ›ªqÅ3ˆ{lçEùõ\«M¤â]Õ¢´k½§!æbLÈl1aæ]jj’Ë1Q®¶:Õµ¬e&´äÄÓ30aÌ8ØßQÈŒ_±Ýhœå{S¶–Õ3ˆÎd2ÛÈßVUð«=ÒªØÚ®b†mèÜ °Û¦&VHåäo x·R¹R»}DÖÏU¬1„ÛåÃPÍÕ f½+„ÙýPªUr×%6iY 9>lŽ:†O®š«5òT Íê*£5¤ªêe¢Ù¢3X›EÐvp¢¼™BpXö›‰m:YhΟ]3™­¦‘²£ÆM2å³tª©V)5,¾ÍÔ>tæŒFÁ´ª9„ ÎNÀ¸×Üc3S]³mà~'9–FÌÈÝ“ë¥ÚöB˜_®ê®š¶tï"ö™Ó*¦ÎÏ%rWlLÛT¦m“™GÆg§Ø œ \fNYÙ$2*KÍ‹ŒÌÚ61ÌÌ?ŸnÐd€ih­Û]L²¨ä5å•#£8°Ã£¡Ñ¥½fŸUMY‘M-W•žZbÛž¡¨–'†S= ZY'¨¢ª ÕÇmB™r–N[zÚÒ‰aJ¡™“i+ctÅV[¯Æ•‚òÈdhD4yÙÙ«6§;BjJ¯r£­é³‚¥U“¥©ƒV&QcwíšwDSØr®Tõ­4X2UºXî3tŒxŽNì–_vŒãl}1DÙ‹2© ŠgÐvT©@Ù–õ&êÔ“‡v3tÐìÐoH_…îÝ—“,T6U§UZ$Œa îc)3¬<ÚÌÆ’`ì£I¶ÍÌŒÃMVÝ4¢´PsDÉœË3´·¢Ø-yuž`ŒlUõØÈZ¦ÄŒÌFÐö°æV[eT¼Ø2¤’Rº¹f,¶°³CS!½†¬Ì}÷ÔUGd,MzS¬RU6«XÙɲYDâ`8ÜÃf?²|Á$ ýº¯#I)n¥‰5%å £°¼Ã6a²Ã8×¾)’JOíˆS@Í:û®kéÌO8_3fÉØsÇÖu€¶’KT [\(¤ºê\¶Y_67Z0ËF9¬9ñ³ïÖѬ°¦ˆ:ª’¡Ñ§£_39€Ñ‹.š¬6ÒréÃTÆFœ'&dA·Œí6cíÞ Ž—P†D“’†Ô™TîÃÓYŒÄq`À¤4Ñ›$C³2SaÁÊCf”lª“L.J ù¹KSÌáɉ’Äsgè<Çq’•{€sº-(”m+±Ë3MªÈðé8L¢3›;5²C-yéƒ¼Ë K¦4®ÒT·Ni,d QG’D„hÔQãkÛg€Í.R©`ç'†¦döHr”dޤGì~ÇoL]Õõá ,Ä÷.6mÆ2¬Ø¶.6†8„xË^`1½‹ÃÓ˦¶µpú£É»|vb0šÄ`‰JPáÌGž9æFãÁuý5¦ê¿Ý<À¥¨RéV†Y©‹(Ä¥™ž4sÌcgǶÆv<ÍŒ<iÀÊ‘–Xà#Œá£,¨ÉbŒNC=ÌI…ÉžÛðtÍÌK¼<õË(¯)•ª6Z)r—¥‰™Ã„Ã(³[ ¾6ÏàŸãÌ©:ÔxkÊÄLcdÙ[“)‘†Ìv8˜c²¯ƒ[ cÇß\œQR«ÆØ³ªM…2ÙLƒæ;$vb<ào²—q¿Þ÷ÿØæäíe2\•£5*×Y&‘v¦/€hx#ó‹2ɦÃ>™­?h7<5*¹[ lð¸1ïh욌ÎP~1vp ¯`¸L®}\©TÐÀwÁÏMPFaøó<'ô³Î?¹ÈU/ï |ÙË•²öÆŒ\  ®ÈÇ;aw6ΞO¼ DƒÿQÆU؉”Cޏ9›t͇Ôü›‚x +`„ §u^•P¤xàùÄci|øòr‹ô{òàøø5+*%+e$‹4Áà%Ã͹râÏ hÎèþ> +|þXJ¶4­’ôE¼©ÆgNÒrn“Îqñ˜wh£ Ó}Œ^¤Ñ©ªt¬Í™ƒM8áŒ7d‰<ãŸÌãÌ€/ „…T±âµ«VqÆpàÒLy1ÆÙ‘7áèá§0"öc4&@*FFzIÛZÈs0qãI†8Ò.®nèÞžöΖ,§ƒô¯2*-o&!ÉŽ>˜“ œÆNÓ‘ÌŒQ£€DH‰ÜúÞ ”ñ.Æc&GM…c‡ÎY3§úgããÞK÷sîHTFKY1\Ì‘3,.fÄógq†Îiï-Ƈÿýû1ð¡æ/õ[bÕ`Ñ—FŒÌ yÀâæã ÜÖ\¼@à@8\‹L¬´Å£)ƒË2:dàcÎF3šÌ3¤Ê¸[>¶þ‹lxßl±™I¶¯Yl­  9˜Øcs\·™§Iþƒ°r¬d R›˜^4!6nI¥ØÀLñh§iâ·oþßíÌ,?¢k9ÍXèn\@Ó3€Ò[žÃf‹8ÊÐÙ×¹ž„A‘n£6RåŠÒ‡c6 ¤9 i6bî6XæY׸þÿ;¶Z©Ž=8–cPÍ1á°Îc<›1 ˜æzÏžCÜcâp8Ðh\ÆfRÌž±”¦xíG2 ¾dÁÁý À="hsv‹xÍ]e-'É—9¸öG1ƒâËÍœaý‰áýâ<œ¶ #ÖêpœŠÈ1âhä=&àÎ0};´qóûØ<á° ¾ø^Ï%±lªJ«T##ògØÈìwiÅä{PüG~ÁÐb©©J¥ªåt±º“œcCaÈìwEÇæ<ØÂ@q8ãÕ3%sK8”j‹›3‘ɰþD~7W)ç·ž=¿~Ú @” m‰’›q:¤œaÌ8²9…Èáþ*á¹}yd¬wivIÆ{¥³\±ÃfC5s9!œ Ø¿‘͆Íÿƒ>ŽGø"UÛWÚ«º)‘ ”‰òÌã9¨}‹ìZ›¶7ÿÀàpñ÷»¥aUèÒÎa4x£8†zpñ—ÜlÇL0G àßüyUÎy"’JXFÊ`ò#0ÅÃ7<ä¥c?“~ç¥ÿdž›xWeë—j7(>̧œiÃ3Æj>rÄÖàÇ3â|2œ „Ý9V¥‹)ÍOlèù5_Œß3ìpÇǘc5ÿš? óÀ'ðR%”²-\„èCñžrœQ™™ xqü\c øÿø>a¥ü3ÙCt"Je"µ»<ìañ8eqÁ›™òx¼\q‹ßá@¯„~LñÜ*‹—Ý2‘:NS3`øÎ˜pÇÐæ¿,9øpïœ;ü9 ¶2ææ3>AÀðÄ>2Ãá|<ç˜Ø~ó?7¸Œ H“ËñÔ›Ei”´N±!éÜ?&I Ö<„ãÖ#þaÙóüüÂ"I 3)Âb£89£Cr±áÍ™6™q±œÞŸàLŸÀcÀõèTéºäK°Â™çhÛ¥KÑå“Ï»>yü?ì=N, +Å`FåŘ<8rðÖ\ÇÓ¬äãŸÏ?ÜñCåCª.þßà@ÿ÷æÚ…¹+ÝX´f¢9#€ù0_0n^9˜èíøü3??ÿâ™@^Ã2­²ªÒùc°86 ec‡Ã¡f×r׸@³¢”ÝZÖZÙ®óbÀÄÍÐó1‡z:XñæÃþ¶÷ÿŽ¿*¦Ñ¥ C•QÕ`r!£N`q¯37œÐh°ÿÿÇ„I¦Ú*œÝ9j8g“.LhK%ƒ<,Î{=ä_€øâÌä²ä*uS“lPO"La1™Xdï²rÈÞ åñŸÿvðêo"ͦY³cPÍI 6y<ñxXãï–9(²]ÃÙ6xgC‰Æx¹”Û„o,‘ÿž†>œ0øÈ.åi­SrÇmü9˜hɱ¦¤ê"Ø[d î[ò;Ï@„ý§¶ÕfE9š«¦Tltbè˜ÈÍò v|a€?Ÿ=ãŸ$2KQ­t1âf4M72qvÇ{™óèÿn^ ŸŽxÞ\¥$âd¸äS¬Ä¡:‡IÔÈe¡‡ð°ýÏÁ÷úʱ§6šçÉŒ:3Iä2o3‚qÓöy=ÆIß:J% ¬‘šRäÚfSLØb•‡5»,ÈøÏ°0àcóºQ6I%.¾µ>s3Nâs›Y‡¹XÇ~~`çüàð¨d“ݦgZŒ”p~1™é‰Í-¤ z‘âåÇx·›¿C™§O+–²I¬9š ;3áìÃÀóÿ/ÃÝ=±¶®rá´ªìlÆ5˜à±¡aù¢X:F¡ï ïüŸ€^üs\§7Ýi–Di‘›`òM'8”äÇÆ±¼‡Ÿ0:ER:k­š³-ÑœÉ)NMÎ&MƘÇyÿÀøÈ9•Q¨Ú8¤gG°Ì9G+6:£Vâ_üq}œ›[dnX˜ÌiƒÐŒå9Wrã LCÿÿñÿÇÀ޳fX0mņ8¢ƒÈ†eÈ\pçΙ¾ã€Álk+tK*p£Í¨:Ñt†Ç.q±ÒºÃ‰Ÿs€ÏôHÒ¹&Ôé’§V‹ñƒÎƒ¬kc±£´Ð¾3@Ç9à“‘´©‘žc8Ç,ËJ™œ¥}¿ãp¡ãnÓ‰Û2TòɘžfIwefÓÞÎs'ÖÿðÛ4«•#µƒUeZÈÙTš.¬²hgFû}à ‘àš%ëŽTÎä©™ÂZGJ‹C1–dØ„ñž~ø\Ã>pÀàÿøøÏ‡PÕCði@Àø øÀ 1œs[RþàGþqøÿð@xFŒîx¼ÿ‡ñxk€0?ð?þ +Îc›†ÁÏày?óüÿÿ€‡ùü_ƒçœ`™{‡‚8y†ü9üÀoïüÈ ûCw~xÓ?à øx€óÏðóËðãÁ7ÂÉÄ€b÷àÿ(aþqìm1#/ÌÃÃà1áÿñ> ð°Œ~Ç*ÙroGÜ!1‹À|RLrhf3Ìòßàÿ¿à¥¡àg3A«å‘ µà<üàqüã̃Ì€Gö‹‘xßö¯ðxAÄÜ|‚æóÅ\d}K’¤’Ïaðàÿ 8v_+F7qÇ›,ðü70@òrølÉÔ‹€À ÿÿbÀ@€¢ìåy²ëÀÿ°8øp6à÷GœqĔΩqT9çƒÿÿ<>ß0ðÌóÿúgž¼2rfxYxñðÿÊñÌrþ€ÿœgqRGx>ÿç€ Ð<ö^\¼’9IÃ;5@À`âƒõpH0ÌF£6x°â?Ãÿ†€ÿ‡Eü‡|ÛEøñÁ÷5‡ˆdøœp0;öðþ÷˜ÿþÍ…1½5˜2sÐÿÏÿ¸ÿÿüÀþåÆ9­ç.PðË?Nøy÷ÿC`˜ðI„c?:%Ï8NqœyÀø‚ÁüûÀR™<=ÆÀÑ$pc†ü~CüŸ€üyžÞaí>ÅÑŸ§üØ1‡ã3t?øë¾ñ‘ØÔÆV̸x£ò.Æz#|ä ð€à1ÿ€…)öe°=ÛƒÿË8‡ÀŽù3’E 03.’<2 ‡Ã—ðøÄ ðpH‡YŠÁŒ Kí4qþßÿÀ8ùüüãÀà?…Ÿ9v8a,øf£äãþýxz?þgÁôÿæ s‰§÷Éó¡ +³ÿóà‡ÿ0aà0æOg+\,çM,c™}˜„€0`€BŽºQË’3œ;áØàx?ø0#ÿ?ÿ?™÷;-Páœh<ÃñHéÄóáÀ~þpÀpàøü|0øï2ì&¥ƒÆy‹¾‡ãø?ÿˆ ðÿ!â)ÍcÆy±,œG=Ç>üðøsC€tú‘’&æM‰¶sNÑùƒ¼·ÿà``ñ8`` 81”?e˜tqÃæÊ:L‹0χü8nþ?`üÈoGë±£‘:¡ÓãLžyà üÁð>?ÿáŽÐ'1ñ%Äxi˜ºç[Ãÿ>ð=ÿÐ_{üþà|dü#šˆó”¢Çrlטÿ´0 à<Ãðp?üx1Š]Á¼ÞžÄ:4î*@Ý?8?ô÷àáÜ8³Ã3a’jU­¬Bðqœx ð1àü0rÃÏœ˜<<ñM<¸Tƒá·‹ À?ø7ÄœóvcN™¶ÈíužSøÿ¼yãïçÀ{þwœaȘnjq†œÃΜ7ðüçùßóŒ`Ÿ€xÁS%;Æ96ØÕlÈ`óa€€?Æ}€ðb?=¾É8O å±Ì³\†'…¥Áþàÿ €çß÷€ð8ã1Àp8Q6fGL¥…¶3ÃÃ>{àâñƒ?àny˜cðÑ[Ü®q”é~@xÿÀ~Ëð¸ÿ€ ÆyÎB×’æbqÌfÔã©À7ÿü/À<> #[FÃ6ù3žO§sP¿ƒÿÏýàùÀþœ7ûÿéÇ…O“Ì^94…Êþd|ûðÀ ÿáÿ˜§ íXàq«8¬€ÀüùàøÀùŸ¸x<â£ í±³Eáó†ð5Ÿ€ÀÏÀ}çÿïà=ާÓ<ÄÓÁðãž9È4CþïþÓÞ÷Áÿï00?lÌæ3¶qì‚ã CÌ×ü3Ïðÿàÿ ñÞ<‰pïÃæd?!ž'\´ü^ÎÌðü7Àž ø àá¦ÇÓ318€îlñ¨Îƒø à#ãø>à 9ÿñÄp<$£ã˜<ÆÜsУÜ7|Ðóæ‚?Í>ĬrOSÏ< œéÒiÓ>ðé<Ÿà?ÂC?ÀþðÈò6±g`™ <öv•¸Êÿþy€yÇ€?üþÿÈÍóùËÇr†’Q⓳³Žþ$9àÿþ8|߃2i2Ûq¸löáãð|ù€qûø>߯üó,i,G‘aA‹±—GÂ>ð|ÿïÎaüÜ:àô‡FÒ’±YO @¿ÿÀü€ð{þ y¾ç +»ž.:s…‹eáÃÃïè(ü¸üÀ%ÇŽ3&è·âLjb8ŸÏÇÿøÿáÓÀ àÿñâOŠ,‘ÅÄ9ó~qž²º­ƒÿÿðƒ‡¼;ÿàø`YŽ(¸ˆäyçËc3r8c½8ÿàaÿïøøðþ€À>dü±Îg%¼gÊÁ2$ºfÿ°x¿çö?(?ûã;†œÊÆv‹ŒŒgÃð`EÕçÿŸŒž>v3îàGÿüøÀ`æÃc›5Œ›ÍsZðŸ¹Çÿ;AÎýþX?ó™®\êy²G¹¬ÉILMù0ͼùïïþ#äûŽpÿÿ¼`B8°@ä¤,ãYãðÈAÀN‡ÿŸ ßÀ‘ÈeC‰Œ¨ñ&•±6Y%›øýÿþÿÿ¹ðÿÁ³ñôï ÁƒtÌcìíÁ6ÂÁ=þó=à?ù ÿüüçbsg÷üüÑäT^aÎ2Ž3a(Ó…OÏøñÿÏ‚ñçŸIà`‚¼Ÿ +E6±˜ûÜSÏaûüñùùþ€'öã!œÛ³ÓÓ†Ä2ir ´ô÷yøüùápŸÍö +Í Òô5GÂÏÄðVU¦ GóƒœÀÜüáÂc„x`µpÃÌ’¹œÁ yY˜f€ƒúÿ›Ïˆ‡À!ùòÙQöw\u3c¦>\Æ$ñ!øD?ßðÿþvsñð€ó4CìTä‹‚™çžæn0ÀàpãÿÿïØ~?;.£:SÃrÉíϤù9þƒÀ@ßß?Çð|ð1T2v=3ƒnÑœM³Äðpÿüðþüÿýè}ãÿÝlj–=¼Ô£>ÉÜqØ'ÿ†?ó§ÿÂà¥Ïþò/†Ç%±o[ºcN? ÿòž/ÿðp`°¡.µx®Î™8œjƒÌŒäÿèûx~àø{Ááÿÿÿveá0íX¾rg±Ï±øÃá€Àp>Ðzçì8¶RqÖ&Ó8ÉПðàû€ðÁÇñ÷€3IÓÆ™´m.µÉb‹ _÷ˆ?øðÆ0?ÿÑáÃÁ,|ØŽÖC0³¦ÏƒÿðÀþý7€ûý€8ümñZtHÃb%£±‹çÿ7Ÿƒþÿ`~0À0ÃÓ-¶XÚ›“—‡U³sæÿóðÿøû›°‡ô3šc Ìd1–GŒÉœ¶–u‡àÙÿüçÿ#ÿqóïx°çiwÄåÓ1,rÌa£ãb?ø`çóü~‡ŸüòHÑI¦qŠUfZ,±bóãïûôÿò7ÎÃÃÿ¯u‘z¬Ås{:0}ñ÷ƒçÿÇgý‡sãÿñøQQJcK)dQ-#+πÞoý'‘ø ëÛ­ÿ5G^7•²uϳ–«þÃûáùãáàžü…’¥·a¬ç%¤ÌîbþõÀÁÏ<ýø@&;4_´s%n†‚ÿÿ„Ã?aË!lµÃñµuÃX ð`þàøÿþ9øcòöã(GZu&¹ÈœÏð}ÏÝü`?þèüp¤73LìR‰å‚NŽZ¦ÿƒ‰ÿð?øø!Ï€'¢”˜¬&MôNayc<ÿÁág>Lþ`Îyó ÏÀþlW™æÛ)yJÆtùpÿãéýÿÿÀÿþþÁ8‹*]ONèÃpŸâïðpÿà|ûmHô­š(´ÔÖ“D>Øþø48|yðøZ¶+'–T§Î7'Çÿwùü Ú øþ1ÿh~žÃQ’ô²Ýœšè<Ïÿü` àpþ÷üø ÿÞm#®o´‡Fd¤Dzç?‡‡P~Ÿ÷†Ϙáÿÿ£I¶8&jÄ–S©&ýÇxðøÆKR†§Ë2‹eØ}‰<øÿè‡á<>'ùø +T6W+*S2¤âɶÏþÿÿÀöÿÈ€_Îà ZRÞ(Ú‡­!¹Œ²¾!àÇÀ ~Nç̸~äg¦¸F2›+æ´I£=€ÿ9Ãðãÿà?ÿÿö?Û$SŽ5FYn­-¹î†|ÿ³âsÆvpCêÆËÔðY§)YÚ3¹±†þÿàüçöÒµÉäìvfZ˜žçþ΀ðèpÃÿxåsÙ3É +u\à \ No newline at end of file diff --git a/astropy/io/fits/tiled_compression/tests/data/m13_plio.fits b/astropy/io/fits/tiled_compression/tests/data/m13_plio.fits new file mode 100644 index 00000000000..c93383bd5c7 --- /dev/null +++ b/astropy/io/fits/tiled_compression/tests/data/m13_plio.fits @@ -0,0 +1,1484 @@ +SIMPLE = T / file does conform to FITS standard BITPIX = 16 / number of bits per data pixel NAXIS = 0 / number of data axes EXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H CHECKSUM= '1aF71a961aC61a96' / HDU checksum updated 2006-11-15T17:28:20 DATASUM = ' 0' / data unit checksum updated 2006-11-15T17:28:20 END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / 8-bit bytes NAXIS = 2 / 2-dimensional binary table NAXIS1 = 8 / width of table in bytes NAXIS2 = 300 / number of rows in table PCOUNT = 165616 / size of special data area GCOUNT = 1 / one data group (required keyword) TFIELDS = 1 / number of fields in each row TTYPE1 = 'COMPRESSED_DATA' / label for field 1 TFORM1 = '1PI(301)' / data format of field: variable length array ZIMAGE = T / extension contains compressed image ZTILE1 = 300 / size of tiles to be compressed ZTILE2 = 1 / size of tiles to be compressed ZCMPTYPE= 'PLIO_1 ' / compression algorithm EXTNAME = 'COMPRESSED_IMAGE' ZSIMPLE = T / file does conform to FITS standard ZBITPIX = 16 / number of bits per data pixel ZNAXIS = 2 / number of data axes ZNAXIS1 = 300 / length of data axis 1 ZNAXIS2 = 300 / length of data axis 2 ZEXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H COMMENT COMMENT This file was produced by the SkyView survey analysis system from COMMENT available astronomical surveys. The data are formatted COMMENT as a simple two-dimensional FITS image with the same units as COMMENT the orginal survey. CTYPE1 = 'RA---TAN' / X-axis type CTYPE2 = 'DEC--TAN' / Y-axis type CRVAL1 = 250.4226 / Reference pixel value CRVAL2 = 36.4602 / Reference pixel value CRPIX1 = 150.500 / Reference pixel CRPIX2 = 150.500 / Reference pixel CDELT1 = -0.00027770002 / Degrees/pixel CDELT2 = 0.00027770002 / Degrees/pixel CROTA1 = 0.00000 / Rotation in degrees. EQUINOX = 2000.00 /Equinox of coordinates ZHECKSUM= '2f4R3c4O2c4O2c4O' / HDU checksum updated 2006-11-15T17:18:55 ZDATASUM= '1803906202' / data unit checksum updated 2006-11-15T17:18:55 CHECKSUM= '9kLgChIZ9hIdAhIZ' / HDU checksum updated 2006-11-15T17:28:20 DATASUM = '3243227777' / data unit checksum updated 2006-11-15T17:28:20 END  + & +4 + L +`t‚Š Œ¢ø¨ú˜øŒö |ó"hö$N÷&:((ô*(ö, -ü +0ÿ2"ý4 68:$<(>. @LB^ DnúF„øHx Jh L‚N˜ PšR²TÚ W +Y [:]L_Vadc„e¨gÈ iöl n6 pLrd tp v‚x”zÄ|ò.bƒ’…¸ ‡ìŠ,ŒJŽr!°(’ò#•B—ˆ™´›èž F¢r%¤¨(¦ò$©B'«Š­Ø° +²:´h"¶œ%¸à »*!½j¿¬Áè$Ä"ÆfȪÊÈ!Í ÏFцÓ®Õâ$ØÚf!Üœ&ÞÞ%á*ãtåžçØê +ì4 îp(ð°,ó"õX%÷œ-ùæ$ü@#þˆ#Î($d!¬+ î  D&„"Ð#Z”Äú%*" t"¸$î%'*%)t(+¾$.!0V2˜4Ô!7&9T ; =à@%B@!DŠFÌHü#K*%MpOºQòTVR(XŽ#ZÞ*]$,_x$aÐ%d+fb(h¸%k"mRo–!qÔtvJ"x‚%zÆ,}*h,¼+„%†j ˆ´$Šô#<!‚(‘Ä!”–V˜ˆš¼œêŸ "¡@£„'¥Â¨ªN¬†®¬°È²ôµ(&·^!¹ª&»ì"¾8À|°ÄÖ!Ç +#ÉL'Ë’Íà!Ð!Ò\ÔžÖÄØèÛÝ4ßdáˆã²!åè$è*%êr%ì¼$ï&ñNóšõØøú8üfþ˜Ðöþ   < p  ¶Ò4ý824 < "J $d &~(–*²,Ðú.Ü0Ð 2î579 ;ò=&ó? +@ðBô DþGIûK" MO0Q<úSB U6 WHYZ[h]t_~ëa„êcZþe.g*i2ük2øm*ùoq su w ýy,ù{&ù}ÿ +úù‚ü„îÿœ + o@ @0@ @0@ @0@0@0@ @` @0@0@````pp`` +``pppp +pp0@` @pp0@ @`````pppp```` +pp +p +0@`p0@ @0@0@0@ @ @0@ @0@0@ @ @ @ @0@p @0@ @0@` @p0@````` `p p0@0@ @ @pp0@ @ @p @0@` @ @p0@ @0@0@ @0@ @0@ @``0@0@````$`M`6ppNp6ppp0@0@0@ @0@```` `)`J`@p pOpFppp0@0@0@ @0@0@ @ @ @0@0@ @0@ @ @ @pp0@ @0@0@ @ÿœ p@ @0@ @0@0@ @ @0@0@0@ @ @ @0@0@ @ @`` +`&`V`XppbpMp0@` @pppp0@ @``` `pp0@` ``7`0pp:p+pp0@ @p0@0@ @ @0@0@ @0@0@ @` @0@0@` @p0@ @0@` @pp0@``` ```pppp0@p` @pp0@0@ @ @0@ @`` @pp0@0@``0@0@0@ @```` +`%`xa`¶pqp”p.p ppp0@0@ @0@```` +`` ppppp0@ @0@ @0@0@ @0@ @ @ @```ppp0@0@ @0@0@ @0@ @ÿœ p@ @0@ @0@0@ @ @0@0@0@ @ @ @0@0@ @ @````J`¶`´p4pÆp‘p60@` ``p pppp0@ @` @```F`¡`|pQp¢pepp0@ @p0@0@ @ @0@0@ @0@0@ @` @0@0@` @pp0@ @ @p` @p0@``` ``$`p"p"pp0@p` @pp0@0@ @ @0@ @`` @pp0@p0@``0@0@0@ @`````G`ða÷aRqqöqpRpppp0@0@ @0@``````ppppp0@ @0@0@0@ @0@ @ @ @``` ppp +0@0@ @0@0@ @0@ @ÿœ`rp0@ @ @0@0@ @ @0@0@ @0@ @ + @````\`â`àpJpþp­p:p ` +`$`2`p&p2pp0@ @0@ @```&`xa `Êpqpšp,p pp`p0@0@0@ @ @0@ @0@ @pp0@`````ppp0@````pp0@``pp0@0@0@``` ``!`p pp p0@ @p0@0@ @ @0@``` @pp0@p0@``0@ @0@0@0@```````Sab5avq5r@qGpZpp0@0@ @` @` @pp0@0@ @`p0@0@0@ @0@0@ @`` @0@````ppppp0@0@ @ @0@ @0@ @ÿœ`rp0@ @ @0@0@ @ @0@0@ @0@ @ + @`` +``D`¢`˜p2pºp}p*p``L`b`pJp\p0p 0@ @0@ @```&`x`õ`ºp}q p–p,p pp`p0@0@0@ @ @0@0@0@ @pp0@```` `pp p0@```ppp0@``pp0@0@ @0@``````ppp0@`` @pp0@0@ @ @0@``` @pp0@p0@``0@ @0@0@0@```````9`Àa‘apãqŒpãpBpp0@0@ @```` @pp0@0@```pp0@0@ @0@0@ @`` @ppp````ppppp0@0@ @ @0@ @0@ @ÿœ `qp0@ @ @0@ @ @0@0@ @0@``0@0@```````;`8ppLp6p`````x`p^pkp8pp0@0@ @0@ @````D`ˆ`fpDp”pXp0@0@0@0@` @```pp0@0@ @0@0@````p ppp`` ``p +pp +0@ @pp0@ @0@``` `pp0@0@``` +```pp0@0@0@ @0@ @`` @pp0@0@0@ @0@ @0@ @ @0@````````R`³`xpap´plp pp0@0@0@` @`` ```p pp p`pp```pp0@p0@ @0@0@ @`` ` pp +p +0@`` @p pp0@ @0@ @ @0@ÿœ +`qp0@ @ @0@ @ @0@0@ @0@``0@0@````` ```pppp```D`X`pFpMp(p +p0@0@ @0@ @``` ``0`&pp4p p +0@ @0@0@` @```pp0@0@0@0@`````p ppp```"`pp#p0@p0@` @0@0@````pp0@ @````$`)`p/p,0@0@0@ @0@ @ @p0@0@0@ @0@ @0@ @ @0@0@`````````-`$pp4p$p pp @0@0@`````#`R`a` pbp^p&p`pp```pp0@p0@ @0@0@ @`` ` pp p +0@`` @ppp0@ @0@ @ @0@ÿœ + o@0@`` @p0@` @p0@0@`` @p0@ @0@`````` `pp +ppppp````$`pp!p0@0@ @ @0@ @``` `ppp0@ @0@ @``` `pp0@``pp0@``` +``pp +p````(`pp+p0@0@ @0@`` @0@ @ @0@``````.`6`p>p8pp0@pp @ @0@ @p0@ @0@0@ @ @ @p`0@``` @```pp +pp0@0@ @`````?`˜`¸`p½p®pAp`0@``pp0@ @0@0@ @ @0@ @`` @ppp` @ppppp0@ @0@ @0@`` @`pp0@ÿœ o@0@`` @pp0@` @p0@0@`` @pp0@ @ @``````pp0@ppp`````pp p0@0@ @ @0@`` @pp0@ @0@ @````ppp```pp0@p````pppp``` ` ppp0@0@ @0@`` @0@ @0@``````&`,`p2p,p0@p0@ @0@ @p0@ @0@0@ @ @ @``ppp0@````0@``pp0@0@0@ @`````M`¨`Ø`pãpÊpGpp0@``pp0@ @0@0@ @ @0@ @`` @p`` ``pp pp pp0@ @0@ @0@`` @`pp0@ÿœ o@`` @pp0@``` +` `p p ppp````pp0@ @`` @0@ @p0@0@p`` @p0@ @0@0@` @0@0@ @ @p0@` @` @pp````pp0@ @0@`` @0@` +``p +pp0@` @0@` @pp @ @0@ @ @``p`` ```pp0@``pp +0@0@0@0@ @0@ @0@0@p @ @0@```ppp0@0@ @ @ @0@0@ @0@ @p````6`v`–`pp’p5p +p0@````ppp0@0@0@ @0@ @`` @` +``J`KppQp=pp0@ @0@0@ @ @0@ÿœ q@0@`` @pp0@````pppppp````p0@ @`` @pp @0@p @p @ @p0@` @0@0@` @0@0@ @ @p0@````pppp````pp0@ @0@p` @ @``pp0@` @0@` @pp @ @0@ @ @``0@``` @` @` +` +ppp p0@0@0@ @0@ @0@0@ @0@`` +`ppp0@0@ @0@ @0@0@ @0@ @p`````2`<` +p;p>p0@0@````ppp0@0@0@ @0@ @ @````5`v`vpp{pfp1p 0@ @0@0@ @ @0@ÿœ t@pp`` @pp0@````pppp +0@p` @p0@` @`` @pp0@ @0@p` @0@ @0@0@`` @ppp0@0@ @ @0@ @ @0@0@ @``` ` +pp +pp0@ @0@pp @ @ @0@0@ @` @0@```ppp0@ @ @` @ @0@```` +` `` ` +p +pp0@p0@ @0@0@ @ @0@0@ @``pp0@0@0@ @ @0@0@0@ @ @0@ @0@ @```` ``ppp0@```ppp0@ @0@0@ @ @ @0@`````6`€`{pp€ppp4p 0@ @0@0@0@ @ @0@ÿœ v@pp`` @pp0@````pppp0@0@ @p` @`` @pp0@ @0@`` @pp0@0@0@`` @ppp0@0@ @ @0@ @ @0@0@ @````ppp +p0@ @ @ @0@0@ @0@0@ @` @0@```ppp0@ @ @`` @0@0@```` +````ppp +0@p0@ @0@0@ @ @0@ @`p0@0@ @ @0@0@0@ @ @0@ @0@ @` @ @p0@0@` @ppp0@ @0@0@ @ @ @0@```` `$`W`SppWpLp#p0@ @0@0@0@ @ @0@ÿœø`s`0@````pppp``` ` +pp +0 @0@0@ @ @``pp0@ @```` ``ppp p0@0@````pp p @`p @ @0@0@0@ @ @``` `pp +p +p0@ @````0@0@0@0@0@ @0@`` @0@0@````pppp +0@ @````pp @0@ @`` @``pp0@ @p0@0@0@ @```pp0@0@0@ @ @0@0@0@0@ @ @ @ @ @pp0@0@p` @0@ @0@0@0@ @ @0@````#`"pp&ppp0@0@ @0@0@ @0@ÿœú`q`0@`` `` ppp pp```pp0@ @0@ @0@``pp0@ @`````(`pp,pp0@0@````ppp @ @pp0@ @0@0@0@ @ @````pppp0@ @` @ @0@0@0@0@ @0@`` @pp @```-`.pp*p(p0@ @0@````pp @ @0@ @0@`pp0@ @p0@0@0@ @`` `pp0@0@0@ @ @0@0@0@0@ @ @`` @pp @pp0@0@` @0@0@0@0@0@ @ @0@```` +` +pp p +p0@0@ @0@0@ @0@ÿœø p@``` ``(`pp*p0@0@0@ @0@ @ @ @0@` @ppp`````` `>`*p#pDp#p +```ppp` @ppp0@``pp0@0@0@0@ @ @ @ @0@0@ @0@ @`` @0@0@0@ @`` @pp @```5`4pp0p.p0@ @ @ppp` @pp @0@ @ @pp @0@ @0@``0@` @pp```pp0@ @ @0@0@ @0@ @ @`````ppp +p0@0@0@````pp0@0@0@```0@0@ @`` @0@0@0@ @ @0@0@ @0@ÿœö p@``` ``0`p"p.p0@0@0@0@ @0@ @ @ @0@` @pp0@```` +``8`&pp`X`p:pMp.p `pp0@0@0@ @ @p @0@0@``0@0@0@ @ @0@0@ @0@0@ @p` @p0@ @0@ @````pppppp``0@``0@ @ @pppp0@ @ @0@0@` @``p````p p0@p0@` @0@0@0@ @ @p0@ @ @`` +`*`j`™`$p‹pŒp8p0@0@ @p @` @pp0@0@ @` @0@0@0@p0@ @0@ @ @0@0@ÿœö o@```` +`` +p pp +p @0@0@0@ @0@ @`` ` +`pp p0@ @``` `p0 +@`"`„`¨`%pjp›p^p0@p0@0@0@ @` @pp0@0@0@`` @0@ @0@0@ @0@0@ @p` @p0@ @0@ @````pppppp``0@``0@ @ @pppp0@ @ @0@0@`` @```````p pp 0@0@``0@0@0@0@ @ @p0@`` @0@`` +`0`z`¹`0p§p¨pBp0@0@ @p @` @pp0@0@ @` @ @0@0@pp0@ @0@ @ @0@0@ÿœ÷ p@``` +` +pp +p0@``0@0@0@ @0@````ppp @pp`` @0@``.``Ä`%p~p³pnpp @ @pp0@0@````pppp0@0@0@ @0@```ppp``0@0@0@ @0@ @0@ @ @0@p` @ @ @````pp0@pp` @ @0@ @``pp0@0@0@` @0@p0@` @`` @` ``p +pppp @` @p0@ @0@ @0@`` @0@p``!`T`„`*prp|p3p +0@ @ @ @pp0@ @ @0@ @`` @`p0@pp0@ @ @0@0@0@ÿœ p@p`` +` +pp +p p0@``0@0@0@ @0@p``` @0@ @pp`` @```k`ˆ`pZpspJpp0@ @pp0@ @``` `pppp0@0@0@ @0@```pppp` @0@0@ @ @0@0@0@ @ @0@``0@ @ @````p p0@p```0@0@0@ @``pp0@0@0@` @ @p0@` @```p```ppppp @` @p0@ @0@ @0@`` @0@p`` `$`4`p*p4pp0@ @ @ @pp0@ @ @0@ @`` @`p0@pp0@ @ @ @0@0@0@ @0@ ÿœô o@`` @pp +p0@`` @pp0@ @0@0@` @`0@ @pp0@ @p``` `)`6` p p-pp`pp0@p0@``````ppp0@ @0@0@ @```ppp0@ @pp0@```ppp0@0@ @ @0@p @0@`` @p0@````p0@pp0@ @` @ppp0@ @ @ @p0@ @p```pp0@0@0@0@ @ @0@ @0@` @0@0@ @ @`` +``p p0@0@ @ @0@0@ @ @0@ @ @0@0@ @``` `pppp0@ @ppp @ ÿœö o@`` @ppp0@````pp0@0@0@0@```0@pp @ @p```` ``pp p` +`pp 0@p0@`` @``pppp`` @0@``pp0@```ppp0@ @pp0@````p pp +0@ @0@p @0@` @p0@``` `p p0@pp0@ @```ppp0@ @0@ @ @p0@ @p````0@pp0@ @0@ @0@ @ @0@` @0@0@ @ @p` @pp0@0@ @ @0@0@ @ @0@ @0@ @` ```p p!pp0@0@ @ppp @ @0@ÿœ n@ @ @0@ @``` `ppp0@p0@ @ @0@` @p0@p @` @0@0@``0@p``` +p +p0@p0@ @0@```p0@` +` pp p p`` @0@p` @pp0@`` +`"`(`p p'p0@``pp0@ @0@``0@0@` @0@``` @pp0@p0@ @```ppp0@ @` @0@0@``0@0@ @p```````pp 0@0@0@`p0@`` @p0@ @`p0@ @ @ @0@ @0@0@0@0@ @0@0@0@0@ @ @0@ + @0@ @````-`?`'ppEp4p pp0@ @0@````pp0@ÿœ + n@ @ @0@ @````ppp0@p0@ @ @0@` @p0@p``` + @p p0@ @ @``` ` +pp0@pp0@ @0@ @0@``0@```ppppp` @ @` @pp0@```(`4`p(p3p0@```p0@ @0@p @0@` @ @p``` @pp0@p0@ @```ppp0@ @` @0@0@``0@ @ @p```````ppp p0@0@0@0@`` @p0@ @`p0@ @ @ @0@0@ @0@0@ @0@0@0@0@ @ @0@ + @0@ @````5`M`/ppVpp!p pp0@```p0@````pp0@p0@ @`` @p0@p```` `$`q`¼`‹pLpÕpŽp/p 0@pp @ @0@ @ @``` @ppp0@ @ @0@0@````pp0@ @0@0@ @ @0@0@ @0@`````pp pp0@ @ @0@0@ +0@ @ @0@ @0@ @ÿœ `qp0@ @ @ @0@0@ @``0@ @0@0@ @````pppp0@ @ @0@``` @pp0@ @``pp @ @0@`` +` @ppp0@ @0@ @0@0@0@ @``p0@``ppp0@``` @ @pp0@```````B`2ppFp+p pp0@```p0@````pp0@pp0@ @`` @p0@p````` ``4`!pp3p&p0@ @p0@ @ @0@````pppp0@ @ @0@0@```p0@ @0@0@ @ @0@0@ @0@`` @`pp0@0@ @0@ @0@ @0@0@ @ @0@ @0@ @ÿœú p@0@ @ @0@ @ @ @0@```pppp0@` @0@0@```ppp0@ @ @ @p` @p`` @ppp +p0@` @0@0@ @`0@0@ @pp0@ @`` @p`` @ppp0@````p``$`$pp.pp``ppp` @`` @ @pppp0@0@ @0@0@`````` +` pp pp pp` @pp0@ @` @0@0@``pppp0@` @p0@0@ @ @p` @0@0@ @p0@ @0@ @`p0@0@0@0@ @0@ @``p0@0@`` @0@p0@ @ @ÿœø p@0@ @0@ @0@ @ @ @0@`` @pp0@` @0@0@```pp0@ @ @ @p` @p`` +``pppp0@` @0@ @0@`0@0@ @p0@ @`` @```%`pppp +0@` ```pp`` ppp`` +`pp +p p``` ``ppppppp0@ @ @0@ @0@``` @```pppp`` @pp0@ @` @0@0@``ppp0@`` @pp0@0@ @`` @0@0@ @p0@ @0@ @0@0@0@0@ @0@ @` @p0@0@`` @pp0@ @ @ÿœ o@0@ @ @0@0@ @ @0@0@ @0@ @ @0@0@`` @pp```pp0@````0@``pp0@ @````pppp0@ @0@``` @pp` @0@` @pp0@0@ @0@```` `$`Q`>p&pNp1p0@` ``!`p pp `pp```` +pp p p`` ` `$` +pp'ppp0@0@0@ @0@`` @ppp` @p` @pppp`` @pp0@ @ @0@ @pp0@ @0@`` @pp0@0@ @ @ @``pp @0@`` @0@0@0@ @0@ @0@ @0@0@`` @p0@0@`` @0@ @pp0@ @ÿœ o@0@ @ @0@ @ @0@0@ @0@ @0@`` @pp```pp0@```` @``pp +0@ @``0@pp0@ @0@```` @p p` @p`` @pp0@0@ @0@p`````4`w`^p8pzpIp0@` ```ppp p0@``` ` +pp pp```4`8` p,p;p&pp @0@0@ @0@`` @ppp` @p` @pppp`` @pp0@ @ @0@ @pp0@0@0@`` @pp0@0@ @ @ @``pp0@0@`` @0@0@0@ @0@ @0@ @0@ @0@0@`` @p0@0@`` @0@ @pp0@ @ÿœ o@ @ @0@````pp0@ @0@0@0@ @ @ @` @0@ @0@0@ @````````p p0@0@ @ @ @0@0@0@`` @ @pp` @p`` @pp0@0@``p0@````.`i`Vp3plpAp0@`` +` `ppp0@````pp0@```1`6`p(p8p$pp```pp0@ @0@` @pp0@`` @pp0@` @0@0@ @ @0@ @0@0@0@0@0@`` @0@0@0@````pp0@0@0@ @ @0@0@0@ @0@0@ +``pp0@ @0@ @0@0@ @ @0@0@ @ÿœ o@ @ @0@``` `p +p0@ @0@0@0@ @ @ @p @ @0@0@ @0@```````pp p0@0@ @ @0@ @ @0@0@`` @0@pp` @``` @pp0@0@``p0@``` ``5`.ppc¶c¸p–sXsq pZp` @pp0@0@` @ppp``0@ @ @0@```ppp0@0@` @`` @pp0@````pp @p```pp0@`` `pp0@pp0@``pp0@ @pp0@``` @```` pp"p0@0@ @p0@0@ @```pp p0@ @`` @0@ @pp0@0@0@ @ @p0@0@`` @pp0@ @ @`````9`Ÿa`Ÿp©qpžp'``pppp0@0@0@ @0@0@ÿœ q@``p0@0@ @``ppp` @p0@```` @ppp p @ @ @ @````ppp0@`` @````p0 +@```ppp``````=`øbÒbÎpprzreqFpJp +0@ @0@p0@p```pp0@ @0@ @````pp0@0@```pp0@ @0@````p0@``pppp````pppp0@0@` @``pppp0@````p````p +p'p0@0@ @pp0@ @0@`````ppp0@ @ @ @ @ @0@ @p` @pp @0@0@0@0@0@`` @p0@ @`` @`````P`àa~`Ïpçq…pÜp;ppp +p0@0@0@0@ @0@0@ÿœ q@``pp0@0@ @``ppp @0@````& @p*p(p p0@ @ @ @`` `$`"p p(pp0@0@`` @``` +`p p +p` @ppp```````taBa*p,qqpšp$p0@0@pp @p```pp0@ @0@````pp0@0@`` +`pp0@````pp0@` @ppp``` +`pppp0@0@``0@``pppp0@```p```` p +pp0@ @ @ @pp0@` @ppp0@ @ @ @ @0@0@`` @pp @0@0@0@0@0@`` @p0@ @`` @`````C`ÈaK`£pËqHp½p7p ppp0@0@0@0@ @0@0@ÿœ o@``pp0@ @ @ @pp0@ @`````&`!pp-p*p p0@ @ @0@ @```,`&pp.pp +0@0@`` @````pp0@ @0@``````` ` `W`Pp +pHpPp,p p @ @ @ @p0@ @0@0@p``` @pp0@0@0@``` +`pppp0@0@````pp0@p` @0@0@```0@pppp @``0@``0@ @0@ @pp`` +`pp pp @` @`ppp0@ @`` @pppp0@0@ @ @ @0@0@ @`` @pp0@0@ @p0@``0@ @ @ @ @```` +`$`a`£`Jplp™p\ppp0@0@0@ @0@0@ÿœ o@ @`0@ @ @ @pp0@````````pppp p0@ @ @0@ @`` ` `p p"pp +0@p` @p```pp0@ @0@p`````````pppp 0@ @ @0@ @ @p0@ @0@0@``` @ppp @0@0@````pppp0@p``0@ @p0@0@```0@`pp0@`` @pp @ @ @ @ @pp`` @p```pp @pppp0@ @`` @pppp0@0@ @ @ @0@0@ @`` @p0@0@ @p0@``0@ @ @ @ @````` ``.`p!p/pp p0@ @0@ @0@0@ÿœ q@ @ @0@0@0@0@`` @ @`` + @p p0@0@ @0@0@0@```` ppp +p0@ @0@0@0@0@ @0@``` @``` @pp +p0@`` @0@ @p`` @pp0@ @0@`````ppp0@ @0@ @` @0@p`` @ppp @````pp0@``0@ @`` @0@p0@```pp0@ @ @p0@``````pppppp0@0@`` @pp0@0@p` @pp0@0@0@ @ @ @0@ @0@ @0@ @0@0@` @ @0@ @p``` @``` `pp p p0@0@ @0@0@ @ÿœ s@0@ @ @0@0@ @0@0@` @`` @0@ @pp @0@0@0@````pppp @ @0@ @0@0@ @0@``` @`` @pp0@`` @0@pp`` @`p0@ @0@``` @ppp0@ @0@ @`` @0@`` +``p pp0@````pp0@`` @0@`` @ @p0@``ppp @p0@ @p0@`````` @p ppp0@0@` @`p0@0@`` @pp @0@0@0@ @ @ @ @0@ @0@` @ @0@ @p```` @pp p @pppp0@0@ @0@0@ @ÿœ `vp0@```pp0@0@ @ @ @p```ppp`ppp0@ @`` @p0@`0@0@ @0@ @p0@0@ @`` @0@ @pp0@ @0@0@`` @0@ @ @0@0@ @`` @ppp0@ @p @ @``p`` ` `@`p,p8pp +0@ @``pp0@`` @0@```````pp +p0@ @0@`ppp0@``pp0@`````%`.`;`(ppDpLp$0@0@0@ @0@`` @0@``pppp0@ @0@` @ @```pp0@ @ @```0@pp`` ````p"p"p0@pppp0@ @p0@ @ @ @0@ÿœ `vp0@```pp0@0@ @ @0@ ``` `pp` `pppp0@`` @p 0 +@`0@0@ @``` `pp +0@0@`` @0@`p0@ @ @0@0@` @0@ @ @0@0@0@`` @ppp0@ @p` @ @``````4`r`6pPphp.p0@ @p @````0@0@````` ` +`ppp0@ @ @`pp0@```ppp0@```` +``:`{`Ppphptp40 +@0@0@ @0@`` @p```pppp0@ @0@`` @ @` +@pp p0@ @ @```0@pp`` ``<`@pp@p@p0@p0@p0@ @p0@ @ @ @0@ÿœ`xp0@ @0@0@0@`` @p0@ @ @0@``` @`` ` p ppp0@````p pp`pp0@ @p``` +`` pp0@0@```0@ @p0@``` @0@``pp0@ @0@ @0@0@``` @p0@0@p```` @`` @``>`ˆ`Bp]pzp;pp``p0@``` +`ppppp0@```` `` ppp 0@```ppp0@p``` @pp0@``````2`w`\ppfpop20 @0@ @0@ @0@`` @p```p p +p0@ @0@``` @````ppp +0@`` @ @0@p0@0@``` ` `L`OppQpOp0@0@0@0@0@ @0@0@ @ @0@ÿœ`zp0@ @0@0@0@```pp0@ @ @0@0@`````` `*`$p p"pp0@````ppp`pp0@ @p````` pp0 @0@```p @ @pp`````` @p +p```pp p0@0@ @0@0@``` @p0@0@p``````` +pppp` `2`v`>pKpjp5p```pp +pp```pp pp0@```` ` @pp0@```pp +p0@```` @pp0@``````C`4pp:pAp0@0@ @ @ @``` `p +p +p0@ @ @````` ``pppp +0@`````pp pp0@0@`````=`;pp=pppp0@ @ @0@``` @pp0@0@ @0@ @0@ @ @0@`` @pp0@```pp0@0@0@ @ @0@ @ @````` +` +@p +``?`¼a``Šq,q>pxp @pp0@ @`` @pp0@` @ @`` @pp0@````p0@ @0@ @pp0@0@``0@``p0@ @pp````` @``ppp0@ @pp0@0@````p` @pp0@` +``p p p p @pp @`` @0@ @` @ @````p`pppp0@0@ @ @ @ @p0@````` ppp```ppp0@0@` @pp0@ @ @0@ÿœ`Ûpp,pp +pp0@ @ @0@```0@pp0@0@ @0@ @0@ @ @ @`` @pp0@``` +`p p0@0@0@ @` @pp0@ @ @`````$`pp,p`/`¨a.`zqqpd0@pp0@ @p` @p0@ @ @0@ @`` @p0@```pp0@ @ @pp @``0@``p0@` @ppp```````` ppp 0@ @pp @0@````p` @pp +0@```5`p$p(pp0@pp @`` @0@0@`` @0@p` @``pp pp @0@ @ @`` @0@pp @````` pp!p ` `` ppp +0@0@```pp0@ @ @0@ÿœ` ppp +pp0@0@``0@0@` @`p0@p0@ @ @0@ @0@ @ @p````` @ppp0@````pp0@0@ @p``` +`pp +p0@````0@```+`:`.ppJp.``P`™`>pˆp„p4p0@```0@``pp0@ @ @``pppp0@```p0@`` @p +ppp` @ @pp```` @p0@``pp````pppp @`````pp p0@0@ @0@p```````p pp`` `2`b`$pApJp-ppp0@ @ @ @0@`` @pp0@` @pp0@```p0@``````p +pp +pp````` ``p pp```pp +p0@ @0@0@0@ @ @0@p`ÿœ!`‚pp0@p0@0@``0@0@````ppp0@ @ @0@ @0@ @p````` @pppp0@````pp0@0@ @``` ``ppp +0@````0@``+`>`*ppLp2p``1`p&p(pp0@`````p`` +``pp0@ @0@``pppp0@```p0@` +` @pppp` @` @pp``` +` +` ppp +0@``pp````ppp0@`` @ppppp0@0@ @0@0@``````ppp```:`j`$pIpRp1ppp0@ @0@ @`` @0@p0@` @pp0@````ppp````-`I`%p/pNp'ppp @````pp +0@`pp0@0@ @0@0@0@ @ @0@p`ÿœ(`yp0@0@ @0@ @p0@0@ @```ppp0@ @p0@`` @0@``pp0@````` +` +`pp p0@`` @p p0@pp @````` +pp0 +@`` @pp0@````(`p p4p"p`` +`ppppp`` `pp0@` ``1`p3p(0@ @pp```pppp`` @pp`` +```ppp p @`` @pp`` +`` `pp0ppp @```` @ppp0@````ppp``pp0@ @` @ppp0@ @ @0@p`` +`$`G`p0p6p"p0@0@ @p0@ @p` @p0@pp0@` @0@``` `pp0@``)`p`­`]pnp¼pfp0@`` @pp0@0@0@ @ @0@0@0@ @` @0@`ÿœ#`wp0@0@ @0@ @p0@0@ @```ppp @ @p0@`` @0@``pp0@`````` +ppp0@`` @pp @p0@``` ` `pp 0@````ppp @``` ``ppp0@``` @````p p p ```,`=` +pCp<0@ @ppp``pppp`` @pp`````ppp p @p` @pp```)`8`.ppLp.pp @````0@pppp`````ppp``pp0@ @` @ppp0@ @0@0@`````` +pppp0@0@ @p0@ @` @pppp0@` @0@``` ` +pp0@` +`5`•`Þ`rpƒpñpŠp(p0@p` @0@0@0@ @ @0@0@0@ @` @0@`ÿœ t@pp @ @0@ @p0@0@ @ @ @0@0@ @`` @0@``pp0@`````pp0@`pp0@0@``pp0@```` @pp`````pp +p0@ @```ppp0@ @````````ppp```*`.`p;p20 @ @pp @ @pp`` @`````pppp0@p0@` @```*`:`3ppQp2p 0@ @ @pppp0@```` @p0@0@ @``` +`pppp0@p0@ @`` @pp0@0@pp @0@0@ @ @pppp @ @0@ @`````p0@` +`'`|`µ`Wphp½pup#p0@p0@ @0@``ppp @ @p0@ @0@`ÿœ t@pp @ @0@ @p0@0@ @ @ @0@0@ @`` @0@``pp0@````` pp0 +@`pp0@0@` `pp0@p @` @pp```` @pp0@ @p0@0@0@ @````````p p +p` +```p +p#p0@ @0@ @ @ @p0@` @```,`pp"pppppp0@`` @p```&`%p p5p"pp @0@ @pppp0@``` @pppp @ @```(`pppp +0@pp @ @0@ @0@ @pp0@0@0@` @`ppp0@ @0@ @``` @ @```B`c`4p;plp?pp0@pp0@ @0@```ppp @ @p0@ @ @0@`ÿœ s@0@ @pp @0@ @ @pp`` @0@p`````pp` @p0@``` @p0@ppp`` ` ppp0@ @`ppp`````` @p +ppp0@```p0@``````` `pppp`` +`` +p +ppp0@p`` @` @0@ppp`````(`\`4p'pFp7ppppp0@`` @ @` ``ppp0@ @p` @pppp0@`` @0@ @pp0@`` `6`o`6p0pXpCp0@pppp @ @`p0@` @pp0@0@0@p` @ @p0@`p0@```` @````!`pp)p0@ @p0@0@ @0@```ppp0@ @p0@ @`ppp @ÿœ s@0@ @0@ @pp @0@ @ @pp`` @0@p``````ppp @p0@``` @``0@`pp`` @pp0@ @ppp0@```````pppp0@` @p0@````````pp``````pppp0@p````` @p ppp``` ```,`j`@p/pVpA0@pp p0@` @ @```ppp0@ @`` @pppp0@`` @0@ @`pp0@```V`µ`ZpRpŒpop$p`pppp @ @`p0@```ppp @0@0@p` @ @p0@ @0@0@``` @`````pp +p0@ @pp @ @0@0@```ppp @p @ @ @pppp @ÿœ`up @ @ @0@0@`` @p0@` @p0@```` +`pp pp0@0@```pp```pp` @p`` @p0@p` @pp0@ @```` +``ppppp````pp p`````` @0@```pp p +p p +pp0@p`````$`*`p7p,pp```` p ``B`0p p>p1p `ppp``pp0@ @ @pp0@`````pp p0@0@````ppp` @ppp```V`²`\pLpŒpop&``ppp p` @0@0@p````pp0@` @ppp0@ @ @0@ @`ppp0@0@`````p0@`` @p0@` @p0@ @ @0@pp0@ @` @pp0@ÿœ`wp0@ @ @0@ @ @pp`` @p0@` @0@p0@``` `` +ppp pp @0@```pp`` `ppp @```ppp0@p` @p0@ @````` `p p pp`````ppp``````0@p````pppp ppp0@p````A`€`º`pÕp”p/p +``2`[`pJp6`` p ppp`pp +p``pp0@ @ @pp0@``` +` ` pp0 +@pp````p p p` @ppp```2`l`8p(pPpEp``ppp p` @p0@0@p````ppp` @ @ppp0@ @ @ @ @ppp0@0@````pp0@` @0@`` @p0@0@ @pp0@ @` @pp0@ @ÿœ%`0 @ @0@0@ @````pp`` @p0@` @0@0@```` `` +ppp pp`0@` @p`` +`pp` @` @ppp0@0@ @0@ @ @`` @ppp0@`````pp0@`` @````p````ppppppp0@p```2`‚aav`q§q(pOp`'`z`Ç`&p·p–p7p ppp0@p0@` `pp0@`` @``ppp0@`` ` @pp ``p0@` +``ppp` @p p +p````%`p ppp +p @pp +p``pppp @0@````p0@ @0@0@pp @ @```ppp0@p0@0@` @0@0@ @ @0@ @ @```pppp0@ @ @p0@ @ @0@0@p```ÿœ(`…p0@ @0@0@ @````pp`` + @pp +p @0@0@`````` `pp +pppp0@`` @pp``pp`` @p``pp +p0@ @0@0@ @``` +``ppp +pp``` ``ppp0@` @p`` @p`` @p 0@pp0@p```6`¤aBa¢` +qñqhp]``K`Æa3`&q-pöpQppppppp0@``pp0@`` @``ppppp``` @pp``p 0 +@` +``p p `` @ppp```` ``pppp` @p +p` +`pppp0@0@``` @pp 0@0@0@ @pp0@ @```pppppp0@0@` @0@0@ @ @0@ @ @```pppp0@ @ @p0@ @ @0@0@````ÿœ$`ƒp0@ @pp0@ @``` `pp` ` @pp0@pp0@0@`` @` @`ppp0@`` @p @`` @0@ @p +p0@ @0@0@```` +``&`p +p0p"p p````.`1ppCp,p +p` @p```p0@ @0@``0@ @p`` `$`t`èaA`qkqpG` `^`ÞaF`qZqpLp0@pppp0@``pp @` @``pp0@p`````p p @p +pp```0@```!pp(p 0 @````` @``` @p```pppp0@` @`` p pp pp0@ @ @0@ @`` @ppppppp @`` @pp0@ @ @0@ @``` @` @pppp0@ @ @ @0@` @pp @ @``ÿœ'`}p +0@ @`p0@ @p`````` +` @pp0@pp0@0@`` @` @``ppp0@`` @p @`` @0@ @pp0@ @ @0@`````-`>`-ppPp2pp`` `)`F`MppgpHpp`` @p`` +`pp0@ @```` @p +p```0`d`…`ppxp!``L`¢`ä`qpºp4p +0@pppp0@` @p @``0@``p0@p`````pp @pp0@p`````"`1pp:p,0 @`````````` @```;`p2p.pp0@0@ @` `` ppppp0@ @ @0@```````p pp ppp0@`` @pp0@ @ @0@ @``` @` @pppp0@ @ @ @ @` @pp0@0@``ÿœ`vp0@` @pp0@ @`````` @p +p0@pp0@ @ @0@` @p0@p @p```pp @ @ @0@0@ @0@`````-`:`,ppFp,pp p``(`@`MppcpB0@`` @pp``pp0@ @p`` ``pppp````#`p'p p ` `,`T`u`pˆpfp0@0@pp0@ @``0@`0@0@p` @````pppp```p0@pp````( 8@pCp0p p @`````` @ @` `*`I`p8p6pp pp0@````p +pp +ppp @0@ @```` ````pppp0@0@0@ @0@0@ @ @ @ @ @` @pp0@ @0@`` @``ppp0@0@ @ÿœ`t0@`` @pp0@ @p``` @ @pp @p0@ @ @0@``p0@` @pp```pp0@0@ @ @0@ @0@ @ @p``` +``"`ppp pp0@``$`-pp7p&0 @`` @pp```p0@ @p````pp"p0@```pppp```$ +@p8p*p0@ @pp0@ @`` @p0@0@p` @p`` +`p pp0@``````p0@` `$ 4@p;p(p +0@p```` @0@` `&`=`pp*p)p0@p0@````ppp0@` @0@````` @`` ppp p0@0@ @0@0@ @ @ @ @p` @pp0@ @0@`` @p``ppp0@0@ @ÿœ r@0@`` @pp0@0@ @ @ @ @0@0@0@ @0@ @0@` @`` `pp0@``pp0@0@ @0@ @`` @ @````` ` ```pppp````pp0@`` @p p````ppp` @p`` ``ppp0@```p0@`` ` ` +ppp0 +@`` @pp0@````p0@p0@```p`` `p p +p0@`````pp!p```.`p+pppppp````p0@p````;`f``pŒ0„@p?p0@``pppppp`` @p0@`````pp0 @`p p0@0@0@p0@p` @0@ @ @0@0@0@ @0@0@`````ppp0@p0@p`ÿœ r@0@` @p0@ @ @ @ @ @0@0@0@ @0@ @0@0@```` ``pp0@``pp0@0@ @ @`` @0@``````` +`` +ppp0@`` `pp0@`` @pp``` `pp`` @p +p```ppp0@```p0@```pppp0@`` @pp0@0@``` @p0@pp`````` `ppp p0@```/`L`GppipBp``,`p!pppp pp` @p0@``` `"`sajbV l@qÜqàpÍp:p +```pp +ppp0@` @p0@```(`D`p-p8p#p pp0@0@p0@` @p0@ @ @0@0@0@ @ @0@0@0@`````ppp0 @p0@ @``ÿœ" r@0@ @0@0@`` @0@```pp0@0@0@```p0@ @0@``` ``,`p)p,p0@0@0@p0@ @`` @` @ @`````` +``p pp +pp`` `pp0@ @ @pp``` `p```"`pp/p"p` @p p +pp`````p0@` `ppppp`` +`pppp0@p @`` @ @pp0@````````ppp +p```K`Š`„p.pÂpnp``"` +pp pp +pp`` @p0@````@`æ#4@e`‚ts¾q©prp` +``p pp pp p +p```pp```6`ªa`:pÊpÐptp$p p0@` @pppp @0@`` @p0@ @ @0@0@0@ @ @0@p`````/`)p p4p'p p0@0@ @``ÿœ% r@0@ @0@0@``0@0@``` `pp0@0@0@```p0@ @0@````$`>`p9p8p0@0@ @0@pp0@ @`` @` @ @````` +``pp +ppp`` @p0@ @0@p````p``?`b`_p +ppfpp @pppp````` +ppp``ppppp`` +`pppp0@``````0@ @pp0@```` `$`:`O`p/pHp4p```O`Ž`’p:pÔpvp```p0@p +p p`` @p0@``` T@a,df3`öuXt²r!p’p`` `pppp +ppp p```````paŽbx`†qòqèpöpHpppp```pppp0@0@`` @p0@ @ @0@0@0@ @ @0@``````3`3p p>p/pp0@0@ @``ÿœ `u0@0@ @0@ @ @``` +`pp +pp0@ @ @0@ @ @0@``` +` `5` p/p.pp @ @pp0@0@0@`` @ @ @ @ @ @```` +```p +pp +p````pp p``pp0@``` `p``]`¤`Âppýp°p/p` @ppp`` `` `pp)p`````pp p``ppppp`` ` +`ppp0@``ppp`````&`f`ª`É`4ppÂpp.```%`X`Zp$pŠpR0@`` @ppp```pp +p0@`` @`B`ìbþd²ats¶q¥ptp```pppppppp @```(`’bc?`¾r—rŠq=p\ppp``` @ppp0@0@ @0@0@ @0@0@ @0@ @ @ @`````!pp+p p p0@ @``ÿœ!`{p0@0@ @0@ @ @``p```pp +pp0@ @ @0@ @ @0@``````ppp +p @ @pp0@0@0@``0@ @ @ @ @ @````` +``pp p +p````pp p` `ppp`` `` pp ``[`¬`Êp +qp¸p/p````pp`` ` ` ` pp-p``` ` +`pppp```p0@` ` @ppp0@``ppp`````^`øafam`qdq^púpRpp +p ``&pp8p"0 +@`0@`` ` +`ppp0@ @`` `"`rabb6`bqÍqÂpÇp

pp +ppppp p``````pp0@``ppp 0@`p```` `` `pppp0@```pppp````pp p````8`R` pFpDp(pppp```ppppp @ @0@0@ @ @p` @p0@ @p` @0@ @pp @0@ @p`ÿœ$`|0@` @pp0@0@```pppp0@0@0@ @0@`````` @pppp0@ @0@``` +`pp0@pp0@ @0@0@ @ @`````` +@p p +0@`````` @p +p``` `````` p +p` @pp pp0@`` ``ppp` @p ppp0@```pppp` @ @`` +```ppp @````pp` `,`›a¬bw`qqq˜q]pºpSp0@```p pp````` `pppp```0@0@0@ @ @````pp0@p @ @0@0@```pp p```` `pp(pp pp0@ @pp0@p` @pp0@0@ @ @0@ @0@p0@ @ @0@0@ @ @0@0@p``p0@ÿœ"`xp`` @p p0@0@````ppp0@0@0@ @0@`````` @pppp0@ @0@ @``` +`pp0@p0@ @pp0@0@ @ @`````` @p p0@`````` @pp``` ``0@`` ``6`R`%p,p`p8pp + @p``` ppp p @pppppp```ppp`` @``` ``pp 0@```` +pp``8`¿aØbopr_q´pËpRpp +`````ppp` @```ppp 0@ @p0@0@ @0@`` @pp0@`` @0@0@0@```pp +p```A`0`p2p@ppp0@p @pp0@`` @pp0@0@ @ @ @0@0@p0@ @ @0@ @ @0@0@```p0@ÿœ`s`` `` ppp0@0@`` @pp0@ @0@ @ @ @ @0@0@ @p0@``` `p0@0@```pp0@`pp0@ @` @ @ @p0@```` @0@```` @pp` `B`¬`Ú`pxqp p-p @pp`` pppp````ppp0@```ppp` @0@```0@pp`````5` p pp`(`œafaÇp*qûqNpmp"p 0@``` `p +p0@0@```pp 0@0@ @p0@` @pp0@`` @p0@0@p @p0@` `,`Z D@pNp[p$pp`0@0@0@0@````ppp0@ @`` @0@0@0@ @pp0@ @ @0@`````p0@ÿœ`s`` ``ppp0@0@ @`` @pp0@ @ @0@ @ @ @0@0@ @0@ @pp0@````p0@0@```ppp```p0@ @` @0@ @p` @0@` @` @0@`` +``8`JppRp0` `baaN`Ùp´q±püp9` @p#p``ppp p```` +pp p pp```ppp`` `pppp @pp`````5` p"p p ``H`²`ãppûpžp7pp0@````p0@ppp``p0@p0@` @pp0@` @pp0@` +@pp0@ @p0@0@` `(`L`<`pFpQp pp`0@0@ @0@0@````ppp0@ @0@0@`` @pp @0@ @pp0@ @ @0@`````p0@ÿœ!`r``` @pp0@0@ @``pp0@ @`` @pp`` @0@pp0@ @````pppp`` @pp0@ @```p0@```ppp` @0@0@`` @ @0@ @p @ @`` `*`T`jp p~pJ``Z`ña6`Åp¤qpèp/` @p"pp``p0@```#`ppppp````pp` +``p +ppp +p0@p0@p````"` +ppp ```2`L`pBp2p ppp0@```` `ppp ppp @ppp0@``` @pp0@```pp0@`` `0@pppp0@ @```&``p&p/p0@`0@````pp0@0@ @pp0@` @0@0@ @ @0@` @0@0@0@0@ @`p0@ @ @0@ÿœ `r``` @p 0@0@ @``pp0@ @`` @pp`` @0@pp0@````pppp`` @pp0@ @` @p0@```ppp`` @0@0@`` @0@0@ @p```0@`` `&`L`^pptpFp`2`}`¦`upXpÝp|p` @pp0@` @```/`pp$ppp``` @p``3`pp&pp +pppppp``` ```pp +pp```` +pppp +pp0@`````pp +pp0@p0@`pp0@`` +` @pp pp```pp0@```` ``p +p!pp +0@ @``` ` +`pp0@ @````pp0@0@pp0@` @0@0@ @0@0@`` @0@0@0@0@ @`p0@ @ @0@ @ÿœ`s`p` @0@0@ @p @ @0@0@ @`` @0@ @0@0@``` +`ppp +0@``pp``` @0@ppp``` @0@`` @ppp````ppp0@`` `pp````*`:ppAp,p``,`6`%ppBp(0@```pp 0@````"`ppppp``` @p``<`pp,p p``pppp` ``ppppppp``` ``pp p`ppp````` +`ppp0@pp0@ @pp```"``pp'p0@ @`pp``````,`n`qp0pŠpLp0 +@ @ @ @`pppp0@````` @pppp0@pp0@ @ @0@ @pp0@ @ @ @0@0@0@ @p0@ +ÿœ`w`pp0@0@ @`` @0@0@0@ @```p0@ @0@0@````ppp +0@``pp``` @pp ppp``` @0@`` @ppp````ppp0@0@`` `pp`````pp0@`` +``pp +p0@`` ``pp0@```` `pp +0@````` ````6`pp,p"p``pppp``$`(pp9ppppp`````pp``pp0 +@ @` @p0@p``pp0@ @ppp` +```ppp 0@ @pp``````.`zaaphqjpÈpHp0@ @ @0@``ppppp``````ppppp0@pp0@ @ @0@ @pp0@ @ @ @0@0@0@ @p0@ @ÿœ`y`ppp0@ @p``````ppp0@ @````pp0@0@0@ @0@` @`` `pp0 @```pp```,`0pp=p,p p0@`` @````pp0@`` @p ppp0@```0@`` @ @pp0@`` @`p0@```#`pp0 @```pp` @ @ @`` +````N``pYplp>p` @p0@``.`3ppIp(p +pppp`` ` `````ppp0@```0@pp`` @pp0@```pp`` ` +`ppp @pp``` +``H`ÇaÄaÈp¶r`l`zpp¥pfpp0@ @ @```pp0@ @ @pp0@` @`` @0@``pppp @p``````` +``p p0 @` +`ppp`` `pp @````H`åbþd¤`Lts\qepd0@````` +pp'p``ppp` +`` @pppppp 0@``pp 0@` @0@```ppp +0@ @0@``` `` p p0 +@`` `.`l`ô`úpZq>p®p=pp` @pppp```pppp`````pp pp```pp0@```0@pp0@`` @ppp```pp0@ @0@ @0@0@ @`` @ÿœ!`s`p0@ @``` @ @`pp p0@ @``` `p +ppp0@ @0@ @ @`0@0@0@```````4`P`RppypNpp0@ @ @` @`p0@ @ @p0@` @```p0@`` @pp0@p`` +``p````pp0@` +`ppp`` ``` @````\aAdf~auüuqïp€p0@````ppppp` `p p p``'`F`JppLp&pppp 0 +@0@``pp0@` @0@ @```ppp +pp0@0@`````pp#p0@```.`X`bp"pvpFpp +p` @ppppp``ppp````` pp p pp`` `pp0@``` @pp0@` @ppp```pp0@ @0@ @0@0@ @`` @ÿœ& s@0@0@ @``` +`p0@0@p0@0@ @`` `p p0@ @ @ @`````ppppp @0@````````&`%p +pacŽe¸`šutFq¨plppp```&`pp'pp``pp0 @``B`ž`¯p4pÏpfpp0 +@ppp +p` @pp @```````` +pp +ppp @ppp```` p pp0@`````p +p"pp 0@` @pppp0@ @ @p`` ``ppppp```pp0@ @`` @pp0@0@ @pp @ @ @0@0@ @ @0@0@`` @ÿœ% s@0@0@ @````p0@0@p0@ @```pp0@ @ @0@``` `ppp0@p0@0@````p`` `` pppp0@ @0@ @` @0@```pp0@pp0@````` `p p 0@``` ``pppp```pp0@```p0@` ``&`ppp``J`n`¬`tpp¤pÁp*`„aâbø`&rr&pÜp8p pp ```"`pp#p p`` ppp ```d`ÞapHq5p¢p @pppp```pp0@p```p` @`ppppp @pp0@`` +`ppp @`````pp +pp0@` @ppp0@ @ @``` ``p pppp```pp0@ @`` @pp0@0@ @pp @ @ @0@0@ @ @0@0@`` @ÿœ t@pp0@`` @p0@0@ @ @ @0@`` @0@``` pp0@pppp0@0@ @ @`` @pp0@```p0@```` @0@` `ppppppp`` +` +```pp p 0@```` @ppp```pp0@``ppp```0`S`p0p4p` `ma(b5aˆp#qärGpÔ``Œ`Ø`pÄp¬pBpp ppp` `` ppp```pp p +p``[`Î`ñp:q0œ@p` +`pppp````p pp +p` @0@ppp @pp` @p0@```p0@```` @ppppp0@` @p0@ @0@`````ppp +0@ @pp0@ @ @0@0@p0@ @ @0@0@0@0@ @ @p``pÿœ t@pp0@`` @p0@ @ @0@ @0@ @`` @0@`` +`0@``pp +pp @0@ @ @ @0@0@```p0@``` +` + @p +p``pppp```````p +pp +p +p0@```` @pp``` `pp0@``pp +p +```<`k` pBpHp ``‡bdcp9stt q”p` `pp0p$ppp ppp`` @0@`` @p p +p``1`z`‰0*@p™pTp``p +pp +```&`*`p$p>pp` @0@pp0@``` @ppp`p0@ @````0@ppp0@p @` @p0@ @0@`````ppp0@ @pp @ @0@ @0@0@ @p0@0@ @ @0@0@0@0@ @ @ @0@`pÿœ r@0@ @``p @0@0@0@``0@0@ @ @ @0@````````p p 0@ @0@`` @0@p```pp```p0@``````pp0@ppp`` ``` +` @pp!ppppp``0@`pp``` + @ppp`` `pp 0 @``4`X`p8p:p``„b2ddc``s°t}q¤pppTp2pp`pp +p +pppp @`````-`p$p$p`` &@`0pp5p"p``ppp```>`H`%pDpjp*p` @0@ @pp @``` @p +p0@p0@``0@ @pppp0@ @ @ @0@0@` @p @0@` @p0@ @p0@ @ @0@``p0@pp @p` @p0@ @` @0@0@ÿœ r@0@ @``` @pp0@0@0@p` @0@ @ @ @` @````p p 0@ @0@`````pppp```pp````0@`````pp ppppp```````pppppppp``0@pp`` ` + +@ppp`` `pp0@` ` `4` +p"pp``TajbÌb(`0rPrñpð`"p pnp>pppppp``pp0@````N`w`pfpLpp0@p @p p +p +p @p`` `"`<`D`pDpjp*0@ @p` +@pp0@```` `ppp +pp0@``0@0@ @pppp0@ @ @ @0@0@` @p` @ @p0@p0@ @ @0@``p0@`0@p` @p0@ @` @0@0@ÿœ p@ @ @0@````````pppp0@0@ @ @ @0@```ppp`` `p0@`p0@``````pp&p0@ @pp``` +``p0@` ```ppp 0@````` +pp``ppp`ppp`` @ppp`` @p0@```0@````` `ppp``'`Ža `ê`pìqpJ`*ppTp2ppppp`` ` +pp pp`` +`%`€`¼`p›p^0@pp"p&pppppp````` ``)`"`p.pBppp0@`` `pp0@``` @ppp +pppp` @0@` @pp0@0@ @p``` @pp0@` @0@``pp0@p0@pp0@ @0@0@ @ @ppp0@`` @pp0@ @```p0@`ÿœ p@ @ @ @``` ````` pppp0@0@ @ @ @0@```ppp```0@``p0@````(`>`:pp\p40@ @p`` ``` +p +ppp``` `ppp` @0@` ``p0 @`p0@`ppp`` @`ppp`` @0@`````` ```` `2pp5p&p```.`N`>`p@pGp +`pp"p0 +@pp pp` +@p pp`` +`#`x`ª` +0‡@pF`` +p0p:p&pp p0@````` ```pppp +pp0@`` +`pp0@```)`pp&p#p +ppp 0@` @0@` @ppp0@0@``` @pp0@` @p p```pp0@ @p0@pp @0@0@ @ @ppp0@`` @pp0@ @p``p @`ÿœ( p@ @``````` `p```p +p pp +pp0@0@`` @ @0@0@`` @0@0@0@````ppp````;`V`Lp&p}pBppppp```0`4`p,p,pp +0@` @`` ``pppp` @``ppp`pp0@```` pp p +p` @p`````` `````<`PppmpHp`` ```pp p `````pppppp ```pp0@```@`_ @pJp`+`p$p.pppp +0@``` ````pp&ppppp @` @`p0@```3`p p(p'pppp +p`` @p p` @pp p0@0@ @0@````ppp`` @p +p +p` @pp0@``pp0@ @p0@0@ @` @pp0@0@`` @pp0@0@ @0@`ÿœ, p@ @````````p` +`%` pp(p!ppp0@0@`` @0@ @0@` @0@0@0@````ppp````3`J`p```K`L`p8pZp*ppp p```(`:`-ppMp&p` @``ppp` +``-`<`C`2`)p*ppJp ``pp p0@```.`„`ªppŒp`p/p"ppp +p``` ` +` @pppp +p`````` p p6pp```0`pp3p`pp +ppp0 @` + @ppp @p0@````ppppp0@```1` +p$p"pppp @pp0@ @0@``` `ppp pp @pp0@ @0@pp @pp0@`````ppp0@0@p0@0@ @0@ @0@ @0@0@ @ÿœ% p@`````0@0@```` +pp p p0@0@`` @0@ @0@``ppp0@``` ``ppp`` +```p ppppp0@`` ``4`5ppFp(0@ppp```>`Âaxa›pNrqZpTp ``*`e`h` pLp‚p:ppp p```F`b`Kpp +` @```ppp```!`,`/``p"pCp"`` +`pp p0@```.`ˆ`¸pp¦plp/pp p +ppp``` @pp p ppp` +````` pp2pp```0`pp=pp` @pp +0 @` @pp +p @p0@`` ` ` `` pp!p 0@``"`5` +p p"pp +p +p @pp0@ @```#`pp pp +p @pp @ @ @pp0@p0@`````p +pp0@ @0@pp0@ @ @0@0@0@ @0@0@ @ÿœ- q@ @ @ @0@0@ @` @ppp0@ @0@ @0@0@ @```ppp0@`````` +ppp ```(`&p +p3p$p +p0@```` ``ppp +``pp0@` `4`¢aDaspLqÑq$pBp`` `M`R`p>pmp0pppp```H`f`OpBppB0 @`````pp pp`````pppp```ppp0@````Z`}pprpL0@`pppp` @``ppppp p`` ``` +`pppp````pp.pp` @ppp ```pppp``ppp```` ``ppppp```#` p ppppp @ppp`` @```@`c`>p!pfpTpp @p0@```0@pp0@0@ @`````ppp +p` @0@ @p0@ @ @ @0@0@0@ @ @0@0@ÿœ$ q@ @ @ @ @0@ @p0@ @0@ @0@0@ @```p0@0@`````` +ppp ``$`D`@pp[p8p0@` @````p`` +`pp0@```Z`¸`·p pÝpœp(pp``!`.` p*p5p`ppp```0`F`)p.pWp*0 @``````pppp`````pp ````pp pp`````"`1`p&p p` `pp p0 @```ppp pp @```ppp` +``pp`p +ppp` @ppp```ppp```ppp`````` ppp +pp``` ` ` `p ppp0@pp````````x`Å`†pKpÆp¤p2p + @p`````ppp0@0@ @`````ppp +p` @0@ @p0@ @`` @pp0@0@ @ @0@0@ÿœ# s@0@`` @p0@p` @ @0@ @0@ @0@ @ @pp0@ @``````ppp``#`F`Ippcp`T`pNpJpppp +p`` ppp0@ +@ppp`` +`ppp`` `p`pppp``ppp```p ppp0@0@````````‚`é`¬phpêp³pp¢p}p(p``ppp``` pppp +0@0@ @```ppp```ppp0@0@ @````ppp0@ @ @0@0@ÿœ( r@``p0@p0@ @`` @``pp0@ @`` @p`ppp0@ @``` `0`ƒ`¢`QphpÇpdp +``pp(p````pppp0@````&`pp)ppp0@````` `` ppppp``` +`$`>`p;p2pp`` @0@0@ @``` ` + @pp0@``` ` @ppppp`` +``` +`` +pp +p pp @` p0 @ @p p +p````,`p``pp=p$p``Q`èa.`Dpâqp‡p ` pp``(pp)ppp pppp0@```p```=`:`p:pCp"0 @```pp``ppp0 +@ @pp0@````` `&`E`,ppHp>p``1`p pp```pppp0@ @0@0@ @`p0@```ppp0@ @0@```` ` ppp +pp @ @0@0@ÿœ$ r@``ppp0@ @`` @``pp0@ @`` @```pp0@ @`````aa`¥pÄq‰p¼p`` ppp`````ppp0@```9`J`(p0pWp*p p0@ @ @`` +`pppp`````,`>`pCp2ppp` @ @ @0@`` +```ppp0@``` ` @ppp pp `` `` `"`0`p#p"p0@``pp `` ` pp +p p```$````+ppGp,p``G`Ôa`PpÞqpƒp`pp``&pp!pppp p0@0@``````B``Š`pŠp¥pFpp` @0@``ppp0@ @pp0@````` +`` pp0@``j`;p pCp``(pp?p)pp0@ @0@0@0@`p0@```ppp0@ @0@`````pp +pp0@ @0@0@ÿœ! r@0@`` @pp0@ @ @ @ @p0@ @p`````pp0@0@p`````pa;a0`“pèq«pÊp p`ppp`````pp +pp`` +``@`X`-p:pgp2p p0@ @ @`` @``````2`7ppBp0pppp````` @p````.`,pp=p"``p``` ` `ppp p0@` ` +``D`NppQp>0@p``pp p ````p ppp``````#pp4p````b`˜`8pepˆpUp`p p` +`ppp +p p p p0@ @````````Ø`Ü`!pÞpÿpbpp` @pp`` ppp p```pp0@``` +`pp```pp``&`Œ`Lp,pZp'`"`1ppTp9pp0@ @pppp @pp @ @0@` @`` @0@pp0@`p0@ @ÿœ+ r@0@`` @pp0@ @ @ @0@`p0@ @p`p````p0@0@p`````L`Ó`à`Sp qpŠppppp0@````ppp`````*`8`p&pEp"p +p @0@0@0@ @ @``````2`7p +pDp,p ppp````` @p```#`6`8ppMp*`````pp```p p0@```)`X`Xp0o@pJ0@` +`p +p p``` `p p$p0 @` ` ` +``ppp`````,`pp,p-pppp` @p`pppp +0@ @``````X`Â`Ô`!pÖpípZp`` +` +pp +p p`pppp```pp0@`` +` +`ppp```pp` `[`7ppAp ``/ppGp-pp0@ @ @pppp @pp0@ @ @0@ @`` @pp0@ @`p0@pp0@ @ÿœ q@0@`` @ @pp0@ @0@0@ @ @ @pp```pp` @``` +``N`d`$pp ppp0@ @``0@p`````pp``L`¡`‚p(p¦p†p&` ` p pp``` 3@`D`#p,pPp$pppppp``` ``` @p pp p` +````p0@pppppppppp0@``4`Z`*p2pTp>p0 @```*`6`p$p9p```p$p&p```` +pppp` @pp` @pp0@`` @pp`` ``p +pp` +` +ppp`` `p p p 0@ @p`pppp0@` @0@``` ``(`p&p!p0@`0@0@ @pp0@0@ÿœ" q@0@ @ @0@ @0@ @p````ppp``p0@```pp0@``````` ``'pp0p"p +p0@`` @0@```p0@ @0@`` ``pp0@0@p`` ``%` +p"p`+`V`bppupZ0@pppp``0@0@```````-`¨a`apmq\q pXp`pp0 @` @`-`.`pp*0@p +ppp0@```` +``%`(` +p&p/p`` +` +``pppp +0@pp````p``*`˜`ô`dp¡pàp…p*0 +@` `"`Q`l`.pTpzp6p`pp0 @````pppp```p0@`ppp0@`` @pp``` `p +p +p``ppp````pp 0@ @0@pp0@ @ @ @0@0@````?`Z` +pWpJp0@0@```p0@ @pp0@ÿœ# q@0@ @ @0@ @0@ @p````ppp``pp````pp0@````````$`1pp:p&p p @` @0@```p0@ @ @````p p 0@ @p```0`O`pJp2`#`f`ˆ`ppzp&p`ppp``p0@0@```` ``9`Ða¸a6p™qœq?plp `pp +0@``&`=`&`pppppppppp +pp```$`G`L`p>pep*`` +` +``pp'p0@ @pp````p` +`B`èaz`ˆq qPp±p2p p` `&`c`€`0php’pBpppp``` `pppppp```ppp`ppp @`` + @p pp``pppp``ppp``` +`ppp0@ @pppp @ @0@ @0@0@```$`W`„`p|pjp)p + @0@` @0@` @p p +0@ÿœ s@0@0@ @ @0@ @0@ @`` @pp0@0@`` @pp0@ @ @``` ``$pp)p0 +@ @ @ @0@` @0@```` @ppp` @`` `&`J`k`phpL``z`ß`Jp·p¼pMp`pp p0@ppp @`````,`œaC`êphq,pëpPp @0 +@p p``D`a`.``pp,```hp2p p|pCpp``p0@``pp&p p```pp`` `p p``F`R`pCp8p+pp +p0@` ``(`"pp&p$p ``2`Øbšcƒ`xrörôq`pTp ``pp pp0@` @0@ppp````ppp0@ @0@0@ @0@ @`````ppppp`` @pp0@` @pp0@ÿœ%`q` @0@ @0@`` @0@ @pp0@ @0@0@0@0@ @0@`` @`p0@0@ @ @ @0@`````ppp0@` @0@pp`` @ @` @ @```<`n`8pCpXp'``Y`$pCpJp!p``` ``ppp +```9`6pp@pAp0@`` +`` 0@p p p``` +`````` +pp p` `:`Œ`„`p–p¤p:` `J`r`pcpTp!pppp````pp`````êa$`p‚p6aTaŒpŽqÿqlp¹pXppp0@`(`H` p+pHp0pp`` +````.`p+p``X`ap +pXpDp)pp 0@````"``pp"p``.`´b&c`fr“r~q'pD0 +@ppppp0@ @0@0@p```pp0@0@ @`0@p```` @ppppp0@ @0@0@``0@0@0@ÿœ"`s` @ @pp`` @ @0@ @pp @ @0@ @0@0@0@````pp0@0@ @ @ @ @````(`pp0p0@` @p0@p0@` @ @`` @0@````<`l`8p=p`p5p``ppp`` ``` `ppp +```;`6pp`ŽbÌbÐpÎsCrXq]pÀpIppp``P`Š`0p]pxpHpp`````F`b`p_pF``T`YppXp@p%pp +p```````ppp```daaŽ`.q[qNpp(0@pp0@p0@ @0@``pppp @ @``0@p```ppppp0@0@ @0@0@``0@0@0@ÿœ`t` @p```ppp @` @pp0@0@ @pp @ @0@` @0@`` +``pppp0@0@` @````<`b`Ip&p{pHp`` `ppppp0@ @ @```ppp``` `"`C`"p#p:p&p +``pp`` ``*`!pp$ppp`` +`$`$`ppp +ppp @``ppp```*`4`p` `2`p1p(0@` ````pp`"`aXaÁ`0q£q–p·p0ppp```6`R`pppp`` `` ` +`pp0@p ```ppp p``:`H`p9p`pp'pp +`` ` ``,`%pp`&`‘aDa¥`0qqŠpÇp8``p p``J`v`pXpXp` `C`2``"`îbRbŽ`°pÉqªrhq¶p¥p,pp``R`{`0pRpppHpp 0@```J`_pp]pLp` @ppp p0@``````& 1@p-p&p +` ``"`*pp5p6p0 +@ @pp0@ @``p0@``pp0@0@ @ @ @ @``` +`pppp0@ @0@0@ @ÿœ% r@ @````pp +0@`` ``pp0@ @0@ @pp0@ @``` ` `pp p```,`pp"pp +pp0@ @0@````S`Ž`mp>p¨pdpp` `pp +pp0@```` @p```0@```p`` @p 0@`pp```U`Š`hp>p£p\ppp +p``` ```0@pp ` ` @ppp ` `6 A@p<0 @ppp``` ```< D@p#``V`¢`Ù`2p¶pÞpœp<`` +pp``>`e`pEpHp`"`?`pp`O`üaca,`“pàrq`p‰p*pp ` `(`:`p$p0p$pp 0@```$`.pp6p"0 @` +``pp +0@``````3`~`‹ppšpjp$p`` +` pppp p0@ @p0@0@ @0@p```pppp0@ @ @```pppp0@ @0@ @0@0@0@0@ @ÿœ% r@ @````pp0@````p p0@ @0@ @pp0@ @``` ``ppp`` ``p ppppp0@ @0@````-`N`=ppXp8pp``pppp0@```` + @p p```` @ppp```ppp`pp```E`v`Zp6ppPpppp```` @`ppp` `!`` +ppp`` %@p"pp0@``````8`D`p ```"`7`.ppRpzp<``p0@`.`I` pp$p +``-`pp&p`<``è`³pxq7pÐpQppp`` ``pp pp +p0@`` `` ppp0@```pp +pp```` `0`¥a–a­pdqêqJpnpp @pp pppp0@0@0@ @p```pppp @0@ @ @0@p```pppp0@ @0@ @0@0@0@0@ @ÿœ( s@0@ @``0@ @```pp0@ @`` @pp @0@``` ` +`pp +pp`` +`pppppp0@ @0@ @`````ppppp +``pppp @````#pp+pp`` ``pppp`` ` +`p +0@``` ``2`)ppHp&p pp` @0@`` `pp```!`$`pp#pp ```ppp`` pp p``` `"`6`.`)`&ppp``p pUp.pppp` +`8`K`$p``p ppppp"``e``Np`p´pnp-pp p @ @`ppp0@``` ``pppp``` +`pp pp`````hascˆc„pðt +rÀpúp>p ppppppppp0@0@ @ @p```0@`ppp0@ @p0@``pp0@ @0@0@ @ @ @0@0@0@0@0@ @ÿœ$ s@ @p`0@ @p @`pp0@`` @pp0@0@``````pp0@ @pp0@ @0@ @`````` @p0@`pppp @`` `$`J`Kppcp4p```$`pp$ppp`` +`p0@``` @`` p +pp +0@`` @p`` @` ``'`$`pp'pp p` ````` +`pp#pp````$`6`G`2pp2p)p` pp-pppp ``$`H`M`p` `4p p\p0p pp"``Y`d`p`p~pBpp +0@0@0@pp0@ @````ppp pp```` +pppp```` `ˆaçd¤džq u s”q€pnp +`p ppppppp0@0@ @ @p```0@`ppp0@ @`p0@``pp0@ @0@0@ @ @ @ @pp0@0@0@0@ @ÿœ! t@``pp0@ @0@ @0@p``` @ppp0@ @```` @pppp @p0@`` @p0@ @``````p0@pp0@`````0`d`jp.pŠpDp +```"` `ppppp``p0@`` @p ppppp @ @ @```` @``.`2`pp.p*pp```(`5`6`pp(p0p9pp @```"`7`*`p$p'ppppp````(`b`^`p``"`d`ìa;`np­pàpap4p`pDppp/p p +``(`p +p(p#` `1``p +p ` `pp7p&ppp`````` @pp +p 0@ @0@` @pp``` @ppp` ` `"` p$p)ppp0@0@0@````pp0@0@````T`Á`º`,p˜pëpvp0@ @` @pp0@0@ @0@ @ @0@ @ÿœ u@p0@0@ @0@ @`````!`ppp0@``0@pp @0@ @``` @```pp0@pp0@`` @p````` ``p +ppp @pp`p```` @pp``````` @pppp p`` @`` ` @pp +p```` +` `Z`š`4pxp`ppp$pp``pp +p``````0@``4`t`®`rpnpÚppp` +`%`L`_`$p(p*pp p3p*pppp`` ``pp p``5`p p`:`Š`npNp°pbpppp` `````ppp p0 @ @p``````pp``pp +pp````ppppppp @p0@`` @ @0@p0@````pa`ÿ`=pÜq9p˜p$p0@`` @pp0@0@0@ @0@ @ @``ÿœ u@p0@0@ @0@ @p```` `pp0@``0@pp @0@ @````pppp0@`` @pp0@`` @``` +`p`` `ppp p```ppp``` @p`` +`pp` @`` +`ppp pp`` @```` ppp` @p` +`F`¾a"`hq +päpIp p.pp``p0@``````pp` `8`z`²`vprpâptpp` ```pp``p`&`c`pJpD0@p pppp``+`p``œa*`àp¢qfp²p&ppp` `````ppp p0@ @p``` +``` p p0 +@ppp +0@` +`` p +pp pppp @0@`` @pp0@p0@````X`¼`Â`3p«pìpppp0@`` @pp0@0@ @0@ @ @``ÿœ%`s`0@p0@0@ @ @0@ @ @ppp` @0@0@` @0@ @`` @pppp0@```pp +p0@`` @0@``` `p +p @ppp```` pp p`` @p 0@```p 0 @p``` @p ppp`` @`````pp```p``ea +a{`|qkqPpup$p)p``````` `````pp` `B`i`\` pZppFpp```pp0@``1`ÈaV`dpðpøphpp$p*p$p`` `#pp`0`Òa‚a pÞq¼pØp-ppp``` ` @ppppp``pp`` +``)`8`pp8p&p +pppp p`` +` pp +ppppp @p @`` @pppp0@ @````+`U`V`pIplp5pppp0@ @ @0@p` @pp0@0@ @0@0@ @`` +ÿœ!`q`0@`0@0@ @ @0@0@ @ @pp`` @0@0@` @0@ @`` @ppp0@```pp +0@`` @0@``` `p0@0@p``` ` pp p``` pp0 +@` ` @p pp`` ` @ppp p`` @`` `` @``&`:` p` +`W`Âa `(qpüp_ppp` @```` ````*`p)p ``Z`k`pJpVpEpp p```p ppp` `Wa`bB` q˜q¬pÔp`Âa]`bpîq +p—p0pp```(`&` p(02@`&`*`p(p9pp p +0@`````pppp0@p0@`` ```p`` pp$p!p`````p*p)p +pp0@ `` ` ppp 0@0@``````p`0@ppp0@0@0@ @pppp0@ @ @0@0@ @ÿœ# r@ @`p0@0@ @ @ @0@0@0@0@ @ @0@0@ @ @`` @`` @pp0@pp`` @0@ @ @````````$` pp&p0@`` `` pp0@`````ppppp0@```````p0@`` @```/`„aap q qp‚p%p p```$`)`(` ```.`p(p1`^`Ó`6p¹pÄp?`B`Xp"pvpLpp`` @pp0@p```R`‰`p[p`pCp0@````` pp```pppppp p p`````ppp 0@0@pp0@p`` ` +````ppp```` @p*p+p +pp0@ @0@p`` ``p +p#p0@0@ @ @0@ @pp0@ @0@0@0@p` @pp0@0@ @ @```p0@0@ @ÿœ% r@ @pp0@0@ @ @ @0@0@0@0@ @ @0@0@ @ @`` @`` @pp0@pp`` @0@ @ @``````2`~`”`+p‚p¢pD0@`` ` `p0@0@ @```` +p ppp 0@``````` pppp```````h`í`Êp%pÔpÎpZppp```(`I`d`V```:`p`pi`b`ÿ`:pßpìpc``.p +p:p,0@```ppp```p```!` +p!ppp 0 @`` +``pp p ` `` ppp`pp p p`` +```ppp0@`p0@pp````````` `0` p,p+pppp0@p0@ @p0@p```!`p +p/p0@0@0@ @ @0@ @pp0@ @0@0@0@p` @pp0@0@ @ @```p0@0@ @ÿœ q@ @ @0@ @ @0@ @0@ @0@ @0@0@`p` @ @`````p0@ppp`` @pp0@ @ @ @``````ta"aX`]q qxpª0"@``` 0@0@`pppp```pppp +ppp````` @p0@```````4`l`Xpp^p_p&ppp ```0`d`”`u`p `@`pˆp•`2`¼`(p¤p´pZp``pp0@```ppp `` +`pp`` ppp +pp0 @` ``pp p +p`` +`pp``ppp p`` +@pp p p0@```ppppp0@```` ``0`d`z`p†p‡p(p p0 @0@p0@0@`````p +p$pp @0@p` @0@ @pp @0@pp0@ @0@0@ @`` @pp0@p`ÿœ q@ @0@ @ @0@ + @0@ @0@0@```` @ @``` `pp0@ppp`` @pp0@ @ @ @`````&`œaŒaÜ`‘qŒrpêp.``' 6@`pppppppp``` ppp ppppp @`` @p`````````"`pppp +ppp ```8`j``mp +p1 @ppp}``V`pNp`p8p```pp`` ` `ppp`` +`pp +`` ppppp0@```pp p +p p` @```pp0@` `` +ppp 0@0@`` +`pp pp0@``````L`ˆ`ž`&pºp½p

`\`F`)ppTpJp)p```ppppp``` ```2```h`pnp•p>pp0@0@`p0@` ``;`:`p*p6ppp pp p`` ` `pp ppp`` ` ppp0@ ` @`` ` `p``6`2`"`ppNpMpp`pp0@pp0@`0@0@p`0@`pp` @ @0@0@0@0@`` @ppppp````ppppp0@ @0@ @ @ @0@``ÿœ( r@0@ @0@0@`` @0@```pp0@ @ @0@` @p```` ``- @`ba`ÊpEpìpåpNpp` +``pp&0 +@ppp`pp` @`` @```?`H`p60Q@p&p``0`D` p*pBp p +ppp @ @p``` @`pp` @p``p0 @ @pp```` @`` +`pp```,`?`$` +pp;p0pp`` @ppppp``` +``L`ˆ`’`p¢pÉpRpp0@``0@`` ``D`q`„`5pJp`pJp@p$ppp ``2`8`p*pAp p pp` @ppp @0@0@``````&`pp``@`,p p8p3p$pp @pp0@0@0@`0@pp0@```0@p0@ @0@0@0@ @ @ppppp````p0@`p0@pp @``0@0@0@``ÿœ# r@0@ @ @0@0@`` @0@```pp0@ @0@`` @p```` `$`7` `.a.b­b*q rPrMp¾pp ```pp$p` ppp ppp` @`` @```)`0 @p&p3pp````ppp pppp @ @`` ```` `p` @pp`p0 @ @0@```ppp`` +` pp```$`-`ppp/p p +````` `pp ppp`` +``@`l`z`pŠp§pFpp0@` @`` `:`X`‰`¤`cppBp†p–pLp:p.p `D`¨`È`pUp&pp0@``0@``8`O`J`U`t`|`p` p®pÖpjpNp>p`Ša^a¶`kq~qÔpÌp(p`` +` pppp` @ppp``````E`Z`pJpYp`` pp pp0@ @pp0@0@ @``ppp @```ppp0@ @ @0@0@ @0@0@0@```pp0@ @0@0@0@ @p0@0@ÿœ, s@0@ @ @0@0@```` @```ppp @0@ @````pp0@p0@``````YaˆcPb|pÞsrÕpôp%pp +``pp``.pp-ppp0@```` @` &@`[`d`!pVpzp00 @ @0@```ppp @```` @p````` @p``` `pppp```p p p p`` @``$`H````”`p¦pÝplp````!`Š`¯`pœp„p:pp +pp````ppp +p p0 +@``pp``<`S`&``8`j`œ`RpŠp´p^pNp:``¢a”aþ`qêr +pìp*p` `` +pp pp` @ppp` @```9`N`p>pCppppp p p0@ @pp @0@``` +` ppp @p0@ppp0@` @ @0@0@0@0@0@0@````p0@ @ @0@0@0@p0@0@ÿœ$ r@0@0@ @```` @pppp0@p0@````` `pppppp````` ``4`Æa£aJpXq€q|pˆpp0@`p`` $@p$pp p0@`` @`` `L`L`$pFpfp*0 @pp0@```ppp```` + @pppp````` @p```!pp$pp ` +``p p`` +` ` @` `F`€`’`Å`â`1qqKpªp9p```ea6ax`qaq&ptpppp +p``````pp&pp`pp p``(`:`p` `E`T`)p>pVp2p6p*p`haaL`nq>qhpžp` +` `pp&p*pp```ppp``pp` +``(`pppp +p @ppp p @``ppp```` ppp` pp p +pppp0@ @`` @ @0@p0@0@````ppp0@0@ @```ppp0@0@0@ÿœ% r@0@0@ @```` @pppp0@p0@``````ppp0@p````````B`ƒ`npp€p|p0p p0@p`` @pp p` @ @ @```&`,`p"p8p0@pp @```pp`````` ppppp````` @pp``pppp `` +pp```2`!pp(p``R`Š`‚``Â`GpÈq+p²pYpp``‰aŽaÔpqÁq^pzppppp +`` ``"`(`pp.pp`pp p +p` `` ` +`$`+`ppppppp`$`d`€`$prpˆ0:@``6`pp:p4pp````pp``pp`` `` ```pp @ppp p @`` +` ppp +``$`#ppp` +`pppp p +p p0@`` @ @ @p0@ @ @`` @pppp0@ @ @```pp p0@0@0@ÿœ+ q@ @0@ @`````ppp0@0@ @0@```` @p0@0@``````````p ppp +pppp0@p```ppp`` @ @``pp````p pp +0@ppp``` @p`````` ppp0@`````+`pp$p+p``pp p``pp``;`h`Ap6pap.``8`Y`B`?```Jp0ppŒpap&p``{a6a[p"qQpÞp`p,p\pJpp` +```!` +ppppppp pp``````p pp ppppp` +``(`pp%p`#`,`*`p2pHp2pp````p0@pp0@```` @` @pp0 @`````pppp``!p p p` &@pppppp0@0@`` @0@ @p0@``````` p pp p0@p @ @ @```pp ppp0@ @ÿœ( q@ @0@ @````pppp0@ @0@0@`````pp0@````p`````ppp0@ppp0@ @``0@p``` @```pp p```ppp @ppp``` @``` +``` +`ppp0@``` ``1` pp,p3p ``p0@``````G`p`Qp6pmp6p` `#````2`p"pLpGpp``O`¦`¡pp•p`´`€p^pèp®p8p`` ```p +p +ppppppppp`` +``p pp`0@```` ` `p``J``Pp2pbp`pHp*pp````p0@pp0@p```0@` @p p 0@``` ```ppp` `pp```` ` ppp)pp +0@0@`` @0@ @0@``` ` +``'`p%p4ppppp @ @ @```pp +ppp0@ @ÿœ% p@ @ @0@ @`` @pp0@` @pp0@`` @ppp0@ @ @ @ @` @` @0@`ppp @0@`` @ @```` ``pp +p @ppp` @p p 0@p````` +` @pp pp p @p````!``pp(0 @ @````` +``2`L`8p p)pp+p`ppp```pppp``*`@`2pp`¤af`àp’qxqpTpp```pp` @0@pppp``` @p +p p @``````p p `:`ÞaW`’påq6p·pNp#0 @```ppp0@p0@`` @ @`p0@``````'`pp*pp @`` ```$` pp,p"p 0@p````ppp @p``````2`B`p=pMp#pp0@ @0@ @```ppp0@0@`ÿœ" p@ @0@0@ @ @`` @p0@` @pp0@``` ` +pp p +p0@ @0@ @` @` @0@`ppp @0@`` @````` @p0@pp`` @p pp pp```` +`&`Q`JppNpDpp +p @p`` +` `` `ppp0@ @```````````pp5p(pp +ppp` @pp``` @p``¼aZ`ÔpŽqXpÿpPpp```pp` + @`ppp ``ppppp0@ @`` ````pp`Pa2b%`®qwqºpõpZp%0 @```ppp0@p0@`` @0@p0@```` +`` +``pp&ppp @`````` pppp0@``` +`ppp0@p``````-`9`p9pDppp0@ @0@ @ @`ppp @0@`ÿœ p@ @p0@ @ @ @0@0@`` @pp0@``` ` +pp +0 +@ @0@ @ @`` @p``pp0@0@0@ @ @````` +`ppp @pp`` +`*`4pp1p$pp`pp```T`³`–pp°p™p8pp`pp`` ```ppp0@````` +```p` +` `0`p'ppppppp``````(` p pp` `f`½`jpFp°pp0p p`` +`pp` +``` +` +ppp `` ppppp`` +` pp +` +```` +pp`Ca4aá`ŽqJq†pçpVpp 0@```ppppp0@``` `` pp ppp0@` `*`B 8@p6p1ppppp 0@p @``` `pp +p p0@````` p pp 0@0@```` ```pp$ppp0@ @ @`p0@p0@ÿœ! p@ @p0@ @ @ @ @0@`` @p0@```pp0@ @0@ @ @`` @p``pp0@0@0@ @ @```` @p0@ @pp`` `&`2pp-p p ``pp p``l`ß`ºppØpÅpHpp``p`` +`ppppp0@`````` +`` +pp +p``` +p ` `pp,p(pp``` +` +``4`.pp1pp ``;`&ppp€pFpp0@0@`` @0@``,`4pp-p$p``` `6`¤`Ó`,p‘p¤plp.pp +```` ` `pp0@`pp p +```ppp0@` +``!ppp0@```ppp +`` ``(`p02@`' @p"p.p pp``pp0@0@pp0@0@```` ` `ppp @```Ya@bebfp rÐr“pÖ0"@``pp p +pp0@ @0@``` + @ppppp p``0@````N`g`pRpQp0p0@` @0@0@```ppp0@ @0@ÿœ%`r`` @ppp @ @p``` +`pp +p0@ @ @ @0@ @ @ @0@0@p``` @`` +``p pp p @0@``` `ppp0@0@```` @0@ @pp0@```p0@````ppp`` `"`]``SpRp¢pV0@ @ppp` @0@``4`>pp5p(p `` ```h`•`8p;pppdp2pp`` @``ppp``ppp```pp0@````pppppp 0@pp`````` p p ``-`pp&p pp p``0@0@p0@0@0@``` @ppp` @p``+`”aa"`qTqGpj0@``ppppp0@ @ @0@`` `` ppppppp`` @`` `*`†`½`p—pŽpOp0@` @0@0@```ppp0@ @0@ÿœ,`q````ppp @0@`` @pp0@ @ @ @ @0@0@ @ @0@ @`````&`pp p0@p``` @````p ppppp0@````` `pp0@`pp0@` @```p``pp`` ``R`t`ApDpƒpDp +``ppp0@```p``"`0`p$pp``pp``;`,`p(p6p&pp +`` +`p` @p 0 @``0@```p0@` +``.`pp$p``p pp p p`` @p pppp``` pppp pp`` @```ppp0@` @pppp```pp +p p``` +pp```0`V`\` +pppjp&p``pppppppp @``pp``` `` ppp +pp0@`` @`` `3`“`Î`p¥p¥pXppp0@ @0@0@`` @pp0@0@0@ÿœ*`q @ @``p0@0@`` @pp0@ @ @ @ @0@0@ @ @0@ @```` `4`p0(@p!p +p``` @```/`pp"pppp0@``````pp 0@pp pp` @``````ppp```*`@`+p$pEp ``L`8p(pXp00@```0@```pp +``` p +p%p ` ``ppppp +`` @`` `ppp`` @```pp```:`R`p7pDp` +`p ppp```*`#p p(pppp`` ``ppp pp`` @```pppp`` ``pp pp`` `ppp0@``pp`` +``` pppp +p``pp0@ppp @```pp``` +`pp ppp0@`` @```#`a`€` +pfphp9ppp0@ @0@0@`` @pp0@0@0@ÿœ, s@ @`` @pppp0@p`` @p0@``0@``pppp0@ @ @ @0@ @ @```+ @pp"ppp``` @``"`5`pp p'ppp0@ @```!` +ppppppp0@```` +` ````p +p p`````pp``F`”`cpRp£pVpp``p0@`` `pp` +``p +p!p`` @ppp pp```` ` `ppp`` @```p````@`a`pBpRp$`pppp p` `#`Z`>p pSp6pp0@` ``p +ppppp0@```&`%pp+p p` ``pp pp```)`pp(ppp``pp`` ``ppppp0@pp @ @pp0@```p0@` @ppp0@p` @p````#`0`p$p'pppp0@ @ @````pppp @0@ÿœ+ u@````pppp0@``` @p0@`` @``pppp0@ @ @ @0@0@ @ @`` @`p pppp`` `pp```)`"`p p+p.p0@ @```` +ppppppp0@``` ``pp ``p +p pp`` +`` +pp ``T` `sp^p¹pZp```pp0@`````` ` ppp``0@ @pppp```` `pp 0 +@ @p`````` `,`E`p.p:p`p pp pp` `/`^`Lp(p_p:pp +0@` ` ` ppp +`ppp +p```2`!pp5p$```ppppp```!`pp$ppp``pp````ppppp0@pp @ @`p0@```p0@0@p`0@p` @pp```` +` pp p +pp`p0@0@ @ @````p +p +p p @0@ÿœ% w@0@``` `ppp +0@`` +``p +p p` @```pp0@````pp0@0@ @0@ @```pppp````p p +p``` `*`p)p2ppp` @``` `pp0@0@ppp``````ppppppp`````p p ` +`<`p`Pp@p…p

pp +`` ` @pp p +p +` + @pp0@0@```1`8`p4pGp 0 @````,`Ša`Îp¦q2p p`4`Z` p4pPp6p ``"`pp)pp p0@```ppp0@ @0@ @ @0@0@0@```p0@ @pppp0@````p0@```ppp0@ @pp0@0@0@0@ @ @```` + @p p 0@``` pÿœ# ‚@ppp` @``0@pp````p0@``pp @`0@ @`0@p0@` @ @0@0@`````ppp0@``ppp0@ @0@``pp0@``````` pp``ppp pp`` +`` @ppp pp p```` @ppppppp`` `````4`9`N`†`Dpep°psp p````&`V`Spp_p>p`L`m` pUpRp.pp` ```pp pp ` +`ppp0@0@```)`8`p0p?p 0 @``` +`(`ˆa`¼p¦q&p˜p`$`<`p p8p&p ``` ppppp0@```ppp0@ @0@ @0@0@0@``p0@ @ @pppp0@````pp0@```ppp0@ @p0@0@0@0@ @ @p``` @pp0@` ``pÿœ! Œ@p p0@````pp0@ @`p0@`` @p0@ @ @0@p @````pp0@ @0@ @` @0@ @` @pp````0@pp` @``` + +@` `"`pp,p0@```-`:`!pp'p +p p"pp +p``` @pppp0@``` @``)`J`Q`x`Ë`vpqp¯p.p`````*`'ppDp,``D```pBp`I`p`»`jppöp¡p*``$`Z`f`pFpEp(p"p``$`4`ppppp ``` +`pppp` `ppp`pp +0@````ppp +p```````.`pp4pp`` +` @ppp +pp @`pp0@`pp0@ @ @pp0@ @`p0@ @```p0@```ppppp0@````p````p0@ @pp0@0@0@0@0@ @ @ @p @ppp`` ``pÿœ!`“pp p +0@`` @pp0@ @ @0@``` `pp +0@0@ @``````pppp`0@``pp0@` @0@0@0@`` @pp````pppp0@`` @ @``` `` +pp0@```H`¸a``ŸpÚq#p\pp8p

`e`4pBpvpWp``Z`Ë`ä`pÞpÝpTpp``` `ppppp``` @pp p`` pp ``pppp``` +`pp +pp````p```pp +p0@ @`0@pp0@`` @pp @`ppp @ @pp0@ @p` @0@`0@ @```ppp0@ @ @ @p`` ``p0@``pppp0@0@0@0@ @ @ @p0@``` +` pÿœ`…ppp0@p` @pp0@ @ @0@ @``` `pp0@0@ @`````.`H`(p4pSp$p``pp``pp 0@` @`p0@`` @p @```pppp0@` @ @````pp 0@```L`ÀaD`§pÊqp@` p4pTp`6p,pFpp p````` pppppp ` `"`pp&pp```ppp0 +@`p0@`````p0 @`pp0@````pp p0@0@ @ @```p0@` ``ppp @ppp0@` @p @ @ @ @``` `` ppp0@`` @pp0@0@` @ppp0@0@0@0@```ppp0@0@ÿœ"`rp0@ @ @ @0@ @```p0@0@ @ @``` ` `n`ò`Ùpfqpžp``ppp```pp +0@```2`#p"p6p0@`` @pp0@``0@ @``````P`°`Žp\pÊpf0@`ppp`` @pp``&` +p$p` `,`p +p(p``p +ppppp`` ` @ppp ppp0@``````````Ò`Þ`pðpÝpH``2`/p&p;pp```` ```.`p0p7pp` +`&` p +p.p#p` ``ppp0 @ @0@`````pp p``pp0@`ppp0@0@````` `pp p````ppp```ppp @0@0@ @`p0@`0@````` +`p +pp0@ @```ppp````ppppppp``p0@```ppp0@0@ÿœ`rp0@ @`` @0@```p0@0@ @ @`````X`Ê`½pVpóp†pppp0@ @pppp````.`#pp6p0@`` @ppp```0@0@```` `.`–aH`üp¤qppÆp/p``pp````p +``#`2`p0p1p```p p` `)pp4pp pp```(`'pp%pp 0@p0@````````D`’`ž`p¬pŸp8p``ppp +`` ` +pp``+`R`)pHpYp"p` +``p +p"pp``` ppp 0@`````ppp` `pppp0@`ppp0@0@```` ``ppp````ppp```pp p @0@0@ @```ppp0@``````ppp0@0@```pppp```pppppp```p0@```ppp0@0@ÿœ' p@0@ @` @ @0@`` +`pppp @0@0@ @p````/`l`cp,ppFpp0@``pppp0@````pp p0@````ppp``ppp0@````` `0`£a„ap¼q¢pâp5`` `p pp```p```.`p(p'p @pp` `:`3ppDp&ppp```0`-p +p/p&0@```````pp``` `4`8`pBpFpp``p0 @```pp``(`J`'p>pLpp `` +`p +ppp```pp p0@ @` @p` @p`` `pppppp @pp0@``` +` + @p pp```ppp p` @ppp @0@````` +` +pp ppp``` `pp0@ppp`` @pp`pp0@ @ @0@0@0@```p0@`` @pp0@p0@ÿœ p@0@ @` @ @``` +ppp p @0@0@ @``````$`#p p+pp0@``pppp @``` +` +pp p0@``````0@`ppp0@``` +` ``g`ü`ÍpˆqpŽp```pppp` @````pp` +pppp``.`+pp6pp +pp``` `!p +p#p0@`````ppppp`` ``pppp +p @p0@`` `pp```*`pp$pp``p +pp +p p```pp0@ @```ppp @``` `ppppp0@pp @```` @p p +p``ppppp```ppp @0@``````pp"ppp````p0@`pp`` @pppp0@ @0@0@0@0@``p0@`` @pp0@p0@ÿœ`r0@ @ @````ppp 0@ @0@ @0@0@` @```pp p0@`` @pp0@`p0@`` @p0@p```` `ppppp` @p``````$`^`Tpp)p +```ppp0@ @`` +`ppp +p```8```L`p,p,p2p6pp`` pppp p` @p0@`` @`````ppp``ppppp @``pp0@`````pp pp``` @pp0@0@0@p0@0@`` @`pp0@ @pp0@``ppp0@ @`````#`pp-pp0@` @0@0@ @``` +``ppp @0@ @pp0@0@ @0@ @0@0@0@ÿœ`r0@ @``````p0@pp @0@ @0@0@` @```ppp0@` @0@`p0@` @p @0@``` `pp0@`` @`` +`pp p```p pp +````pppp 0@p````*`D`"p pJp1p +p`` `ppp` @p` @ppp``!`l`¼`ˆppŒpjpFp:p```pp#pp p```p0@ @ @` @```` pp`pp p ppp0@` @p0@`````pp p` @ @0@0@0@ppp0@`` @pp0@` @ppp @``ppp0@ @``````pp'pp0@ @0@ @ @`````ppp @0@0@pp0@0@ @ @0@0@0@ÿœ`spp```p0@```` pp0@ppp0@0@``0@0@` @ @`p0@0@ @ @pp @ @```p0@````pp``` ```p``p"p/pp``p0@````pp +pp pppp````"`5` pp4p#ppp``ppp``pp` @pp`` +`,`†`é` p@pÒp—p>p%p` ``pp&pp p`` @p```pp0@``` `` +p0@`pp0@ppp0@```p```pp```pppp`` @pp @pp0@0@ @0@ @ @0@``ppp0@ @ @`` ` ` ` `ppp +0@`0@````` ` `p pp @`pppp0@ @0@0@ @0@ÿœ`spp```pp0@```$`0@p%ppppp0@ @p` @0@` @0@pp @ @0@ @p @pp0@ @```p0@p`` @``` `.`z`€ppVp `pFpWp```p +0 +@```pppppppp``````p +pppppp``ppp`p`` @pp`` +`(`r`±`ppDp²pup*p `` ``pppp0@` @```pp +0@````` pp``pp!pppppp0@```pp``pp```pp p +p`` @ppp`pp0@0@0@ @0@ @0@0@``ppp @ @0@``````pp p0@` @p``` +``` +ppp +0@`pppp0@ @ @0@0@ @0@ÿœ q@`` `pp0@````( @pp/ppppp0@ @ @ @0@`` @pp0@` @0@` @pp0@ @```p0@p```````"`€a~a¦pZqkpÒpQpdp[p``$` p"p p0@ @0@pppp`0@````` @0@ppp``ppp0@````0@````L`_`"p8p\p:pp` @ppppp0@``````p +p0@````$`p``’`jpTp›pRppppppp```pp```ppp p`pp0@```pp``pp 0@pp0@`` @0@0@ @0@`` @p0@0@ @````pp p0@`` @``` ``8`Q`8p?pep1pppppp0@ @ @pp0@0@ @ @0@`ÿœ q@``` `pp +0@`` @``pp!ppppp0@ @ @ @0@`` @pp0@` @0@`` @0@pp0@ @``p0@````````:`ôb²b¸pRrur.põpd0=@`3`P`p:p>p"pp0@0@`ppp`p````pp`` @ppp``ppp0@p``` @````0`3pp.p,pp`` @`ppppp0@``````p +pp`` ` `` `` `–aªa4pìq§pêpNpppppp```p`` `P`XppQpLp!p0@ @pp``pp``pp 0@`p0@`` @0@ @````pp0 @0@0@````ppp @` @````U`¾a*`ºpæq_p›p-ppppp0@ @ @pp0@0@ @ @0@`ÿœ& p@````p0@p @`` ` +`p +pppp0@ @ @p``` +` pp +pp0@` @0@`` @ @pp0@pp @ @p``````````Fa&c2bûpˆrír¤q$pLp``C`d`&p:pDp4p+ppp0@``0@0@``` `ppp```pp0@`ppp0@0@````` +```pppp pp```` +`pp p0@``````ppp` +` `````pp5p4pp 0@````ppp p0@`````p pp`` + @p p +p``````pp p0@0@0@0@ @```` +``p +p p0@```ppp @``` @ppp0@`` `pp +p```M`¸`˜pp†pŒpcp,p``ppp0@```S`Ê`ä`.pªpàpp0p ``` +p +pp p0@ @pp` @0@ @p` @pp0@0@```` ``pp p0@ppp @pp0@ @ @`` ``pppp```1`©aqb>aqérkqpCpppp`` @pp +p0@ @0@` @pp0@0@ @ÿœ o@ @0@`````ppp0@ @0@p```0@ @0@ @0@0@0@```p0@pp0@ @0@0@```` @0@`` @p p pp0@ @`` @pp0@`` ```ppp +p``ppp @`````pp +p0@ @pp0@ @``` `` ppp +ppp``p0@ @``` ` +`pp pp`` +``p p0 +@` +``6`>`(ppFpBpp``ppp0@`` +`"`L`_` p/pPpCp&p ```pp +p +p0@ @pp @ @ @0@0@ @ppp @ @``` `pppp0@ppp @p0@ @ @`` ``pppp````=`~`Ö`\p±pÙp^ppppp````pp p0@0@` @ppp0@ÿœ o@ @0@`````ppp0@ @0@````pp0@ @0@0@0@```` @pp0@ @0@0@```` @0@`` @pppp0@` @p0@``` ` `pppp0@p`` @0@`` @pp0@ @pp0@ @``` ``ppp +pppppp0@``` +`p +p p````$`pp!pp` +` `` @p&p&pp @pp0@``````p ppp p``pppp0@ @pp @0@ @ @0@ @ppp` @ @````pp pp @ppp @0@0@ @```` pp0 @pp```$`'`pp:pp +pppp``` ` pp p +0@ @0@0@` @ppp0@ÿœ p@ @`` @0@0@` @0@0@ @``` `pp +p0@ @` @pppp0@`` @pp0@``0@ @0@` @0@0@`` @ppppp @p @0@`` ```ppp p +p0@`` +`pp +p @ @p0@` @pp`p @``` ``pp p +0@pppp0@pp0@```p0@````"`pp%p`` ` ```pp$pppppp0@0@````` +` `ppp 0@p0@0@ @p0@pp```pp0@`0@````pp`` `ppp0@``pp @0@ @0@0@````````ppp`` ``pppp0@0@`` @pp 0@`ppp0@ @pppp0@ @ÿœ!`n @ @`` @0@0@`` @0@0@0@ @``` `pp +p0@ @````p +p pp0@`` @pp0@`` @ @ @pp0@0@0@`` @`ppp` @p0@0@``` ` `pppppp````p +p"p0@ @p`` @pp` @0@```````pp ppppp0@pp0@``pp```` +``ppp` `` `` `pp0ppppp0@0@p````````pp p0@0@0@ @pp0@pp`````p0@`0@````pp```ppp0@``pp @0@ @0@0@````````pp 0 +@```pppp0@0@`` @p pp`` +pp p0@ @pppp0@ @ÿœ#`l````0@ @ @ @``` `pp +pp0@ @0@ @``pp0@`````` ``ppp p0@`` @ppp``` @````pppp0@``p @ @p``pp0@`` @pp```````pppp``"`L``Y`(p>p^p/p` `pp pp @``p`p````pppp0@`````pp p``0@p````p0@`` +`p0@ppp @pp``` `pp p @pp0@ @``` +`pp +p```2`+p p(p(pp +0@ @``0@`````p pp +p````` `pppp p``` ` +ppp 0@0@` @0@```` `` `ppp`pp p p0@`p0@0@````&` pp +`pppp +pppp0@0@0@ @ @ @````pp0@ppp0@` @ppp```ppp @ @0@`` @0@ @pp @`` @pp0@``` @p0@0@p``ppp @0@ @0@0@ÿœ! o@ @0@`` @p```*`?`p-pBp p``pp +p````pp0@``p0@ @pp0@ @ @```p0@``pp @` @`````ppppp0@0@p```` p p0 @0@0@p`````ppp`` ```pppp +0@0@0@ @`````p +p 0@```4`0pp1p&ppppp` +``ppp``p0@p0@```` +``` +`pppp pp0@0@``0@pp```` ` pp ` `p pppppppp @ @0@0@ @ @``` `pp0@pppp0@0@ @ @pp0@ @0@0@0@ @ @0@0@ @ @ @0@0@0@` @0@pp0@ @0@ @0@ @```pppÿœ o@ @`` @p````` ppp p``ppp```$` ppppppp0@0@ @ @``p0@``p` @pp @`` ` `$`p"p$p pp0@0@````` p pp 0@0@p``` @0@``` ```ppp p0@0@0@ @`` ```pp0@```X`PppcpBp`ppp ```pp```p0@pp0@````` +` +`pppp pp0@0@`` @p0@`` `` pp` ` pppp`pppp0@ @0@0@ @``` `pp0@ppp0@ @p0@0@ @0@0@0@ @ @0@0@ @ @ @ @0@ @ @pp0@ @0@0@ @ @ @p``pppÿœ`n @` @0@ @`` @pp0@``0@```2`L`p(p3p"pppp0@ @0@ @ @0@ @0@`````pppp``` ``$`pp"p pp0@`` ``ppp +0@0@ @ @ @0@0@``` +` + @p ppp0@ppp``````:`F`pFp>pp``!`T`RppfpHp``p +p +p @``` +`ppppp0@ @p`````ppppppp``p``` @pp````p``` ppp +p`pp0@0@ @ppp0@0@ @`` `ppp```ppp0@```ppp0@`p` @0@ @0@``pp0@ @ @ @ @p0@p0@0@ @` @ @pp0@ÿœ`n @` @0@ @ @0@0@` @```@`\`*p$p5p2p/ppp0@ @p0@ @0@```` +`pp 0@`````` p pp pp0@p````ppp0@ @ @ @ @0@0@```` @pp +p`0@ppp`````8`Š`ª`p¢pœp4p p``4`4p +pBp0p +``ppp0@p````ppppp0@````ppppp0@ @pp`` @pp```pp```pp p``pp @ @ @pppp0@0@ @`` @```pppp0@```ppp0@`p` @0@ @0@```pp0@ @ @`` @p0@ @0@pp0@0@ @` @ @pp0@ÿœ p@0@ @0@0@ @ @0@`` @```.`F`%p +pp,p3pp p0@`` @0@0@```p0@`````pp +p0@``` ` + @p +p +0@``p0@```ppp0@ @````0@p0@``` @pp` @pppp`````P`È`ð` päpÜpRpp```pppp```ppp0@```` @` @pp0@0@`` +`pp +p0@p````p0@```pp``pp0@pppp``0@`````ppppppp0@0@``````` @pppppp`` @pp0@`0@ @0@0@ @```ppp0@ @p`````pp```pp p0@ @0@ @ @``pppÿœ p@ @0@0@ @ @0@ @0@`` @````"``ppp!ppp0@`` @0@0@`` `pp pp`````ppp0@``` @pp0@``p0@```ppp0@````ppp0@``p0@```ppppp`````D`¨`Ä`pÀpÂpJp p````ppp````pppp`p````` @p p0@0@`` +`p +pp0@``` `pp +p```p```ppp0@pppp``0@``` `$`pp$ppppp0@0@```````0@ppppp0@` @`p0@p @ @0@0@ @``ppp0@ @```` @p````ppp +0@ @0@0@ @ @``pppÿœ`r0@ @0@0@ @ @ @ @```` +``ppp p0@pp0@p` @0@`` +` @pp0 +@` @pp0@ @0@````0@ppp````p0@pp @`` @pp0@ @0@````pp0@`` +` ` ``K`^`p^pgp&0@pp0@```ppppp```` ``` pppp0@p```ppp0@````` +ppp0@ @p``p0@```ppp0@`` `*`m``#prp„p

ppPp9pp``````ppp +p0@````0`pp(p"p p` @0@`````pp``$`Y`cppmpEpp0@0@0@0@ @0@ @0@ @0@ÿœ%`qp`` ``ppp +0@0@ @pp0@ @``0@0@p`````8`Ñaúaé`vr:r,pÓp.p ppp0@` @ @0@0@```pp0@0@ @``` @pp`` @ppp`pp` @pp0@0@ @`` @0@0@0@ @``````pppp @``p0@p```pp0@0@ @ @0@``` +` @pp0@0@```ppp`` @0@```pp`pp0@0@ @ @``` ```,`pp1pppp``p0@p``pp0@p0@p` @```````4`0pp`p$p@p$p 0@0@ @ppp0@````pppp0@````ppppp````0`pp p p +0@0@0@``0@0@0@`` @```C`y`XpMp‘pApp`ppp0@0@0@ @0@0@ @ @ @`p0@0@ÿœ`p0@ @0@ @ @``````pp0@0@ @ @````&`ƒa(a@`qZqBp{ppp0@ @`p0@`` @0@0@``` @ppp``` ``9`x`appvpVp+pp +p0@ @``` ``ppp0@ @0@` @0@ @``` ` @``J`†`Dp^pŠpGp0@`` @` ` `+` +p*p,p0@p0@0@ @ @`0@ @ @```pp0@0@0@ @`0@p`0@0@ @0@````` ppp0@0@0@````pp0@p```` +`pppppp0@`` @p0@````"`ppppp``ppp0@ @0@`` @``/`S`Bp7php/p p0@0@pp0@ @ @0@ @0@ @ @p0@ÿœ`p0@0@ @ @p`````pp0@0@ @`````'`H`N`pVpVp%p +pp0@``pp0@`` @p @ @p`` ` +`p p p`````/`h`WpplpFpppp0@ @p``` +` +pp +p +0@ @0@`0@0@ @``` ` @``*`X`,p@pbp/p 0@`````)```{`pxp|p-ppp0@p0@ @ @0@ @ @```ppp @0@0@ @``ppp` @0@ @0@ @```pp0@0@p```pp0@```` +`pppppp0@`` @p0@``` `` +p p 0 +@```p pp0@ @0@`` @` +``'`pp1pp0@0@pp0@ @ @ @ @0@ @p0@ÿœ`p0@ @0@` @`0@````pp0@0@ @````` ``pppp pp0@```pp0@ @0@```ppp`` +`p +p +0@`````0`+p p1p(pp0@0@0@`` @pp`p0@```p0@0@0@````` ` ``p```pp&p0@````H`®`Õ`2pÒpÜpMp0@`pp0@ @pp @``0@p````pp0@``pp0@```ppp` @p0@` @0@`p` @0@` `pp +pp````` +`pp +p p0@0@` @0@ @`````ppp```` ppp +pp0@ @ @ @``` +`pp p0@0@`p0@ @ @```p0@0@0@ @0@0@ÿœ`p0@ @0@`` @p0@````pp0@ @` @````pppppp0@```pp0@ @0@`` `` +p +p0 +@``ppp`````` ` ppp 0@0@0@0@`` @pp`p0@```p0@0@0@p`````` `p0@`pp +pp @````L`¾`å`*pÜpèpUp0@```p0@ @ @p0@``0@p````pp0@``pp0@```ppp` @pp`` @0@pp @``` @0@``pp +ppp````` +`p +p p0@0@ @ @ @````pp0@```` ppp +pp0@ @ @` @pp0@0@`p0@ @ @```p0@0@0@ @0@0@ÿœ`q0@ @ @```ppp0@ @ @0@ @0@ @ @ @`` @pp0@0@`` @0@```0@p````&`pp&p0@0@`` @``pppp` @0@0@`` @p0@p0@``pppp @0@ @ @``` `pp0@pp0@ @``` +`-`r`’`pˆpŠp7p +0@`` +`ppppp @pp0@ @ @0@p````ppp```pp pp` @ppp` @0@` @pp0@p0@0@`````pp` @ppp0@ @`` +`pp p0@ @0@p`0@ @````pppp````ppppp0@0@0@ @ @0@ @0@0@0@````pppp0@ @0@ÿœ`q0@ @ @```ppp0@ @ @ @0@ @ @`` @pp0@0@`` @0@````ppp```&`pp&p0@0@0@`` @p`pppp`` @`` @p0@p0@``pppp @0@ @ @```p0@pp0@ @`````*`6`p2p2pp0@` ```ppp p @pp0@0@ @0@p````ppp```pp0@` @pp`` @p``` ppp ppp0@0@ @`` @p``pppp0@ @`` +`pp 0@``pppp @ @`p``ppp``` +`pp p`p0@0@0@0@ @ @ @0@0@0@````pppp0@ @0@ÿœ p@ @`` @p0@ @ @p0@ `` @0@0@0@`` @`0@`` `pp 0@` `` p pp0@0@`` @p @p0@```0@`````pp p +pp0@``pp0@0@```0@``` @pp0@ @p```` + @pp 0@p`` +```ppp +0@`pppp0@``p` @```pppp` @0@ @p0@```p````pppppp0@ @`pp```````pppp0@pp`` @0@` +``pppp0@ @ @0@0@`` @0@0@ @p0@0@ @ @0@0@0@ @ @ @pp0@ @ @ÿœ p@ @ @ @p0@ @ @p0@ `` @0@0@0@`````pp```p pp p```ppp0@ @`` @` @p0@p @0@``` ``ppppp0@``p0@ @```0@```` `pp +p 0@` @pp`````pp0@p`` +```ppp`pppppp0@```` @` @ppp0@ @0@ @ppp```pp````ppp pp0@` @ppp``````p p"pp0@ppp` @```.`4pp8p)p0@ @0@0@0@``0@ @0@ @p0@0@ @ @0@0@0@ @ @pp0@ @ @ÿœ `q0@0@0@ @ @ @0@ @`` @p0@ @0@ @0@ @```` +`pp```ppppp @p0@0@ @`````` `p p +p0@0@0@ @``` ``ppppppp @ @` @` @``` ``p +ppp`` @ppp`` `ppp0@ @```` `pp` ` ppp p0@0@ @``0@```pppp` @0@`pp0@````pp`` +`ppp +p0@````ppp0@`````p +p pp0@p @ @0@```?`@ppFp8p0@0@0@0@ @0@ @0@0@ @ @0@ @0@0@ @0@ @0@ @`p0@ @ @ÿœ`q0@0@0@ @ @ @0@ @`` @p0@ @ @0@ @0@ @``` ``p p ``` pp +p pppp0@ @ @`````.`<`p8p4p0@0@0@ @``` ` +` pp pppp p @ @` @````````&`pp*ppp @pp````pppp``` @```` ``pp$pp +0@0@ @`0@`` @pp`` @pp`pp0@````p0@``pp +p0@```*`.pp,p'pp``` +``pppp @`` @0@0@```3`0pp6p*p0@0@0@0@ @0@ @0@0@ @0@ @ @0@ @0@0@ @ @ @0@ @`p0@ @ @ÿœ`o0@0@ @p` @0@ @ @ @0@ @ @`p0@ @ @0@```` +`pp +p````pp +p pp0@ @` @`````.`ƒ`ª`p¢pp:p 0@0@0@`` + @ppp`ppp0@`` @p`````p` +`!`p p$pp0@0@`` @pp0@ @` @p``` ``*`&p +p4p)p 0@0@0@0@ @```` @` @pp0@0@````` `pp @ppp0@```(`.pp.p)p0@`` @ppp0@````ppp0@`` ``pppp +pp0@ @p @0@`` @p```pp0@0@ @0@ @0@0@ @ @ @0@ @```ppp @ @0@0@0@ @ÿœ`q0@0@ @p` @0@ @ @ @0@`` @pp0@ @ @0@````ppp``` ``p +p pp0@ @````` ```B`Éa`pîpýp^p0@0@0@`` +`ppp```pp0@`` @``````p```pppp0@0@0@`` @pp0@ @` @```` ` +``pp,p!p @0@0@0@ @```` @` @p0@```p``` pp0@ppp0@``` @ppp +0@```pppp0@````ppp0@``` ``pppp p0@ @p @0@`` @```pp0@0@ @0@ @0@0@ @ @0@0@ @```ppp @ @0@0@0@ @ÿœ `vp0@ @0@ @p0@`````ppp0@p` @0@```p0@`````pp +p0@0@ @```` +``*`"`0`ª`ð`!pìpöpbpp0@0@0@```pp0@ @p0@0@`````` @ppp` ` +`ppppp0@0@` @0@` @`` `` p``pppp` @ppp0@`````pp0@0@``` +``ppp``p ppppp0@ @``` ` +ppp p0@```ppp0@````ppp0@`````"`p&p"p0@ @p @`` @`` @pp0@ @0@0@ @pp @ @ @`p0@0@ @```p0@` @0@ @0@ÿœ`zpp0@ @0@ @p0@`````ppp0@p` @0@ @0@`````ppp0@0@ @```` +`"`6`"` `@`|`pˆpšp>p +p0@0@0@ @``p0@0@p @0@```` +` +`ppppp`` @pppp0@ @` @0@` @`` ` `p0@ppp `` `pp pp0@``` @ppp0@ @````<`1pp?p `` p pp0@0@ @``` @ppp0@p``ppp @` @``pp0@````:`J` pJpFp0@0@p @`` @`` @pp0@ @0@0@0@ @pp @ @ @pp0@0@ @p``p0@` @0@ @0@0@ÿœ`{pp0@0@0@ @ @0@``pp0@ @```p0@0@0@ @p0@ @ @ @````(` pp%p0@0@ @``````,`p +p``p0p;pp` @p0@``p @ppp0@ @p0@ @` @`` @pppp0@````pp0@```p0@`p0@` @````pp0@p````ppp +pp````` `pp +p p +p`` @` +`%`R`Gpp\p:p `pp 0@`` @ @pp0@0@ @0@0@`` @```pp0@````E`X`pTpSp0@pp0@p @0@`` @pp`pppp```ppppp` @0@0@ @0@0@0@ @ @ @ @ @p0@ÿœ`wp0@0@0@ @ @0@``pp0@ @ @`0@0@0@ @p0@ @ @ @`` ``,`p&p+p0@0@ @``p````p +0@p pp``` `p +p p``p0@ppp0@ @pp @ @``````p0@ppp0@````pp0@```p0@0@` @````pp0@ @`````pppp0@`` `` @p +ppp``pp```F`;ppNp6pppp0@ @`` @0@ @pp0@0@ @0@0@`` @p``pppp````3`@`p8p?p0@pp0@p @ @`` +` pp ppppp`````pppp` @0@0@ @0@0@0@ @ @ @ @ @p0@ÿœ`r0@0@ @0@ @0@0@ @0@0@ @0@ @0@ @``````ppp0@ @ @ @``` pp0@p`````pp0 +@`ppp0@ p````` +``pp p0@p0@ @`` @0@0@```pp p0@````p0@p0@0@ @ @` `` +ppp ppp`` ```` pppp``pp p``$`pp(pp0@p` @0@```ppp0@ @ @0@```p0@ @pp0@`` +``&`pp%p0 @pp0@` @`````ppp +0@0@``` @ppp0@ @0@0@0@ @ @0@ @0@ @ @0@0@ÿœý`p0@ @0@ @0@0@ @0@0@ @0@ @0@ @ @```` ``p pp0@ @ @0@`` @pp``` ````"` pp&0@`pp0@ @``ppp``` +`"`,` p$p,p0@p0@ @`` @0@`` `pp p0@````ppp0@ @ @ @`` +` +pp +pppp``` `` @ppp``p +p p`` `ppp +p0@`` @0@```ppp0@ @0@0@` @p0@pp @p`````pp"0@pp0@ @`````ppp +0@ @``` @ppp0@ @0@0@0@ @ @0@ @ @0@0@ÿœ n@ @ @0@0@ @ @ @0@ @ @0@0@ @ @ @ @```pppp0@ @ @ @``` ``` +``ppp0@0@ @````p pp````;`T`pFpPp"p @ppp @ @ @```pp p0@0@ @` @p0@ @ @0@` @pp0@``` ```ppp @pp`` +`pp +p p0@0@` @0@0@````p0@p` @pp @``pp0@0@0@0@``` ``!`pp$pp0@0@p @````` +ppppp @ @0@` @ppp @0@0@ @ @0@`` @pp0@0@ @0@0@ÿœ n@ @ @0@0@ @ @ @0@ @ @0@0@ @ @ @ @```pp0@0@ @ @ @ @0@0@``` `p`` `ppp0@ @````pppp```=`\` pNpZp"p @ppp @` @0@```pp0@0@ @` @0@ @ @0@` @pp0@ @```"`0` p&p*ppp`` +``pppp0@0@` @ @```pp0@`` @0@pp @``pp0@0@0@0@`````` pppp @0@p @````pppp0@ @0@```ppp @0@0@ @ @0@`````ppp0@0@ @0@0@ÿœ`n0@ @ @p0@0@ @ @`` @0@0@ @ @0@0@0@ @ @0@ @0@ @p``` @pp0@`` @0@ @p0@0@` @`0@```#`pp pp```%`:`p0p:p0@`pp0@ @`` @0@ @`p0@ @0@0@ @ @0@` @0@ @` @p0@` @0@```:`T`pBpLp p p````pppppp0@ @ @``0@ @pp````pp0@0@0@ @pp @p```` `pp +p0@` @p0@ @ @ @``ppp0@0@ @0@0@0@ @0@ @0@ @`````pp p0@0@ÿœ `n0@ @ @p0@0@ @ @`` @p0@ @ @0@0@0@ @0@ @0@ @0@ @p``` @pp0@ @ @ @0@0@ @0@````0@````p ppp``` `` +ppp +0@``pp0@`` @0@ @`p0@ @0@0@ @ @0@` @0@ @`0@pp`` @0@``@`\`pJpTp$p 0@` +``p p +pppp0@ @ @`` @0@p````pp 0@0@0@ @`p0@p` @ @pppp`` @p0@ @ @0@` @pp0@0@ @0@ @0@0@ @ @0@ @0@ @`````pp 0@0@ @0@ÿœ `o0@ @```ppp @0@ @0@ @`````ppp0@ @ @0@0@ @0@ @0@ @ @0@ @`` @pp0@ @p```pppp0@ @ @ @0@``` +`ppp0@````ppp0@`` +`ppp0@ @ @0@``pp0@pppp @ @`` @pp @0@ @0@``` @` +`(`>`p2p:pppp` @p0@p0@0@ @0@ @``````p +pp +p @p @0@```ppp` @pp0@`` @p0@ @ @0@ @`p0@0@0@p``` @p0@ @0@0@` @p``p0@0@0@```p0@ÿœ `o0@ @ @``pppp @0@ @0@ @`````ppp0@ @ @0@0@ @0@ @0@ @ @0@ @`` @pp0@ @``` @pp p0@ @ @0@ @``p0@pp`p` @``0@` ``ppp0@ @0@```0@0@pppp0@ @```ppp @0@0@0@```` @p```` ppp +pp0@ @p0@0@ @0@ @```````pp p` @p0@0@```pp0@ @pp0@`` @p0@ @ @0@ @`0@0@0@p``` @pp0@ @0@0@` @0@ @0@0@0@```p0@ÿœ`o @`` @pp0@0@ @0@`````ppp0@ @0@0@ @0@0@ @ @0@0@ @ @ @ @0@0@```````pppp```pppp0@ @ @0@0@0@````0@``$` p p)p0 +@0@0@ @ @`pppp0@0@`````ppp``pppp0@ @`` @pp```ppp0@0@0@ @0@ @0@0@ @ @` @`````ppp0@``pp0@p0@`` @pp0@0@0@ @` @p0@ @0@0@0@ @0@0@``` @pp0@ @0@0@ @ @ @0@ @0@0@0@ @ @0@ÿœ`o @ @` @pp0@0@ @0@`````ppp0@ @0@0@ @0@0@ @ @0@ @0@0@ @ @ @0@0@ @p``````pp0@```p p +pp0@0@ @ @0@0@````0@``$` p p)p0@0@0@ @``pppp0@````ppp``pppp0@ @` @pp`` @0@0@0@ @0@ @0@0@ @ @```p``` @p0@0@```p0@p0@`` @pp0@0@0@ @`0@p @ @0@0@0@0@ @0@0@``` @pp0@ @0@0@ @ @ @0@ @0@0@0@ @ @ @ÿœ o@` @ @`ppp0@ @0@0@ @ @ @ @0@0@ @ @0@0@0@ @ @`` @ppp0@ @` @ 0@`````ppp``` `pp"ppp0@ @ @ @ @``pp` ``ppp0@0@0@ @pp @` @p0@ @``pp0@``pp0@ @`p0@ @ @0@ @ @0@0@ @p0@```p0@ @` @p0@ @pp0@0@ @pp`0@ @0@ @0@0@0@ @ @ @ppp`` @0@0@0@0@ @0@0@ @`` @pp0@ @0@ @0@ @0@ @0@ @`` @ÿœú`o`` @0@`ppp0@ @0@0@ @ @ @0@0@ @ @0@0@0@ @ @```pppp0@ @` @ @0@`````p0@` `&`6`p&p:ppp0@ @ @ @ @p`pp``` ppp0@0@0@ @p0@```p0@ @p`0@``pp0@ @`p0@ @ @0@ @0@0@ @p0@```p0@ @```p0@ @pp @ @ppp0@ @0@0@0@0@` @0@ @ppp`` @0@0@0@0@ @0@0@ @`` @pp0@ @ +0@ @0@ @0@ @`` @ÿœ`q`` @0@0@0@0@0@0@ @ @ @0@0@`` @0@0@0@ @0@0@ @`````ppp0@ @ @p``` @pp0@```pp```$`9`p*p8p0@0@0@ @ @ @0@0@` @0@p0@ @0@pp @` @ @0@0@ @0@````pp0@` @pp``0@0@ @ @0@``0@ @ @0@p`0@0@ @ @``0@p` @````ppp0@0@p @0@0@0@`` @0@ @pp @` @p0@ @0@0@0@0@ @p` @ @0@0@ @0@0@0@0@ @0@0@ @ @`` @ÿœ `q`` @0@0@0@0@0@0@ @ @ @0@0@`` @0@0@0@ @0@0@ @`````pp +p0@ @```` @pp0@```pp`` +``!` pp$p0@0@0@ @ @ @0@0@p` @ @p0@ @ @pp0@` @ @0@0@ @0@````pp0@` @pp``0@0@ @ @0@`0@ @ @0@``pp0@ @0@0@`` @p` @p`` `ppp 0@0@p @0@0@0@`` @0@ @pp @``p0@ @0@0@0@0@ @p` @0@0@ @0@0@0@0@ @0@0@ @ @` @ÿœ`p @ @0@0@0@ @ @ @0@ @`` @pp0@ @0@ @0@0@ @`````ppp0@0@ @0@```` @pp0@``0@```` `pp pp @pp0@ @0@`` @pp @ @0@0@`` @pp0@` @0@0@` @p0@` @pppp0@ @0@0@ @0@ @ @0@ @ @ @```ppp`` @p0@`` @` +``ppp``pp0 @ @pp0@0@ @``0@0@ @`pp0@ @0@ @0@0@ @ @ @0@0@ @0@0@0@0@ @ @0@ @0@ @ÿœ p@ @0@ @0@ @ @ @0@ @`` @pp0@ @0@ @0@0@ @`````ppp0@0@ @0@```` @pp0@``0@``p @ppp @pp0@ @0@`` @pp @ @0@0@ @````pp0@` @0@0@ @`` @ppp` @`ppp0@ @0@0@ @ @0@ @ @ @`` `ppp```pppp``````8`-p ppÊp™p$ppp``ppp0@0@` @pp @```ppppp0@0@p`` @p0@0@0@ @ @`` @pp0@0@ @ @0@ @```````p pp0@p```` ``pÿœ n@ @ @0@ @0@ @````ppp0@0@ @```` +@ppp0@0@0@0@ @````pp0@0@`` @0@0@ @ @0@0@0@````pp0@0@ @0@`` @pp0@````p```ppp```pp p0@0@ @ @0@ @pp` @0@`` @0@``pp0@ @0@ @0@ @0@```` @```;`:pp>p+pppp``pp0@0@` @pp0@` @``ppp0@0@p`` @p0@0@0@ @ @p` @p0@0@ @ @0@ @`````p0@`ppp0@p````` `pÿœ m@ @pp @ @ @0@ @`` @pp0@0@ @ @``` ``#` +pp p p0@0@`p0@ @p`` @p0@ @` @pp0@` @0@ @``` +`pppppp0@ @p````ppp0@`` @p0@ @0@`` @p0@0@ @ @ @0@0@ @0@` @```pppp` @pp0@ @0@ @0@ @`` @```` +pp +p`pppppp0@`` @`` +``p p pp0@`` @0@0@0@` @pp @0@`0@ @`p0@```p0@0@ @`````)`7` +p7p6pppp0@ @ @ @ÿœú m@ @pp @ @ @0@ @``0@p0@0@ @ @```B`c`"pUpdp%p0@0@``pp0@ @p @ @0@ @` @pp0@` @0@ @````pppppp0@ @p````ppp0@`` @p0@ @pp @0@ @ @ @0@0@ @ @````` @pp +pp` @pp0@ @0@ @0@`` @``` @p``pppppp0@`` @````ppp p0@ @0@0@` @pp @0@` @```p0@0@```p0@0@ @````L`˜`Æ`)pÎpÇpCppp0@ @ @0@ÿœ `n @ @0@0@` @0@` @0@ @p` @p0@0@ @ @`` +`-`v`£`8ppªpCp 0@0@`` @p0@ @0@ @0@p` @0@ @````pp0@0@0@ @ @0@0@ @ @`p0@ @ @ @0@0@ @0@0@ @ @ @ @0@ @0@0@ @```` @p pp0 +@0@pp0@ @ @0@0@ @0@0@ @`` @p` @ @0@ @pp0@`` @```%`pp$pppp0@ @0@```0@ @0@0@``````ppp0@```ppp0@p @``` `)`„aao`Jqyqlp}ppp0@`0@0@ÿœ `n @ @0@0@` @0@`` @0@ @p` @p0@0@ @ @`` +`+`v`¡`0p‹p¢pAp 0@0@`` @pp0@ @0@ @0@p` @0@ @`p``pp0@0@ @ @0@0@0@ @p0@ @ @ @0@0@ @0@0@ @ @ @ @0@ @0@0@ @````&`p pppp +0@0@pp0@ @ @0@0@ @0@0@ @`` @pp @ @0@ @pp0@`` @````ppp ppp0@ @0@``` @0@0@0@``````pp p0@```ppp @p0@``` `+`a%a—`eqªq“p…p pp0@`0@0@ÿœ`n @ @0@0@``0@0@`` @0@0@ @ @p` @0@````D`b`pUp\p'0@0@`` @pp0@ @ @ @0@`` @pp0@ @0@ @0@0@ @ @ @```pp0@0@0@ @ @ @0@0@ @0@ @ @ @ @ @0@ @p0@`` @```(`pp"pp p0@0@0@ @0@0@ @ @0@ @````p0@ @0@ @p0@ @` @`` +``p p 0@ @0@0@ @0@`` @pp0@0@``p````pp p0@` @0@`` @p0@`` +`"`n`Úa:`ZqFqCpbpp0@0@ @0@0@ÿœ`n @ @0@0@`` @0@``0@0@0@ @ @0@ @p` @0@``` ``&` +p#p$p 0@0@`` @p p0@ @ @ @0@`` @pp0@ @0@ @0@0@ @ @ @```pp p0@0@0@ @ @ @0@0@ @0@ @ @ @ @0@ @p0@```p````ppppp0@ @0@ @0@0@ @ @````p0@`0@0@ @0@ @p` @```ppp0@ @0@0@ @0@`` @pp0@0@``` @`pp0@` @0@`` @pp 0@````9`r`›`%p£pœp5pp0@0@ @0@0@ÿœ`n @ @0@0@ @ @0@ @0@`` @0@ @```p0@ @`` +@pp0@` @p`` @pp0@ @0@ @`` @pp0@````pp0@ @0@ @0@0@` @````pp 0@ @pp @0@ @ @0@`0@0@````pp0@0@` @ppp``` `pp`` `pp p0@0@ @ @ @0@0@0@ @ @```0@p0@ @ @` @0@0@ @0@0@0@ @ @`` @pp0@ @`` @ppp0@` @0@`````ppp0@``` +``#`)` p/p-ppp @0@ @ @0@0@ÿœ`n @ @0@0@ @ + @0@``0@0@ @``pp0@ @` @pp @``pp0@ @pp0@ @0@ @`` @pp0@````pp0@ @ @0@0@` @p````ppp`` @pp @0@ @ @0@`0@0@````p p +0@0@` @ppp``` `ppp``ppp0@0@ @ @ @0@0@0@ @ @ @` @pp0@ @ @` @0@0@ @0@0@0@ @ @ @p`` @pp0@0@``` @ppp0@` @``` ` `pp p0@p````` `pp +p0@0@0@ @ @0@0@ÿœë`m @ @0@0@ @0@ + @`p0@0@` @ @0@p`` +`ppp0@0@``` @p p0@ @0@0@0@ @ @0@ @ @0@ @ @pp```ppp0@0@ @0@````` +ppp0@````ppp````p0@0@ @0@0@ @ @0@0@ @0@ @`` @pp @ @0@0@ @ @0@`` @pppp @ @pp0@ @````0@0@0@0@0@`````p0@0@ @ @ @```ppp @0@``` @p0@pp0@ @0@ÿœê`m @ @0@0@ @0@ + @`p0@0@`` @0@0@``` `pp +p0@ @0@ @0@``` ``ppp0@0@0@0@ @ @0@0@ @0@ @`````ppp0@0@ @0@````` +ppp pp````pp0@` @p0@0@ @0@0@ @ @0@0@ @0@ @`` + @p +p0@0@0@ @ @0@` @`ppp0@ @pp @```` @0@0@0@0@````pp0@0@ @ @```ppp @ @p````p0@ @pp0@ @0@ÿœþ`n0@ @ @0@ @ @0@0@ @ @ @0@`` @p0@````pppp0@ @0@ @ @p````/`<`p0p3ppp0@0@ @pp @ @ @ @0@0@ @ @ @p0@``` `` p +pp +0@0@ @ @0@ @0@```` `` +ppp +p0@```pp0@0@ @``0@0@0@0@ @0@ @0@``` ``ppppp0@ @ @0@ @0@0@ @`` @pp0@ @ @p` @pp```pp0@0@0@0@p` @0@p @ @p0@ @0@ @```p0@ @0@0@ @0@ÿœ`n0@ @ @0@ @ @0@0@ @ @ @0@`` @p0@````pppp0@ @0@ @ @p`` `,``¨` pˆppDpp0@0@ @pp @ @ @ @0@0@ @`` @pp0@````` p +pp +0@0@ @ @0@ @0@p`````pppp0@```0@0@0@ @0@`` @0@0@0@ @0@0@`````pp"ppp0@ @ @0@ @0@ @0@0@ @`` @pp0@ @ @`` @p0 +@``pp0@0@0@0@p` @0@p` @ @`p0@p @ @p``p0@ @0@0@ @0@ÿœ n@ @ @0@ @ @0@0@ @0@ @0@ @ @0@ @``0@pp0@ @0@ @````F`×a`pìpïpppp0@0@ @0@ @ @0@0@ @```` @pp +pp0@```pp0@0@ @ @0@` @0@ @0@ @0@0@ @0@ @ @0@``0@p0@0@ @`` @p0@0@0@``` ``ppp0@0@```p0@0@0@0@` @ @pp0@ @``` +` @ppp`ppp0@ @0@0@0@` @pp0@`` @```ppp0@ @0@````pp0@ @0@0@0@0@ÿœü n@ @ @0@0@ @ @0@0@ @0@ @0@ @0@ @0@ @ @p0@ @0@ @````F`Ïa`&pðpñpppp0@0@ @0@ @ @0@0@ @```` @p +p +pp0@`` @0@0@ @ @0@` @0@ @0@0@ @0@ @ @0@``0@p0@0@ @`` @pp0@0@0@```` ` pp p +0@0@```pp0@0@0@0@` @ @pp0@ @````" %@p'p ppppp @0@0@0@`` @pp0@`` @``ppp0@ @0@````pp0@ @0@0@0@0@ÿœø`m @ @0@ @0@```pp0@0@ @ @ @0@0@``0@0@ @ @0@ @0@ @````*`q`š`pˆp‘pBpp0@0@ @0@ @0@ @0@``````ppp p0@ @ @0@0@ @ @`0@0@``ppp0@ @ @pp @ @ @ @0@0@0@ @`` @p +p0@0@ @```ppp0@```ppp0@p0@ @p``` @0@0@0@ @````pppp +pppp @0@0@ @ @ @pp0@ @` @ppp0@ @ @`` @pp0@ @0@0@0@0@ÿœù`m @ @0@ @0@```pp0@0@ @ @ @0@0@``0@0@ @ @0@ @``````.` p,p7ppp0@0@ @0@ @0@ @ @```` `pp"pp p0@ @0@ @0@0@ @ @` @p``` ppp 0@ @ @pp @ @ @0@0@0@ @`` @pp0@0@` @p0@`p0@0@```ppp0@p0@ @````pp0@0@ @```` pppp0@p` @0@0@ @ @ @pp0@ @ @ @`pp0@ @ @```` +`pp p0@ @0@ @0@0@0@ÿœ n@ @0@0@ @```pp0@0@ @ @pp @0@ @0@0@ @ @0@0@ @ @ @``` ```pp +p p0@0@0@ @ @ @ @`````@`~`_p p€php!p0@0@ @0@ @0@0@ @ @ @```!`p p$pp0@``0@ @p0@ @ @ @p0@ @p`0@ @```p0@p````ppp @0@0@0@`` @0@`p0@0@ @````pppp @0@`````ppp0@0@` @0@0@ @ @0@0@0@0@ @ @``pp0@ @ @``` ` `p pp0@ @ @pp0@ @ÿœ n@ @0@0@ @` @`pp0@0@ @ @pp @0@ @0@0@ @ @0@0@ @ @ @```pppppp0@0@0@ @ @ @0@0@ @````$`ˆa`ÉpHqpÜpCp 0@0@ @0@0@ @0@0@ @ @```+`&pp0pp 0@``0@ @p0@ @ @ @p0@ @p` @```pp0@`````pp +p @0@0@0@``0@``pp0@0@`````ppppp @0@`` @p0@0@` @0@0@ @ @0@0@0@0@ @ @``pp0@ @ @``` ``pp +p0@ @ @pp0@ @ÿœ n@0@ @ @ @0@ @`` @pp0@0@ @0@ @0@ @ @0@0@0@0@ @0@` @0@`` @p0@0@0@ @`` @ppp0@```` +`0`·a4`îp(q\q,pTp0@0@ @ @pp0@0@ @ @0@ @```"`p p$pp0@ @0@ @0@0@ @`ppp` @ @0@p```pp0@ @```pp p0@ @ @0@0@```p0@0@``` ` +pp +p pp0@0@ @ @ @0@ @ @0@``p0@pp @0@0@0@ @0@ @ @0@`````ppp0@ @``p0@` @ÿœ n@0@ @ @ @0@ @`` @pp0@0@ @0@ @0@ @ @0@0@0@0@ @0@` @0@` @ @p0@0@0@ @`` @ppp0@`````$`‡`ä`²pqpàp@p +0@0@ @ @pp0@0@ @ @0@```` ppp p0@ @ @0@ @0@0@ @ @p0@p`` @ @0@p```pp0@ @`ppp @ @ @0@0@```p0@0@```` +pp +pppp0@ @ @ @0@ @ @ @0@``p0@pp @0@0@ @0@0@ @0@ @ @0@``` @pp0@ @``p0@` @ÿœý`o @0@ @0@ @`` @pp0@ @ @0@0@0@0@`` @p0@0@0@ @ @`0@ @`0@ @ @pp @0@````pppp @ @```=`l`Qp pwpfp!p0@ @0@0@ @ @ @0@0@ @````pp0@ @p @0@ @0@```pp p0@```ppp @0@0@``0@ @0@0@ @0@ @0@ @ @0@0@` @0@``` `pppppp @ @ @`` @````pp0@p0@0@0@p` @p0@0@ @ @0@0@ @ @pp0@ @ @0@p` @ÿœù`o @0@ @0@ @` @pp0@ @ @0@0@0@0@`` @p0@0@0@ @ @p0@ @`0@ @ @ @pp @0@````pppp @```` `pp!pp p0@ @0@0@0@ @ @ @0@0@ @ @`0@0@ @ @pp @0@ @0@`` +`pp p0@```ppp @0@0@`0@ @0@ @0@ @0@ @0@ @0@````p0@ppp @ @ @``` `pp````pp p pp0@0@0@`` @pp0@0@ @0@0@ @ @`p0@ @ @0@p`0@ÿœù o@0@0@ @ @0@ @p0@ @ @ @ @0@0@0@`` @p0@ @ @p0@ @ @0@ @ @p0@ @0@ @``` `pppp @p @`` @pp0@pp @0@0@ @ @ @0@0@ @0@ @`````pp +p0@ @ @pp`` @p 0@ @0@ @0@ @ @ @0@0@ @ @0@ @0@0@ @ @0@` @p0@0@0@ @0@ @``` +``.`pp"p``1 G@p;p@pp p0@0@`` @pp0@0@ @0@0@ @0@ @ @ @0@ @0@0@ @ÿœÿ`m @0@0@ @ @0@ @p0@ @ @ @ @0@0@0@`` @p0@ @0@ @p0@ @ @0@ @0@ @0@ @````pppp @pp @` @p0@pp @0@0@ @ @ @0@0@ @0@ @``` +`` ppp0@ @ @pp`` @p0@0@0@ @0@ @ @ @0@0@ @ @0@ @0@0@ @ @ @0@` @p0@0@0@ @0@ @````@`x`6p]pbp`N`ß`Ì` pàpÖpdppp0@0@p` @pp0@0@ @0@0@ @ @ @0@ @0@ @0@ @0@0@ @ÿœú`l @ @0@ @ @p0@ @ @ @0@ @0@0@0@ @ @ @0@p @0@0@` @0@0@0@ @ @`` `` `p!p0 @ @pp0@ @ @pp @0@ @0@ @ @0@ @```pp0@ @0@ @`````ppp0@ @ @p0@ @0@``pp0@ @ @0@ @0@0@ @ @ @0@ @0@`` @0@```pp0@`` @pp @```$`h`¼`RpŽpžp`Êb1ad` qøqÌpÍp3p p0@0@0@ @0@ @0@ @0@` @`` `pp p +p0@0@ @0@0@ @0@0@ÿœù`l @ @0@ @ @p0@ @ @ @0@ @0@0@0@ @ @ @ @pp @ @0@`0@0@ @0@ @ @`` +```pp0 @p0@ @ @pp @0@0@0@ @ @ @` @`` +`p p0@ @0@ @``` ``ppp0@ @ @pp0@ @ @``pp0@ @ @0@ @0@0@ @ @ @0@ @``0@p0@``ppp0@`` @pp @```$```ª`Np|p’` a.caÊprªrcqpCpp0@0@0@0@ @ @0@ @0@`````)`pp)p"p +0@0@ @0@0@ @0@0@ÿœ`n0@ @0@ @0@ @0@ @0@0@ @ @0@0@p` @ @ @pp0@ @ @0@ @0@0@ @0@ @ @0@ @`` + @pp0@0@ @p @` @pp0@ @ @0@0@ @````p`` +`p p0@ @ @`` @p p +0@ @ @0@0@0@````pp0@0@ @ @p0@ @0@ @ @0@ @0@`` ` `pp p0@ @pp0@`` @ @pp @````0`T`,p

`&`úbÊajp!r"È?»?ÈÀ@ƒÆACÈB ÂBѽC“µDP»EÎEÀÛFŽÝGiÚHF×I ÎI÷¾JžKƒÀLAÂMÇMÃÏNŠÙOYßP2æQëQ÷ëRâÝSÍÔTªÕU~äVSïW7ðX&èYÙYþØZ×î[¯ý\ÿ]šø^™ï_‘ë`€ðakùb[cTdTúeTõfNìgCêh/ìiójøjø÷kððlçêm×ãnÁæo¤æpŠßqpÓrOÑs"ÑsóÑtÄÔu•Ôvi×w=ÚxâxîëyÐñz»ù{¬ù|¥î}žã~ŒÜoâ€Ké-ì‚êƒèƒìê„Ô酾ㆧ㇊߈m݉LÕŠ)ÏŠþЋÍÎŒÉkËŽ4ÎŽÿËÍɘÑaÂ’$È’æÖ“®ä”„è•hÞ–PÒ—.Ř»˜Å¸™€Æš8Õšþä›Óî¥ëž“ÛŸ~Å Y°¡¨¡Îª¢v¶£ ½£Ö¼¤“»¥O¸¦ +¶¦Â¼§xÀ¨4À¨ôé´Áªw«8Ë«úÓ¬ÅÖ­˜Ð®nϯ>ذ â°åä±Çݲ«Î³ˆ¿´V°µ¥µÅ›¶jœ·¢·¡¬¸C³¸ïµ¹¢´ºW¶» ¶»Á·¼w²½.©½à¤¾‰Ÿ¿-ž¿Ì•ÀjÀÿÁŠ®ˆÃ8ƒÃÀ{ÄCľ~Å=zÅ»Æ5‚Æ´†Ç6ŒÇ¼“ÈH—ÈÛžÉr«Ê³Ê»´Ën³Ì"¥ÌÕ“Íz‘Î ŸÎž«Ï=¯Ïè©Ð—›Ñ@ÑÛ„Òj~ÒîˆÓl“Óô™Ô‡™Õ “Õ¹“ÖL—Ößš×všØ–تŽÙ@ˆÙÎ~ÚV€ÚÔ‡ÛT”ÛÛ Üo¤Ýpl÷ÞÎÉ11YÙvƒà1 x$»¹ŒÈ¹Š‚Fa€‡‡º`Ã<²×wQ1pf3c30؇ØÒy“¼ˆ„Gx³D—¬ šä8b9½†2Ò„!HJ‘„aj<h €6ÉÖy„aŒ)L0 (Àؾã8B0Œ%Â0”fÞl Ì~xw qc6áͶfygÂPÔ!E€ €2'˜B”„çYÎa +Q,^÷‚B€x0U»Î„ã„f1†ÙíÂb92&#:.î9ˆÁo»‰Ê‰»€ŒÏ@&.8g À‘"TJ%`Y†ÖêÇ“ 0¥‘ €à8ýfžI¤‹,Ur•T)˜3«©Ax3fqc6áͶfy£‚"‚h$‘`h, À°"»`ãË7þ¹cŽ 4A$ÐA. €p@À¥ÝñÁ$Ã0AÌc ³!Û„ DdȨ¼¨»ˆä +€@€X»¸œ¨›¸”ªp ‚ 7B'W" +""‘8w*@u…ÚŽv>6&á!ˆ³÷ƒc$’Í4’F†Ü‡Idšà͘s Y˜Æ{y³Ï BgcÀ€ À{—:5/%±ï›P @"‚`àH +txsW« H0à@ƒ®W³‘ ˜mÈ„F>'dÜÄDÄÇ` 0ÀY÷qѶ*åt@@»t‰Êä@@@@@@AÀ@ÀABEDFR)‚À¨ÒþÕ |\,|ÈaFáÈ`ÆiåšdH² @ÀÏìñ&~lÀs Y˜Æ{y³Ï B¦â@"0sTâì,šŠguû0à@…ð AH(&5 ò\5«Ñ‡$p AHie–$½c ÀB)üa•ì"û<Ò̃€ ãù$1‚œC¹EÀnÝ"r¸ð @8`B‰*E Ž@@ @E +à"îLJ°T¤D€ÁŽèˆ-ƒ«¨i<³FH+ô#€p9ýž$Ð/͘r Yž3o ä&†à‚¤£Ïœ † ÃqZ9 °5÷0C¤‚ ‚($±PP€à Áz»`†`‚`ŠH$'eÜÅÅEÅÄ 1Gà€Â /`™w‘aX×ìLb `4m†Ì ¶lÙ¢‚H ‚X ‚)(£ @0 „NïïŽ ‚ 1YäðÕÎ)눈¹´²Í áìa@£¿’I4 Ìñ€r Yž3o ä%Œ'BÔAƱ¤>]ýàÀØ!ôçFȘˆ¨Ì(`ËØ¨˜ˆ˜¬Œ/¹}Ö„a8NÃ…4»ÿ¿„ …I戋¸ŒÈ¸¸Ììߘ¨B€+€llð³fÉQ‘1qYˆ! €Ðð?î¢"`±„!*a°@“0°ßå8ÒØïBŒÒË40 ÁìdÑ_ù$“@¼ÏpÈN/bÆ‚9’"c2á +²ÿí7ÄÀ`#þ6*UW € À€ÇJRˆŠ—7ºÃT½ëBTQ m=Éà@€˜*op,a(V¤¡XB„¡8B¦8,ÌØˆÑ Þ`Œ;~b(ªšt" €@FÀc:ªA$• 0(€„=ÅM$1ÏG°Rœ € +66è®PFBp¢Àpr!MõÜ@@£åM"11ç¿`ˆ8Wòͼ„4Gœ|Â)ž¹B³ß±8„UýÉà ¡˜¨Ø¬¨Øˆ¨˜ˆˆ¬Æ€!`„GG0y‚0íù„0Âr„d(€ß燑%h-€<Š2m¸0à@ 8ð"@‡,tT\Tb‡€#Àxyÿw‘1Pœ(°p’d$_f …ÈASù BÁ‡ÅBÄÃ…þ2³#ÈaOñC¹f–dId€€ ÁÀsø<’,É'úiæšd¤ €Ä0s¹]JîeE  € #§)Jã•]F‚ †K<°c@ Å9ÇIdSËpC0”+ +B¥  à€ 6DEÿXB„a0?r$EÅb#nbˆ†¾ï¬ÕÄT!ÀÇô0ÄxC„!Û0â<„1 ˆ˜ÎWdRª!1º  ‡3™ `šÁ]g–idšYdI$Aƒtår»•]E„%Ó8a@±Âp¦9+æs„a"‚X$‚ ’@,b°X‡y`‚ †`gìu$EÞgGÞÂÀ3ÀX]Æ¡Qw"  c9]‘NWuD (bêå*œªW9H €C`6áU\ÙJ)ÕG +*8‚Lo”¤T§Pˆ!†@ÀÀy?’M,òI4I`F€1ŒÍ¶a³$d  +ä„Næ–X$‚("‚8$’¤Àl¬à~'ÞX ‚"‚Æ`ÀwÑ?X@€À§òÆ“¢H""†>W ¦3»ª 1ßW)TåR¹Ê@@B ·(¢Ž*ªÂ„Џ0>R‘Τ@€À¦ê•ΪQ\ ‹ÆfÛ0Ù„`xá‚"#6Z”% ”³€À:¸Wõ„!J€XÃÌtGˆ@„8ëò(p²Ë$“M#"õ‰2þY$AÎ ª“R©Ý\ªQ FÀQœî®W"I‰Š`h,^ÄEEgfýDÅB0„#û~|!E½™¶Þa0ðc›¿&ÇEDÄDB0€3ÀÇÝÄÅDD@[ðØrOà‚úŒ‹Öid’ia";&ö2;àƒWq1Œ¯&¢ÏÙæ€Aa 8Æöl@ ›õ0PÄ€ð´ÆDÅgfýEDÆ@\ ‹1Ûóá-ìͶðó` &"ÇÆ7â8Rº¨ˆ€`®UP[ðØq$Ea@`€XoÄDlLTDLTTT0µ!yΔ¥à(¸U:rWÌåˆ^3 ÀDûöÌ30ƒŒÂcž—7”0P ƒàٻ„! B³%Bˆ`Ì Dñ†Â`Cˆ rŠwGU*Šˆc @@üòÌŒŸùd–I r1ðÈLÇ™áÛÍ€q$EaÀ`@ À‚~"âbb¢"b¢¢ ±†¹ îp¥( À@\#2ƒ n8ë0ž"xÏÛ0Í„{„Þ<7„MúŒPà<_ÆDDGDÝåDD@›®R¦ªqJ"à Ê)ÜUu9TQÐ +(PÂ{œ'ÂT•!9Â0„#BBDBc1æxvó`p$Ecƒ€X]ÔLDLLDFDLT .ÒJñž8&‚¤”!w߃À`¨fã$ñË[8Ÿíä-„a›aèC‹À k4B (uè–H‰ä‰4¼wÙ¦’y$žDHd¨#€q ±°ÃÑáÌLh€ Š5Ü_ܰGPC$E1ÃCPAxM·€~37‡ßàp™À8¥’Yd‘$³CA&ZI^³ÇPM†Ø[œ€˜Pv¦8#†°aÀ&"ÿo!0aó`Ãl=qx¹DAƒlÙQ¨¥lwÙ¦’y$ž"$dÈ€08àáƒ[ =! ˜@@€´¼#îX#‚(!’ &‚Xá‚!‚( €<&mˆŒñ™¼>ÿqR  Ž‘;TWQ ’¬"ž™â‚æ’¨#‚ãÀ@APÀ vÞ8 Šcî Ǿî¥qLØH[›7™ ‰J €ˆ 6Ѝ§(‰¾–I’K$žÆIM€FŽL!ˆ3ÙûhÄ@ ƒ@€ÜKœðEAPQ±ËEAFa ‘,3ÞöqV@€0 n;TWQ£’j"‚X"‚Xâ’( ‚ç€l‚àfPÍpÁÇÊ €o»©\0ØP˜1á›o3 ”@DƒæB':œ¢&øNêEJ©ò*  cAb ö~ÈM  +@ 0°ðjî*""*2&ö"*2"3LˆÑa† o0ÿpT@ |D)·)ÕTIŒÈÉŒ‹¹Œ‰Ä(PþÌ7Hˆ "¸©ÕÊ!ƒbá[êCùÔP§eB®"F@" tâ„1°¼Ãð8ŒÐ€ +#0³<ãÈpl<1€‚0`qÊUU;©J¡uW:•BDÀ`mÔ ¢ ÀpT@‚†7Ä@ ˜1Üꪑ,“ýTBŒ8/prÁ¤Eö8‚Æp@D1±ØÒû! ‡Y$’4ò¤ƒ’Ȇ‹ä1 Wð¾ÄŒDsð°Ø~š 8€°›<†Èpl<0¡ÁlÏǼÏ$ÄFDDÄDEDA@ÀxƒWñqPD0oR…QÊ"@F Ôú”=,džÊ‘YcÊ âÁ»ÈÒI$ð„oà ƒ…tM,%åFÜ Àà LVMÜd\V\ +º!]@c8Ý"&*%qÂ.9Y„ aÁØÂ#LÃ"33Cmì íŒ ÞXB„%B0„% aàhƒÂ ëÞõ„ *䉌po™ìÂâó–2OeBG,i¦8`Ýüi䄞B7„€Àpúi¡aIC3Œ8à Sy…©)î„áB®ˆR™>݈™±Ù¤M,XȳM*2 À +8+„lqÂpÈŒÌÆ›ØÛ¼°„!J„aJÁ`4Ð<6lß½ë @UÈ DÆoŸá†ˆ 3dˆ˜ÈÈØ„ +ç`÷ÝÆDáLÿØþYb!'ú"B€Py"hJʽÄ€0€,~Çe_ÜEÆD@FÂM› 9ž2DTR2'â"&3";0€±¾GL!c„Áy Œ(ç†ÛÍù³,!B„aF†0ðhƒ‚`SÍç@UH "1†`oŸáð€ÌÌy’"c#"b ;À`‹»ˆ‰ÎȉMÝÁM¨"pF¢Pòhþ¡@ pÌ›ÿ'‰$¸"”p£€ ÆîU8™†FýÄDÆf £í`(@ð,O‹eLÚˆ¹Ä@ ÇÔ"S‚æÛÍù³$DÄFDÄÄÄF!À€x‚׿*¤30r \;Ã{6ì°„a,J8,„T »9B0¬- B­ôB&ÕPÙCÅp¢S¢D`Á¾ªW8W +œD"Ú€¡ƒˆƒa¾W(°…) r½á8B§ÀP|ÆïO¶ŒÂÈb‡2Ä\$&0ˆ³cÌߘaˆê3®£„B ÀT l€t AÂofÃ$DÇ"£îàdßDþÇGEØ@Ñp‹Cüða„À ¡ˆ#c*¨‘m0qFÍœ®Qâ@‰D #LJ"D¸¸P|X{_§\˜ad1|!€#ˆ¸Pžaìy›ó€@1cn•Ôp‰½º¨> LÆw @d `ÁìÌãlvHDÝæƒ +°a +;Ä „q gˆƒ„Ì¡ EŠ„X0èçR¹Wwg‡„€˜BDf3D4$$4 ,$$…“°À;˜‰½Ã_ .(§B@ Êêªáµ®u*ª«Ä0 ¶êR>® Ÿä!€w @d `ÁìÌâ! Øôñ4ȉºÀÂsÂ88@Î!›d8‹Œ ày,òM,ÈI¢ÅÏ 0„€"Ï4À H$ +À`€B"å2€(ä° )ë þ#‚ÀÅÅE8A"`ÕÕUÃ$¾ÄšYäšI"O1€q€ àÿ’i§‘$ŸI,'ù`u Fcl6Ãl0ØôžK4²DDš…wŒ  ®D ¡¶‚oŽLç3 ‚W7wQ™1Y‘Qwp?4hÄ ÿýP„½ý™,ñ0h +€A p$À`h¥<ÀÊpPç©ó¸|›€ €³Û„fNþ4žÍ$ÒI4ò #Œ¿–i¦žyãK$’Iàs |=°Û °ÃbîWQJ¬t"  ˆn¸D€"€7„&ߊ|ᢠ€p:&ïôDÒ¢M?ð— +à Â×w¸„MýÔvWÜ" + + +2Éh@-°#À9AžÇ«‡ƒƒ›!È@!DYí„# œw'³I4’M<„ @@ `1×)Jsœ+ªª‚á€r Y†l`pŒÞ3D 6¥UDAPÛ¨¡±Ù¥˜@8M$Yd’x ¢€@F D˜ÝU)Í–Œ1D‡p¯9ÖÅ'¸S¥wXç‚("’h Ž8%’j:(à`Ü€v{¼ðA›)NR‘ +ÎP®q€D`Ü(çEw)ÄA@˜@1üaá †0r Y†lfŒÞ3D0ß(§DA Ž*‘lzif :Í,H²É$ð BŽ¢#„!ž"YÂXå48@(6°Uyΰ–);Ãõ3¼À³œá(J’„çF’ǘ"àT6Wð… &éT®D Œ g(W: #ƒxm˜ÂÈ„†3ÇáŒaá †0qó‰$¢8üˆ’,“É4]]B›êPˆ¬€ÃM… `, ^e¯r£³£#ý6?$±¥–I## @@Üp¾¢@‚#2l!D qcf„8ûˆÍ„FÿfÏ„"7„1Äfqóa3?~!1 @s<ÇÑ$•Ds¿‘E’HHXHɈ‰ˆŠˆ¹ˆŠŽÌŠ  X ,¹‚ƒ'†6O´S—MË 9¯·ùÞ³ó6xÀ„†!HC"3!Æá"Œ!cd!ÇØFl8[ý›Üã¶ÈCPšÑ!Œ0Øb,ÁàB #LÃ!DöB`6͘w›ýœ[!0°Ñ`pó͘¶l'î"> ˜€D9T"ã|Uq +Æi$‚9$š cž`‚ †ä’….‚ð†+˸$pKÍ£8ÂÜaM¯ûøÀp þr€X0öB€"Mhˆ „Da†ÃmàB #LÃ^ÈLÙ³óxcd'ÈL,4G˜p˜À„DfØÌ'ød8Ã`ó  D€ ÊR»«¨DP( š) މ$‚ç‚ ‚)!Ž"“ãÀ2‚P7U—pÒŒcÿüá a +_ù‚@>Âÿž(ˆ0î0y²#³`Ûd!°ˆ±³oa…¿ð…›7áaƒ˜p*ª”à@@€Ç(îr„ªª+*¢¸â¹€€€„ € å+”§uŠãe•!JRç +•#8BTHà€¸e²\v1 ‹_Ä¡˜÷ì0@ +ˆ€°õÚ0A07\ÉMõ"7Ȩ#³`Ûd!±áˆ±³l7…¼1à¢6Ç„a† `nši&I$ @ ²OäY ¨®¥W(®¥6"‚0 éì®DW¢"ŒnD „îòÏ,Ò}2aÀ+’â¡F +"}ëႼ?c`Œ,µý£Pcçw +mPcâ”0Ì3DcfŸņ±›l? á3‰ DY‹b¡lã¸n)TЍ  ÀœPÝr‚Þ6a¼ÄHH@LaÅL6W"+»ˆŠ1¸y@ã¼³Í4žÌ•J儸¨qƒˆïZÜO$lD!wwq €ã00! Ì,a˜fˆÆÌ30Â!f3máá¼#fq0!ˆ‹8@ÑlT woR¸¥DA‚qGTT0p‚,ÃáDÆ€b†""˜ åDEnœ¡Ct8†ˆxˆ¤Bÿí$i=䈬(–ŸžÐ؛興»¸DS8®ˆL7Qµv߆h°ffÌ„ˆ@ff3ö3BfD#fò10Á›A‰Î_%€q3aœ!ˆ°Â ÀE³ÃQ|8F! @HpCq³É¦‚ßñ™B0 ‹¿¾ˆŽˆÍ±øÈ úÁ p€:ÅúH“ÿQð€ 0$ÃymJ6˜c Ñ`Ì͘€"ÌÌgìf„̈FÍä&ñç³bD& ÝprÍç†n;0@ˆ@DÌ0{È€DvØœDE3‘@ˆlÛÊwPå1 +Þ0¥€`1Ö¤áHJ˜Ö1{ô Ð;Ùý‘4žrDTvL0 “iºŽÉ„1–J˜"{<ÆÃ #ÃÍÛ͆ÂͳÆ{a!rÍç†f#³@ˆ@v{Þ@ @"#mÄDIìÈ 8 +wYd–0Å!Â0¥ 8pÂ?Ž-IÞp”!1 ˆ…&6åWLè…M²Â„©(0 ŠÁwo Ær„©,(P¤Î“"m½žcxaðÃ4GíæÃaf Ì c1¶-pܤB&ò g(¢)„G ßù€ˆ"lÍI…“ßI%àiXv DhïŽ Ž8#-!ˆF„gŠ 0ƒÐ6#ºq.oýœa8 +ˆ¨ €D mH•A°‚Pm²Ê“…¡ +((A·ç:Ns…©n˜d\@ƒr«nqE‘Mõ„2Áï3lJ®r„]@‚ƒDcwüxpܤ@3 D g( ‰°ˆá›ü6:,Ìð`H™‚¥;òà@‰"‚ ø)À9€6‚š¼øp @2d"&&&ó  6ñ‡3ÞJæ."$„C¿i“D‹Â\¿òÚ“„¡ +aÁ¦‰§8Rs¼-Mp€à÷EÄ`ÇpVÇ8*¹P6u„!Àï`Í“¢âb"c²cpÀÀH>¬kׯ<qšY&™; ˆsù¢!’œ Ùã3!ÂpS“´rpP‘ðt +Øïú>n +&r"I=ù)'u‘‚s‡<žDI Hw•'h¨Ž9ç„R6ê&é²Ó÷… €pÏ p,XÓû,i$ŒCqO䉀fƒ{3"6 ¢‚Xá‚( ‚"’@ + ôáÊ;¤ßºã†ƒí€q,óLƒ§t4@È(p€6Cþ@ øB¦‚D#ÑH¬nA!ŒÁ|lpJÀ×/ýœn€@á0xNˆ*fÑp¢º  ƒºp¨Š„AÈÚ²1Ž‚!HÕìmÌ)s÷…" ±f“ÙcI$1 ŸÉ!Ø,ÌÑØ@'L80!@$Q&\rÀƒð(€ei±¢[·. ÛrŒ—Éæ†Eõw“ €a~ši&˜€‡p;É"&˜ˆÙ8Iˆ˜¸XHHˆJ¡à)ã|æÉZNN& U9UÔQF u(‚&ø§RT"m±b´ÿÄÆdF"oîã1Ùx ŒPË,I%ö$ñœSø‘ LÃÁ7¶6 â@‹P!D‰$IZ €^4$ÝÖ,:sâÀ‹,g±€r$B²nâ⣳nr"â#˜` +€ÀA71QQQ‡€< }ÅÆgE Ä8q"@ƒ I"Pô4À%€ihòàÀ 0ÄVA³•]T@D7QÈ!βI$Ä$É?ì ÁçWyb'„%ÿùÈè0P³BD‹ÖI!IËüHÐ&cx€ ½±°„ñ A‡(¢D$­`( + ðym£ªpsct<ÌlÛ!ð„6"±«»„Lƪ¡G(#fááb˜a“2®"/p À(À,^ÅÝÜÅDDXA˜g°w FBDG`f?ü€H!š±A,jî" ’ã&6"*1A€1ÕÄEuRg +¥8#!Ù›l  BZBPµ)G€À€€Á¶ë +B4…-‰NüˆDàuši¹üˆ„çþ1 6†€6 À¼â|à†à’ ‚ †¨’ hDwö$±&!À¯âM$-))ÂhР B >ây„ç Æ„! „†{y a¶8Hbó0x‘Ì„@£î"0‡a¯œ›˜¨œQƒ08òÉ$DÞII8‚Ørœ§q3œdDj$‚J(‚À @r1¯òÉ$qÁa-óÏ,ãˆb€@ÎPu™ Îï*1/"+ \Xˆñ‚!‚( ‚$‚8$’H!°ƒ°ò×q•@puC˸쨨,ᄨ@€¢DѾÂ0¬#ÂPŒÏÍ€{ c„D^fÆBNÄ0€X øˆÄv7ç&æ*'0€(@h|lDDfUäDd}æ39@ÃŽ@˜á^Ï4ñb! =*"‹<Ò;ëŸ ¨ŠšË#ÎxhA…»ß<°1H80hzb³,¿¸¬¤tM¦# * 8`1ðˆßaF„!(B„)K„…–»ˆ¨¨Œ¬Áàv òþŠ!ÞUï™Â…©áh€È ‰æ…aF„`xm€ M±Â¢ÂÀD ™0…`±yqŽ@aaåÜÅDßgfDÜÆÞbP @<ɈyëÛ˜›¸˜F+2æö+ÃLPXäÝ®u‡a9ú Ú´½Ï'_ž1ÃmMa€  +(A7ç8Zy¼!J…¡XNƒ‘ (;É,‘"K4ÓÌB3¿¸ŒŒŒŠE ÷Üb¢ÈBÛsˆ0¸ EüD\LTLLd!¢„ƒ [c„2,x‰ŽB8pçòÉ*$žF•È<ÿ¹Š‰¾Å3!7±·¤ÍbZöæ&â&ŠÈ¹¹Ž`ã 3À$  üZŽTŒ?n¶m[wÉe“×,pÀ[SX(LOs„§B…aZ„æ„PD{à ±²f]ÝÄdddR8“ËûŒ@`…Œ Âû,Ȉ&8Sù'–ieÙh¡ŠƒP碜ˆ"žë¸Tˆ ›6vTˆ)ÔàS€@Lö­7Q‘‘¤"Óÿ€ 0P0 €l!¿ÞŒë RpÆ- ÎpÅ$À±CZÀ ƒAèYËGZU^óåÅÓ§þ˜&’H'Ž( H[à@@Lq΄±ìFwÝá +Z•˜FÞ6Á†g€8²DTL\Lff †%½s€€0ñ€@?¾H‰‰ìo}šY cဪà±ç‰4Iã ‰úù,°TЦŸeH +霙é´ßDB#2‘¿q‘‘H` + ƒvP¹´ïˆFy…©s„ç8r–`x¡UÀÀTàÉ:ÃÊ-EÿlZëþ0•) ÎP,!,0†Ø>Gp…¡d !]¦Ÿï +Z’˜@`ß¶ 3< Å’"¢bâa˜ ( h=u‚ñ€áÏ÷ò"HDñ‹ìѦö÷$ 'îæ22"&9Ž„®îb¢`Tªq]Ê@0tÙ\ ‡:ÿ4HÈ’É ä"*@ \œ( ÿT®ç +R8J›¼çXká§O‚ƒ`²æ@¬4»Ê´ÜÅGBc —Š Û˜Ä0@!¥’x·Îve„‡ÆE¡ã³J‰Š‰ŠŽÊ`(€P ¿n"qˆèÞIȈaÂp9äˆ ˆp€€³ØÀ7$€,'þæ22"&ÅCWwQ10"º¨®åƒ¦éB…=ÒŠDQÔl ˆˆ€€€Ž¤6 *ØæÜò¢I—H¤N‰.<8r`F‘%)ƒ®ÂC‡=ˆASôÞý4±bB\VR„®ncÁ@†®O?s², ‹! z È@03d¨˜¨¨¨ì¦@ köâ'¸Èˆ›¸Æ +ÂÂì` €Yw„1 ÇÊ®p$$<ÿ¸ŽŠ‹ˆÎ΄ÞÄDÅ@Xxó6`€„\wN”ª"A@0ïÀð @p±¿¼ˆåÅ(’ G‡"@…" ŽA" Òœ+|áì‘’ù ;¢ $!‡ñ¥ã²ÌC!ÐЧY0€àá~‹ôÈJŠˆÌŠŠ`PHÁ $'æ".** +B„gYÃÉûƒHn°œ9È\ #žlÜÀÀáßùiäL–_d–h.*JRˆ £¨  DÄÄÆB2#î㣢"2ˆˆA€‚ /î³ ÃÃÞ†!eþÅEÄ@Fà ¡ƒ @bØB1ð„$DY˜ìI%šIQ„ßø0 ˸N„°ÑO|šu„©jF„€p&¸,Ä‘@Ø|¾ç +bT”è¡Ê~ðÄ# R3"naY—°`@È ÛÌŒÈûFa^0Â%ø†Erù ‘Ýy,.!^¤±jfõ£À( +&_œç +B0*‘(æâVøDcd(4hl:ÜãHSˆbéÞœçÂPœ'ì0Úz 6 0 ‹DrdIáˆïb#AÀ£¬0 (å¬#:BXx‚Ÿ*œ!,Jœa$…À¸L·Â”®ïxÑ®nõ‚ÃK€T?øZœ©I‘xäE§ `<<Û¨¬èM Àpd¾Ñ™•鏯1VßÌߨÈ-© Ös†¡(N¢€ÀØOõ„aHNc‘§BKâû6–HÄ %9à@ÆÐ:°¸µ¾ ‚I&‚9ã‚8!‚("‚#€;2Âx ³`q`Ã(™¢HŠÄEÿ،æ x?b³!>J‰¸ŠABæ®ÈWµuƒ‚<,³…)*Bán.º8< Ò€vá¨fq¥&YÞtÖ!:Ñ P™ug(F’…g‡  ðbï"±˜¿ÛÌc:ÛèÉýŒ‚+"öþ:&.+ ûc“H„‘}öm,‘ˆ N<1ÃP@ŠSOŸâF<àA +(`@€³!Œ u™°8¶žñ’¢±0Ä-wÀÀ sÀð¸Í¹˜Þd@ƒA]1áÝf„B9ܱ…!Š[á.:O¼ʨ^…ï(R„'‚AŒç6®žD†—¯ÓO&ˆ3¼À8à/€¤–¸`‚8à‚ †$Š8㊠ ‚0ƒ7ð€u™°8¶žñL„‹Gï$D gáâ/±`Á‚s*B +ä!ˆîY§òxIŒ„g! vZL€Á…€h¸¨¸¨È-!9–‡ „ÆÛÝç:”Ä#Á@àir@-÷‘1Ñ¿7ð„3"/ïê À°-1wQÑ1Q1Qw10*u¤÷Êt¨‚QA€`0 ¸•œa9΄¡R–žç(Z˜A™„t?à ˜"BxÄȉdTÝDê·Á€œqü‰¯DA qïy‘—•˜‚²ÿnŒXñïLNvFd0¬ãKa@` @6KÞý¼aJZ‚DL€±éЉÅBn㨘èûLB +À|øOƽç(Zœa[çBc¡ ÑÈàBBAÁà‡¦*6â&*&+iù„DX^AÀt<1†"00D„ñ‰‘ˆ›e7Q”‰ÔžD(ˆÜB#ïn1AH=7p¦G‘ñÙØ„+êb˜ì=±8¦1Œ‚£þÎTK Ð`/ «x”% c“"ß±ÑØ¨oíÜRiø@8`P4`é<4W}qÅPE0AÔIqÉÀhC4sy B €! ”l¥w"1ª¦ 4A0v Ã0²„=ÈÄ0 Ž{䳿CŸ$•DåF‚¿š†ÂP_PA3›¬R• €€Â<ÒÄ—÷q( `8  t#¼8$ÂîP€Ø_Sox¥oZ… M½ŠÆë=w‡fü¡P·Ú€a€€à7ÜJä’›ñž) Ž#‚&’ˆéŽ "€8‚#6aE‘BÈxƒ0Ç¢0†â/x †`ðÃ@H@ÀF hŽCp/Ic~‚õ$È4ÿø¨Ð¯&„#ƒ¯È"g5±Í$ÔQ$0KB…ž^µ5ÇpCBL +@žçà0 Årq»53Íqá%7ËÐIob°ÁXYqô@S =(*ÿ@À€ |"t¥~íHNpœ#KSÌéHH8‚ 033¢ŽÈHQa8þB@ÑCq€“K$BvsÄ$Yá#)‚ XYéú¸…"#pÄ%xˆŠÁÎw²Íi¡~)â †ANi`šI ŠH¤ŽOy:^ëŽ8ã‚ $’@˳Ӈ@'&hkm¯\¸p!ÅJ$™r`D‰ +H„›ÛŒ(`dŸX•Á€ÂGë@’÷@ƒKœ.Ñ“ûuÄb~:$1à"aD0a0Ü9 + "ðÈÂ`ch@„†üÒʼnƒ!Ü`@$áúH³Á¢d'â½ò1^! ÈBEBe—óƒ ðòÛy‚¬ Ù¿PìLU€<“„ä)™„CHHÁb×1  ðñ?¢t¥r‚L0aµ"VøJW)Š PÁƒ|¥ +u3~f›¬IPÉ ö;¬ðDJ"¢`Ãtˆ©ª„Bœi žxàŠ †¤£Ã¡áÒ0üF†y¸2 ªÀ´/+ó•) +ÑNws„!(Z–COv @átoAÀX@2K6zp`C‡8¢@‡ +(àDˆYÎ9Ssä7zÂ4¶”ÀLf¡eb”Å9 ²L?ºÂpÔaòîÞ£òi¥˜‚‚¿ödÒûfÌ„a0fg†LE²wÜŠ›å"@(€ø6ê`c† 4:…ÌÍÁA$Á”xt48ü+7÷yð0ªÀ”# ØÄ:jF®â#¢P( 3À±ö€ Øb×øðp +U€àVÖ¸ † cŽ  ŠH`Š8 †Ö”„¨…+:Δ”ã ÏX$„*¯:@²¦±ŠR‚@ Nð6:½ï8CHâm§9ébHˆ 0BÀužx’Ïg™‡€ Íäa„‚#€u rÀEš! @LñR…:"nCÈ #g9J,áhN…!(F˜PH(!sk‘.Ü ©ÐÙ †T ]"¯â#¢P<Á÷!VD¾H è€6â:6î:+*&â6 bâ”Guœ)j[ž`Ð` @CéÎ,ilR…`p‡ÁwáûÂØÕ'½ÎF…’" 1 €@ƒºÉ$³À™æx„0Eæð „Š#€s0„Ä[036Þb®êEˆ€Acu(—•˜ÂÀ†ƒÂÖÁØ<†DúC±8ˆô–‡¶ᬄ@áâKü“"'ò~XRÔ¢)Öp…8H@öÂ&ð0Pýë3K$”EpC%ÍÀXàÀ=W<IqÇ@}˜ VÎ; ¦ºc‹ +8ãŒBõqÐ…uë<4SžLÑ#XHt/l]'³ß…À¼#˜ƽ32L“;3Ó‚B ¸<ˆIµ°0 3ØkÀ0 ¤r8åI ‘ÃbrÜEpP‘׎'`0ã­G  J¸ŠŠ‰‹‰ŠŒˆ¸ŠÎ`y@lvñ„áH[vë•\*” € :…#b¢¢c`„ }s³&šh“Dˆ‚@˜8¨ Fsч*$xð`F¡jD_ H&0A}v\ˆ!ɇÇ„€†#)äú²îîÐq™û{ǼñâH‘90L@)4ð!…õ¸SŽDp¦cÇdHP G— 0¨d"…À`¨dš„vüY „¬R|ñØdÒI‡ÃarxÜ5ðГš €h&+z˜˜8(Hèȸ¸88AȲÉYd‰h‘#ÀÈh>æ*"3| ܪå °€ €` lêPÒY¦–"‚8~Ñ$ŸÙàìÉfš$h‘‚Ǩ!œõaÒ‰><°ZWAØ©LH7®Ë‰DI0âX@@+Á’ôã +KäR“œ9æw˜zŸú á$$*—ƒÀ€Ú8.ÇšŽNFÃ2¿¿NR‘óqp0P“XŽ@bv1L€ƒ¾ò0“‘ð±rrq(€†€ƒŒ ÈrPÚ%x€Û(ú˜8(Hèȸø8(88AWuÝE¢Šcàçöi$@ƒ3«”ê"€˜Â cú¸JE[DAD â(¦st.ʨ¥"E$L`m ÍœwI +:B.V0â ›d!±°—ºwàà€„€˜‹îq„'HNs„%JB37àa›`ŽƒÌEs„I‡t@À À +yä’ +öI&žP¡AAÔâ¦pDˆ0uÈ! ÄÛŠˆ)@¡º¡yp!LÅã¤ùë`xoÒÖcÚ¦~ oá—2A+/Óð†düçEÄb‘q÷X€ !öñºÀ3Gi€õÛgV:b:Nž2.F?z0 0@v%¤'x$ +b € +…û9F„!F¥©[„óÈN8Æ"ó¹D†8t@`Ü(yä‰ +öH“O¡BÅ8)³ˆºàA 0l"&Šˆ®S"œ"ˆ¤AÀCJ_x%#Ô{òL@ à²9Àáþ1дñ1ò°q’cÏ7í2p¤¾ê".ëB‡þ΀2ÌPöÛŽl8!P•\&LJ4907U0ÜV>‚(Æ9C,á8A @€ ÀövŒ' +B0Œ%KR·#†ÆB"óy‰\ˆ"Cs@€@(Ï"I!š"h¼H…s|R ˆØÙ)¾(!Àp " ‘W9JⲉSІ¤»í'´AØ"Œp'F~ìed\,Du!BÆ ‹“ý肾Ôq2o§yÂw„qâE +\Úô0 À˜ûuœa +c‰b{Gsœ¡ ß&€pñGþBK'+¹>#!@ÀÌXÑÿ1YÙ?q1‘6&Á˜á66ósD@QÜ¢!Fĉ\ßPˆ0`Ù¦ø ‚0p!€ÙG8DQÜV@Dxð#XL,'Œä¸¬ƒÐ& Fð<Žýb\ŽÜØ¢@;aAáás½8‡¡³€Xpøl]9Î{ýì‚Xå²×h 3ÂÁeíÌF!ƒ‘åµ1¤g8YAÁ ·:j“œá {‰L"]1¼„˜@ "Ày&Œ†JïIxG °`la ±·„ˆÌÆt (L3Ðá¶a €ÄUSº„LÜBl‘áþè!a;ô0ˆîöI ls` ÐiXBo:aSpŒ¿Ø,¢Iõ® ’¯ +(ø›·(‚Pˆ  ‚ãû÷&Ù¤`Äζ7rô !Žtqz"…¾~Y PKÞ<‘¹oóÁ4G ‘Q‚C€<øa»±:)E& {A€ mºØY›ÀÄXf1ì0Äàx (ƒyclÀ! #O$’É€ G?ˆß À pq¿t!vRÉ #€D:À=ý®0ÚW±ˆœ_q<Ò=Ïy¢ÀѰÎÀx%wò$ÈÈ!p8ë4DE:ÃKAPCRA%C<°QD‘€Dð AxÀäD‰! E9aˆhWâ!)“ë„eGeA€8€$|)šôGÂED@À@ÀGNYf0(ʼ g™ûðq‘q2Y™Óèò.?ô€p!Øæ E®9‹oÊ…þ¢B:^.""&"2"".N +R¹ {À!ÖŽì2YwÑ¿1HE¶æÂƒOøäìÈpF ˆð†p`vOüÒCBàPþi!Ç~h`Š`’`’H ‚ † $šIà‰À +@¬@ u +TA·Cn¨œ„EÇ"}wѸÆV0À„,˜1šôÃ`‘(D€C#“¬ƒ”à‘˜™·9zpàB)•°X¯¶œ —€t9€=‚ÛÉÛûuˆ´Ü¡¯5þ‡g¿àQ"ãnDPÛ ˆß +£¨&Ù„tOäÒb$HŽþh‚wåŒ%Ô!HÂp„# +ST¡@0@£à"Ø€3©J"‹tE ÐECˆþ1¬TtBœX%tÃ`‘h„2€A!“-Á‚–ð”¸þ;¿ù\”N%¹<ŽK*ÔUz¸à€.*LŠéÂðŧ›Â”„áƒAg‰'OœóðEb& ?%î8.Hx Œa[¸¨¿MÎeÍÖ#OûAX·ü!0@d8ˆ„!Ežð›ftžY¢çœYbH@2°•ŠE|ÝÝFÅc²ˆ@ Q`xÄÛª”P ‘·[á ‡ñˆOÊ~ƒ yb&ç0Ø<:IHžI%Y¼–øJGÁ4!à±OëÈ`ð˜„Ú™(“ÓdðØa‘N—x€„àè£8¥ÎY(‚Xâ‚ $’"ÒH)4÷Ùʼð@ãK·Q“ “\—¨»]ÌTTK’…®ñØ—ÜÀ€@0ÌDÏ6Ïm„ t;”@@çʨ D3‰ÑQ1L8‰zn-1±X¬”0凋Ý\PN ±ÆøAR=ñÈF[V¼VM¦3*×€ Ššø›Ä£pØtN˜âLˆˆ<3 ð ±oß++m‰I_‹/ zÑò׉ðHàbxH!6äÄ‘" 8°!Ä‘ÓDÿ—´Wð ÃÏ¢‚áÎä¸Îˆ¿¹ŠŠŠ@€ q HµÞv'î`@ f"g›g‡6ÂÀuˆð@ Þy†Ç‘'€ ç9ý–4Hb$Ç䈛¬ÈA„ç/þ  !¶šëîÂjÝiªVó‡1]îÂßç¯Àm€P 1À¢žx˜‚×AÏÈÍÏÁÍRIGÑÃÂBTz´àü;p'0¡Þz’±û·EË“«ç§D¨EL6VÄP0â$7"D*ccö2N±'’6É¢ûß™¶ð³=uˆð„ ó yyÁ8qþ‹HÑ"ÂD\#:Ó†2 z™kMæS˜Y]M?îÆ1(NvâTíï1”Ó¡`‚ Áãçó„8!à"<à'𠔜:ýŒÆãp¸2À Ãm9Ûàˆ€ÀÄ«²ìÓç2ÃDÄã¶sÑTŠ™ºD `c&qB$\Æc„ÔŠ|S©"ƒ¥PCaŌͷ…™†È€s?0…² ÆÑ%ŒˆL @(8³O2i'• jNv¥'Xc8óÿ…gZ$†”92„[üë:R0¤ãO(ä?¼Ñ¬mt`@  +&ßs›Ë€²ràAWÀ?"›éìrX‰Ãàp8 +„C§†:Àœ ˆ N)#îI§¾;ž œ Ìí¢h)$wш$ž,i"õô<ó lšaá Ì1ÂD!ø! D9„8Øs?0„q¶„Á˜ÚI£$˜@€ ë4ñÄÇuBê—œ©ŠFãÆ>>ýã8ñâთ v›FLE§ +0ÈZ÷ +BÞØƒ‡w78¨ ˜ÐàO€¿‰~Ûû¥ƒƒ‹ƒ„„ƒ‹€†®Nʬ7spW$çœqA›‡Á8™ÓPJ2G{  (wþ"YbK×ÐóÌ1²CáÁ8#˜c„ˆ2?¡ C˜C€q)\ˆA"Á€ÛœªU¢b1™7xV/ã.ãÀÀ0ðƒ×dmÓ f-ðPÀâÃÄÜE (0Ï’(yÿ[²¢-öâñŽFÆcƒõ×.bp\@S@ +àJj™ÌìvãÅ&%*ëÛ¢P@0ãÀ ïØ##+_\3Û.Û27 +  ,,=ô·î2 #ÍŽòlu§ë:o_#"ã1HP˜@y…%Șð<`? si9¼à‡cËH7îx´&€ 4¶­6‡ã2¾ñž˜"³ +8XÊDîÙç²Í9²Bs·ªÔç yÕ(IùZzç¦8 €´Ô):ÀXaPøwñÚ{—‚3òú÷¨åšXÂBœè ‡èHWñ“ðƒá‹fñvB€"³Ü q$EEc2°‚$À@°€H½¹‰ŠÎ‰‚Ê…±F„a*{œé£X@ €@Û{<˜p°, –Ä2?q`B‰##P 6s ¦þ1åXñòæs÷ wÇŸ,aY +:d†ì²:dù¡‘à (Ì”«’é'®ùâáÐ`¬ OÚ‘”ÿ‘A’Q’‚ÀjbìE>š"`‰:áÁÃ_/†^wìõˆy…'XPàxB¢ã?®ÿ»ÁÒrÄ?»Ôof–a!Nt0ÐsôI_ÌŸ„ŶŒDŽÈtB6{„q$Dfb²#‘F a xZb"b³" ²„aÌFóœ!:[c{½8ÀÐpX˜îÑ AÀ UY¶ÿçaá`àá&7 ˆ n°p¿¸øËIJbüÀÌÎYñžO ‡MÃ’ºä’0¿ãÑÐà&œ})×™"]YóàJÀ¸R8W?®¡ù·NsîøðT³¨#äÛ£î}0h³fâ6vàø@áØå|òI9ã@_§;Bw‡a‚ ¯˜ J„̦õ ‘ˆ…80XKšºŠŠ‰¹‰±È„L{Yšid–X~3r`a±†À@E†0Û-! +s½çRž(8¡³hàÀ L +…Ó¼àˆHH‰jŠh»8ÈÈX¸˜ nƒæK€¡ t|viI}ö`9?®;g©À`ðFèGŒQ5—GÍp؉"ë)Ff5ùåÛ}°YcÀh¨ÀF³‚ÊýîÜ@Á5êJùㆠ+RÏŽ·^8l7Zzú4C.÷]¼€8‘°¨ ·8а„'XZ˜ÂXŽû8(4B|_]8òK|`X¿`P0xLF1Žc³Ö˜Æ* pAœ²~ˆ¦ +bË¡|Zí‚%Ü—ÕO÷— à‡LfñOüjmÿ°R#׆2Öœ+'%x‹ù 0‚BÂÿcOIÒ"HøR<0ewŸ$8¦T¡Yq`C€=Ã…ýÿ"Í" ÀPï™í€tŒ!8± †xÌa ¨‚x"‚Xà‚H$’Å‚„Æëþ<f—ß$R?í1L0AÞii¸Ç1 abÉ\Á``à|&¼õÎ]Ö¦8—¥÷ØC‰ B#îVL!…3²¢|»[Ö‚?K’gÍÇîü°‰üË3çS¯sÔ¿wêÇ‘9¼1–½08d‹ -xAö @HY±¦‚)*¢J@Àp;#É|Yò؃*{÷9`Žé":ôCsÿ,Ò € Àð9žØsࣘBd(ƒ< UòC±ÁI'B € € OšÇQ—§ŠŠ¨,oÿæph8P¸"ow¬-‹ab‹]¿ör¡T â*ËÞTÄ+ +ÂPçSPïó +q£@h·½øb(ÀA‰,×ùSÏ‚ ’õñcDܹ¼£ÏÈb@pp›^Åû½'¨ïÓ^,8e¾aÎRz ð €T&Ìå?2¦&]8J4qÁ%IÁ`Tqø^;ÿMÝÿòé±K]0EìY¦ö27/Y¥™pxCÛsà¢#˜Bd(ƒ< Æ$)raÀIš„À(àDýÌ­ùÒ%H,¯ÿæ8@ +øÐM{Ö…¡å={Þ´— +@ÌT —¼iˆJ„a…-ñ—ã +q£’kýz1„‚€CP™ŸÂ—×à U5oÃìHÑ;·2j¼¼„à4|0ìð‹~…ü=' ïÓ~Œ8æ‚X(²‰> >ÑP`!ZYè² à@ª6i%4sÁIF‰¸­:8 ÙøJÝa…cÓµËÐ Žäù{9\ˆ `ö?lsáq`fq„ñ"H‘<¸`A‰"gA€Ø€3ž + Sÿ9°âH™ ¶š{­€€4`Ð&½ï BP–)BÔ’\$VŸ÷!0h…ä^`sáqf "ÃОH’!G‡ 1$LÈX0`þÄ)p$H]Oûµ +€ðl!4ïHF„aHb¥3¡ »0œßyëŽI"’ê žEÂÁ‡aÉœX]„ךš<£•ê`ñ!0 ÀöÍ|Qžöذ¶äëõ,aGùûš)¢€À\'þ–„é?¾žÏ4 Ó'DÄp €ÖÉÏ,J•!Ó£9±âÄ™9§¦ ´ÒKk‡(¾2l.j¿||¤-9C‰ab€  €‹âƒ’ †ß’»oïŒCäýŽC¡?˜Xý0Ä¿PVyëˆè$äßÜÄDEdFÇ` B#n2à,1ðrÃ?üBHÛ¬Ÿ„ž´Æc˜¤àÀ,»˜Ì&EÂWñÑqB„ؘÜ(À uc`‰i9ç<øð¢E&P™#Ð(¹€5ôÉo|ÁãH/3Ohû–8$šhtµÜt*T¹C?ÛôãÀ)¢E˜¨há%N™ ÈÁà'˜®Ù#¯&Âê-¾Øê¢Ií‚híÙ„4wÂ8~‰z ‚#ƒ#BΜçJ…!F@H€:˜Þá ÀDDÜ(6prÃ?üBKÙÊ•½0…/¾Â”µ5‚A€%p ¯9B-!8Sœá(RAÌ.¾ahG‚€@†4c3 +uç—  1Qä(ðÂÄ s@ß™%ÏØ ð°tYäìÛßvhkκh@Pö$©Ì©S% é¹çÀ‰\8R'D‹Pˆ€è(€Á…޹§Ø° € x0ØãŽ,:¢™éž˜ê¢‰íž(å'Ô(ÁP{÷~»Fm§1ž-÷‚ãŽ9 Š`’`† +0(À|p31ÎH#€h“À€Ð +°tö~͇á€dû˜¸¬TÜTTs +°@ò⣠´„ájBp„!*bš‡oR`8 ,@>º+‚ù(vžéšx(yxø¸8HHˆê ‚àÃQžçT¿$ +p+ÇÌH—&Hú¯ÑâI|æ§–üú– #—Æö¸'P•_=¹1'CŸI‘ Xr  H”˜YK¾mâh‚¼ Ïó‚˜4`âD¨ñ®Ùä¢h ŽYå,åÂ@J˜;xBsž§»Þ‰)í·úÓ\4Ó<0EÁPGPI¡ ¢@ }òI$ˆ»@ÀE‚ŶÀtö~ÀßøcbJ–yæš208<Ñ!a ÃT„çBT¥# +ÂT¤(À@€Lsü€ ¨¨q—ü˜ó`B^\8’ @j ÈlG çƉ8qñåÈñþÇ H¼pÅ?·åÐäÐñ¨ïç4¸“#G‹\:Y/S¿><8ðàPJÑ (†·ÛxÊ}^ ËÎ@@ +p2:š·„,ƒœêêž«À 0 àW<ïd9paË$é’¥Ó%i…§ûzÂP„á Bp”)Ãð¸à"#zR%ÅÚXx´Øs¿<<XÀ4Y¼0È@@P†2D\#*ææ;*"*#2)€àÃΈ €€È Øóî]8´%GÃ>% !Ó§>J]”%¥Ÿüù$B¡B¢Œ‰–œò €‡À˜¾ÐçÄ™JælŒìŸû¬Šü`¹ È0&#ç‡o×ûÇG3ùììÎ××gfŽx¤ž‚À“ÓòTùÏ  x²×kÑ!ˆR„c2$À!Æÿ|œ$dŒ<<< $DlD”)í]ÅDÄÆEÆFaAË…“y^±€£¼s¿<<¼4Xà  „">•ss•™À p ðž@¨€ˆ +àK2ã.‹ÑòÏ‘Ä4´»ðâF©» +g§<‰¨P‰9!Ë&v€2)€Â>»&i·“Û§tÈ3æÈÄè Äs_0þ;‘{Å¡‚>x2_1[þäÂè7`X ( Eæ«óž2P`IIY:%ä9€h3Àu€p_©åá! äbààà ¡ à!`àâ"¢†r»ŠqA‚L3dJúPcpr &3 ¼Ña¶Å\QÈŠ'Î)EqB¼Â˜„' +Δ”% R”!Ã0€@˜µÀ2( C2æ£d §SBËÇá 8`“Eœ[ëÙ‡…I“0lÉRXvÀH„ÁÎÁPd¸"™ÐmáDÜ,dtDd\%sjAAõ‚  ‚lè5Ï}Ì|‚CTÁyú9ðÒIžPjí@0,ÁxNbÏxà’ X¼ñ[‡@`F3 +PØf¡äà àâáàà`" à à a ¡‹ð´! DDFŒ!`Øt 'ˆ ˜Í ²DLDLSE­±q‘Q‘P‚ÀáåÆû bœ+8RP”-JB°§ +À02>aMê°õ´t^—: …•Æ*íž=a§¿V &@à±Sß;0ît\P¢ÖϨÀ‰€@W}ÝLN ‹J'‘I|Ž5¸m {G˜› +ór¿ädð¸ƒ@ŠX7'ÉúåÈ¡310D}%Ì4­›ßÒuÓ‡$‰™8lŸÜùá|:ÅÀ¨l<§ËxàÀ‰!À… 0¢yá B¢"1ÃÂ1„ ÛuE0€ €Á²‚ `Çu)²”Y”„/ ( C¥ÛÜáJ„)ƒ€0H6Pù:%Íîóœá BP¶-;Ï4°P*e §jVò`h |.&GtP0Á·S*Oè@¶DÜl$ö§De¬Ç$'|üµ•„u=n!4 \bVvïŦ„I­šIeÞÿ´~‘Ì¥¨\)Ô9a6hM?˜s«‡œá\ÏîŒI<(iÞÚã¥ö´=våÀ‘ŠÝ?Hòfq¨<@4İ•ðAÇ A‘G Iâ:©E +â ÆÇUÁÇÇ0uI€!t@€@8y§¤ÐÑÁ…O\$4aËöùǰE$A& Àe ¸æÓÜç8Bд%OsA”4QfO\RK<Ê@ð./1Úç“…hº6®àߨ^‰¹X:ïD„'ŒýÄm¼œD”Å–ÆT}ÎaD€2¸LF÷Ä&ƒÈ²žÉæ¿_9 a¬Ê#¸@GDTÞ-jM^_”i>Ö €ü/gŒp`À4ÓƒÝ?‹ GÞõÇMBbÁ% ¦Á€ )°2ÅS„)^q…! +Jq†©;ˆçRŠRˆ  ÀaÕT ãã˜s$GF r_Ç ,-±Ñ1›˜¨4TqÄWë]JzFß8çŽ †$Áà&ð‡e€Åæ¶2žgXJ’¦¡:r™‡Ä臠lo‚Ig‰À*Ð ` Ý]yÇV,yc8ïÃeþzö›PÿÀ‡~|)”¨tx$@EÈl@92Xû½JQÙÆ~2®(ú¼OëÊÚ4ðø0lÂü䎌¼Ö‡¥f9ÉÑë|PyX¬X4¢Ióž $¢ä`l¼™ ÿq‚‚Í¿Œ‰¸¸È˜ˆ¼VMˆoª”U@@" mÔS¨!˜sš!!Ïæ±¥‰ ìжÃ-÷w& +*mýÞó…!Bœ( 8Be>ÍïXÎã:ÂTÒ‹A}â’A¨wAä móÉ ñ0 +€X `‡èë®8¨«ÅXü8Ðß·O¨××MeÔ, +5ñÁ ¸òðF1½™ÏÜÂ’”z{¶aKºð€Pa¼´ æzgʱrds×·Nl8+`±/¹0&P™œxq&H´À @°„pʘ6~´†×Ýg;„!B0¤'KR³‰ã0 b@Qð†66`tqDG* " ŠD Ú¸‚ bËýAŒÿûOÜdLT ÀÀU”«í§JVw!¾ +Ÿ®9ñw÷Æ”-Ì!M08#` £yÓŒ(•7|  òVxÙõ–ß™GŒñIÑ °ðȲ~¬tüÒxúoÔp+$K‘r¿¹òy¡Üƒpþ âÚÎ;1ã@ ãáà`!!$¤!"çcâa"¥$!æcàb#$+æ@¾&pÍî¼91æÃ‡,8p À +HbF‰(qa…žŽ ˆ0<0<0ŒlÀtx…ˆaDx˜"Ø‘LgES1Ñ\¢@@ˆÓA}pI,sÇ$pPÍÀ”Om + +^N߸îЈ[a 9‹/c3"7È Àq›–»æ>’}†]Ù*yd©!@°.R#1¯/ÎYÂxôd$ Z- 4;¯ÑÖ‡pä$d?d>4äÀƒ )Ãàh +„@áq< ŽE!ò| ˆPÎRÂ~EÎÀñnœÊ9ðˇ,8p À +HbJ‰(ra…žDd ‹=³0 0€€±˜uBˆðÙãB@ `Š8”‚PcÚ¨ÜB(|¢$F²ˆç‚ã–¬ÃÁ¡Øa>t$$ ‘;7Ëay=ÂÌ1ÿ[Þv„¡ +Bœ Æ h‚˜ìÍßt20 #/?»®H0HH.4œ·¹ïCC༠"1;¿  6·»µ¤Àp F%—Qïǃ91à¢&"."bÒŠŽö^ +B2ÁÀ àz W€Äà#€HOì©Ì¦ë®\8p Àƒ`H‰"8aņ'Ôß +6S«ˆ€@APÄ ÎêPuBˆðÙãB‚8ôbF‚½öI|0Ž9e‘*J$‚8#Ž.³O (#¾º0ñ¢ÄŸ“ë!y8CÈ!uÿ˜B–„+cŽb€Âç[G×N‚q@n‡0Îwmç$H.néR¾šqãÁ¸°Žã`7 Ât3ÄüúY`¢Ç.È?úKk€0X <\±Aˆ µì"Qpä2 Y€À«{sáÒÉùàt ŽA, ">ør¤J:›6^‡>Œ Ü3p ;m:¨\gÐÒFÞÕÏ‹G!ø¬%8³J(vyün*_Þç¡}×ÄïF\®éNˆhà<`¬–Tpp OïŽX ‚9`Š`ºIçŽ ‚0€@)ÃÀX´Üb#ã"obb³¼Ày,/8B„aHR„¡R€€|pGó1 »Œ¹ÈÎÄ€F`óþ&æ Q؈ؤ8 „`{ë"Ór`R -öHÂdÂÛÿ‘˜†8@Ìzˆ4–YÒ 80.ƒs¾ã¢J(ªˆl```²R3Þ×!p;†à áÂÏSï¨É–>\%åĽ¬Eö&dfN}l)ËúÂ7ˆD=éB=qÈ…þ00(¥Î„8‘.dOsŒ/ ΄)êWq„áL °À иfð„¡:b¼áB¤!™³w,/B„aHB„¡b‹(8 €¸ïç¡?‘'õP8/°•ŒŽC‚`=ó‘ssŽB-vHÆ òö>ÿï!ÆA˜<ü!„3Á"E;ßþòUº´{Ç$’Q•H°@aÐVÑÜGDXvdâ 9üïúwœx’!D™" @. ‚ØÅü稔X?$'©Ç®Ê§^ûð¬f‘ŸþÝ Z+Ãt +‚álÊ|ŒÒĸKŸú­£ +B4Õ1I^ôÄÓ"õ€@pX ßÛ¨»ŠÎŒ‹ˆ»Œ ÿ ?=ÜÄ7£ÏƒQ%@—p ÀüÌvMÜWUR²A :„‰Û*¹AMñgDM0€ˆR„p8/B7/ÈBH¨U1=Ë$§'µßü±H`†'½Ñˆ}@ ü F€IŒtþݼ„T<|dD4$dDl<ǃÐäv8ÆžÌðéhåB\: ¦Ý +d{°,÷÷ž/…jú€@ K0Œ­{Zò¢¯X>½ÛK%Z Y¶ìó‚;ÈZ`À@¢ÃOo¦ê"ã±Ññp‚ÀàDfPÅÿ3àÀHÐ#LJ° @…¿3t -f~ÀÀÑBº¹B61¸ ` ‰Y‘0‚a~ˆßÜaY«Æ®+œEx]ƒ/“ü©Hh€ƒ˜½J `F€¤ =ŸÕû§F¾2""J2"2‚2–~iÒ\`,ÉÔ\ê"ä)(¡æáà$8„€+|w³íñôñâç°1£ìø 0.À÷0/9fÖ¼,É#(«îùdš9dÑaÁñy^qÞ1Âüáܼý"¤b +uë4A ñ°*‹ƒ7û\ñÃIðC$PGpC UÜ(€r ,³ÆÀÍÅΨ•Ê¦Æâ€&ÁÑ+26AØÿˆ˜œ@V%h+ÃK)È*ÿðÁPuõÿ*R`£˜‡ƒ€(ÀÊ€Ž@ J@w>ïãdH(Pµ2FÅIŠ?ñj3Y'AÎZSKÙÇÁÌ+€„0¯\SûåéÙÆchY[éà€œLæ?-V–¼4é"+Ø«Þx¤Š9dÐ" Âó´ùæ1ËÌ#÷%IˆH§^³DB€‡€Hö­÷1‘q9Ñwñ1Û@sƒy„1`m¼Ä§+Šâ‘6q +ø‘™†fÞGD^t>Îx$úSc¯·º õOs) ad5€€€ð`éÃx¾Ÿ.VJÒèÀ©ƒ% +kW}C ø–ÄôúñÑ 9i=7['@¸`QHxcs_I¹©EŸ×¼- ó„ÀÝ|ÁgŠYÚôwÇG8 3-N¥º’.83°jææ"/"#‘žß¸˜F@@ 01€7UÝB¦o¨!þÀÀsƒ63bÀÛy„Æo0ÈBÀD qdŒÈ¸ÈFNbŒZñÑl +Áà—ô… +"Ý?wÁT“ÇrIDQUA‘Ð +À‡ àGÉhõæjùš$™: +P)ÄÚΦ>"“A$ÈVRSzT/Ú,Aýè/F»“rOÏ^n ¸ÅÛý±)èàI@ˆ»««B•]V1òý&iânŒ3‚_1×°›¬¢ƒ`$ ‚³¸Ø®á8œa &ŸÌ'À& lmhƒˆ¡L9Êrx`ó7†<ö%wE¢‘$3¦lˆSg‡$P"@…5@ Àôüo48¶Vl‘ç—õ 4Câã)Öq¥1„9 η=‡€À0 tç³ê\Ùtbf`@Õ©O ,€ð àÎsÂfóàL"ð³È2ž²xYiY0QY¥µ3ÿ ˆýé…!µÕ¼aN!¿Þ·œ%D$£€ +À-/ǽà†ã‚#‚J>dÓ2utÉÀ!°Ñ6"<ö1PˆQ¾À€r~ó7þ{»ˆ‘()ÆtÀØD¡´AÀB@A@B@ABEHfL¸(€—£{qÝÖ÷±ðqôd’BM|Â0¢œÚ÷á S`<,€Fü×Á£"qƒ±l 6éæÅDÃ3¯Í<šqø((:a;s×ùÙ÷—p’·ëëŽ-p‡¥Ñ&f¢æáâ`!! $0=‘Æ£A‘eZ_Ïneh ƒ€h±¾œ8/rÉ$Á Q2@XàVÀ ^@<HôÇFýÅÅET}Ì32þö:ìy‘E FAâ!ÈX6€tr•Î""à Æ®R$ À ‘‰Åj™º$<¸Ò?I‘Ããh܆ID˜•žóÏRM$ÐTð*& +ÁY=°]„S×%ÚO˜èìb)‚$6ê*É^˜„¼Ó_žAO‚  À#Q=úmŃifÿß<š8#â»íS— =àžJ*‡¶*Úi¿¸2P€v5Ž=€œ +éóáT±?}¸²$@‰’%t%<Î9óX¾0s¬hïg„ˆ3ôdNïd@àlŒc„A¶ 9â;tr•Î""HÕÊ&!‚Áàp†H&S͈ Ãvø5/=&7„C¡pårc—Ï<I4‘A‘dØ +X Ïñ‚(%‚K(žÒ}G!Y‘8C@ˆN(@)`h[F$òO%Ì!$À<XE}ñ'HB +€½;„w;ôåÑÑu²róòp°óß +lÉï¡p¤$ÎuÚoÎ Ä á4pÀ)€®§¶J™ÿÛ‹"DðáÈ™6Ç"ç².x1åŒë4wÓÂ}/ÞÈ Á¶1ŽH€36üG`sUw(‰PÙ°éñ‚D”$<<<$TdÅ@ŒŠðƒÜ±…€„ƒˆ´È€1:ýㆠ’ˆ ‚Âà8( +À¬Ï!Ž8*¢ië,å„ô/{ÎÜ÷B €KaqµT)EÿDJMFD@M#E N ‡©§ã7 HÊÀxB2ÜöóórppÇÁÃf‰šÒõÕŒ¹J˜#  +Of¶6µ8¢€À¸p°<=ï¨ÒZZø±&DƒIáÔü¡‹^xôçÂ$ MÔF2Ûh¢‚Á¶UšZW·±3qø@€B /a„„ˆ"ÛsUw(ˆ@@¾Ÿ#U$óÇÅ%RYƒ`¬å (ÄÝO€µI—Pkc%YMÖ€‘ +R¦r‚ˆCcÔ¢†Â‚õ–XrQN¥@‚ÁƒîSˆ„D¨Ûˆ¸Ì줃 À’×ws‡`Ê$#u €8u—ÔdÑKŽ áE¶]ÿÃð€,10¸+r”}{óÓ‰€CAÖÁÛ‚ðY!?˜ Mò|—`åÃÞ=ƒ°Ø`¡s÷ët¼9T,(PØÙïjÿ4²..¾g½ÝA@A≆fN02u§1iÞaD§!4X.Ì-O53¤â0E~ˆ "L؉=Æ€ÙxiüˆA`Ps¼é4H3àç½f€rQTå Æ ò¹Ä@ˆHfꈑa‚F 76ùH‡gFD@¹ˆG?Ñz¦F˜¸QôÉ¿¸S  „Áp*¿){Þ§†<‡‹­³×K1IqÉQ†—FÝÃÚ'ŒÃ +Ì©‹:ØÏ6ûãÉ… …—>µ¥é¹A„ú¸Ì`hþ :0#áyŸrÉeG<Ç ÑD‘Á™¯˜j‹PTmÖ½ì$<ÓFÓÌ'Ú”„áhBp‡IAÀñ`È?ûÎt„¡H ‚pç½f€q®¢„@ +1ÑßA !@0câ”R„Q(äͪ¸y4$F0B;¤¿„@pu‚CÅ Úû_ò…h€‘‰ ‡×„) cœPp +!1á/£o«_ G=A_w7Q¨€ä£Ÿ2˜0 +0w… +žl–†#ÒÁPø÷¾ó1Y¿¢zî€|7@@Á´¶‹^ $C,àÀ…DP${„1®hP<€È&vø„4Á£†Ápšõ„èYrÓÜåB8ò€8 …|ÂPŒ)B`ßqÜQHCDÙÄ u)@®L6ªâVTB1L8ˆ[â!6À ƒÍûÌA†,A§aØNÏ9â‚™4ãî"ý}ñÁ$Qež,`8~ŽÒŸ6ž»ñdF.î0@·À€h `ðK÷Éhf>L +ˆE?þ¶ÆB„p0%êäÊ7Ú˜éYäËÀ@DFFAÅÃÄÀ@AÀÀCFH@@À@{‡$¦”@T6#ûÒÂ"2gõ„(_)í·¸ÂŽ,‚ $ Ýë B0¤qºPªç" ¸‚„0êTè¥@VB uÜ[B…©¤Æw:Bu¢€ ˜ ¿zNt4`P&Xœüˆ½ rGMa4‘ßÝpA•IFŸyqáÔ@`˜›Þ,¾yçÕ2DÍ +þç¬&ШKð¾¿\³Â’þTs +)Òì ¶ÝÝgeÚj0IB(ŸC@ "p`EŒÕ]ì¼ 4D„\<< D•$´|¬g˜, +P À‰2eÝ_°!n-J¦ç +`®i“ëÞ„aŒ À ‡ÂíÞq„¡BÈÀq9˜gŒ„â8 :•:)D†w„¡ji!œhÀp +L_Myž8°àtÖð$“ôˉLŠ’"Ë|8"DeLU$›8Ô<Ó^Vn_üsY¦ŒŸˆ[êÐlXŒn+OYgŽKýâˆ(¦_ɂɈ· +‚û4Ai™=õ X¼t0à ^Ùð`B‘"8ðáÁ*†ï–·õ— ðâÈF €<âx°MvãѯV<8p Ä91à@‰@àÁà4zâ"c‚ +"Ï»ºŠˆˆÀ†ˆÃqDÍ©L‚!3uÅN‚‚ó†`â‹òFDGbè]ìdF80€yð RÃ^`XxÓðð¢þàÃË«¦}o¦9`‚Š$Š ¼MG&î»>Ѱ()À¼•ç›`ÈH"¼½ÚšøÂÔϧ‚Hï–ͨ‚ gèÀ‹ór , †`½ú&ˆ³'3y†€@@=btû× pA$Aqч +8'+ÎT@AN'ƒ@„¡5ÀâÆ^n6.@ƒ Å®î*3@Œiï\ÇEDÜà@!ˆ°q@)ÅöiA ŸÍO"@°Íþ€1_Ž$‘‘ˆÓût ÇQÂЙ‚ðå¼ø ™‹_û|±ÃY$°dÑÔ O®œI׃€=XpÎ[ç>ê‘ kÇv\iÇ‘ÓÏsÄ‘týïL +Œ “˜«¡A h ;oÙ&F~PûªA’kÿs¬çB„áŽ0pañ´â˜YÚ(€Q, +àWÈ]6 ƒÃâ±8|€Á ,ƒD Å,Òßq‚€P@$½ŽŠ‰¹ÁS¦øs@À8þM"#Îñ"Mh*Q]Ù"QF wP…"¦E +DÉNR|€LƸd¢5êk©`dèvð­\ñÁadÁáÀ¢3“£ß‰2Ã`$™`0‚8®·žœi“,Z‰6üáJB„` DLmÊpsA„‡?I§FÃâDš,Ðrh’Ë*dЀ€ P8;é,‘†@ÈîþK iycÝa DÔG<²dH€`FF +ÕÁdÏA +‘ŠL_½œPÀ   ~â6ýÍdE$3ÃEQÛ\XT]îð@àÁ;£8_tÆ,‚Ë®` \6>0ù,²Kä³É"wCÀ‹AX4nDÜg&ŠyàF‰l¹±âÀ‰p D­pÛ_ípÏLsIph`éÐÃÕõÁ$A,À!6ܧr !EÆ@" 3 Ù&*"2."2+0`xW••@€(!b×w1Ù„ +C~ãÛŒèŒa¤†ˆ£–Lž@A8¡>l^í=°YeØH œ¢y8)²", H>ÃgS’’½‚ëÃTœ%ù‚ƒ€à£¡rt²å×Fzq¨/ûÞd˜t\øÁËòiˆ7;Ød^„páN‚:dH €ã@?2»äŠ8çž8çŽh"’ä“ •és†9`¢I ‹0`ñpHH/WÁx=±Î˜: ­É³²¿s¯ÂB$b’yœ2zô7ÐÀ‹¤ëë†J„Ì„BmôÆ(¤ÜˆC;‡%K*$ÿÀ€Ü9þë,I¡@Ì,²n£`x‹ b(áü€8ˆ‘Cœðrdd¾Ï4±!"œ½€@ Pü¨ˆ Lgq¦¡.5î°¼#8Ó@ÁcV†xk¶9,ûÉ·ßAÀü‘¾4]÷ÛdBþÃÿœòל0(Šóß•Ì(Öa$Þ6±?uQÚ;cކ úñXŒ§úàɃk20°XNîÐ\y øzƒÇ‰Ïߥ®O<³hg†J€Ú0r#ÅÅ)â“9¾¥Ø,y÷z‹!ˆ£ŸÉy¡‘Îðrd2/Y&–DšoQ€@ä‡ €4ÂWXèœR>æ":é„\.Èþ;­0¡'"ûô€¸‹×‡ÿ™ÓŠopáA$ ŸfsÝèH(€ÉtÐ9lÃFÓË!Ž*ï³ê?GL“Á¡`˜‚±Å}ðUÂfÑÁp(ÀŠ%ë\7&*•Ûëðd@Þçÿ,8$Dƒ.Lx±$ò10Dhø•J@LÆ@Q¶ùµRˆˆÀ€ì<ûÄ"Ѷ4@@  ‚¶áq¦Î"ªU@ `Ã%ãÃO&! ‰ŒÉ½ŽÌÆ +Þ a ݰ¡ˆÑg¿ñ?‘ž¿Û¦ +6’O"†Åƒ†¼÷ycƒBÐ.ü5Ú ¸ŽÝç!¸P‚ ñÏ=>:`Ž †áký`ªÄà,Œ?ûh: 3—ÚëÜDå}Gxñ`D‰XqàH ˜l`á$F¢°!é0Ü+nã¹JDF2 =°¡‚¬ƒ%"žDcˆ†Ôáqa 1á@DQø›×²A%ˆ—¬2"ÏÀBÀi÷x`p@èJÏ}â8†G½ÿ7,8ó"HØÐä\ „$Æ_ñbH¸˜l3o>½Å, V &5¼rÍÃE_¯{éôêÑÓp`°È`°üzÿX*³C Ð,,7ûHxñý½.¼K•§á·.Œ;_¦‚Ömψè`6ì&}qáP±\¹óæ‘1óÖ@¯$ °üaˆYw8¦Uð‚@ÀÇ¢p0g”mDö¤(låS"œN(A0ÎQB)1ÁÁƒ}ÜqEU $þD0 n†ÀÁ»%o”§(@@ƒâÂ…! S…À.T/ºZóǦŒáKBÑÊ Ddz¢(¨ òèÃ#a`€N˜úÌæwtYv¾pŸ¶|ZtdfÉHóôã‹SÄ,ÒúÇô:Rð¶Ò+ß‘*<øqáĸLJ×þ= Ã`D.Ý1IÎs-ËR"ºp¦Ü7Â%QÎAˆƒ|!€øgˆ¦@€„êlßwQUA#„„€x yïÏA™ ‚H$š ¤ `˜ñý¸]~qÍðE$B"C•DTE3tÈUa#Ë‹ƒ0< ÂØUèVÎ%‹dÝq.\^<rdh@ðyh~êÀ¡2dˆ2àDä$¸DÀ%›æØ¥?ø°àÐÀU#Q÷ Q€pl†ÓįÙÎs…1î@@0ŦÛmH‚†øŠgAÁŒ 4T!„& “mAC6§SœUP@…›0tý†üðÃDÌÐA]ÆÑÖH€€¨0˜_j ·W\0IDÑÇ"! ;ª"¢žèˆS@DpMEÐXQ@€+9ì² ç†8¨H@ðòÐýÙ"$ ¦D‰ià%L€–lò¼f¿þüØp°| dN4[áÆ ‹Ð¼CO6âþ1 +bpPÔDØ7È‚†ø‰µAlÌ2*á6Ìcõ ç~³I$yâI$¢3ls?3ò¿34IVcqí`° À‹áA¶ìï†L¸Ó=ë€q=’_¤šY0 c‚3óA–IL|(ú~`A`fœ _×$RGôYfI_ï³=wQ1œ@@,Ǿé +ͱRl€@P0Ýî‚BsòI"I4@€!@p¾iæ‹(äFƒ ÁN³!%øÈ‘ü4ÑVQmrÏ ‘A ”Yfð À´G;‚l”|GðÄ'Â…ÁÁpSñƒW>Þá(YG.ù³–„C &77Š×Ëç©ÿâ’ç‚*"‚cbWè¡âß÷QÙw1ØäS͹@£œãs¡IŒqM’â#*þî*"@ÀàÀ‚ßq™±" >‹4þË$‘%ØsꨪAA€àÚœ®0á0 ˆ"Èw„FCˆe„adÇw˜[ž…”Ck߇ .e< ÙOˆ¯xBt@°(€ ø`E8á,wÄ¡ZR<84…€x +B¸¦Ksß &‹h»èÇ…$8ð!@… Ò`1ÎJÿ¬džÆC Ø!Ì1‚"8€hŒ!ˆ’ò#*þî*"0``0p,¡k¸È‹è‘„€séeì²I$Ð8c{tõ2_$@€póõòY’Î&d[ÃáÄLÈBYÆ¢¼å†)10tœqiÞ S*n¯ùÆvÃ@4>¿ìíˆÞ´¤ëKC'¢ÀLHÀÆ^ÒeÖÁƒ_zðâD…IQ Æ9ˆ<Ëã¯y"#|Lâ¡€Á²`ÔÆÔWR”c’DOýšh`00cPC¼’Hƒ³">›¯$šY$gït$\â³nâ€0.÷7qQ9QaÂÑ3ˆq!4rÁ ”Q•px@‚½è +Ák9°›<àƒ $„–×¾æé ‡ö®Q_·—X¨ÓO&†$€'€¤Ï[2ÂÎÛ§¾8$š,úÊs¼‘D€Þ€H¿ýµüB2®P¯Ü`Ä áú +zN8²ÏY&–QU6Ô®H`ÝUô¨ `@9fëßÉ&šy YûÀr$D0¬Zö)€ "À‚Ó{s)Æè‚ƒ‘UQ·$AÁÀAÂCDEFHbü1f QF¡c­iê²&0ïæâà! Ç)ßïÂ&ž +ê߬ÈD¦‚,ø^ ÆË…PC_w×E%yšþÍÈyûÀ€h À‚6uË7ËERC +N—xFEyì"ƒ‹/,DIç‰ ®A*:”@‚À1õR"ÒÃ@ Ht2_¼g›Î„!Rœ`™îr$F0ìòÓÁЃÀ$^ÞÜÅEFDŠÆ:  äES¶8 ˆÑ 9è:@ÁFéÛ,˜e‡ŸÞ\|<8¤ÛLˆ6 ‚Œy&ÙÙ°²€€˜6 Ÿü±¨Zýç8Jˆq3í¢"Jb«’`¬P:¸£M7G    € XC¨Rø"Å ¢6À&N¨R„&çWC jªÔ?ðPK>‡U™ðeÍ( @8p`f{€@qˆ DsΑ?þH“I(ÐÀ€Îåñ dH“ÿé¤LƒA!ˆt2MŒ)€YöCÁÙ£åærL7"™sÃÙê²8\ +(‰Ü€ @`< ‹n ›çDE§$pBãbêÞ4Ò +Sëöp…¹Äå[ºb‘(DB9&¼àXÐÅ‚F èÜþ#‚Cá084$Õ5ŽÄ‘³qù#ÂÄ/xdçr#‹Ò"$òÊ9€”€ï¥‹¦‰Öxˆ‰¢ "):à2 €zU€0ÿTÍÂÅÊÁÀÀ@AÁ@@ÀA@@ÀAÀad qM„TB!ƒQJr  àç/ˆH³"Gþ/ÓHˆ8(HhHˆˆ¨ÉîÁ@”ÚS€l€ï{ñôÉŒL½=ýZ¸¸8Â$–0 +¤/òuöIÑ Œ0æ2oÎ(@F{nqˆû¹¼Lr%ƒÁ$Y‚ìÀÔ0[Ä=C«Åb‘H|v'€B§ˆqÔ +¡Ç?êéø€qÁfßÇeÝ£=7™‘q1¤ˆÅ +P}DÆÔLEÌD#ÊôÁà fh™0€€&`±ÕîÈá0¸Œ €@à° €À pŒ1ì„qÀ0#D{3ÆF†‡:Í4ÓBœýg‰4§H‘¢J‰CÀ@5ôçýñèjHÉNî¾÷äÀ€4 DwŒ4@åÄÈÎ;–Yf˜‚ CKÌ8ÁVKÓ@PÌ-ó DÄT<\§`‚ ÂÜO»ò4õ³ñp×P-ñ¸0s¤x€Q‚Áâ~ã¢.²/â¢ñw’Eb„@2@x |EÜÄMÌTTq “Ó€D&B\ PîEË‹ß^O ƒÂà¨D À`(€ÁÃqÀ @$@Afüˆ& ò•Ê! :ŠVXR¥©*AÍ.&½á¤¤v¿ó +ƒ0ñF—8¤9Šóß±111DˆG Â8³ÿaLëOÔ Ã X[ö‰ˆèy88JÄÑàXC‘d»^6‚‚>¦~.*¡L%Fá¦/ߤyŒ,<Ÿ¸è»¬‹ˆ¨üD]Üi"³„€d€ð%ñs‘71QѤŠxÊ#H@8Dª¨Yø˜x8Héù88(8„8"Ø`p€„@¿BBcmb,G,Ddh°  S¼Hˆ“a ŽÀ’÷i¼ŒCˆW–½Ž‹½Š$Dcç"†ø‹(ªh¶r!Cb-)LcSÍê€P€&ËÞP”-KN°ò"ÓdË €qy?¸ˆZÓjΉ΅N_tQ(c»¼!8•1‰Rˆ !€ Ž®®Í¢ d&B¶Â + u\j¢Ê Cy·™Qw·1("x !a¯ø ¢3m$f#&&&2* € ¸DÂb¢ˆ‰¿ŽÅg$ óœ‹¹ÌΈ‚#ŽØDdþDõ/³hsÓI’aµ”ßo8`nÝß AT‘G qSW|B“ÐÀ$€D.föP¦&ŒåKBT”$FbÌD° °ÕÜeÌDs!K‚!€c¹ÜåO ACd&À  >Á<1³ArôM?^ˆÑbKÈ ÀQü "3cn,-KR3œ# +B˜( °`®õÈÿ3„(9'DL˜@ P¬D0F8¼‰&Œ”å|Œ†Šê“ªb'ä49ÔÐEy„ùßÀ4Ÿ‚°¾tÁI aòI +¹éLaA·Âåïʳ9Ò¤!  a _áH<ۈ˸˜â€Â¬ˆ +çþÇ’I¦’4Ý¶D +"m«Šu)B·"Sg&Üæ`7Áí€n,-IRSœ# +B˜0€@€ € ƒ»K•_3¤ 9$Dûä…9}ˆƒc…äO(Ñ“uƒ1ËÊ“ÔàîÑÏK Bˆ(„Ó¾ ,¸|>m>ÂP•# + ñD +¹TçDbÛ.}{ààAC£lÎ…) ÂÄÆ K}ÓÄŸ·—q1ÄÀ Y0¡Ç;õžI%š$Ó ‚qÖPˆŒ `"!p,! Bœ)IBt +ðÀ21ùû•O4â0n$è)·( !‘l‰•@ @,=û÷ÀÁ'ÄlY"q¤_Êì Zbbb£ˆâ¦ÏáB@`|†ÝÞÿ ë÷¥¯8Bs… œ`Á†cý'„¿bb²bï@¡dˆáƒ€»¿ÓKI¢L 0…gˆ`ˆl0ð°†6À!Ú, ˜Yä!Dp$DTDDfT~„@Yö¬•Ë€À׬’IäHÒ&ë2TBÂs”4À@1À˜]xÞðŒ%‡Ž0¸Š÷„aDæeEÅ @@vJÓ +Sdo +>›´¿Ñ§±›ãš¾áyÎ&s0Æ_¤ñ¯˜˜ì¨»„RôAÃpÅGwö4°²M$aÂu""?Œ6Øaá`3 ópæ‹0l&a¡‚ o$TDDFTLTc~ÆeÇ„xh£Ówq„3‰mûÅ€ <ÏMìDF0‡.âb‹ +BPŒ' aE‚íîq„g:Zœ@0V;O<„@ +°¿º$Ï9ä‚ &ÒÈëŽXç‚AÎ Ä•Ê8ïÑ>F$wA„N€¡Õø±¤ÿÙf bwtA t‚ ðƒ%S¨®ˆ!G ÀËøƒÞ„3Do$TDDFTLTFD\LfD( Âx$,hÛ¸ˆè‹¸Èˆˆˆ¸¤gd¾2ˆ +xy鹈‹Å1õÜTQaHJ„á,sž†Ó#Îtµ€€¡ƒ€7ï¡@£0Œÿ:(®¹ã‚9*²ÈãŽã‚ȃœ®®ò$õ Hý Ÿ¡L‹e)M·Ç*UB#7 „Ã| ÉUN+ˆ‚G È‹ b/xNÍq$DDLdLLTTDDLfT0 `BÀXà ,›¸ˆ¬¨¸˜ˆäWübE áúÄ@À pï"gñÜî±$4“IpG I4‘AuIñÇ “X˜ èÀN ÁñÔ¡tmÓÛ%RC=sÁ$TU$E±Â9à€ˆpŽðɽ»ù(ùz%ŒB!ÇzëìY‰ˆdÂŒ6lˆ¦Ç ª…w(ˆ{—Ø’£!ˆýpsù¢É4|!Dq’M$²Í4ñ%ˆš €bcÁàÛ¸ˆ¬¨¸˜„'‹üG"„ o¢† Ñ!¸‚>¨ÒU$QÇ0A$ÑÃ]dsϲUb` À(T`øFïT]%÷Ó$ÖE=sÇ$4Ue]µÒ8ð@BÖD#‡|›áŸùI{y“€À%{s•`ÀÒã*$OŠá":¨o!Lu1¢ÂÔ¤+8¤' +QVû„0£ +,8ñ@À¡»Â„#¹Ã”Äã{ÎXóKæIü—„g¿ö0€À`°h»Š‰Ê»ŠF:ç†$àЀŒq¿5ç}sÁRE’Yf€À6n‚"Ž4ùN‰µbâ"± T'í€à@ð qQq1$IŽ"QÀs æñŸžaÂÉ•s{•0 ð€iw“&¢¹‹¨¡´H"n +ñÌ’_d’Hˆ"WñÀsÅN„!Îp¥1Ë]=Ë B„Mü³œáŠ^ð¬(À@Mî…g)Ó @ðõß“MÀ€ +À1€køÓ·^\x!D…J: €€×øˆ’Ë}™rœå„˜¸È¬ ¼KჃϸ¨¸˜È$ÁÄJ8tÍÅþg™ ¤ÌÌ‹Šý¹ˆ‹ŠŽ0Í`Kâ&,U)Ȉ«”Aa&pDã%ÜLTLEÌVVdmÄ0`b@<òYö„) +B“½aÆ›iÕ  +Á—öZNó„¡8J`$ ƒ§÷Îs…(€€(=5ôzpàDØp¤&€7€CzöçÍ(`@‘yA†,üIë¼ÈÊÈ߸Ȭ¡€£‰²\DVrSÂ×ÁÎû{,aˆ^ãE2_fx†@$wF„çûr¨0Àç²Ä—¬ÑË20@‰ä ° +  ò8O‹\\vÀ`$€@ ‚@ 2\EÆFBñbîn/î1@±i²D™4±ç‰‚0@8¢‰œ2M¾WÈ‚P:b„† yp -›ßw*R €øÈfu§(N”g Ƥ¡P´Ã‚†3»Î((åÅLÞ·¬-bÜË(R†,!Z >>ŒãÂ„áƒÆÀr±„$ɤ‰xÉäIO°v8‹N7‚Àà0‚@ ÐH, @ PaN¥°P( "^·œ'ÑAAkŒŸùg*J„)(Nœ¡jb‹XP2a_ʆQ³¹N)HŠÎÅÁŒ7N›:§ÊDR4@.îÎpŒ!HB“¼ç +SŒç9AŒJÉ^Ü <åïüdvOÌ8$’H(h  „<Ñt|×<Á A À ˆØrÈ«e(¢¹Ù8Š‚€$ë à,€5ànÉ-®'‚Àà0‚@ ÐH, @ PQ ŠyàŠK¸0 +Œ­=³ÁpE'NR¿3G‘A IÁqQU’H˜,0°5X÷ø`¨@!™(aÝÎ)H«•X €‚!Œ ºˆ"cª§ÊDE`<ƒÁp £8B0…!Nsœ1D)YÞq$Bm^߀Xå¦..+&& ID4 €d ! +æŽXã‚!‚(!‚a‘qÔ§¤Hˆ˜ûŠQ:`FP h9€Ç^saÇ(p`B‰p A +$ãYõÏ™ À•€¬š£Î9`ФÉbˆÙ£´ÐACEpEÔmÅPxX° Âßç‚¢3 à8ÀÀ ø»ˆ¸ŠŠŒ¸ˆˆŠ 0ˆ ;`p´,fÀ‘``h¿‹‰ŠÌÉý‹Ì Ì[þä—0„"¸Œ €€ð$ùFzîb" ±„±h@ <X€t>«9Â0œã BPŒ%°áÑÀqÕÔR²ÆwRˆ \ âÀ †S¬ç b;Œ%IB¤-Ñ#i&ûÏ• >\ì ·½×0E2U¦‘眥„!HF¤a8ZÃD†@`À`êó…$Ø ž¼ç7ÇpGEPAAD*„A0ÆW ‹Jr¸C„‡É%•G,ˆ@€ˆ8çô—±ˆÈûˆB 0Óˇ ðµìDTb‘Q€ÐZn"bîb¢¢¢ /‡Gqår*N„@0g9Tˆ"0àÀ«…›{qŒEúã¢b!D"Ûl*’M;šà€—!÷8F„a,jœ‰wŠû‰‰ŠÎÄG @ \D$¬\S ÀîY¿&ãÁáÄ(Å2Blq Ę<”n"¡É'’%D–áÇù @A8Wäˆ¸Ì‰ŠŒA‹ÿ ( +(—÷1P*(¨ € êäùJD&±›`q fÀ„0G!„Dy ˜¦*Ò½¹¸Æ"þâ£b(a`’àbcÑ ‚΃–Ig–b4ÿøbgye• #Pâôñ%`,à J€w<Ó~L‘"G‹!ÃQ!Žt1 ÎðCsž Èç¨Ð½'“ô‘2,'?©ˆ_’"ã"**1 Ïÿ‚@Q@Ð<¿½Š€øxC‚/dY˜€ØÍ°r fa€Df3g˜IŽ¿‰ŠÎ‹ŠÊ¸þ@p€°‡¤Uˆ*!0ÞBNnp0Ú¹J€F ÃcŠ%Z"À À3ç\i„wÏ,rAqÁAåCn;áp§èh¾‘£Â‡Qùìò"#'äc@€† +~8’Ê–hˆÈ¿¨€0 (÷Ù [ó1›oá„"EÞr f`Dfy³ÎHäT]ÜLVT\v+?åÀ ` h§pˆ¦[؉UT‚€ÁÂn µr•‚7M¸¨´—ç˜#»Áb†(MôgHNs„ahDr¡„n;áâž&—±r€G‚ÿg‘"''fˆÎðp!ËôÒ%Lþ ÀŒp¯½’±æy¶þ„"QÍàq<€Là ä„eEÅÄÅeE\)‰=°((ðñi²L‰*N³"y$A3C¨D`BˆöBF8‚0ò_cŒÏ\^ ؃˸ͻ¹ŠÉ&T1™ïùB!qQ7¢;è€À†ƒÅïñ"z„#€  ÞÏÑ  Á!ÀQìI&‘eŒšyæ’ùæßì0Â`‹3Àq<Íà äŒÊ‹‹‰ŽÊG] +HxŸSï\Ži&D™'YRy$ €Àp€r¼É|’i“x˜@`‡ÄIÉuŽe{þ"ðÀÀv‡þ]¦îæ+(™P†G¯æv‡?D h<ò|ÜABÛhB†ÀA üÅÝæDB $~ÆDDÄLLVLd\TDùæßï &‹3Àr m†yŒÈM4‘‘±Q˜ãöh,[ï"î$‘QHÄ"-sAÁV ²-·‘Q‘QÂ@ °ṳ́ Nq|H„ ‚¸`%À§³Íia=ýxܾ ˆu E‡,ç )3ç + à‘„ÇÏÞ0„å +Bp@£< ïÞ„!BŒ! B0”! À#ó1æ÷›6ȳÌr m†yŒÈM29âI,Ñ0`pR`%9ÿòbÂP„-Žc@| ›ì'ÂPÒ (”Õýȳo"¢#2"2Œ €`€·îDY.äZÛy™‘œ „€x .b⢖"iz!£¾ëᜰB(s–p…ˆ€æÁpëó¦y¾Ýå +RPœ' ,ÄÄDÄDLDtLtD|aŒÌy½æf !‘g˜p ³ÐžÌÍàfGü‘&ž"aÃðB‡ ×ÉRA$Ö <˜Ñ ‘Ÿ1Á0CÚaóÃ$$[î""#2¢säi€ ð5ÿHìdmüTvEó + 0…€€|ÄÄÅŽi‘HƒA_ÇŒ†…#ŽrÎá@¸6·Xcš…÷ÙšÅ'[Æ €8 X /ob"¢j&+2/—ûf3cÌÌy †ØÌr = ìÌÞ@&ù"M<ˆA3Ÿ„ÁÂõòcÀ…$ÊY:¤3 `<žôà 0%T¡|xQ# ïä’DM21 B B€8甑ّ7qQYá…Ç€'ÔLlXæ™4ˆÐ¯$ È¡ LJÉq  KØC+×sqŠB-?1ƒ 0Š:É4ºY‘$B#ºÀ³±æÇ™˜mŒÀwcªª*º…MÈ€€! CvJ§Q1ÄÆw#À…EN +ˆŒ¨ +à5ˆyáÁ$J‡.ì²I2#IÊáqN¯e$DvD|VDB*ø†û™]Ù""±X‹ûˆ˜Ì0<àÀƒ@ñÿãš2s‚c‡dB:û!1ƒ`PÀ,zb"¢j"323 ý€æ6‰†D2" 3€{ÝUEWP©¹!ü©§’Iy€†À8å–cA”hH,$à Ãà ÀŽóŽ!‚$‚)#Žàid’Ddg?ØD—•ÖaÉ2Hñ’H@`#X§ +@0 ÁÀçå„!nzŸç8Jà  "à:>ƒ +Ÿnð„Å)ŠfÎÂ&6¨‚ˆ¶tZpà +(°`Ì!Â2„!JR¥¡ÛÆ †"a¢Ù¡# 3c|ÝÔâŠê"o”B`Éw@AÀ,!XR’@XOãà*~4”'xbS”#:Ή'±"$0HÅò229Ë™×q—qÙXC/ÛÀÄ‚KòΔÓriªi*A@Ð@ € €šôÊÿ„!ÈO DcÂÈ@ˆËOhAÀàFûÎ0![–„g)æs @€QP˜7‡Œ3Ù£€x ã 7„",Ã2DÄÆD]ÅEEDGÀ,ÐµÄÆ3%€€‚ÅÄ7ù‡ ³í{{p(¦ÔPD&æmÂ!DgZâ.#²"³2þc"8@°h¿,áEŠr 'šjwàðXØ`mùÖó…!ÈL`ˆÆØB""ZhÀƒ€|Dóî0!(yD®®ã:ΔE ƒxcØÃ<9˜8s c=‡¶ÆòDÅDDÄEDDDbˆ‚à4Z㣠 +a^»ˆ”p@ —¶ÝÄDI>L€€úÉÖi‘'ža)ü‘’ùü±¢@`89g$`P‚_ºÚ3x08Ø|M¹Â¥!UDBmºŠQÁC8¡¼°²‚ÁÂφDÑ„o¸R•†$#3à ‰ÑCfáHH€‹1¼‚$; á">ÄpÃm˜`á0Ì@AdŠMÎQÇ$Z Àtœ Ãõ1Á\0IñÁ@x3@ˆ?<4B͆la¼Ç˜l;y˜pÊ*¹Â…aˆ7s© ƒ`m³6‡6Ž$ˆ"sÔIf’p)òS¨lÜ! f7„ 16Џ¡L“QLtDlà †`BA&@(T/«ŽH´8wàF¥Ž8â‚#Ž ‚ÌÞ ÀÉ~xh…› ØÃy0Øvó0p\¥r„B@ Ì›åAæ  ‰ç¿ É""0¬Jíq€À ŸD0°²Â!ŒòÀ!DǪAm¾UA0Ü@„ jwr«…R„BfȬÁãJp@ &BëÎ0„#B0„"|F„0þl,Á†80xÃ=‡˜p\¥r„@+À)ЪQ„ 6ªUuQJE„! +j•Ü1¨^p„ ¿! p\ Ÿ†3Ðï 1ìa +&â•H "67Ê¢™¸€B>wr«…Wn"úÌÆXiÿu|FÃþ`Ä¥ˆª †ÁN)UÁßærÝJ¥"Y0Ä(6Æb©D@Á¾)[ª¢”F‚X"‚h Ž *‚Xä¢JxÍÿx‚AH+5Œaã40„{z+Š'H Dcåd¦â1 q]gši$–"I:!AÜLBp ·I†:â$PÝ4¿0|6ógŽK,Ó"B€!À(u–Y¦–H~ @Àr²É4“JŒ™'x`@`!ŽÀqERˆ ƒ7Å+uTR¢Â0”% +N”+N! ¤aFßÚ p +€DÇ8ªr‘P>wub¸ ‚c‹œ¬ŠTÙbnW³Í4’K~€€ Pqý4` ÀàuöIb#$'¡›óÃo6xä²Í2$ 8 +e–i¥’€r,#Bp”%´-HÎó€àX˜ß ÂÃ~ù""ã#¢bb1Æ_Úÿ€ð° |Ú%7À‚PÊc9!¬€ à +æžY'‰YfŒ8Ÿù,ÒI4ÒÂKð 8SAIáà3ÙÛPEpI%RGpG¡â3¿fx»¢RŠ@@Áºº‘"LC»” r,#Bp”%”%IRÓ @ €€&| . øã’x‘¥–t0‘üIâ p +?§ù?Xb4óÄÖd0 1,O…ƒÀ‰ˆ‹Œˆ‰‰ŽŠNuʪR¸¤ð ` 3ÀDÏ‚à8@1 ®Ì8R%C—$HÑ!Ã8#1°;ög‹¸ˆWVA3n®¡„È_ÌLR +p,!B0”!„%JRP¢€À€`ÄÂ#xfz#?6dE¾¡Jî)Nä†0Ç ?ˆhW²¢Ïyâ @/Ϧ` €È}yÆ„¡(N„¡F…lÛøvnÁ<Ñ"PV€˜#€ð å»qâP¡œ¸°$@ƒ80á„   ƒÞÙøì‘HþËo$–X‚ €9Iîp„-ÇCßp,!B0”!„%HRP§@&.øDo ÀðDgæÌ„$AàfÞyŒ@@QÄŸ¢4W²CK<“Ä‘@@ð,X@€ϹˆŠŠ‹‹Š‰‰ŽŒ ³oáÛahÏ4H”ƒ€ˆàÖ#€G)×…tåÉ(¡Á‡!€€€.#ÀïlüvH„$e‰7’K4A €9Iîp„-¡¯€o’I4“O$ÒĈ€€€€qÃä$ˆ#½ŒÀ„"0aÜE\å +wp€`ñTqJuq£gDEE‡½€€BÁ£Ûs•”x„‹=›ÓI4“P° €`˜{o–Ê£¾yà†$Š cˆ0!ˆ¢Æ„'ÆgŠ©¦ûŠ+•H" € @ »¸¼F–oóÌy°á@ ˆ§*‘( ×W9B !0î"(ç(S»„ Á˜ª:•UÄ ÙÑ €„Àc  €cç)\©ä§"Aƒü!DìØcÉijJ +° +°Wùÿi)Þð„a +zCB ‹œ[ž*¬…O¸¢¹A‚ gþt" 'n óóg˜ˆ’&**18ÀÄýÄÄdÜEDâPÎ(ŠôJu(‚ ƒbmº•TÈAÃc¢¦â †û36ÀØC „""„4{ÏxI•Á†Yš´ÜÿÅgF3žˆÂÁŒxDÇÂÃ-'8B¤§B„!(Zˆ€ò€t “ÎÂ#æ`n óóg¸‰aJ„ X(d.üáR‘œá(FQÅQ¾‰N¥@A`Ì""<üBDp„1>öfm™„ €0ycr«©UÕ+*+‘y“{s÷ІÆ xˆáÞ< ¢cá0€M$uÁYtÁÁA5‹€av„¨k™ƒo ÍŒ!Áï4!ˆÍ0IPE¡Ð †€„»Ë0E$PGEøÃÆ@<Bˆ!±™¶`d@fa±ÿlÃ0ÙŒ`d F À8y,ýbM,²D–Y‰••ÅCÀ0€xðŠ#?yA›pžD¹±áÀ‰"D8ðàÀ"XR â â+=ç„øX0o ÍŒ†4!ˆÍ0IPE¡° „€„;Ë0M$ÐOpEøÃÆ„<…7˜lÑÌÃc"ª«¹E(®9Ê@@€ AaeÜL]ÌdTLLDdLLQ"³¢î¢¢c"nâ#™Ð€` +`žDgï! °n<‰tcÃDHqáÆ"H°¬€p” +`”$ÿžGá`Ào 3c…€ÈL0üÒP„' +pXA@ áßÂ…9èwó„¡üÌxB3Ù´<à @€€#¶fU:R”R¹MÄŠD +°yëø˜˜ˆÈˆ˜˜ˆÈ¨¸ÁÌ„B¼I Ìòl†1³àBh€Î`ZÎp…- ÂÜ$BEÊÞ0•(’A—÷p›3co 3?ó f…ቀˆ°0Er€cqˆ`ú•Js©@B`c"(6W)]o$–0@‡yc$åè@Â8êXÌ0Ç·áä8aÙŠ‹‹‰ˆÄDØFBaDgàÀ„…aÛxÀo<öðÃÁ³d=†0Þ<^lžiš‰€^à˜ø "ÿ9p`A +`@(`A¡€7ÄJ]JV& ß~\‹›®(@A@cwq€B`€3¸ & uÝÔŠ•¼‘Qˆ€ +€€÷ÜþÔLD\Lf"6Â2„"‹? Bˆ3ÛÆn <Ù¿Â";ÂfÇá˜ðÆMX$4FÀ€!ÑÇ ÁAAÁ$ŠD ŒªWUW(ƒ…Œ  Á0⪔îâ‚1÷H „ÃP€ &n•‘ŽvHè„ u€°,»»ž‹ˆ‹ŒŒŽ‹±UOnRª ‚†è&ÛØn <Ù¿À  ;ÂfÇùøc%†p ð8°.D÷8Â0„% „# +BDŠ`pž`y«Ø¨¸è˜ˆˆˆ˜¨ÁÈšr à8È’I4_¦‰ ~öÃÀ°Â'ÇP‚cn”ˆ±Xæ 7ÀçóæžIâDš~7D‰{Í4’D @B +lØØoÍì?ˆ#¼!扰ö36Ã2(€€"Àƒ;º”¥U)I<ÏÀ~ä7ÇGPApAÐC0HHÈÈ¡€€xýŒËŠˆ‰¸ŠŠˆVéYqQ‚²@Á +îäBœ›¸¡!ƒT)Ä@@D0c9È(êå+¸~xÑ#%ï4’M€ÀCÎð8`b ÃoÍì?!…¼!扰ö36Ã2 BŠ ‹o001ŒÉâNH +¤OÖr-xp!Àp!À40 ÀˆHȈP`À <Ÿ±™qQ7QQŠÝ+%T@ † È€€„  +îä\Alw@ L mL>`#y ÌÛœ(¤[rª” €€` ƒ†" o~fóLl†ÏfÛa¸CŠq¨ÝWq@‚ + mÄ£74¸žwW3Û‡ P!@‹ 0#@…$ˆŒ„ Àxƒ_±Q1Q11Q¢o%P§d1œPA&¼rM4²Æ&å–0€`N)×ÉC36fÈG¢oaöŒÃØ€„D*(D7Po~fó@Àl†ÏfÛa¸CŠtJ(ÝWq@‚ƒÜHñ($à“¸0?¿Ó‡ P!@‹ 0#@…#’H‚ à8WY¢EšYd(nh•JqA1ñT&mI¦–Y‰ºË@pâžÿ0flÀÍMì!‡0öBC€ˆÃ†B#p Çùï0„ d6Û@ÄvÃ28d#æz!¿„@DÂh(H:€E¸ @¬†¸àŠ ‚ `‚`’ ŠE¢ "ÆUTÕÊá„À |"&ÚÄGܧ!\¢¹ÄT:„€€ÆÍ•™Â9  1Þ„ høüÃÀ͇0ÚÀp Çùï8Md6Û@ÄvÃG „|Ì0D7䈜‘Œ +€+À$›w(åEqESjáG€B`á6Ô9]H6{áÇ€ˆ Q9&š@avÀÀ ãòË,“ @Ñ<7ï3"0æB0p{ Ù£™çÆöBùàqg9E7Er‚ªP vÕÕ\¥QP£º•Å‚` Š6`ùƒ1žf1†bæÙ†ð†-°–„¡J(  äàôTÆ×Ìᕈ=‡›<Ì|Ø0Àn 7°Íš9žy od&ŽÀâÎ9O4’yY ÀbÏÚp‰„::&ý™áÇ,Y&–$ €Ö4H½€#í˜Ï3Ã1ólÃ1€Cm„ð À…DÍ„Á°ò!á.ø þ±¼ØpàAEì³0ðpp0”!ÂP„# +BP„%E YÐÀ"^ðœ1Œ3` \ No newline at end of file diff --git a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py index 47048c7c9bc..3b0d69bb79b 100644 --- a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py @@ -1,3 +1,5 @@ +from pathlib import Path + import numpy as np import pytest from numpy.testing import assert_equal @@ -9,6 +11,10 @@ decompress_hdu, decompress_tile, ) +from astropy.io.fits.tiled_compression.tiled_compression import ( + _buffer_to_array, + _header_to_settings, +) COMPRESSION_TYPES = [ "GZIP_1", @@ -31,9 +37,6 @@ @pytest.mark.parametrize(("compression_type", "dtype"), parameters) def test_basic(tmp_path, compression_type, dtype): - # In future can pass in settings as part of the parameterization - settings = {} - # Generate compressed file dynamically original_data = np.arange(144).reshape((12, 12)).astype(dtype) @@ -49,24 +52,7 @@ def test_basic(tmp_path, compression_type, dtype): # Load in raw compressed data hdulist = fits.open(tmp_path / "test.fits", disable_image_compression=True) - tile_shape = (hdulist[1].header["ZTILE2"], hdulist[1].header["ZTILE1"]) - - if compression_type == "GZIP_2": - settings["itemsize"] = original_data.dtype.itemsize - elif compression_type == "PLIO_1": - settings["tilesize"] = np.product(tile_shape) - elif compression_type == "RICE_1": - settings["blocksize"] = hdulist[1].header["ZVAL1"] - settings["bytepix"] = hdulist[1].header["ZVAL2"] - settings["tilesize"] = np.product(tile_shape) - elif compression_type == "HCOMPRESS_1": - # TODO: generalize bytepix, we need to pick 4 or 8 and then cast down - # later to smaller ints if needed. - settings["bytepix"] = 4 - settings["scale"] = hdulist[1].header["ZVAL1"] - settings["smooth"] = hdulist[1].header["ZVAL2"] - settings["nx"] = hdulist[1].header["ZTILE2"] - settings["ny"] = hdulist[1].header["ZTILE1"] + settings = settings_from_hdu(hdulist[1], original_data) # Test decompression of the first tile @@ -214,3 +200,42 @@ def test_compress_hdu(tmp_path, compression_type, dtype): heap_length_c, heap_data_c = compress_hdu_c(hdu) _assert_heap_same(heap_data, heap_data_c, 9) + + +@pytest.fixture +def canonical_data_base_path(): + return Path(__file__).parent / "data" + + +@pytest.fixture( + params=(Path(__file__).parent / "data").glob("m13_*.fits"), ids=lambda x: x.name +) +def canonical_int_hdus(request): + """ + This fixture provides 4 files downloaded from https://fits.gsfc.nasa.gov/registry/tilecompression.html + + Which are used as canonical tests of data not compressed by Astropy. + """ + with fits.open(request.param, disable_image_compression=True) as hdul: + yield hdul[1] + + +@pytest.fixture +def original_int_hdu(canonical_data_base_path): + with fits.open(canonical_data_base_path / "m13.fits") as hdul: + yield hdul[0] + + +def test_canonical_data(original_int_hdu, canonical_int_hdus): + hdr = canonical_int_hdus.header + tile_size = (hdr["ZTILE2"], hdr["ZTILE1"]) + compression_type = hdr["ZCMPTYPE"] + original_tile_1 = original_int_hdu.data[: tile_size[0], : tile_size[1]] + compressed_tile_bytes = canonical_int_hdus.data["COMPRESSED_DATA"][0].tobytes() + + settings = _header_to_settings(canonical_int_hdus.header) + tile_data_buffer = decompress_tile( + compressed_tile_bytes, algorithm=compression_type, **settings + ) + tile_data = _buffer_to_array(tile_data_buffer, hdr) + np.testing.assert_allclose(original_tile_1, tile_data) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 7fe5f73238b..da0c1b1a940 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -449,8 +449,8 @@ def _header_to_settings(header): elif header["ZCMPTYPE"] == "PLIO_1": settings["tilesize"] = np.product(tile_shape) elif header["ZCMPTYPE"] == "RICE_1": - settings["blocksize"] = header["ZVAL1"] - settings["bytepix"] = header["ZVAL2"] + settings["blocksize"] = header.get("ZVAL1", 32) + settings["bytepix"] = header.get("ZVAL2", 4) settings["tilesize"] = np.product(tile_shape) elif header["ZCMPTYPE"] == "HCOMPRESS_1": settings["bytepix"] = 4 @@ -462,6 +462,30 @@ def _header_to_settings(header): return settings +def _buffer_to_array(tile_buffer, header): + """ + Convert a buffer to an array using the header. + + This is a helper function which takes a raw buffer (as output by .decode) + and using the FITS header translates it into a numpy array with the correct + dtype, endianess and shape. + """ + tile_shape = (header["ZTILE2"], header["ZTILE1"]) + + if header["ZCMPTYPE"].startswith("GZIP") and header["ZBITPIX"] > 8: + # TOOD: support float types + int_size = header["ZBITPIX"] // 8 + tile_data = np.asarray(tile_buffer).view(f">i{int_size}").reshape(tile_shape) + else: + if tile_buffer.format == "b": + # NOTE: this feels like a Numpy bug - need to investigate + tile_data = np.asarray(tile_buffer, dtype=np.uint8).reshape(tile_shape) + else: + tile_data = np.asarray(tile_buffer).reshape(tile_shape) + + return tile_data + + def decompress_hdu(hdu): """ Drop-in replacement for decompress_hdu from compressionmodule.c @@ -480,20 +504,7 @@ def decompress_hdu(hdu): tile_buffer = decompress_tile( cdata, algorithm=hdu._header["ZCMPTYPE"], **settings ) - - if hdu._header["ZCMPTYPE"].startswith("GZIP") and hdu._header["ZBITPIX"] > 8: - # TOOD: support float types - int_size = hdu._header["ZBITPIX"] // 8 - tile_data = ( - np.asarray(tile_buffer).view(f">i{int_size}").reshape(tile_shape) - ) - else: - if tile_buffer.format == "b": - # NOTE: this feels like a Numpy bug - need to investigate - tile_data = np.asarray(tile_buffer, dtype=np.uint8).reshape(tile_shape) - else: - tile_data = np.asarray(tile_buffer).reshape(tile_shape) - + tile_data = _buffer_to_array(tile_buffer, hdu._header) data[ istart : istart + tile_shape[0], jstart : jstart + tile_shape[1] ] = tile_data From 992c03b4c1d5edbd967773252170a0c2acc3b1bc Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Mon, 14 Nov 2022 17:50:19 +0000 Subject: [PATCH 03/32] Fix up some tests and let gzip fail --- .../tests/test_tiled_compression.py | 33 ++++++++++--------- .../tiled_compression/tiled_compression.py | 23 ++++++++++--- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py index 3b0d69bb79b..16c4092aafe 100644 --- a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py @@ -52,7 +52,7 @@ def test_basic(tmp_path, compression_type, dtype): # Load in raw compressed data hdulist = fits.open(tmp_path / "test.fits", disable_image_compression=True) - settings = settings_from_hdu(hdulist[1], original_data) + settings = _header_to_settings(hdulist[1].header) # Test decompression of the first tile @@ -61,19 +61,7 @@ def test_basic(tmp_path, compression_type, dtype): tile_data_buffer = decompress_tile( compressed_tile_bytes, algorithm=compression_type, **settings ) - - # TODO: determine whether we are happy with having to interpret the returned bytes from - # the GZip codec or whether we want to set the dtype as a setting to the codec. - if compression_type.startswith("GZIP"): - # NOTE: It looks like the data is stored as big endian data even if it was - # originally little-endian. - tile_data = ( - np.asarray(tile_data_buffer) - .view(original_data.dtype.newbyteorder(">")) - .reshape(tile_shape) - ) - else: - tile_data = np.asarray(tile_data_buffer).reshape(tile_shape) + tile_data = _buffer_to_array(tile_data_buffer, hdulist[1].header) assert_equal(tile_data, original_data[:4, :4]) @@ -226,16 +214,29 @@ def original_int_hdu(canonical_data_base_path): yield hdul[0] +# pytest-openfiles does not correctly check for open files when the files are +# opened in a fixture, so we skip the check here. +# https://github.com/astropy/pytest-openfiles/issues/32 +@pytest.mark.openfiles_ignore def test_canonical_data(original_int_hdu, canonical_int_hdus): hdr = canonical_int_hdus.header tile_size = (hdr["ZTILE2"], hdr["ZTILE1"]) compression_type = hdr["ZCMPTYPE"] original_tile_1 = original_int_hdu.data[: tile_size[0], : tile_size[1]] - compressed_tile_bytes = canonical_int_hdus.data["COMPRESSED_DATA"][0].tobytes() + original_compressed_tile_bytes = canonical_int_hdus.data["COMPRESSED_DATA"][0].tobytes() # fmt: skip settings = _header_to_settings(canonical_int_hdus.header) tile_data_buffer = decompress_tile( - compressed_tile_bytes, algorithm=compression_type, **settings + original_compressed_tile_bytes, algorithm=compression_type, **settings ) tile_data = _buffer_to_array(tile_data_buffer, hdr) np.testing.assert_allclose(original_tile_1, tile_data) + + # gzip compression can be non-deterministic i.e. compression level, + # compressed data dtype etc. So this test can't work for GZIP files. + if not compression_type.startswith("GZIP"): + # Now compress the original data and see if we can recover the compressed bytes we loaded + compressed_tile_data = compress_tile( + original_tile_1, algorithm=compression_type, **settings + ) + assert compressed_tile_data == original_compressed_tile_bytes diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index da0c1b1a940..8bf786b26dc 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -472,10 +472,22 @@ def _buffer_to_array(tile_buffer, header): """ tile_shape = (header["ZTILE2"], header["ZTILE1"]) - if header["ZCMPTYPE"].startswith("GZIP") and header["ZBITPIX"] > 8: - # TOOD: support float types - int_size = header["ZBITPIX"] // 8 - tile_data = np.asarray(tile_buffer).view(f">i{int_size}").reshape(tile_shape) + if header["ZCMPTYPE"].startswith("GZIP"): + # This algorithm is taken from fitsio + # https://github.com/astropy/astropy/blob/a8cb1668d4835562b89c0d0b3448ac72ca44db63/cextern/cfitsio/lib/imcompress.c#L6345-L6388 + tilelen = np.product(tile_shape) + tilebytesize = len(tile_buffer) + if tilebytesize == tilelen * 2: + dtype = ">i2" + elif tilebytesize == tilelen * 4: + # TOOD: support float32? + dtype = ">i4" + elif tilebytesize == tilelen * 8: + dtype = ">f8" + else: + # Just return the raw bytes + dtype = ">u1" + tile_data = np.asarray(tile_buffer).view(dtype).reshape(tile_shape) else: if tile_buffer.format == "b": # NOTE: this feels like a Numpy bug - need to investigate @@ -506,7 +518,8 @@ def decompress_hdu(hdu): ) tile_data = _buffer_to_array(tile_buffer, hdu._header) data[ - istart : istart + tile_shape[0], jstart : jstart + tile_shape[1] + istart : istart + tile_shape[0], + jstart : jstart + tile_shape[1], ] = tile_data jstart += tile_shape[1] if jstart >= data_shape[1]: From 17dce4ed482792f0a1b82ff278bc8fc6ad2d8edc Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Thu, 24 Nov 2022 15:02:50 +0000 Subject: [PATCH 04/32] Test against calls to fitsio --- .github/workflows/ci_workflows.yml | 7 +- .../fits/tiled_compression/tests/conftest.py | 30 ++++ .../tiled_compression/tests/test_fitsio.py | 132 ++++++++++++++++++ .../tests/test_tiled_compression.py | 31 +--- tox.ini | 5 +- 5 files changed, 179 insertions(+), 26 deletions(-) create mode 100644 astropy/io/fits/tiled_compression/tests/conftest.py create mode 100644 astropy/io/fits/tiled_compression/tests/test_fitsio.py diff --git a/.github/workflows/ci_workflows.yml b/.github/workflows/ci_workflows.yml index 1c6ef24c2a7..def2d79d5e7 100644 --- a/.github/workflows/ci_workflows.yml +++ b/.github/workflows/ci_workflows.yml @@ -65,7 +65,12 @@ jobs: # leave open any files. This has a performance impact on running the # tests, hence why it is not enabled by default. - name: Python 3.9 with all optional dependencies - linux: py39-test-alldeps + linux: py39-test-alldeps-fitsio + libraries: + apt: + - language-pack-fr + - tzdata + - libcfitsio-dev toxargs: -v --develop posargs: --open-files --run-slow diff --git a/astropy/io/fits/tiled_compression/tests/conftest.py b/astropy/io/fits/tiled_compression/tests/conftest.py new file mode 100644 index 00000000000..2061460bbc9 --- /dev/null +++ b/astropy/io/fits/tiled_compression/tests/conftest.py @@ -0,0 +1,30 @@ +import pytest + +COMPRESSION_TYPES = [ + "GZIP_1", + "GZIP_2", + "RICE_1", + "HCOMPRESS_1", + "PLIO_1", +] + +compression_dtype_parameters = [] +for compression_type in COMPRESSION_TYPES: + # io.fits doesn't seem able to compress 64-bit data, even though e.g. GZIP_? + # and HCOMPRESS_1 should be able to handle it. + for itemsize in [1, 2, 4]: + for endian in ["<", ">"]: + format = "u" if itemsize == 1 else "i" + compression_dtype_parameters.append( + (compression_type, f"{endian}{format}{itemsize}") + ) + + +@pytest.fixture( + params=compression_dtype_parameters, scope="module", ids=lambda x: f"{x[0]} {x[1]}" +) +def compression_type_dtype(request): + """ + This fixture provides a matrix of compression type and dtype parameters. + """ + return request.param diff --git a/astropy/io/fits/tiled_compression/tests/test_fitsio.py b/astropy/io/fits/tiled_compression/tests/test_fitsio.py new file mode 100644 index 00000000000..08402bdba10 --- /dev/null +++ b/astropy/io/fits/tiled_compression/tests/test_fitsio.py @@ -0,0 +1,132 @@ +""" +This test file uses the https://github.com/esheldon/fitsio package to verify +our compression and decompression routines against the implementation in +cfitsio. + +*Note*: The fitsio library is GPL licensed, therefore it could be interpreted + that so is this test file. Given that this test file isn't imported anywhere + else in the code this shouldn't cause us any issues. Please bear this in mind + when editing this file. +""" +import numpy as np +import pytest + +from astropy.io import fits + +fitsio = pytest.importorskip("fitsio") + + +@pytest.fixture( + scope="module", + params=[ + (10,), + (12, 12), + (15, 15), + (15, 15, 15), + # >3D Data are not currently supported with astropy + # (15, 15, 15, 15), + ], + ids=lambda x: f"shape: {x}", +) +def base_original_data(request, compression_type_dtype): + if compression_type_dtype[0] == "HCOMPRESS_1" and len(request.param) < 2: + pytest.xfail("HCOMPRESS is 2D only apparently") + size = np.product(request.param) + return np.arange(size).reshape(request.param) + + +@pytest.fixture( + scope="module", + ids=lambda x: f"tiles per axis: {x}", + params=[3, 2, 1, (1, 3, -99), (1, 2, 3), (1, -99, -99)], +) +def tile_dims(request, base_original_data, compression_type_dtype): + compression_type = compression_type_dtype[0] + tile_scale_factor = np.array(request.param) + if tile_scale_factor.ndim > 0: + tile_scale_factor = tile_scale_factor[: base_original_data.ndim] + + if compression_type == "HCOMPRESS_1": + # If HCOMPRESS then we can only have 2D tiles so all other dims must + # have tile length of 1 + if tile_scale_factor.ndim > 2: + tile_scale_factor[2:] = -99 + + if np.count_nonzero(tile_scale_factor != -99) != 2: + pytest.xfail("HCOMPRESS needs two non-unity tile dimensions.") + + tile_scale_factor = np.where( + tile_scale_factor == -99, base_original_data.shape, tile_scale_factor + ) + + return tuple( + np.array(np.array(base_original_data.shape) / tile_scale_factor, dtype=int) + ) + + +@pytest.fixture(scope="module") +def fitsio_compressed_file_path( + tmp_path_factory, + compression_type_dtype, + base_original_data, + tile_dims, +): + compression_type, dtype = compression_type_dtype + if base_original_data.ndim > 2 and "u1" in dtype: + pytest.xfail("These don't work") + tmp_path = tmp_path_factory.mktemp("fitsio") + original_data = base_original_data.astype(dtype) + + filename = tmp_path / f"{compression_type}_{dtype}.fits" + fits = fitsio.FITS(filename, "rw") + fits.write(original_data, compress=compression_type, tile_dims=tile_dims) + + return filename + + +@pytest.fixture(scope="module") +def astropy_compressed_file_path( + tmp_path_factory, compression_type_dtype, base_original_data +): + compression_type, dtype = compression_type_dtype + if base_original_data.ndim > 2 and "u1" in dtype: + pytest.xfail("These don't work") + original_data = base_original_data.astype(dtype) + + tmp_path = tmp_path_factory.mktemp("astropy") + filename = tmp_path / f"{compression_type}_{dtype}.fits" + hdu = fits.CompImageHDU(data=original_data, compression_type=compression_type) + hdu.writeto(filename) + + return filename + + +def test_decompress( + fitsio_compressed_file_path, base_original_data, compression_type_dtype +): + compression_type, dtype = compression_type_dtype + + with fits.open(fitsio_compressed_file_path) as hdul: + data = hdul[1].data + + assert hdul[1]._header["ZCMPTYPE"] == compression_type + assert hdul[1].data.dtype.kind == np.dtype(dtype).kind + assert hdul[1].data.dtype.itemsize == np.dtype(dtype).itemsize + # assert hdul[1].data.dtype.byteorder == np.dtype(dtype).byteorder + np.testing.assert_allclose(data, base_original_data) + + +def test_compress( + astropy_compressed_file_path, base_original_data, compression_type_dtype +): + compression_type, dtype = compression_type_dtype + + fits = fitsio.FITS(astropy_compressed_file_path, "r") + header = fits[1].read_header() + data = fits[1].read() + + assert header["ZCMPTYPE"] == compression_type + assert data.dtype.kind == np.dtype(dtype).kind + assert data.dtype.itemsize == np.dtype(dtype).itemsize + # assert data.dtype.byteorder == np.dtype(dtype).byteorder + np.testing.assert_allclose(data, base_original_data) diff --git a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py index 16c4092aafe..28edc9852c3 100644 --- a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py @@ -16,26 +16,9 @@ _header_to_settings, ) -COMPRESSION_TYPES = [ - "GZIP_1", - "GZIP_2", - "RICE_1", - "HCOMPRESS_1", - "PLIO_1", -] - -parameters = [] -for compression_type in COMPRESSION_TYPES: - # io.fits doesn't seem able to compress 64-bit data, even though e.g. GZIP_? - # and HCOMPRESS_1 should be able to handle it. - for itemsize in [1, 2, 4]: - for endian in ["<", ">"]: - format = "u" if itemsize == 1 else "i" - parameters.append((compression_type, f"{endian}{format}{itemsize}")) - - -@pytest.mark.parametrize(("compression_type", "dtype"), parameters) -def test_basic(tmp_path, compression_type, dtype): + +def test_basic(tmp_path, compression_type_dtype): + compression_type, dtype = compression_type_dtype # Generate compressed file dynamically @@ -100,8 +83,8 @@ def test_basic(tmp_path, compression_type, dtype): hdulist_new.close() -@pytest.mark.parametrize(("compression_type", "dtype"), parameters) -def test_decompress_hdu(tmp_path, compression_type, dtype): +def test_decompress_hdu(tmp_path, compression_type_dtype): + compression_type, dtype = compression_type_dtype # NOTE: for now this test is designed to compare the Python implementation # of decompress_hdu with the C implementation - once we get rid of the C @@ -159,8 +142,8 @@ def _assert_heap_same(heap_a, heap_b, n_tiles): assert_equal(data_a, data_b) -@pytest.mark.parametrize(("compression_type", "dtype"), parameters) -def test_compress_hdu(tmp_path, compression_type, dtype): +def test_compress_hdu(tmp_path, compression_type_dtype): + compression_type, dtype = compression_type_dtype # NOTE: for now this test is designed to compare the Python implementation # of compress_hdu with the C implementation - once we get rid of the C diff --git a/tox.ini b/tox.ini index 824d2a43fe3..600b55a0941 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - py{38,39,310,311,dev}-test{,-image,-recdeps,-alldeps,-oldestdeps,-devdeps,-devinfra,-predeps,-numpy120,-numpy121,-numpy122,-numpy123,-mpl311}{,-cov}{,-clocale} + py{38,39,310,311,dev}-test{,-image,-recdeps,-alldeps,-oldestdeps,-devdeps,-devinfra,-predeps,-numpy120,-numpy121,-numpy122,-numpy123,-mpl311}{,-cov}{,-clocale}{,-fitsio} build_docs linkcheck codestyle @@ -73,6 +73,9 @@ deps = image: scipy image: pytest-mpl + # Some FITS tests use fitsio as a comparison + fitsio: fitsio + # The oldestdeps factor is intended to be used to install the oldest versions of all # dependencies that have a minimum version. oldestdeps: numpy==1.20.* From 7d84890f8fb5648ff8b304deb32675c1d7c0737b Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Thu, 24 Nov 2022 15:53:45 +0000 Subject: [PATCH 05/32] Remove compressionmodule.c in favor of astropy.io.fits.tiled_compression --- astropy/io/fits/setup_package.py | 82 -- astropy/io/fits/src/compressionmodule.c | 1308 ----------------------- astropy/io/fits/src/compressionmodule.h | 41 - 3 files changed, 1431 deletions(-) delete mode 100644 astropy/io/fits/setup_package.py delete mode 100644 astropy/io/fits/src/compressionmodule.c delete mode 100644 astropy/io/fits/src/compressionmodule.h diff --git a/astropy/io/fits/setup_package.py b/astropy/io/fits/setup_package.py deleted file mode 100644 index 494fdd1f4c2..00000000000 --- a/astropy/io/fits/setup_package.py +++ /dev/null @@ -1,82 +0,0 @@ -# Licensed under a 3-clause BSD style license - see PYFITS.rst - -import os -import sys -from collections import defaultdict -from glob import glob - -import numpy -from setuptools import Extension - -from extension_helpers import get_compiler, pkg_config - - -def _get_compression_extension(): - debug = "--debug" in sys.argv - - cfg = defaultdict(list) - cfg["include_dirs"].append(numpy.get_include()) - cfg["sources"].append( - os.path.join(os.path.dirname(__file__), "src", "compressionmodule.c") - ) - - if int(os.environ.get("ASTROPY_USE_SYSTEM_CFITSIO", 0)) or int( - os.environ.get("ASTROPY_USE_SYSTEM_ALL", 0) - ): - for k, v in pkg_config(["cfitsio"], ["cfitsio"]).items(): - cfg[k].extend(v) - else: - if get_compiler() == "msvc": - # These come from the CFITSIO vcc makefile, except the last - # which ensures on windows we do not include unistd.h (in regular - # compilation of cfitsio, an empty file would be generated) - cfg["extra_compile_args"].extend( - [ - "/D", - "WIN32", - "/D", - "_WINDOWS", - "/D", - "_MBCS", - "/D", - "_USRDLL", - "/D", - "_CRT_SECURE_NO_DEPRECATE", - "/D", - "YY_NO_UNISTD_H", - ] - ) - else: - cfg["extra_compile_args"].extend(["-Wno-declaration-after-statement"]) - - cfg["define_macros"].append(("HAVE_UNISTD_H", None)) - - if not debug: - # these switches are to silence warnings from compiling CFITSIO - # For full silencing, some are added that only are used in - # later versions of gcc (versions approximate; see #6474) - cfg["extra_compile_args"].extend( - [ - "-Wno-strict-prototypes", - "-Wno-unused", - "-Wno-uninitialized", - "-Wno-unused-result", # gcc >~4.8 - "-Wno-misleading-indentation", # gcc >~7.2 - "-Wno-format-overflow", # gcc >~7.2 - ] - ) - - cfitsio_lib_path = os.path.join("cextern", "cfitsio", "lib") - cfitsio_zlib_path = os.path.join("cextern", "cfitsio", "zlib") - cfitsio_files = glob(os.path.join(cfitsio_lib_path, "*.c")) - cfitsio_zlib_files = glob(os.path.join(cfitsio_zlib_path, "*.c")) - cfg["include_dirs"].append(cfitsio_lib_path) - cfg["include_dirs"].append(cfitsio_zlib_path) - cfg["sources"].extend(cfitsio_files) - cfg["sources"].extend(cfitsio_zlib_files) - - return Extension("astropy.io.fits.compression", **cfg) - - -def get_extensions(): - return [_get_compression_extension()] diff --git a/astropy/io/fits/src/compressionmodule.c b/astropy/io/fits/src/compressionmodule.c deleted file mode 100644 index b8bfc399722..00000000000 --- a/astropy/io/fits/src/compressionmodule.c +++ /dev/null @@ -1,1308 +0,0 @@ -/* "compression module */ - -/*****************************************************************************/ -/* */ -/* The compression software is a python module implemented in C that, when */ -/* accessed through the astropy module, supports the storage of compressed */ -/* images in FITS binary tables. An n-dimensional image is divided into a */ -/* rectangular grid of subimages or 'tiles'. Each tile is then compressed */ -/* as a continuous block of data, and the resulting compressed byte stream */ -/* is stored in a row of a variable length column in a FITS binary table. */ -/* The default tiling pattern treats each row of a 2-dimensional image */ -/* (or higher dimensional cube) as a tile, such that each tile contains */ -/* NAXIS1 pixels. */ -/* */ -/* This module contains three functions that are callable from python. The */ -/* first is compress_hdu. This function takes an */ -/* astropy.io.fits.CompImageHDU object containing the uncompressed image */ -/* data and returns the compressed data for all tiles into the */ -/* .compressed_data attribute of that HDU. */ -/* */ -/* The second function is decompress_hdu. It takes an */ -/* astropy.io.fits.CompImageHDU object that already has compressed data in */ -/* its .compressed_data attribute. It returns the decompressed image data */ -/* into the HDU's .data attribute. */ -/* */ -/* Copyright (C) 2013 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. */ -/* */ -/* Some of the source code used by this module was copied and modified from */ -/* the FITSIO software that was written by William Pence at the High Energy */ -/* Astrophysic Science Archive Research Center (HEASARC) at the NASA Goddard */ -/* Space Flight Center. That software contained the following copyright and */ -/* warranty notices: */ -/* */ -/* Copyright (Unpublished--all rights reserved under the copyright laws of */ -/* the United States), U.S. Government as represented by the Administrator */ -/* of the National Aeronautics and Space Administration. No copyright is */ -/* claimed in the United States under Title 17, U.S. Code. */ -/* */ -/* Permission to freely use, copy, modify, and distribute this software */ -/* and its documentation without fee is hereby granted, provided that this */ -/* copyright notice and disclaimer of warranty appears in all copies. */ -/* */ -/* DISCLAIMER: */ -/* */ -/* THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, */ -/* EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, */ -/* ANY WARRANTY THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY */ -/* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR */ -/* PURPOSE, AND FREEDOM FROM INFRINGEMENT, AND ANY WARRANTY THAT THE */ -/* DOCUMENTATION WILL CONFORM TO THE SOFTWARE, OR ANY WARRANTY THAT THE */ -/* SOFTWARE WILL BE ERROR FREE. IN NO EVENT SHALL NASA BE LIABLE FOR ANY */ -/* DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL OR */ -/* CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, OR IN ANY WAY */ -/* CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, */ -/* CONTRACT, TORT , OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY */ -/* PERSONS OR PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED */ -/* FROM, OR AROSE OUT OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR */ -/* SERVICES PROVIDED HEREUNDER." */ -/* */ -/*****************************************************************************/ - -/* Include the Python C API */ - -#include -#include -#include -#include - -#include -#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION -#include -#include -#include "compressionmodule.h" - - -/* These defaults mirror the defaults in astropy.io.fits.hdu.compressed */ -#define DEFAULT_COMPRESSION_TYPE "RICE_1" -#define DEFAULT_QUANTIZE_LEVEL 16.0 -#define DEFAULT_HCOMP_SCALE 0 -#define DEFAULT_HCOMP_SMOOTH 0 -#define DEFAULT_BLOCK_SIZE 32 -#define DEFAULT_BYTE_PIX 4 - -/* Flags to pass to get_header_* functions to control error messages. */ -typedef enum { - HDR_NOFLAG = 0, - HDR_FAIL_KEY_MISSING = 1 << 0, - HDR_FAIL_VAL_NEGATIVE = 1 << 1, -} HeaderGetFlags; - - -/* Report any error based on the status returned from cfitsio. */ -void process_status_err(int status) -{ - PyObject* except_type; - char err_msg[81]; - char def_err_msg[81]; - - err_msg[0] = '\0'; - def_err_msg[0] = '\0'; - - switch (status) { - case MEMORY_ALLOCATION: - except_type = PyExc_MemoryError; - break; - case OVERFLOW_ERR: - except_type = PyExc_OverflowError; - break; - case BAD_COL_NUM: - strcpy(def_err_msg, "bad column number"); - except_type = PyExc_ValueError; - break; - case BAD_PIX_NUM: - strcpy(def_err_msg, "bad pixel number"); - except_type = PyExc_ValueError; - break; - case NEG_AXIS: - strcpy(def_err_msg, "negative axis number"); - except_type = PyExc_ValueError; - break; - case BAD_DATATYPE: - strcpy(def_err_msg, "bad data type"); - except_type = PyExc_TypeError; - break; - case NO_COMPRESSED_TILE: - strcpy(def_err_msg, "no compressed or uncompressed data for tile."); - except_type = PyExc_ValueError; - break; - default: - except_type = PyExc_RuntimeError; - break; - } - - if (fits_read_errmsg(err_msg)) { - PyErr_SetString(except_type, err_msg); - } else if (*def_err_msg) { - PyErr_SetString(except_type, def_err_msg); - } else { - PyErr_Format(except_type, "unknown error %i.", status); - } -} - - -void bitpix_to_datatypes(int bitpix, int* datatype, int* npdatatype) { - /* Given a FITS BITPIX value, returns the appropriate CFITSIO type code and - Numpy type code for that BITPIX into datatype and npdatatype - respectively. - */ - switch (bitpix) { - case BYTE_IMG: - *datatype = TBYTE; - *npdatatype = NPY_UINT8; - break; - case SHORT_IMG: - *datatype = TSHORT; - *npdatatype = NPY_INT16; - break; - case LONG_IMG: - *datatype = TINT; - *npdatatype = NPY_INT32; - break; - case LONGLONG_IMG: - *datatype = TLONGLONG; - *npdatatype = NPY_LONGLONG; - break; - case FLOAT_IMG: - *datatype = TFLOAT; - *npdatatype = NPY_FLOAT; - break; - case DOUBLE_IMG: - *datatype = TDOUBLE; - *npdatatype = NPY_DOUBLE; - break; - default: - PyErr_Format(PyExc_ValueError, "Invalid value for BITPIX: %d", - bitpix); - break; - } - - return; -} - - - -int compress_type_from_string(char* zcmptype) { - if (0 == strcmp(zcmptype, "RICE_1")) { - return RICE_1; - } else if (0 == strcmp(zcmptype, "GZIP_1")) { - return GZIP_1; - } else if (0 == strcmp(zcmptype, "GZIP_2")) { - return GZIP_2; - } else if (0 == strcmp(zcmptype, "PLIO_1")) { - return PLIO_1; - } else if (0 == strcmp(zcmptype, "HCOMPRESS_1")) { - return HCOMPRESS_1; - } - /* CFITSIO adds a compression type alias for RICE_1 compression - as a flag for using subtractive_dither_2 */ - else if (0 == strcmp(zcmptype, "RICE_ONE")) { - return RICE_1; - } - else { - PyErr_Format(PyExc_ValueError, "Unrecognized compression type: %s", - zcmptype); - return -1; - } -} - - -PyObject * -get_header_value(PyObject* header, const char* key, HeaderGetFlags flags) { - PyObject* hdrkey; - PyObject* hdrval; - hdrkey = PyUnicode_FromString(key); - if (hdrkey == NULL) { - return NULL; - } - hdrval = PyObject_GetItem(header, hdrkey); - Py_DECREF(hdrkey); - if ((flags & HDR_FAIL_KEY_MISSING) == 0) { - /* Normally we have a default so we want to ignore the exception in - any case. But if the flag was given this step must be skipped. */ - PyErr_Clear(); - } - return hdrval; -} - - -// TODO: It might be possible to simplify these further by making the -// conversion function (eg. PyString_AsString) an argument to a macro or -// something, but I'm not sure yet how easy it is to generalize the error -// handling -/* The get_header_* functions resemble "Header.get" where "def" is the default - value, "keyword" is a string representing the header-key and the result is - stored in "val". - The function returns 0 on success, 1 if the header didn't have the keyword - and the default was applied and -1 (with an exception set) if an Exception - happened (like a MemoryError or Overflow). -*/ -#define GET_HEADER_SUCCESS 0 -#define GET_HEADER_DEFAULT_USED 1 -#define GET_HEADER_FAILED -1 -int get_header_string(PyObject* header, const char* keyword, char* val, - const char* def, HeaderGetFlags flags) { - /* nonnegative doesn't make sense for strings*/ - assert(!(flags & HDR_FAIL_VAL_NEGATIVE)); - PyObject* keyval = get_header_value(header, keyword, flags); - - if (keyval == NULL) { - strncpy(val, def, 72); - return PyErr_Occurred() ? GET_HEADER_FAILED : GET_HEADER_DEFAULT_USED; - } - PyObject* tmp = PyUnicode_AsLatin1String(keyval); - // FITS header values should always be ASCII, but Latin1 is on the - // safe side - Py_DECREF(keyval); - if (tmp == NULL) { - /* could always fail to allocate the memory or such like. */ - return GET_HEADER_FAILED; - } - strncpy(val, PyBytes_AsString(tmp), 72); - Py_DECREF(tmp); - return GET_HEADER_SUCCESS; -} - - -int get_header_long(PyObject* header, const char* keyword, long* val, long def, - HeaderGetFlags flags) { - PyObject* keyval = get_header_value(header, keyword, flags); - - if (keyval == NULL) { - *val = def; - return PyErr_Occurred() ? GET_HEADER_FAILED : GET_HEADER_DEFAULT_USED; - } - long tmp = PyLong_AsLong(keyval); - Py_DECREF(keyval); - if (PyErr_Occurred()) { - return GET_HEADER_FAILED; - } - if ((flags & HDR_FAIL_VAL_NEGATIVE) && (tmp < 0)) { - PyErr_Format(PyExc_ValueError, "%s should not be negative.", keyword); - return GET_HEADER_FAILED; - } - *val = tmp; - return GET_HEADER_SUCCESS; -} - - -int get_header_int(PyObject* header, const char* keyword, int* val, int def, - HeaderGetFlags flags) { - long tmp; - int ret = get_header_long(header, keyword, &tmp, def, flags); - if (ret == GET_HEADER_SUCCESS) { - if (tmp >= INT_MIN && tmp <= INT_MAX) { - *val = (int) tmp; - } else { - PyErr_Format(PyExc_OverflowError, "Cannot convert %ld to C 'int'", tmp); - ret = GET_HEADER_FAILED; - } - } - return ret; -} - - -int get_header_double(PyObject* header, const char* keyword, double* val, - double def, HeaderGetFlags flags) { - /* nonnegative isn't currently used for doubles/floats. But if needed one - could simply remove the assert again and implement the negative check. */ - assert(!(flags & HDR_FAIL_VAL_NEGATIVE)); - PyObject* keyval = get_header_value(header, keyword, flags); - - if (keyval == NULL) { - *val = def; - return PyErr_Occurred() ? GET_HEADER_FAILED : GET_HEADER_DEFAULT_USED; - } - double tmp = PyFloat_AsDouble(keyval); - Py_DECREF(keyval); - if (PyErr_Occurred()) { - return GET_HEADER_FAILED; - } - *val = tmp; - return GET_HEADER_SUCCESS; -} - - -int get_header_float(PyObject* header, const char* keyword, float* val, - float def, HeaderGetFlags flags) { - double tmp; - int ret = get_header_double(header, keyword, &tmp, def, flags); - if (ret == GET_HEADER_SUCCESS) { - if (tmp == 0.0 || (fabs(tmp) >= FLT_MIN && fabs(tmp) <= FLT_MAX)) { - *val = (float) tmp; - } else { - PyErr_SetString(PyExc_OverflowError, - "Cannot convert 'double' to 'float'"); - ret = GET_HEADER_FAILED; - } - } - return ret; -} - - -int get_header_longlong(PyObject* header, const char* keyword, long long* val, - long long def, HeaderGetFlags flags) { - PyObject* keyval = get_header_value(header, keyword, flags); - - if (keyval == NULL) { - *val = def; - return PyErr_Occurred() ? GET_HEADER_FAILED : GET_HEADER_DEFAULT_USED; - } - long long tmp = PyLong_AsLongLong(keyval); - Py_DECREF(keyval); - if (PyErr_Occurred()) { - return GET_HEADER_FAILED; - } - if ((flags & HDR_FAIL_VAL_NEGATIVE) && (tmp < 0)) { - PyErr_Format(PyExc_ValueError, "%s should not be negative.", keyword); - return GET_HEADER_FAILED; - } - *val = tmp; - return GET_HEADER_SUCCESS; -} - - -void tcolumns_from_header(fitsfile* fileptr, PyObject* header, - tcolumn** columns) { - // Creates the array of tcolumn structures from the table column keywords - // read from the astropy.io.fits.Header object; caller is responsible for - // freeing the memory allocated for this array - - tcolumn* column; - char tkw[9]; - - int tfields; - char ttype[72]; - char tform[72]; - int dtcode; - long trepeat; - long twidth; - long long totalwidth; - int status = 0; - int idx; - - if (get_header_int(header, "TFIELDS", &tfields, 0, HDR_FAIL_VAL_NEGATIVE) == GET_HEADER_FAILED) { - return; - } - /* To avoid issues in the loop we need to limit the number of TFIELDs to - 999. Otherwise we would exceed the maximum length of the keyword name of - 8. This could lead to multiple accesses of the same header keyword with - snprintf because we limit it to 8 characters + null-termination. */ - if (tfields > 999) { - PyErr_SetString(PyExc_ValueError, "The TFIELDS value exceeds 999."); - return; - } - - // This used to use PyMem_New, but don't do that; CFITSIO will later - // free() this object when the file is closed, so just use malloc here - // *columns = column = PyMem_New(tcolumn, (size_t) tfields); - *columns = column = calloc((size_t) tfields, sizeof(tcolumn)); - if (column == NULL) { - PyErr_SetString(PyExc_MemoryError, - "Couldn't allocate memory for columns."); - return; - } - - - for (idx = 1; idx <= tfields; idx++, column++) { - /* set some invalid defaults */ - column->ttype[0] = '\0'; - column->tbcol = 0; - column->tdatatype = -9999; /* this default used by cfitsio */ - column->trepeat = 1; - column->strnull[0] = '\0'; - column->tform[0] = '\0'; - column->twidth = 0; - - snprintf(tkw, 9, "TTYPE%u", idx); - if (get_header_string(header, tkw, ttype, "", HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - strncpy(column->ttype, ttype, 69); - column->ttype[69] = '\0'; - - snprintf(tkw, 9, "TFORM%u", idx); - if (get_header_string(header, tkw, tform, "", HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - strncpy(column->tform, tform, 9); - column->tform[9] = '\0'; - fits_binary_tform(tform, &dtcode, &trepeat, &twidth, &status); - if (status != 0) { - process_status_err(status); - return; - } - - column->tdatatype = dtcode; - column->trepeat = trepeat; - column->twidth = twidth; - - snprintf(tkw, 9, "TSCAL%u", idx); - if (get_header_double(header, tkw, &(column->tscale), 1.0, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - - snprintf(tkw, 9, "TZERO%u", idx); - if (get_header_double(header, tkw, &(column->tzero), 0.0, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - - snprintf(tkw, 9, "TNULL%u", idx); - if (get_header_longlong(header, tkw, &(column->tnull), NULL_UNDEFINED, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - } - - fileptr->Fptr->tableptr = *columns; - fileptr->Fptr->tfield = tfields; - - // This routine from CFITSIO calculates the byte offset of each column - // and stores it in the column->tbcol field - ffgtbc(fileptr, &totalwidth, &status); - if (status != 0) { - process_status_err(status); - } - - return; -} - - - -void configure_compression(fitsfile* fileptr, PyObject* header) { - /* Configure the compression-related elements in the fitsfile struct - using values in the FITS header. */ - - FITSfile* Fptr; - - int tfields; - tcolumn* columns; - - char keyword[9]; - char zname[72]; - int znaxis; - char tmp[72]; - float version; - - int idx; - - Fptr = fileptr->Fptr; - tfields = Fptr->tfield; - columns = Fptr->tableptr; - - int tmp_retval; - - // Get the ZBITPIX header value; if this is missing we're in trouble - if (get_header_int(header, "ZBITPIX", &(Fptr->zbitpix), 0, HDR_FAIL_KEY_MISSING) != GET_HEADER_SUCCESS) { - return; - } - - // By default assume there is no ZBLANK column and check for ZBLANK or - // BLANK in the header - Fptr->cn_zblank = Fptr->cn_zzero = Fptr->cn_zscale = -1; - Fptr->cn_uncompressed = 0; - Fptr->cn_gzip_data = 0; - - // Check for a ZBLANK, ZZERO, ZSCALE, and - // UNCOMPRESSED_DATA/GZIP_COMPRESSED_DATA columns in the compressed data - // table - for (idx = 0; idx < tfields; idx++) { - if (0 == strncmp(columns[idx].ttype, "UNCOMPRESSED_DATA", 18)) { - Fptr->cn_uncompressed = idx + 1; - } else if (0 == strncmp(columns[idx].ttype, - "GZIP_COMPRESSED_DATA", 21)) { - Fptr->cn_gzip_data = idx + 1; - } else if (0 == strncmp(columns[idx].ttype, "ZSCALE", 7)) { - Fptr->cn_zscale = idx + 1; - } else if (0 == strncmp(columns[idx].ttype, "ZZERO", 6)) { - Fptr->cn_zzero = idx + 1; - } else if (0 == strncmp(columns[idx].ttype, "ZBLANK", 7)) { - Fptr->cn_zblank = idx + 1; - } - } - - Fptr->zblank = 0; - if (Fptr->cn_zblank < 1) { - // No ZBLANK column--check the ZBLANK and BLANK heard keywords - switch (get_header_int(header, "ZBLANK", &(Fptr->zblank), 0, HDR_NOFLAG)) { - case GET_HEADER_FAILED: - return; - case GET_HEADER_DEFAULT_USED: - // ZBLANK keyword not found - if (get_header_int(header, "BLANK", &(Fptr->zblank), 0, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - break; - default: - break; - } - } - - Fptr->zscale = 1.0; - if (Fptr->cn_zscale < 1) { - switch (get_header_double(header, "ZSCALE", &(Fptr->zscale), 1.0, HDR_NOFLAG)) { - case GET_HEADER_FAILED: - return; - case GET_HEADER_DEFAULT_USED: - Fptr->cn_zscale = 0; - break; - default: - break; - } - } - Fptr->cn_bscale = Fptr->zscale; - - Fptr->zzero = 0.0; - if (Fptr->cn_zzero < 1) { - switch (get_header_double(header, "ZZERO", &(Fptr->zzero), 0.0, HDR_NOFLAG)) { - case GET_HEADER_FAILED: - return; - case GET_HEADER_DEFAULT_USED: - Fptr->cn_zzero = 0; - break; - default: - break; - } - } - Fptr->cn_bzero = Fptr->zzero; - - if (get_header_string(header, "ZCMPTYPE", tmp, DEFAULT_COMPRESSION_TYPE, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - strncpy(Fptr->zcmptype, tmp, 11); - Fptr->zcmptype[strlen(tmp)] = '\0'; - - Fptr->compress_type = compress_type_from_string(Fptr->zcmptype); - if (PyErr_Occurred()) { - return; - } - - if (get_header_int(header, "ZNAXIS", &znaxis, 0, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - Fptr->zndim = znaxis; - - if (znaxis > MAX_COMPRESS_DIM) { - // The CFITSIO compression code currently only supports up to 6 - // dimensions by default. - znaxis = MAX_COMPRESS_DIM; - } - - Fptr->tilerow = NULL; - Fptr->maxtilelen = 1; - for (idx = 1; idx <= znaxis; idx++) { - snprintf(keyword, 9, "ZNAXIS%u", idx); - if (get_header_long(header, keyword, Fptr->znaxis + idx - 1, 0, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - snprintf(keyword, 9, "ZTILE%u", idx); - if (get_header_long(header, keyword, Fptr->tilesize + idx - 1, 0, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - Fptr->maxtilelen *= Fptr->tilesize[idx - 1]; - } - - // Set some more default compression options - Fptr->rice_blocksize = DEFAULT_BLOCK_SIZE; - Fptr->rice_bytepix = DEFAULT_BYTE_PIX; - Fptr->quantize_level = DEFAULT_QUANTIZE_LEVEL; - Fptr->hcomp_smooth = DEFAULT_HCOMP_SMOOTH; - Fptr->hcomp_scale = DEFAULT_HCOMP_SCALE; - - // Now process the ZVALn keywords - idx = 1; - while (1) { - snprintf(keyword, 9, "ZNAME%u", idx); - // Assumes there are no gaps in the ZNAMEn keywords; this same - // assumption was made in the Python code. This could be done slightly - // more flexibly by using a wildcard slice of the header - tmp_retval = get_header_string(header, keyword, zname, "", HDR_NOFLAG); - if (tmp_retval == GET_HEADER_FAILED) { - return; - } else if (tmp_retval == 1) { - break; - } - - snprintf(keyword, 9, "ZVAL%u", idx); - if (Fptr->compress_type == RICE_1) { - if (0 == strcmp(zname, "BLOCKSIZE")) { - if (get_header_int(header, keyword, &(Fptr->rice_blocksize), - DEFAULT_BLOCK_SIZE, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - } else if (0 == strcmp(zname, "BYTEPIX")) { - if (get_header_int(header, keyword, &(Fptr->rice_bytepix), - DEFAULT_BYTE_PIX, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - } - } else if (Fptr->compress_type == HCOMPRESS_1) { - if (0 == strcmp(zname, "SMOOTH")) { - if (get_header_int(header, keyword, &(Fptr->hcomp_smooth), - DEFAULT_HCOMP_SMOOTH, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - } else if (0 == strcmp(zname, "SCALE")) { - if (get_header_float(header, keyword, &(Fptr->hcomp_scale), - DEFAULT_HCOMP_SCALE, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - } - } - if (Fptr->zbitpix < 0 && 0 == strcmp(zname, "NOISEBIT")) { - if (get_header_float(header, keyword, &(Fptr->quantize_level), - DEFAULT_QUANTIZE_LEVEL, HDR_NOFLAG) == GET_HEADER_FAILED) { - return; - } - if (Fptr->quantize_level == 0.0) { - /* NOISEBIT == 0 is equivalent to no quantize */ - Fptr->quantize_level = NO_QUANTIZE; - } - } - - idx++; - } - - /* The ZQUANTIZ keyword determines the quantization algorithm; NO_QUANTIZE - implies lossless compression */ - tmp_retval = get_header_string(header, "ZQUANTIZ", tmp, "", HDR_NOFLAG); - if (tmp_retval == GET_HEADER_FAILED) { - return; - } else if (tmp_retval == GET_HEADER_SUCCESS) { - /* Ugh; the fact that cfitsio defines its version as a float makes - preprocessor comparison impossible */ - fits_get_version(&version); - if (0 == strcmp(tmp, "NONE")) { - Fptr->quantize_level = NO_QUANTIZE; - } else if (0 == strcmp(tmp, "SUBTRACTIVE_DITHER_1")) { - // Added in CFITSIO 3.35, this also changed the name of the - // quantize_dither struct member to quantize_method - Fptr->quantize_method = SUBTRACTIVE_DITHER_1; - } else if (0 == strcmp(tmp, "SUBTRACTIVE_DITHER_2")) { - Fptr->quantize_method = SUBTRACTIVE_DITHER_2; - } else { - Fptr->quantize_method = NO_DITHER; - } - } else { - Fptr->quantize_method = NO_DITHER; - } - - if (Fptr->quantize_method != NO_DITHER) { - switch (get_header_int(header, "ZDITHER0", &(Fptr->dither_seed), 0, HDR_NOFLAG)) { - case GET_HEADER_FAILED: - return; - case GET_HEADER_DEFAULT_USED: // ZDITHER0 keyword not found - Fptr->dither_seed = 0; - Fptr->request_dither_seed = 0; - break; - default: - break; - } - } - - Fptr->compressimg = 1; - Fptr->maxelem = imcomp_calc_max_elem(Fptr->compress_type, - Fptr->maxtilelen, - Fptr->zbitpix, - Fptr->rice_blocksize); - Fptr->cn_compressed = 1; - return; -} - - -void init_output_buffer(PyObject* hdu, void** buf, size_t* bufsize) { - // Determines a good size for the output data buffer and allocates - // memory for it, returning the address and size of the allocated - // memory into **buf and *bufsize respectively. - - PyObject* header = NULL; - char keyword[9]; - char tmp[72]; - int znaxis; - int compress_type; - int zbitpix; - int rice_blocksize = 0; - long long rowlen; - long long nrows; - long maxelem; - long tilelen; - unsigned long maxtilelen = 1; - int idx; - - header = PyObject_GetAttrString(hdu, "_header"); - if (header == NULL) { - return; - } - - if (get_header_int(header, "ZNAXIS", &znaxis, 0, - HDR_FAIL_KEY_MISSING | HDR_FAIL_VAL_NEGATIVE) != GET_HEADER_SUCCESS) { - goto fail; - } - - if (znaxis > 999) { - PyErr_SetString(PyExc_ValueError, "ZNAXIS is greater than 999."); - goto fail; - } - - for (idx = 1; idx <= znaxis; idx++) { - snprintf(keyword, 9, "ZTILE%u", idx); - if (get_header_long(header, keyword, &tilelen, 1, HDR_NOFLAG) == GET_HEADER_FAILED) { - goto fail; - } - maxtilelen *= tilelen; - } - - if (get_header_string(header, "ZCMPTYPE", tmp, DEFAULT_COMPRESSION_TYPE, HDR_NOFLAG) == GET_HEADER_FAILED) { - goto fail; - } - compress_type = compress_type_from_string(tmp); - if (PyErr_Occurred()) { - goto fail; - } - if (compress_type == RICE_1) { - if (get_header_int(header, "ZVAL1", &rice_blocksize, 0, HDR_NOFLAG) == GET_HEADER_FAILED) { - goto fail; - } - } - - /* Because we calculate the size of the buffer based on these values they - must not be negative. Otherwise it would wrap around during the casting - to size_t and give huge values. */ - if (get_header_longlong(header, "NAXIS1", &rowlen, 0, HDR_FAIL_VAL_NEGATIVE) == GET_HEADER_FAILED) { - goto fail; - } - if (get_header_longlong(header, "NAXIS2", &nrows, 0, HDR_FAIL_VAL_NEGATIVE) == GET_HEADER_FAILED) { - goto fail; - } - - // Get the ZBITPIX header value; if this is missing we're in trouble - if (get_header_int(header, "ZBITPIX", &zbitpix, 0, HDR_FAIL_KEY_MISSING) != GET_HEADER_SUCCESS) { - goto fail; - } - - maxelem = imcomp_calc_max_elem(compress_type, maxtilelen, zbitpix, - rice_blocksize); - - *bufsize = ((size_t) (rowlen * nrows) + (nrows * maxelem)); - - if (*bufsize < IOBUFLEN) { - // We must have a full FITS block at a minimum - *bufsize = IOBUFLEN; - } else if (*bufsize % IOBUFLEN != 0) { - // Still make sure to pad out to a multiple of 2880 byte blocks - // otherwise CFITSIO can get read errors when it tries to read - // a partial block that goes past the end of the file - *bufsize += ((size_t) (IOBUFLEN - (*bufsize % IOBUFLEN))); - } - - *buf = calloc(*bufsize, sizeof(char)); - if (*buf == NULL) { - // Checking if calloc failed. - PyErr_SetString(PyExc_MemoryError, - "Failed to allocate memory for output data buffer."); - goto fail; - } - -fail: - Py_DECREF(header); - return; -} - - -void get_hdu_data_base(PyObject* hdu, void** buf, size_t* bufsize) { - // Given a pointer to an HDU object, returns a pointer to the deepest base - // array of that HDU's data array into **buf, and the size of that array - // into *bufsize. - - PyArrayObject* data = NULL; - PyArrayObject* base; - PyArrayObject* tmp; - - data = (PyArrayObject*) PyObject_GetAttrString(hdu, "compressed_data"); - if (data == NULL) { - goto fail; - } - - // Walk the array data bases until we find the lowest ndarray base; for - // CompImageHDUs there should always be at least one contiguous byte array - // allocated for the table and its heap - if (!PyObject_TypeCheck(data, &PyArray_Type)) { - PyErr_SetString(PyExc_TypeError, - "CompImageHDU.compressed_data must be a numpy.ndarray"); - goto fail; - } - - tmp = base = data; - while (PyObject_TypeCheck((PyObject*) tmp, &PyArray_Type)) { - base = tmp; - *bufsize = (size_t) PyArray_NBYTES(base); - tmp = (PyArrayObject*) PyArray_BASE(base); - if (tmp == NULL) { - break; - } - } - - *buf = PyArray_DATA(base); -fail: - Py_XDECREF(data); - return; -} - - -void open_from_hdu(fitsfile** fileptr, void** buf, size_t* bufsize, - PyObject* hdu, tcolumn** columns, int mode) { - - PyObject* header = NULL; - FITSfile* Fptr; - - int status = 0; - long long rowlen; - long long nrows; - long long heapsize; - long long theap; - - header = PyObject_GetAttrString(hdu, "_header"); - if (header == NULL) { - goto fail; - } - - if (get_header_longlong(header, "NAXIS1", &rowlen, 0, HDR_NOFLAG) == GET_HEADER_FAILED) { - goto fail; - } - if (get_header_longlong(header, "NAXIS2", &nrows, 0, HDR_NOFLAG) == GET_HEADER_FAILED) { - goto fail; - } - - // The PCOUNT keyword contains the number of bytes in the table heap - if (get_header_longlong(header, "PCOUNT", &heapsize, 0, HDR_FAIL_VAL_NEGATIVE) == GET_HEADER_FAILED) { - goto fail; - } - - // The THEAP keyword gives the offset of the heap from the beginning of - // the HDU data portion; normally this offset is 0 but it can be set - // to something else with THEAP - if (get_header_longlong(header, "THEAP", &theap, 0, HDR_NOFLAG) == GET_HEADER_FAILED) { - goto fail; - } - - fits_create_memfile(fileptr, buf, bufsize, 0, realloc, &status); - if (status != 0) { - process_status_err(status); - goto fail; - } - - Fptr = (*fileptr)->Fptr; - - // Now we have some fun munging some of the elements in the fitsfile struct - Fptr->writemode = mode; - Fptr->open_count = 1; - Fptr->hdutype = BINARY_TBL; /* This is a binary table HDU */ - Fptr->lasthdu = 1; - Fptr->headstart[0] = 0; - Fptr->headend = 0; - Fptr->datastart = 0; /* There is no header, data starts at 0 */ - Fptr->origrows = Fptr->numrows = nrows; - Fptr->rowlength = rowlen; - if (theap != 0) { - Fptr->heapstart = theap; - } else { - Fptr->heapstart = rowlen * nrows; - } - - Fptr->heapsize = heapsize; - - // Configure the array of table column structs from the Astropy header - // instead of allowing CFITSIO to try to read from the header - tcolumns_from_header(*fileptr, header, columns); - if (PyErr_Occurred()) { - goto fail; - } - - // If any errors occur in this function they'll bubble up from here to - // compression_decompress_hdu - configure_compression(*fileptr, header); - -fail: - Py_XDECREF(header); - return; -} - - -/* define a PyCapsule_Destructor, using the correct deallocator for buff */ -void free_wrap(void *capsule){ - void * obj = PyCapsule_GetPointer(capsule, PyCapsule_GetName(capsule)); - free(obj); -}; - - -PyObject* compression_compress_hdu(PyObject* self, PyObject* args) -{ - PyObject* hdu; - PyObject* retval = NULL; - tcolumn* columns = NULL; - - void* outbuf = NULL; - size_t outbufsize; - - PyObject* tmp_indata; - PyArrayObject* indata = NULL; - PyArrayObject* tmp; - npy_intp znaxis; - int datatype; - int npdatatype; - unsigned long long heapsize; - - fitsfile* fileptr = NULL; - FITSfile* Fptr = NULL; - int status = 0; - - if (!PyArg_ParseTuple(args, "O:compression.compress_hdu", &hdu)) { - return NULL; - } - - // For HDU compression never use CFITSIO to write directly to the file; - // although there's nothing wrong with CFITSIO, right now that would cause - // too much confusion to Astropy's internal book keeping. - // We just need to get the compressed bytes and Astropy will handle the - // writing of them. - init_output_buffer(hdu, &outbuf, &outbufsize); - if (outbuf == NULL) { - return NULL; - } - - open_from_hdu(&fileptr, &outbuf, &outbufsize, hdu, &columns, READWRITE); - if (PyErr_Occurred()) { - goto fail; - } - - Fptr = fileptr->Fptr; - - bitpix_to_datatypes(Fptr->zbitpix, &datatype, &npdatatype); - if (PyErr_Occurred()) { - goto fail; - } - - /* The data attribute could be something different from an array, i.e. None */ - tmp_indata = PyObject_GetAttrString(hdu, "data"); - if (tmp_indata == NULL) { - goto fail; - } - - if (!PyObject_TypeCheck(tmp_indata, &PyArray_Type)) { - PyErr_SetString(PyExc_TypeError, - "CompImageHDU.data must be a numpy.ndarray"); - Py_DECREF(tmp_indata); - goto fail; - } - - indata = (PyArrayObject*) tmp_indata; - - fits_write_img(fileptr, datatype, 1, PyArray_SIZE(indata), - PyArray_DATA(indata), &status); - if (status != 0) { - process_status_err(status); - goto fail; - } - - fits_flush_buffer(fileptr, 1, &status); - if (status != 0) { - process_status_err(status); - goto fail; - } - - // Previously this used outbufsize as the size to use for the new Numpy - // byte array. However outbufsize is usually larger than necessary to - // store all the compressed data exactly; instead use the exact size - // of the compressed data from the heapsize plus the size of the table - // itself - heapsize = (unsigned long long) Fptr->heapsize; - znaxis = (npy_intp) (Fptr->heapstart + heapsize); - - if (znaxis < outbufsize) { - void* tmp_outbuf = NULL; - // Go ahead and truncate to the size in znaxis to free the - // redundant allocation - if (znaxis == 0) { - /* This really shouldn't happen, but if it did, we would have a - problem because realloc would deallocate outbuf AND return NULL. - */ - PyErr_SetString(PyExc_ValueError, - "Calculated array size is zero. This shouldn't happen!"); - goto fail; - } - tmp_outbuf = realloc(outbuf, (size_t) znaxis); - if (tmp_outbuf == NULL) { - PyErr_SetString(PyExc_MemoryError, - "Couldn't resize the output-buffer."); - goto fail; - } - outbuf = tmp_outbuf; - } - - tmp = (PyArrayObject*) PyArray_SimpleNewFromData(1, &znaxis, NPY_UBYTE, - outbuf); - if (tmp == NULL) { - /* Really not sure if it's always safe to free outbuf when - PyArray_SimpleNewFromData failed (which is unlikely but could happen) - but it seems like if it fails then the outbuf NEEDS to be freed... */ - goto fail; - } - - // Take responsibility for outbuf by wrapping it in a capsule and - // setting tmp.base to the capsule - // See explanations on memory policy in the numpy docs for more details: - // https://github.com/numpy/numpy/blob/main/doc/source/reference/c-api/data_memory.rst - PyObject *capsule = PyCapsule_New(outbuf, "wrapped_data", - (PyCapsule_Destructor)&free_wrap); - if (PyArray_SetBaseObject(tmp, capsule) == -1) { - Py_DECREF(tmp); - goto cleanup; - } - - // Leaves refcount of tmp untouched, so its refcount should remain as 1 - retval = Py_BuildValue("KN", heapsize, tmp); - if (retval == NULL) { - Py_DECREF(tmp); - goto cleanup; - } - - goto cleanup; - -fail: - if (outbuf != NULL) { - // At this point outbuf should never not be NULL, but in principle - // buggy code somewhere in CFITSIO or Numpy could set it to NULL - free(outbuf); - } -cleanup: - if (columns != NULL) { - free(columns); - /* See https://github.com/astropy/astropy/pull/4489 - We can only set the tableptr to NULL if Fptr is actually not NULL. - */ - if (fileptr != NULL && fileptr->Fptr != NULL) { - fileptr->Fptr->tableptr = NULL; - } - } - - if (fileptr != NULL) { - status = 1; // Disable header-related errors - fits_close_file(fileptr, &status); - if (status != 1) { - process_status_err(status); - retval = NULL; - } - } - - Py_XDECREF(indata); - - // Clear any messages remaining in CFITSIO's error stack - fits_clear_errmsg(); - - return retval; -} - - -PyObject* compression_decompress_hdu(PyObject* self, PyObject* args) -{ - - PyObject* hdu; - tcolumn* columns = NULL; - - void* inbuf; - size_t inbufsize; - - PyArrayObject* outdata = NULL; - int datatype; - int npdatatype; - npy_intp zndim; - npy_intp* znaxis = NULL; - long arrsize; - - fitsfile* fileptr = NULL; - int anynul = 0; - int status = 0; - int idx; - - int free_columns_manually = 1; - - if (!PyArg_ParseTuple(args, "O:compression.decompress_hdu", &hdu)) { - return NULL; - } - - // Grab a pointer to the input data from the HDU's compressed_data - // attribute - get_hdu_data_base(hdu, &inbuf, &inbufsize); - if (PyErr_Occurred()) { - return NULL; - } else if (inbufsize == 0) { - // The compressed data buffer is empty (probably zero rows, for an - // empty "compressed" image. Just return None in this case. - Py_RETURN_NONE; - } - - open_from_hdu(&fileptr, &inbuf, &inbufsize, hdu, &columns, READONLY); - if (PyErr_Occurred()) { - goto fail; - } - - bitpix_to_datatypes(fileptr->Fptr->zbitpix, &datatype, &npdatatype); - if (PyErr_Occurred()) { - goto fail; - } - - zndim = (npy_intp)fileptr->Fptr->zndim; - znaxis = PyMem_Malloc(sizeof(npy_intp) * zndim); - if (znaxis == NULL) { - goto fail; - } - - arrsize = 1; - for (idx = 0; idx < zndim; idx++) { - znaxis[zndim - idx - 1] = fileptr->Fptr->znaxis[idx]; - arrsize *= fileptr->Fptr->znaxis[idx]; - } - - /* Create and allocate a new array for the decompressed data */ - outdata = (PyArrayObject*) PyArray_SimpleNew(zndim, znaxis, npdatatype); - if (outdata == NULL) { - goto fail; - } - - fits_read_img(fileptr, datatype, 1, arrsize, NULL, PyArray_DATA(outdata), - &anynul, &status); - /* At this point we need to let CFITSIO clean up the tableptr and the - compressed tile cache. */ - free_columns_manually = 0; - if (status != 0) { - process_status_err(status); - Py_DECREF(outdata); - outdata = NULL; - } - -fail: - // CFITSIO will free this object in the ffchdu function by way of - // fits_close_file; we need to let CFITSIO handle this so that it also - // cleans up the compressed tile cache - but that's only necessary in case - // we called "fits_read_img"... - if (free_columns_manually && columns != NULL) { - free(columns); - if (fileptr != NULL && fileptr->Fptr != NULL) { - fileptr->Fptr->tableptr = NULL; - } - } - - if (fileptr != NULL) { - status = 1;// Disable header-related errors - fits_close_file(fileptr, &status); - if (status != 1) { - process_status_err(status); - outdata = NULL; - } - } - - if (znaxis != NULL) { - PyMem_Free(znaxis); - } - - // Clear any messages remaining in CFITSIO's error stack - fits_clear_errmsg(); - - return (PyObject*) outdata; -} - - -/* CFITSIO version float as returned by fits_get_version() */ -static double cfitsio_version; - - -int compression_module_init(PyObject* module) { - /* Python version-independent initialization routine for the - compression module. Returns 0 on success and -1 (with exception set) - on failure. */ - PyObject* tmp; - float version_tmp; - int ret; - - fits_get_version(&version_tmp); - cfitsio_version = (double) version_tmp; - /* The conversion to double can lead to some rounding errors; round to the - nearest 3 decimal places, which should be accurate for any past or - current CFITSIO version. This is why relying on floats for version - comparison isn't generally a bright idea... */ - cfitsio_version = floor((1000 * version_tmp + 0.5)) / 1000; - - tmp = PyFloat_FromDouble(cfitsio_version); - if (tmp == NULL) { - return -1; - } - ret = PyObject_SetAttrString(module, "CFITSIO_VERSION", tmp); - Py_DECREF(tmp); - return ret; -} - - -/* Method table mapping names to wrappers */ -static PyMethodDef compression_methods[] = -{ - {"compress_hdu", compression_compress_hdu, METH_VARARGS}, - {"decompress_hdu", compression_decompress_hdu, METH_VARARGS}, - {NULL, NULL} -}; - -static struct PyModuleDef compressionmodule = { - PyModuleDef_HEAD_INIT, - "compression", - "astropy.compression module", - -1, /* No global state */ - compression_methods -}; - -PyObject * -PyInit_compression(void) -{ - PyObject* module = PyModule_Create(&compressionmodule); - if (module == NULL) { - return NULL; - } - if (compression_module_init(module)) { - Py_DECREF(module); - return NULL; - } - - /* Needed to use Numpy routines */ - /* Note -- import_array() is a macro that behaves differently in Python2.x - * vs. Python 3. See the discussion at: - * https://groups.google.com/d/topic/astropy-dev/6_AesAsCauM/discussion - */ - import_array(); - return module; -} diff --git a/astropy/io/fits/src/compressionmodule.h b/astropy/io/fits/src/compressionmodule.h deleted file mode 100644 index 7c13387a5ba..00000000000 --- a/astropy/io/fits/src/compressionmodule.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef _COMPRESSIONMODULE_H -#define _COMPRESSIONMODULE_H - - -/* CFITSIO version-specific feature support */ -#ifndef CFITSIO_MAJOR - // Define a minimized version - #define CFITSIO_MAJOR 0 - #ifdef _MSC_VER - #pragma warning ( "CFITSIO_MAJOR not defined; your CFITSIO version may be too old; compile at your own risk" ) - #else - #warning "CFITSIO_MAJOR not defined; your CFITSIO version may be too old; compile at your own risk" - #endif -#endif - -#ifndef CFITSIO_MINOR - #define CFITSIO_MINOR 0 -#endif - - -#if CFITSIO_MAJOR == 3 && CFITSIO_MINOR < 35 - #ifdef _MSC_VER - #pragma warning ( "your CFITSIO version is too old; use 3.35 or later" ) - #else - #warning "your CFITSIO version is too old; use 3.35 or later" - #endif -#endif - - -/* These defaults mirror the defaults in io.fits.hdu.compressed */ -#define DEFAULT_COMPRESSION_TYPE "RICE_1" -#define DEFAULT_QUANTIZE_LEVEL 16.0 -#define DEFAULT_HCOMP_SCALE 0 -#define DEFAULT_HCOMP_SMOOTH 0 -#define DEFAULT_BLOCK_SIZE 32 -#define DEFAULT_BYTE_PIX 4 - -/* This constant is defined by cfitsio in imcompress.c */ -#define NO_QUANTIZE 9999 - -#endif From f26913d0abeab7f812a4bbd8ec65a7a7ed917f4e Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Thu, 24 Nov 2022 16:04:04 +0000 Subject: [PATCH 06/32] Use new Python compress_hdu and decompress_hdu functions --- astropy/io/fits/hdu/compressed.py | 31 +++---------------- .../fits/tests/test_compression_failures.py | 2 +- .../tests/test_tiled_compression.py | 16 +--------- 3 files changed, 7 insertions(+), 42 deletions(-) diff --git a/astropy/io/fits/hdu/compressed.py b/astropy/io/fits/hdu/compressed.py index 2e10cfa5ae5..a678aca67e0 100644 --- a/astropy/io/fits/hdu/compressed.py +++ b/astropy/io/fits/hdu/compressed.py @@ -17,6 +17,7 @@ from astropy.io.fits.column import TDEF_RE, ColDefs, Column from astropy.io.fits.fitsrec import FITS_rec from astropy.io.fits.header import Header +from astropy.io.fits.tiled_compression import compress_hdu, decompress_hdu from astropy.io.fits.util import ( _get_array_mmap, _is_int, @@ -30,13 +31,7 @@ from .image import ImageHDU from .table import BinTableHDU -try: - from astropy.io.fits import compression - - COMPRESSION_SUPPORTED = COMPRESSION_ENABLED = True -except ImportError: - COMPRESSION_SUPPORTED = COMPRESSION_ENABLED = False - +COMPRESSION_ENABLED = True # Quantization dithering method constants; these are right out of fitsio.h NO_DITHER = -1 @@ -642,14 +637,6 @@ def __init__( same image will always use the same seed. """ - if not COMPRESSION_SUPPORTED: - # TODO: Raise a more specific Exception type - raise Exception( - "The astropy.io.fits.compression module is not " - "available. Creation of compressed image HDUs is " - "disabled." - ) - compression_type = CMTYPE_ALIASES.get(compression_type, compression_type) if data is DELAYED: @@ -764,16 +751,8 @@ def match_header(cls, header): if "ZIMAGE" not in header or not header["ZIMAGE"]: return False - if COMPRESSION_SUPPORTED and COMPRESSION_ENABLED: + if COMPRESSION_ENABLED: return True - elif not COMPRESSION_SUPPORTED: - warnings.warn( - "Failure matching header to a compressed image " - "HDU: The compression module is not available.\n" - "The HDU will be treated as a Binary Table HDU.", - AstropyUserWarning, - ) - return False else: # Compression is supported but disabled; just pass silently (#92) return False @@ -1495,7 +1474,7 @@ def _update_header_data( @lazyproperty def data(self): # The data attribute is the image data (not the table data). - data = compression.decompress_hdu(self) + data = decompress_hdu(self) if data is None: return data @@ -1836,7 +1815,7 @@ def _update_compressed_data(self): # self.compressed_data, and writes directly to it # compress_hdu returns the size of the heap for the written # compressed image table - heapsize, self.compressed_data = compression.compress_hdu(self) + heapsize, self.compressed_data = compress_hdu(self) finally: # if data was byteswapped return it to its original order if should_swap: diff --git a/astropy/io/fits/tests/test_compression_failures.py b/astropy/io/fits/tests/test_compression_failures.py index 3117c5fdb1c..98bddfcd791 100644 --- a/astropy/io/fits/tests/test_compression_failures.py +++ b/astropy/io/fits/tests/test_compression_failures.py @@ -4,7 +4,7 @@ import pytest from astropy.io import fits -from astropy.io.fits.compression import compress_hdu +from astropy.io.fits.tiled_compression import compress_hdu from .conftest import FitsTestCase diff --git a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py index 28edc9852c3..0233c8f4bd4 100644 --- a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py @@ -108,12 +108,6 @@ def test_decompress_hdu(tmp_path, compression_type_dtype): assert_equal(data, original_data) - # NOTE: the following block can be removed once we remove the C implementation - from astropy.io.fits.compression import decompress_hdu as decompress_hdu_c - - data_c = decompress_hdu_c(hdu) - assert_equal(data, data_c) - hdulist.close() @@ -162,15 +156,7 @@ def test_compress_hdu(tmp_path, compression_type_dtype): heap_length, heap_data = compress_hdu(hdu) - # NOTE: the following block can be removed once we remove the C implementation - from astropy.io.fits.compression import compress_hdu as compress_hdu_c - - # Note that when CompImageHDU calls compress_hdu it converts to native byte - # order first, so we have to do this here. - hdu.data = hdu.data.astype(hdu.data.dtype.newbyteorder("=")) - heap_length_c, heap_data_c = compress_hdu_c(hdu) - - _assert_heap_same(heap_data, heap_data_c, 9) + # TODO: need to make this test more useful! @pytest.fixture From b51be83b3728be1392ae3662d5a2ce074c9d0886 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Thu, 24 Nov 2022 16:33:39 +0000 Subject: [PATCH 07/32] Various improvements to (de)compress_hdu --- astropy/io/fits/hdu/compressed.py | 14 -- .../tiled_compression/tiled_compression.py | 136 +++++++++++++----- 2 files changed, 102 insertions(+), 48 deletions(-) diff --git a/astropy/io/fits/hdu/compressed.py b/astropy/io/fits/hdu/compressed.py index a678aca67e0..983d8956d53 100644 --- a/astropy/io/fits/hdu/compressed.py +++ b/astropy/io/fits/hdu/compressed.py @@ -1781,17 +1781,6 @@ def _update_compressed_data(self): self.data - _pseudo_zero(self.data.dtype), dtype=f"=i{self.data.dtype.itemsize}", ) - should_swap = False - else: - should_swap = not self.data.dtype.isnative - - if should_swap: - if self.data.flags.writeable: - self.data.byteswap(True) - else: - # For read-only arrays, there is no way around making - # a byteswapped copy of the data. - self.data = self.data.byteswap(False) try: nrows = self._header["NAXIS2"] @@ -1817,9 +1806,6 @@ def _update_compressed_data(self): # compressed image table heapsize, self.compressed_data = compress_hdu(self) finally: - # if data was byteswapped return it to its original order - if should_swap: - self.data.byteswap(True) self.data = old_data # CFITSIO will write the compressed data in big-endian order diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 8bf786b26dc..5753d7f4a54 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -7,6 +7,7 @@ import numpy as np +from astropy.io.fits.hdu.base import BITPIX2DTYPE from astropy.io.fits.tiled_compression._compression import ( compress_hcompress_1_c, compress_plio_1_c, @@ -349,7 +350,7 @@ class HCompress1(Codec): codec_id = "FITS_HCOMPRESS1" - def __init__(self, scale: float, smooth: bool, bytepix: int, nx: int, ny: int): + def __init__(self, scale: int, smooth: bool, bytepix: int, nx: int, ny: int): self.scale = scale self.smooth = smooth self.bytepix = bytepix @@ -438,9 +439,17 @@ def compress_tile(buf, *, algorithm: str, **kwargs): return ALGORITHMS[algorithm](**kwargs).encode(buf) +def _tile_shape(header): + return tuple(header[f"ZTILE{idx}"] for idx in range(header["ZNAXIS"], 0, -1)) + + +def _data_shape(header): + return tuple(header[f"ZNAXIS{idx}"] for idx in range(header["ZNAXIS"], 0, -1)) + + def _header_to_settings(header): - tile_shape = (header["ZTILE2"], header["ZTILE1"]) + tile_shape = _tile_shape(header) settings = {} @@ -454,7 +463,7 @@ def _header_to_settings(header): settings["tilesize"] = np.product(tile_shape) elif header["ZCMPTYPE"] == "HCOMPRESS_1": settings["bytepix"] = 4 - settings["scale"] = header["ZVAL1"] + settings["scale"] = int(header["ZVAL1"]) settings["smooth"] = header["ZVAL2"] settings["nx"] = header["ZTILE2"] settings["ny"] = header["ZTILE1"] @@ -470,7 +479,8 @@ def _buffer_to_array(tile_buffer, header): and using the FITS header translates it into a numpy array with the correct dtype, endianess and shape. """ - tile_shape = (header["ZTILE2"], header["ZTILE1"]) + + tile_shape = _tile_shape(header) if header["ZCMPTYPE"].startswith("GZIP"): # This algorithm is taken from fitsio @@ -498,33 +508,71 @@ def _buffer_to_array(tile_buffer, header): return tile_data +def _check_compressed_header(header): + + # Check for overflows which might cause issues when calling C code + + for kw in ["ZNAXIS", "ZVAL1", "ZVAL2", "ZBLANK", "BLANK"]: + if kw in header: + if np.intc(header[kw]) < 0: + raise OverflowError() + + for i in range(1, header["ZNAXIS"] + 1): + for kw_name in ["ZNAXIS", "ZTILE"]: + kw = f"{kw_name}{i}" + if kw in header: + if np.int32(header[kw]) < 0: + raise OverflowError() + + for i in range(1, header["NAXIS"] + 1): + kw = f"NAXIS{i}" + if kw in header: + if np.int64(header[kw]) < 0: + raise OverflowError() + + for kw in ["TNULL1", "PCOUNT", "THEAP"]: + if kw in header: + if np.int64(header[kw]) < 0: + raise OverflowError() + + for kw in ["ZVAL3"]: + if kw in header: + if np.isinf(np.float32(header[kw])): + raise OverflowError() + + def decompress_hdu(hdu): """ Drop-in replacement for decompress_hdu from compressionmodule.c """ - tile_shape = (hdu._header["ZTILE2"], hdu._header["ZTILE1"]) - data_shape = (hdu._header["ZNAXIS1"], hdu._header["ZNAXIS2"]) + _check_compressed_header(hdu._header) + + tile_shape = _tile_shape(hdu._header) + data_shape = _data_shape(hdu._header) settings = _header_to_settings(hdu._header) - data = np.zeros(data_shape, dtype="i4") + data = np.zeros(data_shape, dtype=BITPIX2DTYPE[hdu._header["ZBITPIX"]]) - istart = 0 - jstart = 0 + istart = np.zeros(data.ndim, dtype=int) for cdata in hdu.compressed_data["COMPRESSED_DATA"]: tile_buffer = decompress_tile( cdata, algorithm=hdu._header["ZCMPTYPE"], **settings ) tile_data = _buffer_to_array(tile_buffer, hdu._header) - data[ - istart : istart + tile_shape[0], - jstart : jstart + tile_shape[1], - ] = tile_data - jstart += tile_shape[1] - if jstart >= data_shape[1]: - jstart = 0 - istart += tile_shape[0] + slices = tuple( + [ + slice(istart[idx], istart[idx] + tile_shape[idx]) + for idx in range(len(istart)) + ] + ) + data[slices] = tile_data + istart[-1] += tile_shape[-1] + for idx in range(data.ndim - 1, 0, -1): + if istart[idx] >= data_shape[idx]: + istart[idx] = 0 + istart[idx - 1] += tile_shape[idx - 1] return data @@ -534,30 +582,50 @@ def compress_hdu(hdu): Drop-in replacement for compress_hdu from compressionmodule.c """ + _check_compressed_header(hdu._header) + # For now this is very inefficient, just a proof of concept! settings = _header_to_settings(hdu._header) - tile_shape = (hdu._header["ZTILE2"], hdu._header["ZTILE1"]) - data_shape = (hdu._header["ZNAXIS1"], hdu._header["ZNAXIS2"]) + tile_shape = _tile_shape(hdu._header) + data_shape = _data_shape(hdu._header) compressed_bytes = [] - for i in range(0, data_shape[0], tile_shape[0]): - for j in range(0, data_shape[1], tile_shape[1]): - # TODO: deal with data not being integer number of tiles - data = hdu.data[i : i + tile_shape[0], j : j + tile_shape[1]] - # The original compress_hdu assumed the data was in native endian, so we - # change this here: - if hdu._header["ZCMPTYPE"].startswith("GZIP"): - # This is apparently needed so that our heap data agrees with - # the C implementation!? - data = data.astype(data.dtype.newbyteorder(">")) - else: - if not data.dtype.isnative: - data = data.astype(data.dtype.newbyteorder("=")) - cbytes = compress_tile(data, algorithm=hdu._header["ZCMPTYPE"], **settings) - compressed_bytes.append(cbytes) + istart = np.zeros(len(data_shape), dtype=int) + + while True: + + slices = tuple( + [ + slice(istart[idx], istart[idx] + tile_shape[idx]) + for idx in range(len(istart)) + ] + ) + + # TODO: deal with data not being integer number of tiles + data = hdu.data[slices] + # The original compress_hdu assumed the data was in native endian, so we + # change this here: + if hdu._header["ZCMPTYPE"].startswith("GZIP"): + # This is apparently needed so that our heap data agrees with + # the C implementation!? + data = data.astype(data.dtype.newbyteorder(">")) + else: + if not data.dtype.isnative: + data = data.astype(data.dtype.newbyteorder("=")) + cbytes = compress_tile(data, algorithm=hdu._header["ZCMPTYPE"], **settings) + compressed_bytes.append(cbytes) + + istart[-1] += tile_shape[-1] + for idx in range(data.ndim - 1, 0, -1): + if istart[idx] >= data_shape[idx]: + istart[idx] = 0 + istart[idx - 1] += tile_shape[idx - 1] + + if istart[0] >= data_shape[0]: + break heap_header = np.zeros(len(compressed_bytes) * 2, ">i4") for i in range(len(compressed_bytes)): From c3575fc968dc3ddfc2de4a92b0ef463bd79c229b Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Mon, 28 Nov 2022 10:42:34 +0000 Subject: [PATCH 08/32] Fix fitsio tests for all compression types --- .../fits/tiled_compression/src/compression.c | 29 +++++++--- .../tiled_compression/tests/test_fitsio.py | 2 +- .../tiled_compression/tiled_compression.py | 53 ++++++++++++++++--- 3 files changed, 67 insertions(+), 17 deletions(-) diff --git a/astropy/io/fits/tiled_compression/src/compression.c b/astropy/io/fits/tiled_compression/src/compression.c index 907bcf28f13..5df8705bd84 100644 --- a/astropy/io/fits/tiled_compression/src/compression.c +++ b/astropy/io/fits/tiled_compression/src/compression.c @@ -79,6 +79,8 @@ static PyObject *compress_plio_1_c(PyObject *self, PyObject *args) { Py_ssize_t count; PyObject *result; + int maxelem; + int npix; short *compressed_values; int compressed_length; int *decompressed_values; @@ -87,17 +89,20 @@ static PyObject *compress_plio_1_c(PyObject *self, PyObject *args) { return NULL; } - compressed_values = (short *)malloc(count * 4); + // maxelem adapted from cfitsio's imcomp_calc_max_elem function + maxelem = count; + + compressed_values = (short *)malloc(maxelem); decompressed_values = (int *)str; - count /= 4; + npix = count / 4; // Zero the compressed values array - for (int i = 0; i < count * 2; i++) { + for (int i = 0; i < maxelem / 2; i++) { compressed_values[i] = 0; } - compressed_length = pl_p2li(decompressed_values, 1, compressed_values, (int)count); + compressed_length = pl_p2li(decompressed_values, 1, compressed_values, npix); buf = (char *)compressed_values; @@ -126,9 +131,9 @@ static PyObject *decompress_plio_1_c(PyObject *self, PyObject *args) { // NOTE: the second *4 shouldn't be needed but ran into segfaults with // smaller buffers. - decompressed_values = (int *)malloc(tilesize * 4 * 4); + decompressed_values = (int *)malloc(sizeof(int) * tilesize); - pl_l2pi(compressed_values, 1, decompressed_values, (int)count); + pl_l2pi(compressed_values, 1, decompressed_values, tilesize); buf = (char *)decompressed_values; @@ -147,6 +152,7 @@ static PyObject *compress_rice_1_c(PyObject *self, PyObject *args) { int blocksize, bytepix; + int maxelem; unsigned char *compressed_values; int compressed_length; signed char *decompressed_values_byte; @@ -157,7 +163,10 @@ static PyObject *compress_rice_1_c(PyObject *self, PyObject *args) { return NULL; } - compressed_values = (unsigned char *)malloc(count * 4); + // maxelem adapted from cfitsio's imcomp_calc_max_elem function + maxelem = count + count / bytepix / blocksize + 2 + 4; + + compressed_values = (unsigned char *)malloc(maxelem); if (bytepix == 1) { decompressed_values_byte = (signed char *)str; @@ -225,6 +234,7 @@ static PyObject *compress_hcompress_1_c(PyObject *self, PyObject *args) { int bytepix, nx, ny, scale; int status=0; // Important to initialize this to zero otherwise will fail silently + int maxelem; char *compressed_values; int *decompressed_values_int; long long *decompressed_values_longlong; @@ -233,7 +243,10 @@ static PyObject *compress_hcompress_1_c(PyObject *self, PyObject *args) { return NULL; } - compressed_values = (char *)malloc(count * 4); + // maxelem adapted from cfitsio's imcomp_calc_max_elem function + maxelem = count / 4 * 2.2 + 26; + + compressed_values = (char *)malloc(maxelem); if (bytepix == 4) { decompressed_values_int = (int *)str; diff --git a/astropy/io/fits/tiled_compression/tests/test_fitsio.py b/astropy/io/fits/tiled_compression/tests/test_fitsio.py index 08402bdba10..95b180f5731 100644 --- a/astropy/io/fits/tiled_compression/tests/test_fitsio.py +++ b/astropy/io/fits/tiled_compression/tests/test_fitsio.py @@ -29,7 +29,7 @@ ids=lambda x: f"shape: {x}", ) def base_original_data(request, compression_type_dtype): - if compression_type_dtype[0] == "HCOMPRESS_1" and len(request.param) < 2: + if compression_type_dtype[0] == "HCOMPRESS_1" and len(request.param) != 2: pytest.xfail("HCOMPRESS is 2D only apparently") size = np.product(request.param) return np.arange(size).reshape(request.param) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 5753d7f4a54..9984808a076 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -471,7 +471,7 @@ def _header_to_settings(header): return settings -def _buffer_to_array(tile_buffer, header): +def _buffer_to_array(tile_buffer, header, tile_shape=None): """ Convert a buffer to an array using the header. @@ -480,7 +480,8 @@ def _buffer_to_array(tile_buffer, header): dtype, endianess and shape. """ - tile_shape = _tile_shape(header) + if tile_shape is None: + tile_shape = _tile_shape(header) if header["ZCMPTYPE"].startswith("GZIP"): # This algorithm is taken from fitsio @@ -499,6 +500,12 @@ def _buffer_to_array(tile_buffer, header): dtype = ">u1" tile_data = np.asarray(tile_buffer).view(dtype).reshape(tile_shape) else: + + # For RICE_1 compression the tiles that are on the edge can end up + # being padded, so we truncate excess values + if header["ZCMPTYPE"] in ("RICE_1", "PLIO_1"): + tile_buffer = tile_buffer[: np.product(tile_shape)] + if tile_buffer.format == "b": # NOTE: this feels like a Numpy bug - need to investigate tile_data = np.asarray(tile_buffer, dtype=np.uint8).reshape(tile_shape) @@ -560,14 +567,26 @@ def decompress_hdu(hdu): tile_buffer = decompress_tile( cdata, algorithm=hdu._header["ZCMPTYPE"], **settings ) - tile_data = _buffer_to_array(tile_buffer, hdu._header) - slices = tuple( + + # In the following, we don't need to special case tiles near the edge + # as Numpy will automatically ignore parts of the slices that are out + # of bounds. + tile_slices = tuple( [ slice(istart[idx], istart[idx] + tile_shape[idx]) for idx in range(len(istart)) ] ) - data[slices] = tile_data + + # For tiles near the edge, the tile shape from the header might not be + # correct so we have to pass the shape manually. + actual_tile_shape = data[tile_slices].shape + + tile_data = _buffer_to_array( + tile_buffer, hdu._header, tile_shape=actual_tile_shape + ) + + data[tile_slices] = tile_data istart[-1] += tile_shape[-1] for idx in range(data.ndim - 1, 0, -1): if istart[idx] >= data_shape[idx]: @@ -597,6 +616,9 @@ def compress_hdu(hdu): while True: + # In the following, we don't need to special case tiles near the edge + # as Numpy will automatically ignore parts of the slices that are out + # of bounds. slices = tuple( [ slice(istart[idx], istart[idx] + tile_shape[idx]) @@ -627,11 +649,26 @@ def compress_hdu(hdu): if istart[0] >= data_shape[0]: break - heap_header = np.zeros(len(compressed_bytes) * 2, ">i4") + header_len = len(compressed_bytes) * 2 + + heap_header = np.zeros(header_len, ">i4") for i in range(len(compressed_bytes)): heap_header[i * 2] = len(compressed_bytes[i]) heap_header[1 + i * 2] = heap_header[: i * 2 : 2].sum() + # For PLIO_1, the size of each heap element is a factor of two lower than + # the real size - not clear if this is deliberate or bug somewhere. + + for i in range(len(compressed_bytes)): + heap_header[i * 2] /= 2 + + compressed_bytes = b"".join(compressed_bytes) + + # For PLIO_1, it looks like the compressed data is byteswapped + if hdu._header["ZCMPTYPE"] == "PLIO_1": + compressed_bytes = ( + np.frombuffer(compressed_bytes, dtype="i2").tobytes() + ) - heap = heap_header.tobytes() + b"".join(compressed_bytes) + heap = heap_header.tobytes() + compressed_bytes - return heap_header[::2].sum(), np.frombuffer(heap, dtype=np.uint8) + return len(compressed_bytes), np.frombuffer(heap, dtype=np.uint8) From 1a283af041ac3d2b6b7fba3d48d34324636644d4 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Mon, 28 Nov 2022 23:34:06 +0000 Subject: [PATCH 09/32] More fixes to compress_hdu --- .../tiled_compression/tiled_compression.py | 75 +++++++++++++++++-- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 9984808a076..aa9d84030d3 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -521,25 +521,25 @@ def _check_compressed_header(header): for kw in ["ZNAXIS", "ZVAL1", "ZVAL2", "ZBLANK", "BLANK"]: if kw in header: - if np.intc(header[kw]) < 0: + if header[kw] > 0 and np.intc(header[kw]) < 0: raise OverflowError() for i in range(1, header["ZNAXIS"] + 1): for kw_name in ["ZNAXIS", "ZTILE"]: kw = f"{kw_name}{i}" if kw in header: - if np.int32(header[kw]) < 0: + if header[kw] > 0 and np.int32(header[kw]) < 0: raise OverflowError() for i in range(1, header["NAXIS"] + 1): kw = f"NAXIS{i}" if kw in header: - if np.int64(header[kw]) < 0: + if header[kw] > 0 and np.int64(header[kw]) < 0: raise OverflowError() for kw in ["TNULL1", "PCOUNT", "THEAP"]: if kw in header: - if np.int64(header[kw]) < 0: + if header[kw] > 0 and np.int64(header[kw]) < 0: raise OverflowError() for kw in ["ZVAL3"]: @@ -547,6 +547,61 @@ def _check_compressed_header(header): if np.isinf(np.float32(header[kw])): raise OverflowError() + # Validate data types + + for kw in ["ZSCALE", "ZZERO", "TZERO1", "TSCAL1"]: + if kw in header: + if not np.isreal(header[kw]): + raise TypeError(f"{kw} should be floating-point") + + for kw in ["TTYPE1", "TFORM1", "ZCMPTYPE", "ZNAME1", "ZQUANTIZ"]: + if kw in header: + if not isinstance(header[kw], str): + raise TypeError(f"{kw} should be a string") + + for kw in ["ZDITHER0"]: + if kw in header: + if not np.isreal(header[kw]) or not float(header[kw]).is_integer(): + raise TypeError(f"{kw} should be an integer") + + if "TFORM1" in header: + for valid in ["1PB", "1PI", "1PJ", "1QB", "1QI", "1QJ"]: + if header["TFORM1"].startswith(valid): + break + else: + raise RuntimeError(f"Invalid TFORM1: {header['TFORM1']}") + + # Check values + + for kw in ["TFIELDS", "PCOUNT"] + [ + f"NAXIS{idx + 1}" for idx in range(header["NAXIS"]) + ]: + if kw in header: + if header[kw] < 0: + raise ValueError(f"{kw} should not be negative.") + + for kw in ["ZNAXIS", "TFIELDS"]: + if kw in header: + if header[kw] < 0 or header[kw] > 999: + raise ValueError(f"{kw} should be in the range 0 to 999") + + if header["ZBITPIX"] not in [8, 16, 32, 64, -32, -64]: + raise ValueError(f"Invalid value for BITPIX: {header['ZBITPIX']}") + + if header["ZCMPTYPE"] not in [ + "GZIP_1", + "GZIP_2", + "PLIO_1", + "RICE_1", + "HCOMPRESS_1", + ]: + raise ValueError(f"Unrecognized compression type: {header['ZCMPTYPE']}") + + # Check that certain keys are present + + header["ZNAXIS"] + header["ZBITPIX"] + def decompress_hdu(hdu): """ @@ -601,6 +656,9 @@ def compress_hdu(hdu): Drop-in replacement for compress_hdu from compressionmodule.c """ + if not isinstance(hdu.data, np.ndarray): + raise TypeError("CompImageHDU.data must be a numpy.ndarray") + _check_compressed_header(hdu._header) # For now this is very inefficient, just a proof of concept! @@ -655,11 +713,12 @@ def compress_hdu(hdu): for i in range(len(compressed_bytes)): heap_header[i * 2] = len(compressed_bytes[i]) heap_header[1 + i * 2] = heap_header[: i * 2 : 2].sum() - # For PLIO_1, the size of each heap element is a factor of two lower than - # the real size - not clear if this is deliberate or bug somewhere. - for i in range(len(compressed_bytes)): - heap_header[i * 2] /= 2 + # For PLIO_1, the size of each heap element is a factor of two lower than + # the real size - not clear if this is deliberate or bug somewhere. + if hdu._header["ZCMPTYPE"] == "PLIO_1": + for i in range(len(compressed_bytes)): + heap_header[i * 2] /= 2 compressed_bytes = b"".join(compressed_bytes) From 03ee0635225675e4b2cb71dc3834232159da896c Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Fri, 25 Nov 2022 12:39:33 +0000 Subject: [PATCH 10/32] Improve the fitsio tests --- .../fits/tiled_compression/tests/conftest.py | 51 +++++-- .../tiled_compression/tests/test_fitsio.py | 130 +++++++++++------- .../tests/test_tiled_compression.py | 15 +- .../tiled_compression/tiled_compression.py | 14 +- tox.ini | 1 + 5 files changed, 136 insertions(+), 75 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tests/conftest.py b/astropy/io/fits/tiled_compression/tests/conftest.py index 2061460bbc9..01c04735487 100644 --- a/astropy/io/fits/tiled_compression/tests/conftest.py +++ b/astropy/io/fits/tiled_compression/tests/conftest.py @@ -8,23 +8,44 @@ "PLIO_1", ] -compression_dtype_parameters = [] -for compression_type in COMPRESSION_TYPES: - # io.fits doesn't seem able to compress 64-bit data, even though e.g. GZIP_? - # and HCOMPRESS_1 should be able to handle it. - for itemsize in [1, 2, 4]: - for endian in ["<", ">"]: - format = "u" if itemsize == 1 else "i" - compression_dtype_parameters.append( - (compression_type, f"{endian}{format}{itemsize}") - ) + +def _generate_comp_type_dtype_parameters(): + comp_type_dtype_parameters = [] + for compression_type in COMPRESSION_TYPES: + for types in { + # "float", + "integer", + }: + # io.fits doesn't seem able to compress 64-bit data, even though e.g. GZIP_? + # and HCOMPRESS_1 should be able to handle it. + itemsizes = { + "integer": [1, 2, 4], + "float": [4, 8], + }[types] + for itemsize in itemsizes: + for endian in ["<", ">"]: + format = "f" if types == "float" else "i" + format = "u" if itemsize == 1 else format + comp_type_dtype_parameters.append( + (compression_type, f"{endian}{format}{itemsize}") + ) + return comp_type_dtype_parameters @pytest.fixture( - params=compression_dtype_parameters, scope="module", ids=lambda x: f"{x[0]} {x[1]}" + scope="session", + params=_generate_comp_type_dtype_parameters(), + ids=lambda x: f"{x[0]}-{x[1]}", ) -def compression_type_dtype(request): - """ - This fixture provides a matrix of compression type and dtype parameters. - """ +def comp_type_dtype(request): return request.param + + +@pytest.fixture(scope="session") +def compression_type(comp_type_dtype): + return comp_type_dtype[0] + + +@pytest.fixture(scope="session") +def dtype(comp_type_dtype): + return comp_type_dtype[1] diff --git a/astropy/io/fits/tiled_compression/tests/test_fitsio.py b/astropy/io/fits/tiled_compression/tests/test_fitsio.py index 95b180f5731..4fa084db681 100644 --- a/astropy/io/fits/tiled_compression/tests/test_fitsio.py +++ b/astropy/io/fits/tiled_compression/tests/test_fitsio.py @@ -8,70 +8,102 @@ else in the code this shouldn't cause us any issues. Please bear this in mind when editing this file. """ +import itertools +import os + import numpy as np import pytest from astropy.io import fits -fitsio = pytest.importorskip("fitsio") +# This is so that tox can force this file to be run, and not be silently +# skipped on CI, but in all other test runs it's skipped. +if "ASTROPY_ALWAYS_TEST_FITSIO" in os.environ: + import fitsio +else: + fitsio = pytest.importorskip("fitsio") + + +@pytest.fixture(scope="session") +def numpy_rng(): + return np.random.default_rng() + + +def _expand(params): + """ + Expands a list of N iterables of parameters into a flat list with all + combinations of all parameters. + """ + expanded = [] + for ele in params: + expanded += list(itertools.product(*ele)) + return expanded @pytest.fixture( scope="module", - params=[ - (10,), - (12, 12), - (15, 15), - (15, 15, 15), - # >3D Data are not currently supported with astropy - # (15, 15, 15, 15), - ], - ids=lambda x: f"shape: {x}", + params=_expand( + [ + [((10,),), ((5,), (1,), (3,))], + [((12, 12),), ((1, 12), (4, 5), (6, 6))], + [((15, 15),), ((1, 15), (5, 1), (5, 5))], + [ + ((15, 15, 15),), + ((5, 5, 1), (5, 7, 1), (1, 5, 4), (1, 1, 15), (15, 1, 5)), + ], + # >3D Data are not currently supported by cfitsio + # [ + # ((15, 15, 15, 15),), + # ( + # (5, 5, 5, 5), + # (1, 5, 1, 5), + # (3, 1, 4, 5), + # ), + # ], + ], + ), + ids=lambda x: f"shape: {x[0]} tile_dims: {x[1]}", ) -def base_original_data(request, compression_type_dtype): - if compression_type_dtype[0] == "HCOMPRESS_1" and len(request.param) != 2: +def array_shapes_tile_dims(request, comp_type_dtype): + shape, tile_dim = request.param + compression_type = comp_type_dtype[0] + # H_COMPRESS needs >=2D data and always 2D tiles + if compression_type == "HCOMPRESS_1" and ( + len(shape) < 2 or np.count_nonzero(np.array(tile_dim) != 1) != 2 + ): pytest.xfail("HCOMPRESS is 2D only apparently") - size = np.product(request.param) - return np.arange(size).reshape(request.param) + return shape, tile_dim -@pytest.fixture( - scope="module", - ids=lambda x: f"tiles per axis: {x}", - params=[3, 2, 1, (1, 3, -99), (1, 2, 3), (1, -99, -99)], -) -def tile_dims(request, base_original_data, compression_type_dtype): - compression_type = compression_type_dtype[0] - tile_scale_factor = np.array(request.param) - if tile_scale_factor.ndim > 0: - tile_scale_factor = tile_scale_factor[: base_original_data.ndim] +@pytest.fixture(scope="module") +def tile_dims(array_shapes_tile_dims): + return array_shapes_tile_dims[1] - if compression_type == "HCOMPRESS_1": - # If HCOMPRESS then we can only have 2D tiles so all other dims must - # have tile length of 1 - if tile_scale_factor.ndim > 2: - tile_scale_factor[2:] = -99 - if np.count_nonzero(tile_scale_factor != -99) != 2: - pytest.xfail("HCOMPRESS needs two non-unity tile dimensions.") +@pytest.fixture(scope="module") +def data_shape(array_shapes_tile_dims): + return array_shapes_tile_dims[0] - tile_scale_factor = np.where( - tile_scale_factor == -99, base_original_data.shape, tile_scale_factor - ) - return tuple( - np.array(np.array(base_original_data.shape) / tile_scale_factor, dtype=int) - ) +@pytest.fixture(scope="module") +def base_original_data(data_shape, dtype, numpy_rng, compression_type): + random = numpy_rng.uniform(high=255, size=data_shape) + # There seems to be a bug with the fitsio library where HCOMPRESS doesn't + # work with int16 random data, so use a bit for structured test data. + if compression_type.startswith("HCOMPRESS") and "i2" in dtype or "u1" in dtype: + random = np.arange(np.product(data_shape)).reshape(data_shape) + return random.astype(dtype) @pytest.fixture(scope="module") def fitsio_compressed_file_path( tmp_path_factory, - compression_type_dtype, + comp_type_dtype, base_original_data, + data_shape, # For debuging tile_dims, ): - compression_type, dtype = compression_type_dtype + compression_type, dtype = comp_type_dtype if base_original_data.ndim > 2 and "u1" in dtype: pytest.xfail("These don't work") tmp_path = tmp_path_factory.mktemp("fitsio") @@ -86,9 +118,12 @@ def fitsio_compressed_file_path( @pytest.fixture(scope="module") def astropy_compressed_file_path( - tmp_path_factory, compression_type_dtype, base_original_data + comp_type_dtype, + tmp_path_factory, + base_original_data, + data_shape, # For debuging ): - compression_type, dtype = compression_type_dtype + compression_type, dtype = comp_type_dtype if base_original_data.ndim > 2 and "u1" in dtype: pytest.xfail("These don't work") original_data = base_original_data.astype(dtype) @@ -102,9 +137,11 @@ def astropy_compressed_file_path( def test_decompress( - fitsio_compressed_file_path, base_original_data, compression_type_dtype + fitsio_compressed_file_path, + base_original_data, + compression_type, + dtype, ): - compression_type, dtype = compression_type_dtype with fits.open(fitsio_compressed_file_path) as hdul: data = hdul[1].data @@ -117,10 +154,11 @@ def test_decompress( def test_compress( - astropy_compressed_file_path, base_original_data, compression_type_dtype + astropy_compressed_file_path, + base_original_data, + compression_type, + dtype, ): - compression_type, dtype = compression_type_dtype - fits = fitsio.FITS(astropy_compressed_file_path, "r") header = fits[1].read_header() data = fits[1].read() diff --git a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py index 0233c8f4bd4..12d1a0beef4 100644 --- a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py @@ -17,9 +17,7 @@ ) -def test_basic(tmp_path, compression_type_dtype): - compression_type, dtype = compression_type_dtype - +def test_basic(tmp_path, compression_type, dtype): # Generate compressed file dynamically original_data = np.arange(144).reshape((12, 12)).astype(dtype) @@ -83,9 +81,7 @@ def test_basic(tmp_path, compression_type_dtype): hdulist_new.close() -def test_decompress_hdu(tmp_path, compression_type_dtype): - compression_type, dtype = compression_type_dtype - +def test_decompress_hdu(tmp_path, compression_type, dtype): # NOTE: for now this test is designed to compare the Python implementation # of decompress_hdu with the C implementation - once we get rid of the C # implementation we should update this test. @@ -136,16 +132,11 @@ def _assert_heap_same(heap_a, heap_b, n_tiles): assert_equal(data_a, data_b) -def test_compress_hdu(tmp_path, compression_type_dtype): - compression_type, dtype = compression_type_dtype - +def test_compress_hdu(compression_type, dtype): # NOTE: for now this test is designed to compare the Python implementation # of compress_hdu with the C implementation - once we get rid of the C # implementation we should update this test. - if compression_type == "PLIO_1": - pytest.xfail() - original_data = np.arange(144).reshape((12, 12)).astype(dtype) header = fits.Header() diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index aa9d84030d3..7df22641ae9 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -465,8 +465,18 @@ def _header_to_settings(header): settings["bytepix"] = 4 settings["scale"] = int(header["ZVAL1"]) settings["smooth"] = header["ZVAL2"] - settings["nx"] = header["ZTILE2"] - settings["ny"] = header["ZTILE1"] + # HCOMPRESS requires 2D tiles, so to find the shape of the 2D tile we + # need to ignore all length 1 tile dimensions + # Also cfitsio expects the tile shape in C order, so reverse it + shape_2d = tuple( + header[f"ZTILE{n}"] + for n in range(header["ZNAXIS"], 0, -1) + if header[f"ZTILE{n}"] != 1 + ) + if len(shape_2d) != 2: + raise ValueError(f"HCOMPRESS expects two dimensional tiles, got {shape_2d}") + settings["nx"] = shape_2d[0] + settings["ny"] = shape_2d[1] return settings diff --git a/tox.ini b/tox.ini index 600b55a0941..7ab75802614 100644 --- a/tox.ini +++ b/tox.ini @@ -31,6 +31,7 @@ setenv = clocale: LC_CTYPE = C.ascii clocale: LC_ALL = C devdeps: PIP_EXTRA_INDEX_URL = https://pypi.anaconda.org/scipy-wheels-nightly/simple + fitsio: ASTROPY_ALWAYS_TESTS_FITSIO = "true" # Run the tests in a temporary directory to make sure that we don't import # astropy from the source tree From 625aba6fb3e8c54a3e45e67092cb850fb125201a Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Tue, 29 Nov 2022 15:02:51 +0000 Subject: [PATCH 11/32] Support lossless GZIP decompression (with fitsio tests) --- .../fits/tiled_compression/tests/conftest.py | 74 ++++++++++++------- .../tiled_compression/tests/test_fitsio.py | 71 +++++++++--------- .../tiled_compression/tiled_compression.py | 13 +++- 3 files changed, 88 insertions(+), 70 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tests/conftest.py b/astropy/io/fits/tiled_compression/tests/conftest.py index 01c04735487..ad2e98e688b 100644 --- a/astropy/io/fits/tiled_compression/tests/conftest.py +++ b/astropy/io/fits/tiled_compression/tests/conftest.py @@ -1,3 +1,5 @@ +import itertools + import pytest COMPRESSION_TYPES = [ @@ -9,43 +11,59 @@ ] -def _generate_comp_type_dtype_parameters(): - comp_type_dtype_parameters = [] - for compression_type in COMPRESSION_TYPES: - for types in { - # "float", - "integer", - }: - # io.fits doesn't seem able to compress 64-bit data, even though e.g. GZIP_? - # and HCOMPRESS_1 should be able to handle it. - itemsizes = { - "integer": [1, 2, 4], - "float": [4, 8], - }[types] - for itemsize in itemsizes: - for endian in ["<", ">"]: - format = "f" if types == "float" else "i" - format = "u" if itemsize == 1 else format - comp_type_dtype_parameters.append( - (compression_type, f"{endian}{format}{itemsize}") - ) - return comp_type_dtype_parameters +def _expand(*params): + """ + Expands a list of N iterables of parameters into a flat list with all + combinations of all parameters. + """ + expanded = [] + for ele in params: + expanded += list(itertools.product(*ele)) + return expanded + + +ALL_INTEGER_DTYPES = [ + "".join(ele) + for ele in _expand([("<", ">"), ("i",), ("2", "4")], [("<", ">"), ("u",), ("1",)]) +] +ALL_FLOAT_DTYPES = ["".join(ele) for ele in _expand([("<", ">"), ("f",), ("4", "8")])] @pytest.fixture( scope="session", - params=_generate_comp_type_dtype_parameters(), - ids=lambda x: f"{x[0]}-{x[1]}", + ids=lambda x: " ".join(map(str, x)), + # The params here are compression type, parameters for the compression / + # quantise and dtype + params=_expand( + # Test all compression types with default compression parameters for + # all integers + [ + COMPRESSION_TYPES, + ({},), + ALL_INTEGER_DTYPES, + ], + # GZIP supports lossless non-quantized floating point data + [ + ("GZIP_1", "GZIP_2"), + ({"qlevel": None},), + ALL_FLOAT_DTYPES, + ], + ), ) -def comp_type_dtype(request): +def comp_param_dtype(request): return request.param @pytest.fixture(scope="session") -def compression_type(comp_type_dtype): - return comp_type_dtype[0] +def compression_type(comp_param_dtype): + return comp_param_dtype[0] + + +@pytest.fixture(scope="session") +def compression_param(comp_param_dtype): + return comp_param_dtype[1] @pytest.fixture(scope="session") -def dtype(comp_type_dtype): - return comp_type_dtype[1] +def dtype(comp_param_dtype): + return comp_param_dtype[2] diff --git a/astropy/io/fits/tiled_compression/tests/test_fitsio.py b/astropy/io/fits/tiled_compression/tests/test_fitsio.py index 4fa084db681..88b13f8d948 100644 --- a/astropy/io/fits/tiled_compression/tests/test_fitsio.py +++ b/astropy/io/fits/tiled_compression/tests/test_fitsio.py @@ -8,7 +8,6 @@ else in the code this shouldn't cause us any issues. Please bear this in mind when editing this file. """ -import itertools import os import numpy as np @@ -16,6 +15,8 @@ from astropy.io import fits +from .conftest import _expand + # This is so that tox can force this file to be run, and not be silently # skipped on CI, but in all other test runs it's skipped. if "ASTROPY_ALWAYS_TEST_FITSIO" in os.environ: @@ -29,44 +30,30 @@ def numpy_rng(): return np.random.default_rng() -def _expand(params): - """ - Expands a list of N iterables of parameters into a flat list with all - combinations of all parameters. - """ - expanded = [] - for ele in params: - expanded += list(itertools.product(*ele)) - return expanded - - @pytest.fixture( scope="module", params=_expand( + [((10,),), ((5,), (1,), (3,))], + [((12, 12),), ((1, 12), (4, 5), (6, 6))], + [((15, 15),), ((1, 15), (5, 1), (5, 5))], [ - [((10,),), ((5,), (1,), (3,))], - [((12, 12),), ((1, 12), (4, 5), (6, 6))], - [((15, 15),), ((1, 15), (5, 1), (5, 5))], - [ - ((15, 15, 15),), - ((5, 5, 1), (5, 7, 1), (1, 5, 4), (1, 1, 15), (15, 1, 5)), - ], - # >3D Data are not currently supported by cfitsio - # [ - # ((15, 15, 15, 15),), - # ( - # (5, 5, 5, 5), - # (1, 5, 1, 5), - # (3, 1, 4, 5), - # ), - # ], + ((15, 15, 15),), + ((5, 5, 1), (5, 7, 1), (1, 5, 4), (1, 1, 15), (15, 1, 5)), ], + # >3D Data are not currently supported by cfitsio + # [ + # ((15, 15, 15, 15),), + # ( + # (5, 5, 5, 5), + # (1, 5, 1, 5), + # (3, 1, 4, 5), + # ), + # ], ), ids=lambda x: f"shape: {x[0]} tile_dims: {x[1]}", ) -def array_shapes_tile_dims(request, comp_type_dtype): +def array_shapes_tile_dims(request, compression_type): shape, tile_dim = request.param - compression_type = comp_type_dtype[0] # H_COMPRESS needs >=2D data and always 2D tiles if compression_type == "HCOMPRESS_1" and ( len(shape) < 2 or np.count_nonzero(np.array(tile_dim) != 1) != 2 @@ -98,12 +85,12 @@ def base_original_data(data_shape, dtype, numpy_rng, compression_type): @pytest.fixture(scope="module") def fitsio_compressed_file_path( tmp_path_factory, - comp_type_dtype, + comp_param_dtype, base_original_data, data_shape, # For debuging tile_dims, ): - compression_type, dtype = comp_type_dtype + compression_type, param, dtype = comp_param_dtype if base_original_data.ndim > 2 and "u1" in dtype: pytest.xfail("These don't work") tmp_path = tmp_path_factory.mktemp("fitsio") @@ -111,26 +98,34 @@ def fitsio_compressed_file_path( filename = tmp_path / f"{compression_type}_{dtype}.fits" fits = fitsio.FITS(filename, "rw") - fits.write(original_data, compress=compression_type, tile_dims=tile_dims) + fits.write(original_data, compress=compression_type, tile_dims=tile_dims, **param) return filename @pytest.fixture(scope="module") def astropy_compressed_file_path( - comp_type_dtype, + comp_param_dtype, tmp_path_factory, base_original_data, data_shape, # For debuging ): - compression_type, dtype = comp_type_dtype - if base_original_data.ndim > 2 and "u1" in dtype: - pytest.xfail("These don't work") + compression_type, param, dtype = comp_param_dtype original_data = base_original_data.astype(dtype) tmp_path = tmp_path_factory.mktemp("astropy") filename = tmp_path / f"{compression_type}_{dtype}.fits" - hdu = fits.CompImageHDU(data=original_data, compression_type=compression_type) + # Convert fitsio kwargs to astropy kwargs + _map = {"qlevel": "quantize_level"} + param = {_map[k]: v for k, v in param.items()} + + # Map quantize_level + if param.get("quantize_level", "missing") is None: + param["quantize_level"] = 0.0 + + hdu = fits.CompImageHDU( + data=original_data, compression_type=compression_type, **param + ) hdu.writeto(filename) return filename diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 7df22641ae9..915a47650ad 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -454,7 +454,7 @@ def _header_to_settings(header): settings = {} if header["ZCMPTYPE"] == "GZIP_2": - settings["itemsize"] = header["ZBITPIX"] // 8 + settings["itemsize"] = abs(header["ZBITPIX"]) // 8 elif header["ZCMPTYPE"] == "PLIO_1": settings["tilesize"] = np.product(tile_shape) elif header["ZCMPTYPE"] == "RICE_1": @@ -501,10 +501,15 @@ def _buffer_to_array(tile_buffer, header, tile_shape=None): if tilebytesize == tilelen * 2: dtype = ">i2" elif tilebytesize == tilelen * 4: - # TOOD: support float32? - dtype = ">i4" + if header["ZBITPIX"] < 0: + dtype = ">f4" + else: + dtype = ">i4" elif tilebytesize == tilelen * 8: - dtype = ">f8" + if header["ZBITPIX"] < 0: + dtype = ">f8" + else: + dtype = ">i8" else: # Just return the raw bytes dtype = ">u1" From 55a6839613361cd36b052b5f0b3e84fedf9d7644 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Tue, 29 Nov 2022 11:23:06 +0000 Subject: [PATCH 12/32] Use cfitsio for quantization --- .../fits/tiled_compression/setup_package.py | 2 + .../fits/tiled_compression/src/compression.c | 204 +++++++++++++++++- .../io/fits/tiled_compression/src/fitsio2.h | 10 + .../fits/tiled_compression/src/imcompress.h | 90 ++++++++ .../io/fits/tiled_compression/src/quantize.h | 7 + .../tiled_compression/tiled_compression.py | 113 ++++++++++ 6 files changed, 424 insertions(+), 2 deletions(-) create mode 100644 astropy/io/fits/tiled_compression/src/imcompress.h create mode 100644 astropy/io/fits/tiled_compression/src/quantize.h diff --git a/astropy/io/fits/tiled_compression/setup_package.py b/astropy/io/fits/tiled_compression/setup_package.py index 25370610555..c5b1f953a03 100644 --- a/astropy/io/fits/tiled_compression/setup_package.py +++ b/astropy/io/fits/tiled_compression/setup_package.py @@ -17,6 +17,8 @@ def get_extensions(): os.path.join("cextern", "cfitsio", "lib", "ricecomp.c"), os.path.join("cextern", "cfitsio", "lib", "fits_hcompress.c"), os.path.join("cextern", "cfitsio", "lib", "fits_hdecompress.c"), + os.path.join("cextern", "cfitsio", "lib", "quantize.c"), + os.path.join("cextern", "cfitsio", "lib", "imcompress.c"), ], include_dirs=[SRC_DIR], ) diff --git a/astropy/io/fits/tiled_compression/src/compression.c b/astropy/io/fits/tiled_compression/src/compression.c index 5df8705bd84..0208e217e7e 100644 --- a/astropy/io/fits/tiled_compression/src/compression.c +++ b/astropy/io/fits/tiled_compression/src/compression.c @@ -4,7 +4,9 @@ #include #include #include +#include #include +#include #include // TODO: use better estimates for compressed buffer sizes, as done in @@ -29,8 +31,14 @@ static char compress_plio_1_c_docstring[] = "Compress data using PLIO_1"; static char decompress_plio_1_c_docstring[] = "Decompress data using PLIO_1"; static char compress_rice_1_c_docstring[] = "Compress data using RICE_1"; static char decompress_rice_1_c_docstring[] = "Decompress data using RICE_1"; -static char compress_hcompress_1_c_docstring[] = "Compress data using HCOMPRESS_1"; -static char decompress_hcompress_1_c_docstring[] = "Decompress data using HCOMPRESS_1"; +static char compress_hcompress_1_c_docstring[] = + "Compress data using HCOMPRESS_1"; +static char decompress_hcompress_1_c_docstring[] = + "Decompress data using HCOMPRESS_1"; +static char quantize_float_c_docstring[] = "Quantize float data"; +static char quantize_double_c_docstring[] = "Quantize float data"; +static char unquantize_float_c_docstring[] = "Unquantize data to float"; +static char unquantize_double_c_docstring[] = "Unquantize data to double"; /* Declare the C functions here. */ static PyObject *compress_plio_1_c(PyObject *self, PyObject *args); @@ -39,6 +47,10 @@ static PyObject *compress_rice_1_c(PyObject *self, PyObject *args); static PyObject *decompress_rice_1_c(PyObject *self, PyObject *args); static PyObject *compress_hcompress_1_c(PyObject *self, PyObject *args); static PyObject *decompress_hcompress_1_c(PyObject *self, PyObject *args); +static PyObject *quantize_float_c(PyObject *self, PyObject *args); +static PyObject *quantize_double_c(PyObject *self, PyObject *args); +static PyObject *unquantize_float_c(PyObject *self, PyObject *args); +static PyObject *unquantize_double_c(PyObject *self, PyObject *args); /* Define the methods that will be available on the module. */ static PyMethodDef module_methods[] = { @@ -48,6 +60,10 @@ static PyMethodDef module_methods[] = { {"decompress_rice_1_c", decompress_rice_1_c, METH_VARARGS, decompress_rice_1_c_docstring}, {"compress_hcompress_1_c", compress_hcompress_1_c, METH_VARARGS, compress_hcompress_1_c_docstring}, {"decompress_hcompress_1_c", decompress_hcompress_1_c, METH_VARARGS, decompress_hcompress_1_c_docstring}, + {"quantize_float_c", quantize_float_c, METH_VARARGS, quantize_float_c_docstring}, + {"quantize_double_c", quantize_double_c, METH_VARARGS, quantize_double_c_docstring}, + {"unquantize_float_c", unquantize_float_c, METH_VARARGS, unquantize_float_c_docstring}, + {"unquantize_double_c", unquantize_double_c, METH_VARARGS, unquantize_double_c_docstring}, {NULL, NULL, 0, NULL} }; @@ -297,3 +313,187 @@ static PyObject *decompress_hcompress_1_c(PyObject *self, PyObject *args) { free(dbytes); return result; } + +static PyObject *quantize_float_c(PyObject *self, PyObject *args) { + + const char *input_bytes; + Py_ssize_t nbytes; + PyObject *result; + + float *input_data; + + long row, nx, ny; + int nullcheck; + float in_null_value; + float qlevel; + int dither_method; + + int *quantized_data; + char *quantized_bytes; + double bscale, bzero; + int iminval, imaxval; + + if (!PyArg_ParseTuple(args, "y#lllidfi", &input_bytes, &nbytes, &row, &nx, + &ny, &nullcheck, &in_null_value, &qlevel, + &dither_method)) { + return NULL; + } + + input_data = (float *)input_bytes; + quantized_data = (int *)malloc(nx * ny * sizeof(int)); + + fits_quantize_float(row, input_data, nx, ny, nullcheck, in_null_value, qlevel, + dither_method, quantized_data, &bscale, &bzero, &iminval, + &imaxval); + + quantized_bytes = (char *)quantized_data; + + result = Py_BuildValue("y#ddii", quantized_bytes, nx * ny * sizeof(int), + bscale, bzero, iminval, imaxval); + free(quantized_bytes); + return result; +} + +static PyObject *quantize_double_c(PyObject *self, PyObject *args) { + + const char *input_bytes; + Py_ssize_t nbytes; + PyObject *result; + + double *input_data; + + long row, nx, ny; + int nullcheck; + double in_null_value; + float qlevel; + int dither_method; + + int *quantized_data; + char *quantized_bytes; + double bscale, bzero; + int iminval, imaxval; + + if (!PyArg_ParseTuple(args, "y#lllidfi", &input_bytes, &nbytes, &row, &nx, + &ny, &nullcheck, &in_null_value, &qlevel, + &dither_method)) { + return NULL; + } + + input_data = (double *)input_bytes; + quantized_data = (int *)malloc(nx * ny * sizeof(int)); + + fits_quantize_double(row, input_data, nx, ny, nullcheck, in_null_value, + qlevel, dither_method, quantized_data, &bscale, &bzero, + &iminval, &imaxval); + + quantized_bytes = (char *)quantized_data; + + result = Py_BuildValue("y#ddii", quantized_bytes, nx * ny * sizeof(int), + bscale, bzero, iminval, imaxval); + free(quantized_bytes); + return result; +} + +static PyObject *unquantize_float_c(PyObject *self, PyObject *args) { + + const char *input_bytes; + Py_ssize_t nbytes; + PyObject *result; + + long row, npix; + int nullcheck; + int tnull; + float nullval; + int dither_method; + + double bscale, bzero; + int bytepix; // int size + int status = 0; + + int *anynull; + float *output_data; + char *output_bytes; + + if (!PyArg_ParseTuple(args, "y#llddiiifi", &input_bytes, &nbytes, &row, &npix, + &bscale, &bzero, &dither_method, &nullcheck, &tnull, + &nullval, &bytepix)) { + return NULL; + } + +// TODO: add support, if needed, for nullcheck=1 + +anynull = (int *)malloc(npix * sizeof(int)); +output_data = (float *)malloc(npix * sizeof(float)); + +if (bytepix == 1) { + unquantize_i1r4(row, (unsigned char *)input_bytes, npix, bscale, bzero, + dither_method, nullcheck, (unsigned char)tnull, nullval, + NULL, anynull, output_data, &status); +} else if (bytepix == 2) { + unquantize_i2r4(row, (short *)input_bytes, npix, bscale, bzero, + dither_method, nullcheck, (short)tnull, nullval, NULL, + anynull, output_data, &status); +} else if (bytepix == 4) { + unquantize_i4r4(row, (int *)input_bytes, npix, bscale, bzero, dither_method, + nullcheck, (int)tnull, nullval, NULL, anynull, output_data, + &status); +} + +output_bytes = (char *)output_data; + + result = Py_BuildValue("y#", output_bytes, npix * sizeof(float)); + free(output_bytes); + return result; +} + +static PyObject *unquantize_double_c(PyObject *self, PyObject *args) { + + const char *input_bytes; + Py_ssize_t nbytes; + PyObject *result; + + long row, npix; + int nullcheck; + int tnull; + double nullval; + int dither_method; + + double bscale, bzero; + int bytepix; // int size + int status = 0; + + int *anynull; + double *output_data; + char *output_bytes; + + if (!PyArg_ParseTuple(args, "y#llddiiidi", &input_bytes, &nbytes, &row, &npix, + &bscale, &bzero, &dither_method, &nullcheck, &tnull, + &nullval, &bytepix)) { + return NULL; + } + +// TODO: add support, if needed, for nullcheck=1 + +anynull = (int *)malloc(npix * sizeof(int)); +output_data = (double *)malloc(npix * sizeof(double)); + +if (bytepix == 1) { + unquantize_i1r8(row, (unsigned char *)input_bytes, npix, bscale, bzero, + dither_method, nullcheck, (unsigned char)tnull, nullval, + NULL, anynull, output_data, &status); +} else if (bytepix == 2) { + unquantize_i2r8(row, (short *)input_bytes, npix, bscale, bzero, + dither_method, nullcheck, (short)tnull, nullval, NULL, + anynull, output_data, &status); +} else if (bytepix == 4) { + unquantize_i4r8(row, (int *)input_bytes, npix, bscale, bzero, dither_method, + nullcheck, (int)tnull, nullval, NULL, anynull, output_data, + &status); +} + +output_bytes = (char *)output_data; + + result = Py_BuildValue("y#", output_bytes, npix * sizeof(double)); + free(output_bytes); + return result; +} diff --git a/astropy/io/fits/tiled_compression/src/fitsio2.h b/astropy/io/fits/tiled_compression/src/fitsio2.h index 16fcfff8734..35bfb4a61ae 100644 --- a/astropy/io/fits/tiled_compression/src/fitsio2.h +++ b/astropy/io/fits/tiled_compression/src/fitsio2.h @@ -45,3 +45,13 @@ extern int Fitsio_Pthread_Status; #define FFUNLOCK #define ffstrtok(str, tok, save) strtok(str, tok) #endif + +#define N_RANDOM 10000 /* DO NOT CHANGE THIS; used when quantizing real numbers */ + +#define MEMORY_ALLOCATION 113 /* Could not allocate memory */ + +#define NO_DITHER -1 +#define SUBTRACTIVE_DITHER_1 1 +#define SUBTRACTIVE_DITHER_2 2 + +int fits_init_randoms(void); diff --git a/astropy/io/fits/tiled_compression/src/imcompress.h b/astropy/io/fits/tiled_compression/src/imcompress.h new file mode 100644 index 00000000000..6a3e09a38a6 --- /dev/null +++ b/astropy/io/fits/tiled_compression/src/imcompress.h @@ -0,0 +1,90 @@ +int unquantize_i1r4(long row, + unsigned char *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - which subtractive dither method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ + float nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + float *output, /* O - array of converted pixels */ + int *status); /* IO - error status */ +int unquantize_i2r4(long row, + short *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - which subtractive dither method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + short tnull, /* I - value of FITS TNULLn keyword if any */ + float nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + float *output, /* O - array of converted pixels */ + int *status); /* IO - error status */ +int unquantize_i4r4(long row, + int *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - which subtractive dither method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + int tnull, /* I - value of FITS TNULLn keyword if any */ + float nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + float *output, /* O - array of converted pixels */ + int *status); /* IO - error status */ +int unquantize_i1r8(long row, + unsigned char *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - which subtractive dither method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ + double nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + double *output, /* O - array of converted pixels */ + int *status); /* IO - error status */ +int unquantize_i2r8(long row, + short *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - which subtractive dither method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + short tnull, /* I - value of FITS TNULLn keyword if any */ + double nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + double *output, /* O - array of converted pixels */ + int *status); /* IO - error status */ +int unquantize_i4r8(long row, + int *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - which subtractive dither method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + int tnull, /* I - value of FITS TNULLn keyword if any */ + double nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + double *output, /* O - array of converted pixels */ + int *status); /* IO - error status */ diff --git a/astropy/io/fits/tiled_compression/src/quantize.h b/astropy/io/fits/tiled_compression/src/quantize.h new file mode 100644 index 00000000000..3c35047ba8b --- /dev/null +++ b/astropy/io/fits/tiled_compression/src/quantize.h @@ -0,0 +1,7 @@ +int fits_quantize_float (long row, float fdata[], long nxpix, long nypix, int nullcheck, + float in_null_value, float qlevel, int dither_method, int idata[], double *bscale, + double *bzero, int *iminval, int *imaxval); + +int fits_quantize_double (long row, double fdata[], long nxpix, long nypix, int nullcheck, + double in_null_value, float qlevel, int dither_method, int idata[], double *bscale, + double *bzero, int *iminval, int *imaxval); diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 915a47650ad..c077724da61 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -15,6 +15,10 @@ decompress_hcompress_1_c, decompress_plio_1_c, decompress_rice_1_c, + quantize_double_c, + quantize_float_c, + unquantize_double_c, + unquantize_float_c, ) __all__ = [ @@ -23,6 +27,7 @@ "Rice1", "PLIO1", "HCompress1", + "Quantize", "compress_tile", "decompress_tile", "compress_hdu", @@ -75,6 +80,114 @@ def decode(self, buf, out=None): # pragma: no cover """ +class Quantize(Codec): + """ + Quantization of floating-point data following the FITS standard. + """ + + codec_id = "FITS_QUANTIZE" + + def __init__(self, row: int, dither_method: int, quantize_level: int, bitpix: int): + super().__init__() + self.row = row + # TODO: pass dither method as a string instead of int? + self.quantize_level = quantize_level + self.dither_method = dither_method + self.bitpix = bitpix + + # NOTE: below we use decode_quantized and encode_quantized instead of + # decode and encode as we need to break with the numcodec API and take/return + # scale and zero in addition to quantized value. We should figure out how + # to properly use the numcodec API for this use case. + + def decode_quantized(self, buf, scale, zero): + """ + Unquantize data + + Parameters + ---------- + buf + The buffer to unquantize. + + Returns + ------- + buf + The unquantized buffer. + """ + qbytes = np.asarray(buf) + # TODO: figure out if we need to support null checking + if self.bitpix == -32: + ubytes = unquantize_float_c( + qbytes, + self.row, + qbytes.size, + scale, + zero, + self.dither_method, + 0, + 0, + 0.0, + qbytes.dtype.itemsize, + ) + elif self.bitpix == -64: + ubytes = unquantize_double_c( + qbytes, + self.row, + qbytes.size, + scale, + zero, + self.dither_method, + 0, + 0, + 0.0, + qbytes.dtype.itemsize, + ) + else: + raise TypeError("bitpix should be one of -32 or -64") + return np.frombuffer(ubytes, dtype=BITPIX2DTYPE[self.bitpix]).data + + def encode_quantized(self, buf): + """ + Quantize data. + + Parameters + ---------- + buf + The buffer to quantize. + + Returns + ------- + buf + A buffer with quantized data. + """ + + uarray = np.asarray(buf) + # TODO: figure out if we need to support null checking + if uarray.dtype.itemsize == 4: + qbytes, scale, zero = quantize_float_c( + uarray.tobytes(), + self.row, + uarray.size, + 1, + 0, + 0, + self.quantize_level, + self.dither_method, + )[:3] + elif uarray.dtype.itemsize == 8: + qbytes, scale, zero = quantize_double_c( + uarray.tobytes(), + self.row, + uarray.size, + 1, + 0, + 0, + self.quantize_level, + self.dither_method, + )[:3] + return np.frombuffer(qbytes, dtype=np.int32).data, scale, zero + + class Gzip1(Codec): """ The FITS GZIP 1 compression and decompression algorithm. From 249cbe4e77c4bea9b34805082ec6061622d387bb Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Tue, 29 Nov 2022 16:01:21 +0000 Subject: [PATCH 13/32] Starting adding support for de-quantization in decompress_hdu --- .../fits/tiled_compression/src/compression.c | 22 ++-- .../tiled_compression/tiled_compression.py | 115 +++++++++++++++--- 2 files changed, 108 insertions(+), 29 deletions(-) diff --git a/astropy/io/fits/tiled_compression/src/compression.c b/astropy/io/fits/tiled_compression/src/compression.c index 0208e217e7e..13c3c86021a 100644 --- a/astropy/io/fits/tiled_compression/src/compression.c +++ b/astropy/io/fits/tiled_compression/src/compression.c @@ -333,6 +333,8 @@ static PyObject *quantize_float_c(PyObject *self, PyObject *args) { double bscale, bzero; int iminval, imaxval; + int status; + if (!PyArg_ParseTuple(args, "y#lllidfi", &input_bytes, &nbytes, &row, &nx, &ny, &nullcheck, &in_null_value, &qlevel, &dither_method)) { @@ -342,13 +344,13 @@ static PyObject *quantize_float_c(PyObject *self, PyObject *args) { input_data = (float *)input_bytes; quantized_data = (int *)malloc(nx * ny * sizeof(int)); - fits_quantize_float(row, input_data, nx, ny, nullcheck, in_null_value, qlevel, - dither_method, quantized_data, &bscale, &bzero, &iminval, - &imaxval); + status = fits_quantize_float(row, input_data, nx, ny, nullcheck, in_null_value, qlevel, + dither_method, quantized_data, &bscale, &bzero, &iminval, + &imaxval); quantized_bytes = (char *)quantized_data; - result = Py_BuildValue("y#ddii", quantized_bytes, nx * ny * sizeof(int), + result = Py_BuildValue("y#iddii", quantized_bytes, nx * ny * sizeof(int), status, bscale, bzero, iminval, imaxval); free(quantized_bytes); return result; @@ -373,6 +375,8 @@ static PyObject *quantize_double_c(PyObject *self, PyObject *args) { double bscale, bzero; int iminval, imaxval; + int status; + if (!PyArg_ParseTuple(args, "y#lllidfi", &input_bytes, &nbytes, &row, &nx, &ny, &nullcheck, &in_null_value, &qlevel, &dither_method)) { @@ -382,14 +386,14 @@ static PyObject *quantize_double_c(PyObject *self, PyObject *args) { input_data = (double *)input_bytes; quantized_data = (int *)malloc(nx * ny * sizeof(int)); - fits_quantize_double(row, input_data, nx, ny, nullcheck, in_null_value, - qlevel, dither_method, quantized_data, &bscale, &bzero, - &iminval, &imaxval); + status = fits_quantize_double(row, input_data, nx, ny, nullcheck, in_null_value, + qlevel, dither_method, quantized_data, &bscale, &bzero, + &iminval, &imaxval); quantized_bytes = (char *)quantized_data; - result = Py_BuildValue("y#ddii", quantized_bytes, nx * ny * sizeof(int), - bscale, bzero, iminval, imaxval); + result = Py_BuildValue("y#iddii", quantized_bytes, nx * ny * sizeof(int), status, + bscale, bzero, iminval, imaxval); free(quantized_bytes); return result; } diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index c077724da61..7af0c891b3c 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -34,6 +34,12 @@ "decompress_hdu", ] +DITHER_METHODS = {"NO_DITHER": -1, "SUBTRACTIVE_DITHER_1": 1, "SUBTRACTIVE_DITHER_2": 2} + + +class QuantizationFailedException(Exception): + pass + # We define our compression classes in the form of a numcodecs class. We make # the dependency on numcodecs optional as we can use them internally without it @@ -164,7 +170,7 @@ def encode_quantized(self, buf): uarray = np.asarray(buf) # TODO: figure out if we need to support null checking if uarray.dtype.itemsize == 4: - qbytes, scale, zero = quantize_float_c( + qbytes, status, scale, zero = quantize_float_c( uarray.tobytes(), self.row, uarray.size, @@ -173,9 +179,9 @@ def encode_quantized(self, buf): 0, self.quantize_level, self.dither_method, - )[:3] + )[:4] elif uarray.dtype.itemsize == 8: - qbytes, scale, zero = quantize_double_c( + qbytes, status, scale, zero = quantize_double_c( uarray.tobytes(), self.row, uarray.size, @@ -184,8 +190,11 @@ def encode_quantized(self, buf): 0, self.quantize_level, self.dither_method, - )[:3] - return np.frombuffer(qbytes, dtype=np.int32).data, scale, zero + )[:4] + if status == 0: + raise QuantizationFailedException() + else: + return np.frombuffer(qbytes, dtype=np.int32).data, scale, zero class Gzip1(Codec): @@ -746,10 +755,18 @@ def decompress_hdu(hdu): data = np.zeros(data_shape, dtype=BITPIX2DTYPE[hdu._header["ZBITPIX"]]) istart = np.zeros(data.ndim, dtype=int) - for cdata in hdu.compressed_data["COMPRESSED_DATA"]: - tile_buffer = decompress_tile( - cdata, algorithm=hdu._header["ZCMPTYPE"], **settings - ) + for irow, row in enumerate(hdu.compressed_data): + + cdata = row["COMPRESSED_DATA"] + + if len(cdata) == 0: + tile_buffer = decompress_tile( + row["GZIP_COMPRESSED_DATA"], algorithm="GZIP_1" + ) + else: + tile_buffer = decompress_tile( + cdata, algorithm=hdu._header["ZCMPTYPE"], **settings + ) # In the following, we don't need to special case tiles near the edge # as Numpy will automatically ignore parts of the slices that are out @@ -769,6 +786,15 @@ def decompress_hdu(hdu): tile_buffer, hdu._header, tile_shape=actual_tile_shape ) + # TODO: have a more robust way of determining whether we need to + # dequantize + if hdu._header["ZBITPIX"] < 0 and tile_data.dtype.kind != "f": + dither_method = DITHER_METHODS[hdu._header.get("ZQUANTIZ", "NO_DITHER")] + q = Quantize(irow, dither_method, None, hdu._header["ZBITPIX"]) + tile_data = np.asarray( + q.decode_quantized(tile_data, row["ZSCALE"], row["ZZERO"]) + ).reshape(actual_tile_shape) + data[tile_slices] = tile_data istart[-1] += tile_shape[-1] for idx in range(data.ndim - 1, 0, -1): @@ -797,7 +823,11 @@ def compress_hdu(hdu): data_shape = _data_shape(hdu._header) compressed_bytes = [] + lossless = [] + scales = [] + zeros = [] + irow = 0 istart = np.zeros(len(data_shape), dtype=int) while True: @@ -812,21 +842,49 @@ def compress_hdu(hdu): ] ) - # TODO: deal with data not being integer number of tiles data = hdu.data[slices] + + store_lossless = False + + if data.dtype.kind == "f": + # TODO: use NOISEBIT quantize level + dither_method = DITHER_METHODS[hdu._header.get("ZQUANTIZ", "NO_DITHER")] + q = Quantize(irow, dither_method, 10, hdu._header["ZBITPIX"]) + original_shape = data.shape + try: + data, scale, zero = q.encode_quantized(data) + except QuantizationFailedException: + scales.append(0) + zeros.append(0) + lossless.append(True) + else: + data = np.asarray(data).reshape(original_shape) + scales.append(scale) + zeros.append(zero) + lossless.append(False) + else: + scales.append(0) + zeros.append(0) + lossless.append(False) + # The original compress_hdu assumed the data was in native endian, so we # change this here: - if hdu._header["ZCMPTYPE"].startswith("GZIP"): + if hdu._header["ZCMPTYPE"].startswith("GZIP") or lossless[-1]: # This is apparently needed so that our heap data agrees with # the C implementation!? data = data.astype(data.dtype.newbyteorder(">")) else: if not data.dtype.isnative: data = data.astype(data.dtype.newbyteorder("=")) - cbytes = compress_tile(data, algorithm=hdu._header["ZCMPTYPE"], **settings) + + if lossless[-1]: + cbytes = compress_tile(data, algorithm="GZIP_1") + else: + cbytes = compress_tile(data, algorithm=hdu._header["ZCMPTYPE"], **settings) compressed_bytes.append(cbytes) istart[-1] += tile_shape[-1] + for idx in range(data.ndim - 1, 0, -1): if istart[idx] >= data_shape[idx]: istart[idx] = 0 @@ -835,18 +893,29 @@ def compress_hdu(hdu): if istart[0] >= data_shape[0]: break - header_len = len(compressed_bytes) * 2 + irow += 1 - heap_header = np.zeros(header_len, ">i4") - for i in range(len(compressed_bytes)): - heap_header[i * 2] = len(compressed_bytes[i]) - heap_header[1 + i * 2] = heap_header[: i * 2 : 2].sum() + table = np.zeros(len(compressed_bytes), dtype=hdu.columns.dtype.newbyteorder(">")) + + if "ZSCALE" in table.dtype.names: + table["ZSCALE"] = np.array(scales) + table["ZZERO"] = np.array(zeros) + + for irow, cbytes in enumerate(compressed_bytes): + table["COMPRESSED_DATA"][irow, 0] = len(cbytes) + + table["COMPRESSED_DATA"][0, 1] = 0 + table["COMPRESSED_DATA"][1:, 1] = np.cumsum(table["COMPRESSED_DATA"][:-1, 0]) + + for irow in range(len(compressed_bytes)): + if lossless[irow]: + table["GZIP_COMPRESSED_DATA"][irow] = table["COMPRESSED_DATA"][irow] + table["COMPRESSED_DATA"][irow] = 0 # For PLIO_1, the size of each heap element is a factor of two lower than # the real size - not clear if this is deliberate or bug somewhere. if hdu._header["ZCMPTYPE"] == "PLIO_1": - for i in range(len(compressed_bytes)): - heap_header[i * 2] /= 2 + table["COMPRESSED_DATA"][:, 0] //= 2 compressed_bytes = b"".join(compressed_bytes) @@ -856,6 +925,12 @@ def compress_hdu(hdu): np.frombuffer(compressed_bytes, dtype="i2").tobytes() ) - heap = heap_header.tobytes() + compressed_bytes + table_bytes = table.tobytes() + + if len(table_bytes) != hdu._theap: + raise Exception( + f"Unexpected compressed table size (expected {hdu._theap}, got {len(table_bytes)})" + ) + heap = table.tobytes() + compressed_bytes return len(compressed_bytes), np.frombuffer(heap, dtype=np.uint8) From 4e109a2fcff9b21cebed6da5b339575384292cb3 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Thu, 1 Dec 2022 05:42:10 +0000 Subject: [PATCH 14/32] Finish implementing reading in of lossless GZIP fallback --- .../tiled_compression/tiled_compression.py | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 7af0c891b3c..85b90c6ce4a 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -603,7 +603,7 @@ def _header_to_settings(header): return settings -def _buffer_to_array(tile_buffer, header, tile_shape=None): +def _buffer_to_array(tile_buffer, header, tile_shape=None, algorithm=None): """ Convert a buffer to an array using the header. @@ -615,7 +615,10 @@ def _buffer_to_array(tile_buffer, header, tile_shape=None): if tile_shape is None: tile_shape = _tile_shape(header) - if header["ZCMPTYPE"].startswith("GZIP"): + if algorithm is None: + algorithm = header["ZCMPTYPE"] + + if algorithm.startswith("GZIP"): # This algorithm is taken from fitsio # https://github.com/astropy/astropy/blob/a8cb1668d4835562b89c0d0b3448ac72ca44db63/cextern/cfitsio/lib/imcompress.c#L6345-L6388 tilelen = np.product(tile_shape) @@ -640,7 +643,7 @@ def _buffer_to_array(tile_buffer, header, tile_shape=None): # For RICE_1 compression the tiles that are on the edge can end up # being padded, so we truncate excess values - if header["ZCMPTYPE"] in ("RICE_1", "PLIO_1"): + if algorithm in ("RICE_1", "PLIO_1"): tile_buffer = tile_buffer[: np.product(tile_shape)] if tile_buffer.format == "b": @@ -759,7 +762,9 @@ def decompress_hdu(hdu): cdata = row["COMPRESSED_DATA"] - if len(cdata) == 0: + lossless = len(cdata) == 0 + + if lossless: tile_buffer = decompress_tile( row["GZIP_COMPRESSED_DATA"], algorithm="GZIP_1" ) @@ -782,9 +787,17 @@ def decompress_hdu(hdu): # correct so we have to pass the shape manually. actual_tile_shape = data[tile_slices].shape - tile_data = _buffer_to_array( - tile_buffer, hdu._header, tile_shape=actual_tile_shape - ) + if lossless: + tile_data = _buffer_to_array( + tile_buffer, + hdu._header, + tile_shape=actual_tile_shape, + algorithm="GZIP_1", + ) + else: + tile_data = _buffer_to_array( + tile_buffer, hdu._header, tile_shape=actual_tile_shape + ) # TODO: have a more robust way of determining whether we need to # dequantize @@ -844,9 +857,7 @@ def compress_hdu(hdu): data = hdu.data[slices] - store_lossless = False - - if data.dtype.kind == "f": + if data.dtype.kind == "f" and hdu._header.get("NOISEBIT", 0) > 0: # TODO: use NOISEBIT quantize level dither_method = DITHER_METHODS[hdu._header.get("ZQUANTIZ", "NO_DITHER")] q = Quantize(irow, dither_method, 10, hdu._header["ZBITPIX"]) From 773b3f782bc4abdfb891e1c837bdfe0ec3011647 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Thu, 1 Dec 2022 06:55:44 +0000 Subject: [PATCH 15/32] Enable lossy floating point tests for GZIP --- .../fits/tiled_compression/tests/conftest.py | 10 ++ .../tiled_compression/tests/test_fitsio.py | 46 ++++++- .../tiled_compression/tiled_compression.py | 124 +++++++++++------- 3 files changed, 126 insertions(+), 54 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tests/conftest.py b/astropy/io/fits/tiled_compression/tests/conftest.py index ad2e98e688b..8e0c189ae1c 100644 --- a/astropy/io/fits/tiled_compression/tests/conftest.py +++ b/astropy/io/fits/tiled_compression/tests/conftest.py @@ -48,6 +48,16 @@ def _expand(*params): ({"qlevel": None},), ALL_FLOAT_DTYPES, ], + # For now test more quantization parameters with GZIP_1 only + [ + COMPRESSION_TYPES, + ( + {"qlevel": 5, "qmethod": -1}, + {"qlevel": 10, "qmethod": 1}, + {"qlevel": 20, "qmethod": 2}, + ), + ALL_FLOAT_DTYPES, + ], ), ) def comp_param_dtype(request): diff --git a/astropy/io/fits/tiled_compression/tests/test_fitsio.py b/astropy/io/fits/tiled_compression/tests/test_fitsio.py index 88b13f8d948..498b5536713 100644 --- a/astropy/io/fits/tiled_compression/tests/test_fitsio.py +++ b/astropy/io/fits/tiled_compression/tests/test_fitsio.py @@ -91,8 +91,24 @@ def fitsio_compressed_file_path( tile_dims, ): compression_type, param, dtype = comp_param_dtype + if base_original_data.ndim > 2 and "u1" in dtype: pytest.xfail("These don't work") + + if compression_type == "PLIO_1" and "f" in dtype: + # fitsio fails with a compression error + pytest.xfail("fitsio fails to write these") + + if ( + compression_type == "HCOMPRESS_1" + and "f" in dtype + and param.get("qmethod", None) == 2 + ): + # fitsio writes these files with very large/incorrect zzero values, whereas + # qmethod == 1 works (and the two methods should be identical except for the + # treatment of zeros) + pytest.xfail("fitsio writes these files with very large/incorrect zzero values") + tmp_path = tmp_path_factory.mktemp("fitsio") original_data = base_original_data.astype(dtype) @@ -116,7 +132,7 @@ def astropy_compressed_file_path( tmp_path = tmp_path_factory.mktemp("astropy") filename = tmp_path / f"{compression_type}_{dtype}.fits" # Convert fitsio kwargs to astropy kwargs - _map = {"qlevel": "quantize_level"} + _map = {"qlevel": "quantize_level", "qmethod": "quantize_method"} param = {_map[k]: v for k, v in param.items()} # Map quantize_level @@ -141,11 +157,19 @@ def test_decompress( with fits.open(fitsio_compressed_file_path) as hdul: data = hdul[1].data - assert hdul[1]._header["ZCMPTYPE"] == compression_type + assert hdul[1]._header["ZCMPTYPE"].replace("ONE", "1") == compression_type assert hdul[1].data.dtype.kind == np.dtype(dtype).kind assert hdul[1].data.dtype.itemsize == np.dtype(dtype).itemsize # assert hdul[1].data.dtype.byteorder == np.dtype(dtype).byteorder - np.testing.assert_allclose(data, base_original_data) + + # The data might not always match the original data exactly in the case of + # lossy compression so instead of comparing the array read by astropy to the + # original data, we compare it to the data read in by fitsio (as those + # should match) + + fts = fitsio.FITS(fitsio_compressed_file_path) + data2 = fts[1].read() + np.testing.assert_allclose(data, data2) def test_compress( @@ -154,12 +178,20 @@ def test_compress( compression_type, dtype, ): - fits = fitsio.FITS(astropy_compressed_file_path, "r") - header = fits[1].read_header() - data = fits[1].read() + + fts = fitsio.FITS(astropy_compressed_file_path, "r") + header = fts[1].read_header() + data = fts[1].read() assert header["ZCMPTYPE"] == compression_type assert data.dtype.kind == np.dtype(dtype).kind assert data.dtype.itemsize == np.dtype(dtype).itemsize # assert data.dtype.byteorder == np.dtype(dtype).byteorder - np.testing.assert_allclose(data, base_original_data) + + # The data might not always match the original data exactly in the case of + # lossy compression so instead of comparing the array read by fitsio to the + # original data, we compare it to the data read in by astropy (as those + # should match) + + with fits.open(astropy_compressed_file_path) as hdul: + np.testing.assert_allclose(data, hdul[1].data) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 85b90c6ce4a..0660bc3f356 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -121,10 +121,14 @@ def decode_quantized(self, buf, scale, zero): The unquantized buffer. """ qbytes = np.asarray(buf) + qbytes = qbytes.astype(qbytes.dtype.newbyteorder("=")) # TODO: figure out if we need to support null checking + if self.dither_method == -1: + # For NO_DITHER we should just use the scale and zero directly + return qbytes * scale + zero if self.bitpix == -32: ubytes = unquantize_float_c( - qbytes, + qbytes.tobytes(), self.row, qbytes.size, scale, @@ -137,7 +141,7 @@ def decode_quantized(self, buf, scale, zero): ) elif self.bitpix == -64: ubytes = unquantize_double_c( - qbytes, + qbytes.tobytes(), self.row, qbytes.size, scale, @@ -166,8 +170,8 @@ def encode_quantized(self, buf): buf A buffer with quantized data. """ - uarray = np.asarray(buf) + uarray = uarray.astype(uarray.dtype.newbyteorder("=")) # TODO: figure out if we need to support null checking if uarray.dtype.itemsize == 4: qbytes, status, scale, zero = quantize_float_c( @@ -320,8 +324,10 @@ def encode(self, buf): The decompressed buffer. """ # Start off by shuffling buffer - array = np.asarray(buf).ravel().view(np.uint8) - shuffled_buffer = array.reshape((-1, self.itemsize)).T.ravel().tobytes() + array = np.asarray(buf).ravel() + itemsize = array.dtype.itemsize + array = array.view(np.uint8) + shuffled_buffer = array.reshape((-1, itemsize)).T.ravel().tobytes() return gzip_compress(shuffled_buffer) @@ -524,6 +530,7 @@ def encode(self, buf): "GZIP_1": Gzip1, "GZIP_2": Gzip2, "RICE_1": Rice1, + "RICE_ONE": Rice1, "PLIO_1": PLIO1, "HCOMPRESS_1": HCompress1, } @@ -579,14 +586,14 @@ def _header_to_settings(header): settings["itemsize"] = abs(header["ZBITPIX"]) // 8 elif header["ZCMPTYPE"] == "PLIO_1": settings["tilesize"] = np.product(tile_shape) - elif header["ZCMPTYPE"] == "RICE_1": - settings["blocksize"] = header.get("ZVAL1", 32) - settings["bytepix"] = header.get("ZVAL2", 4) + elif header["ZCMPTYPE"] in ("RICE_1", "RICE_ONE"): + settings["blocksize"] = _get_compression_setting(header, "BLOCKSIZE", 32) + settings["bytepix"] = _get_compression_setting(header, "BYTEPIX", 4) settings["tilesize"] = np.product(tile_shape) elif header["ZCMPTYPE"] == "HCOMPRESS_1": settings["bytepix"] = 4 - settings["scale"] = int(header["ZVAL1"]) - settings["smooth"] = header["ZVAL2"] + settings["scale"] = int(_get_compression_setting(header, "SCALE", 0)) + settings["smooth"] = _get_compression_setting(header, "SMOOTH", 0) # HCOMPRESS requires 2D tiles, so to find the shape of the 2D tile we # need to ignore all length 1 tile dimensions # Also cfitsio expects the tile shape in C order, so reverse it @@ -603,7 +610,9 @@ def _header_to_settings(header): return settings -def _buffer_to_array(tile_buffer, header, tile_shape=None, algorithm=None): +def _buffer_to_array( + tile_buffer, header, tile_shape=None, algorithm=None, lossless=False +): """ Convert a buffer to an array using the header. @@ -626,12 +635,12 @@ def _buffer_to_array(tile_buffer, header, tile_shape=None, algorithm=None): if tilebytesize == tilelen * 2: dtype = ">i2" elif tilebytesize == tilelen * 4: - if header["ZBITPIX"] < 0: + if header["ZBITPIX"] < 0 and lossless: dtype = ">f4" else: dtype = ">i4" elif tilebytesize == tilelen * 8: - if header["ZBITPIX"] < 0: + if header["ZBITPIX"] < 0 and lossless: dtype = ">f8" else: dtype = ">i8" @@ -643,7 +652,7 @@ def _buffer_to_array(tile_buffer, header, tile_shape=None, algorithm=None): # For RICE_1 compression the tiles that are on the edge can end up # being padded, so we truncate excess values - if algorithm in ("RICE_1", "PLIO_1"): + if algorithm in ("RICE_1", "RICE_ONE", "PLIO_1"): tile_buffer = tile_buffer[: np.product(tile_shape)] if tile_buffer.format == "b": @@ -728,13 +737,7 @@ def _check_compressed_header(header): if header["ZBITPIX"] not in [8, 16, 32, 64, -32, -64]: raise ValueError(f"Invalid value for BITPIX: {header['ZBITPIX']}") - if header["ZCMPTYPE"] not in [ - "GZIP_1", - "GZIP_2", - "PLIO_1", - "RICE_1", - "HCOMPRESS_1", - ]: + if header["ZCMPTYPE"] not in ALGORITHMS: raise ValueError(f"Unrecognized compression type: {header['ZCMPTYPE']}") # Check that certain keys are present @@ -743,6 +746,15 @@ def _check_compressed_header(header): header["ZBITPIX"] +def _get_compression_setting(header, name, default): + for i in range(1, 1000): + if f"ZNAME{i}" not in header: + break + if header[f"ZNAME{i}"].lower() == name.lower(): + return header[f"ZVAL{i}"] + return default + + def decompress_hdu(hdu): """ Drop-in replacement for decompress_hdu from compressionmodule.c @@ -757,22 +769,11 @@ def decompress_hdu(hdu): data = np.zeros(data_shape, dtype=BITPIX2DTYPE[hdu._header["ZBITPIX"]]) + quantize = "ZSCALE" in hdu.compressed_data.dtype.names + istart = np.zeros(data.ndim, dtype=int) for irow, row in enumerate(hdu.compressed_data): - cdata = row["COMPRESSED_DATA"] - - lossless = len(cdata) == 0 - - if lossless: - tile_buffer = decompress_tile( - row["GZIP_COMPRESSED_DATA"], algorithm="GZIP_1" - ) - else: - tile_buffer = decompress_tile( - cdata, algorithm=hdu._header["ZCMPTYPE"], **settings - ) - # In the following, we don't need to special case tiles near the edge # as Numpy will automatically ignore parts of the slices that are out # of bounds. @@ -787,26 +788,48 @@ def decompress_hdu(hdu): # correct so we have to pass the shape manually. actual_tile_shape = data[tile_slices].shape + cdata = row["COMPRESSED_DATA"] + + if irow == 0 and hdu._header["ZCMPTYPE"] == "GZIP_2": + # Decompress with GZIP_1 just to find the total number of + # elements in the uncompressed data + tile_data = np.asarray( + decompress_tile(row["COMPRESSED_DATA"], algorithm="GZIP_1") + ) + settings["itemsize"] = tile_data.size // int(np.product(actual_tile_shape)) + + lossless = len(cdata) == 0 + if lossless: + tile_buffer = decompress_tile( + row["GZIP_COMPRESSED_DATA"], algorithm="GZIP_1" + ) tile_data = _buffer_to_array( tile_buffer, hdu._header, tile_shape=actual_tile_shape, algorithm="GZIP_1", + lossless=True, ) else: + tile_buffer = decompress_tile( + cdata, algorithm=hdu._header["ZCMPTYPE"], **settings + ) tile_data = _buffer_to_array( - tile_buffer, hdu._header, tile_shape=actual_tile_shape + tile_buffer, + hdu._header, + tile_shape=actual_tile_shape, + lossless=lossless or not quantize, ) - - # TODO: have a more robust way of determining whether we need to - # dequantize - if hdu._header["ZBITPIX"] < 0 and tile_data.dtype.kind != "f": - dither_method = DITHER_METHODS[hdu._header.get("ZQUANTIZ", "NO_DITHER")] - q = Quantize(irow, dither_method, None, hdu._header["ZBITPIX"]) - tile_data = np.asarray( - q.decode_quantized(tile_data, row["ZSCALE"], row["ZZERO"]) - ).reshape(actual_tile_shape) + if quantize: + dither_method = DITHER_METHODS[hdu._header.get("ZQUANTIZ", "NO_DITHER")] + dither_seed = hdu._header.get("ZDITHER0", 0) + q = Quantize( + irow + dither_seed, dither_method, None, hdu._header["ZBITPIX"] + ) + tile_data = np.asarray( + q.decode_quantized(tile_data, row["ZSCALE"], row["ZZERO"]) + ).reshape(actual_tile_shape) data[tile_slices] = tile_data istart[-1] += tile_shape[-1] @@ -843,6 +866,8 @@ def compress_hdu(hdu): irow = 0 istart = np.zeros(len(data_shape), dtype=int) + noisebit = _get_compression_setting(hdu._header, "noisebit", 0) + while True: # In the following, we don't need to special case tiles near the edge @@ -857,10 +882,15 @@ def compress_hdu(hdu): data = hdu.data[slices] - if data.dtype.kind == "f" and hdu._header.get("NOISEBIT", 0) > 0: - # TODO: use NOISEBIT quantize level + quantize = "ZSCALE" in hdu.columns.dtype.names + + if data.dtype.kind == "f" and quantize: + noisebit = _get_compression_setting(hdu._header, "noisebit", 0) dither_method = DITHER_METHODS[hdu._header.get("ZQUANTIZ", "NO_DITHER")] - q = Quantize(irow, dither_method, 10, hdu._header["ZBITPIX"]) + dither_seed = hdu._header.get("ZDITHER0", 0) + q = Quantize( + irow + dither_seed, dither_method, noisebit, hdu._header["ZBITPIX"] + ) original_shape = data.shape try: data, scale, zero = q.encode_quantized(data) From 94a30b16fc288be17b5bbb59f34e9e0886182d5d Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Fri, 2 Dec 2022 14:34:57 +0000 Subject: [PATCH 16/32] Remove unneeded tests and simplify canonical tests --- .../fits/tiled_compression/tests/conftest.py | 1 - .../tests/test_tiled_compression.py | 170 +----------------- 2 files changed, 3 insertions(+), 168 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tests/conftest.py b/astropy/io/fits/tiled_compression/tests/conftest.py index 8e0c189ae1c..e633be924ac 100644 --- a/astropy/io/fits/tiled_compression/tests/conftest.py +++ b/astropy/io/fits/tiled_compression/tests/conftest.py @@ -48,7 +48,6 @@ def _expand(*params): ({"qlevel": None},), ALL_FLOAT_DTYPES, ], - # For now test more quantization parameters with GZIP_1 only [ COMPRESSION_TYPES, ( diff --git a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py index 12d1a0beef4..6699beaef2e 100644 --- a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py @@ -1,153 +1,9 @@ from pathlib import Path -import numpy as np import pytest -from numpy.testing import assert_equal +from numpy.testing import assert_allclose from astropy.io import fits -from astropy.io.fits.tiled_compression import ( - compress_hdu, - compress_tile, - decompress_hdu, - decompress_tile, -) -from astropy.io.fits.tiled_compression.tiled_compression import ( - _buffer_to_array, - _header_to_settings, -) - - -def test_basic(tmp_path, compression_type, dtype): - # Generate compressed file dynamically - - original_data = np.arange(144).reshape((12, 12)).astype(dtype) - - header = fits.Header() - - hdu = fits.CompImageHDU( - original_data, header, compression_type=compression_type, tile_size=(4, 4) - ) - - hdu.writeto(tmp_path / "test.fits") - - # Load in raw compressed data - hdulist = fits.open(tmp_path / "test.fits", disable_image_compression=True) - - settings = _header_to_settings(hdulist[1].header) - - # Test decompression of the first tile - - compressed_tile_bytes = hdulist[1].data["COMPRESSED_DATA"][0].tobytes() - - tile_data_buffer = decompress_tile( - compressed_tile_bytes, algorithm=compression_type, **settings - ) - tile_data = _buffer_to_array(tile_data_buffer, hdulist[1].header) - - assert_equal(tile_data, original_data[:4, :4]) - - # Now compress the original data and compare to compressed bytes. Since - # the exact compressed bytes might not match (e.g. for GZIP it will depend - # on the compression level) we instead put the compressed bytes into the - # original BinTableHDU, then read it in as a normal compressed HDU and make - # sure the final data match. - - if compression_type.startswith("GZIP"): - # NOTE: It looks like the data is stored as big endian data even if it was - # originally little-endian. - tile_data_buffer = ( - original_data[:4, :4].astype(original_data.dtype.newbyteorder(">")).data - ) - else: - tile_data_buffer = original_data[:4, :4].data - - compressed_tile_bytes = compress_tile( - tile_data_buffer, algorithm=compression_type, **settings - ) - - # Then check that it also round-trips if we go through fits.open - if compression_type == "PLIO_1": - hdulist[1].data["COMPRESSED_DATA"][0] = np.frombuffer( - compressed_tile_bytes, dtype=np.int16 - ) - else: - hdulist[1].data["COMPRESSED_DATA"][0] = np.frombuffer( - compressed_tile_bytes, dtype=np.uint8 - ) - hdulist[1].writeto(tmp_path / "updated.fits") - hdulist.close() - hdulist_new = fits.open(tmp_path / "updated.fits") - assert_equal(hdulist_new[1].data, original_data) - hdulist_new.close() - - -def test_decompress_hdu(tmp_path, compression_type, dtype): - # NOTE: for now this test is designed to compare the Python implementation - # of decompress_hdu with the C implementation - once we get rid of the C - # implementation we should update this test. - - original_data = np.arange(144).reshape((12, 12)).astype(dtype) - - header = fits.Header() - - hdu = fits.CompImageHDU( - original_data, header, compression_type=compression_type, tile_size=(4, 4) - ) - - hdu.writeto(tmp_path / "test.fits") - - # Load in CompImageHDU - hdulist = fits.open(tmp_path / "test.fits") - hdu = hdulist[1] - - data = decompress_hdu(hdu) - - assert_equal(data, original_data) - - hdulist.close() - - -def _assert_heap_same(heap_a, heap_b, n_tiles): - # The exact length and data might not always match because they might be - # equivalent but store the tiles in a different order or with padding, so - # we need to interpret the heap data and actually check chunk by chunk - heap_header_a = heap_a[: n_tiles * 2 * 4].view(">i4") - heap_header_b = heap_b[: n_tiles * 2 * 4].view(">i4") - for itile in range(n_tiles): - len_a = heap_header_a[itile * 2] - off_a = heap_header_a[itile * 2 + 1] + n_tiles * 2 * 4 - len_b = heap_header_b[itile * 2] - off_b = heap_header_b[itile * 2 + 1] + n_tiles * 2 * 4 - data_a = heap_a[off_a : off_a + len_a] - data_b = heap_b[off_b : off_b + len_b] - if np.all(data_a[:4] == np.array([31, 139, 8, 0], dtype=np.uint8)): - # gzip compressed - need to compare decompressed bytes as compression - # is non-deterministic due to e.g. compression level and so on - from gzip import decompress - - data_a = decompress(data_a.tobytes()) - data_b = decompress(data_b.tobytes()) - assert data_a == data_b - else: - assert_equal(data_a, data_b) - - -def test_compress_hdu(compression_type, dtype): - # NOTE: for now this test is designed to compare the Python implementation - # of compress_hdu with the C implementation - once we get rid of the C - # implementation we should update this test. - - original_data = np.arange(144).reshape((12, 12)).astype(dtype) - - header = fits.Header() - - hdu = fits.CompImageHDU( - original_data, header, compression_type=compression_type, tile_size=(4, 4) - ) - - heap_length, heap_data = compress_hdu(hdu) - - # TODO: need to make this test more useful! @pytest.fixture @@ -164,7 +20,7 @@ def canonical_int_hdus(request): Which are used as canonical tests of data not compressed by Astropy. """ - with fits.open(request.param, disable_image_compression=True) as hdul: + with fits.open(request.param) as hdul: yield hdul[1] @@ -179,24 +35,4 @@ def original_int_hdu(canonical_data_base_path): # https://github.com/astropy/pytest-openfiles/issues/32 @pytest.mark.openfiles_ignore def test_canonical_data(original_int_hdu, canonical_int_hdus): - hdr = canonical_int_hdus.header - tile_size = (hdr["ZTILE2"], hdr["ZTILE1"]) - compression_type = hdr["ZCMPTYPE"] - original_tile_1 = original_int_hdu.data[: tile_size[0], : tile_size[1]] - original_compressed_tile_bytes = canonical_int_hdus.data["COMPRESSED_DATA"][0].tobytes() # fmt: skip - - settings = _header_to_settings(canonical_int_hdus.header) - tile_data_buffer = decompress_tile( - original_compressed_tile_bytes, algorithm=compression_type, **settings - ) - tile_data = _buffer_to_array(tile_data_buffer, hdr) - np.testing.assert_allclose(original_tile_1, tile_data) - - # gzip compression can be non-deterministic i.e. compression level, - # compressed data dtype etc. So this test can't work for GZIP files. - if not compression_type.startswith("GZIP"): - # Now compress the original data and see if we can recover the compressed bytes we loaded - compressed_tile_data = compress_tile( - original_tile_1, algorithm=compression_type, **settings - ) - assert compressed_tile_data == original_compressed_tile_bytes + assert_allclose(original_int_hdu.data, canonical_int_hdus.data) From b0263b120708a12d241c7c52064ccc44cce5621a Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Sat, 3 Dec 2022 10:53:29 +0000 Subject: [PATCH 17/32] Renamed lossless to gzip_fallback and don't have Quantize inherit from Codec --- .../tiled_compression/tiled_compression.py | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 0660bc3f356..7effd439bcc 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -86,13 +86,11 @@ def decode(self, buf, out=None): # pragma: no cover """ -class Quantize(Codec): +class Quantize: """ Quantization of floating-point data following the FITS standard. """ - codec_id = "FITS_QUANTIZE" - def __init__(self, row: int, dither_method: int, quantize_level: int, bitpix: int): super().__init__() self.row = row @@ -798,9 +796,9 @@ def decompress_hdu(hdu): ) settings["itemsize"] = tile_data.size // int(np.product(actual_tile_shape)) - lossless = len(cdata) == 0 + gzip_fallback = len(cdata) == 0 - if lossless: + if gzip_fallback: tile_buffer = decompress_tile( row["GZIP_COMPRESSED_DATA"], algorithm="GZIP_1" ) @@ -819,7 +817,7 @@ def decompress_hdu(hdu): tile_buffer, hdu._header, tile_shape=actual_tile_shape, - lossless=lossless or not quantize, + lossless=not quantize, ) if quantize: dither_method = DITHER_METHODS[hdu._header.get("ZQUANTIZ", "NO_DITHER")] @@ -859,7 +857,7 @@ def compress_hdu(hdu): data_shape = _data_shape(hdu._header) compressed_bytes = [] - lossless = [] + gzip_fallback = [] scales = [] zeros = [] @@ -897,20 +895,20 @@ def compress_hdu(hdu): except QuantizationFailedException: scales.append(0) zeros.append(0) - lossless.append(True) + gzip_fallback.append(True) else: data = np.asarray(data).reshape(original_shape) scales.append(scale) zeros.append(zero) - lossless.append(False) + gzip_fallback.append(False) else: scales.append(0) zeros.append(0) - lossless.append(False) + gzip_fallback.append(False) # The original compress_hdu assumed the data was in native endian, so we # change this here: - if hdu._header["ZCMPTYPE"].startswith("GZIP") or lossless[-1]: + if hdu._header["ZCMPTYPE"].startswith("GZIP") or gzip_fallback[-1]: # This is apparently needed so that our heap data agrees with # the C implementation!? data = data.astype(data.dtype.newbyteorder(">")) @@ -918,7 +916,7 @@ def compress_hdu(hdu): if not data.dtype.isnative: data = data.astype(data.dtype.newbyteorder("=")) - if lossless[-1]: + if gzip_fallback[-1]: cbytes = compress_tile(data, algorithm="GZIP_1") else: cbytes = compress_tile(data, algorithm=hdu._header["ZCMPTYPE"], **settings) @@ -949,7 +947,7 @@ def compress_hdu(hdu): table["COMPRESSED_DATA"][1:, 1] = np.cumsum(table["COMPRESSED_DATA"][:-1, 0]) for irow in range(len(compressed_bytes)): - if lossless[irow]: + if gzip_fallback[irow]: table["GZIP_COMPRESSED_DATA"][irow] = table["COMPRESSED_DATA"][irow] table["COMPRESSED_DATA"][irow] = 0 From 444c20e367de2de853b72ca2107c4190c7a60292 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Tue, 6 Dec 2022 16:35:39 +0000 Subject: [PATCH 18/32] Split up tiled_compression.py --- astropy/io/fits/tiled_compression/_codecs.py | 360 ++++++++++++ .../fits/tiled_compression/_quantization.py | 134 +++++ .../tiled_compression/tiled_compression.py | 520 +----------------- 3 files changed, 496 insertions(+), 518 deletions(-) create mode 100644 astropy/io/fits/tiled_compression/_codecs.py create mode 100644 astropy/io/fits/tiled_compression/_quantization.py diff --git a/astropy/io/fits/tiled_compression/_codecs.py b/astropy/io/fits/tiled_compression/_codecs.py new file mode 100644 index 00000000000..b72bdc25600 --- /dev/null +++ b/astropy/io/fits/tiled_compression/_codecs.py @@ -0,0 +1,360 @@ +""" +This file contains the FITS compression algorithms in numcodecs style Codecs. + +This module is currently private as these classes have not been tested +externally to Astropy and their behaviour may have to change. +""" +from gzip import compress as gzip_compress +from gzip import decompress as gzip_decompress + +import numpy as np + +from astropy.io.fits.tiled_compression._compression import ( + compress_hcompress_1_c, + compress_plio_1_c, + compress_rice_1_c, + decompress_hcompress_1_c, + decompress_plio_1_c, + decompress_rice_1_c, +) + +try: + from numcodecs.abc import Codec +except ImportError: + + class Codec: + codec_id = None + + +__all__ = [ + "Gzip1", + "Gzip2", + "Rice1", + "PLIO1", + "HCompress1", +] + + +class Gzip1(Codec): + """ + The FITS GZIP 1 compression and decompression algorithm. + + The Gzip algorithm is used in the free GNU software compression utility of + the same name. It was created by J. L. Gailly and M. Adler, based on the + DEFLATE algorithm (Deutsch 1996), which is a combination of LZ77 (Ziv & + Lempel 1977) and Huffman coding. + """ + + codec_id = "FITS_GZIP1" + + def decode(self, buf): + """ + Decompress buffer using the GZIP_1 algorithm. + + {_GZIP_1_DESCRIPTION} + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + dbytes = gzip_decompress(cbytes) + return np.frombuffer(dbytes, dtype=np.uint8).data + + def encode(self, buf): + """ + Compress the data in the buffer using the GZIP_1 algorithm. + + Parameters + ---------- + buf + The buffer to compress. + + Returns + ------- + buf + A buffer with compressed data. + """ + dbytes = np.asarray(buf).tobytes() + return gzip_compress(dbytes) + + +class Gzip2(Codec): + """ + The FTIS GZIP2 compression and decompression algorithm. + + The gzip2 algorithm is a variation on 'GZIP 1'. In this case the buffer in + the array of data values are shuffled so that they are arranged in order of + decreasing significance before being compressed. + + For example, a five-element contiguous array of two-byte (16-bit) integer + values, with an original big-endian byte order of: + + .. math:: + A1 A2 B1 B2 C1 C2 D1 D2 E1 E2 + + will have the following byte order after shuffling: + + .. math:: + A1 B1 C1 D1 E1 A2 B2 C2 D2 E2, + + where A1, B1, C1, D1, and E1 are the most-significant buffer from + each of the integer values. + + Byte shuffling shall only be performed for integer or floating-point + numeric data types; logical, bit, and character types must not be shuffled. + + Parameters + ---------- + itemsize + The number of buffer per value (e.g. 2 for a 16-bit integer) + + """ + + codec_id = "FITS_GZIP2" + + def __init__(self, itemsize: int): + super().__init__() + self.itemsize = itemsize + + def decode(self, buf): + """ + Decompress buffer using the GZIP_2 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + # Start off by unshuffling buffer + unshuffled_buffer = gzip_decompress(cbytes) + array = np.frombuffer(unshuffled_buffer, dtype=np.uint8) + return array.reshape((self.itemsize, -1)).T.ravel().data + + def encode(self, buf): + """ + Compress the data in the buffer using the GZIP_2 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + # Start off by shuffling buffer + array = np.asarray(buf).ravel() + itemsize = array.dtype.itemsize + array = array.view(np.uint8) + shuffled_buffer = array.reshape((-1, itemsize)).T.ravel().tobytes() + return gzip_compress(shuffled_buffer) + + +class Rice1(Codec): + """ + The FTIS RICE1 compression and decompression algorithm. + + The Rice algorithm [1]_ is simple and very fast It requires only enough + memory to hold a single block of 16 or 32 pixels at a time. It codes the + pixels in small blocks and so is able to adapt very quickly to changes in + the input image statistics (e.g., Rice has no problem handling cosmic rays, + bright stars, saturated pixels, etc.). + + Parameters + ---------- + blocksize + The blocksize to use, each tile is coded into blocks a number of pixels + wide. The default value in FITS headers is 32 pixels per block. + + bytepix + The number of 8-bit buffer in each original integer pixel value. + + References + ---------- + .. [1] Rice, R. F., Yeh, P.-S., and Miller, W. H. 1993, in Proc. of the 9th + AIAA Computing in Aerospace Conf., AIAA-93-4541-CP, American Institute of + Aeronautics and Astronautics [https://doi.org/10.2514/6.1993-4541] + """ + + codec_id = "FITS_RICE1" + + def __init__(self, blocksize: int, bytepix: int, tilesize: int): + self.blocksize = blocksize + self.bytepix = bytepix + self.tilesize = tilesize + + def decode(self, buf): + """ + Decompress buffer using the RICE_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + dbytes = decompress_rice_1_c( + cbytes, self.blocksize, self.bytepix, self.tilesize + ) + return np.frombuffer(dbytes, dtype=f"i{self.bytepix}").data + + def encode(self, buf): + """ + Compress the data in the buffer using the RICE_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + A buffer with decompressed data. + """ + dbytes = np.asarray(buf).astype(f"i{self.bytepix}").tobytes() + return compress_rice_1_c(dbytes, self.blocksize, self.bytepix) + + +class PLIO1(Codec): + """ + The FTIS PLIO1 compression and decompression algorithm. + + The IRAF PLIO (pixel list) algorithm was developed to store integer-valued + image masks in a compressed form. Such masks often have large regions of + constant value hence are highly compressible. The compression algorithm + used is based on run-length encoding, with the ability to dynamically + follow level changes in the image, allowing a 16-bit encoding to be used + regardless of the image depth. + """ + + codec_id = "FITS_PLIO1" + + def __init__(self, tilesize: int): + self.tilesize = tilesize + + def decode(self, buf): + """ + Decompress buffer using the PLIO_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + The decompressed buffer. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + dbytes = decompress_plio_1_c(cbytes, self.tilesize) + return np.frombuffer(dbytes, dtype="i4").data + + def encode(self, buf): + """ + Compress the data in the buffer using the PLIO_1 algorithm. + """ + dbytes = np.asarray(buf).astype("i4").tobytes() + return compress_plio_1_c(dbytes) + + +class HCompress1(Codec): + """ + The FTIS PLIO1 compression and decompression algorithm. + + Hcompress is an the image compression package written by Richard L. White + for use at the Space Telescope Science Institute. Hcompress was used to + compress the STScI Digitized Sky Survey and has also been used to compress + the preview images in the Hubble Data Archive. + + The technique gives very good compression for astronomical images and is + relatively fast. The calculations are carried out using integer arithmetic + and are entirely reversible. Consequently, the program can be used for + either lossy or lossless compression, with no special approach needed for + the lossless case. + + Parameters + ---------- + scale + The integer scale parameter determines the amount of compression. Scale + = 0 or 1 leads to lossless compression, i.e. the decompressed image has + exactly the same pixel values as the original image. If the scale + factor is greater than 1 then the compression is lossy: the + decompressed image will not be exactly the same as the original + + smooth + At high compressions factors the decompressed image begins to appear + blocky because of the way information is discarded. This blockiness + ness is greatly reduced, producing more pleasing images, if the image + is smoothed slightly during decompression. + """ + + codec_id = "FITS_HCOMPRESS1" + + def __init__(self, scale: int, smooth: bool, bytepix: int, nx: int, ny: int): + self.scale = scale + self.smooth = smooth + self.bytepix = bytepix + # NOTE: we should probably make this less confusing, but nx is shape[0] and ny is shape[1] + self.nx = nx + self.ny = ny + + def decode(self, buf): + """ + Decompress buffer using the HCOMPRESS_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + A buffer with decompressed data. + """ + cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() + dbytes = decompress_hcompress_1_c( + cbytes, self.nx, self.ny, self.scale, self.smooth, self.bytepix + ) + return np.frombuffer(dbytes, dtype=f"i{self.bytepix}").data + + def encode(self, buf): + """ + Compress the data in the buffer using the HCOMPRESS_1 algorithm. + + Parameters + ---------- + buf + The buffer to decompress. + + Returns + ------- + buf + A buffer with decompressed data. + """ + dbytes = np.asarray(buf).astype(f"i{self.bytepix}").tobytes() + return compress_hcompress_1_c( + dbytes, self.nx, self.ny, self.scale, self.bytepix + ) diff --git a/astropy/io/fits/tiled_compression/_quantization.py b/astropy/io/fits/tiled_compression/_quantization.py new file mode 100644 index 00000000000..a6f7386a1c2 --- /dev/null +++ b/astropy/io/fits/tiled_compression/_quantization.py @@ -0,0 +1,134 @@ +""" +This file contains the code for Quantizing / Dequantizing floats. +""" +import numpy as np + +from astropy.io.fits.hdu.base import BITPIX2DTYPE +from astropy.io.fits.tiled_compression._compression import ( + quantize_double_c, + quantize_float_c, + unquantize_double_c, + unquantize_float_c, +) + +__all__ = ["Quantize"] + + +DITHER_METHODS = {"NO_DITHER": -1, "SUBTRACTIVE_DITHER_1": 1, "SUBTRACTIVE_DITHER_2": 2} + + +class QuantizationFailedException(Exception): + pass + + +class Quantize: + """ + Quantization of floating-point data following the FITS standard. + """ + + def __init__(self, row: int, dither_method: int, quantize_level: int, bitpix: int): + super().__init__() + self.row = row + # TODO: pass dither method as a string instead of int? + self.quantize_level = quantize_level + self.dither_method = dither_method + self.bitpix = bitpix + + # NOTE: below we use decode_quantized and encode_quantized instead of + # decode and encode as we need to break with the numcodec API and take/return + # scale and zero in addition to quantized value. We should figure out how + # to properly use the numcodec API for this use case. + + def decode_quantized(self, buf, scale, zero): + """ + Unquantize data + + Parameters + ---------- + buf + The buffer to unquantize. + + Returns + ------- + buf + The unquantized buffer. + """ + qbytes = np.asarray(buf) + qbytes = qbytes.astype(qbytes.dtype.newbyteorder("=")) + # TODO: figure out if we need to support null checking + if self.dither_method == -1: + # For NO_DITHER we should just use the scale and zero directly + return qbytes * scale + zero + if self.bitpix == -32: + ubytes = unquantize_float_c( + qbytes.tobytes(), + self.row, + qbytes.size, + scale, + zero, + self.dither_method, + 0, + 0, + 0.0, + qbytes.dtype.itemsize, + ) + elif self.bitpix == -64: + ubytes = unquantize_double_c( + qbytes.tobytes(), + self.row, + qbytes.size, + scale, + zero, + self.dither_method, + 0, + 0, + 0.0, + qbytes.dtype.itemsize, + ) + else: + raise TypeError("bitpix should be one of -32 or -64") + return np.frombuffer(ubytes, dtype=BITPIX2DTYPE[self.bitpix]).data + + def encode_quantized(self, buf): + """ + Quantize data. + + Parameters + ---------- + buf + The buffer to quantize. + + Returns + ------- + buf + A buffer with quantized data. + """ + uarray = np.asarray(buf) + uarray = uarray.astype(uarray.dtype.newbyteorder("=")) + # TODO: figure out if we need to support null checking + if uarray.dtype.itemsize == 4: + qbytes, status, scale, zero = quantize_float_c( + uarray.tobytes(), + self.row, + uarray.size, + 1, + 0, + 0, + self.quantize_level, + self.dither_method, + )[:4] + elif uarray.dtype.itemsize == 8: + qbytes, status, scale, zero = quantize_double_c( + uarray.tobytes(), + self.row, + uarray.size, + 1, + 0, + 0, + self.quantize_level, + self.dither_method, + )[:4] + if status == 0: + raise QuantizationFailedException() + else: + return np.frombuffer(qbytes, dtype=np.int32).data, scale, zero diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 7effd439bcc..21cd6fa8896 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -1,528 +1,12 @@ """ This module contains low level helper functions for compressing and decompressing buffer for the Tiled Table Compression algorithms as specified in the FITS 4 standard. """ -from abc import abstractmethod -from gzip import compress as gzip_compress -from gzip import decompress as gzip_decompress - import numpy as np from astropy.io.fits.hdu.base import BITPIX2DTYPE -from astropy.io.fits.tiled_compression._compression import ( - compress_hcompress_1_c, - compress_plio_1_c, - compress_rice_1_c, - decompress_hcompress_1_c, - decompress_plio_1_c, - decompress_rice_1_c, - quantize_double_c, - quantize_float_c, - unquantize_double_c, - unquantize_float_c, -) - -__all__ = [ - "Gzip1", - "Gzip2", - "Rice1", - "PLIO1", - "HCompress1", - "Quantize", - "compress_tile", - "decompress_tile", - "compress_hdu", - "decompress_hdu", -] - -DITHER_METHODS = {"NO_DITHER": -1, "SUBTRACTIVE_DITHER_1": 1, "SUBTRACTIVE_DITHER_2": 2} - - -class QuantizationFailedException(Exception): - pass - - -# We define our compression classes in the form of a numcodecs class. We make -# the dependency on numcodecs optional as we can use them internally without it -# (for now). -try: - from numcodecs.abc import Codec -except ImportError: - - class Codec: - codec_id = None - """Codec identifier.""" - - @abstractmethod - def encode(self, buf): # pragma: no cover - """Encode data in `buf`. - Parameters - ---------- - buf : buffer-like - Data to be encoded. May be any object supporting the new-style - buffer protocol. - Returns - ------- - enc : buffer-like - Encoded data. May be any object supporting the new-style buffer - protocol. - """ - - @abstractmethod - def decode(self, buf, out=None): # pragma: no cover - """Decode data in `buf`. - Parameters - ---------- - buf : buffer-like - Encoded data. May be any object supporting the new-style buffer - protocol. - out : buffer-like, optional - Writeable buffer to store decoded data. N.B. if provided, this buffer must - be exactly the right size to store the decoded data. - Returns - ------- - dec : buffer-like - Decoded data. May be any object supporting the new-style - buffer protocol. - """ - - -class Quantize: - """ - Quantization of floating-point data following the FITS standard. - """ - - def __init__(self, row: int, dither_method: int, quantize_level: int, bitpix: int): - super().__init__() - self.row = row - # TODO: pass dither method as a string instead of int? - self.quantize_level = quantize_level - self.dither_method = dither_method - self.bitpix = bitpix - - # NOTE: below we use decode_quantized and encode_quantized instead of - # decode and encode as we need to break with the numcodec API and take/return - # scale and zero in addition to quantized value. We should figure out how - # to properly use the numcodec API for this use case. - - def decode_quantized(self, buf, scale, zero): - """ - Unquantize data - - Parameters - ---------- - buf - The buffer to unquantize. - - Returns - ------- - buf - The unquantized buffer. - """ - qbytes = np.asarray(buf) - qbytes = qbytes.astype(qbytes.dtype.newbyteorder("=")) - # TODO: figure out if we need to support null checking - if self.dither_method == -1: - # For NO_DITHER we should just use the scale and zero directly - return qbytes * scale + zero - if self.bitpix == -32: - ubytes = unquantize_float_c( - qbytes.tobytes(), - self.row, - qbytes.size, - scale, - zero, - self.dither_method, - 0, - 0, - 0.0, - qbytes.dtype.itemsize, - ) - elif self.bitpix == -64: - ubytes = unquantize_double_c( - qbytes.tobytes(), - self.row, - qbytes.size, - scale, - zero, - self.dither_method, - 0, - 0, - 0.0, - qbytes.dtype.itemsize, - ) - else: - raise TypeError("bitpix should be one of -32 or -64") - return np.frombuffer(ubytes, dtype=BITPIX2DTYPE[self.bitpix]).data - - def encode_quantized(self, buf): - """ - Quantize data. - - Parameters - ---------- - buf - The buffer to quantize. - - Returns - ------- - buf - A buffer with quantized data. - """ - uarray = np.asarray(buf) - uarray = uarray.astype(uarray.dtype.newbyteorder("=")) - # TODO: figure out if we need to support null checking - if uarray.dtype.itemsize == 4: - qbytes, status, scale, zero = quantize_float_c( - uarray.tobytes(), - self.row, - uarray.size, - 1, - 0, - 0, - self.quantize_level, - self.dither_method, - )[:4] - elif uarray.dtype.itemsize == 8: - qbytes, status, scale, zero = quantize_double_c( - uarray.tobytes(), - self.row, - uarray.size, - 1, - 0, - 0, - self.quantize_level, - self.dither_method, - )[:4] - if status == 0: - raise QuantizationFailedException() - else: - return np.frombuffer(qbytes, dtype=np.int32).data, scale, zero - - -class Gzip1(Codec): - """ - The FITS GZIP 1 compression and decompression algorithm. - - The Gzip algorithm is used in the free GNU software compression utility of - the same name. It was created by J. L. Gailly and M. Adler, based on the - DEFLATE algorithm (Deutsch 1996), which is a combination of LZ77 (Ziv & - Lempel 1977) and Huffman coding. - """ - - codec_id = "FITS_GZIP1" - - def decode(self, buf): - """ - Decompress buffer using the GZIP_1 algorithm. - - {_GZIP_1_DESCRIPTION} - - Parameters - ---------- - buf - The buffer to decompress. - - Returns - ------- - buf - The decompressed buffer. - """ - cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() - dbytes = gzip_decompress(cbytes) - return np.frombuffer(dbytes, dtype=np.uint8).data - - def encode(self, buf): - """ - Compress the data in the buffer using the GZIP_1 algorithm. - - Parameters - ---------- - buf - The buffer to compress. - - Returns - ------- - buf - A buffer with compressed data. - """ - dbytes = np.asarray(buf).tobytes() - return gzip_compress(dbytes) - - -class Gzip2(Codec): - """ - The FTIS GZIP2 compression and decompression algorithm. - - The gzip2 algorithm is a variation on 'GZIP 1'. In this case the buffer in - the array of data values are shuffled so that they are arranged in order of - decreasing significance before being compressed. - - For example, a five-element contiguous array of two-byte (16-bit) integer - values, with an original big-endian byte order of: - - .. math:: - A1 A2 B1 B2 C1 C2 D1 D2 E1 E2 - - will have the following byte order after shuffling: - - .. math:: - A1 B1 C1 D1 E1 A2 B2 C2 D2 E2, - - where A1, B1, C1, D1, and E1 are the most-significant buffer from - each of the integer values. - - Byte shuffling shall only be performed for integer or floating-point - numeric data types; logical, bit, and character types must not be shuffled. - - Parameters - ---------- - itemsize - The number of buffer per value (e.g. 2 for a 16-bit integer) - - """ - - codec_id = "FITS_GZIP2" - - def __init__(self, itemsize: int): - super().__init__() - self.itemsize = itemsize - - def decode(self, buf): - """ - Decompress buffer using the GZIP_2 algorithm. - - Parameters - ---------- - buf - The buffer to decompress. - - Returns - ------- - buf - The decompressed buffer. - """ - cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() - # Start off by unshuffling buffer - unshuffled_buffer = gzip_decompress(cbytes) - array = np.frombuffer(unshuffled_buffer, dtype=np.uint8) - return array.reshape((self.itemsize, -1)).T.ravel().data - - def encode(self, buf): - """ - Compress the data in the buffer using the GZIP_2 algorithm. - - Parameters - ---------- - buf - The buffer to decompress. - - Returns - ------- - buf - The decompressed buffer. - """ - # Start off by shuffling buffer - array = np.asarray(buf).ravel() - itemsize = array.dtype.itemsize - array = array.view(np.uint8) - shuffled_buffer = array.reshape((-1, itemsize)).T.ravel().tobytes() - return gzip_compress(shuffled_buffer) - - -class Rice1(Codec): - """ - The FTIS RICE1 compression and decompression algorithm. - - The Rice algorithm [1]_ is simple and very fast It requires only enough - memory to hold a single block of 16 or 32 pixels at a time. It codes the - pixels in small blocks and so is able to adapt very quickly to changes in - the input image statistics (e.g., Rice has no problem handling cosmic rays, - bright stars, saturated pixels, etc.). - - Parameters - ---------- - blocksize - The blocksize to use, each tile is coded into blocks a number of pixels - wide. The default value in FITS headers is 32 pixels per block. - - bytepix - The number of 8-bit buffer in each original integer pixel value. - - References - ---------- - .. [1] Rice, R. F., Yeh, P.-S., and Miller, W. H. 1993, in Proc. of the 9th - AIAA Computing in Aerospace Conf., AIAA-93-4541-CP, American Institute of - Aeronautics and Astronautics [https://doi.org/10.2514/6.1993-4541] - """ - - codec_id = "FITS_RICE1" - - def __init__(self, blocksize: int, bytepix: int, tilesize: int): - self.blocksize = blocksize - self.bytepix = bytepix - self.tilesize = tilesize - - def decode(self, buf): - """ - Decompress buffer using the RICE_1 algorithm. - - Parameters - ---------- - buf - The buffer to decompress. - - Returns - ------- - buf - The decompressed buffer. - """ - cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() - dbytes = decompress_rice_1_c( - cbytes, self.blocksize, self.bytepix, self.tilesize - ) - return np.frombuffer(dbytes, dtype=f"i{self.bytepix}").data - - def encode(self, buf): - """ - Compress the data in the buffer using the RICE_1 algorithm. - - Parameters - ---------- - buf - The buffer to decompress. - - Returns - ------- - buf - A buffer with decompressed data. - """ - dbytes = np.asarray(buf).astype(f"i{self.bytepix}").tobytes() - return compress_rice_1_c(dbytes, self.blocksize, self.bytepix) - - -class PLIO1(Codec): - """ - The FTIS PLIO1 compression and decompression algorithm. - - The IRAF PLIO (pixel list) algorithm was developed to store integer-valued - image masks in a compressed form. Such masks often have large regions of - constant value hence are highly compressible. The compression algorithm - used is based on run-length encoding, with the ability to dynamically - follow level changes in the image, allowing a 16-bit encoding to be used - regardless of the image depth. - """ - - codec_id = "FITS_PLIO1" - - def __init__(self, tilesize: int): - self.tilesize = tilesize - - def decode(self, buf): - """ - Decompress buffer using the PLIO_1 algorithm. - - Parameters - ---------- - buf - The buffer to decompress. - - Returns - ------- - buf - The decompressed buffer. - """ - cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() - dbytes = decompress_plio_1_c(cbytes, self.tilesize) - return np.frombuffer(dbytes, dtype="i4").data - - def encode(self, buf): - """ - Compress the data in the buffer using the PLIO_1 algorithm. - """ - dbytes = np.asarray(buf).astype("i4").tobytes() - return compress_plio_1_c(dbytes) - - -class HCompress1(Codec): - """ - The FTIS PLIO1 compression and decompression algorithm. - - Hcompress is an the image compression package written by Richard L. White - for use at the Space Telescope Science Institute. Hcompress was used to - compress the STScI Digitized Sky Survey and has also been used to compress - the preview images in the Hubble Data Archive. - - The technique gives very good compression for astronomical images and is - relatively fast. The calculations are carried out using integer arithmetic - and are entirely reversible. Consequently, the program can be used for - either lossy or lossless compression, with no special approach needed for - the lossless case. - - Parameters - ---------- - scale - The integer scale parameter determines the amount of compression. Scale - = 0 or 1 leads to lossless compression, i.e. the decompressed image has - exactly the same pixel values as the original image. If the scale - factor is greater than 1 then the compression is lossy: the - decompressed image will not be exactly the same as the original - - smooth - At high compressions factors the decompressed image begins to appear - blocky because of the way information is discarded. This blockiness - ness is greatly reduced, producing more pleasing images, if the image - is smoothed slightly during decompression. - """ - - codec_id = "FITS_HCOMPRESS1" - - def __init__(self, scale: int, smooth: bool, bytepix: int, nx: int, ny: int): - self.scale = scale - self.smooth = smooth - self.bytepix = bytepix - # NOTE: we should probably make this less confusing, but nx is shape[0] and ny is shape[1] - self.nx = nx - self.ny = ny - - def decode(self, buf): - """ - Decompress buffer using the HCOMPRESS_1 algorithm. - - Parameters - ---------- - buf - The buffer to decompress. - - Returns - ------- - buf - A buffer with decompressed data. - """ - cbytes = np.frombuffer(buf, dtype=np.uint8).tobytes() - dbytes = decompress_hcompress_1_c( - cbytes, self.nx, self.ny, self.scale, self.smooth, self.bytepix - ) - return np.frombuffer(dbytes, dtype=f"i{self.bytepix}").data - - def encode(self, buf): - """ - Compress the data in the buffer using the HCOMPRESS_1 algorithm. - - Parameters - ---------- - buf - The buffer to decompress. - - Returns - ------- - buf - A buffer with decompressed data. - """ - dbytes = np.asarray(buf).astype(f"i{self.bytepix}").tobytes() - return compress_hcompress_1_c( - dbytes, self.nx, self.ny, self.scale, self.bytepix - ) +from ._codecs import PLIO1, Gzip1, Gzip2, HCompress1, Rice1 +from ._quantization import DITHER_METHODS, QuantizationFailedException, Quantize ALGORITHMS = { "GZIP_1": Gzip1, From 9e8b9931724c6e30bd28fac3f01a1b5d17a37d81 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Tue, 13 Dec 2022 22:28:55 +0000 Subject: [PATCH 19/32] Fixed test_empty --- astropy/io/fits/tiled_compression/tiled_compression.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 21cd6fa8896..b1918da30d8 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -253,6 +253,9 @@ def decompress_hdu(hdu): quantize = "ZSCALE" in hdu.compressed_data.dtype.names + if len(hdu.compressed_data) == 0: + return None + istart = np.zeros(data.ndim, dtype=int) for irow, row in enumerate(hdu.compressed_data): @@ -352,6 +355,9 @@ def compress_hdu(hdu): while True: + if hdu.data is None: + break + # In the following, we don't need to special case tiles near the edge # as Numpy will automatically ignore parts of the slices that are out # of bounds. @@ -427,7 +433,7 @@ def compress_hdu(hdu): for irow, cbytes in enumerate(compressed_bytes): table["COMPRESSED_DATA"][irow, 0] = len(cbytes) - table["COMPRESSED_DATA"][0, 1] = 0 + table["COMPRESSED_DATA"][:1, 1] = 0 table["COMPRESSED_DATA"][1:, 1] = np.cumsum(table["COMPRESSED_DATA"][:-1, 0]) for irow in range(len(compressed_bytes)): From 6018700721b39851ac37feb066887d794cc41e62 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Tue, 13 Dec 2022 22:43:10 +0000 Subject: [PATCH 20/32] Fix tests related to PLIO compression (in which gzip fallback is used) --- .../io/fits/tiled_compression/tiled_compression.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index b1918da30d8..bffdc31216b 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -446,13 +446,17 @@ def compress_hdu(hdu): if hdu._header["ZCMPTYPE"] == "PLIO_1": table["COMPRESSED_DATA"][:, 0] //= 2 - compressed_bytes = b"".join(compressed_bytes) - # For PLIO_1, it looks like the compressed data is byteswapped if hdu._header["ZCMPTYPE"] == "PLIO_1": - compressed_bytes = ( - np.frombuffer(compressed_bytes, dtype="i2").tobytes() - ) + for irow in range(len(compressed_bytes)): + if not gzip_fallback[irow]: + compressed_bytes[irow] = ( + np.frombuffer(compressed_bytes[irow], dtype="i2") + .tobytes() + ) + + compressed_bytes = b"".join(compressed_bytes) table_bytes = table.tobytes() From e7a5a776883d732eb3d24550c90eec2e4c586def Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Tue, 13 Dec 2022 16:02:35 +0000 Subject: [PATCH 21/32] Include an exact zero in test data and make sure this is preserved exactly with SUBTRACTIVE_DITHER_2 --- .../io/fits/tiled_compression/tests/test_fitsio.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tests/test_fitsio.py b/astropy/io/fits/tiled_compression/tests/test_fitsio.py index 498b5536713..35ad2576ad9 100644 --- a/astropy/io/fits/tiled_compression/tests/test_fitsio.py +++ b/astropy/io/fits/tiled_compression/tests/test_fitsio.py @@ -75,6 +75,9 @@ def data_shape(array_shapes_tile_dims): @pytest.fixture(scope="module") def base_original_data(data_shape, dtype, numpy_rng, compression_type): random = numpy_rng.uniform(high=255, size=data_shape) + # Set first value to be exactly zero as zero values require special treatment + # for SUBTRACTIVE_DITHER_2 + random.ravel()[0] = 0.0 # There seems to be a bug with the fitsio library where HCOMPRESS doesn't # work with int16 random data, so use a bit for structured test data. if compression_type.startswith("HCOMPRESS") and "i2" in dtype or "u1" in dtype: @@ -149,11 +152,11 @@ def astropy_compressed_file_path( def test_decompress( fitsio_compressed_file_path, - base_original_data, - compression_type, - dtype, + comp_param_dtype, ): + compression_type, param, dtype = comp_param_dtype + with fits.open(fitsio_compressed_file_path) as hdul: data = hdul[1].data @@ -171,10 +174,13 @@ def test_decompress( data2 = fts[1].read() np.testing.assert_allclose(data, data2) + # The first value should be exactly equal to zero when using SUBTRACTIVE_DITHER_2 + if param.get("qmethod", None) == 2: + assert data.ravel()[0] == 0.0 + def test_compress( astropy_compressed_file_path, - base_original_data, compression_type, dtype, ): From e302f8d3146a6ef2fab8156378daaeaa997633e2 Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Tue, 13 Dec 2022 15:37:57 +0000 Subject: [PATCH 22/32] Added support for ZBLANK --- astropy/io/fits/hdu/compressed.py | 5 ++ .../tests/data/compressed_with_nan.fits | 2 + .../tests/test_tiled_compression.py | 27 ++++++++- .../tiled_compression/tiled_compression.py | 56 +++++++++++++++++++ 4 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 astropy/io/fits/tiled_compression/tests/data/compressed_with_nan.fits diff --git a/astropy/io/fits/hdu/compressed.py b/astropy/io/fits/hdu/compressed.py index 983d8956d53..e62e96a8606 100644 --- a/astropy/io/fits/hdu/compressed.py +++ b/astropy/io/fits/hdu/compressed.py @@ -1484,6 +1484,11 @@ def data(self): new_dtype = self._dtype_for_bitpix() data = np.array(data, dtype=new_dtype) + # TODO: ZBLANK should not be dealt with here as it should apply + # to data before being unquantized, which has already happened in + # decompress_hdu, so we should remove references to ZBLANK here. + # However, it might be that we still need to handle BLANK. + zblank = None if "ZBLANK" in self.compressed_data.columns.names: diff --git a/astropy/io/fits/tiled_compression/tests/data/compressed_with_nan.fits b/astropy/io/fits/tiled_compression/tests/data/compressed_with_nan.fits new file mode 100644 index 00000000000..5a1f6a4ba4c --- /dev/null +++ b/astropy/io/fits/tiled_compression/tests/data/compressed_with_nan.fits @@ -0,0 +1,2 @@ +SIMPLE = T / file does conform to FITS standard BITPIX = 16 / number of bits per data pixel NAXIS = 0 / number of data axes EXTEND = T / FITS dataset may contain extensions COMMENT FITS (Flexible Image Transport System) format is defined in 'AstronomyCOMMENT and Astrophysics', volume 376, page 359; bibcode: 2001A&A...376..359H CHECKSUM= '2cH54Z932bG32Z93' / HDU checksum updated 2022-12-13T14:49:19 DATASUM = ' 0' / data unit checksum updated 2022-12-13T14:49:19 END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / 8-bit bytes NAXIS = 2 / 2-dimensional binary table NAXIS1 = 24 / width of table in bytes NAXIS2 = 4 / number of rows in table PCOUNT = 106 / size of special data area GCOUNT = 1 / one data group (required keyword) TFIELDS = 3 / number of fields in each row TTYPE1 = 'COMPRESSED_DATA' / label for field 1 TFORM1 = '1PB(28) ' / data format of field: variable length array TTYPE2 = 'ZSCALE ' / label for field 2 TFORM2 = '1D ' / data format of field: 8-byte DOUBLE TTYPE3 = 'ZZERO ' / label for field 3 TFORM3 = '1D ' / data format of field: 8-byte DOUBLE ZIMAGE = T / extension contains compressed image ZTILE1 = 6 / size of tiles to be compressed ZTILE2 = 6 / size of tiles to be compressed ZCMPTYPE= 'RICE_1 ' / compression algorithm ZBLANK = -2147483647 / null value in the compressed integer array ZNAME1 = 'BLOCKSIZE' / compression block size ZVAL1 = 32 / pixels per block ZNAME2 = 'BYTEPIX ' / bytes per pixel (1, 2, 4, or 8) ZVAL2 = 4 / bytes per pixel (1, 2, 4, or 8) EXTNAME = 'COMPRESSED_IMAGE' ZSIMPLE = T / conforms to FITS standard ZBITPIX = -64 / array data type ZNAXIS = 2 / number of array dimensions ZNAXIS1 = 12 ZNAXIS2 = 12 ZEXTEND = T ZQUANTIZ= 'SUBTRACTIVE_DITHER_1' / Pixel Quantization Algorithm HISTORY Image was compressed by CFITSIO using scaled integer quantization: HISTORY q = 4.000000 / quantized level scaling parameter HISTORY 'SUBTRACTIVE_DITHER_1' / Pixel Quantization Algorithm ZDITHER0= 2961 / dithering offset when quantizing floats CHECKSUM= 'Qa6JTX4HQa4HQU4H' / HDU checksum updated 2022-12-13T14:49:19 DATASUM = '2516956193' / data unit checksum updated 2022-12-13T14:49:19 END ?àÆîð DAÐÆí£?àÆîð D@ZŠ>6?àÆîð D@QôC¼î3P?àÆîð D@S†’ +#oq€ $fV`À GV ®ÌÁUÕ˜+3«p‘DÈ€dDD"" #“"0DHl‰€FLˆDDÀ""&6`6ˆ̈€FDÈ&D@#d2"`ˆˆ \ No newline at end of file diff --git a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py index 6699beaef2e..720c0033b36 100644 --- a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py @@ -1,7 +1,8 @@ from pathlib import Path +import numpy as np import pytest -from numpy.testing import assert_allclose +from numpy.testing import assert_allclose, assert_equal from astropy.io import fits @@ -36,3 +37,27 @@ def original_int_hdu(canonical_data_base_path): @pytest.mark.openfiles_ignore def test_canonical_data(original_int_hdu, canonical_int_hdus): assert_allclose(original_int_hdu.data, canonical_int_hdus.data) + + +def test_zblank_support(canonical_data_base_path, tmp_path): + + # This uses a test 12x12 image which contains a NaN value in the [1, 1] + # pixel - it was compressed using fpack which automatically added a ZBLANK + # header keyword + + reference = np.arange(144).reshape((12, 12)).astype(float) + reference[1, 1] = np.nan + + with fits.open(canonical_data_base_path / "compressed_with_nan.fits") as hdul: + assert_equal(np.round(hdul[1].data), reference) + + # Now generate a file ourselves and check that the output has the ZBLANK + # keyword set automatically + + hdu = fits.CompImageHDU(data=reference, compression_type="RICE_1", tile_size=(6, 6)) + + hdu.writeto(tmp_path / "test_zblank.fits") + + with fits.open(tmp_path / "test_zblank.fits") as hdul: + assert "ZBLANK" in hdul[1].header + assert_equal(np.round(hdul[1].data), reference) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index bffdc31216b..87eb2f0b312 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -17,6 +17,8 @@ "HCOMPRESS_1": HCompress1, } +DEFAULT_ZBLANK = -2147483648 + def decompress_tile(buf, *, algorithm: str, **kwargs): """ @@ -306,6 +308,17 @@ def decompress_hdu(hdu): tile_shape=actual_tile_shape, lossless=not quantize, ) + + if "ZBLANK" in row.array.names: + zblank = row["ZBLANK"] + elif "ZBLANK" in hdu._header: + zblank = hdu._header["ZBLANK"] + else: + zblank = None + + if zblank is not None: + blank_mask = tile_data == zblank + if quantize: dither_method = DITHER_METHODS[hdu._header.get("ZQUANTIZ", "NO_DITHER")] dither_seed = hdu._header.get("ZDITHER0", 0) @@ -316,6 +329,11 @@ def decompress_hdu(hdu): q.decode_quantized(tile_data, row["ZSCALE"], row["ZZERO"]) ).reshape(actual_tile_shape) + if zblank is not None: + if not tile_data.flags.writeable: + tile_data = tile_data.copy() + tile_data[blank_mask] = np.nan + data[tile_slices] = tile_data istart[-1] += tile_shape[-1] for idx in range(data.ndim - 1, 0, -1): @@ -347,6 +365,7 @@ def compress_hdu(hdu): gzip_fallback = [] scales = [] zeros = [] + zblank = None irow = 0 istart = np.zeros(len(data_shape), dtype=int) @@ -380,17 +399,51 @@ def compress_hdu(hdu): irow + dither_seed, dither_method, noisebit, hdu._header["ZBITPIX"] ) original_shape = data.shape + + # If there are any NaN values in the data, we should reset them to + # a value that will not affect the quantization (an already existing + # data value in the array) and we can then reset this after quantization + # to ZBLANK and set the appropriate header keyword + nan_mask = np.isnan(data) + any_nan = np.any(nan_mask) + if any_nan: + # Note that we need to copy here to avoid modifying the input array. + data = data.copy() + if np.all(nan_mask): + data[nan_mask] = 0 + else: + data[nan_mask] = np.nanmin(data) + try: data, scale, zero = q.encode_quantized(data) except QuantizationFailedException: + + if any_nan: + # reset NaN values since we will losslessly compress. + data[nan_mask] = np.nan + scales.append(0) zeros.append(0) gzip_fallback.append(True) + else: data = np.asarray(data).reshape(original_shape) + + if any_nan: + if not data.flags.writeable: + data = data.copy() + # For now, we just use the default ZBLANK value and assume + # this is the same for all tiles. We could generalize this + # to allow different ZBLANK values (for example if the data + # includes this value by chance) and to allow different values + # per tile, which is allowed by the FITS standard. + data[nan_mask] = DEFAULT_ZBLANK + zblank = DEFAULT_ZBLANK + scales.append(scale) zeros.append(zero) gzip_fallback.append(False) + else: scales.append(0) zeros.append(0) @@ -424,6 +477,9 @@ def compress_hdu(hdu): irow += 1 + if zblank is not None: + hdu._header['ZBLANK'] = zblank + table = np.zeros(len(compressed_bytes), dtype=hdu.columns.dtype.newbyteorder(">")) if "ZSCALE" in table.dtype.names: From f58e55df90af3dced8ccd99f8e0a3d2848703be7 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Thu, 15 Dec 2022 10:53:48 +0000 Subject: [PATCH 23/32] Attempt to fix the PLIO compression segfault --- astropy/io/fits/tiled_compression/_codecs.py | 2 +- .../io/fits/tiled_compression/src/compression.c | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/astropy/io/fits/tiled_compression/_codecs.py b/astropy/io/fits/tiled_compression/_codecs.py index b72bdc25600..1272bc63bbb 100644 --- a/astropy/io/fits/tiled_compression/_codecs.py +++ b/astropy/io/fits/tiled_compression/_codecs.py @@ -276,7 +276,7 @@ def encode(self, buf): Compress the data in the buffer using the PLIO_1 algorithm. """ dbytes = np.asarray(buf).astype("i4").tobytes() - return compress_plio_1_c(dbytes) + return compress_plio_1_c(dbytes, self.tilesize) class HCompress1(Codec): diff --git a/astropy/io/fits/tiled_compression/src/compression.c b/astropy/io/fits/tiled_compression/src/compression.c index 13c3c86021a..f948395ea8d 100644 --- a/astropy/io/fits/tiled_compression/src/compression.c +++ b/astropy/io/fits/tiled_compression/src/compression.c @@ -96,29 +96,27 @@ static PyObject *compress_plio_1_c(PyObject *self, PyObject *args) { PyObject *result; int maxelem; - int npix; + int tilesize; short *compressed_values; int compressed_length; int *decompressed_values; - if (!PyArg_ParseTuple(args, "y#", &str, &count)) { + if (!PyArg_ParseTuple(args, "y#i", &str, &count, &tilesize)) { return NULL; } - // maxelem adapted from cfitsio's imcomp_calc_max_elem function - maxelem = count; - + // For PLIO imcomp_calc_max_elem in cfitsio does this to calculate max memory: + maxelem = tilesize * sizeof(int); compressed_values = (short *)malloc(maxelem); decompressed_values = (int *)str; - npix = count / 4; // Zero the compressed values array for (int i = 0; i < maxelem / 2; i++) { compressed_values[i] = 0; } - compressed_length = pl_p2li(decompressed_values, 1, compressed_values, npix); + compressed_length = pl_p2li(decompressed_values, 1, compressed_values, tilesize); buf = (char *)compressed_values; @@ -145,8 +143,6 @@ static PyObject *decompress_plio_1_c(PyObject *self, PyObject *args) { compressed_values = (short *)str; - // NOTE: the second *4 shouldn't be needed but ran into segfaults with - // smaller buffers. decompressed_values = (int *)malloc(sizeof(int) * tilesize); pl_l2pi(compressed_values, 1, decompressed_values, tilesize); From bfd4e2f85f5816c287642eb68f506c1ab70c3006 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Wed, 14 Dec 2022 15:49:53 +0000 Subject: [PATCH 24/32] TMP: Remove some unused code from imcompress.c This commit should be able to be dropped / modified when we actually remove the unused bits of cfitsio. --- cextern/cfitsio/lib/imcompress.c | 10188 ++--------------------------- 1 file changed, 397 insertions(+), 9791 deletions(-) diff --git a/cextern/cfitsio/lib/imcompress.c b/cextern/cfitsio/lib/imcompress.c index ce3ab0fb92e..58f6fbe17f9 100644 --- a/cextern/cfitsio/lib/imcompress.c +++ b/cextern/cfitsio/lib/imcompress.c @@ -21,157 +21,6 @@ char results[999][30]; float *fits_rand_value = 0; -int imcomp_write_nocompress_tile(fitsfile *outfptr, long row, int datatype, - void *tiledata, long tilelen, int nullcheck, void *nullflagval, int *status); -int imcomp_convert_tile_tshort(fitsfile *outfptr, void *tiledata, long tilelen, - int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale, - double zero, double actual_bzero, int *intlength, int *status); -int imcomp_convert_tile_tushort(fitsfile *outfptr, void *tiledata, long tilelen, - int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale, - double zero, int *intlength, int *status); -int imcomp_convert_tile_tint(fitsfile *outfptr, void *tiledata, long tilelen, - int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale, - double zero, int *intlength, int *status); -int imcomp_convert_tile_tuint(fitsfile *outfptr, void *tiledata, long tilelen, - int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale, - double zero, int *intlength, int *status); -int imcomp_convert_tile_tbyte(fitsfile *outfptr, void *tiledata, long tilelen, - int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale, - double zero, int *intlength, int *status); -int imcomp_convert_tile_tsbyte(fitsfile *outfptr, void *tiledata, long tilelen, - int nullcheck, void *nullflagval, int nullval, int zbitpix, double scale, - double zero, int *intlength, int *status); -int imcomp_convert_tile_tfloat(fitsfile *outfptr, long row, void *tiledata, long tilelen, - long tilenx, long tileny, int nullcheck, void *nullflagval, int nullval, int zbitpix, - double scale, double zero, int *intlength, int *flag, double *bscale, double *bzero,int *status); -int imcomp_convert_tile_tdouble(fitsfile *outfptr, long row, void *tiledata, long tilelen, - long tilenx, long tileny, int nullcheck, void *nullflagval, int nullval, int zbitpix, - double scale, double zero, int *intlength, int *flag, double *bscale, double *bzero, int *status); - -static int unquantize_i1r4(long row, - unsigned char *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ - float nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - float *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -static int unquantize_i2r4(long row, - short *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - short tnull, /* I - value of FITS TNULLn keyword if any */ - float nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - float *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -static int unquantize_i4r4(long row, - INT32BIT *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - INT32BIT tnull, /* I - value of FITS TNULLn keyword if any */ - float nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - float *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -static int unquantize_i1r8(long row, - unsigned char *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ - double nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - double *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -static int unquantize_i2r8(long row, - short *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - short tnull, /* I - value of FITS TNULLn keyword if any */ - double nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - double *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -static int unquantize_i4r8(long row, - INT32BIT *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - INT32BIT tnull, /* I - value of FITS TNULLn keyword if any */ - double nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - double *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -static int imcomp_float2nan(float *indata, long tilelen, int *outdata, - float nullflagval, int *status); -static int imcomp_double2nan(double *indata, long tilelen, LONGLONG *outdata, - double nullflagval, int *status); -static int fits_read_write_compressed_img(fitsfile *fptr, /* I - FITS file pointer */ - int datatype, /* I - datatype of the array to be returned */ - LONGLONG *infpixel, /* I - 'bottom left corner' of the subsection */ - LONGLONG *inlpixel, /* I - 'top right corner' of the subsection */ - long *ininc, /* I - increment to be applied in each dimension */ - int nullcheck, /* I - 0 for no null checking */ - /* 1: set undefined pixels = nullval */ - void *nullval, /* I - value for undefined pixels */ - int *anynul, /* O - set to 1 if any values are null; else 0 */ - fitsfile *outfptr, /* I - FITS file pointer */ - int *status); - -static int fits_shuffle_8bytes(char *heap, LONGLONG length, int *status); -static int fits_shuffle_4bytes(char *heap, LONGLONG length, int *status); -static int fits_shuffle_2bytes(char *heap, LONGLONG length, int *status); -static int fits_unshuffle_8bytes(char *heap, LONGLONG length, int *status); -static int fits_unshuffle_4bytes(char *heap, LONGLONG length, int *status); -static int fits_unshuffle_2bytes(char *heap, LONGLONG length, int *status); - -static int fits_int_to_longlong_inplace(int *intarray, long length, int *status); -static int fits_short_to_int_inplace(short *intarray, long length, int shift, int *status); -static int fits_ushort_to_int_inplace(unsigned short *intarray, long length, int shift, int *status); -static int fits_sbyte_to_int_inplace(signed char *intarray, long length, int *status); -static int fits_ubyte_to_int_inplace(unsigned char *intarray, long length, int *status); - -static int fits_calc_tile_rows(long *tlpixel, long *tfpixel, int ndim, long *trowsize, long *ntrows, int *status); - -/* only used for diagnoitic purposes */ -/* int fits_get_case(int *c1, int*c2, int*c3); */ -/*---------------------------------------------------------------------------*/ int fits_init_randoms(void) { /* initialize an array of random numbers */ @@ -182,7 +31,7 @@ int fits_init_randoms(void) { double temp, seed; FFLOCK; - + if (fits_rand_value) { FFUNLOCK; return(0); /* array is already initialized */ @@ -196,14 +45,14 @@ int fits_init_randoms(void) { FFUNLOCK; return(MEMORY_ALLOCATION); } - + /* We need a portable algorithm that anyone can use to generate this exact same sequence of random number. The C 'rand' function is not suitable because it is not available to Fortran or Java programmers. - Instead, use a well known simple algorithm published here: + Instead, use a well known simple algorithm published here: "Random number generators: good ones are hard to find", Communications of the ACM, - Volume 31 , Issue 10 (October 1988) Pages: 1192 - 1201 - */ + Volume 31 , Issue 10 (October 1988) Pages: 1192 - 1201 + */ /* initialize the random numbers */ seed = 1; @@ -215,10 +64,10 @@ int fits_init_randoms(void) { FFUNLOCK; - /* - IMPORTANT NOTE: the 10000th seed value must have the value 1043618065 if the + /* + IMPORTANT NOTE: the 10000th seed value must have the value 1043618065 if the algorithm has been implemented correctly */ - + if ( (int) seed != 1043618065) { ffpmsg("fits_init_randoms generated incorrect random number sequence"); return(1); @@ -226,9730 +75,487 @@ int fits_init_randoms(void) { return(0); } } -/*--------------------------------------------------------------------------*/ -void bz_internal_error(int errcode) -{ - /* external function declared by the bzip2 code in bzlib_private.h */ - ffpmsg("bzip2 returned an internal error"); - ffpmsg("This should never happen"); - return; -} -/*--------------------------------------------------------------------------*/ -int fits_set_compression_type(fitsfile *fptr, /* I - FITS file pointer */ - int ctype, /* image compression type code; */ - /* allowed values: RICE_1, GZIP_1, GZIP_2, PLIO_1, */ - /* HCOMPRESS_1, BZIP2_1, and NOCOMPRESS */ - int *status) /* IO - error status */ -{ -/* - This routine specifies the image compression algorithm that should be - used when writing a FITS image. The image is divided into tiles, and - each tile is compressed and stored in a row of at variable length binary - table column. -*/ - if (ctype != RICE_1 && - ctype != GZIP_1 && - ctype != GZIP_2 && - ctype != PLIO_1 && - ctype != HCOMPRESS_1 && - ctype != BZIP2_1 && - ctype != NOCOMPRESS && - ctype != 0) - { - ffpmsg("unknown compression algorithm (fits_set_compression_type)"); - *status = DATA_COMPRESSION_ERR; - } else { - (fptr->Fptr)->request_compress_type = ctype; - } - return(*status); -} /*--------------------------------------------------------------------------*/ -int fits_set_tile_dim(fitsfile *fptr, /* I - FITS file pointer */ - int ndim, /* number of dimensions in the compressed image */ - long *dims, /* size of image compression tile in each dimension */ - /* default tile size = (NAXIS1, 1, 1, ...) */ - int *status) /* IO - error status */ -{ +int unquantize_i1r4(long row, /* tile number = row number in table */ + unsigned char *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - dithering method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ + float nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + float *output, /* O - array of converted pixels */ + int *status) /* IO - error status */ /* - This routine specifies the size (dimension) of the image - compression tiles that should be used when writing a FITS - image. The image is divided into tiles, and each tile is compressed - and stored in a row of at variable length binary table column. + Unquantize byte values into the scaled floating point values */ - int ii; +{ + long ii; + int nextrand, iseed; - if (ndim < 0 || ndim > MAX_COMPRESS_DIM) - { - *status = BAD_DIMEN; - ffpmsg("illegal number of tile dimensions (fits_set_tile_dim)"); - return(*status); - } + if (!fits_rand_value) + if (fits_init_randoms()) return(MEMORY_ALLOCATION); - for (ii = 0; ii < ndim; ii++) - { - (fptr->Fptr)->request_tilesize[ii] = dims[ii]; - } + /* initialize the index to the next random number in the list */ + iseed = (int) ((row - 1) % N_RANDOM); + nextrand = (int) (fits_rand_value[iseed] * 500); - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_set_quantize_level(fitsfile *fptr, /* I - FITS file pointer */ - float qlevel, /* floating point quantization level */ - int *status) /* IO - error status */ -{ + if (nullcheck == 0) /* no null checking required */ + { + for (ii = 0; ii < ntodo; ii++) + { /* - This routine specifies the value of the quantization level, q, that - should be used when compressing floating point images. The image is - divided into tiles, and each tile is compressed and stored in a row - of at variable length binary table column. + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else */ - if (qlevel == 0.) - { - /* this means don't quantize the floating point values. Instead, */ - /* the floating point values will be losslessly compressed */ - (fptr->Fptr)->request_quantize_level = NO_QUANTIZE; - } else { + output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - (fptr->Fptr)->request_quantize_level = qlevel; + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_set_quantize_method(fitsfile *fptr, /* I - FITS file pointer */ - int method, /* quantization method */ - int *status) /* IO - error status */ -{ + else /* must check for null values */ + { + for (ii = 0; ii < ntodo; ii++) + { + if (input[ii] == tnull) + { + *anynull = 1; + if (nullcheck == 1) + output[ii] = nullval; + else + nullarray[ii] = 1; + } + else + { /* - This routine specifies what type of dithering (randomization) should - be performed when quantizing floating point images to integer prior to - compression. A value of -1 means do no dithering. A value of 0 means - use the default SUBTRACTIVE_DITHER_1 (which is equivalent to dither = 1). - A value of 2 means use SUBTRACTIVE_DITHER_2. + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else */ + output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); + } - if (method < -1 || method > 2) - { - ffpmsg("illegal dithering value (fits_set_quantize_method)"); - *status = DATA_COMPRESSION_ERR; - } else { - - if (method == 0) method = 1; - (fptr->Fptr)->request_quantize_method = method; + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } } return(*status); } /*--------------------------------------------------------------------------*/ -int fits_set_quantize_dither(fitsfile *fptr, /* I - FITS file pointer */ - int dither, /* dither type */ - int *status) /* IO - error status */ -{ +int unquantize_i2r4(long row, /* seed for random values */ + short *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - dithering method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + short tnull, /* I - value of FITS TNULLn keyword if any */ + float nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + float *output, /* O - array of converted pixels */ + int *status) /* IO - error status */ /* - the name of this routine has changed. This is kept here only for backwards - compatibility for any software that may be calling the old routine. + Unquantize short integer values into the scaled floating point values */ - - fits_set_quantize_method(fptr, dither, status); - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_set_dither_seed(fitsfile *fptr, /* I - FITS file pointer */ - int seed, /* random dithering seed value (1 to 10000) */ - int *status) /* IO - error status */ { -/* - This routine specifies the value of the offset that should be applied when - calculating the random dithering when quantizing floating point iamges. - A random offset should be applied to each image to avoid quantization - effects when taking the difference of 2 images, or co-adding a set of - images. Without this random offset, the corresponding pixel in every image - will have exactly the same dithering. - - offset = 0 means use the default random dithering based on system time - offset = negative means randomly chose dithering based on 1st tile checksum - offset = [1 - 10000] means use that particular dithering pattern + long ii; + int nextrand, iseed; -*/ - /* if positive, ensure that the value is in the range 1 to 10000 */ - if (seed > 10000) { - ffpmsg("illegal dithering seed value (fits_set_dither_seed)"); - *status = DATA_COMPRESSION_ERR; - } else { - (fptr->Fptr)->request_dither_seed = seed; - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_set_dither_offset(fitsfile *fptr, /* I - FITS file pointer */ - int offset, /* random dithering offset value (1 to 10000) */ - int *status) /* IO - error status */ -{ + if (!fits_rand_value) + if (fits_init_randoms()) return(MEMORY_ALLOCATION); + + /* initialize the index to the next random number in the list */ + iseed = (int) ((row - 1) % N_RANDOM); + nextrand = (int) (fits_rand_value[iseed] * 500); + + if (nullcheck == 0) /* no null checking required */ + { + for (ii = 0; ii < ntodo; ii++) + { /* - The name of this routine has changed. This is kept just for - backwards compatibility with any software that calls the old name + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else */ + output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - fits_set_dither_seed(fptr, offset, status); - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_set_noise_bits(fitsfile *fptr, /* I - FITS file pointer */ - int noisebits, /* noise_bits parameter value */ - /* (default = 4) */ - int *status) /* IO - error status */ -{ + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } + } + else /* must check for null values */ + { + for (ii = 0; ii < ntodo; ii++) + { + if (input[ii] == tnull) + { + *anynull = 1; + if (nullcheck == 1) + output[ii] = nullval; + else + nullarray[ii] = 1; + } + else + { /* - ******************************************************************** - ******************************************************************** - THIS ROUTINE IS PROVIDED ONLY FOR BACKWARDS COMPATIBILITY; - ALL NEW SOFTWARE SHOULD CALL fits_set_quantize_level INSTEAD - ******************************************************************** - ******************************************************************** - - This routine specifies the value of the noice_bits parameter that - should be used when compressing floating point images. The image is - divided into tiles, and each tile is compressed and stored in a row - of at variable length binary table column. - - Feb 2008: the "noisebits" parameter has been replaced with the more - general "quantize level" parameter. + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else */ - float qlevel; + output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); + } - if (noisebits < 1 || noisebits > 16) - { - *status = DATA_COMPRESSION_ERR; - ffpmsg("illegal number of noise bits (fits_set_noise_bits)"); - return(*status); + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } } - qlevel = (float) pow (2., (double)noisebits); - fits_set_quantize_level(fptr, qlevel, status); - return(*status); } /*--------------------------------------------------------------------------*/ -int fits_set_hcomp_scale(fitsfile *fptr, /* I - FITS file pointer */ - float scale, /* hcompress scale parameter value */ - /* (default = 0.) */ - int *status) /* IO - error status */ -{ -/* - This routine specifies the value of the hcompress scale parameter. -*/ - (fptr->Fptr)->request_hcomp_scale = scale; - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_set_hcomp_smooth(fitsfile *fptr, /* I - FITS file pointer */ - int smooth, /* hcompress smooth parameter value */ - /* if scale > 1 and smooth != 0, then */ - /* the image will be smoothed when it is */ - /* decompressed to remove some of the */ - /* 'blockiness' in the image produced */ - /* by the lossy compression */ - int *status) /* IO - error status */ -{ -/* - This routine specifies the value of the hcompress scale parameter. -*/ - - (fptr->Fptr)->request_hcomp_smooth = smooth; - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_set_lossy_int(fitsfile *fptr, /* I - FITS file pointer */ - int lossy_int, /* I - True (!= 0) or False (0) */ - int *status) /* IO - error status */ -{ -/* - This routine specifies whether images with integer pixel values should - quantized and compressed the same way float images are compressed. - The default is to not do this, and instead apply a lossless compression - algorithm to integer images. -*/ - - (fptr->Fptr)->request_lossy_int_compress = lossy_int; - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_set_huge_hdu(fitsfile *fptr, /* I - FITS file pointer */ - int huge, /* I - True (!= 0) or False (0) */ - int *status) /* IO - error status */ -{ -/* - This routine specifies whether the HDU that is being compressed is so large - (i.e., > 4 GB) that the 'Q' type variable length array columns should be used - rather than the normal 'P' type. The allows the heap pointers to be stored - as 64-bit quantities, rather than just 32-bits. -*/ - - (fptr->Fptr)->request_huge_hdu = huge; - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_get_compression_type(fitsfile *fptr, /* I - FITS file pointer */ - int *ctype, /* image compression type code; */ - /* allowed values: */ - /* RICE_1, GZIP_1, GZIP_2, PLIO_1, HCOMPRESS_1, BZIP2_1 */ - int *status) /* IO - error status */ -{ -/* - This routine returns the image compression algorithm that should be - used when writing a FITS image. The image is divided into tiles, and - each tile is compressed and stored in a row of at variable length binary - table column. -*/ - *ctype = (fptr->Fptr)->request_compress_type; - - if (*ctype != RICE_1 && - *ctype != GZIP_1 && - *ctype != GZIP_2 && - *ctype != PLIO_1 && - *ctype != HCOMPRESS_1 && - *ctype != BZIP2_1 && - *ctype != NOCOMPRESS && - *ctype != 0 ) - - { - ffpmsg("unknown compression algorithm (fits_get_compression_type)"); - *status = DATA_COMPRESSION_ERR; - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_get_tile_dim(fitsfile *fptr, /* I - FITS file pointer */ - int ndim, /* number of dimensions in the compressed image */ - long *dims, /* size of image compression tile in each dimension */ - /* default tile size = (NAXIS1, 1, 1, ...) */ - int *status) /* IO - error status */ -{ -/* - This routine returns the size (dimension) of the image - compression tiles that should be used when writing a FITS - image. The image is divided into tiles, and each tile is compressed - and stored in a row of at variable length binary table column. -*/ - int ii; - - if (ndim < 0 || ndim > MAX_COMPRESS_DIM) - { - *status = BAD_DIMEN; - ffpmsg("illegal number of tile dimensions (fits_get_tile_dim)"); - return(*status); - } - - for (ii = 0; ii < ndim; ii++) - { - dims[ii] = (fptr->Fptr)->request_tilesize[ii]; - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_unset_compression_param( - fitsfile *fptr, - int *status) -{ - int ii; - - (fptr->Fptr)->compress_type = 0; - (fptr->Fptr)->quantize_level = 0; - (fptr->Fptr)->quantize_method = 0; - (fptr->Fptr)->dither_seed = 0; - (fptr->Fptr)->hcomp_scale = 0; - - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - (fptr->Fptr)->tilesize[ii] = 0; - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_unset_compression_request( - fitsfile *fptr, - int *status) -{ - int ii; - - (fptr->Fptr)->request_compress_type = 0; - (fptr->Fptr)->request_quantize_level = 0; - (fptr->Fptr)->request_quantize_method = 0; - (fptr->Fptr)->request_dither_seed = 0; - (fptr->Fptr)->request_hcomp_scale = 0; - (fptr->Fptr)->request_lossy_int_compress = 0; - (fptr->Fptr)->request_huge_hdu = 0; - - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - (fptr->Fptr)->request_tilesize[ii] = 0; - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_set_compression_pref( - fitsfile *infptr, - fitsfile *outfptr, - int *status) -{ -/* - Set the preference for various compression options, based - on keywords in the input file that - provide guidance about how the HDU should be compressed when written - to the output file. -*/ - - int ii, naxis, nkeys, comptype; - int ivalue; - long tiledim[6]= {1,1,1,1,1,1}; - char card[FLEN_CARD], value[FLEN_VALUE]; - double qvalue; - float hscale; - LONGLONG datastart, dataend; - if (*status > 0) - return(*status); - - /* check the size of the HDU that is to be compressed */ - fits_get_hduaddrll(infptr, NULL, &datastart, &dataend, status); - if ( (LONGLONG)(dataend - datastart) > UINT32_MAX) { - /* use 64-bit '1Q' variable length columns instead of '1P' columns */ - /* for large files, in case the heap size becomes larger than 2**32 bytes*/ - fits_set_huge_hdu(outfptr, 1, status); - } - - fits_get_hdrspace(infptr, &nkeys, NULL, status); - - /* look for a image compression directive keywords (begin with 'FZ') */ - for (ii = 2; ii <= nkeys; ii++) { - - fits_read_record(infptr, ii, card, status); - - if (!strncmp(card, "FZ", 2) ){ - - /* get the keyword value string */ - fits_parse_value(card, value, NULL, status); - - if (!strncmp(card+2, "ALGOR", 5) ) { - - /* set the desired compression algorithm */ - /* allowed values: RICE_1, GZIP_1, GZIP_2, PLIO_1, */ - /* HCOMPRESS_1, BZIP2_1, and NOCOMPRESS */ - - if (!fits_strncasecmp(value, "'RICE_1", 7) ) { - comptype = RICE_1; - } else if (!fits_strncasecmp(value, "'GZIP_1", 7) ) { - comptype = GZIP_1; - } else if (!fits_strncasecmp(value, "'GZIP_2", 7) ) { - comptype = GZIP_2; - } else if (!fits_strncasecmp(value, "'PLIO_1", 7) ) { - comptype = PLIO_1; - } else if (!fits_strncasecmp(value, "'HCOMPRESS_1", 12) ) { - comptype = HCOMPRESS_1; - } else if (!fits_strncasecmp(value, "'NONE", 5) ) { - comptype = NOCOMPRESS; - } else { - ffpmsg("Unknown FZALGOR keyword compression algorithm:"); - ffpmsg(value); - return(*status = DATA_COMPRESSION_ERR); - } - - fits_set_compression_type (outfptr, comptype, status); - - } else if (!strncmp(card+2, "TILE ", 6) ) { - - if (!fits_strncasecmp(value, "'row", 4) ) { - tiledim[0] = -1; - } else if (!fits_strncasecmp(value, "'whole", 6) ) { - tiledim[0] = -1; - tiledim[1] = -1; - tiledim[2] = -1; - } else { - ffdtdm(infptr, value, 0,6, &naxis, tiledim, status); - } - - /* set the desired tile size */ - fits_set_tile_dim (outfptr, 6, tiledim, status); - - } else if (!strncmp(card+2, "QVALUE", 6) ) { - - /* set the desired Q quantization value */ - qvalue = atof(value); - fits_set_quantize_level (outfptr, (float) qvalue, status); - - } else if (!strncmp(card+2, "QMETHD", 6) ) { - - if (!fits_strncasecmp(value, "'no_dither", 10) ) { - ivalue = -1; /* just quantize, with no dithering */ - } else if (!fits_strncasecmp(value, "'subtractive_dither_1", 21) ) { - ivalue = SUBTRACTIVE_DITHER_1; /* use subtractive dithering */ - } else if (!fits_strncasecmp(value, "'subtractive_dither_2", 21) ) { - ivalue = SUBTRACTIVE_DITHER_2; /* dither, except preserve zero-valued pixels */ - } else { - ffpmsg("Unknown value for FZQUANT keyword: (set_compression_pref)"); - ffpmsg(value); - return(*status = DATA_COMPRESSION_ERR); - } - - fits_set_quantize_method(outfptr, ivalue, status); - - } else if (!strncmp(card+2, "DTHRSD", 6) ) { - - if (!fits_strncasecmp(value, "'checksum", 9) ) { - ivalue = -1; /* use checksum of first tile */ - } else if (!fits_strncasecmp(value, "'clock", 6) ) { - ivalue = 0; /* set dithering seed based on system clock */ - } else { /* read integer value */ - if (*value == '\'') - ivalue = (int) atol(value+1); /* allow for leading quote character */ - else - ivalue = (int) atol(value); - - if (ivalue < 1 || ivalue > 10000) { - ffpmsg("Invalid value for FZDTHRSD keyword: (set_compression_pref)"); - ffpmsg(value); - return(*status = DATA_COMPRESSION_ERR); - } - } - - /* set the desired dithering */ - fits_set_dither_seed(outfptr, ivalue, status); - - } else if (!strncmp(card+2, "I2F", 3) ) { - - /* set whether to convert integers to float then use lossy compression */ - if (!fits_strcasecmp(value, "t") ) { - fits_set_lossy_int (outfptr, 1, status); - } else if (!fits_strcasecmp(value, "f") ) { - fits_set_lossy_int (outfptr, 0, status); - } else { - ffpmsg("Unknown value for FZI2F keyword: (set_compression_pref)"); - ffpmsg(value); - return(*status = DATA_COMPRESSION_ERR); - } - - } else if (!strncmp(card+2, "HSCALE ", 6) ) { - - /* set the desired Hcompress scale value */ - hscale = (float) atof(value); - fits_set_hcomp_scale (outfptr, hscale, status); - } - } - } - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_get_noise_bits(fitsfile *fptr, /* I - FITS file pointer */ - int *noisebits, /* noise_bits parameter value */ - /* (default = 4) */ - int *status) /* IO - error status */ -{ -/* - ******************************************************************** - ******************************************************************** - THIS ROUTINE IS PROVIDED ONLY FOR BACKWARDS COMPATIBILITY; - ALL NEW SOFTWARE SHOULD CALL fits_set_quantize_level INSTEAD - ******************************************************************** - ******************************************************************** - - - This routine returns the value of the noice_bits parameter that - should be used when compressing floating point images. The image is - divided into tiles, and each tile is compressed and stored in a row - of at variable length binary table column. - - Feb 2008: code changed to use the more general "quantize level" parameter - rather than the "noise bits" parameter. If quantize level is greater than - zero, then the previous noisebits parameter is approximately given by - - noise bits = natural logarithm (quantize level) / natural log (2) - - This result is rounded to the nearest integer. -*/ - double qlevel; - - qlevel = (fptr->Fptr)->request_quantize_level; - - if (qlevel > 0. && qlevel < 65537. ) - *noisebits = (int) ((log(qlevel) / log(2.0)) + 0.5); - else - *noisebits = 0; - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_get_quantize_level(fitsfile *fptr, /* I - FITS file pointer */ - float *qlevel, /* quantize level parameter value */ - int *status) /* IO - error status */ -{ -/* - This routine returns the value of the noice_bits parameter that - should be used when compressing floating point images. The image is - divided into tiles, and each tile is compressed and stored in a row - of at variable length binary table column. -*/ - - if ((fptr->Fptr)->request_quantize_level == NO_QUANTIZE) { - *qlevel = 0; - } else { - *qlevel = (fptr->Fptr)->request_quantize_level; - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_get_dither_seed(fitsfile *fptr, /* I - FITS file pointer */ - int *offset, /* dithering offset parameter value */ - int *status) /* IO - error status */ -{ -/* - This routine returns the value of the dithering offset parameter that - is used when compressing floating point images. The image is - divided into tiles, and each tile is compressed and stored in a row - of at variable length binary table column. -*/ - - *offset = (fptr->Fptr)->request_dither_seed; - return(*status); -}/*--------------------------------------------------------------------------*/ -int fits_get_hcomp_scale(fitsfile *fptr, /* I - FITS file pointer */ - float *scale, /* Hcompress scale parameter value */ - int *status) /* IO - error status */ - -{ -/* - This routine returns the value of the noice_bits parameter that - should be used when compressing floating point images. The image is - divided into tiles, and each tile is compressed and stored in a row - of at variable length binary table column. -*/ - - *scale = (fptr->Fptr)->request_hcomp_scale; - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_get_hcomp_smooth(fitsfile *fptr, /* I - FITS file pointer */ - int *smooth, /* Hcompress smooth parameter value */ - int *status) /* IO - error status */ - -{ - *smooth = (fptr->Fptr)->request_hcomp_smooth; - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_img_compress(fitsfile *infptr, /* pointer to image to be compressed */ - fitsfile *outfptr, /* empty HDU for output compressed image */ - int *status) /* IO - error status */ - +int unquantize_i4r4(long row, /* tile number = row number in table */ + int *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - dithering method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + int tnull, /* I - value of FITS TNULLn keyword if any */ + float nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + float *output, /* O - array of converted pixels */ + int *status) /* IO - error status */ /* - This routine initializes the output table, copies all the keywords, - and loops through the input image, compressing the data and - writing the compressed tiles to the output table. - - This is a high level routine that is called by the fpack and funpack - FITS compression utilities. + Unquantize int integer values into the scaled floating point values */ { - int bitpix, naxis; - long naxes[MAX_COMPRESS_DIM]; -/* int c1, c2, c3; */ - - if (*status > 0) - return(*status); - - - /* get datatype and size of input image */ - if (fits_get_img_param(infptr, MAX_COMPRESS_DIM, &bitpix, - &naxis, naxes, status) > 0) - return(*status); + long ii; + int nextrand, iseed; - if (naxis < 1 || naxis > MAX_COMPRESS_DIM) - { - ffpmsg("Image cannot be compressed: NAXIS out of range"); - return(*status = BAD_NAXIS); - } + if (fits_rand_value == 0) + if (fits_init_randoms()) return(MEMORY_ALLOCATION); - /* create a new empty HDU in the output file now, before setting the */ - /* compression preferences. This HDU will become a binary table that */ - /* contains the compressed image. If necessary, create a dummy primary */ - /* array, which much precede the binary table extension. */ - - ffcrhd(outfptr, status); /* this does nothing if the output file is empty */ + /* initialize the index to the next random number in the list */ + iseed = (int) ((row - 1) % N_RANDOM); + nextrand = (int) (fits_rand_value[iseed] * 500); - if ((outfptr->Fptr)->curhdu == 0) /* have to create dummy primary array */ + if (nullcheck == 0) /* no null checking required */ { - ffcrim(outfptr, 16, 0, NULL, status); - ffcrhd(outfptr, status); - } else { - /* unset any compress parameter preferences that may have been - set when closing the previous HDU in the output file */ - fits_unset_compression_param(outfptr, status); - } - - /* set any compress parameter preferences as given in the input file */ - fits_set_compression_pref(infptr, outfptr, status); - - /* special case: the quantization level is not given by a keyword in */ - /* the HDU header, so we have to explicitly copy the requested value */ - /* to the actual value */ -/* do this in imcomp_get_compressed_image_par, instead - if ( (outfptr->Fptr)->request_quantize_level != 0.) - (outfptr->Fptr)->quantize_level = (outfptr->Fptr)->request_quantize_level; -*/ - /* if requested, treat integer images same as a float image. */ - /* Then the pixels will be quantized (lossy algorithm) to achieve */ - /* higher amounts of compression than with lossless algorithms */ - - if ( (outfptr->Fptr)->request_lossy_int_compress != 0 && bitpix > 0) - bitpix = FLOAT_IMG; /* compress integer images as if float */ - - /* initialize output table */ - if (imcomp_init_table(outfptr, bitpix, naxis, naxes, 0, status) > 0) - return (*status); - - /* Copy the image header keywords to the table header. */ - if (imcomp_copy_img2comp(infptr, outfptr, status) > 0) - return (*status); - - /* turn off any intensity scaling (defined by BSCALE and BZERO */ - /* keywords) so that unscaled values will be read by CFITSIO */ - /* (except if quantizing an int image, same as a float image) */ - if ( (outfptr->Fptr)->request_lossy_int_compress == 0 && bitpix > 0) - ffpscl(infptr, 1.0, 0.0, status); - - /* force a rescan of the output file keywords, so that */ - /* the compression parameters will be copied to the internal */ - /* fitsfile structure used by CFITSIO */ - ffrdef(outfptr, status); - - /* turn off any intensity scaling (defined by BSCALE and BZERO */ - /* keywords) so that unscaled values will be written by CFITSIO */ - /* (except if quantizing an int image, same as a float image) */ - if ( (outfptr->Fptr)->request_lossy_int_compress == 0 && bitpix > 0) - ffpscl(outfptr, 1.0, 0.0, status); - - /* Read each image tile, compress, and write to a table row. */ - imcomp_compress_image (infptr, outfptr, status); - - /* force another rescan of the output file keywords, to */ - /* update PCOUNT and TFORMn = '1PB(iii)' keyword values. */ - ffrdef(outfptr, status); - - /* unset any previously set compress parameter preferences */ - fits_unset_compression_request(outfptr, status); - -/* - fits_get_case(&c1, &c2, &c3); - printf("c1, c2, c3 = %d, %d, %d\n", c1, c2, c3); -*/ - - return (*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_init_table(fitsfile *outfptr, - int inbitpix, - int naxis, - long *naxes, - int writebitpix, /* write the ZBITPIX, ZNAXIS, and ZNAXES keyword? */ - int *status) -/* - create a BINTABLE extension for the output compressed image. -*/ -{ - char keyname[FLEN_KEYWORD], zcmptype[12]; - int ii, remain, ndiv, addToDim, ncols, bitpix; - long nrows; - char *ttype[] = {"COMPRESSED_DATA", "ZSCALE", "ZZERO"}; - char *tform[3]; - char tf0[4], tf1[4], tf2[4]; - char *tunit[] = {"\0", "\0", "\0" }; - char comm[FLEN_COMMENT]; - long actual_tilesize[MAX_COMPRESS_DIM]; /* Actual size to use for tiles */ - int is_primary=0; /* Is this attempting to write to the primary? */ - int nQualifyDims=0; /* For Hcompress, number of image dimensions with required pixels. */ - int noHigherDims=1; /* Set to true if all tile dims other than x are size 1. */ - int firstDim=-1, secondDim=-1; /* Indices of first and second tiles dimensions - with width > 1 */ - - if (*status > 0) - return(*status); - - /* check for special case of losslessly compressing floating point */ - /* images. Only compression algorithm that supports this is GZIP */ - if ( (inbitpix < 0) && ((outfptr->Fptr)->request_quantize_level == NO_QUANTIZE) ) { - if (((outfptr->Fptr)->request_compress_type != GZIP_1) && - ((outfptr->Fptr)->request_compress_type != GZIP_2)) { - ffpmsg("Lossless compression of floating point images must use GZIP (imcomp_init_table)"); - return(*status = DATA_COMPRESSION_ERR); - } - } - - /* set default compression parameter values, if undefined */ - - if ( (outfptr->Fptr)->request_compress_type == 0) { - /* use RICE_1 by default */ - (outfptr->Fptr)->request_compress_type = RICE_1; - } - - if (inbitpix < 0 && (outfptr->Fptr)->request_quantize_level != NO_QUANTIZE) { - /* set defaults for quantizing floating point images */ - if ( (outfptr->Fptr)->request_quantize_method == 0) { - /* set default dithering method */ - (outfptr->Fptr)->request_quantize_method = SUBTRACTIVE_DITHER_1; - } - - if ( (outfptr->Fptr)->request_quantize_level == 0) { - if ((outfptr->Fptr)->request_quantize_method == NO_DITHER) { - /* must use finer quantization if no dithering is done */ - (outfptr->Fptr)->request_quantize_level = 16; - } else { - (outfptr->Fptr)->request_quantize_level = 4; - } - } - } - - /* special case: the quantization level is not given by a keyword in */ - /* the HDU header, so we have to explicitly copy the requested value */ - /* to the actual value */ -/* do this in imcomp_get_compressed_image_par, instead - if ( (outfptr->Fptr)->request_quantize_level != 0.) - (outfptr->Fptr)->quantize_level = (outfptr->Fptr)->request_quantize_level; -*/ - /* test for the 2 special cases that represent unsigned integers */ - if (inbitpix == USHORT_IMG) - bitpix = SHORT_IMG; - else if (inbitpix == ULONG_IMG) - bitpix = LONG_IMG; - else if (inbitpix == SBYTE_IMG) - bitpix = BYTE_IMG; - else - bitpix = inbitpix; - - /* reset default tile dimensions too if required */ - memcpy(actual_tilesize, outfptr->Fptr->request_tilesize, MAX_COMPRESS_DIM * sizeof(long)); - - if ((outfptr->Fptr)->request_compress_type == HCOMPRESS_1) { - - /* Tiles must ultimately have 2 (and only 2) dimensions, each with - at least 4 pixels. First catch the case where the image - itself won't allow this. */ - if (naxis < 2 ) { - ffpmsg("Hcompress cannot be used with 1-dimensional images (imcomp_init_table)"); - return(*status = DATA_COMPRESSION_ERR); - } - for (ii=0; ii= 4) - ++nQualifyDims; - } - if (nQualifyDims < 2) - { - ffpmsg("Hcompress minimum image dimension is 4 pixels (imcomp_init_table)"); - return(*status = DATA_COMPRESSION_ERR); - } - - /* Handle 2 special cases for backwards compatibility. - 1) If both X and Y tile dims are set to full size, ignore - any other requested dimensions and just set their sizes to 1. - 2) If X is full size and all the rest are size 1, attempt to - find a reasonable size for Y. All other 1-D tile specifications - will be rejected. */ - for (ii=1; ii 3) { - actual_tilesize[1] = 16; - } else if (naxes[1] % 24 == 0 || naxes[1] % 24 > 3) { - actual_tilesize[1] = 24; - } else if (naxes[1] % 20 == 0 || naxes[1] % 20 > 3) { - actual_tilesize[1] = 20; - } else if (naxes[1] % 30 == 0 || naxes[1] % 30 > 3) { - actual_tilesize[1] = 30; - } else if (naxes[1] % 28 == 0 || naxes[1] % 28 > 3) { - actual_tilesize[1] = 28; - } else if (naxes[1] % 26 == 0 || naxes[1] % 26 > 3) { - actual_tilesize[1] = 26; - } else if (naxes[1] % 22 == 0 || naxes[1] % 22 > 3) { - actual_tilesize[1] = 22; - } else if (naxes[1] % 18 == 0 || naxes[1] % 18 > 3) { - actual_tilesize[1] = 18; - } else if (naxes[1] % 14 == 0 || naxes[1] % 14 > 3) { - actual_tilesize[1] = 14; - } else { - actual_tilesize[1] = 17; - } - } - } else { - if (actual_tilesize[0] <= 0) - actual_tilesize[0] = naxes[0]; - for (ii=1; ii 1) - { - if (firstDim < 0) - firstDim = ii; - else if (secondDim < 0) - secondDim = ii; - else - { - ffpmsg("Hcompress tiles can only have 2 dimensions (imcomp_init_table)"); - return(*status = DATA_COMPRESSION_ERR); - } - } - } - if (firstDim < 0 || secondDim < 0) - { - ffpmsg("Hcompress tiles must have 2 dimensions (imcomp_init_table)"); - return(*status = DATA_COMPRESSION_ERR); - } - - if (actual_tilesize[firstDim] < 4 || actual_tilesize[secondDim] < 4) - { - ffpmsg("Hcompress minimum tile dimension is 4 pixels (imcomp_init_table)"); - return (*status = DATA_COMPRESSION_ERR); - } - - /* check if requested tile size causes the last tile to to have less than 4 pixels */ - remain = naxes[firstDim] % (actual_tilesize[firstDim]); /* 1st dimension */ - if (remain > 0 && remain < 4) { - ndiv = naxes[firstDim]/actual_tilesize[firstDim]; /* integer truncation is intentional */ - addToDim = ceil((double)remain/ndiv); - (actual_tilesize[firstDim]) += addToDim; /* increase tile size */ - - remain = naxes[firstDim] % (actual_tilesize[firstDim]); - if (remain > 0 && remain < 4) { - ffpmsg("Last tile along 1st dimension has less than 4 pixels (imcomp_init_table)"); - return(*status = DATA_COMPRESSION_ERR); - } - } - - remain = naxes[secondDim] % (actual_tilesize[secondDim]); /* 2nd dimension */ - if (remain > 0 && remain < 4) { - ndiv = naxes[secondDim]/actual_tilesize[secondDim]; /* integer truncation is intentional */ - addToDim = ceil((double)remain/ndiv); - (actual_tilesize[secondDim]) += addToDim; /* increase tile size */ - - remain = naxes[secondDim] % (actual_tilesize[secondDim]); - if (remain > 0 && remain < 4) { - ffpmsg("Last tile along 2nd dimension has less than 4 pixels (imcomp_init_table)"); - return(*status = DATA_COMPRESSION_ERR); - } - } - - } /* end, if HCOMPRESS_1 */ - - for (ii = 0; ii < naxis; ii++) { - if (ii == 0) { /* first axis is different */ - if (actual_tilesize[ii] <= 0) { - actual_tilesize[ii] = naxes[ii]; - } - } else { - if (actual_tilesize[ii] < 0) { - actual_tilesize[ii] = naxes[ii]; /* negative value maean use whole length */ - } else if (actual_tilesize[ii] == 0) { - actual_tilesize[ii] = 1; /* zero value means use default value = 1 */ - } - } - } - - /* ---- set up array of TFORM strings -------------------------------*/ - if ( (outfptr->Fptr)->request_huge_hdu != 0) { - strcpy(tf0, "1QB"); - } else { - strcpy(tf0, "1PB"); - } - strcpy(tf1, "1D"); - strcpy(tf2, "1D"); - - tform[0] = tf0; - tform[1] = tf1; - tform[2] = tf2; - - /* calculate number of rows in output table */ - nrows = 1; - for (ii = 0; ii < naxis; ii++) - { - nrows = nrows * ((naxes[ii] - 1)/ (actual_tilesize[ii]) + 1); - } - - /* determine the default number of columns in the output table */ - if (bitpix < 0 && (outfptr->Fptr)->request_quantize_level != NO_QUANTIZE) - ncols = 3; /* quantized and scaled floating point image */ - else - ncols = 1; /* default table has just one 'COMPRESSED_DATA' column */ - - if ((outfptr->Fptr)->request_compress_type == RICE_1) - { - strcpy(zcmptype, "RICE_1"); - } - else if ((outfptr->Fptr)->request_compress_type == GZIP_1) - { - strcpy(zcmptype, "GZIP_1"); - } - else if ((outfptr->Fptr)->request_compress_type == GZIP_2) - { - strcpy(zcmptype, "GZIP_2"); - } - else if ((outfptr->Fptr)->request_compress_type == BZIP2_1) - { - strcpy(zcmptype, "BZIP2_1"); - } - else if ((outfptr->Fptr)->request_compress_type == PLIO_1) - { - strcpy(zcmptype, "PLIO_1"); - /* the PLIO compression algorithm outputs short integers, not bytes */ - if ( (outfptr->Fptr)->request_huge_hdu != 0) { - strcpy(tform[0], "1QI"); - } else { - strcpy(tform[0], "1PI"); - } - } - else if ((outfptr->Fptr)->request_compress_type == HCOMPRESS_1) - { - strcpy(zcmptype, "HCOMPRESS_1"); - } - else if ((outfptr->Fptr)->request_compress_type == NOCOMPRESS) - { - strcpy(zcmptype, "NOCOMPRESS"); - } - else - { - ffpmsg("unknown compression type (imcomp_init_table)"); - return(*status = DATA_COMPRESSION_ERR); - } - - /* If attempting to write compressed image to primary, the - call to ffcrtb will increment Fptr->curhdu to 1. Therefore - we need to test now for setting is_primary */ - is_primary = (outfptr->Fptr->curhdu == 0); - /* create the bintable extension to contain the compressed image */ - ffcrtb(outfptr, BINARY_TBL, nrows, ncols, ttype, - tform, tunit, 0, status); - - /* Add standard header keywords. */ - ffpkyl (outfptr, "ZIMAGE", 1, - "extension contains compressed image", status); - - if (writebitpix) { - /* write the keywords defining the datatype and dimensions of */ - /* the uncompressed image. If not, these keywords will be */ - /* copied later from the input uncompressed image */ - - if (is_primary) - ffpkyl (outfptr, "ZSIMPLE", 1, - "file does conform to FITS standard", status); - ffpkyj (outfptr, "ZBITPIX", bitpix, - "data type of original image", status); - ffpkyj (outfptr, "ZNAXIS", naxis, - "dimension of original image", status); - - for (ii = 0; ii < naxis; ii++) - { - snprintf (keyname, FLEN_KEYWORD,"ZNAXIS%d", ii+1); - ffpkyj (outfptr, keyname, naxes[ii], - "length of original image axis", status); - } - } - - for (ii = 0; ii < naxis; ii++) - { - snprintf (keyname, FLEN_KEYWORD,"ZTILE%d", ii+1); - ffpkyj (outfptr, keyname, actual_tilesize[ii], - "size of tiles to be compressed", status); - } + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else + output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - if (bitpix < 0) { - - if ((outfptr->Fptr)->request_quantize_level == NO_QUANTIZE) { - ffpkys(outfptr, "ZQUANTIZ", "NONE", - "Lossless compression without quantization", status); - } else { - - /* Unless dithering has been specifically turned off by setting */ - /* request_quantize_method = -1, use dithering by default */ - /* when quantizing floating point images. */ - - if ( (outfptr->Fptr)->request_quantize_method == 0) - (outfptr->Fptr)->request_quantize_method = SUBTRACTIVE_DITHER_1; - - /* HCompress must not use SUBTRACTIVE_DITHER_2. If user is requesting - this, assign SUBTRACTIVE_DITHER_1 instead. */ - if ((outfptr->Fptr)->request_quantize_method == SUBTRACTIVE_DITHER_2 - && !(strcmp(zcmptype,"HCOMPRESS_1"))) { - (outfptr->Fptr)->request_quantize_method = SUBTRACTIVE_DITHER_1; - fprintf(stderr,"Warning: CFITSIO does not allow subtractive_dither_2 when using Hcompress algorithm.\nWill use subtractive_dither_1 instead.\n"); + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } } - - if ((outfptr->Fptr)->request_quantize_method == SUBTRACTIVE_DITHER_1) { - ffpkys(outfptr, "ZQUANTIZ", "SUBTRACTIVE_DITHER_1", - "Pixel Quantization Algorithm", status); - - /* also write the associated ZDITHER0 keyword with a default value */ - /* which may get updated later. */ - ffpky(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->request_dither_seed), - "dithering offset when quantizing floats", status); - - } else if ((outfptr->Fptr)->request_quantize_method == SUBTRACTIVE_DITHER_2) { - ffpkys(outfptr, "ZQUANTIZ", "SUBTRACTIVE_DITHER_2", - "Pixel Quantization Algorithm", status); - - /* also write the associated ZDITHER0 keyword with a default value */ - /* which may get updated later. */ - ffpky(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->request_dither_seed), - "dithering offset when quantizing floats", status); - - if (!strcmp(zcmptype, "RICE_1")) { - /* when using this new dithering method, change the compression type */ - /* to an alias, so that old versions of funpack will not be able to */ - /* created a corrupted uncompressed image. */ - /* ******* can remove this cludge after about June 2015, after most old versions of fpack are gone */ - strcpy(zcmptype, "RICE_ONE"); - } - - } else if ((outfptr->Fptr)->request_quantize_method == NO_DITHER) { - ffpkys(outfptr, "ZQUANTIZ", "NO_DITHER", - "No dithering during quantization", status); - } - - } } - - ffpkys (outfptr, "ZCMPTYPE", zcmptype, - "compression algorithm", status); - - /* write any algorithm-specific keywords */ - if ((outfptr->Fptr)->request_compress_type == RICE_1) + else /* must check for null values */ { - ffpkys (outfptr, "ZNAME1", "BLOCKSIZE", - "compression block size", status); - - /* for now at least, the block size is always 32 */ - ffpkyj (outfptr, "ZVAL1", 32, - "pixels per block", status); - - ffpkys (outfptr, "ZNAME2", "BYTEPIX", - "bytes per pixel (1, 2, 4, or 8)", status); - - if (bitpix == BYTE_IMG) - ffpkyj (outfptr, "ZVAL2", 1, - "bytes per pixel (1, 2, 4, or 8)", status); - else if (bitpix == SHORT_IMG) - ffpkyj (outfptr, "ZVAL2", 2, - "bytes per pixel (1, 2, 4, or 8)", status); - else - ffpkyj (outfptr, "ZVAL2", 4, - "bytes per pixel (1, 2, 4, or 8)", status); - - } - else if ((outfptr->Fptr)->request_compress_type == HCOMPRESS_1) - { - ffpkys (outfptr, "ZNAME1", "SCALE", - "HCOMPRESS scale factor", status); - ffpkye (outfptr, "ZVAL1", (outfptr->Fptr)->request_hcomp_scale, - 7, "HCOMPRESS scale factor", status); - - ffpkys (outfptr, "ZNAME2", "SMOOTH", - "HCOMPRESS smooth option", status); - ffpkyj (outfptr, "ZVAL2", (long) (outfptr->Fptr)->request_hcomp_smooth, - "HCOMPRESS smooth option", status); - } - - /* Write the BSCALE and BZERO keywords, if an unsigned integer image */ - if (inbitpix == USHORT_IMG) - { - strcpy(comm, "offset data range to that of unsigned short"); - ffpkyg(outfptr, "BZERO", 32768., 0, comm, status); - strcpy(comm, "default scaling factor"); - ffpkyg(outfptr, "BSCALE", 1.0, 0, comm, status); - } - else if (inbitpix == SBYTE_IMG) - { - strcpy(comm, "offset data range to that of signed byte"); - ffpkyg(outfptr, "BZERO", -128., 0, comm, status); - strcpy(comm, "default scaling factor"); - ffpkyg(outfptr, "BSCALE", 1.0, 0, comm, status); - } - else if (inbitpix == ULONG_IMG) - { - strcpy(comm, "offset data range to that of unsigned long"); - ffpkyg(outfptr, "BZERO", 2147483648., 0, comm, status); - strcpy(comm, "default scaling factor"); - ffpkyg(outfptr, "BSCALE", 1.0, 0, comm, status); - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_calc_max_elem (int comptype, int nx, int zbitpix, int blocksize) - -/* This function returns the maximum number of bytes in a compressed - image line. - - nx = maximum number of pixels in a tile - blocksize is only relevant for RICE compression -*/ -{ - if (comptype == RICE_1) - { - if (zbitpix == 16) - return (sizeof(short) * nx + nx / blocksize + 2 + 4); - else - return (sizeof(float) * nx + nx / blocksize + 2 + 4); - } - else if ((comptype == GZIP_1) || (comptype == GZIP_2)) - { - /* gzip usually compressed by at least a factor of 2 for I*4 images */ - /* and somewhat less for I*2 images */ - /* If this size turns out to be too small, then the gzip */ - /* compression routine will allocate more space as required */ - /* to be on the safe size, allocate buffer same size as input */ - - if (zbitpix == 16) - return(nx * 2); - else if (zbitpix == 8) - return(nx); - else - return(nx * 4); - } - else if (comptype == BZIP2_1) - { - /* To guarantee that the compressed data will fit, allocate an output - buffer of size 1% larger than the uncompressed data, plus 600 bytes */ - - return((int) (nx * 1.01 * zbitpix / 8. + 601.)); - } - else if (comptype == HCOMPRESS_1) - { - /* Imperical evidence suggests in the worst case, - the compressed stream could be up to 10% larger than the original - image. Add 26 byte overhead, only significant for very small tiles - - Possible improvement: may need to allow a larger size for 32-bit images */ - - if (zbitpix == 16 || zbitpix == 8) - - return( (int) (nx * 2.2 + 26)); /* will be compressing 16-bit int array */ - else - return( (int) (nx * 4.4 + 26)); /* will be compressing 32-bit int array */ - } - else - return(nx * sizeof(int)); -} -/*--------------------------------------------------------------------------*/ -int imcomp_compress_image (fitsfile *infptr, fitsfile *outfptr, int *status) - -/* This routine does the following: - - reads an image one tile at a time - - if it is a float or double image, then it tries to quantize the pixels - into scaled integers. - - it then compressess the integer pixels, or if the it was not - possible to quantize the floating point pixels, then it losslessly - compresses them with gzip - - writes the compressed byte stream to the output FITS file -*/ -{ - double *tiledata; - int anynul, gotnulls = 0, datatype; - long ii, row; - int naxis; - double dummy = 0., dblnull = DOUBLENULLVALUE; - float fltnull = FLOATNULLVALUE; - long maxtilelen, tilelen, incre[] = {1, 1, 1, 1, 1, 1}; - long naxes[MAX_COMPRESS_DIM], fpixel[MAX_COMPRESS_DIM]; - long lpixel[MAX_COMPRESS_DIM], tile[MAX_COMPRESS_DIM]; - long tilesize[MAX_COMPRESS_DIM]; - long i0, i1, i2, i3, i4, i5, trowsize, ntrows; - char card[FLEN_CARD]; - - if (*status > 0) - return(*status); - - maxtilelen = (outfptr->Fptr)->maxtilelen; - - /* - Allocate buffer to hold 1 tile of data; size depends on which compression - algorithm is used: - - Rice and GZIP will compress byte, short, or int arrays without conversion. - PLIO requires 4-byte int values, so byte and short arrays must be converted to int. - HCompress internally converts byte or short values to ints, and - converts int values to 8-byte longlong integers. - */ - - if ((outfptr->Fptr)->zbitpix == FLOAT_IMG) - { - datatype = TFLOAT; - - if ( (outfptr->Fptr)->compress_type == HCOMPRESS_1) { - /* need twice as much scratch space (8 bytes per pixel) */ - tiledata = (double*) malloc (maxtilelen * 2 *sizeof (float)); - } else { - tiledata = (double*) malloc (maxtilelen * sizeof (float)); - } - } - else if ((outfptr->Fptr)->zbitpix == DOUBLE_IMG) - { - datatype = TDOUBLE; - tiledata = (double*) malloc (maxtilelen * sizeof (double)); - } - else if ((outfptr->Fptr)->zbitpix == SHORT_IMG) - { - datatype = TSHORT; - if ( (outfptr->Fptr)->compress_type == RICE_1 || - (outfptr->Fptr)->compress_type == GZIP_1 || - (outfptr->Fptr)->compress_type == GZIP_2 || - (outfptr->Fptr)->compress_type == BZIP2_1 || - (outfptr->Fptr)->compress_type == NOCOMPRESS) { - /* only need buffer of I*2 pixels for gzip, bzip2, and Rice */ - - tiledata = (double*) malloc (maxtilelen * sizeof (short)); - } else { - /* need buffer of I*4 pixels for Hcompress and PLIO */ - tiledata = (double*) malloc (maxtilelen * sizeof (int)); - } - } - else if ((outfptr->Fptr)->zbitpix == BYTE_IMG) - { - - datatype = TBYTE; - if ( (outfptr->Fptr)->compress_type == RICE_1 || - (outfptr->Fptr)->compress_type == BZIP2_1 || - (outfptr->Fptr)->compress_type == GZIP_1 || - (outfptr->Fptr)->compress_type == GZIP_2) { - /* only need buffer of I*1 pixels for gzip, bzip2, and Rice */ - - tiledata = (double*) malloc (maxtilelen); - } else { - /* need buffer of I*4 pixels for Hcompress and PLIO */ - tiledata = (double*) malloc (maxtilelen * sizeof (int)); - } - } - else if ((outfptr->Fptr)->zbitpix == LONG_IMG) - { - datatype = TINT; - if ( (outfptr->Fptr)->compress_type == HCOMPRESS_1) { - /* need twice as much scratch space (8 bytes per pixel) */ - - tiledata = (double*) malloc (maxtilelen * 2 * sizeof (int)); - } else { - /* only need buffer of I*4 pixels for gzip, bzip2, Rice, and PLIO */ - - tiledata = (double*) malloc (maxtilelen * sizeof (int)); - } - } - else - { - ffpmsg("Bad image datatype. (imcomp_compress_image)"); - return (*status = MEMORY_ALLOCATION); - } - - if (tiledata == NULL) - { - ffpmsg("Out of memory. (imcomp_compress_image)"); - return (*status = MEMORY_ALLOCATION); - } - - /* calculate size of tile in each dimension */ - naxis = (outfptr->Fptr)->zndim; - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - if (ii < naxis) - { - naxes[ii] = (outfptr->Fptr)->znaxis[ii]; - tilesize[ii] = (outfptr->Fptr)->tilesize[ii]; - } - else - { - naxes[ii] = 1; - tilesize[ii] = 1; - } - } - row = 1; - - /* set up big loop over up to 6 dimensions */ - for (i5 = 1; i5 <= naxes[5]; i5 += tilesize[5]) - { - fpixel[5] = i5; - lpixel[5] = minvalue(i5 + tilesize[5] - 1, naxes[5]); - tile[5] = lpixel[5] - fpixel[5] + 1; - for (i4 = 1; i4 <= naxes[4]; i4 += tilesize[4]) - { - fpixel[4] = i4; - lpixel[4] = minvalue(i4 + tilesize[4] - 1, naxes[4]); - tile[4] = lpixel[4] - fpixel[4] + 1; - for (i3 = 1; i3 <= naxes[3]; i3 += tilesize[3]) - { - fpixel[3] = i3; - lpixel[3] = minvalue(i3 + tilesize[3] - 1, naxes[3]); - tile[3] = lpixel[3] - fpixel[3] + 1; - for (i2 = 1; i2 <= naxes[2]; i2 += tilesize[2]) - { - fpixel[2] = i2; - lpixel[2] = minvalue(i2 + tilesize[2] - 1, naxes[2]); - tile[2] = lpixel[2] - fpixel[2] + 1; - for (i1 = 1; i1 <= naxes[1]; i1 += tilesize[1]) - { - fpixel[1] = i1; - lpixel[1] = minvalue(i1 + tilesize[1] - 1, naxes[1]); - tile[1] = lpixel[1] - fpixel[1] + 1; - for (i0 = 1; i0 <= naxes[0]; i0 += tilesize[0]) - { - fpixel[0] = i0; - lpixel[0] = minvalue(i0 + tilesize[0] - 1, naxes[0]); - tile[0] = lpixel[0] - fpixel[0] + 1; - - /* number of pixels in this tile */ - tilelen = tile[0]; - for (ii = 1; ii < naxis; ii++) - { - tilelen *= tile[ii]; - } - - /* read next tile of data from image */ - anynul = 0; - if (datatype == TFLOAT) - { - ffgsve(infptr, 1, naxis, naxes, fpixel, lpixel, incre, - FLOATNULLVALUE, (float *) tiledata, &anynul, status); - } - else if (datatype == TDOUBLE) - { - ffgsvd(infptr, 1, naxis, naxes, fpixel, lpixel, incre, - DOUBLENULLVALUE, tiledata, &anynul, status); - } - else if (datatype == TINT) - { - ffgsvk(infptr, 1, naxis, naxes, fpixel, lpixel, incre, - 0, (int *) tiledata, &anynul, status); - } - else if (datatype == TSHORT) - { - ffgsvi(infptr, 1, naxis, naxes, fpixel, lpixel, incre, - 0, (short *) tiledata, &anynul, status); - } - else if (datatype == TBYTE) - { - ffgsvb(infptr, 1, naxis, naxes, fpixel, lpixel, incre, - 0, (unsigned char *) tiledata, &anynul, status); - } - else - { - ffpmsg("Error bad datatype of image tile to compress"); - free(tiledata); - return (*status); - } - - /* now compress the tile, and write to row of binary table */ - /* NOTE: we don't have to worry about the presence of null values in the - array if it is an integer array: the null value is simply encoded - in the compressed array just like any other pixel value. - - If it is a floating point array, then we need to check for null - only if the anynul parameter returned a true value when reading the tile - */ - - /* Collapse sizes of higher dimension tiles into 2 dimensional - equivalents needed by the quantizing algorithms for - floating point types */ - fits_calc_tile_rows(lpixel, fpixel, naxis, &trowsize, - &ntrows, status); - - if (anynul && datatype == TFLOAT) { - imcomp_compress_tile(outfptr, row, datatype, tiledata, tilelen, - trowsize, ntrows, 1, &fltnull, status); - } else if (anynul && datatype == TDOUBLE) { - imcomp_compress_tile(outfptr, row, datatype, tiledata, tilelen, - trowsize, ntrows, 1, &dblnull, status); - } else { - imcomp_compress_tile(outfptr, row, datatype, tiledata, tilelen, - trowsize, ntrows, 0, &dummy, status); - } - - /* set flag if we found any null values */ - if (anynul) - gotnulls = 1; - - /* check for any error in the previous operations */ - if (*status > 0) - { - ffpmsg("Error writing compressed image to table"); - free(tiledata); - return (*status); - } - - row++; - } - } - } - } - } - } - - free (tiledata); /* finished with this buffer */ - - /* insert ZBLANK keyword if necessary; only for TFLOAT or TDOUBLE images */ - if (gotnulls) - { - ffgcrd(outfptr, "ZCMPTYPE", card, status); - ffikyj(outfptr, "ZBLANK", COMPRESS_NULL_VALUE, - "null value in the compressed integer array", status); - } - - return (*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_compress_tile (fitsfile *outfptr, - long row, /* tile number = row in the binary table that holds the compressed data */ - int datatype, - void *tiledata, - long tilelen, - long tilenx, - long tileny, - int nullcheck, - void *nullflagval, - int *status) - -/* - This is the main compression routine. - - This routine does the following to the input tile of pixels: - - if it is a float or double image, then it quantizes the pixels - - compresses the integer pixel values - - writes the compressed byte stream to the FITS file. - - If the tile cannot be quantized than the raw float or double values - are losslessly compressed with gzip and then written to the output table. - - This input array may be modified by this routine. If the array is of type TINT - or TFLOAT, and the compression type is HCOMPRESS, then it must have been - allocated to be twice as large (8 bytes per pixel) to provide scratch space. - - Note that this routine does not fully support the implicit datatype conversion that - is supported when writing to normal FITS images. The datatype of the input array - must have the same datatype (either signed or unsigned) as the output (compressed) - FITS image in some cases. -*/ -{ - int *idata; /* quantized integer data */ - int cn_zblank, zbitpix, nullval; - int flag = 1; /* true by default; only = 0 if float data couldn't be quantized */ - int intlength; /* size of integers to be compressed */ - double scale, zero, actual_bzero; - long ii; - size_t clen; /* size of cbuf */ - short *cbuf; /* compressed data */ - int nelem = 0; /* number of bytes */ - int tilecol; - size_t gzip_nelem = 0; - unsigned int bzlen; - int ihcompscale; - float hcompscale; - double noise2, noise3, noise5; - double bscale[1] = {1.}, bzero[1] = {0.}; /* scaling parameters */ - long hcomp_len; - LONGLONG *lldata; - - if (*status > 0) - return(*status); - - /* check for special case of losslessly compressing floating point */ - /* images. Only compression algorithm that supports this is GZIP */ - if ( (outfptr->Fptr)->quantize_level == NO_QUANTIZE) { - if (((outfptr->Fptr)->compress_type != GZIP_1) && - ((outfptr->Fptr)->compress_type != GZIP_2)) { - switch (datatype) { - case TFLOAT: - case TDOUBLE: - case TCOMPLEX: - case TDBLCOMPLEX: - ffpmsg("Lossless compression of floating point images must use GZIP (imcomp_compress_tile)"); - return(*status = DATA_COMPRESSION_ERR); - default: - break; - } - } - } - - /* free the previously saved tile if the input tile is for the same row */ - if ((outfptr->Fptr)->tilerow) { /* has the tile cache been allocated? */ - - /* calculate the column bin of the compressed tile */ - tilecol = (row - 1) % ((long)(((outfptr->Fptr)->znaxis[0] - 1) / ((outfptr->Fptr)->tilesize[0])) + 1); - - if ((outfptr->Fptr)->tilerow[tilecol] == row) { - if (((outfptr->Fptr)->tiledata)[tilecol]) { - free(((outfptr->Fptr)->tiledata)[tilecol]); - } - - if (((outfptr->Fptr)->tilenullarray)[tilecol]) { - free(((outfptr->Fptr)->tilenullarray)[tilecol]); - } - - ((outfptr->Fptr)->tiledata)[tilecol] = 0; - ((outfptr->Fptr)->tilenullarray)[tilecol] = 0; - (outfptr->Fptr)->tilerow[tilecol] = 0; - (outfptr->Fptr)->tiledatasize[tilecol] = 0; - (outfptr->Fptr)->tiletype[tilecol] = 0; - (outfptr->Fptr)->tileanynull[tilecol] = 0; - } - } - - if ( (outfptr->Fptr)->compress_type == NOCOMPRESS) { - /* Special case when using NOCOMPRESS for diagnostic purposes in fpack */ - if (imcomp_write_nocompress_tile(outfptr, row, datatype, tiledata, tilelen, - nullcheck, nullflagval, status) > 0) { - return(*status); - } - return(*status); - } - - /* =========================================================================== */ - /* initialize various parameters */ - idata = (int *) tiledata; /* may overwrite the input tiledata in place */ - - /* zbitpix is the BITPIX keyword value in the uncompressed FITS image */ - zbitpix = (outfptr->Fptr)->zbitpix; - - /* if the tile/image has an integer datatype, see if a null value has */ - /* been defined (with the BLANK keyword in a normal FITS image). */ - /* If so, and if the input tile array also contains null pixels, */ - /* (represented by pixels that have a value = nullflagval) then */ - /* any pixels whose value = nullflagval, must be set to the value = nullval */ - /* before the pixel array is compressed. These null pixel values must */ - /* not be inverse scaled by the BSCALE/BZERO values, if present. */ - - cn_zblank = (outfptr->Fptr)->cn_zblank; - nullval = (outfptr->Fptr)->zblank; - - if (zbitpix > 0 && cn_zblank != -1) /* If the integer image has no defined null */ - nullcheck = 0; /* value, then don't bother checking input array for nulls. */ - - /* if the BSCALE and BZERO keywords exist, then the input values must */ - /* be inverse scaled by this factor, before the values are compressed. */ - /* (The program may have turned off scaling, which over rides the keywords) */ - - scale = (outfptr->Fptr)->cn_bscale; - zero = (outfptr->Fptr)->cn_bzero; - actual_bzero = (outfptr->Fptr)->cn_actual_bzero; - - /* =========================================================================== */ - /* prepare the tile of pixel values for compression */ - if (datatype == TSHORT) { - imcomp_convert_tile_tshort(outfptr, tiledata, tilelen, nullcheck, nullflagval, - nullval, zbitpix, scale, zero, actual_bzero, &intlength, status); - } else if (datatype == TUSHORT) { - imcomp_convert_tile_tushort(outfptr, tiledata, tilelen, nullcheck, nullflagval, - nullval, zbitpix, scale, zero, &intlength, status); - } else if (datatype == TBYTE) { - imcomp_convert_tile_tbyte(outfptr, tiledata, tilelen, nullcheck, nullflagval, - nullval, zbitpix, scale, zero, &intlength, status); - } else if (datatype == TSBYTE) { - imcomp_convert_tile_tsbyte(outfptr, tiledata, tilelen, nullcheck, nullflagval, - nullval, zbitpix, scale, zero, &intlength, status); - } else if (datatype == TINT) { - imcomp_convert_tile_tint(outfptr, tiledata, tilelen, nullcheck, nullflagval, - nullval, zbitpix, scale, zero, &intlength, status); - } else if (datatype == TUINT) { - imcomp_convert_tile_tuint(outfptr, tiledata, tilelen, nullcheck, nullflagval, - nullval, zbitpix, scale, zero, &intlength, status); - } else if (datatype == TLONG && sizeof(long) == 8) { - ffpmsg("Integer*8 Long datatype is not supported when writing to compressed images"); - return(*status = BAD_DATATYPE); - } else if (datatype == TULONG && sizeof(long) == 8) { - ffpmsg("Unsigned integer*8 datatype is not supported when writing to compressed images"); - return(*status = BAD_DATATYPE); - } else if (datatype == TFLOAT) { - imcomp_convert_tile_tfloat(outfptr, row, tiledata, tilelen, tilenx, tileny, nullcheck, - nullflagval, nullval, zbitpix, scale, zero, &intlength, &flag, bscale, bzero, status); - } else if (datatype == TDOUBLE) { - imcomp_convert_tile_tdouble(outfptr, row, tiledata, tilelen, tilenx, tileny, nullcheck, - nullflagval, nullval, zbitpix, scale, zero, &intlength, &flag, bscale, bzero, status); - } else { - ffpmsg("unsupported image datatype (imcomp_compress_tile)"); - return(*status = BAD_DATATYPE); - } - - if (*status > 0) - return(*status); /* return if error occurs */ - - /* =========================================================================== */ - if (flag) /* now compress the integer data array */ - { - /* allocate buffer for the compressed tile bytes */ - clen = (outfptr->Fptr)->maxelem; - cbuf = (short *) calloc (clen, sizeof (unsigned char)); - - if (cbuf == NULL) { - ffpmsg("Memory allocation failure. (imcomp_compress_tile)"); - return (*status = MEMORY_ALLOCATION); - } - - /* =========================================================================== */ - if ( (outfptr->Fptr)->compress_type == RICE_1) - { - if (intlength == 2) { - nelem = fits_rcomp_short ((short *)idata, tilelen, (unsigned char *) cbuf, - clen, (outfptr->Fptr)->rice_blocksize); - } else if (intlength == 1) { - nelem = fits_rcomp_byte ((signed char *)idata, tilelen, (unsigned char *) cbuf, - clen, (outfptr->Fptr)->rice_blocksize); - } else { - nelem = fits_rcomp (idata, tilelen, (unsigned char *) cbuf, - clen, (outfptr->Fptr)->rice_blocksize); - } - - if (nelem < 0) /* data compression error condition */ + for (ii = 0; ii < ntodo; ii++) { - free (cbuf); - ffpmsg("error Rice compressing image tile (imcomp_compress_tile)"); - return (*status = DATA_COMPRESSION_ERR); - } - - /* Write the compressed byte stream. */ - ffpclb(outfptr, (outfptr->Fptr)->cn_compressed, row, 1, - nelem, (unsigned char *) cbuf, status); - } - - /* =========================================================================== */ - else if ( (outfptr->Fptr)->compress_type == PLIO_1) - { - for (ii = 0; ii < tilelen; ii++) { - if (idata[ii] < 0 || idata[ii] > 16777215) + if (input[ii] == tnull) { - /* plio algorithn only supports positive 24 bit ints */ - ffpmsg("data out of range for PLIO compression (0 - 2**24)"); - return(*status = DATA_COMPRESSION_ERR); - } - } - - nelem = pl_p2li (idata, 1, cbuf, tilelen); - - if (nelem < 0) /* data compression error condition */ - { - free (cbuf); - ffpmsg("error PLIO compressing image tile (imcomp_compress_tile)"); - return (*status = DATA_COMPRESSION_ERR); - } - - /* Write the compressed byte stream. */ - ffpcli(outfptr, (outfptr->Fptr)->cn_compressed, row, 1, - nelem, cbuf, status); - } - - /* =========================================================================== */ - else if ( ((outfptr->Fptr)->compress_type == GZIP_1) || - ((outfptr->Fptr)->compress_type == GZIP_2) ) { - - if ((outfptr->Fptr)->quantize_level == NO_QUANTIZE && datatype == TFLOAT) { - /* Special case of losslessly compressing floating point pixels with GZIP */ - /* In this case we compress the input tile array directly */ - -#if BYTESWAPPED - ffswap4((int*) tiledata, tilelen); -#endif - if ( (outfptr->Fptr)->compress_type == GZIP_2 ) - fits_shuffle_4bytes((char *) tiledata, tilelen, status); - - compress2mem_from_mem((char *) tiledata, tilelen * sizeof(float), - (char **) &cbuf, &clen, realloc, &gzip_nelem, status); - - } else if ((outfptr->Fptr)->quantize_level == NO_QUANTIZE && datatype == TDOUBLE) { - /* Special case of losslessly compressing double pixels with GZIP */ - /* In this case we compress the input tile array directly */ - -#if BYTESWAPPED - ffswap8((double *) tiledata, tilelen); -#endif - if ( (outfptr->Fptr)->compress_type == GZIP_2 ) - fits_shuffle_8bytes((char *) tiledata, tilelen, status); - - compress2mem_from_mem((char *) tiledata, tilelen * sizeof(double), - (char **) &cbuf, &clen, realloc, &gzip_nelem, status); - - } else { - - /* compress the integer idata array */ - -#if BYTESWAPPED - if (intlength == 2) - ffswap2((short *) idata, tilelen); - else if (intlength == 4) - ffswap4(idata, tilelen); -#endif - - if (intlength == 2) { - - if ( (outfptr->Fptr)->compress_type == GZIP_2 ) - fits_shuffle_2bytes((char *) tiledata, tilelen, status); - - compress2mem_from_mem((char *) idata, tilelen * sizeof(short), - (char **) &cbuf, &clen, realloc, &gzip_nelem, status); - - } else if (intlength == 1) { - - compress2mem_from_mem((char *) idata, tilelen * sizeof(unsigned char), - (char **) &cbuf, &clen, realloc, &gzip_nelem, status); - - } else { - - if ( (outfptr->Fptr)->compress_type == GZIP_2 ) - fits_shuffle_4bytes((char *) tiledata, tilelen, status); - - compress2mem_from_mem((char *) idata, tilelen * sizeof(int), - (char **) &cbuf, &clen, realloc, &gzip_nelem, status); - } - } - - /* Write the compressed byte stream. */ - ffpclb(outfptr, (outfptr->Fptr)->cn_compressed, row, 1, - gzip_nelem, (unsigned char *) cbuf, status); - - /* =========================================================================== */ - } else if ( (outfptr->Fptr)->compress_type == BZIP2_1) { - -#if BYTESWAPPED - if (intlength == 2) - ffswap2((short *) idata, tilelen); - else if (intlength == 4) - ffswap4(idata, tilelen); -#endif - - bzlen = (unsigned int) clen; - - /* call bzip2 with blocksize = 900K, verbosity = 0, and default workfactor */ - -/* bzip2 is not supported in the public release. This is only for test purposes. - if (BZ2_bzBuffToBuffCompress( (char *) cbuf, &bzlen, - (char *) idata, (unsigned int) (tilelen * intlength), 9, 0, 0) ) -*/ - { - ffpmsg("bzip2 compression error"); - return(*status = DATA_COMPRESSION_ERR); - } - - /* Write the compressed byte stream. */ - ffpclb(outfptr, (outfptr->Fptr)->cn_compressed, row, 1, - bzlen, (unsigned char *) cbuf, status); - - /* =========================================================================== */ - } else if ( (outfptr->Fptr)->compress_type == HCOMPRESS_1) { - /* - if hcompscale is positive, then we have to multiply - the value by the RMS background noise to get the - absolute scale value. If negative, then it gives the - absolute scale value directly. - */ - hcompscale = (outfptr->Fptr)->hcomp_scale; - - if (hcompscale > 0.) { - fits_img_stats_int(idata, tilenx, tileny, nullcheck, - nullval, 0,0,0,0,0,0,&noise2,&noise3,&noise5,status); - - /* use the minimum of the 3 noise estimates */ - if (noise2 != 0. && noise2 < noise3) noise3 = noise2; - if (noise5 != 0. && noise5 < noise3) noise3 = noise5; - - hcompscale = (float) (hcompscale * noise3); - - } else if (hcompscale < 0.) { - - hcompscale = hcompscale * -1.0F; - } - - ihcompscale = (int) (hcompscale + 0.5); - - hcomp_len = clen; /* allocated size of the buffer */ - - if (zbitpix == BYTE_IMG || zbitpix == SHORT_IMG) { - fits_hcompress(idata, tilenx, tileny, - ihcompscale, (char *) cbuf, &hcomp_len, status); - - } else { - /* have to convert idata to an I*8 array, in place */ - /* idata must have been allocated large enough to do this */ - - fits_int_to_longlong_inplace(idata, tilelen, status); - lldata = (LONGLONG *) idata; - - fits_hcompress64(lldata, tilenx, tileny, - ihcompscale, (char *) cbuf, &hcomp_len, status); - } - - /* Write the compressed byte stream. */ - ffpclb(outfptr, (outfptr->Fptr)->cn_compressed, row, 1, - hcomp_len, (unsigned char *) cbuf, status); - } - - /* =========================================================================== */ - if ((outfptr->Fptr)->cn_zscale > 0) - { - /* write the linear scaling parameters for this tile */ - ffpcld (outfptr, (outfptr->Fptr)->cn_zscale, row, 1, 1, - bscale, status); - ffpcld (outfptr, (outfptr->Fptr)->cn_zzero, row, 1, 1, - bzero, status); - } - - free(cbuf); /* finished with this buffer */ - - /* =========================================================================== */ - } else { /* if flag == 0., floating point data couldn't be quantized */ - - /* losslessly compress the data with gzip. */ - - /* if gzip2 compressed data column doesn't exist, create it */ - if ((outfptr->Fptr)->cn_gzip_data < 1) { - if ( (outfptr->Fptr)->request_huge_hdu != 0) { - fits_insert_col(outfptr, 999, "GZIP_COMPRESSED_DATA", "1QB", status); - } else { - fits_insert_col(outfptr, 999, "GZIP_COMPRESSED_DATA", "1PB", status); - } - - if (*status <= 0) /* save the number of this column */ - ffgcno(outfptr, CASEINSEN, "GZIP_COMPRESSED_DATA", - &(outfptr->Fptr)->cn_gzip_data, status); - } - - if (datatype == TFLOAT) { - /* allocate buffer for the compressed tile bytes */ - /* make it 10% larger than the original uncompressed data */ - clen = (size_t) (tilelen * sizeof(float) * 1.1); - cbuf = (short *) calloc (clen, sizeof (unsigned char)); - - if (cbuf == NULL) - { - ffpmsg("Memory allocation error. (imcomp_compress_tile)"); - return (*status = MEMORY_ALLOCATION); - } - - /* convert null values to NaNs in place, if necessary */ - if (nullcheck == 1) { - imcomp_float2nan((float *) tiledata, tilelen, (int *) tiledata, - *(float *) (nullflagval), status); - } - -#if BYTESWAPPED - ffswap4((int*) tiledata, tilelen); -#endif - compress2mem_from_mem((char *) tiledata, tilelen * sizeof(float), - (char **) &cbuf, &clen, realloc, &gzip_nelem, status); - - } else { /* datatype == TDOUBLE */ - - /* allocate buffer for the compressed tile bytes */ - /* make it 10% larger than the original uncompressed data */ - clen = (size_t) (tilelen * sizeof(double) * 1.1); - cbuf = (short *) calloc (clen, sizeof (unsigned char)); - - if (cbuf == NULL) - { - ffpmsg("Memory allocation error. (imcomp_compress_tile)"); - return (*status = MEMORY_ALLOCATION); - } - - /* convert null values to NaNs in place, if necessary */ - if (nullcheck == 1) { - imcomp_double2nan((double *) tiledata, tilelen, (LONGLONG *) tiledata, - *(double *) (nullflagval), status); - } - -#if BYTESWAPPED - ffswap8((double*) tiledata, tilelen); -#endif - compress2mem_from_mem((char *) tiledata, tilelen * sizeof(double), - (char **) &cbuf, &clen, realloc, &gzip_nelem, status); - } - - /* Write the compressed byte stream. */ - ffpclb(outfptr, (outfptr->Fptr)->cn_gzip_data, row, 1, - gzip_nelem, (unsigned char *) cbuf, status); - - free(cbuf); /* finished with this buffer */ - } - - return(*status); -} - -/*--------------------------------------------------------------------------*/ -int imcomp_write_nocompress_tile(fitsfile *outfptr, - long row, - int datatype, - void *tiledata, - long tilelen, - int nullcheck, - void *nullflagval, - int *status) -{ - char coltype[4]; - - /* Write the uncompressed image tile pixels to the tile-compressed image file. */ - /* This is a special case when using NOCOMPRESS for diagnostic purposes in fpack. */ - /* Currently, this only supports a limited number of data types and */ - /* does not fully support null-valued pixels in the image. */ - - if ((outfptr->Fptr)->cn_uncompressed < 1) { - /* uncompressed data column doesn't exist, so append new column to table */ - if (datatype == TSHORT) { - strcpy(coltype, "1PI"); - } else if (datatype == TINT) { - strcpy(coltype, "1PJ"); - } else if (datatype == TFLOAT) { - strcpy(coltype, "1QE"); - } else { - ffpmsg("NOCOMPRESSION option only supported for int*2, int*4, and float*4 images"); - return(*status = DATA_COMPRESSION_ERR); - } - - fits_insert_col(outfptr, 999, "UNCOMPRESSED_DATA", coltype, status); /* create column */ - } - - fits_get_colnum(outfptr, CASEINSEN, "UNCOMPRESSED_DATA", - &(outfptr->Fptr)->cn_uncompressed, status); /* save col. num. */ - - fits_write_col(outfptr, datatype, (outfptr->Fptr)->cn_uncompressed, row, 1, - tilelen, tiledata, status); /* write the tile data */ - return (*status); -} - /*--------------------------------------------------------------------------*/ -int imcomp_convert_tile_tshort( - fitsfile *outfptr, - void *tiledata, - long tilelen, - int nullcheck, - void *nullflagval, - int nullval, - int zbitpix, - double scale, - double zero, - double actual_bzero, - int *intlength, - int *status) -{ - /* Prepare the input tile array of pixels for compression. */ - /* Convert input integer*2 tile array in place to 4 or 8-byte ints for compression, */ - /* If needed, convert 4 or 8-byte ints and do null value substitution. */ - /* Note that the calling routine must have allocated the input array big enough */ - /* to be able to do this. */ - - short *sbuff; - int flagval, *idata; - long ii; - - /* We only support writing this integer*2 tile data to a FITS image with - BITPIX = 16 and with BZERO = 0 and BSCALE = 1. */ - - if (zbitpix != SHORT_IMG || scale != 1.0 || zero != 0.0) { - ffpmsg("Datatype conversion/scaling is not supported when writing to compressed images"); - return(*status = DATA_COMPRESSION_ERR); - } - - sbuff = (short *) tiledata; - idata = (int *) tiledata; - - if ( (outfptr->Fptr)->compress_type == RICE_1 || (outfptr->Fptr)->compress_type == GZIP_1 - || (outfptr->Fptr)->compress_type == GZIP_2 || (outfptr->Fptr)->compress_type == BZIP2_1 ) - { - /* don't have to convert to int if using gzip, bzip2 or Rice compression */ - *intlength = 2; - - if (nullcheck == 1) { - /* reset pixels equal to flagval to the FITS null value, prior to compression */ - flagval = *(short *) (nullflagval); - if (flagval != nullval) { - for (ii = tilelen - 1; ii >= 0; ii--) { - if (sbuff[ii] == (short) flagval) - sbuff[ii] = (short) nullval; - } - } - } - } else if ((outfptr->Fptr)->compress_type == HCOMPRESS_1) { - /* have to convert to int if using HCOMPRESS */ - *intlength = 4; - - if (nullcheck == 1) { - /* reset pixels equal to flagval to the FITS null value, prior to compression */ - flagval = *(short *) (nullflagval); - for (ii = tilelen - 1; ii >= 0; ii--) { - if (sbuff[ii] == (short) flagval) - idata[ii] = nullval; - else - idata[ii] = (int) sbuff[ii]; - } - } else { /* just do the data type conversion to int */ - /* have to convert sbuff to an I*4 array, in place */ - /* sbuff must have been allocated large enough to do this */ - fits_short_to_int_inplace(sbuff, tilelen, 0, status); - } - } else { - /* have to convert to int if using PLIO */ - *intlength = 4; - if (zero == 0. && actual_bzero == 32768.) { - /* Here we are compressing unsigned 16-bit integers that have */ - /* been offset by -32768 using the standard FITS convention. */ - /* Since PLIO cannot deal with negative values, we must apply */ - /* the shift of 32786 to the values to make them all positive. */ - /* The inverse negative shift will be applied in */ - /* imcomp_decompress_tile when reading the compressed tile. */ - if (nullcheck == 1) { - /* reset pixels equal to flagval to the FITS null value, prior to compression */ - flagval = *(short *) (nullflagval); - for (ii = tilelen - 1; ii >= 0; ii--) { - if (sbuff[ii] == (short) flagval) - idata[ii] = nullval; - else - idata[ii] = (int) sbuff[ii] + 32768; - } - } else { - /* have to convert sbuff to an I*4 array, in place */ - /* sbuff must have been allocated large enough to do this */ - fits_short_to_int_inplace(sbuff, tilelen, 32768, status); - } - } else { - /* This is not an unsigned 16-bit integer array, so process normally */ - if (nullcheck == 1) { - /* reset pixels equal to flagval to the FITS null value, prior to compression */ - flagval = *(short *) (nullflagval); - for (ii = tilelen - 1; ii >= 0; ii--) { - if (sbuff[ii] == (short) flagval) - idata[ii] = nullval; - else - idata[ii] = (int) sbuff[ii]; - } - } else { /* just do the data type conversion to int */ - /* have to convert sbuff to an I*4 array, in place */ - /* sbuff must have been allocated large enough to do this */ - fits_short_to_int_inplace(sbuff, tilelen, 0, status); - } - } - } - return(*status); -} - /*--------------------------------------------------------------------------*/ -int imcomp_convert_tile_tushort( - fitsfile *outfptr, - void *tiledata, - long tilelen, - int nullcheck, - void *nullflagval, - int nullval, - int zbitpix, - double scale, - double zero, - int *intlength, - int *status) -{ - /* Prepare the input tile array of pixels for compression. */ - /* Convert input unsigned integer*2 tile array in place to 4 or 8-byte ints for compression, */ - /* If needed, convert 4 or 8-byte ints and do null value substitution. */ - /* Note that the calling routine must have allocated the input array big enough */ - /* to be able to do this. */ - - unsigned short *usbuff; - short *sbuff; - int flagval, *idata; - long ii; - - /* datatype of input array is unsigned short. We only support writing this datatype - to a FITS image with BITPIX = 16 and with BZERO = 0 and BSCALE = 32768. */ - - if (zbitpix != SHORT_IMG || scale != 1.0 || zero != 32768.) { - ffpmsg("Implicit datatype conversion is not supported when writing to compressed images"); - return(*status = DATA_COMPRESSION_ERR); - } - - usbuff = (unsigned short *) tiledata; - sbuff = (short *) tiledata; - idata = (int *) tiledata; - - if ((outfptr->Fptr)->compress_type == RICE_1 || (outfptr->Fptr)->compress_type == GZIP_1 - || (outfptr->Fptr)->compress_type == GZIP_2 || (outfptr->Fptr)->compress_type == BZIP2_1) - { - /* don't have to convert to int if using gzip, bzip2, or Rice compression */ - *intlength = 2; - - /* offset the unsigned value by -32768 to a signed short value. */ - /* It is more efficient to do this by just flipping the most significant of the 16 bits */ - - if (nullcheck == 1) { - /* reset pixels equal to flagval to the FITS null value, prior to compression */ - flagval = *(unsigned short *) (nullflagval); - for (ii = tilelen - 1; ii >= 0; ii--) { - if (usbuff[ii] == (unsigned short) flagval) - sbuff[ii] = (short) nullval; - else - usbuff[ii] = (usbuff[ii]) ^ 0x8000; - } - } else { - /* just offset the pixel values by 32768 (by flipping the MSB */ - for (ii = tilelen - 1; ii >= 0; ii--) - usbuff[ii] = (usbuff[ii]) ^ 0x8000; - } - } else { - /* have to convert to int if using HCOMPRESS or PLIO */ - *intlength = 4; - - if (nullcheck == 1) { - /* offset the pixel values by 32768, and */ - /* reset pixels equal to flagval to nullval */ - flagval = *(unsigned short *) (nullflagval); - for (ii = tilelen - 1; ii >= 0; ii--) { - if (usbuff[ii] == (unsigned short) flagval) - idata[ii] = nullval; - else - idata[ii] = ((int) usbuff[ii]) - 32768; - } - } else { /* just do the data type conversion to int */ - /* for HCOMPRESS we need to simply subtract 32768 */ - /* for PLIO, have to convert usbuff to an I*4 array, in place */ - /* usbuff must have been allocated large enough to do this */ - - if ((outfptr->Fptr)->compress_type == HCOMPRESS_1) { - fits_ushort_to_int_inplace(usbuff, tilelen, -32768, status); - } else { - fits_ushort_to_int_inplace(usbuff, tilelen, 0, status); - } - } - } - - return(*status); -} - /*--------------------------------------------------------------------------*/ -int imcomp_convert_tile_tint( - fitsfile *outfptr, - void *tiledata, - long tilelen, - int nullcheck, - void *nullflagval, - int nullval, - int zbitpix, - double scale, - double zero, - int *intlength, - int *status) -{ - /* Prepare the input tile array of pixels for compression. */ - /* Convert input integer tile array in place to 4 or 8-byte ints for compression, */ - /* If needed, do null value substitution. */ - - int flagval, *idata; - long ii; - - - /* datatype of input array is int. We only support writing this datatype - to a FITS image with BITPIX = 32 and with BZERO = 0 and BSCALE = 1. */ - - if (zbitpix != LONG_IMG || scale != 1.0 || zero != 0.) { - ffpmsg("Implicit datatype conversion is not supported when writing to compressed images"); - return(*status = DATA_COMPRESSION_ERR); - } - - idata = (int *) tiledata; - *intlength = 4; - - if (nullcheck == 1) { - /* no datatype conversion is required for any of the compression algorithms, - except possibly for HCOMPRESS (to I*8), which is handled later. - Just reset pixels equal to flagval to the FITS null value */ - flagval = *(int *) (nullflagval); - if (flagval != nullval) { - for (ii = tilelen - 1; ii >= 0; ii--) { - if (idata[ii] == flagval) - idata[ii] = nullval; - } - } - } - - return(*status); -} - /*--------------------------------------------------------------------------*/ -int imcomp_convert_tile_tuint( - fitsfile *outfptr, - void *tiledata, - long tilelen, - int nullcheck, - void *nullflagval, - int nullval, - int zbitpix, - double scale, - double zero, - int *intlength, - int *status) -{ - /* Prepare the input tile array of pixels for compression. */ - /* Convert input unsigned integer tile array in place to 4 or 8-byte ints for compression, */ - /* If needed, do null value substitution. */ - - - int *idata; - unsigned int *uintbuff, uintflagval; - long ii; - - /* datatype of input array is unsigned int. We only support writing this datatype - to a FITS image with BITPIX = 32 and with BZERO = 0 and BSCALE = 2147483648. */ - - if (zbitpix != LONG_IMG || scale != 1.0 || zero != 2147483648.) { - ffpmsg("Implicit datatype conversion is not supported when writing to compressed images"); - return(*status = DATA_COMPRESSION_ERR); - } - - *intlength = 4; - idata = (int *) tiledata; - uintbuff = (unsigned int *) tiledata; - - /* offset the unsigned value by -2147483648 to a signed int value. */ - /* It is more efficient to do this by just flipping the most significant of the 32 bits */ - - if (nullcheck == 1) { - /* reset pixels equal to flagval to nullval and */ - /* offset the other pixel values (by flipping the MSB) */ - uintflagval = *(unsigned int *) (nullflagval); - for (ii = tilelen - 1; ii >= 0; ii--) { - if (uintbuff[ii] == uintflagval) - idata[ii] = nullval; - else - uintbuff[ii] = (uintbuff[ii]) ^ 0x80000000; - } - } else { - /* just offset the pixel values (by flipping the MSB) */ - for (ii = tilelen - 1; ii >= 0; ii--) - uintbuff[ii] = (uintbuff[ii]) ^ 0x80000000; - } - - return(*status); -} - /*--------------------------------------------------------------------------*/ -int imcomp_convert_tile_tbyte( - fitsfile *outfptr, - void *tiledata, - long tilelen, - int nullcheck, - void *nullflagval, - int nullval, - int zbitpix, - double scale, - double zero, - int *intlength, - int *status) -{ - /* Prepare the input tile array of pixels for compression. */ - /* Convert input unsigned integer*1 tile array in place to 4 or 8-byte ints for compression, */ - /* If needed, convert 4 or 8-byte ints and do null value substitution. */ - /* Note that the calling routine must have allocated the input array big enough */ - /* to be able to do this. */ - - int flagval, *idata; - long ii; - unsigned char *usbbuff; - - /* datatype of input array is unsigned byte. We only support writing this datatype - to a FITS image with BITPIX = 8 and with BZERO = 0 and BSCALE = 1. */ - - if (zbitpix != BYTE_IMG || scale != 1.0 || zero != 0.) { - ffpmsg("Implicit datatype conversion is not supported when writing to compressed images"); - return(*status = DATA_COMPRESSION_ERR); - } - - idata = (int *) tiledata; - usbbuff = (unsigned char *) tiledata; - - if ( (outfptr->Fptr)->compress_type == RICE_1 || (outfptr->Fptr)->compress_type == GZIP_1 - || (outfptr->Fptr)->compress_type == GZIP_2 || (outfptr->Fptr)->compress_type == BZIP2_1 ) - { - /* don't have to convert to int if using gzip, bzip2, or Rice compression */ - *intlength = 1; - - if (nullcheck == 1) { - /* reset pixels equal to flagval to the FITS null value, prior to compression */ - flagval = *(unsigned char *) (nullflagval); - if (flagval != nullval) { - for (ii = tilelen - 1; ii >= 0; ii--) { - if (usbbuff[ii] == (unsigned char) flagval) - usbbuff[ii] = (unsigned char) nullval; - } - } - } - } else { - /* have to convert to int if using HCOMPRESS or PLIO */ - *intlength = 4; - - if (nullcheck == 1) { - /* reset pixels equal to flagval to the FITS null value, prior to compression */ - flagval = *(unsigned char *) (nullflagval); - for (ii = tilelen - 1; ii >= 0; ii--) { - if (usbbuff[ii] == (unsigned char) flagval) - idata[ii] = nullval; - else - idata[ii] = (int) usbbuff[ii]; - } - } else { /* just do the data type conversion to int */ - /* have to convert usbbuff to an I*4 array, in place */ - /* usbbuff must have been allocated large enough to do this */ - fits_ubyte_to_int_inplace(usbbuff, tilelen, status); - } - } - - return(*status); -} - /*--------------------------------------------------------------------------*/ -int imcomp_convert_tile_tsbyte( - fitsfile *outfptr, - void *tiledata, - long tilelen, - int nullcheck, - void *nullflagval, - int nullval, - int zbitpix, - double scale, - double zero, - int *intlength, - int *status) -{ - /* Prepare the input tile array of pixels for compression. */ - /* Convert input integer*1 tile array in place to 4 or 8-byte ints for compression, */ - /* If needed, convert 4 or 8-byte ints and do null value substitution. */ - /* Note that the calling routine must have allocated the input array big enough */ - /* to be able to do this. */ - - int flagval, *idata; - long ii; - signed char *sbbuff; - - /* datatype of input array is signed byte. We only support writing this datatype - to a FITS image with BITPIX = 8 and with BZERO = 0 and BSCALE = -128. */ - - if (zbitpix != BYTE_IMG|| scale != 1.0 || zero != -128.) { - ffpmsg("Implicit datatype conversion is not supported when writing to compressed images"); - return(*status = DATA_COMPRESSION_ERR); - } - - idata = (int *) tiledata; - sbbuff = (signed char *) tiledata; - - if ( (outfptr->Fptr)->compress_type == RICE_1 || (outfptr->Fptr)->compress_type == GZIP_1 - || (outfptr->Fptr)->compress_type == GZIP_2 || (outfptr->Fptr)->compress_type == BZIP2_1 ) - { - /* don't have to convert to int if using gzip, bzip2 or Rice compression */ - *intlength = 1; - - if (nullcheck == 1) { - /* reset pixels equal to flagval to the FITS null value, prior to compression */ - /* offset the other pixel values (by flipping the MSB) */ - - flagval = *(signed char *) (nullflagval); - for (ii = tilelen - 1; ii >= 0; ii--) { - if (sbbuff[ii] == (signed char) flagval) - sbbuff[ii] = (signed char) nullval; + *anynull = 1; + if (nullcheck == 1) + output[ii] = nullval; else - sbbuff[ii] = (sbbuff[ii]) ^ 0x80; } - } else { /* just offset the pixel values (by flipping the MSB) */ - for (ii = tilelen - 1; ii >= 0; ii--) - sbbuff[ii] = (sbbuff[ii]) ^ 0x80; - } - - } else { - /* have to convert to int if using HCOMPRESS or PLIO */ - *intlength = 4; - - if (nullcheck == 1) { - /* reset pixels equal to flagval to the FITS null value, prior to compression */ - flagval = *(signed char *) (nullflagval); - for (ii = tilelen - 1; ii >= 0; ii--) { - if (sbbuff[ii] == (signed char) flagval) - idata[ii] = nullval; - else - idata[ii] = ((int) sbbuff[ii]) + 128; - } - } else { /* just do the data type conversion to int */ - /* have to convert sbbuff to an I*4 array, in place */ - /* sbbuff must have been allocated large enough to do this */ - fits_sbyte_to_int_inplace(sbbuff, tilelen, status); - } - } - - return(*status); -} - /*--------------------------------------------------------------------------*/ -int imcomp_convert_tile_tfloat( - fitsfile *outfptr, - long row, - void *tiledata, - long tilelen, - long tilenx, - long tileny, - int nullcheck, - void *nullflagval, - int nullval, - int zbitpix, - double scale, - double zero, - int *intlength, - int *flag, - double *bscale, - double *bzero, - int *status) -{ - /* Prepare the input tile array of pixels for compression. */ - /* Convert input float tile array in place to 4 or 8-byte ints for compression, */ - /* If needed, convert 4 or 8-byte ints and do null value substitution. */ - /* Note that the calling routine must have allocated the input array big enough */ - /* to be able to do this. */ - - int *idata; - long irow, ii; - float floatnull; - unsigned char *usbbuff; - unsigned long dithersum; - int iminval = 0, imaxval = 0; /* min and max quantized integers */ - - /* datatype of input array is double. We only support writing this datatype - to a FITS image with BITPIX = -64 or -32, except we also support the special case where - BITPIX = 32 and BZERO = 0 and BSCALE = 1. */ - - if ((zbitpix != LONG_IMG && zbitpix != DOUBLE_IMG && zbitpix != FLOAT_IMG) || scale != 1.0 || zero != 0.) { - ffpmsg("Implicit datatype conversion is not supported when writing to compressed images"); - return(*status = DATA_COMPRESSION_ERR); - } - - *intlength = 4; - idata = (int *) tiledata; - - /* if the tile-compressed table contains zscale and zzero columns */ - /* then scale and quantize the input floating point data. */ - - if ((outfptr->Fptr)->cn_zscale > 0) { - /* quantize the float values into integers */ - - if (nullcheck == 1) - floatnull = *(float *) (nullflagval); - else - floatnull = FLOATNULLVALUE; /* NaNs are represented by this, by default */ - - if ((outfptr->Fptr)->quantize_method == SUBTRACTIVE_DITHER_1 || - (outfptr->Fptr)->quantize_method == SUBTRACTIVE_DITHER_2) { - - /* see if the dithering offset value needs to be initialized */ - if ((outfptr->Fptr)->request_dither_seed == 0 && (outfptr->Fptr)->dither_seed == 0) { - - /* This means randomly choose the dithering offset based on the system time. */ - /* The offset will have a value between 1 and 10000, inclusive. */ - /* The time function returns an integer value that is incremented each second. */ - /* The clock function returns the elapsed CPU time, in integer CLOCKS_PER_SEC units. */ - /* The CPU time returned by clock is typically (on linux PC) only good to 0.01 sec */ - /* Summing the 2 quantities may help avoid cases where 2 executions of the program */ - /* (perhaps in a multithreaded environoment) end up with exactly the same dither seed */ - /* value. The sum is incremented by the current HDU number in the file to provide */ - /* further randomization. This randomization is desireable if multiple compressed */ - /* images will be summed (or differenced). In such cases, the benefits of dithering */ - /* may be lost if all the images use exactly the same sequence of random numbers when */ - /* calculating the dithering offsets. */ - - (outfptr->Fptr)->dither_seed = - (( (int)time(NULL) + ( (int) clock() / (int) (CLOCKS_PER_SEC / 100)) + (outfptr->Fptr)->curhdu) % 10000) + 1; - - /* update the header keyword with this new value */ - fits_update_key(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->dither_seed), - NULL, status); - - } else if ((outfptr->Fptr)->request_dither_seed < 0 && (outfptr->Fptr)->dither_seed < 0) { - - /* this means randomly choose the dithering offset based on some hash function */ - /* of the first input tile of data to be quantized and compressed. This ensures that */ - /* the same offset value is used for a given image every time it is compressed. */ - - usbbuff = (unsigned char *) tiledata; - dithersum = 0; - for (ii = 0; ii < 4 * tilelen; ii++) { - dithersum += usbbuff[ii]; /* doesn't matter if there is an integer overflow */ - } - (outfptr->Fptr)->dither_seed = ((int) (dithersum % 10000)) + 1; - - /* update the header keyword with this new value */ - fits_update_key(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->dither_seed), - NULL, status); - } - - /* subtract 1 to convert from 1-based to 0-based element number */ - irow = row + (outfptr->Fptr)->dither_seed - 1; /* dither the quantized values */ - - } else if ((outfptr->Fptr)->quantize_method == -1) { - irow = 0; /* do not dither the quantized values */ - } else { - ffpmsg("Unknown dithering method."); - ffpmsg("May need to install a newer version of CFITSIO."); - return(*status = DATA_COMPRESSION_ERR); - } - - *flag = fits_quantize_float (irow, (float *) tiledata, tilenx, tileny, - nullcheck, floatnull, (outfptr->Fptr)->quantize_level, - (outfptr->Fptr)->quantize_method, idata, bscale, bzero, &iminval, &imaxval); - - if (*flag > 1) - return(*status = *flag); - } - else if ((outfptr->Fptr)->quantize_level != NO_QUANTIZE) - { - /* if floating point pixels are not being losslessly compressed, then */ - /* input float data is implicitly converted (truncated) to integers */ - if ((scale != 1. || zero != 0.)) /* must scale the values */ - imcomp_nullscalefloats((float *) tiledata, tilelen, idata, scale, zero, - nullcheck, *(float *) (nullflagval), nullval, status); - else - imcomp_nullfloats((float *) tiledata, tilelen, idata, - nullcheck, *(float *) (nullflagval), nullval, status); - } - else if ((outfptr->Fptr)->quantize_level == NO_QUANTIZE) - { - /* just convert null values to NaNs in place, if necessary, then do lossless gzip compression */ - if (nullcheck == 1) { - imcomp_float2nan((float *) tiledata, tilelen, (int *) tiledata, - *(float *) (nullflagval), status); - } - } - - return(*status); -} - /*--------------------------------------------------------------------------*/ -int imcomp_convert_tile_tdouble( - fitsfile *outfptr, - long row, - void *tiledata, - long tilelen, - long tilenx, - long tileny, - int nullcheck, - void *nullflagval, - int nullval, - int zbitpix, - double scale, - double zero, - int *intlength, - int *flag, - double *bscale, - double *bzero, - int *status) -{ - /* Prepare the input tile array of pixels for compression. */ - /* Convert input double tile array in place to 4-byte ints for compression, */ - /* If needed, convert 4 or 8-byte ints and do null value substitution. */ - /* Note that the calling routine must have allocated the input array big enough */ - /* to be able to do this. */ - - int *idata; - long irow, ii; - double doublenull; - unsigned char *usbbuff; - unsigned long dithersum; - int iminval = 0, imaxval = 0; /* min and max quantized integers */ - - /* datatype of input array is double. We only support writing this datatype - to a FITS image with BITPIX = -64 or -32, except we also support the special case where - BITPIX = 32 and BZERO = 0 and BSCALE = 1. */ - - if ((zbitpix != LONG_IMG && zbitpix != DOUBLE_IMG && zbitpix != FLOAT_IMG) || scale != 1.0 || zero != 0.) { - ffpmsg("Implicit datatype conversion is not supported when writing to compressed images"); - return(*status = DATA_COMPRESSION_ERR); - } - - *intlength = 4; - idata = (int *) tiledata; - - /* if the tile-compressed table contains zscale and zzero columns */ - /* then scale and quantize the input floating point data. */ - /* Otherwise, just truncate the floats to integers. */ - - if ((outfptr->Fptr)->cn_zscale > 0) - { - if (nullcheck == 1) - doublenull = *(double *) (nullflagval); - else - doublenull = DOUBLENULLVALUE; - - /* quantize the double values into integers */ - if ((outfptr->Fptr)->quantize_method == SUBTRACTIVE_DITHER_1 || - (outfptr->Fptr)->quantize_method == SUBTRACTIVE_DITHER_2) { - - /* see if the dithering offset value needs to be initialized (see above) */ - if ((outfptr->Fptr)->request_dither_seed == 0 && (outfptr->Fptr)->dither_seed == 0) { - - (outfptr->Fptr)->dither_seed = - (( (int)time(NULL) + ( (int) clock() / (int) (CLOCKS_PER_SEC / 100)) + (outfptr->Fptr)->curhdu) % 10000) + 1; - - /* update the header keyword with this new value */ - fits_update_key(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->dither_seed), - NULL, status); - - } else if ((outfptr->Fptr)->request_dither_seed < 0 && (outfptr->Fptr)->dither_seed < 0) { - - usbbuff = (unsigned char *) tiledata; - dithersum = 0; - for (ii = 0; ii < 8 * tilelen; ii++) { - dithersum += usbbuff[ii]; - } - (outfptr->Fptr)->dither_seed = ((int) (dithersum % 10000)) + 1; - - /* update the header keyword with this new value */ - fits_update_key(outfptr, TINT, "ZDITHER0", &((outfptr->Fptr)->dither_seed), - NULL, status); - } - - irow = row + (outfptr->Fptr)->dither_seed - 1; /* dither the quantized values */ - - } else if ((outfptr->Fptr)->quantize_method == -1) { - irow = 0; /* do not dither the quantized values */ - } else { - ffpmsg("Unknown subtractive dithering method."); - ffpmsg("May need to install a newer version of CFITSIO."); - return(*status = DATA_COMPRESSION_ERR); - } - - *flag = fits_quantize_double (irow, (double *) tiledata, tilenx, tileny, - nullcheck, doublenull, (outfptr->Fptr)->quantize_level, - (outfptr->Fptr)->quantize_method, idata, - bscale, bzero, &iminval, &imaxval); - - if (*flag > 1) - return(*status = *flag); - } - else if ((outfptr->Fptr)->quantize_level != NO_QUANTIZE) - { - /* if floating point pixels are not being losslessly compressed, then */ - /* input float data is implicitly converted (truncated) to integers */ - if ((scale != 1. || zero != 0.)) /* must scale the values */ - imcomp_nullscaledoubles((double *) tiledata, tilelen, idata, scale, zero, - nullcheck, *(double *) (nullflagval), nullval, status); - else - imcomp_nulldoubles((double *) tiledata, tilelen, idata, - nullcheck, *(double *) (nullflagval), nullval, status); - } - else if ((outfptr->Fptr)->quantize_level == NO_QUANTIZE) - { - /* just convert null values to NaNs in place, if necessary, then do lossless gzip compression */ - if (nullcheck == 1) { - imcomp_double2nan((double *) tiledata, tilelen, (LONGLONG *) tiledata, - *(double *) (nullflagval), status); - } - } - - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_nullscale( - int *idata, - long tilelen, - int nullflagval, - int nullval, - double scale, - double zero, - int *status) -/* - do null value substitution AND scaling of the integer array. - If array value = nullflagval, then set the value to nullval. - Otherwise, inverse scale the integer value. -*/ -{ - long ii; - double dvalue; - - for (ii=0; ii < tilelen; ii++) - { - if (idata[ii] == nullflagval) - idata[ii] = nullval; - else - { - dvalue = (idata[ii] - zero) / scale; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_nullvalues( - int *idata, - long tilelen, - int nullflagval, - int nullval, - int *status) -/* - do null value substitution. - If array value = nullflagval, then set the value to nullval. -*/ -{ - long ii; - - for (ii=0; ii < tilelen; ii++) - { - if (idata[ii] == nullflagval) - idata[ii] = nullval; - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_scalevalues( - int *idata, - long tilelen, - double scale, - double zero, - int *status) -/* - do inverse scaling the integer values. -*/ -{ - long ii; - double dvalue; - - for (ii=0; ii < tilelen; ii++) - { - dvalue = (idata[ii] - zero) / scale; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_nullscalei2( - short *idata, - long tilelen, - short nullflagval, - short nullval, - double scale, - double zero, - int *status) -/* - do null value substitution AND scaling of the integer array. - If array value = nullflagval, then set the value to nullval. - Otherwise, inverse scale the integer value. -*/ -{ - long ii; - double dvalue; - - for (ii=0; ii < tilelen; ii++) - { - if (idata[ii] == nullflagval) - idata[ii] = nullval; - else - { - dvalue = (idata[ii] - zero) / scale; - - if (dvalue < DSHRT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = SHRT_MIN; - } - else if (dvalue > DSHRT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = SHRT_MAX; - } - else - { - if (dvalue >= 0) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_nullvaluesi2( - short *idata, - long tilelen, - short nullflagval, - short nullval, - int *status) -/* - do null value substitution. - If array value = nullflagval, then set the value to nullval. -*/ -{ - long ii; - - for (ii=0; ii < tilelen; ii++) - { - if (idata[ii] == nullflagval) - idata[ii] = nullval; - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_scalevaluesi2( - short *idata, - long tilelen, - double scale, - double zero, - int *status) -/* - do inverse scaling the integer values. -*/ -{ - long ii; - double dvalue; - - for (ii=0; ii < tilelen; ii++) - { - dvalue = (idata[ii] - zero) / scale; - - if (dvalue < DSHRT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = SHRT_MIN; - } - else if (dvalue > DSHRT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = SHRT_MAX; - } - else - { - if (dvalue >= 0) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_nullfloats( - float *fdata, - long tilelen, - int *idata, - int nullcheck, - float nullflagval, - int nullval, - int *status) -/* - do null value substitution of the float array. - If array value = nullflagval, then set the output value to FLOATNULLVALUE. -*/ -{ - long ii; - double dvalue; - - if (nullcheck == 1) /* must check for null values */ - { - for (ii=0; ii < tilelen; ii++) - { - if (fdata[ii] == nullflagval) - idata[ii] = nullval; - else - { - dvalue = fdata[ii]; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - } - } - else /* don't have to worry about null values */ - { - for (ii=0; ii < tilelen; ii++) - { - dvalue = fdata[ii]; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_nullscalefloats( - float *fdata, - long tilelen, - int *idata, - double scale, - double zero, - int nullcheck, - float nullflagval, - int nullval, - int *status) -/* - do null value substitution of the float array. - If array value = nullflagval, then set the output value to FLOATNULLVALUE. - Otherwise, inverse scale the integer value. -*/ -{ - long ii; - double dvalue; - - if (nullcheck == 1) /* must check for null values */ - { - for (ii=0; ii < tilelen; ii++) - { - if (fdata[ii] == nullflagval) - idata[ii] = nullval; - else - { - dvalue = (fdata[ii] - zero) / scale; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0.) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - } - } - else /* don't have to worry about null values */ - { - for (ii=0; ii < tilelen; ii++) - { - dvalue = (fdata[ii] - zero) / scale; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0.) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_nulldoubles( - double *fdata, - long tilelen, - int *idata, - int nullcheck, - double nullflagval, - int nullval, - int *status) -/* - do null value substitution of the float array. - If array value = nullflagval, then set the output value to FLOATNULLVALUE. - Otherwise, inverse scale the integer value. -*/ -{ - long ii; - double dvalue; - - if (nullcheck == 1) /* must check for null values */ - { - for (ii=0; ii < tilelen; ii++) - { - if (fdata[ii] == nullflagval) - idata[ii] = nullval; - else - { - dvalue = fdata[ii]; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0.) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - } - } - else /* don't have to worry about null values */ - { - for (ii=0; ii < tilelen; ii++) - { - dvalue = fdata[ii]; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0.) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int imcomp_nullscaledoubles( - double *fdata, - long tilelen, - int *idata, - double scale, - double zero, - int nullcheck, - double nullflagval, - int nullval, - int *status) -/* - do null value substitution of the float array. - If array value = nullflagval, then set the output value to FLOATNULLVALUE. - Otherwise, inverse scale the integer value. -*/ -{ - long ii; - double dvalue; - - if (nullcheck == 1) /* must check for null values */ - { - for (ii=0; ii < tilelen; ii++) - { - if (fdata[ii] == nullflagval) - idata[ii] = nullval; - else - { - dvalue = (fdata[ii] - zero) / scale; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0.) - idata[ii] = (int) (dvalue + .5); - else - idata[ii] = (int) (dvalue - .5); - } - } - } - } - else /* don't have to worry about null values */ - { - for (ii=0; ii < tilelen; ii++) - { - dvalue = (fdata[ii] - zero) / scale; - - if (dvalue < DINT_MIN) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MIN; - } - else if (dvalue > DINT_MAX) - { - *status = OVERFLOW_ERR; - idata[ii] = INT32_MAX; - } - else - { - if (dvalue >= 0.) - idata[ii] = (int) (dvalue + .5); + nullarray[ii] = 1; + } else - idata[ii] = (int) (dvalue - .5); - } - } - } - return(*status); -} -/*---------------------------------------------------------------------------*/ -int fits_write_compressed_img(fitsfile *fptr, /* I - FITS file pointer */ - int datatype, /* I - datatype of the array to be written */ - long *infpixel, /* I - 'bottom left corner' of the subsection */ - long *inlpixel, /* I - 'top right corner' of the subsection */ - int nullcheck, /* I - 0 for no null checking */ - /* 1: pixels that are = nullval will be */ - /* written with the FITS null pixel value */ - /* (floating point arrays only) */ - void *array, /* I - array of values to be written */ - void *nullval, /* I - undefined pixel value */ - int *status) /* IO - error status */ -/* - Write a section of a compressed image. -*/ -{ - int tiledim[MAX_COMPRESS_DIM]; - long naxis[MAX_COMPRESS_DIM]; - long tilesize[MAX_COMPRESS_DIM], thistilesize[MAX_COMPRESS_DIM]; - long ftile[MAX_COMPRESS_DIM], ltile[MAX_COMPRESS_DIM]; - long tfpixel[MAX_COMPRESS_DIM], tlpixel[MAX_COMPRESS_DIM]; - long rowdim[MAX_COMPRESS_DIM], offset[MAX_COMPRESS_DIM],ntemp; - long fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM]; - long i5, i4, i3, i2, i1, i0, irow, trowsize, ntrows; - int ii, ndim, pixlen, tilenul; - int tstatus, buffpixsiz; - void *buffer; - char *bnullarray = 0, card[FLEN_CARD]; - - if (*status > 0) - return(*status); - - if (!fits_is_compressed_image(fptr, status) ) - { - ffpmsg("CHDU is not a compressed image (fits_write_compressed_img)"); - return(*status = DATA_COMPRESSION_ERR); - } - - /* reset position to the correct HDU if necessary */ - if (fptr->HDUposition != (fptr->Fptr)->curhdu) - ffmahd(fptr, (fptr->HDUposition) + 1, NULL, status); - - /* rescan header if data structure is undefined */ - else if ((fptr->Fptr)->datastart == DATA_UNDEFINED) - if ( ffrdef(fptr, status) > 0) - return(*status); - - - /* ===================================================================== */ - - - if (datatype == TSHORT || datatype == TUSHORT) - { - pixlen = sizeof(short); - } - else if (datatype == TINT || datatype == TUINT) - { - pixlen = sizeof(int); - } - else if (datatype == TBYTE || datatype == TSBYTE) - { - pixlen = 1; - } - else if (datatype == TLONG || datatype == TULONG) - { - pixlen = sizeof(long); - } - else if (datatype == TFLOAT) - { - pixlen = sizeof(float); - } - else if (datatype == TDOUBLE) - { - pixlen = sizeof(double); - } - else - { - ffpmsg("unsupported datatype for compressing image"); - return(*status = BAD_DATATYPE); - } - - /* ===================================================================== */ - - /* allocate scratch space for processing one tile of the image */ - buffpixsiz = pixlen; /* this is the minimum pixel size */ - - if ( (fptr->Fptr)->compress_type == HCOMPRESS_1) { /* need 4 or 8 bytes per pixel */ - if ((fptr->Fptr)->zbitpix == BYTE_IMG || - (fptr->Fptr)->zbitpix == SHORT_IMG ) - buffpixsiz = maxvalue(buffpixsiz, 4); - else - buffpixsiz = 8; - } - else if ( (fptr->Fptr)->compress_type == PLIO_1) { /* need 4 bytes per pixel */ - buffpixsiz = maxvalue(buffpixsiz, 4); - } - else if ( (fptr->Fptr)->compress_type == RICE_1 || - (fptr->Fptr)->compress_type == GZIP_1 || - (fptr->Fptr)->compress_type == GZIP_2 || - (fptr->Fptr)->compress_type == BZIP2_1) { /* need 1, 2, or 4 bytes per pixel */ - if ((fptr->Fptr)->zbitpix == BYTE_IMG) - buffpixsiz = maxvalue(buffpixsiz, 1); - else if ((fptr->Fptr)->zbitpix == SHORT_IMG) - buffpixsiz = maxvalue(buffpixsiz, 2); - else - buffpixsiz = maxvalue(buffpixsiz, 4); - } - else - { - ffpmsg("unsupported image compression algorithm"); - return(*status = BAD_DATATYPE); - } - - /* cast to double to force alignment on 8-byte addresses */ - buffer = (double *) calloc ((fptr->Fptr)->maxtilelen, buffpixsiz); - - if (buffer == NULL) - { - ffpmsg("Out of memory (fits_write_compress_img)"); - return (*status = MEMORY_ALLOCATION); - } - - /* ===================================================================== */ - - /* initialize all the arrays */ - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - naxis[ii] = 1; - tiledim[ii] = 1; - tilesize[ii] = 1; - ftile[ii] = 1; - ltile[ii] = 1; - rowdim[ii] = 1; - } - - ndim = (fptr->Fptr)->zndim; - ntemp = 1; - for (ii = 0; ii < ndim; ii++) - { - fpixel[ii] = infpixel[ii]; - lpixel[ii] = inlpixel[ii]; - - /* calc number of tiles in each dimension, and tile containing */ - /* the first and last pixel we want to read in each dimension */ - naxis[ii] = (fptr->Fptr)->znaxis[ii]; - if (fpixel[ii] < 1) - { - free(buffer); - return(*status = BAD_PIX_NUM); - } - - tilesize[ii] = (fptr->Fptr)->tilesize[ii]; - tiledim[ii] = (naxis[ii] - 1) / tilesize[ii] + 1; - ftile[ii] = (fpixel[ii] - 1) / tilesize[ii] + 1; - ltile[ii] = minvalue((lpixel[ii] - 1) / tilesize[ii] + 1, - tiledim[ii]); - rowdim[ii] = ntemp; /* total tiles in each dimension */ - ntemp *= tiledim[ii]; - } - - /* support up to 6 dimensions for now */ - /* tfpixel and tlpixel are the first and last image pixels */ - /* along each dimension of the compression tile */ - for (i5 = ftile[5]; i5 <= ltile[5]; i5++) - { - tfpixel[5] = (i5 - 1) * tilesize[5] + 1; - tlpixel[5] = minvalue(tfpixel[5] + tilesize[5] - 1, - naxis[5]); - thistilesize[5] = tlpixel[5] - tfpixel[5] + 1; - offset[5] = (i5 - 1) * rowdim[5]; - for (i4 = ftile[4]; i4 <= ltile[4]; i4++) - { - tfpixel[4] = (i4 - 1) * tilesize[4] + 1; - tlpixel[4] = minvalue(tfpixel[4] + tilesize[4] - 1, - naxis[4]); - thistilesize[4] = thistilesize[5] * (tlpixel[4] - tfpixel[4] + 1); - offset[4] = (i4 - 1) * rowdim[4] + offset[5]; - for (i3 = ftile[3]; i3 <= ltile[3]; i3++) - { - tfpixel[3] = (i3 - 1) * tilesize[3] + 1; - tlpixel[3] = minvalue(tfpixel[3] + tilesize[3] - 1, - naxis[3]); - thistilesize[3] = thistilesize[4] * (tlpixel[3] - tfpixel[3] + 1); - offset[3] = (i3 - 1) * rowdim[3] + offset[4]; - for (i2 = ftile[2]; i2 <= ltile[2]; i2++) - { - tfpixel[2] = (i2 - 1) * tilesize[2] + 1; - tlpixel[2] = minvalue(tfpixel[2] + tilesize[2] - 1, - naxis[2]); - thistilesize[2] = thistilesize[3] * (tlpixel[2] - tfpixel[2] + 1); - offset[2] = (i2 - 1) * rowdim[2] + offset[3]; - for (i1 = ftile[1]; i1 <= ltile[1]; i1++) - { - tfpixel[1] = (i1 - 1) * tilesize[1] + 1; - tlpixel[1] = minvalue(tfpixel[1] + tilesize[1] - 1, - naxis[1]); - thistilesize[1] = thistilesize[2] * (tlpixel[1] - tfpixel[1] + 1); - offset[1] = (i1 - 1) * rowdim[1] + offset[2]; - for (i0 = ftile[0]; i0 <= ltile[0]; i0++) - { - tfpixel[0] = (i0 - 1) * tilesize[0] + 1; - tlpixel[0] = minvalue(tfpixel[0] + tilesize[0] - 1, - naxis[0]); - thistilesize[0] = thistilesize[1] * (tlpixel[0] - tfpixel[0] + 1); - /* calculate row of table containing this tile */ - irow = i0 + offset[1]; - - /* read and uncompress this row (tile) of the table */ - /* also do type conversion and undefined pixel substitution */ - /* at this point */ - imcomp_decompress_tile(fptr, irow, thistilesize[0], - datatype, nullcheck, nullval, buffer, bnullarray, &tilenul, - status); - - if (*status == NO_COMPRESSED_TILE) - { - /* tile doesn't exist, so initialize to zero */ - memset(buffer, 0, pixlen * thistilesize[0]); - *status = 0; - } - - /* copy the intersecting pixels to this tile from the input */ - imcomp_merge_overlap(buffer, pixlen, ndim, tfpixel, tlpixel, - bnullarray, array, fpixel, lpixel, nullcheck, status); - - /* Collapse sizes of higher dimension tiles into 2 dimensional - equivalents needed by the quantizing algorithms for - floating point types */ - fits_calc_tile_rows(tlpixel, tfpixel, ndim, &trowsize, - &ntrows, status); - - /* compress the tile again, and write it back to the FITS file */ - imcomp_compress_tile (fptr, irow, datatype, buffer, - thistilesize[0], - trowsize, - ntrows, - nullcheck, nullval, - status); - } - } - } - } - } - } - free(buffer); - - - if ((fptr->Fptr)->zbitpix < 0 && nullcheck != 0) { -/* - This is a floating point FITS image with possible null values. - It is too messy to test if any null values are actually written, so - just assume so. We need to make sure that the - ZBLANK keyword is present in the compressed image header. If it is not - there then we need to insert the keyword. -*/ - tstatus = 0; - ffgcrd(fptr, "ZBLANK", card, &tstatus); - - if (tstatus) { /* have to insert the ZBLANK keyword */ - ffgcrd(fptr, "ZCMPTYPE", card, status); - ffikyj(fptr, "ZBLANK", COMPRESS_NULL_VALUE, - "null value in the compressed integer array", status); - - /* set this value into the internal structure; it is used if */ - /* the program reads back the values from the array */ - - (fptr->Fptr)->zblank = COMPRESS_NULL_VALUE; - (fptr->Fptr)->cn_zblank = -1; /* flag for a constant ZBLANK */ - } - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_write_compressed_pixels(fitsfile *fptr, /* I - FITS file pointer */ - int datatype, /* I - datatype of the array to be written */ - LONGLONG fpixel, /* I - 'first pixel to write */ - LONGLONG npixel, /* I - number of pixels to write */ - int nullcheck, /* I - 0 for no null checking */ - /* 1: pixels that are = nullval will be */ - /* written with the FITS null pixel value */ - /* (floating point arrays only) */ - void *array, /* I - array of values to write */ - void *nullval, /* I - value used to represent undefined pixels*/ - int *status) /* IO - error status */ -/* - Write a consecutive set of pixels to a compressed image. This routine - interpretes the n-dimensional image as a long one-dimensional array. - This is actually a rather inconvenient way to write compressed images in - general, and could be rather inefficient if the requested pixels to be - written are located in many different image compression tiles. - - The general strategy used here is to write the requested pixels in blocks - that correspond to rectangular image sections. -*/ -{ - int naxis, ii, bytesperpixel; - long naxes[MAX_COMPRESS_DIM], nread; - LONGLONG tfirst, tlast, last0, last1, dimsize[MAX_COMPRESS_DIM]; - long nplane, firstcoord[MAX_COMPRESS_DIM], lastcoord[MAX_COMPRESS_DIM]; - char *arrayptr; - - if (*status > 0) - return(*status); - - arrayptr = (char *) array; - - /* get size of array pixels, in bytes */ - bytesperpixel = ffpxsz(datatype); - - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - naxes[ii] = 1; - firstcoord[ii] = 0; - lastcoord[ii] = 0; - } - - /* determine the dimensions of the image to be written */ - ffgidm(fptr, &naxis, status); - ffgisz(fptr, MAX_COMPRESS_DIM, naxes, status); - - /* calc the cumulative number of pixels in each successive dimension */ - dimsize[0] = 1; - for (ii = 1; ii < MAX_COMPRESS_DIM; ii++) - dimsize[ii] = dimsize[ii - 1] * naxes[ii - 1]; - - /* determine the coordinate of the first and last pixel in the image */ - /* Use zero based indexes here */ - tfirst = fpixel - 1; - tlast = tfirst + npixel - 1; - for (ii = naxis - 1; ii >= 0; ii--) - { - firstcoord[ii] = (long) (tfirst / dimsize[ii]); - lastcoord[ii] = (long) (tlast / dimsize[ii]); - tfirst = tfirst - firstcoord[ii] * dimsize[ii]; - tlast = tlast - lastcoord[ii] * dimsize[ii]; - } - - /* to simplify things, treat 1-D, 2-D, and 3-D images as separate cases */ - - if (naxis == 1) - { - /* Simple: just write the requested range of pixels */ - - firstcoord[0] = firstcoord[0] + 1; - lastcoord[0] = lastcoord[0] + 1; - fits_write_compressed_img(fptr, datatype, firstcoord, lastcoord, - nullcheck, array, nullval, status); - return(*status); - } - else if (naxis == 2) - { - nplane = 0; /* write 1st (and only) plane of the image */ - fits_write_compressed_img_plane(fptr, datatype, bytesperpixel, - nplane, firstcoord, lastcoord, naxes, nullcheck, - array, nullval, &nread, status); - } - else if (naxis == 3) - { - /* test for special case: writing an integral number of planes */ - if (firstcoord[0] == 0 && firstcoord[1] == 0 && - lastcoord[0] == naxes[0] - 1 && lastcoord[1] == naxes[1] - 1) - { - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - /* convert from zero base to 1 base */ - (firstcoord[ii])++; - (lastcoord[ii])++; - } - - /* we can write the contiguous block of pixels in one go */ - fits_write_compressed_img(fptr, datatype, firstcoord, lastcoord, - nullcheck, array, nullval, status); - return(*status); - } - - /* save last coordinate in temporary variables */ - last0 = lastcoord[0]; - last1 = lastcoord[1]; - - if (firstcoord[2] < lastcoord[2]) - { - /* we will write up to the last pixel in all but the last plane */ - lastcoord[0] = naxes[0] - 1; - lastcoord[1] = naxes[1] - 1; - } - - /* write one plane of the cube at a time, for simplicity */ - for (nplane = firstcoord[2]; nplane <= lastcoord[2]; nplane++) - { - if (nplane == lastcoord[2]) - { - lastcoord[0] = (long) last0; - lastcoord[1] = (long) last1; - } - - fits_write_compressed_img_plane(fptr, datatype, bytesperpixel, - nplane, firstcoord, lastcoord, naxes, nullcheck, - arrayptr, nullval, &nread, status); - - /* for all subsequent planes, we start with the first pixel */ - firstcoord[0] = 0; - firstcoord[1] = 0; - - /* increment pointers to next elements to be written */ - arrayptr = arrayptr + nread * bytesperpixel; - } - } - else - { - ffpmsg("only 1D, 2D, or 3D images are currently supported"); - return(*status = DATA_COMPRESSION_ERR); - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_write_compressed_img_plane(fitsfile *fptr, /* I - FITS file */ - int datatype, /* I - datatype of the array to be written */ - int bytesperpixel, /* I - number of bytes per pixel in array */ - long nplane, /* I - which plane of the cube to write */ - long *firstcoord, /* I coordinate of first pixel to write */ - long *lastcoord, /* I coordinate of last pixel to write */ - long *naxes, /* I size of each image dimension */ - int nullcheck, /* I - 0 for no null checking */ - /* 1: pixels that are = nullval will be */ - /* written with the FITS null pixel value */ - /* (floating point arrays only) */ - void *array, /* I - array of values that are written */ - void *nullval, /* I - value for undefined pixels */ - long *nread, /* O - total number of pixels written */ - int *status) /* IO - error status */ - - /* - in general we have to write the first partial row of the image, - followed by the middle complete rows, followed by the last - partial row of the image. If the first or last rows are complete, - then write them at the same time as all the middle rows. - */ -{ - /* bottom left coord. and top right coord. */ - long blc[MAX_COMPRESS_DIM], trc[MAX_COMPRESS_DIM]; - char *arrayptr; - - *nread = 0; - - arrayptr = (char *) array; - - blc[2] = nplane + 1; - trc[2] = nplane + 1; - - if (firstcoord[0] != 0) - { - /* have to read a partial first row */ - blc[0] = firstcoord[0] + 1; - blc[1] = firstcoord[1] + 1; - trc[1] = blc[1]; - if (lastcoord[1] == firstcoord[1]) - trc[0] = lastcoord[0] + 1; /* 1st and last pixels in same row */ - else - trc[0] = naxes[0]; /* read entire rest of the row */ - - fits_write_compressed_img(fptr, datatype, blc, trc, - nullcheck, arrayptr, nullval, status); - - *nread = *nread + trc[0] - blc[0] + 1; - - if (lastcoord[1] == firstcoord[1]) - { - return(*status); /* finished */ - } - - /* set starting coord to beginning of next line */ - firstcoord[0] = 0; - firstcoord[1] += 1; - arrayptr = arrayptr + (trc[0] - blc[0] + 1) * bytesperpixel; - } - - /* write contiguous complete rows of the image, if any */ - blc[0] = 1; - blc[1] = firstcoord[1] + 1; - trc[0] = naxes[0]; - - if (lastcoord[0] + 1 == naxes[0]) - { - /* can write the last complete row, too */ - trc[1] = lastcoord[1] + 1; - } - else - { - /* last row is incomplete; have to read it separately */ - trc[1] = lastcoord[1]; - } - - if (trc[1] >= blc[1]) /* must have at least one whole line to read */ - { - fits_write_compressed_img(fptr, datatype, blc, trc, - nullcheck, arrayptr, nullval, status); - - *nread = *nread + (trc[1] - blc[1] + 1) * naxes[0]; - - if (lastcoord[1] + 1 == trc[1]) - return(*status); /* finished */ - - /* increment pointers for the last partial row */ - arrayptr = arrayptr + (trc[1] - blc[1] + 1) * naxes[0] * bytesperpixel; - - } - - if (trc[1] == lastcoord[1] + 1) - return(*status); /* all done */ - - /* set starting and ending coord to last line */ - - trc[0] = lastcoord[0] + 1; - trc[1] = lastcoord[1] + 1; - blc[1] = trc[1]; - - fits_write_compressed_img(fptr, datatype, blc, trc, - nullcheck, arrayptr, nullval, status); - - *nread = *nread + trc[0] - blc[0] + 1; - - return(*status); -} - -/* ######################################################################## */ -/* ### Image Decompression Routines ### */ -/* ######################################################################## */ - -/*--------------------------------------------------------------------------*/ -int fits_img_decompress (fitsfile *infptr, /* image (bintable) to uncompress */ - fitsfile *outfptr, /* empty HDU for output uncompressed image */ - int *status) /* IO - error status */ - -/* - This routine decompresses the whole image and writes it to the output file. -*/ - -{ - int ii, datatype = 0; - int nullcheck, anynul; - LONGLONG fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM]; - long inc[MAX_COMPRESS_DIM]; - long imgsize; - float *nulladdr, fnulval; - double dnulval; - - if (fits_img_decompress_header(infptr, outfptr, status) > 0) - { - return (*status); - } - - /* force a rescan of the output header keywords, then reset the scaling */ - /* in case the BSCALE and BZERO keywords are present, so that the */ - /* decompressed values won't be scaled when written to the output image */ - ffrdef(outfptr, status); - ffpscl(outfptr, 1.0, 0.0, status); - ffpscl(infptr, 1.0, 0.0, status); - - /* initialize; no null checking is needed for integer images */ - nullcheck = 0; - nulladdr = &fnulval; - - /* determine datatype for image */ - if ((infptr->Fptr)->zbitpix == BYTE_IMG) - { - datatype = TBYTE; - } - else if ((infptr->Fptr)->zbitpix == SHORT_IMG) - { - datatype = TSHORT; - } - else if ((infptr->Fptr)->zbitpix == LONG_IMG) - { - datatype = TINT; - } - else if ((infptr->Fptr)->zbitpix == FLOAT_IMG) - { - /* In the case of float images we must check for NaNs */ - nullcheck = 1; - fnulval = FLOATNULLVALUE; - nulladdr = &fnulval; - datatype = TFLOAT; - } - else if ((infptr->Fptr)->zbitpix == DOUBLE_IMG) - { - /* In the case of double images we must check for NaNs */ - nullcheck = 1; - dnulval = DOUBLENULLVALUE; - nulladdr = (float *) &dnulval; - datatype = TDOUBLE; - } - - /* calculate size of the image (in pixels) */ - imgsize = 1; - for (ii = 0; ii < (infptr->Fptr)->zndim; ii++) - { - imgsize *= (infptr->Fptr)->znaxis[ii]; - fpixel[ii] = 1; /* Set first and last pixel to */ - lpixel[ii] = (infptr->Fptr)->znaxis[ii]; /* include the entire image. */ - inc[ii] = 1; - } - - /* uncompress the input image and write to output image, one tile at a time */ - - fits_read_write_compressed_img(infptr, datatype, fpixel, lpixel, inc, - nullcheck, nulladdr, &anynul, outfptr, status); - - return (*status); -} -/*--------------------------------------------------------------------------*/ -int fits_decompress_img (fitsfile *infptr, /* image (bintable) to uncompress */ - fitsfile *outfptr, /* empty HDU for output uncompressed image */ - int *status) /* IO - error status */ - -/* - THIS IS AN OBSOLETE ROUTINE. USE fits_img_decompress instead!!! - - This routine decompresses the whole image and writes it to the output file. -*/ - -{ - double *data; - int ii, datatype = 0, byte_per_pix = 0; - int nullcheck, anynul; - LONGLONG fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM]; - long inc[MAX_COMPRESS_DIM]; - long imgsize, memsize; - float *nulladdr, fnulval; - double dnulval; - - if (*status > 0) - return(*status); - - if (!fits_is_compressed_image(infptr, status) ) - { - ffpmsg("CHDU is not a compressed image (fits_decompress_img)"); - return(*status = DATA_DECOMPRESSION_ERR); - } - - /* create an empty output image with the correct dimensions */ - if (ffcrim(outfptr, (infptr->Fptr)->zbitpix, (infptr->Fptr)->zndim, - (infptr->Fptr)->znaxis, status) > 0) - { - ffpmsg("error creating output decompressed image HDU"); - return (*status); - } - /* Copy the table header to the image header. */ - if (imcomp_copy_imheader(infptr, outfptr, status) > 0) - { - ffpmsg("error copying header of compressed image"); - return (*status); - } - - /* force a rescan of the output header keywords, then reset the scaling */ - /* in case the BSCALE and BZERO keywords are present, so that the */ - /* decompressed values won't be scaled when written to the output image */ - ffrdef(outfptr, status); - ffpscl(outfptr, 1.0, 0.0, status); - ffpscl(infptr, 1.0, 0.0, status); - - /* initialize; no null checking is needed for integer images */ - nullcheck = 0; - nulladdr = &fnulval; - - /* determine datatype for image */ - if ((infptr->Fptr)->zbitpix == BYTE_IMG) - { - datatype = TBYTE; - byte_per_pix = 1; - } - else if ((infptr->Fptr)->zbitpix == SHORT_IMG) - { - datatype = TSHORT; - byte_per_pix = sizeof(short); - } - else if ((infptr->Fptr)->zbitpix == LONG_IMG) - { - datatype = TINT; - byte_per_pix = sizeof(int); - } - else if ((infptr->Fptr)->zbitpix == FLOAT_IMG) - { - /* In the case of float images we must check for NaNs */ - nullcheck = 1; - fnulval = FLOATNULLVALUE; - nulladdr = &fnulval; - datatype = TFLOAT; - byte_per_pix = sizeof(float); - } - else if ((infptr->Fptr)->zbitpix == DOUBLE_IMG) - { - /* In the case of double images we must check for NaNs */ - nullcheck = 1; - dnulval = DOUBLENULLVALUE; - nulladdr = (float *) &dnulval; - datatype = TDOUBLE; - byte_per_pix = sizeof(double); - } - - /* calculate size of the image (in pixels) */ - imgsize = 1; - for (ii = 0; ii < (infptr->Fptr)->zndim; ii++) - { - imgsize *= (infptr->Fptr)->znaxis[ii]; - fpixel[ii] = 1; /* Set first and last pixel to */ - lpixel[ii] = (infptr->Fptr)->znaxis[ii]; /* include the entire image. */ - inc[ii] = 1; - } - /* Calc equivalent number of double pixels same size as whole the image. */ - /* We use double datatype to force the memory to be aligned properly */ - memsize = ((imgsize * byte_per_pix) - 1) / sizeof(double) + 1; - - /* allocate memory for the image */ - data = (double*) calloc (memsize, sizeof(double)); - if (!data) - { - ffpmsg("Couldn't allocate memory for the uncompressed image"); - return(*status = MEMORY_ALLOCATION); - } - - /* uncompress the entire image into memory */ - /* This routine should be enhanced sometime to only need enough */ - /* memory to uncompress one tile at a time. */ - fits_read_compressed_img(infptr, datatype, fpixel, lpixel, inc, - nullcheck, nulladdr, data, NULL, &anynul, status); - - /* write the image to the output file */ - if (anynul) - fits_write_imgnull(outfptr, datatype, 1, imgsize, data, nulladdr, - status); - else - fits_write_img(outfptr, datatype, 1, imgsize, data, status); - - free(data); - return (*status); -} -/*--------------------------------------------------------------------------*/ -int fits_img_decompress_header(fitsfile *infptr, /* image (bintable) to uncompress */ - fitsfile *outfptr, /* empty HDU for output uncompressed image */ - int *status) /* IO - error status */ - -/* - This routine reads the header of the input tile compressed image and - converts it to that of a standard uncompress FITS image. -*/ - -{ - int writeprime = 0; - int hdupos, inhdupos, numkeys; - int nullprime = 0, copyprime = 0, norec = 0, tstatus; - char card[FLEN_CARD]; - int ii, naxis, bitpix; - long naxes[MAX_COMPRESS_DIM]; - - if (*status > 0) - return(*status); - else if (*status == -1) { - *status = 0; - writeprime = 1; - } - - if (!fits_is_compressed_image(infptr, status) ) - { - ffpmsg("CHDU is not a compressed image (fits_img_decompress)"); - return(*status = DATA_DECOMPRESSION_ERR); - } - - /* get information about the state of the output file; does it already */ - /* contain any keywords and HDUs? */ - fits_get_hdu_num(infptr, &inhdupos); /* Get the current output HDU position */ - fits_get_hdu_num(outfptr, &hdupos); /* Get the current output HDU position */ - fits_get_hdrspace(outfptr, &numkeys, 0, status); - - /* Was the input compressed HDU originally the primary array image? */ - tstatus = 0; - if (!fits_read_card(infptr, "ZSIMPLE", card, &tstatus)) { - /* yes, input HDU was a primary array (not an IMAGE extension) */ - /* Now determine if we can uncompress it into the primary array of */ - /* the output file. This is only possible if the output file */ - /* currently only contains a null primary array, with no addition */ - /* header keywords and with no following extension in the FITS file. */ - - if (hdupos == 1) { /* are we positioned at the primary array? */ - if (numkeys == 0) { /* primary HDU is completely empty */ - nullprime = 1; - } else { - fits_get_img_param(outfptr, MAX_COMPRESS_DIM, &bitpix, &naxis, naxes, status); - - if (naxis == 0) { /* is this a null image? */ - nullprime = 1; - - if (inhdupos == 2) /* must be at the first extension */ - copyprime = 1; - } - } - } - } - - if (nullprime) { - /* We will delete the existing keywords in the null primary array - and uncompress the input image into the primary array of the output. - Some of these keywords may be added back to the uncompressed image - header later. - */ - - for (ii = numkeys; ii > 0; ii--) - fits_delete_record(outfptr, ii, status); - - } else { - - /* if the ZTENSION keyword doesn't exist, then we have to - write the required keywords manually */ - tstatus = 0; - if (fits_read_card(infptr, "ZTENSION", card, &tstatus)) { - - /* create an empty output image with the correct dimensions */ - if (ffcrim(outfptr, (infptr->Fptr)->zbitpix, (infptr->Fptr)->zndim, - (infptr->Fptr)->znaxis, status) > 0) - { - ffpmsg("error creating output decompressed image HDU"); - return (*status); - } - - norec = 1; /* the required keywords have already been written */ - - } else { /* the input compressed image does have ZTENSION keyword */ - - if (writeprime) { /* convert the image extension to a primary array */ - /* have to write the required keywords manually */ - - /* create an empty output image with the correct dimensions */ - if (ffcrim(outfptr, (infptr->Fptr)->zbitpix, (infptr->Fptr)->zndim, - (infptr->Fptr)->znaxis, status) > 0) - { - ffpmsg("error creating output decompressed image HDU"); - return (*status); - } - - norec = 1; /* the required keywords have already been written */ - - } else { /* write the input compressed image to an image extension */ - - if (numkeys == 0) { /* the output file is currently completely empty */ - - /* In this case, the input is a compressed IMAGE extension. */ - /* Since the uncompressed output file is currently completely empty, */ - /* we need to write a null primary array before uncompressing the */ - /* image extension */ - - ffcrim(outfptr, 8, 0, naxes, status); /* naxes is not used */ - - /* now create the empty extension to uncompress into */ - if (fits_create_hdu(outfptr, status) > 0) - { - ffpmsg("error creating output decompressed image HDU"); - return (*status); - } - - } else { - /* just create a new empty extension, then copy all the required */ - /* keywords into it. */ - fits_create_hdu(outfptr, status); - } - } - } - - } - - if (*status > 0) { - ffpmsg("error creating output decompressed image HDU"); - return (*status); - } - - /* Copy the table header to the image header. */ - - if (imcomp_copy_comp2img(infptr, outfptr, norec, status) > 0) - { - ffpmsg("error copying header keywords from compressed image"); - } - - if (copyprime) { - /* append any unexpected keywords from the primary array. - This includes any keywords except SIMPLE, BITPIX, NAXIS, - EXTEND, COMMENT, HISTORY, CHECKSUM, and DATASUM. - */ - - fits_movabs_hdu(infptr, 1, NULL, status); /* move to primary array */ - - /* do this so that any new keywords get written before any blank - keywords that may have been appended by imcomp_copy_comp2img */ - fits_set_hdustruc(outfptr, status); - - if (imcomp_copy_prime2img(infptr, outfptr, status) > 0) - { - ffpmsg("error copying primary keywords from compressed file"); - } - - fits_movabs_hdu(infptr, 2, NULL, status); /* move back to where we were */ - } - - return (*status); -} -/*---------------------------------------------------------------------------*/ -int fits_read_compressed_img(fitsfile *fptr, /* I - FITS file pointer */ - int datatype, /* I - datatype of the array to be returned */ - LONGLONG *infpixel, /* I - 'bottom left corner' of the subsection */ - LONGLONG *inlpixel, /* I - 'top right corner' of the subsection */ - long *ininc, /* I - increment to be applied in each dimension */ - int nullcheck, /* I - 0 for no null checking */ - /* 1: set undefined pixels = nullval */ - /* 2: set nullarray=1 for undefined pixels */ - void *nullval, /* I - value for undefined pixels */ - void *array, /* O - array of values that are returned */ - char *nullarray, /* O - array of flags = 1 if nullcheck = 2 */ - int *anynul, /* O - set to 1 if any values are null; else 0 */ - int *status) /* IO - error status */ -/* - Read a section of a compressed image; Note: lpixel may be larger than the - size of the uncompressed image. Only the pixels within the image will be - returned. -*/ -{ - long naxis[MAX_COMPRESS_DIM], tiledim[MAX_COMPRESS_DIM]; - long tilesize[MAX_COMPRESS_DIM], thistilesize[MAX_COMPRESS_DIM]; - long ftile[MAX_COMPRESS_DIM], ltile[MAX_COMPRESS_DIM]; - long tfpixel[MAX_COMPRESS_DIM], tlpixel[MAX_COMPRESS_DIM]; - long rowdim[MAX_COMPRESS_DIM], offset[MAX_COMPRESS_DIM],ntemp; - long fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM]; - long inc[MAX_COMPRESS_DIM]; - long i5, i4, i3, i2, i1, i0, irow; - int ii, ndim, pixlen, tilenul=0; - void *buffer; - char *bnullarray = 0; - double testnullval = 0.; - - if (*status > 0) - return(*status); - - if (!fits_is_compressed_image(fptr, status) ) - { - ffpmsg("CHDU is not a compressed image (fits_read_compressed_img)"); - return(*status = DATA_DECOMPRESSION_ERR); - } - - /* get temporary space for uncompressing one image tile */ - if (datatype == TSHORT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (short)); - pixlen = sizeof(short); - if (nullval) - testnullval = *(short *) nullval; - } - else if (datatype == TINT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (int)); - pixlen = sizeof(int); - if (nullval) - testnullval = *(int *) nullval; - } - else if (datatype == TLONG) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (long)); - pixlen = sizeof(long); - if (nullval) - testnullval = *(long *) nullval; - } - else if (datatype == TFLOAT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (float)); - pixlen = sizeof(float); - if (nullval) - testnullval = *(float *) nullval; - } - else if (datatype == TDOUBLE) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (double)); - pixlen = sizeof(double); - if (nullval) - testnullval = *(double *) nullval; - } - else if (datatype == TUSHORT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned short)); - pixlen = sizeof(short); - if (nullval) - testnullval = *(unsigned short *) nullval; - } - else if (datatype == TUINT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned int)); - pixlen = sizeof(int); - if (nullval) - testnullval = *(unsigned int *) nullval; - } - else if (datatype == TULONG) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned long)); - pixlen = sizeof(long); - if (nullval) - testnullval = *(unsigned long *) nullval; - } - else if (datatype == TBYTE || datatype == TSBYTE) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (char)); - pixlen = 1; - if (nullval) - testnullval = *(unsigned char *) nullval; - } - else - { - ffpmsg("unsupported datatype for uncompressing image"); - return(*status = BAD_DATATYPE); - } - - /* If nullcheck ==1 and nullval == 0, then this means that the */ - /* calling routine does not want to check for null pixels in the array */ - if (nullcheck == 1 && testnullval == 0.) - nullcheck = 0; - - if (buffer == NULL) - { - ffpmsg("Out of memory (fits_read_compress_img)"); - return (*status = MEMORY_ALLOCATION); - } - - /* allocate memory for a null flag array, if needed */ - if (nullcheck == 2) - { - bnullarray = calloc ((fptr->Fptr)->maxtilelen, sizeof (char)); - - if (bnullarray == NULL) - { - ffpmsg("Out of memory (fits_read_compress_img)"); - free(buffer); - return (*status = MEMORY_ALLOCATION); - } - } - - /* initialize all the arrays */ - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - naxis[ii] = 1; - tiledim[ii] = 1; - tilesize[ii] = 1; - ftile[ii] = 1; - ltile[ii] = 1; - rowdim[ii] = 1; - } - - ndim = (fptr->Fptr)->zndim; - ntemp = 1; - for (ii = 0; ii < ndim; ii++) - { - /* support for mirror-reversed image sections */ - if (infpixel[ii] <= inlpixel[ii]) - { - fpixel[ii] = (long) infpixel[ii]; - lpixel[ii] = (long) inlpixel[ii]; - inc[ii] = ininc[ii]; - } - else - { - fpixel[ii] = (long) inlpixel[ii]; - lpixel[ii] = (long) infpixel[ii]; - inc[ii] = -ininc[ii]; - } - - /* calc number of tiles in each dimension, and tile containing */ - /* the first and last pixel we want to read in each dimension */ - naxis[ii] = (fptr->Fptr)->znaxis[ii]; - if (fpixel[ii] < 1) - { - if (nullcheck == 2) - { - free(bnullarray); - } - free(buffer); - return(*status = BAD_PIX_NUM); - } - - tilesize[ii] = (fptr->Fptr)->tilesize[ii]; - tiledim[ii] = (naxis[ii] - 1) / tilesize[ii] + 1; - ftile[ii] = (fpixel[ii] - 1) / tilesize[ii] + 1; - ltile[ii] = minvalue((lpixel[ii] - 1) / tilesize[ii] + 1, - tiledim[ii]); - rowdim[ii] = ntemp; /* total tiles in each dimension */ - ntemp *= tiledim[ii]; - } - - if (anynul) - *anynul = 0; /* initialize */ - - /* support up to 6 dimensions for now */ - /* tfpixel and tlpixel are the first and last image pixels */ - /* along each dimension of the compression tile */ - for (i5 = ftile[5]; i5 <= ltile[5]; i5++) - { - tfpixel[5] = (i5 - 1) * tilesize[5] + 1; - tlpixel[5] = minvalue(tfpixel[5] + tilesize[5] - 1, - naxis[5]); - thistilesize[5] = tlpixel[5] - tfpixel[5] + 1; - offset[5] = (i5 - 1) * rowdim[5]; - for (i4 = ftile[4]; i4 <= ltile[4]; i4++) - { - tfpixel[4] = (i4 - 1) * tilesize[4] + 1; - tlpixel[4] = minvalue(tfpixel[4] + tilesize[4] - 1, - naxis[4]); - thistilesize[4] = thistilesize[5] * (tlpixel[4] - tfpixel[4] + 1); - offset[4] = (i4 - 1) * rowdim[4] + offset[5]; - for (i3 = ftile[3]; i3 <= ltile[3]; i3++) - { - tfpixel[3] = (i3 - 1) * tilesize[3] + 1; - tlpixel[3] = minvalue(tfpixel[3] + tilesize[3] - 1, - naxis[3]); - thistilesize[3] = thistilesize[4] * (tlpixel[3] - tfpixel[3] + 1); - offset[3] = (i3 - 1) * rowdim[3] + offset[4]; - for (i2 = ftile[2]; i2 <= ltile[2]; i2++) - { - tfpixel[2] = (i2 - 1) * tilesize[2] + 1; - tlpixel[2] = minvalue(tfpixel[2] + tilesize[2] - 1, - naxis[2]); - thistilesize[2] = thistilesize[3] * (tlpixel[2] - tfpixel[2] + 1); - offset[2] = (i2 - 1) * rowdim[2] + offset[3]; - for (i1 = ftile[1]; i1 <= ltile[1]; i1++) - { - tfpixel[1] = (i1 - 1) * tilesize[1] + 1; - tlpixel[1] = minvalue(tfpixel[1] + tilesize[1] - 1, - naxis[1]); - thistilesize[1] = thistilesize[2] * (tlpixel[1] - tfpixel[1] + 1); - offset[1] = (i1 - 1) * rowdim[1] + offset[2]; - for (i0 = ftile[0]; i0 <= ltile[0]; i0++) - { - tfpixel[0] = (i0 - 1) * tilesize[0] + 1; - tlpixel[0] = minvalue(tfpixel[0] + tilesize[0] - 1, - naxis[0]); - thistilesize[0] = thistilesize[1] * (tlpixel[0] - tfpixel[0] + 1); - /* calculate row of table containing this tile */ - irow = i0 + offset[1]; - -/* -printf("row %d, %d %d, %d %d, %d %d; %d\n", - irow, tfpixel[0],tlpixel[0],tfpixel[1],tlpixel[1],tfpixel[2],tlpixel[2], - thistilesize[0]); -*/ - /* test if there are any intersecting pixels in this tile and the output image */ - if (imcomp_test_overlap(ndim, tfpixel, tlpixel, - fpixel, lpixel, inc, status)) { - /* read and uncompress this row (tile) of the table */ - /* also do type conversion and undefined pixel substitution */ - /* at this point */ - - imcomp_decompress_tile(fptr, irow, thistilesize[0], - datatype, nullcheck, nullval, buffer, bnullarray, &tilenul, - status); - - if (tilenul && anynul) - *anynul = 1; /* there are null pixels */ -/* -printf(" pixlen=%d, ndim=%d, %d %d %d, %d %d %d, %d %d %d\n", - pixlen, ndim, fpixel[0],lpixel[0],inc[0],fpixel[1],lpixel[1],inc[1], - fpixel[2],lpixel[2],inc[2]); -*/ - /* copy the intersecting pixels from this tile to the output */ - imcomp_copy_overlap(buffer, pixlen, ndim, tfpixel, tlpixel, - bnullarray, array, fpixel, lpixel, inc, nullcheck, - nullarray, status); - } - } - } - } - } - } - } - if (nullcheck == 2) - { - free(bnullarray); - } - free(buffer); - - return(*status); -} -/*---------------------------------------------------------------------------*/ -int fits_read_write_compressed_img(fitsfile *fptr, /* I - FITS file pointer */ - int datatype, /* I - datatype of the array to be returned */ - LONGLONG *infpixel, /* I - 'bottom left corner' of the subsection */ - LONGLONG *inlpixel, /* I - 'top right corner' of the subsection */ - long *ininc, /* I - increment to be applied in each dimension */ - int nullcheck, /* I - 0 for no null checking */ - /* 1: set undefined pixels = nullval */ - void *nullval, /* I - value for undefined pixels */ - int *anynul, /* O - set to 1 if any values are null; else 0 */ - fitsfile *outfptr, /* I - FITS file pointer */ - int *status) /* IO - error status */ -/* - This is similar to fits_read_compressed_img, except that it writes - the pixels to the output image, on a tile by tile basis instead of returning - the array. -*/ -{ - long naxis[MAX_COMPRESS_DIM], tiledim[MAX_COMPRESS_DIM]; - long tilesize[MAX_COMPRESS_DIM], thistilesize[MAX_COMPRESS_DIM]; - long ftile[MAX_COMPRESS_DIM], ltile[MAX_COMPRESS_DIM]; - long tfpixel[MAX_COMPRESS_DIM], tlpixel[MAX_COMPRESS_DIM]; - long rowdim[MAX_COMPRESS_DIM], offset[MAX_COMPRESS_DIM],ntemp; - long fpixel[MAX_COMPRESS_DIM], lpixel[MAX_COMPRESS_DIM]; - long inc[MAX_COMPRESS_DIM]; - long i5, i4, i3, i2, i1, i0, irow; - int ii, ndim, tilenul; - void *buffer; - char *bnullarray = 0, *cnull; - LONGLONG firstelem; - - if (*status > 0) - return(*status); - - if (!fits_is_compressed_image(fptr, status) ) - { - ffpmsg("CHDU is not a compressed image (fits_read_compressed_img)"); - return(*status = DATA_DECOMPRESSION_ERR); - } - - cnull = (char *) nullval; /* used to test if the nullval = 0 */ - - /* get temporary space for uncompressing one image tile */ - /* If nullval == 0, then this means that the */ - /* calling routine does not want to check for null pixels in the array */ - if (datatype == TSHORT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (short)); - if (cnull) { - if (cnull[0] == 0 && cnull[1] == 0 ) { - nullcheck = 0; - } - } - } - else if (datatype == TINT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (int)); - if (cnull) { - if (cnull[0] == 0 && cnull[1] == 0 && cnull[2] == 0 && cnull[3] == 0 ) { - nullcheck = 0; - } - } - } - else if (datatype == TLONG) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (long)); - if (cnull) { - if (cnull[0] == 0 && cnull[1] == 0 && cnull[2] == 0 && cnull[3] == 0 ) { - nullcheck = 0; - } - } - } - else if (datatype == TFLOAT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (float)); - if (cnull) { - if (cnull[0] == 0 && cnull[1] == 0 && cnull[2] == 0 && cnull[3] == 0 ) { - nullcheck = 0; - } - } - } - else if (datatype == TDOUBLE) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (double)); - if (cnull) { - if (cnull[0] == 0 && cnull[1] == 0 && cnull[2] == 0 && cnull[3] == 0 && - cnull[4] == 0 && cnull[5] == 0 && cnull[6] == 0 && cnull[7] == 0 ) { - nullcheck = 0; - } - } - } - else if (datatype == TUSHORT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned short)); - if (cnull) { - if (cnull[0] == 0 && cnull[1] == 0 ){ - nullcheck = 0; - } - } - } - else if (datatype == TUINT) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned int)); - if (cnull) { - if (cnull[0] == 0 && cnull[1] == 0 && cnull[2] == 0 && cnull[3] == 0 ){ - nullcheck = 0; - } - } - } - else if (datatype == TULONG) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (unsigned long)); - if (cnull) { - if (cnull[0] == 0 && cnull[1] == 0 && cnull[2] == 0 && cnull[3] == 0 ){ - nullcheck = 0; - } - } - } - else if (datatype == TBYTE || datatype == TSBYTE) - { - buffer = malloc ((fptr->Fptr)->maxtilelen * sizeof (char)); - if (cnull) { - if (cnull[0] == 0){ - nullcheck = 0; - } - } - } - else - { - ffpmsg("unsupported datatype for uncompressing image"); - return(*status = BAD_DATATYPE); - } - - if (buffer == NULL) - { - ffpmsg("Out of memory (fits_read_compress_img)"); - return (*status = MEMORY_ALLOCATION); - } - - /* initialize all the arrays */ - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - naxis[ii] = 1; - tiledim[ii] = 1; - tilesize[ii] = 1; - ftile[ii] = 1; - ltile[ii] = 1; - rowdim[ii] = 1; - } - - ndim = (fptr->Fptr)->zndim; - ntemp = 1; - for (ii = 0; ii < ndim; ii++) - { - /* support for mirror-reversed image sections */ - if (infpixel[ii] <= inlpixel[ii]) - { - fpixel[ii] = (long) infpixel[ii]; - lpixel[ii] = (long) inlpixel[ii]; - inc[ii] = ininc[ii]; - } - else - { - fpixel[ii] = (long) inlpixel[ii]; - lpixel[ii] = (long) infpixel[ii]; - inc[ii] = -ininc[ii]; - } - - /* calc number of tiles in each dimension, and tile containing */ - /* the first and last pixel we want to read in each dimension */ - naxis[ii] = (fptr->Fptr)->znaxis[ii]; - if (fpixel[ii] < 1) - { - free(buffer); - return(*status = BAD_PIX_NUM); - } - - tilesize[ii] = (fptr->Fptr)->tilesize[ii]; - tiledim[ii] = (naxis[ii] - 1) / tilesize[ii] + 1; - ftile[ii] = (fpixel[ii] - 1) / tilesize[ii] + 1; - ltile[ii] = minvalue((lpixel[ii] - 1) / tilesize[ii] + 1, - tiledim[ii]); - rowdim[ii] = ntemp; /* total tiles in each dimension */ - ntemp *= tiledim[ii]; - } - - if (anynul) - *anynul = 0; /* initialize */ - - firstelem = 1; - - /* support up to 6 dimensions for now */ - /* tfpixel and tlpixel are the first and last image pixels */ - /* along each dimension of the compression tile */ - for (i5 = ftile[5]; i5 <= ltile[5]; i5++) - { - tfpixel[5] = (i5 - 1) * tilesize[5] + 1; - tlpixel[5] = minvalue(tfpixel[5] + tilesize[5] - 1, - naxis[5]); - thistilesize[5] = tlpixel[5] - tfpixel[5] + 1; - offset[5] = (i5 - 1) * rowdim[5]; - for (i4 = ftile[4]; i4 <= ltile[4]; i4++) - { - tfpixel[4] = (i4 - 1) * tilesize[4] + 1; - tlpixel[4] = minvalue(tfpixel[4] + tilesize[4] - 1, - naxis[4]); - thistilesize[4] = thistilesize[5] * (tlpixel[4] - tfpixel[4] + 1); - offset[4] = (i4 - 1) * rowdim[4] + offset[5]; - for (i3 = ftile[3]; i3 <= ltile[3]; i3++) - { - tfpixel[3] = (i3 - 1) * tilesize[3] + 1; - tlpixel[3] = minvalue(tfpixel[3] + tilesize[3] - 1, - naxis[3]); - thistilesize[3] = thistilesize[4] * (tlpixel[3] - tfpixel[3] + 1); - offset[3] = (i3 - 1) * rowdim[3] + offset[4]; - for (i2 = ftile[2]; i2 <= ltile[2]; i2++) - { - tfpixel[2] = (i2 - 1) * tilesize[2] + 1; - tlpixel[2] = minvalue(tfpixel[2] + tilesize[2] - 1, - naxis[2]); - thistilesize[2] = thistilesize[3] * (tlpixel[2] - tfpixel[2] + 1); - offset[2] = (i2 - 1) * rowdim[2] + offset[3]; - for (i1 = ftile[1]; i1 <= ltile[1]; i1++) - { - tfpixel[1] = (i1 - 1) * tilesize[1] + 1; - tlpixel[1] = minvalue(tfpixel[1] + tilesize[1] - 1, - naxis[1]); - thistilesize[1] = thistilesize[2] * (tlpixel[1] - tfpixel[1] + 1); - offset[1] = (i1 - 1) * rowdim[1] + offset[2]; - for (i0 = ftile[0]; i0 <= ltile[0]; i0++) - { - tfpixel[0] = (i0 - 1) * tilesize[0] + 1; - tlpixel[0] = minvalue(tfpixel[0] + tilesize[0] - 1, - naxis[0]); - thistilesize[0] = thistilesize[1] * (tlpixel[0] - tfpixel[0] + 1); - /* calculate row of table containing this tile */ - irow = i0 + offset[1]; - - /* read and uncompress this row (tile) of the table */ - /* also do type conversion and undefined pixel substitution */ - /* at this point */ - - imcomp_decompress_tile(fptr, irow, thistilesize[0], - datatype, nullcheck, nullval, buffer, bnullarray, &tilenul, - status); - - /* write the image to the output file */ - - if (tilenul && anynul) { - /* this assumes that the tiled pixels are in the same order - as in the uncompressed FITS image. This is not necessarily - the case, but it almost alway is in practice. - Note that null checking is not performed for integer images, - so this could only be a problem for tile compressed floating - point images that use an unconventional tiling pattern. - */ - fits_write_imgnull(outfptr, datatype, firstelem, thistilesize[0], - buffer, nullval, status); - } else { - fits_write_subset(outfptr, datatype, tfpixel, tlpixel, - buffer, status); - } - - firstelem += thistilesize[0]; - - } - } - } - } - } - } - - free(buffer); - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_read_compressed_pixels(fitsfile *fptr, /* I - FITS file pointer */ - int datatype, /* I - datatype of the array to be returned */ - LONGLONG fpixel, /* I - 'first pixel to read */ - LONGLONG npixel, /* I - number of pixels to read */ - int nullcheck, /* I - 0 for no null checking */ - /* 1: set undefined pixels = nullval */ - /* 2: set nullarray=1 for undefined pixels */ - void *nullval, /* I - value for undefined pixels */ - void *array, /* O - array of values that are returned */ - char *nullarray, /* O - array of flags = 1 if nullcheck = 2 */ - int *anynul, /* O - set to 1 if any values are null; else 0 */ - int *status) /* IO - error status */ -/* - Read a consecutive set of pixels from a compressed image. This routine - interpretes the n-dimensional image as a long one-dimensional array. - This is actually a rather inconvenient way to read compressed images in - general, and could be rather inefficient if the requested pixels to be - read are located in many different image compression tiles. - - The general strategy used here is to read the requested pixels in blocks - that correspond to rectangular image sections. -*/ -{ - int naxis, ii, bytesperpixel, planenul; - long naxes[MAX_COMPRESS_DIM], nread; - long nplane, inc[MAX_COMPRESS_DIM]; - LONGLONG tfirst, tlast, last0, last1, dimsize[MAX_COMPRESS_DIM]; - LONGLONG firstcoord[MAX_COMPRESS_DIM], lastcoord[MAX_COMPRESS_DIM]; - char *arrayptr, *nullarrayptr; - - if (*status > 0) - return(*status); - - arrayptr = (char *) array; - nullarrayptr = nullarray; - - /* get size of array pixels, in bytes */ - bytesperpixel = ffpxsz(datatype); - - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - naxes[ii] = 1; - firstcoord[ii] = 0; - lastcoord[ii] = 0; - inc[ii] = 1; - } - - /* determine the dimensions of the image to be read */ - ffgidm(fptr, &naxis, status); - ffgisz(fptr, MAX_COMPRESS_DIM, naxes, status); - - /* calc the cumulative number of pixels in each successive dimension */ - dimsize[0] = 1; - for (ii = 1; ii < MAX_COMPRESS_DIM; ii++) - dimsize[ii] = dimsize[ii - 1] * naxes[ii - 1]; - - /* determine the coordinate of the first and last pixel in the image */ - /* Use zero based indexes here */ - tfirst = fpixel - 1; - tlast = tfirst + npixel - 1; - for (ii = naxis - 1; ii >= 0; ii--) - { - firstcoord[ii] = tfirst / dimsize[ii]; - lastcoord[ii] = tlast / dimsize[ii]; - tfirst = tfirst - firstcoord[ii] * dimsize[ii]; - tlast = tlast - lastcoord[ii] * dimsize[ii]; - } - - /* to simplify things, treat 1-D, 2-D, and 3-D images as separate cases */ - - if (naxis == 1) - { - /* Simple: just read the requested range of pixels */ - - firstcoord[0] = firstcoord[0] + 1; - lastcoord[0] = lastcoord[0] + 1; - fits_read_compressed_img(fptr, datatype, firstcoord, lastcoord, inc, - nullcheck, nullval, array, nullarray, anynul, status); - return(*status); - } - else if (naxis == 2) - { - nplane = 0; /* read 1st (and only) plane of the image */ - - fits_read_compressed_img_plane(fptr, datatype, bytesperpixel, - nplane, firstcoord, lastcoord, inc, naxes, nullcheck, nullval, - array, nullarray, anynul, &nread, status); - } - else if (naxis == 3) - { - /* test for special case: reading an integral number of planes */ - if (firstcoord[0] == 0 && firstcoord[1] == 0 && - lastcoord[0] == naxes[0] - 1 && lastcoord[1] == naxes[1] - 1) - { - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - /* convert from zero base to 1 base */ - (firstcoord[ii])++; - (lastcoord[ii])++; - } - - /* we can read the contiguous block of pixels in one go */ - fits_read_compressed_img(fptr, datatype, firstcoord, lastcoord, inc, - nullcheck, nullval, array, nullarray, anynul, status); - - return(*status); - } - - if (anynul) - *anynul = 0; /* initialize */ - - /* save last coordinate in temporary variables */ - last0 = lastcoord[0]; - last1 = lastcoord[1]; - - if (firstcoord[2] < lastcoord[2]) - { - /* we will read up to the last pixel in all but the last plane */ - lastcoord[0] = naxes[0] - 1; - lastcoord[1] = naxes[1] - 1; - } - - /* read one plane of the cube at a time, for simplicity */ - for (nplane = (long) firstcoord[2]; nplane <= lastcoord[2]; nplane++) - { - if (nplane == lastcoord[2]) - { - lastcoord[0] = last0; - lastcoord[1] = last1; - } - - fits_read_compressed_img_plane(fptr, datatype, bytesperpixel, - nplane, firstcoord, lastcoord, inc, naxes, nullcheck, nullval, - arrayptr, nullarrayptr, &planenul, &nread, status); - - if (planenul && anynul) - *anynul = 1; /* there are null pixels */ - - /* for all subsequent planes, we start with the first pixel */ - firstcoord[0] = 0; - firstcoord[1] = 0; - - /* increment pointers to next elements to be read */ - arrayptr = arrayptr + nread * bytesperpixel; - if (nullarrayptr && (nullcheck == 2) ) - nullarrayptr = nullarrayptr + nread; - } - } - else - { - ffpmsg("only 1D, 2D, or 3D images are currently supported"); - return(*status = DATA_DECOMPRESSION_ERR); - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_read_compressed_img_plane(fitsfile *fptr, /* I - FITS file */ - int datatype, /* I - datatype of the array to be returned */ - int bytesperpixel, /* I - number of bytes per pixel in array */ - long nplane, /* I - which plane of the cube to read */ - LONGLONG *firstcoord, /* coordinate of first pixel to read */ - LONGLONG *lastcoord, /* coordinate of last pixel to read */ - long *inc, /* increment of pixels to read */ - long *naxes, /* size of each image dimension */ - int nullcheck, /* I - 0 for no null checking */ - /* 1: set undefined pixels = nullval */ - /* 2: set nullarray=1 for undefined pixels */ - void *nullval, /* I - value for undefined pixels */ - void *array, /* O - array of values that are returned */ - char *nullarray, /* O - array of flags = 1 if nullcheck = 2 */ - int *anynul, /* O - set to 1 if any values are null; else 0 */ - long *nread, /* O - total number of pixels read and returned*/ - int *status) /* IO - error status */ - - /* - in general we have to read the first partial row of the image, - followed by the middle complete rows, followed by the last - partial row of the image. If the first or last rows are complete, - then read them at the same time as all the middle rows. - */ -{ - /* bottom left coord. and top right coord. */ - LONGLONG blc[MAX_COMPRESS_DIM], trc[MAX_COMPRESS_DIM]; - char *arrayptr, *nullarrayptr; - int tnull; - - if (anynul) - *anynul = 0; - - *nread = 0; - - arrayptr = (char *) array; - nullarrayptr = nullarray; - - blc[2] = nplane + 1; - trc[2] = nplane + 1; - - if (firstcoord[0] != 0) - { - /* have to read a partial first row */ - blc[0] = firstcoord[0] + 1; - blc[1] = firstcoord[1] + 1; - trc[1] = blc[1]; - if (lastcoord[1] == firstcoord[1]) - trc[0] = lastcoord[0] + 1; /* 1st and last pixels in same row */ - else - trc[0] = naxes[0]; /* read entire rest of the row */ - - fits_read_compressed_img(fptr, datatype, blc, trc, inc, - nullcheck, nullval, arrayptr, nullarrayptr, &tnull, status); - - *nread = *nread + (long) (trc[0] - blc[0] + 1); - - if (tnull && anynul) - *anynul = 1; /* there are null pixels */ - - if (lastcoord[1] == firstcoord[1]) - { - return(*status); /* finished */ - } - - /* set starting coord to beginning of next line */ - firstcoord[0] = 0; - firstcoord[1] += 1; - arrayptr = arrayptr + (trc[0] - blc[0] + 1) * bytesperpixel; - if (nullarrayptr && (nullcheck == 2) ) - nullarrayptr = nullarrayptr + (trc[0] - blc[0] + 1); - - } - - /* read contiguous complete rows of the image, if any */ - blc[0] = 1; - blc[1] = firstcoord[1] + 1; - trc[0] = naxes[0]; - - if (lastcoord[0] + 1 == naxes[0]) - { - /* can read the last complete row, too */ - trc[1] = lastcoord[1] + 1; - } - else - { - /* last row is incomplete; have to read it separately */ - trc[1] = lastcoord[1]; - } - - if (trc[1] >= blc[1]) /* must have at least one whole line to read */ - { - fits_read_compressed_img(fptr, datatype, blc, trc, inc, - nullcheck, nullval, arrayptr, nullarrayptr, &tnull, status); - - *nread = *nread + (long) ((trc[1] - blc[1] + 1) * naxes[0]); - - if (tnull && anynul) - *anynul = 1; - - if (lastcoord[1] + 1 == trc[1]) - return(*status); /* finished */ - - /* increment pointers for the last partial row */ - arrayptr = arrayptr + (trc[1] - blc[1] + 1) * naxes[0] * bytesperpixel; - if (nullarrayptr && (nullcheck == 2) ) - nullarrayptr = nullarrayptr + (trc[1] - blc[1] + 1) * naxes[0]; - } - - if (trc[1] == lastcoord[1] + 1) - return(*status); /* all done */ - - /* set starting and ending coord to last line */ - - trc[0] = lastcoord[0] + 1; - trc[1] = lastcoord[1] + 1; - blc[1] = trc[1]; - - fits_read_compressed_img(fptr, datatype, blc, trc, inc, - nullcheck, nullval, arrayptr, nullarrayptr, &tnull, status); - - if (tnull && anynul) - *anynul = 1; - - *nread = *nread + (long) (trc[0] - blc[0] + 1); - - return(*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_get_compressed_image_par(fitsfile *infptr, int *status) - -/* - This routine reads keywords from a BINTABLE extension containing a - compressed image. -*/ -{ - char keyword[FLEN_KEYWORD]; - char value[FLEN_VALUE]; - int ii, tstatus, tstatus2, doffset, oldFormat=0, colNum=0; - long expect_nrows, maxtilelen; - - if (*status > 0) - return(*status); - - /* Copy relevant header keyword values to structure */ - if (ffgky (infptr, TSTRING, "ZCMPTYPE", value, NULL, status) > 0) - { - ffpmsg("required ZCMPTYPE compression keyword not found in"); - ffpmsg(" imcomp_get_compressed_image_par"); - return(*status); - } - - (infptr->Fptr)->zcmptype[0] = '\0'; - strncat((infptr->Fptr)->zcmptype, value, 11); - - if (!FSTRCMP(value, "RICE_1") || !FSTRCMP(value, "RICE_ONE") ) - (infptr->Fptr)->compress_type = RICE_1; - else if (!FSTRCMP(value, "HCOMPRESS_1") ) - (infptr->Fptr)->compress_type = HCOMPRESS_1; - else if (!FSTRCMP(value, "GZIP_1") ) - (infptr->Fptr)->compress_type = GZIP_1; - else if (!FSTRCMP(value, "GZIP_2") ) - (infptr->Fptr)->compress_type = GZIP_2; - else if (!FSTRCMP(value, "BZIP2_1") ) - (infptr->Fptr)->compress_type = BZIP2_1; - else if (!FSTRCMP(value, "PLIO_1") ) - (infptr->Fptr)->compress_type = PLIO_1; - else if (!FSTRCMP(value, "NOCOMPRESS") ) - (infptr->Fptr)->compress_type = NOCOMPRESS; - else - { - ffpmsg("Unknown image compression type:"); - ffpmsg(value); - return (*status = DATA_DECOMPRESSION_ERR); - } - - if (ffgky (infptr, TINT, "ZBITPIX", &(infptr->Fptr)->zbitpix, - NULL, status) > 0) - { - ffpmsg("required ZBITPIX compression keyword not found"); - return(*status); - } - - /* If ZZERO and ZSCALE columns don't exist for floating-point types, - assume there is NO quantization. Treat exactly as if it had ZQUANTIZ='NONE'. - This is true regardless of whether or not file has a ZQUANTIZ keyword. */ - tstatus=0; - tstatus2=0; - if ((infptr->Fptr->zbitpix < 0) && - (fits_get_colnum(infptr,CASEINSEN,"ZZERO",&colNum,&tstatus) - == COL_NOT_FOUND) && - (fits_get_colnum(infptr,CASEINSEN,"ZSCALE",&colNum,&tstatus2) - == COL_NOT_FOUND)) { - (infptr->Fptr)->quantize_level = NO_QUANTIZE; - } - else { - /* get the floating point to integer quantization type, if present. */ - /* FITS files produced before 2009 will not have this keyword */ - tstatus = 0; - if (ffgky(infptr, TSTRING, "ZQUANTIZ", value, NULL, &tstatus) > 0) - { - (infptr->Fptr)->quantize_method = 0; - (infptr->Fptr)->quantize_level = 0; - } else { - - if (!FSTRCMP(value, "NONE") ) { - (infptr->Fptr)->quantize_level = NO_QUANTIZE; - } else if (!FSTRCMP(value, "SUBTRACTIVE_DITHER_1") ) - (infptr->Fptr)->quantize_method = SUBTRACTIVE_DITHER_1; - else if (!FSTRCMP(value, "SUBTRACTIVE_DITHER_2") ) - (infptr->Fptr)->quantize_method = SUBTRACTIVE_DITHER_2; - else if (!FSTRCMP(value, "NO_DITHER") ) - (infptr->Fptr)->quantize_method = NO_DITHER; - else - (infptr->Fptr)->quantize_method = 0; - } - } - - /* get the floating point quantization dithering offset, if present. */ - /* FITS files produced before October 2009 will not have this keyword */ - tstatus = 0; - if (ffgky(infptr, TINT, "ZDITHER0", &doffset, NULL, &tstatus) > 0) - { - /* by default start with 1st element of random sequence */ - (infptr->Fptr)->dither_seed = 1; - } else { - (infptr->Fptr)->dither_seed = doffset; - } - - if (ffgky (infptr,TINT, "ZNAXIS", &(infptr->Fptr)->zndim, NULL, status) > 0) - { - ffpmsg("required ZNAXIS compression keyword not found"); - return(*status); - } - - if ((infptr->Fptr)->zndim < 1) - { - ffpmsg("Compressed image has no data (ZNAXIS < 1)"); - return (*status = BAD_NAXIS); - } - - if ((infptr->Fptr)->zndim > MAX_COMPRESS_DIM) - { - ffpmsg("Compressed image has too many dimensions"); - return(*status = BAD_NAXIS); - } - - expect_nrows = 1; - maxtilelen = 1; - for (ii = 0; ii < (infptr->Fptr)->zndim; ii++) - { - /* get image size */ - snprintf (keyword, FLEN_KEYWORD,"ZNAXIS%d", ii+1); - ffgky (infptr, TLONG,keyword, &(infptr->Fptr)->znaxis[ii],NULL,status); - - if (*status > 0) - { - ffpmsg("required ZNAXISn compression keyword not found"); - return(*status); - } - - /* get compression tile size */ - snprintf (keyword, FLEN_KEYWORD,"ZTILE%d", ii+1); - - /* set default tile size in case keywords are not present */ - if (ii == 0) - (infptr->Fptr)->tilesize[0] = (infptr->Fptr)->znaxis[0]; - else - (infptr->Fptr)->tilesize[ii] = 1; - - tstatus = 0; - ffgky (infptr, TLONG, keyword, &(infptr->Fptr)->tilesize[ii], NULL, - &tstatus); - - expect_nrows *= (((infptr->Fptr)->znaxis[ii] - 1) / - (infptr->Fptr)->tilesize[ii]+ 1); - maxtilelen *= (infptr->Fptr)->tilesize[ii]; - } - - /* check number of rows */ - if (expect_nrows != (infptr->Fptr)->numrows) - { - ffpmsg( - "number of table rows != the number of tiles in compressed image"); - return (*status = DATA_DECOMPRESSION_ERR); - } - - /* read any algorithm specific parameters */ - if ((infptr->Fptr)->compress_type == RICE_1 ) - { - if (ffgky(infptr, TINT,"ZVAL1", &(infptr->Fptr)->rice_blocksize, - NULL, status) > 0) - { - ffpmsg("required ZVAL1 compression keyword not found"); - return(*status); - } - - tstatus = 0; - /* First check for very old files, where ZVAL2 wasn't yet designated - for bytepix */ - if (!ffgky(infptr, TSTRING, "ZNAME2", value, NULL, &tstatus) - && !FSTRCMP(value, "NOISEBIT")) - { - oldFormat = 1; - } - - tstatus = 0; - if (oldFormat || ffgky(infptr, TINT,"ZVAL2", &(infptr->Fptr)->rice_bytepix, - NULL, &tstatus) > 0) - { - (infptr->Fptr)->rice_bytepix = 4; /* default value */ - } - - if ((infptr->Fptr)->rice_blocksize < 16 && - (infptr->Fptr)->rice_bytepix > 8) { - /* values are reversed */ - tstatus = (infptr->Fptr)->rice_bytepix; - (infptr->Fptr)->rice_bytepix = (infptr->Fptr)->rice_blocksize; - (infptr->Fptr)->rice_blocksize = tstatus; - } - } else if ((infptr->Fptr)->compress_type == HCOMPRESS_1 ) { - - if (ffgky(infptr, TFLOAT,"ZVAL1", &(infptr->Fptr)->hcomp_scale, - NULL, status) > 0) - { - ffpmsg("required ZVAL1 compression keyword not found"); - return(*status); - } - - tstatus = 0; - ffgky(infptr, TINT,"ZVAL2", &(infptr->Fptr)->hcomp_smooth, - NULL, &tstatus); - } - - /* store number of pixels in each compression tile, */ - /* and max size of the compressed tile buffer */ - (infptr->Fptr)->maxtilelen = maxtilelen; - - (infptr->Fptr)->maxelem = - imcomp_calc_max_elem ((infptr->Fptr)->compress_type, maxtilelen, - (infptr->Fptr)->zbitpix, (infptr->Fptr)->rice_blocksize); - - /* Get Column numbers. */ - if (ffgcno(infptr, CASEINSEN, "COMPRESSED_DATA", - &(infptr->Fptr)->cn_compressed, status) > 0) - { - ffpmsg("couldn't find COMPRESSED_DATA column (fits_get_compressed_img_par)"); - return(*status = DATA_DECOMPRESSION_ERR); - } - - ffpmrk(); /* put mark on message stack; erase any messages after this */ - - tstatus = 0; - ffgcno(infptr,CASEINSEN, "UNCOMPRESSED_DATA", - &(infptr->Fptr)->cn_uncompressed, &tstatus); - - tstatus = 0; - ffgcno(infptr,CASEINSEN, "GZIP_COMPRESSED_DATA", - &(infptr->Fptr)->cn_gzip_data, &tstatus); - - tstatus = 0; - if (ffgcno(infptr, CASEINSEN, "ZSCALE", &(infptr->Fptr)->cn_zscale, - &tstatus) > 0) - { - /* CMPSCALE column doesn't exist; see if there is a keyword */ - tstatus = 0; - if (ffgky(infptr, TDOUBLE, "ZSCALE", &(infptr->Fptr)->zscale, NULL, - &tstatus) <= 0) - (infptr->Fptr)->cn_zscale = -1; /* flag for a constant ZSCALE */ - } - - tstatus = 0; - if (ffgcno(infptr, CASEINSEN, "ZZERO", &(infptr->Fptr)->cn_zzero, - &tstatus) > 0) - { - /* CMPZERO column doesn't exist; see if there is a keyword */ - tstatus = 0; - if (ffgky(infptr, TDOUBLE, "ZZERO", &(infptr->Fptr)->zzero, NULL, - &tstatus) <= 0) - (infptr->Fptr)->cn_zzero = -1; /* flag for a constant ZZERO */ - } - - tstatus = 0; - if (ffgcno(infptr, CASEINSEN, "ZBLANK", &(infptr->Fptr)->cn_zblank, - &tstatus) > 0) - { - /* ZBLANK column doesn't exist; see if there is a keyword */ - tstatus = 0; - if (ffgky(infptr, TINT, "ZBLANK", &(infptr->Fptr)->zblank, NULL, - &tstatus) <= 0) { - (infptr->Fptr)->cn_zblank = -1; /* flag for a constant ZBLANK */ - - } else { - /* ZBLANK keyword doesn't exist; see if there is a BLANK keyword */ - tstatus = 0; - if (ffgky(infptr, TINT, "BLANK", &(infptr->Fptr)->zblank, NULL, - &tstatus) <= 0) - (infptr->Fptr)->cn_zblank = -1; /* flag for a constant ZBLANK */ - } - } - - /* read the conventional BSCALE and BZERO scaling keywords, if present */ - tstatus = 0; - if (ffgky (infptr, TDOUBLE, "BSCALE", &(infptr->Fptr)->cn_bscale, - NULL, &tstatus) > 0) - { - (infptr->Fptr)->cn_bscale = 1.0; - } - - tstatus = 0; - if (ffgky (infptr, TDOUBLE, "BZERO", &(infptr->Fptr)->cn_bzero, - NULL, &tstatus) > 0) - { - (infptr->Fptr)->cn_bzero = 0.0; - (infptr->Fptr)->cn_actual_bzero = 0.0; - } else { - (infptr->Fptr)->cn_actual_bzero = (infptr->Fptr)->cn_bzero; - } - - /* special case: the quantization level is not given by a keyword in */ - /* the HDU header, so we have to explicitly copy the requested value */ - /* to the actual value */ - if ( (infptr->Fptr)->request_quantize_level != 0.) - (infptr->Fptr)->quantize_level = (infptr->Fptr)->request_quantize_level; - - ffcmrk(); /* clear any spurious error messages, back to the mark */ - return (*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_copy_imheader(fitsfile *infptr, fitsfile *outfptr, int *status) -/* - This routine reads the header keywords from the input image and - copies them to the output image; the manditory structural keywords - and the checksum keywords are not copied. If the DATE keyword is copied, - then it is updated with the current date and time. -*/ -{ - int nkeys, ii, keyclass; - char card[FLEN_CARD]; /* a header record */ - - if (*status > 0) - return(*status); - - ffghsp(infptr, &nkeys, NULL, status); /* get number of keywords in image */ - - for (ii = 5; ii <= nkeys; ii++) /* skip the first 4 keywords */ - { - ffgrec(infptr, ii, card, status); - - keyclass = ffgkcl(card); /* Get the type/class of keyword */ - - /* don't copy structural keywords or checksum keywords */ - if ((keyclass <= TYP_CMPRS_KEY) || (keyclass == TYP_CKSUM_KEY)) - continue; - - if (FSTRNCMP(card, "DATE ", 5) == 0) /* write current date */ - { - ffpdat(outfptr, status); - } - else if (FSTRNCMP(card, "EXTNAME ", 8) == 0) - { - /* don't copy default EXTNAME keyword from a compressed image */ - if (FSTRNCMP(card, "EXTNAME = 'COMPRESSED_IMAGE'", 28)) - { - /* if EXTNAME keyword already exists, overwrite it */ - /* otherwise append a new EXTNAME keyword */ - ffucrd(outfptr, "EXTNAME", card, status); - } - } - else - { - /* just copy the keyword to the output header */ - ffprec (outfptr, card, status); - } - - if (*status > 0) - return (*status); - } - return (*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_copy_img2comp(fitsfile *infptr, fitsfile *outfptr, int *status) -/* - This routine copies the header keywords from the uncompressed input image - and to the compressed image (in a binary table) -*/ -{ - char card[FLEN_CARD], card2[FLEN_CARD]; /* a header record */ - int nkeys, nmore, ii, jj, tstatus, bitpix; - - /* tile compressed image keyword translation table */ - /* INPUT OUTPUT */ - /* 01234567 01234567 */ - char *patterns[][2] = {{"SIMPLE", "ZSIMPLE" }, - {"XTENSION", "ZTENSION" }, - {"BITPIX", "ZBITPIX" }, - {"NAXIS", "ZNAXIS" }, - {"NAXISm", "ZNAXISm" }, - {"EXTEND", "ZEXTEND" }, - {"BLOCKED", "ZBLOCKED"}, - {"PCOUNT", "ZPCOUNT" }, - {"GCOUNT", "ZGCOUNT" }, - - {"CHECKSUM","ZHECKSUM"}, /* save original checksums */ - {"DATASUM", "ZDATASUM"}, - - {"*", "+" }}; /* copy all other keywords */ - int npat; - - if (*status > 0) - return(*status); - - /* write a default EXTNAME keyword if it doesn't exist in input file*/ - fits_read_card(infptr, "EXTNAME", card, status); - - if (*status) { - *status = 0; - strcpy(card, "EXTNAME = 'COMPRESSED_IMAGE'"); - fits_write_record(outfptr, card, status); - } - - /* copy all the keywords from the input file to the output */ - npat = sizeof(patterns)/sizeof(patterns[0][0])/2; - fits_translate_keywords(infptr, outfptr, 1, patterns, npat, - 0, 0, 0, status); - - - if ( (outfptr->Fptr)->request_lossy_int_compress != 0) { - - /* request was made to compress integer images as if they had float pixels. */ - /* If input image has positive bitpix value, then reset the output ZBITPIX */ - /* value to -32. */ - - fits_read_key(infptr, TINT, "BITPIX", &bitpix, NULL, status); - - if (*status <= 0 && bitpix > 0) { - fits_modify_key_lng(outfptr, "ZBITPIX", -32, NULL, status); - - /* also delete the BSCALE, BZERO, and BLANK keywords */ - tstatus = 0; - fits_delete_key(outfptr, "BSCALE", &tstatus); - tstatus = 0; - fits_delete_key(outfptr, "BZERO", &tstatus); - tstatus = 0; - fits_delete_key(outfptr, "BLANK", &tstatus); - } - } - - /* - For compatibility with software that uses an older version of CFITSIO, - we must make certain that the new ZQUANTIZ keyword, if it exists, must - occur after the other peudo-required keywords (e.g., ZSIMPLE, ZBITPIX, - etc.). Do this by trying to delete the keyword. If that succeeds (and - thus the keyword did exist) then rewrite the keyword at the end of header. - In principle this should not be necessary once all software has upgraded - to a newer version of CFITSIO (version number greater than 3.181, newer - than August 2009). - - Do the same for the new ZDITHER0 keyword. - */ - - tstatus = 0; - if (fits_read_card(outfptr, "ZQUANTIZ", card, &tstatus) == 0) - { - fits_delete_key(outfptr, "ZQUANTIZ", status); - - /* rewrite the deleted keyword at the end of the header */ - fits_write_record(outfptr, card, status); - - /* write some associated HISTORY keywords */ - fits_parse_value(card, card2, NULL, status); - if (fits_strncasecmp(card2, "'NONE", 5) ) { - /* the value is not 'NONE' */ - fits_write_history(outfptr, - "Image was compressed by CFITSIO using scaled integer quantization:", status); - snprintf(card2, FLEN_CARD," q = %f / quantized level scaling parameter", - (outfptr->Fptr)->request_quantize_level); - fits_write_history(outfptr, card2, status); - fits_write_history(outfptr, card+10, status); - } - } - - tstatus = 0; - if (fits_read_card(outfptr, "ZDITHER0", card, &tstatus) == 0) - { - fits_delete_key(outfptr, "ZDITHER0", status); - - /* rewrite the deleted keyword at the end of the header */ - fits_write_record(outfptr, card, status); - } - - - ffghsp(infptr, &nkeys, &nmore, status); /* get number of keywords in image */ - - nmore = nmore / 36; /* how many completely empty header blocks are there? */ - - /* preserve the same number of spare header blocks in the output header */ - - for (jj = 0; jj < nmore; jj++) - for (ii = 0; ii < 36; ii++) - fits_write_record(outfptr, " ", status); - - return (*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_copy_comp2img(fitsfile *infptr, fitsfile *outfptr, - int norec, int *status) -/* - This routine copies the header keywords from the compressed input image - and to the uncompressed image (in a binary table) -*/ -{ - char card[FLEN_CARD]; /* a header record */ - char *patterns[40][2]; - char negative[] = "-"; - int ii,jj, npat, nreq, nsp, tstatus = 0; - int nkeys, nmore; - - /* tile compressed image keyword translation table */ - /* INPUT OUTPUT */ - /* 01234567 01234567 */ - - /* only translate these if required keywords not already written */ - char *reqkeys[][2] = { - {"ZSIMPLE", "SIMPLE" }, - {"ZTENSION", "XTENSION"}, - {"ZBITPIX", "BITPIX" }, - {"ZNAXIS", "NAXIS" }, - {"ZNAXISm", "NAXISm" }, - {"ZEXTEND", "EXTEND" }, - {"ZBLOCKED", "BLOCKED"}, - {"ZPCOUNT", "PCOUNT" }, - {"ZGCOUNT", "GCOUNT" }, - {"ZHECKSUM", "CHECKSUM"}, /* restore original checksums */ - {"ZDATASUM", "DATASUM"}}; - - /* other special keywords */ - char *spkeys[][2] = { - {"XTENSION", "-" }, - {"BITPIX", "-" }, - {"NAXIS", "-" }, - {"NAXISm", "-" }, - {"PCOUNT", "-" }, - {"GCOUNT", "-" }, - {"TFIELDS", "-" }, - {"TTYPEm", "-" }, - {"TFORMm", "-" }, - {"THEAP", "-" }, - {"ZIMAGE", "-" }, - {"ZQUANTIZ", "-" }, - {"ZDITHER0", "-" }, - {"ZTILEm", "-" }, - {"ZCMPTYPE", "-" }, - {"ZBLANK", "-" }, - {"ZNAMEm", "-" }, - {"ZVALm", "-" }, - - {"CHECKSUM","-" }, /* delete checksums */ - {"DATASUM", "-" }, - {"EXTNAME", "+" }, /* we may change this, below */ - {"*", "+" }}; - - - if (*status > 0) - return(*status); - - nreq = sizeof(reqkeys)/sizeof(reqkeys[0][0])/2; - nsp = sizeof(spkeys)/sizeof(spkeys[0][0])/2; - - /* construct translation patterns */ - - for (ii = 0; ii < nreq; ii++) { - patterns[ii][0] = reqkeys[ii][0]; - - if (norec) - patterns[ii][1] = negative; - else - patterns[ii][1] = reqkeys[ii][1]; - } - - for (ii = 0; ii < nsp; ii++) { - patterns[ii+nreq][0] = spkeys[ii][0]; - patterns[ii+nreq][1] = spkeys[ii][1]; - } - - npat = nreq + nsp; - - /* see if the EXTNAME keyword should be copied or not */ - fits_read_card(infptr, "EXTNAME", card, &tstatus); - - if (tstatus == 0) { - if (!strncmp(card, "EXTNAME = 'COMPRESSED_IMAGE'", 28)) - patterns[npat-2][1] = negative; - } - - /* translate and copy the keywords from the input file to the output */ - fits_translate_keywords(infptr, outfptr, 1, patterns, npat, - 0, 0, 0, status); - - ffghsp(infptr, &nkeys, &nmore, status); /* get number of keywords in image */ - - nmore = nmore / 36; /* how many completely empty header blocks are there? */ - - /* preserve the same number of spare header blocks in the output header */ - - for (jj = 0; jj < nmore; jj++) - for (ii = 0; ii < 36; ii++) - fits_write_record(outfptr, " ", status); - - - return (*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_copy_prime2img(fitsfile *infptr, fitsfile *outfptr, int *status) -/* - This routine copies any unexpected keywords from the primary array - of the compressed input image into the header of the uncompressed image - (which is the primary array of the output file). -*/ -{ - int nsp; - - /* keywords that will not be copied */ - char *spkeys[][2] = { - {"SIMPLE", "-" }, - {"BITPIX", "-" }, - {"NAXIS", "-" }, - {"NAXISm", "-" }, - {"PCOUNT", "-" }, - {"EXTEND", "-" }, - {"GCOUNT", "-" }, - {"CHECKSUM","-" }, - {"DATASUM", "-" }, - {"EXTNAME", "-" }, - {"HISTORY", "-" }, - {"COMMENT", "-" }, - {"*", "+" }}; - - if (*status > 0) - return(*status); - - nsp = sizeof(spkeys)/sizeof(spkeys[0][0])/2; - - /* translate and copy the keywords from the input file to the output */ - fits_translate_keywords(infptr, outfptr, 1, spkeys, nsp, - 0, 0, 0, status); - - return (*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_decompress_tile (fitsfile *infptr, - int nrow, /* I - row of table to read and uncompress */ - int tilelen, /* I - number of pixels in the tile */ - int datatype, /* I - datatype to be returned in 'buffer' */ - int nullcheck, /* I - 0 for no null checking */ - void *nulval, /* I - value to be used for undefined pixels */ - void *buffer, /* O - buffer for returned decompressed values */ - char *bnullarray, /* O - buffer for returned null flags */ - int *anynul, /* O - any null values returned? */ - int *status) - -/* This routine decompresses one tile of the image */ -{ - int *idata = 0; - int tiledatatype, pixlen = 0; /* uncompressed integer data */ - size_t idatalen, tilebytesize; - int ii, tnull; /* value in the data which represents nulls */ - unsigned char *cbuf; /* compressed data */ - unsigned char charnull = 0; - short snull = 0; - int blocksize, ntilebins, tilecol = 0; - float fnulval=0; - float *tempfloat = 0; - double *tempdouble = 0; - double dnulval=0; - double bscale, bzero, actual_bzero, dummy = 0; /* scaling parameters */ - long tilesize; /* number of bytes */ - int smooth, nx, ny, scale; /* hcompress parameters */ - LONGLONG nelemll = 0, offset = 0; - - if (*status > 0) - return(*status); - - - /* **************************************************************** */ - /* allocate pointers to array of cached uncompressed tiles, if not already done */ - if ((infptr->Fptr)->tilerow == 0) { - - /* calculate number of column bins of compressed tile */ - ntilebins = (((infptr->Fptr)->znaxis[0] - 1) / ((infptr->Fptr)->tilesize[0])) + 1; - - if ((infptr->Fptr)->znaxis[0] != (infptr->Fptr)->tilesize[0] || - (infptr->Fptr)->tilesize[1] != 1 ) { /* don't cache the tile if only single row of the image */ - - (infptr->Fptr)->tilerow = (int *) calloc (ntilebins, sizeof(int)); - (infptr->Fptr)->tiledata = (void**) calloc (ntilebins, sizeof(void*)); - (infptr->Fptr)->tilenullarray = (void **) calloc (ntilebins, sizeof(char*)); - (infptr->Fptr)->tiledatasize = (long *) calloc (ntilebins, sizeof(long)); - (infptr->Fptr)->tiletype = (int *) calloc (ntilebins, sizeof(int)); - (infptr->Fptr)->tileanynull = (int *) calloc (ntilebins, sizeof(int)); - } - } - - /* **************************************************************** */ - /* check if this tile was cached; if so, just copy it out */ - if ((infptr->Fptr)->tilerow) { - /* calculate the column bin of the compressed tile */ - tilecol = (nrow - 1) % ((long)(((infptr->Fptr)->znaxis[0] - 1) / ((infptr->Fptr)->tilesize[0])) + 1); - - if (nrow == (infptr->Fptr)->tilerow[tilecol] && datatype == (infptr->Fptr)->tiletype[tilecol] ) { - - memcpy(buffer, ((infptr->Fptr)->tiledata)[tilecol], (infptr->Fptr)->tiledatasize[tilecol]); - - if (nullcheck == 2) - memcpy(bnullarray, (infptr->Fptr)->tilenullarray[tilecol], tilelen); - - *anynul = (infptr->Fptr)->tileanynull[tilecol]; - - return(*status); - } - } - - /* **************************************************************** */ - /* get length of the compressed byte stream */ - ffgdesll (infptr, (infptr->Fptr)->cn_compressed, nrow, &nelemll, &offset, - status); - - /* EOF error here indicates that this tile has not yet been written */ - if (*status == END_OF_FILE) - return(*status = NO_COMPRESSED_TILE); - - /* **************************************************************** */ - if (nelemll == 0) /* special case: tile was not compressed normally */ - { - if ((infptr->Fptr)->cn_uncompressed >= 1 ) { - - /* This option of writing the uncompressed floating point data */ - /* to the tile compressed file was used until about May 2011. */ - /* This was replaced by the more efficient option of gzipping the */ - /* floating point data before writing it to the tile-compressed file */ - - /* no compressed data, so simply read the uncompressed data */ - /* directly from the UNCOMPRESSED_DATA column */ - ffgdesll (infptr, (infptr->Fptr)->cn_uncompressed, nrow, &nelemll, - &offset, status); - - if (nelemll == 0 && offset == 0) /* this should never happen */ - return (*status = NO_COMPRESSED_TILE); - - if (nullcheck <= 1) { /* set any null values in the array = nulval */ - fits_read_col(infptr, datatype, (infptr->Fptr)->cn_uncompressed, - nrow, 1, (long) nelemll, nulval, buffer, anynul, status); - } else { /* set the bnullarray = 1 for any null values in the array */ - fits_read_colnull(infptr, datatype, (infptr->Fptr)->cn_uncompressed, - nrow, 1, (long) nelemll, buffer, bnullarray, anynul, status); - } - } else if ((infptr->Fptr)->cn_gzip_data >= 1) { - - /* This is the newer option, that was introduced in May 2011 */ - /* floating point data was not quantized, so read the losslessly */ - /* compressed data from the GZIP_COMPRESSED_DATA column */ - - ffgdesll (infptr, (infptr->Fptr)->cn_gzip_data, nrow, &nelemll, - &offset, status); - - if (nelemll == 0 && offset == 0) /* this should never happen */ - return (*status = NO_COMPRESSED_TILE); - - /* allocate memory for the compressed tile of data */ - cbuf = (unsigned char *) malloc ((long) nelemll); - if (cbuf == NULL) { - ffpmsg("error allocating memory for gzipped tile (imcomp_decompress_tile)"); - return (*status = MEMORY_ALLOCATION); - } - - /* read array of compressed bytes */ - if (fits_read_col(infptr, TBYTE, (infptr->Fptr)->cn_gzip_data, nrow, - 1, (long) nelemll, &charnull, cbuf, NULL, status) > 0) { - ffpmsg("error reading compressed byte stream from binary table"); - free (cbuf); - return (*status); - } - - /* size of the returned (uncompressed) data buffer, in bytes */ - if ((infptr->Fptr)->zbitpix == FLOAT_IMG) { - idatalen = tilelen * sizeof(float); - } else if ((infptr->Fptr)->zbitpix == DOUBLE_IMG) { - idatalen = tilelen * sizeof(double); - } else { - /* this should never happen! */ - ffpmsg("incompatible data type in gzipped floating-point tile-compressed image"); - free (cbuf); - return (*status = DATA_DECOMPRESSION_ERR); - } - - /* Do not allow image float/doubles into int arrays */ - if (datatype != TFLOAT && datatype != TDOUBLE) - { - ffpmsg("attempting to read compressed float or double image into incompatible data type"); - free(cbuf); - return (*status = DATA_DECOMPRESSION_ERR); - } - - if (datatype == TFLOAT && (infptr->Fptr)->zbitpix == DOUBLE_IMG) - { - tempdouble = (double*)malloc(idatalen); - if (tempdouble == NULL) { - ffpmsg("Memory allocation failure for tempdouble. (imcomp_decompress_tile)"); - free (cbuf); - return (*status = MEMORY_ALLOCATION); - } - - /* uncompress the data into temp buffer */ - if (uncompress2mem_from_mem ((char *)cbuf, (long) nelemll, - (char **) &tempdouble, &idatalen, NULL, &tilebytesize, status)) { - ffpmsg("failed to gunzip the image tile"); - free (tempdouble); - free (cbuf); - return (*status); - } - } - else if (datatype == TDOUBLE && (infptr->Fptr)->zbitpix == FLOAT_IMG) { - /* have to allocat a temporary buffer for the uncompressed data in the */ - /* case where a gzipped "float" tile is returned as a "double" array */ - tempfloat = (float*) malloc (idatalen); - - if (tempfloat == NULL) { - ffpmsg("Memory allocation failure for tempfloat. (imcomp_decompress_tile)"); - free (cbuf); - return (*status = MEMORY_ALLOCATION); - } - - /* uncompress the data into temp buffer */ - if (uncompress2mem_from_mem ((char *)cbuf, (long) nelemll, - (char **) &tempfloat, &idatalen, NULL, &tilebytesize, status)) { - ffpmsg("failed to gunzip the image tile"); - free (tempfloat); - free (cbuf); - return (*status); - } - } else { - - /* uncompress the data directly into the output buffer in all other cases */ - if (uncompress2mem_from_mem ((char *)cbuf, (long) nelemll, - (char **) &buffer, &idatalen, NULL, &tilebytesize, status)) { - ffpmsg("failed to gunzip the image tile"); - free (cbuf); - return (*status); - } - } - - free(cbuf); - - /* do byte swapping and null value substitution for the tile of pixels */ - if (tilebytesize == 4 * tilelen) { /* float pixels */ - -#if BYTESWAPPED - if (tempfloat) - ffswap4((int *) tempfloat, tilelen); - else - ffswap4((int *) buffer, tilelen); -#endif - if (datatype == TFLOAT) { - if (nulval) { - fnulval = *(float *) nulval; - } - - fffr4r4((float *) buffer, (long) tilelen, 1., 0., nullcheck, - fnulval, bnullarray, anynul, - (float *) buffer, status); - } else if (datatype == TDOUBLE) { - if (nulval) { - dnulval = *(double *) nulval; - } - - /* note that the R*4 data are in the tempfloat array in this case */ - fffr4r8((float *) tempfloat, (long) tilelen, 1., 0., nullcheck, - dnulval, bnullarray, anynul, - (double *) buffer, status); - free(tempfloat); - - } else { - ffpmsg("implicit data type conversion is not supported for gzipped image tiles"); - return (*status = DATA_DECOMPRESSION_ERR); - } - } else if (tilebytesize == 8 * tilelen) { /* double pixels */ - -#if BYTESWAPPED - if (tempdouble) - ffswap8((double *) tempdouble, tilelen); - else - ffswap8((double *) buffer, tilelen); -#endif - if (datatype == TFLOAT) { - if (nulval) { - fnulval = *(float *) nulval; - } - - fffr8r4((double *) tempdouble, (long) tilelen, 1., 0., nullcheck, - fnulval, bnullarray, anynul, - (float *) buffer, status); - free(tempdouble); - tempdouble=0; - } else if (datatype == TDOUBLE) { - if (nulval) { - dnulval = *(double *) nulval; - } - - fffr8r8((double *) buffer, (long) tilelen, 1., 0., nullcheck, - dnulval, bnullarray, anynul, - (double *) buffer, status); - } else { - ffpmsg("implicit data type conversion is not supported in tile-compressed images"); - return (*status = DATA_DECOMPRESSION_ERR); - } - } else { - ffpmsg("error: uncompressed tile has wrong size"); - return (*status = DATA_DECOMPRESSION_ERR); - } - - /* end of special case of losslessly gzipping a floating-point image tile */ - } else { /* this should never happen */ - *status = NO_COMPRESSED_TILE; - } - - return(*status); - } - - /* **************************************************************** */ - /* deal with the normal case of a compressed tile of pixels */ - if (nullcheck == 2) { - for (ii = 0; ii < tilelen; ii++) /* initialize the null flage array */ - bnullarray[ii] = 0; - } - - if (anynul) - *anynul = 0; - - /* get linear scaling and offset values, if they exist */ - actual_bzero = (infptr->Fptr)->cn_actual_bzero; - if ((infptr->Fptr)->cn_zscale == 0) { - /* set default scaling, if scaling is not defined */ - bscale = 1.; - bzero = 0.; - } else if ((infptr->Fptr)->cn_zscale == -1) { - bscale = (infptr->Fptr)->zscale; - bzero = (infptr->Fptr)->zzero; - } else { - /* read the linear scale and offset values for this row */ - ffgcvd (infptr, (infptr->Fptr)->cn_zscale, nrow, 1, 1, 0., - &bscale, NULL, status); - ffgcvd (infptr, (infptr->Fptr)->cn_zzero, nrow, 1, 1, 0., - &bzero, NULL, status); - if (*status > 0) - { - ffpmsg("error reading scaling factor and offset for compressed tile"); - return (*status); - } - - /* test if floating-point FITS image also has non-default BSCALE and */ - /* BZERO keywords. If so, we have to combine the 2 linear scaling factors. */ - - if ( ((infptr->Fptr)->zbitpix == FLOAT_IMG || - (infptr->Fptr)->zbitpix == DOUBLE_IMG ) - && - ((infptr->Fptr)->cn_bscale != 1.0 || - (infptr->Fptr)->cn_bzero != 0.0 ) ) - { - bscale = bscale * (infptr->Fptr)->cn_bscale; - bzero = bzero * (infptr->Fptr)->cn_bscale + (infptr->Fptr)->cn_bzero; - } - } - - if (bscale == 1.0 && bzero == 0.0 ) { - /* if no other scaling has been specified, try using the values - given by the BSCALE and BZERO keywords, if any */ - - bscale = (infptr->Fptr)->cn_bscale; - bzero = (infptr->Fptr)->cn_bzero; - } - - /* ************************************************************* */ - /* get the value used to represent nulls in the int array */ - if ((infptr->Fptr)->cn_zblank == 0) { - nullcheck = 0; /* no null value; don't check for nulls */ - } else if ((infptr->Fptr)->cn_zblank == -1) { - tnull = (infptr->Fptr)->zblank; /* use the the ZBLANK keyword */ - } else { - /* read the null value for this row */ - ffgcvk (infptr, (infptr->Fptr)->cn_zblank, nrow, 1, 1, 0, - &tnull, NULL, status); - if (*status > 0) { - ffpmsg("error reading null value for compressed tile"); - return (*status); - } - } - - /* ************************************************************* */ - /* allocate memory for the uncompressed array of tile integers */ - /* The size depends on the datatype and the compression type. */ - - if ((infptr->Fptr)->compress_type == HCOMPRESS_1 && - ((infptr->Fptr)->zbitpix != BYTE_IMG && - (infptr->Fptr)->zbitpix != SHORT_IMG) ) { - - idatalen = tilelen * sizeof(LONGLONG); /* 8 bytes per pixel */ - - } else if ( (infptr->Fptr)->compress_type == RICE_1 && - (infptr->Fptr)->zbitpix == BYTE_IMG && - (infptr->Fptr)->rice_bytepix == 1) { - - idatalen = tilelen * sizeof(char); /* 1 byte per pixel */ - } else if ( ( (infptr->Fptr)->compress_type == GZIP_1 || - (infptr->Fptr)->compress_type == GZIP_2 || - (infptr->Fptr)->compress_type == BZIP2_1 ) && - (infptr->Fptr)->zbitpix == BYTE_IMG ) { - - idatalen = tilelen * sizeof(char); /* 1 byte per pixel */ - } else if ( (infptr->Fptr)->compress_type == RICE_1 && - (infptr->Fptr)->zbitpix == SHORT_IMG && - (infptr->Fptr)->rice_bytepix == 2) { - - idatalen = tilelen * sizeof(short); /* 2 bytes per pixel */ - } else if ( ( (infptr->Fptr)->compress_type == GZIP_1 || - (infptr->Fptr)->compress_type == GZIP_2 || - (infptr->Fptr)->compress_type == BZIP2_1 ) && - (infptr->Fptr)->zbitpix == SHORT_IMG ) { - - idatalen = tilelen * sizeof(short); /* 2 bytes per pixel */ - } else if ( ( (infptr->Fptr)->compress_type == GZIP_1 || - (infptr->Fptr)->compress_type == GZIP_2 || - (infptr->Fptr)->compress_type == BZIP2_1 ) && - (infptr->Fptr)->zbitpix == DOUBLE_IMG ) { - - idatalen = tilelen * sizeof(double); /* 8 bytes per pixel */ - } else { - idatalen = tilelen * sizeof(int); /* all other cases have int pixels */ - } - - idata = (int*) malloc (idatalen); - if (idata == NULL) { - ffpmsg("Memory allocation failure for idata. (imcomp_decompress_tile)"); - return (*status = MEMORY_ALLOCATION); - } - - /* ************************************************************* */ - /* allocate memory for the compressed bytes */ - - if ((infptr->Fptr)->compress_type == PLIO_1) { - cbuf = (unsigned char *) malloc ((long) nelemll * sizeof (short)); - } else { - cbuf = (unsigned char *) malloc ((long) nelemll); - } - if (cbuf == NULL) { - ffpmsg("Out of memory for cbuf. (imcomp_decompress_tile)"); - free(idata); - return (*status = MEMORY_ALLOCATION); - } - - /* ************************************************************* */ - /* read the compressed bytes from the FITS file */ - - if ((infptr->Fptr)->compress_type == PLIO_1) { - fits_read_col(infptr, TSHORT, (infptr->Fptr)->cn_compressed, nrow, - 1, (long) nelemll, &snull, (short *) cbuf, NULL, status); - } else { - fits_read_col(infptr, TBYTE, (infptr->Fptr)->cn_compressed, nrow, - 1, (long) nelemll, &charnull, cbuf, NULL, status); - } - - if (*status > 0) { - ffpmsg("error reading compressed byte stream from binary table"); - free (cbuf); - free(idata); - return (*status); - } - - /* ************************************************************* */ - /* call the algorithm-specific code to uncompress the tile */ - - if ((infptr->Fptr)->compress_type == RICE_1) { - - blocksize = (infptr->Fptr)->rice_blocksize; - - if ((infptr->Fptr)->rice_bytepix == 1 ) { - *status = fits_rdecomp_byte (cbuf, (long) nelemll, (unsigned char *)idata, - tilelen, blocksize); - tiledatatype = TBYTE; - } else if ((infptr->Fptr)->rice_bytepix == 2 ) { - *status = fits_rdecomp_short (cbuf, (long) nelemll, (unsigned short *)idata, - tilelen, blocksize); - tiledatatype = TSHORT; - } else { - *status = fits_rdecomp (cbuf, (long) nelemll, (unsigned int *)idata, - tilelen, blocksize); - tiledatatype = TINT; - } - - /* ************************************************************* */ - } else if ((infptr->Fptr)->compress_type == HCOMPRESS_1) { - - smooth = (infptr->Fptr)->hcomp_smooth; - - if ( ((infptr->Fptr)->zbitpix == BYTE_IMG || (infptr->Fptr)->zbitpix == SHORT_IMG)) { - *status = fits_hdecompress(cbuf, smooth, idata, &nx, &ny, - &scale, status); - } else { /* zbitpix = LONG_IMG (32) */ - /* idata must have been allocated twice as large for this to work */ - *status = fits_hdecompress64(cbuf, smooth, (LONGLONG *) idata, &nx, &ny, - &scale, status); - } - - tiledatatype = TINT; - - /* ************************************************************* */ - } else if ((infptr->Fptr)->compress_type == PLIO_1) { - - pl_l2pi ((short *) cbuf, 1, idata, tilelen); /* uncompress the data */ - tiledatatype = TINT; - - /* ************************************************************* */ - } else if ( ((infptr->Fptr)->compress_type == GZIP_1) || - ((infptr->Fptr)->compress_type == GZIP_2) ) { - - uncompress2mem_from_mem ((char *)cbuf, (long) nelemll, - (char **) &idata, &idatalen, realloc, &tilebytesize, status); - - /* determine the data type of the uncompressed array, and */ - /* do byte unshuffling and unswapping if needed */ - if (tilebytesize == (size_t) (tilelen * 2)) { - /* this is a short I*2 array */ - tiledatatype = TSHORT; - - if ( (infptr->Fptr)->compress_type == GZIP_2 ) - fits_unshuffle_2bytes((char *) idata, tilelen, status); - -#if BYTESWAPPED - ffswap2((short *) idata, tilelen); -#endif - - } else if (tilebytesize == (size_t) (tilelen * 4)) { - /* this is a int I*4 array (or maybe R*4) */ - tiledatatype = TINT; - - if ( (infptr->Fptr)->compress_type == GZIP_2 ) - fits_unshuffle_4bytes((char *) idata, tilelen, status); - -#if BYTESWAPPED - ffswap4(idata, tilelen); -#endif - - } else if (tilebytesize == (size_t) (tilelen * 8)) { - /* this is a R*8 double array */ - tiledatatype = TDOUBLE; - - if ( (infptr->Fptr)->compress_type == GZIP_2 ) - fits_unshuffle_8bytes((char *) idata, tilelen, status); -#if BYTESWAPPED - ffswap8((double *) idata, tilelen); -#endif - - } else if (tilebytesize == (size_t) tilelen) { - - /* this is an unsigned char I*1 array */ - tiledatatype = TBYTE; - - } else { - ffpmsg("error: uncompressed tile has wrong size"); - free(idata); - return (*status = DATA_DECOMPRESSION_ERR); - } - - /* ************************************************************* */ - } else if ((infptr->Fptr)->compress_type == BZIP2_1) { - -/* BZIP2 is not supported in the public release; this is only for test purposes - - if (BZ2_bzBuffToBuffDecompress ((char *) idata, &idatalen, - (char *)cbuf, (unsigned int) nelemll, 0, 0) ) -*/ - { - ffpmsg("bzip2 decompression error"); - free(idata); - free (cbuf); - return (*status = DATA_DECOMPRESSION_ERR); - } - - if ((infptr->Fptr)->zbitpix == BYTE_IMG) { - tiledatatype = TBYTE; - } else if ((infptr->Fptr)->zbitpix == SHORT_IMG) { - tiledatatype = TSHORT; -#if BYTESWAPPED - ffswap2((short *) idata, tilelen); -#endif - } else { - tiledatatype = TINT; -#if BYTESWAPPED - ffswap4(idata, tilelen); -#endif - } - - /* ************************************************************* */ - } else { - ffpmsg("unknown compression algorithm"); - free(idata); - return (*status = DATA_DECOMPRESSION_ERR); - } - - free(cbuf); - if (*status) { /* error uncompressing the tile */ - free(idata); - return (*status); - } - - /* ************************************************************* */ - /* copy the uncompressed tile data to the output buffer, doing */ - /* null checking, datatype conversion and linear scaling, if necessary */ - - if (nulval == 0) - nulval = &dummy; /* set address to dummy value */ - - if (datatype == TSHORT) - { - pixlen = sizeof(short); - - if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) { - /* the floating point pixels were losselessly compressed with GZIP */ - /* Just have to copy the values to the output array */ - - if (tiledatatype == TINT) { - fffr4i2((float *) idata, tilelen, bscale, bzero, nullcheck, - *(short *) nulval, bnullarray, anynul, - (short *) buffer, status); - } else { - fffr8i2((double *) idata, tilelen, bscale, bzero, nullcheck, - *(short *) nulval, bnullarray, anynul, - (short *) buffer, status); - } - } else if (tiledatatype == TINT) { - if ((infptr->Fptr)->compress_type == PLIO_1 && actual_bzero == 32768.) { - /* special case where unsigned 16-bit integers have been */ - /* offset by +32768 when using PLIO */ - fffi4i2(idata, tilelen, bscale, bzero - 32768., nullcheck, tnull, - *(short *) nulval, bnullarray, anynul, - (short *) buffer, status); - } else { - fffi4i2(idata, tilelen, bscale, bzero, nullcheck, tnull, - *(short *) nulval, bnullarray, anynul, - (short *) buffer, status); - - /* - Hcompress is a special case: ignore any numerical overflow - errors that may have occurred during the integer*4 to integer*2 - convertion. Overflows can happen when a lossy Hcompress algorithm - is invoked (with a non-zero scale factor). The fffi4i2 routine - clips the returned values to be within the legal I*2 range, so - all we need to is to reset the error status to zero. - */ - - if ((infptr->Fptr)->compress_type == HCOMPRESS_1) { - if ((*status == NUM_OVERFLOW) || (*status == OVERFLOW_ERR)) - *status = 0; - } - } - } else if (tiledatatype == TSHORT) { - fffi2i2((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - *(short *) nulval, bnullarray, anynul, - (short *) buffer, status); - } else if (tiledatatype == TBYTE) { - fffi1i2((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - *(short *) nulval, bnullarray, anynul, - (short *) buffer, status); - } - } - else if (datatype == TINT) - { - pixlen = sizeof(int); - - if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) { - /* the floating point pixels were losselessly compressed with GZIP */ - /* Just have to copy the values to the output array */ - - if (tiledatatype == TINT) { - fffr4int((float *) idata, tilelen, bscale, bzero, nullcheck, - *(int *) nulval, bnullarray, anynul, - (int *) buffer, status); - } else { - fffr8int((double *) idata, tilelen, bscale, bzero, nullcheck, - *(int *) nulval, bnullarray, anynul, - (int *) buffer, status); - } - } else if (tiledatatype == TINT) - if ((infptr->Fptr)->compress_type == PLIO_1 && actual_bzero == 32768.) { - /* special case where unsigned 16-bit integers have been */ - /* offset by +32768 when using PLIO */ - fffi4int(idata, (long) tilelen, bscale, bzero - 32768., nullcheck, tnull, - *(int *) nulval, bnullarray, anynul, - (int *) buffer, status); - } else { - fffi4int(idata, (long) tilelen, bscale, bzero, nullcheck, tnull, - *(int *) nulval, bnullarray, anynul, - (int *) buffer, status); - } - else if (tiledatatype == TSHORT) - fffi2int((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - *(int *) nulval, bnullarray, anynul, - (int *) buffer, status); - else if (tiledatatype == TBYTE) - fffi1int((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - *(int *) nulval, bnullarray, anynul, - (int *) buffer, status); - } - else if (datatype == TLONG) - { - pixlen = sizeof(long); - - if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) { - /* the floating point pixels were losselessly compressed with GZIP */ - /* Just have to copy the values to the output array */ - - if (tiledatatype == TINT) { - fffr4i4((float *) idata, tilelen, bscale, bzero, nullcheck, - *(long *) nulval, bnullarray, anynul, - (long *) buffer, status); - } else { - fffr8i4((double *) idata, tilelen, bscale, bzero, nullcheck, - *(long *) nulval, bnullarray, anynul, - (long *) buffer, status); - } - } else if (tiledatatype == TINT) - if ((infptr->Fptr)->compress_type == PLIO_1 && actual_bzero == 32768.) { - /* special case where unsigned 16-bit integers have been */ - /* offset by +32768 when using PLIO */ - fffi4i4(idata, tilelen, bscale, bzero - 32768., nullcheck, tnull, - *(long *) nulval, bnullarray, anynul, - (long *) buffer, status); - } else { - fffi4i4(idata, tilelen, bscale, bzero, nullcheck, tnull, - *(long *) nulval, bnullarray, anynul, - (long *) buffer, status); - } - else if (tiledatatype == TSHORT) - fffi2i4((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - *(long *) nulval, bnullarray, anynul, - (long *) buffer, status); - else if (tiledatatype == TBYTE) - fffi1i4((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - *(long *) nulval, bnullarray, anynul, - (long *) buffer, status); - } - else if (datatype == TFLOAT) - { - pixlen = sizeof(float); - if (nulval) { - fnulval = *(float *) nulval; - } - - if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) { - /* the floating point pixels were losselessly compressed with GZIP */ - /* Just have to copy the values to the output array */ - - if (tiledatatype == TINT) { - fffr4r4((float *) idata, tilelen, bscale, bzero, nullcheck, - fnulval, bnullarray, anynul, - (float *) buffer, status); - } else { - fffr8r4((double *) idata, tilelen, bscale, bzero, nullcheck, - fnulval, bnullarray, anynul, - (float *) buffer, status); - } - - } else if ((infptr->Fptr)->quantize_method == SUBTRACTIVE_DITHER_1 || - (infptr->Fptr)->quantize_method == SUBTRACTIVE_DITHER_2) { - - /* use the new dithering algorithm (introduced in July 2009) */ - - if (tiledatatype == TINT) - unquantize_i4r4(nrow + (infptr->Fptr)->dither_seed - 1, idata, - tilelen, bscale, bzero, (infptr->Fptr)->quantize_method, nullcheck, tnull, - fnulval, bnullarray, anynul, - (float *) buffer, status); - else if (tiledatatype == TSHORT) - unquantize_i2r4(nrow + (infptr->Fptr)->dither_seed - 1, (short *)idata, - tilelen, bscale, bzero, (infptr->Fptr)->quantize_method, nullcheck, (short) tnull, - fnulval, bnullarray, anynul, - (float *) buffer, status); - else if (tiledatatype == TBYTE) - unquantize_i1r4(nrow + (infptr->Fptr)->dither_seed - 1, (unsigned char *)idata, - tilelen, bscale, bzero, (infptr->Fptr)->quantize_method, nullcheck, (unsigned char) tnull, - fnulval, bnullarray, anynul, - (float *) buffer, status); - - } else { /* use the old "round to nearest level" quantization algorithm */ - - if (tiledatatype == TINT) - if ((infptr->Fptr)->compress_type == PLIO_1 && actual_bzero == 32768.) { - /* special case where unsigned 16-bit integers have been */ - /* offset by +32768 when using PLIO */ - fffi4r4(idata, tilelen, bscale, bzero - 32768., nullcheck, tnull, - fnulval, bnullarray, anynul, - (float *) buffer, status); - } else { - fffi4r4(idata, tilelen, bscale, bzero, nullcheck, tnull, - fnulval, bnullarray, anynul, - (float *) buffer, status); - } - else if (tiledatatype == TSHORT) - fffi2r4((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - fnulval, bnullarray, anynul, - (float *) buffer, status); - else if (tiledatatype == TBYTE) - fffi1r4((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - fnulval, bnullarray, anynul, - (float *) buffer, status); - } - } - else if (datatype == TDOUBLE) - { - pixlen = sizeof(double); - if (nulval) { - dnulval = *(double *) nulval; - } - - if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) { - /* the floating point pixels were losselessly compressed with GZIP */ - /* Just have to copy the values to the output array */ - - if (tiledatatype == TINT) { - fffr4r8((float *) idata, tilelen, bscale, bzero, nullcheck, - dnulval, bnullarray, anynul, - (double *) buffer, status); - } else { - fffr8r8((double *) idata, tilelen, bscale, bzero, nullcheck, - dnulval, bnullarray, anynul, - (double *) buffer, status); - } - - } else if ((infptr->Fptr)->quantize_method == SUBTRACTIVE_DITHER_1 || - (infptr->Fptr)->quantize_method == SUBTRACTIVE_DITHER_2) { - - /* use the new dithering algorithm (introduced in July 2009) */ - if (tiledatatype == TINT) - unquantize_i4r8(nrow + (infptr->Fptr)->dither_seed - 1, idata, - tilelen, bscale, bzero, (infptr->Fptr)->quantize_method, nullcheck, tnull, - dnulval, bnullarray, anynul, - (double *) buffer, status); - else if (tiledatatype == TSHORT) - unquantize_i2r8(nrow + (infptr->Fptr)->dither_seed - 1, (short *)idata, - tilelen, bscale, bzero, (infptr->Fptr)->quantize_method, nullcheck, (short) tnull, - dnulval, bnullarray, anynul, - (double *) buffer, status); - else if (tiledatatype == TBYTE) - unquantize_i1r8(nrow + (infptr->Fptr)->dither_seed - 1, (unsigned char *)idata, - tilelen, bscale, bzero, (infptr->Fptr)->quantize_method, nullcheck, (unsigned char) tnull, - dnulval, bnullarray, anynul, - (double *) buffer, status); - - } else { /* use the old "round to nearest level" quantization algorithm */ - - if (tiledatatype == TINT) { - if ((infptr->Fptr)->compress_type == PLIO_1 && actual_bzero == 32768.) { - /* special case where unsigned 16-bit integers have been */ - /* offset by +32768 when using PLIO */ - fffi4r8(idata, tilelen, bscale, bzero - 32768., nullcheck, tnull, - dnulval, bnullarray, anynul, - (double *) buffer, status); - } else { - fffi4r8(idata, tilelen, bscale, bzero, nullcheck, tnull, - dnulval, bnullarray, anynul, - (double *) buffer, status); - } - } else if (tiledatatype == TSHORT) { - fffi2r8((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - dnulval, bnullarray, anynul, - (double *) buffer, status); - } else if (tiledatatype == TBYTE) - fffi1r8((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - dnulval, bnullarray, anynul, - (double *) buffer, status); - } - } - else if (datatype == TBYTE) - { - pixlen = sizeof(char); - if (tiledatatype == TINT) - fffi4i1(idata, tilelen, bscale, bzero, nullcheck, tnull, - *(unsigned char *) nulval, bnullarray, anynul, - (unsigned char *) buffer, status); - else if (tiledatatype == TSHORT) - fffi2i1((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - *(unsigned char *) nulval, bnullarray, anynul, - (unsigned char *) buffer, status); - else if (tiledatatype == TBYTE) - fffi1i1((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - *(unsigned char *) nulval, bnullarray, anynul, - (unsigned char *) buffer, status); - } - else if (datatype == TSBYTE) - { - pixlen = sizeof(char); - if (tiledatatype == TINT) - fffi4s1(idata, tilelen, bscale, bzero, nullcheck, tnull, - *(signed char *) nulval, bnullarray, anynul, - (signed char *) buffer, status); - else if (tiledatatype == TSHORT) - fffi2s1((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - *(signed char *) nulval, bnullarray, anynul, - (signed char *) buffer, status); - else if (tiledatatype == TBYTE) - fffi1s1((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - *(signed char *) nulval, bnullarray, anynul, - (signed char *) buffer, status); - } - else if (datatype == TUSHORT) - { - pixlen = sizeof(short); - - if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) { - /* the floating point pixels were losselessly compressed with GZIP */ - /* Just have to copy the values to the output array */ - - if (tiledatatype == TINT) { - fffr4u2((float *) idata, tilelen, bscale, bzero, nullcheck, - *(unsigned short *) nulval, bnullarray, anynul, - (unsigned short *) buffer, status); - } else { - fffr8u2((double *) idata, tilelen, bscale, bzero, nullcheck, - *(unsigned short *) nulval, bnullarray, anynul, - (unsigned short *) buffer, status); - } - } else if (tiledatatype == TINT) - if ((infptr->Fptr)->compress_type == PLIO_1 && actual_bzero == 32768.) { - /* special case where unsigned 16-bit integers have been */ - /* offset by +32768 when using PLIO */ - fffi4u2(idata, tilelen, bscale, bzero - 32768., nullcheck, tnull, - *(unsigned short *) nulval, bnullarray, anynul, - (unsigned short *) buffer, status); - } else { - fffi4u2(idata, tilelen, bscale, bzero, nullcheck, tnull, - *(unsigned short *) nulval, bnullarray, anynul, - (unsigned short *) buffer, status); - } - else if (tiledatatype == TSHORT) - fffi2u2((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - *(unsigned short *) nulval, bnullarray, anynul, - (unsigned short *) buffer, status); - else if (tiledatatype == TBYTE) - fffi1u2((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - *(unsigned short *) nulval, bnullarray, anynul, - (unsigned short *) buffer, status); - } - else if (datatype == TUINT) - { - pixlen = sizeof(int); - - if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) { - /* the floating point pixels were losselessly compressed with GZIP */ - /* Just have to copy the values to the output array */ - - if (tiledatatype == TINT) { - fffr4uint((float *) idata, tilelen, bscale, bzero, nullcheck, - *(unsigned int *) nulval, bnullarray, anynul, - (unsigned int *) buffer, status); - } else { - fffr8uint((double *) idata, tilelen, bscale, bzero, nullcheck, - *(unsigned int *) nulval, bnullarray, anynul, - (unsigned int *) buffer, status); - } - } else - if (tiledatatype == TINT) - if ((infptr->Fptr)->compress_type == PLIO_1 && actual_bzero == 32768.) { - /* special case where unsigned 16-bit integers have been */ - /* offset by +32768 when using PLIO */ - fffi4uint(idata, tilelen, bscale, bzero - 32768., nullcheck, tnull, - *(unsigned int *) nulval, bnullarray, anynul, - (unsigned int *) buffer, status); - } else { - fffi4uint(idata, tilelen, bscale, bzero, nullcheck, tnull, - *(unsigned int *) nulval, bnullarray, anynul, - (unsigned int *) buffer, status); - } - else if (tiledatatype == TSHORT) - fffi2uint((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - *(unsigned int *) nulval, bnullarray, anynul, - (unsigned int *) buffer, status); - else if (tiledatatype == TBYTE) - fffi1uint((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - *(unsigned int *) nulval, bnullarray, anynul, - (unsigned int *) buffer, status); - } - else if (datatype == TULONG) - { - pixlen = sizeof(long); - - if ((infptr->Fptr)->quantize_level == NO_QUANTIZE) { - /* the floating point pixels were losselessly compressed with GZIP */ - /* Just have to copy the values to the output array */ - - if (tiledatatype == TINT) { - fffr4u4((float *) idata, tilelen, bscale, bzero, nullcheck, - *(unsigned long *) nulval, bnullarray, anynul, - (unsigned long *) buffer, status); - } else { - fffr8u4((double *) idata, tilelen, bscale, bzero, nullcheck, - *(unsigned long *) nulval, bnullarray, anynul, - (unsigned long *) buffer, status); - } - } else if (tiledatatype == TINT) - if ((infptr->Fptr)->compress_type == PLIO_1 && actual_bzero == 32768.) { - /* special case where unsigned 16-bit integers have been */ - /* offset by +32768 when using PLIO */ - fffi4u4(idata, tilelen, bscale, bzero - 32768., nullcheck, tnull, - *(unsigned long *) nulval, bnullarray, anynul, - (unsigned long *) buffer, status); - } else { - fffi4u4(idata, tilelen, bscale, bzero, nullcheck, tnull, - *(unsigned long *) nulval, bnullarray, anynul, - (unsigned long *) buffer, status); - } - else if (tiledatatype == TSHORT) - fffi2u4((short *)idata, tilelen, bscale, bzero, nullcheck, (short) tnull, - *(unsigned long *) nulval, bnullarray, anynul, - (unsigned long *) buffer, status); - else if (tiledatatype == TBYTE) - fffi1u4((unsigned char *)idata, tilelen, bscale, bzero, nullcheck, (unsigned char) tnull, - *(unsigned long *) nulval, bnullarray, anynul, - (unsigned long *) buffer, status); - } - else - *status = BAD_DATATYPE; - - free(idata); /* don't need the uncompressed tile any more */ - - /* **************************************************************** */ - /* cache the tile, in case the application wants it again */ - - /* Don't cache the tile if tile is a single row of the image; - it is less likely that the cache will be used in this cases, - so it is not worth the time and the memory overheads. - */ - - if ((infptr->Fptr)->tilerow) { /* make sure cache has been allocated */ - if ((infptr->Fptr)->znaxis[0] != (infptr->Fptr)->tilesize[0] || - (infptr->Fptr)->tilesize[1] != 1 ) - { - tilesize = pixlen * tilelen; - - /* check that tile size/type has not changed */ - if (tilesize != (infptr->Fptr)->tiledatasize[tilecol] || - datatype != (infptr->Fptr)->tiletype[tilecol] ) { - - if (((infptr->Fptr)->tiledata)[tilecol]) { - free(((infptr->Fptr)->tiledata)[tilecol]); - } - - if (((infptr->Fptr)->tilenullarray)[tilecol]) { - free(((infptr->Fptr)->tilenullarray)[tilecol]); - } - - ((infptr->Fptr)->tilenullarray)[tilecol] = 0; - ((infptr->Fptr)->tilerow)[tilecol] = 0; - ((infptr->Fptr)->tiledatasize)[tilecol] = 0; - ((infptr->Fptr)->tiletype)[tilecol] = 0; - - /* allocate new array(s) */ - ((infptr->Fptr)->tiledata)[tilecol] = malloc(tilesize); - - if (((infptr->Fptr)->tiledata)[tilecol] == 0) - return (*status); - - if (nullcheck == 2) { /* also need array of null pixel flags */ - (infptr->Fptr)->tilenullarray[tilecol] = malloc(tilelen); - if ((infptr->Fptr)->tilenullarray[tilecol] == 0) - return (*status); - } - - (infptr->Fptr)->tiledatasize[tilecol] = tilesize; - (infptr->Fptr)->tiletype[tilecol] = datatype; - } - - /* copy the tile array(s) into cache buffer */ - memcpy((infptr->Fptr)->tiledata[tilecol], buffer, tilesize); - - if (nullcheck == 2) { - if ((infptr->Fptr)->tilenullarray == 0) { - (infptr->Fptr)->tilenullarray[tilecol] = malloc(tilelen); - } - memcpy((infptr->Fptr)->tilenullarray[tilecol], bnullarray, tilelen); - } - - (infptr->Fptr)->tilerow[tilecol] = nrow; - (infptr->Fptr)->tileanynull[tilecol] = *anynul; - } - } - return (*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_test_overlap ( - int ndim, /* I - number of dimension in the tile and image */ - long *tfpixel, /* I - first pixel number in each dim. of the tile */ - long *tlpixel, /* I - last pixel number in each dim. of the tile */ - long *fpixel, /* I - first pixel number in each dim. of the image */ - long *lpixel, /* I - last pixel number in each dim. of the image */ - long *ininc, /* I - increment to be applied in each image dimen. */ - int *status) - -/* - test if there are any intersecting pixels between this tile and the section - of the image defined by fixel, lpixel, ininc. -*/ -{ - long imgdim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */ - /* output image, allowing for inc factor */ - long tiledim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */ - /* tile, array; inc factor is not relevant */ - long imgfpix[MAX_COMPRESS_DIM]; /* 1st img pix overlapping tile: 0 base, */ - /* allowing for inc factor */ - long imglpix[MAX_COMPRESS_DIM]; /* last img pix overlapping tile 0 base, */ - /* allowing for inc factor */ - long tilefpix[MAX_COMPRESS_DIM]; /* 1st tile pix overlapping img 0 base, */ - /* allowing for inc factor */ - long inc[MAX_COMPRESS_DIM]; /* local copy of input ininc */ - int ii; - long tf, tl; - - if (*status > 0) - return(*status); - - - /* ------------------------------------------------------------ */ - /* calc amount of overlap in each dimension; if there is zero */ - /* overlap in any dimension then just return */ - /* ------------------------------------------------------------ */ - - for (ii = 0; ii < ndim; ii++) - { - if (tlpixel[ii] < fpixel[ii] || tfpixel[ii] > lpixel[ii]) - return(0); /* there are no overlapping pixels */ - - inc[ii] = ininc[ii]; - - /* calc dimensions of the output image section */ - imgdim[ii] = (lpixel[ii] - fpixel[ii]) / labs(inc[ii]) + 1; - if (imgdim[ii] < 1) { - *status = NEG_AXIS; - return(0); - } - - /* calc dimensions of the tile */ - tiledim[ii] = tlpixel[ii] - tfpixel[ii] + 1; - if (tiledim[ii] < 1) { - *status = NEG_AXIS; - return(0); - } - - if (ii > 0) - tiledim[ii] *= tiledim[ii - 1]; /* product of dimensions */ - - /* first and last pixels in image that overlap with the tile, 0 base */ - tf = tfpixel[ii] - 1; - tl = tlpixel[ii] - 1; - - /* skip this plane if it falls in the cracks of the subsampled image */ - while ((tf-(fpixel[ii] - 1)) % labs(inc[ii])) - { - tf++; - if (tf > tl) - return(0); /* no overlapping pixels */ - } - - while ((tl-(fpixel[ii] - 1)) % labs(inc[ii])) - { - tl--; - if (tf > tl) - return(0); /* no overlapping pixels */ - } - imgfpix[ii] = maxvalue((tf - fpixel[ii] +1) / labs(inc[ii]) , 0); - imglpix[ii] = minvalue((tl - fpixel[ii] +1) / labs(inc[ii]) , - imgdim[ii] - 1); - - /* first pixel in the tile that overlaps with the image (0 base) */ - tilefpix[ii] = maxvalue(fpixel[ii] - tfpixel[ii], 0); - - while ((tfpixel[ii] + tilefpix[ii] - fpixel[ii]) % labs(inc[ii])) - { - (tilefpix[ii])++; - if (tilefpix[ii] >= tiledim[ii]) - return(0); /* no overlapping pixels */ - } - - if (ii > 0) - imgdim[ii] *= imgdim[ii - 1]; /* product of dimensions */ - } - - return(1); /* there appears to be intersecting pixels */ -} -/*--------------------------------------------------------------------------*/ -int imcomp_copy_overlap ( - char *tile, /* I - multi dimensional array of tile pixels */ - int pixlen, /* I - number of bytes in each tile or image pixel */ - int ndim, /* I - number of dimension in the tile and image */ - long *tfpixel, /* I - first pixel number in each dim. of the tile */ - long *tlpixel, /* I - last pixel number in each dim. of the tile */ - char *bnullarray, /* I - array of null flags; used if nullcheck = 2 */ - char *image, /* O - multi dimensional output image */ - long *fpixel, /* I - first pixel number in each dim. of the image */ - long *lpixel, /* I - last pixel number in each dim. of the image */ - long *ininc, /* I - increment to be applied in each image dimen. */ - int nullcheck, /* I - 0, 1: do nothing; 2: set nullarray for nulls */ - char *nullarray, - int *status) - -/* - copy the intersecting pixels from a decompressed tile to the output image. - Both the tile and the image must have the same number of dimensions. -*/ -{ - long imgdim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */ - /* output image, allowing for inc factor */ - long tiledim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */ - /* tile, array; inc factor is not relevant */ - long imgfpix[MAX_COMPRESS_DIM]; /* 1st img pix overlapping tile: 0 base, */ - /* allowing for inc factor */ - long imglpix[MAX_COMPRESS_DIM]; /* last img pix overlapping tile 0 base, */ - /* allowing for inc factor */ - long tilefpix[MAX_COMPRESS_DIM]; /* 1st tile pix overlapping img 0 base, */ - /* allowing for inc factor */ - long inc[MAX_COMPRESS_DIM]; /* local copy of input ininc */ - long i1, i2, i3, i4; /* offset along each axis of the image */ - long it1, it2, it3, it4; - long im1, im2, im3, im4; /* offset to image pixel, allowing for inc */ - long ipos, tf, tl; - long t2, t3, t4; /* offset along each axis of the tile */ - long tilepix, imgpix, tilepixbyte, imgpixbyte; - int ii, overlap_bytes, overlap_flags; - - if (*status > 0) - return(*status); - - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - /* set default values for higher dimensions */ - inc[ii] = 1; - imgdim[ii] = 1; - tiledim[ii] = 1; - imgfpix[ii] = 0; - imglpix[ii] = 0; - tilefpix[ii] = 0; - } - - /* ------------------------------------------------------------ */ - /* calc amount of overlap in each dimension; if there is zero */ - /* overlap in any dimension then just return */ - /* ------------------------------------------------------------ */ - - for (ii = 0; ii < ndim; ii++) - { - if (tlpixel[ii] < fpixel[ii] || tfpixel[ii] > lpixel[ii]) - return(*status); /* there are no overlapping pixels */ - - inc[ii] = ininc[ii]; - - /* calc dimensions of the output image section */ - imgdim[ii] = (lpixel[ii] - fpixel[ii]) / labs(inc[ii]) + 1; - if (imgdim[ii] < 1) - return(*status = NEG_AXIS); - - /* calc dimensions of the tile */ - tiledim[ii] = tlpixel[ii] - tfpixel[ii] + 1; - if (tiledim[ii] < 1) - return(*status = NEG_AXIS); - - if (ii > 0) - tiledim[ii] *= tiledim[ii - 1]; /* product of dimensions */ - - /* first and last pixels in image that overlap with the tile, 0 base */ - tf = tfpixel[ii] - 1; - tl = tlpixel[ii] - 1; - - /* skip this plane if it falls in the cracks of the subsampled image */ - while ((tf-(fpixel[ii] - 1)) % labs(inc[ii])) - { - tf++; - if (tf > tl) - return(*status); /* no overlapping pixels */ - } - - while ((tl-(fpixel[ii] - 1)) % labs(inc[ii])) - { - tl--; - if (tf > tl) - return(*status); /* no overlapping pixels */ - } - imgfpix[ii] = maxvalue((tf - fpixel[ii] +1) / labs(inc[ii]) , 0); - imglpix[ii] = minvalue((tl - fpixel[ii] +1) / labs(inc[ii]) , - imgdim[ii] - 1); - - /* first pixel in the tile that overlaps with the image (0 base) */ - tilefpix[ii] = maxvalue(fpixel[ii] - tfpixel[ii], 0); - - while ((tfpixel[ii] + tilefpix[ii] - fpixel[ii]) % labs(inc[ii])) - { - (tilefpix[ii])++; - if (tilefpix[ii] >= tiledim[ii]) - return(*status); /* no overlapping pixels */ - } -/* -printf("ii tfpixel, tlpixel %d %d %d \n",ii, tfpixel[ii], tlpixel[ii]); -printf("ii, tf, tl, imgfpix,imglpix, tilefpix %d %d %d %d %d %d\n",ii, - tf,tl,imgfpix[ii], imglpix[ii],tilefpix[ii]); -*/ - if (ii > 0) - imgdim[ii] *= imgdim[ii - 1]; /* product of dimensions */ - } - - /* ---------------------------------------------------------------- */ - /* calc number of pixels in each row (first dimension) that overlap */ - /* multiply by pixlen to get number of bytes to copy in each loop */ - /* ---------------------------------------------------------------- */ - - if (inc[0] != 1) - overlap_flags = 1; /* can only copy 1 pixel at a time */ - else - overlap_flags = imglpix[0] - imgfpix[0] + 1; /* can copy whole row */ - - overlap_bytes = overlap_flags * pixlen; - - /* support up to 5 dimensions for now */ - for (i4 = 0, it4=0; i4 <= imglpix[4] - imgfpix[4]; i4++, it4++) - { - /* increment plane if it falls in the cracks of the subsampled image */ - while (ndim > 4 && (tfpixel[4] + tilefpix[4] - fpixel[4] + it4) - % labs(inc[4]) != 0) - it4++; - - /* offset to start of hypercube */ - if (inc[4] > 0) - im4 = (i4 + imgfpix[4]) * imgdim[3]; - else - im4 = imgdim[4] - (i4 + 1 + imgfpix[4]) * imgdim[3]; - - t4 = (tilefpix[4] + it4) * tiledim[3]; - for (i3 = 0, it3=0; i3 <= imglpix[3] - imgfpix[3]; i3++, it3++) - { - /* increment plane if it falls in the cracks of the subsampled image */ - while (ndim > 3 && (tfpixel[3] + tilefpix[3] - fpixel[3] + it3) - % labs(inc[3]) != 0) - it3++; - - /* offset to start of cube */ - if (inc[3] > 0) - im3 = (i3 + imgfpix[3]) * imgdim[2] + im4; - else - im3 = imgdim[3] - (i3 + 1 + imgfpix[3]) * imgdim[2] + im4; - - t3 = (tilefpix[3] + it3) * tiledim[2] + t4; - - /* loop through planes of the image */ - for (i2 = 0, it2=0; i2 <= imglpix[2] - imgfpix[2]; i2++, it2++) - { - /* incre plane if it falls in the cracks of the subsampled image */ - while (ndim > 2 && (tfpixel[2] + tilefpix[2] - fpixel[2] + it2) - % labs(inc[2]) != 0) - it2++; - - /* offset to start of plane */ - if (inc[2] > 0) - im2 = (i2 + imgfpix[2]) * imgdim[1] + im3; - else - im2 = imgdim[2] - (i2 + 1 + imgfpix[2]) * imgdim[1] + im3; - - t2 = (tilefpix[2] + it2) * tiledim[1] + t3; - - /* loop through rows of the image */ - for (i1 = 0, it1=0; i1 <= imglpix[1] - imgfpix[1]; i1++, it1++) - { - /* incre row if it falls in the cracks of the subsampled image */ - while (ndim > 1 && (tfpixel[1] + tilefpix[1] - fpixel[1] + it1) - % labs(inc[1]) != 0) - it1++; - - /* calc position of first pixel in tile to be copied */ - tilepix = tilefpix[0] + (tilefpix[1] + it1) * tiledim[0] + t2; - - /* offset to start of row */ - if (inc[1] > 0) - im1 = (i1 + imgfpix[1]) * imgdim[0] + im2; - else - im1 = imgdim[1] - (i1 + 1 + imgfpix[1]) * imgdim[0] + im2; -/* -printf("inc = %d %d %d %d\n",inc[0],inc[1],inc[2],inc[3]); -printf("im1,im2,im3,im4 = %d %d %d %d\n",im1,im2,im3,im4); -*/ - /* offset to byte within the row */ - if (inc[0] > 0) - imgpix = imgfpix[0] + im1; - else - imgpix = imgdim[0] - 1 - imgfpix[0] + im1; -/* -printf("tilefpix0,1, imgfpix1, it1, inc1, t2= %d %d %d %d %d %d\n", - tilefpix[0],tilefpix[1],imgfpix[1],it1,inc[1], t2); -printf("i1, it1, tilepix, imgpix %d %d %d %d \n", i1, it1, tilepix, imgpix); -*/ - /* loop over pixels along one row of the image */ - for (ipos = imgfpix[0]; ipos <= imglpix[0]; ipos += overlap_flags) - { - if (nullcheck == 2) - { - /* copy overlapping null flags from tile to image */ - memcpy(nullarray + imgpix, bnullarray + tilepix, - overlap_flags); - } - - /* convert from image pixel to byte offset */ - tilepixbyte = tilepix * pixlen; - imgpixbyte = imgpix * pixlen; -/* -printf(" tilepix, tilepixbyte, imgpix, imgpixbyte= %d %d %d %d\n", - tilepix, tilepixbyte, imgpix, imgpixbyte); -*/ - /* copy overlapping row of pixels from tile to image */ - memcpy(image + imgpixbyte, tile + tilepixbyte, overlap_bytes); - - tilepix += (overlap_flags * labs(inc[0])); - if (inc[0] > 0) - imgpix += overlap_flags; - else - imgpix -= overlap_flags; - } - } - } - } - } - return(*status); -} -/*--------------------------------------------------------------------------*/ -int imcomp_merge_overlap ( - char *tile, /* O - multi dimensional array of tile pixels */ - int pixlen, /* I - number of bytes in each tile or image pixel */ - int ndim, /* I - number of dimension in the tile and image */ - long *tfpixel, /* I - first pixel number in each dim. of the tile */ - long *tlpixel, /* I - last pixel number in each dim. of the tile */ - char *bnullarray, /* I - array of null flags; used if nullcheck = 2 */ - char *image, /* I - multi dimensional output image */ - long *fpixel, /* I - first pixel number in each dim. of the image */ - long *lpixel, /* I - last pixel number in each dim. of the image */ - int nullcheck, /* I - 0, 1: do nothing; 2: set nullarray for nulls */ - int *status) - -/* - Similar to imcomp_copy_overlap, except it copies the overlapping pixels from - the 'image' to the 'tile'. -*/ -{ - long imgdim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */ - /* output image, allowing for inc factor */ - long tiledim[MAX_COMPRESS_DIM]; /* product of preceding dimensions in the */ - /* tile, array; inc factor is not relevant */ - long imgfpix[MAX_COMPRESS_DIM]; /* 1st img pix overlapping tile: 0 base, */ - /* allowing for inc factor */ - long imglpix[MAX_COMPRESS_DIM]; /* last img pix overlapping tile 0 base, */ - /* allowing for inc factor */ - long tilefpix[MAX_COMPRESS_DIM]; /* 1st tile pix overlapping img 0 base, */ - /* allowing for inc factor */ - long inc[MAX_COMPRESS_DIM]; /* local copy of input ininc */ - long i1, i2, i3, i4; /* offset along each axis of the image */ - long it1, it2, it3, it4; - long im1, im2, im3, im4; /* offset to image pixel, allowing for inc */ - long ipos, tf, tl; - long t2, t3, t4; /* offset along each axis of the tile */ - long tilepix, imgpix, tilepixbyte, imgpixbyte; - int ii, overlap_bytes, overlap_flags; - - if (*status > 0) - return(*status); - - for (ii = 0; ii < MAX_COMPRESS_DIM; ii++) - { - /* set default values for higher dimensions */ - inc[ii] = 1; - imgdim[ii] = 1; - tiledim[ii] = 1; - imgfpix[ii] = 0; - imglpix[ii] = 0; - tilefpix[ii] = 0; - } - - /* ------------------------------------------------------------ */ - /* calc amount of overlap in each dimension; if there is zero */ - /* overlap in any dimension then just return */ - /* ------------------------------------------------------------ */ - - for (ii = 0; ii < ndim; ii++) - { - if (tlpixel[ii] < fpixel[ii] || tfpixel[ii] > lpixel[ii]) - return(*status); /* there are no overlapping pixels */ - - /* calc dimensions of the output image section */ - imgdim[ii] = (lpixel[ii] - fpixel[ii]) / labs(inc[ii]) + 1; - if (imgdim[ii] < 1) - return(*status = NEG_AXIS); - - /* calc dimensions of the tile */ - tiledim[ii] = tlpixel[ii] - tfpixel[ii] + 1; - if (tiledim[ii] < 1) - return(*status = NEG_AXIS); - - if (ii > 0) - tiledim[ii] *= tiledim[ii - 1]; /* product of dimensions */ - - /* first and last pixels in image that overlap with the tile, 0 base */ - tf = tfpixel[ii] - 1; - tl = tlpixel[ii] - 1; - - /* skip this plane if it falls in the cracks of the subsampled image */ - while ((tf-(fpixel[ii] - 1)) % labs(inc[ii])) - { - tf++; - if (tf > tl) - return(*status); /* no overlapping pixels */ - } - - while ((tl-(fpixel[ii] - 1)) % labs(inc[ii])) - { - tl--; - if (tf > tl) - return(*status); /* no overlapping pixels */ - } - imgfpix[ii] = maxvalue((tf - fpixel[ii] +1) / labs(inc[ii]) , 0); - imglpix[ii] = minvalue((tl - fpixel[ii] +1) / labs(inc[ii]) , - imgdim[ii] - 1); - - /* first pixel in the tile that overlaps with the image (0 base) */ - tilefpix[ii] = maxvalue(fpixel[ii] - tfpixel[ii], 0); - - while ((tfpixel[ii] + tilefpix[ii] - fpixel[ii]) % labs(inc[ii])) - { - (tilefpix[ii])++; - if (tilefpix[ii] >= tiledim[ii]) - return(*status); /* no overlapping pixels */ - } -/* -printf("ii tfpixel, tlpixel %d %d %d \n",ii, tfpixel[ii], tlpixel[ii]); -printf("ii, tf, tl, imgfpix,imglpix, tilefpix %d %d %d %d %d %d\n",ii, - tf,tl,imgfpix[ii], imglpix[ii],tilefpix[ii]); -*/ - if (ii > 0) - imgdim[ii] *= imgdim[ii - 1]; /* product of dimensions */ - } - - /* ---------------------------------------------------------------- */ - /* calc number of pixels in each row (first dimension) that overlap */ - /* multiply by pixlen to get number of bytes to copy in each loop */ - /* ---------------------------------------------------------------- */ - - if (inc[0] != 1) - overlap_flags = 1; /* can only copy 1 pixel at a time */ - else - overlap_flags = imglpix[0] - imgfpix[0] + 1; /* can copy whole row */ - - overlap_bytes = overlap_flags * pixlen; - - /* support up to 5 dimensions for now */ - for (i4 = 0, it4=0; i4 <= imglpix[4] - imgfpix[4]; i4++, it4++) - { - /* increment plane if it falls in the cracks of the subsampled image */ - while (ndim > 4 && (tfpixel[4] + tilefpix[4] - fpixel[4] + it4) - % labs(inc[4]) != 0) - it4++; - - /* offset to start of hypercube */ - if (inc[4] > 0) - im4 = (i4 + imgfpix[4]) * imgdim[3]; - else - im4 = imgdim[4] - (i4 + 1 + imgfpix[4]) * imgdim[3]; - - t4 = (tilefpix[4] + it4) * tiledim[3]; - for (i3 = 0, it3=0; i3 <= imglpix[3] - imgfpix[3]; i3++, it3++) - { - /* increment plane if it falls in the cracks of the subsampled image */ - while (ndim > 3 && (tfpixel[3] + tilefpix[3] - fpixel[3] + it3) - % labs(inc[3]) != 0) - it3++; - - /* offset to start of cube */ - if (inc[3] > 0) - im3 = (i3 + imgfpix[3]) * imgdim[2] + im4; - else - im3 = imgdim[3] - (i3 + 1 + imgfpix[3]) * imgdim[2] + im4; - - t3 = (tilefpix[3] + it3) * tiledim[2] + t4; - - /* loop through planes of the image */ - for (i2 = 0, it2=0; i2 <= imglpix[2] - imgfpix[2]; i2++, it2++) - { - /* incre plane if it falls in the cracks of the subsampled image */ - while (ndim > 2 && (tfpixel[2] + tilefpix[2] - fpixel[2] + it2) - % labs(inc[2]) != 0) - it2++; - - /* offset to start of plane */ - if (inc[2] > 0) - im2 = (i2 + imgfpix[2]) * imgdim[1] + im3; - else - im2 = imgdim[2] - (i2 + 1 + imgfpix[2]) * imgdim[1] + im3; - - t2 = (tilefpix[2] + it2) * tiledim[1] + t3; - - /* loop through rows of the image */ - for (i1 = 0, it1=0; i1 <= imglpix[1] - imgfpix[1]; i1++, it1++) - { - /* incre row if it falls in the cracks of the subsampled image */ - while (ndim > 1 && (tfpixel[1] + tilefpix[1] - fpixel[1] + it1) - % labs(inc[1]) != 0) - it1++; - - /* calc position of first pixel in tile to be copied */ - tilepix = tilefpix[0] + (tilefpix[1] + it1) * tiledim[0] + t2; - - /* offset to start of row */ - if (inc[1] > 0) - im1 = (i1 + imgfpix[1]) * imgdim[0] + im2; - else - im1 = imgdim[1] - (i1 + 1 + imgfpix[1]) * imgdim[0] + im2; -/* -printf("inc = %d %d %d %d\n",inc[0],inc[1],inc[2],inc[3]); -printf("im1,im2,im3,im4 = %d %d %d %d\n",im1,im2,im3,im4); -*/ - /* offset to byte within the row */ - if (inc[0] > 0) - imgpix = imgfpix[0] + im1; - else - imgpix = imgdim[0] - 1 - imgfpix[0] + im1; -/* -printf("tilefpix0,1, imgfpix1, it1, inc1, t2= %d %d %d %d %d %d\n", - tilefpix[0],tilefpix[1],imgfpix[1],it1,inc[1], t2); -printf("i1, it1, tilepix, imgpix %d %d %d %d \n", i1, it1, tilepix, imgpix); -*/ - /* loop over pixels along one row of the image */ - for (ipos = imgfpix[0]; ipos <= imglpix[0]; ipos += overlap_flags) - { - /* convert from image pixel to byte offset */ - tilepixbyte = tilepix * pixlen; - imgpixbyte = imgpix * pixlen; -/* -printf(" tilepix, tilepixbyte, imgpix, imgpixbyte= %d %d %d %d\n", - tilepix, tilepixbyte, imgpix, imgpixbyte); -*/ - /* copy overlapping row of pixels from image to tile */ - memcpy(tile + tilepixbyte, image + imgpixbyte, overlap_bytes); - - tilepix += (overlap_flags * labs(inc[0])); - if (inc[0] > 0) - imgpix += overlap_flags; - else - imgpix -= overlap_flags; - } - } - } - } - } - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int unquantize_i1r4(long row, /* tile number = row number in table */ - unsigned char *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - dithering method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ - float nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - float *output, /* O - array of converted pixels */ - int *status) /* IO - error status */ -/* - Unquantize byte values into the scaled floating point values -*/ -{ - long ii; - int nextrand, iseed; - - if (!fits_rand_value) - if (fits_init_randoms()) return(MEMORY_ALLOCATION); - - /* initialize the index to the next random number in the list */ - iseed = (int) ((row - 1) % N_RANDOM); - nextrand = (int) (fits_rand_value[iseed] * 500); - - if (nullcheck == 0) /* no null checking required */ - { - for (ii = 0; ii < ntodo; ii++) - { -/* - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else -*/ - output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - else /* must check for null values */ - { - for (ii = 0; ii < ntodo; ii++) - { - if (input[ii] == tnull) - { - *anynull = 1; - if (nullcheck == 1) - output[ii] = nullval; - else - nullarray[ii] = 1; - } - else - { -/* - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else -*/ - output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - } - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int unquantize_i2r4(long row, /* seed for random values */ - short *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - dithering method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - short tnull, /* I - value of FITS TNULLn keyword if any */ - float nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - float *output, /* O - array of converted pixels */ - int *status) /* IO - error status */ -/* - Unquantize short integer values into the scaled floating point values -*/ -{ - long ii; - int nextrand, iseed; - - if (!fits_rand_value) - if (fits_init_randoms()) return(MEMORY_ALLOCATION); - - /* initialize the index to the next random number in the list */ - iseed = (int) ((row - 1) % N_RANDOM); - nextrand = (int) (fits_rand_value[iseed] * 500); - - if (nullcheck == 0) /* no null checking required */ - { - for (ii = 0; ii < ntodo; ii++) - { -/* - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else -*/ - output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - else /* must check for null values */ - { - for (ii = 0; ii < ntodo; ii++) - { - if (input[ii] == tnull) - { - *anynull = 1; - if (nullcheck == 1) - output[ii] = nullval; - else - nullarray[ii] = 1; - } - else - { -/* - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else -*/ - output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - } - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int unquantize_i4r4(long row, /* tile number = row number in table */ - INT32BIT *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - dithering method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - INT32BIT tnull, /* I - value of FITS TNULLn keyword if any */ - float nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - float *output, /* O - array of converted pixels */ - int *status) /* IO - error status */ -/* - Unquantize int integer values into the scaled floating point values -*/ -{ - long ii; - int nextrand, iseed; - - if (fits_rand_value == 0) - if (fits_init_randoms()) return(MEMORY_ALLOCATION); - - /* initialize the index to the next random number in the list */ - iseed = (int) ((row - 1) % N_RANDOM); - nextrand = (int) (fits_rand_value[iseed] * 500); - - if (nullcheck == 0) /* no null checking required */ - { - for (ii = 0; ii < ntodo; ii++) - { - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else - output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - else /* must check for null values */ - { - for (ii = 0; ii < ntodo; ii++) - { - if (input[ii] == tnull) - { - *anynull = 1; - if (nullcheck == 1) - output[ii] = nullval; - else - nullarray[ii] = 1; - } - else - { - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else - output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - } - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int unquantize_i1r8(long row, /* tile number = row number in table */ - unsigned char *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - dithering method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ - double nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - double *output, /* O - array of converted pixels */ - int *status) /* IO - error status */ -/* - Unquantize byte values into the scaled floating point values -*/ -{ - long ii; - int nextrand, iseed; - - if (!fits_rand_value) - if (fits_init_randoms()) return(MEMORY_ALLOCATION); - - /* initialize the index to the next random number in the list */ - iseed = (int) ((row - 1) % N_RANDOM); - nextrand = (int) (fits_rand_value[iseed] * 500); - - if (nullcheck == 0) /* no null checking required */ - { - for (ii = 0; ii < ntodo; ii++) - { -/* - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else -*/ - output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - else /* must check for null values */ - { - for (ii = 0; ii < ntodo; ii++) - { - if (input[ii] == tnull) - { - *anynull = 1; - if (nullcheck == 1) - output[ii] = nullval; - else - nullarray[ii] = 1; - } - else - { -/* - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else -*/ - output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - } - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int unquantize_i2r8(long row, /* tile number = row number in table */ - short *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - dithering method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - short tnull, /* I - value of FITS TNULLn keyword if any */ - double nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - double *output, /* O - array of converted pixels */ - int *status) /* IO - error status */ -/* - Unquantize short integer values into the scaled floating point values -*/ -{ - long ii; - int nextrand, iseed; - - if (!fits_rand_value) - if (fits_init_randoms()) return(MEMORY_ALLOCATION); - - /* initialize the index to the next random number in the list */ - iseed = (int) ((row - 1) % N_RANDOM); - nextrand = (int) (fits_rand_value[iseed] * 500); - - if (nullcheck == 0) /* no null checking required */ - { - for (ii = 0; ii < ntodo; ii++) - { -/* - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else -*/ - output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - else /* must check for null values */ - { - for (ii = 0; ii < ntodo; ii++) - { - if (input[ii] == tnull) - { - *anynull = 1; - if (nullcheck == 1) - output[ii] = nullval; - else - nullarray[ii] = 1; - } - else - { -/* if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else -*/ - output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - } - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int unquantize_i4r8(long row, /* tile number = row number in table */ - INT32BIT *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - dithering method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - INT32BIT tnull, /* I - value of FITS TNULLn keyword if any */ - double nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - double *output, /* O - array of converted pixels */ - int *status) /* IO - error status */ -/* - Unquantize int integer values into the scaled floating point values -*/ -{ - long ii; - int nextrand, iseed; - - if (fits_rand_value == 0) - if (fits_init_randoms()) return(MEMORY_ALLOCATION); - - /* initialize the index to the next random number in the list */ - iseed = (int) ((row - 1) % N_RANDOM); - nextrand = (int) (fits_rand_value[iseed] * 500); - - if (nullcheck == 0) /* no null checking required */ - { - for (ii = 0; ii < ntodo; ii++) - { - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else - output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - else /* must check for null values */ - { - for (ii = 0; ii < ntodo; ii++) - { - if (input[ii] == tnull) - { - *anynull = 1; - if (nullcheck == 1) - output[ii] = nullval; - else - nullarray[ii] = 1; - } - else - { - if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) - output[ii] = 0.0; - else - output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - } - - nextrand++; - if (nextrand == N_RANDOM) { - iseed++; - if (iseed == N_RANDOM) iseed = 0; - nextrand = (int) (fits_rand_value[iseed] * 500); - } - } - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int imcomp_float2nan(float *indata, - long tilelen, - int *outdata, - float nullflagval, - int *status) -/* - convert pixels that are equal to nullflag to NaNs. - Note that indata and outdata point to the same location. -*/ -{ - int ii; - - for (ii = 0; ii < tilelen; ii++) { - - if (indata[ii] == nullflagval) - outdata[ii] = -1; /* integer -1 has the same bit pattern as a real*4 NaN */ - } - - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int imcomp_double2nan(double *indata, - long tilelen, - LONGLONG *outdata, - double nullflagval, - int *status) -/* - convert pixels that are equal to nullflag to NaNs. - Note that indata and outdata point to the same location. -*/ -{ - int ii; - - for (ii = 0; ii < tilelen; ii++) { - - if (indata[ii] == nullflagval) - outdata[ii] = -1; /* integer -1 has the same bit pattern as a real*8 NaN */ - } - - return(*status); -} - -/* ======================================================================= */ -/* TABLE COMPRESSION ROUTINES */ -/* =-====================================================================== */ - -/*--------------------------------------------------------------------------*/ -int fits_compress_table(fitsfile *infptr, fitsfile *outfptr, int *status) - -/* - Compress the input FITS Binary Table. - - First divide the table into equal sized chunks (analogous to image tiles) where all - the contain the same number of rows (except perhaps for the last chunk - which may contain fewer rows). The chunks should not be too large to copy into memory - (currently, about 100 MB max seems a reasonable size). - - Then, on a chunk by piece basis, do the following: - - 1. Transpose the table from its original row-major order, into column-major order. - All the bytes for each column are then continuous. In addition, the bytes within - each table element may be shuffled so that the most significant - byte of every element occurs first in the array, followed by the next most - significant byte, and so on to the least significant byte. Byte shuffling often - improves the gzip compression of floating-point arrays. - - 2. Compress the contiguous array of bytes in each column using the specified - compression method. If no method is specifed, then a default method for that - data type is chosen. - - 3. Store the compressed stream of bytes into a column that has the same name - as in the input table, but which has a variable-length array data type (1QB). - The output table will contain one row for each piece of the original table. - - 4. If the input table contain variable-length arrays, then each VLA - is compressed individually, and written to the heap in the output table. - Note that the output table will contain 2 sets of pointers for each VLA column. - The first set contains the pointers to the uncompressed VLAs from the input table - and the second is the set of pointers to the compressed VLAs in the output table. - The latter set of pointers is used to reconstruct table when it is uncompressed, - so that the heap has exactly the same structure as in the original file. The 2 - sets of pointers are concatinated together, compressed with gzip, and written to - the output table. When reading the compressed table, the only VLA that is directly - visible is this compressed array of descriptors. One has to uncompress this array - to be able to to read all the descriptors to the individual VLAs in the column. -*/ -{ - long maxchunksize = 10000000; /* default value for the size of each chunk of the table */ - - char *cm_buffer; /* memory buffer for the transposed, Column-Major, chunk of the table */ - LONGLONG cm_colstart[1000]; /* starting offset of each column in the cm_buffer */ - LONGLONG rm_repeat[1000]; /* repeat count of each column in the input row-major table */ - LONGLONG rm_colwidth[999]; /* width in bytes of each column in the input row-major table */ - LONGLONG cm_repeat[999]; /* total number of elements in each column of the transposed column-major table */ - - int coltype[999]; /* data type code for each column */ - int compalgor[999], default_algor = 0; /* compression algorithm to be applied to each column */ - float cratio[999]; /* compression ratio for each column (for diagnostic purposes) */ - - float compressed_size, uncompressed_size, tot_compressed_size, tot_uncompressed_size; - LONGLONG nrows, firstrow; - LONGLONG headstart, datastart, dataend, startbyte, jj, kk, naxis1; - LONGLONG vlalen, vlamemlen, vlastart, bytepos; - long repeat, width, nchunks, rowspertile, lastrows; - int ii, ll, ncols, hdutype, ltrue = 1, print_report = 0, tstatus; - char *cptr, keyname[9], tform[40], *cdescript; - char comm[FLEN_COMMENT], keyvalue[FLEN_VALUE], *cvlamem, tempstring[FLEN_VALUE], card[FLEN_CARD]; - - LONGLONG *descriptors, *outdescript, *vlamem; - int *pdescriptors; - size_t dlen, datasize, compmemlen; - - /* ================================================================================== */ - /* perform initial sanity checks */ - /* ================================================================================== */ - - /* special input flag value that means print out diagnostics */ - if (*status == -999) { - print_report = 1; - *status = 0; - } - - if (*status > 0) - return(*status); - - fits_get_hdu_type(infptr, &hdutype, status); - if (hdutype != BINARY_TBL) { - *status = NOT_BTABLE; - return(*status); - } - - if (infptr == outfptr) { - ffpmsg("Cannot compress table 'in place' (fits_compress_table)"); - ffpmsg(" outfptr cannot be the same as infptr."); - *status = DATA_COMPRESSION_ERR; - return(*status); - } - - /* get dimensions of the table */ - fits_get_num_rowsll(infptr, &nrows, status); - fits_get_num_cols(infptr, &ncols, status); - fits_read_key(infptr, TLONGLONG, "NAXIS1", &naxis1, NULL, status); - /* get offset to the start of the data and total size of the table (including the heap) */ - fits_get_hduaddrll(infptr, &headstart, &datastart, &dataend, status); - - if (*status > 0) - return(*status); - - tstatus = 0; - if (!fits_read_key(infptr, TSTRING, "FZALGOR", tempstring, NULL, &tstatus)) { - - if (!fits_strcasecmp(tempstring, "NONE")) { - default_algor = NOCOMPRESS; - } else if (!fits_strcasecmp(tempstring, "GZIP") || !fits_strcasecmp(tempstring, "GZIP_1")) { - default_algor = GZIP_1; - } else if (!fits_strcasecmp(tempstring, "GZIP_2")) { - default_algor = GZIP_2; - } else if (!fits_strcasecmp(tempstring, "RICE_1")) { - default_algor = RICE_1; - } else { - ffpmsg("FZALGOR specifies unsupported table compression algorithm:"); - ffpmsg(tempstring); - *status = DATA_COMPRESSION_ERR; - return(*status); - } - } - - /* just copy the HDU verbatim if the table has 0 columns or rows or if the table */ - /* is less than 5760 bytes (2 blocks) in size, or compression directive keyword = "NONE" */ - if (nrows < 1 || ncols < 1 || (dataend - datastart) < 5760 || default_algor == NOCOMPRESS) { - fits_copy_hdu (infptr, outfptr, 0, status); - return(*status); - } - - /* Check if the chunk size has been specified with the FZTILELN keyword. */ - /* If not, calculate a default number of rows per chunck, */ - - tstatus = 0; - if (fits_read_key(infptr, TLONG, "FZTILELN", &rowspertile, NULL, &tstatus)) { - rowspertile = (long) (maxchunksize / naxis1); - } - - if (rowspertile < 1) rowspertile = 1; - if (rowspertile > nrows) rowspertile = (long) nrows; - - nchunks = (long) ((nrows - 1) / rowspertile + 1); /* total number of chunks */ - lastrows = (long) (nrows - ((nchunks - 1) * rowspertile)); /* number of rows in last chunk */ - - /* allocate space for the transposed, column-major chunk of the table */ - cm_buffer = calloc((size_t) naxis1, (size_t) rowspertile); - if (!cm_buffer) { - ffpmsg("Could not allocate cm_buffer for transposed table"); - *status = MEMORY_ALLOCATION; - return(*status); - } - - /* ================================================================================== */ - /* Construct the header of the output compressed table */ - /* ================================================================================== */ - fits_copy_header(infptr, outfptr, status); /* start with verbatim copy of the input header */ - - fits_write_key(outfptr, TLOGICAL, "ZTABLE", <rue, "this is a compressed table", status); - fits_write_key(outfptr, TLONG, "ZTILELEN", &rowspertile, "number of rows in each tile", status); - - fits_read_card(outfptr, "NAXIS1", card, status); /* copy NAXIS1 to ZNAXIS1 */ - strncpy(card, "ZNAXIS1", 7); - fits_write_record(outfptr, card, status); - - fits_read_card(outfptr, "NAXIS2", card, status); /* copy NAXIS2 to ZNAXIS2 */ - strncpy(card, "ZNAXIS2", 7); - fits_write_record(outfptr, card, status); - - fits_read_card(outfptr, "PCOUNT", card, status); /* copy PCOUNT to ZPCOUNT */ - strncpy(card, "ZPCOUNT", 7); - fits_write_record(outfptr, card, status); - - fits_modify_key_lng(outfptr, "NAXIS2", nchunks, "&", status); /* 1 row per chunk */ - fits_modify_key_lng(outfptr, "NAXIS1", ncols * 16, "&", status); /* 16 bytes for each 1QB column */ - fits_modify_key_lng(outfptr, "PCOUNT", 0L, "&", status); /* reset PCOUNT to 0 */ - - /* rename the Checksum keywords, if they exist */ - tstatus = 0; - fits_modify_name(outfptr, "CHECKSUM", "ZHECKSUM", &tstatus); - tstatus = 0; - fits_modify_name(outfptr, "DATASUM", "ZDATASUM", &tstatus); - - /* ================================================================================== */ - /* Now loop over each column of the input table: write the column-specific keywords */ - /* and determine which compression algorithm to use. */ - /* Also calculate various offsets to the start of the column data in both the */ - /* original row-major table and in the transposed column-major form of the table. */ - /* ================================================================================== */ - - cm_colstart[0] = 0; - for (ii = 0; ii < ncols; ii++) { - - /* get the structural parameters of the original uncompressed column */ - fits_make_keyn("TFORM", ii+1, keyname, status); - fits_read_key(outfptr, TSTRING, keyname, tform, comm, status); - fits_binary_tform(tform, coltype+ii, &repeat, &width, status); /* get the repeat count and the width */ - - /* preserve the original TFORM value and comment string in a ZFORMn keyword */ - fits_read_card(outfptr, keyname, card, status); - card[0] = 'Z'; - fits_write_record(outfptr, card, status); - - /* All columns in the compressed table will have a variable-length array type. */ - fits_modify_key_str(outfptr, keyname, "1QB", "&", status); /* Use 'Q' pointers (64-bit) */ - - /* deal with special cases: bit, string, and variable length array columns */ - if (coltype[ii] == TBIT) { - repeat = (repeat + 7) / 8; /* convert from bits to equivalent number of bytes */ - } else if (coltype[ii] == TSTRING) { - width = 1; /* ignore the optional 'w' in 'rAw' format */ - } else if (coltype[ii] < 0) { /* pointer to variable length array */ - if (strchr(tform,'Q') ) { - width = 16; /* 'Q' descriptor has 64-bit pointers */ - } else { - width = 8; /* 'P' descriptor has 32-bit pointers */ - } - repeat = 1; - } - - rm_repeat[ii] = repeat; - rm_colwidth[ii] = repeat * width; /* column width (in bytes)in the input table */ - - /* starting offset of each field in the OUTPUT transposed column-major table */ - cm_colstart[ii + 1] = cm_colstart[ii] + rm_colwidth[ii] * rowspertile; - /* total number of elements in each column of the transposed column-major table */ - cm_repeat[ii] = rm_repeat[ii] * rowspertile; - - compalgor[ii] = default_algor; /* initialize the column compression algorithm to the default */ - - /* check if a compression method has been specified for this column */ - fits_make_keyn("FZALG", ii+1, keyname, status); - tstatus = 0; - if (!fits_read_key(outfptr, TSTRING, keyname, tempstring, NULL, &tstatus)) { - - if (!fits_strcasecmp(tempstring, "GZIP") || !fits_strcasecmp(tempstring, "GZIP_1")) { - compalgor[ii] = GZIP_1; - } else if (!fits_strcasecmp(tempstring, "GZIP_2")) { - compalgor[ii] = GZIP_2; - } else if (!fits_strcasecmp(tempstring, "RICE_1")) { - compalgor[ii] = RICE_1; - } else { - ffpmsg("Unsupported table compression algorithm specification."); - ffpmsg(keyname); - ffpmsg(tempstring); - *status = DATA_COMPRESSION_ERR; - free(cm_buffer); - return(*status); - } - } - - /* do sanity check of the requested algorithm and override if necessary */ - if ( abs(coltype[ii]) == TLOGICAL || abs(coltype[ii]) == TBIT || abs(coltype[ii]) == TSTRING) { - if (compalgor[ii] != GZIP_1) { - compalgor[ii] = GZIP_1; - } - } else if ( abs(coltype[ii]) == TCOMPLEX || abs(coltype[ii]) == TDBLCOMPLEX || - abs(coltype[ii]) == TFLOAT || abs(coltype[ii]) == TDOUBLE || - abs(coltype[ii]) == TLONGLONG ) { - if (compalgor[ii] != GZIP_1 && compalgor[ii] != GZIP_2) { - compalgor[ii] = GZIP_2; /* gzip_2 usually works better gzip_1 */ - } - } else if ( abs(coltype[ii]) == TSHORT ) { - if (compalgor[ii] != GZIP_1 && compalgor[ii] != GZIP_2 && compalgor[ii] != RICE_1) { - compalgor[ii] = GZIP_2; /* gzip_2 usually works better rice_1 */ - } - } else if ( abs(coltype[ii]) == TLONG ) { - if (compalgor[ii] != GZIP_1 && compalgor[ii] != GZIP_2 && compalgor[ii] != RICE_1) { - compalgor[ii] = RICE_1; - } - } else if ( abs(coltype[ii]) == TBYTE ) { - if (compalgor[ii] != GZIP_1 && compalgor[ii] != RICE_1 ) { - compalgor[ii] = GZIP_1; - } - } - } /* end of loop over columns */ - - /* ================================================================================== */ - /* now process each chunk of the table, in turn */ - /* ================================================================================== */ - - tot_uncompressed_size = 0.; - tot_compressed_size = 0; - firstrow = 1; - for (ll = 0; ll < nchunks; ll++) { - - if (ll == nchunks - 1) { /* the last chunk may have fewer rows */ - rowspertile = lastrows; - for (ii = 0; ii < ncols; ii++) { - cm_colstart[ii + 1] = cm_colstart[ii] + (rm_colwidth[ii] * rowspertile); - cm_repeat[ii] = rm_repeat[ii] * rowspertile; - } - } - - /* move to the start of the chunk in the input table */ - ffmbyt(infptr, datastart, 0, status); - - /* ================================================================================*/ - /* First, transpose this chunck from row-major order to column-major order */ - /* At the same time, shuffle the bytes in each datum, if doing GZIP_2 compression */ - /* ================================================================================*/ - - for (jj = 0; jj < rowspertile; jj++) { /* loop over rows */ - for (ii = 0; ii < ncols; ii++) { /* loop over columns */ - - if (rm_repeat[ii] > 0) { /* skip virtual columns that have 0 elements */ - - kk = 0; - - /* if the GZIP_2 compression algorithm is used, shuffle the bytes */ - if (coltype[ii] == TSHORT && compalgor[ii] == GZIP_2) { - while(kk < rm_colwidth[ii]) { - cptr = cm_buffer + (cm_colstart[ii] + (jj * rm_repeat[ii]) + kk/2); - ffgbyt(infptr, 1, cptr, status); /* get 1st byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 2nd byte */ - kk += 2; - } - } else if ((coltype[ii] == TFLOAT || coltype[ii] == TLONG) && compalgor[ii] == GZIP_2) { - while(kk < rm_colwidth[ii]) { - cptr = cm_buffer + (cm_colstart[ii] + (jj * rm_repeat[ii]) + kk/4); - ffgbyt(infptr, 1, cptr, status); /* get 1st byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 2nd byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 3rd byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 4th byte */ - kk += 4; - } - } else if ( (coltype[ii] == TDOUBLE || coltype[ii] == TLONGLONG) && compalgor[ii] == GZIP_2) { - while(kk < rm_colwidth[ii]) { - cptr = cm_buffer + (cm_colstart[ii] + (jj * rm_repeat[ii]) + kk/8); - ffgbyt(infptr, 1, cptr, status); /* get 1st byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 2nd byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 3rd byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 4th byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 5th byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 6th byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 7th byte */ - cptr += cm_repeat[ii]; - ffgbyt(infptr, 1, cptr, status); /* get 8th byte */ - kk += 8; - } - } else { /* all other cases: don't shuffle the bytes; simply transpose the column */ - cptr = cm_buffer + (cm_colstart[ii] + (jj * rm_colwidth[ii])); /* addr to copy to */ - startbyte = (infptr->Fptr)->bytepos; /* save the starting byte location */ - ffgbyt(infptr, rm_colwidth[ii], cptr, status); /* copy all the bytes */ - - if (rm_colwidth[ii] >= MINDIRECT) { /* have to explicitly move to next byte */ - ffmbyt(infptr, startbyte + rm_colwidth[ii], 0, status); - } - } /* end of test of coltypee */ - - } /* end of not virtual column */ - } /* end of loop over columns */ - } /* end of loop over rows */ - - /* ================================================================================*/ - /* now compress each column in the transposed chunk of the table */ - /* ================================================================================*/ - - fits_set_hdustruc(outfptr, status); /* initialize structures in the output table */ - - for (ii = 0; ii < ncols; ii++) { /* loop over columns */ - /* initialize the diagnostic compression results string */ - snprintf(results[ii],30,"%3d %3d %3d ", ii+1, coltype[ii], compalgor[ii]); - cratio[ii] = 0; - - if (rm_repeat[ii] > 0) { /* skip virtual columns with zero width */ - - if (coltype[ii] < 0) { /* this is a variable length array (VLA) column */ - - /*=========================================================================*/ - /* variable-length array columns are a complicated special case */ - /*=========================================================================*/ - - /* allocate memory to hold all the VLA descriptors from the input table, plus */ - /* room to hold the descriptors to the compressed VLAs in the output table */ - /* In total, there will be 2 descriptors for each row in this chunk */ - - uncompressed_size = 0.; - compressed_size = 0; - - datasize = (size_t) (cm_colstart[ii + 1] - cm_colstart[ii]); /* size of input descriptors */ - - cdescript = calloc(datasize + (rowspertile * 16), 1); /* room for both descriptors */ - if (!cdescript) { - ffpmsg("Could not allocate buffer for descriptors"); - *status = MEMORY_ALLOCATION; - free(cm_buffer); - return(*status); - } - - /* copy the input descriptors to this array */ - memcpy(cdescript, &cm_buffer[cm_colstart[ii]], datasize); -#if BYTESWAPPED - /* byte-swap the integer values into the native machine representation */ - if (rm_colwidth[ii] == 16) { - ffswap8((double *) cdescript, rowspertile * 2); - } else { - ffswap4((int *) cdescript, rowspertile * 2); - } -#endif - descriptors = (LONGLONG *) cdescript; /* use this for Q type descriptors */ - pdescriptors = (int *) cdescript; /* use this instead for or P type descriptors */ - /* pointer to the 2nd set of descriptors */ - outdescript = (LONGLONG *) (cdescript + datasize); /* this is a LONGLONG pointer */ - - for (jj = 0; jj < rowspertile; jj++) { /* loop to compress each VLA in turn */ - - if (rm_colwidth[ii] == 16) { /* if Q pointers */ - vlalen = descriptors[jj * 2]; - vlastart = descriptors[(jj * 2) + 1]; - } else { /* if P pointers */ - vlalen = (LONGLONG) pdescriptors[jj * 2]; - vlastart = (LONGLONG) pdescriptors[(jj * 2) + 1]; - } - - if (vlalen > 0) { /* skip zero-length VLAs */ - - vlamemlen = vlalen * (int) (-coltype[ii] / 10); - vlamem = (LONGLONG *) malloc((size_t) vlamemlen); /* memory for the input uncompressed VLA */ - if (!vlamem) { - ffpmsg("Could not allocate buffer for VLA"); - *status = MEMORY_ALLOCATION; - free(cdescript); free(cm_buffer); - return(*status); - } - - compmemlen = (size_t) (vlalen * ((LONGLONG) (-coltype[ii] / 10)) * 1.5); - if (compmemlen < 100) compmemlen = 100; - cvlamem = malloc(compmemlen); /* memory for the output compressed VLA */ - if (!cvlamem) { - ffpmsg("Could not allocate buffer for compressed data"); - *status = MEMORY_ALLOCATION; - free(vlamem); free(cdescript); free(cm_buffer); - return(*status); - } - - /* read the raw bytes directly from the heap, without any byte-swapping or null value detection */ - bytepos = (infptr->Fptr)->datastart + (infptr->Fptr)->heapstart + vlastart; - ffmbyt(infptr, bytepos, REPORT_EOF, status); - ffgbyt(infptr, vlamemlen, vlamem, status); /* read the bytes */ - uncompressed_size += vlamemlen; /* total size of the uncompressed VLAs */ - tot_uncompressed_size += vlamemlen; /* total size of the uncompressed file */ - - /* compress the VLA with the appropriate algorithm */ - if (compalgor[ii] == RICE_1) { - - if (-coltype[ii] == TSHORT) { -#if BYTESWAPPED - ffswap2((short *) (vlamem), (long) vlalen); -#endif - dlen = fits_rcomp_short ((short *)(vlamem), (int) vlalen, (unsigned char *) cvlamem, - (int) compmemlen, 32); - } else if (-coltype[ii] == TLONG) { -#if BYTESWAPPED - ffswap4((int *) (vlamem), (long) vlalen); -#endif - dlen = fits_rcomp ((int *)(vlamem), (int) vlalen, (unsigned char *) cvlamem, - (int) compmemlen, 32); - } else if (-coltype[ii] == TBYTE) { - dlen = fits_rcomp_byte ((signed char *)(vlamem), (int) vlalen, (unsigned char *) cvlamem, - (int) compmemlen, 32); - } else { - /* this should not happen */ - ffpmsg(" Error: cannot compress this column type with the RICE algorithm"); - free(vlamem); free(cdescript); free(cm_buffer); free(cvlamem); - *status = DATA_COMPRESSION_ERR; - return(*status); - } - } else if (compalgor[ii] == GZIP_1 || compalgor[ii] == GZIP_2){ - if (compalgor[ii] == GZIP_2 ) { /* shuffle the bytes before gzipping them */ - if ( (int) (-coltype[ii] / 10) == 2) { - fits_shuffle_2bytes((char *) vlamem, vlalen, status); - } else if ( (int) (-coltype[ii] / 10) == 4) { - fits_shuffle_4bytes((char *) vlamem, vlalen, status); - } else if ( (int) (-coltype[ii] / 10) == 8) { - fits_shuffle_8bytes((char *) vlamem, vlalen, status); - } - } - /*: gzip compress the array of bytes */ - compress2mem_from_mem( (char *) vlamem, (size_t) vlamemlen, - &cvlamem, &compmemlen, realloc, &dlen, status); - } else { - /* this should not happen */ - ffpmsg(" Error: unknown compression algorithm"); - free(vlamem); free(cdescript); free(cm_buffer); free(cvlamem); - *status = DATA_COMPRESSION_ERR; - return(*status); - } - - /* write the compressed array to the output table, but... */ - /* We use a trick of always writing the array to the same row of the output table */ - /* and then copy the descriptor into the array of descriptors that we allocated. */ - - /* First, reset the descriptor */ - fits_write_descript(outfptr, ii+1, ll+1, 0, 0, status); - - /* write the compressed VLA if it is smaller than the original, else write */ - /* the uncompressed array */ - fits_set_tscale(outfptr, ii + 1, 1.0, 0.0, status); /* turn off any data scaling, first */ - if (dlen < vlamemlen) { - fits_write_col(outfptr, TBYTE, ii + 1, ll+1, 1, dlen, cvlamem, status); - compressed_size += dlen; /* total size of the compressed VLAs */ - tot_compressed_size += dlen; /* total size of the compressed file */ - } else { - if ( -coltype[ii] != TBYTE && compalgor[ii] != GZIP_1) { - /* it is probably faster to reread the raw bytes, rather than unshuffle or unswap them */ - bytepos = (infptr->Fptr)->datastart + (infptr->Fptr)->heapstart + vlastart; - ffmbyt(infptr, bytepos, REPORT_EOF, status); - ffgbyt(infptr, vlamemlen, vlamem, status); /* read the bytes */ - } - fits_write_col(outfptr, TBYTE, ii + 1, ll+1, 1, vlamemlen, vlamem, status); - compressed_size += vlamemlen; /* total size of the compressed VLAs */ - tot_compressed_size += vlamemlen; /* total size of the compressed file */ - } - - /* read back the descriptor and save it in the array of descriptors */ - fits_read_descriptll(outfptr, ii + 1, ll + 1, outdescript+(jj*2), outdescript+(jj*2)+1, status); - free(cvlamem); free(vlamem); - - } /* end of vlalen > 0 */ - } /* end of loop over rows */ - - if (compressed_size != 0) - cratio[ii] = uncompressed_size / compressed_size; - - snprintf(tempstring,FLEN_VALUE," r=%6.2f",cratio[ii]); - strncat(results[ii],tempstring, 29-strlen(results[ii])); - - /* now we just have to compress the array of descriptors (both input and output) */ - /* and write them to the output table. */ - - /* allocate memory for the compressed descriptors */ - cvlamem = malloc(datasize + (rowspertile * 16) ); - if (!cvlamem) { - ffpmsg("Could not allocate buffer for compressed data"); - *status = MEMORY_ALLOCATION; - free(cdescript); free(cm_buffer); - return(*status); - } - -#if BYTESWAPPED - /* byte swap the input and output descriptors */ - if (rm_colwidth[ii] == 16) { - ffswap8((double *) cdescript, rowspertile * 2); - } else { - ffswap4((int *) cdescript, rowspertile * 2); - } - ffswap8((double *) outdescript, rowspertile * 2); -#endif - /* compress the array contain both sets of descriptors */ - compress2mem_from_mem((char *) cdescript, datasize + (rowspertile * 16), - &cvlamem, &datasize, realloc, &dlen, status); - - free(cdescript); - - /* write the compressed descriptors to the output column */ - fits_set_tscale(outfptr, ii + 1, 1.0, 0.0, status); /* turn off any data scaling, first */ - fits_write_descript(outfptr, ii+1, ll+1, 0, 0, status); /* First, reset the descriptor */ - fits_write_col(outfptr, TBYTE, ii + 1, ll+1, 1, dlen, cvlamem, status); - free(cvlamem); - - if (ll == 0) { /* only write the ZCTYPn keyword once, while processing the first column */ - fits_make_keyn("ZCTYP", ii+1, keyname, status); - - if (compalgor[ii] == RICE_1) { - strcpy(keyvalue, "RICE_1"); - } else if (compalgor[ii] == GZIP_2) { - strcpy(keyvalue, "GZIP_2"); - } else { - strcpy(keyvalue, "GZIP_1"); - } - - fits_write_key(outfptr, TSTRING, keyname, keyvalue, - "compression algorithm for column", status); - } - - continue; /* jump to end of loop, to go to next column */ - } /* end of VLA case */ - - /* ================================================================================*/ - /* deal with all the normal fixed-length columns here */ - /* ================================================================================*/ - - /* allocate memory for the compressed data */ - datasize = (size_t) (cm_colstart[ii + 1] - cm_colstart[ii]); - cvlamem = malloc(datasize*2); - tot_uncompressed_size += datasize; - - if (!cvlamem) { - ffpmsg("Could not allocate buffer for compressed data"); - *status = MEMORY_ALLOCATION; - free(cm_buffer); - return(*status); - } - - if (compalgor[ii] == RICE_1) { - if (coltype[ii] == TSHORT) { -#if BYTESWAPPED - ffswap2((short *) (cm_buffer + cm_colstart[ii]), datasize / 2); -#endif - dlen = fits_rcomp_short ((short *)(cm_buffer + cm_colstart[ii]), datasize / 2, (unsigned char *) cvlamem, - datasize * 2, 32); - - } else if (coltype[ii] == TLONG) { -#if BYTESWAPPED - ffswap4((int *) (cm_buffer + cm_colstart[ii]), datasize / 4); -#endif - dlen = fits_rcomp ((int *)(cm_buffer + cm_colstart[ii]), datasize / 4, (unsigned char *) cvlamem, - datasize * 2, 32); - - } else if (coltype[ii] == TBYTE) { - - dlen = fits_rcomp_byte ((signed char *)(cm_buffer + cm_colstart[ii]), datasize, (unsigned char *) cvlamem, - datasize * 2, 32); - } else { /* this should not happen */ - ffpmsg(" Error: cannot compress this column type with the RICE algorthm"); - free(cvlamem); free(cm_buffer); - *status = DATA_COMPRESSION_ERR; - return(*status); - } - } else { - /* all other cases: gzip compress the column (bytes may have been shuffled previously) */ - compress2mem_from_mem(cm_buffer + cm_colstart[ii], datasize, - &cvlamem, &datasize, realloc, &dlen, status); - } - - if (ll == 0) { /* only write the ZCTYPn keyword once, while processing the first column */ - fits_make_keyn("ZCTYP", ii+1, keyname, status); - - if (compalgor[ii] == RICE_1) { - strcpy(keyvalue, "RICE_1"); - } else if (compalgor[ii] == GZIP_2) { - strcpy(keyvalue, "GZIP_2"); - } else { - strcpy(keyvalue, "GZIP_1"); - } - - fits_write_key(outfptr, TSTRING, keyname, keyvalue, - "compression algorithm for column", status); - } - - /* write the compressed data to the output column */ - fits_set_tscale(outfptr, ii + 1, 1.0, 0.0, status); /* turn off any data scaling, first */ - fits_write_col(outfptr, TBYTE, ii + 1, ll+1, 1, dlen, cvlamem, status); - tot_compressed_size += dlen; - - free(cvlamem); /* don't need the compressed data any more */ - - /* create diagnostic messages */ - if (dlen != 0) - cratio[ii] = (float) datasize / (float) dlen; /* compression ratio of the column */ - - snprintf(tempstring,FLEN_VALUE," r=%6.2f",cratio[ii]); - strncat(results[ii],tempstring,29-strlen(results[ii])); - - } /* end of not a virtual column */ - } /* end of loop over columns */ - - datastart += (rowspertile * naxis1); /* increment to start of next chunk */ - firstrow += rowspertile; /* increment first row in next chunk */ - - if (print_report) { - printf("\nChunk = %d\n",ll+1); - for (ii = 0; ii < ncols; ii++) { - printf("%s\n", results[ii]); - } - } - - } /* end of loop over chunks of the table */ - - /* =================================================================================*/ - /* all done; just clean up and return */ - /* ================================================================================*/ - - free(cm_buffer); - fits_set_hdustruc(outfptr, status); /* reset internal structures */ - - if (print_report) { - - if (tot_compressed_size != 0) - printf("\nTotal data size (MB) %.3f -> %.3f, ratio = %.3f\n", tot_uncompressed_size/1000000., - tot_compressed_size/1000000., tot_uncompressed_size/tot_compressed_size); - } - return(*status); -} -/*--------------------------------------------------------------------------*/ -int fits_uncompress_table(fitsfile *infptr, fitsfile *outfptr, int *status) - -/* - Uncompress the table that was compressed with fits_compress_table -*/ -{ - char colcode[999]; /* column data type code character */ - char coltype[999]; /* column data type numeric code value */ - char *cm_buffer; /* memory buffer for the transposed, Column-Major, chunk of the table */ - char *rm_buffer; /* memory buffer for the original, Row-Major, chunk of the table */ - LONGLONG nrows, rmajor_colwidth[999], rmajor_colstart[1000], cmajor_colstart[1000]; - LONGLONG cmajor_repeat[999], rmajor_repeat[999], cmajor_bytespan[999], kk; - LONGLONG headstart, datastart = 0, dataend, rowsremain, *descript, *qdescript = 0; - LONGLONG rowstart, cvlalen, cvlastart, vlalen, vlastart; - long repeat, width, vla_repeat, vla_address, rowspertile, ntile; - int ncols, hdutype, inttype, anynull, tstatus, zctype[999], addspace = 0, *pdescript = 0; - char *cptr, keyname[9], tform[40]; - long pcount, zheapptr, naxis1, naxis2, ii, jj; - char *ptr, comm[FLEN_COMMENT], zvalue[FLEN_VALUE], *uncompressed_vla = 0, *compressed_vla; - char card[FLEN_CARD]; - size_t dlen, fullsize, cm_size, bytepos, vlamemlen; - - /* ================================================================================== */ - /* perform initial sanity checks */ - /* ================================================================================== */ - if (*status > 0) - return(*status); - - fits_get_hdu_type(infptr, &hdutype, status); - if (hdutype != BINARY_TBL) { - ffpmsg("This is not a binary table, so cannot uncompress it!"); - *status = NOT_BTABLE; - return(*status); - } - - if (fits_read_key(infptr, TLOGICAL, "ZTABLE", &tstatus, NULL, status)) { - /* just copy the HDU if the table is not compressed */ - if (infptr != outfptr) { - fits_copy_hdu (infptr, outfptr, 0, status); - } - return(*status); - } - - fits_get_num_rowsll(infptr, &nrows, status); - fits_get_num_cols(infptr, &ncols, status); - - if ((ncols < 1)) { - /* just copy the HDU if the table does not have more than 0 columns */ - if (infptr != outfptr) { - fits_copy_hdu (infptr, outfptr, 0, status); - } - return(*status); - } - - fits_read_key(infptr, TLONG, "ZTILELEN", &rowspertile, comm, status); - if (*status > 0) { - ffpmsg("Could not find the required ZTILELEN keyword"); - *status = DATA_DECOMPRESSION_ERR; - return(*status); - } - - /**** get size of the uncompressed table */ - fits_read_key(infptr, TLONG, "ZNAXIS1", &naxis1, comm, status); - if (*status > 0) { - ffpmsg("Could not find the required ZNAXIS1 keyword"); - *status = DATA_DECOMPRESSION_ERR; - return(*status); - } - - fits_read_key(infptr, TLONG, "ZNAXIS2", &naxis2, comm, status); - if (*status > 0) { - ffpmsg("Could not find the required ZNAXIS2 keyword"); - *status = DATA_DECOMPRESSION_ERR; - return(*status); - } - - /* silently ignore illegal ZTILELEN value if too large */ - if (rowspertile > naxis2) rowspertile = naxis2; - - fits_read_key(infptr, TLONG, "ZPCOUNT", &pcount, comm, status); - if (*status > 0) { - ffpmsg("Could not find the required ZPCOUNT keyword"); - *status = DATA_DECOMPRESSION_ERR; - return(*status); - } - - tstatus = 0; - fits_read_key(infptr, TLONG, "ZHEAPPTR", &zheapptr, comm, &tstatus); - if (tstatus > 0) { - zheapptr = 0; /* uncompressed table has no heap */ - } - - /* ================================================================================== */ - /* copy of the input header, then recreate the uncompressed table keywords */ - /* ================================================================================== */ - fits_copy_header(infptr, outfptr, status); - - /* reset the NAXIS1, NAXIS2. and PCOUNT keywords to the original */ - fits_read_card(outfptr, "ZNAXIS1", card, status); - strncpy(card, "NAXIS1 ", 7); - fits_update_card(outfptr, "NAXIS1", card, status); - - fits_read_card(outfptr, "ZNAXIS2", card, status); - strncpy(card, "NAXIS2 ", 7); - fits_update_card(outfptr, "NAXIS2", card, status); - - fits_read_card(outfptr, "ZPCOUNT", card, status); - strncpy(card, "PCOUNT ", 7); - fits_update_card(outfptr, "PCOUNT", card, status); - - fits_delete_key(outfptr, "ZTABLE", status); - fits_delete_key(outfptr, "ZTILELEN", status); - fits_delete_key(outfptr, "ZNAXIS1", status); - fits_delete_key(outfptr, "ZNAXIS2", status); - fits_delete_key(outfptr, "ZPCOUNT", status); - tstatus = 0; - fits_delete_key(outfptr, "CHECKSUM", &tstatus); - tstatus = 0; - fits_delete_key(outfptr, "DATASUM", &tstatus); - /* restore the Checksum keywords, if they exist */ - tstatus = 0; - fits_modify_name(outfptr, "ZHECKSUM", "CHECKSUM", &tstatus); - tstatus = 0; - fits_modify_name(outfptr, "ZDATASUM", "DATASUM", &tstatus); - - /* ================================================================================== */ - /* determine compression paramters for each column and write column-specific keywords */ - /* ================================================================================== */ - for (ii = 0; ii < ncols; ii++) { - - /* get the original column type, repeat count, and unit width */ - fits_make_keyn("ZFORM", ii+1, keyname, status); - fits_read_key(infptr, TSTRING, keyname, tform, comm, status); - - /* restore the original TFORM value and comment */ - fits_read_card(outfptr, keyname, card, status); - card[0] = 'T'; - keyname[0] = 'T'; - fits_update_card(outfptr, keyname, card, status); - - /* now delete the ZFORM keyword */ - keyname[0] = 'Z'; - fits_delete_key(outfptr, keyname, status); - - cptr = tform; - while(isdigit(*cptr)) cptr++; - colcode[ii] = *cptr; /* save the column type code */ - - fits_binary_tform(tform, &inttype, &repeat, &width, status); - coltype[ii] = inttype; - - /* deal with special cases */ - if (abs(coltype[ii]) == TBIT) { - repeat = (repeat + 7) / 8 ; /* convert from bits to bytes */ - } else if (abs(coltype[ii]) == TSTRING) { - width = 1; - } else if (coltype[ii] < 0) { /* pointer to variable length array */ - if (colcode[ii] == 'P') - width = 8; /* this is a 'P' column */ - else - width = 16; /* this is a 'Q' not a 'P' column */ - - addspace += 16; /* need space for a second set of Q pointers for this column */ - } - - rmajor_repeat[ii] = repeat; - - /* width (in bytes) of each field in the row-major table */ - rmajor_colwidth[ii] = rmajor_repeat[ii] * width; - - /* construct the ZCTYPn keyword name then read the keyword */ - fits_make_keyn("ZCTYP", ii+1, keyname, status); - tstatus = 0; - fits_read_key(infptr, TSTRING, keyname, zvalue, NULL, &tstatus); - if (tstatus) { - zctype[ii] = GZIP_2; - } else { - if (!strcmp(zvalue, "GZIP_2")) { - zctype[ii] = GZIP_2; - } else if (!strcmp(zvalue, "GZIP_1")) { - zctype[ii] = GZIP_1; - } else if (!strcmp(zvalue, "RICE_1")) { - zctype[ii] = RICE_1; - } else { - ffpmsg("Unrecognized ZCTYPn keyword compression code:"); - ffpmsg(zvalue); - *status = DATA_DECOMPRESSION_ERR; - return(*status); - } - - /* delete this keyword from the uncompressed header */ - fits_delete_key(outfptr, keyname, status); - } - } - - /* rescan header keywords to reset internal table structure parameters */ - fits_set_hdustruc(outfptr, status); - - /* ================================================================================== */ - /* allocate memory for the transposed and untransposed tile of the table */ - /* ================================================================================== */ - - fullsize = naxis1 * rowspertile; - cm_size = fullsize + (addspace * rowspertile); - - cm_buffer = malloc(cm_size); - if (!cm_buffer) { - ffpmsg("Could not allocate buffer for transformed column-major table"); - *status = MEMORY_ALLOCATION; - return(*status); - } - - rm_buffer = malloc(fullsize); - if (!rm_buffer) { - ffpmsg("Could not allocate buffer for untransformed row-major table"); - *status = MEMORY_ALLOCATION; - free(cm_buffer); - return(*status); - } - - /* ================================================================================== */ - /* Main loop over all the tiles */ - /* ================================================================================== */ - - rowsremain = naxis2; - rowstart = 1; - ntile = 0; - - while(rowsremain) { - - /* ================================================================================== */ - /* loop over each column: read and uncompress the bytes */ - /* ================================================================================== */ - ntile++; - rmajor_colstart[0] = 0; - cmajor_colstart[0] = 0; - for (ii = 0; ii < ncols; ii++) { - - cmajor_repeat[ii] = rmajor_repeat[ii] * rowspertile; - - /* starting offset of each field in the column-major table */ - if (coltype[ii] > 0) { /* normal fixed length column */ - cmajor_colstart[ii + 1] = cmajor_colstart[ii] + rmajor_colwidth[ii] * rowspertile; - } else { /* VLA column: reserve space for the 2nd set of Q pointers */ - cmajor_colstart[ii + 1] = cmajor_colstart[ii] + (rmajor_colwidth[ii] + 16) * rowspertile; - } - /* length of each sequence of bytes, after sorting them in signicant order */ - cmajor_bytespan[ii] = (rmajor_repeat[ii] * rowspertile); - - /* starting offset of each field in the row-major table */ - rmajor_colstart[ii + 1] = rmajor_colstart[ii] + rmajor_colwidth[ii]; - - if (rmajor_repeat[ii] > 0) { /* ignore columns with 0 elements */ - - /* read compressed bytes from input table */ - fits_read_descript(infptr, ii + 1, ntile, &vla_repeat, &vla_address, status); - - /* allocate memory and read in the compressed bytes */ - ptr = malloc(vla_repeat); - if (!ptr) { - ffpmsg("Could not allocate buffer for uncompressed bytes"); - *status = MEMORY_ALLOCATION; - free(rm_buffer); free(cm_buffer); - return(*status); - } - - fits_set_tscale(infptr, ii + 1, 1.0, 0.0, status); /* turn off any data scaling, first */ - fits_read_col_byt(infptr, ii + 1, ntile, 1, vla_repeat, 0, (unsigned char *) ptr, &anynull, status); - cptr = cm_buffer + cmajor_colstart[ii]; - - /* size in bytes of the uncompressed column of bytes */ - fullsize = (size_t) (cmajor_colstart[ii+1] - cmajor_colstart[ii]); - - switch (colcode[ii]) { - - case 'I': - - if (zctype[ii] == RICE_1) { - dlen = fits_rdecomp_short((unsigned char *)ptr, vla_repeat, (unsigned short *)cptr, - fullsize / 2, 32); -#if BYTESWAPPED - ffswap2((short *) cptr, fullsize / 2); -#endif - } else { /* gunzip the data into the correct location */ - uncompress2mem_from_mem(ptr, vla_repeat, &cptr, &fullsize, realloc, &dlen, status); - } - break; - - case 'J': - - if (zctype[ii] == RICE_1) { - dlen = fits_rdecomp ((unsigned char *) ptr, vla_repeat, (unsigned int *)cptr, - fullsize / 4, 32); -#if BYTESWAPPED - ffswap4((int *) cptr, fullsize / 4); -#endif - } else { /* gunzip the data into the correct location */ - uncompress2mem_from_mem(ptr, vla_repeat, &cptr, &fullsize, realloc, &dlen, status); - } - break; - - case 'B': - - if (zctype[ii] == RICE_1) { - dlen = fits_rdecomp_byte ((unsigned char *) ptr, vla_repeat, (unsigned char *)cptr, - fullsize, 32); - } else { /* gunzip the data into the correct location */ - uncompress2mem_from_mem(ptr, vla_repeat, &cptr, &fullsize, realloc, &dlen, status); - } - break; - - default: - /* all variable length array columns are included in this case */ - /* gunzip the data into the correct location in the full table buffer */ - uncompress2mem_from_mem(ptr, vla_repeat, - &cptr, &fullsize, realloc, &dlen, status); - - } /* end of switch block */ - - free(ptr); - } /* end of rmajor_repeat > 0 */ - } /* end of loop over columns */ - - /* now transpose the rows and columns (from cm_buffer to rm_buffer) */ - /* move each byte, in turn, from the cm_buffer to the appropriate place in the rm_buffer */ - for (ii = 0; ii < ncols; ii++) { /* loop over columns */ - ptr = (char *) (cm_buffer + cmajor_colstart[ii]); /* initialize ptr to start of the column in the cm_buffer */ - if (rmajor_repeat[ii] > 0) { /* skip columns with zero elements */ - if (coltype[ii] > 0) { /* normal fixed length array columns */ - if (zctype[ii] == GZIP_2) { /* need to unshuffle the bytes */ - - /* recombine the byte planes for the 2-byte, 4-byte, and 8-byte numeric columns */ - switch (colcode[ii]) { - - case 'I': - /* get the 1st byte of each I*2 value */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols])); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 2; - } - } - /* get the 2nd byte of each I*2 value */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 1); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 2; - } - } - break; - - case 'J': - case 'E': - /* get the 1st byte of each 4-byte value */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols])); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 4; - } - } - /* get the 2nd byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 1); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 4; - } - } - /* get the 3rd byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 2); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 4; - } - } - /* get the 4th byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 3); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 4; - } - } - break; - - case 'D': - case 'K': - /* get the 1st byte of each 8-byte value */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols])); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 8; - } - } - /* get the 2nd byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 1); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 8; - } - } - /* get the 3rd byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 2); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 8; - } - } - /* get the 4th byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 3); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 8; - } - } - /* get the 5th byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 4); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 8; - } - } - /* get the 6th byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 5); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 8; - } - } - /* get the 7th byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 6); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 8; - } - } - /* get the 8th byte */ - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + (jj * rmajor_colstart[ncols]) + 7); - for (kk = 0; kk < rmajor_repeat[ii]; kk++) { - *cptr = *ptr; /* copy 1 byte */ - ptr++; - cptr += 8; - } - } - break; - - default: /* should never get here */ - ffpmsg("Error: unexpected attempt to use GZIP_2 to compress a column unsuitable data type"); - *status = DATA_DECOMPRESSION_ERR; - free(rm_buffer); free(cm_buffer); - return(*status); - - } /* end of switch for shuffling the bytes*/ - - } else { /* not GZIP_2, don't have to shuffle bytes, so just transpose the rows and columns */ - - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output table */ - cptr = rm_buffer + (rmajor_colstart[ii] + jj * rmajor_colstart[ncols]); /* addr to copy to */ - memcpy(cptr, ptr, (size_t) rmajor_colwidth[ii]); - - ptr += (rmajor_colwidth[ii]); - } - } - } else { /* transpose the variable length array pointers */ - - for (jj = 0; jj < rowspertile; jj++) { /* loop over number of rows in the output uncompressed table */ - cptr = rm_buffer + (rmajor_colstart[ii] + jj * rmajor_colstart[ncols]); /* addr to copy to */ - memcpy(cptr, ptr, (size_t) rmajor_colwidth[ii]); - - ptr += (rmajor_colwidth[ii]); - } - - if (rmajor_colwidth[ii] == 8 ) { /* these are P-type descriptors */ - pdescript = (int *) (cm_buffer + cmajor_colstart[ii]); -#if BYTESWAPPED - ffswap4((int *) pdescript, rowspertile * 2); /* byte-swap the descriptor */ -#endif - } else if (rmajor_colwidth[ii] == 16 ) { /* these are Q-type descriptors */ - qdescript = (LONGLONG *) (cm_buffer + cmajor_colstart[ii]); -#if BYTESWAPPED - ffswap8((double *) qdescript, rowspertile * 2); /* byte-swap the descriptor */ -#endif - } else { /* this should never happen */ - ffpmsg("Error: Descriptor column is neither 8 nor 16 bytes wide"); - free(rm_buffer); free(cm_buffer); - *status = DATA_DECOMPRESSION_ERR; - return(*status); - } - - /* First, set pointer to the Q descriptors, and byte-swap them, if needed */ - descript = (LONGLONG*) (cm_buffer + cmajor_colstart[ii] + (rmajor_colwidth[ii] * rowspertile)); -#if BYTESWAPPED - /* byte-swap the descriptor */ - ffswap8((double *) descript, rowspertile * 2); -#endif - - /* now uncompress all the individual VLAs, and */ - /* write them to their original location in the uncompressed file */ - - for (jj = 0; jj < rowspertile; jj++) { /* loop over rows */ - /* get the size and location of the compressed VLA in the compressed table */ - cvlalen = descript[jj * 2]; - cvlastart = descript[(jj * 2) + 1]; - if (cvlalen > 0 ) { - - /* get the size and location to write the uncompressed VLA in the uncompressed table */ - if (rmajor_colwidth[ii] == 8 ) { - vlalen = pdescript[jj * 2]; - vlastart = pdescript[(jj * 2) + 1]; - } else { - vlalen = qdescript[jj * 2]; - vlastart = qdescript[(jj * 2) + 1]; - } - vlamemlen = (size_t) (vlalen * (-coltype[ii] / 10)); /* size of the uncompressed VLA, in bytes */ - - /* allocate memory for the compressed vla */ - compressed_vla = malloc( (size_t) cvlalen); - if (!compressed_vla) { - ffpmsg("Could not allocate buffer for compressed VLA"); - free(rm_buffer); free(cm_buffer); - *status = MEMORY_ALLOCATION; - return(*status); - } - - /* read the compressed VLA from the heap in the input compressed table */ - bytepos = (size_t) ((infptr->Fptr)->datastart + (infptr->Fptr)->heapstart + cvlastart); - ffmbyt(infptr, bytepos, REPORT_EOF, status); - ffgbyt(infptr, cvlalen, compressed_vla, status); /* read the bytes */ - /* if the VLA couldn't be compressed, just copy it directly to the output uncompressed table */ - if (cvlalen == vlamemlen ) { - bytepos = (size_t) ((outfptr->Fptr)->datastart + (outfptr->Fptr)->heapstart + vlastart); - ffmbyt(outfptr, bytepos, IGNORE_EOF, status); - ffpbyt(outfptr, cvlalen, compressed_vla, status); /* write the bytes */ - } else { /* uncompress the VLA */ - - /* allocate memory for the uncompressed VLA */ - uncompressed_vla = malloc(vlamemlen); - if (!uncompressed_vla) { - ffpmsg("Could not allocate buffer for uncompressed VLA"); - *status = MEMORY_ALLOCATION; - free(compressed_vla); free(rm_buffer); free(cm_buffer); - return(*status); - } - /* uncompress the VLA with the appropriate algorithm */ - if (zctype[ii] == RICE_1) { - - if (-coltype[ii] == TSHORT) { - dlen = fits_rdecomp_short((unsigned char *) compressed_vla, (int) cvlalen, (unsigned short *)uncompressed_vla, - (int) vlalen, 32); -#if BYTESWAPPED - ffswap2((short *) uncompressed_vla, (long) vlalen); -#endif - } else if (-coltype[ii] == TLONG) { - dlen = fits_rdecomp((unsigned char *) compressed_vla, (int) cvlalen, (unsigned int *)uncompressed_vla, - (int) vlalen, 32); -#if BYTESWAPPED - ffswap4((int *) uncompressed_vla, (long) vlalen); -#endif - } else if (-coltype[ii] == TBYTE) { - dlen = fits_rdecomp_byte((unsigned char *) compressed_vla, (int) cvlalen, (unsigned char *) uncompressed_vla, - (int) vlalen, 32); - } else { - /* this should not happen */ - ffpmsg(" Error: cannot uncompress this column type with the RICE algorithm"); - - *status = DATA_DECOMPRESSION_ERR; - free(uncompressed_vla); free(compressed_vla); free(rm_buffer); free(cm_buffer); - return(*status); - } - - } else if (zctype[ii] == GZIP_1 || zctype[ii] == GZIP_2){ - - /*: gzip uncompress the array of bytes */ - uncompress2mem_from_mem( compressed_vla, (size_t) cvlalen, &uncompressed_vla, &vlamemlen, realloc, &vlamemlen, status); - - if (zctype[ii] == GZIP_2 ) { - /* unshuffle the bytes after ungzipping them */ - if ( (int) (-coltype[ii] / 10) == 2) { - fits_unshuffle_2bytes((char *) uncompressed_vla, vlalen, status); - } else if ( (int) (-coltype[ii] / 10) == 4) { - fits_unshuffle_4bytes((char *) uncompressed_vla, vlalen, status); - } else if ( (int) (-coltype[ii] / 10) == 8) { - fits_unshuffle_8bytes((char *) uncompressed_vla, vlalen, status); - } - } - - } else { - /* this should not happen */ - ffpmsg(" Error: unknown compression algorithm"); - free(uncompressed_vla); free(compressed_vla); free(rm_buffer); free(cm_buffer); - *status = DATA_COMPRESSION_ERR; - return(*status); - } - - bytepos = (size_t) ((outfptr->Fptr)->datastart + (outfptr->Fptr)->heapstart + vlastart); - ffmbyt(outfptr, bytepos, IGNORE_EOF, status); - ffpbyt(outfptr, vlamemlen, uncompressed_vla, status); /* write the bytes */ - - free(uncompressed_vla); - } /* end of uncompress VLA */ - - free(compressed_vla); - - } /* end of vlalen > 0 */ - } /* end of loop over rowspertile */ - - } /* end of variable length array section*/ - } /* end of if column repeat > 0 */ - } /* end of ncols loop */ - - /* copy the buffer of data to the output data unit */ - - if (datastart == 0) fits_get_hduaddrll(outfptr, &headstart, &datastart, &dataend, status); - - ffmbyt(outfptr, datastart, 1, status); - ffpbyt(outfptr, naxis1 * rowspertile, rm_buffer, status); - - /* increment pointers for next tile */ - rowstart += rowspertile; - rowsremain -= rowspertile; - datastart += (naxis1 * rowspertile); - if (rowspertile > rowsremain) rowspertile = (long) rowsremain; - - } /* end of while rows still remain */ - - free(rm_buffer); - free(cm_buffer); - - /* reset internal table structure parameters */ - fits_set_hdustruc(outfptr, status); - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int fits_shuffle_2bytes(char *heap, LONGLONG length, int *status) - -/* shuffle the bytes in an array of 2-byte integers in the heap */ - -{ - LONGLONG ii; - char *ptr, *cptr, *heapptr; - - ptr = malloc((size_t) (length * 2)); - heapptr = heap; - cptr = ptr; - - for (ii = 0; ii < length; ii++) { - *cptr = *heapptr; - heapptr++; - *(cptr + length) = *heapptr; - heapptr++; - cptr++; - } - - memcpy(heap, ptr, (size_t) (length * 2)); - free(ptr); - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int fits_shuffle_4bytes(char *heap, LONGLONG length, int *status) - -/* shuffle the bytes in an array of 4-byte integers or floats */ - -{ - LONGLONG ii; - char *ptr, *cptr, *heapptr; - - ptr = malloc((size_t) (length * 4)); - if (!ptr) { - ffpmsg("malloc failed\n"); - return(*status); - } - - heapptr = heap; - cptr = ptr; - - for (ii = 0; ii < length; ii++) { - *cptr = *heapptr; - heapptr++; - *(cptr + length) = *heapptr; - heapptr++; - *(cptr + (length * 2)) = *heapptr; - heapptr++; - *(cptr + (length * 3)) = *heapptr; - heapptr++; - cptr++; - } - - memcpy(heap, ptr, (size_t) (length * 4)); - free(ptr); - - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int fits_shuffle_8bytes(char *heap, LONGLONG length, int *status) - -/* shuffle the bytes in an array of 8-byte integers or doubles in the heap */ - -{ - LONGLONG ii; - char *ptr, *cptr, *heapptr; - - ptr = calloc(1, (size_t) (length * 8)); - heapptr = heap; - -/* for some bizarre reason this loop fails to compile under OpenSolaris using - the proprietary SunStudioExpress C compiler; use the following equivalent - loop instead. - - cptr = ptr; - - for (ii = 0; ii < length; ii++) { - *cptr = *heapptr; - heapptr++; - *(cptr + length) = *heapptr; - heapptr++; - *(cptr + (length * 2)) = *heapptr; - heapptr++; - *(cptr + (length * 3)) = *heapptr; - heapptr++; - *(cptr + (length * 4)) = *heapptr; - heapptr++; - *(cptr + (length * 5)) = *heapptr; - heapptr++; - *(cptr + (length * 6)) = *heapptr; - heapptr++; - *(cptr + (length * 7)) = *heapptr; - heapptr++; - cptr++; - } -*/ - for (ii = 0; ii < length; ii++) { - cptr = ptr + ii; - - *cptr = *heapptr; - - heapptr++; - cptr += length; - *cptr = *heapptr; - - heapptr++; - cptr += length; - *cptr = *heapptr; - - heapptr++; - cptr += length; - *cptr = *heapptr; - - heapptr++; - cptr += length; - *cptr = *heapptr; - - heapptr++; - cptr += length; - *cptr = *heapptr; - - heapptr++; - cptr += length; - *cptr = *heapptr; - - heapptr++; - cptr += length; - *cptr = *heapptr; - - heapptr++; - } - - memcpy(heap, ptr, (size_t) (length * 8)); - free(ptr); - - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int fits_unshuffle_2bytes(char *heap, LONGLONG length, int *status) - -/* unshuffle the bytes in an array of 2-byte integers */ - -{ - LONGLONG ii; - char *ptr, *cptr, *heapptr; - - ptr = malloc((size_t) (length * 2)); - heapptr = heap + (2 * length) - 1; - cptr = ptr + (2 * length) - 1; - - for (ii = 0; ii < length; ii++) { - *cptr = *heapptr; - cptr--; - *cptr = *(heapptr - length); - cptr--; - heapptr--; - } - - memcpy(heap, ptr, (size_t) (length * 2)); - free(ptr); - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int fits_unshuffle_4bytes(char *heap, LONGLONG length, int *status) - -/* unshuffle the bytes in an array of 4-byte integers or floats */ + { + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else + output[ii] = (float) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); + } -{ - LONGLONG ii; - char *ptr, *cptr, *heapptr; - - ptr = malloc((size_t) (length * 4)); - heapptr = heap + (4 * length) -1; - cptr = ptr + (4 * length) -1; - - for (ii = 0; ii < length; ii++) { - *cptr = *heapptr; - cptr--; - *cptr = *(heapptr - length); - cptr--; - *cptr = *(heapptr - (2 * length)); - cptr--; - *cptr = *(heapptr - (3 * length)); - cptr--; - heapptr--; + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } } - - memcpy(heap, ptr, (size_t) (length * 4)); - free(ptr); - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int fits_unshuffle_8bytes(char *heap, LONGLONG length, int *status) -/* unshuffle the bytes in an array of 8-byte integers or doubles */ - -{ - LONGLONG ii; - char *ptr, *cptr, *heapptr; - - ptr = malloc((size_t) (length * 8)); - heapptr = heap + (8 * length) - 1; - cptr = ptr + (8 * length) -1; - - for (ii = 0; ii < length; ii++) { - *cptr = *heapptr; - cptr--; - *cptr = *(heapptr - length); - cptr--; - *cptr = *(heapptr - (2 * length)); - cptr--; - *cptr = *(heapptr - (3 * length)); - cptr--; - *cptr = *(heapptr - (4 * length)); - cptr--; - *cptr = *(heapptr - (5 * length)); - cptr--; - *cptr = *(heapptr - (6 * length)); - cptr--; - *cptr = *(heapptr - (7 * length)); - cptr--; - heapptr--; - } - - memcpy(heap, ptr, (size_t) (length * 8)); - free(ptr); return(*status); } /*--------------------------------------------------------------------------*/ -static int fits_int_to_longlong_inplace(int *intarray, long length, int *status) - -/* convert the input array of 32-bit integers into an array of 64-bit integers, -in place. This will overwrite the input array with the new longer array starting -at the same memory location. - -Note that aliasing the same memory location with pointers of different datatypes is -not allowed in strict ANSI C99, however it is used here for efficency. In principle, -one could simply copy the input array in reverse order to the output array, -but this only works if the compiler performs the operation in strict order. Certain -compiler optimization techniques may vioate this assumption. Therefore, we first -copy a section of the input array to a temporary intermediate array, before copying -the longer datatype values back to the original array. +int unquantize_i1r8(long row, /* tile number = row number in table */ + unsigned char *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - dithering method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ + double nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + double *output, /* O - array of converted pixels */ + int *status) /* IO - error status */ +/* + Unquantize byte values into the scaled floating point values */ - { - LONGLONG *longlongarray, *aliasarray; - long ii, ntodo, firstelem, nmax = 10000; - - if (*status > 0) - return(*status); - - ntodo = nmax; - if (length < nmax) ntodo = length; - - firstelem = length - ntodo; /* first element to be converted */ - - longlongarray = (LONGLONG *) malloc(ntodo * sizeof(LONGLONG)); - - if (longlongarray == NULL) - { - ffpmsg("Out of memory. (fits_int_to_longlong_inplace)"); - return (*status = MEMORY_ALLOCATION); - } - - aliasarray = (LONGLONG *) intarray; /* alias pointer to the input array */ - - while (ntodo > 0) { - - /* do datatype conversion into temp array */ - for (ii = 0; ii < ntodo; ii++) { - longlongarray[ii] = intarray[ii + firstelem]; - } - - /* copy temp array back to alias */ - memcpy(&(aliasarray[firstelem]), longlongarray, ntodo * 8); - - if (firstelem == 0) { /* we are all done */ - ntodo = 0; - } else { /* recalculate ntodo and firstelem for next loop */ - if (firstelem > nmax) { - firstelem -= nmax; - } else { - ntodo = firstelem; - firstelem = 0; - } - } - } + long ii; + int nextrand, iseed; - free(longlongarray); - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int fits_short_to_int_inplace(short *shortarray, long length, int shift, int *status) + if (!fits_rand_value) + if (fits_init_randoms()) return(MEMORY_ALLOCATION); -/* convert the input array of 16-bit integers into an array of 32-bit integers, -in place. This will overwrite the input array with the new longer array starting -at the same memory location. + /* initialize the index to the next random number in the list */ + iseed = (int) ((row - 1) % N_RANDOM); + nextrand = (int) (fits_rand_value[iseed] * 500); -Note that aliasing the same memory location with pointers of different datatypes is -not allowed in strict ANSI C99, however it is used here for efficency. In principle, -one could simply copy the input array in reverse order to the output array, -but this only works if the compiler performs the operation in strict order. Certain -compiler optimization techniques may vioate this assumption. Therefore, we first -copy a section of the input array to a temporary intermediate array, before copying -the longer datatype values back to the original array. + if (nullcheck == 0) /* no null checking required */ + { + for (ii = 0; ii < ntodo; ii++) + { +/* + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else */ + output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); -{ - int *intarray, *aliasarray; - long ii, ntodo, firstelem, nmax = 10000; - - if (*status > 0) - return(*status); - - ntodo = nmax; - if (length < nmax) ntodo = length; - - firstelem = length - ntodo; /* first element to be converted */ - - intarray = (int *) malloc(ntodo * sizeof(int)); - - if (intarray == NULL) - { - ffpmsg("Out of memory. (fits_short_to_int_inplace)"); - return (*status = MEMORY_ALLOCATION); + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } } + else /* must check for null values */ + { + for (ii = 0; ii < ntodo; ii++) + { + if (input[ii] == tnull) + { + *anynull = 1; + if (nullcheck == 1) + output[ii] = nullval; + else + nullarray[ii] = 1; + } + else + { +/* + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else +*/ + output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); + } - aliasarray = (int *) shortarray; /* alias pointer to the input array */ - - while (ntodo > 0) { - - /* do datatype conversion into temp array */ - for (ii = 0; ii < ntodo; ii++) { - intarray[ii] = (int)(shortarray[ii + firstelem]) + shift; - } - - /* copy temp array back to alias */ - memcpy(&(aliasarray[firstelem]), intarray, ntodo * 4); - - if (firstelem == 0) { /* we are all done */ - ntodo = 0; - } else { /* recalculate ntodo and firstelem for next loop */ - if (firstelem > nmax) { - firstelem -= nmax; - } else { - ntodo = firstelem; - firstelem = 0; - } - } + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } } - free(intarray); return(*status); } /*--------------------------------------------------------------------------*/ -static int fits_ushort_to_int_inplace(unsigned short *ushortarray, long length, - int shift, int *status) - -/* convert the input array of 16-bit unsigned integers into an array of 32-bit integers, -in place. This will overwrite the input array with the new longer array starting -at the same memory location. - -Note that aliasing the same memory location with pointers of different datatypes is -not allowed in strict ANSI C99, however it is used here for efficency. In principle, -one could simply copy the input array in reverse order to the output array, -but this only works if the compiler performs the operation in strict order. Certain -compiler optimization techniques may vioate this assumption. Therefore, we first -copy a section of the input array to a temporary intermediate array, before copying -the longer datatype values back to the original array. +int unquantize_i2r8(long row, /* tile number = row number in table */ + short *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - dithering method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + short tnull, /* I - value of FITS TNULLn keyword if any */ + double nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + double *output, /* O - array of converted pixels */ + int *status) /* IO - error status */ +/* + Unquantize short integer values into the scaled floating point values */ - { - int *intarray, *aliasarray; - long ii, ntodo, firstelem, nmax = 10000; - - if (*status > 0) - return(*status); - - ntodo = nmax; - if (length < nmax) ntodo = length; - - firstelem = length - ntodo; /* first element to be converted */ - - intarray = (int *) malloc(ntodo * sizeof(int)); - - if (intarray == NULL) - { - ffpmsg("Out of memory. (fits_ushort_to_int_inplace)"); - return (*status = MEMORY_ALLOCATION); - } - - aliasarray = (int *) ushortarray; /* alias pointer to the input array */ - - while (ntodo > 0) { - - /* do datatype conversion into temp array */ - for (ii = 0; ii < ntodo; ii++) { - intarray[ii] = (int)(ushortarray[ii + firstelem]) + shift; - } - - /* copy temp array back to alias */ - memcpy(&(aliasarray[firstelem]), intarray, ntodo * 4); - - if (firstelem == 0) { /* we are all done */ - ntodo = 0; - } else { /* recalculate ntodo and firstelem for next loop */ - if (firstelem > nmax) { - firstelem -= nmax; - } else { - ntodo = firstelem; - firstelem = 0; - } - } - } + long ii; + int nextrand, iseed; - free(intarray); - return(*status); -} -/*--------------------------------------------------------------------------*/ -static int fits_ubyte_to_int_inplace(unsigned char *ubytearray, long length, - int *status) + if (!fits_rand_value) + if (fits_init_randoms()) return(MEMORY_ALLOCATION); -/* convert the input array of 8-bit unsigned integers into an array of 32-bit integers, -in place. This will overwrite the input array with the new longer array starting -at the same memory location. + /* initialize the index to the next random number in the list */ + iseed = (int) ((row - 1) % N_RANDOM); + nextrand = (int) (fits_rand_value[iseed] * 500); -Note that aliasing the same memory location with pointers of different datatypes is -not allowed in strict ANSI C99, however it is used here for efficency. In principle, -one could simply copy the input array in reverse order to the output array, -but this only works if the compiler performs the operation in strict order. Certain -compiler optimization techniques may vioate this assumption. Therefore, we first -copy a section of the input array to a temporary intermediate array, before copying -the longer datatype values back to the original array. + if (nullcheck == 0) /* no null checking required */ + { + for (ii = 0; ii < ntodo; ii++) + { +/* + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else */ + output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); -{ - int *intarray, *aliasarray; - long ii, ntodo, firstelem, nmax = 10000; - - if (*status > 0) - return(*status); - - ntodo = nmax; - if (length < nmax) ntodo = length; - - firstelem = length - ntodo; /* first element to be converted */ - - intarray = (int *) malloc(ntodo * sizeof(int)); - - if (intarray == NULL) - { - ffpmsg("Out of memory. (fits_ubyte_to_int_inplace)"); - return (*status = MEMORY_ALLOCATION); + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } } + else /* must check for null values */ + { + for (ii = 0; ii < ntodo; ii++) + { + if (input[ii] == tnull) + { + *anynull = 1; + if (nullcheck == 1) + output[ii] = nullval; + else + nullarray[ii] = 1; + } + else + { +/* if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else +*/ + output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); + } - aliasarray = (int *) ubytearray; /* alias pointer to the input array */ - - while (ntodo > 0) { - - /* do datatype conversion into temp array */ - for (ii = 0; ii < ntodo; ii++) { - intarray[ii] = ubytearray[ii + firstelem]; - } - - /* copy temp array back to alias */ - memcpy(&(aliasarray[firstelem]), intarray, ntodo * 4); - - if (firstelem == 0) { /* we are all done */ - ntodo = 0; - } else { /* recalculate ntodo and firstelem for next loop */ - if (firstelem > nmax) { - firstelem -= nmax; - } else { - ntodo = firstelem; - firstelem = 0; - } - } + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } } - free(intarray); return(*status); } /*--------------------------------------------------------------------------*/ -static int fits_sbyte_to_int_inplace(signed char *sbytearray, long length, - int *status) - -/* convert the input array of 8-bit signed integers into an array of 32-bit integers, -in place. This will overwrite the input array with the new longer array starting -at the same memory location. - -Note that aliasing the same memory location with pointers of different datatypes is -not allowed in strict ANSI C99, however it is used here for efficency. In principle, -one could simply copy the input array in reverse order to the output array, -but this only works if the compiler performs the operation in strict order. Certain -compiler optimization techniques may vioate this assumption. Therefore, we first -copy a section of the input array to a temporary intermediate array, before copying -the longer datatype values back to the original array. -*/ - +int unquantize_i4r8(long row, /* tile number = row number in table */ + int *input, /* I - array of values to be converted */ + long ntodo, /* I - number of elements in the array */ + double scale, /* I - FITS TSCALn or BSCALE value */ + double zero, /* I - FITS TZEROn or BZERO value */ + int dither_method, /* I - dithering method to use */ + int nullcheck, /* I - null checking code; 0 = don't check */ + /* 1:set null pixels = nullval */ + /* 2: if null pixel, set nullarray = 1 */ + int tnull, /* I - value of FITS TNULLn keyword if any */ + double nullval, /* I - set null pixels, if nullcheck = 1 */ + char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ + int *anynull, /* O - set to 1 if any pixels are null */ + double *output, /* O - array of converted pixels */ + int *status) /* IO - error status */ /* -!!!!!!!!!!!!!!!!! -NOTE THAT THIS IS A SPECIALIZED ROUTINE THAT ADDS AN OFFSET OF 128 TO THE ARRAY VALUES -!!!!!!!!!!!!!!!!! + Unquantize int integer values into the scaled floating point values */ - { - int *intarray, *aliasarray; - long ii, ntodo, firstelem, nmax = 10000; - - if (*status > 0) - return(*status); + long ii; + int nextrand, iseed; - ntodo = nmax; - if (length < nmax) ntodo = length; - - firstelem = length - ntodo; /* first element to be converted */ - - intarray = (int *) malloc(ntodo * sizeof(int)); - - if (intarray == NULL) - { - ffpmsg("Out of memory. (fits_sbyte_to_int_inplace)"); - return (*status = MEMORY_ALLOCATION); - } + if (fits_rand_value == 0) + if (fits_init_randoms()) return(MEMORY_ALLOCATION); - aliasarray = (int *) sbytearray; /* alias pointer to the input array */ + /* initialize the index to the next random number in the list */ + iseed = (int) ((row - 1) % N_RANDOM); + nextrand = (int) (fits_rand_value[iseed] * 500); - while (ntodo > 0) { - - /* do datatype conversion into temp array */ - for (ii = 0; ii < ntodo; ii++) { - intarray[ii] = sbytearray[ii + firstelem] + 128; /* !! Note the offset !! */ - } + if (nullcheck == 0) /* no null checking required */ + { + for (ii = 0; ii < ntodo; ii++) + { + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else + output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); - /* copy temp array back to alias */ - memcpy(&(aliasarray[firstelem]), intarray, ntodo * 4); - - if (firstelem == 0) { /* we are all done */ - ntodo = 0; - } else { /* recalculate ntodo and firstelem for next loop */ - if (firstelem > nmax) { - firstelem -= nmax; - } else { - ntodo = firstelem; - firstelem = 0; - } - } + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } } + else /* must check for null values */ + { + for (ii = 0; ii < ntodo; ii++) + { + if (input[ii] == tnull) + { + *anynull = 1; + if (nullcheck == 1) + output[ii] = nullval; + else + nullarray[ii] = 1; + } + else + { + if (dither_method == SUBTRACTIVE_DITHER_2 && input[ii] == ZERO_VALUE) + output[ii] = 0.0; + else + output[ii] = (double) (((double) input[ii] - fits_rand_value[nextrand] + 0.5) * scale + zero); + } - free(intarray); - return(*status); -} - -int fits_calc_tile_rows(long *tlpixel, long *tfpixel, int ndim, long *trowsize, long *ntrows, int *status) -{ - - /* The quantizing algorithms treat all N-dimensional tiles as if they - were 2 dimensions (trowsize * ntrows). This sets trowsize to the - first dimensional size encountered that's > 1 (typically the X dimension). - ntrows will then be the product of the remaining dimensional sizes. - - Examples: Tile = (5,4,1,3): trowsize=5, ntrows=12 - Tile = (1,1,5): trowsize=5, ntrows=1 - */ + nextrand++; + if (nextrand == N_RANDOM) { + iseed++; + if (iseed == N_RANDOM) iseed = 0; + nextrand = (int) (fits_rand_value[iseed] * 500); + } + } + } - int ii; - long np; - - if (*status) - return (*status); - - *trowsize = 0; - *ntrows = 1; - for (ii=0; ii 1) - { - if (!(*trowsize)) - *trowsize = np; - else - *ntrows *= np; - } - } - if (!(*trowsize)) - { - /* Should only get here for the unusual case of all tile dimensions - having size = 1 */ - *trowsize = 1; - } - - return (*status); + return(*status); } From 25519583622986c4dcbd0b8c1aecddba86737623 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Thu, 15 Dec 2022 15:08:19 +0000 Subject: [PATCH 25/32] Apply suggestions from code review --- astropy/io/fits/tiled_compression/_codecs.py | 2 -- astropy/io/fits/tiled_compression/tests/conftest.py | 1 + astropy/io/fits/tiled_compression/tests/test_fitsio.py | 10 +--------- astropy/io/fits/tiled_compression/tiled_compression.py | 1 - 4 files changed, 2 insertions(+), 12 deletions(-) diff --git a/astropy/io/fits/tiled_compression/_codecs.py b/astropy/io/fits/tiled_compression/_codecs.py index 1272bc63bbb..8400bbd8c94 100644 --- a/astropy/io/fits/tiled_compression/_codecs.py +++ b/astropy/io/fits/tiled_compression/_codecs.py @@ -51,8 +51,6 @@ def decode(self, buf): """ Decompress buffer using the GZIP_1 algorithm. - {_GZIP_1_DESCRIPTION} - Parameters ---------- buf diff --git a/astropy/io/fits/tiled_compression/tests/conftest.py b/astropy/io/fits/tiled_compression/tests/conftest.py index e633be924ac..aaf0cc75354 100644 --- a/astropy/io/fits/tiled_compression/tests/conftest.py +++ b/astropy/io/fits/tiled_compression/tests/conftest.py @@ -48,6 +48,7 @@ def _expand(*params): ({"qlevel": None},), ALL_FLOAT_DTYPES, ], + # All compression types can also take quantized floating point input [ COMPRESSION_TYPES, ( diff --git a/astropy/io/fits/tiled_compression/tests/test_fitsio.py b/astropy/io/fits/tiled_compression/tests/test_fitsio.py index 35ad2576ad9..687d8098c66 100644 --- a/astropy/io/fits/tiled_compression/tests/test_fitsio.py +++ b/astropy/io/fits/tiled_compression/tests/test_fitsio.py @@ -18,7 +18,7 @@ from .conftest import _expand # This is so that tox can force this file to be run, and not be silently -# skipped on CI, but in all other test runs it's skipped. +# skipped on CI, but in all other test runs it's skipped if fitsio isn't present. if "ASTROPY_ALWAYS_TEST_FITSIO" in os.environ: import fitsio else: @@ -41,14 +41,6 @@ def numpy_rng(): ((5, 5, 1), (5, 7, 1), (1, 5, 4), (1, 1, 15), (15, 1, 5)), ], # >3D Data are not currently supported by cfitsio - # [ - # ((15, 15, 15, 15),), - # ( - # (5, 5, 5, 5), - # (1, 5, 1, 5), - # (3, 1, 4, 5), - # ), - # ], ), ids=lambda x: f"shape: {x[0]} tile_dims: {x[1]}", ) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 87eb2f0b312..56a4eef80d0 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -133,7 +133,6 @@ def _buffer_to_array( dtype = ">u1" tile_data = np.asarray(tile_buffer).view(dtype).reshape(tile_shape) else: - # For RICE_1 compression the tiles that are on the edge can end up # being padded, so we truncate excess values if algorithm in ("RICE_1", "RICE_ONE", "PLIO_1"): From 0949b4154242d24630bc287446239b86306d7079 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Thu, 15 Dec 2022 15:43:59 +0000 Subject: [PATCH 26/32] Some docstring improvements --- .../tiled_compression/tiled_compression.py | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tiled_compression.py b/astropy/io/fits/tiled_compression/tiled_compression.py index 56a4eef80d0..bce8ed8b7ac 100644 --- a/astropy/io/fits/tiled_compression/tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tiled_compression.py @@ -1,5 +1,7 @@ """ -This module contains low level helper functions for compressing and decompressing buffer for the Tiled Table Compression algorithms as specified in the FITS 4 standard. +This module contains low level helper functions for compressing and +decompressing buffer for the Tiled Table Compression algorithms as specified in +the FITS 4 standard. """ import numpy as np @@ -240,7 +242,18 @@ def _get_compression_setting(header, name, default): def decompress_hdu(hdu): """ - Drop-in replacement for decompress_hdu from compressionmodule.c + Decompress the data in a `~astropy.io.fits.CompImageHDU`. + + Parameters + ---------- + hdu : `astropy.io.fits.CompImageHDU` + Input HDU to decompress the data for. + + Returns + ------- + + data : `numpy.ndarray` + The decompressed data array. """ _check_compressed_header(hdu._header) @@ -345,7 +358,22 @@ def decompress_hdu(hdu): def compress_hdu(hdu): """ - Drop-in replacement for compress_hdu from compressionmodule.c + Compress the data in a `~astropy.io.fits.CompImageHDU`. + + The input HDU is expected to have a uncompressed numpy array as it's + ``.data`` attribute. + + Parameters + ---------- + hdu : `astropy.io.fits.CompImageHDU` + Input HDU to compress the data for. + + Returns + ------- + nbytes : `int` + The number of bytes for the data once compressed. + cbytes : `numpy.ndarray` + The compressed bytes as a unit8 numpy array. """ if not isinstance(hdu.data, np.ndarray): @@ -477,7 +505,7 @@ def compress_hdu(hdu): irow += 1 if zblank is not None: - hdu._header['ZBLANK'] = zblank + hdu._header["ZBLANK"] = zblank table = np.zeros(len(compressed_bytes), dtype=hdu.columns.dtype.newbyteorder(">")) From 857337fb7f3efb7490808d38e6c82b609b77cd70 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Thu, 15 Dec 2022 17:54:30 +0000 Subject: [PATCH 27/32] Guard against a segfault in HCOMPRESS This validates the case where the shape for a tile is passed incorrectly to the C wrapper --- astropy/io/fits/tiled_compression/src/compression.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/astropy/io/fits/tiled_compression/src/compression.c b/astropy/io/fits/tiled_compression/src/compression.c index f948395ea8d..3aed92de4a7 100644 --- a/astropy/io/fits/tiled_compression/src/compression.c +++ b/astropy/io/fits/tiled_compression/src/compression.c @@ -9,11 +9,6 @@ #include #include -// TODO: use better estimates for compressed buffer sizes, as done in -// imcomp_calc_max_elem in cfitsio. For now we assume the -// compressed data won't be more than four times the size of the -// uncompressed data, which is safe but too generous. - // Some of the cfitsio compression files use ffpmsg // so we provide a dummy function to replace this. void ffpmsg(const char *err_message) {} @@ -255,6 +250,12 @@ static PyObject *compress_hcompress_1_c(PyObject *self, PyObject *args) { return NULL; } + if (count != nx * ny * bytepix) { + PyErr_SetString(PyExc_ValueError, + "The tile dimensions and dtype do not match the number of bytes provided."); + return (PyObject *)NULL; + } + // maxelem adapted from cfitsio's imcomp_calc_max_elem function maxelem = count / 4 * 2.2 + 26; From 3b52ce460b539183b46b121d7f34fe8b7eb2efbd Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Thu, 15 Dec 2022 18:04:24 +0000 Subject: [PATCH 28/32] i heard you like PLIO segfaults, so I made PLIO segfault again --- .../fits/tiled_compression/tests/conftest.py | 18 ++++++++ .../tiled_compression/tests/test_fitsio.py | 28 ++++++------ .../tests/test_tiled_compression.py | 43 +++++++++++++++++++ 3 files changed, 74 insertions(+), 15 deletions(-) diff --git a/astropy/io/fits/tiled_compression/tests/conftest.py b/astropy/io/fits/tiled_compression/tests/conftest.py index aaf0cc75354..dff152018be 100644 --- a/astropy/io/fits/tiled_compression/tests/conftest.py +++ b/astropy/io/fits/tiled_compression/tests/conftest.py @@ -1,5 +1,6 @@ import itertools +import numpy as np import pytest COMPRESSION_TYPES = [ @@ -11,6 +12,18 @@ ] +def fitsio_param_to_astropy_param(param): + # Convert fitsio kwargs to astropy kwargs + _map = {"qlevel": "quantize_level", "qmethod": "quantize_method"} + param = {_map[k]: v for k, v in param.items()} + + # Map quantize_level + if param.get("quantize_level", "missing") is None: + param["quantize_level"] = 0.0 + + return param + + def _expand(*params): """ Expands a list of N iterables of parameters into a flat list with all @@ -77,3 +90,8 @@ def compression_param(comp_param_dtype): @pytest.fixture(scope="session") def dtype(comp_param_dtype): return comp_param_dtype[2] + + +@pytest.fixture(scope="session") +def numpy_rng(): + return np.random.default_rng() diff --git a/astropy/io/fits/tiled_compression/tests/test_fitsio.py b/astropy/io/fits/tiled_compression/tests/test_fitsio.py index 687d8098c66..9e2783c0226 100644 --- a/astropy/io/fits/tiled_compression/tests/test_fitsio.py +++ b/astropy/io/fits/tiled_compression/tests/test_fitsio.py @@ -15,7 +15,7 @@ from astropy.io import fits -from .conftest import _expand +from .conftest import _expand, fitsio_param_to_astropy_param # This is so that tox can force this file to be run, and not be silently # skipped on CI, but in all other test runs it's skipped if fitsio isn't present. @@ -25,21 +25,25 @@ fitsio = pytest.importorskip("fitsio") -@pytest.fixture(scope="session") -def numpy_rng(): - return np.random.default_rng() - - @pytest.fixture( scope="module", params=_expand( [((10,),), ((5,), (1,), (3,))], - [((12, 12),), ((1, 12), (4, 5), (6, 6))], + [((12, 12),), ((1, 12), (4, 5), (6, 6), None)], [((15, 15),), ((1, 15), (5, 1), (5, 5))], [ ((15, 15, 15),), ((5, 5, 1), (5, 7, 1), (1, 5, 4), (1, 1, 15), (15, 1, 5)), ], + # Test the situation where the tile shape is passed larger than the + # array shape + [ + ((4, 4, 5),), + ( + (5, 5, 1), + None, + ), + ], # >3D Data are not currently supported by cfitsio ), ids=lambda x: f"shape: {x[0]} tile_dims: {x[1]}", @@ -50,7 +54,7 @@ def array_shapes_tile_dims(request, compression_type): if compression_type == "HCOMPRESS_1" and ( len(shape) < 2 or np.count_nonzero(np.array(tile_dim) != 1) != 2 ): - pytest.xfail("HCOMPRESS is 2D only apparently") + pytest.xfail("HCOMPRESS requires 2D tiles.") return shape, tile_dim @@ -126,14 +130,8 @@ def astropy_compressed_file_path( tmp_path = tmp_path_factory.mktemp("astropy") filename = tmp_path / f"{compression_type}_{dtype}.fits" - # Convert fitsio kwargs to astropy kwargs - _map = {"qlevel": "quantize_level", "qmethod": "quantize_method"} - param = {_map[k]: v for k, v in param.items()} - - # Map quantize_level - if param.get("quantize_level", "missing") is None: - param["quantize_level"] = 0.0 + param = fitsio_param_to_astropy_param(param) hdu = fits.CompImageHDU( data=original_data, compression_type=compression_type, **param ) diff --git a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py index 720c0033b36..5b96391b0a1 100644 --- a/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py +++ b/astropy/io/fits/tiled_compression/tests/test_tiled_compression.py @@ -6,6 +6,8 @@ from astropy.io import fits +from .conftest import fitsio_param_to_astropy_param + @pytest.fixture def canonical_data_base_path(): @@ -61,3 +63,44 @@ def test_zblank_support(canonical_data_base_path, tmp_path): with fits.open(tmp_path / "test_zblank.fits") as hdul: assert "ZBLANK" in hdul[1].header assert_equal(np.round(hdul[1].data), reference) + + +@pytest.mark.parametrize( + ("shape", "tile_dim"), + ( + # ([5, 5, 5], [5, 5, 5]), + # ([5, 5, 5], [5, 5, 1]), # something for HCOMPRESS + # ([10, 15, 20], [5, 5, 5]), + # ([10, 5, 12], [5, 5, 5]), + ([2, 3, 4, 5], [1, 1, 2, 3]), + # ([2, 3, 4, 5], [5, 5, 1, 1]), + ), +) +def test_roundtrip_high_D( + numpy_rng, compression_type, compression_param, tmp_path, dtype, shape, tile_dim +): + if compression_type == "HCOMPRESS_1" and ( + len(shape) < 2 or np.count_nonzero(np.array(tile_dim) != 1) != 2 + ): + pytest.xfail("HCOMPRESS requires 2D tiles.") + random = numpy_rng.uniform(high=255, size=shape) + # Set first value to be exactly zero as zero values require special treatment + # for SUBTRACTIVE_DITHER_2 + random.ravel()[0] = 0.0 + original_data = random.astype(dtype) + + filename = tmp_path / f"{compression_type}_{dtype}.fits" + + # breakpoint() + param = fitsio_param_to_astropy_param(compression_param) + hdu = fits.CompImageHDU( + data=original_data, + compression_type=compression_type, + tile_size=tile_dim, + **param, + ) + hdu.writeto(filename) + + with fits.open(filename) as hdul: + a = hdul[1].data + # np.testing.assert_allclose(original_data, hdul[1].data, atol=1) From 7e1d65905f142e106846ec25bb96b73103e59c7a Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Fri, 16 Dec 2022 09:48:17 +0000 Subject: [PATCH 29/32] TMP: Remove gate on initial_checks --- .github/workflows/ci_workflows.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci_workflows.yml b/.github/workflows/ci_workflows.yml index def2d79d5e7..985bcf74f69 100644 --- a/.github/workflows/ci_workflows.yml +++ b/.github/workflows/ci_workflows.yml @@ -45,7 +45,7 @@ jobs: } tests: - needs: [initial_checks] + # needs: [initial_checks] uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 with: submodules: false @@ -87,7 +87,7 @@ jobs: posargs: --durations=50 --run-slow allowed_failures: - needs: [initial_checks] + # needs: [initial_checks] uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1 with: submodules: false @@ -106,7 +106,7 @@ jobs: parallel_and_32bit: name: 32-bit and parallel runs-on: ubuntu-latest - needs: [initial_checks] + # needs: [initial_checks] container: image: quay.io/pypa/manylinux2014_i686:2022-04-03-da6ecb3 steps: From e28e36957839a1f0506b113b7807b94c1f58ae88 Mon Sep 17 00:00:00 2001 From: Stuart Mumford Date: Fri, 16 Dec 2022 10:20:51 +0000 Subject: [PATCH 30/32] Actually test the tile shape we wanted to test --- .../io/fits/tiled_compression/tests/test_fitsio.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/astropy/io/fits/tiled_compression/tests/test_fitsio.py b/astropy/io/fits/tiled_compression/tests/test_fitsio.py index 9e2783c0226..7ec1de63410 100644 --- a/astropy/io/fits/tiled_compression/tests/test_fitsio.py +++ b/astropy/io/fits/tiled_compression/tests/test_fitsio.py @@ -44,6 +44,11 @@ None, ), ], + # Test shapes which caused errors + [ + ((3, 4, 5),), + ((1, 2, 3),), + ], # >3D Data are not currently supported by cfitsio ), ids=lambda x: f"shape: {x[0]} tile_dims: {x[1]}", @@ -124,6 +129,7 @@ def astropy_compressed_file_path( tmp_path_factory, base_original_data, data_shape, # For debuging + tile_dims, ): compression_type, param, dtype = comp_param_dtype original_data = base_original_data.astype(dtype) @@ -133,7 +139,10 @@ def astropy_compressed_file_path( param = fitsio_param_to_astropy_param(param) hdu = fits.CompImageHDU( - data=original_data, compression_type=compression_type, **param + data=original_data, + compression_type=compression_type, + tile_size=tile_dims, + **param, ) hdu.writeto(filename) From dca8c626a2a8e4727e5282ae11c5d202a63c69bd Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Fri, 16 Dec 2022 11:45:04 +0000 Subject: [PATCH 31/32] Initial work for unquantization in Python --- .../fits/tiled_compression/_quantization.py | 73 ++++++++++++------- 1 file changed, 48 insertions(+), 25 deletions(-) diff --git a/astropy/io/fits/tiled_compression/_quantization.py b/astropy/io/fits/tiled_compression/_quantization.py index a6f7386a1c2..59788dcee69 100644 --- a/astropy/io/fits/tiled_compression/_quantization.py +++ b/astropy/io/fits/tiled_compression/_quantization.py @@ -14,6 +14,28 @@ __all__ = ["Quantize"] +N_RANDOM = 10000 + + +def _generate_random(): + # This generates a canonical list of 10000 'random' values which the FITS + # standard requires for quantization. + a = 16807.0; + m = 2147483647.0; + rand_value = np.zeros(N_RANDOM) + seed = 1; + for ii in range(N_RANDOM): + temp = a * seed + seed = temp - m * int(temp / m); + rand_value[ii] = seed / m; + if seed != 1_043_618_065: + raise ValueError(f"Unexpected 10,000th seed: {seed}") + return rand_value + + +RANDOM_VALUES = _generate_random() + + DITHER_METHODS = {"NO_DITHER": -1, "SUBTRACTIVE_DITHER_1": 1, "SUBTRACTIVE_DITHER_2": 2} @@ -59,35 +81,36 @@ def decode_quantized(self, buf, scale, zero): if self.dither_method == -1: # For NO_DITHER we should just use the scale and zero directly return qbytes * scale + zero + + qbytes = qbytes.ravel() + if self.bitpix == -32: - ubytes = unquantize_float_c( - qbytes.tobytes(), - self.row, - qbytes.size, - scale, - zero, - self.dither_method, - 0, - 0, - 0.0, - qbytes.dtype.itemsize, - ) + output = np.zeros(qbytes.shape, dtype=np.float32) elif self.bitpix == -64: - ubytes = unquantize_double_c( - qbytes.tobytes(), - self.row, - qbytes.size, - scale, - zero, - self.dither_method, - 0, - 0, - 0.0, - qbytes.dtype.itemsize, - ) + output = np.zeros(qbytes.shape, dtype=float) else: raise TypeError("bitpix should be one of -32 or -64") - return np.frombuffer(ubytes, dtype=BITPIX2DTYPE[self.bitpix]).data + + n_values = len(qbytes) + + iseed = (self.row - 1) % N_RANDOM + nextrand = int(RANDOM_VALUES[iseed] * 500) + + # TODO: re-write the following without a loop! + + for ii in range(n_values): + if self.dither_method == 2 and qbytes[ii] == -2147483646: + output[ii] = 0. + else: + output[ii] = (qbytes[ii] - RANDOM_VALUES[nextrand] + 0.5) * scale + zero + nextrand += 1 + if nextrand == N_RANDOM: + iseed += 1 + if iseed == N_RANDOM: + iseed = 0 + nextrand = int(RANDOM_VALUES[iseed] * 500) + + return output.data def encode_quantized(self, buf): """ From a862c3e00d13cdad1166228d353824c271ca953d Mon Sep 17 00:00:00 2001 From: Thomas Robitaille Date: Fri, 16 Dec 2022 11:48:32 +0000 Subject: [PATCH 32/32] Remove unneeded files --- .../fits/tiled_compression/setup_package.py | 1 - .../fits/tiled_compression/src/compression.c | 105 ------------------ .../fits/tiled_compression/src/imcompress.h | 90 --------------- 3 files changed, 196 deletions(-) delete mode 100644 astropy/io/fits/tiled_compression/src/imcompress.h diff --git a/astropy/io/fits/tiled_compression/setup_package.py b/astropy/io/fits/tiled_compression/setup_package.py index c5b1f953a03..c4a3a7cc611 100644 --- a/astropy/io/fits/tiled_compression/setup_package.py +++ b/astropy/io/fits/tiled_compression/setup_package.py @@ -18,7 +18,6 @@ def get_extensions(): os.path.join("cextern", "cfitsio", "lib", "fits_hcompress.c"), os.path.join("cextern", "cfitsio", "lib", "fits_hdecompress.c"), os.path.join("cextern", "cfitsio", "lib", "quantize.c"), - os.path.join("cextern", "cfitsio", "lib", "imcompress.c"), ], include_dirs=[SRC_DIR], ) diff --git a/astropy/io/fits/tiled_compression/src/compression.c b/astropy/io/fits/tiled_compression/src/compression.c index 3aed92de4a7..239d8c563de 100644 --- a/astropy/io/fits/tiled_compression/src/compression.c +++ b/astropy/io/fits/tiled_compression/src/compression.c @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -394,107 +393,3 @@ static PyObject *quantize_double_c(PyObject *self, PyObject *args) { free(quantized_bytes); return result; } - -static PyObject *unquantize_float_c(PyObject *self, PyObject *args) { - - const char *input_bytes; - Py_ssize_t nbytes; - PyObject *result; - - long row, npix; - int nullcheck; - int tnull; - float nullval; - int dither_method; - - double bscale, bzero; - int bytepix; // int size - int status = 0; - - int *anynull; - float *output_data; - char *output_bytes; - - if (!PyArg_ParseTuple(args, "y#llddiiifi", &input_bytes, &nbytes, &row, &npix, - &bscale, &bzero, &dither_method, &nullcheck, &tnull, - &nullval, &bytepix)) { - return NULL; - } - -// TODO: add support, if needed, for nullcheck=1 - -anynull = (int *)malloc(npix * sizeof(int)); -output_data = (float *)malloc(npix * sizeof(float)); - -if (bytepix == 1) { - unquantize_i1r4(row, (unsigned char *)input_bytes, npix, bscale, bzero, - dither_method, nullcheck, (unsigned char)tnull, nullval, - NULL, anynull, output_data, &status); -} else if (bytepix == 2) { - unquantize_i2r4(row, (short *)input_bytes, npix, bscale, bzero, - dither_method, nullcheck, (short)tnull, nullval, NULL, - anynull, output_data, &status); -} else if (bytepix == 4) { - unquantize_i4r4(row, (int *)input_bytes, npix, bscale, bzero, dither_method, - nullcheck, (int)tnull, nullval, NULL, anynull, output_data, - &status); -} - -output_bytes = (char *)output_data; - - result = Py_BuildValue("y#", output_bytes, npix * sizeof(float)); - free(output_bytes); - return result; -} - -static PyObject *unquantize_double_c(PyObject *self, PyObject *args) { - - const char *input_bytes; - Py_ssize_t nbytes; - PyObject *result; - - long row, npix; - int nullcheck; - int tnull; - double nullval; - int dither_method; - - double bscale, bzero; - int bytepix; // int size - int status = 0; - - int *anynull; - double *output_data; - char *output_bytes; - - if (!PyArg_ParseTuple(args, "y#llddiiidi", &input_bytes, &nbytes, &row, &npix, - &bscale, &bzero, &dither_method, &nullcheck, &tnull, - &nullval, &bytepix)) { - return NULL; - } - -// TODO: add support, if needed, for nullcheck=1 - -anynull = (int *)malloc(npix * sizeof(int)); -output_data = (double *)malloc(npix * sizeof(double)); - -if (bytepix == 1) { - unquantize_i1r8(row, (unsigned char *)input_bytes, npix, bscale, bzero, - dither_method, nullcheck, (unsigned char)tnull, nullval, - NULL, anynull, output_data, &status); -} else if (bytepix == 2) { - unquantize_i2r8(row, (short *)input_bytes, npix, bscale, bzero, - dither_method, nullcheck, (short)tnull, nullval, NULL, - anynull, output_data, &status); -} else if (bytepix == 4) { - unquantize_i4r8(row, (int *)input_bytes, npix, bscale, bzero, dither_method, - nullcheck, (int)tnull, nullval, NULL, anynull, output_data, - &status); -} - -output_bytes = (char *)output_data; - - result = Py_BuildValue("y#", output_bytes, npix * sizeof(double)); - free(output_bytes); - return result; -} diff --git a/astropy/io/fits/tiled_compression/src/imcompress.h b/astropy/io/fits/tiled_compression/src/imcompress.h deleted file mode 100644 index 6a3e09a38a6..00000000000 --- a/astropy/io/fits/tiled_compression/src/imcompress.h +++ /dev/null @@ -1,90 +0,0 @@ -int unquantize_i1r4(long row, - unsigned char *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ - float nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - float *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -int unquantize_i2r4(long row, - short *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - short tnull, /* I - value of FITS TNULLn keyword if any */ - float nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - float *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -int unquantize_i4r4(long row, - int *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - int tnull, /* I - value of FITS TNULLn keyword if any */ - float nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - float *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -int unquantize_i1r8(long row, - unsigned char *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - unsigned char tnull, /* I - value of FITS TNULLn keyword if any */ - double nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - double *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -int unquantize_i2r8(long row, - short *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - short tnull, /* I - value of FITS TNULLn keyword if any */ - double nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - double *output, /* O - array of converted pixels */ - int *status); /* IO - error status */ -int unquantize_i4r8(long row, - int *input, /* I - array of values to be converted */ - long ntodo, /* I - number of elements in the array */ - double scale, /* I - FITS TSCALn or BSCALE value */ - double zero, /* I - FITS TZEROn or BZERO value */ - int dither_method, /* I - which subtractive dither method to use */ - int nullcheck, /* I - null checking code; 0 = don't check */ - /* 1:set null pixels = nullval */ - /* 2: if null pixel, set nullarray = 1 */ - int tnull, /* I - value of FITS TNULLn keyword if any */ - double nullval, /* I - set null pixels, if nullcheck = 1 */ - char *nullarray, /* I - bad pixel array, if nullcheck = 2 */ - int *anynull, /* O - set to 1 if any pixels are null */ - double *output, /* O - array of converted pixels */ - int *status); /* IO - error status */