Skip to content

Hands-on: a feature vector from a sliding window

Module 4 — Signal analysis · Slides: slides.md · Module overview · Course page

Fill five points in s10_windowing.py to buffer the signal, compute mean, std and per-band energy, then slide the window with 50% overlap, producing a six-value feature vector that follows real motion — while seeing that this is the unit of data for the next module’s dataset.

By the end of this lesson, you will:

  1. Fill the five points in practice/s10_windowing.py until all six feature bars move and the window counter rises every HOP samples.
  2. Measure and record std at rest versus while shaking, and explain why they differ.
  3. Explain why the order mean → std matters, and why forgetting buf = buf[-WIN:] leaks memory.

You’ve been through lesson 4.5, and know WIN, HOP, and the six features. Keep your learning log ready to note the std value at rest and while shaking.

The whole file reads as one sentence: buffer the signal → once a window’s worth is collected, squeeze it into features → show them as bars → slide the window → loop. The five points we fill in are: (1) buf.append(az), inside a loop that reads sensors.bmi270.motion() one HOP point at a time, delaying 20 ms (50 Hz) (2) mean = sum(win) / n (3) std = math.sqrt(sum((x - mean) ** 2 for x in win) / n), which must always come after mean, since it uses the mean value (4) band_e.append(sum((x - m) ** 2 for x in s) / len(s)), inside a loop over four bands, and (5) buf = buf[-WIN:], after squeezing the features, to keep only the last WIN points, so the next window overlaps 50%. If you forget this last point, the buffer grows without bound until memory runs out.

The display is already given — the bars are scaled roughly (mean and std multiplied by 2, bands by 0.02), just enough to be visible to the eye. lcd.console prints the feature vector’s real values alongside it. When feeding a real model, we’ll normalize systematically in module 5, but the same principle applies: bring every feature into a similar scale. Everything here is plain Python on the CM33 — no NPU needed.

Success in this pair of lessons is being able to say why std is low at rest, and how this feature vector becomes a dataset. In the next module, we’ll do the same thing but with a label attached, saved to CSV, to train a model in TensorFlow.

s10_windowing_full.py adds highlighting the strongest band, deciding “still” or “moving” from std with a threshold STD_MOVE (in m/s², adjustable per board), and measuring the window rate per second — an example of rule-based classification on features we built ourselves.

File What this file teaches
examples/s10_windowing_full.py A visual feature front-end: windowing + a feature vector (full version)

The # TODO: comments are at lines 37 (mean), 39 (std), 48 (band energy), 78 (buf.append), and 91 (buf = buf[-WIN:]). Replace pass or the 0.0 value with the calls the hints describe, then alternate holding still and shaking. If the bars never move, check your indentation and the mean → std order.

Practice file Topic
practice/s10_windowing.py What a model “sees”: windowing + a feature vector (the fill-in-the-code version)

Open the solution after trying on your own at least once, and read how to use the solutions first.

Solution Pairs with
solution/s10_windowing.py practice/s10_windowing.py

The same questions are in quiz.yaml for automated checking.

  1. You run it, and every bar stays still, and the window counter never rises at all. Which point is most likely still empty? (single choice · objective 1)

    • a) buf.append(az), so the buffer never reaches a full window
    • b) band_e.append(…)
    • c) buf = buf[-WIN:]
    • d) std
    Solution

    a — if values are never stored in the buffer, the condition len(buf) >= WIN can never become true, so features are never squeezed, and windows never counted.

  2. The board lies still and std is low, but once shaken, std is very high. Why? (single choice · objective 2)

    • a) Shaking always raises az’s average
    • b) std measures how spread out values are from the average; shaking makes az oscillate widely around the average
    • c) The sensor changes units while shaking
    • d) The window shortens while shaking
    Solution

    b — at rest, every point sits close to the average, so std is small. While shaking, points spread far from the average, so std is large — this is why a simple statistical feature separates postures well.

  3. If the std line is written before the mean line inside features(), what happens? (single choice · objective 3)

    • a) You get the same value
    • b) std is computed against the starting value mean = 0.0, so it’s wrong (you get a value close to the signal’s level, not its spread)
    • c) The program always throws NameError
    • d) band energy becomes wrong instead
    Solution

    b — std needs an already-computed mean. If mean is still 0.0, the sum of (x − 0)² reflects the signal’s magnitude, not its spread around the average.

  4. You forget to fill in buf = buf[-WIN:]. What’s the long-term result? (single choice · objective 3)

    • a) Windows don’t overlap, but everything else is normal
    • b) The buffer keeps growing without bound until memory runs out
    • c) Every feature becomes zero
    • d) The sampling rate speeds up
    Solution

    b — features() still uses buf[-WIN:], so it looks like it’s working normally at first, but the list keeps getting appended to forever — a memory leak that eventually hangs the board.

