{
  "source_file": "0930",
  "source_platform": "Hashnode",
  "author": "Lakshmi Mahabaleshwara",
  "title_listed": "Testing a Medical Image De-identification App with Passmark: How I Built and Tested Aegis",
  "keywords": "",
  "domain": "图像处理",
  "content_type": "深度技术解析",
  "is_paid_flag": "否",
  "url": "https://lakshmi-mahabaleshwara.hashnode.dev/testing-a-medical-image-de-identification-app-with-passmark-how-i-built-and-tested-aegis",
  "publish_date": "2026-05-10",
  "corpus_stage": "",
  "notes": "",
  "id": "c91b66334a57",
  "collected_at": "2026-10-02T14:12:08",
  "title": "Testing a Medical Image De-Identification App with Passmark",
  "body_markdown": "Testing a Medical Image De-identification App with Passmark: How I Built and Tested Aegis\nFrom manual pixel scrubbing to batch automation — deploying Aegis on Hugging Face and testing with AI-driven Playwright.\nUpdated\nMay 10, 2026\n•\n7\nmin read\n•\nView as Markdown\nL\nLakshmi Mahabaleshwara\nData Engineer and AI developer focused on healthcare AI, medical imaging, and scalable ML systems. Building open-source tools for medical data de-identification, privacy-preserving AI, and healthcare analytics.\nThe bottleneck nobody automates\nIf you work with medical imaging data, you've probably heard this story or lived it. A researcher needs 500 ultrasound images for a study. Every single one has patient names, dates, and hospital IDs burned into the pixels. Not in DICOM metadata that you can strip programmatically. Literally rendered as text overlays on the image itself.\nThe standard approach? Open each image one by one in a tool like 3D Slicer or RadiAnt DICOM Viewer, manually locate the PHI regions, black them out, save, repeat. For 500 images, that's hours of mind-numbing work. For 10,000 images: a modest dataset by today's standards, it's simply not feasible.\nThis is why I built\nAegis\n— an open-source pipeline that does this automatically, in batch, across entire datasets. For the Breaking Apps Hackathon, I deployed it as a web demo and wrote an AI-powered test suite for it using Passmark.\nWhat is Aegis?\nAegis\nis an open-source medical image de-identification pipeline. It handles both DICOM files (the native format of medical scanners) and standard JPEG/PNG images. The core pipeline has three layers:\nLayer 1 — Clinical Allowlist.\nDevice manufacturer names like \"Mindray\" or \"GE\", imaging modes like \"2D\" or \"Doppler\", and measurement text like \"D 13.0cm\" are recognized and preserved. You don't want to black out the clinical information that makes the image diagnostically useful.\nLayer 2 — PHI Heuristics.\nRegex patterns catch obvious PHI: date-ID combinations (\n20260117-091825\n), hospital names, doctor prefixes (\nDr. Smith\n), and long numeric IDs. These are redacted immediately without needing AI inference.\nLayer 3 — Stanford NER.\nEverything that wasn't caught by layers 1–2 goes through the Stanford De-identifier NER model (F1 ≥ 97.9%), which classifies text into 22 entity types — PATIENT, DOCTOR, HOSPITAL, DATE, IDNUM, and more. Any PHI entity gets redacted.\nThe key difference from manual tools: Aegis processes entire datasets in batch. No clicking through images one by one. Point it at a folder, walk away, and come back to a clean dataset.\nDeploying to Hugging Face Spaces with Gradio\nFor the hackathon, I needed a publicly accessible web app — no bot checks, no login walls. Hugging Face Spaces with Gradio was the natural choice.\nThe demo lets you see the pipeline in action on a single image:\nUpload a medical image (JPEG/PNG)\nClick \"De-identify Image\"\nSee the redacted output, an annotated overlay showing what was detected, and a detailed report\nHere's the core of the classification logic:\ndef classify_text(text): \n\n  # Layer 1 — clinical allowlist \n  if is_clinical_allowlisted(text): return {\"is_phi\": False,    \"label\": \"CLINICAL\"}\n\n  # Layer 2 — clinical patterns (probe IDs, measurements)\n  if matches_clinical_pattern(text):\n    return {\"is_phi\": False, \"label\": \"CLINICAL\"}\n\n  # Layer 3 — PHI heuristics\n  if matches_phi_heuristic(text):\n    return {\"is_phi\": True, \"label\": \"PHI_HEURISTIC\"}\n\n  # Layer 4 — Stanford NER model\n  if classify_with_ner(text):\n    return {\"is_phi\": True, \"label\": \"PHI_NER\"}\n\nreturn {\"is_phi\": False, \"label\": \"SAFE\"}\nEach text region found by EasyOCR goes through this pipeline. The order matters — cheap checks first (allowlist, regex), expensive AI inference last. This keeps batch processing fast.\nDeployment\nwas three files pushed to a HF Spaces repo:\napp.py\n— the Gradio application\nrequirements.txt\n— dependencies (gradio, easyocr, transformers, torch)\nREADME.md\n— HF Spaces metadata\nWriting Passmark Tests\nThis is where it gets interesting.\nPassmark\nlets you write Playwright tests in plain English. The AI figures out how to interact with your app — no CSS selectors, no\ndata-testid\nattributes, no brittle XPath queries.\nThis matters especially for Gradio apps. Gradio generates dynamic HTML where the DOM structure isn't designed for test automation. Traditional Playwright tests would mean reverse-engineering the class names and element hierarchy. Passmark skips that entirely.\nHere's my test for the core de-identification flow:\nimport { test, expect } from \"@playwright/test\";\nimport { runSteps } from \"passmark\";\n\ntest(\"Upload image and verify de-identification runs\", async ({ page }) => {\n  test.setTimeout(180_000);\n\n  await runSteps({\n    page,\n    userFlow: \"Load Aegis app\",\n    steps: [\n      { description: \"Navigate to the Aegis app URL\" },\n      {\n        description: \"Wait for page to load\",\n        waitUntil: \"The page shows text about Medical Image and has a large blue button\",\n      },\n    ],\n    assertions: [{ assertion: \"You can see an image upload area on the page\" }],\n    test,\n    expect,\n  });\n\n  // Native Playwright for file upload (Gradio's hidden input)\n  const fileInput = page.locator('input[type=\"file\"]');\n  await fileInput.setInputFiles(\"tests/fixtures/sample_medical_image.png\");\n  await page.waitForTimeout(2000);\n\n  await runSteps({\n    page,\n    userFlow: \"Run de-identification\",\n    steps: [\n      {\n        description: \"Click the large blue button below the image upload area\",\n        waitUntil: \"A report appears with words like REDACTED or PRESERVED\",\n      },\n    ],\n    assertions: [\n      { assertion: \"You can see text mentioning 'Total text regions detected'\" },\n      { assertion: \"You can see an output image on the right side of the page\" },\n    ],\n    test,\n    expect,\n  });\n});\nOne practical lesson: I used a hybrid approach — Passmark for navigation and assertions, native Playwright for the file upload. Gradio's file input is a hidden\n<input type=\"file\">\nelement, and Passmark's AI agent struggled with the drag-and-drop widget. Native Playwright's\nsetInputFiles\nworks reliably. This hybrid pattern is worth knowing for anyone testing Gradio or Streamlit apps with Passmark.\nThe full test suite covers five flows:\nApp loads correctly\n— heading, upload area, and primary button exist\nUpload and de-identify\n— the core happy path\nTab switching\n— Redacted ↔ Annotated views work\nEmpty state\n— clicking without an upload shows a warning\nReport structure\n— output table has the expected columns (Text, Confidence, Classification, Action)\nSetting up the test project\n# Initialize\nmkdir aegis-passmark-tests && cd aegis-passmark-tests\nnpm init -y\nnpm install @playwright/test passmark dotenv\nnpx playwright install chromium\n\n# Add your API keys (.env)\nOPENROUTER_API_KEY=sk-or-...  # free credits from hackathon registration\n\n# Run tests\nnpx playwright test\nWhat I learned\nThe classification problem is harder than detection.\nFinding text in medical images is straightforward — EasyOCR handles it well. The real challenge is deciding\nwhich\ntext is PHI and which is clinically relevant. \"D 13.0cm\" is a measurement. \"Jane Smith\" is a patient name. \"GE\" is a device manufacturer. \"MI 0.7\" is a mechanical index. The three-layer pipeline exists because no single approach handles all these correctly.\nManual tools don't scale.\nTools like 3D Slicer and RadiAnt are excellent for viewing and annotating individual images. But using them for de-identification means opening each image, manually identifying PHI regions, blacking them out, and saving — one at a time. For research datasets with thousands of images, this is the bottleneck that blocks entire projects. Batch automation changes the game.\nPassmark is well-suited for AI-generated UIs.\nGradio, Streamlit, and similar frameworks produce UIs where the DOM structure is an implementation detail, not a stable API. Natural language test steps abstract over that. One gotcha: be very specific in your descriptions. \"Click the De-identify button\" failed because the button had an emoji prefix. \"Click the large blue button\" worked immediately.\nThe hybrid testing pattern is practical.\nPure Passmark tests are elegant, but some UI interactions (file uploads, drag-and-drop) work better with native Playwright. Mixing both in the same test gives you the best of both worlds — AI-driven navigation and assertions, deterministic file operations.\nLinks\nAegis GitHub\n:\ngithub.com/lakshmi-mahabaleshwara/aegis\nLive Demo\n:\nHugging Face Space\nPassmark Test Repo\n:\nhttps://github.com/lakshmi-mahabaleshwara/aegis-passmark-tests\nPassmark\n:\npassmark.dev\nThis project was created for the\n#BreakingAppsHackathon\n, where I used Passmark, Playwright, Gradio, and Hugging Face Spaces to test Aegis: Healthcare AI application.\nAegis is DICOM PS3.15 and HIPAA Safe Harbor compliant. The Stanford De-identifier NER model is from\nStanfordAIMI\n.\n#\nbreakingappshackathon\n#\nplaywright\n#\ngradio\n#\npassmark\n#\nartificial-intelligence\n#\nplaywright-automation\n17\nviews",
  "metadata": {
    "title": "Testing a Medical Image De-Identification App with Passmark",
    "description": "Learn how I deployed Aegis with Gradio and Hugging Face Spaces, then used Passmark and Playwright to test a healthcare AI application.",
    "keywords": null,
    "author": null,
    "og:title": "Testing a Medical Image De-Identification App with Passmark",
    "og:description": "Learn how I deployed Aegis with Gradio and Hugging Face Spaces, then used Passmark and Playwright to test a healthcare AI application.",
    "og:url": "https://lakshmi-mahabaleshwara.hashnode.dev/testing-a-medical-image-de-identification-app-with-passmark-how-i-built-and-tested-aegis",
    "og:site_name": "Lakshmi Mahabaleshwara",
    "og:image": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/fa333788-266e-482e-ae85-8aa7653824b9.png",
    "og:image:width": "1200",
    "og:image:height": "630",
    "og:type": "article",
    "twitter:card": "summary_large_image",
    "twitter:title": "Testing a Medical Image De-Identification App with Passmark",
    "twitter:description": "Learn how I deployed Aegis with Gradio and Hugging Face Spaces, then used Passmark and Playwright to test a healthcare AI application.",
    "twitter:image": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/fa333788-266e-482e-ae85-8aa7653824b9.png",
    "article:published_time": "2026-05-10T06:33:59.906Z",
    "article:modified_time": "2026-05-10T06:33:59.906Z",
    "article:author": "https://lakshmi-mahabaleshwara.hashnode.dev/@lakshmi-mahabalesh"
  },
  "paywall": {
    "listed_paid": false,
    "detected": false,
    "marker": "",
    "public_only": false,
    "policy": "付费/会员内容只保留页面公开可见部分，不绕过付费墙"
  },
  "crawl": {
    "success": true,
    "status_code": 200,
    "error": ""
  },
  "code_blocks": [
    {
      "file": "attachments/c91b66334a57/code/body-000.txt",
      "chars": 565,
      "language": "text"
    },
    {
      "file": "attachments/c91b66334a57/code/body-001.txt",
      "chars": 1307,
      "language": "text"
    },
    {
      "file": "attachments/c91b66334a57/code/body-002.txt",
      "chars": 291,
      "language": "text"
    }
  ],
  "images": [
    {
      "status": "ok",
      "url": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/fa333788-266e-482e-ae85-8aa7653824b9.png",
      "bytes": 38512,
      "reused": true,
      "local_path": "attachments/c91b66334a57/images/002.png",
      "src": "https://cdn.hashnode.com/uploads/covers/69fd77e89f93a850a46d376f/fa333788-266e-482e-ae85-8aa7653824b9.png",
      "alt": "Testing a Medical Image De-identification App with Passmark: How I Built and Tested Aegis",
      "downloaded": true
    }
  ],
  "audio": [],
  "videos": [],
  "links": [
    {
      "href": "https://lakshmi-mahabaleshwara.hashnode.dev/testing-a-medical-image-de-identification-app-with-passmark-how-i-built-and-tested-aegis.md",
      "label": "View as Markdown",
      "type": "a"
    },
    {
      "href": "https://hashnode.com/@lakshmi-mahabalesh",
      "label": "L",
      "type": "a"
    },
    {
      "href": "https://www.linkedin.com/in/lakshmimahabaleshwara/",
      "label": "",
      "type": "a"
    },
    {
      "href": "https://github.com/lakshmi-mahabaleshwara/aegis",
      "label": "Aegis",
      "type": "a"
    },
    {
      "href": "https://passmark.dev/",
      "label": "Passmark",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/spaces/lakshmi-mahabaleshwara/aegis-medical-image-deidentification",
      "label": "Hugging Face Space",
      "type": "a"
    },
    {
      "href": "https://github.com/lakshmi-mahabaleshwara/aegis-passmark-tests",
      "label": "https://github.com/lakshmi-mahabaleshwara/aegis-passmark-tests",
      "type": "a"
    },
    {
      "href": "http://passmark.dev/",
      "label": "passmark.dev",
      "type": "a"
    },
    {
      "href": "https://hashnode.com/hackathons/breaking-things",
      "label": "#BreakingAppsHackathon",
      "type": "a"
    },
    {
      "href": "https://huggingface.co/StanfordAIMI/stanford-deidentifier-base",
      "label": "StanfordAIMI",
      "type": "a"
    },
    {
      "href": "https://lakshmi-mahabaleshwara.hashnode.dev/tag/breakingappshackathon",
      "label": "# breakingappshackathon",
      "type": "a"
    },
    {
      "href": "https://lakshmi-mahabaleshwara.hashnode.dev/tag/playwright",
      "label": "# playwright",
      "type": "a"
    },
    {
      "href": "https://lakshmi-mahabaleshwara.hashnode.dev/tag/gradio",
      "label": "# gradio",
      "type": "a"
    },
    {
      "href": "https://lakshmi-mahabaleshwara.hashnode.dev/tag/passmark",
      "label": "# passmark",
      "type": "a"
    },
    {
      "href": "https://lakshmi-mahabaleshwara.hashnode.dev/tag/artificial-intelligence",
      "label": "# artificial-intelligence",
      "type": "a"
    },
    {
      "href": "https://lakshmi-mahabaleshwara.hashnode.dev/tag/playwright-automation",
      "label": "# playwright-automation",
      "type": "a"
    }
  ],
  "retry_at": "2026-10-03T08:09:32",
  "repair_version": 2,
  "repaired_at": "2026-10-14T10:50:59Z",
  "source_url": "https://lakshmi-mahabaleshwara.hashnode.dev/testing-a-medical-image-de-identification-app-with-passmark-how-i-built-and-tested-aegis",
  "papers": [],
  "actual_video_files": [],
  "body_chars": 9035,
  "expected_body_images": 1,
  "discovery_status": "ok",
  "delivery_status": "ok",
  "missing_reason": "",
  "raw_html": "attachments/c91b66334a57/pages/source.html",
  "local_html": "attachments/c91b66334a57/pages/article.html",
  "body_base_path": "attachments/c91b66334a57/pages/",
  "body_markdown_path": "attachments/c91b66334a57/pages/article.md"
}