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.