Skip to content

Interpretability artifacts

The shared interpretability module defines the validated adapter-to-renderer contract. Method-specific score extraction remains inside each adapter.

interpretability

Typed patch-evidence artifacts and coordinate-aware heatmap rendering.

The benchmark deliberately uses the term evidence rather than calling every map attention. Adapters identify the native quantity they expose (pooling attention, prompt relevance, patch probability, and so on), while this module only validates, serializes, and renders those quantities.

PatchEvidence dataclass

PatchEvidence(
    scores: Tensor,
    score_type: str,
    description: str,
    scale: str = "patch",
    feature_path_key: str = "feature_path_column",
    class_index: int | None = None,
    native: bool = True,
    coordinates: Tensor | None = None,
    index_order: str = "feature_rows",
)

One patch-aligned explanation emitted by a method adapter.

Parameters:

Name Type Description Default
scores Tensor

One finite scalar per patch.

required
score_type str

Precise semantic name, such as pooling_attention.

required
description str

Human-readable definition of the score.

required
scale str

Scale label used in filenames and figures.

'patch'
feature_path_key str

Config key whose value names the manifest feature column for this scale.

'feature_path_column'
class_index int | None

Class queried by a class-specific score, if applicable.

None
native bool

Whether the score is computed by the released architecture.

True
coordinates Tensor | None

Optional level-0 [N, 2] coordinates already aligned to scores. Most adapters leave this unset and the CLI reads the coordinates from the provenance-bound feature HDF5.

None
index_order str

Patch ordering convention. feature_rows is the normal feature-file order; hipss_regions is HIPSS's sorted region traversal and is resolved by the heatmap CLI.

'feature_rows'

validated

validated() -> 'PatchEvidence'

Return a detached CPU copy after enforcing the artifact contract.

Source code in common/interpretability.py
def validated(self) -> "PatchEvidence":
    """Return a detached CPU copy after enforcing the artifact contract."""
    scores = self.scores.detach().float().cpu().reshape(-1)
    if scores.numel() == 0 or not torch.isfinite(scores).all():
        raise ValueError("patch evidence scores must be non-empty and finite")
    if not self.score_type.strip() or not self.description.strip():
        raise ValueError("patch evidence requires score_type and description")
    if self.class_index is not None and self.class_index < 0:
        raise ValueError("patch evidence class_index must be non-negative")
    if self.index_order not in {"feature_rows", "hipss_regions"}:
        raise ValueError(f"unknown patch evidence ordering {self.index_order!r}")
    coordinates = self.coordinates
    if coordinates is not None:
        coordinates = coordinates.detach().cpu()
        if coordinates.ndim == 3 and coordinates.shape[0] == 1:
            coordinates = coordinates.squeeze(0)
        if coordinates.shape != (scores.numel(), 2):
            raise ValueError(
                "patch evidence coordinates must have shape "
                f"[{scores.numel()}, 2], got {tuple(coordinates.shape)}")
        if not torch.isfinite(coordinates.float()).all():
            raise ValueError("patch evidence coordinates must be finite")
        coordinates = coordinates.long()
    return PatchEvidence(
        scores=scores,
        score_type=self.score_type.strip(),
        description=self.description.strip(),
        scale=self.scale.strip() or "patch",
        feature_path_key=self.feature_path_key,
        class_index=self.class_index,
        native=bool(self.native),
        coordinates=coordinates,
        index_order=self.index_order,
    )

InterpretabilityResult dataclass

InterpretabilityResult(
    logits: Tensor,
    evidence: Sequence[PatchEvidence],
    notes: Sequence[str] = tuple(),
)

Prediction plus one or more faithful patch-evidence maps.

validated

validated(n_classes: int) -> 'InterpretabilityResult'

Validate class scores and every patch-evidence payload.

Source code in common/interpretability.py
def validated(self, n_classes: int) -> "InterpretabilityResult":
    """Validate class scores and every patch-evidence payload."""
    logits = self.logits.detach().float().cpu()
    if logits.ndim == 1:
        logits = logits.unsqueeze(0)
    if logits.shape != (1, int(n_classes)):
        raise ValueError(
            f"interpretability logits must be [1,{n_classes}], got "
            f"{tuple(logits.shape)}")
    if not torch.isfinite(logits).all():
        raise ValueError("interpretability logits contain NaN or infinity")
    evidence = tuple(item.validated() for item in self.evidence)
    if not evidence:
        raise ValueError("interpretability result contains no patch evidence")
    for item in evidence:
        if item.class_index is not None and item.class_index >= n_classes:
            raise ValueError(
                f"evidence class index {item.class_index} is outside "
                f"[0, {n_classes})")
    return InterpretabilityResult(
        logits=logits, evidence=evidence,
        notes=tuple(str(note) for note in self.notes),
    )

