AyoubChLin commited on
Commit
cec9b43
1 Parent(s): f0133b7

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +314 -0
app.py ADDED
@@ -0,0 +1,314 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import docx2txt
3
+ import re
4
+
5
+ phrases_of_recommendation = {
6
+ "a contrôler après traitement",
7
+ "une mammographie est à prévoir dans deux ans dans le cadre de dépistage",
8
+ "un contrôle échographique après traitement est indiqué notamment à droite",
9
+ "intérêt d'un contrôle échographique dans 4 à 6 mois, d'un dosage de la prolactinémie, et d'une étude cytologique du liquide d'écoulement mamelonnaire à gauche si persistance. à compléter par cytoponction",
10
+ "a recontrôler dans 06 mois",
11
+ "nécessitant une vérification histologique par macro-biopsie sous stéréotaxie",
12
+ "un contrôle échographique est souhaitable dans six mois",
13
+ "un prélèvement histologique par microbiopsie est indiqué",
14
+ "un contrôle échographique après traitement est indiqué",
15
+ "un contrôle échographique est souhaitable dans 04 à 06 mois",
16
+ "à confronter aux données mammo et échographiques antérieures",
17
+ "prévoir un contrôle échographique dans 04 mois",
18
+ "une vérification cytologique est souhaitable au niveau du qme droit",
19
+ "un contrôle échographique dans 6 mois",
20
+ "prévoir un contrôle échographique dans 06 mois",
21
+ "a recontrôler dans 4 à 6 mois",
22
+ "il serait souhaitable de compléter par une microbiopsie à droite et une cytoponction à gauche",
23
+ "un contrôle échographique après traitement bien conduit est nécessaire à gauche voir prélèvement si persistance ainsi qu'une vérification histologique de la masse mammaire droite",
24
+ "à compléter par irm",
25
+ "une vérification histologique par microbiopsie échoguidée est souhaitable à gauche",
26
+ "un contrôle échographique après traitement bien conduit est nécessaire",
27
+ "un complément irm mammaire après résolution des phénomènes inflammatoires est souhaitable afin de guider un éventuel prélèvement percutané",
28
+ "un contrôle échographique est souhaitable après traitement",
29
+ "intérêt d'un contrôle dans 06 mois",
30
+ "intérêt d'une corrélation aux explorations appropriées",
31
+ "la vérification histologique par microbiopsie échoguidée est indiquée",
32
+ "à compléter par une microbiopsie",
33
+ "une vérification histologique par microbiopsie est nécessaire à droite, ainsi d'une vérification cytologique du creux axillaire est indiquée",
34
+ "un complément irm mammaire est souhaitable pour mieux caractériser l'image de distorsion architecturale du qse droit, et vu les antécédents familiaux de la patiente",
35
+ "vérification histologique dans la crainte d'une dégénérescence",
36
+ "à compléter par micro-biopsie, associée à une adénopathie axillaire homolatérale, à compléter par cytoponction",
37
+ "un complément irm mammaire est également souhaitable vu les antécédents familiaux de la patiente",
38
+ "un contrôle échographique est souhaitable dans 04 mois",
39
+ "prévoir une micro-biopsie échoguidée",
40
+ "un contrôle échographique dans 04 à 06 mois est souhaitable",
41
+ "vérification histologique par macro-biopsie sous-stéréotaxie, notamment dans ce contexte",
42
+ "prévoir un contrôle échographique dans six mois",
43
+ "une vérification histologique par micro-biopsie échoguidée"
44
+ }
45
+
46
+ def preprocess(report):
47
+ """Preprocesses the report by converting it to lowercase, removing extra whitespaces, and replacing apostrophes."""
48
+ # Convertir le rapport en minuscules pour une correspondance insensible à la casse
49
+ report = report.lower()
50
+ # Remplacer les apostrophes dans le rapport
51
+ report = report.replace('’', "'")
52
+ # Supprimer les espaces blancs supplémentaires
53
+ report = re.sub(r'\s+', ' ', report)
54
+
55
+ return report
56
+
57
+
58
+ def extract_date(report):
59
+ """Extracts the date from the report."""
60
+ date_pattern = r'(\b(?:lundi|mardi|mercredi|jeudi|vendredi|samedi|dimanche)?\s*\d{1,2}\s+\w+\s+\d{4}\b)'
61
+ date_match = re.search(date_pattern, report)
62
+ return date_match.group(0).strip().capitalize() if date_match else 'Unknown'
63
+
64
+ def extract_patient_id(head):
65
+ """Extracts the patient ID from the report."""
66
+ patient_id_pattern = r'(pat-\d+)'
67
+ patient_id_match = re.search(patient_id_pattern, head)
68
+ return patient_id_match.group(1) if patient_id_match else 'Unknown'
69
+
70
+ def extract_age(head):
71
+ """Extracts the age from the report."""
72
+ age_pattern = r'(\d+)\s*(?:ANS|ans)'
73
+ age_match = re.search(age_pattern, head)
74
+ return age_match.group(1) if age_match else 'Unknown'
75
+
76
+
77
+ def extract_line_after_age(head):
78
+ """Extracts the non-empty line that appears after the word 'ANS' or 'ans' in the report."""
79
+ pattern_age = r'\b\d+\s*ANS?\b'
80
+ age_match = re.search(pattern_age, head, re.IGNORECASE)
81
+
82
+ if age_match:
83
+ next_line_start = head.find('\n', age_match.end())
84
+ next_line_start = head.find('\n', next_line_start + 1) # Look for the next line
85
+
86
+ # Find the first non-empty line after 'ANS' or 'ans'
87
+ next_line_end = head.find('\n', next_line_start + 1)
88
+ while next_line_end < len(head) and head[next_line_start:next_line_end].strip() == '':
89
+ next_line_end = head.find('\n', next_line_end + 1)
90
+
91
+ # Extract the line after 'ANS' or 'ans' (non-empty)
92
+ line = head[next_line_start:next_line_end].strip()
93
+ return line
94
+ else:
95
+ return None
96
+
97
+
98
+ def extract_indication(head):
99
+ """Extracts the indication from the report."""
100
+ indication_pattern = r'(?i)(indication|motif)\s*:\s*(.*)'
101
+ indication_match = re.search(indication_pattern, head)
102
+ return indication_match.group(2).strip() if indication_match else 'no indication'
103
+
104
+ def extract_mammographie(result):
105
+ """Extracte les informations de mammographie du résultat."""
106
+ pattern = r'RESULTATS\s*(.*?)\s*(?:le complément échographique|échographie)'
107
+ match = re.search(pattern, result, re.DOTALL | re.IGNORECASE)
108
+
109
+ mammographie = {'mammo_droite': 'Pas de mammographie', 'mammo_gauche': 'Pas de mammographie', 'mammo_both': 'Pas de mammographie', 'extracted_mammographie': 'Pas de mammographie'}
110
+
111
+ if match:
112
+ mammographie_text = match.group(1).strip()
113
+ mammo_droite = []
114
+ mammo_gauche = []
115
+ mammo_both = []
116
+
117
+ sentences = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s', mammographie_text) # Split into sentences
118
+
119
+ for sentence in sentences:
120
+ if 'gauche' in sentence.lower():
121
+ mammo_gauche.append(sentence)
122
+ elif 'droit' in sentence.lower():
123
+ mammo_droite.append(sentence)
124
+ else:
125
+ mammo_both.append(sentence)
126
+
127
+ # Joining sentences back into strings
128
+ mammographie['extracted_mammographie'] = mammographie_text
129
+ mammographie['mammo_droite'] = '. '.join(mammo_droite) if mammo_droite else 'Pas de mammographie'
130
+ mammographie['mammo_gauche'] = '. '.join(mammo_gauche) if mammo_gauche else 'Pas de mammographie'
131
+ mammographie['mammo_both'] = '. '.join(mammo_both) if mammo_both else 'Pas de mammographie'
132
+
133
+ return mammographie
134
+
135
+
136
+ def extract_echographie(result):
137
+ """Extracte les informations d'echographie du résultat."""
138
+ pattern = r'(?:échographie|complément échographique)(.*?)(?=conclusion|$)'
139
+ match = re.search(pattern, result, re.DOTALL | re.IGNORECASE)
140
+
141
+ echographie = {'echo_droite': 'Pas d\'échographie', 'echo_gauche': 'Pas d\'échographie', 'echo_both': 'Pas d\'échographie', 'extracted_echographie': 'Pas d\'échographie'}
142
+
143
+ if match:
144
+ echographie_text = match.group(1).strip()
145
+ echo_droite = []
146
+ echo_gauche = []
147
+ echo_both = []
148
+
149
+ sentences = re.split(r'(?<!\w\.\w.)(?<![A-Z][a-z]\.)(?<=\.|\?)\s',echographie_text) # Split into sentences
150
+
151
+ for sentence in sentences:
152
+ if 'gauche' in sentence.lower():
153
+ echo_gauche.append(sentence)
154
+ elif 'droit' in sentence.lower():
155
+ echo_droite.append(sentence)
156
+ else:
157
+ echo_both.append(sentence)
158
+
159
+ # Joining sentences back into strings
160
+ echographie['echo_droite'] = '. '.join(echo_droite) if echo_droite else 'Pas d\'échographie'
161
+ echographie['echo_gauche'] = '. '.join(echo_gauche) if echo_gauche else 'Pas d\'échographie'
162
+ echographie['echo_both'] = '. '.join(echo_both) if echo_both else 'Pas d\'échographie'
163
+
164
+ # Ajout de la partie échographie extraite
165
+ echographie['extracted_echographie'] = echographie_text
166
+
167
+ return echographie
168
+
169
+
170
+ def extract_recommendations(conclusion):
171
+ """Extracts the recommendations from the report."""
172
+ recommendations = []
173
+ for recommendation in phrases_of_recommendation:
174
+ if re.search(re.escape(recommendation), conclusion):
175
+ recommendations.append(recommendation)
176
+ return recommendations
177
+
178
+
179
+ def extract_classification(report):
180
+ # Utiliser des motifs regex pour rechercher des informations spécifiques
181
+ birads_both_pattern = [
182
+ r'bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+au\s+niveau\s+des\s+deux\s+seins',
183
+ r'bilatérale\s+classé\s+bi-rads\s+(\d+[a-c]?)',
184
+ r'bi-rads\s+(\d+[a-c]?)de\s+l\'acr\s+de\s+façon\s+bilatérale',
185
+ r'classée\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+droite\s+comme\s+à\s+gauche',
186
+ r'classé\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+droite\s+comme\s+à\s+gauche',
187
+ r'examen.*classée\s+bi-rads\s+(\d+[a-c]?)',
188
+ r'bi-rads\s+(\d+)\s+de\s+l\'acr(?!.*\bdroite\b)(?!.*\bgauche\b)',# classées bi-rads 1 de l'acr à droite comme à gauche.
189
+ r'classées\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+droite\s+comme\s+à\s+gauche',
190
+ ]
191
+ birads_patterns_droit = [# sein droit classé bi-rads 1 de l'acr
192
+ r'(?:examen\s+du\s+sein\s+droite\s+est\s+classé\s+bi-rads\s+(\d+[a-c]?))',
193
+ r'(?:examen\s+classé\s+bi-rads\s+(\d+[a-c]?)\s*de\s+l\'acr\s+à\s+droite)', #Examen classé BI-RADS 3 de l’ACR à droite
194
+ r'(?:classées\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+droite)',
195
+ r'(?:classé\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+droite)',
196
+ r'(?:classée\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+droite)',
197
+ r'(?:\s*(\d+[a-c]?)\s*de\s*l\'acr\s*à\s*droite)',
198
+ r'(?:examen\s+classé\s+bi-rads\s+(\d+[a-c]?)\s+à\s+droite\s+de\s+l\'acr)',
199
+ r'(?:examen\s+classé\s+bi-rads\s+(\d+[a-c]?)\s+à\s+droite\s+de\s+l\'acr)', #gauches classés BI-RADS 4 de l'ACR, la masse mammaire gauche, classée BI-RADS 6 de l’ACR.
200
+ r'(?:droits\s+classés\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr)',# droit, classé BI-RADS 3 de l’ACR.
201
+ r'(?:droit/s+,/s+classé\s+bi-rads\s+(\d+[a-c]?)\s+\s+de\s+l\'acr)',#Kyste remanié mammaire droit, classé BI-RADS 3 de l’ACR.
202
+ r'(?:droite\s*[\w\s]+\s*,\s*classé\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr)'#droite (qsi), classée birads 4 de l'acr
203
+ r'(?:sein\s+droit\s+classé\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr)',
204
+ r'(?:droite\s*[\w\s]+\s*,\s*classée\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr)' ,
205
+
206
+ ]
207
+ birads_patterns_gauche = [
208
+ r'(?:examen\s+du\s+sein\s+gauche\s+est\s+classé\s+bi-rads\s+(\d+[a-c]?))',
209
+ r'(?:examen\s+classé\s+bi-rads\s+(\d+[a-c]?)\s*de\s+l\'acr\s+à\s+gauche)',
210
+ r'(?:classées\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+gauche)',
211
+ r'(?:classé\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+gauche)',
212
+ r'(?:classée\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+gauche)',
213
+ r'(?:bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr\s+à\s+gauche)',
214
+ r'(?:examen\s+classé\s+bi-rads\s+(\d+[a-c]?)\s+à\s+gauche\s+de\s+l\'acr)',
215
+ r'(?:examen\s+classé\s+bi-rads\s+(\d+[a-c]?)\s+à\s+gauche\s+de\s+l\'acr)',
216
+ r'(?:gauches\s+classés\s+bi-rads\s+(\d+[a-c]?)\s+à\s+droite\s+de\s+l\'acr)',
217
+ r'(?:gauche\s+classée\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr)',
218
+ r'(?:gauche/s+,/s+classé\s+bi-rads\s+(\d+[a-c]?)\s+\s+de\s+l\'acr)',
219
+ r'(?:gauche\s*[\w\s]+\s*,\s*classé\s+bi-rads\s+(\d+[a-c]?)\s+de\s+l\'acr)',# sein gauche classé bi-rads 3de l'acr.
220
+ r'(?:gauche\s+classé\s+bi-rads\s+(\d+[a-c]?)de\s+l\'acr)',
221
+
222
+
223
+
224
+ ]
225
+
226
+ classifications = {'Left Breast Classification': 'Unknown', 'Right Breast Classification': 'Unknown'}
227
+ for pattern in birads_both_pattern:
228
+ match = re.search(pattern, report)
229
+ if match:
230
+ classification = match.group(1)
231
+ classifications['Left Breast Classification'] = classification
232
+ classifications['Right Breast Classification'] = classification
233
+ break
234
+
235
+ if classifications['Left Breast Classification'] == 'Unknown':
236
+ for pattern in birads_patterns_gauche:
237
+ match = re.search(pattern, report)
238
+ if match:
239
+ classifications['Left Breast Classification'] = match.group(1)
240
+ break
241
+
242
+ if classifications['Right Breast Classification'] == 'Unknown':
243
+ for pattern in birads_patterns_droit:
244
+ match = re.search(pattern, report)
245
+ if match:
246
+ classifications['Right Breast Classification'] = match.group(1)
247
+ break
248
+
249
+ return classifications
250
+
251
+
252
+ def extractReportPart(report):
253
+ # Define patterns
254
+ result_pattern = r'(?i)(RESULTATS?\s*:\s*)'
255
+ conclusion_pattern = r'(?i)(CONCLUSIONS?\s*:\s*)'
256
+
257
+ result_match = re.search(result_pattern, report)
258
+ conclusion_match = re.search(conclusion_pattern, report)
259
+
260
+ # Split the text based on patterns
261
+ head_text =report[:result_match.start()] if result_pattern else ''
262
+ result_text = report[result_match.start():conclusion_match.start()] if result_match and conclusion_match else report[result_match.start():] if result_match else ''
263
+ conclusion_text = report[conclusion_match.start():] if conclusion_match else ''
264
+
265
+ return preprocess(head_text),preprocess(result_text),preprocess(conclusion_text)
266
+
267
+
268
+ def extract_information(report:str):
269
+ """Extracts all the relevant information from the mammography report."""
270
+
271
+
272
+ head, result, conclusion = extractReportPart(report)
273
+ report = preprocess(report)
274
+ extracted_info = {
275
+ 'report':report,
276
+ 'head':head,
277
+ 'result':result,
278
+ 'conclusion':conclusion,
279
+ 'Date': extract_date(head),
280
+ 'Patient ID': extract_patient_id(head),
281
+ 'Age': extract_age(head),
282
+ # 'title': extract_line_after_age(head),
283
+ 'Indication':extract_indication(head),
284
+ **extract_mammographie(result),
285
+ **extract_echographie(result),
286
+ 'Recommendations': extract_recommendations(conclusion),
287
+ **extract_classification(conclusion)
288
+ }
289
+ return extracted_info
290
+
291
+
292
+
293
+ def main():
294
+ st.title("Mammography Report Processing")
295
+
296
+ # Upload text or .docx file
297
+ upload_type = st.radio("Select upload type", ("Text", "Document"))
298
+
299
+ if upload_type == "Text":
300
+ report_text = st.text_area("Enter the report text")
301
+ if report_text and st.button("Process Report"):
302
+ extracted_info = extract_information(report_text)
303
+ st.write(extracted_info)
304
+
305
+ elif upload_type == "Document":
306
+ uploaded_file = st.file_uploader("Choose a .docx file", type="docx")
307
+ if uploaded_file is not None:
308
+ report_text = docx2txt.process(uploaded_file)
309
+ if st.button("Process Report"):
310
+ extracted_info = extract_information(report_text)
311
+ st.write(extracted_info)
312
+
313
+ if __name__ == "__main__":
314
+ main()