From 0ab90cb5eef1fde94ef3425a668fb55fc6a778dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Tschumperl=C3=A9?= Date: Tue, 31 May 2022 09:49:01 +0200 Subject: [PATCH] Fix some memory leaks (result of the 6-weeks internship of Bryan Huet). --- build_tools.bash | 19 ++--- gmicpy.cpp | 195 ++++++++++++++++++++++++++++++++++++----------- setup.py | 5 +- 3 files changed, 164 insertions(+), 55 deletions(-) diff --git a/build_tools.bash b/build_tools.bash index b194c9b1..b86aa4a0 100644 --- a/build_tools.bash +++ b/build_tools.bash @@ -23,7 +23,7 @@ fi # Guess target G'MIC version from VERSION file's contents if [ -f "VERSION" ]; then FILE_BASED_GMIC_VERSION=$(cat VERSION) - GMIC_SRC_VERSION=$(cat VERSION | grep -oE "[0-9]\.[0-9]\.[0-9][a-z]?(_pre(release)?(0-9+)?)?") + GMIC_SRC_VERSION=$(cat VERSION | grep -E "[0-9]\.[0-9]\.[0-9][a-z]?(_pre(release)?(0-9+)?)?") fi GMIC_VERSION=${GMIC_VERSION:-$GMIC_SRC_VERSION} GMIC_PY_PACKAGE_VERSION=${FILE_BASED_GMIC_VERSION:-$GMIC_VERSION} @@ -84,21 +84,21 @@ function 11_send_to_pypi () { export TWINE_PASSWORD="$TWINE_PASSWORD_GITHUB_SECRET" # See: Github Workflow scripts export TWINE_REPOSITORY_URL=https://upload.pypi.org/legacy/ TWINE=twine - + # Do not reupload same-version wheels our source archives TWINE_OPTIONS="--skip-existing --verbose" - + echo "TWINE UPLOAD STEP: Contents of dist/ and wheelhouse directories are:" find dist/ find *wheel* - + # Upload sdist source tar.gz archive if found if [ -d "dist/" ]; then for a in `ls dist/*.tar.gz`; do $TWINE upload $a $TWINE_OPTIONS done fi - + # Upload binary python wheels if found if [ -d "wheelhouse/" ]; then for a in `ls wheelhouse/* | grep -E 'manylinux|macosx'`; do # Keep /* wildcard for proper relative paths!! @@ -110,6 +110,7 @@ function 11_send_to_pypi () { function 1_clean_and_regrab_gmic_src () { set -x + #GMIC_ARCHIVE_GLOB=gmic_3.1.0_pre220419.tar.gz GMIC_ARCHIVE_GLOB=gmic_${GMIC_VERSION}.tar.gz # GMIC_ARCHIVE_GLOB=gmic_${GMIC_VERSION}*.tar.gz if [[ $GMIC_VERSION == *"pre"* ]]; then @@ -226,7 +227,7 @@ function 33_build_manylinux () { then PRE_CMD= fi - + rm wheelhouse/*$PYBIN_PREFIX*$PLAT* -f docker pull $DOCKER_IMAGE docker run --rm -e PLAT=$PLAT -v `pwd`:/io $DOCKER_IMAGE find /io @@ -251,7 +252,7 @@ function 3_test_compiled_so () { TEST_FILES="${TEST_FILES:-../../tests/test_gmic_py.py ../../tests/test_gmic_numpy.py ../../tests/test_gmic_numpy_toolkits.py ../../tests/test_gmic_py_memfreeing.py}" #TEST_FILES="${TEST_FILES:-../../tests/test_gmic_py_memfreeing.py}" FAILED_SUITES=0 - $PIP3 uninstall gmic -y; cd $GMIC_LIB_DIR ; LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ; $PIP3 install -r ../../dev-requirements.txt ; pwd; ls; + $PIP3 uninstall gmic -y; cd $GMIC_LIB_DIR ; LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ; $PIP3 install -r ../../dev-requirements.txt ; pwd; ls; for TEST_FILE in $TEST_FILES; do # $PIP3 uninstall gmic -y; cd $GMIC_LIB_DIR ; LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ; $PIP3 install -r ../../dev-requirements.txt ; pwd; ls; PYTHONMALLOC=malloc valgrind --show-leak-kinds=all --leak-check=full --log-file=/tmp/valgrind-output $PYTHON3 -m pytest $TEST_FILES $PYTEST_EXPRESSION_PARAM -vvv -rxXs || { echo "Fatal error while running pytests" ; exit 1 ; } ; cd ../.. @@ -291,7 +292,7 @@ function 31_test_compiled_so_filters_io () { find ~/.config/gmic $PIP3 uninstall gmic -y; cd ./build/lib*$PYTHON_VERSION*/ ; LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH ; $PIP3 install -r ../../dev-requirements.txt ; pwd; ls; $PYTHON3 -m pytest ../../tests/test_gmic_py_filters_io.py $PYTEST_EXPRESSION_PARAM $PYTEST_NB_THREADS -vvv -rxXs || { echo "Fatal error while running pytests" ; exit 1 ; } ; cd ../.. find ~/.config/gmic -} +} function 4_build_wheel () { $PIP3 install wheel || { echo "Fatal wheel package install error" ; exit 1 ; } @@ -322,7 +323,7 @@ function 6b_make_doc_without_c_recompilation () { function --help () { # declares an array with the emojis we want to support EMOJIS=(🍇 🍈 🍉 🍊 🍋 🍌 🍍 🥭 🍎 🍏 🍐 🍑 🍒 🍓 🥝 🍅 🥥 🥑 🍆 🥔 🥕 🌽 🌶 🥒 🥬 🥦) - + # selects a random element from the EMOJIS set echo "☀️ G'MIC Python Binding Development & Build Tools ☀️ " diff --git a/gmicpy.cpp b/gmicpy.cpp index ac1eb3d2..ccf04805 100644 --- a/gmicpy.cpp +++ b/gmicpy.cpp @@ -34,7 +34,7 @@ static PyTypeObject PyGmicType = { }; typedef struct { - PyObject_HEAD gmic_image *_gmic_image; // G'MIC library's Gmic Image + PyObject_HEAD gmic_image *_gmic_image;// G'MIC library's Gmic Image } PyGmicImage; typedef struct { @@ -78,18 +78,25 @@ void swap_gmic_list_item_into_gmic_image(gmic_list &images, int position, PyGmicImage *image) { + // Put back the possibly modified reallocated image buffer into the // original external GmicImage Back up the image data into the original // external image before it gets freed + swap(image->_gmic_image->_data, images[position]._data); image->_gmic_image->_width = images[position]._width; image->_gmic_image->_height = images[position]._height; image->_gmic_image->_depth = images[position]._depth; image->_gmic_image->_spectrum = images[position]._spectrum; image->_gmic_image->_is_shared = images[position]._is_shared; + // Prevent freeing the data buffer's pointer now copied into the external // image + + // [MODIF 2022] + /* images[position]._data = 0; + */ } #ifdef gmic_py_jupyter_ipython_display @@ -152,12 +159,17 @@ gmic_py_str_replace_display_to_output(char *orig, char *extension) Py_INCREF(pyglobs); // sanity checks and initialization - if (!orig || !rep) + if (!orig || !rep){ + // [MODIF 2022] + Py_XDECREF(pyglobs); return NULL; + } len_rep = strlen(rep); - if (len_rep == 0) + if (len_rep == 0){ + // [MODIF 2022] + Py_XDECREF(pyglobs); return NULL; // empty rep causes infinite loop during count - + } // build a first uuid to detect fixed replacement string length // 'with' becomes thus '/tmp/unique-id.png' // 'with_globbed' becomes '/tmp/unique-id*.png' @@ -180,8 +192,11 @@ gmic_py_str_replace_display_to_output(char *orig, char *extension) tmp = result = (char *)malloc(strlen(orig) + (len_with - len_rep) * count + 1); - if (!result) - return NULL; + if (!result){ + // [MODIF 2022] + Py_XDECREF(pyglobs); + return NULL; // empty rep causes infinite loop during count + } // first time through the loop, all the variable are set correctly // from here on, @@ -213,17 +228,20 @@ gmic_py_str_replace_display_to_output(char *orig, char *extension) } strcpy(tmp, orig); + // [MODIF 2022] pyresult = PyList_New(0); - PyList_Append(pyresult, Py_BuildValue("s", result)); + PyObject *temp = Py_BuildValue("s",result); + PyList_Append(pyresult, temp); PyList_Append(pyresult, pyglobs); - Py_DECREF(pyglobs); - + Py_XDECREF(pyglobs); + Py_XDECREF(temp); return pyresult; } PyObject * gmic_py_display_with_matplotlib_or_ipython(PyObject *image_files_glob_strings) { + //fprintf(stdout, "gmic_py_display_with_matplotlib_or_ipython\n"); if (!PyList_Check(image_files_glob_strings)) { PyErr_Format(GmicException, "input globs list is not a Python list"); @@ -351,6 +369,7 @@ gmic_py_display_with_matplotlib_or_ipython(PyObject *image_files_glob_strings) PyObject * autoload_wurlitzer_into_ipython() { + //fprintf(stdout, "autoload_wurlitzer_into_ipython\n"); PyObject *wurlitzer_module = NULL; PyObject *ipython_module = NULL; PyObject *ipython_handler = NULL; @@ -468,6 +487,7 @@ autoload_wurlitzer_into_ipython() // end gmic_py_jupyter_ipython_display #endif + static PyObject * run_impl(PyObject *self, PyObject *args, PyObject *kwargs) { @@ -600,8 +620,11 @@ run_impl(PyObject *self, PyObject *args, PyObject *kwargs) "of '%.400s'(s)", Py_TYPE(input_gmic_image_names)->tp_name, PyUnicode_Type.tp_name); - Py_XDECREF(input_gmic_images); - Py_XDECREF(input_gmic_image_names); + + // [MODIF 2022] + Py_XDECREF(input_gmic_images); + Py_XDECREF(input_gmic_image_names); + Py_XDECREF(iter); return NULL; } @@ -637,6 +660,8 @@ run_impl(PyObject *self, PyObject *args, PyObject *kwargs) swap_gmic_image_into_gmic_list( (PyGmicImage *)current_image, images, image_position); + // [MODIF 2022] + Py_XDECREF(current_image); image_position++; } @@ -644,8 +669,9 @@ run_impl(PyObject *self, PyObject *args, PyObject *kwargs) ((PyGmic *)self) ->_gmic->run(commands_line, images, image_names, 0, 0); + // [MODIF 2022] // Prevent images auto-deallocation by G'MIC - image_position = 0; + //image_position = 0; // Bring new images set back into the Python world // (change List items in-place) First empty the input @@ -684,10 +710,14 @@ run_impl(PyObject *self, PyObject *args, PyObject *kwargs) "it to provided 'images' parameter list."); return NULL; } - - PyList_Append(input_gmic_images, new_gmic_image); + // [MODIF 2022] + PyObject *temp = new_gmic_image; + PyList_Append(input_gmic_images, temp); + Py_XDECREF(temp); + Py_XDECREF(_data); } - + // [MODIF 2022] + Py_XDECREF(iter); // B/ Else if a single GmicImage was provided } else if (Py_TYPE(input_gmic_images) == @@ -752,8 +782,11 @@ run_impl(PyObject *self, PyObject *args, PyObject *kwargs) // Add image names from the Gmic List of names cimglist_for(image_names, l) { - PyList_Append(input_gmic_image_names, - PyUnicode_FromString(image_names[l])); + // [MODIF 2022] + PyObject *temp = PyUnicode_FromString(image_names[l]); + PyList_Append(input_gmic_image_names, + temp); + Py_XDECREF(temp); } } // ii) If a str parameter was provided @@ -816,6 +849,8 @@ import_numpy_module() // exit raising numpy_module import exception if (!numpy_module) { PyErr_Clear(); + // [MODIF 2022] + Py_XDECREF(numpy_module); return PyErr_Format(GmicException, "The 'numpy' module cannot be imported. Is it " "installed or in your Python path?"); @@ -854,7 +889,6 @@ PyGmicImage_from_numpy_helper(PyObject *cls, PyObject *args, PyObject *kwargs) char const *keywords[] = {"numpy_array", "deinterleave", "permute", NULL}; PyGmicImage *py_gmicimage_to_fill = NULL; char *arg_permute = NULL; - numpy_module = import_numpy_module(); if (!numpy_module) return NULL; @@ -882,7 +916,10 @@ PyGmicImage_from_numpy_helper(PyObject *cls, PyObject *args, PyObject *kwargs) "Provided 'data' of type 'numpy.ndarray' must be between " "1D and 4D ('data.ndim'=%d).", ndarray_ndim); - return NULL; + // [MODIF 2022] + Py_XDECREF(py_arg_ndarray); + Py_XDECREF(py_arg_deinterleave); + return NULL; } // Get input ndarray.dtype and prevent non-integer/float/bool data @@ -899,7 +936,11 @@ PyGmicImage_from_numpy_helper(PyObject *cls, PyObject *args, PyObject *kwargs) "contain numbers ie. its 'dtype.kind'(=%U) is not one of " "'b', 'i', 'u', 'f'.", ndarray_dtype_kind); - // TODO pytest this + // [MODIF 2022] + Py_XDECREF(ndarray_dtype); + Py_XDECREF(ndarray_dtype_kind); + Py_XDECREF(py_arg_ndarray); + Py_XDECREF(py_arg_deinterleave); return NULL; } @@ -912,9 +953,13 @@ PyGmicImage_from_numpy_helper(PyObject *cls, PyObject *args, PyObject *kwargs) // array. So, using 'astype' is the most stable, less // memory-efficient way // :-) :-/ + + // [MODIF 2022] + PyObject *attr_str_for_float = PyObject_GetAttrString(numpy_module, "float32"); float32_ndarray = PyObject_CallMethod(py_arg_ndarray, "astype", "O", - PyObject_GetAttrString(numpy_module, "float32")); + attr_str_for_float); + Py_XDECREF(attr_str_for_float); // Get unsqueezed shape of numpy array -> GmicImage width, height, // depth, spectrum Getting a shape with the most axes from array: @@ -931,6 +976,7 @@ PyGmicImage_from_numpy_helper(PyObject *cls, PyObject *args, PyObject *kwargs) // After this the shape should be (w, h, 1, 3) ndarray_shape_tuple = PyObject_GetAttrString( ndarray_as_3d_unsqueezed_view_expanded_dims, "shape"); + _height = (unsigned int)PyLong_AsSize_t(PyTuple_GetItem(ndarray_shape_tuple, 0)); _width = @@ -940,6 +986,8 @@ PyGmicImage_from_numpy_helper(PyObject *cls, PyObject *args, PyObject *kwargs) _spectrum = (unsigned int)PyLong_AsSize_t(PyTuple_GetItem(ndarray_shape_tuple, 3)); + + py_gmicimage_to_fill = (PyGmicImage *)PyObject_CallFunction( (PyObject *)&PyGmicImageType, (const char *)"OIIII", Py_None, // This empty _data buffer will be regenerated by the @@ -977,6 +1025,9 @@ PyGmicImage_from_numpy_helper(PyObject *cls, PyObject *args, PyObject *kwargs) } } + + + Py_XDECREF(py_arg_ndarray); Py_XDECREF(py_arg_deinterleave); Py_XDECREF(ndarray_dtype); @@ -990,6 +1041,7 @@ PyGmicImage_from_numpy_helper(PyObject *cls, PyObject *args, PyObject *kwargs) Py_XDECREF(numpy_module); return (PyObject *)py_gmicimage_to_fill; + } static PyObject * @@ -999,6 +1051,8 @@ PyGmicImage_from_numpy(PyObject *cls, PyObject *args, PyObject *kwargs) PyObject *arg_np_array = NULL; // No defaults PyObject *a = NULL; PyObject *kw = NULL; + PyObject *tr = NULL; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O", (char **)keywords, &arg_np_array)) { @@ -1008,16 +1062,20 @@ PyGmicImage_from_numpy(PyObject *cls, PyObject *args, PyObject *kwargs) kw = PyDict_New(); PyDict_SetItemString(kw, "numpy_array", arg_np_array); PyDict_SetItemString(kw, "deinterleave", Py_True); - - Py_DECREF(arg_np_array); - - return PyObject_Call(PyObject_GetAttrString(cls, "from_numpy_helper"), a, - kw); + Py_XDECREF(arg_np_array); + + // [MODIF 2022] + Py_XDECREF(args); + PyObject *get_attr = PyObject_GetAttrString(cls, "from_numpy_helper"); + PyObject *ret = PyObject_Call(get_attr, a,kw); + Py_XDECREF(get_attr); + return ret; } static PyObject * PyGmicImage_to_numpy(PyObject *self, PyObject *args, PyObject *kwargs) { + PyObject *a = NULL; PyObject *kw = NULL; @@ -1025,8 +1083,15 @@ PyGmicImage_to_numpy(PyObject *self, PyObject *args, PyObject *kwargs) kw = PyDict_New(); PyDict_SetItemString(kw, "interleave", Py_True); - return PyObject_Call(PyObject_GetAttrString(self, "to_numpy_helper"), a, - kw); + // [MODIF 2022] + PyObject *numpy_get_attr = PyObject_GetAttrString(self, "to_numpy_helper"); + PyObject *call_to_numpy = PyObject_Call(numpy_get_attr, a, kw); + + Py_XDECREF(a); + Py_XDECREF(kw); + Py_XDECREF(numpy_get_attr); + + return call_to_numpy; } static PyObject * @@ -1066,8 +1131,14 @@ PyGmicImage_to_skimage(PyObject *self, PyObject *args, PyObject *kwargs) py_permute_str = PyUnicode_FromString("zyxc"); PyDict_SetItemString(kw, "permute", py_permute_str); - return PyObject_Call(PyObject_GetAttrString(self, "to_numpy_helper"), a, - kw); + PyObject *numpy_get_attr = PyObject_GetAttrString(self, "to_numpy_helper"); + PyObject *call_to_numpy = PyObject_Call(numpy_get_attr, a, kw); + + Py_XDECREF(numpy_get_attr); + Py_XDECREF(a); + Py_XDECREF(kw); + + return call_to_numpy; } static PyObject * @@ -1115,8 +1186,15 @@ PyGmicImage_from_PIL(PyObject *cls, PyObject *args, PyObject *kwargs) Py_DECREF(PIL_Image_mod); Py_DECREF(numpy_mod); - return PyObject_Call(PyObject_GetAttrString(cls, "from_numpy_helper"), a, - kw); + // [MODIF 2022] + PyObject *numpy_get_attr = PyObject_GetAttrString(cls, "from_numpy_helper"); + PyObject *call_to_numpy = PyObject_Call(numpy_get_attr, a, kw); + + Py_XDECREF(numpy_get_attr); + Py_XDECREF(a); + Py_XDECREF(kw); + + return call_to_numpy; } static PyObject * @@ -1170,9 +1248,13 @@ PyGmicImage_to_PIL(PyObject *self, PyObject *args, PyObject *kwargs) py_permute_str = PyUnicode_FromString("zyxc"); PyDict_SetItemString(kw, "permute", py_permute_str); + + PyObject *test = PyObject_GetAttrString(self, "to_numpy_helper"); + prePIL_np_array = - PyObject_Call(PyObject_GetAttrString(self, "to_numpy_helper"), a, kw); + PyObject_Call(test, a, kw); + Py_XDECREF(test); if (!prePIL_np_array) { return NULL; } @@ -1185,9 +1267,17 @@ PyGmicImage_to_PIL(PyObject *self, PyObject *args, PyObject *kwargs) Py_DECREF(a); Py_XDECREF(arg_astype); - return PyObject_CallFunction( - PyObject_GetAttrString(PIL_Image_mod, "fromarray"), "OO", + // [MODIF 2022] + PyObject *part = PyObject_GetAttrString(PIL_Image_mod, "fromarray"); + PyObject *funct = PyObject_CallFunction( + part, "OO", prePIL_np_array, arg_mode); + + Py_XDECREF(part); + Py_XDECREF(prePIL_np_array); + + + return funct; } // End of ifdef gmic_py_numpy @@ -1269,7 +1359,6 @@ static PyMethodDef PyGmic_methods[] = { }; // ------------ G'MIC IMAGE BINDING ----// - PyObject * PyGmicImage_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) { @@ -1295,6 +1384,7 @@ PyGmicImage_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) char const *keywords[] = {"data", "width", "height", "depth", "spectrum", "shared", NULL}; + // Parameters parsing and checking if (!PyArg_ParseTupleAndKeywords( args, kwargs, "|OIIIIp", (char **)keywords, &bytesObj, &_width, @@ -1321,6 +1411,7 @@ PyGmicImage_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) "Parameter 'data' must be a " "pure-python 'bytes' buffer object."); // TODO pytest this + Py_XDECREF(bytesObj); return NULL; } } @@ -1331,13 +1422,17 @@ PyGmicImage_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) // bytesarray-based bytes object to be ingested by the _data // parameter if (_width >= 1 && _height >= 1 && _depth >= 1 && _spectrum >= 1) { - bytesObj = PyObject_CallFunction( - (PyObject *)&PyBytes_Type, "O", + PyObject *pybytes_type = (PyObject *)&PyBytes_Type; + PyObject *pybytearray_type = PyObject_CallFunction( (PyObject *)&PyByteArray_Type, "I", - _width * _height * _depth * _spectrum * sizeof(T), NULL), - NULL); - Py_INCREF(bytesObj); + _width * _height * _depth * _spectrum * sizeof(T), NULL); + + bytesObj = PyObject_CallFunction(pybytes_type, "O",pybytearray_type,NULL); + + //Py_INCREF(bytesObj); + + Py_XDECREF(pybytearray_type); bytesObj_is_bytes = true; // TODO pytest this } @@ -1346,6 +1441,7 @@ PyGmicImage_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) "If you do not provide a 'data' parameter, make at " "least all dimensions >=1."); // TODO pytest this + Py_XDECREF(bytesObj); return NULL; } } @@ -1360,6 +1456,7 @@ PyGmicImage_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) "different than the _data buffer size in bytes (%d)", dimensions_product, sizeof(T), dimensions_product * sizeof(T), _data_bytes_size); + Py_XDECREF(bytesObj); return NULL; } @@ -1379,13 +1476,17 @@ PyGmicImage_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) "are you requesting too much memory (%d bytes)?", _width, _height, _depth, _spectrum, dimensions_product * sizeof(T)); + Py_XDECREF(bytesObj); return NULL; } + memcpy(self->_gmic_image->_data, PyBytes_AsString(bytesObj), PyBytes_Size(bytesObj)); + // [MODIF 2022] Py_XDECREF(bytesObj); + Py_XDECREF(args); return (PyObject *)self; } @@ -1415,6 +1516,7 @@ PyGmicImage_call(PyObject *self, PyObject *args, PyObject *kwargs) return NULL; } + return PyFloat_FromDouble( (*((PyGmicImage *)self)->_gmic_image)(x, y, z, c)); } @@ -1442,7 +1544,7 @@ PyGmic_alloc(PyTypeObject *type, Py_ssize_t nitems) static void PyGmicImage_free(PyObject *v) { - PyObject_Free(v); + PyObject_Free(v); } static void @@ -1458,6 +1560,7 @@ PyGmicImage_dealloc(PyGmicImage *self) self->_gmic_image = NULL; GMIC_PY_LOG("PyGmicImage_dealloc\n"); Py_TYPE(self)->tp_free((PyObject *)self); + } static void @@ -1465,7 +1568,6 @@ PyGmic_dealloc(PyGmic *self) { delete self->_gmic; self->_gmic = NULL; - // keep this in place for test_gmic_py_memfreeing.py pytest case GMIC_PY_LOG("PyGmic_dealloc\n"); Py_TYPE(self)->tp_free((PyObject *)self); } @@ -1680,7 +1782,6 @@ PyGmicImage_to_numpy_helper(PyGmicImage *self, PyObject *args, char *arg_permute = NULL; char arg_permute_default[] = "xyzc"; size_t permute_axis = 0; // iterator - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|Opsp", (char **)keywords, &arg_astype, &arg_interleave, &arg_permute, &arg_squeeze_shape)) { @@ -1755,6 +1856,7 @@ PyGmicImage_to_numpy_helper(PyGmicImage *self, PyObject *args, ndarray_bytes_buffer_ptr = numpy_buffer; // If interleaving is needed, copy the gmic_image buffer towards // numpy by interleaving RRR,GGG,BBB into RGB,RGB,RGB + if (arg_interleave) { for (unsigned int x = 0; x < self->_gmic_image->_width; x++) { for (unsigned int y = 0; y < self->_gmic_image->_height; y++) { @@ -1766,7 +1868,7 @@ PyGmicImage_to_numpy_helper(PyGmicImage *self, PyObject *args, } } } - } + } } else { // If deinterleaving is not needed, since this is G'MIC's @@ -2133,6 +2235,9 @@ PyInit_gmic() PyModule_AddObject(m, "__build__", gmicpy_build_info); // For more debugging, the user can look at __spec__ automatically // set by setup.py + Py_XDECREF(&PyGmicImageType); + Py_XDECREF(&PyGmicType); + Py_XDECREF(GmicException); return m; } diff --git a/setup.py b/setup.py index 6e38e240..aafa6daa 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ # Macros to toggle (gmic or CImg will do C/C++ #ifdef checks on them, testing mostly only their existence) # cimg_date and cimg_date are true variables, the value of which is checked in the C/C++ source define_macros = [ - ("gmic_build", None), + ("gmic_core", None), ("cimg_date", '""'), ("cimg_time", '""'), ("gmic_is_parallel", None), @@ -131,6 +131,9 @@ # Technique highlighted here: https://packaging.python.org/guides/single-sourcing-package-version/ with open(path.join(".", "VERSION")) as version_file: version = version_file.read().strip() + if "pre" in version: + version_prerelease = version.split("pre",1)[1] + define_macros += [("gmic_prerelease","\""+version_prerelease+"\"")] setup( name="gmic",