←back to Blog

Run MATLAB-Style Code Inside Python by Connecting Octave with the oct2py Library

Run MATLAB-Style Code Inside Python by Connecting Octave with the oct2py Library

In this tutorial, we explore how to seamlessly run MATLAB-style code inside Python by connecting Octave with the oct2py library. We will set up the environment on Google Colab, exchange data between NumPy and Octave, write and call .m files, visualize plots generated in Octave within Python, and work with toolboxes, structs, and .mat files. This integration provides the flexibility of Python’s ecosystem while leveraging the familiar syntax and numerical power of MATLAB/Octave in a single workflow.

Setting Up the Environment

First, we begin by setting up Octave and essential libraries in Google Colab:

!apt-get -qq update
!apt-get -qq install -y octave gnuplot octave-signal octave-control > /dev/null
!python -m pip -q install oct2py scipy matplotlib pillow

Next, we initialize an Oct2Py session and define a helper function to display Octave-generated plots directly in our Python workflow:

from oct2py import Oct2Py, Oct2PyError
import numpy as np, matplotlib.pyplot as plt, textwrap
from scipy.io import savemat, loadmat
from PIL import Image

oc = Oct2Py()
print("Octave version:", oc.eval("version"))

def show_png(path, title=None):
   img = Image.open(path)
   plt.figure(figsize=(5,4)); plt.imshow(img); plt.axis("off")
   if title: plt.title(title)
   plt.show()

Basic Operations and Data Exchange

We test the bridge between Python and Octave by running basic matrix operations and exchanging NumPy arrays with Octave:

print("\n--- Basic eval ---")
print(oc.eval("A = magic(4); A"))
print("eig(A) diag:", oc.eval("[V,D]=eig(A); diag(D)'"))
print("sin(pi/4):", oc.eval("sin(pi/4)"))

print("\n--- NumPy exchange ---")
x = np.linspace(0, 2*np.pi, 100)
y = np.sin(x) + 0.1*np.random.randn(x.size)
y_filt = oc.feval("conv", y, np.ones(5)/5.0, "same") 
print("y_filt shape:", np.asarray(y_filt).shape)

Working with Structs and Files

We demonstrate how to push Python lists into Octave as cell arrays, build a struct, and retrieve it back into Python:

print("\n--- Cells & Structs ---")
cells = ["hello", 42, [1,2,3]]
oc.push("C", cells)
oc.eval("s = struct('name','Ada','score',99,'tags',{C});")
s = oc.pull("s")
print("Struct from Octave -> Python:", s)

Next, we write a custom gradient_descent.m in Octave and call it from Python:

print("\n--- Writing and calling .m files ---")
gd_code = r"""
function [w, hist] = gradient_descent(X, y, alpha, iters)
 % X: (n,m), y: (n,1). Adds bias; returns weights and loss history.
 if size(X,2) == 0, error('X must be 2D'); end
 n = rows(X);
 Xb = [ones(n,1), X];
 m = columns(Xb);
 w = zeros(m,1);
 hist = zeros(iters,1);
 for t=1:iters
   yhat = Xb*w;
   g = (Xb'*(yhat - y))/n;
   w = w - alpha * g;
   hist(t) = (sum((yhat - y).^2)/(2*n));
 endfor
endfunction
"""
with open("gradient_descent.m","w") as f: f.write(textwrap.dedent(gd_code))

Plotting and Visualization

We render a damped sine plot in an off-screen Octave figure and display the saved PNG inline in our Python notebook:

oc.eval("x = linspace(0,2*pi,400); y = sin(2*x) .* exp(-0.2*x);")
oc.eval("figure('visible','off'); plot(x,y,'linewidth',2); grid on; title('Damped Sine (Octave)');")
plot_path = "/content/oct_plot.png"
oc.eval(f"print('{plot_path}','-dpng'); close all;")
show_png(plot_path, title="Octave-generated Plot")

Advanced Usage: Packages and Function Handles

We load the signal and control packages for designing a Butterworth filter in Octave and visualize the filtered waveform back in Python:

print("\n--- Packages (signal/control) ---")
signal_ok = True
try:
   oc.eval("pkg load signal; pkg load control;")
   print("Loaded: signal, control")
except Oct2PyError as e:
   signal_ok = False
   print("Could not load signal/control, skipping package demo.\nReason:", str(e).splitlines()[0])

if signal_ok:
   oc.push("t", np.linspace(0,1,800))
   oc.eval("x = sin(2*pi*5*t) + 0.5*sin(2*pi*40*t);")
   oc.eval("[b,a] = butter(4, 10/(800/2)); xf = filtfilt(b,a,x);")
   xf = oc.pull("xf")
   plt.figure(); plt.plot(xf); plt.title("Octave signal package: filtered"); plt.show()

.mat File Exchange and Error Handling

We exchange .mat files between Python and Octave, confirming seamless data flow:

print("\n--- .mat I/O ---")
data_py = {"A": np.arange(9).reshape(3,3), "label": "demo"}
savemat("demo.mat", data_py)
oc.eval("load('demo.mat'); A2 = A + 1;")
oc.eval("save('-mat','demo_from_octave.mat','A2','label');")
back = loadmat("demo_from_octave.mat")
print("Keys from Octave-saved mat:", list(back.keys()))

We also test error handling by catching an Octave error as a Python exception:

print("\n--- Error handling ---")
try:
   oc.eval("no_such_function(1,2,3);")
except Oct2PyError as e:
   print("Caught Octave error as Python exception:\n", str(e).splitlines()[0])

Benchmarking and Multi-File Pipelines

We benchmark vectorized versus looped summations in Octave and apply filtering in a multi-file pipeline:

print("\n--- Simple Octave benchmark ---")
oc.eval("N = 2e6; a = rand(N,1);")

oc.eval("tic; s1 = sum(a); tv = toc;")
t_vec = float(oc.pull("tv"))

oc.eval("tic; s2 = 0; for i=1:length(a), s2 += a(i); end; tl = toc;")
t_loop = float(oc.pull("tl"))

print(f"Vectorized sum: {t_vec:.4f}s | Loop sum: {t_loop:.4f}s")

print("\n--- Multi-file pipeline ---")
pipeline_m = r"""
function out = mini_pipeline(x, fs)
 try, pkg load signal; catch, end
 [b,a] = butter(6, 0.2);
 y = filtfilt(b,a,x);
 y_env = abs(hilbert(y));
 out = struct('rms', sqrt(mean(y.^2)), 'peak', max(abs(y)), 'env', y_env(1:10));
end
"""
with open("mini_pipeline.m","w") as f: f.write(textwrap.dedent(pipeline_m))

fs = 200.0
sig = np.sin(2*np.pi*3*np.linspace(0,3,int(3*fs))) + 0.1*np.random.randn(int(3*fs))
out = oc.mini_pipeline(sig, fs, nout=1)
print("mini_pipeline -> keys:", list(out.keys()))
print("RMS ~", float(out["rms"]), "| Peak ~", float(out["peak"]), "| env head:", np.ravel(out["env"])[:5])

In conclusion, we have integrated Octave’s MATLAB-compatible features directly into Python and Colab. We tested data exchange, custom functions, plotting, package usage, and performance benchmarking, demonstrating that it is possible to mix MATLAB/Octave workflows with Python efficiently.

Feel free to check out our GitHub Page for tutorials, codes, and notebooks. Additionally, subscribe to our newsletter for more insights.