Spaces:
Runtime error
Runtime error
wire up Phyphox pipeline: full 561 feature extraction from raw CSV
Browse files- data/norm_params.json +1 -0
- src/phyphox_app_block.py +190 -0
- src/phyphox_pipeline.py +516 -0
- src/streamlit_app.py +4 -31
data/norm_params.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"tBodyAcc-mean()-X": {"min": -1.0, "max": 1.0}, "tBodyAcc-mean()-Y": {"min": -1.0, "max": 1.0}, "tBodyAcc-mean()-Z": {"min": -1.0, "max": 1.0}, "tBodyAcc-std()-X": {"min": -1.0, "max": 1.0}, "tBodyAcc-std()-Y": {"min": -0.99987292, "max": 0.91623796}, "tBodyAcc-std()-Z": {"min": -1.0, "max": 1.0}, "tBodyAcc-mad()-X": {"min": -1.0, "max": 1.0}, "tBodyAcc-mad()-Y": {"min": -1.0, "max": 0.96766404}, "tBodyAcc-mad()-Z": {"min": -1.0, "max": 1.0}, "tBodyAcc-max()-X": {"min": -1.0, "max": 1.0}, "tBodyAcc-max()-Y": {"min": -1.0, "max": 1.0}, "tBodyAcc-max()-Z": {"min": -1.0, "max": 1.0}, "tBodyAcc-min()-X": {"min": -1.0, "max": 1.0}, "tBodyAcc-min()-Y": {"min": -1.0, "max": 1.0}, "tBodyAcc-min()-Z": {"min": -1.0, "max": 1.0}, "tBodyAcc-sma()": {"min": -1.0, "max": 1.0}, "tBodyAcc-energy()-X": {"min": -1.0, "max": 1.0}, "tBodyAcc-energy()-Y": {"min": -0.9999986, "max": 1.0}, "tBodyAcc-energy()-Z": {"min": -1.0, "max": 1.0}, "tBodyAcc-iqr()-X": {"min": -1.0, "max": 1.0}, "tBodyAcc-iqr()-Y": {"min": -1.0, "max": 1.0}, "tBodyAcc-iqr()-Z": {"min": -1.0, "max": 1.0}, "tBodyAcc-entropy()-X": {"min": -1.0, "max": 0.91966171}, "tBodyAcc-entropy()-Y": {"min": -1.0, "max": 1.0}, "tBodyAcc-entropy()-Z": {"min": -1.0, "max": 1.0}, "tBodyAcc-arCoeff()-X,1": {"min": -0.92589735, "max": 1.0}, "tBodyAcc-arCoeff()-X,2": {"min": -0.9630993, "max": 0.97844881}, "tBodyAcc-arCoeff()-X,3": {"min": -1.0, "max": 1.0}, "tBodyAcc-arCoeff()-X,4": {"min": -0.82205333, "max": 1.0}, "tBodyAcc-arCoeff()-Y,1": {"min": -1.0, "max": 1.0}, "tBodyAcc-arCoeff()-Y,2": {"min": -1.0, "max": 1.0}, "tBodyAcc-arCoeff()-Y,3": {"min": -1.0, "max": 1.0}, "tBodyAcc-arCoeff()-Y,4": {"min": -1.0, "max": 1.0}, "tBodyAcc-arCoeff()-Z,1": {"min": -1.0, "max": 0.81462304}, "tBodyAcc-arCoeff()-Z,2": {"min": -0.75375375, "max": 1.0}, "tBodyAcc-arCoeff()-Z,3": {"min": -1.0, "max": 0.99720675}, "tBodyAcc-arCoeff()-Z,4": {"min": -1.0, "max": 1.0}, "tBodyAcc-correlation()-X,Y": {"min": -1.0, "max": 1.0}, "tBodyAcc-correlation()-X,Z": {"min": -1.0, "max": 1.0}, "tBodyAcc-correlation()-Y,Z": {"min": -0.97221933, "max": 1.0}, "tGravityAcc-mean()-X": {"min": -1.0, "max": 0.99154906}, "tGravityAcc-mean()-Y": {"min": -0.53522237, "max": 1.0}, "tGravityAcc-mean()-Z": {"min": -1.0, "max": 1.0}, "tGravityAcc-std()-X": {"min": -1.0, "max": 1.0}, "tGravityAcc-std()-Y": {"min": -0.99982977, "max": 1.0}, "tGravityAcc-std()-Z": {"min": -1.0, "max": 1.0}, "tGravityAcc-mad()-X": {"min": -1.0, "max": 1.0}, "tGravityAcc-mad()-Y": {"min": -0.99984878, "max": 1.0}, "tGravityAcc-mad()-Z": {"min": -1.0, "max": 1.0}, "tGravityAcc-max()-X": {"min": -1.0, "max": 1.0}, "tGravityAcc-max()-Y": {"min": -0.49387412, "max": 0.96830748}, "tGravityAcc-max()-Z": {"min": -1.0, "max": 0.99658549}, "tGravityAcc-min()-X": {"min": -1.0, "max": 1.0}, "tGravityAcc-min()-Y": {"min": -0.56815651, "max": 1.0}, "tGravityAcc-min()-Z": {"min": -1.0, "max": 1.0}, "tGravityAcc-sma()": {"min": -1.0, "max": 1.0}, "tGravityAcc-energy()-X": {"min": -1.0, "max": 0.975855}, "tGravityAcc-energy()-Y": {"min": -1.0, "max": 1.0}, "tGravityAcc-energy()-Z": {"min": -0.99999923, "max": 1.0}, "tGravityAcc-iqr()-X": {"min": -1.0, "max": 1.0}, "tGravityAcc-iqr()-Y": {"min": -0.99994572, "max": 1.0}, "tGravityAcc-iqr()-Z": {"min": -0.99996947, "max": 1.0}, "tGravityAcc-entropy()-X": {"min": -1.0, "max": 1.0}, "tGravityAcc-entropy()-Y": {"min": -1.0, "max": 1.0}, "tGravityAcc-entropy()-Z": {"min": -1.0, "max": 1.0}, "tGravityAcc-arCoeff()-X,1": {"min": -1.0, "max": 1.0}, "tGravityAcc-arCoeff()-X,2": {"min": -1.0, "max": 1.0}, "tGravityAcc-arCoeff()-X,3": {"min": -1.0, "max": 1.0}, "tGravityAcc-arCoeff()-X,4": {"min": -1.0, "max": 1.0}, "tGravityAcc-arCoeff()-Y,1": {"min": -1.0, "max": 0.81316975}, "tGravityAcc-arCoeff()-Y,2": {"min": -0.95417313, "max": 1.0}, "tGravityAcc-arCoeff()-Y,3": {"min": -1.0, "max": 1.0}, "tGravityAcc-arCoeff()-Y,4": {"min": -1.0, "max": 1.0}, "tGravityAcc-arCoeff()-Z,1": {"min": -1.0, "max": 0.64877436}, "tGravityAcc-arCoeff()-Z,2": {"min": -0.61763642, "max": 1.0}, "tGravityAcc-arCoeff()-Z,3": {"min": -1.0, "max": 0.58576939}, "tGravityAcc-arCoeff()-Z,4": {"min": -0.55400018, "max": 1.0}, "tGravityAcc-correlation()-X,Y": {"min": -1.0, "max": 1.0}, "tGravityAcc-correlation()-X,Z": {"min": -1.0, "max": 1.0}, "tGravityAcc-correlation()-Y,Z": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-mean()-X": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-mean()-Y": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-mean()-Z": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-std()-X": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-std()-Y": {"min": -1.0, "max": 0.80707793}, "tBodyAccJerk-std()-Z": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-mad()-X": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-mad()-Y": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-mad()-Z": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-max()-X": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-max()-Y": {"min": -1.0, "max": 0.62437285}, "tBodyAccJerk-max()-Z": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-min()-X": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-min()-Y": {"min": -0.74690717, "max": 1.0}, "tBodyAccJerk-min()-Z": {"min": -1.0, "max": 0.99926934}, "tBodyAccJerk-sma()": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-energy()-X": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-energy()-Y": {"min": -1.0, "max": 0.63441522}, "tBodyAccJerk-energy()-Z": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-iqr()-X": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-iqr()-Y": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-iqr()-Z": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-entropy()-X": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-entropy()-Y": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-entropy()-Z": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-arCoeff()-X,1": {"min": -0.97483758, "max": 1.0}, "tBodyAccJerk-arCoeff()-X,2": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-arCoeff()-X,3": {"min": -1.0, "max": 0.96457587}, "tBodyAccJerk-arCoeff()-X,4": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-arCoeff()-Y,1": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-arCoeff()-Y,2": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-arCoeff()-Y,3": {"min": -1.0, "max": 0.99159215}, "tBodyAccJerk-arCoeff()-Y,4": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-arCoeff()-Z,1": {"min": -1.0, "max": 0.92520697}, "tBodyAccJerk-arCoeff()-Z,2": {"min": -0.79906863, "max": 1.0}, "tBodyAccJerk-arCoeff()-Z,3": {"min": -1.0, "max": 0.93354746}, "tBodyAccJerk-arCoeff()-Z,4": {"min": -1.0, "max": 1.0}, "tBodyAccJerk-correlation()-X,Y": {"min": -1.0, "max": 0.90448}, "tBodyAccJerk-correlation()-X,Z": {"min": -1.0, "max": 0.99325846}, "tBodyAccJerk-correlation()-Y,Z": {"min": -0.95950504, "max": 1.0}, "tBodyGyro-mean()-X": {"min": -0.91404957, "max": 0.73895313}, "tBodyGyro-mean()-Y": {"min": -0.85197318, "max": 1.0}, "tBodyGyro-mean()-Z": {"min": -0.90285978, "max": 1.0}, "tBodyGyro-std()-X": {"min": -1.0, "max": 1.0}, "tBodyGyro-std()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyro-std()-Z": {"min": -1.0, "max": 1.0}, "tBodyGyro-mad()-X": {"min": -1.0, "max": 1.0}, "tBodyGyro-mad()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyro-mad()-Z": {"min": -0.99991531, "max": 1.0}, "tBodyGyro-max()-X": {"min": -1.0, "max": 1.0}, "tBodyGyro-max()-Y": {"min": -0.99801363, "max": 1.0}, "tBodyGyro-max()-Z": {"min": -0.9434386, "max": 1.0}, "tBodyGyro-min()-X": {"min": -1.0, "max": 1.0}, "tBodyGyro-min()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyro-min()-Z": {"min": -1.0, "max": 1.0}, "tBodyGyro-sma()": {"min": -1.0, "max": 1.0}, "tBodyGyro-energy()-X": {"min": -1.0, "max": 1.0}, "tBodyGyro-energy()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyro-energy()-Z": {"min": -1.0, "max": 1.0}, "tBodyGyro-iqr()-X": {"min": -1.0, "max": 1.0}, "tBodyGyro-iqr()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyro-iqr()-Z": {"min": -0.99998266, "max": 1.0}, "tBodyGyro-entropy()-X": {"min": -1.0, "max": 1.0}, "tBodyGyro-entropy()-Y": {"min": -1.0, "max": 0.972798}, "tBodyGyro-entropy()-Z": {"min": -1.0, "max": 1.0}, "tBodyGyro-arCoeff()-X,1": {"min": -1.0, "max": 1.0}, "tBodyGyro-arCoeff()-X,2": {"min": -1.0, "max": 1.0}, "tBodyGyro-arCoeff()-X,3": {"min": -1.0, "max": 1.0}, "tBodyGyro-arCoeff()-X,4": {"min": -0.96659121, "max": 0.85273955}, "tBodyGyro-arCoeff()-Y,1": {"min": -1.0, "max": 1.0}, "tBodyGyro-arCoeff()-Y,2": {"min": -1.0, "max": 1.0}, "tBodyGyro-arCoeff()-Y,3": {"min": -0.96253937, "max": 1.0}, "tBodyGyro-arCoeff()-Y,4": {"min": -1.0, "max": 1.0}, "tBodyGyro-arCoeff()-Z,1": {"min": -0.90717752, "max": 0.86609404}, "tBodyGyro-arCoeff()-Z,2": {"min": -0.92338795, "max": 0.94520707}, "tBodyGyro-arCoeff()-Z,3": {"min": -0.94730392, "max": 1.0}, "tBodyGyro-arCoeff()-Z,4": {"min": -1.0, "max": 0.98513795}, "tBodyGyro-correlation()-X,Y": {"min": -1.0, "max": 1.0}, "tBodyGyro-correlation()-X,Z": {"min": -1.0, "max": 1.0}, "tBodyGyro-correlation()-Y,Z": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-mean()-X": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-mean()-Y": {"min": -1.0, "max": 0.84803136}, "tBodyGyroJerk-mean()-Z": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-std()-X": {"min": -0.99988263, "max": 1.0}, "tBodyGyroJerk-std()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-std()-Z": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-mad()-X": {"min": -0.9998892, "max": 1.0}, "tBodyGyroJerk-mad()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-mad()-Z": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-max()-X": {"min": -1.0, "max": 0.92841647}, "tBodyGyroJerk-max()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-max()-Z": {"min": -1.0, "max": 0.97903145}, "tBodyGyroJerk-min()-X": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-min()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-min()-Z": {"min": -0.7597897, "max": 1.0}, "tBodyGyroJerk-sma()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-energy()-X": {"min": -0.99999952, "max": 1.0}, "tBodyGyroJerk-energy()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-energy()-Z": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-iqr()-X": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-iqr()-Y": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-iqr()-Z": {"min": -0.9996578, "max": 1.0}, "tBodyGyroJerk-entropy()-X": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-entropy()-Y": {"min": -1.0, "max": 0.99093504}, "tBodyGyroJerk-entropy()-Z": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-arCoeff()-X,1": {"min": -0.90591841, "max": 1.0}, "tBodyGyroJerk-arCoeff()-X,2": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-arCoeff()-X,3": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-arCoeff()-X,4": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-arCoeff()-Y,1": {"min": -0.92113806, "max": 1.0}, "tBodyGyroJerk-arCoeff()-Y,2": {"min": -1.0, "max": 0.89744607}, "tBodyGyroJerk-arCoeff()-Y,3": {"min": -0.77359738, "max": 0.99901522}, "tBodyGyroJerk-arCoeff()-Y,4": {"min": -1.0, "max": 0.92337662}, "tBodyGyroJerk-arCoeff()-Z,1": {"min": -0.95282078, "max": 0.94558999}, "tBodyGyroJerk-arCoeff()-Z,2": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-arCoeff()-Z,3": {"min": -0.95389422, "max": 1.0}, "tBodyGyroJerk-arCoeff()-Z,4": {"min": -0.95448559, "max": 1.0}, "tBodyGyroJerk-correlation()-X,Y": {"min": -0.8952687, "max": 1.0}, "tBodyGyroJerk-correlation()-X,Z": {"min": -1.0, "max": 1.0}, "tBodyGyroJerk-correlation()-Y,Z": {"min": -1.0, "max": 0.99680328}, "tBodyAccMag-mean()": {"min": -1.0, "max": 1.0}, "tBodyAccMag-std()": {"min": -1.0, "max": 1.0}, "tBodyAccMag-mad()": {"min": -1.0, "max": 1.0}, "tBodyAccMag-max()": {"min": -1.0, "max": 1.0}, "tBodyAccMag-min()": {"min": -1.0, "max": 1.0}, "tBodyAccMag-sma()": {"min": -1.0, "max": 1.0}, "tBodyAccMag-energy()": {"min": -1.0, "max": 1.0}, "tBodyAccMag-iqr()": {"min": -1.0, "max": 1.0}, "tBodyAccMag-entropy()": {"min": -0.99537696, "max": 1.0}, "tBodyAccMag-arCoeff()1": {"min": -1.0, "max": 1.0}, "tBodyAccMag-arCoeff()2": {"min": -1.0, "max": 1.0}, "tBodyAccMag-arCoeff()3": {"min": -1.0, "max": 0.994731}, "tBodyAccMag-arCoeff()4": {"min": -1.0, "max": 1.0}, "tGravityAccMag-mean()": {"min": -1.0, "max": 1.0}, "tGravityAccMag-std()": {"min": -1.0, "max": 1.0}, "tGravityAccMag-mad()": {"min": -1.0, "max": 1.0}, "tGravityAccMag-max()": {"min": -1.0, "max": 1.0}, "tGravityAccMag-min()": {"min": -1.0, "max": 1.0}, "tGravityAccMag-sma()": {"min": -1.0, "max": 1.0}, "tGravityAccMag-energy()": {"min": -1.0, "max": 1.0}, "tGravityAccMag-iqr()": {"min": -1.0, "max": 1.0}, "tGravityAccMag-entropy()": {"min": -0.99537696, "max": 1.0}, "tGravityAccMag-arCoeff()1": {"min": -1.0, "max": 1.0}, "tGravityAccMag-arCoeff()2": {"min": -1.0, "max": 1.0}, "tGravityAccMag-arCoeff()3": {"min": -1.0, "max": 0.994731}, "tGravityAccMag-arCoeff()4": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-mean()": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-std()": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-mad()": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-max()": {"min": -1.0, "max": 0.98365438}, "tBodyAccJerkMag-min()": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-sma()": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-energy()": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-iqr()": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-entropy()": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-arCoeff()1": {"min": -1.0, "max": 0.86283136}, "tBodyAccJerkMag-arCoeff()2": {"min": -1.0, "max": 1.0}, "tBodyAccJerkMag-arCoeff()3": {"min": -0.83055874, "max": 0.91434541}, "tBodyAccJerkMag-arCoeff()4": {"min": -1.0, "max": 0.97433286}, "tBodyGyroMag-mean()": {"min": -1.0, "max": 1.0}, "tBodyGyroMag-std()": {"min": -1.0, "max": 1.0}, "tBodyGyroMag-mad()": {"min": -1.0, "max": 1.0}, "tBodyGyroMag-max()": {"min": -1.0, "max": 1.0}, "tBodyGyroMag-min()": {"min": -0.99983528, "max": 1.0}, "tBodyGyroMag-sma()": {"min": -1.0, "max": 1.0}, "tBodyGyroMag-energy()": {"min": -1.0, "max": 1.0}, "tBodyGyroMag-iqr()": {"min": -1.0, "max": 0.96899245}, "tBodyGyroMag-entropy()": {"min": -1.0, "max": 0.99263589}, "tBodyGyroMag-arCoeff()1": {"min": -1.0, "max": 0.99352363}, "tBodyGyroMag-arCoeff()2": {"min": -1.0, "max": 1.0}, "tBodyGyroMag-arCoeff()3": {"min": -0.89991431, "max": 0.99462757}, "tBodyGyroMag-arCoeff()4": {"min": -0.99742291, "max": 1.0}, "tBodyGyroJerkMag-mean()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerkMag-std()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerkMag-mad()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerkMag-max()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerkMag-min()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerkMag-sma()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerkMag-energy()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerkMag-iqr()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerkMag-entropy()": {"min": -1.0, "max": 1.0}, "tBodyGyroJerkMag-arCoeff()1": {"min": -1.0, "max": 0.99282314}, "tBodyGyroJerkMag-arCoeff()2": {"min": -0.99766425, "max": 1.0}, "tBodyGyroJerkMag-arCoeff()3": {"min": -1.0, "max": 0.88033546}, "tBodyGyroJerkMag-arCoeff()4": {"min": -1.0, "max": 0.83672766}, "fBodyAcc-mean()-X": {"min": -1.0, "max": 1.0}, "fBodyAcc-mean()-Y": {"min": -1.0, "max": 0.97184989}, "fBodyAcc-mean()-Z": {"min": -1.0, "max": 1.0}, "fBodyAcc-std()-X": {"min": -1.0, "max": 1.0}, "fBodyAcc-std()-Y": {"min": -0.9998571, "max": 0.86033908}, "fBodyAcc-std()-Z": {"min": -1.0, "max": 1.0}, "fBodyAcc-mad()-X": {"min": -1.0, "max": 1.0}, "fBodyAcc-mad()-Y": {"min": -0.99997862, "max": 0.96120357}, "fBodyAcc-mad()-Z": {"min": -1.0, "max": 1.0}, "fBodyAcc-max()-X": {"min": -1.0, "max": 1.0}, "fBodyAcc-max()-Y": {"min": -1.0, "max": 1.0}, "fBodyAcc-max()-Z": {"min": -0.99980523, "max": 1.0}, "fBodyAcc-min()-X": {"min": -0.99999831, "max": 1.0}, "fBodyAcc-min()-Y": {"min": -1.0, "max": 1.0}, "fBodyAcc-min()-Z": {"min": -1.0, "max": 1.0}, "fBodyAcc-sma()": {"min": -1.0, "max": 1.0}, "fBodyAcc-energy()-X": {"min": -1.0, "max": 1.0}, "fBodyAcc-energy()-Y": {"min": -0.99999872, "max": 0.83686006}, "fBodyAcc-energy()-Z": {"min": -1.0, "max": 1.0}, "fBodyAcc-iqr()-X": {"min": -1.0, "max": 1.0}, "fBodyAcc-iqr()-Y": {"min": -1.0, "max": 1.0}, "fBodyAcc-iqr()-Z": {"min": -1.0, "max": 1.0}, "fBodyAcc-entropy()-X": {"min": -1.0, "max": 1.0}, "fBodyAcc-entropy()-Y": {"min": -1.0, "max": 0.90901463}, "fBodyAcc-entropy()-Z": {"min": -1.0, "max": 1.0}, "fBodyAcc-maxInds-X": {"min": -1.0, "max": 1.0}, "fBodyAcc-maxInds-Y": {"min": -1.0, "max": 1.0}, "fBodyAcc-maxInds-Z": {"min": -1.0, "max": 0.92307692}, "fBodyAcc-meanFreq()-X": {"min": -1.0, "max": 0.9141467}, "fBodyAcc-meanFreq()-Y": {"min": -1.0, "max": 1.0}, "fBodyAcc-meanFreq()-Z": {"min": -1.0, "max": 1.0}, "fBodyAcc-skewness()-X": {"min": -0.93120793, "max": 1.0}, "fBodyAcc-kurtosis()-X": {"min": -0.99941209, "max": 1.0}, "fBodyAcc-skewness()-Y": {"min": -1.0, "max": 0.97555005}, "fBodyAcc-kurtosis()-Y": {"min": -1.0, "max": 0.96627476}, "fBodyAcc-skewness()-Z": {"min": -1.0, "max": 0.9849423}, "fBodyAcc-kurtosis()-Z": {"min": -1.0, "max": 0.97998881}, "fBodyAcc-bandsEnergy()-1,8": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-9,16": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-17,24": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-25,32": {"min": -0.99999405, "max": 0.88145236}, "fBodyAcc-bandsEnergy()-33,40": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-41,48": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-49,56": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-57,64": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-1,16": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-17,32": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-33,48": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-49,64": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-1,24": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-25,48": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-1,8.1": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-9,16.1": {"min": -1.0, "max": 0.94996979}, "fBodyAcc-bandsEnergy()-17,24.1": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-25,32.1": {"min": -1.0, "max": 0.95807252}, "fBodyAcc-bandsEnergy()-33,40.1": {"min": -1.0, "max": 0.84661001}, "fBodyAcc-bandsEnergy()-41,48.1": {"min": -1.0, "max": 0.89774723}, "fBodyAcc-bandsEnergy()-49,56.1": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-57,64.1": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-1,16.1": {"min": -1.0, "max": 0.86657061}, "fBodyAcc-bandsEnergy()-17,32.1": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-33,48.1": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-49,64.1": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-1,24.1": {"min": -0.99999419, "max": 0.8210904}, "fBodyAcc-bandsEnergy()-25,48.1": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-1,8.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-9,16.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-17,24.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-25,32.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-33,40.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-41,48.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-49,56.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-57,64.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-1,16.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-17,32.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-33,48.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-49,64.2": {"min": -0.99999706, "max": 1.0}, "fBodyAcc-bandsEnergy()-1,24.2": {"min": -1.0, "max": 1.0}, "fBodyAcc-bandsEnergy()-25,48.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-mean()-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-mean()-Y": {"min": -1.0, "max": 0.64680948}, "fBodyAccJerk-mean()-Z": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-std()-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-std()-Y": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-std()-Z": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-mad()-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-mad()-Y": {"min": -1.0, "max": 0.80506356}, "fBodyAccJerk-mad()-Z": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-max()-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-max()-Y": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-max()-Z": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-min()-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-min()-Y": {"min": -0.99999774, "max": 1.0}, "fBodyAccJerk-min()-Z": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-sma()": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-energy()-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-energy()-Y": {"min": -1.0, "max": 0.63432019}, "fBodyAccJerk-energy()-Z": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-iqr()-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-iqr()-Y": {"min": -1.0, "max": 0.64132146}, "fBodyAccJerk-iqr()-Z": {"min": -0.9991623, "max": 1.0}, "fBodyAccJerk-entropy()-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-entropy()-Y": {"min": -1.0, "max": 0.99682502}, "fBodyAccJerk-entropy()-Z": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-maxInds-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-maxInds-Y": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-maxInds-Z": {"min": -1.0, "max": 0.96}, "fBodyAccJerk-meanFreq()-X": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-meanFreq()-Y": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-meanFreq()-Z": {"min": -1.0, "max": 0.67862794}, "fBodyAccJerk-skewness()-X": {"min": -1.0, "max": 0.82920557}, "fBodyAccJerk-kurtosis()-X": {"min": -1.0, "max": 0.66004197}, "fBodyAccJerk-skewness()-Y": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-kurtosis()-Y": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-skewness()-Z": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-kurtosis()-Z": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-1,8": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-9,16": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-17,24": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-25,32": {"min": -0.99999606, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-33,40": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-41,48": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-49,56": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-57,64": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-1,16": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-17,32": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-33,48": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-49,64": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-1,24": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-25,48": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-1,8.1": {"min": -1.0, "max": 0.70292796}, "fBodyAccJerk-bandsEnergy()-9,16.1": {"min": -1.0, "max": 0.89173636}, "fBodyAccJerk-bandsEnergy()-17,24.1": {"min": -1.0, "max": 0.90836103}, "fBodyAccJerk-bandsEnergy()-25,32.1": {"min": -1.0, "max": 0.88786954}, "fBodyAccJerk-bandsEnergy()-33,40.1": {"min": -1.0, "max": 0.63241538}, "fBodyAccJerk-bandsEnergy()-41,48.1": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-49,56.1": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-57,64.1": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-1,16.1": {"min": -1.0, "max": 0.89789843}, "fBodyAccJerk-bandsEnergy()-17,32.1": {"min": -1.0, "max": 0.9303172}, "fBodyAccJerk-bandsEnergy()-33,48.1": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-49,64.1": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-1,24.1": {"min": -1.0, "max": 0.91489223}, "fBodyAccJerk-bandsEnergy()-25,48.1": {"min": -1.0, "max": 0.80737469}, "fBodyAccJerk-bandsEnergy()-1,8.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-9,16.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-17,24.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-25,32.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-33,40.2": {"min": -0.99998949, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-41,48.2": {"min": -0.99999894, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-49,56.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-57,64.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-1,16.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-17,32.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-33,48.2": {"min": -0.99996684, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-49,64.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-1,24.2": {"min": -1.0, "max": 1.0}, "fBodyAccJerk-bandsEnergy()-25,48.2": {"min": -0.99999585, "max": 1.0}, "fBodyGyro-mean()-X": {"min": -1.0, "max": 1.0}, "fBodyGyro-mean()-Y": {"min": -1.0, "max": 1.0}, "fBodyGyro-mean()-Z": {"min": -1.0, "max": 1.0}, "fBodyGyro-std()-X": {"min": -1.0, "max": 1.0}, "fBodyGyro-std()-Y": {"min": -1.0, "max": 1.0}, "fBodyGyro-std()-Z": {"min": -1.0, "max": 1.0}, "fBodyGyro-mad()-X": {"min": -1.0, "max": 1.0}, "fBodyGyro-mad()-Y": {"min": -1.0, "max": 1.0}, "fBodyGyro-mad()-Z": {"min": -1.0, "max": 1.0}, "fBodyGyro-max()-X": {"min": -1.0, "max": 1.0}, "fBodyGyro-max()-Y": {"min": -1.0, "max": 1.0}, "fBodyGyro-max()-Z": {"min": -1.0, "max": 1.0}, "fBodyGyro-min()-X": {"min": -0.99999996, "max": 0.46519723}, "fBodyGyro-min()-Y": {"min": -0.9999967, "max": 0.79449806}, "fBodyGyro-min()-Z": {"min": -0.99999972, "max": 1.0}, "fBodyGyro-sma()": {"min": -1.0, "max": 1.0}, "fBodyGyro-energy()-X": {"min": -1.0, "max": 1.0}, "fBodyGyro-energy()-Y": {"min": -1.0, "max": 1.0}, "fBodyGyro-energy()-Z": {"min": -1.0, "max": 1.0}, "fBodyGyro-iqr()-X": {"min": -1.0, "max": 1.0}, "fBodyGyro-iqr()-Y": {"min": -1.0, "max": 1.0}, "fBodyGyro-iqr()-Z": {"min": -1.0, "max": 1.0}, "fBodyGyro-entropy()-X": {"min": -1.0, "max": 1.0}, "fBodyGyro-entropy()-Y": {"min": -1.0, "max": 1.0}, "fBodyGyro-entropy()-Z": {"min": -1.0, "max": 1.0}, "fBodyGyro-maxInds-X": {"min": -1.0, "max": 1.0}, "fBodyGyro-maxInds-Y": {"min": -1.0, "max": 0.80645161}, "fBodyGyro-maxInds-Z": {"min": -1.0, "max": 0.65517241}, "fBodyGyro-meanFreq()-X": {"min": -1.0, "max": 1.0}, "fBodyGyro-meanFreq()-Y": {"min": -1.0, "max": 0.99374103}, "fBodyGyro-meanFreq()-Z": {"min": -0.96634502, "max": 1.0}, "fBodyGyro-skewness()-X": {"min": -1.0, "max": 0.92089008}, "fBodyGyro-kurtosis()-X": {"min": -1.0, "max": 0.8944949}, "fBodyGyro-skewness()-Y": {"min": -1.0, "max": 0.98132937}, "fBodyGyro-kurtosis()-Y": {"min": -0.99821279, "max": 0.97760819}, "fBodyGyro-skewness()-Z": {"min": -0.90898061, "max": 0.9035711}, "fBodyGyro-kurtosis()-Z": {"min": -1.0, "max": 0.857113}, "fBodyGyro-bandsEnergy()-1,8": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-9,16": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-17,24": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-25,32": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-33,40": {"min": -0.99999991, "max": 1.0}, "fBodyGyro-bandsEnergy()-41,48": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-49,56": {"min": -0.99999967, "max": 1.0}, "fBodyGyro-bandsEnergy()-57,64": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-1,16": {"min": -0.99999982, "max": 1.0}, "fBodyGyro-bandsEnergy()-17,32": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-33,48": {"min": -0.99999696, "max": 1.0}, "fBodyGyro-bandsEnergy()-49,64": {"min": -0.99999982, "max": 1.0}, "fBodyGyro-bandsEnergy()-1,24": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-25,48": {"min": -0.99999832, "max": 1.0}, "fBodyGyro-bandsEnergy()-1,8.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-9,16.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-17,24.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-25,32.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-33,40.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-41,48.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-49,56.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-57,64.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-1,16.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-17,32.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-33,48.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-49,64.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-1,24.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-25,48.1": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-1,8.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-9,16.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-17,24.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-25,32.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-33,40.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-41,48.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-49,56.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-57,64.2": {"min": -0.99999999, "max": 1.0}, "fBodyGyro-bandsEnergy()-1,16.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-17,32.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-33,48.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-49,64.2": {"min": -1.0, "max": 1.0}, "fBodyGyro-bandsEnergy()-1,24.2": {"min": -0.99999974, "max": 1.0}, "fBodyGyro-bandsEnergy()-25,48.2": {"min": -1.0, "max": 1.0}, "fBodyAccMag-mean()": {"min": -0.99989278, "max": 1.0}, "fBodyAccMag-std()": {"min": -1.0, "max": 1.0}, "fBodyAccMag-mad()": {"min": -1.0, "max": 1.0}, "fBodyAccMag-max()": {"min": -1.0, "max": 1.0}, "fBodyAccMag-min()": {"min": -1.0, "max": 1.0}, "fBodyAccMag-sma()": {"min": -0.99989278, "max": 1.0}, "fBodyAccMag-energy()": {"min": -1.0, "max": 1.0}, "fBodyAccMag-iqr()": {"min": -1.0, "max": 1.0}, "fBodyAccMag-entropy()": {"min": -1.0, "max": 1.0}, "fBodyAccMag-maxInds": {"min": -1.0, "max": 1.0}, "fBodyAccMag-meanFreq()": {"min": -1.0, "max": 1.0}, "fBodyAccMag-skewness()": {"min": -1.0, "max": 1.0}, "fBodyAccMag-kurtosis()": {"min": -1.0, "max": 1.0}, "fBodyBodyAccJerkMag-mean()": {"min": -1.0, "max": 1.0}, "fBodyBodyAccJerkMag-std()": {"min": -0.99996007, "max": 1.0}, "fBodyBodyAccJerkMag-mad()": {"min": -0.9991519, "max": 1.0}, "fBodyBodyAccJerkMag-max()": {"min": -1.0, "max": 1.0}, "fBodyBodyAccJerkMag-min()": {"min": -1.0, "max": 1.0}, "fBodyBodyAccJerkMag-sma()": {"min": -1.0, "max": 1.0}, "fBodyBodyAccJerkMag-energy()": {"min": -1.0, "max": 1.0}, "fBodyBodyAccJerkMag-iqr()": {"min": -0.99949935, "max": 1.0}, "fBodyBodyAccJerkMag-entropy()": {"min": -1.0, "max": 1.0}, "fBodyBodyAccJerkMag-maxInds": {"min": -1.0, "max": 1.0}, "fBodyBodyAccJerkMag-meanFreq()": {"min": -1.0, "max": 0.97582069}, "fBodyBodyAccJerkMag-skewness()": {"min": -1.0, "max": 1.0}, "fBodyBodyAccJerkMag-kurtosis()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-mean()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-std()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-mad()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-max()": {"min": -1.0, "max": 0.84211941}, "fBodyBodyGyroMag-min()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-sma()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-energy()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-iqr()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-entropy()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-maxInds": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroMag-meanFreq()": {"min": -0.99750026, "max": 1.0}, "fBodyBodyGyroMag-skewness()": {"min": -1.0, "max": 0.96931086}, "fBodyBodyGyroMag-kurtosis()": {"min": -1.0, "max": 0.94935049}, "fBodyBodyGyroJerkMag-mean()": {"min": -0.99999636, "max": 1.0}, "fBodyBodyGyroJerkMag-std()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroJerkMag-mad()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroJerkMag-max()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroJerkMag-min()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroJerkMag-sma()": {"min": -0.99999636, "max": 1.0}, "fBodyBodyGyroJerkMag-energy()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroJerkMag-iqr()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroJerkMag-entropy()": {"min": -1.0, "max": 1.0}, "fBodyBodyGyroJerkMag-maxInds": {"min": -1.0, "max": 0.96825397}, "fBodyBodyGyroJerkMag-meanFreq()": {"min": -1.0, "max": 0.94669994}, "fBodyBodyGyroJerkMag-skewness()": {"min": -0.99535747, "max": 0.98953847}, "fBodyBodyGyroJerkMag-kurtosis()": {"min": -0.99976469, "max": 0.95684539}, "angle(tBodyAccMean,gravity)": {"min": -0.97658002, "max": 1.0}, "angle(tBodyAccJerkMean),gravityMean)": {"min": -1.0, "max": 1.0}, "angle(tBodyGyroMean,gravityMean)": {"min": -1.0, "max": 0.99870219}, "angle(tBodyGyroJerkMean,gravityMean)": {"min": -1.0, "max": 0.99607819}, "angle(X,gravityMean)": {"min": -1.0, "max": 1.0}, "angle(Y,gravityMean)": {"min": -1.0, "max": 0.47815733}, "angle(Z,gravityMean)": {"min": -1.0, "max": 1.0}}
|
src/phyphox_app_block.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Streamlit UI block for Tab 2 β Phyphox live sensor upload.
|
| 2 |
+
|
| 3 |
+
Call from streamlit_app.py:
|
| 4 |
+
|
| 5 |
+
from phyphox_app_block import render_phyphox_tab
|
| 6 |
+
with tab2:
|
| 7 |
+
render_phyphox_tab(ffn_model, ffn_status, cnn_model, cnn_status)
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
import os
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
import pandas as pd
|
| 15 |
+
import streamlit as st
|
| 16 |
+
|
| 17 |
+
from phyphox_pipeline import process_phyphox_files, FS, WINDOW, STEP
|
| 18 |
+
|
| 19 |
+
# ββ Constants βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 20 |
+
|
| 21 |
+
LABEL_MAP = {
|
| 22 |
+
0: "WALKING",
|
| 23 |
+
1: "WALKING_UPSTAIRS",
|
| 24 |
+
2: "WALKING_DOWNSTAIRS",
|
| 25 |
+
3: "SITTING",
|
| 26 |
+
4: "STANDING",
|
| 27 |
+
5: "LAYING",
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
EXPLANATIONS = {
|
| 31 |
+
"LAYING": "Minimal movement detected across all axes β consistent with a stationary horizontal posture.",
|
| 32 |
+
"SITTING": "Low dynamic acceleration with stable gravity β stationary upright posture.",
|
| 33 |
+
"STANDING": "Similar to sitting with slight postural micro-movements.",
|
| 34 |
+
"WALKING": "Rhythmic periodic acceleration on the vertical axis β level walking at normal cadence.",
|
| 35 |
+
"WALKING_DOWNSTAIRS": "Downward gravitational shift with higher impact peaks β descending stairs.",
|
| 36 |
+
"WALKING_UPSTAIRS": "Elevated vertical acceleration effort β climbing stairs.",
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
# ββ Normalisation βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 40 |
+
|
| 41 |
+
@st.cache_resource
|
| 42 |
+
def _load_norm_params(norm_path: str):
|
| 43 |
+
"""Load per-feature min/max from norm_params.json in features.txt order."""
|
| 44 |
+
with open(norm_path) as f:
|
| 45 |
+
d = json.load(f)
|
| 46 |
+
min_vals = np.array([v["min"] for v in d.values()], dtype=np.float32)
|
| 47 |
+
max_vals = np.array([v["max"] for v in d.values()], dtype=np.float32)
|
| 48 |
+
return min_vals, max_vals
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def _normalize(features: np.ndarray, min_vals: np.ndarray, max_vals: np.ndarray) -> np.ndarray:
|
| 52 |
+
"""Best-effort feature-level min-max scaling to [-1, 1].
|
| 53 |
+
|
| 54 |
+
Uses per-feature min/max observed in the UCI HAR training set. This is
|
| 55 |
+
an approximation β the UCI pipeline normalises raw signals before feature
|
| 56 |
+
extraction, so physical-unit features may fall outside the training range.
|
| 57 |
+
Values are clipped before scaling to keep outputs bounded.
|
| 58 |
+
"""
|
| 59 |
+
rng = max_vals - min_vals
|
| 60 |
+
rng = np.where(rng < 1e-8, 1.0, rng) # avoid div-by-zero
|
| 61 |
+
clipped = np.clip(features, min_vals, max_vals)
|
| 62 |
+
return 2.0 * (clipped - min_vals) / rng - 1.0
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
# ββ Public render function ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 66 |
+
|
| 67 |
+
def render_phyphox_tab(
|
| 68 |
+
ffn_model, ffn_status: str,
|
| 69 |
+
cnn_model, cnn_status: str,
|
| 70 |
+
norm_params_path: str,
|
| 71 |
+
) -> None:
|
| 72 |
+
st.subheader("Upload Phyphox sensor recording")
|
| 73 |
+
st.markdown("""
|
| 74 |
+
**How to record your own data:**
|
| 75 |
+
1. Install [Phyphox](https://phyphox.org/) on your phone
|
| 76 |
+
2. Open **Acceleration (without g)** and **Gyroscope** β record simultaneously
|
| 77 |
+
3. Hold the phone at your waist (same position as the UCI dataset)
|
| 78 |
+
4. Record at least 3 seconds of a single activity
|
| 79 |
+
5. Export both experiments as CSV and upload below
|
| 80 |
+
""")
|
| 81 |
+
|
| 82 |
+
col1, col2 = st.columns(2)
|
| 83 |
+
with col1:
|
| 84 |
+
acc_file = st.file_uploader(
|
| 85 |
+
"Accelerometer CSV",
|
| 86 |
+
type=["csv"],
|
| 87 |
+
key="acc_upload",
|
| 88 |
+
help="Columns: Time (s), X (m/sΒ²), Y (m/sΒ²), Z (m/sΒ²)",
|
| 89 |
+
)
|
| 90 |
+
with col2:
|
| 91 |
+
gyro_file = st.file_uploader(
|
| 92 |
+
"Gyroscope CSV",
|
| 93 |
+
type=["csv"],
|
| 94 |
+
key="gyro_upload",
|
| 95 |
+
help="Columns: Time (s), X (rad/s), Y (rad/s), Z (rad/s)",
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
if acc_file is None or gyro_file is None:
|
| 99 |
+
st.info("Upload both files to continue.")
|
| 100 |
+
return
|
| 101 |
+
|
| 102 |
+
# ββ Feature extraction ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 103 |
+
try:
|
| 104 |
+
with st.spinner("Extracting 561 features from sensor dataβ¦"):
|
| 105 |
+
features, pipeline_warnings = process_phyphox_files(acc_file, gyro_file)
|
| 106 |
+
except ValueError as err:
|
| 107 |
+
st.error(str(err))
|
| 108 |
+
return
|
| 109 |
+
except Exception as err:
|
| 110 |
+
st.error(f"Unexpected error during feature extraction: {err}")
|
| 111 |
+
return
|
| 112 |
+
|
| 113 |
+
for w in pipeline_warnings:
|
| 114 |
+
st.warning(w)
|
| 115 |
+
|
| 116 |
+
n_windows = len(features)
|
| 117 |
+
duration_s = (n_windows - 1) * (STEP / FS) + (WINDOW / FS)
|
| 118 |
+
|
| 119 |
+
c1, c2, c3 = st.columns(3)
|
| 120 |
+
c1.metric("Windows extracted", n_windows)
|
| 121 |
+
c2.metric("Approx. duration", f"{duration_s:.1f} s")
|
| 122 |
+
c3.metric("Features per window", 561)
|
| 123 |
+
st.caption(
|
| 124 |
+
f"Each window = {WINDOW / FS:.2f} s at {FS} Hz Β· "
|
| 125 |
+
f"50% overlap ({STEP / FS:.2f} s hop)"
|
| 126 |
+
)
|
| 127 |
+
|
| 128 |
+
# ββ Normalisation βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 129 |
+
if os.path.exists(norm_params_path):
|
| 130 |
+
min_vals, max_vals = _load_norm_params(norm_params_path)
|
| 131 |
+
features = _normalize(features, min_vals, max_vals)
|
| 132 |
+
st.caption(
|
| 133 |
+
"Features scaled to [β1, 1] using per-feature min/max from the UCI HAR "
|
| 134 |
+
"training set. Values outside the training range are clipped before scaling."
|
| 135 |
+
)
|
| 136 |
+
else:
|
| 137 |
+
st.warning(
|
| 138 |
+
"norm_params.json not found β features are in physical units. "
|
| 139 |
+
"Predictions will be unreliable until normalisation is applied."
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
# ββ Predictions βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 143 |
+
if ffn_status != "ready" and cnn_status != "ready":
|
| 144 |
+
st.warning("Models not loaded β cannot predict yet.")
|
| 145 |
+
return
|
| 146 |
+
|
| 147 |
+
st.markdown("---")
|
| 148 |
+
st.subheader("Model comparison")
|
| 149 |
+
|
| 150 |
+
left, right = st.columns(2)
|
| 151 |
+
|
| 152 |
+
def _render_model_col(col, model, status, name):
|
| 153 |
+
with col:
|
| 154 |
+
st.markdown(f"#### {name}")
|
| 155 |
+
if status != "ready":
|
| 156 |
+
st.error(f"Model not loaded β {status}")
|
| 157 |
+
return
|
| 158 |
+
|
| 159 |
+
probs_all = model.predict(features, verbose=0) # (n_windows, 6)
|
| 160 |
+
pred_labels = [LABEL_MAP[int(np.argmax(p))] for p in probs_all]
|
| 161 |
+
|
| 162 |
+
from collections import Counter
|
| 163 |
+
vote = Counter(pred_labels).most_common(1)[0][0]
|
| 164 |
+
avg_conf = float(np.mean(np.max(probs_all, axis=1))) * 100
|
| 165 |
+
|
| 166 |
+
st.success(f"**{vote}** Β· {avg_conf:.1f}% avg confidence")
|
| 167 |
+
st.markdown(f"_{EXPLANATIONS[vote]}_")
|
| 168 |
+
|
| 169 |
+
if n_windows > 1:
|
| 170 |
+
with st.expander(f"Per-window breakdown ({n_windows} windows)"):
|
| 171 |
+
rows = []
|
| 172 |
+
for i, (p, label) in enumerate(zip(probs_all, pred_labels)):
|
| 173 |
+
t_start = i * STEP / FS
|
| 174 |
+
rows.append({
|
| 175 |
+
"Window": i + 1,
|
| 176 |
+
"Time (s)": f"{t_start:.1f}β{t_start + WINDOW/FS:.1f}",
|
| 177 |
+
"Prediction": label,
|
| 178 |
+
"Confidence": f"{float(np.max(p))*100:.1f}%",
|
| 179 |
+
})
|
| 180 |
+
st.dataframe(pd.DataFrame(rows), use_container_width=True)
|
| 181 |
+
|
| 182 |
+
mean_probs = probs_all.mean(axis=0)
|
| 183 |
+
st.markdown("**Average confidence across all classes**")
|
| 184 |
+
st.bar_chart(pd.DataFrame(
|
| 185 |
+
{"Confidence (%)": [float(mean_probs[i]) * 100 for i in range(6)]},
|
| 186 |
+
index=[LABEL_MAP[i] for i in range(6)],
|
| 187 |
+
))
|
| 188 |
+
|
| 189 |
+
_render_model_col(left, ffn_model, ffn_status, "Feedforward Network")
|
| 190 |
+
_render_model_col(right, cnn_model, cnn_status, "1D Convolutional Network")
|
src/phyphox_pipeline.py
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Phyphox sensor pipeline for Human Activity Recognition.
|
| 2 |
+
|
| 3 |
+
Converts raw Phyphox accelerometer + gyroscope CSV exports into the
|
| 4 |
+
561-feature vector expected by the UCI HAR classifier.
|
| 5 |
+
|
| 6 |
+
Feature order matches features.txt exactly:
|
| 7 |
+
1-200 : time-domain 3-axis signals (5 signals Γ 40 features)
|
| 8 |
+
201-265 : time-domain magnitudes (5 signals Γ 13 features)
|
| 9 |
+
266-502 : frequency-domain 3-axis (3 signals Γ 79 features)
|
| 10 |
+
503-554 : frequency-domain magnitudes (4 signals Γ 13 features)
|
| 11 |
+
555-561 : angle features (7 features)
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
import io
|
| 15 |
+
import numpy as np
|
| 16 |
+
import pandas as pd
|
| 17 |
+
from scipy import signal as sp_signal
|
| 18 |
+
from scipy.stats import skew, kurtosis as sp_kurtosis
|
| 19 |
+
|
| 20 |
+
# ββ Constants βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 21 |
+
|
| 22 |
+
FS = 50 # target sampling rate Hz
|
| 23 |
+
WINDOW = 128 # samples per window (2.56 s)
|
| 24 |
+
STEP = 64 # hop size β 50% overlap
|
| 25 |
+
AR_ORDER = 4 # Burg AR model order
|
| 26 |
+
|
| 27 |
+
# 14 frequency band pairs (1-indexed, inclusive) applied per axis
|
| 28 |
+
BANDS = [
|
| 29 |
+
(1, 8), (9, 16), (17, 24), (25, 32), (33, 40), (41, 48),
|
| 30 |
+
(49, 56), (57, 64), (1, 16), (17, 32), (33, 48), (49, 64),
|
| 31 |
+
(1, 24), (25, 48),
|
| 32 |
+
]
|
| 33 |
+
|
| 34 |
+
ACC_COLS = ["Time (s)", "X (m/s^2)", "Y (m/s^2)", "Z (m/s^2)"]
|
| 35 |
+
GYRO_COLS = ["Time (s)", "X (rad/s)", "Y (rad/s)", "Z (rad/s)"]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
# ββ CSV helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 39 |
+
|
| 40 |
+
def _parse_csv(file_obj, expected_cols: list) -> pd.DataFrame:
|
| 41 |
+
"""Parse a Phyphox CSV export and validate required columns.
|
| 42 |
+
|
| 43 |
+
Args:
|
| 44 |
+
file_obj: file-like object (bytes or str) from Phyphox export
|
| 45 |
+
expected_cols: list of required column names
|
| 46 |
+
|
| 47 |
+
Returns:
|
| 48 |
+
DataFrame with numeric data, NaN rows dropped
|
| 49 |
+
|
| 50 |
+
Raises:
|
| 51 |
+
ValueError: if columns are missing or file cannot be parsed
|
| 52 |
+
"""
|
| 53 |
+
try:
|
| 54 |
+
raw = file_obj.read()
|
| 55 |
+
if isinstance(raw, bytes):
|
| 56 |
+
raw = raw.decode("utf-8")
|
| 57 |
+
df = pd.read_csv(io.StringIO(raw), float_precision="high")
|
| 58 |
+
except Exception as exc:
|
| 59 |
+
raise ValueError(f"Cannot parse CSV: {exc}") from exc
|
| 60 |
+
|
| 61 |
+
df.columns = [c.strip('"').strip() for c in df.columns]
|
| 62 |
+
missing = [c for c in expected_cols if c not in df.columns]
|
| 63 |
+
if missing:
|
| 64 |
+
raise ValueError(
|
| 65 |
+
f"Missing columns {missing}. Found: {list(df.columns)}. "
|
| 66 |
+
"Check you uploaded the correct Phyphox CSV (Accelerometer or Gyroscope)."
|
| 67 |
+
)
|
| 68 |
+
return df[expected_cols].apply(pd.to_numeric, errors="coerce").dropna()
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# ββ DSP helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 72 |
+
|
| 73 |
+
def _butter_lp(data: np.ndarray, cutoff: float, fs: float = FS, order: int = 3) -> np.ndarray:
|
| 74 |
+
"""Zero-phase Butterworth low-pass filter applied along axis 0.
|
| 75 |
+
|
| 76 |
+
Args:
|
| 77 |
+
data: 1-D or 2-D array
|
| 78 |
+
cutoff: cutoff frequency in Hz
|
| 79 |
+
fs: sampling rate in Hz
|
| 80 |
+
order: filter order
|
| 81 |
+
|
| 82 |
+
Returns:
|
| 83 |
+
Filtered array, same shape as input
|
| 84 |
+
"""
|
| 85 |
+
b, a = sp_signal.butter(order, cutoff / (fs / 2.0), btype="low")
|
| 86 |
+
if data.ndim == 1:
|
| 87 |
+
return sp_signal.filtfilt(b, a, data)
|
| 88 |
+
return np.column_stack(
|
| 89 |
+
[sp_signal.filtfilt(b, a, data[:, i]) for i in range(data.shape[1])]
|
| 90 |
+
)
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
def _median_filt(data: np.ndarray, k: int = 3) -> np.ndarray:
|
| 94 |
+
"""Median filter applied along axis 0.
|
| 95 |
+
|
| 96 |
+
Args:
|
| 97 |
+
data: 1-D or 2-D array
|
| 98 |
+
k: kernel size (must be odd)
|
| 99 |
+
|
| 100 |
+
Returns:
|
| 101 |
+
Filtered array, same shape as input
|
| 102 |
+
"""
|
| 103 |
+
if data.ndim == 1:
|
| 104 |
+
return sp_signal.medfilt(data, kernel_size=k)
|
| 105 |
+
return np.column_stack(
|
| 106 |
+
[sp_signal.medfilt(data[:, i], kernel_size=k) for i in range(data.shape[1])]
|
| 107 |
+
)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
def _burg_ar(x: np.ndarray, order: int = AR_ORDER) -> np.ndarray:
|
| 111 |
+
"""Burg method autoregressive coefficients.
|
| 112 |
+
|
| 113 |
+
Implements the standard Burg recursion with Levinson-Durbin update.
|
| 114 |
+
Mean-centres the signal before fitting.
|
| 115 |
+
|
| 116 |
+
Args:
|
| 117 |
+
x: 1-D signal array
|
| 118 |
+
order: AR model order
|
| 119 |
+
|
| 120 |
+
Returns:
|
| 121 |
+
Array of `order` AR coefficients [a1, a2, ..., ap]
|
| 122 |
+
"""
|
| 123 |
+
x = np.asarray(x, dtype=np.float64)
|
| 124 |
+
x = x - x.mean()
|
| 125 |
+
N = len(x)
|
| 126 |
+
ef = x.copy()
|
| 127 |
+
eb = x.copy()
|
| 128 |
+
a = np.zeros(order)
|
| 129 |
+
|
| 130 |
+
for m in range(1, order + 1):
|
| 131 |
+
f = ef[m:].copy()
|
| 132 |
+
b = eb[m - 1: N - 1].copy()
|
| 133 |
+
|
| 134 |
+
denom = np.dot(f, f) + np.dot(b, b) + 1e-12
|
| 135 |
+
km = -2.0 * np.dot(f, b) / denom
|
| 136 |
+
|
| 137 |
+
# Levinson-Durbin update of AR polynomial
|
| 138 |
+
a_prev = a[:m - 1].copy()
|
| 139 |
+
for j in range(m - 1):
|
| 140 |
+
a[j] = a_prev[j] + km * a_prev[m - 2 - j]
|
| 141 |
+
a[m - 1] = km
|
| 142 |
+
|
| 143 |
+
# Update forward/backward prediction errors
|
| 144 |
+
ef[m:] = f + km * b
|
| 145 |
+
eb[m - 1: N - 1] = b + km * f
|
| 146 |
+
|
| 147 |
+
return a
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def _entropy(x: np.ndarray) -> float:
|
| 151 |
+
"""Normalised signal entropy via absolute-value probability distribution.
|
| 152 |
+
|
| 153 |
+
Args:
|
| 154 |
+
x: 1-D array
|
| 155 |
+
|
| 156 |
+
Returns:
|
| 157 |
+
Entropy value (>= 0)
|
| 158 |
+
"""
|
| 159 |
+
total = np.abs(x).sum()
|
| 160 |
+
if total < 1e-12:
|
| 161 |
+
return 0.0
|
| 162 |
+
p = np.abs(x) / total
|
| 163 |
+
p = p[p > 0]
|
| 164 |
+
return float(-np.sum(p * np.log(p)))
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def _bands_energy(fft_mag: np.ndarray) -> np.ndarray:
|
| 168 |
+
"""Energy in each of the 14 UCI HAR frequency bands (1-indexed, inclusive).
|
| 169 |
+
|
| 170 |
+
Args:
|
| 171 |
+
fft_mag: FFT magnitude array, must contain at least 64 values
|
| 172 |
+
|
| 173 |
+
Returns:
|
| 174 |
+
Array of 14 energy values
|
| 175 |
+
"""
|
| 176 |
+
m = fft_mag[:64]
|
| 177 |
+
return np.array([float(np.sum(m[s - 1: e] ** 2)) for s, e in BANDS])
|
| 178 |
+
|
| 179 |
+
|
| 180 |
+
def _safe_corr(a: np.ndarray, b: np.ndarray) -> float:
|
| 181 |
+
"""Pearson correlation, returns 0.0 if either signal is constant.
|
| 182 |
+
|
| 183 |
+
Args:
|
| 184 |
+
a: first 1-D array
|
| 185 |
+
b: second 1-D array
|
| 186 |
+
|
| 187 |
+
Returns:
|
| 188 |
+
Correlation coefficient in [-1, 1]
|
| 189 |
+
"""
|
| 190 |
+
if a.std() < 1e-10 or b.std() < 1e-10:
|
| 191 |
+
return 0.0
|
| 192 |
+
r = np.corrcoef(a, b)[0, 1]
|
| 193 |
+
return 0.0 if not np.isfinite(r) else float(r)
|
| 194 |
+
|
| 195 |
+
|
| 196 |
+
def _angle(u: np.ndarray, v: np.ndarray) -> float:
|
| 197 |
+
"""Angle in radians between two 3-D vectors.
|
| 198 |
+
|
| 199 |
+
Args:
|
| 200 |
+
u: first vector, shape (3,)
|
| 201 |
+
v: second vector, shape (3,)
|
| 202 |
+
|
| 203 |
+
Returns:
|
| 204 |
+
Angle in radians, or 0.0 if either vector is zero
|
| 205 |
+
"""
|
| 206 |
+
un, vn = np.linalg.norm(u), np.linalg.norm(v)
|
| 207 |
+
if un < 1e-10 or vn < 1e-10:
|
| 208 |
+
return 0.0
|
| 209 |
+
return float(np.arccos(np.clip(np.dot(u, v) / (un * vn), -1.0, 1.0)))
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
# ββ Feature extractors ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 213 |
+
|
| 214 |
+
def _t3ax(sig: np.ndarray) -> np.ndarray:
|
| 215 |
+
"""40 time-domain features from a 3-axis signal (N, 3).
|
| 216 |
+
|
| 217 |
+
Order: meanΓ3, stdΓ3, madΓ3, maxΓ3, minΓ3, sma,
|
| 218 |
+
energyΓ3, iqrΓ3, entropyΓ3, arCoeffΓ12, correlationΓ3
|
| 219 |
+
"""
|
| 220 |
+
N = len(sig)
|
| 221 |
+
x, y, z = sig[:, 0], sig[:, 1], sig[:, 2]
|
| 222 |
+
out = []
|
| 223 |
+
|
| 224 |
+
out += [x.mean(), y.mean(), z.mean()]
|
| 225 |
+
out += [x.std(), y.std(), z.std()]
|
| 226 |
+
out += [
|
| 227 |
+
float(np.median(np.abs(x - np.median(x)))),
|
| 228 |
+
float(np.median(np.abs(y - np.median(y)))),
|
| 229 |
+
float(np.median(np.abs(z - np.median(z)))),
|
| 230 |
+
]
|
| 231 |
+
out += [x.max(), y.max(), z.max()]
|
| 232 |
+
out += [x.min(), y.min(), z.min()]
|
| 233 |
+
out += [float((np.abs(x) + np.abs(y) + np.abs(z)).sum() / N)] # sma
|
| 234 |
+
out += [float(np.sum(x ** 2) / N), float(np.sum(y ** 2) / N), float(np.sum(z ** 2) / N)]
|
| 235 |
+
out += [
|
| 236 |
+
float(np.percentile(x, 75) - np.percentile(x, 25)),
|
| 237 |
+
float(np.percentile(y, 75) - np.percentile(y, 25)),
|
| 238 |
+
float(np.percentile(z, 75) - np.percentile(z, 25)),
|
| 239 |
+
]
|
| 240 |
+
out += [_entropy(x), _entropy(y), _entropy(z)]
|
| 241 |
+
out += _burg_ar(x).tolist()
|
| 242 |
+
out += _burg_ar(y).tolist()
|
| 243 |
+
out += _burg_ar(z).tolist()
|
| 244 |
+
out += [_safe_corr(x, y), _safe_corr(x, z), _safe_corr(y, z)]
|
| 245 |
+
|
| 246 |
+
return np.array(out, dtype=np.float64) # 40 values
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
def _tmag(sig: np.ndarray) -> np.ndarray:
|
| 250 |
+
"""13 time-domain features from a 1-D magnitude signal.
|
| 251 |
+
|
| 252 |
+
Order: mean, std, mad, max, min, sma, energy, iqr, entropy, arCoeffΓ4
|
| 253 |
+
"""
|
| 254 |
+
N = len(sig)
|
| 255 |
+
out = [
|
| 256 |
+
float(sig.mean()),
|
| 257 |
+
float(sig.std()),
|
| 258 |
+
float(np.median(np.abs(sig - np.median(sig)))),
|
| 259 |
+
float(sig.max()),
|
| 260 |
+
float(sig.min()),
|
| 261 |
+
float(np.abs(sig).sum() / N), # sma (1-D)
|
| 262 |
+
float(np.sum(sig ** 2) / N), # energy
|
| 263 |
+
float(np.percentile(sig, 75) - np.percentile(sig, 25)),
|
| 264 |
+
_entropy(sig),
|
| 265 |
+
]
|
| 266 |
+
out += _burg_ar(sig).tolist()
|
| 267 |
+
return np.array(out, dtype=np.float64) # 13 values
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def _f3ax(sig: np.ndarray) -> np.ndarray:
|
| 271 |
+
"""79 frequency-domain features from a 3-axis signal (N, 3).
|
| 272 |
+
|
| 273 |
+
Order: meanΓ3, stdΓ3, madΓ3, maxΓ3, minΓ3, sma,
|
| 274 |
+
energyΓ3, iqrΓ3, entropyΓ3,
|
| 275 |
+
maxIndsΓ3, meanFreqΓ3,
|
| 276 |
+
(skewness, kurtosis)Γ3 interleaved,
|
| 277 |
+
bandsEnergyΓ14 per axis (Γ3 axes = 42)
|
| 278 |
+
"""
|
| 279 |
+
x, y, z = sig[:, 0], sig[:, 1], sig[:, 2]
|
| 280 |
+
|
| 281 |
+
def _fft(s):
|
| 282 |
+
return np.abs(np.fft.rfft(s))[:64]
|
| 283 |
+
|
| 284 |
+
fx, fy, fz = _fft(x), _fft(y), _fft(z)
|
| 285 |
+
bins = np.arange(1, 65, dtype=np.float64) # 1-indexed bin numbers
|
| 286 |
+
|
| 287 |
+
def _mfreq(fm):
|
| 288 |
+
t = fm.sum()
|
| 289 |
+
return float(np.dot(bins[:len(fm)], fm) / t) if t > 1e-12 else 0.0
|
| 290 |
+
|
| 291 |
+
def _maxinds(fm):
|
| 292 |
+
return float(np.argmax(fm) + 1) # 1-indexed
|
| 293 |
+
|
| 294 |
+
out = []
|
| 295 |
+
out += [fx.mean(), fy.mean(), fz.mean()]
|
| 296 |
+
out += [fx.std(), fy.std(), fz.std()]
|
| 297 |
+
out += [
|
| 298 |
+
float(np.median(np.abs(fx - np.median(fx)))),
|
| 299 |
+
float(np.median(np.abs(fy - np.median(fy)))),
|
| 300 |
+
float(np.median(np.abs(fz - np.median(fz)))),
|
| 301 |
+
]
|
| 302 |
+
out += [fx.max(), fy.max(), fz.max()]
|
| 303 |
+
out += [fx.min(), fy.min(), fz.min()]
|
| 304 |
+
n = len(fx)
|
| 305 |
+
out += [float((fx + fy + fz).sum() / n)] # sma of FFT mags
|
| 306 |
+
out += [float(np.sum(fx ** 2) / n), float(np.sum(fy ** 2) / n), float(np.sum(fz ** 2) / n)]
|
| 307 |
+
out += [
|
| 308 |
+
float(np.percentile(fx, 75) - np.percentile(fx, 25)),
|
| 309 |
+
float(np.percentile(fy, 75) - np.percentile(fy, 25)),
|
| 310 |
+
float(np.percentile(fz, 75) - np.percentile(fz, 25)),
|
| 311 |
+
]
|
| 312 |
+
out += [_entropy(fx), _entropy(fy), _entropy(fz)]
|
| 313 |
+
out += [_maxinds(fx), _maxinds(fy), _maxinds(fz)]
|
| 314 |
+
out += [_mfreq(fx), _mfreq(fy), _mfreq(fz)]
|
| 315 |
+
# skewness/kurtosis interleaved per axis (skX,kurX, skY,kurY, skZ,kurZ)
|
| 316 |
+
out += [float(skew(fx)), float(sp_kurtosis(fx))]
|
| 317 |
+
out += [float(skew(fy)), float(sp_kurtosis(fy))]
|
| 318 |
+
out += [float(skew(fz)), float(sp_kurtosis(fz))]
|
| 319 |
+
out += _bands_energy(fx).tolist()
|
| 320 |
+
out += _bands_energy(fy).tolist()
|
| 321 |
+
out += _bands_energy(fz).tolist()
|
| 322 |
+
|
| 323 |
+
return np.array(out, dtype=np.float64) # 79 values
|
| 324 |
+
|
| 325 |
+
|
| 326 |
+
def _fmag(sig: np.ndarray) -> np.ndarray:
|
| 327 |
+
"""13 frequency-domain features from a 1-D magnitude signal.
|
| 328 |
+
|
| 329 |
+
Order: mean, std, mad, max, min, sma, energy, iqr, entropy,
|
| 330 |
+
maxInds, meanFreq, skewness, kurtosis
|
| 331 |
+
"""
|
| 332 |
+
fm = np.abs(np.fft.rfft(sig))[:64]
|
| 333 |
+
n = len(fm)
|
| 334 |
+
bins = np.arange(1, n + 1, dtype=np.float64)
|
| 335 |
+
total = fm.sum()
|
| 336 |
+
out = [
|
| 337 |
+
float(fm.mean()),
|
| 338 |
+
float(fm.std()),
|
| 339 |
+
float(np.median(np.abs(fm - np.median(fm)))),
|
| 340 |
+
float(fm.max()),
|
| 341 |
+
float(fm.min()),
|
| 342 |
+
float(np.abs(fm).sum() / n),
|
| 343 |
+
float(np.sum(fm ** 2) / n),
|
| 344 |
+
float(np.percentile(fm, 75) - np.percentile(fm, 25)),
|
| 345 |
+
_entropy(fm),
|
| 346 |
+
float(np.argmax(fm) + 1), # maxInds (1-indexed)
|
| 347 |
+
float(np.dot(bins, fm) / total) if total > 1e-12 else 0.0, # meanFreq
|
| 348 |
+
float(skew(fm)),
|
| 349 |
+
float(sp_kurtosis(fm)),
|
| 350 |
+
]
|
| 351 |
+
return np.array(out, dtype=np.float64) # 13 values
|
| 352 |
+
|
| 353 |
+
|
| 354 |
+
def _window_features(
|
| 355 |
+
body_acc: np.ndarray,
|
| 356 |
+
grav_acc: np.ndarray,
|
| 357 |
+
body_jerk: np.ndarray,
|
| 358 |
+
gyro: np.ndarray,
|
| 359 |
+
gyro_jerk: np.ndarray,
|
| 360 |
+
) -> np.ndarray:
|
| 361 |
+
"""Extract all 561 features from one pre-processed window.
|
| 362 |
+
|
| 363 |
+
Args:
|
| 364 |
+
body_acc: body linear acceleration (128, 3) m/sΒ²
|
| 365 |
+
grav_acc: gravity component (128, 3) m/sΒ²
|
| 366 |
+
body_jerk: body jerk (127, 3) m/sΒ³
|
| 367 |
+
gyro: angular velocity (128, 3) rad/s
|
| 368 |
+
gyro_jerk: gyro jerk (127, 3) rad/sΒ²
|
| 369 |
+
|
| 370 |
+
Returns:
|
| 371 |
+
1-D array of 561 features
|
| 372 |
+
"""
|
| 373 |
+
# Magnitudes
|
| 374 |
+
ba_mag = np.linalg.norm(body_acc, axis=1)
|
| 375 |
+
ga_mag = np.linalg.norm(grav_acc, axis=1)
|
| 376 |
+
bj_mag = np.linalg.norm(body_jerk, axis=1)
|
| 377 |
+
gy_mag = np.linalg.norm(gyro, axis=1)
|
| 378 |
+
gj_mag = np.linalg.norm(gyro_jerk, axis=1)
|
| 379 |
+
|
| 380 |
+
parts = []
|
| 381 |
+
|
| 382 |
+
# Time 3-axis (5 Γ 40 = 200)
|
| 383 |
+
for sig in [body_acc, grav_acc, body_jerk, gyro, gyro_jerk]:
|
| 384 |
+
parts.append(_t3ax(sig))
|
| 385 |
+
|
| 386 |
+
# Time magnitudes (5 Γ 13 = 65)
|
| 387 |
+
for mag in [ba_mag, ga_mag, bj_mag, gy_mag, gj_mag]:
|
| 388 |
+
parts.append(_tmag(mag))
|
| 389 |
+
|
| 390 |
+
# Freq 3-axis (3 Γ 79 = 237)
|
| 391 |
+
for sig in [body_acc, body_jerk, gyro]:
|
| 392 |
+
parts.append(_f3ax(sig))
|
| 393 |
+
|
| 394 |
+
# Freq magnitudes (4 Γ 13 = 52)
|
| 395 |
+
for mag in [ba_mag, bj_mag, gy_mag, gj_mag]:
|
| 396 |
+
parts.append(_fmag(mag))
|
| 397 |
+
|
| 398 |
+
# Angle features (7)
|
| 399 |
+
ba_mean = body_acc.mean(axis=0)
|
| 400 |
+
ga_mean = grav_acc.mean(axis=0)
|
| 401 |
+
bj_mean = body_jerk.mean(axis=0)
|
| 402 |
+
gy_mean = gyro.mean(axis=0)
|
| 403 |
+
gj_mean = gyro_jerk.mean(axis=0)
|
| 404 |
+
|
| 405 |
+
parts.append(np.array([
|
| 406 |
+
_angle(ba_mean, ga_mean),
|
| 407 |
+
_angle(bj_mean, ga_mean),
|
| 408 |
+
_angle(gy_mean, ga_mean),
|
| 409 |
+
_angle(gj_mean, ga_mean),
|
| 410 |
+
_angle(np.array([1.0, 0.0, 0.0]), ga_mean),
|
| 411 |
+
_angle(np.array([0.0, 1.0, 0.0]), ga_mean),
|
| 412 |
+
_angle(np.array([0.0, 0.0, 1.0]), ga_mean),
|
| 413 |
+
]))
|
| 414 |
+
|
| 415 |
+
result = np.concatenate(parts)
|
| 416 |
+
assert result.shape == (561,), f"Feature count error: got {result.shape[0]}, expected 561"
|
| 417 |
+
return result
|
| 418 |
+
|
| 419 |
+
|
| 420 |
+
# ββ Public API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 421 |
+
|
| 422 |
+
def process_phyphox_files(
|
| 423 |
+
acc_file,
|
| 424 |
+
gyro_file,
|
| 425 |
+
) -> tuple:
|
| 426 |
+
"""Convert Phyphox CSV exports to (n_windows, 561) feature array.
|
| 427 |
+
|
| 428 |
+
Pipeline:
|
| 429 |
+
1. Parse + validate both CSVs
|
| 430 |
+
2. Interpolate onto common 50 Hz grid
|
| 431 |
+
3. Segment: 128-sample windows, 64-sample hop (50% overlap)
|
| 432 |
+
4. Per window: median filter β 20 Hz Butterworth β gravity separation
|
| 433 |
+
at 0.3 Hz β jerk β magnitudes β 561 features
|
| 434 |
+
|
| 435 |
+
Args:
|
| 436 |
+
acc_file: file-like object β Phyphox Accelerometer CSV
|
| 437 |
+
(columns: Time (s), X (m/s^2), Y (m/s^2), Z (m/s^2))
|
| 438 |
+
gyro_file: file-like object β Phyphox Gyroscope CSV
|
| 439 |
+
(columns: Time (s), X (rad/s), Y (rad/s), Z (rad/s))
|
| 440 |
+
|
| 441 |
+
Returns:
|
| 442 |
+
Tuple of:
|
| 443 |
+
np.ndarray shape (n_windows, 561) οΏ½οΏ½ raw (un-normalised) features
|
| 444 |
+
list[str] β warning messages
|
| 445 |
+
|
| 446 |
+
Raises:
|
| 447 |
+
ValueError: invalid format, wrong columns, or < 3 s of data
|
| 448 |
+
"""
|
| 449 |
+
warnings: list = []
|
| 450 |
+
|
| 451 |
+
acc_df = _parse_csv(acc_file, ACC_COLS)
|
| 452 |
+
gyro_df = _parse_csv(gyro_file, GYRO_COLS)
|
| 453 |
+
|
| 454 |
+
acc_t = acc_df["Time (s)"].values
|
| 455 |
+
acc_xyz = acc_df[["X (m/s^2)", "Y (m/s^2)", "Z (m/s^2)"]].values
|
| 456 |
+
gyro_t = gyro_df["Time (s)"].values
|
| 457 |
+
gyro_xyz = gyro_df[["X (rad/s)", "Y (rad/s)", "Z (rad/s)"]].values
|
| 458 |
+
|
| 459 |
+
# Common time window
|
| 460 |
+
t0 = max(acc_t[0], gyro_t[0])
|
| 461 |
+
t1 = min(acc_t[-1], gyro_t[-1])
|
| 462 |
+
duration = t1 - t0
|
| 463 |
+
|
| 464 |
+
if duration < 3.0:
|
| 465 |
+
raise ValueError(
|
| 466 |
+
f"Recording is {duration:.2f} s β minimum 3 seconds required. "
|
| 467 |
+
"Hold the phone still or walk for at least 3 seconds before exporting."
|
| 468 |
+
)
|
| 469 |
+
|
| 470 |
+
t_grid = np.arange(t0, t1, 1.0 / FS)
|
| 471 |
+
am = (acc_t >= t0) & (acc_t <= t1)
|
| 472 |
+
gm = (gyro_t >= t0) & (gyro_t <= t1)
|
| 473 |
+
|
| 474 |
+
acc_50 = np.column_stack(
|
| 475 |
+
[np.interp(t_grid, acc_t[am], acc_xyz[am, i]) for i in range(3)]
|
| 476 |
+
)
|
| 477 |
+
gyro_50 = np.column_stack(
|
| 478 |
+
[np.interp(t_grid, gyro_t[gm], gyro_xyz[gm, i]) for i in range(3)]
|
| 479 |
+
)
|
| 480 |
+
|
| 481 |
+
n = min(len(acc_50), len(gyro_50))
|
| 482 |
+
acc_50, gyro_50 = acc_50[:n], gyro_50[:n]
|
| 483 |
+
|
| 484 |
+
n_windows = max(0, (n - WINDOW) // STEP + 1)
|
| 485 |
+
if n_windows == 0:
|
| 486 |
+
raise ValueError(
|
| 487 |
+
f"Only {n} samples ({n / FS:.1f} s) after alignment β "
|
| 488 |
+
f"need at least {WINDOW} samples ({WINDOW / FS:.1f} s)."
|
| 489 |
+
)
|
| 490 |
+
|
| 491 |
+
if duration > 60:
|
| 492 |
+
warnings.append(f"Long recording ({duration:.0f} s) β {n_windows} windows extracted.")
|
| 493 |
+
|
| 494 |
+
all_features = []
|
| 495 |
+
dt = 1.0 / FS
|
| 496 |
+
|
| 497 |
+
for start in range(0, n - WINDOW + 1, STEP):
|
| 498 |
+
end = start + WINDOW
|
| 499 |
+
aw = acc_50[start:end] # (128, 3)
|
| 500 |
+
gw = gyro_50[start:end] # (128, 3)
|
| 501 |
+
|
| 502 |
+
# 1. Noise: median filter then 3rd-order Butterworth LP at 20 Hz
|
| 503 |
+
aw = _butter_lp(_median_filt(aw), cutoff=20.0)
|
| 504 |
+
gw = _butter_lp(_median_filt(gw), cutoff=20.0)
|
| 505 |
+
|
| 506 |
+
# 2. Gravity separation: LP at 0.3 Hz
|
| 507 |
+
grav = _butter_lp(aw, cutoff=0.3)
|
| 508 |
+
body = aw - grav
|
| 509 |
+
|
| 510 |
+
# 3. Jerk: finite difference β (127, 3)
|
| 511 |
+
body_jerk = np.diff(body, axis=0) / dt
|
| 512 |
+
gyro_jerk = np.diff(gw, axis=0) / dt
|
| 513 |
+
|
| 514 |
+
all_features.append(_window_features(body, grav, body_jerk, gw, gyro_jerk))
|
| 515 |
+
|
| 516 |
+
return np.array(all_features), warnings
|
src/streamlit_app.py
CHANGED
|
@@ -7,6 +7,7 @@ import pandas as pd
|
|
| 7 |
_SRC_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 8 |
_REPO_ROOT = os.path.dirname(_SRC_DIR)
|
| 9 |
_SAMPLES_PATH = os.path.join(_REPO_ROOT, "data", "samples.csv")
|
|
|
|
| 10 |
|
| 11 |
# ββ Constants ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 12 |
|
|
@@ -193,36 +194,8 @@ with tab1:
|
|
| 193 |
except FileNotFoundError:
|
| 194 |
st.error("Sample data file not found. Add `data/samples.csv` to the repo.")
|
| 195 |
|
| 196 |
-
# ββ Tab 2: Phyphox upload
|
| 197 |
|
| 198 |
with tab2:
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
**How to record your own data:**
|
| 202 |
-
1. Install [Phyphox](https://phyphox.org/) on your phone
|
| 203 |
-
2. Open the **Acceleration (without g)** and **Gyroscope** experiments
|
| 204 |
-
3. Record at least 3 seconds of a single activity
|
| 205 |
-
4. Export as CSV and upload below
|
| 206 |
-
""")
|
| 207 |
-
|
| 208 |
-
uploaded_file = st.file_uploader(
|
| 209 |
-
"Upload Phyphox CSV export",
|
| 210 |
-
type=["csv"],
|
| 211 |
-
help="Export from Phyphox as CSV β must contain accelerometer and gyroscope columns"
|
| 212 |
-
)
|
| 213 |
-
|
| 214 |
-
if uploaded_file is not None:
|
| 215 |
-
st.info(
|
| 216 |
-
"Phyphox pipeline coming soon. "
|
| 217 |
-
"Feature extraction from raw sensor readings "
|
| 218 |
-
"(filtering β jerk β FFT β 561 features) is under development."
|
| 219 |
-
)
|
| 220 |
-
try:
|
| 221 |
-
preview = pd.read_csv(uploaded_file)
|
| 222 |
-
st.markdown("**File preview:**")
|
| 223 |
-
st.dataframe(preview.head(10))
|
| 224 |
-
st.caption(
|
| 225 |
-
f"{len(preview)} rows Β· {len(preview.columns)} columns detected"
|
| 226 |
-
)
|
| 227 |
-
except Exception as e:
|
| 228 |
-
st.error(f"Could not read file: {e}")
|
|
|
|
| 7 |
_SRC_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 8 |
_REPO_ROOT = os.path.dirname(_SRC_DIR)
|
| 9 |
_SAMPLES_PATH = os.path.join(_REPO_ROOT, "data", "samples.csv")
|
| 10 |
+
_NORM_PATH = os.path.join(_REPO_ROOT, "data", "norm_params.json")
|
| 11 |
|
| 12 |
# ββ Constants ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 13 |
|
|
|
|
| 194 |
except FileNotFoundError:
|
| 195 |
st.error("Sample data file not found. Add `data/samples.csv` to the repo.")
|
| 196 |
|
| 197 |
+
# ββ Tab 2: Phyphox upload βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 198 |
|
| 199 |
with tab2:
|
| 200 |
+
from phyphox_app_block import render_phyphox_tab
|
| 201 |
+
render_phyphox_tab(ffn_model, ffn_status, cnn_model, cnn_status, _NORM_PATH)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|