#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION #define PY_SSIZE_T_CLEAN #include #include #include #include #include #include #include /* Define docstrings */ static char module_docstring[] = "Core compression/decompression functions wrapped from cfitsio."; 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 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); 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); 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); static PyObject *CfitsioException = NULL; /* 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}, {"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} }; /* This is the function that is called on import. */ static PyModuleDef compression = { PyModuleDef_HEAD_INIT, "_compression", module_docstring, -1, module_methods, }; PyMODINIT_FUNC PyInit__compression(void) { PyObject* m; m = PyModule_Create(&compression); /* Initialize new exception object */ CfitsioException = PyErr_NewException("_compression.CfitsioException", NULL, NULL); /* Add exception object to your module */ PyModule_AddObject(m, "CfitsioException", CfitsioException); return m; }; // Some of the cfitsio compression functions use this function to put an error // message on the stack. We provide an implementation which sets the Python // error state with our custom exception type, and the message provided by the // cfitsio call. In our wrapper functions we can then check if the Python error // state is set and then return NULL to raise the error. void ffpmsg(const char *err_message) { PyGILState_STATE gstate; gstate = PyGILState_Ensure(); PyErr_SetString(CfitsioException, err_message); PyGILState_Release(gstate); } /* PLIO/IRAF compression */ static PyObject *compress_plio_1_c(PyObject *self, PyObject *args) { const char *str; char *buf; Py_ssize_t count; PyObject *result; int maxelem; int tilesize; short *compressed_values; int compressed_length; int *decompressed_values; if (!PyArg_ParseTuple(args, "y#i", &str, &count, &tilesize)) { return NULL; } decompressed_values = (int *)str; for (int ii = 0; ii < tilesize; ii++) { if (decompressed_values[ii] < 0 || decompressed_values[ii] > 16777215) { /* plio algorithm only supports positive 24 bit ints */ PyErr_SetString(PyExc_ValueError, "data out of range for PLIO compression (0 - 2**24)"); return (PyObject *)NULL; } } // For PLIO imcomp_calc_max_elem in cfitsio does this to calculate max memory: maxelem = tilesize; // However, when compressing small numbers of random integers you can end up // using more memory for the compressed bytes. In the worst case scenario we // tested, compressing a single 4 byte integer will compress to 16 bytes. We // therefore allocate a buffer 4 ints larger than we need here to give that // margin of error. compressed_values = (short *)calloc(maxelem + 4, sizeof(int)); decompressed_values = (int *)str; compressed_length = pl_p2li(decompressed_values, 1, compressed_values, tilesize); if (PyErr_Occurred() != NULL) { // If an error condition inside the cfitsio function, the call inside // cfitsio should have called the ffpmsg function which sets the Python // exception, so we just return here to raise an error. return (PyObject *)NULL; } 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; decompressed_values = (int *)calloc(tilesize, sizeof(int)); pl_l2pi(compressed_values, 1, decompressed_values, tilesize); if (PyErr_Occurred() != NULL) { // If an error condition inside the cfitsio function, the call inside // cfitsio should have called the ffpmsg function which sets the Python // exception, so we just return here to raise an error. return (PyObject *)NULL; } buf = (char *)decompressed_values; result = Py_BuildValue("y#", buf, tilesize * sizeof(int)); 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; int maxelem; 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; } Py_BEGIN_ALLOW_THREADS // 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; 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); } Py_END_ALLOW_THREADS if (PyErr_Occurred() != NULL) { // If an error condition inside the cfitsio function, the call inside // cfitsio should have called the ffpmsg function which sets the Python // exception, so we just return here to raise an error. return (PyObject *)NULL; } 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; } Py_BEGIN_ALLOW_THREADS 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; } Py_END_ALLOW_THREADS if (PyErr_Occurred() != NULL) { // If an error condition inside the cfitsio function, the call inside // cfitsio should have called the ffpmsg function which sets the Python // exception, so we just return here to raise an error. return (PyObject *)NULL; } 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 int maxelem; char *compressed_values; int *decompressed_values_int; long buffer_size; long long *decompressed_values_longlong; if (!PyArg_ParseTuple(args, "y#iiii", &str, &count, &nx, &ny, &scale, &bytepix)) { return NULL; } if (bytepix != 4 && bytepix != 8) { PyErr_SetString(PyExc_ValueError, "HCompress can only work with 4 or 8 byte integers."); return (PyObject *)NULL; } if ((nx < 4) || (ny < 4)) { PyErr_SetString(PyExc_ValueError, "HCOMPRESS requires tiles of at least 4x4 pixels."); return (PyObject *)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; } Py_BEGIN_ALLOW_THREADS // maxelem adapted from cfitsio's imcomp_calc_max_elem function maxelem = count / 4 * 2.2 + 26; // Apparently with the above calculation we can still end up allocating too // small of a buffer, this could never happen by more than 32 bytes // riiiiiight. // TODO: Do a small buffer calculation to tune this number like we did for PLIO compressed_values = (char *)calloc(maxelem + 4, sizeof(long long)); buffer_size = (maxelem + 4) * sizeof(long long); if (bytepix == 4) { decompressed_values_int = (int *)str; fits_hcompress(decompressed_values_int, ny, nx, scale, compressed_values, &buffer_size, &status); } else { decompressed_values_longlong = (long long *)str; fits_hcompress64(decompressed_values_longlong, ny, nx, scale, compressed_values, &buffer_size, &status); } Py_END_ALLOW_THREADS if (PyErr_Occurred() != NULL) { // If an error condition inside the cfitsio function, the call inside // cfitsio should have called the ffpmsg function which sets the Python // exception, so we just return here to raise an error. return (PyObject *)NULL; } if (status != 0) { PyErr_SetString(PyExc_ValueError, "Status returned from cfitsio is not zero for an unknown reason."); return (PyObject *)NULL; } result = Py_BuildValue("y#", compressed_values, buffer_size); 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; } if (bytepix != 4 && bytepix != 8) { PyErr_SetString(PyExc_ValueError, "HCompress can only work with 4 or 8 byte integers."); return (PyObject *)NULL; } Py_BEGIN_ALLOW_THREADS compressed_values = (unsigned char *)str; 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); } Py_END_ALLOW_THREADS if (PyErr_Occurred() != NULL) { // If an error condition inside the cfitsio function, the call inside // cfitsio should have called the ffpmsg function which sets the Python // exception, so we just return here to raise an error. return (PyObject *)NULL; } if (status != 0) { PyErr_SetString(PyExc_ValueError, "Status returned from cfitsio is not zero for an unknown reason."); return (PyObject *)NULL; } // fits_hdecompress[64] always returns 4 byte integers result = Py_BuildValue("y#", dbytes, nx * ny * 4); 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; int status; Py_ssize_t output_length; if (!PyArg_ParseTuple(args, "y#lllidfi", &input_bytes, &nbytes, &row, &nx, &ny, &nullcheck, &in_null_value, &qlevel, &dither_method)) { return NULL; } Py_BEGIN_ALLOW_THREADS input_data = (float *)input_bytes; quantized_data = (int *)malloc(nx * ny * sizeof(int)); 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; output_length = nx * ny * sizeof(int); Py_END_ALLOW_THREADS result = Py_BuildValue("y#iddii", quantized_bytes, output_length, status, 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; int status; if (!PyArg_ParseTuple(args, "y#lllidfi", &input_bytes, &nbytes, &row, &nx, &ny, &nullcheck, &in_null_value, &qlevel, &dither_method)) { return NULL; } Py_BEGIN_ALLOW_THREADS input_data = (double *)input_bytes; quantized_data = (int *)malloc(nx * ny * sizeof(int)); 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; Py_END_ALLOW_THREADS result = Py_BuildValue("y#iddii", quantized_bytes, nx * ny * sizeof(int), status, 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 Py_BEGIN_ALLOW_THREADS anynull = (int *)malloc(npix * sizeof(int)); output_data = (float *)calloc(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; Py_END_ALLOW_THREADS result = Py_BuildValue("y#", output_bytes, npix * sizeof(float)); free(output_bytes); free(anynull); 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 Py_BEGIN_ALLOW_THREADS 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; Py_END_ALLOW_THREADS result = Py_BuildValue("y#", output_bytes, npix * sizeof(double)); free(output_bytes); free(anynull); return result; }