Skip to content

How to Visualize Parameter Maps

Create visualizations of perfusion parameter maps.

Basic Parameter Map

Display a single parameter map:

Display a single Ktrans map

import matplotlib.pyplot as plt
import osipy

# Load or compute parameter map
ktrans = result.parameter_maps['Ktrans'].values

# Select slice
slice_idx = ktrans.shape[2] // 2

# Display
fig, ax = plt.subplots(figsize=(6, 6))
im = ax.imshow(ktrans[:, :, slice_idx], cmap='hot', vmin=0, vmax=0.5)
ax.set_title('Ktrans (min⁻¹)')
ax.axis('off')
plt.colorbar(im, ax=ax, shrink=0.8)
plt.savefig('ktrans_map.png', dpi=300, bbox_inches='tight')
plt.show()

Multi-Parameter Display

Show multiple parameters together:

Display multiple parameter maps in a grid

import matplotlib.pyplot as plt

# Parameters to display
params = {
    'Ktrans': (result.parameter_maps['Ktrans'].values, 'hot', 0, 0.5, 'min⁻¹'),
    've': (result.parameter_maps['ve'].values, 'viridis', 0, 1, 'fraction'),
    'vp': (result.parameter_maps['vp'].values, 'plasma', 0, 0.2, 'fraction'),
    'R²': (result.r_squared_map, 'gray', 0, 1, ''),
}

fig, axes = plt.subplots(2, 2, figsize=(10, 10))
slice_idx = result.parameter_maps['Ktrans'].values.shape[2] // 2

for ax, (name, (data, cmap, vmin, vmax, units)) in zip(axes.flat, params.items()):
    im = ax.imshow(data[:, :, slice_idx], cmap=cmap, vmin=vmin, vmax=vmax)
    ax.set_title(f'{name} ({units})' if units else name)
    ax.axis('off')
    plt.colorbar(im, ax=ax, shrink=0.8)

plt.tight_layout()
plt.savefig('parameter_maps.png', dpi=300, bbox_inches='tight')
plt.show()

Slice Montage

Display all slices in a grid:

Create a slice montage

import numpy as np
import matplotlib.pyplot as plt

def plot_montage(data, title, cmap='hot', vmin=None, vmax=None, n_cols=6):
    """Create a slice montage."""
    n_slices = data.shape[2]
    n_rows = int(np.ceil(n_slices / n_cols))

    fig, axes = plt.subplots(n_rows, n_cols, figsize=(2*n_cols, 2*n_rows))
    axes = axes.flatten()

    for i in range(n_slices):
        axes[i].imshow(data[:, :, i], cmap=cmap, vmin=vmin, vmax=vmax)
        axes[i].axis('off')
        axes[i].set_title(f'z={i}', fontsize=8)

    # Hide unused axes
    for i in range(n_slices, len(axes)):
        axes[i].axis('off')

    fig.suptitle(title, fontsize=14, y=1.02)
    plt.tight_layout()
    return fig

# Create montage
fig = plot_montage(result.parameter_maps['Ktrans'].values, 'Ktrans (min⁻¹)', vmin=0, vmax=0.5)
plt.savefig('ktrans_montage.png', dpi=200, bbox_inches='tight')

Overlay on Anatomy

Overlay parameter map on anatomical image:

Overlay parameter map on anatomy

import matplotlib.pyplot as plt
import numpy as np

def overlay_map(anatomy, parameter, mask=None, slice_idx=None,
                cmap='hot', alpha=0.7, vmin=None, vmax=None):
    """Overlay parameter map on anatomy."""
    if slice_idx is None:
        slice_idx = anatomy.shape[2] // 2

    fig, ax = plt.subplots(figsize=(8, 8))

    # Show anatomy
    ax.imshow(anatomy[:, :, slice_idx], cmap='gray')

    # Overlay parameter (masked)
    param_slice = parameter[:, :, slice_idx].copy()
    if mask is not None:
        param_slice = np.ma.masked_where(~mask[:, :, slice_idx], param_slice)

    im = ax.imshow(param_slice, cmap=cmap, alpha=alpha, vmin=vmin, vmax=vmax)
    plt.colorbar(im, ax=ax, shrink=0.8)
    ax.axis('off')

    return fig

