
In this tutorial, we explore how we can seamlessly run MATLAB-style code inside Python by connecting Octave with the oct2py library. We 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 even work with toolboxes, structs, and .mat files. By doing this, we gain the flexibility of Python’s ecosystem while continuing to leverage the familiar syntax and numerical power of MATLAB/Octave in a single workflow. Check out the FULL CODES here.
!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()
We begin by setting up Octave and essential libraries in Google Colab, ensuring that we have Octave-Forge packages and Python dependencies ready. We then initialize an Oct2Py session and define a helper function so we can display Octave-generated plots directly in our Python workflow. Check out the FULL CODES here.
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)
We test the bridge between Python and Octave by running basic matrix operations, eigenvalue decomposition, and trigonometric evaluations directly in Octave. We then exchange NumPy arrays with Octave to perform a convolution filter and verify its shape. Finally, we demonstrate how to push Python lists into Octave as cell arrays, build a struct, and retrieve it back into Python for seamless data sharing. Check out the FULL CODES here.
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))
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]))
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")
We write a custom gradient_descent.m in Octave, call it from Python with nout=2, and confirm that we recover reasonable weights and a decreasing loss. We then render a damped sine plot in an off-screen Octave figure and display the saved PNG inline in our Python notebook, keeping the whole workflow seamless. Check out the FULL CODES here.
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()
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))
We load the signal and control packages so we can design a Butterworth filter in Octave and visualize the filtered waveform back in Python. We also work with function handles by evaluating an anonymous quadratic inside Octave and, for robustness, define a named quadfun.m that we call from Python, showing both handle-based and file-based function calls in the same flow. Check out the FULL CODES here.
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])
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!")
We exchange .mat files between Python and Octave, confirming that data flows both ways without issues. We also test error handling by catching an Octave error as a Python exception. Next, we benchmark vectorized versus looped summations in Octave, showing the performance edge of vectorization. Finally, we built a multi-file pipeline that applies filtering and envelope detection, returning key statistics to Python, which demonstrates how we can organize Octave code into reusable components within our Python workflow.
In conclusion, we see how we can integrate Octave’s MATLAB-compatible features directly into Python and Colab. We successfully test data exchange, custom functions, plotting, package usage, and performance benchmarking, demonstrating that we can mix MATLAB/Octave workflows with Python without leaving our notebook. By combining the strengths of both environments, we put ourselves in a position to solve problems more efficiently and with greater flexibility.
Check out the FULL CODES here. Feel free to check out our GitHub Page for Tutorials, Codes and Notebooks. Also, feel free to follow us on Twitter and don’t forget to join our 100k+ ML SubReddit and Subscribe to our Newsletter.
Asif Razzaq is the CEO of Marktechpost Media Inc.. As a visionary entrepreneur and engineer, Asif is committed to harnessing the potential of Artificial Intelligence for social good. His most recent endeavor is the launch of an Artificial Intelligence Media Platform, Marktechpost, which stands out for its in-depth coverage of machine learning and deep learning news that is both technically sound and easily understandable by a wide audience. The platform boasts of over 2 million monthly views, illustrating its popularity among audiences.