pseudopros 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. pseudopros-0.1.0/.github/workflows/publish.yml +46 -0
  2. pseudopros-0.1.0/.gitignore +15 -0
  3. pseudopros-0.1.0/.python-version +1 -0
  4. pseudopros-0.1.0/LICENSE +21 -0
  5. pseudopros-0.1.0/PKG-INFO +366 -0
  6. pseudopros-0.1.0/Packaging.md +26 -0
  7. pseudopros-0.1.0/README.md +302 -0
  8. pseudopros-0.1.0/experiments/CONCEPT.MD +36 -0
  9. pseudopros-0.1.0/experiments/Experiments.log +279 -0
  10. pseudopros-0.1.0/experiments/exp-001/download_hdtf.py +200 -0
  11. pseudopros-0.1.0/experiments/exp-001/eval.py +118 -0
  12. pseudopros-0.1.0/experiments/exp-001/train.py +553 -0
  13. pseudopros-0.1.0/experiments/exp-002/Dockerfile +68 -0
  14. pseudopros-0.1.0/experiments/exp-002/PLAN.md +165 -0
  15. pseudopros-0.1.0/experiments/exp-002/download_hdtf.py +200 -0
  16. pseudopros-0.1.0/experiments/exp-002/entrypoint.sh +30 -0
  17. pseudopros-0.1.0/experiments/exp-002/eval.py +118 -0
  18. pseudopros-0.1.0/experiments/exp-002/openshift/00-pvc.yaml +56 -0
  19. pseudopros-0.1.0/experiments/exp-002/openshift/01-secret.example.yaml +19 -0
  20. pseudopros-0.1.0/experiments/exp-002/openshift/02-job-precompute.yaml +75 -0
  21. pseudopros-0.1.0/experiments/exp-002/openshift/03-job-train.yaml +76 -0
  22. pseudopros-0.1.0/experiments/exp-002/openshift/04-build.yaml +40 -0
  23. pseudopros-0.1.0/experiments/exp-002/openshift/05-debug-pod.yaml +51 -0
  24. pseudopros-0.1.0/experiments/exp-002/openshift/README.md +118 -0
  25. pseudopros-0.1.0/experiments/exp-002/train.py +582 -0
  26. pseudopros-0.1.0/licenses.json +282 -0
  27. pseudopros-0.1.0/pyproject.toml +87 -0
  28. pseudopros-0.1.0/scripts/test_fomm_inference.py +99 -0
  29. pseudopros-0.1.0/scripts/webcam_demo.py +53 -0
  30. pseudopros-0.1.0/src/pseudopros/__init__.py +26 -0
  31. pseudopros-0.1.0/src/pseudopros/_cli.py +159 -0
  32. pseudopros-0.1.0/src/pseudopros/_fomm/__init__.py +0 -0
  33. pseudopros-0.1.0/src/pseudopros/_fomm/modules/__init__.py +0 -0
  34. pseudopros-0.1.0/src/pseudopros/_fomm/modules/dense_motion.py +113 -0
  35. pseudopros-0.1.0/src/pseudopros/_fomm/modules/generator.py +97 -0
  36. pseudopros-0.1.0/src/pseudopros/_fomm/modules/keypoint_detector.py +75 -0
  37. pseudopros-0.1.0/src/pseudopros/_fomm/modules/util.py +245 -0
  38. pseudopros-0.1.0/src/pseudopros/_fomm/sync_batchnorm/__init__.py +12 -0
  39. pseudopros-0.1.0/src/pseudopros/_fomm/sync_batchnorm/batchnorm.py +315 -0
  40. pseudopros-0.1.0/src/pseudopros/_fomm/sync_batchnorm/comm.py +137 -0
  41. pseudopros-0.1.0/src/pseudopros/_fomm/sync_batchnorm/replicate.py +94 -0
  42. pseudopros-0.1.0/src/pseudopros/anonymizer.py +227 -0
  43. pseudopros-0.1.0/src/pseudopros/embedding.py +115 -0
  44. pseudopros-0.1.0/src/pseudopros/fomm.py +299 -0
  45. pseudopros-0.1.0/src/pseudopros/gst.py +231 -0
  46. pseudopros-0.1.0/src/pseudopros/keying.py +113 -0
  47. pseudopros-0.1.0/src/pseudopros/security.py +177 -0
  48. pseudopros-0.1.0/src/pseudopros/synthesizer.py +262 -0
  49. pseudopros-0.1.0/src/pseudopros/unified_renderer/__init__.py +12 -0
  50. pseudopros-0.1.0/src/pseudopros/unified_renderer/model.py +234 -0
  51. pseudopros-0.1.0/tests/__init__.py +0 -0
  52. pseudopros-0.1.0/tests/test_anonymizer.py +216 -0
  53. pseudopros-0.1.0/tests/test_cli.py +209 -0
  54. pseudopros-0.1.0/tests/test_embedding.py +107 -0
  55. pseudopros-0.1.0/tests/test_fomm.py +71 -0
  56. pseudopros-0.1.0/tests/test_fomm_pretrained.py +54 -0
  57. pseudopros-0.1.0/tests/test_fomm_subject.py +110 -0
  58. pseudopros-0.1.0/tests/test_gst.py +166 -0
  59. pseudopros-0.1.0/tests/test_keying.py +107 -0
  60. pseudopros-0.1.0/tests/test_security.py +169 -0
  61. pseudopros-0.1.0/tests/test_synthesizer.py +138 -0
  62. pseudopros-0.1.0/vendor/first-order-model/__init__.py +0 -0
  63. pseudopros-0.1.0/vendor/first-order-model/demo.py +168 -0
  64. pseudopros-0.1.0/vendor/first-order-model/modules/__init__.py +0 -0
  65. pseudopros-0.1.0/vendor/first-order-model/modules/dense_motion.py +113 -0
  66. pseudopros-0.1.0/vendor/first-order-model/modules/generator.py +97 -0
  67. pseudopros-0.1.0/vendor/first-order-model/modules/keypoint_detector.py +75 -0
  68. pseudopros-0.1.0/vendor/first-order-model/modules/util.py +245 -0
  69. pseudopros-0.1.0/vendor/first-order-model/sync_batchnorm/__init__.py +12 -0
  70. pseudopros-0.1.0/vendor/first-order-model/sync_batchnorm/batchnorm.py +315 -0
  71. pseudopros-0.1.0/vendor/first-order-model/sync_batchnorm/comm.py +137 -0
  72. pseudopros-0.1.0/vendor/first-order-model/sync_batchnorm/replicate.py +94 -0
