Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
4c77fe5
Initial fall detection commit
alexschmander Jun 19, 2025
10947fe
Make it train
knoellle Jun 19, 2025
64fbd0a
Save tflite model
knoellle Jun 24, 2025
09f4a1e
Add minimal tflite inference node
alexschmander Jun 26, 2025
d61107e
Initial dirty dataset pipeline
alexschmander Jul 1, 2025
4119072
Built mcap to dataset pipeline
knoellle Jul 1, 2025
2541967
Clean up
knoellle Jul 1, 2025
e79517f
Implement proper labelling
knoellle Jul 1, 2025
4f3ce30
Generate dataset from multiple mcap files and fix training
knoellle Jul 2, 2025
841716a
Run the model on the nao
knoellle Jul 2, 2025
901e194
Limit tflite to one thread
knoellle Jul 2, 2025
843addb
Clean up
knoellle Jul 2, 2025
5bcf79c
Use actual sensor data for inference
knoellle Jul 2, 2025
19b3269
Print mcap path while converting to parquet
alexschmander Jul 3, 2025
06cb8e4
Dataset class balancing
alexschmander Jul 3, 2025
7c3d636
Clean up
alexschmander Jul 3, 2025
5d082e2
Add LSTM training
alexschmander Jul 3, 2025
d7febcc
Matplotlib -> plotly
alexschmander Jul 4, 2025
f0a5d08
Fix training LSTM using CUDA
alexschmander Jul 4, 2025
ea6e9d7
Generate fake `robot_orientation` for tinyml in sim
alexschmander Jul 4, 2025
add47d1
Clean up
alexschmander Jul 4, 2025
83057f3
Merge train history and confusion matrix figure
alexschmander Jul 4, 2025
879ab2a
Update architecture, add `BatchNormalization` layers
alexschmander Jul 4, 2025
c4f4053
Don't fail export on mcap read error
alexschmander Jul 4, 2025
b6d0f9c
Add data path option
alexschmander Jul 4, 2025
d81aa17
Add wandb logging
alexschmander Jul 6, 2025
155b80b
Add wandb project name
alexschmander Jul 8, 2025
a47a633
Add code as artifact
knoellle Jul 8, 2025
124fc39
Use 2D convolution with 4,1 stride
knoellle Jul 8, 2025
322aa46
Save artifact
alexschmander Jul 8, 2025
b68b423
Add wandb config
alexschmander Jul 10, 2025
395df07
Change used features to direct imu sensor data
alexschmander Jul 10, 2025
e73bef7
Simplify data windowing
alexschmander Jul 10, 2025
bc5da31
New labels: `Stable`, `SoonToBeUnstable`
alexschmander Jul 13, 2025
d4df1d6
Group data also over game phase
alexschmander Jul 13, 2025
56e8d16
Dynamic `label_shift` dataset generation
alexschmander Jul 15, 2025
8d0ed63
Improve training stability
alexschmander Jul 16, 2025
51887cf
Final tinyml setup
alexschmander Jul 17, 2025
7edfd5d
Correct fall state detection inference input
alexschmander Jul 17, 2025
278f2c1
Add final test accuracies and test plots
alexschmander Jul 17, 2025
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
99 changes: 99 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ structopt = "0.3.26"
syn = { version = "2.0.98", features = ["extra-traits", "full"] }
systemd = "0.10.0"
tempfile = "3.17.0"
tflite = { path = "../../tflite-rs" }
thiserror = "2.0.11"
threadbound = "0.1.7"
tokio = { version = "1.43.0", features = ["full"] }
Expand Down
1 change: 1 addition & 0 deletions crates/bevyhavior_simulator/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fn main() -> Result<()> {
"control::primary_state_filter",
"control::referee_position_provider",
"control::ready_signal_detection_filter",
"control::fall_state_detection",
"control::role_assignment",
"control::rule_obstacle_composer",
"control::search_suggestor",
Expand Down
4 changes: 3 additions & 1 deletion crates/bevyhavior_simulator/src/fake_data.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{net::SocketAddr, time::Duration};

use color_eyre::Result;
use linear_algebra::Isometry2;
use linear_algebra::{Isometry2, Orientation3};
use projection::camera_matrices::CameraMatrices;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -67,6 +67,7 @@ pub struct MainOutputs {
pub calibration_command: MainOutput<Option<CalibrationCommand>>,
pub stand_up_front_estimated_remaining_duration: MainOutput<Option<Duration>>,
pub camera_matrices: MainOutput<Option<CameraMatrices>>,
pub robot_orientation: MainOutput<Option<Orientation3<Field>>>,
}

impl FakeData {
Expand Down Expand Up @@ -107,6 +108,7 @@ impl FakeData {
.into(),
calibration_command: last_database.calibration_command.into(),
camera_matrices: last_database.camera_matrices.clone().into(),
robot_orientation: last_database.robot_orientation.into(),
})
}
}
8 changes: 7 additions & 1 deletion crates/bevyhavior_simulator/src/interfake.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use std::{
mem::take,
path::PathBuf,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
};

