How to Implement Re-identification with Reversible De-identification in OpenMed
To implement re-identification with reversible de-identification in OpenMed, set keep_mapping=True when calling deidentify() to capture placeholder-to-original mappings, then pass the de-identified text and mapping dictionary to reidentify() to restore the original PII.
OpenMed provides a privacy-preserving NLP pipeline that supports reversible de-identification, allowing you to mask sensitive patient information for analysis while maintaining the ability to restore original values when necessary. This workflow is essential for healthcare applications requiring audit trails or downstream systems that need to recover original identifiers after secure processing. The implementation centers on a protected mapping dictionary generated by the de-identification functions in openmed/core/pii.py.
How Reversible De-identification Works
The reversible workflow relies on a three-step process encoded in OpenMed’s core PII module. When you enable mapping preservation, the library maintains a strict one-to-one correspondence between redacted placeholders and their original values, enabling perfect reconstruction of the source document.
The DeidentificationResult Structure
According to the source code at line 997 in [openmed/core/pii.py](https://github.com/maziyarpanahi/openmed/blob/master/openmed/core/pii.py#L997), the DeidentificationResult class acts as a container for the operation metadata. When keep_mapping=True is passed to deidentify() (implemented at line 5070), the result object includes a mapping attribute—a Python dictionary structured as {"[NAME]": "John Doe", "[PHONE]": "555-123-4567"}.
The mapping stores the relationship between the redacted token (key) and the original PII string (value), regardless of which masking method you employ.
The Re-identification Algorithm
The reidentify() helper function, defined at line 1345 in [openmed/core/pii.py](https://github.com/maziyarpanahi/openmed/blob/master/openmed/core/pii.py#L1345), performs the restoration through a straightforward string replacement loop:
def reidentify(
deidentified_text: str,
mapping: dict[str, str],
) -> str:
"""Re-identify text using stored mapping."""
result = deidentified_text
for redacted, original in mapping.items():
result = result.replace(redacted, original)
return result
This function iterates over the stored mapping dictionary and replaces every occurrence of each placeholder with its corresponding original value. The algorithm assumes exact token matching, so the integrity of the mapping is critical for accurate reconstruction.
Step-by-Step Implementation
Step 1: De-identify with Mapping Preservation
Call deidentify() with the keep_mapping=True parameter to generate the reversible mapping. This works with any masking method—mask, replace, hash, or remove:
from openmed import deidentify
doc = "Patient John Doe (DOB: 01/15/1970) called from 555-123-4567"
result = deidentify(
doc,
method="mask",
keep_mapping=True, # Critical for re-identification
)
print(result.deidentified_text)
# Output: Patient [NAME] (DOB: [DATE]/1970) called from [PHONE]
print(result.mapping)
# Output: {'[NAME]': 'John Doe', '[DATE]': '01/15/1970', '[PHONE]': '555-123-4567'}
Step 2: Secure Storage of the Mapping
The mapping dictionary contains raw PII and must be treated as sensitive data. In production environments, encrypt this mapping at rest and restrict access to authorized personnel only. The docstring of reidentify() explicitly warns developers that the mapping contains unprotected personal information.
Step 3: Restore Original Text with reidentify()
Pass the de-identified string and the secured mapping to reidentify() to recover the original document:
from openmed import reidentify
original = reidentify(result.deidentified_text, result.mapping)
print(original)
# Output: Patient John Doe (DOB: 01/15/1970) called from 555-123-4567
Complete Code Examples
Round-Trip with Realistic Replacement Data
When using the replace method with consistent=True, the mapping stores the surrogate-to-original relationship, enabling re-identification even after realistic fake data generation:
result = deidentify(
doc,
method="replace",
keep_mapping=True,
lang="en",
consistent=True, # Stable surrogates for repeated entities
)
# The mapping now contains surrogate → original pairs
# Example: {'Alice Smith': 'John Doe'}
recovered = reidentify(result.deidentified_text, result.mapping)
assert recovered == doc
Unit Testing the Reversible Workflow
Validate your implementation using the round-trip pattern found in the official test suite at line 718 of [tests/unit/test_privacy_filter_security.py](https://github.com/maziyarpanahi/openmed/blob/master/tests/unit/test_privacy_filter_security.py#L718):
def test_round_trip():
text = "Dr. Ana Gómez, ID 123-45-6789, called on 12/05/2021."
res = deidentify(text, method="mask", keep_mapping=True)
recovered = reidentify(res.deidentified_text, res.mapping)
assert recovered == text
Security Considerations for Production
The mapping dictionary represents a complete decryption key for your de-identified corpus. Unlike one-way hashing or masking, reversible de-identification requires strict security controls:
- Encryption at rest: Store the mapping file using AES-256 or equivalent encryption
- Access control: Limit mapping retrieval to authenticated audit.systems or authorized clinical reviewers
- Audit logging: Track every re-identification event with timestamps and user identifiers
- Retention policies: Automatically purge mappings after the legally required retention period expires
The OpenMed source code does not automatically encrypt the mapping object—this responsibility falls to the implementing organization.
Key Source Files
| File | Role |
|---|---|
openmed/core/pii.py |
Contains deidentify() (line 5070), reidentify() (line 1345), and DeidentificationResult (line 997) |
openmed/core/pii_i18n.py |
Language-specific fake data tables for the replace method |
openmed/core/anonymizer/engine.py |
Surrogate generation logic for synthetic replacement values |
tests/unit/test_privacy_filter_security.py |
Test suite validating the reversible workflow (line 718) |
Summary
- Set
keep_mapping=Trueindeidentify()to capture the placeholder-to-original mapping required for re-identification - Store the mapping securely—it contains raw PII and functions as a decryption key for your data
- Call
reidentify()with the de-identified text and mapping dictionary to restore original values - Validate with round-trip tests to ensure the mapping correctly handles all PII entities in your documents
- Secure the mapping using encryption and access controls appropriate for sensitive health information
Frequently Asked Questions
What happens if I call reidentify() without a mapping?
If you attempt to call reidentify() with an empty or missing mapping dictionary, the function will return the de-identified text unchanged. The original PII cannot be restored without the mapping—this is an intentional privacy-by-design feature that prevents accidental re-identification when the mapping is intentionally discarded.
Does the mapping work with all de-identification methods?
Yes, the mapping mechanism functions across all OpenMed methods including mask, replace, hash, and remove. However, note that when using hash, the mapping still contains the original PII values, which may violate the security assumptions of hashing if the mapping is compromised. For true one-way anonymization, use keep_mapping=False.
How does OpenMed handle repeated PII entities in the mapping?
OpenMed consolidates repeated entities into a single mapping entry. For example, if "John Doe" appears multiple times in a document and is replaced with "[NAME]" consistently, the mapping dictionary will contain one entry {'[NAME]': 'John Doe'}. The reidentify() function uses str.replace(), which substitutes all occurrences of the placeholder automatically.
Is the mapping encrypted automatically by OpenMed?
No, OpenMed returns the mapping as a standard Python dictionary in memory (or serialized JSON). The library does not implement automatic encryption of the mapping object. You must implement encryption at rest and secure transmission protocols (TLS 1.3) when persisting or transmitting the mapping in production healthcare environments.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →