Skip to content

single_instance

sleap_nn.inference.layers.single_instance

SingleInstanceLayer — single-pose-per-frame inference.

Single-instance models predict one pose per frame from a confmap-only head. The layer:

  1. Accepts np.ndarray or torch.Tensor directly (real-time / notebook use cases that don't want to spin up a sio.Video).
  2. Runs the backend (PyTorch / ONNX / TensorRT) — same code path; the ModelBackend protocol abstracts the runtime.
  3. Decodes confmaps to keypoints via :mod:sleap_nn.inference.ops.peaks.
  4. Reverses the coord ladder via :mod:sleap_nn.inference.ops.coord so Outputs.pred_keypoints is in original-image space.

Classes:

Name Description
SingleInstanceLayer

Single-pose-per-frame inference layer.

SingleInstanceLayer

Bases: InferenceLayer

Single-pose-per-frame inference layer.

Parameters:

Name Type Description Default
backend ModelBackend

Runtime backend (e.g. TorchBackend(model=lightning_module)).

required
preprocess_config Optional[PreprocessConfig]

Pre-forward transformation knobs.

None
postprocess_config Optional[PostprocessConfig]

Peak decoding + intermediate-return knobs.

None
output_stride int

Stride between confmap and input pixels (read from the head config at construction).

required
max_stride int

Backbone-network stride; inputs are padded bottom-right to a multiple of this in preprocess. Default 1 (no pad).

1

Methods:

Name Description
__init__

Compose the layer with default empty configs when omitted.

postprocess

Decode confmaps → keypoints, reverse coord ladder, build Outputs.

Source code in sleap_nn/inference/layers/single_instance.py
class SingleInstanceLayer(InferenceLayer):
    """Single-pose-per-frame inference layer.

    Args:
        backend: Runtime backend (e.g. ``TorchBackend(model=lightning_module)``).
        preprocess_config: Pre-forward transformation knobs.
        postprocess_config: Peak decoding + intermediate-return knobs.
        output_stride: Stride between confmap and input pixels (read from
            the head config at construction).
        max_stride: Backbone-network stride; inputs are padded bottom-right
            to a multiple of this in ``preprocess``. Default ``1`` (no pad).
    """

    _HEAD_OUTPUT_KEY: str = "SingleInstanceConfmapsHead"

    def __init__(
        self,
        backend: ModelBackend,
        output_stride: int,
        max_stride: int = 1,
        preprocess_config: Optional[PreprocessConfig] = None,
        postprocess_config: Optional[PostprocessConfig] = None,
    ) -> None:
        """Compose the layer with default empty configs when omitted."""
        super().__init__(
            backend=backend,
            preprocess_config=preprocess_config or PreprocessConfig(),
            postprocess_config=postprocess_config or PostprocessConfig(),
            output_stride=output_stride,
            max_stride=max_stride,
        )

    # ──────────────────────────────────────────────────────────────────
    # Postprocess
    # ──────────────────────────────────────────────────────────────────

    def postprocess(self, raw_out: dict, info: PreprocInfo) -> Outputs:
        """Decode confmaps → keypoints, reverse coord ladder, build ``Outputs``.

        This layer always runs the torch decode path: it is only ever built
        with a ``TorchBackend`` (``does_baked_postproc=False``). The exported
        ONNX/TRT path uses the separate :class:`ExportedSingleInstanceLayer`
        adapter, which returns already-final peaks without the coord ladder —
        so this method must NOT special-case a baked backend (doing so would
        double-apply the ladder; #584).
        """
        confmaps = self._extract_confmaps(raw_out)
        peaks, vals = find_global_peaks(
            confmaps.detach(),
            threshold=self.postprocess_config.peak_threshold,
            refinement=self.postprocess_config.effective_refinement,
            integral_patch_size=self.postprocess_config.integral_patch_size,
        )

        # Coord ladder: confmap pixels → input pixels → original-image pixels.
        peaks = undo_stride(peaks, info.output_stride)
        peaks = undo_input_scale(peaks, info.input_scale)
        peaks = undo_eff_scale(peaks, info.eff_scale)

        # ``find_global_peaks`` returns (B, N, 2) — single-instance has I=1.
        # Reshape to the canonical (B, I=1, N, 2) / (B, I=1, N) Outputs shape.
        peaks_BIN2 = peaks.unsqueeze(1)
        vals_BIN = vals.unsqueeze(1)

        outputs = Outputs(
            pred_keypoints=peaks_BIN2,
            pred_peak_values=vals_BIN,
            preprocess_info=info,
        )
        if self.postprocess_config.return_confmaps and confmaps is not None:
            outputs = attrs.evolve(outputs, pred_confmaps=confmaps.detach())
        return outputs

__init__(backend, output_stride, max_stride=1, preprocess_config=None, postprocess_config=None)

Compose the layer with default empty configs when omitted.

Source code in sleap_nn/inference/layers/single_instance.py
def __init__(
    self,
    backend: ModelBackend,
    output_stride: int,
    max_stride: int = 1,
    preprocess_config: Optional[PreprocessConfig] = None,
    postprocess_config: Optional[PostprocessConfig] = None,
) -> None:
    """Compose the layer with default empty configs when omitted."""
    super().__init__(
        backend=backend,
        preprocess_config=preprocess_config or PreprocessConfig(),
        postprocess_config=postprocess_config or PostprocessConfig(),
        output_stride=output_stride,
        max_stride=max_stride,
    )

postprocess(raw_out, info)

Decode confmaps → keypoints, reverse coord ladder, build Outputs.

This layer always runs the torch decode path: it is only ever built with a TorchBackend (does_baked_postproc=False). The exported ONNX/TRT path uses the separate :class:ExportedSingleInstanceLayer adapter, which returns already-final peaks without the coord ladder — so this method must NOT special-case a baked backend (doing so would double-apply the ladder; #584).

Source code in sleap_nn/inference/layers/single_instance.py
def postprocess(self, raw_out: dict, info: PreprocInfo) -> Outputs:
    """Decode confmaps → keypoints, reverse coord ladder, build ``Outputs``.

    This layer always runs the torch decode path: it is only ever built
    with a ``TorchBackend`` (``does_baked_postproc=False``). The exported
    ONNX/TRT path uses the separate :class:`ExportedSingleInstanceLayer`
    adapter, which returns already-final peaks without the coord ladder —
    so this method must NOT special-case a baked backend (doing so would
    double-apply the ladder; #584).
    """
    confmaps = self._extract_confmaps(raw_out)
    peaks, vals = find_global_peaks(
        confmaps.detach(),
        threshold=self.postprocess_config.peak_threshold,
        refinement=self.postprocess_config.effective_refinement,
        integral_patch_size=self.postprocess_config.integral_patch_size,
    )

    # Coord ladder: confmap pixels → input pixels → original-image pixels.
    peaks = undo_stride(peaks, info.output_stride)
    peaks = undo_input_scale(peaks, info.input_scale)
    peaks = undo_eff_scale(peaks, info.eff_scale)

    # ``find_global_peaks`` returns (B, N, 2) — single-instance has I=1.
    # Reshape to the canonical (B, I=1, N, 2) / (B, I=1, N) Outputs shape.
    peaks_BIN2 = peaks.unsqueeze(1)
    vals_BIN = vals.unsqueeze(1)

    outputs = Outputs(
        pred_keypoints=peaks_BIN2,
        pred_peak_values=vals_BIN,
        preprocess_info=info,
    )
    if self.postprocess_config.return_confmaps and confmaps is not None:
        outputs = attrs.evolve(outputs, pred_confmaps=confmaps.detach())
    return outputs