@@ -0,0 +1,46 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+ id-token: write
10
+ attestations: write
11
+
12
+ jobs:
13
+ build:
14
+ name: Build distribution
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: actions/setup-python@v5
19
+ with:
20
+ python-version: "3.12"
21
+ - name: Install hatch
22
+ run: pip install hatch
23
+ - name: Build wheel and sdist
24
+ run: hatch build
25
+ - name: Attest build provenance
26
+ uses: actions/attest-build-provenance@v2
27
+ continue-on-error: true
28
+ with:
29
+ subject-path: dist/*
30
+ - uses: actions/upload-artifact@v4
31
+ with:
32
+ name: dist
33
+ path: dist/
34
+
35
+ publish:
36
+ name: Publish to PyPI
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+ environment: pypi
40
+ steps:
41
+ - uses: actions/download-artifact@v4
42
+ with:
43
+ name: dist
44
+ path: dist/
45
+ - name: Publish via Trusted Publisher (OIDC)
46
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,15 @@
1
+
2
+ media/*
3
+
4
+ *.ipynb_checkpoints/*
5
+
6
+ .venv/
7
+ __pycache__/
8
+ *.pyc
9
+ *.egg-info/
10
+ dist/
11
+ .pytest_cache/
12
+ .claude/
13
+ .spark/
14
+ CLAUDE.MD
15
+ uv.lock
@@ -0,0 +1 @@
1
+ 3.12.8
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Cory Root
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,366 @@
1
+ Metadata-Version: 2.4
2
+ Name: pseudopros
3
+ Version: 0.1.0
4
+ Summary: Cryptographically secure facial anonymization for computer vision pipelines
5
+ Project-URL: Homepage, https://github.com/dactylroot/pseudopros
6
+ Project-URL: Repository, https://github.com/dactylroot/pseudopros
7
+ Project-URL: Issues, https://github.com/dactylroot/pseudopros/issues
8
+ Author-email: Cory Root <dactylroot@gmail.com>
9
+ License: MIT License
10
+
11
+ Copyright (c) 2024 Cory Root
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in all
21
+ copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
+ SOFTWARE.
30
+ License-File: LICENSE
31
+ Keywords: anonymization,computer-vision,cryptography,face,privacy,re-identification,video
32
+ Classifier: Development Status :: 3 - Alpha
33
+ Classifier: Intended Audience :: Developers
34
+ Classifier: Intended Audience :: Science/Research
35
+ Classifier: License :: OSI Approved :: MIT License
36
+ Classifier: Programming Language :: Python :: 3
37
+ Classifier: Programming Language :: Python :: 3.12
38
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
39
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
40
+ Classifier: Topic :: Security :: Cryptography
41
+ Requires-Python: >=3.12
42
+ Requires-Dist: accelerate>=0.30
43
+ Requires-Dist: cryptography>=41.0
44
+ Requires-Dist: diffusers<0.31,>=0.28
45
+ Requires-Dist: huggingface-hub>=0.20
46
+ Requires-Dist: imageio-ffmpeg>=0.4
47
+ Requires-Dist: imageio>=2.31
48
+ Requires-Dist: insightface>=0.7
49
+ Requires-Dist: numpy<2.0,>=1.24
50
+ Requires-Dist: onnxruntime<1.20,>=1.16
51
+ Requires-Dist: opencv-python>=4.8
52
+ Requires-Dist: pillow>=10.0
53
+ Requires-Dist: pyyaml>=6.0
54
+ Requires-Dist: scikit-image<0.24,>=0.21
55
+ Requires-Dist: scipy<1.14,>=1.11
56
+ Requires-Dist: torch<2.3,>=2.2
57
+ Requires-Dist: torchvision<0.18,>=0.17
58
+ Provides-Extra: dev
59
+ Requires-Dist: hatch>=1.12; extra == 'dev'
60
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
61
+ Requires-Dist: pytest>=8.0; extra == 'dev'
62
+ Requires-Dist: rtsp>=2.0.2; extra == 'dev'
63
+ Description-Content-Type: text/markdown
64
+
65
+ # Pseudopros
66
+
67
+ Pseudo (false) + Prosopon (face/mask)
68
+
69
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⡆⠀⠀⠀⠀
70
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⠃⣠⠀⠀⠀
71
+ ⠀⠀⠀⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣤⣶⣿⣿⣿⣿⣿⠇⢰⣿⣇⠀⠀
72
+ ⠀⠀⣼⣿⣿⣶⣤⣀⡀⠀⠀⠀⠀⠻⠿⠟⠛⣿⣿⣿⣿⣿⣿⡏⠀⠛⣿⣿⡄⠀
73
+ ⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣶⣶⣶⡆⢀⡛⠛⠛⠛⢿⣿⣆⣀⣠⣿⣿⡧⠀
74
+ ⠀⢸⣉⠁⠀⢠⣿⣿⠁⣈⡙⠛⠻⠿⠿⠃⠸⠿⠇⠀⠀⢈⡁⢸⣿⣿⡿⠟⣡⠀
75
+ ⠀⣿⣿⣆⣀⣸⣿⡿⠀⣿⣿⣿⣷⣶⣶⣶⣶⣶⣶⣾⣿⣿⣇⠈⠟⢉⣤⣾⡇⠀
76
+ ⠀⢸⣿⣿⣿⣿⣿⡇⠀⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠀⣶⣿⣿⠟⠀⠀
77
+ ⠀⠈⣉⣉⣉⣉⡉⠓⠀⣶⣦⣤⠀⠉⠙⣿⣿⡏⠉⠀⣤⣤⡖⠀⠉⠉⠁⠀⠀⠀
78
+ ⠀⠀⠘⢿⣿⣿⣿⣿⡀⢿⣿⣿⣶⣤⠀⣿⣿⠁⢠⣶⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀
79
+ ⠀⠀⠀⠈⠛⠻⠿⠟⠃⠘⣿⣿⣿⣿⣶⣿⣿⣶⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀
80
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣯⣉⠉⠉⠉⢉⣩⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀
81
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣿⣿⣶⣿⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀
82
+ ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
83
+
84
+ A Python library for cryptographically secure facial anonymization in computer vision pipelines.
85
+
86
+ Pseudopros replaces real faces in video streams with deterministic synthetic faces that:
87
+
88
+ - **Cannot be re-identified** - output faces are cryptographically unlinked from the original (validated against ArcFace ReID)
89
+ - **Are repeatable** - the same input face always produces the same synthetic output for a given key and salt
90
+ - **Are cross-source** - the same key and salt produce consistent identities across independently captured video of the same subject
91
+ - **Preserve motion** - expression, pose, and movement data are retained for downstream tracking and aggregate analysis
92
+
93
+ Intended for use within streaming pipelines (e.g. GStreamer). Keys and salts are passed as parameters and should be stored remotely, never embedded with the video.
94
+
95
+ ## Security properties
96
+
97
+ | Property | Mechanism |
98
+ |---|---|
99
+ | Re-identification resistance | LDM-generated face is orthogonal to original in ArcFace space (validated: similarity ≈ -0.03 vs threshold 0.28) |
100
+ | Repeatability | HKDF is deterministic; same inputs always produce the same seed and therefore the same synthetic face |
101
+ | Cross-source consistency | Key derivation depends only on the identity embedding, not the video source |
102
+ | Key isolation | Rotating the salt changes all synthetic identities without changing the secret |
103
+
104
+ ## Cryptography
105
+
106
+ pseudopros depends on the [PyCA `cryptography`](https://cryptography.io) library for one purpose: **HKDF-SHA256** key derivation in `keying.py`.
107
+
108
+ **HKDF** (HMAC-based Key Derivation Function, [RFC 5869](https://datatracker.ietf.org/doc/html/rfc5869)) turns a secret key and a subject embedding into a fixed-length seed:
109
+
110
+ | Input | Value |
111
+ |---|---|
112
+ | IKM (input key material) | Your secret bytes (`secret`) |
113
+ | Salt | Per-deployment salt (`salt`) |
114
+ | Info | `b"pseudopros-v1:" + subject_embedding_bytes` |
115
+ | Output length | 32 bytes |
116
+
117
+ The `info` field provides domain separation: it binds the derived seed to this library version and to the specific subject identity. Changing the subject embedding, the salt, or the secret each produces an independent seed.
118
+
119
+ No encryption, signing, MAC verification, or other crypto primitives are used anywhere else in the codebase.
120
+
121
+ ## How it works
122
+
123
+ 1. **Enrollment** - ArcFace embeds the subject's face into a 512-dimensional identity vector
124
+ 2. **Key derivation** - HKDF-SHA256 over `(secret, salt, identity_vector)` produces a 32-byte seed
125
+ 3. **Synthetic face generation** - the seed drives a Latent Diffusion Model to produce a deterministic synthetic face unrelated to the original
126
+ 4. **Animation** - First Order Motion Model transfers the subject's motion onto the synthetic face frame by frame
127
+ 5. **Paste-back** - the animated region is blended back into the original frame with a soft oval mask
128
+
129
+ The synthetic face is the cryptographic output: it is a pure function of `(secret, salt, subject)` with no recoverable path to the original identity.
130
+
131
+ ## Installation
132
+
133
+ ```bash
134
+ pip install pseudopros
135
+ ```
136
+
137
+ Requires Python 3.12. PyTorch is installed from the PyTorch CPU index on Intel Mac:
138
+
139
+ ```bash
140
+ pip install pseudopros --extra-index-url https://download.pytorch.org/whl/cpu
141
+ ```
142
+
143
+ On first use, pseudopros downloads three models:
144
+
145
+ - **InsightFace buffalo_l** (~280 MB, cached in `~/.insightface/`) - ArcFace face detection and embedding
146
+ - **dactylroot/pseudopros-fomm** (~695 MB, cached in `~/.cache/huggingface/`) - First Order Motion Model checkpoint
147
+ - **CompVis/ldm-celebahq-256** (~1.3 GB, cached in `~/.cache/huggingface/`) - Latent Diffusion Model for synthetic face generation (only when using `RosterSynthesizer`)
148
+
149
+ ## Usage
150
+
151
+ ### Common usage
152
+
153
+ ```python
154
+ import pseudopros
155
+
156
+ # Build the pipeline (FOMM weights auto-download ~695 MB on first call)
157
+ anon = pseudopros.Anonymizer.build(
158
+ secret=b"your-secret-key-min-16-bytes",
159
+ salt=b"your-deployment-salt",
160
+ )
161
+
162
+ # Enroll subjects from representative frames
163
+ anon.enroll(frame_of_alice, "alice")
164
+ anon.enroll(frame_of_bob, "bob")
165
+
166
+ # Process frames — every enrolled subject found is anonymized
167
+ for frame in video_frames:
168
+ output = anon.process(frame) # HWC uint8 RGB, same shape as input
169
+ ```
170
+
171
+ Each subject keeps their own motion baseline across frames. Faces not matching any
172
+ enrolled subject are left unchanged. To remove a subject mid-stream:
173
+
174
+ ```python
175
+ anon.unenroll("alice")
176
+ print(anon.enrolled_ids) # ["bob"]
177
+ ```
178
+
179
+ To use a local FOMM checkpoint instead of downloading:
180
+
181
+ ```python
182
+ anon = pseudopros.Anonymizer.build(
183
+ secret=b"your-secret-key-min-16-bytes",
184
+ salt=b"your-deployment-salt",
185
+ checkpoint_path="path/to/fomm.pth.tar",
186
+ config_path="path/to/fomm-config.yml",
187
+ )
188
+ ```
189
+
190
+ ### Security validation
191
+
192
+ ```python
193
+ import pseudopros
194
+
195
+ audit = pseudopros.SecurityAudit(embedder)
196
+ result = audit.audit_frame(original_frame, anonymized_frame)
197
+
198
+ print(result.is_secure) # True if similarity < threshold
199
+ print(result.similarity) # ArcFace cosine similarity (lower is more secure)
200
+
201
+ # Audit a sequence
202
+ summary = audit.audit_sequence(original_frames, anonymized_frames)
203
+ print(summary)
204
+ # SecurityAudit - 30 frame(s)
205
+ # All secure: True
206
+ # Threshold: 0.280
207
+ # Max similarity: 0.0031
208
+ # Mean similarity: -0.0018
209
+ # Face detection: 30/30 anonymized frames
210
+ ```
211
+
212
+ ### Configuring the synthesizer
213
+
214
+ The default synthesizer is `NoiseSynthesizer` - a fast placeholder that produces non-face-like output. Use it during development to skip the LDM model download.
215
+
216
+ For production anonymization, use `RosterSynthesizer` (downloads ~1.3 GB on first call):
217
+
218
+ ```python
219
+ import pseudopros
220
+
221
+ anon = pseudopros.Anonymizer.build(
222
+ secret=b"your-secret-key",
223
+ salt=b"your-salt",
224
+ synthesizer=pseudopros.RosterSynthesizer(num_steps=50),
225
+ )
226
+ ```
227
+
228
+ For large known subject populations, `RapidSynthesizer` uses a pre-generated face bank for O(1) enrollment with no per-subject model inference:
229
+
230
+ ```python
231
+ anon = pseudopros.Anonymizer.build(
232
+ secret=b"your-secret-key",
233
+ salt=b"your-salt",
234
+ synthesizer=pseudopros.RapidSynthesizer("path/to/face_bank.npy"),
235
+ )
236
+
237
+ # Build a face bank offline (one-time, ~20s per face on CPU)
238
+ pseudopros.build_face_bank(n=10_000, output_path="face_bank.npy")
239
+ ```
240
+
241
+ ### Advanced / custom components
242
+
243
+ Lower-level components are importable from their submodules when you need to customize the pipeline:
244
+
245
+ ```python
246
+ # Custom embedder (e.g. swap InsightFace for another ArcFace backend)
247
+ from pseudopros.embedding import InsightFaceEmbedder, FaceEmbedder, DetectedFace
248
+
249
+ embedder = InsightFaceEmbedder(model_pack="buffalo_sc") # lighter model
250
+ anon = pseudopros.Anonymizer(model, key, embedder)
251
+
252
+ # Custom synthesizer (implement SynthesizerBackend protocol)
253
+ from pseudopros.synthesizer import SynthesizerBackend
254
+
255
+ class MyGANSynthesizer:
256
+ def synthesize(self, seed: bytes) -> np.ndarray:
257
+ ... # return (256, 256, 3) float32 [0, 1]
258
+
259
+ # Access FOMM directly
260
+ from pseudopros.fomm import FommModel, FommSubjectState
261
+
262
+ model = FommModel.from_pretrained(device="cpu") # auto-download
263
+ model = FommModel.from_checkpoint("model.pth.tar", "config.yml", device="cpu") # local
264
+
265
+ # Per-subject stateless API — safe to interleave subjects on a shared model
266
+ state_alice = model.prepare_subject(reference_image_alice)
267
+ state_bob = model.prepare_subject(reference_image_bob)
268
+
269
+ output_alice, state_alice = model.animate_subject(driving_frame_alice, state_alice)
270
+ output_bob, state_bob = model.animate_subject(driving_frame_bob, state_bob)
271
+
272
+ # Audit result types
273
+ from pseudopros.security import AuditSummary, FrameAuditResult
274
+ ```
275
+
276
+ ## Command-line interface
277
+
278
+ Anonymize a video file directly from the terminal:
279
+
280
+ ```bash
281
+ pseudopros anonymize input.mp4 output.mp4 \
282
+ --enrollment face.jpg \
283
+ --secret-file /path/to/secret.bin \
284
+ --synthesizer roster
285
+ ```
286
+
287
+ Secret options (pick one):
288
+
289
+ | Flag | Description |
290
+ |---|---|
291
+ | `--secret-file PATH` | Read raw secret bytes from a file. Recommended for production. |
292
+ | `--secret-hex HEX` | Secret as a hex string. |
293
+ | `PSEUDOPROS_SECRET` | Env var (hex). Used when no flag is provided. |
294
+
295
+ Additional options: `--salt-hex HEX`, `--device cpu\|cuda\|mps`, `--checkpoint PATH --config PATH` (to use a local FOMM checkpoint instead of downloading).
296
+
297
+ ## GStreamer integration
298
+
299
+ `pseudopros.gst` provides a `pseudopros` GStreamer element that applies face anonymization as an in-place video filter. It is a separate submodule so that the rest of the library works without GStreamer installed.
300
+
301
+ ### Requirements
302
+
303
+ ```bash
304
+ # Debian / Ubuntu
305
+ sudo apt-get install python3-gi gstreamer1.0-python3-plugin-scanner \
306
+ gstreamer1.0-plugins-base gstreamer1.0-plugins-good
307
+
308
+ # macOS (Homebrew)
309
+ brew install pygobject3 gstreamer
310
+ ```
311
+
312
+ ### Usage
313
+
314
+ Register the element once after `Gst.init()`, then use it in any pipeline string:
315
+
316
+ ```python
317
+ import gi
318
+ gi.require_version("Gst", "1.0")
319
+ from gi.repository import Gst
320
+
321
+ import pseudopros.gst as ppgst
322
+
323
+ Gst.init(None)
324
+ ppgst.register()
325
+
326
+ pipeline = Gst.parse_launch(
327
+ "v4l2src"
328
+ " ! videoconvert ! video/x-raw,format=RGB"
329
+ " ! pseudopros"
330
+ " secret=BASE64_SECRET"
331
+ " salt=BASE64_SALT"
332
+ " enrollment-image=/path/to/face.jpg"
333
+ " synthesizer=noise"
334
+ " ! videoconvert ! autovideosink"
335
+ )
336
+ pipeline.set_state(Gst.State.PLAYING)
337
+ ```
338
+
339
+ ### Element properties
340
+
341
+ | Property | Type | Description |
342
+ |---|---|---|
343
+ | `secret` | string | Base64-encoded secret key (min 16 bytes decoded) |
344
+ | `salt` | string | Base64-encoded deployment salt (default: `pseudopros`) |
345
+ | `enrollment-image` | string | Path to an image file for subject enrollment |
346
+ | `synthesizer` | string | `noise` (fast placeholder) or `roster` (full LDM, ~1.3 GB download) |
347
+
348
+ Secrets are base64-encoded for safe embedding in pipeline strings:
349
+
350
+ ```python
351
+ import base64
352
+ secret_b64 = base64.b64encode(b"your-secret-key-min-16-bytes").decode()
353
+ salt_b64 = base64.b64encode(b"your-deployment-salt").decode()
354
+ ```
355
+
356
+ The element enrolls on the first frame after `enrollment-image` is set. Changing `secret` or `salt` at runtime clears the cached anonymizer and re-enrolls on the next frame.
357
+
358
+ ## Running tests
359
+
360
+ ```bash
361
+ # Fast tests only (no model downloads)
362
+ pytest -m "not slow"
363
+
364
+ # Full suite including security validation (~5 min)
365
+ pytest
366
+ ```
@@ -0,0 +1,26 @@
1
+ ## Releasing a new version
2
+
3
+ 1. Bump `version` in `pyproject.toml`
4
+ 2. Commit and push:
5
+ 3. Create a GitHub Release (e.g. tagged `vX.Y.Z`) — this triggers the publish workflow:
6
+
7
+ The workflow builds the wheel, attests SLSA provenance, and publishes to PyPI via OIDC Trusted Publisher (no API tokens required).
8
+
9
+ ## README.md is the single source of truth
10
+
11
+ `pyproject.toml` embeds `README.md` as the PyPI long description. The package `__doc__`
12
+ is also read from it at import time via `importlib.metadata`. Do not duplicate
13
+ description content elsewhere.
14
+
15
+ ## Development setup
16
+
17
+ Use `pip install -e .` for an editable install. Do not use `uv pip install -e .` — uv
18
+ builds a wheel and copies it to site-packages rather than creating a true editable link,
19
+ so source changes are not reflected until you reinstall.
20
+
21
+ ```bash
22
+ pip install -e .
23
+ pytest -m "not slow" # fast suite (~30s, no model downloads)
24
+ pytest # full suite (~5 min, downloads models on first run)
25
+ ```
26
+