"""
cyto.postprocessing.plots.network
==================================
Single-frame network overlay visualisations for cell network analysis.
All functions operate on a single NetworkX graph (one frame, one cell type)
and produce image-space overlays. Designed for interactive notebook use.
No GPU dependencies — importable without pyclesperanto.
"""
import numpy as np
import matplotlib.pyplot as plt
import networkx as nx
from skimage import exposure
from skimage.segmentation import find_boundaries
# ---------------------------------------------------------------------------
# Colour utilities
# ---------------------------------------------------------------------------
[docs]
def hex_to_rgb(hex_color):
"""Convert a hex colour string to an (R, G, B) tuple in [0, 1] range.
Parameters
----------
hex_color : str
Hex colour string, e.g. ``"#39FF14"`` or ``"39FF14"``.
Returns
-------
tuple of float
(R, G, B) each in [0, 1].
"""
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i + 2], 16) / 255. for i in (0, 2, 4))
# ---------------------------------------------------------------------------
# Segmentation overlays
# ---------------------------------------------------------------------------
[docs]
def visualize_segmentations(
image_stack1, seg_label1, title1, color_rgb1,
image_stack2, seg_label2, title2, color_rgb2,
tail=1,
):
"""Plot two segmentation results side by side with boundary overlays.
Parameters
----------
image_stack1, image_stack2 : ndarray
2-D grayscale images for the first and second cell type.
seg_label1, seg_label2 : ndarray
Corresponding integer label arrays.
title1, title2 : str
Subplot titles.
color_rgb1, color_rgb2 : array-like of float
RGB colour (values in [0, 1]) used to tint each cell-type image.
tail : float, optional
Percentile clipping for intensity rescaling (default 1).
Returns
-------
matplotlib.figure.Figure
"""
edge_rgb = np.array([1, 1, 0])
fig, axes = plt.subplots(1, 2, figsize=(10, 5))
for ax, img, labels, title, color in [
(axes[0], image_stack1, seg_label1, title1, color_rgb1),
(axes[1], image_stack2, seg_label2, title2, color_rgb2),
]:
edges = find_boundaries(labels, mode='outer')
pl, pu = np.percentile(img.ravel(), (tail, 100 - tail))
img_norm = exposure.rescale_intensity(img, in_range=(pl, pu), out_range=(0, 1))
rgb = np.zeros((*img.shape, 3))
for c in range(3):
rgb[..., c] = img_norm * color[c]
rgb[edges] = edge_rgb
ax.imshow(rgb)
ax.set_title(title)
ax.axis('off')
plt.tight_layout()
return fig
# ---------------------------------------------------------------------------
# Network overlays
# ---------------------------------------------------------------------------
[docs]
def draw_network_with_clustering(G, ax, title, vmin, vmax):
"""Draw a NetworkX graph coloured by per-node clustering coefficient.
Nodes are coloured on the ``plasma`` colormap from *vmin* to *vmax*.
Edges are drawn in grey. A colorbar labelled "Clustering Coefficient"
is added to the axes.
Parameters
----------
G : networkx.Graph
Graph with a ``'pos'`` node attribute giving (x, y) image coordinates.
ax : matplotlib.axes.Axes
Target axes.
title : str
Axes title.
vmin, vmax : float
Colormap limits (use a shared range across subplots for comparability).
"""
pos = nx.get_node_attributes(G, 'pos')
clustering = nx.clustering(G)
node_colors = [clustering[n] for n in G.nodes()]
nodes = nx.draw_networkx_nodes(
G, pos,
node_color=node_colors,
cmap=plt.cm.plasma,
node_size=50,
ax=ax,
vmin=vmin,
vmax=vmax,
)
nx.draw_networkx_edges(G, pos, ax=ax, edge_color='grey', width=1.5, alpha=0.3)
ax.set_title(title)
ax.set_aspect('equal')
cbar = plt.colorbar(nodes, ax=ax, fraction=0.046, pad=0.04)
cbar.set_label('Clustering Coefficient')
nodes.set_clim(vmin, vmax)
[docs]
def plot_network_centrality(G, centrality, title, ax, cmap=None, vmin=None, vmax=None):
"""Draw a NetworkX graph coloured by an arbitrary centrality measure.
Parameters
----------
G : networkx.Graph
Graph with a ``'pos'`` node attribute.
centrality : dict
Mapping of node → centrality value (output of any ``nx.*_centrality``
function).
title : str
Axes title.
ax : matplotlib.axes.Axes
Target axes.
cmap : matplotlib.colors.Colormap, optional
Defaults to ``plt.cm.viridis``.
vmin, vmax : float, optional
Colormap limits.
"""
if cmap is None:
cmap = plt.cm.viridis
pos = nx.get_node_attributes(G, 'pos')
values = np.array(list(centrality.values()))
nodes = nx.draw_networkx_nodes(
G, pos,
node_color=values,
cmap=cmap,
node_size=50,
ax=ax,
vmin=vmin,
vmax=vmax,
)
nx.draw_networkx_edges(G, pos, ax=ax, edge_color='grey', width=1.5, alpha=0.3)
ax.set_title(title)
ax.set_aspect('equal')
cbar = plt.colorbar(nodes, ax=ax, fraction=0.046, pad=0.04)
cbar.set_label('Centrality')
nodes.set_clim(vmin, vmax)