From 3d8a32882b3f67d1d6e9f15982577a136da34b6a Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 13 Feb 2025 17:15:12 -0800 Subject: [PATCH 1/7] update to use new buffer functions that can handle images of different sizes/types --- src/pymmcore/pymmcore_swig.i | 38 +++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/pymmcore/pymmcore_swig.i b/src/pymmcore/pymmcore_swig.i index 9276c08d..c6b7ad10 100644 --- a/src/pymmcore/pymmcore_swig.i +++ b/src/pymmcore/pymmcore_swig.i @@ -56,32 +56,38 @@ import_array(); %typemap(out) void* { npy_intp dims[2]; - dims[0] = (arg1)->getImageHeight(); - dims[1] = (arg1)->getImageWidth(); + dims[0] = (arg1)->getImageHeight((const char*)result); + dims[1] = (arg1)->getImageWidth((const char*)result); npy_intp pixelCount = dims[0] * dims[1]; - if ((arg1)->getBytesPerPixel() == 1) + unsigned bytesPerPixel = (arg1)->getBytesPerPixel((const char*)result); + + if (bytesPerPixel == 1) { PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT8); memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount); + (arg1)->ReleaseReadAccess((const char*)result); $result = numpyArray; } - else if ((arg1)->getBytesPerPixel() == 2) + else if (bytesPerPixel == 2) { PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT16); memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount * 2); + (arg1)->ReleaseReadAccess((const char*)result); $result = numpyArray; } - else if ((arg1)->getBytesPerPixel() == 4) + else if (bytesPerPixel == 4) { PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT32); memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount * 4); + (arg1)->ReleaseReadAccess((const char*)result); $result = numpyArray; } - else if ((arg1)->getBytesPerPixel() == 8) + else if (bytesPerPixel == 8) { PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT64); memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount * 8); + (arg1)->ReleaseReadAccess((const char*)result); $result = numpyArray; } else @@ -99,16 +105,15 @@ import_array(); { //Here we assume we are getting RGBA (32 bits). npy_intp dims[3]; - dims[0] = (arg1)->getImageHeight(); - dims[1] = (arg1)->getImageWidth(); + dims[0] = (arg1)->getImageHeight((const char*)result); + dims[1] = (arg1)->getImageWidth((const char*)result); dims[2] = 3; // RGB - unsigned numChannels = (arg1)->getNumberOfComponents(); + unsigned numChannels = (arg1)->getNumberOfComponents((const char*)result); unsigned char * pyBuf; unsigned char * coreBuf = (unsigned char *) result; - if ((arg1)->getBytesPerPixel() == 4 && numChannels == 1) + if ((arg1)->getBytesPerPixel((const char*)result) == 4 && numChannels == 1) { - // create new numpy array object PyObject * numpyArray = PyArray_SimpleNew(3, dims, NPY_UINT8); @@ -116,7 +121,6 @@ import_array(); pyBuf = (unsigned char *) PyArray_DATA((PyArrayObject *) numpyArray); // copy R,G,B but leave out A in RGBA to return a WxHx3-dimensional array - long pixelCount = dims[0] * dims[1]; for (long i=0; iReleaseReadAccess((const char*)result); $result = numpyArray; - } else { - // don't know how to map - // TODO: thow exception? - $result = 0; + PyErr_SetString(PyExc_RuntimeError, "Unsupported image format"); + return NULL; } } From caa555805d4d64b890964bb156904aaf2932dd4d Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 22 Feb 2025 08:25:52 -0800 Subject: [PATCH 2/7] delete unused typemap --- src/pymmcore/pymmcore_swig.i | 49 +++++------------------------------- 1 file changed, 6 insertions(+), 43 deletions(-) diff --git a/src/pymmcore/pymmcore_swig.i b/src/pymmcore/pymmcore_swig.i index c6b7ad10..6dbaaffb 100644 --- a/src/pymmcore/pymmcore_swig.i +++ b/src/pymmcore/pymmcore_swig.i @@ -55,6 +55,7 @@ import_array(); %typemap(out) void* { + npy_intp dims[2]; dims[0] = (arg1)->getImageHeight((const char*)result); dims[1] = (arg1)->getImageWidth((const char*)result); @@ -100,49 +101,6 @@ import_array(); } } - -%typemap(out) unsigned int* -{ - //Here we assume we are getting RGBA (32 bits). - npy_intp dims[3]; - dims[0] = (arg1)->getImageHeight((const char*)result); - dims[1] = (arg1)->getImageWidth((const char*)result); - dims[2] = 3; // RGB - unsigned numChannels = (arg1)->getNumberOfComponents((const char*)result); - unsigned char * pyBuf; - unsigned char * coreBuf = (unsigned char *) result; - - if ((arg1)->getBytesPerPixel((const char*)result) == 4 && numChannels == 1) - { - // create new numpy array object - PyObject * numpyArray = PyArray_SimpleNew(3, dims, NPY_UINT8); - - // get a pointer to the data buffer - pyBuf = (unsigned char *) PyArray_DATA((PyArrayObject *) numpyArray); - - // copy R,G,B but leave out A in RGBA to return a WxHx3-dimensional array - long pixelCount = dims[0] * dims[1]; - - for (long i=0; iReleaseReadAccess((const char*)result); - - $result = numpyArray; - } - else - { - PyErr_SetString(PyExc_RuntimeError, "Unsupported image format"); - return NULL; - } -} - /* tell SWIG to treat char ** as a list of strings */ /* From https://stackoverflow.com/questions/3494598/passing-a-list-of-strings-to-from-python-ctypes-to-c-function-expecting-char */ /* XXX No need to freearg the vector, right? */ @@ -397,6 +355,11 @@ namespace std { %apply int &OUTPUT { int &xSize }; %apply int &OUTPUT { int &ySize }; +%apply int &OUTPUT { int &width }; +%apply int &OUTPUT { int &height }; +%apply int &OUTPUT { int &byteDepth }; +%apply int &OUTPUT { int &nComponents }; + %include "MMDeviceConstants.h" %include "Error.h" %include "Configuration.h" From f18c547ef9403b097de004146c01b0f8b2ef0465 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 22 Feb 2025 10:18:30 -0800 Subject: [PATCH 3/7] update SWIG layer for v2 buffer --- src/pymmcore/pymmcore_swig.i | 105 +++++++++++++++++++++++++++++++---- 1 file changed, 95 insertions(+), 10 deletions(-) diff --git a/src/pymmcore/pymmcore_swig.i b/src/pymmcore/pymmcore_swig.i index 6dbaaffb..e3d86528 100644 --- a/src/pymmcore/pymmcore_swig.i +++ b/src/pymmcore/pymmcore_swig.i @@ -55,40 +55,45 @@ import_array(); %typemap(out) void* { + // nullptr, return None + if (result == NULL) { + Py_INCREF(Py_None); + $result = Py_None; + return $result; + } - npy_intp dims[2]; - dims[0] = (arg1)->getImageHeight((const char*)result); - dims[1] = (arg1)->getImageWidth((const char*)result); - npy_intp pixelCount = dims[0] * dims[1]; - - unsigned bytesPerPixel = (arg1)->getBytesPerPixel((const char*)result); + // TODO: need to all null check here? + int width, height, bytesPerPixel, numComponents; + (arg1)->getImageProperties(result, width, height, bytesPerPixel, numComponents); + int pixelCount = width * height; + npy_intp dims[2] = {height, width}; if (bytesPerPixel == 1) { PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT8); memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount); - (arg1)->ReleaseReadAccess((const char*)result); + (arg1)->releaseReadAccess(result); $result = numpyArray; } else if (bytesPerPixel == 2) { PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT16); memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount * 2); - (arg1)->ReleaseReadAccess((const char*)result); + (arg1)->releaseReadAccess(result); $result = numpyArray; } else if (bytesPerPixel == 4) { PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT32); memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount * 4); - (arg1)->ReleaseReadAccess((const char*)result); + (arg1)->releaseReadAccess(result); $result = numpyArray; } else if (bytesPerPixel == 8) { PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT64); memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount * 8); - (arg1)->ReleaseReadAccess((const char*)result); + (arg1)->releaseReadAccess(result); $result = numpyArray; } else @@ -101,6 +106,78 @@ import_array(); } } +// This is conceptually similar to the void* typemap above, +// but requires slightly different calls because BufferDataPointer +// is different from the data-returning void* methods of the Core. +%typemap(out) BufferDataPointerVoidStar { + + npy_intp numBytes = (arg1)->getSizeBytes(); + // nullptr, return None + if (numBytes == 0) { + Py_INCREF(Py_None); + $result = Py_None; + return $result; + } + + int width, height, bytesPerPixel, numComponents; + (arg1)->getImageProperties(width, height, bytesPerPixel, numComponents); + int pixelCount = width * height; + npy_intp dims[2] = {height, width}; + + if (bytesPerPixel == 1) + { + PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT8); + memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount); + // No realease here because that is done explicitly for BufferDataPointer + $result = numpyArray; + } + else if (bytesPerPixel == 2) + { + PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT16); + memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount * 2); + // No realease here because that is done explicitly for BufferDataPointer + $result = numpyArray; + } + else if (bytesPerPixel == 4) + { + PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT32); + memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount * 4); + // No realease here because that is done explicitly for BufferDataPointer + $result = numpyArray; + } + else if (bytesPerPixel == 8) + { + PyObject * numpyArray = PyArray_SimpleNew(2, dims, NPY_UINT64); + memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount * 8); + // No realease here because that is done explicitly for BufferDataPointer + $result = numpyArray; + } + else + { + // don't know how to map + // TODO: thow exception? + // XXX Must do something, as returning NULL without setting error results + // in an opaque error. + $result = 0; + } +} + + +// Unlike void* above, this alias to void* is mapped to long so it can be used as a pointer +// address instead of having the data it points to copied +%typemap(out) DataPtr { + // Convert the DataPtr to a Python integer + $result = PyLong_FromVoidPtr((void *)$1); +} + +%typemap(in) DataPtr { + // Convert the Python integer back to a DataPtr + $1 = (DataPtr)PyLong_AsVoidPtr($input); +} + + + + /* tell SWIG to treat char ** as a list of strings */ /* From https://stackoverflow.com/questions/3494598/passing-a-list-of-strings-to-from-python-ctypes-to-c-function-expecting-char */ /* XXX No need to freearg the vector, right? */ @@ -248,6 +325,7 @@ import_array(); #include "ImageMetadata.h" #include "MMEventCallback.h" #include "MMCore.h" +#include "BufferDataPointer.h" %} // Exception handling. Tranditionally, MMCore uses exception specifications @@ -360,9 +438,16 @@ namespace std { %apply int &OUTPUT { int &byteDepth }; %apply int &OUTPUT { int &nComponents }; +// These are needed by the void* typemaps to copy pixels and then +// release them, but they shouldn't be needed by pymmcore +// because their functionality is handled by the BufferDataPointer class +%ignore CMMCore::getImageProperties(DataPtr, int&, int&, int&, int&); +%ignore CMMCore::releaseReadAccess(DataPtr); + %include "MMDeviceConstants.h" %include "Error.h" %include "Configuration.h" %include "MMCore.h" %include "ImageMetadata.h" %include "MMEventCallback.h" +%include "BufferDataPointer.h" From 513c902e36e498d24ae1aafa1684c597a26c1011 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Sat, 22 Feb 2025 10:33:28 -0800 Subject: [PATCH 4/7] remove old comment --- src/pymmcore/pymmcore_swig.i | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pymmcore/pymmcore_swig.i b/src/pymmcore/pymmcore_swig.i index e3d86528..fcaf8b6d 100644 --- a/src/pymmcore/pymmcore_swig.i +++ b/src/pymmcore/pymmcore_swig.i @@ -62,7 +62,6 @@ import_array(); return $result; } - // TODO: need to all null check here? int width, height, bytesPerPixel, numComponents; (arg1)->getImageProperties(result, width, height, bytesPerPixel, numComponents); int pixelCount = width * height; From 326c6b2026649423b63e5c7dfd4aaffa29afc507 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:19:18 -0800 Subject: [PATCH 5/7] update include file name --- src/pymmcore/pymmcore_swig.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pymmcore/pymmcore_swig.i b/src/pymmcore/pymmcore_swig.i index fcaf8b6d..14c7f41a 100644 --- a/src/pymmcore/pymmcore_swig.i +++ b/src/pymmcore/pymmcore_swig.i @@ -324,7 +324,7 @@ import_array(); #include "ImageMetadata.h" #include "MMEventCallback.h" #include "MMCore.h" -#include "BufferDataPointer.h" +#include "NewBufferDataPointer.h" %} // Exception handling. Tranditionally, MMCore uses exception specifications @@ -449,4 +449,4 @@ namespace std { %include "MMCore.h" %include "ImageMetadata.h" %include "MMEventCallback.h" -%include "BufferDataPointer.h" +%include "NewBufferDataPointer.h" From c875f6a24e4514ad085a8a67b421c3c33b23a1da Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:25:36 -0800 Subject: [PATCH 6/7] correct file name --- src/pymmcore/pymmcore_swig.i | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pymmcore/pymmcore_swig.i b/src/pymmcore/pymmcore_swig.i index 14c7f41a..35e5417c 100644 --- a/src/pymmcore/pymmcore_swig.i +++ b/src/pymmcore/pymmcore_swig.i @@ -324,7 +324,7 @@ import_array(); #include "ImageMetadata.h" #include "MMEventCallback.h" #include "MMCore.h" -#include "NewBufferDataPointer.h" +#include "NewDataBufferPointer.h" %} // Exception handling. Tranditionally, MMCore uses exception specifications @@ -449,4 +449,4 @@ namespace std { %include "MMCore.h" %include "ImageMetadata.h" %include "MMEventCallback.h" -%include "NewBufferDataPointer.h" +%include "NewDataBufferPointer.h" From 635f8447f3fd72ff878d02aaa0dc8699b92d9d82 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 11:52:20 -0800 Subject: [PATCH 7/7] allow retrieval of non image data --- src/pymmcore/pymmcore_swig.i | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/pymmcore/pymmcore_swig.i b/src/pymmcore/pymmcore_swig.i index 35e5417c..bdd3c7a5 100644 --- a/src/pymmcore/pymmcore_swig.i +++ b/src/pymmcore/pymmcore_swig.i @@ -119,7 +119,23 @@ import_array(); } int width, height, bytesPerPixel, numComponents; - (arg1)->getImageProperties(width, height, bytesPerPixel, numComponents); + bool propertiesOK = (arg1)->getImageProperties(width, height, bytesPerPixel, numComponents); + + // If getImageProperties fails, its not image data. Assume 1 byte per pixel + // If more data types are supported in the future, could add other + // checks here to return other data types. + if (!propertiesOK) { + bytesPerPixel = 1; + numComponents = 1; + int pixelCount = numBytes; + npy_intp dims[1] = {pixelCount}; + + PyObject * numpyArray = PyArray_SimpleNew(1, dims, NPY_UINT8); + memcpy(PyArray_DATA((PyArrayObject *) numpyArray), result, pixelCount); + $result = numpyArray; + return $result; + } + int pixelCount = width * height; npy_intp dims[2] = {height, width};