[Insight-users] [PATCH-SET #2] itkPyCommand - a working observer mechanism for ITK Python

Charl P. Botha c . p . botha at ewi . tudelft . nl
Mon, 21 Jul 2003 23:41:47 +0200


--GvXjxJ+pjyke8COw
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

Dear list, especially Brad and Bill,

I have solved my previous problem of being able to pass a PyObject*
unaltered through to wrapped code.  The reason that I was trying to change
this, is that I wished to create an itk::PyCommand class, analogous to
TclCommand for the Tcl wrapping but able to take an arbitrary Python
function object.  I have now completed this work.

My changes consist of: 

1. two new files, itkPyCommand.h and itkPyCommand.cxx, that should be added
to Insight/Wrapping/CSwig/Common (i.e. where itkTclCommand.* lives),

2. a patch, itkPyObject-integration.diff, that modifies all the necessary
meta-sources in that directory (e.g. CMakeLists) so that the PyCommand
is built when applicable,

3. another patch to CableSwig that changes the code so that the Swig typemap
is adapted to let PyObject* pointers through unaltered and finally

4. an example, cannyEdgeDetectionImageFilter-PyCommand.py, that illustrates
the use of the PyCommand (have a look, it's nice and easy!).

Please review and apply these changes.  I believe that they make the Python
ITK bindings even more useful, as without them there is no way to use any
kind of observer object from pure Python.

Thanks,
Charl

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

--GvXjxJ+pjyke8COw
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


--GvXjxJ+pjyke8COw
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

--GvXjxJ+pjyke8COw
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="itkPyCommand-integration.diff"

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	21 Jul 2003 21:24:27 -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	21 Jul 2003 21:24:27 -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	21 Jul 2003 21:24:27 -0000
@@ -1,2 +1,3 @@
 #define ITK_WRAP_PACKAGE "ITKCommonPython"
+#define ITK_PYTHON_WRAP
 #include "wrap_ITKCommon.cxx"

--GvXjxJ+pjyke8COw
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="CableSwig-PyObject-GoesThrough.diff"

Index: Executables/CableSwig.cxx
===================================================================
RCS file: /cvsroot/CableSwig/CableSwig/Executables/CableSwig.cxx,v
retrieving revision 1.7
diff -u -r1.7 CableSwig.cxx
--- Executables/CableSwig.cxx	21 Jul 2003 13:48:38 -0000	1.7
+++ Executables/CableSwig.cxx	21 Jul 2003 21:26:00 -0000
@@ -1160,6 +1160,29 @@
     appendChild(top, header);
     appendChild(top, init);
     }
+    
+  // This instructs SWIG to leave PyObject * parameters alone
+  // -- Charl P. Botha <cpbotha at ieee . org>
+  if(pythonWrap)
+    {
+    // first we create a node with "type" == "p._object", because that's
+    // how CableSwig sees a PyObject*
+    Node* PyObjectNode = NewHash();
+    Setattr(PyObjectNode, "type", "p._object");
+    // this code will tell Swig to pass PyObject* straight through without
+    // trying to unpack or convert
+    char *code = "$1 = $input;";
+    // we also need to tell it that it shouldn't ignore the PyObject* param
+    // if we don't do the kwargs bit (see lang.cxx:typemapDirective())
+    Hash* kwargs = NewHash();
+    Setattr(kwargs, "name", "numinputs");
+    Setattr(kwargs, "value", "1");
+    // register it all
+    Swig_typemap_register("in", PyObjectNode, code, NULL, kwargs);
+    // get rid of our tracks
+    Delete(kwargs);
+    Delete(PyObjectNode);
+    }
   
   // collect up all classes to be included or imported
   this->DetermineClassesToWrap(cns);

--GvXjxJ+pjyke8COw
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()

--GvXjxJ+pjyke8COw--