ChaoukiBenzekri commited on
Commit
02d0e3d
1 Parent(s): 39dff5a

Add exploration py file

Browse files
Files changed (1) hide show
  1. exploration.py +383 -0
exploration.py ADDED
@@ -0,0 +1,383 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import seaborn as sns
4
+ import numpy as np
5
+ import os
6
+ import random
7
+ import matplotlib.pyplot as plt
8
+ import matplotlib.image as mpimg
9
+ import plotly.express as px
10
+ from plotly.subplots import make_subplots
11
+ import plotly.graph_objects as go
12
+ from PIL import Image
13
+ from custom_functions import calc_mean_intensity, source_extract
14
+
15
+ df_images = pd.read_csv('data\df_images.csv')
16
+ df_masks = pd.read_csv('data\df_masks.csv')
17
+ df_combined = pd.read_csv('data\df_combined.csv')
18
+ df_metadata = pd.read_csv('data\df_metadata.csv')
19
+
20
+ def show_exploration():
21
+ # Style des onglets
22
+ st.markdown("""
23
+ <style>
24
+ .stTabs [data-baseweb="tab-list"] {
25
+ display: flex;
26
+ gap: 10px;
27
+ }
28
+
29
+ .stTabs [data-baseweb="tab"] {
30
+ padding: 10px 15px;
31
+ border: 1px solid transparent;
32
+ border-radius: 5px 5px 0 0;
33
+ background-color: transparent;
34
+ cursor: pointer;
35
+ transition: all 0.3s ease;
36
+ }
37
+
38
+ .stTabs [data-baseweb="tab"]:hover {
39
+ background-color: #8f8d9b;
40
+ }
41
+
42
+ .stTabs [aria-selected="true"] {
43
+ background-color: #57546a;
44
+ border-color: #ccc;
45
+ border-bottom-color: transparent;
46
+ }
47
+ </style>""", unsafe_allow_html = True)
48
+
49
+ tab1, tab2 = st.tabs(["🗂️ Métadonnées", "🖼️ Images & masques"])
50
+
51
+ ### Premier onglet
52
+ with tab1:
53
+ st.header("Exploration des métadonnées")
54
+ st.markdown('''
55
+ Nous avons à notre disposition un important jeu de données provenant de [Kaggle](https://www.kaggle.com/datasets/tawsifurrahman/covid19-radiography-database).
56
+ Il s'agit de 21 165 images de radiographies pulmonaires labellisées dans leur nom, ainsi qu'autant de masques basiques associés aux images. De plus, quatre fichiers de métadonnées au format *.xlxs sont disponibles en accompagnement des quatre catégories.
57
+
58
+ Au regard du peu d'informations présents dans les métadonnées, nous avons cherché à créer un nouveau tableau plus complet, nous permettant d'appréhender plus efficacement le jeu de données. Cette démarche nous a permis de détecter de nombreuses erreurs.
59
+ ''')
60
+
61
+ col1, col2 = st.columns([1, 1])
62
+
63
+ with col1:
64
+ st.subheader("Métadonnées fournies")
65
+ st.dataframe(df_metadata.head())
66
+
67
+ ## Valeurs uniques
68
+ unique_format = df_metadata["FORMAT"].unique()
69
+ unique_format = ", ".join(map(str, unique_format))
70
+ unique_resolution = df_metadata["SIZE"].unique()
71
+ unique_resolution = ", ".join(map(str, unique_resolution))
72
+
73
+ data = [
74
+ ("Liste des résolutions", unique_resolution),
75
+ ("Liste des formats", unique_format),
76
+ ("Nombre d'images", len(df_metadata))
77
+ ]
78
+
79
+ # Créer le DataFrame à partir de la liste de tuples
80
+ df2 = pd.DataFrame(data, columns = ["Titre", "Variable"])
81
+
82
+ # Convertir le dataframe en HTML avec les styles CSS
83
+ html_table2 = df2.to_html(index = False, header = False, justify = 'center', classes = 'styled-table', border = 0)
84
+
85
+ # Afficher le HTML dans Streamlit avec la largeur calculée
86
+ st.markdown(f"<div style='border: 1px solid white; border-radius: 5px; padding: 10px; background-color: #343434; line-height: 1; width: 270px; margin: 0 auto;'>{html_table2}</div>", unsafe_allow_html=True)
87
+
88
+ with col2:
89
+ st.subheader("Métadonnées réelles")
90
+ st.dataframe(df_images.head())
91
+
92
+ ## Valeurs uniques
93
+ unique_classes = df_images['LABEL'].unique()
94
+ unique_classes = ", ".join(map(str, unique_classes))
95
+ unique_sources = df_images['SOURCE'].dropna().unique()
96
+ unique_sources = ", ".join(map(str, unique_sources))
97
+ unique_format = df_images["FORMAT"].unique()
98
+ unique_format = ", ".join(map(str, unique_format))
99
+ unique_resolution = df_images["SIZE"].unique()
100
+ unique_resolution = ", ".join(map(str, unique_resolution))
101
+ unique_channel = df_images["CHANNELS"].unique()
102
+ unique_channel = ", ".join(map(str, unique_channel))
103
+
104
+ data = [
105
+ ("Liste des résolutions", unique_resolution),
106
+ ("Liste des formats", unique_format),
107
+ ("Liste des classes", unique_classes),
108
+ ("Liste des sources", unique_sources),
109
+ ("Nombre de canaux", unique_channel),
110
+ ("Nombre d'images", len(df_images))
111
+ ]
112
+
113
+ # Créer le DataFrame à partir de la liste de tuples
114
+ df = pd.DataFrame(data, columns = ["Titre", "Variable"])
115
+
116
+ # Convertir le dataframe en HTML avec les styles CSS
117
+ html_table = df.to_html(index = False, header = False, justify = 'center', classes = 'styled-table', border = 0)
118
+
119
+ # Afficher le HTML dans Streamlit avec la largeur calculée
120
+ st.markdown(f"<div style='border: 1px solid white; border-radius: 5px; padding: 10px; background-color: #343434; line-height: 1; width: 616px; margin: 0 auto;'>{html_table}</div>", unsafe_allow_html=True)
121
+
122
+
123
+ # ==================================================================================================
124
+ # ==================================================================================================
125
+
126
+ st.header("Analyse du nombre d'images")
127
+
128
+ col1, col2 = st.columns([0.4, 0.6])
129
+
130
+ with col1:
131
+ ## Countplot
132
+ # Preprocess des données pour affichage
133
+ total_images = df_images.groupby('LABEL').size().reset_index(name='Total Images')
134
+ max_values = df_images.groupby('LABEL')['LABEL'].count().reset_index(name='Max Images')
135
+ df_merged = pd.merge(total_images, max_values, on='LABEL')
136
+ df_sorted = df_merged.sort_values('Total Images', ascending = False)
137
+
138
+ palette = {'Normal': '#A1C9F4', 'Lung_Opacity': '#8DE5A1', 'COVID': '#FFB482', 'Viral Pneumonia': '#D0BBFF'}
139
+
140
+ # Création du graphique Plotly avec affichage des valeurs maximales uniquement au survol
141
+ fig = px.bar(df_sorted, x = 'LABEL', y = 'Total Images', color = 'LABEL',
142
+ color_discrete_map = palette,
143
+ title = "Distribution du nombre d'images par LABEL",
144
+ labels = {'LABEL': 'Label', 'Total Images': 'Nombre d\'images', 'Max Images': 'Nombre maximal d\'images'},
145
+ hover_data = {'LABEL': False, 'Max Images': True, 'Total Images': False}
146
+ )
147
+
148
+ fig.update_layout(
149
+ xaxis_title = 'Label',
150
+ yaxis_title = "Nombre d'images",
151
+ showlegend = True,
152
+ bargap = 0.2, # Espace entre les barres
153
+ width = 600,
154
+ height = 500
155
+ )
156
+
157
+ st.plotly_chart(fig)
158
+
159
+ with col2:
160
+ st.markdown("Les classes sont particulièrement déséquilibrées, il est cependant intéressant de constater que la classe minoritaire contient tout de même 1345 images.")
161
+
162
+ st.header("", divider = 'gray')
163
+
164
+ # ==================================================================================================
165
+ # ==================================================================================================
166
+
167
+ col1, col2 = st.columns([0.5, 0.5])
168
+
169
+ with col1:
170
+ ## Pieplot
171
+ # Supposons que df_images est votre DataFrame et 'SOURCE' est la colonne d'intérêt
172
+ source_counts = df_images['SOURCE'].value_counts()
173
+
174
+ # Création d'un graphique pie avec Plotly
175
+ fig = px.pie(
176
+ values = source_counts.values,
177
+ names = source_counts.index,
178
+ title = 'Répartition des sources des images',
179
+ hole = 0.5,
180
+ color_discrete_sequence = px.colors.qualitative.Pastel
181
+ )
182
+
183
+ fig.update_traces(
184
+ textinfo = 'label+percent',
185
+ marker = dict(line = dict(color = 'black', width = 1)),
186
+ )
187
+
188
+ fig.update_layout(
189
+ showlegend = True,
190
+ width = 700,
191
+ height = 700
192
+ )
193
+
194
+ # Affichage du graphique avec Streamlit
195
+ st.plotly_chart(fig)
196
+
197
+ with col2:
198
+ ## Barplot
199
+ df_count = df_images.groupby(['SOURCE', 'LABEL']).size().reset_index(name='NOMBRE_IMAGES')
200
+
201
+ # Créer un graphique empilé avec Plotly
202
+ fig2 = px.bar(df_count, x='SOURCE', y='NOMBRE_IMAGES', color='LABEL', barmode='stack',
203
+ title='Nombre d\'images par label et par source',
204
+ labels={'NOMBRE_IMAGES': 'Nombre d\'images', 'LABEL': 'Label', 'SOURCE': 'Source'},
205
+ color_discrete_sequence = px.colors.qualitative.Pastel)
206
+
207
+ fig2.update_traces(
208
+ marker = dict(line = dict(color = 'black', width = 1)),
209
+ )
210
+
211
+ fig2.update_layout(
212
+ showlegend = True,
213
+ width = 700,
214
+ height = 700
215
+ )
216
+ # Affichage du graphique avec Streamlit
217
+ st.plotly_chart(fig2)
218
+
219
+
220
+ ### Deuxième onglet
221
+ with tab2:
222
+ st.header("Exploration des images")
223
+
224
+ ## Affichage d'une image aléatoire pour chaque catégorie
225
+ dossier_radio = "radios/"
226
+ sous_dossiers = [d for d in os.listdir(dossier_radio) if os.path.isdir(os.path.join(dossier_radio, d))]
227
+
228
+ st.info("Cliquez sur le boutton ci-dessous pour afficher un échantillon d'images", icon="ℹ️")
229
+ if st.button("Afficher une image aléatoire de chaque LABEL"):
230
+ cols = st.columns(len(sous_dossiers))
231
+ for i, sous_dossier in enumerate(sous_dossiers):
232
+ sous_dossier_path = os.path.join(dossier_radio, sous_dossier)
233
+
234
+ fichiers_images = [f for f in os.listdir(sous_dossier_path) if f.endswith(('.png', '.jpg', '.jpeg'))]
235
+
236
+ if fichiers_images:
237
+ image_selectionnee = random.choice(fichiers_images)
238
+ image_path = os.path.join(sous_dossier_path, image_selectionnee)
239
+
240
+ with open(image_path, "rb") as f:
241
+ image = Image.open(f)
242
+ cols[i].image(image, caption=f"{sous_dossier}", use_column_width = "auto", width = 299)
243
+ st.success('Images affichées avec succès !', icon = "✅")
244
+ st.markdown('''
245
+ Nous pouvons remarquer que les images sont toutes en nuances de gris, malgré leur nombre de canaux quelques fois différent.
246
+ De plus, toutes les radiographies semblent avoir été prises selon une méthode standard, mettant bien les poumons au centre de l'image.
247
+ Quelques variations peuvent cependant apparaître (bras vers le haut, artefacts visuels, annotations, etc.)
248
+ ''')
249
+ st.header("", divider = 'gray')
250
+
251
+ # ==================================================================================================
252
+ # ==================================================================================================
253
+
254
+ col1, col2 = st.columns([0.6, 0.4])
255
+
256
+ with col1:
257
+ ## Violintplot
258
+ palette = {
259
+ 'Normal (Sans masque)': '#A1C9F4',
260
+ 'Normal (Avec masque)': '#517EA4',
261
+ 'Lung_Opacity (Sans masque)': '#8DE5A1',
262
+ 'Lung_Opacity (Avec masque)': '#4E7D51',
263
+ 'COVID (Sans masque)': '#FFB482',
264
+ 'COVID (Avec masque)': '#BF6D41',
265
+ 'Viral Pneumonia (Sans masque)': '#D0BBFF',
266
+ 'Viral Pneumonia (Avec masque)': '#7E6CBF'
267
+ }
268
+
269
+ new_labels = ['COVID\nAvec masque', 'COVID\nSans masque',
270
+ 'Lung_Opacity\nAvec masque', 'Lung_Opacity\nSans masque',
271
+ 'Normal\nAvec masque', 'Normal\nSans masque',
272
+ 'Viral Pneumonia\nAvec masque', 'Viral Pneumonia\nSans masque']
273
+
274
+ # Création du graphique Plotly
275
+ fig = px.violin(df_combined, x = 'Label_Masque', y = 'COMBINED_INTENSITY',
276
+ violinmode = 'overlay',
277
+ color = 'Label_Masque',
278
+ color_discrete_map = palette,
279
+ category_orders = {'Label_Masque': new_labels},
280
+ title = "Distribution de l'intensité lumineuse moyenne normalisée",
281
+ labels = {'Label_Masque': ''},
282
+ )
283
+
284
+ # Mise en forme du graphique
285
+ fig.update_layout(
286
+ yaxis_title = 'Intensité lumineuse moyenne',
287
+ xaxis_title = '',
288
+ width = 1000,
289
+ height = 600
290
+ )
291
+
292
+ # Affichage du graphique
293
+ st.plotly_chart(fig)
294
+
295
+ with col2:
296
+ st.markdown('''
297
+ L'application des masques réduit considérablement l'intensité lumineuse moyenne des images.
298
+ Ce comportement est tout à fait normal car les masques noircient les parties non pertinentes et font ainsi tendre la moyenne des pixels vers 0.
299
+
300
+ Ainsi, les valeurs restantes sont les valeurs ayant de la pertinence dans ce que nous cherchons à faire observer au modèle.
301
+ Mais ceci peut aussi entrainer une diminution de la variabilité des images, pouvant entrainer plus de difficultés à la généralisation.
302
+ De plus, ceci génère une contrainte supplémentaire lors du déploiement du modèle, nécessitant d'appliquer un masque spécifique aux nouvelles données avant introduction dans le modèle.
303
+ ''')
304
+
305
+ st.header("", divider = 'gray')
306
+
307
+ # ==================================================================================================
308
+ # ==================================================================================================
309
+
310
+ ## Histogramme de la fréquence de l'intensité des pixels
311
+ palette_list = ['#A1C9F4','#8DE5A1','#FFB482', '#D0BBFF']
312
+
313
+ st.info("Cliquez sur le bouton ci-dessous pour afficher un échantillon d'images et leur histogramme", icon="ℹ️")
314
+ if st.button("Afficher la fréquence de l'intensité des pixels"):
315
+ # Créer une ligne avec quatre colonnes
316
+ cols = st.columns(4)
317
+ for idx, sous_dossier in enumerate(sous_dossiers):
318
+ sous_dossier_path = os.path.join(dossier_radio, sous_dossier)
319
+ fichiers_images = [f for f in os.listdir(sous_dossier_path) if f.endswith(('.png', '.jpg', '.jpeg'))]
320
+
321
+ if fichiers_images:
322
+ image_selectionnee = random.choice(fichiers_images)
323
+ image_path = os.path.join(sous_dossier_path, image_selectionnee)
324
+
325
+ with open(image_path, "rb") as f:
326
+ image = Image.open(f)
327
+ # Afficher l'image avec sa véritable résolution sans agrandissement
328
+ cols[idx].image(image, caption=f"{sous_dossier}", width = image.width, use_column_width = "auto")
329
+
330
+ image_array = np.array(image)
331
+ if len(image_array.shape) == 3:
332
+ image_array = image_array.mean(axis=2)
333
+
334
+ filtered_array = image_array[image_array > 0]
335
+ hist_values, bin_edges = np.histogram(filtered_array, bins=255, range=(0, 256))
336
+
337
+ # Création de l'histogramme avec Plotly
338
+ fig = px.bar(
339
+ x=bin_edges[:-1],
340
+ y=hist_values,
341
+ labels={'x': 'Intensité des pixels', 'y': 'Nombre de pixels'},
342
+ color_discrete_sequence=[palette_list[idx]]
343
+ )
344
+ cols[idx].plotly_chart(fig, use_container_width=True)
345
+ st.success('Histogrammes générés avec succès !', icon="✅")
346
+ st.header("", divider='gray')
347
+
348
+ # ==================================================================================================
349
+ # ==================================================================================================
350
+
351
+ col1, col2 = st.columns([0.4, 0.6])
352
+
353
+ with col1:
354
+ st.markdown('''
355
+ La surface utile des masques est l'ensemble des pixels apportant de l'information dans l'analyse de notre problématique.
356
+ En pratique, il s'agit de l'ensemble des pixels définissant les poumons sur la radiographie.
357
+ Des éléments artefactuels peu commun sur la radiographies hors des poumons peut générer de la variabilité que le modèle va prendre en compte, le poussant alors à "observer" des zones qui ne sont pas pertinentes pour notre problématique.
358
+ C'est là que l'application des masques pour limiter la "vision" du modèle aux poumons peut s'avérer utile.
359
+ ''')
360
+
361
+ with col2:
362
+ ## Surface utile
363
+ # Création de l'histogramme avec Plotly Express
364
+ fig2 = px.histogram(df_masks, x = 'RATIO', color = 'LABEL',
365
+ nbins = 70,
366
+ barmode = 'overlay', # Superpose les distributions
367
+ color_discrete_sequence = ['#A1C9F4', '#8DE5A1', '#FFB482', '#D0BBFF'], # Palette de couleurs
368
+ opacity = 0.75)
369
+
370
+ # Personnalisation supplémentaire
371
+ fig2.update_traces(marker_line_color = 'gray', marker_line_width = 1.5) # Ajouter la bordure de barre
372
+ fig2.update_layout(
373
+ title = 'Ratio de la surface utile en appliquant les masques',
374
+ xaxis_title = 'Ratio',
375
+ yaxis_title = 'Nombre de cas',
376
+ legend_title = 'Label',
377
+ height = 500,
378
+ width = 800
379
+ )
380
+
381
+ # Affichage du graphique
382
+ st.plotly_chart(fig2)
383
+