Fola-lad commited on
Commit
a65288a
Β·
1 Parent(s): ef6be14

working placeholder app with sample selector and phyphox tab

Browse files
Files changed (4) hide show
  1. README.md +10 -12
  2. data/samples.csv +0 -0
  3. requirements.txt +4 -2
  4. src/streamlit_app.py +201 -37
README.md CHANGED
@@ -1,19 +1,17 @@
1
  ---
2
- title: ActRecog
3
- emoji: πŸš€
4
- colorFrom: red
5
- colorTo: red
6
  sdk: docker
7
- app_port: 8501
8
- tags:
9
- - streamlit
10
  pinned: false
11
- short_description: Streamlit template space
12
  ---
13
 
14
- # Welcome to Streamlit!
15
 
16
- Edit `/src/streamlit_app.py` to customize this app to your heart's desire. :heart:
 
 
17
 
18
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
19
- forums](https://discuss.streamlit.io).
 
1
  ---
2
+ title: Human Activity Recognition
3
+ emoji: πŸƒ
4
+ colorFrom: blue
5
+ colorTo: green
6
  sdk: docker
7
+ app_file: src/streamlit_app.py
 
 
8
  pinned: false
 
9
  ---
10
 
11
+ ## Human Activity Recognition
12
 
13
+ 6-class activity classifier using 561 smartphone sensor features
14
+ from the UCI HAR dataset. Built for DAT606 β€” Advanced Machine Learning,
15
+ Pan-Atlantic University.
16
 
17
+ **Activities:** LAYING Β· SITTING Β· STANDING Β· WALKING Β· WALKING_DOWNSTAIRS Β· WALKING_UPSTAIRS
 
data/samples.csv ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt CHANGED
@@ -1,3 +1,5 @@
1
- altair
2
  pandas
3
- streamlit
 
 
 
1
+ streamlit
2
  pandas
3
+ numpy
4
+ tensorflow-cpu==2.16.2
5
+ scipy
src/streamlit_app.py CHANGED
@@ -1,40 +1,204 @@
1
- import altair as alt
2
  import numpy as np
3
  import pandas as pd
4
- import streamlit as st
5
 
6
- """
7
- # Welcome to Streamlit!
8
-
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
12
-
13
- In the meantime, below is an example of what you can do with just a few lines of code:
14
- """
15
-
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
  import numpy as np
3
  import pandas as pd
 
4
 
5
+ # ── Constants ──────────────────────────────────────────────────────────────
6
+
7
+ LABEL_MAP = {
8
+ 0: "LAYING",
9
+ 1: "SITTING",
10
+ 2: "STANDING",
11
+ 3: "WALKING",
12
+ 4: "WALKING_DOWNSTAIRS",
13
+ 5: "WALKING_UPSTAIRS",
14
+ }
15
+
16
+ ACTIVITY_ICONS = {
17
+ "LAYING": "πŸ›οΈ",
18
+ "SITTING": "πŸͺ‘",
19
+ "STANDING": "🧍",
20
+ "WALKING": "🚢",
21
+ "WALKING_DOWNSTAIRS": "⬇️",
22
+ "WALKING_UPSTAIRS": "⬆️",
23
+ }
24
+
25
+ EXPLANATIONS = {
26
+ "LAYING": "Minimal movement detected across all axes with low acceleration magnitude β€” consistent with a stationary horizontal posture.",
27
+ "SITTING": "Low dynamic acceleration with a stable gravity component suggests a stationary upright posture with little body movement.",
28
+ "STANDING": "Similar to sitting but with slight postural micro-movements. This class is often the hardest to distinguish from sitting.",
29
+ "WALKING": "Rhythmic periodic acceleration with peaks on the vertical axis β€” consistent with level walking at normal cadence.",
30
+ "WALKING_DOWNSTAIRS": "Downward gravitational shift with higher impact peaks characteristic of descending a staircase.",
31
+ "WALKING_UPSTAIRS": "Elevated vertical acceleration effort with upward body displacement β€” consistent with climbing stairs.",
32
+ }
33
+
34
+ # ── Model loader ────────────────────────────────────────────────────────────
35
+
36
+ @st.cache_resource
37
+ def load_model():
38
+ try:
39
+ import tensorflow as tf
40
+ model = tf.keras.models.load_model("model.keras")
41
+ return model
42
+ except Exception:
43
+ return None
44
+
45
+ # ── Page config ─────────────────────────────────────────────────────────────
46
+
47
+ st.set_page_config(
48
+ page_title="Human Activity Recognition",
49
+ page_icon="πŸƒ",
50
+ layout="centered"
51
+ )
52
+
53
+ st.title("πŸƒ Human Activity Recognition")
54
+ st.markdown(
55
+ "Deep learning classifier trained on 561 smartphone sensor features "
56
+ "from the [UCI HAR dataset](https://www.kaggle.com/datasets/uciml/human-activity-recognition-with-smartphones). "
57
+ "Classifies six daily activities from accelerometer and gyroscope readings."
58
+ )
59
+
60
+ # ── Sidebar ──────────────────────────────────────────────────────────────────
61
+
62
+ with st.sidebar:
63
+ st.header("About")
64
+ st.markdown("""
65
+ **Dataset:** UCI Human Activity Recognition
66
+ **Subjects:** 30 volunteers aged 19–48
67
+ **Sensor:** Samsung Galaxy S II (waist-mounted)
68
+ **Sampling rate:** 50Hz
69
+ **Features:** 561 time + frequency domain features
70
+ **Classes:** 6 activities of daily living
71
+ """)
72
+ st.markdown("---")
73
+ st.markdown("**Model performance on test set**")
74
+ st.metric("Accuracy", "β€”")
75
+ st.metric("Macro F1", "β€”")
76
+ st.markdown("---")
77
+ st.caption("DAT606 Group Assignment Β· Pan-Atlantic University")
78
+
79
+ # ── Model status ─────────────────────────────────────────────────────────────
80
+
81
+ model = load_model()
82
+
83
+ if model is None:
84
+ st.warning(
85
+ "⚠️ Model not yet available. "
86
+ "The interface is fully built β€” predictions will activate once "
87
+ "`model.keras` is uploaded.",
88
+ icon="⚠️"
89
+ )
90
+
91
+ # ── Tabs ────────────────────────────────────���────────────────────────────────
92
+
93
+ tab1, tab2 = st.tabs(["πŸ“‹ Select a Sample", "πŸ“ Upload Phyphox CSV"])
94
+
95
+ # ── Tab 1: Sample selector ───────────────────────────────────────────────────
96
+
97
+ with tab1:
98
+ st.subheader("Select a pre-loaded test sample")
99
+ st.caption(
100
+ "Each sample is one 2.56-second window of sensor data "
101
+ "from a test subject the model has never seen during training."
102
+ )
103
+
104
+ try:
105
+ samples_df = pd.read_csv("data/samples.csv")
106
+ feature_cols = [
107
+ c for c in samples_df.columns
108
+ if c not in ["Activity", "subject"]
109
+ ]
110
+
111
+ sample_labels = [
112
+ f"Sample {i+1} β€” {row['Activity']}"
113
+ for i, (_, row) in enumerate(samples_df.iterrows())
114
+ ]
115
+
116
+ selected = st.selectbox("Choose a sample:", sample_labels)
117
+ selected_idx = sample_labels.index(selected)
118
+ selected_row = samples_df.iloc[selected_idx]
119
+ true_label = selected_row["Activity"]
120
+ feature_vector = selected_row[feature_cols].values.astype(np.float32)
121
+
122
+ col1, col2 = st.columns(2)
123
+ with col1:
124
+ st.metric(
125
+ "True Activity",
126
+ f"{ACTIVITY_ICONS.get(true_label, '')} {true_label}"
127
+ )
128
+ with col2:
129
+ st.metric("Feature count", len(feature_vector))
130
+
131
+ if st.button("πŸ” Classify this sample", type="primary"):
132
+ if model is None:
133
+ st.error("Model not loaded β€” cannot predict yet.")
134
+ else:
135
+ arr = feature_vector.reshape(1, -1)
136
+ probs = model.predict(arr, verbose=0)[0]
137
+ pred_idx = int(np.argmax(probs))
138
+ pred_label = LABEL_MAP[pred_idx]
139
+ confidence = float(probs[pred_idx]) * 100
140
+ correct = pred_label == true_label
141
+
142
+ st.markdown("---")
143
+ st.subheader("Result")
144
+
145
+ if correct:
146
+ st.success(
147
+ f"{ACTIVITY_ICONS.get(pred_label, '')} **{pred_label}** "
148
+ f"Β· {confidence:.1f}% confidence Β· βœ“ Correct"
149
+ )
150
+ else:
151
+ st.error(
152
+ f"{ACTIVITY_ICONS.get(pred_label, '')} **{pred_label}** "
153
+ f"Β· {confidence:.1f}% confidence Β· "
154
+ f"βœ— Incorrect (true: {true_label})"
155
+ )
156
+
157
+ st.markdown(f"_{EXPLANATIONS[pred_label]}_")
158
+ st.markdown("**Confidence across all classes**")
159
+
160
+ chart_data = pd.DataFrame({
161
+ "Confidence (%)": [
162
+ float(probs[i]) * 100 for i in range(6)
163
+ ]
164
+ }, index=[LABEL_MAP[i] for i in range(6)])
165
+
166
+ st.bar_chart(chart_data)
167
+
168
+ except FileNotFoundError:
169
+ st.error("Sample data file not found. Add `data/samples.csv` to the repo.")
170
+
171
+ # ── Tab 2: Phyphox upload (placeholder) ─────────────────────────────────────
172
+
173
+ with tab2:
174
+ st.subheader("Upload Phyphox sensor recording")
175
+ st.markdown("""
176
+ **How to record your own data:**
177
+ 1. Install [Phyphox](https://phyphox.org/) on your phone
178
+ 2. Open the **Acceleration (without g)** and **Gyroscope** experiments
179
+ 3. Record at least 3 seconds of a single activity
180
+ 4. Export as CSV and upload below
181
+ """)
182
+
183
+ uploaded_file = st.file_uploader(
184
+ "Upload Phyphox CSV export",
185
+ type=["csv"],
186
+ help="Export from Phyphox as CSV β€” must contain accelerometer and gyroscope columns"
187
+ )
188
+
189
+ if uploaded_file is not None:
190
+ st.info(
191
+ "πŸ“Œ Phyphox pipeline coming soon. "
192
+ "Feature extraction from raw sensor readings "
193
+ "(filtering β†’ jerk β†’ FFT β†’ 561 features) is under development.",
194
+ icon="πŸ”§"
195
+ )
196
+ try:
197
+ preview = pd.read_csv(uploaded_file)
198
+ st.markdown("**File preview:**")
199
+ st.dataframe(preview.head(10))
200
+ st.caption(
201
+ f"{len(preview)} rows Β· {len(preview.columns)} columns detected"
202
+ )
203
+ except Exception as e:
204
+ st.error(f"Could not read file: {e}")