CoordinateBag dataclass

CoordinateBag(
    coordinates: ndarray,
    patch_size_level0: int,
    level0_width: int,
    level0_height: int,
)

Level-0 coordinates and geometry read from one feature HDF5.

load_coordinate_bag

load_coordinate_bag(path: str | Path) -> CoordinateBag

Read aligned level-0 coordinates without loading feature vectors.

Source code in common/interpretability.py
def load_coordinate_bag(path: str | Path) -> CoordinateBag:
    """Read aligned level-0 coordinates without loading feature vectors."""
    source = Path(path).expanduser()
    if source.suffix.lower() not in {".h5", ".hdf5"}:
        raise ValueError(
            f"heatmaps require a coordinate-bearing HDF5 feature file: {source}")
    with h5py.File(source, "r") as handle:
        if "coords" not in handle:
            raise ValueError(f"{source}: feature file has no 'coords' dataset")
        dataset = handle["coords"]
        coordinates = np.asarray(dataset[:], dtype=np.int64)
        attrs = dict(dataset.attrs)
    if coordinates.ndim != 2 or coordinates.shape[1:] != (2,):
        raise ValueError(f"{source}: coords must have shape [N,2]")
    if len(coordinates) == 0 or not np.isfinite(coordinates).all():
        raise ValueError(f"{source}: coords must be non-empty and finite")

    def positive_int(key: str, default: int) -> int:
        raw = attrs.get(key, default)
        try:
            value = int(round(float(raw)))
        except (TypeError, ValueError) as error:
            raise ValueError(f"{source}: invalid coords attribute {key}={raw!r}") from error
        if value <= 0:
            raise ValueError(f"{source}: coords attribute {key} must be positive")
        return value

    unique_x = np.diff(np.unique(coordinates[:, 0]))
    unique_y = np.diff(np.unique(coordinates[:, 1]))
    positive_steps = np.concatenate([
        unique_x[unique_x > 0], unique_y[unique_y > 0],
    ])
    inferred_span = int(positive_steps.min()) if len(positive_steps) else 1
    patch_size = positive_int("patch_size_level0", inferred_span)
    width = positive_int(
        "level0_width", int(coordinates[:, 0].max()) + patch_size)
    height = positive_int(
        "level0_height", int(coordinates[:, 1].max()) + patch_size)
    return CoordinateBag(coordinates, patch_size, width, height)

align_coordinates

align_coordinates(
    bag: CoordinateBag,
    score_count: int,
    *,
    max_patches: int | None = None,
    index_order: str = "feature_rows",
    region_span_level0: int | None = None,
) -> ndarray

Align stored coordinates with deterministic evaluation-time sampling.

Source code in common/interpretability.py
def align_coordinates(
    bag: CoordinateBag, score_count: int, *, max_patches: int | None = None,
    index_order: str = "feature_rows", region_span_level0: int | None = None,
) -> np.ndarray:
    """Align stored coordinates with deterministic evaluation-time sampling."""
    coordinates = bag.coordinates
    if index_order == "hipss_regions":
        if not region_span_level0 or region_span_level0 <= 0:
            raise ValueError("HIPSS coordinate alignment requires region_span_level0")
        keys = coordinates // int(region_span_level0)
        groups: dict[tuple[int, int], list[int]] = {}
        for index, key in enumerate(map(tuple, keys.tolist())):
            groups.setdefault(key, []).append(index)
        order = [index for key in sorted(groups) for index in groups[key]]
        coordinates = coordinates[np.asarray(order, dtype=np.int64)]
    if len(coordinates) == score_count:
        return coordinates
    if (max_patches is not None and score_count == int(max_patches)
            and len(coordinates) > score_count):
        indices = np.rint(np.linspace(
            0, len(coordinates) - 1, score_count)).astype(np.int64)
        return coordinates[indices]
    raise ValueError(
        f"patch evidence has {score_count} rows but the feature file has "
        f"{len(coordinates)} coordinates")

render_heatmap

render_heatmap(
    scores: ndarray,
    coordinates: ndarray,
    geometry: CoordinateBag,
    output: str | Path,
    *,
    wsi_path: str | Path | None = None,
    alpha: float = 0.55,
    max_size: int = 2048,
    percentile_range: tuple[float, float] = (1.0, 99.0),
) -> Path

Render patch evidence on a thumbnail or a coordinate-only canvas.

