academic-pdf-redaction
Redact text from PDF documents for blind review anonymization
- License 许可
- Apache-2.0
- 最近更新
- 2026-11-14
- Stars
- ★ 0
- Forks
- 1
01 前言 Front Matter
- name
- academic-pdf-redaction
- description
- Redact text from PDF documents for blind review anonymization
03 SKILL.md 正文
PDF Redaction for Blind Review
Redact identifying information from academic papers for blind review.
CRITICAL RULES
- PRESERVE References section - Self-citations MUST remain intact. Prefer keeping the References pages byte-identical to the original (skip redaction entirely on those pages) so self-citations are provably untouched.
- ONLY redact specific text matches - Never redact entire pages/regions
- VERIFY output - Check page counts preserved and ≥80% text retention, then run a regex leak scan on pre-references pages.
Common Pitfalls to AVOID
# ❌ WRONG - This removes ALL text from the page:
for block in page.get_text("blocks"):
page.add_redact_annot(fitz.Rect(block[:4]))
# ❌ WRONG - Drawing rectangles over text:
page.draw_rect(fitz.Rect(0, 0, 600, 100), fill=(0,0,0))
# ✅ CORRECT - Only redact specific search matches:
for rect in page.search_for("John Smith"):
page.add_redact_annot(rect)
Patterns to Redact (Before References Only)
IMPORTANT: Use FULL names/phrases, not partial matches!
- ✅ "John Smith" (full name)
- ❌ "Smith" (partial - would incorrectly match "Smith et al." citations in References)
- Author names - FULL names only (e.g., "John Smith", not just "Smith")
- Affiliations - Universities, companies (e.g., "Duke University")
- Email addresses - Pattern:
*@*.edu,*@*.com - Venue names - Conference/workshop names (e.g., "ICML 2024", "ICML Workshop"), including "Accepted at ..." / "To appear in ..." banners.
- arXiv identifiers - Pattern:
arXiv:XXXX.XXXXX, AND arXiv banner fragments left on the side margin such as subject tags[eess.AS],[cs.CL]and submission dates like30 Sep 2025. These often appear on a rotated margin and must be searched for explicitly. - DOIs - Pattern:
10.XXXX/... - Acknowledgement content - Names AND grant/funder identifiers (e.g., ARO/NSF/DARPA grant numbers like
W911NF-23-2-0224). Grant numbers frequently break across lines (e.g.,W911NF/-23-2-/0224), so also add patterns for each broken fragment and the surrounding phrase (e.g.,and ARO,was supported in part by). After redaction, re-open the file and inspect residual context around funder acronyms to catch stray connective tokens likeand ARO. - Equal contribution footnotes - e.g., "Equal contribution", "* Equal contribution"
PyMuPDF (fitz) - Recommended Approach
import fitz
import os
def redact_with_pymupdf(input_path: str, output_path: str, patterns: list[str]):
"""Redact specific patterns from PDF using PyMuPDF."""
doc = fitz.open(input_path)
# Find References page - stop redacting there (leave those pages byte-identical)
references_page = None
for i, page in enumerate(doc):
if "references" in page.get_text().lower():
references_page = i
break
for page_num, page in enumerate(doc):
if references_page is not None and page_num >= references_page:
continue # Skip References section entirely -> byte-identical
for pattern in patterns:
for rect in page.search_for(pattern):
page.add_redact_annot(rect, fill=(0, 0, 0))
page.apply_redactions()
os.makedirs(os.path.dirname(output_path), exist_ok=True)
doc.save(output_path)
doc.close()
verify_redaction(input_path, output_path)
REQUIRED: Verification + Leak Scan
Always run BOTH after redaction:
import fitz, re
def verify_redaction(original_path, output_path):
"""Verify redaction didn't corrupt the PDF."""
orig = fitz.open(original_path)
redc = fitz.open(output_path)
orig_len = sum(len(p.get_text()) for p in orig)
redc_len = sum(len(p.get_text()) for p in redc)
print(f"Original: {len(orig)} pages, {orig_len} chars")
print(f"Redacted: {len(redc)} pages, {redc_len} chars")
print(f"Retained: {redc_len/orig_len:.1%}")
if len(redc) != len(orig):
raise ValueError(f"Page count changed: {len(orig)} -> {len(redc)}")
if redc_len < 1000:
raise ValueError(f"PDF corrupted: only {redc_len} chars remain!")
if redc_len < orig_len * 0.7:
raise ValueError(f"Too much removed: kept only {redc_len/orig_len:.0%}")
orig.close(); redc.close()
print("✓ Verification passed")
def leak_scan(output_path, references_page, known_leaks: list[str], regexes: list[str]):
"""Scan pre-references pages for any residual identifying tokens."""
doc = fitz.open(output_path)
hits = []
for i, page in enumerate(doc):
if references_page is not None and i >= references_page:
break
txt = page.get_text()
for s in known_leaks:
if s.lower() in txt.lower():
hits.append((i, s))
for pat in regexes:
for m in re.finditer(pat, txt, flags=re.IGNORECASE):
hits.append((i, m.group(0)))
doc.close()
if hits:
print("✗ LEAKS:", hits)
else:
print("✓ CLEAN")
return hits
Run leak_scan with regexes covering: author-name list, affiliations, \w+@\w+\.\w+, arXiv:\d{4}\.\d{4,5}, subject tags \[[a-z]+\.[A-Z]{2}\], dates like \d{1,2}\s+[A-Z][a-z]{2}\s+20\d{2}, DOIs 10\.\d{4,9}/, grant IDs (e.g., W911NF[-\d]*, \d{7} NSF-style), and connective tokens near funders (and ARO, and NSF).
Iterative fix loop
If the leak scan surfaces a residual token, inspect its surrounding context with page.get_text() slice to catch line-broken fragments (e.g., a grant ID split across three lines yielding a stray and ARO). Add each fragment as its own pattern, re-run, and re-scan until CLEAN for every paper.
Preserving self-citations
Because References pages are skipped entirely, self-citations remain intact by construction. After saving, optionally confirm by comparing extracted text of References pages between original and redacted files and asserting they are byte-identical.
04 同仓库更多 Skills
video-frame-extraction
Extract frames from video files and save them as images using OpenCV
openai-vision
Analyze images and multi-frame sequences using OpenAI GPT vision models
image-ocr
Extract text content from images using Tesseract OCR via Python, including structured field extraction from receipts and forms
video-frame-extraction
Extract frames from video files and save them as images using OpenCV
openai-vision
Analyze images and multi-frame sequences using OpenAI GPT vision models
image-ocr
Extract text content from images using Tesseract OCR via Python, including structured field extraction from receipts and forms