Tutorial: Visualizing Hi-C contact matrices with gunz_cm.visualizations#

This tutorial walks through the three display helpers in gunz_cm.visualizations.display:

  • display_matrix - full square heatmap with optional row/column value bars, log or linear normalization, and centered colormap

  • display_triangle - upper-triangular half-matrix view (useful when the matrix is symmetric)

  • display_compartments - 2x2 A/B quadrant grid that aggregates a binary compartment vector

All three render into the current matplotlib figure. We use matplotlib.use('Agg') for a non-interactive backend and call plt.gcf().savefig(...) after each call to write the plot to disk instead of opening a window.

## Initialization

%load_ext autoreload
%autoreload 2

import sys
from git.repo import Repo

repo = Repo('.', search_parent_directories=True)
ROOT = repo.working_tree_dir

assert isinstance(ROOT, str)
sys.path.append(ROOT)
print(f'Repo root: {ROOT}')

Repo root: /home/adhisant/workspace/gunz-cm
## Imports

from pathlib import Path

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from IPython.display import display as ipydisplay
from IPython.display import Image as ipyimage
import numpy as np
from numpy.typing import NDArray

np.bool8 = bool  # shim: numpy 2.x removed np.bool8 referenced in source annotations

from gunz_cm.visualizations.display import (
    display_matrix,
    display_triangle,
    display_compartments,
)

## Create synthetic data: 10x10 matrix with distance decay + log-normal visibility bias,
## plus a 4-block A/B compartment vector that drives display_compartments below.

rng = np.random.default_rng(42)
n = 10

linear_coords = np.arange(n, dtype=float)
genomic_dist = np.abs(linear_coords[:, None] - linear_coords[None, :])

bias = rng.lognormal(mean=0.0, sigma=0.6, size=n)
decay = np.exp(-genomic_dist / 3.0)
cm_dense = bias[:, None] * bias[None, :] * decay * 100.0
np.fill_diagonal(cm_dense, 0.0)
matrix: NDArray = cm_dense

compartments = np.array([0, 0, 0, 1, 1, 0, 0, 1, 1, 1], dtype=bool)
row_values = bias

print('Synthetic contact matrix:')
print(f'  shape     = {matrix.shape}')
print(f'  bias      =', np.round(bias, 2))
print(f'  sum       =', round(float(matrix.sum()), 2))
print('Compartments (A=False / B=True):', compartments.astype(int).tolist())

Synthetic contact matrix:
  shape     = (10, 10)
  bias      = [1.2  0.54 1.57 1.76 0.31 0.46 1.08 0.83 0.99 0.6 ]
  sum       = 2811.42
Compartments (A=False / B=True): [0, 0, 0, 1, 1, 0, 0, 1, 1, 1]
## Step 1: display_matrix with log=True and row_values for a per-bin value bar.

out1 = str(Path(ROOT) / 'notebooks' / 'tutorial_visualize_matrix.png')

try:
    display_matrix(
        matrix,
        log=True,
        title='Step 1: display_matrix (log, row_values=bias)',
        row_values=row_values,
        row_values_title='bias',
        row_values_log=True,
    )
    fig = plt.gcf()
    fig.savefig(out1, dpi=100, bbox_inches='tight')
    plt.close('all')
    print(f'Saved: {out1}')
except Exception as exc:
    plt.close('all')
    print(f'[skip] display_matrix failed: {exc!r}')
ipydisplay(ipyimage(filename=out1))

Saved: /home/adhisant/workspace/gunz-cm/notebooks/tutorial_visualize_matrix.png

png

## Step 2: display_triangle renders only the upper triangle of a symmetric matrix
## with an optional values bar on the hypotenuse.

out2 = str(Path(ROOT) / 'notebooks' / 'tutorial_visualize_triangle.png')

try:
    display_triangle(
        matrix,
        log=True,
        title='Step 2: display_triangle (log, upper triangle)',
        values=row_values,
        values_log=True,
        values_title='bias',
    )
    fig = plt.gcf()
    fig.savefig(out2, dpi=100, bbox_inches='tight')
    plt.close('all')
    print(f'Saved: {out2}')
except Exception as exc:
    plt.close('all')
    print(f'[skip] display_triangle failed: {exc!r}')
ipydisplay(ipyimage(filename=out2))

Saved: /home/adhisant/workspace/gunz-cm/notebooks/tutorial_visualize_triangle.png

png

