Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
85e510c
Add face anonymization module for photogrammetry scans
Mah-diaa Mar 9, 2026
cafb370
Add auto nasion detection and anonymization notebooks
Mah-diaa Mar 20, 2026
f48015e
Merge pull request #1 from Mah-diaa/feature/auto-nasion-detection
Mah-diaa Mar 20, 2026
9502f9a
Redesign validation: replace redundant metrics with sanity check + 10…
Mah-diaa Mar 31, 2026
30c373e
Merge pull request #2 from Mah-diaa/feature/validation-sanity-check
Mah-diaa Mar 31, 2026
822e3c1
Add face anonymization module for photogrammetry scans
Mah-diaa Apr 6, 2026
35e330b
Add interactive add/remove mode and adjustable brush to refinement UI
Mah-diaa Apr 6, 2026
df82868
Improve refinement UI controls visibility and clear outputs
Mah-diaa Apr 6, 2026
d469a11
Improve anonymization reliability and expose public API
Mah-diaa Apr 13, 2026
8a9dab1
Make mediapipe an optional extra for anonymization
Mah-diaa Apr 13, 2026
d63d526
Add manual 5pt anonymization notebook and reorganize examples
Mah-diaa Apr 16, 2026
db366d2
Update notebook 48 with latest outputs
Mah-diaa Apr 16, 2026
06d2282
Remove smoothing-based anonymization code and exploratory notebooks
Mah-diaa Apr 16, 2026
be4ee21
Extract manual-pipeline helpers into face_detector and slim notebook 48
Mah-diaa Apr 16, 2026
95a7a4c
Remove MediaPipe pipeline from main (moved to auto-detection-pipeline…
Mah-diaa Apr 17, 2026
53b7bd1
Split face_detector into preprocessing/landmarks/mask; drop dead pick…
Mah-diaa Apr 17, 2026
f024bc0
Preserve texture UVs through anonymization and add save helper
Mah-diaa Apr 18, 2026
0db1c62
Switch anonymization to CTF frame; add revert_to_einstar_frame and la…
Mah-diaa Apr 21, 2026
6de0cf9
Merge remote-tracking branch 'upstream/dev'
Mah-diaa Apr 21, 2026
138c5df
Add thesis_results_out/ to .gitignore
Mah-diaa May 1, 2026
651c7ef
Restrict main to deletion-only anonymization pipeline; move validatio…
Mah-diaa May 1, 2026
5dd37c2
Remove geometric Nz-fallback landmark detection (kept on auto-detecti…
Mah-diaa May 3, 2026
713436a
Add face anonymization module for photogrammetry scans
Mah-diaa May 3, 2026
2240761
Add manual 5-point anonymization example notebook
Mah-diaa May 3, 2026
01a6317
Drop tests for removed detect_landmarks_from_nasion; fix isolate_head…
Mah-diaa May 3, 2026
9b7f408
Expand anonymization test coverage: all 8 public functions plus end-t…
Mah-diaa May 3, 2026
fe86585
Refactor anonymization module: add anonymize_scan orchestrator and sh…
Mah-diaa May 3, 2026
215ad35
Update example notebook for anonymize_scan API
Mah-diaa May 3, 2026
b9782bf
Merge main into feature/face-anonymization
Mah-diaa May 3, 2026
4a9ae70
Drop machine-specific paths and ANON_OPTIONS dict from notebook 51
Mah-diaa May 5, 2026
e5e4f50
Add README, reproducibility script, and gitignore for thesis submission
Mah-diaa May 5, 2026
6118b00
Remove reproduce_anonymization.py
Mah-diaa May 5, 2026
d3e5b97
Expose mask kwargs in notebook 51; drop batch-script mention from README
Mah-diaa May 5, 2026
887040b
Update branch layout table in README
Mah-diaa May 5, 2026
3575007
Move _copy_visual/_rebuild_mesh to _utils; fix docstring language
Mah-diaa May 6, 2026
9ec2df4
Remove unused _resolve_texture_image import; fix em dashes in pipelin…
Mah-diaa May 6, 2026
f078191
Add anonymize_scan tests: smoke, ctf frame, missing landmark
Mah-diaa May 6, 2026
34e2688
test + readme: add docstrings, file tree, and test approach docs
Mah-diaa May 6, 2026
1640ab8
readme: remove em dashes and AI phrasing
Mah-diaa May 6, 2026
8cec88b
anonymization: drop landmarks kwarg and tsv sidecar from save_anonymi…
Mah-diaa May 7, 2026
3b196c9
readme: drop tsv-sidecar mention; describe validation-branch notebook…
Mah-diaa May 7, 2026
d530df0
readme: list notebook 69 in validation-branch table
Mah-diaa May 7, 2026
af89378
readme + nb 51 intro: tighten prose, drop AI-style padding
Mah-diaa May 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,9 @@ scratch/
plugins/

.claude/worktrees/sharp-burnell/

# thesis-only generated artifacts (regenerable from validation notebooks)
examples/head_models/thesis_results_out/

# machine-local scan-data symlink (points to /home/ma7/BA/PG_Subjects)
examples/head_models/PG_Subjects
108 changes: 108 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,111 @@
# Face Anonymization for Photogrammetry Scans

M.Sc. thesis at TU Berlin / IBS Lab: "Development of a Landmark-Aware Face-Removal Algorithm for Photogrammetric Head Scans for Data Protection".

This is a fork of [cedalion](https://github.com/ibs-lab/cedalion) with a geometric face anonymization module for Einstar photogrammetry scans used in fNIRS research. The module lives at:

```
src/cedalion/geometry/photogrammetry/anonymization/
```

## Installation

Requires the cedalion conda environment:

```bash
conda env create -f environment_dev.yml
conda activate cedalion
pip install -e .
```

## Usage

### Interactive single-scan workflow

Open `examples/head_models/51_manual_5pt_anonymization.ipynb`. The notebook:
1. Loads an Einstar scan (`cedalion.io.read_einstar_obj`)
2. Picks the five 10-20 landmarks interactively (Nz, Iz, Cz, LPA, RPA)
3. Calls `anonymize_scan(surface, landmarks)`
4. Shows a before/after comparison
5. Saves the anonymized OBJ + sanitized JPG texture bundle via `save_anonymized_scan`

## Module structure

```
src/cedalion/geometry/photogrammetry/anonymization/
├── __init__.py public API, re-exports all functions listed below
├── pipeline.py anonymize_scan (entry point)
├── preprocessing.py normalize_axes, isolate_head, align_axes_from_landmarks,
│ revert_to_einstar_frame
├── mask.py detect_cap_boundary, face_mask_from_landmarks,
│ delete_masked_vertices, save_anonymized_scan
└── _utils.py private helpers shared by preprocessing and mask
(_rebuild_mesh, _copy_visual, _reindex_faces,
_apply_affine, _transform_labeled_points,
_ear_midpoint, _upper_head_centroid,
_resolve_texture_image)

examples/head_models/
└── 51_manual_5pt_anonymization.ipynb interactive workflow notebook

tests/
└── test_anonymization.py 26 unit tests
```

Pipeline steps inside `anonymize_scan`:

1. `normalize_axes`: rotate so +Y points anterior (handles arbitrary Einstar orientation)
2. `isolate_head`: remove body, shoulders, and disconnected fragments
3. `align_axes_from_landmarks`: map to CTF frame (+X anterior, +Y left, +Z up)
4. `detect_cap_boundary`: locate the front cap-edge height along Z
5. `face_mask_from_landmarks`: face region union ear spheres, clamped below the cap
6. Landmark preservation: 8 mm spheres around each landmark + midline nasion strip
7. `delete_masked_vertices`: drop triangles touching any masked vertex, UVs in sync
8. `revert_to_einstar_frame`: return to `crs="digitized"` for saving

## Tests

```bash
pytest tests/test_anonymization.py -v
```

26 tests covering all eight public functions and the end-to-end pipeline. No real scan data needed: all tests build synthetic geometry with `trimesh.creation.icosphere`, the same approach used elsewhere in cedalion (see `test_geodesics.py`, `test_dataclasses_geometry.py`). Three fixtures are shared:

- `simple_sphere_surface`: unit icosphere for geometry-only checks
- `head_like_surface`: elongated icosphere (X scaled x1.2), giving the masking step a real face region to remove
- `axis_normalized_landmarks`: five `LabeledPoints` (Nz, Iz, Cz, LPA, RPA) on the sphere axes in the post-`normalize_axes` frame

## Branch layout

| Branch | Contents |
|--------|---------|
| `feature/face-anonymization` | **This branch**: thesis implementation (anonymization module, notebook 51, test suite) |
| `main` | Upstream cedalion base |
| `validation/face-anonymization` | Measurement notebooks behind the thesis results tables (see below) |
| `auxiliary/mediapipe-nasion` | Experimental automatic nasion detection (MediaPipe) |

### Validation branch contents

The notebooks on `validation/face-anonymization` produce the per-subject numbers in the thesis results tables. They run on top of the shipped pipeline's output and do not modify it.

| Notebook | What it checks | Headline result on the 11-subject cohort |
|---|---|---|
| `64_batch_validation` | Driver that runs the four validators below on every subject and emits per-subject CSVs | per-subject CSVs feeding the thesis tables |
| `66_preservation_check` | 10-20 landmark deviation + bit-exact vertex preservation across the surviving surface | 0.000 mm across all 55 landmark measurements (5 landmarks x 11 subjects) |
| `68_coreg_invariance` | Cedalion `ColoredStickerProcessor` re-run on original vs anonymized mesh (optode-cap subcohort) | 131 matched stickers, 0 mm deviation in sticker centres and scalp-projected optode positions |
| `69_coreg_visual_check` | Side-by-side render of detected stickers on the original and anonymized mesh; any original-side detection without a match on the anonymized side is drawn in red so the operator can spot it | qualitative; the manual visibility check that backs notebook 68 |
| `70_auxiliary_nasion` | MediaPipe Face Landmarker auto-nasion vs the manually picked nasion | 13.68 mm cohort mean offset, 30.78 mm worst case |
| `72_face_detectability_comparison` | MediaPipe Face Detector hit counts under a 21-view sweep: original vs vertex-deletion vs noise-perturbation | 93/231 -> 34/231 (63% reduction); optode-cap subcohort 32/147 -> 4/147 (88%); noise reaches 28/231 (Wilcoxon p=0.375, not distinguishable from deletion) |
| `73_s8_mediapipe_boxes` | BlazeFace bounding box and six-keypoint inspection on a single subject | qualitative figure for the discussion of why post-deletion residual hits are silhouette artefacts |

Helper modules `_thesis_data.py`, `_thesis_pipeline.py`, `_validator_noise.py`, and `_validator_render.py` hold the cohort walk, pipeline call, noise-perturbation operator, and contact-sheet rendering shared across these notebooks.

---

*Upstream cedalion documentation below.*

---

# Cedalion - fNIRS analysis toolbox

A python-based framework for the data-driven analysis of multimodal fNIRS and DOT in naturalistic environments. Developed by the [Intelligent Biomedical Sensing (IBS) Lab](https://ibs-lab.com/) with and for the community.
Expand Down
8 changes: 7 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# Changelog

## Unreleased changes (available on the `dev` branch)


### Additions and Changes

#### Photogrammetry

- Added `cedalion.geometry.photogrammetry.anonymization`, a deletion-based face removal pipeline for photogrammetry scans. From the five anatomical landmarks (Nz, Iz, Cz, LPA, RPA), the pipeline normalizes axes, isolates the head, detects the cap boundary, builds a face mask, deletes the masked vertices, and saves the anonymized mesh together with the landmarks. See `examples/head_models/51_manual_5pt_anonymization.ipynb` for an end-to-end walkthrough.

## Version 26.04.0 (in preparation)

### Additions and Changes
Expand Down
Loading