File size: 18,862 Bytes
02d0e3d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
import streamlit as st
import pandas as pd
import seaborn as sns
import numpy as np
import os
import random
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import plotly.express as px
from plotly.subplots import make_subplots
import plotly.graph_objects as go
from PIL import Image
from custom_functions import calc_mean_intensity, source_extract

df_images = pd.read_csv('data\df_images.csv')
df_masks = pd.read_csv('data\df_masks.csv')
df_combined = pd.read_csv('data\df_combined.csv')
df_metadata = pd.read_csv('data\df_metadata.csv')

def show_exploration():
    # Style des onglets
    st.markdown("""
        <style>
            .stTabs [data-baseweb="tab-list"] {
                display: flex;
                gap: 10px;
            }

            .stTabs [data-baseweb="tab"] {
                padding: 10px 15px;
                border: 1px solid transparent;
                border-radius: 5px 5px 0 0;
                background-color: transparent;
                cursor: pointer;
                transition: all 0.3s ease;
            }

            .stTabs [data-baseweb="tab"]:hover {
                background-color: #8f8d9b;
            }

            .stTabs [aria-selected="true"] {
                background-color:  #57546a;
                border-color: #ccc;
                border-bottom-color: transparent;
            }
        </style>""", unsafe_allow_html = True)

    tab1, tab2 = st.tabs(["🗂️ Métadonnées", "🖼️ Images & masques"])

    ### Premier onglet
    with tab1:
        st.header("Exploration des métadonnées")
        st.markdown('''
        Nous avons à notre disposition un important jeu de données provenant de [Kaggle](https://www.kaggle.com/datasets/tawsifurrahman/covid19-radiography-database). 
        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.

        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.
                    ''')
        
        col1, col2 = st.columns([1, 1])

        with col1:
            st.subheader("Métadonnées fournies")
            st.dataframe(df_metadata.head())

            ## Valeurs uniques
            unique_format = df_metadata["FORMAT"].unique()
            unique_format = ", ".join(map(str, unique_format))    
            unique_resolution = df_metadata["SIZE"].unique()
            unique_resolution = ", ".join(map(str, unique_resolution))

            data = [
                ("Liste des résolutions", unique_resolution),
                ("Liste des formats", unique_format),
                ("Nombre d'images", len(df_metadata))
            ]

            # Créer le DataFrame à partir de la liste de tuples
            df2 = pd.DataFrame(data, columns = ["Titre", "Variable"])

            # Convertir le dataframe en HTML avec les styles CSS
            html_table2 = df2.to_html(index = False, header = False, justify = 'center', classes = 'styled-table', border = 0)

            # Afficher le HTML dans Streamlit avec la largeur calculée
            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)            

        with col2:
            st.subheader("Métadonnées réelles")
            st.dataframe(df_images.head())

            ## Valeurs uniques
            unique_classes = df_images['LABEL'].unique()
            unique_classes = ", ".join(map(str, unique_classes))
            unique_sources = df_images['SOURCE'].dropna().unique()
            unique_sources = ", ".join(map(str, unique_sources))
            unique_format = df_images["FORMAT"].unique()
            unique_format = ", ".join(map(str, unique_format))    
            unique_resolution = df_images["SIZE"].unique()
            unique_resolution = ", ".join(map(str, unique_resolution))
            unique_channel = df_images["CHANNELS"].unique()
            unique_channel = ", ".join(map(str, unique_channel))

            data = [
                ("Liste des résolutions", unique_resolution),
                ("Liste des formats", unique_format),
                ("Liste des classes", unique_classes),
                ("Liste des sources", unique_sources),
                ("Nombre de canaux", unique_channel),
                ("Nombre d'images", len(df_images))
            ]

            # Créer le DataFrame à partir de la liste de tuples
            df = pd.DataFrame(data, columns = ["Titre", "Variable"])

            # Convertir le dataframe en HTML avec les styles CSS
            html_table = df.to_html(index = False, header = False, justify = 'center', classes = 'styled-table', border = 0)

            # Afficher le HTML dans Streamlit avec la largeur calculée
            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)


        # ==================================================================================================
        # ==================================================================================================

        st.header("Analyse du nombre d'images")

        col1, col2 = st.columns([0.4, 0.6])
        
        with col1:
            ## Countplot
            # Preprocess des données pour affichage
            total_images = df_images.groupby('LABEL').size().reset_index(name='Total Images')
            max_values = df_images.groupby('LABEL')['LABEL'].count().reset_index(name='Max Images')
            df_merged = pd.merge(total_images, max_values, on='LABEL')
            df_sorted = df_merged.sort_values('Total Images', ascending = False)

            palette = {'Normal': '#A1C9F4', 'Lung_Opacity': '#8DE5A1', 'COVID': '#FFB482', 'Viral Pneumonia': '#D0BBFF'}

            # Création du graphique Plotly avec affichage des valeurs maximales uniquement au survol
            fig = px.bar(df_sorted, x = 'LABEL', y = 'Total Images', color = 'LABEL',
                        color_discrete_map = palette,
                        title = "Distribution du nombre d'images par LABEL",
                        labels = {'LABEL': 'Label', 'Total Images': 'Nombre d\'images', 'Max Images': 'Nombre maximal d\'images'},
                        hover_data = {'LABEL': False, 'Max Images': True, 'Total Images': False}
                        )

            fig.update_layout(
                xaxis_title = 'Label',
                yaxis_title = "Nombre d'images",
                showlegend = True,
                bargap = 0.2,  # Espace entre les barres
                width = 600,
                height = 500
            )

            st.plotly_chart(fig)

        with col2:
            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.")
        
        st.header("", divider = 'gray')
        
        # ==================================================================================================
        # ==================================================================================================

        col1, col2 = st.columns([0.5, 0.5])

        with col1:
            ## Pieplot
            # Supposons que df_images est votre DataFrame et 'SOURCE' est la colonne d'intérêt
            source_counts = df_images['SOURCE'].value_counts()

            # Création d'un graphique pie avec Plotly
            fig = px.pie(
                values = source_counts.values, 
                names = source_counts.index, 
                title = 'Répartition des sources des images',
                hole = 0.5, 
                color_discrete_sequence = px.colors.qualitative.Pastel
            )

            fig.update_traces(
                textinfo = 'label+percent',
                marker = dict(line = dict(color = 'black', width = 1)),
            )

            fig.update_layout(
                showlegend = True,
                width = 700,
                height = 700
            )

            # Affichage du graphique avec Streamlit
            st.plotly_chart(fig)

        with col2:
            ## Barplot
            df_count = df_images.groupby(['SOURCE', 'LABEL']).size().reset_index(name='NOMBRE_IMAGES')

            # Créer un graphique empilé avec Plotly
            fig2 = px.bar(df_count, x='SOURCE', y='NOMBRE_IMAGES', color='LABEL', barmode='stack',
                        title='Nombre d\'images par label et par source',
                        labels={'NOMBRE_IMAGES': 'Nombre d\'images', 'LABEL': 'Label', 'SOURCE': 'Source'}, 
                        color_discrete_sequence = px.colors.qualitative.Pastel)
            
            fig2.update_traces(
                marker = dict(line = dict(color = 'black', width = 1)),
            )

            fig2.update_layout(
                showlegend = True,
                width = 700,
                height = 700
            )
            # Affichage du graphique avec Streamlit
            st.plotly_chart(fig2)


    ### Deuxième onglet
    with tab2:
        st.header("Exploration des images")

        ## Affichage d'une image aléatoire pour chaque catégorie
        dossier_radio = "radios/"
        sous_dossiers = [d for d in os.listdir(dossier_radio) if os.path.isdir(os.path.join(dossier_radio, d))]

        st.info("Cliquez sur le boutton ci-dessous pour afficher un échantillon d'images", icon="ℹ️")
        if st.button("Afficher une image aléatoire de chaque LABEL"):
            cols = st.columns(len(sous_dossiers))
            for i, sous_dossier in enumerate(sous_dossiers):
                    sous_dossier_path = os.path.join(dossier_radio, sous_dossier)
                    
                    fichiers_images = [f for f in os.listdir(sous_dossier_path) if f.endswith(('.png', '.jpg', '.jpeg'))]
                    
                    if fichiers_images:
                        image_selectionnee = random.choice(fichiers_images)
                        image_path = os.path.join(sous_dossier_path, image_selectionnee)
                        
                        with open(image_path, "rb") as f:
                            image = Image.open(f)
                            cols[i].image(image, caption=f"{sous_dossier}", use_column_width = "auto", width = 299)
            st.success('Images affichées avec succès !', icon = "✅")
            st.markdown('''
                        Nous pouvons remarquer que les images sont toutes en nuances de gris, malgré leur nombre de canaux quelques fois différent. 
                        De plus, toutes les radiographies semblent avoir été prises selon une méthode standard, mettant bien les poumons au centre de l'image. 
                        Quelques variations peuvent cependant apparaître (bras vers le haut, artefacts visuels, annotations, etc.)
                        ''')
        st.header("", divider = 'gray')
        
        # ==================================================================================================
        # ==================================================================================================

        col1, col2 = st.columns([0.6, 0.4])

        with col1:
            ## Violintplot
            palette = {
                'Normal (Sans masque)': '#A1C9F4',
                'Normal (Avec masque)': '#517EA4',
                'Lung_Opacity (Sans masque)': '#8DE5A1',
                'Lung_Opacity (Avec masque)': '#4E7D51',
                'COVID (Sans masque)': '#FFB482',
                'COVID (Avec masque)': '#BF6D41',
                'Viral Pneumonia (Sans masque)': '#D0BBFF',
                'Viral Pneumonia (Avec masque)': '#7E6CBF'
            }

            new_labels = ['COVID\nAvec masque', 'COVID\nSans masque',
                        'Lung_Opacity\nAvec masque', 'Lung_Opacity\nSans masque',
                        'Normal\nAvec masque', 'Normal\nSans masque',
                        'Viral Pneumonia\nAvec masque', 'Viral Pneumonia\nSans masque']

            # Création du graphique Plotly
            fig = px.violin(df_combined, x = 'Label_Masque', y = 'COMBINED_INTENSITY',
                            violinmode = 'overlay',
                            color = 'Label_Masque',
                            color_discrete_map = palette,
                            category_orders = {'Label_Masque': new_labels},
                            title = "Distribution de l'intensité lumineuse moyenne normalisée",
                            labels = {'Label_Masque': ''},
                            )

            # Mise en forme du graphique
            fig.update_layout(
                yaxis_title = 'Intensité lumineuse moyenne',
                xaxis_title = '',
                width = 1000,
                height = 600
            )

            # Affichage du graphique
            st.plotly_chart(fig)

        with col2:
            st.markdown('''
                        L'application des masques réduit considérablement l'intensité lumineuse moyenne des images. 
                        Ce comportement est tout à fait normal car les masques noircient les parties non pertinentes et font ainsi tendre la moyenne des pixels vers 0.

                        Ainsi, les valeurs restantes sont les valeurs ayant de la pertinence dans ce que nous cherchons à faire observer au modèle. 
                        Mais ceci peut aussi entrainer une diminution de la variabilité des images, pouvant entrainer plus de difficultés à la généralisation.
                        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.
                        ''')
        
        st.header("", divider = 'gray')
        
        # ==================================================================================================
        # ==================================================================================================

        ## Histogramme de la fréquence de l'intensité des pixels
        palette_list = ['#A1C9F4','#8DE5A1','#FFB482', '#D0BBFF']

        st.info("Cliquez sur le bouton ci-dessous pour afficher un échantillon d'images et leur histogramme", icon="ℹ️")
        if st.button("Afficher la fréquence de l'intensité des pixels"):
            # Créer une ligne avec quatre colonnes
            cols = st.columns(4)
            for idx, sous_dossier in enumerate(sous_dossiers):
                sous_dossier_path = os.path.join(dossier_radio, sous_dossier)
                fichiers_images = [f for f in os.listdir(sous_dossier_path) if f.endswith(('.png', '.jpg', '.jpeg'))]
                
                if fichiers_images:
                    image_selectionnee = random.choice(fichiers_images)
                    image_path = os.path.join(sous_dossier_path, image_selectionnee)
                    
                    with open(image_path, "rb") as f:
                        image = Image.open(f)
                        # Afficher l'image avec sa véritable résolution sans agrandissement
                        cols[idx].image(image, caption=f"{sous_dossier}", width = image.width, use_column_width = "auto")
                        
                        image_array = np.array(image)
                        if len(image_array.shape) == 3:
                            image_array = image_array.mean(axis=2)

                        filtered_array = image_array[image_array > 0]
                        hist_values, bin_edges = np.histogram(filtered_array, bins=255, range=(0, 256))
                        
                        # Création de l'histogramme avec Plotly
                        fig = px.bar(
                            x=bin_edges[:-1],
                            y=hist_values,
                            labels={'x': 'Intensité des pixels', 'y': 'Nombre de pixels'},
                            color_discrete_sequence=[palette_list[idx]]
                        )
                        cols[idx].plotly_chart(fig, use_container_width=True)
            st.success('Histogrammes générés avec succès !', icon="✅")
        st.header("", divider='gray')
        
        # ==================================================================================================
        # ==================================================================================================

        col1, col2 = st.columns([0.4, 0.6])

        with col1:
            st.markdown('''
                        La surface utile des masques est l'ensemble des pixels apportant de l'information dans l'analyse de notre problématique. 
                        En pratique, il s'agit de l'ensemble des pixels définissant les poumons sur la radiographie.
                        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.
                        C'est là que l'application des masques pour limiter la "vision" du modèle aux poumons peut s'avérer utile.
                        ''')

        with col2:
            ## Surface utile
            # Création de l'histogramme avec Plotly Express
            fig2 = px.histogram(df_masks, x = 'RATIO', color = 'LABEL',
                            nbins = 70,
                            barmode = 'overlay',  # Superpose les distributions
                            color_discrete_sequence = ['#A1C9F4', '#8DE5A1', '#FFB482', '#D0BBFF'],  # Palette de couleurs
                            opacity = 0.75)

            # Personnalisation supplémentaire
            fig2.update_traces(marker_line_color = 'gray', marker_line_width = 1.5)  # Ajouter la bordure de barre
            fig2.update_layout(
                title = 'Ratio de la surface utile en appliquant les masques',
                xaxis_title = 'Ratio',
                yaxis_title = 'Nombre de cas',
                legend_title = 'Label',
                height = 500,
                width = 800
            )

            # Affichage du graphique
            st.plotly_chart(fig2)