Spaces:
Sleeping
Sleeping
File size: 20,435 Bytes
0d27c43 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 | # DEVLOG — totes-emosh
## 2026-06-07 chore: rename Fingerprint → EmotionMap; attribution footer; verdict bands
**Capability:** facial-expression-toy-app (static-only)
**Uses:** —
**Approach:** Rename across current-facing materials at Gordon's
call — "Fingerprint" doesn't make sense in context, defined term is
now **EmotionMap**. Bulk replace in `app.py`, `app/tiles.py` (PDF
title + temp-file prefix), `README.md`, `.codewright.yaml`, plus all
lesson docs (`formative-activity.html`, `expert-insight.{html,md}`,
`key-takeaways.{html,md}`, `W3L4-deliverable.md`,
`teaching/.../README.md`). Word template script renamed
`scratch/build_emotionmap_template.py`; Word file regenerated as
`Lesson4-EmotionMap-template.docx`; old `.docx` deleted.
Also (earlier same day): added the standard attribution footer to
the app (`gr.HTML` block under the download button) and to the
single-page PDF (line below the privacy reminder) — *Created by Dr.
Gordon Wright — A LittleMonkeyLab caper. Part of the Goldsmiths MSc
in Psychology, Week 3 Part 4.* Verdict bands made far more visible:
full-width coloured strips (green AGREES / orange DISAGREES), white
bold 15pt title + 11pt detail, taller tile (TILE_H 396, VERDICT_H
44). Empty tiles get a matching grey "awaiting your attempt" band so
the grid stays aligned. Deleted dead `app/description.py` (no
longer imported anywhere).
Smoke tests: `app.py` imports clean, both PDF modes render against
the six bundled sample faces (`lesson4-emotionmap*-{face,wireframe}.pdf`),
the attribution line and EmotionMap title both stamp correctly.
Historical DEVLOG / `toy-app-plan.md` entries deliberately left
intact for the historical record.
---
## 2026-06-05 feat: six-emotion replication grid + wireframe (face-free) mode + single-page A4 PDF artefact
**Capability:** facial-expression-toy-app (static-only)
**Uses:** mediapipe (478-point face mesh), Pillow (compositing), matplotlib (PDF)
**Approach:** Reshaped the toy app around the *replication challenge*
framing. New `app/tiles.py` owns:
- A six-tile grid (2 rows × 3 cols) keyed by `BASIC_EMOTIONS = [happy,
sad, fear, disgust, anger, surprise]`. Neutral dropped — it isn't
part of the replication ask.
- A composite per-tile renderer (`render_tile`, `render_filled_tile`,
`render_empty_tile`) that produces a single 280×370 PIL image
containing a header strip (`1. HAPPY`), the face (or wireframe)
thumbnail, a 7-emotion classifier bar strip, and a coloured verdict
line (`agrees: Happiness (0.99)` / `sees: Fear (0.51) — not Anger`).
- `render_wireframe()` — anonymised landmark mesh, drawn from
hand-coded subsets of MediaPipe FACEMESH connections (face oval,
lips outer + inner, both eyes, both brows, nose bridge) plus dots
for every one of the 478 landmarks. The result is recognisably
face-shaped — you can read brows pulled down on the angry attempt,
a smile on the happy attempt — without any identifying photographic
content.
- `export_single_page_pdf()` — one A4 portrait page with a title row,
name strip, 2×3 tile grid, status footer, and privacy reminder.
Replaces the two-page radar / two-mode export from earlier today.
State model: `app/session.py` now exposes
`empty_session() -> {emotion: Capture|None}` over the six emotions and
a `Capture` dataclass with `intended`, `landmarks`, `bbox`, and
`image_size` (the last three are needed so the wireframe can be
re-drawn at the exact crop of the face). `session_status()` reports
"N / 6 captured · classifier agreed on M / 6".
UI rewrite in `app.py`: gone are the logo row, multi-line header,
two-paragraph privacy banner, separate Static / Dynamic / Fingerprint
tabs, gr.Gallery, and the radar plot. Replaced by:
- A one-line `<h>` header with a small `<details>` privacy disclosure.
- A single compact input row (image upload/webcam + intended-emotion
dropdown + wireframe toggle + submit + clear-all).
- A six-tile grid where every tile re-renders on every action.
- Per-tile **Retry** buttons that clear just that slot.
- A name field + single **Download single-page Fingerprint PDF**
button at the bottom.
Toggling Wireframe re-renders every visible tile in real time, so
students can preview either format before they download.
`app/fingerprint.py` and `scratch/smoke_fingerprint.py` deleted —
radar and two-PDF export are gone. `app/app_utils.preprocess_image_and_predict()`
now returns the bbox and landmarks alongside the face/heatmap/probs/
blendshapes so the tiles module can reconstruct the wireframe from a
stored Capture.
Word doc Fingerprint template regenerated as a paper fallback — a
2×3 replication grid table mirroring the in-app layout, plus the
Privacy and Reflection sections. Lesson copy updated:
`formative-activity.html` now frames the activity as a
six-emotion replication challenge with the wireframe-vs-face format
choice; `expert-insight.md` and `key-takeaways.md` rewritten around
the per-slot agree/disagree pattern.
**Related:** online-teaching-for-bence
Reframing note (same day, post-shipping): Gordon shared the original
lecture brief, which puts the activity at slides 23-24 as the
experiential capstone after a deception-focused arc (Spy the Lie /
Duping Delight → Ekman micro-expressions → TSA SPOT boondoggle →
Porter & ten Brinke 2008 on posed emotion → FACS survived, the
deception theory didn't). The six-tile replication grid lands as a
single-person Porter & ten Brinke replication: pose each emotion in
turn; happy will be easiest to fake; the classifier disagreement
pattern *is* the data. No app changes needed for that reframing —
the existing per-tile agree/disagree verdict already exposes the
Porter & ten Brinke effect. Lesson copy in
`online-teaching-for-bence` updated accordingly:
`expert-insight.md` rewritten in Gordon's voice around the deception
arc, `formative-activity.html` now leads with the two stated LOs,
`key-takeaways.md` rewritten to match.
Smoke tests: `scratch/smoke_tiles.py` builds synthetic state, renders
both face and wireframe tile grids, and exports both PDF modes.
`scratch/smoke_real_face.py` runs the six bundled sample images
through the real classifier + landmarker; all six produced a face,
landmarks (478 each), bbox, and a top-class read — five correct
(Happy 0.99, Sad 0.99, Fear 0.99, Disgust 0.99, Surprise 0.76),
Anger misread as Fear 0.51 (which is itself the variability story
the lesson is now built around). Sample tile renders at
`scratch/tile-samples/` confirm the face mode and wireframe mode
both communicate the pose cleanly.
`PYTHONUNBUFFERED=1 uv run python app.py` reaches Gradio's
"Running on local URL" within a few seconds, no warnings, no
deprecations.
---
## 2026-06-05 chore: stripped to static-only; basic-emotions scope; video moves to a separate app
**Capability:** facial-expression-toy-app (static-only)
**Uses:** gradio, torch, mediapipe (tasks API), grad-cam
**Approach:** Activity scope re-cut at Gordon's call: too many trials,
basic emotions in the static path are enough on their own. Dynamic
Faces tab removed from `app.py`. `submit_video_and_add`,
`clear_dynamic_info`, and all video-tab widgets gone. `LABEL_PRESETS`
trimmed from 17 entries (warm-up × 7 + AU × 6 + condition × 3 + custom)
to 8 (basic emotions × 7 + custom). Static tab renamed to "Faces" and
the preset dropdown relabelled "Emotion you posed".
Dead code purged: deleted `app/face_utils.py` (`display_info` overlay
was video-only), `app/plot.py` (`statistics_plot` line graph was
video-only), `scratch/smoke_video_peaks.py`. `app/app_utils.py` now
exports only `preprocess_image_and_predict`. `app/model.py` no longer
loads the LSTM weights or class; `LSTMPyTorch` removed from
`app/model_architectures.py`; `config.toml` `[model_dynamic]` section
and stale `FRAME_DOWNSAMPLING` removed. Deleted the bundled LSTM
checkpoint (`FER_dinamic_LSTM_IEMOCAP.pt`, ~11 MB) and the leftover
demo render artefacts (`result_face.mp4`, `result_hm.mp4`).
Word-doc Fingerprint template regenerated against the slimmed scope:
A1/A2 (basic-emotion stills + radar) and B1/B2 (basic-emotion bar
charts + radar) — AU-build and crash-emotion sections gone. Privacy
checklist and reflection prompts retained, prompts retargeted to the
basic-emotions experience.
Bumped `config.toml` `APP_VERSION` to 0.3.0.
**Related:** online-teaching-for-bence
Smoke tests: `import` of `app.py` via the spec loader builds the
Blocks cleanly. `PYTHONUNBUFFERED=1 uv run python app.py` reaches
"Running on local URL: http://127.0.0.1:7861" with no warnings, no
deprecation notices. The Fingerprint PDF export still works against
the new label scheme — radar uses session-level means so it doesn't
care about label structure.
The video element will live in a separate app (TBC). The HF Space
push should now be of this slimmer canonical version.
---
## 2026-06-05 feat: R3 + R4 + R5 landed; Word doc Fingerprint template generated
**Capability:** facial-expression-toy-app
**Uses:** matplotlib (polar + PdfPages), python-docx
**Approach:** New `app/fingerprint.py` owns the session-level analytics
and export. R3: `session_radar(captures)` builds a polar plot of mean
classifier confidence per emotion across the whole session (Static
captures + video peaks combined), with one coloured marker per emotion
and a filled polygon. Empty sessions render a blank grid so the panel
doesn't pop in/out. R4: `export_fingerprint_pdf(captures, mode)` uses
`matplotlib.backends.backend_pdf.PdfPages` to assemble the Fingerprint
in either Format A (cover with radar, summary bar, then a 4-up face
grid per page) or Format B (cover with radar, summary bar, a labelled
table of top emotions, then per-capture probability bars 4-up). Returns
a NamedTemporaryFile path so `gr.File` can serve it as a download. R5:
`PRIVACY_BANNER` `gr.Markdown` block sits above the tab strip with a
new `.privacy-banner` CSS rule (warm cream block, amber left border).
New "Fingerprint" tab shows the live radar and a format-toggle radio +
"Download Fingerprint PDF" button; the radar updates from every event
that mutates the session (`submit`, `submit_dynamic`, remove-last,
clear-session). `pyproject.toml` and `requirements.txt` gain
`matplotlib>=3.8.0` and `python-docx>=1.1.0`.
Also generated the Word-doc Fingerprint template at
`Lesson4-Fingerprint-template.docx` (~39 KB) via
`scratch/build_fingerprint_template.py`. Single document carries both
Format A (warm-up stills, AU-build attempts, crash-emotion captures,
radar) and Format B (warm-up bars, AU table, per-condition time-series
tables, radar) panels with delete-the-other-format instructions; a
shared Privacy Confirm checklist and Reflection section close the
document. Mirrors §6 of `formative-activity.html` verbatim.
**Related:** online-teaching-for-bence
Smoke tests: `scratch/smoke_fingerprint.py` builds 7 synthetic
captures, renders the radar (`Figure`, 1 axis), exports Format A
(~75 KB PDF) and Format B (~79 KB PDF), and verifies the empty-session
paths produce valid PDFs too (~43 KB / 44 KB). Full app launch:
`PYTHONUNBUFFERED=1 uv run python app.py` reaches "Running on local
URL: http://127.0.0.1:7861" cleanly, no warnings, no deprecations.
UI flow (radar update on submit, format toggle, PDF download) needs a
browser eyeball — can't verify click wiring from CLI.
R-list now complete: R0 ✅ R1 ✅ R2 ✅ R3 ✅ R4 ✅ R5 ✅. Next step is
pushing back to the HF Space as the new canonical version once Gordon
has eyeballed the UI flow in a browser.
---
## 2026-06-03 feat: R2 — video sweep + per-emotion peak frames feed the shared session
**Capability:** facial-expression-toy-app
**Uses:** mediapipe, torch (LSTM), cv2, gradio
**Approach:** Extended `preprocess_video_and_predict` to keep a
`frame_records` buffer of `(frame_idx, face_crop, prob_vector,
blendshapes)` for each analysed frame (one every `FRAME_DOWNSAMPLING`
face-detected frames). Post-loop, `_extract_peak_captures` derives 7
`Capture` instances by per-emotion argmax across the LSTM probability
vectors. Returns the existing 4-tuple (videos + plot) extended to a
5-tuple with the peak captures list. `app.py` lifted `session_captures`
from the Static tab scope to the demo level so both tabs share one
session; new `submit_video_and_add` handler runs the video pipeline,
merges the 7 peaks into the same `gr.State` that R1's Static submit
writes to, and surfaces a status markdown on the Dynamic tab telling
the student what was added. `Capture.heatmap` made `Optional` —
peak captures don't carry a heatmap (would require 7 extra Grad-CAM
passes; pedagogically not required).
**Related:** online-teaching-for-bence
Smoke test on the bundled `videos/BillClinton.mp4`: 7 peak captures
extracted. Pedagogically useful artefact: `peak-from-video:happiness`
lands on a frame where the actual top class is *Anger* 0.48 — the
clip never registers strongly happy, so the "happiest" frame is
still angry. That mismatch between the label (what we extracted
for) and the top emotion (what the classifier called it) is the
variability story made directly visible in the gallery caption.
`scratch/smoke_video_peaks.py` runs the pipeline end-to-end on the
bundled clip; Gradio app boots in ~18s with no warnings.
---
## 2026-06-03 feat: R1 — multi-capture session memory landed
**Capability:** facial-expression-toy-app
**Uses:** gradio (gr.State, gr.Gallery), mediapipe, torch
**Approach:** New `app/session.py` carrying a `Capture` dataclass
(label, face crop, heatmap, emotion probabilities, blendshapes) plus
`LABEL_PRESETS` (17 entries: 7 warm-up emotions, 6 hard AUs from the
FACS Build-an-AU section, 3 crash-emotion conditions, and a custom
free-text option). `app_utils.preprocess_image_and_predict` extended
to a 4-tuple return that surfaces the blendshape dict R0 plumbed.
`app.py` Static tab rewritten: per-tab `gr.State` accumulates
`Capture` instances across submissions; a label-preset dropdown +
visibility-toggled custom textbox set the label before each submission;
a `gr.Gallery` renders the session as a grid of cropped-face thumbnails
captioned with the resolved label and the top emotion; Remove-last and
Clear-session buttons act on the state. Latest-capture readout (face +
heatmap + 7-emotion label) preserved alongside the gallery so each
submission gives immediate feedback. Also moved `gr.Blocks(css=)` to
`launch(css_paths=[...])` (Gradio 6.0 deprecation).
**Related:** online-teaching-for-bence
Smoke tests in `scratch/`: session ops correct, Capture renders the
right caption (`warm-up:happy · Happiness (0.99)`), blendshapes line
up with AU 12 (`mouthSmileLeft/Right` ~0.92 on Happy.png). Gradio app
boots in ~18s with no deprecation warnings, server reaches "Running
on" state. UI flow (gallery rendering, click handlers, visibility
toggles) needs manual browser eyeball — verifying click wiring from
the CLI isn't feasible.
Env hygiene: `numexpr` (transitive of the now-removed py-feat) was
hard-failing pandas import on numpy 2.x. Uninstalled — pandas degrades
gracefully without it. Worth a clean venv rebuild from `requirements.txt`
at some point but not blocking.
---
## 2026-06-03 feat: R0 — face detection switched to MediaPipe FaceLandmarker tasks API
**Capability:** facial-expression-toy-app
**Uses:** mediapipe (tasks API), torch, pytorch_grad_cam
**Approach:** Implemented R0 (Option δ). New `app/face_landmarker.py`
module wraps MediaPipe FaceLandmarker as a lazy singleton, exposing
`detect(pil_image) -> (landmarks, blendshapes)` and
`bbox_from_landmarks(...)`. Bundle downloads on first call via the
existing `load_model()` helper, driven by a new `[model_landmarker]`
section in `config.toml`. `app/app_utils.py` rewritten to call the new
module instead of `mp.solutions.face_mesh` (broken on Apple Silicon);
return signatures preserved so `app.py` is unchanged. Dead code
(`get_box`, `norm_coordinates`) removed from `app/face_utils.py`;
`display_info` retained for the dynamic-video overlay. Smoke test
post-R0 confirms end-to-end pipeline runs cleanly and classifier
output matches pre-R0 results within rounding (Disgust 0.987,
Fear 0.992, Happiness 0.986, Sadness 0.990, Surprise 0.763).
Blendshapes are extracted on every detection but not yet surfaced to
the UI — they land with R1's session memory rewrite.
**Related:** online-teaching-for-bence
Also cleaned: broken py-feat install removed from the venv. uv.lock
committed for reproducible builds.
---
## 2026-06-03 audit: pipeline walked, smoke-tested upstream, evaluated AU detection options
**Capability:** facial-expression-toy-app
**Uses:** mediapipe (tasks API), torch, pytorch_grad_cam (rejected: py-feat)
**Approach:** Walked the existing pipeline (`app.py` + `app/` package, six
modules) and ran three smoke tests under `scratch/`. Confirmed the
ResNet50 + Grad-CAM static path works on bundled samples — top class
hits the labelled emotion on every well-defined sample (Disgust 0.92,
Fear 0.97, Happiness 0.995, Sadness 0.99, Surprise 0.84; Anger
top-2 with Fear; "contempt" maps to Anger because the model has no
contempt class). MediaPipe legacy solutions API
(`mp.solutions.face_mesh` in `app_utils.py`) is broken on Apple Silicon
mediapipe>=0.10 — only the tasks API ships; blocks local dev as-is.
Py-feat 0.6.2 rejected as an AU-detection route: imports fail against
torchvision 0.27 (uses removed `torchvision.io.read_video`), confirmed
in both the main venv and an isolated venv. Pivoted to MediaPipe Face
Landmarker (tasks API), which downloads its own bundle and runs on
Apple Silicon: bilateral ARKit blendshape output maps cleanly onto the
lesson's target AUs (`browInnerUp` = AU 1, `cheekSquintLeft/Right` =
AU 6 Duchenne, `mouthDimpleLeft/Right` = AU 14 dimpler, etc.). Sample
sanity-check: Fear shows asymmetric `browOuterUp` 0.49/0.29; contempt
shows unilateral `mouthDimpleLeft` 0.10 alongside asymmetric smile.
**Related:** online-teaching-for-bence
**Recommendation pending user approval: Option δ.** Replace
`mp.solutions.face_mesh` with the tasks API Face Landmarker; reuse the
same model for both face cropping (478 landmarks → bbox, replacing
`get_box`) and blendshape output (AU-equivalent feedback). Keep
upstream ResNet50/LSTM for emotion classification. Three wins: fixes
local dev, gives the FACS Build-an-AU section the per-AU feedback it
needs, no new heavy dependency. Adds R0 ahead of R1 in the R-plan at
`~/Code/teaching/online-teaching-for-bence/toy-app-plan.md`:
"Replace legacy `mp.solutions.face_mesh` with the tasks API Face
Landmarker — required to run on Apple Silicon, unlocks blendshape
output." Awaiting user approval before implementation.
Smoke tests live under `scratch/` (gitignored content, scripts kept
local; not committed — they're throwaway evidence for this decision).
---
## 2026-06-03 chore: rename repo from all-a-bit-emotional to totes-emosh
**Capability:** facial-expression-toy-app
**Uses:** —
**Approach:** Renamed the local codewright fork from `all-a-bit-emotional`
to `totes-emosh` at Gordon's request. Updated `.codewright.yaml`,
`pyproject.toml`, README, DEVLOG, registry, and capability index.
Upstream HuggingFace Space slug at LittleMonkeyLab/All_a_bit_emotional
is unchanged.
**Related:** online-teaching-for-bence
---
## 2026-06-03 init: standalone fork of LittleMonkeyLab/All_a_bit_emotional HF Space
**Capability:** new: facial-expression-toy-app
**Uses:** gradio, torch, mediapipe, grad-cam, hf-hub (upstream Space)
**Approach:** Downloaded the live HuggingFace Space via `hf download` and
re-anchored it as a codewright-managed repo at
`~/Code/research/totes-emosh/` (originally named `all-a-bit-emotional`,
renamed same day). No code changes — clean fork of
the deployed Space as the baseline for the toy-app rework described in
`~/Code/teaching/online-teaching-for-bence/toy-app-plan.md` (R1–R5).
Added `pyproject.toml` as the codewright-canonical dependency manifest;
preserved upstream `requirements.txt` for HuggingFace Space deployment.
**Related:** online-teaching-for-bence
Standalone exists so the rework can be code-reviewed and version-controlled
before pushing back upstream as the new canonical Space. Original Space
remains live and unchanged at
https://huggingface.co/spaces/LittleMonkeyLab/All_a_bit_emotional.
Next session: walk the existing `app/` package and plan the R1 (multi-capture
session memory) integration point.
---
|