Skip to content

Latest commit

Β 

History

34 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Live posture debug image with labeled AprilTags and the superimposed posture line

🦴 Posture Watcher

A webcam + AprilTag + e-ink feedback loop for better posture at your desk.

Rust 2021 macOS Badger2040 AprilTag 36h11 v0.1.0

Quick Start Β· How It Works Β· Features Β· Gallery Β· Calibration Β· CLI Toolbox


Posture Watcher is a small end-to-end posture feedback loop:

  1. A side-mounted webcam watches AprilTags on the ear, C7, shoulder, and optional hip.
  2. A Rust analyzer turns those tags into marker geometry, placement diagnostics, and a simple spine/head curve.
  3. A Badger2040 e-ink display, used in portrait orientation, shows the feedback where it is easy to glance at while working.
  4. A native macOS wrapper owns Camera permission, shows the same display as the Badger, and gives debugging controls when the hardware is not nearby.

Philosophy: The goal is not to nag on every frame. The app samples slowly, averages over a rolling window, and refuses to show a posture curve when the markers are visible but anatomically implausible.

πŸ“Έ Gallery

Live debug overlay macOS app window
Live posture debug image with labeled AprilTags and the superimposed posture line Posture Watcher macOS app window showing ready tags, good placement, labeled points, and the mirrored Badger curve
Badger in action Wearing the tags
Badger2040 mounted under the monitor showing the posture curve Placeholder for photo of wearing AprilTags

The wearing-tags placeholder is intentional β€” drop in a real photo after the next physical test.

πŸš€ Quick Start

Prerequisites

What Details
Badger2040 Connected over USB-C (default port: /dev/cu.usbmodem83201)
Logitech C930e Mounted sideways for maximum vertical image height
AprilTags Printed from the tag36h11 family

1. Build & Install

# Build the Rust CLI
cargo build

# Install the Badger receiver (backs up existing main.py first)
cargo run -- install-badger

# Build and install the macOS app
POSTURE_WATCHER_INSTALL_DIR=/Applications scripts/install-macos-app.sh --open

The installer builds target/macos/Posture Watcher.app, copies it to /Applications, removes quarantine metadata, and opens it. macOS will prompt for Camera permission.

πŸ” Optional: Launch at login
POSTURE_WATCHER_INSTALL_DIR=/Applications scripts/install-macos-app.sh --launch-at-login
scripts/disable-launch-at-login.sh   # to undo

2. Print & Place Tags

cargo run -- stickers --open

This prints a sheet with four flat tags plus a foldable C7 side-facing flag template.

Tag Landmark Placement
tag36h11-0 πŸ‘‚ Ear / tragus Small cartilage bump in front of ear canal
tag36h11-1 🦴 C7 Base of neck β€” use the foldable side-facing flag
tag36h11-2 πŸ’ͺ Shoulder / acromion Bony shelf at the outside/top of the shoulder
tag36h11-3 🦡 Hip / belt (Optional) Side hip / waistband, helps with sit/stand detection

3. Launch & Check

Open /Applications/Posture Watcher.app and look for:

Status Meaning
βœ… Badger connected E-ink receiver is ACKing payloads
βœ… Tags ready Required tags are visible
βœ… Placement good Marker geometry is plausible
βœ… Baseline ready Sitting and standing have enough good samples
⚠️ Move ear tag up Tags visible but probably misplaced β€” fix before trusting the curve

4. Save Samples

Use Save Sample whenever you have a useful sitting, standing, good, or bad setup. Samples go to:

~/Library/Application Support/Posture Watcher/samples/<mode>/

Each sample includes the raw frame, debug images, and a *-tags.txt report with marker coordinates, detected mode, placement score, and posture measurements.


βš™οΈ How It Works

The Loop

The macOS app captures frames via AVFoundation and writes a rolling burst to disk. The Rust analyzer inspects recent frames, detects AprilTags, fuses the strongest observations, and pushes posture feedback to both the app window and the Badger display.

Camera β†’ Burst Frames β†’ AprilTag Detection β†’ Marker Fusion β†’ Rolling Average β†’ Badger + App

Burst Sampling

Each update is based on fused AprilTag observations from multiple recent frames, so one blurry or occluded frame doesn't dominate.

Setting Default Env Var
Update interval 15s POSTURE_WATCHER_INTERVAL_SECS
Burst frames 8 POSTURE_WATCHER_BURST_FRAMES
Frame interval 0.25s POSTURE_WATCHER_BURST_FRAME_INTERVAL_SECS

Sitting vs Standing

