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("""
""", 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"
{html_table2}
", 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"{html_table}
", 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)