[Insight-users] Getting PyObject* through to wrapped C++ code

Charl P. Botha c . p . botha at ewi . tudelft . nl
Tue, 22 Jul 2003 23:19:47 +0200


--HlL+5n6rz5pIUxbD
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Dear Bill and list,

On Tue, Jul 22, 2003 at 02:33:40PM -0400, Bill Hoffman wrote:
> Please try a proof of concept.

It's worked better than we expected.  There is now an itk.swg file in
Insight/Wrapping/CSwig.  It's taking care of the PyObject* mapping, as well
as the actual "throws" typemap for the std::exception exception.  The amount
of code now left in CableSwig with regards to this is minimal: only
attaching the "throws" attribute to the method node.  This should remain, as
eventually gcc_xml will give CableSwig correct throws type information.

What I did was to move the "-l" library handling out of the NoCable clause
of cableSwigMain and adapted all the Wrapping/CSwig top-level CMakeLists.txt
file to make use of this by passing in itk.swg.  The advantage of this, is
that we can pass in more swig files than just itk.swg: e.g. per package or
per-class specific interface files!

I've attached a bunch of patches and new files.  It was a bit difficult to
separate out the PyCommand changes, so they're included.  I'm hoping they
can go in with this lot, as they also constitute a nice test-case for the
new functionality.

So, here we go:

1. CableSwig-itk.swg.diff - a patch to the top-level CableSwig directory
which adapts cableSwigMain.cxx to enable library files and backs out most of
my "throws" typemap code from CableSwig.cxx, as this is now implicitly in
itk.swg.

2. itk.swg - this new file must go in Insight/Wrapping/CSwig (remember to
cvs add ;)  It handles exceptions for all languages now, not only Python and
Tcl8.

3. Insight-itk.swg-PyCommand.diff - a patch to top-level Insight that adapts
the Wrapping/CSwig/CmakeLists files so that cswig makes use of itk.swg.
This patch also adapts Wrapping/CSwig/Common/CMakeLists to integrate the
PyCommand class.

4. wrap_ITKPyUtils.cxx, itkPyCommand.h and itkPyCommand.cxx - these should
be copied into Wrapping/CSwig/Common and cvs add'ed.  They enable the use of
AddObserver from Python.

5. cannyEdgeDetectionImageFilter-PyCommand.py - this illustrates the use of
PyCommand and also of the new exception handling if the filename isn't
correctly set. :)

I hope that this is satisfactory.

Thanks,
Charl

-- 
charl p. botha http://cpbotha . net/ http://visualisation . tudelft . nl/

--HlL+5n6rz5pIUxbD
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="CableSwig-itk.swg.diff"

Index: Executables/CableSwig.cxx
===================================================================
RCS file: /cvsroot/CableSwig/CableSwig/Executables/CableSwig.cxx,v
retrieving revision 1.8
diff -u -r1.8 CableSwig.cxx
--- Executables/CableSwig.cxx	22 Jul 2003 17:04:14 -0000	1.8
+++ Executables/CableSwig.cxx	22 Jul 2003 20:55:13 -0000
@@ -318,40 +318,23 @@
   // The following clause will instruct SWIG to let standard C++
   // exceptions propagate through as Python exceptions.  Original
   // version contributed by Charl P. Botha <cpbotha [AT] ieee.org>
-  
+  //
   // attaching this node as "throws" attribute to the method object
   // will cause the swig emit code to add an exception handler the
   // node only has to have a type-attribute with the type of the
-  // object that will be thrown
+  // object that will be thrown - in itk.swg the actual typemaps
+  // live that map from the type of the throws to the catch code
+  // that will actually be emited
+  // 
+  // As soon as CableSwig gets proper throw() types from gcc_xml,
+  // this little piece of code has to be adapted ever so slightly.
   Node *catchNode = NewHash();
+  // for now we assume that every method could throw a std::exception
   Setattr(catchNode, "type", "std::exception");
   Setattr(m, "throws", catchNode);