## Step 3: display_compartments partitions the matrix into the four A/A, A/B, B/A, B/B
## quadrants from a boolean compartment vector (A=False, B=True) and plots them in a 2x2 grid.

out3 = str(Path(ROOT) / 'notebooks' / 'tutorial_visualize_compartments.png')

try:
    display_compartments(
        matrix,
        compartments,
        log=True,
        title='Step 3: display_compartments (A=False, B=True)',
    )
    fig = plt.gcf()
    fig.savefig(out3, dpi=100, bbox_inches='tight')
    plt.close('all')
    print(f'Saved: {out3}')
except Exception as exc:
    plt.close('all')
    print(f'[skip] display_compartments failed: {exc!r}')
ipydisplay(ipyimage(filename=out3))

Saved: /home/adhisant/workspace/gunz-cm/notebooks/tutorial_visualize_compartments.png

png

## Step 4: Common parameters - linear (log=False), divergent colormap centered at 1,
## and hlines/vlines marking A/B compartment boundaries.

boundaries = np.where(np.diff(compartments.astype(int)))[0] + 1
out4 = str(Path(ROOT) / 'notebooks' / 'tutorial_visualize_centered.png')

try:
    display_matrix(
        matrix,
        log=False,
        center=1,
        title='Step 4: display_matrix (linear, center=1, hlines at A/B boundaries)',
        hlines=boundaries,
        vlines=boundaries,
    )
    fig = plt.gcf()
    fig.savefig(out4, dpi=100, bbox_inches='tight')
    plt.close('all')
    print(f'Saved: {out4}')
    print(f'A/B boundaries drawn at indices: {boundaries.tolist()}')
except Exception as exc:
    plt.close('all')
    print(f'[skip] display_matrix (centered) failed: {exc!r}')
ipydisplay(ipyimage(filename=out4))

Saved: /home/adhisant/workspace/gunz-cm/notebooks/tutorial_visualize_centered.png
A/B boundaries drawn at indices: [3, 5, 7]

png

Summary#

In this tutorial you learned how to:

  1. Build a synthetic Hi-C matrix with distance decay and visibility bias, plus a synthetic A/B compartment vector.

  2. Render a full heatmap with display_matrix(..., log=True, row_values=...) to attach a per-bin value bar.

  3. Render the upper triangle with display_triangle(..., log=True, values=...) for symmetric matrices.

  4. Render the 2x2 A/B quadrant grid with display_compartments(matrix, compartments, log=True).

  5. Switch to a divergent colormap with display_matrix(..., log=False, center=1, hlines=..., vlines=...) and overlay reference lines at compartment boundaries.

All three helpers return a matplotlib figure that the caller saves to disk via plt.gcf().savefig(...) (or shows interactively with %matplotlib inline / widget). The same pattern works on real data: load a contact matrix with gunz_cm.loaders.load_cm_data(...) and pass coo_matrix(...).toarray() as the matrix argument.

Next steps:

  • tutorial_load_cooler.ipynb - loading a real .cool/.mcool matrix

  • tutorial_compartments.ipynb - computing A/B compartments from a real matrix

  • tutorial_filter_normalize.ipynb - filtering and balancing before display

## Cleanup - confirm the four PNGs were written next to the notebook.

import os

saved = [
    str(Path(ROOT) / 'notebooks' / 'tutorial_visualize_matrix.png'),
    str(Path(ROOT) / 'notebooks' / 'tutorial_visualize_triangle.png'),
    str(Path(ROOT) / 'notebooks' / 'tutorial_visualize_compartments.png'),
    str(Path(ROOT) / 'notebooks' / 'tutorial_visualize_centered.png'),
]
for p in saved:
    if os.path.exists(p):
        size_kb = os.path.getsize(p) / 1024.0
        print(f'kept:   {p}  ({size_kb:.1f} KB)')
    else:
        print(f'missing: {p}')
plt.close('all')

kept:   /home/adhisant/workspace/gunz-cm/notebooks/tutorial_visualize_matrix.png  (30.4 KB)
kept:   /home/adhisant/workspace/gunz-cm/notebooks/tutorial_visualize_triangle.png  (35.5 KB)
kept:   /home/adhisant/workspace/gunz-cm/notebooks/tutorial_visualize_compartments.png  (27.6 KB)
kept:   /home/adhisant/workspace/gunz-cm/notebooks/tutorial_visualize_centered.png  (25.6 KB)