Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions configs/model/tcn_single_joint.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# TCN Single Joint Model Configuration
# Scaled version of default tcn configuration

name: tcn_single_joint
type: tcn

# Architecture parameters
architecture:
input_size: 7 # 6 IMU features + 1 joint angle
output_size: 1 # 1 joint moment for testing

# Single joint parameters
num_channels: [6, 6, 6, 6, 6] # 5 layers with 6 channels each -> this is scaled from the default 25 since we are dealing with 1/4 of the information (reduce overfitting)
kernel_size: 7
dropout: 0.2
spatial_dropout: false
activation: ReLU
norm: weight_norm

# Same Computed properties
effective_history: 187 # timesteps
receptive_field_ms: 1870 # milliseconds at 100Hz

# Same Normalization
normalization:
enabled: false
use_dataset_stats: true

# Same weight initialization
initialization:
conv_std: 0.01
linear_std: 0.01

data_mapping:
joint_index: 0 # UPDATE to reflect joint being tested. Hip L = 0, Hip R = 1, Knee L = 2, Knee R = 3
joint_features: [0, 1, 2, 3, 4, 5, 6] # UPDATE to reflect columns containing features of the joint being tested


estimated_params: 47000
4 changes: 4 additions & 0 deletions configs/pilots/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "default",
"mass_kg": 1.0
}
1 change: 1 addition & 0 deletions deploy/exoskeleton-server.service
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ ExecStart=/home/dylan-exo/ml_stuff/exoskeleton-ai/.venv/bin/uvicorn scripts.serv
WorkingDirectory=/home/dylan-exo/ml_stuff/exoskeleton-ai
Restart=on-failure
User=dylan-exo
Environment=PILOT=default

[Install]
WantedBy=multi-user.target
16 changes: 9 additions & 7 deletions scripts/export_onnx.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
DEFAULT_MODEL_PATH = "outputs/2026-01-08/18-29-05/best_model.pt"


def load_model(model_path: Path) -> tuple[torch.nn.Module, int]:
def load_model(model_path: Path):
checkpoint = torch.load(model_path, map_location="cpu")

config_path = model_path.parent / "config.yaml"
Expand All @@ -33,22 +33,24 @@ def load_model(model_path: Path) -> tuple[torch.nn.Module, int]:
model.load_state_dict(checkpoint["model_state_dict"])
model.eval()

window_size: int = config.model.effective_history
return model, window_size
return model, config


def export(model_path: Path, output_path: Path) -> None:
print(f"Loading model: {model_path}")
model, window_size = load_model(model_path)
model, config = load_model(model_path)
window_size: int = config.model.effective_history

num_params = sum(p.numel() for p in model.parameters())
input_size: int = config.model.architecture.input_size
output_size: int = config.model.architecture.output_size
print(f" Parameters: {num_params:,}")
print(f" Window size: {window_size} timesteps")
print(f" Input shape: (1, {window_size}, 28)")
print(f" Output shape: (1, {window_size}, 4)")
print(f" Input shape: (1, {window_size}, {input_size})")
print(f" Output shape: (1, {window_size}, {output_size})")

# Representative input for tracing
dummy_input = torch.randn(1, window_size, 28)
dummy_input = torch.randn(1, window_size, input_size)

with torch.no_grad():
pytorch_output = model(dummy_input)
Expand Down
82 changes: 82 additions & 0 deletions scripts/predict_msgpack.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/**
* predict_msgpack.cpp
*
* Minimal C++ client that sends a single msgpack inference request
* to the exoskeleton TCN server and prints the predicted torques.
*
* Compile:
* g++ -O2 -std=c++17 predict_msgpack.cpp -lcurl -o predict_msgpack
*
* Run:
* ./predict_msgpack
*/

#include <iostream>
#include <string>
#include <vector>

#include <curl/curl.h>
#include <msgpack.hpp>

static const char* MSGPACK_URL = "http://127.0.0.1:8000/predict_msgpack?model=single_joint";
static const int WINDOW_SIZE = 187;
static const int FEATURE_COUNT = 7; // hip_left only: 6 IMU + 1 joint angle

static size_t write_callback(char* ptr, size_t size, size_t nmemb, void* userdata) {
auto* buf = static_cast<std::string*>(userdata);
buf->append(ptr, size * nmemb);
return size * nmemb;
}

int main() {
// Build flat input: WINDOW_SIZE * FEATURE_COUNT float32 values (zeroed)
std::vector<float> flat(WINDOW_SIZE * FEATURE_COUNT, 0.0f);

// Pack with msgpack
msgpack::sbuffer buf;
msgpack::pack(buf, flat);

// Send POST request
curl_global_init(CURL_GLOBAL_DEFAULT);
CURL* curl = curl_easy_init();
if (!curl) {
std::cerr << "Failed to init curl\n";
return 1;
}

std::string response_body;
struct curl_slist* headers = nullptr;
headers = curl_slist_append(headers, "Content-Type: application/x-msgpack");

curl_easy_setopt(curl, CURLOPT_URL, MSGPACK_URL);
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, buf.data());
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)buf.size());
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_body);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);

CURLcode res = curl_easy_perform(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

curl_slist_free_all(headers);
curl_easy_cleanup(curl);
curl_global_cleanup();

if (res != CURLE_OK || http_code != 200) {
std::cerr << "Request failed (HTTP " << http_code << "): " << curl_easy_strerror(res) << "\n";
return 1;
}

// Unpack response map
msgpack::object_handle oh = msgpack::unpack(response_body.data(), response_body.size());
std::map<std::string, double> result;
oh.get().convert(result);

std::cout << "hip_left: " << result["hip_left"] << " Nm\n";
std::cout << "inference_ms: " << result["inference_ms"] << " ms\n";

return 0;
}
Loading