NumPy Over A Network (II)

I promised to post the plugin code I wrote for NDIToolbox that demos one way to use NumPy over a network, and here you go. The scenario here is that you’ve written a plugin that uses scikits-image; rather than make your users install scikits-image you’ve decided to host the number-crunching code yourself and have plugins that “phone home” for the results. After looking at your options you’ve decided XML-RPC and SimpleXMLRPCServer look promising.

server.py

    #!/usr/bin/env python
    """server.py - example of hosting NumPy functions on a server via XML-RPC

    Chris R. Coughlin (TRI/Austin, Inc.)
    """

    # A modified version of the Python example code for SimpleXMLRPCServer
    # http://docs.python.org/library/simplexmlrpcserver.html#module-SimpleXMLRPCServer
    # Demonstrates one way to move large NumPy arrays between a client and server -
    # client sends NumPy array, server performs calculations and returns results
    from SimpleXMLRPCServer import SimpleXMLRPCServer
    from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
    from skimage import filter
    import numpy as np
    class RequestHandler(SimpleXMLRPCRequestHandler):
        rpc_paths = ('/edge_detector',)

    # Create server
    server = SimpleXMLRPCServer(("", 8000),
                                requestHandler=RequestHandler)
    server.register_introspection_functions()

    def sobel_edges(arr_as_list):
        """Applies the Sobel operator to the provided data,
        returns the edges detected.  Use numpy.array(returned_data) to
        produce NumPy array."""
        an_array = np.array(arr_as_list)
        edges = filter.sobel(an_array)
        return edges.tolist()
    server.register_function(sobel_edges, 'sobel_edges')

    def canny_edges(arr_as_list, std_dev="1.0", low_t="0.1", high_t="0.2"):
        """Applies the Canny algorithm to the provided data,
        returns the edges detected.  Use numpy.array(returned_data) to
        produce NumPy array."""
        an_array = np.array(arr_as_list)
        sigma = float(std_dev)
        low_threshold = float(low_t)
        high_threshold = float(high_t)
        edges = filter.canny(an_array, sigma, low_threshold, high_threshold)
        return edges.tolist()
    server.register_function(canny_edges, 'canny_edges')

    # Run the server's main loop
    server.serve_forever()

As before, the server isn’t all that different from the sample code illustrating SimpleXMLRPCServer. You’ve decided to write a single “edge detection” server app that provides a few different edge detection algorithms through XML-RPC; your NDIToolbox plugins will send the server their NumPy data and the server sends back the detected edges for replot in NDIToolbox.

The first plugin you’ll write is a Sobel edge detection plugin – it’s a nice place to start with scikits-image since it doesn’t take any arguments.

sobel_edge_detection_plugin.py

"""sobel_edge_detection_plugin.py - simple A7117 plugin that demonstrates
    XML-RPC communication by returning edges found in an image

    Chris R. Coughlin (TRI/Austin, Inc.)
    """

    __author__ = 'Chris R. Coughlin'

    from models.abstractplugin import TRIPlugin
    import numpy as np
    import xmlrpclib

    class SobelPlugin(TRIPlugin):
        """Returns absolute magnitude Sobel to find edges in an image

http://en.wikipedia.org/wiki/Sobel_operator"""

        name = "Sobel Edge Detection"
        description = "Applies the Sobel operator to the current data to detect edges"
        authors = "Chris R. Coughlin (TRI/Austin, Inc.)"
        version = "1.0"
        url = "www.tri-austin.com"
        copyright = "Copyright (C) 2012 TRI/Austin, Inc.  All rights reserved."

        def __init__(self):
            super(SobelPlugin, self).__init__(self.name, self.description,
                                                  self.authors, self.url, self.copyright)
            self.config = {'server_url':'http://192.168.1.100:8000/edge_detector'}
            self.srvr = xmlrpclib.ServerProxy(self.config['server_url'])

        def run(self):
            """Executes the plugin - returns a new NumPy array with
            edges detected in original data"""
            if self._data is not None:
                self._data = self.srvr.sobel_edges(self._data.astype(np.float64).tolist())

You do list the URL to your edge detection server as a config option so that the user has the chance to update it if necessary.

Quick aside – in an NDIToolbox plugin, any field you add to the plugin’s config dict is presented to the user as an editable field. When the user runs the plugin, NDIToolbox brings up an additional config window that allows them to edit the configuration as required.

Here’s the before and after of the Sobel plugin on Windows:


The next edge detection algorithm your server provides is the Canny algorithm – unlike Sobel, there are some parameters that the user can configure in this method, so we’ll add them to the plugin’s config dict to make them user-editable.

canny_edge_detection_plugin.py

"""canny_edge_detection_plugin.py - simple A7117 plugin that demonstrates
XML-RPC communication by returning edges found in an image

Chris R. Coughlin (TRI/Austin, Inc.)
"""

__author__ = 'Chris R. Coughlin'

from models.abstractplugin import TRIPlugin
import numpy as np
import xmlrpclib

class CannyPlugin(TRIPlugin):
    """Detects edges in an image with the Canny algorithm

http://en.wikipedia.org/wiki/Canny_edge_detector

    """

    name = "Canny Edge Detection"
    description = "Applies the Canny algorithm to the current data to detect edges"
    authors = "Chris R. Coughlin (TRI/Austin, Inc.)"
    version = "1.0"
    url = "www.tri-austin.com"
    copyright = "Copyright (C) 2012 TRI/Austin, Inc.  All rights reserved."

    def __init__(self):
        super(CannyPlugin, self).__init__(self.name, self.description,
                                              self.authors, self.url, self.copyright)
        self.config = {'sigmas':1,
            'low_threshold':0.1,
            'high_threshold':0.2,
            'server_url':'http://192.168.1.100:8000/edge_detector'}
        self.srvr = xmlrpclib.ServerProxy(self.config['server_url'])

    def run(self):
        """Executes the plugin - returns a new NumPy array with
        edges detected in original data"""
        if self._data is not None:
            self._data = self.srvr.canny_edges(self._data.astype(np.float64).tolist(), self.config['sigmas'], self.config['low_threshold'], self.config['high_threshold'])

And here’s the before and after on the same ultrasonic C-scan data as shown above for the Sobel edge detection.


(The Canny results aren’t as nice as the Sobel, but we can always tweak the input parameters until we get better results.)

To distribute your new NDIToolbox plugins, you could just provide your users with a copy of both plugin Python files and ask them to copy to their NDIToolbox plugins folder, but NDIToolbox will do that for them automatically if you package them properly as ZIP files. To do that for the Sobel plugin for example, create a new ZIP sobel_edge_detection_plugin.zip and add sobel_edge_detection_plugin.py and a README to the archive such as the following.

README

Sobel Edge Detection - returns detected edges in image plot data
Chris R. Coughlin (TRI/Austin, Inc.)
www.tri-austin.com
Uses the Sobel operation (http://en.wikipedia.org/wiki/Sobel_operator) to detect edges in 2D data.

Once we’ve created the sobel_edge_detection_plugin.zip file, we can directly provide the archive to the user and have them perform a local installation. The other option is to host the archive on a server and allow the user to perform a remote installation such as the Linux user shown below.