-  
-  // this has to be added to the swig typemap... when hitting a method
-  // with a "throws" attribute, it will look up the "type" attribute
-  // of that "throws" attribute, once again look up the "throws" of
-  // that result, and use the "code" attribute of the final result as
-  // handler (catch) code - we should only have to do this
-  // registration once, but for now it's much clearer here and it does
-  // no real harm being called multiple times
-  if (m_WrapLanguage == "python")
-    {
-    char code[] = 
-      "PyErr_SetString(PyExc_RuntimeError, _e.what());\n"
-      "SWIG_fail;";
-    Swig_typemap_register("throws", catchNode, code, NULL, NULL);
-
-    }
-  else if(m_WrapLanguage == "tcl8")
-    {
-    char code[] = 
-      "Tcl_SetObjResult(interp, Tcl_NewStringObj(_e.what(), -1));\n"
-      "SWIG_fail;";
-    Swig_typemap_register("throws", catchNode, code, NULL, NULL);
-    }
   // take care of the memory
   Delete(catchNode);
-  
+
   ParmList* parms = 0;
   Parm* pp = 0;
   std::string allParams;
@@ -1162,7 +1145,7 @@
     appendChild(top, header);
     appendChild(top, init);
     }
-  
+    
   // collect up all classes to be included or imported
   this->DetermineClassesToWrap(cns);
   // first process imported classes 
Index: Executables/cableSwigMain.cxx
===================================================================
RCS file: /cvsroot/CableSwig/CableSwig/Executables/cableSwigMain.cxx,v
retrieving revision 1.5
diff -u -r1.5 cableSwigMain.cxx
--- Executables/cableSwigMain.cxx	28 May 2003 21:55:34 -0000	1.5
+++ Executables/cableSwigMain.cxx	22 Jul 2003 20:55:13 -0000
@@ -543,11 +543,14 @@
       if(NoCable)
         {
         Printf(fs,"%%include \"%s\"\n", Swig_last_file());
-        for (i = 0; i < Len(libfiles); i++) 
-          {
-          Printf(fs,"\n%%include \"%s\"\n", Getitem(libfiles,i));
-          }
         }