use hula_types::hardware::Paths;
use parking_lot::Mutex;

use buffered_watch::{Receiver, Sender};
Expand Down Expand Up @@ -70,7 +72,11 @@ impl SpeakerInterface for Interfake {

impl PathsInterface for Interfake {
fn get_paths(&self) -> hula_types::hardware::Paths {
unimplemented!()
Paths {
motions: PathBuf::from("etc/motions"),
neural_networks: PathBuf::from("etc/neural_networks"),
sounds: PathBuf::from("etc/sounds"),
}
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/control/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ serde = { workspace = true }
smallvec = { workspace = true }
spl_network_messages = { workspace = true }
splines = { workspace = true }
tflite = { workspace = true }
types = { workspace = true }
walking_engine = { workspace = true }
138 changes: 138 additions & 0 deletions crates/control/src/fall_state_detection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
use std::collections::VecDeque;

use color_eyre::Result;
use hardware::PathsInterface;
use serde::{Deserialize, Serialize};

use context_attribute::context;
use framework::{deserialize_not_implemented, MainOutput};
use tflite::{ops::builtin::BuiltinOpResolver, FlatBufferModel, InterpreterBuilder};
use types::{
fall_state::{FallState, FallStateTinyML},
sensor_data::SensorData,
};

#[derive(Deserialize, Serialize)]
pub struct FallStateDetection {
last_fall_state: FallState,
#[serde(skip, default = "deserialize_not_implemented")]
model: FlatBufferModel,
datas: Vec<VecDeque<f32>>,
}

#[context]
pub struct CreationContext {
hardware_interface: HardwareInterface,
}

#[context]
pub struct CycleContext {
sensor_data: Input<SensorData, "sensor_data">,
has_ground_contact: Input<bool, "has_ground_contact">,
}

#[context]
#[derive(Default)]
pub struct MainOutputs {
pub fall_state_tinyml: MainOutput<FallStateTinyML>,
pub soft_fall_state_tinyml: MainOutput<f32>,
}

impl FallStateDetection {
pub fn new(context: CreationContext<impl PathsInterface>) -> Result<Self> {
let paths = context.hardware_interface.get_paths();
let neural_network_folder = paths.neural_networks;

let model_path = neural_network_folder.join("fall_detection_shift_23.tflite");

let model = FlatBufferModel::build_from_file(model_path)?;

Ok(Self {
last_fall_state: Default::default(),
model,
datas: vec![VecDeque::new(); 6],
})
}

pub fn cycle(&mut self, context: CycleContext) -> Result<MainOutputs> {
// TODO: hadle primary state unstiff

let inertial_measurement_unit = context.sensor_data.inertial_measurement_unit;
let linear_accelerations = inertial_measurement_unit.linear_acceleration.inner;
let roll_pitch = inertial_measurement_unit.roll_pitch.inner;
let has_ground_contact = if *context.has_ground_contact {
1.0
} else {
0.0
};

let resolver = BuiltinOpResolver::default();

let builder = InterpreterBuilder::new(&self.model, &resolver)?;
let mut interpreter = builder.build()?;
interpreter.set_num_threads(1);

interpreter.allocate_tensors()?;

let inputs = interpreter.inputs().to_vec();
assert_eq!(inputs.len(), 1);

let input_index = inputs[0];

let outputs = interpreter.outputs().to_vec();
assert_eq!(outputs.len(), 1);

let output_index = outputs[0];

let input_tensor = interpreter.tensor_info(input_index).unwrap();

self.datas[0].push_back(linear_accelerations.x);
self.datas[1].push_back(linear_accelerations.y);
self.datas[2].push_back(linear_accelerations.z);
self.datas[3].push_back(roll_pitch.x);
self.datas[4].push_back(roll_pitch.y);
self.datas[5].push_back(has_ground_contact);

let max_size = input_tensor.dims[1];
for i in 0..input_tensor.dims[2] {
while self.datas[i].len() > max_size {
self.datas[i].pop_front();
}
}

dbg!(input_tensor.dims);

if self.datas[0].len() == max_size {
interpreter.tensor_data_mut(input_index).unwrap()[0..max_size]
.copy_from_slice(self.datas[0].make_contiguous());
interpreter.tensor_data_mut(input_index).unwrap()[max_size..max_size * 2]
.copy_from_slice(self.datas[1].make_contiguous());
interpreter.tensor_data_mut(input_index).unwrap()[max_size * 2..max_size * 3]
.copy_from_slice(self.datas[2].make_contiguous());
interpreter.tensor_data_mut(input_index).unwrap()[max_size * 3..max_size * 4]
.copy_from_slice(self.datas[3].make_contiguous());
interpreter.tensor_data_mut(input_index).unwrap()[max_size * 4..max_size * 5]
.copy_from_slice(self.datas[4].make_contiguous());
interpreter.tensor_data_mut(input_index).unwrap()[max_size * 5..max_size * 6]
.copy_from_slice(self.datas[5].make_contiguous());
}

interpreter.invoke()?;

let output: &[f32] = interpreter.tensor_data(output_index)?;
assert!(output.len() == 1);

let guess = *output.first().unwrap();

let fall_state = if guess <= 5.0 {
FallStateTinyML::Stable
} else {
FallStateTinyML::SoonToBeUnstable
};

Ok(MainOutputs {
fall_state_tinyml: fall_state.into(),
soft_fall_state_tinyml: guess.into(),
})
}
}
1 change: 1 addition & 0 deletions crates/control/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod calibration_controller;
pub mod camera_matrix_calculator;
pub mod center_of_mass_provider;
pub mod dribble_path_planner;
pub mod fall_state_detection;
pub mod fall_state_estimation;
pub mod filtered_game_controller_state_timer;
pub mod foot_bumper_filter;
Expand Down
1 change: 1 addition & 0 deletions crates/hulk_manifest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ pub fn collect_hulk_cyclers(root: impl AsRef<Path>) -> Result<Cyclers, Error> {
"control::center_of_mass_provider",
"control::dribble_path_planner",
"control::fall_state_estimation",
"control::fall_state_detection",
"control::filtered_game_controller_state_timer",
"control::foot_bumper_filter",
"control::free_kick_signal_filter",
Expand Down
18 changes: 18 additions & 0 deletions crates/types/src/fall_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,21 @@ pub enum FallState {
kind: Kind,
},
}

#[derive(
Clone,
Copy,
Debug,
Default,
Deserialize,
PartialEq,
Serialize,
PathSerialize,
PathDeserialize,
PathIntrospect,
)]
pub enum FallStateTinyML {
#[default]
Stable,
SoonToBeUnstable,
}
Binary file not shown.
5 changes: 5 additions & 0 deletions tools/machine-learning/fall_detection/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.parquet
*.tflite
*.keras
wandb/
confusion_matrix.html
1 change: 1 addition & 0 deletions tools/machine-learning/fall_detection/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
Empty file.
Loading