Introduction

Vital now supports an initial Python interface via a C interface library.

This iPython notebook intends to introduce this interface and provide a simple demonstration of its use and capability in its current state.

As this is currently still in a proof-of-concept phase, only a subset of Vital data structures and algorithms are available via the Python interface, and yet also limited in functionality within Python (e.g. only simple data accessors and manipuators are available.

Setting up the environment

In order to access and use the Vital python interface:

  • When configuring Vital CMake, additionally enable VITAL_ENABLE_C_LIB and VITAL_ENABLE_PYTHON
  • C interface library must have been built shared

Before interacting with the Vital python bindings, the Vital common setup script should be sourced. This is located in either the build or installation directory, which ever is relevant.

When working with the bindings while in the source tree, a setup script may be sourced in

<SOURCE>/vital/bindings/python/setup_vital_python.sh

This simply adds that containing directory to the PYTHON_PATH.

A Windows equivalent batch script will be provided in the future.

Unit Tests

Tests for the python interface are available in

<SOURCE>/vital/bindings/python/vital/tests/

and require the nose python package. To run:

$ nosetests ./vital/python/vital/tests

Using the Vital Python Interface

The Vital Python interface is very similar to the C++ interface, with a slight difference in how algorithms are treated. All together, the Vital Python module primarily consists of a collection of data structures and a collection of algorithms types.

Importing Vital

Once the environment is setup Vital is imported under the name vital


In [ ]:
# Importing Vital Python module
import vital
print vital

Data structures

The data structures provided in the Python interface are intended to provide the same functions as their C++ counter parts but maintain pythonic design and interaction. Most data structures are importable, for convenience, in the root vital module.

NOTE: Currently, the only data structure that is 100% implemented (compared to the source data structure in C++) is the config_block structue (the VitalConfigBlock class in Python). Other data structures in the Python interface are only partially implemented in support of the currently implemented algorithms.

Currently implemented data structures (in whole or part):

  • algorithm_plugin_manager
  • camera
  • camera_map
  • config_block (complete interface)
  • image
  • image_container
  • track
  • track_set

Example: Using the ConfigBlock


In [ ]:
# The config block structure
from vital import ConfigBlock

# Creating an empyty config block:
cb = ConfigBlock("SomeNameNotRequired") 
print "empty ConfigBlock keys",  cb.available_keys()  # an empty list

cb.set_value('foobar', 'true')
print "updated ConfigBlock keys", cb.available_keys()     # Now has one element, 'foobar'
print "value of 'foobar':", cb.get_value('foobar')  # Get string value
# This happens to be a valid boolean string, so we can get it as a boolean value, too
if cb.get_value_bool('foobar'):
    print "foobar is on (%s)" % cb.get_value_bool('foobar')
else:
    print "foobar is off (%s)" % cb.get_value_bool('foobar')

Error Handling

The C interface implements an error handle structure, that many functions take in and set, in case an exception is thrown in the C++ code. When an exception is detected, a non-zero error code is set. The Python interface uses these handles to propagate any errors that occur in the C/C++ code (aside from unavoidable things like segfaults) as raised exceptions in Python.

While there is a catch-all return code and Python exception class for generic errors, specific Python exception classes may be associated to specific return codes on a per-function basis for more fine-grained exception handling.

Example: C++ exceptions translated to Python

Config blocks may be read from file. If constructed from a file that doesn't exist, the C++ interface would throw an exception. This is also the case in the Python interface due to automatic error propagation, which happens to be a specific exception class due to the Python implementation knowing that the C interface will return different error codes for specific errors.


In [ ]:
from vital import ConfigBlock
from vital.exceptions.config_block_io import VitalConfigBlockIoFileNotFoundException
try:
    cb = ConfigBlock.from_file("/This/is/probably/not/a/file/on/your/disk.lalalalala")
except VitalConfigBlockIoFileNotFoundException as err:
    print "Exception caught:", err

Other functions may only throw the generic base VITAL Python exception due to a current lack of implementation on the Python side, or the C interface does not yet return fine-grained error codes.


In [ ]:
from vital.types import TrackSet
from vital.exceptions.base import VitalBaseException
# An empty track set
ts = TrackSet()
try:
    ts.write_tracks_file("not_enough_tracks.txt")
except VitalBaseException as err:
    print "Exception caught:", err

Plugin Management

Just as in C++, we need to load the dynamic plugins before we can instantiate abstract algorithms with concrete instances. In Python this is done via the vital.apm module. In order for plugins to be picked up, the environment variable KWIVER_PLUGIN_PATH should be set to a colon separated sequence of directories to look in.

In the below example, we set the path to point to a build of MAP-Tk's built plugin libraries.


In [ ]:
import os
from vital import apm

# os.environ['KWIVER_PLUGIN_PATH'] = '/home/purg/dev/maptk/build-dev_ocv_3.x/lib/maptk'
# OR
apm.add_search_path( '/home/purg/dev/maptk/build-dev_ocv_3.x/lib/maptk' )

# Nothing registered initially:
print "Initially registered modlues:", apm.registered_module_names()

# Register an invalid specific module:
apm.register_plugins("vital_core")
print "Single module registration:", apm.registered_module_names()

# Register a valid specific module:
apm.register_plugins("maptk_core_plugin")
print "Single module registration:", apm.registered_module_names()

# Register all available modules (recommended, thread-safe):
print "Reg all once:", apm.register_plugins_once()
print "All available modules:", apm.registered_module_names()

NOTE: It is possible to compile the VITAL system statically, but the C interface libraray dynamically. In this case, dynamic plugins are not supported. It is still required to call VitalAlgorithmPluginManager.register_plugins to register available algorithm implementations, however the system will only register those implementations that have been baked into the libraries at compile time. Be aware that in this case no modules will be reported as registered via the VitalAlgorithmPluginManager.registered_module_names() method even when algorithm implementations are actually registered.

Algorithms

In the C++ interface, abstract algorithms are defined, but need to be instantiated with concrete derived algorithms provided by the plugins. Static member functions on abstract base class for each algorithm can list the loaded algorithm implementations by name and create an instance of any implementaiton by string name.

In the Python interface, each algorithm class represents one of the C++ declared algorithm definition types. They act like a shared pointer would in the C++ interface.

Undefined Algorithm Instances

All algorithm instances must be named (a configuration requirement) and can be initially created with an undefined implementation type, or with a specific implementation. Valid implementation names (types) are determined by what plugins are loaded at the time of instance construction.

When undefined, a call to the impl_name() instance method returns None, and calls to implementation methods raise an exception stating that we cannot operate on a null pointer.


In [ ]:
from vital.algo import ImageIo
from vital.exceptions.base import VitalBaseException
iio = ImageIo('algo_name')
print "iio implementation name:", iio.impl_name()
try:
    iio.load('foo.jpg')
except VitalBaseException as err:
    print err

Instantiating Algorithm Implementations

When using algorithm instances interactively, available implementations can be viewed via the registered_names() class method.


In [ ]:
ImageIo.registered_names()

If a specific implementation is known, it may be initialized via the create(...) class method, or by VitalConfigBlock configuration.


In [ ]:
# Directly creating a new algorithm via implementation name
iio_ocv = ImageIo.create("iio_ocv", "ocv")
print "Created Implementation type:", iio_ocv.impl_name()

Configuring an Algorithm via ConfigBlock


In [ ]:
iio = ImageIo('iio') # and unconfigured image_io algorithm
cb = iio.get_config() # get the configuration
# iio.impl_name() == None
print cb.as_string()  # To see the current configuration

In [ ]:
cb.set_value('iio:type', 'ocv')
iio.set_config(cb)
print "Using Image IO implementation:", iio.impl_name()
print iio.get_config().as_string()

A More Interesting Configuration Example


In [ ]:
from vital.algo import TrackFeatures
tracker = TrackFeatures.create("tracker", "core")
print tracker.get_config().as_string()

In [ ]:
cb = tracker.get_config()
cb.set_value("tracker:core:descriptor_extractor:type", "ocv_SURF")
tracker.set_config(cb)
print tracker.get_config().as_string()

In [ ]:
cb = tracker.get_config()
surf_cb = cb.subblock_view("tracker:core:descriptor_extractor:ocv_SURF")
print "Before:"
print surf_cb.as_string()
print "----------------"
surf_cb.set_value("upright", True)
surf_cb.set_value("hessian_threshold", 750)
print "After:"
print surf_cb.as_string()
print "----------------"
tracker.set_config(cb)
print tracker.get_config().as_string()

Future Work

Going forward, the following should be achieved:

  • Finish interfacing remaining Vital data structures and structure APIs
  • Allow further access to underlying data, including using Numpy to represent data arrays and matricies.
  • Allow algorithm implementations in Python that are then generally usable within the Vital system via a Python algorithm plugin.