Spaces:
Running
Running
Commit
·
b300e4f
1
Parent(s):
455d70f
basic files
Browse files- app.py +362 -0
- requirements.txt +0 -0
app.py
ADDED
@@ -0,0 +1,362 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging
|
2 |
+
import logging.config
|
3 |
+
import re
|
4 |
+
from pathlib import Path
|
5 |
+
|
6 |
+
import gradio as gr
|
7 |
+
import matplotlib.pyplot as plt
|
8 |
+
import pandas as pd
|
9 |
+
import seaborn as sns
|
10 |
+
import yaml
|
11 |
+
|
12 |
+
import pteredactyl as pt
|
13 |
+
from pteredactyl.defaults import change_model # Ensure this import is correct
|
14 |
+
|
15 |
+
# Logging configuration. This is only done at root level
|
16 |
+
logging_config = yaml.safe_load(Path("logging.yaml").read_text())
|
17 |
+
logging.config.dictConfig(logging_config)
|
18 |
+
|
19 |
+
# Get the logger
|
20 |
+
log = logging.getLogger(__name__)
|
21 |
+
|
22 |
+
# Load the model
|
23 |
+
log.info("Starting App")
|
24 |
+
|
25 |
+
sample_text = """
|
26 |
+
1. Dr. Huntington (Patient No: 1234567890) diagnosed Ms. Alzheimer with Alzheimer's disease during her last visit to the Huntington Medical Center on 12/12/2023. The prognosis was grim, but Dr. Huntington assured Ms. Alzheimer that the facility was well-equipped to handle her condition despite the lack of a cure for Alzheimer's.
|
27 |
+
|
28 |
+
2. Paget Brewster (Patient No: 0987654321), a 45-year-old woman, was recently diagnosed with Paget's disease of bone by her physician, Dr. Graves at St. Jenny's Hospital on 01/06/2026 Postcode: JE30 6YN. Paget's disease is a chronic di[PERSON]der that affects bone remodeling, leading to weakened and deformed bones. Brewster's case is not related to Grave's disease, an autoimmune disorder affecting the thyroid gland.
|
29 |
+
|
30 |
+
3. Crohn Marshall (Patient No: 943 476 5918), a 28-year-old man, has been battling Crohn's disease for the past five years. Crohn's disease is a type of inflammatory bowel disease (IBD) that causes inflammation of the digestive tract. Marshall's condition is managed by his gastroenterologist, Dr. Ulcerative Colitis, who specializes in treating IBD patients at the Royal Free Hospital.
|
31 |
+
|
32 |
+
4. Addison Montgomery (NHS No: 5566778899), a 32-year-old woman, was rushed to University College Hospital on 18/09/2023 after experiencing severe abdominal pain and fatigue. After a series of tests, Dr. Cushing diagnosed Montgomery with Addison's disease, a rare disorder of the adrenal glands. Postcode is NH77 9AF. Montgomery's condition is not related to Cushing's syndrome, which is caused by excessive cortisol production.
|
33 |
+
|
34 |
+
5. Lou Gehrig (NHS No: 943 476 5919), a renowned baseball player, was diagnosed with amyotrophic lateral sclerosis (ALS) in 1939 at the Mayo Clinic. ALS, also known as Lou Gehrig's disease, is a progressive neurodegenerative disorder that affects nerve cells in the brain and spinal cord. Gehrig's diagnosis was confirmed by his neurologist, Dr. Bell, who noted that the condition was not related to Bell's palsy, a temporary facial paralysis.
|
35 |
+
|
36 |
+
6. Parkinson Brown (Patient No No: 3344556677), a 62-year-old man, has been living with Parkinson's disease for the past decade. Parkinson's disease is a neurodegenerative disorder that affects movement and balance. Brown's condition is managed by his neurologist, Dr. Lewy Body, who noted that Brown's symptoms were not related to Lewy body dementia, another neurodegenerative disorder, at King's College Hospital.
|
37 |
+
|
38 |
+
7. Kaposi Sarcoma (Patient No: 9988776655), a 35-year-old man, was recently diagnosed with Kaposi's sarcoma, a type of cancer that develops from the cells that line lymph or blood vessels, at Guy's Hospital on 17/04/2023. Sarcoma's diagnosis was confirmed by his oncologist, Dr. Burkitt Lymphoma, who noted that the condition was not related to Burkitt's lymphoma, an aggressive form of non-Hodgkin's lymphoma. He died on 17/04/2023.
|
39 |
+
|
40 |
+
8. Dr. Kawasaki (Patient No No: 2233445566) treated young Henoch Schonlein for Henoch-Schönlein purpura, a rare disorder that causes inflammation of the blood vessels, at Great Ormond Street Hospital on 05/05/2024. Schonlein's case was not related to Kawasaki disease, a condition that primarily affects children and causes inflammation in the walls of medium-sized arteries.
|
41 |
+
|
42 |
+
9. Wilson Menkes (NHS No: 943 476 5916), a 42-year-old man, was diagnosed with Wilson's disease, a rare genetic disorder that causes copper to accumulate in the body. Menkes' diagnosis was confirmed by his geneticist, Dr. Niemann Pick, at Addenbrooke's Hospital on 02/02/2025, who noted that the condition was not related to Niemann-Pick disease, another rare genetic disorder that affects lipid storage. Postcode was GH75 3HF.
|
43 |
+
|
44 |
+
10. Dr. Marfan (Patient No No: 4455667788) treated Ms. Ehlers Danlos for Ehlers-Danlos syndrome, a group of inherited disorders that affect the connective tissues, at the Royal Brompton Hospital on 30/11/2024. Danlos' case was not related to Marfan syndrome, another genetic disorder that affects connective tissue development and leads to abnormalities in the bones, eyes, and cardiovascular system. Dr Jab's username is: jabba
|
45 |
+
"""
|
46 |
+
|
47 |
+
# Gold Standard Text
|
48 |
+
reference_text = """
|
49 |
+
1. [PERSON] (Patient No: [ID]) diagnosed [PERSON] with Alzheimer's disease during her last visit to the [LOCATION] on [DATE_TIME]. The prognosis was grim, but [PERSON] assured [PERSON] that the facility was well-equipped to handle her condition despite the lack of a cure for Alzheimer's.
|
50 |
+
|
51 |
+
2. [PERSON] (Patient No: [ID]), a 45-year-old woman, was recently diagnosed with Paget's disease of bone by her physician, [PERSON] at [LOCATION] on [DATE_TIME] Postcode: [POSTCODE]. Paget's disease is a chronic disorder that affects bone remodeling, leading to weakened and deformed bones. [PERSON]'s case is not related to Grave's disease, an autoimmune disorder affecting the thyroid gland.
|
52 |
+
|
53 |
+
3. [PERSON] ([LOCATION] No: [NHS_NUMBER]), a 28-year-old man, has been battling Crohn's disease for the past five years. Crohn's disease is a type of inflammatory bowel disease (IBD) that causes inflammation of the digestive tract. [PERSON]'s condition is managed by his gastroenterologist, [PERSON] Ulcerative Colitis, who specializes in treating IBD patients at the [LOCATION].
|
54 |
+
|
55 |
+
4. [PERSON] (Patient No: [ID]), a 32-year-old woman, was rushed to [LOCATION] on [DATE_TIME] after experiencing severe abdominal pain and fatigue. After a series of tests, [PERSON] diagnosed [PERSON] with Addison's disease, a rare disorder of the adrenal glands. Postcode is [POSTCODE]. [PERSON]'s condition is not related to Cushing's syndrome, which is caused by excessive cortisol production.
|
56 |
+
|
57 |
+
5. [PERSON] ([LOCATION] No: [NHS_NUMBER]), a renowned baseball player, was diagnosed with amyotrophic lateral sclerosis (ALS) in 1939 at the [LOCATION]. ALS, also known as Lou Gehrig's disease, is a progressive neurodegenerative disorder that affects nerve cells in the brain and spinal cord. Gehrig's diagnosis was confirmed by his neurologist, [PERSON], who noted that the condition was not related to Bell's palsy, a temporary facial paralysis.
|
58 |
+
|
59 |
+
6. [PERSON] (Patient No: [ID]), a 62-year-old man, has been living with Parkinson's disease for the past decade. Parkinson's disease is a neurodegenerative disorder that affects movement and balance. [PERSON]'s condition is managed by his neurologist, [PERSON], who noted that [PERSON]'s symptoms were not related to Lewy body dementia, another neurodegenerative disorder, at [LOCATION].
|
60 |
+
|
61 |
+
7. [PERSON] (Patient No: [ID]), a 35-year-old man, was recently diagnosed with Kaposi's sarcoma, a type of cancer that develops from the cells that line lymph or blood vessels, at [LOCATION] on [DATE_TIME]. Sarcoma's diagnosis was confirmed by his oncologist, [PERSON] Lymphoma, who noted that the condition was not related to Burkitt's lymphoma, an aggressive form of non-Hodgkin's lymphoma. He died on [DATE_TIME].
|
62 |
+
|
63 |
+
8. [PERSON] (Patient No: [ID]) treated young Henoch Schonlein for Henoch-Schönlein purpura, a rare disorder that causes inflammation of the blood vessels, at [LOCATION] on [DATE_TIME]. [PERSON]'s case was not related to Kawasaki disease, a condition that primarily affects children and causes inflammation in the walls of medium-sized arteries.
|
64 |
+
|
65 |
+
9. [PERSON] ([LOCATION] No: [NHS_NUMBER]), a 42-year-old man, was diagnosed with Wilson's disease, a rare genetic disorder that causes copper to accumulate in the body. [PERSON]'s diagnosis was confirmed by his geneticist, [PERSON], at [LOCATION] on [DATE_TIME], who noted that the condition was not related to Niemann-Pick disease, another rare genetic disorder that affects lipid storage. Postcode was [POSTCODE].
|
66 |
+
|
67 |
+
10. [PERSON] (Patient No: [ID]) treated [PERSON] for Ehlers-Danlos syndrome, a group of inherited disorders that affect the connective tissues, at the [LOCATION] on [DATE_TIME]. [PERSON]'s case was not related to Marfan syndrome, another genetic disorder that affects connective tissue development and leads to abnormalities in the bones, eyes, and cardiovascular system. Dr [PERSON]'s username is: [USERNAME]
|
68 |
+
"""
|
69 |
+
|
70 |
+
|
71 |
+
def redact(text: str, model_name: str):
|
72 |
+
model_paths = {
|
73 |
+
"Stanford Base De-Identifier": "StanfordAIMI/stanford-deidentifier-base",
|
74 |
+
# "Stanford with Radiology and i2b2": "StanfordAIMI/stanford-deidentifier-with-radiology-reports-and-i2b2",
|
75 |
+
"Deberta PII": "lakshyakh93/deberta_finetuned_pii",
|
76 |
+
# "Gliner PII": "urchade/gliner_multi_pii-v1",
|
77 |
+
# "Spacy PII": "beki/en_spacy_pii_distilbert",
|
78 |
+
"Nikhilrk De-Identify": "nikhilrk/de-identify",
|
79 |
+
}
|
80 |
+
|
81 |
+
model_path = model_paths.get(model_name, "StanfordAIMI/stanford-deidentifier-base")
|
82 |
+
|
83 |
+
# Log the model being changed to
|
84 |
+
log.info(f"Changing to model: {model_path}")
|
85 |
+
|
86 |
+
if model_path:
|
87 |
+
change_model(model_path)
|
88 |
+
else:
|
89 |
+
raise ValueError("No valid model path provided.")
|
90 |
+
|
91 |
+
anonymized_text = pt.anonymise(text, model_path=model_path) # Pass model_path
|
92 |
+
anonymized_text = anonymized_text.replace("<", "[").replace(">", "]")
|
93 |
+
return anonymized_text
|
94 |
+
|
95 |
+
|
96 |
+
def extract_tokens(text):
|
97 |
+
tokens = re.findall(r"\[(.*?)\]", text)
|
98 |
+
return tokens
|
99 |
+
|
100 |
+
|
101 |
+
def compare_tokens(reference_tokens, redacted_tokens):
|
102 |
+
tp = 0
|
103 |
+
fn = 0
|
104 |
+
fp = 0
|
105 |
+
|
106 |
+
reference_count = {
|
107 |
+
token: reference_tokens.count(token) for token in set(reference_tokens)
|
108 |
+
}
|
109 |
+
redacted_count = {
|
110 |
+
token: redacted_tokens.count(token) for token in set(redacted_tokens)
|
111 |
+
}
|
112 |
+
|
113 |
+
for token in reference_count:
|
114 |
+
if token in redacted_count:
|
115 |
+
tp += min(reference_count[token], redacted_count[token])
|
116 |
+
fn += max(reference_count[token] - redacted_count[token], 0)
|
117 |
+
fp += max(redacted_count[token] - reference_count[token], 0)
|
118 |
+
else:
|
119 |
+
fn += reference_count[token]
|
120 |
+
|
121 |
+
for token in redacted_count:
|
122 |
+
if token not in reference_count:
|
123 |
+
fp += redacted_count[token]
|
124 |
+
|
125 |
+
return tp, fn, fp
|
126 |
+
|
127 |
+
|
128 |
+
def calculate_true_negatives(total_tokens, total_entities, tp, fn, fp):
|
129 |
+
tn = total_tokens - (total_entities + fp + fn + tp)
|
130 |
+
return tn
|
131 |
+
|
132 |
+
|
133 |
+
def count_entities_and_compute_metrics(reference_text: str, redacted_text: str):
|
134 |
+
reference_tokens = extract_tokens(reference_text)
|
135 |
+
redacted_tokens = extract_tokens(redacted_text)
|
136 |
+
|
137 |
+
tp_count, fn_count, fp_count = compare_tokens(reference_tokens, redacted_tokens)
|
138 |
+
|
139 |
+
total_tokens = len(reference_text.split())
|
140 |
+
total_entities = len(reference_tokens)
|
141 |
+
tn_count = calculate_true_negatives(
|
142 |
+
total_tokens, total_entities, tp_count, fn_count, fp_count
|
143 |
+
)
|
144 |
+
|
145 |
+
return tp_count, fn_count, fp_count, tn_count
|
146 |
+
|
147 |
+
|
148 |
+
def flag_errors(reference_text: str, redacted_text: str):
|
149 |
+
fn_count = 0
|
150 |
+
fp_count = 0
|
151 |
+
|
152 |
+
reference_tokens = [
|
153 |
+
(match.group(1), match.start())
|
154 |
+
for match in re.finditer(r"\[(.*?)\]", reference_text)
|
155 |
+
]
|
156 |
+
redacted_tokens = [
|
157 |
+
(match.group(1), match.start())
|
158 |
+
for match in re.finditer(r"\[(.*?)\]", redacted_text)
|
159 |
+
]
|
160 |
+
|
161 |
+
reference_set = set(token for token, _ in reference_tokens)
|
162 |
+
redacted_set = set(token for token, _ in redacted_tokens)
|
163 |
+
|
164 |
+
flagged_reference_text = reference_text
|
165 |
+
flagged_redacted_text = redacted_text
|
166 |
+
|
167 |
+
for token, _ in reference_tokens:
|
168 |
+
if token not in redacted_set:
|
169 |
+
fn_count += 1
|
170 |
+
flagged_reference_text = flagged_reference_text.replace(
|
171 |
+
f"[{token}]", f"[FALSE_NEGATIVE]{token}[/FALSE_NEGATIVE]"
|
172 |
+
)
|
173 |
+
|
174 |
+
for token, _ in redacted_tokens:
|
175 |
+
if token not in reference_set and token not in [
|
176 |
+
"FALSE_NEGATIVE",
|
177 |
+
"/FALSE_NEGATIVE",
|
178 |
+
]:
|
179 |
+
fp_count += 1
|
180 |
+
flagged_redacted_text = flagged_redacted_text.replace(
|
181 |
+
f"[{token}]", f"[FALSE_POSITIVE]{token}[/FALSE_POSITIVE]"
|
182 |
+
)
|
183 |
+
|
184 |
+
return flagged_reference_text, flagged_redacted_text, fn_count, fp_count
|
185 |
+
|
186 |
+
|
187 |
+
def visualize_entities(redacted_text: str):
|
188 |
+
colors = {
|
189 |
+
"PERSON": "linear-gradient(90deg, #aa9cfc, #fc9ce7)",
|
190 |
+
"ID": "linear-gradient(90deg, #ff9a9e, #fecfef)",
|
191 |
+
"GPE": "linear-gradient(90deg, #fccb90, #d57eeb)",
|
192 |
+
"NHS_NUMBER": "linear-gradient(90deg, #ff9a9e, #fecfef)",
|
193 |
+
"DATE_TIME": "linear-gradient(90deg, #fddb92, #d1fdff)",
|
194 |
+
"LOCATION": "linear-gradient(90deg, #a1c4fd, #c2e9fb)",
|
195 |
+
"EVENT": "linear-gradient(90deg, #a6c0fe, #f68084)",
|
196 |
+
"POSTCODE": "linear-gradient(90deg, #c2e59c, #64b3f4)",
|
197 |
+
"USERNAME": "linear-gradient(90deg, #aa9cfc, #fc9ce7)",
|
198 |
+
"FALSE_NEGATIVE": "linear-gradient(90deg, #ff6b6b, #ff9a9e)", # Red for false negatives
|
199 |
+
"/FALSE_NEGATIVE": "linear-gradient(90deg, #ff6b6b, #ff9a9e)", # Red for false negatives
|
200 |
+
"FALSE_POSITIVE": "linear-gradient(90deg, #ffcccb, #ff6666)", # Light red for false positives
|
201 |
+
"/FALSE_POSITIVE": "linear-gradient(90deg, #ffcccb, #ff6666)", # Light red for false positives
|
202 |
+
}
|
203 |
+
|
204 |
+
token_colors = {
|
205 |
+
"[PERSON]": "PERSON",
|
206 |
+
"[LOCATION]": "LOCATION",
|
207 |
+
"[ID]": "ID",
|
208 |
+
"[NHS_NUMBER]": "NHS_NUMBER",
|
209 |
+
"[DATE_TIME]": "DATE_TIME",
|
210 |
+
"[EVENT]": "EVENT",
|
211 |
+
"[POSTCODE]": "POSTCODE",
|
212 |
+
"[USERNAME]": "USERNAME",
|
213 |
+
"[FALSE_NEGATIVE]": "FALSE_NEGATIVE",
|
214 |
+
"[/FALSE_NEGATIVE]": "/FALSE_NEGATIVE",
|
215 |
+
"[FALSE_POSITIVE]": "FALSE_POSITIVE",
|
216 |
+
"[/FALSE_POSITIVE]": "/FALSE_POSITIVE",
|
217 |
+
}
|
218 |
+
|
219 |
+
def wrap_token_in_html(text, token, color):
|
220 |
+
parts = text.split(token)
|
221 |
+
wrapped_token = f'<span style="background: {color}; padding: 2px; border-radius: 3px;">{token}</span>'
|
222 |
+
return wrapped_token.join(parts)
|
223 |
+
|
224 |
+
for token, color_class in token_colors.items():
|
225 |
+
redacted_text = wrap_token_in_html(redacted_text, token, colors[color_class])
|
226 |
+
|
227 |
+
return f'<div style="white-space: pre-wrap; border: 1px solid #ccc; padding: 10px; border-radius: 5px;">{redacted_text}</div>'
|
228 |
+
|
229 |
+
|
230 |
+
def generate_confusion_matrix(tp_count, fn_count, fp_count, tn_count):
|
231 |
+
data = {
|
232 |
+
"Actual Positive": [tp_count, fn_count],
|
233 |
+
"Actual Negative": [fp_count, tn_count],
|
234 |
+
}
|
235 |
+
df = pd.DataFrame(data, index=["Predicted Positive", "Predicted Negative"])
|
236 |
+
plt.figure(figsize=(8, 6))
|
237 |
+
sns.heatmap(df, annot=True, fmt="d", cmap="Blues")
|
238 |
+
plt.title("Confusion Matrix")
|
239 |
+
plt.xlabel("Actual")
|
240 |
+
plt.ylabel("Predicted")
|
241 |
+
return plt
|
242 |
+
|
243 |
+
|
244 |
+
def calculate_metrics(tp_count, fn_count, fp_count, tn_count):
|
245 |
+
accuracy = (tp_count + tn_count) / (tp_count + fn_count + fp_count + tn_count)
|
246 |
+
precision = tp_count / (tp_count + fp_count) if (tp_count + fp_count) > 0 else 0
|
247 |
+
recall = tp_count / (tp_count + fn_count) if (tp_count + fn_count) > 0 else 0
|
248 |
+
f1_score = (
|
249 |
+
2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0
|
250 |
+
)
|
251 |
+
metrics_table = f"""
|
252 |
+
<table>
|
253 |
+
<tr><th>Metric</th><th>Value</th></tr>
|
254 |
+
<tr><td>Accuracy: [(TP+TN) / (TP + FN + FP + TN)] </td><td>{accuracy:.2f}</td></tr>
|
255 |
+
<tr><td>Precision: [TP / (TP + FP)] </td><td>{precision:.2f}</td></tr>
|
256 |
+
<tr><td>Recall: [TP / (TP + FN)] </td><td>{recall:.2f}</td></tr>
|
257 |
+
<tr><td>F1 Score: [2 * Precision * Recall / (Precision + Recall)] </td><td>{f1_score:.2f}</td></tr>
|
258 |
+
</table>
|
259 |
+
"""
|
260 |
+
return metrics_table
|
261 |
+
|
262 |
+
|
263 |
+
def redact_and_visualize(text: str, model_name: str):
|
264 |
+
total_tokens = len(reference_text.split())
|
265 |
+
|
266 |
+
# Redact the text
|
267 |
+
redacted_text = redact(text, model_name)
|
268 |
+
|
269 |
+
# Flag false positives and false negatives
|
270 |
+
reference_text_with_fn, redacted_text_with_fp, fn_count, fp_count = flag_errors(
|
271 |
+
reference_text, redacted_text
|
272 |
+
)
|
273 |
+
|
274 |
+
# Print the final texts with flags for debugging
|
275 |
+
log.debug("Final Reference Text with False Negatives:")
|
276 |
+
log.debug(reference_text_with_fn)
|
277 |
+
log.debug("\nFinal Redacted Text with False Positives:")
|
278 |
+
log.debug(redacted_text_with_fp)
|
279 |
+
|
280 |
+
# Count entities and compute metrics
|
281 |
+
tp_count, fn_count, fp_count, tn_count = count_entities_and_compute_metrics(
|
282 |
+
reference_text_with_fn, redacted_text_with_fp
|
283 |
+
)
|
284 |
+
|
285 |
+
# Visualize the redacted text
|
286 |
+
visualized_html = visualize_entities(redacted_text_with_fp)
|
287 |
+
|
288 |
+
# Generate confusion matrix and metrics table
|
289 |
+
confusion_matrix_plot = generate_confusion_matrix(
|
290 |
+
tp_count, fn_count, fp_count, tn_count
|
291 |
+
)
|
292 |
+
metrics_table = calculate_metrics(tp_count, fn_count, fp_count, tn_count)
|
293 |
+
|
294 |
+
return (
|
295 |
+
visualized_html,
|
296 |
+
f"Total False Negatives: {fn_count}",
|
297 |
+
f"Total True Positives: {tp_count}",
|
298 |
+
f"Total True Negatives: {tn_count}",
|
299 |
+
f"Total False Positives: {fp_count}",
|
300 |
+
confusion_matrix_plot,
|
301 |
+
metrics_table,
|
302 |
+
)
|
303 |
+
|
304 |
+
|
305 |
+
hint = """
|
306 |
+
# Guide/Instructions
|
307 |
+
|
308 |
+
## How the tool works:
|
309 |
+
|
310 |
+
When the input text is entered, the tool redacts the entered text with labelled masking tokens and then assesses the models results. You can test the text against different models by selecting from the dropdown.
|
311 |
+
|
312 |
+
### Strengths
|
313 |
+
- The Stanford De-Identifier Base Model is 99% accurate on our test set of radiology reports. The others are really to illustrate its superiority.
|
314 |
+
|
315 |
+
- This test set here was derived after lots of experimentation to make the challenge as hard as possible. It is the toughest PII benchmark we have seen so far.
|
316 |
+
|
317 |
+
### Limitations
|
318 |
+
- The tool was not designed initially to redact clinic letters as it was developed primarily on radiology reports in the US. We have made some augmentations to cover postcodes but these might not always work.
|
319 |
+
|
320 |
+
- It may overly aggressively redact text because it was built as a research tool where precision is prized > recall but the recall is also high.
|
321 |
+
"""
|
322 |
+
|
323 |
+
description = """
|
324 |
+
*Release Date:* 29/06/2024
|
325 |
+
|
326 |
+
*Version:* **1.0** - Working Proof of Concept Demo with API option and webapp demonstration.
|
327 |
+
|
328 |
+
*Authors:* **Cai Davis, Michael George, Matt Stammers**
|
329 |
+
"""
|
330 |
+
|
331 |
+
iface = gr.Interface(
|
332 |
+
fn=redact_and_visualize,
|
333 |
+
inputs=[
|
334 |
+
gr.Textbox(value=sample_text, label="Input Text", lines=25),
|
335 |
+
gr.Dropdown(
|
336 |
+
choices=[
|
337 |
+
"Stanford Base De-Identifier",
|
338 |
+
# "Stanford with Radiology and i2b2",
|
339 |
+
"Deberta PII",
|
340 |
+
# "Gliner PII",
|
341 |
+
# "Spacy PII",
|
342 |
+
"Nikhilrk De-Identify",
|
343 |
+
],
|
344 |
+
label="Model",
|
345 |
+
value="Stanford Base De-Identifier", # Make sure this matches one of the choices
|
346 |
+
),
|
347 |
+
],
|
348 |
+
outputs=[
|
349 |
+
gr.HTML(label="Anonymised Text with Visualization"),
|
350 |
+
gr.Textbox(label="Total False Negatives", lines=1),
|
351 |
+
gr.Textbox(label="Total True Positives", lines=1),
|
352 |
+
gr.Textbox(label="Total True Negatives", lines=1),
|
353 |
+
gr.Textbox(label="Total False Positives", lines=1),
|
354 |
+
gr.Plot(label="Confusion Matrix"),
|
355 |
+
gr.HTML(label="Evaluation Metrics"),
|
356 |
+
],
|
357 |
+
title="SETT: Data and AI. Pteredactyl Demo",
|
358 |
+
description=description,
|
359 |
+
article=hint,
|
360 |
+
)
|
361 |
+
|
362 |
+
iface.launch(server_name="0.0.0.0", server_port=7800)
|
requirements.txt
ADDED
The diff for this file is too large to render.
See raw diff
|
|