# Visualization ## Overview Histolab provides several built-in visualization methods to help inspect slides, preview tile locations, visualize masks, and assess extraction quality. Proper visualization is essential for validating preprocessing pipelines, debugging extraction issues, and presenting results. ## Slide Visualization ### Thumbnail Display ```python from histolab.slide import Slide import matplotlib.pyplot as plt slide = Slide("slide.svs", processed_path="output/") # Display thumbnail plt.figure(figsize=(10, 10)) plt.imshow(slide.thumbnail) plt.title(f"Slide: {slide.name}") plt.axis('off') plt.show() ``` ### Save Thumbnail to Disk ```python # Save thumbnail as image file slide.save_thumbnail() # Saves to processed_path/thumbnails/slide_name_thumb.png ``` ### Scaled Images ```python # Get scaled version of slide at specific downsample factor scaled_img = slide.scaled_image(scale_factor=32) plt.imshow(scaled_img) plt.title(f"Slide at 32x downsample") plt.show() ``` ## Mask Visualization ### Using locate_mask() ```python from histolab.masks import TissueMask, BiggestTissueBoxMask # Visualize TissueMask tissue_mask = TissueMask() slide.locate_mask(tissue_mask) # Visualize BiggestTissueBoxMask biggest_mask = BiggestTissueBoxMask() slide.locate_mask(biggest_mask) ``` This displays the slide thumbnail with mask boundaries overlaid in red. ### Manual Mask Visualization ```python import matplotlib.pyplot as plt from histolab.masks import TissueMask slide = Slide("slide.svs", processed_path="output/") mask = TissueMask() # Generate mask mask_array = mask(slide) # Create side-by-side comparison fig, axes = plt.subplots(1, 3, figsize=(20, 7)) # Original thumbnail axes[0].imshow(slide.thumbnail) axes[0].set_title("Original Slide") axes[0].axis('off') # Binary mask axes[1].imshow(mask_array, cmap='gray') axes[1].set_title("Tissue Mask") axes[1].axis('off') # Overlay mask on thumbnail from matplotlib.colors import ListedColormap overlay = slide.thumbnail.copy() axes[2].imshow(overlay) axes[2].imshow(mask_array, cmap=ListedColormap(['none', 'red']), alpha=0.3) axes[2].set_title("Mask Overlay") axes[2].axis('off') plt.tight_layout() plt.show() ``` ### Comparing Multiple Masks ```python from histolab.masks import TissueMask, BiggestTissueBoxMask masks = { 'TissueMask': TissueMask(), 'BiggestTissueBoxMask': BiggestTissueBoxMask() } fig, axes = plt.subplots(1, len(masks) + 1, figsize=(20, 6)) # Original axes[0].imshow(slide.thumbnail) axes[0].set_title("Original") axes[0].axis('off') # Each mask for idx, (name, mask) in enumerate(masks.items(), 1): mask_array = mask(slide) axes[idx].imshow(mask_array, cmap='gray') axes[idx].set_title(name) axes[idx].axis('off') plt.tight_layout() plt.show() ``` ## Tile Location Preview ### Using locate_tiles() Preview tile locations before extraction: ```python from histolab.tiler import RandomTiler, GridTiler, ScoreTiler from histolab.scorer import NucleiScorer # RandomTiler preview random_tiler = RandomTiler( tile_size=(512, 512), n_tiles=50, level=0, seed=42 ) random_tiler.locate_tiles(slide, n_tiles=20) # GridTiler preview grid_tiler = GridTiler( tile_size=(512, 512), level=0 ) grid_tiler.locate_tiles(slide) # ScoreTiler preview score_tiler = ScoreTiler( tile_size=(512, 512), n_tiles=30, scorer=NucleiScorer() ) score_tiler.locate_tiles(slide, n_tiles=15) ``` This displays colored rectangles on the slide thumbnail indicating where tiles will be extracted. ### Custom Tile Location Visualization ```python import matplotlib.pyplot as plt import matplotlib.patches as patches from histolab.tiler import RandomTiler slide = Slide("slide.svs", processed_path="output/") tiler = RandomTiler(tile_size=(512, 512), n_tiles=30, seed=42) # Get thumbnail and scale factor thumbnail = slide.thumbnail scale_factor = slide.dimensions[0] / thumbnail.size[0] # Generate tile coordinates (without extracting) fig, ax = plt.subplots(figsize=(12, 12)) ax.imshow(thumbnail) ax.set_title("Tile Locations Preview") ax.axis('off') # Manually add rectangles for each tile location # Note: This is conceptual - actual implementation would retrieve coordinates from tiler tile_coords = [] # Would be populated by tiler logic for coord in tile_coords: x, y = coord[0] / scale_factor, coord[1] / scale_factor w, h = 512 / scale_factor, 512 / scale_factor rect = patches.Rectangle((x, y), w, h, linewidth=2, edgecolor='red', facecolor='none') ax.add_patch(rect) plt.show() ``` ## Tile Visualization ### Display Extracted Tiles ```python from pathlib import Path from PIL import Image import matplotlib.pyplot as plt tile_dir = Path("output/tiles/") tile_paths = list(tile_dir.glob("*.png"))[:16] # First 16 tiles fig, axes = plt.subplots(4, 4, figsize=(12, 12)) axes = axes.ravel() for idx, tile_path in enumerate(tile_paths): tile_img = Image.open(tile_path) axes[idx].imshow(tile_img) axes[idx].set_title(tile_path.stem, fontsize=8) axes[idx].axis('off') plt.tight_layout() plt.show() ``` ### Tile Grid Mosaic ```python def create_tile_mosaic(tile_dir, grid_size=(4, 4)): """Create mosaic of tiles.""" tile_paths = list(Path(tile_dir).glob("*.png"))[:grid_size[0] * grid_size[1]] fig, axes = plt.subplots(grid_size[0], grid_size[1], figsize=(16, 16)) for idx, tile_path in enumerate(tile_paths): row = idx // grid_size[1] col = idx % grid_size[1] tile_img = Image.open(tile_path) axes[row, col].imshow(tile_img) axes[row, col].axis('off') plt.tight_layout() plt.savefig("tile_mosaic.png", dpi=150, bbox_inches='tight') plt.show() create_tile_mosaic("output/tiles/", grid_size=(5, 5)) ``` ### Tile with Tissue Mask Overlay ```python from histolab.tile import Tile import matplotlib.pyplot as plt # Assume we have a tile object tile = Tile(image=pil_image, coords=(x, y)) # Calculate tissue mask tile.calculate_tissue_mask() fig, axes = plt.subplots(1, 3, figsize=(15, 5)) # Original tile axes[0].imshow(tile.image) axes[0].set_title("Original Tile") axes[0].axis('off') # Tissue mask axes[1].imshow(tile.tissue_mask, cmap='gray') axes[1].set_title(f"Tissue Mask ({tile.tissue_ratio:.1%} tissue)") axes[1].axis('off') # Overlay axes[2].imshow(tile.image) axes[2].imshow(tile.tissue_mask, cmap='Reds', alpha=0.3) axes[2].set_title("Overlay") axes[2].axis('off') plt.tight_layout() plt.show() ``` ## Quality Assessment Visualization ### Tile Score Distribution ```python import pandas as pd import matplotlib.pyplot as plt import seaborn as sns # Load tile report from ScoreTiler report_df = pd.read_csv("tiles_report.csv") # Score distribution histogram plt.figure(figsize=(10, 6)) plt.hist(report_df['score'], bins=30, edgecolor='black', alpha=0.7) plt.xlabel('Tile Score') plt.ylabel('Frequency') plt.title('Distribution of Tile Scores') plt.grid(axis='y', alpha=0.3) plt.show() # Score vs tissue percentage scatter plt.figure(figsize=(10, 6)) plt.scatter(report_df['tissue_percent'], report_df['score'], alpha=0.5) plt.xlabel('Tissue Percentage') plt.ylabel('Tile Score') plt.title('Tile Score vs Tissue Coverage') plt.grid(alpha=0.3) plt.show() ``` ### Top vs Bottom Scoring Tiles ```python import pandas as pd from PIL import Image import matplotlib.pyplot as plt # Load tile report report_df = pd.read_csv("tiles_report.csv") report_df = report_df.sort_values('score', ascending=False) # Top 8 tiles top_tiles = report_df.head(8) # Bottom 8 tiles bottom_tiles = report_df.tail(8) fig, axes = plt.subplots(2, 8, figsize=(20, 6)) # Display top tiles for idx, (_, row) in enumerate(top_tiles.iterrows()): tile_img = Image.open(f"output/tiles/{row['tile_name']}") axes[0, idx].imshow(tile_img) axes[0, idx].set_title(f"Score: {row['score']:.3f}", fontsize=8) axes[0, idx].axis('off') # Display bottom tiles for idx, (_, row) in enumerate(bottom_tiles.iterrows()): tile_img = Image.open(f"output/tiles/{row['tile_name']}") axes[1, idx].imshow(tile_img) axes[1, idx].set_title(f"Score: {row['score']:.3f}", fontsize=8) axes[1, idx].axis('off') axes[0, 0].set_ylabel('Top Scoring', fontsize=12) axes[1, 0].set_ylabel('Bottom Scoring', fontsize=12) plt.tight_layout() plt.savefig("score_comparison.png", dpi=150, bbox_inches='tight') plt.show() ``` ## Multi-Slide Visualization ### Slide Collection Thumbnails ```python from pathlib import Path from histolab.slide import Slide import matplotlib.pyplot as plt slide_dir = Path("slides/") slide_paths = list(slide_dir.glob("*.svs"))[:9] fig, axes = plt.subplots(3, 3, figsize=(15, 15)) axes = axes.ravel() for idx, slide_path in enumerate(slide_paths): slide = Slide(slide_path, processed_path="output/") axes[idx].imshow(slide.thumbnail) axes[idx].set_title(slide.name, fontsize=10) axes[idx].axis('off') plt.tight_layout() plt.savefig("slide_collection.png", dpi=150, bbox_inches='tight') plt.show() ``` ### Tissue Coverage Comparison ```python from pathlib import Path from histolab.slide import Slide from histolab.masks import TissueMask import matplotlib.pyplot as plt import numpy as np slide_paths = list(Path("slides/").glob("*.svs")) tissue_percentages = [] slide_names = [] for slide_path in slide_paths: slide = Slide(slide_path, processed_path="output/") mask = TissueMask()(slide) tissue_pct = mask.sum() / mask.size * 100 tissue_percentages.append(tissue_pct) slide_names.append(slide.name) # Bar plot plt.figure(figsize=(12, 6)) plt.bar(range(len(slide_names)), tissue_percentages) plt.xticks(range(len(slide_names)), slide_names, rotation=45, ha='right') plt.ylabel('Tissue Coverage (%)') plt.title('Tissue Coverage Across Slides') plt.grid(axis='y', alpha=0.3) plt.tight_layout() plt.show() ``` ## Filter Effect Visualization ### Before and After Filtering ```python from histolab.filters.image_filters import RgbToGrayscale, HistogramEqualization from histolab.filters.compositions import Compose # Define filter pipeline filter_pipeline = Compose([ RgbToGrayscale(), HistogramEqualization() ]) # Original vs filtered fig, axes = plt.subplots(1, 2, figsize=(12, 6)) axes[0].imshow(slide.thumbnail) axes[0].set_title("Original") axes[0].axis('off') filtered = filter_pipeline(slide.thumbnail) axes[1].imshow(filtered, cmap='gray') axes[1].set_title("After Filtering") axes[1].axis('off') plt.tight_layout() plt.show() ``` ### Multi-Step Filter Visualization ```python from histolab.filters.image_filters import RgbToGrayscale, OtsuThreshold from histolab.filters.morphological_filters import BinaryDilation, RemoveSmallObjects # Individual filter steps steps = [ ("Original", None), ("Grayscale", RgbToGrayscale()), ("Otsu Threshold", Compose([RgbToGrayscale(), OtsuThreshold()])), ("After Dilation", Compose([RgbToGrayscale(), OtsuThreshold(), BinaryDilation(disk_size=5)])), ("Remove Small Objects", Compose([RgbToGrayscale(), OtsuThreshold(), BinaryDilation(disk_size=5), RemoveSmallObjects(area_threshold=500)])) ] fig, axes = plt.subplots(1, len(steps), figsize=(20, 4)) for idx, (title, filter_fn) in enumerate(steps): if filter_fn is None: axes[idx].imshow(slide.thumbnail) else: result = filter_fn(slide.thumbnail) axes[idx].imshow(result, cmap='gray') axes[idx].set_title(title, fontsize=10) axes[idx].axis('off') plt.tight_layout() plt.show() ``` ## Exporting Visualizations ### High-Resolution Exports ```python # Export high-resolution figure fig, ax = plt.subplots(figsize=(20, 20)) ax.imshow(slide.thumbnail) ax.axis('off') plt.savefig("slide_high_res.png", dpi=300, bbox_inches='tight', pad_inches=0) plt.close() ``` ### PDF Reports ```python from matplotlib.backends.backend_pdf import PdfPages # Create multi-page PDF report with PdfPages('slide_report.pdf') as pdf: # Page 1: Slide thumbnail fig1, ax1 = plt.subplots(figsize=(10, 10)) ax1.imshow(slide.thumbnail) ax1.set_title(f"Slide: {slide.name}") ax1.axis('off') pdf.savefig(fig1, bbox_inches='tight') plt.close() # Page 2: Tissue mask fig2, ax2 = plt.subplots(figsize=(10, 10)) mask = TissueMask()(slide) ax2.imshow(mask, cmap='gray') ax2.set_title("Tissue Mask") ax2.axis('off') pdf.savefig(fig2, bbox_inches='tight') plt.close() # Page 3: Tile locations fig3, ax3 = plt.subplots(figsize=(10, 10)) tiler = RandomTiler(tile_size=(512, 512), n_tiles=30) tiler.locate_tiles(slide) pdf.savefig(fig3, bbox_inches='tight') plt.close() ``` ## Interactive Visualization (Jupyter) ### IPython Widgets for Exploration ```python from ipywidgets import interact, IntSlider import matplotlib.pyplot as plt from histolab.filters.morphological_filters import BinaryDilation @interact(disk_size=IntSlider(min=1, max=20, value=5)) def explore_dilation(disk_size): """Interactive dilation exploration.""" filter_pipeline = Compose([ RgbToGrayscale(), OtsuThreshold(), BinaryDilation(disk_size=disk_size) ]) result = filter_pipeline(slide.thumbnail) plt.figure(figsize=(10, 10)) plt.imshow(result, cmap='gray') plt.title(f"Binary Dilation (disk_size={disk_size})") plt.axis('off') plt.show() ``` ## Best Practices 1. **Always preview before processing**: Use thumbnails and `locate_tiles()` to validate settings 2. **Use side-by-side comparisons**: Show before/after for filter effects 3. **Label clearly**: Include titles, axes labels, and legends 4. **Export high-resolution**: Use 300 DPI for publication-quality figures 5. **Save intermediate visualizations**: Document processing steps 6. **Use colormaps appropriately**: 'gray' for binary masks, 'viridis' for heatmaps 7. **Create reusable visualization functions**: Standardize reporting across projects