Algorithm Tutorial
Creating custom, high level algorithms consists of two distinct steps: writing the algorithm itself, and embedding its source into the IPLT build tree.
As an example, two very simple algorithm will be written: The first will count all values in an image or function that are larger than a certain threshold. Since this algorithm only needs to read values, it will be implemented by deriving it from ConstAlgorithm. The second algorithm will set all values below a certain threshold to the threshold value; this will be implemented using ModIPAlgorithm.
The following steps will now be demonstrated in order to get this algorithm up-and-running:
- Directory Structure Setup
- Algorithm Test Setup
- Preparing the Testing Skeleton
- Implementing the Expected Functionality
- Coding Algorithm
- Preparing Python Wrapper
- Using The Algorithm from Python
Naming is important. We need a name for the collection (or module) of algorithms, as well as a name for each individual one within the collection. For this example, the module name is thres, the first algorithm will be ThresCount, and the second one ThresSet.
In the end, this algorithm module consist of
- Two header files, which are staged under stage/include/iplt/thres to allow other components (such as the unit-tests, the python module, or other algorithms) to utilize the module.
- A shared library called (on linux) libiplt_alg_thres.so, which is staged in stage/lib and required at run-time.
- A shared Python object named _thres.so, which is staged under stage/lib/pymod/iplt/alg/thres.
- A Python initialization file __init__.py, which is also staged under stage/lib/pymod/iplt/alg/thres and is required for the directory thres to be recognized as a Python module.
Directory Structure Setup
For the purpose of this example, we create a directory named thres under src/alg/, and populate it with the three subdirectories lib, pymod, and tests. Then we add a SConscript file in thres, consisting of a single line:
SConscript(dirs=['lib','pymod','tests'])
Algorithm Test Setup
Surprising or irritating as it may seem, the first thing on the agenda is the unit test for this algorithm, located under thres/tests.
- Note
- Tests, and in this particular case unit-test, are an essential component of any modern software development effort. They not only ascertain the continued correct functionality after refactoring cycles or platform porting, they often consist in the first step of writing new code: First you write the test, than you implement the functionality. Sounds crazy? Really works! You can call that developing in expectancy mode.
Preparing the Testing Skeleton
Before we code the test routines, the required skeleton needs to be put in place. The test will be done with the boost::test framework, which is used throughout IPLT.
The main code is put into tests.cc, which will become the test executable:
#include <boost/test/unit_test.hpp> using boost::unit_test_framework::test_suite; #include "test_thres_count.hh" #include "test_thres_set.hh" test_suite* init_unit_test_suite( int argc, char * argv[] ) { std::auto_ptr<test_suite> test(BOOST_TEST_SUITE( "Module thres Test" )); test->add(CreateThresCountTest()); test->add(CreateThresSetTest()); return test.release(); }
The two relevant lines contain the addition of the test suites for both algorithms, which are created by the factory functions CreateThresCountTest() and CreateThresFillTest(), respectively. And yes, these don't exist yet.
The factory function declaration for the counting algorithm is put into test_thres_count.hh:
#ifndef IPLT_ALG_THRES_TEST_COUNT_H #define IPLT_ALG_THRES_TEST_COUNT_H #include <boost/test/unit_test.hpp> using boost::unit_test_framework::test_suite; test_suite* CreateThresCountTest(); #endif
And the one for the threshold setting algorithm is put into test_thres_set.hh:
#ifndef IPLT_ALG_THRES_TEST_SET_H #define IPLT_ALG_THRES_TEST_SET_H #include <boost/test/unit_test.hpp> using boost::unit_test_framework::test_suite; test_suite* CreateThresSetTest(); #endif
The definitions of both factory functions is found in the actual test code test_thres_count.cc and test_thres_set.cc, respectively. Again, we first have skeleton version for both of these.
test_thres_count.cc:
#include "test_thres_count.hh" // additional include statements will go here namespace test_thres_count { void test() { // test code will go here } } test_suite* CreateThresCountTest() { using namespace test_thres_count; test_suite* ts=BOOST_TEST_SUITE("ThresCount Test"); ts->add(BOOST_TEST_CASE(&test)); return ts; }
test_thres_set.cc:
#include "test_thres_count.hh" // additional include statements will go here namespace test_thres_count { void test() { // test code will go here } } test_suite* CreateThresCountTest() { using namespace test_thres_count; test_suite* ts=BOOST_TEST_SUITE("ThresCount Test"); ts->add(BOOST_TEST_CASE(&test)); return ts; }
Finally the build script skeleton thres/tests/SConscript:
Import(["test_env"]) sources = Split(""" tests.cc test_thres_count.cc test_thres_set.cc """) e=test_env.Copy() # additional environment info will go here t=e.Program('tests',sources)
Implementing the Expected Functionality
ThresCount Test
Now lets fill in the actual algorithm usage code. First, the declaration of the algorithm needs to be included from its header. These are the two include statements we want to add, along with the namespace code:
#include <iplt/image.hh> using namespace iplt; #include <iplt/alg/thres/thres_count.hh> using namespace iplt::thres;
The first two lines are needed for creating and handling an ImageHandle and is part of the standard iplt distribution. The second two lines are required for our particular algorithm class.
- Note
- Neither the file thres_count.hh exists yet nor the namespace thres. Remember, this is coding in expectency mode, ie we expect the algorithm to be used in this way.
Next, the testing routine. As a first thing, an image needs to be created, with which the functionality of the algorithm can be tested:
ImageHandle ih = CreateImage(Extent((Size(4,4))));
The factory method CreateImage() from the standard IPLT distribution takes care of this. The ImageHandle ih now referes to a 4x4 real, spatial image, with all values set to zero. To populate the image with values, the following code is used:
double v=0.0; for(ExtentIterator(ih.GetExtent()); !it.AtEnd(); ++it) { ih.SetReal(it, v); v+=1.0; }
Now the algorithm object of our ThresCount class is created.
ThresCount thres_count(5.0);
- Note
- Again, this is in expectancy mode: We want to have an algorithm that uses a threshold, and it makes only sense that this threshold is given upon instantiation; we will need to remember this once we implement the constructor.
As the next step, the algorithm is applied to the image, using the appropriate method from ImageHandle:
ih.Apply(a);
Finally the test: since the values in the image were consecutively set from 0.0 to 15.0, what is the expected outcome of the algorithm? In the above description, it says "counts all values larger than a given threshold". In this case particular test case this means all values above 5.0, so the expected count is 10.
To retrieve the count, the algorithm object requires some sort of getter method, which we expect to be called GetCount(). This is combined with the boost::test facility macro to see if the algorithm worked:
BOOST_CHECK(a.GetCount()==10);
That's it.
Here is the finished file test_thres_count.cc.
#include "test_thres_count.hh" #include <iplt/image.hh> using namespace iplt; #include <iplt/alg/thres/thres_count.hh> using namespace iplt::alg::thres; namespace test_thres_count { void test() { ImageHandle ih = CreateImage(Extent((Size(4,4)))); double v=0.0; for(ExtentIterator it(ih.GetExtent()); !it.AtEnd(); ++it) { ih.SetReal(it, v); v+=1.0; } ThresCount thres_count(5.0); ih.Apply(thres_count); BOOST_CHECK(thres_count.GetCount()==10); } } test_suite* CreateThresCountTest() { using namespace test_thres_count; test_suite* ts=BOOST_TEST_SUITE("ThresCount Test"); ts->add(BOOST_TEST_CASE(&test)); return ts; }
ThresSet Test
The test for ThresSet is build in a similar way. A test image is setup, and the initialization of the algorithm should also accept the threshold value to be used:
ImageHandle ih = CreateImage(Extent((Size(4,4)))); double v=0.0; for(ExtentIterator it(ih.GetExtent()); !it.AtEnd(); ++it) { ih.SetReal(it, v); v+=1.0; } ThresCount thres_set(5.0); ih.ApplyIP(thres_set);
- Note
- There are two posibilities to apply this algorithm to the image: in-place (used here) and out-of-place. This works even though the ThresSet algorithm itself is implemented in-place.
What is the expected outcome of applying the algorithm? The description above said: "will set all values below a certain threshold to the threshold value". Therefore there should not be a value below 5.0 in the image:
for(ExtentIterator it(ih.GetExtent()); !it.AtEnd(); ++it) { BOOST_CHECK(ih.GetReal(it)>=5.0); }
Here is the final test file test_thres_set.cc:
#include "test_thres_set.hh" #include <iplt/image.hh> using namespace iplt; #include <iplt/alg/thres/thres_set.hh> using namespace iplt::alg::thres; namespace test_thres_set { void test() { ImageHandle ih = CreateImage(Extent((Size(4,4)))); double v=0.0; for(ExtentIterator it(ih.GetExtent()); !it.AtEnd(); ++it) { ih.SetReal(it, v); v+=1.0; } ThresSet thres_set(5.0); ih.ApplyIP(thres_set); for(ExtentIterator it(ih.GetExtent()); !it.AtEnd(); ++it) { BOOST_CHECK(ih.GetReal(it)>=5.0); } } } test_suite* CreateThresSetTest() { using namespace test_thres_set; test_suite* ts=BOOST_TEST_SUITE("ThresSet Test"); ts->add(BOOST_TEST_CASE(&test)); return ts; }
SConscript
The unit-test implementation is almost done. The only thing that remains is telling the linker to include the algorithm code, so SConscript is enhanced by one line, just prior to the e.Program() statement.
Import(["test_env"]) sources = Split(""" tests.cc test_thres_count.cc test_thres_set.cc """) e=test_env.Copy() # additional libraries to link: e.Append(LIBS = 'iplt_alg_thres') t=e.Program('tests',sources)
Again the expectancy thing: we want the final algorithm library to be available under this name, so we need to remember it when we create the library.
Coding Algorithm
After so much expecting and looking into the future, this is the part where the actual algorithm gets implemented, and the building script is setup to place the header and lib at the right location, so thay they are found by the unit tests.
In the lib subdirectory, a SConscript is required, plus the header and source files of the algorithm implementation.
ThresCount
thres_count.hh:
#ifndef THRES_COUNT_H #define THRES_COUNT_H #include <iplt/algorithm.hh> namespace iplt { namespace alg { namespace thres { class ThresCount: public NonModAlgorithm { public: ThresCount(Real thres); // retrieve count int GetCount() const; // algorithm interface virtual void Visit(const ConstImageHandle& i); virtual void Visit(const Function& f); protected: void VisitData(const Data& d); private: Real thres_; int count_; }; } } } // namespaces #endif
thres_count.cc:
#include "thres_count.hh" using namespace iplt; using namespace iplt::alg::thres; ThresCount::ThresCount(Real thres): NonModAlgorithm("ThresCount"), thres_(thres), count_(0) {} void ThresCount::Visit(const ConstImageHandle& i) { // reroute to generic method VisitData(i); } void ThresCount::Visit(const Function& f) { // reroute to generic method VisitData(f); } void ThresCount::VisitData(const Data& d) { count_=0; // make sure count is reset for each visit for(ExtentIterator it(d.GetExtent()); !it.AtEnd(); ++it) { if(d.GetReal(it)>thres_) { ++count_; } } } int ThresCount::GetCount() const { return count_; }
- Note
- The polymorphic separation into ConstImageHandle and Function is not utilized in this example, since both Visit() methods just call VisitData() to perform the actual calculation.
The method body of VisitData() is the actual algorithm implementation, using the private member variables thres_ and count_.
ThresSet
thres_set.hh:
#ifndef THRES_SET_H #define THRES_SET_H #include <iplt/algorithm.hh> namespace iplt { namespace alg { namespace thres { class ThresSet: public ModIPAlgorithm { public: ThresSet(Real thres); // algorithm interface virtual void Visit(ImageHandle& i); private: Real thres_; }; } } } // namespaces #endif
thres_set.cc:
#include "thres_set.hh" using namespace iplt; using namespace iplt::alg::thres; ThresSet::ThresSet(Real thres): ModIPAlgorithm("ThresSet"), thres_(thres) {} void ThresSet::Visit(ImageHandle& i) { for(ExtentIterator it(i.GetExtent()); !it.AtEnd(); ++it) { if(i.GetReal(it)<thres_) { i.SetReal(it,thres_); } } }
Build
Finally, here is the build script thres/lib/SConscript that builds a shared library and stages it together with the header:
Import('alg_env') headers = Split(""" thres_count.hh thres_set.hh """) sources = Split(""" thres_count.cc thres_set.cc """) e=alg_env.Copy() shlib = e.SharedLibrary('iplt_alg_thres',sources) e.InstallLib(shlib) e.InstallHeader(headers,'alg/thres')
Preparing Python Wrapper
In alg/thres/pymod/, the main wrapper code to export both ThresCount and ThresSet is put into a file named wrap_thres.cc:
#include <boost/python.hpp> using namespace boost::python; #include <iplt/alg/thres/thres_count.hh> #include <iplt/alg/thres/thres_set.hh> using namespace iplt; using namespace iplt::alg::thres; BOOST_PYTHON_MODULE(_tres) { class_<ThresCount, bases<NonModAlgorithm> >("ThresCount", init<Real>() ) .def("GetCount", &ThresCount::GetCount) ; class_<ThresSet, bases<ModIPAlgorithm> >("ThresSet", init<Real>() ) ; }
The thres/pymod/SConscript to build the Python wrapper:
Import("pymod_env") pe=pymod_env.Copy() pe.Append(LIBS = 'iplt_alg_thres') mod=pe.Pymod('_thres','wrap_thres.cc') pe.InstallPymod(mod,'alg/thres') pe.InstallPymod('__init__.py','alg/thres')
And finally the compact Python initialization file __init__.py:
from _thres import *
Using The Algorithm from Python
After building everything from the top-level (the directory where SConstruct is located) to ensure that everything is properly installed, start up either iplt or giplt and type
from alg import thres thres_count = thres.ThresCount(3.0) thres_set = thres.ThresSet(2.0)
