Skip to content

Commit

Permalink
BUG: Specify itk package in SWIG Python modules
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
thewtex committed May 25, 2020
1 parent 1ab2baa commit 98fc085
Show file tree
Hide file tree
Showing 16 changed files with 197 additions and 141 deletions.
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions Modules/Bridge/NumPy/wrapping/PyBuffer.i.in
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@
imageView = itkPyBuffer@[email protected](ndarr, is_vector)

# Duplicate the image to let it manage its own memory buffer
duplicator = itkImageDuplicator@[email protected]()
duplicator.SetInputImage(imageView)
import itk
duplicator = itk.ImageDuplicator.New(imageView)
duplicator.Update()
return duplicator.GetOutput()

Expand Down
2 changes: 0 additions & 2 deletions Modules/Bridge/NumPy/wrapping/PyBuffer.i.init
Original file line number Diff line number Diff line change
Expand Up @@ -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 *
%}
2 changes: 1 addition & 1 deletion Modules/Bridge/VtkGlue/wrapping/VtkGlue.i
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
#endif

#ifdef SWIGPYTHON
%module VtkGluePython
%module(package=\"itk\",threads=\"1\") VtkGluePython
%{
#include "vtkPythonUtil.h"
Expand Down
4 changes: 2 additions & 2 deletions Modules/Core/Common/wrapping/itkFloatingPointExceptions.wrap
Original file line number Diff line number Diff line change
@@ -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")
Original file line number Diff line number Diff line change
@@ -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()
16 changes: 8 additions & 8 deletions Wrapping/Generators/Python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 );
}
")
Expand Down Expand Up @@ -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()

Expand Down
39 changes: 1 addition & 38 deletions Wrapping/Generators/Python/PyBase/pyBase.i
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
%module pyBasePython
%module(package="itk") pyBasePython

%include <exception.i>
%include <typemaps.i>
Expand Down Expand Up @@ -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__() {
Expand Down
6 changes: 3 additions & 3 deletions Wrapping/Generators/Python/Tests/PythonTypeTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down
7 changes: 5 additions & 2 deletions Wrapping/Generators/Python/Tests/getNameOfClass.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading

0 comments on commit 98fc085

Please sign in to comment.