Run Octave Parallel with Python Using Oct2py

Draw Data while Octave Script Executes in Own Thread, Exchange Data Via TCP

·

4 min read

I will show here how to run Octave script in it's own thread and to have this script send data to Python using a TCP connection. To convert the matrix data from Octave to Python I use JSON. Python matplotlib draws the data while the Octave-script keeps running in parallel.

Why I do this

Got a bunch of existing Octave scripts to read out some hardware. If I'd had to do it from scratch I'd write it completely in python. But to get things going faster I wanted to keep the existing Octave / Matlab code, but connect it with Python. I found the neat oct2py bridge that allows to execute .m octave files using python and also has easy ways to read and write variable content from and to Octave. Unfortunately it has a big downside: Speed. Additionally Octave does not support running separate threads, at least not when one thread is supposed to receive data, while the other is supposed to update a drawing. Therefore the data drawing always blocks the reception. My first idea was to just pull the data out to python and draw with matplotlib in a separate task, but I discovered that it took around 200ms for each call to oct2py. Even just extracting a variable takes this amount of time. This might not be relevant for a starting a script, but for repeated calls it's unacceptably slow. It turned out that a Ocatve script that runs in it's own loop was acceptably fast for my purpose, but there seems no simple way to exchange data while a script is running. Since Octave and Python both support TCP and JSON this seems a good out-of-the-box choice to convert the Octave matrix data to Python numpy arrays.

Step 1

Set up a TCP Server. This code is similar to what is found in the Python doumentation.

# Filename tcphandler.py
import socketserver
import json

class MyTCPHandler(socketserver.BaseRequestHandler):
    callback = None

    """
    The RequestHandler class for our server.
    It is instantiated once per connection to the server, and must
    override the handle() method to implement communication to the
    client.
    """
    def handle(self):
        # self.request is the TCP socket connected to the client
        data = bytearray()
        datachunk = self.request.recv(1024)
        while datachunk:
            # glue the chunks together, alternatively use larger buffer
            data.extend(datachunk)
            datachunk = self.request.recv(1024)
        if MyTCPHandler.callback is not None and len(data) > 0:
            MyTCPHandler.callback(json.loads(data))

Step 2

Python Thread Running the Octave Script

# Filename octavethread.py
import threading
from oct2py import octave

class OctaveThread(threading.Thread):

    def __init__(self, name='octave-thread'):
        super(OctaveThread, self).__init__(name=name)
        self.start()


    def run(self):
        octave.addpath('./Octave') # add sub directory containing my .m files
        # octave.eval('my_octave_init_script') #optionally executes my_octave_init_script.m
        # when using 'eval' any variables are available within the next script as well
        # execute my_octave_script.m which has an endless loop and will never return
        octave.eval('my_octave_script')

Step 3

TCP Server and Drawing with matplotlib. Won't win a price for beauty, but this is just some rapid prototyping

# This is the main script to run
import threading
import tcphandler
import socketserver
import octavethread
from matplotlib import pyplot as plt

received_data = []
data_available = threading.Event()

class ServerThread(threading.Thread):

    def __init__(self, name='server-thread'):
        super(ServerThread, self).__init__(name=name)
        self.start()

    def run(self):
        #start TCP server
        tcphandler.MyTCPHandler.callback = datacallback
        HOST, PORT = "localhost", 9999
        # Create the server, binding to localhost on port 9999
        server = socketserver.TCPServer((HOST, PORT), tcphandler.MyTCPHandler)
        # Activate the server; this will keep running until you
        # interrupt the program with Ctrl-C
        server.serve_forever()

# Decouple receiving and plotting. 
# Therefore data is only copied on receive, and an event is raised to trigger updating the drawing
def datacallback(data):
    received_data.append(data) #copy the data
    data_available.set() #raise event

# Start the threads
othread = octavethread.OctaveThread()
sthread = ServerThread()

# Keep updating the drawing
while True:
    #Wait forever until new data_available event is raised
    data_available.wait()
    # Plot the latest data-set received. 
    plt.plot(received_data[len(received_data)-1] )
    # For some reason this works to have an animated-like drawing
    plt.draw()
    plt.pause(0.0001)
    plt.clf()
    # clear the event
    data_available.clear()

Step 4

Send some data from the Octave script

% Filename my_octave_script.m located in /Octave directory
pkg load instrument-control % to have tcpclient
pkg load io  % to have JSON

% send some random data 
while(1);
    pause (0.05) % simulate the execution time your script needs
    randdata = rand(1,100); % some random data to send
    tcpcli = tcpclient("127.0.0.1", 9999);
    write(tcpcli, toJSON(randdata));
end

Step 5

Run the main script (Step3) to see the fast changin plot of the random data

image.png

Sending data from Python to Octave should work the very same way.