The app auto-detects sitting/standing from shoulder-to-hip geometry when those tags are visible. Use the Mode picker to override or leave on Auto.

Each mode keeps its own rolling average window, so switching desk modes doesn't blend postures together.

Safety Rails

Posture feedback only appears when markers are trustworthy:

Condition Display
No tags for extended period No person found
Tags visible, geometry implausible Move ear tag up / Aim C7 flag / Check markers
Tags visible, geometry good Live posture curve βœ…

Short marker dropouts are counted but don't immediately replace the curve β€” sustained problems trigger a fix suggestion.


🎯 Features

Area Feature What It Does
Capture Native macOS app Owns Camera permission, camera selection, live capture, and the daily workflow
Sideways webcam Defaults to 90Β° CCW rotation for the C930e mounted vertically
Markers Printable AprilTags Generates ear, C7, shoulder, and hip tags from tag36h11
C7 flag workflow Foldable C7 flag template + anchor correction for side-facing neck marker
Detection Burst sampling Checks several recent frames so one bad frame doesn't dominate
Marker fusion Combines strongest observations across the burst before calculating posture
Posture Rolling average curve Long-window posture trend instead of twitchy frame-by-frame warnings
Sitting/standing modes Auto, Sitting, and Standing with separate rolling windows and baselines
Calibration Personal baselines Builds sitting and standing baselines from your own saved good samples
Feedback Baseline comparison Live curve against a dashed calibrated baseline
Quality strip Recent valid/missed samples as a compact strip
Placement warnings Actionable messages like Aim C7 flag or Move ear tag up
Hardware Badger2040 display Same posture feedback on a USB-connected e-ink display
USB orientation Supports USB-C at top or bottom
Debug Labeled overlays Debug images with labeled points in the macOS preview
Badger renderer Renders exact Badger framebuffer without aiming a camera at the screen
Sample capture Saves raw frames, overlays, and tag reports
Setup Installable .app Installs to /Applications with optional launch-at-login
Badger backup/restore Backs up main.py before installing; can restore later

πŸ”§ Calibration

Calibration has three layers β€” do them in order.

1. πŸ“· Camera Geometry

Keep the camera boring and repeatable:

  • Same camera, desk position, and side-view angle each day
  • C930e sideways with --rotate ccw90
  • Frame the body so all four tags are visible without being tiny
  • Don't move the camera between sitting and standing

2. πŸ“Œ Marker Placement

Get the app to say Tags ready β†’ then Placement good.

🏷️ Detailed Marker Placement Guide

Place every marker on the camera-facing side of your body. This is a side-view tracker.

Side-view guide for placing ear, C7, shoulder, and hip AprilTags

tag36h11-0: Ear / Tragus

Put this tag at the tragus region β€” the small cartilage bump immediately in front of the ear canal. Keep hair, glasses arms, headphones, and mask straps from covering the tag.

tag36h11-1: C7

C7 is the prominent bump at the base of the neck:

  1. Gently bend your head forward and feel for the most prominent lower-neck bump.
  2. Gently extend your neck; the C7 bump tends to stay prominent while the segment above it moves more.
  3. Put the tag centered over that bump, on skin or a tight collar.

⚠️ Important: Don't stick this tag flat to the back of your neck β€” the side camera will see only the paper edge. Use the foldable flag template instead.

C7 flag assembly:

  1. Print and cut out the C7 side-facing flag template
  2. Tape the ANCHOR area directly over C7
  3. Fold the tag panel on the dashed line so the tag face points toward the camera
  4. Stiffen with a second layer of tape or thin cardstock

The app applies a C7 anchor correction (default 0.75 tag widths, configurable via POSTURE_WATCHER_C7_ANCHOR_OFFSET_TAG_WIDTHS).

tag36h11-2: Shoulder / Acromion

Use the acromion: the bony roof at the outside/top of the shoulder. Trace the collarbone outward until you reach the flat bony shelf. Avoid the upper arm muscle and loose sleeve fabric.

tag36h11-3: Hip / Pelvis

For sit/stand detection. Prefer a belt clip or tight waistband on the camera-facing side, roughly aligned with the greater trochanter area. Loose shirt hems drift independently.

Daily Placement Check

Before trusting the curve:

  1. Tags ready β†’ all required tags visible
  2. Placement good β†’ geometry is plausible
  3. Detected Sitting or Detected Standing β†’ mode agrees with reality

If you see Aim C7 flag, Move ear tag up, or similar β€” fix the stickers first.

3. πŸ“ Personal Baseline

