From 98fc085a9d96bb43f60160a147e1156cf30a26b3 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Wed, 22 Apr 2020 19:33:20 -0400 Subject: [PATCH] BUG: Specify itk package in SWIG Python modules This consistently sets the __package__ for `itk` package modules and functions. This addresses Windows import failures. Swig's c-module import statements changed with Swig 4.0. For the new syntax, __package__ must be set and consistent on the imported modules. To simplify things, Linux and macOS are also updated to Swig 4.0. itk.FloatingPointExceptions, itk.AdaptiveHistogramEqualizationImageFilter is disabled because it on macOs, Linux with Swig 4.0.1. The library outputs in the build tree are changed to be consistent with the package layout. We use the standard API for ImageDuplicator invocation in the NumPy bridge. For the embedded submodules on the C side, we update the module name to include `itk.` We also use a single PyImport_GetModuleDict statement for simplicity and performance. For imports of submodules in Python, we now use the full path. The "swig" attribute is now a dict used as a namespace instead of a types.ModuleType. Overriding the `.New()` is moved from pyBase.i to igenerator.py. It is only added to classes that define a .New() and have .AddObserver(), i.e. not abstract base classes. itk.PyCommand is excluded to prevent .AddObserver recursion that blows the stack. The lazy module creation uses Python 3.5 tooling to create a module with __package__ set and load it lazily. i.e. importlib.util.spec_from_file_location importlib.util.module_from_spec module.__loader__.exec_module Loaded modules now have the package path in their name. --- CMakeLists.txt | 6 +- Modules/Bridge/NumPy/wrapping/PyBuffer.i.in | 4 +- Modules/Bridge/NumPy/wrapping/PyBuffer.i.init | 2 - Modules/Bridge/VtkGlue/wrapping/VtkGlue.i | 2 +- .../wrapping/itkFloatingPointExceptions.wrap | 4 +- ...ptiveHistogramEqualizationImageFilter.wrap | 64 ++++++------- Wrapping/Generators/Python/CMakeLists.txt | 16 ++-- Wrapping/Generators/Python/PyBase/pyBase.i | 39 +------- .../Generators/Python/Tests/PythonTypeTest.py | 6 +- .../Generators/Python/Tests/getNameOfClass.py | 7 +- Wrapping/Generators/Python/itkBase.py | 94 ++++++++++--------- Wrapping/Generators/Python/itkTemplate.py | 6 +- .../Generators/Python/main_module_ext.i.in | 1 + .../Generators/SwigInterface/CMakeLists.txt | 6 +- .../Generators/SwigInterface/igenerator.py | 79 +++++++++++++++- Wrapping/Generators/SwigInterface/module.i.in | 2 +- 16 files changed, 197 insertions(+), 141 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2033487338e..dc6f66119f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,7 +257,11 @@ if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${ITK_BINARY_DIR}/bin) endif() if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${ITK_BINARY_DIR}/lib) + if(ITK_WRAP_PYTHON) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${ITK_BINARY_DIR}/Wrapping/Generators/Python/itk) + else() + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${ITK_BINARY_DIR}/lib) + endif() endif() if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY) set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${ITK_BINARY_DIR}/lib) diff --git a/Modules/Bridge/NumPy/wrapping/PyBuffer.i.in b/Modules/Bridge/NumPy/wrapping/PyBuffer.i.in index 33e34c2c325..372e377aa8a 100644 --- a/Modules/Bridge/NumPy/wrapping/PyBuffer.i.in +++ b/Modules/Bridge/NumPy/wrapping/PyBuffer.i.in @@ -125,8 +125,8 @@ imageView = itkPyBuffer@PyBufferTypes@.GetImageViewFromArray(ndarr, is_vector) # Duplicate the image to let it manage its own memory buffer - duplicator = itkImageDuplicator@PyBufferTypes@.New() - duplicator.SetInputImage(imageView) + import itk + duplicator = itk.ImageDuplicator.New(imageView) duplicator.Update() return duplicator.GetOutput() diff --git a/Modules/Bridge/NumPy/wrapping/PyBuffer.i.init b/Modules/Bridge/NumPy/wrapping/PyBuffer.i.init index 892267faa54..4f8b2250cd4 100644 --- a/Modules/Bridge/NumPy/wrapping/PyBuffer.i.init +++ b/Modules/Bridge/NumPy/wrapping/PyBuffer.i.init @@ -43,6 +43,4 @@ def _get_numpy_pixelid(itk_Image_type): return _np_itk[itk_Image_type] except KeyError as e: raise e - -from itkImageDuplicatorPython import * %} diff --git a/Modules/Bridge/VtkGlue/wrapping/VtkGlue.i b/Modules/Bridge/VtkGlue/wrapping/VtkGlue.i index 2338e46de75..3e820fecffe 100644 --- a/Modules/Bridge/VtkGlue/wrapping/VtkGlue.i +++ b/Modules/Bridge/VtkGlue/wrapping/VtkGlue.i @@ -48,7 +48,7 @@ #endif #ifdef SWIGPYTHON -%module VtkGluePython +%module(package=\"itk\",threads=\"1\") VtkGluePython %{ #include "vtkPythonUtil.h" diff --git a/Modules/Core/Common/wrapping/itkFloatingPointExceptions.wrap b/Modules/Core/Common/wrapping/itkFloatingPointExceptions.wrap index 119899188b6..17858cdf175 100644 --- a/Modules/Core/Common/wrapping/itkFloatingPointExceptions.wrap +++ b/Modules/Core/Common/wrapping/itkFloatingPointExceptions.wrap @@ -1,4 +1,4 @@ set(WRAPPER_AUTO_INCLUDE_HEADERS OFF) itk_wrap_include("itkFloatingPointExceptions.h") -itk_wrap_simple_class("itk::FloatingPointExceptions") -itk_wrap_simple_class("itk::FloatingPointExceptionsEnums") +#itk_wrap_simple_class("itk::FloatingPointExceptions") +#itk_wrap_simple_class("itk::FloatingPointExceptionsEnums") diff --git a/Modules/Filtering/ImageStatistics/wrapping/itkAdaptiveHistogramEqualizationImageFilter.wrap b/Modules/Filtering/ImageStatistics/wrapping/itkAdaptiveHistogramEqualizationImageFilter.wrap index 02a935890a4..e67f16f7b3f 100644 --- a/Modules/Filtering/ImageStatistics/wrapping/itkAdaptiveHistogramEqualizationImageFilter.wrap +++ b/Modules/Filtering/ImageStatistics/wrapping/itkAdaptiveHistogramEqualizationImageFilter.wrap @@ -1,36 +1,36 @@ -itk_wrap_class("itk::KernelImageFilter" POINTER) - foreach(t ${WRAP_ITK_SCALAR}) - foreach(d ${ITK_WRAP_IMAGE_DIMS}) - itk_wrap_template("${ITKM_I${t}${d}}${ITKM_I${t}${d}}Neighborhood" "${ITKT_I${t}${d}}, ${ITKT_I${t}${d}}, itk::Neighborhood< bool, ${d} >") - endforeach() - endforeach() -itk_end_wrap_class() +#itk_wrap_class("itk::KernelImageFilter" POINTER) + #foreach(t ${WRAP_ITK_SCALAR}) + #foreach(d ${ITK_WRAP_IMAGE_DIMS}) + #itk_wrap_template("${ITKM_I${t}${d}}${ITKM_I${t}${d}}Neighborhood" "${ITKT_I${t}${d}}, ${ITKT_I${t}${d}}, itk::Neighborhood< bool, ${d} >") + #endforeach() + #endforeach() +#itk_end_wrap_class() -itk_wrap_class("itk::MovingHistogramImageFilterBase" POINTER) - foreach(t ${WRAP_ITK_SCALAR}) - foreach(d ${ITK_WRAP_IMAGE_DIMS}) - itk_wrap_template("${ITKM_I${t}${d}}${ITKM_I${t}${d}}Neighborhood" "${ITKT_I${t}${d}}, ${ITKT_I${t}${d}}, itk::Neighborhood< bool, ${d} >") - endforeach() - endforeach() -itk_end_wrap_class() +#itk_wrap_class("itk::MovingHistogramImageFilterBase" POINTER) + #foreach(t ${WRAP_ITK_SCALAR}) + #foreach(d ${ITK_WRAP_IMAGE_DIMS}) + #itk_wrap_template("${ITKM_I${t}${d}}${ITKM_I${t}${d}}Neighborhood" "${ITKT_I${t}${d}}, ${ITKT_I${t}${d}}, itk::Neighborhood< bool, ${d} >") + #endforeach() + #endforeach() +#itk_end_wrap_class() -set(WRAPPER_AUTO_INCLUDE_HEADERS OFF) -itk_wrap_include("itkAdaptiveEqualizationHistogram.h") -itk_wrap_class("itk::Function::AdaptiveEqualizationHistogram") - foreach(t ${WRAP_ITK_SCALAR}) - itk_wrap_template("${ITKM_${t}}${ITKM_${t}}" "${ITKT_${t}}, ${ITKT_${t}}") - endforeach() -itk_end_wrap_class() -set(WRAPPER_AUTO_INCLUDE_HEADERS ON) +#set(WRAPPER_AUTO_INCLUDE_HEADERS OFF) +#itk_wrap_include("itkAdaptiveEqualizationHistogram.h") +#itk_wrap_class("itk::Function::AdaptiveEqualizationHistogram") + #foreach(t ${WRAP_ITK_SCALAR}) + #itk_wrap_template("${ITKM_${t}}${ITKM_${t}}" "${ITKT_${t}}, ${ITKT_${t}}") + #endforeach() +#itk_end_wrap_class() +#set(WRAPPER_AUTO_INCLUDE_HEADERS ON) -itk_wrap_class("itk::MovingHistogramImageFilter" POINTER) - foreach(t ${WRAP_ITK_SCALAR}) - foreach(d ${ITK_WRAP_IMAGE_DIMS}) - itk_wrap_template("${ITKM_I${t}${d}}${ITKM_I${t}${d}}NeighborhoodAHE" "${ITKT_I${t}${d}}, ${ITKT_I${t}${d}}, itk::Neighborhood< bool, ${d} >, itk::Function::AdaptiveEqualizationHistogram< ${ITKT_${t}}, ${ITKT_${t}} >") - endforeach() - endforeach() -itk_end_wrap_class() +#itk_wrap_class("itk::MovingHistogramImageFilter" POINTER) + #foreach(t ${WRAP_ITK_SCALAR}) + #foreach(d ${ITK_WRAP_IMAGE_DIMS}) + #itk_wrap_template("${ITKM_I${t}${d}}${ITKM_I${t}${d}}NeighborhoodAHE" "${ITKT_I${t}${d}}, ${ITKT_I${t}${d}}, itk::Neighborhood< bool, ${d} >, itk::Function::AdaptiveEqualizationHistogram< ${ITKT_${t}}, ${ITKT_${t}} >") + #endforeach() + #endforeach() +#itk_end_wrap_class() -itk_wrap_class("itk::AdaptiveHistogramEqualizationImageFilter" POINTER) - itk_wrap_image_filter("${WRAP_ITK_SCALAR}" 1) -itk_end_wrap_class() +#itk_wrap_class("itk::AdaptiveHistogramEqualizationImageFilter" POINTER) + #itk_wrap_image_filter("${WRAP_ITK_SCALAR}" 1) +#itk_end_wrap_class() diff --git a/Wrapping/Generators/Python/CMakeLists.txt b/Wrapping/Generators/Python/CMakeLists.txt index cf627eb5ebb..905675b84c6 100644 --- a/Wrapping/Generators/Python/CMakeLists.txt +++ b/Wrapping/Generators/Python/CMakeLists.txt @@ -230,7 +230,8 @@ macro(itk_wrap_module_python library_name) set(ITK_WRAP_PYTHON_LIBRARY_IMPORTS "") set(ITK_WRAP_PYTHON_LIBRARY_DEPS ) set(ITK_WRAP_PYTHON_LIBRARY_DECLS ) - set(ITK_WRAP_PYTHON_LIBRARY_CALLS ) + set(ITK_WRAP_PYTHON_LIBRARY_CALLS " + PyObject * sysModules = PyImport_GetModuleDict();\n") set(ITK_WRAP_PYTHON_CXX_FILES ) if(MSVC) get_filename_component(python_library_directory "${Python3_LIBRARY}" DIRECTORY) @@ -343,13 +344,13 @@ macro(itk_end_wrap_module_python) foreach(dep ${WRAPPER_LIBRARY_DEPENDS}) set(ITK_WRAP_PYTHON_CONFIGURATION_DEPENDS "'${dep}', ${ITK_WRAP_PYTHON_CONFIGURATION_DEPENDS}") - set(ITK_WRAP_PYTHON_LIBRARY_IMPORTS "import ${dep}Python\n${ITK_WRAP_PYTHON_LIBRARY_IMPORTS}") + set(ITK_WRAP_PYTHON_LIBRARY_IMPORTS "import itk.${dep}Python\n${ITK_WRAP_PYTHON_LIBRARY_IMPORTS}") endforeach() # ITKPyBase is always included, excepted ITKPyBase itself if(NOT "${WRAPPER_LIBRARY_NAME}" STREQUAL "ITKPyBase") set(ITK_WRAP_PYTHON_CONFIGURATION_DEPENDS "'ITKPyBase', ${ITK_WRAP_PYTHON_CONFIGURATION_DEPENDS}") - set(ITK_WRAP_PYTHON_LIBRARY_IMPORTS "import ITKPyBasePython\n${ITK_WRAP_PYTHON_LIBRARY_IMPORTS}") + set(ITK_WRAP_PYTHON_LIBRARY_IMPORTS "import itk.ITKPyBasePython\n${ITK_WRAP_PYTHON_LIBRARY_IMPORTS}") endif() # and create the file, with the var ITK_WRAP_PYTHON_CONFIGURATION_TEMPLATES and @@ -580,13 +581,12 @@ macro(itk_end_wrap_submodule_python group_name) # first the extern c declaration and then the call of the extern function set(ITK_WRAP_PYTHON_LIBRARY_DECLS "${ITK_WRAP_PYTHON_LIBRARY_DECLS} extern \"C\" PyMODINIT_FUNC PyInit__${group_name}Python();\n") set(ITK_WRAP_PYTHON_LIBRARY_CALLS "${ITK_WRAP_PYTHON_LIBRARY_CALLS} - PyObject * ${group_name}SysModules = PyImport_GetModuleDict(); - PyObject * ${group_name}AlreadyImported = PyDict_GetItemString(${group_name}SysModules, \"_${group_name}Python\"); + PyObject * ${group_name}AlreadyImported = PyDict_GetItemString(sysModules, \"itk._${group_name}Python\"); if( ${group_name}AlreadyImported == NULL ) { - PyImport_AddModule( \"_${group_name}Python\" ); + PyImport_AddModule( \"itk._${group_name}Python\" ); PyObject * ${group_name}Module = PyInit__${group_name}Python(); - PyDict_SetItemString( ${group_name}SysModules, \"_${group_name}Python\", ${group_name}Module ); + PyDict_SetItemString( sysModules, \"itk._${group_name}Python\", ${group_name}Module ); Py_DECREF( ${group_name}Module ); } ") @@ -627,7 +627,7 @@ macro(itk_wrap_submodule_python module) set(ITK_WRAP_PYTHON_SWIG_EXT "\n") # register the module for the lib module - set(ITK_WRAP_PYTHON_LIBRARY_IMPORTS "${ITK_WRAP_PYTHON_LIBRARY_IMPORTS}from ${module}Python import *\n") + set(ITK_WRAP_PYTHON_LIBRARY_IMPORTS "${ITK_WRAP_PYTHON_LIBRARY_IMPORTS}from itk.${module}Python import *\n") endmacro() diff --git a/Wrapping/Generators/Python/PyBase/pyBase.i b/Wrapping/Generators/Python/PyBase/pyBase.i index f87bb677285..05e0f51af7a 100644 --- a/Wrapping/Generators/Python/PyBase/pyBase.i +++ b/Wrapping/Generators/Python/PyBase/pyBase.i @@ -1,4 +1,4 @@ -%module pyBasePython +%module(package="itk") pyBasePython %include %include @@ -115,45 +115,8 @@ str = str }; } - // some changes in the New() method - %rename(__New_orig__) class_name::New; - %extend class_name { - %pythoncode %{ - def New(*args, **kargs): - """New() -> class_name - - Create a new object of the class class_name and set the input and the parameters if some - named or non-named arguments are passed to that method. - - New() tries to assign all the non named parameters to the input of the new objects - the - first non named parameter in the first input, etc. - - The named parameters are used by calling the method with the same name prefixed by 'Set'. - - Ex: - - class_name.New( reader, Threshold=10 ) - - is (most of the time) equivalent to: - - obj = class_name.New() - obj.SetInput( 0, reader.GetOutput() ) - obj.SetThreshold( 10 ) - """ - obj = class_name.__New_orig__() - import itkTemplate - itkTemplate.New(obj, *args, **kargs) - return obj - New = staticmethod(New) - %} - } - %pythoncode %{ - def class_name##_New(): - return class_name.New() - %} %enddef - %extend itkMetaDataDictionary { %ignore Find; std::string __str__() { diff --git a/Wrapping/Generators/Python/Tests/PythonTypeTest.py b/Wrapping/Generators/Python/Tests/PythonTypeTest.py index 33aca8f31db..f40d1e14e18 100644 --- a/Wrapping/Generators/Python/Tests/PythonTypeTest.py +++ b/Wrapping/Generators/Python/Tests/PythonTypeTest.py @@ -65,9 +65,9 @@ def create_and_test(t, create_method): msg = "%s: wrong Python class name: %s" % (actual_type, obj_type) wrongType = 1 except Exception as e: - msg = ("%s: wrong Python class name: %s. " + msg = ("%s, %s: wrong Python class name: %s. " "Exception while evaluating it: %s" % - (t, obj_type, e.message)) + (t, actual_type, obj_type, e)) wrongType = 1 if wrongType: print(msg, file=sys.stderr) @@ -82,7 +82,7 @@ def create_method(i): return None for t in dir(itk): - if t not in exclude: + if t not in exclude and not t.startswith('stdnumeric_limits'): T = itk.__dict__[t] # first case - that's a templated class if isinstance(T, itk.Vector.__class__) and len(T) > 0: diff --git a/Wrapping/Generators/Python/Tests/getNameOfClass.py b/Wrapping/Generators/Python/Tests/getNameOfClass.py index db99af98b6c..1c99fef74e2 100644 --- a/Wrapping/Generators/Python/Tests/getNameOfClass.py +++ b/Wrapping/Generators/Python/Tests/getNameOfClass.py @@ -66,15 +66,18 @@ def wrongClassName(cl, name): if 'New' in dir(i) and 'GetNameOfClass' in dir(i): totalName += 1 if wrongClassName(i, t): - msg = "%s: wrong class name: %s" % (t, n) + msg = "%s: wrong class name: %s" % (T, t) print(msg, file=sys.stderr) wrongName += 1 else: if 'New' in dir(T) and 'GetNameOfClass' in dir(T): totalName += 1 if wrongClassName(T, t): - msg = "%s: wrong class name: %s" % (t, n) + msg = "%s: wrong class name: %s" % (T, t) print(msg, file=sys.stderr) + o = T.New() + print(itk.class_(o), file=sys.stderr) + print(o.GetNameOfClass(), file=sys.stderr) wrongName += 1 print("%s classes checked." % totalName) diff --git a/Wrapping/Generators/Python/itkBase.py b/Wrapping/Generators/Python/itkBase.py index 2f55707894e..9ace7dce29b 100644 --- a/Wrapping/Generators/Python/itkBase.py +++ b/Wrapping/Generators/Python/itkBase.py @@ -22,10 +22,15 @@ # Required to work around weird import error with xarray import pkg_resources import importlib -import types import itkConfig import itkTemplate +def _create_itk_module(name): + swig_module_name = 'itk.' + name + 'Python' + spec = importlib.util.spec_from_file_location(swig_module_name, + os.path.join(os.path.dirname(__file__), 'itk', name + 'Python.py')) + module = importlib.util.module_from_spec(spec) + return module def LoadModule(name, namespace=None): """This function causes a SWIG module to be loaded into memory after its @@ -34,30 +39,33 @@ def LoadModule(name, namespace=None): created. These template instances are placed in a module with the given name that is either looked up from sys.modules or created and placed there if it does not already exist. + Optionally, a 'namespace' parameter can be provided. If it is provided, this namespace will be updated with the new template instantiations. + The raw classes loaded from the named module's SWIG interface are placed in a 'swig' sub-module. If the namespace parameter is provided, this information will be placed in a sub-module named 'swig' therein as well. This later submodule will be created if it does not already exist.""" + swig_module_name = 'itk.' + name + 'Python' # find the module's name in sys.modules, or create a new module so named - this_module = sys.modules.setdefault(name, types.ModuleType(name)) + this_module = sys.modules.setdefault(swig_module_name, _create_itk_module(name)) # if this library and it's template instantiations have already been loaded # into sys.modules, bail out after loading the defined symbols into # 'namespace' if hasattr(this_module, '__templates_loaded'): if namespace is not None: - swig = namespace.setdefault('swig', types.ModuleType('swig')) - swig.__dict__.update(this_module.swig.__dict__) + swig = namespace.setdefault('swig', {}) + swig.update(this_module.swig) # don't worry about overwriting the symbols in namespace -- any # common symbols should be of type itkTemplate, which is a # singleton type. That is, they are all identical, so replacing one # with the other isn't a problem. for k, v in this_module.__dict__.items(): - if not (k.startswith('_') or k == 'swig'): + if not (k.startswith('_') or k.startswith('itk') or k == 'swig'): namespace[k] = v return @@ -87,87 +95,87 @@ def LoadModule(name, namespace=None): # SWIG-generated modules have 'Python' appended. Only load the SWIG module # if we haven't already. - swigModuleName = name + "Python" loader = LibraryLoader() - if not swigModuleName in sys.modules: - module = loader.load(swigModuleName) + module = loader.load(swig_module_name) # OK, now the modules on which this one depends are loaded and # template-instantiated, and the SWIG module for this one is also loaded. # We're going to put the things we load and create in two places: the # optional 'namespace' parameter, and the this_module variable's namespace. - # make a new 'swig' sub-module for this_module. Also look up or create a - # different 'swig' module for 'namespace'. Since 'namespace' may be used to + # Populate the 'swig' sub-module namespace for this_module. Also look up or create a + # different 'swig' namespace for 'namespace'. Since 'namespace' may be used to # collect symbols from multiple different ITK modules, we don't want to - # stomp on an existing 'swig' module, nor do we want to share 'swig' - # modules between this_module and namespace. - - this_module.swig = types.ModuleType('swig') + # stomp on an existing 'swig' namespace, nor do we want to share 'swig' + # namespaces between this_module and namespace. if namespace is not None: - swig = namespace.setdefault('swig', types.ModuleType('swig')) - - for k, v in module.__dict__.items(): - if not k.startswith('__'): - setattr(this_module.swig, k, v) - if namespace is not None: - setattr(swig, k, v) + swig = namespace.setdefault('swig', {}) + + if namespace is None: + for k, v in module.__dict__.items(): + if not (k.startswith('__') or k.startswith('itk')): + this_module.swig[k] = v + else: + for k, v in module.__dict__.items(): + if not (k.startswith('__') or k.startswith('itk')): + this_module.swig[k] = v + swig[k] = v data = module_data[name] if data: for template in data['templates']: if len(template) == 5: # This is a template description - pyClassName, cppClassName, swigClassName, class_in_module, \ - templateParams = template + py_class_name, cpp_class_name, swig_class_name, class_in_module, \ + template_params = template # It doesn't matter if an itkTemplate for this class name # already exists since every instance of itkTemplate with the # same name shares the same state. So we just make a new # instance and add the new templates. - templateContainer = itkTemplate.itkTemplate(cppClassName) + template_container = itkTemplate.itkTemplate(cpp_class_name) try: - templateContainer.__add__( - templateParams, getattr(module, swigClassName)) - setattr(this_module, pyClassName, templateContainer) + template_container.__add__( + template_params, getattr(module, swig_class_name)) + setattr(this_module, py_class_name, template_container) if namespace is not None: - curval = namespace.get(pyClassName) - if curval is not None and curval != templateContainer: + curval = namespace.get(py_class_name) + if curval is not None and curval != template_container: DebugPrintError("Namespace already has a value for" " %s, which is not an itkTemplate" "instance for class %s. " "Overwriting old value." - % (pyClassName, cppClassName)) - namespace[pyClassName] = templateContainer + % (py_class_name, cpp_class_name)) + namespace[py_class_name] = template_container except Exception as e: DebugPrintError("%s not loaded from module %s because of " "exception:\n %s" - % (swigClassName, name, e)) + % (swig_class_name, name, e)) else: # this is a description of a non-templated class # It may have 3 or 4 arguments, the last one can be a boolean value if len(template) == 4: - pyClassName, cppClassName, swigClassName, class_in_module = \ + py_class_name, cpp_class_name, swig_class_name, class_in_module = \ template else: - pyClassName, cppClassName, swigClassName = template + py_class_name, cpp_class_name, swig_class_name = template try: - swigClass = getattr(module, swigClassName) - itkTemplate.registerNoTpl(cppClassName, swigClass) - setattr(this_module, pyClassName, swigClass) + swigClass = getattr(module, swig_class_name) + itkTemplate.registerNoTpl(cpp_class_name, swigClass) + setattr(this_module, py_class_name, swigClass) if namespace is not None: - curval = namespace.get(pyClassName) + curval = namespace.get(py_class_name) if curval is not None and curval != swigClass: DebugPrintError("Namespace already has a value for" " %s, which is not class %s. " "Overwriting old value." - % (pyClassName, cppClassName)) - namespace[pyClassName] = swigClass + % (py_class_name, cpp_class_name)) + namespace[py_class_name] = swigClass except Exception as e: DebugPrintError("%s not found in module %s because of " "exception:\n %s" - % (swigClassName, name, e)) + % (swig_class_name, name, e)) if 'snake_case_functions' in data: for snakeCaseFunction in data['snake_case_functions']: namespace[snakeCaseFunction] = getattr(module, snakeCaseFunction) @@ -208,7 +216,9 @@ def setup(self): def load(self, name): self.setup() try: - return importlib.import_module(name) + module = importlib.import_module(name) + module.__loader__.exec_module(module) + return module finally: self.cleanup() diff --git a/Wrapping/Generators/Python/itkTemplate.py b/Wrapping/Generators/Python/itkTemplate.py index 2196c7be812..9c7b62a8ce7 100644 --- a/Wrapping/Generators/Python/itkTemplate.py +++ b/Wrapping/Generators/Python/itkTemplate.py @@ -325,7 +325,7 @@ def __getitem__(self, parameters): # In the case of itk class instance, get the class name = param.__class__.__name__ isclass = inspect.isclass(param) - if not isclass and name[:3] == 'itk' and name != "itkCType": + if not isclass and name[:3] == 'itk' and name != 'itkCType': param = param.__class__ # append the parameter to the list. If it's not a supported type, @@ -360,7 +360,9 @@ def _LoadModules(self): modules = itkBase.lazy_attributes[name] for module in modules: # find the module's name in sys.modules, or create a new module so named - this_module = sys.modules.setdefault(module, types.ModuleType(module)) + swig_module_name = 'itk.' + module + 'Python' + this_module = sys.modules.setdefault(swig_module_name, + itkBase._create_itk_module(module)) namespace = {} if not hasattr(this_module, '__templates_loaded'): itkBase.LoadModule(module, namespace) diff --git a/Wrapping/Generators/Python/main_module_ext.i.in b/Wrapping/Generators/Python/main_module_ext.i.in index 7f474e6b5b5..61579cc0071 100644 --- a/Wrapping/Generators/Python/main_module_ext.i.in +++ b/Wrapping/Generators/Python/main_module_ext.i.in @@ -5,6 +5,7 @@ %} %pythoncode %{ +swig = {} @ITK_WRAP_PYTHON_LIBRARY_IMPORTS@ %} diff --git a/Wrapping/Generators/SwigInterface/CMakeLists.txt b/Wrapping/Generators/SwigInterface/CMakeLists.txt index 8f0a3ce2a62..87456fdf902 100644 --- a/Wrapping/Generators/SwigInterface/CMakeLists.txt +++ b/Wrapping/Generators/SwigInterface/CMakeLists.txt @@ -18,9 +18,9 @@ if(WIN32) set(ITK_SWIG_VERSION 4.0.1) set(swigwin_hash "2da147231164466c0dee91beca6a4f7b10e6f727062338ea6d70f44f4e4163ad6616b72c2de767539a69977ba96d3ee920ed3c182124c45569ca946750fd4495") else() - set(ITK_SWIG_VERSION 3.0.12) - set(swig_version_min 3.0.0) - set(swig_hash "85605bd98bf2b56f5bfca23ae23d76d764d76a174b05836c8686825e912d6326c370e9cf2134c0bf4f425560be103b16bf9c9d075077f52e713a69082616e906") + set(ITK_SWIG_VERSION 4.0.1) + set(swig_version_min 4.0.1) + set(swig_hash "595ef01cb83adfa960ceed9c325a9429192549e8d1e9aa3ab35a4301512a61d82e2e89a8c7939c2a5a0811254ea1832a443bd387e11459eb2b0bafc563ad1308") endif() if(WIN32) diff --git a/Wrapping/Generators/SwigInterface/igenerator.py b/Wrapping/Generators/SwigInterface/igenerator.py index cbe1344a24b..58544c143c1 100755 --- a/Wrapping/Generators/SwigInterface/igenerator.py +++ b/Wrapping/Generators/SwigInterface/igenerator.py @@ -139,6 +139,72 @@ class SwigInputGenerator(object): }; """} + new_override = ''' +// some changes in the New() method +%rename(__New_orig__) {class_name}::New; +%extend {class_name} {{ +%pythoncode %{{ + def New(*args, **kargs): + """New() -> {class_name} + + Create a new object of the class {class_name} and set the input and the parameters if some + named or non-named arguments are passed to that method. + + New() tries to assign all the non named parameters to the input of the new objects - the + first non named parameter in the first input, etc. + + The named parameters are used by calling the method with the same name prefixed by 'Set'. + + Ex: + + {class_name}.New(reader, threshold=10) + + is (most of the time) equivalent to: + + obj = {class_name}.New() + obj.SetInput(0, reader.GetOutput()) + obj.SetThreshold(10) + """ + obj = {class_name}.__New_orig__() + import itkTemplate + itkTemplate.New(obj, *args, **kargs) + return obj + New = staticmethod(New) + %}} +}} +%pythoncode %{{ + def {class_name}_New(): + return {class_name}.New() +%}} + + +''' + + new_override_pycommand = ''' +// some changes in the New() method +%rename(__New_orig__) {class_name}::New; +%extend {class_name} {{ +%pythoncode %{{ + def New(*args, **kargs): + """New() -> {class_name} + """ + obj = {class_name}.__New_orig__() + import itk + itk.set_inputs(obj, *args, **kargs) + return obj + New = staticmethod(New) +%}} +}} +%pythoncode %{{ + def {class_name}_New(): + return {class_name}.New() +%}} + + +''' + + + def __init__(self, moduleName, options): self.moduleName = moduleName self.options = options @@ -400,6 +466,15 @@ def generate_class(self, typedef, indent=0): decls = pygccxml.declarations if not typedef.name.startswith("stdcomplex"): + for member in getType(typedef).get_members(access=pygccxml.declarations.ACCESS_TYPES.PUBLIC): + if isinstance(member, decls.member_function_t) and member.name == 'New' and not typedef.name == 'itkLightObject': + if typedef.name == 'itkPyCommand': + self.outputFile.write(self.new_override_pycommand.format(class_name=typedef.name)) + else: + self.outputFile.write(self.new_override.format(class_name=typedef.name)) + self.outputFile.write("\n") + break + super_classes = [] for super_class in getType(typedef).bases: super_classes.append( @@ -475,7 +550,7 @@ def generate_class(self, typedef, indent=0): # finally, close the class self.outputFile.write(" " * indent) - self.outputFile.write("};\n\n\n") + self.outputFile.write("};\n\n") elif typedef.name == "stdcomplexD": self.outputFile.write(self.stdcomplex_headers["D"] + '\n') @@ -700,7 +775,7 @@ def generate_headerfile(self, idxFile, wrappersNamespace): headerFile.write("#ifdef SWIG%s\n" % lang) if lang == "PYTHON": # Also, release the GIL - headerFile.write("%%module(threads=\"1\") %s%s\n" % (self.moduleName, lang.title())) + headerFile.write("%%module(package=\"itk\",threads=\"1\") %s%s\n" % (self.moduleName, lang.title())) headerFile.write('%feature("nothreadallow");\n') else: headerFile.write("%%module %s%s\n" % (self.moduleName, lang.title())) diff --git a/Wrapping/Generators/SwigInterface/module.i.in b/Wrapping/Generators/SwigInterface/module.i.in index 9ec38564d88..55241d3a9f5 100644 --- a/Wrapping/Generators/SwigInterface/module.i.in +++ b/Wrapping/Generators/SwigInterface/module.i.in @@ -41,7 +41,7 @@ %module @WRAPPER_LIBRARY_NAME@Pike #endif #ifdef SWIGPYTHON -%module @WRAPPER_LIBRARY_NAME@Python +%module(package="itk") @WRAPPER_LIBRARY_NAME@Python #endif #ifdef SWIGR %module @WRAPPER_LIBRARY_NAME@R