# How to Implement Re-identification with Reversible De-identification in OpenMed

> Learn to implement re-identification with reversible de-identification in OpenMed. Set keep_mapping=True in deidentify() and use reidentify() to restore PII.

- Repository: [Maziyar Panahi/openmed](https://github.com/maziyarpanahi/openmed)
- Tags: how-to-guide
- Published: 2026-06-11

---

**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`](https://github.com/maziyarpanahi/openmed/blob/main/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/main/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/main/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:

```python
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`:

```python
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:

```python
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:

```python
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/main/tests/unit/test_privacy_filter_security.py)](https://github.com/maziyarpanahi/openmed/blob/master/tests/unit/test_privacy_filter_security.py#L718):

```python
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`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/core/pii.py)** | Contains `deidentify()` (line 5070), `reidentify()` (line 1345), and `DeidentificationResult` (line 997) |
| **[`openmed/core/pii_i18n.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/core/pii_i18n.py)** | Language-specific fake data tables for the `replace` method |
| **[`openmed/core/anonymizer/engine.py`](https://github.com/maziyarpanahi/openmed/blob/main/openmed/core/anonymizer/engine.py)** | Surrogate generation logic for synthetic replacement values |
| **[`tests/unit/test_privacy_filter_security.py`](https://github.com/maziyarpanahi/openmed/blob/main/tests/unit/test_privacy_filter_security.py)** | Test suite validating the reversible workflow (line 718) |

## Summary

- **Set `keep_mapping=True`** in `deidentify()` 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.