# Use
anatomy = dce_data.mean(axis=-1)  # Mean DCE signal as anatomy
fig = overlay_map(anatomy, result.parameter_maps['Ktrans'].values, mask=result.quality_mask > 0,
                  vmin=0, vmax=0.5)
plt.savefig('ktrans_overlay.png', dpi=300, bbox_inches='tight')

Histogram Analysis

Plot parameter distributions:

Plot parameter histograms

import matplotlib.pyplot as plt
import numpy as np

def plot_histograms(result, mask):
    """Plot histograms of all parameters."""
    params = ['Ktrans', 've', 'vp']

    fig, axes = plt.subplots(1, 3, figsize=(12, 4))

    for ax, param in zip(axes, params):
        values = result.parameter_maps[param].values[mask > 0]

        ax.hist(values, bins=50, color='steelblue', edgecolor='white', alpha=0.8)
        ax.axvline(values.mean(), color='red', linestyle='--',
                   label=f'Mean: {values.mean():.3f}')
        ax.axvline(np.median(values), color='green', linestyle=':',
                   label=f'Median: {np.median(values):.3f}')

        ax.set_xlabel(param)
        ax.set_ylabel('Voxel Count')
        ax.legend(fontsize=8)

    plt.tight_layout()
    return fig

fig = plot_histograms(result, result.quality_mask)
plt.savefig('parameter_histograms.png', dpi=150)

Time Course Plots

Visualize concentration time courses:

Plot AIF and sample tissue time courses

import matplotlib.pyplot as plt
import numpy as np

def plot_time_courses(concentration, time, aif, mask, n_samples=5):
    """Plot AIF and sample tissue curves."""
    fig, ax = plt.subplots(figsize=(10, 5))

    # Plot AIF
    ax.plot(time, aif.concentration, 'r-', linewidth=2, label='AIF')

    # Sample tissue curves
    valid_indices = np.where(mask > 0)
    n_valid = len(valid_indices[0])
    sample_idx = np.linspace(0, n_valid-1, n_samples, dtype=int)

    for i, idx in enumerate(sample_idx):
        x, y, z = valid_indices[0][idx], valid_indices[1][idx], valid_indices[2][idx]
        ax.plot(time, concentration[x, y, z, :], alpha=0.6, label=f'Tissue {i+1}')

    ax.set_xlabel('Time (s)')
    ax.set_ylabel('Concentration (mM)')
    ax.set_title('Concentration Time Curves')
    ax.legend(loc='upper right')
    ax.grid(True, alpha=0.3)

    return fig

fig = plot_time_courses(concentration, time, aif, result.quality_mask)
plt.savefig('time_courses.png', dpi=150)

Quality Mask Visualization

Show which voxels passed quality control:

Visualize quality mask and R-squared distribution

import matplotlib.pyplot as plt

def plot_quality_mask(quality_mask, r_squared, slice_idx=None):
    """Visualize quality mask and R² distribution."""
    if slice_idx is None:
        slice_idx = quality_mask.shape[2] // 2

    fig, axes = plt.subplots(1, 3, figsize=(12, 4))

    # Quality mask
    axes[0].imshow(quality_mask[:, :, slice_idx], cmap='binary')
    axes[0].set_title('Quality Mask')
    axes[0].axis('off')

    # R² map
    im = axes[1].imshow(r_squared[:, :, slice_idx], cmap='viridis', vmin=0, vmax=1)
    axes[1].set_title('R² Map')
    axes[1].axis('off')
    plt.colorbar(im, ax=axes[1], shrink=0.8)

    # R² histogram
    valid_r2 = r_squared[quality_mask > 0]
    axes[2].hist(valid_r2, bins=50, color='steelblue', edgecolor='white')
    axes[2].axvline(0.5, color='red', linestyle='--', label='Threshold (0.5)')
    axes[2].set_xlabel('R²')
    axes[2].set_ylabel('Count')
    axes[2].set_title(f'R² Distribution\n(n={len(valid_r2)} voxels)')
    axes[2].legend()

    plt.tight_layout()
    return fig

fig = plot_quality_mask(result.quality_mask, result.r_squared_map)
plt.savefig('quality_metrics.png', dpi=150)

Publication-Ready Figure

Create a complete figure for publication:

Create publication-ready multi-panel figure

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec

