Run MATLAB-Style Octave Code from Python with oct2py — Colab Guide

Overview

This tutorial shows how to run MATLAB-style Octave code directly from Python using the oct2py library. The approach lets you keep working in Python and Colab while calling Octave functions, exchanging arrays, saving and loading .mat files, plotting from Octave and displaying the results in Python, and using Octave toolboxes.

Set up in Google Colab

Install Octave, supporting packages and Python dependencies in the Colab environment, then import the Python libraries and initialize an Oct2Py session. A small helper displays Octave-generated PNG plots inline in the notebook.

!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
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 Octave evaluations and NumPy exchange

You can evaluate Octave expressions, run matrix operations, and call Octave functions while passing NumPy arrays back and forth.

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)


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)

Writing and calling .m files from Python

You can write Octave function files (.m) from Python, then call them through oct2py with multiple outputs.

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))


np.random.seed(0)
X = np.random.randn(200, 3)
true_w = np.array([2.0, -1.0, 0.5, 3.0])    
y = true_w[0] + X @ true_w[1:] + 0.3*np.random.randn(200)
w_est, hist = oc.gradient_descent(X, y.reshape(-1,1), 0.1, 100, nout=2)
print("Estimated w:", np.ravel(w_est))
print("Final loss:", float(np.ravel(hist)[-1]))

Plotting from Octave and displaying in Python

Render a figure in Octave off-screen, save it as PNG, and display it inline with the helper function.

print("\n--- Octave plotting -> PNG -> Python display ---")
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")

Using packages like signal and control

Load Octave-Forge packages (if available) to use filters and other signal processing routines, push time vectors from Python and pull results back.

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()

Function handles and named functions

You can evaluate anonymous functions inside Octave and also write named .m files and call them from Python.

print("\n--- Function handles ---")
oc.eval("""
f = @(z) z.^2 + 3*z + 2;
vals = feval(f, [0 1 2 3]);
""")
vals = oc.pull("vals")
print("f([0,1,2,3]) =", np.ravel(vals))


quadfun_code = r"""
function y = quadfun(z)
 y = z.^2 + 3*z + 2;
end
"""
with open("quadfun.m","w") as f: f.write(textwrap.dedent(quadfun_code))
vals2 = oc.quadfun(np.array([0,1,2,3], dtype=float))
print("quadfun([0,1,2,3]) =", np.ravel(vals2))

.mat I/O and error handling

Save Python data as .mat, load and modify from Octave, then read back. Octave exceptions surface as Python exceptions that you can catch.

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()))


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])

Benchmark and multi-file pipelines

You can run simple benchmarks in Octave and build multi-file pipelines implemented in .m files to be called from Python.

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])


print("\nAll sections executed. You are now running MATLAB/Octave code from Python!")

What this gives you

The workflow demonstrated above integrates Octave’s MATLAB-compatible syntax into a Python-driven analysis pipeline. You can prototype in Octave, reuse existing .m code, access Octave toolboxes, and still leverage Python’s visualization and data ecosystem in Colab.