Source code in common/interpretability.py
def render_heatmap(
    scores: np.ndarray, coordinates: np.ndarray, geometry: CoordinateBag,
    output: str | Path, *, wsi_path: str | Path | None = None,
    alpha: float = 0.55, max_size: int = 2048,
    percentile_range: tuple[float, float] = (1.0, 99.0),
) -> Path:
    """Render patch evidence on a thumbnail or a coordinate-only canvas."""
    if not 0.0 <= alpha <= 1.0:
        raise ValueError("heatmap alpha must be in [0, 1]")
    if max_size <= 0:
        raise ValueError("heatmap max_size must be positive")
    scores = np.asarray(scores, dtype=np.float64).reshape(-1)
    coordinates = np.asarray(coordinates, dtype=np.int64)
    if coordinates.shape != (len(scores), 2):
        raise ValueError("heatmap scores and coordinates do not align")
    lower, upper = percentile_range
    if not 0.0 <= lower < upper <= 100.0:
        raise ValueError("heatmap percentile range must satisfy 0 <= low < high <= 100")

    scale = min(
        float(max_size) / geometry.level0_width,
        float(max_size) / geometry.level0_height,
        1.0,
    )
    size = (
        max(1, int(round(geometry.level0_width * scale))),
        max(1, int(round(geometry.level0_height * scale))),
    )
    if wsi_path is None:
        base = Image.new("RGB", size, (242, 242, 242))
    else:
        try:
            import openslide
        except ImportError as error:
            raise RuntimeError(
                "WSI overlays require openslide-python; omit --wsi for a "
                "coordinate-only heatmap") from error
        slide = openslide.OpenSlide(str(Path(wsi_path).expanduser()))
        try:
            if tuple(map(int, slide.dimensions)) != (
                    geometry.level0_width, geometry.level0_height):
                raise ValueError(
                    "WSI level-0 dimensions do not match the feature "
                    f"coordinate frame: WSI={tuple(slide.dimensions)}, "
                    "features="
                    f"({geometry.level0_width}, {geometry.level0_height})")
            base = slide.get_thumbnail(size).convert("RGB")
        finally:
            slide.close()
        if base.size != size:
            base = base.resize(size, Image.Resampling.LANCZOS)

    # Use matplotlib's perceptually ordered magma map, but import it only in
    # this explicit rendering path so contract validation stays lightweight.
    from matplotlib import colormaps
    normalized = _normalise_scores(scores, lower, upper)
    colors = (colormaps["magma"](normalized)[:, :3] * 255).astype(np.uint8)
    overlay = Image.new("RGBA", size, (0, 0, 0, 0))
    draw = ImageDraw.Draw(overlay)
    span = max(1, int(round(geometry.patch_size_level0 * scale)))
    opacity = int(round(255 * alpha))
    for (x, y), color in zip(coordinates, colors):
        left, top = int(round(x * scale)), int(round(y * scale))
        draw.rectangle(
            (left, top, left + span - 1, top + span - 1),
            fill=(int(color[0]), int(color[1]), int(color[2]), opacity),
        )
    rendered = Image.alpha_composite(base.convert("RGBA"), overlay).convert("RGB")
    destination = Path(output).expanduser()
    destination.parent.mkdir(parents=True, exist_ok=True)
    rendered.save(destination)
    return destination

write_evidence_table

write_evidence_table(
    evidence: PatchEvidence,
    coordinates: ndarray,
    output: str | Path,
) -> Path

Write lossless patch scores and coordinates as a CSV artifact.

Source code in common/interpretability.py
def write_evidence_table(
    evidence: PatchEvidence, coordinates: np.ndarray, output: str | Path,
) -> Path:
    """Write lossless patch scores and coordinates as a CSV artifact."""
    evidence = evidence.validated()
    coordinates = np.asarray(coordinates, dtype=np.int64)
    if coordinates.shape != (evidence.scores.numel(), 2):
        raise ValueError("evidence table coordinates do not align with scores")
    frame = pd.DataFrame({
        "patch_index": np.arange(evidence.scores.numel(), dtype=np.int64),
        "x_level0": coordinates[:, 0],
        "y_level0": coordinates[:, 1],
        "score": evidence.scores.numpy(),
        "score_type": evidence.score_type,
        "scale": evidence.scale,
        "class_index": evidence.class_index,
        "native": evidence.native,
    })
    destination = Path(output).expanduser()
    destination.parent.mkdir(parents=True, exist_ok=True)
    frame.to_csv(destination, index=False)
    return destination

write_manifest

write_manifest(
    payload: Mapping[str, Any], output: str | Path
) -> Path

Write the interpretation sidecar with stable formatting.

Source code in common/interpretability.py
def write_manifest(payload: Mapping[str, Any], output: str | Path) -> Path:
    """Write the interpretation sidecar with stable formatting."""
    destination = Path(output).expanduser()
    destination.parent.mkdir(parents=True, exist_ok=True)
    destination.write_text(
        json.dumps(dict(payload), indent=2, sort_keys=True) + "\n",
        encoding="utf-8",
    )
    return destination