Don't calibrate against "perfect posture." Calibrate against your own repeatable, comfortable, clinician-approved working positions.

  1. Set Mode β†’ Standing, set up normally, wait for Placement good, hit Save Sample 3Γ— over a minute.
  2. Repeat in Sitting mode.
  3. Click Calibrate in the app, or:
cargo run -- calibrate-baseline

This writes to ~/Library/Application Support/Posture Watcher/calibration/baseline.txt. Once ready, the Badger shows baseline-relative drift like sit -3deg or std +2deg with a dashed baseline curve.

πŸŽ›οΈ What to tune later

Once you have good samples in both modes:

  • Mode detection: shoulder-to-hip geometry and absolute marker positions
  • Head/neck trend: craniovertebral angle from tragus/ear to C7
  • Shoulder/torso trend: shoulder, C7, and hip relationship
  • Feedback threshold: how far and how long you drift before the Badger looks "off"

The important idea is trend feedback over time β€” noticing sustained drift, not forcing a rigid pose.


πŸ› οΈ CLI Toolbox

Snapshot & diagnostics
# Analyze current app frame
cargo run -- snapshot \
  --input "$HOME/Library/Application Support/Posture Watcher/latest-frame.jpg" \
  --rotate ccw90 --out-dir artifacts/snapshot

# Full diagnostic
cargo run -- doctor

Doctor checks: C930e in camera list, one-frame capture works, Badger ACKs, tag detection works, baseline smoke test passes.

Live capture
# CLI live capture
cargo run -- live --camera "Logitech Webcam C930e" --port /dev/cu.usbmodem83201

# Live from app frame file
cargo run -- live-file \
  --input "$HOME/Library/Application Support/Posture Watcher/latest-frame.jpg" \
  --burst-dir "$HOME/Library/Application Support/Posture Watcher/burst" \
  --burst-frames 8

Useful flags:

cargo run -- live --capture-backend imagesnap
cargo run -- live --capture-backend ffmpeg --ffmpeg-input "0:none"
cargo run -- live --capture-timeout-secs 5
cargo run -- live --rotate none
cargo run -- live --baseline "$HOME/Library/Application Support/Posture Watcher/calibration/baseline.txt"
cargo run -- live --mode sitting
cargo run -- live-file --input "artifacts/tagged-samples/<sample>-tagged.png" --rotate none --once --no-badger
Sample tools & Badger
# Generate fake tagged samples
cargo run -- annotate-samples

# Run sample sequence β†’ Badger
cargo run -- run-samples --send-badger

# Render Badger framebuffer from latest payload
scripts/render-badger-display.py \
  --out artifacts/badger-debug/latest-mounted.png \
  --raw-out artifacts/badger-debug/latest-raw.png

# Restore original Badger launcher
cargo run -- restore-badger
Watch logs
scripts/watch-macos-app-log.sh
Environment overrides
POSTURE_WATCHER_CAMERA="Logitech Webcam C930e"
POSTURE_WATCHER_PORT="/dev/cu.usbmodem83201"
POSTURE_WATCHER_INTERVAL_SECS=15
POSTURE_WATCHER_NO_PERSON_AFTER_SECS=60
POSTURE_WATCHER_ROTATE=ccw90
POSTURE_WATCHER_BADGER_ORIENTATION=usb-bottom
POSTURE_WATCHER_NO_BADGER=1

πŸ§‘β€πŸ’» How I'm Using It

The Badger sits in portrait orientation just below the monitor, close enough to glance at without turning the setup into another dashboard. The macOS app stays open as the debugging view, while the e-ink display shows the low-friction posture trace.

Badger2040 mounted below the monitor with the macOS app and live camera debug view visible

πŸ—οΈ How I Built It

The hardware loop is deliberately simple: the camera is mounted to the side for a clean profile view, a printed AprilTag sheet provides the daily markers, and the Badger plugs in over USB-C so the Rust analyzer can push the same curve to the desk display.

Desk setup with side camera, printed AprilTag sheet, and Badger2040 connected over USB-C

The software loop was built with Codex using a long-running /goal: "set up a working e2e Rust app for tracking my posture using a webcam and e-ink device." Codex drove the implementation across the Rust analyzer, Badger MicroPython receiver, macOS camera app, hardware checks, README screenshots, and iterative fixes while I tested the physical setup at the desk.

Codex pursuing the posture watcher build as a long-running goal


πŸ“š Research Notes

This project uses photogrammetry-style marker tracking, not medical diagnosis.

The calibration approach is based on a few practical constraints from the literature:


Built with πŸ¦€ Rust, 🍎 macOS, and 🦑 Badger2040 β€” by Jess Martin

About

Camera + April-tag based posture monitor with e-ink display

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages