Package: https://zenodo.org/records/3632035
Il link fornito conduce al dataset “ICIAR 2018 Grand Challenge on Breast Cancer Histology Images (BACH)”, ospitato su Zenodo. Questo non è un database nel senso tradizionale (come un sistema SQL o NoSQL), bensì una collezione di file, principalmente immagini istologiche di campioni di tessuto mammario, accompagnate da metadati e, presumibilmente, file di etichette.
Il software Python che svilupperemo sarà orientato alla gestione, al caricamento, alla pre-elaborazione e, potenzialmente, all’analisi di queste immagini. Data la natura del dataset, le librerie per l’elaborazione di immagini e l’apprendimento automatico saranno centrali.
Scopo: imparare a usare db massivi di materiale oncologico per ipotizzare nuove soluzioni, velocizzare algoritmi e tools usati attualmente, creare nuovi concetti per l’accelerazione dell’apprendimento da parte di umani e macchine.
Test sperimentale 0.0.0.1, Luigi Usai, Quartucciu.
Analisi Preliminare del Dataset (basata sulle informazioni pubbliche)
-
Contenuto Principale:
- Immagini Microscopiche: Il dataset contiene circa 400 immagini microscopiche (formato
.tif
), ad alta risoluzione (tipicamente 2048×1536 pixel), colorate con ematossilina ed eosina (H&E). Queste immagini sono classificate in quattro categorie: normale, benigna, carcinoma in situ e carcinoma invasivo (100 immagini per categoria). - Immagini Whole-Slide (WSI): Sono presenti anche immagini “whole-slide” (formato
.svs
), che sono scansioni complete di vetrini istologici, di dimensioni molto maggiori e utilizzate per compiti di segmentazione a livello di pixel. - File Ausiliari: Solitamente, questi dataset includono file
README.txt
con descrizioni dettagliate,LICENSE.txt
(spesso una licenza Creative Commons come CC BY-NC-ND), e file CSV o di testo contenenti le etichette delle immagini o le coordinate delle regioni di interesse.
- Immagini Microscopiche: Il dataset contiene circa 400 immagini microscopiche (formato
-
Struttura dei File (Presunta):
- I file sono distribuiti in archivi ZIP (
ICIAR2018_BACH_Challenge.zip
per il training/validation set eICIAR2018_BACH_Challenge_TestDataset.zip
per il test set). - All’interno dello ZIP principale, le immagini
.tif
sono spesso organizzate in sottocartelle corrispondenti alle loro classi (es.Photos/Benign/
,Photos/Invasive/
, ecc.).
- I file sono distribuiti in archivi ZIP (
Architettura del Software Python Proposto
Progetteremo un software modulare in Python che permetta di:
- Scaricare e decomprimere i dati (gestione manuale consigliata per file di grandi dimensioni).
- Caricare le immagini microscopiche (
.tif
) e le relative etichette. - Effettuare una pre-elaborazione di base delle immagini.
- Fornire utility per l’esplorazione del dataset (es. visualizzare immagini, mostrare statistiche).
- (Opzionale, ma comune) Strutturare i dati per l’addestramento di modelli di apprendimento automatico.
Librerie Python Fondamentali:
os
,shutil
,zipfile
: Per la gestione dei file e delle directory.Pillow
(PIL) oOpenCV-Python
(cv2
): Per il caricamento, la manipolazione e la pre-elaborazione delle immagini TIFF.NumPy
: Per la gestione efficiente di array multidimensionali (le immagini sono array di pixel).Pandas
: Per la gestione di dati tabulari (es. file CSV con etichette).Matplotlib
oSeaborn
: Per la visualizzazione di immagini e grafici.OpenSlide-Python
: Necessaria se si intende lavorare con le immagini Whole-Slide (.svs
).Requests
(ourllib
): Se si volesse automatizzare il download (sconsigliato per file così grandi senza gestione di resume).Scikit-learn
: Per utility di apprendimento automatico (es. divisione train/test, metriche).
Istruzioni per la Configurazione sul Suo PC
Passo 1: Installazione di Python
- Se non ha Python installato, scarichi l’ultima versione stabile (es. Python 3.9+) dal sito ufficiale python.org.
- Durante l’installazione su Windows, si assicuri di selezionare l’opzione “Add Python to PATH”.
Passo 2: Creazione di un Ambiente Virtuale (Altamente Raccomandato)
Un ambiente virtuale isola le dipendenze del progetto, prevenendo conflitti tra librerie di progetti diversi.
python -m venv bach_env
Attivi l’ambiente virtuale:
- Su Windows (cmd):
Bash
bach_env\Scripts\activate
- Su macOS/Linux (bash):
Bash
source bach_env/bin/activate
Vedrà (bach_env)
all’inizio del prompt del suo terminale.
Passo 3: Installazione delle Librerie Python Necessarie
Con l’ambiente virtuale attivo, installi le librerie usando pip
:
pip install Pillow opencv-python numpy pandas matplotlib scikit-learn requests openslide-python
Nota: openslide-python
potrebbe avere dipendenze a livello di sistema. Per Windows, potrebbe essere necessario scaricare i binari di OpenSlide come indicato nella documentazione di openslide-python
(spesso cercando “openslide windows binaries” e aggiungendo la directory bin
al PATH di sistema). Per Linux/macOS, si installa solitamente tramite package manager (es. sudo apt-get install openslide-tools
o brew install openslide
).
Passo 4: Download e Decompressione del Dataset
- Visiti la pagina Zenodo: https://zenodo.org/records/3632035
- Scarichi i file:
ICIAR2018_BACH_Challenge.zip
(10.4 GB)- (Opzionale)
ICIAR2018_BACH_Challenge_TestDataset.zip
(3.0 GB) README.txt
LICENSE.txt
- Crei una cartella per il suo progetto, ad esempio
ICIAR_BACH_Project
. - All’interno di
ICIAR_BACH_Project
, crei una sottocartelladataset
. - Sposti i file scaricati in
ICIAR_BACH_Project/dataset
. - Decomprima
ICIAR2018_BACH_Challenge.zip
all’interno della cartelladataset
. Questo creerà probabilmente una struttura di cartelle contenente le immagini. È fondamentale ispezionare il fileREADME.txt
per comprendere l’esatta struttura e il significato dei file (es. come sono codificate le etichette, se esiste un file CSV apposito).
Struttura Attesa della Cartella di Progetto (Esempio):
ICIAR_BACH_Project/
├── bach_env/ # Ambiente virtuale Python
├── dataset/
│ ├── ICIAR2018_BACH_Challenge.zip # Archivio originale
│ ├── README.txt
│ ├── LICENSE.txt
│ └── Photos/ # O altra cartella radice dopo la decompressione
│ ├── Benign/
│ │ ├── image_benign_001.tif
│ │ └── ...
│ ├── Invasive/
│ │ ├── image_invasive_001.tif
│ │ └── ...
│ ├── InSitu/
│ │ ├── image_insitu_001.tif
│ │ └── ...
│ └── Normal/
│ ├── image_normal_001.tif
│ └── ...
│ └── (eventuali altri file come labels.csv)
└── src/ # Cartella per i suoi script Python
├── data_loader.py
├── image_processor.py
├── main.py
└── utils.py
Sviluppo del Software Python (Moduli Concettuali)
Creeremo alcuni file Python nella cartella src/
.
1. src/config.py
(Configurazioni)
# src/config.py
import os
# Percorsi di base
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # ICIAR_BACH_Project
DATASET_DIR = os.path.join(PROJECT_DIR, "dataset")
MICROSCOPY_IMAGES_DIR = os.path.join(DATASET_DIR, "Photos") # Assumendo che le immagini siano in 'Photos' dopo la decompressione
# Classi del dataset (basate sulla struttura delle cartelle o su un file di etichette)
CLASSES = ["Normal", "Benign", "InSitu", "Invasive"] # Modificare se necessario
# Parametri per le immagini
IMAGE_WIDTH = 2048
IMAGE_HEIGHT = 1536
IMAGE_CHANNELS = 3 # RGB
2. src/data_loader.py
(Caricamento Dati)
# src/data_loader.py
import os
import glob
from PIL import Image
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
import config
def load_microscopy_images_from_folders(base_dir=config.MICROSCOPY_IMAGES_DIR, classes=config.CLASSES):
"""
Carica le immagini microscopiche e le loro etichette basandosi sulla struttura delle cartelle.
Args:
base_dir (str): Directory radice contenente le sottocartelle delle classi.
classes (list): Lista dei nomi delle classi (corrispondenti ai nomi delle sottocartelle).
Returns:
tuple: (lista_immagini_numpy, lista_etichette_numeriche, mappatura_etichette)
"""
images = []
labels = []
label_map = {class_name: i for i, class_name in enumerate(classes)}
print(f"Caricamento immagini da: {base_dir}")
if not os.path.isdir(base_dir):
print(f"ERRORE: La directory delle immagini '{base_dir}' non esiste. Verificare il percorso e la decompressione.")
return [], [], {}
for class_name in classes:
class_dir = os.path.join(base_dir, class_name)
if not os.path.isdir(class_dir):
print(f"ATTENZIONE: La directory per la classe '{class_name}' non è stata trovata in '{class_dir}'.")
continue
image_files = glob.glob(os.path.join(class_dir, "*.tif"))
print(f"Trovate {len(image_files)} immagini per la classe '{class_name}'.")
for img_path in image_files:
try:
with Image.open(img_path) as img:
# Assicurarsi che l'immagine sia RGB
img = img.convert("RGB")
images.append(np.array(img))
labels.append(label_map[class_name])
except Exception as e:
print(f"Errore nel caricamento o conversione dell'immagine {img_path}: {e}")
if not images:
print("Nessuna immagine caricata. Controllare la struttura delle cartelle e i log.")
return np.array([]), np.array([]), {}
return np.array(images, dtype=object), np.array(labels), label_map
# Potrebbe essere necessario un loader per un file CSV se le etichette sono lì
# def load_labels_from_csv(csv_path):
# if not os.path.exists(csv_path):
# print(f"File CSV delle etichette non trovato: {csv_path}")
# return None
# return pd.read_csv(csv_path)
if __name__ == '__main__':
print("Test caricamento dati...")
# Questo percorso deve riflettere la struttura effettiva dopo la decompressione
# Potrebbe essere 'ICIAR2018_BACH_Challenge/Photos' o simile
# Verificare il contenuto di README.txt per la struttura esatta.
actual_microscopy_dir = config.MICROSCOPY_IMAGES_DIR
# Se lo zip estrae una cartella radice, es:
# os.path.join(config.DATASET_DIR, "ICIAR2018_BACH_Challenge/Photos")
all_images, all_labels, label_mapping = load_microscopy_images_from_folders(actual_microscopy_dir)
if all_images.size > 0:
print(f"Caricate {len(all_images)} immagini.")
print(f"Forma della prima immagine: {all_images[0].shape}")
print(f"Numero di etichette: {len(all_labels)}")
print(f"Mappatura etichette: {label_mapping}")
# Esempio di divisione dati
X_train, X_test, y_train, y_test = train_test_split(all_images, all_labels, test_size=0.2, random_state=42, stratify=all_labels)
print(f"Dimensioni Training set: {len(X_train)} immagini")
print(f"Dimensioni Test set: {len(X_test)} immagini")
else:
print("Nessun dato caricato. Verificare i percorsi e la struttura del dataset.")
3. src/image_processor.py
(Pre-elaborazione Immagini)
# src/image_processor.py
import cv2
import numpy as np
import config
def preprocess_image(image_array, target_size=(224, 224)):
"""
Pre-elabora un'immagine: resize e normalizzazione.
Args:
image_array (np.array): Immagine come array NumPy (H, W, C).
target_size (tuple): Dimensione desiderata (width, height).
Returns:
np.array: Immagine pre-elaborata.
"""
# Ridimensionamento (OpenCV usa (width, height))
resized_image = cv2.resize(image_array, target_size, interpolation=cv2.INTER_AREA)
# Normalizzazione dei pixel a [0, 1]
normalized_image = resized_image / 255.0
return normalized_image.astype(np.float32)
if __name__ == '__main__':
# Carica un'immagine di esempio per testare
# (è necessario che data_loader.py funzioni e carichi almeno un'immagine)
from data_loader import load_microscopy_images_from_folders
images, labels, _ = load_microscopy_images_from_folders(config.MICROSCOPY_IMAGES_DIR)
if images.size > 0:
sample_image = images[0]
print(f"Forma originale: {sample_image.shape}, Tipo: {sample_image.dtype}")
processed_sample = preprocess_image(sample_image)
print(f"Forma pre-elaborata: {processed_sample.shape}, Tipo: {processed_sample.dtype}")
# Per visualizzare (opzionale, richiede matplotlib)
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2)
ax[0].imshow(sample_image)
ax[0].set_title("Originale")
ax[1].imshow(processed_sample) # Se normalizzata, i colori potrebbero apparire strani senza denormalizzazione o scaling
ax[1].set_title("Pre-elaborata (224x224)")
plt.show()
else:
print("Nessuna immagine campione da pre-elaborare.")
4. src/utils.py
(Utility Varie)
# src/utils.py
import matplotlib.pyplot as plt
import numpy as np
import config
def display_sample_images(images, labels, label_map, num_samples=5):
"""
Visualizza un numero di immagini campione con le loro etichette.
Args:
images (np.array): Array di immagini.
labels (np.array): Array di etichette numeriche.
label_map (dict): Mappatura da etichetta numerica a nome della classe.
num_samples (int): Numero di campioni da visualizzare.
"""
if images.size == 0:
print("Nessuna immagine da visualizzare.")
return
inverse_label_map = {v: k for k, v in label_map.items()}
indices = np.random.choice(len(images), num_samples, replace=False)
plt.figure(figsize=(15, 5 * (num_samples // 3 + 1)))
for i, idx in enumerate(indices):
plt.subplot( (num_samples // 3 + 1) , 3, i + 1)
plt.imshow(images[idx])
plt.title(f"Classe: {inverse_label_map[labels[idx]]}")
plt.axis("off")
plt.tight_layout()
plt.show()
# Funzioni per OpenSlide (se si usano file .svs)
try:
import openslide
except ImportError:
print("Libreria openslide-python non trovata. Le funzionalità per WSI (.svs) non saranno disponibili.")
openslide = None
def load_wsi_image(svs_file_path):
if not openslide:
raise ImportError("openslide-python non è installata.")
if not os.path.exists(svs_file_path):
raise FileNotFoundError(f"File WSI non trovato: {svs_file_path}")
slide = openslide.OpenSlide(svs_file_path)
print(f"Caricata WSI: {svs_file_path}")
print(f"Livelli: {slide.level_count}")
print(f"Dimensioni Livello 0: {slide.level_dimensions[0]}")
return slide
def get_wsi_thumbnail(slide, size=(512, 512)):
if not openslide:
raise ImportError("openslide-python non è installata.")
return slide.get_thumbnail(size)
5. src/main.py
(Script Principale)
# src/main.py
import os
import config
from data_loader import load_microscopy_images_from_folders
from image_processor import preprocess_image
from utils import display_sample_images, load_wsi_image, get_wsi_thumbnail
def run_microscopy_pipeline():
print("Avvio pipeline per immagini microscopiche...")
# Assicurarsi che il percorso MICROSCOPY_IMAGES_DIR in config.py sia corretto
# e punti alla cartella che contiene le sottocartelle delle classi (Benign, Invasive, etc.)
# Potrebbe essere necessario aggiustarlo in base a come lo ZIP si decomprime, ad es.:
# actual_microscopy_path = os.path.join(config.DATASET_DIR, "ICIAR2018_BACH_Challenge_Photos_V1", "Photos")
# oppure semplicemente config.MICROSCOPY_IMAGES_DIR se "Photos" è direttamente in "dataset"
images, labels, label_map = load_microscopy_images_from_folders(config.MICROSCOPY_IMAGES_DIR, config.CLASSES)
if images.size == 0:
print("Nessuna immagine microscopica caricata. Terminazione pipeline.")
print(f"Verificare che la cartella '{config.MICROSCOPY_IMAGES_DIR}' contenga le sottocartelle delle classi '{config.CLASSES}' con immagini .tif.")
return
print(f"\nCaricate {len(images)} immagini microscopiche.")
display_sample_images(images, labels, label_map, num_samples=6)
print("\nPre-elaborazione di un campione...")
sample_for_processing = images[0]
processed_sample = preprocess_image(sample_for_processing, target_size=(256, 256))
print(f"Forma campione pre-elaborato: {processed_sample.shape}")
# Qui si potrebbe aggiungere logica per addestrare un modello, fare analisi, ecc.
print("\nPipeline per immagini microscopiche completata.")
def run_wsi_pipeline_sample():
if not os.path.exists(os.path.join(config.DATASET_DIR, "WSI")): # Assumendo che i file .svs siano in dataset/WSI
print("Cartella WSI non trovata, saltando esempio WSI.")
return
print("\nAvvio pipeline di esempio per Whole-Slide Images (WSI)...")
# Trovare un file .svs di esempio (modificare il percorso se necessario)
# Questa parte è speculativa, dipende dalla disponibilità e struttura dei file .svs
sample_svs_files = [f for f in os.listdir(os.path.join(config.DATASET_DIR, "WSI")) if f.endswith(".svs")]
if not sample_svs_files:
print("Nessun file .svs trovato nella cartella dataset/WSI. Impossibile eseguire l'esempio WSI.")
return
sample_svs_path = os.path.join(config.DATASET_DIR, "WSI", sample_svs_files[0])
try:
slide = load_wsi_image(sample_svs_path)
thumbnail = get_wsi_thumbnail(slide)
print(f"Thumbnail generata per {sample_svs_path} con dimensioni {thumbnail.size}")
# Per visualizzare (opzionale, richiede matplotlib)
import matplotlib.pyplot as plt
plt.imshow(thumbnail)
plt.title(f"Thumbnail WSI: {os.path.basename(sample_svs_path)}")
plt.show()
slide.close()
except Exception as e:
print(f"Errore durante l'elaborazione WSI: {e}")
print("\nPipeline di esempio WSI completata.")
if __name__ == "__main__":
print(f"Directory del progetto: {config.PROJECT_DIR}")
print(f"Directory del dataset: {config.DATASET_DIR}")
# Controllare se la directory delle immagini microscopiche esiste
if not os.path.isdir(config.MICROSCOPY_IMAGES_DIR):
print(f"ERRORE CRITICO: La directory specificata per le immagini microscopiche '{config.MICROSCOPY_IMAGES_DIR}' non esiste.")
print("Possibili cause:")
print("1. Il file .zip non è stato decompresso, o non nella cartella 'dataset'.")
print("2. Il percorso in 'config.py' (MICROSCOPY_IMAGES_DIR) non riflette la struttura delle cartelle effettiva dopo la decompressione.")
print(" Ad esempio, se lo zip crea una cartella radice come 'ICIAR2018_BACH_Challenge', il percorso dovrebbe essere aggiornato.")
print(" Si prega di controllare la struttura delle cartelle in 'ICIAR_BACH_Project/dataset/' e aggiustare 'config.MICROSCOPY_IMAGES_DIR'.")
else:
run_microscopy_pipeline()
# run_wsi_pipeline_sample() # Decommentare se si hanno file .svs e OpenSlide configurato
Esecuzione del Software
- Navighi nel terminale (con l’ambiente virtuale
bach_env
attivo) alla cartellaICIAR_BACH_Project/src/
. - Esegua lo script principale:
Bash
python main.py
Considerazioni Finali e Ulteriori Sviluppi
- Struttura Esatta del Dataset: Il codice fornito assume una certa struttura delle cartelle (es.
Photos/Classe/*.tif
). È cruciale ispezionare il fileREADME.txt
del dataset e il contenuto effettivo delle cartelle dopo la decompressione. Potrebbe essere necessario adattare i percorsi inconfig.py
edata_loader.py
. Ad esempio, se lo ZIP estrae una cartella radice tipoICIAR2018_BACH_Challenge_Photos_V1
, alloraconfig.MICROSCOPY_IMAGES_DIR
dovrà essereos.path.join(DATASET_DIR, "ICIAR2018_BACH_Challenge_Photos_V1", "Photos")
. - Gestione della Memoria: Caricare tutte le immagini (10.4 GB decompresse possono essere molte di più in memoria come array NumPy) contemporaneamente potrebbe esaurire la RAM. Per dataset di grandi dimensioni, si usano tecniche come:
- Generatori di Dati: Funzioni Python (
yield
) o classi (comekeras.utils.Sequence
in TensorFlow/Keras) che caricano e pre-elaborano i dati in batch “al volo” durante l’addestramento di un modello. - Memory-Mapped Files: Per accedere a porzioni di file grandi senza caricarli interamente in memoria.
- Generatori di Dati: Funzioni Python (
- File di Etichette CSV: Se le etichette sono in un file CSV invece che dedotte dalla struttura delle cartelle,
data_loader.py
dovrà essere modificato per leggere quel file (usandopandas
) e associare le etichette ai file immagine corretti. - Elaborazione Avanzata: Questo software fornisce le basi. Per compiti di classificazione del cancro, si passerebbe all’addestramento di Reti Neurali Convoluzionali (CNN) usando librerie come TensorFlow/Keras o PyTorch.
- Whole-Slide Images (.svs): L’elaborazione di file
.svs
è più complessa a causa delle loro dimensioni.OpenSlide
permette di accedere a diverse risoluzioni (livelli) e di estrarre patch (porzioni dell’immagine) senza caricare l’intera WSI in memoria. Larun_wsi_pipeline_sample()
fornisce un inizio basilare.
Questo approccio Le fornisce una base solida e scientificamente strutturata per lavorare con il dataset ICIAR BACH. Si ricordi di adattare i percorsi e la logica di caricamento in base alla struttura esatta dei file che troverà dopo la decompressione e consultando il README.txt
.