Skip to content

Commit 9723229

Browse files
committed
Implement proper labelling
1 parent 7224405 commit 9723229

6 files changed

Lines changed: 96 additions & 31 deletions

File tree

crates/control/src/fall_state_detection.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ impl FallStateDetection {
6262
}
6363

6464
pub fn cycle(&mut self, context: CycleContext<impl PathsInterface>) -> Result<MainOutputs> {
65+
// TODO: hadle primary state unstiff
66+
6567
// let cycle_start = context.cycle_time.start_time;
6668
// let inertial_measurement_unit = context.sensor_data.inertial_measurement_unit;
6769
// let (roll, pitch, _) = context.robot_orientation.inner.euler_angles();

tools/machine-learning/fall_detection/data_loading/load_data.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,22 @@ def unwrap(self, data: dict | str) -> str:
5858
raise ValueError(f"did not expect fall state {data}")
5959

6060

61+
class HasGroundContactUnwrapper(Unwrapper):
62+
pass
63+
64+
65+
class PrimaryStateUnwrapper(Unwrapper):
66+
pass
67+
68+
6169
OUTPUTS = {
6270
"Control.main_outputs.sensor_data": SensorDataUnwrapper(),
6371
"Control.main_outputs.robot_orientation": RobotOrientationUnwrapper(),
6472
"Control.main_outputs.center_of_mass": CenterOfMassUnwrapper(),
6573
"Control.main_outputs.zero_moment_point": ZeroMomentPointUnwrapper(),
6674
"Control.main_outputs.fall_state": FallStateUnwrapper(),
75+
"Control.main_outputs.has_ground_contact": HasGroundContactUnwrapper(),
76+
"Control.main_outputs.primary_state": PrimaryStateUnwrapper(),
6777
}
6878

6979

tools/machine-learning/fall_detection/dataset/dataset.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def __init__(
2828

2929
self.labeller = PseudoLabeller()
3030
self.features = features
31+
print(dataframe.columns)
3132

3233
number_of_nulls = (
3334
dataframe.select(features)
@@ -77,13 +78,15 @@ def to_windowed(
7778
)
7879
self.input_data = windows.select(self.features)
7980
# windowed_labels = windowed_dataframe.agg(pl.col("labels")).drop("index")
80-
self.labels = pl.Series(
81-
[
82-
# todo: state prediction label index
83-
row[-1]
84-
# print(row)
85-
for row in windows.get_column("labels")
86-
]
81+
self.labels = pl.DataFrame(
82+
{
83+
"labels": [
84+
# todo: state prediction label index
85+
row[-1]
86+
# print(row)
87+
for row in windows.get_column("labels")
88+
]
89+
}
8790
)
8891

8992
def __len__(self) -> int:
@@ -92,6 +95,9 @@ def __len__(self) -> int:
9295
def n_features(self) -> int:
9396
return self.input_data.size(1)
9497

98+
def n_classes(self) -> int:
99+
return len(self.labeller.label_type)
100+
95101
def __getitem__(self, index: int) -> tuple[tf.Tensor, tf.Tensor]:
96102
mask = self.groups == index
97103
return self.input_data[mask], self.labels[mask]

tools/machine-learning/fall_detection/dataset/pseudo_labels.py

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,46 @@
1-
from dataclasses import dataclass
1+
from enum import Enum
2+
23
import polars as pl
34

5+
from dataclasses import dataclass
6+
47

58
@dataclass
69
class PseudoLabelParameters:
7-
pitch_threshold: float = 1.0
10+
falling_threshold: float = 0.6
11+
fallen_threshold: float = 1.3
812

913

10-
LABELS = {
11-
0: "Other",
12-
1: "Fallen",
13-
}
14+
class Label(int, Enum):
15+
Upright = 0
16+
Falling = 1
17+
Fallen = 2
1418

1519

1620
class PseudoLabeller:
21+
label_type = Label
22+
1723
def __init__(self, parameters: PseudoLabelParameters | None = None):
1824
self.parameters = parameters or PseudoLabelParameters()
1925

2026
def generate_labels(self, data: pl.DataFrame) -> pl.Series:
27+
has_ground_contact = pl.col("Control.main_outputs.has_ground_contact")
28+
pitch = pl.col("Control.main_outputs.robot_orientation.pitch")
29+
# print(data.select(primary_state))
2130
return (
2231
data.select(
23-
self.map_to_schema(
24-
pl.col("Control.main_outputs.robot_orientation.pitch").abs()
25-
> self.parameters.pitch_threshold
32+
pl.when(has_ground_contact)
33+
.then(
34+
pl.when(pitch.abs() > self.parameters.falling_threshold)
35+
.then(pl.lit(Label.Falling))
36+
.otherwise(pl.lit(Label.Upright))
37+
)
38+
.otherwise(
39+
pl.when(pitch.abs() > self.parameters.fallen_threshold)
40+
.then(pl.lit(Label.Fallen))
41+
.otherwise(pl.lit(Label.Falling))
2642
)
2743
)
2844
.to_series()
2945
.alias("labels")
3046
)
31-
32-
def map_to_schema(self, expression: pl.Expr) -> pl.Expr:
33-
return pl.when(expression).then(pl.lit(1)).otherwise(pl.lit(0))

tools/machine-learning/fall_detection/scripts/inspect-data.py

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,56 @@
22
import plotly.express as px
33
import plotly.io as pio
44
from data_loading import load
5+
from dataset import FallenDataset
56

67

78
def main():
89
pio.renderers.default = "browser"
9-
data = load("data.parquet")
10-
# px.scatter(
11-
# data, x="time", y="Control.main_outputs.fall_state", color="robot_identifier"
12-
# ).show()
10+
11+
df = load("data.parquet")
12+
dataset = FallenDataset(
13+
df,
14+
group_keys=["robot_identifier", "match_identifier"],
15+
features=[
16+
pl.col("Control.main_outputs.robot_orientation.pitch"),
17+
pl.col("Control.main_outputs.robot_orientation.roll"),
18+
pl.col("Control.main_outputs.robot_orientation.yaw"),
19+
pl.col("Control.main_outputs.has_ground_contact"),
20+
],
21+
)
22+
dataset.to_windowed(window_stride=1 / 83)
23+
24+
df = (
25+
dataset.input_data.hstack(dataset.labels)
26+
.with_row_index()
27+
.hstack(
28+
dataset.input_data.select(
29+
pl.col(
30+
"Control.main_outputs.robot_orientation.pitch"
31+
).list.last()
32+
).rename({"Control.main_outputs.robot_orientation.pitch": "pitch"})
33+
)
34+
)
35+
print(df)
36+
# print(df[0, 0].shape)
37+
1338
# px.scatter(
14-
# data.filter(pl.col("robot_identifier") == "10.1.24.33"),
39+
# data,
1540
# x="time",
16-
# y="Control.main_outputs.robot_orientation.pitch",
17-
# color="Control.main_outputs.fall_state",
41+
# y="Control.main_outputs.fall_state",
42+
# color="robot_identifier",
1843
# ).show()
44+
print(
45+
df.select(
46+
pl.col("Control.main_outputs.robot_orientation.pitch").list.last()
47+
)
48+
)
49+
px.scatter(
50+
df, # .filter(pl.col("robot_identifier") == "10.1.24.33"),
51+
x="index",
52+
y="pitch",
53+
color="labels",
54+
).show()
1955

2056

2157
if __name__ == "__main__":

tools/machine-learning/fall_detection/scripts/train_sequential.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,6 @@ def split_data(input_data, labels):
134134

135135

136136
if __name__ == "__main__":
137-
labels = ["Unknown", "Upright", "Falling", "Fallen"]
138-
139137
df = load("data.parquet")
140138
dataset = FallenDataset(
141139
df,
@@ -149,14 +147,14 @@ def split_data(input_data, labels):
149147
)
150148
dataset.to_windowed()
151149

152-
model = build_model(len(labels))
150+
model = build_model(dataset.n_classes())
153151

154152
input_data = dataset.get_input_tensor()
155153
data_labels = dataset.get_labels_tensor()
156154
(x_train, y_train, x_test, y_test) = split_data(input_data, data_labels)
157155

158-
y_train = keras.utils.to_categorical(y_train, len(labels))
159-
y_test = keras.utils.to_categorical(y_test, len(labels))
156+
y_train = keras.utils.to_categorical(y_train, dataset.n_classes())
157+
y_test = keras.utils.to_categorical(y_test, dataset.n_classes())
160158

161159
train_model(model, x_train, y_train, x_test, y_test)
162160

0 commit comments

Comments
 (0)