This repository contains the code, data pipeline, and trained models for the paper:
"On-Body Distance Estimation Using IMU Orientation and BLE RSSI with a Quaternion-Based LSTM" (Under review)
The system estimates the distance between two wearable sensor nodes — one on the right forearm and one on the pelvis — by fusing BLE RSSI with IMU orientation quaternions using a deep learning model (QuaternionRNN).
| Method | Median MAE (m) | Mean MAE (m) |
|---|---|---|
| QuaternionRNN (Proposed) | 0.056 | 0.069 |
| RSSI Only (ML baseline) | 0.132 | 0.156 |
| UKF (RSSI only) | 0.181 | 0.180 |
| UKF (Orientation-aware) | 0.222 | 0.313 |
| EKF Baseline | 0.306 | 0.576 |
Evaluated under leave-one-out cross-validation across 6 participants.
| Sensor | Role | Frequency |
|---|---|---|
| BNO-085 (custom build) | IMU + BLE RSSI — worn on right forearm and pelvis | 40 Hz |
| XSens MVN (motion capture suit) | Ground truth position, orientation, acceleration | 240 Hz |
The BNO-085 measures 3D orientation (quaternion) and received signal strength (RSSI in dBm) simultaneously. XSens is used only to generate ground truth inter-sensor distance labels for training and evaluation.
IMU_RSSI/
│
├── ML_analysis/ # Main machine learning pipeline
│ ├── build_dataset.py # Reads CSVs, builds HDF5 train/test splits
│ ├── common.py # Quaternion utilities (multiply, inverse, normalize)
│ ├── train.py # Training script (QuaternionRNN, per-participant)
│ ├── evaluate_all_models.py # Evaluation and plotting for trained models
│ ├── compare_all.py # Combines all method errors for comparison
│ │
│ ├── models/
│ │ └── quat_mlp/
│ │ └── quat_MLP.py # QuaternionRNN model definition (LSTM-based)
│ │
│ ├── trained_weights/ # Saved model weights (.pth files)
│ │
│ ├── Dataset/
│ │ ├── all_data/ # Raw per-participant CSV files
│ │ └── output_data/ # HDF5 train/test splits per participant
│ │
│ ├── kalman_baseline/ # UKF and EKF baselines for comparison
│ │ ├── path_loss.py # Log-distance path loss model fitting
│ │ ├── ukf_runner.py # UKF variants (RSSI-only, orientation, accel-aided)
│ │ ├── load_raw_data.py # Loads raw acceleration/orientation CSVs
│ │ ├── evaluate_comparison.py # Runs all methods and generates comparison plots
│ │ ├── plot_paper_comparison.py # Log-scale boxplot for paper figures
│ │ ├── plot_all_comparisons.py # Grouped boxplots (3, 4, 5 method variants)
│ │ ├── run_comparison.ipynb # Main notebook: run UKF comparison pipeline
│ │ ├── compare_all_methods.ipynb # Combines LSTM + RSS + EKF + UKF results
│ │ └── plot_comparisons.ipynb # Re-plots from saved JSON (no recompute)
│ │
│ └── EKF_baseline/
│ └── ekf.py # Extended Kalman Filter implementation
│
├── Experiment_Data/ # Raw experiment recordings (P03-P08)
│ └── P03/, P04/, ... # Per-participant folders with session subfolders
│
├── Codes/ # Arduino/embedded firmware for BNO-085 sensors
│
├── ImuRssi_utils.py # Quaternion math, rotation matrix utilities
├── XSens_BNO_data_utils.py # Data loading utilities for XSens and BNO CSVs
│
└── Python Scripts[Jupyter notebooks] # Data synchronization, cleaning, visualization
Python version: 3.10 or 3.11
pip install numpy pandas matplotlib torch h5py scipy scikit-learn
pip install pytransform3d open3d openpyxl filterpyRaw CSV files follow this naming convention:
{SYSTEM}_{PARTICIPANT}_{SESSION}_{LOCATION}_{DATATYPE}.csv
| Field | Values |
|---|---|
| SYSTEM | BNO, XSENS |
| PARTICIPANT | P03 – P08 |
| SESSION | S1, S2, S3 |
| LOCATION | PLV (pelvis), RFA (right forearm), PLV_RFA |
| DATATYPE | ori (4 cols, quaternion), acc (3 cols), rssi (1 col), dist (1 col) |
All files have no header row. Columns are ordered as described above.
Open and run ML_analysis/build_dataset.py or the train_all_models.ipynb notebook. Set paths to your all_data/ directory and output directory.
cd ML_analysis
python train.pyTrains one QuaternionRNN model per participant under leave-one-out cross-validation. Saves best weights to models/.
python evaluate_all_models.pyGenerates scatter plots and boxplots of per-participant errors.
Open ML_analysis/kalman_baseline/run_comparison.ipynb, set the config paths, and run all cells. This fits UKF/EKF baselines on training data and evaluates them on the same test splits as the LSTM.
Open ML_analysis/kalman_baseline/compare_all_methods.ipynb to combine all 5 methods and generate the grouped boxplot figures used in the paper.
QuaternionRNN (ML_analysis/models/quat_mlp/quat_MLP.py)
- Input: sliding window of length
T=5over 5-dimensional vectors[q_w, q_x, q_y, q_z, RSSI] - Architecture: 2-layer LSTM, hidden size 16, dropout 0.1
- Output: scalar distance prediction (metres)
- Loss: L1 (MAE)
- Optimizer: Adam, lr=0.001, 300 epochs, batch size 2048
The relative quaternion q_norm = q_PLV^{-1} ⊗ q_RFA is computed during dataset building so the model sees orientation of the forearm relative to the pelvis, not in the global frame.
Three UKF variants are implemented in kalman_baseline/:
| Variant | Measurement model |
|---|---|
| UKF (RSSI only) | RSSI = RSSI(d0) - 10·n·log10(d/d0) + v |
| UKF (Orientation-aware) | Above + linear quaternion correction c·q |
| UKF (Accel-aided) | Orientation-aware + adaptive process noise scaled by |a_RFA - a_PLV| |
Path-loss reference: d0 = 0.61 m (24 inches), RSSI(d0) = -60 dBm (measured experimentally). All other parameters are fit from training data via OLS.
If you use this code or dataset, please cite:
[Citation to be added upon publication]
For questions about the code or dataset, please open a GitHub issue.