+      // But still do the -l libraries, as these are used to perform some
+      // application-specific changes
+      for (i = 0; i < Len(libfiles); i++) 
+        {
+        Printf(fs,"\n%%include \"%s\"\n", Getitem(libfiles,i));
+        }
+        
       Seek(fs,0,SEEK_SET);
       cpps = Preprocessor_parse(fs);
       if (Swig_error_count()) {

--HlL+5n6rz5pIUxbD
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="itk.swg"

/* This is an itk-specific typemap used by CableSwig.  Also see comments
 * and "throws" code in CableSwig.cxx.
 *   -- Charl P. Botha <cpbotha AT ieee.org>
 */

#ifdef SWIGPYTHON

/* ------------------------------------------------------------
 * PyObject *  - Just pass straight through unmodified
 * This is default behaviour for python.swg, but Cable passes
 * a PyObject * through as a "p._object", so we redo the typemap
 * ------------------------------------------------------------ */
  
%typemap(in)   p._object "$1 = $input;";
%typemap(out)  p._object "$result = $1;";

#endif

%include exception.i

/* A "throws" attribute with the "std::exception" type is added synthetically
 * to each method node by CableSwig.cxx.  When gcc_xml starts passing through
 * correct throws types, this typemap could be optionally extended to
 * account for more different types.  This should work fine for now though.
 */
 
%typemap(throws) std::exception {
   SWIG_exception(SWIG_RuntimeError, _e.what());
}


--HlL+5n6rz5pIUxbD
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="Insight-itk.swg-PyCommand.diff"

Index: Wrapping/CSwig/CMakeLists.txt
===================================================================
RCS file: /cvsroot/Insight/Insight/Wrapping/CSwig/CMakeLists.txt,v
retrieving revision 1.18
diff -u -r1.18 CMakeLists.txt
--- Wrapping/CSwig/CMakeLists.txt	14 Jul 2003 12:51:07 -0000	1.18
+++ Wrapping/CSwig/CMakeLists.txt	22 Jul 2003 21:01:23 -0000
@@ -172,7 +172,8 @@
      COMMENT "${OutputTclCxx} from "
      SOURCE ${Bin}/${InputIdx}
      COMMAND ${CSWIG}
-     ARGS -c ${CINDEX} ${IGNORE_WARNINGS} -depend ${Bin}/${InputXml}.depend
+     ARGS -l${ITK_TOP}/Wrapping/CSwig/itk.swg
+          -c ${CINDEX} ${IGNORE_WARNINGS} -depend ${Bin}/${InputXml}.depend
           -o ${Bin}/${OutputTclCxx} -tcl -pkgversion "${ITK_VERSION_STRING}" -c++ ${Bin}/${InputXml}
      TARGET ${Library}
      OUTPUTS ${Bin}/${OutputTclCxx}
@@ -189,7 +190,8 @@
      COMMENT "${OutputTclCxx} from "
      SOURCE ${Bin}/${InputIdx}
      COMMAND ${CSWIG} 
-     ARGS -c ${CINDEX} ${IGNORE_WARNINGS} -depend ${Bin}/${InputXml}.depend
+     ARGS -l${ITK_TOP}/Wrapping/CSwig/itk.swg
+          -c ${CINDEX} ${IGNORE_WARNINGS} -depend ${Bin}/${InputXml}.depend
           -bindir "${EXECUTABLE_OUTPUT_PATH}/${CMAKE_CFG_INTDIR}" 
           -o ${Bin}/${OutputTclCxx} -python -c++ ${Bin}/${InputXml}
      TARGET ${Library}
@@ -207,7 +209,8 @@
      COMMENT "${OutputTclCxx} from "
      SOURCE ${Bin}/${InputIdx}
      COMMAND ${CSWIG} 
-     ARGS -c ${CINDEX} ${IGNORE_WARNINGS} -depend ${Bin}/${InputXml}.depend
+     ARGS -l${ITK_TOP}/Wrapping/CSwig/itk.swg
+          -c ${CINDEX} ${IGNORE_WARNINGS} -depend ${Bin}/${InputXml}.depend
           -bindir "${EXECUTABLE_OUTPUT_PATH}/${CMAKE_CFG_INTDIR}" 
           -o ${Bin}/${OutputTclCxx} -java -c++ ${Bin}/${InputXml}
      TARGET ${Library}
Index: Wrapping/CSwig/Common/CMakeLists.txt
===================================================================
RCS file: /cvsroot/Insight/Insight/Wrapping/CSwig/Common/CMakeLists.txt,v
retrieving revision 1.15
diff -u -r1.15 CMakeLists.txt
--- Wrapping/CSwig/Common/CMakeLists.txt	10 Jul 2003 19:34:59 -0000	1.15
+++ Wrapping/CSwig/Common/CMakeLists.txt	22 Jul 2003 21:01:23 -0000
@@ -41,7 +41,7 @@
    SET(INDEX_FILE_CONTENT "${INDEX_FILE_CONTENT}${WrapITK_BINARY_DIR}/Common/${Source}.idx\n")
 ENDFOREACH(Source)
 SET(WRAP_TCL_SOURCES ${WRAP_TCL_SOURCES} wrap_ITKCommonTclTcl.cxx wrap_ITKUtilsTcl.cxx)
-SET(WRAP_PYTHON_SOURCES ${WRAP_PYTHON_SOURCES} wrap_ITKCommonPythonPython.cxx)
+SET(WRAP_PYTHON_SOURCES ${WRAP_PYTHON_SOURCES} wrap_ITKCommonPythonPython.cxx wrap_ITKPyUtilsPython.cxx)
 
 CONFIGURE_FILE(
 ${WrapITK_SOURCE_DIR}/Master.mdx.in
@@ -72,9 +72,10 @@
 ENDIF(ITK_CSWIG_TCL)
 
 
-IF(ITK_CSWIG_PYTHON) 
+IF(ITK_CSWIG_PYTHON)
+  SET(SWIG_INC ${SWIG_INC} -I${PYTHON_INCLUDE_PATH})
   SET_SOURCE_FILES_PROPERTIES(SwigExtras_wrapPython.cxx GENERATED)
-  ADD_LIBRARY(_ITKCommonPython MODULE ${WRAP_PYTHON_SOURCES} itkStringStream.cxx SwigExtras_wrapPython.cxx )
+  ADD_LIBRARY(_ITKCommonPython MODULE ${WRAP_PYTHON_SOURCES} itkStringStream.cxx itkPyCommand.cxx SwigExtras_wrapPython.cxx )
   ADD_CUSTOM_COMMAND(
     COMMENT "run native swig on SwigExtras.i"
     SOURCE ${ITK_TOP}/Wrapping/CSwig/Common/SwigExtras.i
@@ -150,5 +151,7 @@
 # python
   WRAP_PYTHON_SOURCES(${ITK_TOP}/Wrapping/CSwig/Common ${WrapITK_BINARY_DIR}/Common
                       wrap_ITKCommonPython _ITKCommonPython "${MASTER_INDEX_FILES}" "${ALL_IDX_FILES}")
+  WRAP_PYTHON_SOURCES(${ITK_TOP}/Wrapping/CSwig/Common ${WrapITK_BINARY_DIR}/Common
+                      wrap_ITKPyUtils _ITKCommonPython "${MASTER_INDEX_FILES}" "${ALL_IDX_FILES}")
 ENDIF(ITK_CSWIG_PYTHON)
 
Index: Wrapping/CSwig/Common/wrap_ITKCommon.cxx
===================================================================
RCS file: /cvsroot/Insight/Insight/Wrapping/CSwig/Common/wrap_ITKCommon.cxx,v
retrieving revision 1.9
diff -u -r1.9 wrap_ITKCommon.cxx
--- Wrapping/CSwig/Common/wrap_ITKCommon.cxx	14 Jul 2003 12:30:49 -0000	1.9
+++ Wrapping/CSwig/Common/wrap_ITKCommon.cxx	22 Jul 2003 21:01:23 -0000
@@ -49,6 +49,9 @@
 #ifdef ITK_TCL_WRAP
     ITK_WRAP_GROUP(ITKUtils),
 #endif
+#ifdef ITK_PYTHON_WRAP
+    ITK_WRAP_GROUP(ITKPyUtils),
+#endif
     "SwigExtras",
     ITK_WRAP_GROUP(itkVector),
     ITK_WRAP_GROUP(itkVersorTransform)
Index: Wrapping/CSwig/Common/wrap_ITKCommonPython.cxx
===================================================================
RCS file: /cvsroot/Insight/Insight/Wrapping/CSwig/Common/wrap_ITKCommonPython.cxx,v
retrieving revision 1.1
diff -u -r1.1 wrap_ITKCommonPython.cxx
--- Wrapping/CSwig/Common/wrap_ITKCommonPython.cxx	13 May 2003 20:28:38 -0000	1.1
+++ Wrapping/CSwig/Common/wrap_ITKCommonPython.cxx	22 Jul 2003 21:01:24 -0000
@@ -1,2 +1,3 @@
 #define ITK_WRAP_PACKAGE "ITKCommonPython"
+#define ITK_PYTHON_WRAP
 #include "wrap_ITKCommon.cxx"

--HlL+5n6rz5pIUxbD
Content-Type: text/x-c++src; charset=us-ascii
Content-Disposition: attachment; filename="wrap_ITKPyUtils.cxx"

/*=========================================================================

  Program:   Insight Segmentation & Registration Toolkit
  Module:    $RCSfile: wrap_ITKUtils.cxx,v $
  Language:  C++
  Date:      $Date: 2003/06/24 22:17:07 $
  Version:   $Revision: 1.2 $

  Copyright (c) 2002 Insight Consortium. All rights reserved.
  See ITKCopyright.txt or http://www . itk . org/HTML/Copyright . htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#include "itkPyCommand.h"

#ifdef CABLE_CONFIGURATION
#include "itkCSwigMacros.h"

namespace _cable_
{
  const char* const group = ITK_WRAP_GROUP(ITKPyUtils);
  namespace wrappers
  {
    ITK_WRAP_OBJECT(PyCommand);
  }
}


#endif

--HlL+5n6rz5pIUxbD
Content-Type: text/x-chdr; charset=us-ascii
Content-Disposition: attachment; filename="itkPyCommand.h"

/*=========================================================================

  Program:   Insight Segmentation & Registration Toolkit
  Module:    $RCSfile: itkPyCommand.h,v $
  Language:  C++
  Date:      $Date: 2003/06/06 15:04:28 $
  Version:   $Revision: 1.1 $

  Copyright (c) 2002 Insight Consortium. All rights reserved.
  See ITKCopyright.txt or http://www . itk . org/HTML/Copyright . htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#ifndef _itkPyCommand_h
#define _itkPyCommand_h

#include "itkCommand.h"

#include <Python.h>

namespace itk
{

/** \Class PyCommand
 *  \brief Command subclass that calls a Python callable object, e.g.
 *  a Python function.
 * 
 * With this class, arbitrary Python callable objects (e.g. functions)
 * can be associated with an instance to be used in AddObserver calls.
 * This is analogous to itk::TclCommand, but then a tad more flexible. ;)
 *
 * This class was contributed by Charl P. Botha <cpbotha |AT| ieee.org>
 */
class PyCommand : public Command
{
public:
  ///! Standard "Self" typedef.
  typedef PyCommand         Self;

  ///! Smart pointer typedef support.
  typedef SmartPointer<Self>  Pointer;

  ///! Run-time type information (and related methods).
  itkTypeMacro(PyCommand,Command);

  ///! Method for creation through the object factory.
  itkNewMacro(Self);

  void SetCommandCallable(PyObject *obj);

  void Execute(Object *, const EventObject&);
  void Execute(const Object *, const EventObject&);

protected:
  PyCommand();
  ~PyCommand();
  void PyExecute();
  PyCommand(const Self&);     // Not implemented.
  void operator=(const Self&); // Not implemented.

private:
  PyObject *obj;
};


} // namespace itk

#endif // _itkPyCommand_h


--HlL+5n6rz5pIUxbD
Content-Type: text/x-c++src; charset=us-ascii
Content-Disposition: attachment; filename="itkPyCommand.cxx"

/*=========================================================================

  Program:   Insight Segmentation & Registration Toolkit
  Module:    $RCSfile: itkPyCommand.cxx,v $
  Language:  C++
  Date:      $Date: 2003/06/06 15:04:28 $
  Version:   $Revision: 1.1 $

  Copyright (c) 2002 Insight Consortium. All rights reserved.
  See ITKCopyright.txt or http://www . itk . org/HTML/Copyright . htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even 
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#include "itkPyCommand.h"

namespace itk
{

PyCommand::PyCommand()
{
    this->obj = NULL;
}

PyCommand::~PyCommand()
{
    if (this->obj)
    {
        Py_DECREF(this->obj);
    }
    this->obj = NULL;
}
    
void PyCommand::SetCommandCallable(PyObject *obj)
{
    this->obj = obj;
}

///! Execute the callback to the Tcl interpreter.
void PyCommand::Execute(Object *, const EventObject&)
{
    this->PyExecute();
}


///! Execute the callback to the Tcl interpreter with a const LightObject
void PyCommand::Execute(const Object*, const EventObject&)
{
    this->PyExecute();

}

void PyCommand::PyExecute()
{
    PyObject *result;

    result = PyEval_CallObject(this->obj, (PyObject *)NULL);

    if (result)
    {
        Py_DECREF(result);
    }
    else
    {
        PyErr_Print();
    }
}



} // namespace itk

--HlL+5n6rz5pIUxbD
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="cannyEdgeDetectionImageFilter-PyCommand.py"

from InsightToolkit import *

reader = itkImageFileReaderF2_New()
canny  = itkCannyEdgeDetectionImageFilterF2F2_New()

def progressCallback():
    print canny.GetProgress()
    
command = itkPyCommand_New()
command.SetCommandCallable(progressCallback)
canny.AddObserver(itkProgressEvent(), command.GetPointer())

rescaler = itkRescaleIntensityImageFilterF2US2_New()
writer = itkImageFileWriterUS2_New()
canny.SetInput(reader.GetOutput())
rescaler.SetInput(canny.GetOutput())
writer.SetInput(rescaler.GetOutput())

rescaler.SetOutputMinimum(0)
rescaler.SetOutputMaximum(65535)

reader.SetFileName("/home/cpbotha/build/Insight/Testing/Data/Input/cthead1.png")
writer.SetFileName("./testout.png")
writer.Update()

--HlL+5n6rz5pIUxbD--