diff --git a/Wrapping/Common/mitk_swig_common.i b/Wrapping/Common/mitk_swig_common.i index 9574fdca48..7896f9e8f6 100644 --- a/Wrapping/Common/mitk_swig_common.i +++ b/Wrapping/Common/mitk_swig_common.i @@ -1,31 +1,33 @@ // Ignore common warnings: // 302 : Redefinition of Macro, usually not a problem // 362 : Operator= is ignored. Can't help it. // 503 : Can't wrap operator of type "*" unless renamed to a valid identifier, no problem as operator not needed. // 509 : Overloaded function ignored. Usually not a problem, as overloaded functions shouldn't give different results. // 511 : Can't use keyword arguments with overloaded functions #pragma SWIG nowarn=302,362,503,509,511 // Splitted the information about the addition files into sub-files: // Include c++ code in this file. It is basically a c++-header wrapped in the commands so it is included in the std-file %include // SWIG-Macro definition goes in here, for example SWIG_ADD_MITK_CLASS %include // Includes of STD-Files goes in here %include // information about classes that are going to be wrapped are in here: %include // // How to wrap a new class: // ------------------------------------ // 1. Add the c++ include file to mitk_swig_cpp_include.i // If the class is in a new module, make sure that this module is added as dependency in cmake // 2. Add the class definition in mitk_swig_classes.i // If the definition of the class needs new macros, for example because it is not in the core // and has a new Export-Macro, be sure to define this macro first. // If the class inherit from mitk::BaseData use the SWIG_ADD_MITK_CLASS macro, as it defines // some redundante code. -// \ No newline at end of file +// + +std::vector mitk::GetImageSize(mitk::Image::Pointer image); \ No newline at end of file diff --git a/Wrapping/Common/mitk_swig_cpp_include.i b/Wrapping/Common/mitk_swig_cpp_include.i index 975a2bdc01..d833c5aaaf 100644 --- a/Wrapping/Common/mitk_swig_cpp_include.i +++ b/Wrapping/Common/mitk_swig_cpp_include.i @@ -1,15 +1,40 @@ %{ #include #include #include #include #include #include #include // SWIG Doesn't wrap namespaces. This leads to some problems, if the namespaces are not used. using namespace mitk; using namespace itk; -%} \ No newline at end of file + +std::vector GetImageSize(mitk::Image::Pointer image) +{ + std::vector< unsigned int > size; + unsigned int dimension = image->GetDimension(); + for (int i = 0; i < dimension; ++i) + { + size.push_back(image->GetDimension(i)); + } + return size; +} + +std::vector GetImageSize(mitk::Image* image) +{ + std::vector< unsigned int > size; + unsigned int dimension = image->GetDimension(); + for (int i = 0; i < dimension; ++i) + { + size.push_back(image->GetDimension(i)); + } + return size; +} +%} + +std::vector GetImageSize(mitk::Image::Pointer image); +std::vector GetImageSize(mitk::Image* image); diff --git a/Wrapping/Python/CMakeLists.txt b/Wrapping/Python/CMakeLists.txt index 96630c936c..524678ad82 100644 --- a/Wrapping/Python/CMakeLists.txt +++ b/Wrapping/Python/CMakeLists.txt @@ -1,77 +1,77 @@ # Version 2.8.1 is the minium requirement for this script. # this is lower than the general minimum requirement. #cmake_minimum_required ( VERSION 2.8.1 FATAL_ERROR ) include(mitkTargetLinkLibrariesWithDynamicLookup) project( MITK_Python ) set(CMAKE_SHARED_LINKER_FLAGS "" CACHE INTERNAL "" FORCE) set(CMAKE_MODULE_LINKER_FLAGS "" CACHE INTERNAL "" FORCE) mitk_check_dynamic_lookup(MODULE SHARED MITK_UNDEFINED_SYMBOLS_ALLOWED ) # # Find the necessary libraries etc.. # if ( MITK_UNDEFINED_SYMBOLS_ALLOWED ) set( _QUIET_LIBRARY "QUIET" ) else() set( _QUIET_LIBRARY "REQUIRED" ) endif() find_package ( PythonInterp REQUIRED ) find_package ( PythonLibs ${_QUIET_LIBRARY} ) - +include_directories ( ${CMAKE_CURRENT_SOURCE_DIR} ) # # Options # option ( MITK_PYTHON_THREADS "Enable threaded python usage by unlocking the GIL." ON ) mark_as_advanced( MITK_PYTHON_THREADS ) option ( MITK_PYTHON_EGG "Add building of python eggs to the dist target." OFF ) mark_as_advanced( MITK_PYTHON_EGG ) option ( MITK_PYTHON_WHEEL "Add building of python wheels to the dist target." ON ) mark_as_advanced( MITK_PYTHON_WHEEL ) # Prepare the SWIG-File, i.e. especially add necessary include folders mitkSwigPrepareFiles(MITK.i "MitkCore") # Add additional SWIG Parameters # These parameters depend on the target language set(CMAKE_SWIG_FLAGS ${CMAKE_SWIG_GLOBAL_FLAGS} -features autodoc=1 -keyword ) if( MITK_PYTHON_THREADS ) set(CMAKE_SWIG_FLAGS ${CMAKE_SWIG_FLAGS} -threads) endif() set(CMAKE_SWIG_OUTDIR ${CMAKE_CURRENT_BINARY_DIR}) # Create the actual SWIG project swig_add_module(PythonMITK python MITK.i ) mitkSwigAddLibraryDependencies(PythonMITK "MitkCore") mitk_target_link_libraries_with_dynamic_lookup(${SWIG_MODULE_PythonMITK_REAL_NAME} ${PYTHON_LIBRARIES}) if(DEFINED SKBUILD) message(WARNING "SKBuild exists") # Currently this installation install(FILES ${CMAKE_CURRENT_BINARY_DIR}/PythonMITK.py ${CMAKE_CURRENT_SOURCE_DIR}/Packaging/__init__.py # ${SimpleITK_DOC_FILES} DESTINATION PythonMITK COMPONENT Runtime ) install(TARGETS ${SWIG_MODULE_PythonMITK_REAL_NAME} RUNTIME DESTINATION PythonMITK LIBRARY DESTINATION PythonMITK COMPONENT Runtime ) else() message(WARNING "SKBuild missing") include(LegacyPackaging.cmake) endif() diff --git a/Wrapping/Python/MITK.i b/Wrapping/Python/MITK.i index 536ae52089..362033a9ca 100644 --- a/Wrapping/Python/MITK.i +++ b/Wrapping/Python/MITK.i @@ -1,4 +1,158 @@ %module PythonMITK %include +%{ +#include "mitkNumpyArrayConversion.cxx" +%} + +// Numpy array conversion support +%native(_GetMemoryViewFromImage) PyObject *mitk_GetMemoryViewFromImage( PyObject *self, PyObject *args ); + + +%pythoncode %{ + +HAVE_NUMPY = True +try: + import numpy +except ImportError: + HAVE_NUMPY = False + + +def _get_numpy_dtype( mitkImage ): + """Given a MITK image, returns the numpy.dtype which describes the data""" + + if not HAVE_NUMPY: + raise ImportError('Numpy not available.') + + # this is a mapping from MITK's pixel id to numpy's dtype + return numpy.float64 + _mitk_np = {sitkUInt8:numpy.uint8, + sitkUInt16:numpy.uint16, + sitkUInt32:numpy.uint32, + sitkUInt64:numpy.uint64, + sitkInt8:numpy.int8, + sitkInt16:numpy.int16, + sitkInt32:numpy.int32, + sitkInt64:numpy.int64, + sitkFloat32:numpy.float32, + sitkFloat64:numpy.float64, + sitkComplexFloat32:numpy.complex64, + sitkComplexFloat64:numpy.complex128, + sitkVectorUInt8:numpy.uint8, + sitkVectorInt8:numpy.int8, + sitkVectorUInt16:numpy.uint16, + sitkVectorInt16:numpy.int16, + sitkVectorUInt32:numpy.uint32, + sitkVectorInt32:numpy.int32, + sitkVectorUInt64:numpy.uint64, + sitkVectorInt64:numpy.int64, + sitkVectorFloat32:numpy.float32, + sitkVectorFloat64:numpy.float64, + sitkLabelUInt8:numpy.uint8, + sitkLabelUInt16:numpy.uint16, + sitkLabelUInt32:numpy.uint32, + sitkLabelUInt64:numpy.uint64 + } + + return _mitk_np[ sitkImage.GetPixelIDValue() ] + + + +def _get_mitk_pixelid(numpy_array_type): + """Returns a SimpleITK PixelID given a numpy array.""" + + if not HAVE_NUMPY: + raise ImportError('Numpy not available.') + + # This is a Mapping from numpy array types to sitks pixel types. + _np_mitk = {numpy.character:sitkUInt8, + numpy.uint8:sitkUInt8, + numpy.uint16:sitkUInt16, + numpy.uint32:sitkUInt32, + numpy.uint64:sitkUInt64, + numpy.int8:sitkInt8, + numpy.int16:sitkInt16, + numpy.int32:sitkInt32, + numpy.int64:sitkInt64, + numpy.float32:sitkFloat32, + numpy.float64:sitkFloat64, + numpy.complex64:sitkComplexFloat32, + numpy.complex128:sitkComplexFloat64 + } + + try: + return _np_mitk[numpy_array_type.dtype] + except KeyError: + for key in _np_mitk: + if numpy.issubdtype(numpy_array_type.dtype, key): + return _np_mitk[key] + raise TypeError('dtype: {0} is not supported.'.format(numpy_array_type.dtype)) + +def _get_sitk_vector_pixelid(numpy_array_type): + """Returns a SimpleITK vecotr PixelID given a numpy array.""" + + if not HAVE_NUMPY: + raise ImportError('Numpy not available.') + + # This is a Mapping from numpy array types to sitks pixel types. + _np_sitk = {numpy.character:sitkVectorUInt8, + numpy.uint8:sitkVectorUInt8, + numpy.uint16:sitkVectorUInt16, + numpy.uint32:sitkVectorUInt32, + numpy.uint64:sitkVectorUInt64, + numpy.int8:sitkVectorInt8, + numpy.int16:sitkVectorInt16, + numpy.int32:sitkVectorInt32, + numpy.int64:sitkVectorInt64, + numpy.float32:sitkVectorFloat32, + numpy.float64:sitkVectorFloat64, + } + + try: + return _np_sitk[numpy_array_type.dtype] + except KeyError: + for key in _np_sitk: + if numpy.issubdtype(numpy_array_type.dtype, key): + return _np_sitk[key] + raise TypeError('dtype: {0} is not supported.'.format(numpy_array_type.dtype)) + + +# SimplyITK <-> Numpy Array conversion support. +#http://www.nickdarnell.com/swig-casting-revisited/ +def GetArrayViewFromImage(image): + """Get a NumPy ndarray view of a SimpleITK Image. + + Returns a Numpy ndarray object as a "view" of the SimpleITK's Image buffer. This reduces pixel buffer copies, but requires that the SimpleITK image object is kept around while the buffer is being used. + + + """ + + if not HAVE_NUMPY: + raise ImportError('NumPy not available.') + + dtype = _get_numpy_dtype( image ) + + shape = GetImageSize(image); + if image.GetPixelType().GetNumberOfComponents() > 1: + shape = ( image.GetPixelType().GetNumberOfComponents(), ) + shape + + imageMemoryView = _PythonMITK._GetMemoryViewFromImage(image) + arrayView = numpy.asarray(imageMemoryView).view(dtype = dtype) + arrayView.shape = shape[::-1] + + return arrayView + +def GetArrayFromImage(image): + """Get a NumPy ndarray from a SimpleITK Image. + + This is a deep copy of the image buffer and is completely safe and without potential side effects. + """ + + # TODO: If the image is already not unique then a second copy may be made before the numpy copy is done. + arrayView = GetArrayViewFromImage(image) + + # perform deep copy of the image buffer + return numpy.array(arrayView, copy=True) + +%} \ No newline at end of file diff --git a/Wrapping/Python/mitkNumpyArrayConversion.cxx b/Wrapping/Python/mitkNumpyArrayConversion.cxx new file mode 100644 index 0000000000..1e6228689b --- /dev/null +++ b/Wrapping/Python/mitkNumpyArrayConversion.cxx @@ -0,0 +1,288 @@ +/*========================================================================= +* +* Copyright Insight Software Consortium +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0.txt +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +*=========================================================================*/ + +#include +#include + +#include +#include + +#include "mitkImage.h" + + +// Python is written in C +#ifdef __cplusplus +extern "C" +{ +#endif + + +/** An internal function that returns a memoryview object to the + * SimpleITK Image's buffer (shallow). The correct copy and writing + * policies need to be done by the end-user method. + */ +static PyObject * +mitk_GetMemoryViewFromImage( PyObject *SWIGUNUSEDPARM(self), PyObject *args ) +{ + const void * mitkBufferPtr; + Py_ssize_t len; + std::vector< unsigned int > size; + size_t pixelSize = 1; + + unsigned int dimension; + + /* Cast over to a sitk Image. */ + PyObject * pyImage; + void * voidImage; + mitk::Image * mitkImage; + int res = 0; + + PyObject * memoryView = NULL; + Py_buffer pyBuffer; + memset(&pyBuffer, 0, sizeof(Py_buffer)); + + + if( !PyArg_ParseTuple( args, "O", &pyImage ) ) + { + SWIG_fail; // SWIG_fail is a macro that says goto: fail (return NULL) + } + res = SWIG_ConvertPtr( pyImage, &voidImage, SWIGTYPE_p_mitk__Image, 0 ); + if (!SWIG_IsOK(res)) + { + mitk::Image::Pointer tmpImage; + res = SWIG_ConvertPtr(pyImage, &voidImage, SWIGTYPE_p_itk__SmartPointerT_mitk__Image_t, 0); + if (!SWIG_IsOK(res)) + { + SWIG_exception_fail(SWIG_ArgError(res), "in method 'GetByteArrayFromImage', argument needs to be of type 'sitk::Image *'"); + } + tmpImage = *(reinterpret_cast(voidImage)); + voidImage = reinterpret_cast(tmpImage.GetPointer()); + } + mitkImage = reinterpret_cast< mitk::Image * >( voidImage ); + + mitkBufferPtr = mitkImage->GetData(); + pixelSize = mitkImage->GetPixelType().GetBitsPerComponent() / 8; + + dimension = mitkImage->GetDimension(); + for (int i = 0; i < dimension; ++i) + { + size.push_back(mitkImage->GetDimension(i)); + } + + // if the image is a vector just treat is as another dimension + if ( mitkImage->GetPixelType().GetNumberOfComponents() > 1 ) + { + size.push_back( mitkImage->GetPixelType().GetNumberOfComponents() ); + } + + len = std::accumulate( size.begin(), size.end(), unsigned int(1), std::multiplies() ); + len *= pixelSize; + + if (PyBuffer_FillInfo(&pyBuffer, NULL, (void*)mitkBufferPtr, len, true, PyBUF_CONTIG_RO)!=0) + { + SWIG_fail; + } + memoryView = PyMemoryView_FromBuffer(&pyBuffer); + + PyBuffer_Release(&pyBuffer); + return memoryView; + +fail: + Py_XDECREF( memoryView ); + return NULL; +} +// +///** An internal function that performs a deep copy of the image buffer +// * into a python byte array. The byte array can later be converted +// * into a numpy array with the frombuffer method. +// */ +//static PyObject* +//sitk_SetImageFromArray( PyObject *SWIGUNUSEDPARM(self), PyObject *args ) +//{ +// PyObject * pyImage = NULL; +// +// const void *buffer; +// Py_ssize_t buffer_len; +// Py_buffer pyBuffer; +// memset(&pyBuffer, 0, sizeof(Py_buffer)); +// +// const sitk::Image * sitkImage = NULL; +// void * sitkBufferPtr = NULL; +// size_t pixelSize = 1; +// +// unsigned int dimension = 0; +// std::vector< unsigned int > size; +// size_t len = 1; +// +// // We wish to support both the new PEP3118 buffer interface and the +// // older. So we first try to parse the arguments with the new buffer +// // protocol, then the old. +// if (!PyArg_ParseTuple( args, "s*O", &pyBuffer, &pyImage ) ) +// { +// PyErr_Clear(); +// +//#ifdef PY_SSIZE_T_CLEAN +// typedef Py_ssize_t bufSizeType; +//#else +// typedef int bufSizeType; +//#endif +// +// bufSizeType _len; +// // This function takes 2 arguments from python, the first is an +// // python object which support the old "ReadBuffer" interface +// if( !PyArg_ParseTuple( args, "s#O", &buffer, &_len, &pyImage ) ) +// { +// return NULL; +// } +// buffer_len = _len; +// } +// else +// { +// if ( PyBuffer_IsContiguous( &pyBuffer, 'C' ) != 1 ) +// { +// PyBuffer_Release( &pyBuffer ); +// PyErr_SetString( PyExc_TypeError, "A C Contiguous buffer object is required." ); +// return NULL; +// } +// buffer_len = pyBuffer.len; +// buffer = pyBuffer.buf; +// } +// +// /* Cast over to a sitk Image. */ +// { +// void * voidImage; +// int res = 0; +// res = SWIG_ConvertPtr( pyImage, &voidImage, SWIGTYPE_p_itk__simple__Image, 0 ); +// if( !SWIG_IsOK( res ) ) +// { +// SWIG_exception_fail(SWIG_ArgError(res), "in method 'SetImageFromArray', argument needs to be of type 'sitk::Image *'"); +// } +// sitkImage = reinterpret_cast< sitk::Image * >( voidImage ); +// } +// +// try +// { +// switch( sitkImage->GetPixelIDValue() ) +// { +// case sitk::sitkUnknown: +// PyErr_SetString( PyExc_RuntimeError, "Unknown pixel type." ); +// goto fail; +// break; +// case sitk::ConditionalValue< sitk::sitkVectorUInt8 != sitk::sitkUnknown, sitk::sitkVectorUInt8, -14 >::Value: +// case sitk::ConditionalValue< sitk::sitkUInt8 != sitk::sitkUnknown, sitk::sitkUInt8, -2 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsUInt8(); +// pixelSize = sizeof( uint8_t ); +// break; +// case sitk::ConditionalValue< sitk::sitkVectorInt8 != sitk::sitkUnknown, sitk::sitkVectorInt8, -15 >::Value: +// case sitk::ConditionalValue< sitk::sitkInt8 != sitk::sitkUnknown, sitk::sitkInt8, -3 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsInt8(); +// pixelSize = sizeof( int8_t ); +// break; +// case sitk::ConditionalValue< sitk::sitkVectorUInt16 != sitk::sitkUnknown, sitk::sitkVectorUInt16, -16 >::Value: +// case sitk::ConditionalValue< sitk::sitkUInt16 != sitk::sitkUnknown, sitk::sitkUInt16, -4 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsUInt16(); +// pixelSize = sizeof( uint16_t ); +// break; +// case sitk::ConditionalValue< sitk::sitkVectorInt16 != sitk::sitkUnknown, sitk::sitkVectorInt16, -17 >::Value: +// case sitk::ConditionalValue< sitk::sitkInt16 != sitk::sitkUnknown, sitk::sitkInt16, -5 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsInt16(); +// pixelSize = sizeof( int16_t ); +// break; +// case sitk::ConditionalValue< sitk::sitkVectorUInt32 != sitk::sitkUnknown, sitk::sitkVectorUInt32, -18 >::Value: +// case sitk::ConditionalValue< sitk::sitkUInt32 != sitk::sitkUnknown, sitk::sitkUInt32, -6 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsUInt32(); +// pixelSize = sizeof( uint32_t ); +// break; +// case sitk::ConditionalValue< sitk::sitkVectorInt32 != sitk::sitkUnknown, sitk::sitkVectorInt32, -19 >::Value: +// case sitk::ConditionalValue< sitk::sitkInt32 != sitk::sitkUnknown, sitk::sitkInt32, -7 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsInt32(); +// pixelSize = sizeof( int32_t ); +// break; +// case sitk::ConditionalValue< sitk::sitkVectorUInt64 != sitk::sitkUnknown, sitk::sitkVectorUInt64, -20 >::Value: +// case sitk::ConditionalValue< sitk::sitkUInt64 != sitk::sitkUnknown, sitk::sitkUInt64, -8 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsUInt64(); +// pixelSize = sizeof( uint64_t ); +// break; +// case sitk::ConditionalValue< sitk::sitkVectorInt64 != sitk::sitkUnknown, sitk::sitkVectorInt64, -21 >::Value: +// case sitk::ConditionalValue< sitk::sitkInt64 != sitk::sitkUnknown, sitk::sitkInt64, -9 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsInt64(); +// pixelSize = sizeof( int64_t ); +// break; +// case sitk::ConditionalValue< sitk::sitkVectorFloat32 != sitk::sitkUnknown, sitk::sitkVectorFloat32, -22 >::Value: +// case sitk::ConditionalValue< sitk::sitkFloat32 != sitk::sitkUnknown, sitk::sitkFloat32, -10 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsFloat(); +// pixelSize = sizeof( float ); +// break; +// case sitk::ConditionalValue< sitk::sitkVectorFloat64 != sitk::sitkUnknown, sitk::sitkVectorFloat64, -23 >::Value: +// case sitk::ConditionalValue< sitk::sitkFloat64 != sitk::sitkUnknown, sitk::sitkFloat64, -11 >::Value: +// sitkBufferPtr = (void *)sitkImage->GetBufferAsDouble(); // \todo rename to Float64 for consistency +// pixelSize = sizeof( double ); +// break; +// case sitk::ConditionalValue< sitk::sitkComplexFloat32 != sitk::sitkUnknown, sitk::sitkComplexFloat32, -12 >::Value: +// case sitk::ConditionalValue< sitk::sitkComplexFloat64 != sitk::sitkUnknown, sitk::sitkComplexFloat64, -13 >::Value: +// PyErr_SetString( PyExc_RuntimeError, "Images of Complex Pixel types currently are not supported." ); +// goto fail; +// break; +// default: +// PyErr_SetString( PyExc_RuntimeError, "Unknown pixel type." ); +// goto fail; +// } +// } +// catch( const std::exception &e ) +// { +// std::string msg = "Exception thrown in SimpleITK new Image: "; +// msg += e.what(); +// PyErr_SetString( PyExc_RuntimeError, msg.c_str() ); +// goto fail; +// } +// +// +// dimension = sitkImage->GetDimension(); +// size = sitkImage->GetSize(); +// +// // if the image is a vector just treat is as another dimension +// if ( sitkImage->GetNumberOfComponentsPerPixel() > 1 ) +// { +// size.push_back( sitkImage->GetNumberOfComponentsPerPixel() ); +// } +// +// len = std::accumulate( size.begin(), size.end(), size_t(1), std::multiplies() ); +// len *= pixelSize; +// +// if ( buffer_len != len ) +// { +// PyErr_SetString( PyExc_RuntimeError, "Size mismatch of image and Buffer." ); +// goto fail; +// } +// +// memcpy( (void *)sitkBufferPtr, buffer, len ); +// +// +// PyBuffer_Release( &pyBuffer ); +// Py_RETURN_NONE; +// +//fail: +// PyBuffer_Release( &pyBuffer ); +// return NULL; +//} + + + +#ifdef __cplusplus +} // end extern "C" +#endif