| |
| |
| |
| """End-to-end inference + redaction example for screenpipe/pii-image-redactor. |
| |
| Usage: |
| python examples/inference.py <screenshot.png> |
| |
| Output: |
| - Prints detections (label, score, bbox) to stdout |
| - Writes <screenshot>_redacted.png next to the input with detected |
| regions blacked out |
| """ |
| from __future__ import annotations |
|
|
| import sys |
| import time |
| from pathlib import Path |
|
|
| import numpy as np |
| import onnxruntime as ort |
| from PIL import Image, ImageDraw |
|
|
| CLASSES = [ |
| "private_person", "private_email", "private_phone", |
| "private_address", "private_url", "private_company", |
| "private_repo", "private_handle", "private_channel", |
| "private_id", "private_date", "secret", |
| ] |
| INPUT_SIZE = 320 |
| NUM_CLASSES = 12 |
| NUM_QUERIES = 300 |
| THRESHOLD = 0.30 |
|
|
|
|
| def detect(model_path: str, image_path: str) -> tuple[list[dict], float]: |
| sess = ort.InferenceSession( |
| model_path, |
| providers=["CoreMLExecutionProvider", "CPUExecutionProvider"], |
| ) |
|
|
| img = Image.open(image_path).convert("RGB") |
| W, H = img.size |
| resized = img.resize((INPUT_SIZE, INPUT_SIZE), Image.BILINEAR) |
| arr = np.asarray(resized, dtype=np.float32) / 255.0 |
| arr = (arr - np.array([0.485, 0.456, 0.406], dtype=np.float32)) \ |
| / np.array([0.229, 0.224, 0.225], dtype=np.float32) |
| arr = arr.transpose(2, 0, 1)[None].astype(np.float32) |
|
|
| t0 = time.time() |
| boxes, logits = sess.run(None, {sess.get_inputs()[0].name: arr}) |
| latency_ms = (time.time() - t0) * 1000.0 |
|
|
| boxes = boxes[0] |
| logits = logits[0] |
|
|
| probs = 1.0 / (1.0 + np.exp(-logits[:, :NUM_CLASSES])) |
| best_class = probs.argmax(axis=1) |
| best_score = probs[np.arange(NUM_QUERIES), best_class] |
| keep = best_score >= THRESHOLD |
|
|
| dets: list[dict] = [] |
| for q in np.where(keep)[0]: |
| cx, cy, bw, bh = boxes[q] |
| x1 = max(0.0, (cx - bw / 2) * W) |
| y1 = max(0.0, (cy - bh / 2) * H) |
| w_px = bw * W |
| h_px = bh * H |
| if w_px <= 0 or h_px <= 0: |
| continue |
| dets.append({ |
| "label": CLASSES[best_class[q]], |
| "score": float(best_score[q]), |
| "bbox": [int(x1), int(y1), int(w_px), int(h_px)], |
| }) |
| dets.sort(key=lambda d: -d["score"]) |
| return dets, latency_ms |
|
|
|
|
| def redact(image_path: str, dets: list[dict], out_path: str) -> None: |
| """Draw solid black rectangles over each detection. Solid black, |
| NOT blur — blur is reversible by super-resolution attacks.""" |
| img = Image.open(image_path).convert("RGB") |
| draw = ImageDraw.Draw(img) |
| for d in dets: |
| x, y, w, h = d["bbox"] |
| draw.rectangle([x, y, x + w, y + h], fill=(0, 0, 0)) |
| img.save(out_path) |
|
|
|
|
| def main() -> int: |
| if len(sys.argv) < 2: |
| print("usage: python inference.py <image.png> [model.onnx]", file=sys.stderr) |
| return 2 |
| image_path = sys.argv[1] |
| model_path = sys.argv[2] if len(sys.argv) >= 3 else "rfdetr_v8.onnx" |
|
|
| dets, latency = detect(model_path, image_path) |
| print(f"[detect] {len(dets)} detections in {latency:.0f} ms") |
| for d in dets: |
| x, y, w, h = d["bbox"] |
| print(f" {d['label']:18} score={d['score']:.2f} bbox=[{x},{y},{w},{h}]") |
|
|
| out = Path(image_path).with_name(Path(image_path).stem + "_redacted.png") |
| redact(image_path, dets, str(out)) |
| print(f"[redact] wrote {out}") |
| return 0 |
|
|
|
|
| if __name__ == "__main__": |
| sys.exit(main()) |
|
|