def create_publication_figure(result, concentration, time, aif, anatomy):
    """Create publication-ready figure."""
    fig = plt.figure(figsize=(12, 10))
    gs = gridspec.GridSpec(3, 3, figure=fig, hspace=0.3, wspace=0.3)

    slice_idx = result.parameter_maps['Ktrans'].values.shape[2] // 2
    mask = result.quality_mask > 0

    # Row 1: Parameter maps
    ax1 = fig.add_subplot(gs[0, 0])
    im1 = ax1.imshow(result.parameter_maps['Ktrans'].values[:, :, slice_idx], cmap='hot', vmin=0, vmax=0.5)
    ax1.set_title('Ktrans (min⁻¹)')
    ax1.axis('off')
    plt.colorbar(im1, ax=ax1, shrink=0.6)

    ax2 = fig.add_subplot(gs[0, 1])
    im2 = ax2.imshow(result.parameter_maps['ve'].values[:, :, slice_idx], cmap='viridis', vmin=0, vmax=1)
    ax2.set_title('ve')
    ax2.axis('off')
    plt.colorbar(im2, ax=ax2, shrink=0.6)

    ax3 = fig.add_subplot(gs[0, 2])
    im3 = ax3.imshow(result.parameter_maps['vp'].values[:, :, slice_idx], cmap='plasma', vmin=0, vmax=0.2)
    ax3.set_title('vp')
    ax3.axis('off')
    plt.colorbar(im3, ax=ax3, shrink=0.6)

    # Row 2: Overlay and quality
    ax4 = fig.add_subplot(gs[1, 0])
    ax4.imshow(anatomy[:, :, slice_idx], cmap='gray')
    ax4.set_title('Anatomy')
    ax4.axis('off')

    ax5 = fig.add_subplot(gs[1, 1])
    ax5.imshow(anatomy[:, :, slice_idx], cmap='gray')
    ktrans_masked = np.ma.masked_where(~mask[:, :, slice_idx], result.parameter_maps['Ktrans'].values[:, :, slice_idx])
    im5 = ax5.imshow(ktrans_masked, cmap='hot', alpha=0.7, vmin=0, vmax=0.5)
    ax5.set_title('Ktrans Overlay')
    ax5.axis('off')
    plt.colorbar(im5, ax=ax5, shrink=0.6)

    ax6 = fig.add_subplot(gs[1, 2])
    im6 = ax6.imshow(result.r_squared_map[:, :, slice_idx], cmap='gray', vmin=0, vmax=1)
    ax6.set_title('R²')
    ax6.axis('off')
    plt.colorbar(im6, ax=ax6, shrink=0.6)

    # Row 3: Time courses and histograms
    ax7 = fig.add_subplot(gs[2, :2])
    ax7.plot(time, aif.concentration, 'r-', linewidth=2, label='AIF')
    sample_idx = np.where(mask)
    for i in range(3):
        idx = i * len(sample_idx[0]) // 4
        x, y, z = sample_idx[0][idx], sample_idx[1][idx], sample_idx[2][idx]
        ax7.plot(time, concentration[x, y, z, :], alpha=0.6)
    ax7.set_xlabel('Time (s)')
    ax7.set_ylabel('Concentration (mM)')
    ax7.set_title('Time Curves')
    ax7.grid(True, alpha=0.3)

    ax8 = fig.add_subplot(gs[2, 2])
    ax8.hist(result.parameter_maps['Ktrans'].values[mask], bins=30, color='steelblue', alpha=0.8)
    ax8.set_xlabel('Ktrans (min⁻¹)')
    ax8.set_ylabel('Count')
    ax8.set_title('Ktrans Distribution')

    plt.suptitle('DCE-MRI Analysis Results', fontsize=14, y=1.02)
    return fig

fig = create_publication_figure(result, concentration, time, aif, dce_data.mean(axis=-1))
plt.savefig('publication_figure.png', dpi=300, bbox_inches='tight')

Colormaps for Perfusion

Recommended colormaps:

Parameter Colormap Range
Ktrans hot 0-0.5 min⁻¹
ve viridis 0-1
vp plasma 0-0.2
CBV hot 0-10 ml/100g
CBF jet 0-100 ml/100g/min
MTT viridis 0-10 s
gray 0-1

See Also