The MVP for lessons 4.5–4.6: build a feature vector from a raw signal by hand — slice a window (window + hop), then squeeze it into mean, std and band values that change with motion.

  • All five points in the practice file are filled in, and it runs on the emulator or the board.
  • Lie still, then shake, and note the std value for both in your learning log — how many times larger, and why?
  • Change HOP to match WIN, and observe how the window rate changes, and what you risk missing.
  • Be able to explain what a window and a hop are, why they must overlap, and what std tells you.

In the next module (Training), we’ll collect data like this with a label attached, into a real dataset — split into train, val, test — and train a model of our own.

Next lesson: lesson 5.1 — Dataset engineering: class balance, windows and the train/val/test split

  • Are these six features enough to tell idle, circle and shaking apart? If not, what feature would you add?
  • If your bands were split by frequency instead of time, how close would the result be to what audio models use?

Review questions

Answer on your own first, then open the answer.

  1. All bars are still and the window counter never rises. Which point is most likely empty? (Objective 1)

    1. buf.append(az) ทำให้ buffer ไม่เคยครบหน้าต่าง
    2. band_e.append(...)
    3. buf = buf[-WIN:]
    4. std
    Show answer

    Answer: A. buf.append(az) ทำให้ buffer ไม่เคยครบหน้าต่าง

    ถ้าไม่เก็บค่าเข้า buffer เงื่อนไข len(buf) >= WIN ไม่มีวันจริง จึงไม่มีการบีบ feature และไม่มีการนับหน้าต่าง

  2. At rest std is low, but shaking makes it very high. Why? (Objective 2)

    1. เขย่าทำให้ค่าเฉลี่ยของ az สูงขึ้นเสมอ
    2. std วัดว่าค่ากระจายห่างจากค่าเฉลี่ยแค่ไหน เขย่าทำให้ az แกว่งไปมารอบค่าเฉลี่ยมาก
    3. เซนเซอร์เปลี่ยนหน่วยตอนเขย่า
    4. หน้าต่างสั้นลงตอนเขย่า
    Show answer

    Answer: B. std วัดว่าค่ากระจายห่างจากค่าเฉลี่ยแค่ไหน เขย่าทำให้ az แกว่งไปมารอบค่าเฉลี่ยมาก

    ตอนนิ่งทุกจุดใกล้ค่าเฉลี่ย std จึงเล็ก ตอนเขย่าจุดกระจายห่างค่าเฉลี่ย std จึงใหญ่ เป็นเหตุผลที่ feature สถิติง่าย ๆ แยกท่าทางได้ดี

  3. If the std line comes before the mean line in features(), what happens? (Objective 3)

    1. ได้ค่าเท่าเดิม
    2. std คำนวณจาก mean = 0.0 ค่าเริ่มต้น จึงผิด (ได้ค่าใกล้ระดับของสัญญาณแทนการแกว่ง)
    3. โปรแกรมโยน NameError ทุกครั้ง
    4. band energy ผิดแทน
    Show answer

    Answer: B. std คำนวณจาก mean = 0.0 ค่าเริ่มต้น จึงผิด (ได้ค่าใกล้ระดับของสัญญาณแทนการแกว่ง)

    std ต้องใช้ mean ที่คำนวณแล้ว ถ้า mean ยังเป็น 0.0 ผลรวม (x − 0)² จะสะท้อนขนาดของสัญญาณ ไม่ใช่การกระจายรอบค่าเฉลี่ย

  4. You forget buf = buf[-WIN:]. What is the long-term effect? (Objective 3)

    1. หน้าต่างไม่ซ้อนกันแต่ทุกอย่างปกติ
    2. buffer โตขึ้นเรื่อย ๆ ไม่มีที่สิ้นสุดจนหน่วยความจำหมด
    3. feature ทุกตัวเป็นศูนย์
    4. อัตราสุ่มเร็วขึ้น
    Show answer

    Answer: B. buffer โตขึ้นเรื่อย ๆ ไม่มีที่สิ้นสุดจนหน่วยความจำหมด

    features() ยังใช้ buf[-WIN:] จึงดูเหมือนทำงานปกติในช่วงแรก แต่ list ถูกต่อท้ายไม่หยุด เป็น memory leak ที่ทำให้บอร์ดค้างในที่สุด

Cite this lesson

If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.

"Hands-on: a feature vector from a sliding window" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0

Thai attribution: "ลงมือทำ: feature vector จากหน้าต่างเลื่อน" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0

Lesson link: https://tesaiot.github.io/tesa-qualification-program/en/courses/edge-ai-developer/m04-analysis/l06-windowing-lab/

Full guide: how to cite TESA

TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0

Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA