MATLAB to Python Migration Guide
This guide helps users transition from the original MATLAB Tracker Component Library to the Python port (pyTCL/nrl-tracker).
Installation
pip install nrl-tracker
For optional features:
pip install nrl-tracker[astronomy] # Orbital mechanics with astropy
pip install nrl-tracker[geodesy] # Advanced geodetic functions
pip install nrl-tracker[signal] # Wavelet transforms
pip install nrl-tracker[all] # Everything
Naming Conventions
Function names follow Python conventions (snake_case) instead of MATLAB’s PascalCase:
MATLAB |
Python |
Module |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Import Structure
MATLAB (flat namespace):
% MATLAB - all functions in path
F = FPolyKal(T, 2);
[xPred, PPred] = KalmanPredict(x, P, F, Q);
Python (hierarchical modules):
# Python - import from modules
from pytcl.dynamic_models import f_constant_velocity
from pytcl.dynamic_estimation import kf_predict
F = f_constant_velocity(T=1.0, num_dims=2)
pred = kf_predict(x, P, F, Q)
# Or import the whole module
import pytcl.dynamic_estimation as de
pred = de.kf_predict(x, P, F, Q)
Return Values
MATLAB uses multiple output arguments; Python uses named tuples:
MATLAB:
[xUpdate, PUpdate, innov, S] = KalmanUpdate(xPred, PPred, z, H, R);
Python:
from pytcl.dynamic_estimation import kf_update
result = kf_update(x_pred, P_pred, z, H, R)
# Access fields by name
x_update = result.x
P_update = result.P
innovation = result.innovation
S = result.S
# Or unpack directly
x, P, innov, S = result.x, result.P, result.innovation, result.S
Array Indexing
MATLAB uses 1-based indexing; Python/NumPy uses 0-based:
% MATLAB
x = [1, 2, 3, 4, 5];
first = x(1); % 1
last = x(end); % 5
subset = x(2:4); % [2, 3, 4]
# Python
import numpy as np
x = np.array([1, 2, 3, 4, 5])
first = x[0] # 1
last = x[-1] # 5
subset = x[1:4] # [2, 3, 4]
Matrix Operations
Most operations are similar, but some differ:
Operation |
MATLAB |
Python (NumPy) |
|---|---|---|
Matrix multiply |
|
|
Element-wise multiply |
|
|
Transpose |
|
|
Inverse |
|
|
Solve Ax=b |
|
|
Concatenate horizontal |
|
|
Concatenate vertical |
|
|
Identity matrix |
|
|
Zeros matrix |
|
|
Diagonal matrix |
|
|
Example Migration: Kalman Filter
MATLAB:
% Initialize
x = [0; 0; 0; 0]; % [x, vx, y, vy]
P = eye(4) * 100;
% Motion model
T = 1.0; % time step
F = FPolyKal(T, 2); % 2D constant velocity
q = 0.1; % process noise
Q = QPolyKal(T, [q; q], 2);
% Measurement model
H = [1, 0, 0, 0; 0, 0, 1, 0]; % position only
R = eye(2) * 10;
% Filter loop
for k = 1:length(measurements)
% Predict
[xPred, PPred] = KalmanPredict(x, P, F, Q);
% Update
z = measurements(:, k);
[x, P] = KalmanUpdate(xPred, PPred, z, H, R);
end
Python:
import numpy as np
from pytcl.dynamic_estimation import kf_predict, kf_update
from pytcl.dynamic_models import f_constant_velocity, q_constant_velocity
# Initialize
x = np.array([0.0, 0.0, 0.0, 0.0]) # [x, vx, y, vy]
P = np.eye(4) * 100
# Motion model
T = 1.0 # time step
F = f_constant_velocity(T=T, num_dims=2) # 2D constant velocity
Q = q_constant_velocity(T=T, sigma_a=0.1, num_dims=2)
# Measurement model
H = np.array([[1, 0, 0, 0], [0, 0, 1, 0]]) # position only
R = np.eye(2) * 10
# Filter loop
for z in measurements:
# Predict
pred = kf_predict(x, P, F, Q)
# Update
upd = kf_update(pred.x, pred.P, z, H, R)
x, P = upd.x, upd.P
Example Migration: Coordinate Conversion
MATLAB:
% Cartesian to spherical
cartPoint = [1000; 2000; 3000];
[r, az, el] = Cart2Sphere(cartPoint);
% Back to Cartesian
cartBack = Sphere2Cart([r; az; el]);
% Geodetic to ECEF
lat = 40.7128 * pi/180; % NYC latitude
lon = -74.0060 * pi/180;
alt = 10; % meters
ecef = Ellipse2Cart([lat; lon; alt]);
Python:
import numpy as np
from pytcl.coordinate_systems import (
cart2sphere, sphere2cart,
geodetic2ecef, ecef2geodetic
)
# Cartesian to spherical
cart_point = np.array([1000, 2000, 3000])
r, az, el = cart2sphere(cart_point)
# Back to Cartesian
cart_back = sphere2cart(r, az, el)
# Geodetic to ECEF
lat = np.radians(40.7128) # NYC latitude
lon = np.radians(-74.0060)
alt = 10 # meters
ecef = geodetic2ecef(lat, lon, alt)
Example Migration: Data Association
MATLAB:
% Cost matrix (tracks x measurements)
C = [10, 5, 13; 3, 15, 8; 12, 7, 9];
% Hungarian algorithm
[assignment, cost] = assign2D(C);
% GNN with gating
trackStates = {[10; 20], [30; 40]};
trackCovs = {eye(2)*4, eye(2)*4};
measurements = [10.5, 30.2, 100; 19.8, 40.5, 100];
result = GNNAssociation(trackStates, trackCovs, measurements, H, R);
Python:
import numpy as np
from pytcl.assignment_algorithms import (
hungarian, gnn_association, gated_gnn_association
)
# Cost matrix (tracks x measurements)
C = np.array([[10, 5, 13], [3, 15, 8], [12, 7, 9]], dtype=float)
# Hungarian algorithm
result = hungarian(C)
assignment = result.assignment
cost = result.cost
# GNN with gating
track_preds = np.array([[10.0, 20.0], [30.0, 40.0]])
track_covs = np.array([np.eye(2) * 4 for _ in range(2)])
measurements = np.array([[10.5, 19.8], [30.2, 40.5], [100.0, 100.0]])
result = gated_gnn_association(track_preds, track_covs, measurements)
Module Mapping Reference
MATLAB Folder |
Python Module |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Common Gotchas
Row vs Column Vectors
MATLAB distinguishes between row and column vectors. NumPy 1D arrays are neither:
x = np.array([1, 2, 3]) # Shape: (3,) - neither row nor column x_row = x.reshape(1, -1) # Shape: (1, 3) - row vector x_col = x.reshape(-1, 1) # Shape: (3, 1) - column vector
In-place Operations
NumPy arrays can be modified in-place, which may cause unexpected behavior:
# This modifies the original! x = np.array([1, 2, 3]) y = x y[0] = 999 # x is now [999, 2, 3] # Use .copy() to avoid this y = x.copy() y[0] = 999 # x is still [1, 2, 3]
Angle Units
pyTCL uses radians consistently (like MATLAB TCL), but be careful with NumPy:
# Convert degrees to radians lat_rad = np.radians(40.7128) # Convert radians to degrees lat_deg = np.degrees(lat_rad)
Matrix vs Array
Use
@for matrix multiplication,*for element-wise:A = np.array([[1, 2], [3, 4]]) B = np.array([[5, 6], [7, 8]]) A @ B # Matrix multiply: [[19, 22], [43, 50]] A * B # Element-wise: [[5, 12], [21, 32]]
Complex Conjugate Transpose
MATLAB’s
'is conjugate transpose. Use.conj().Tin NumPy:A = np.array([[1+2j, 3+4j]]) A.T # Transpose only: [[1+2j], [3+4j]] A.conj().T # Conjugate transpose: [[1-2j], [3-4j]]
Getting Help
API Documentation: https://pytcl.readthedocs.io
GitHub Issues: https://github.com/nedonatelli/TCL/issues
Original MATLAB Library: https://github.com/USNavalResearchLaboratory/TrackerComponentLibrary
Type Hints
pyTCL includes type hints for better IDE support:
from pytcl.dynamic_estimation import kf_predict, KFPredictResult
from numpy.typing import NDArray
import numpy as np
def my_filter(
x: NDArray[np.floating],
P: NDArray[np.floating],
F: NDArray[np.floating],
Q: NDArray[np.floating],
) -> KFPredictResult:
return kf_predict(x, P, F, Q)
Performance Tips
Use NumPy vectorized operations instead of Python loops
Pre-allocate arrays for large simulations
Use Numba (included as dependency) for custom numerical functions
Consider scipy.linalg for specialized linear algebra
# Slow: Python loop
result = []
for i in range(1000):
result.append(some_function(data[i]))
# Fast: Vectorized
result = some_function(data) # If function supports arrays
# Fast: Pre-allocated
result = np.zeros(1000)
for i in range(1000):
result[i] = some_function(data[i])