Skip to content

deeplabcut.pose_estimation_pytorch.data.postprocessor

Post-process predictions made by models.

Classes:

Name Description
AddContextToOutput

Adds items from the context to the output, such as the bounding boxes contained

AssignIndividualIdentities

Assigns predicted identities to individuals.

BboxToCoco

Transforms bounding boxes from xyxy to COCO format (xywh)

ComposePostprocessor

Class to preprocess an image and turn it into a batch of inputs before running

ConcatenateOutputs

Checks that there is a single prediction for the image and returns it.

PadOutputs

Pads the outputs to have the maximum number of individuals.

Postprocessor

A post-processor can be called on the output of a model

PredictKeypointIdentities

Assigns predicted identities to keypoints.

PrepareBackboneFeatures

Adds backbone features for each individual and keypoint to the outputs.

RemoveLowConfidenceBoxes

Removes low confidence bounding boxes from detector output before they reach the

RescaleAndOffset

Rescales and offsets predictions back to their position in the original image.

TrimOutputs

Ensures all outputs have at most max_individuals detections.

Functions:

Name Description
build_bottom_up_postprocessor

Creates a postprocessor for bottom-up pose estimation (or object detection)

build_detector_postprocessor

Creates a postprocessor for top-down pose estimation.

build_top_down_postprocessor

Creates a postprocessor for top-down pose estimation.

AddContextToOutput

Bases: Postprocessor

Adds items from the context to the output, such as the bounding boxes contained during top-down inference.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class AddContextToOutput(Postprocessor):
    """Adds items from the context to the output, such as the bounding boxes contained
    during top-down inference."""

    def __init__(self, keys: list[str]) -> None:
        super().__init__()
        self.keys = keys

    def __call__(
        self,
        predictions: dict[str, np.ndarray],
        context: Context,
    ) -> tuple[dict[str, np.ndarray], Context]:
        for k in self.keys:
            if k in context:
                predictions[k] = context[k].copy()
        return predictions, context

AssignIndividualIdentities

Bases: Postprocessor

Assigns predicted identities to individuals.

Attributes:

Name Type Description
identity_key

Key with which to add predicted identities in the predictions dict

pose_key

Key for the bodyparts in the predictions dict

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class AssignIndividualIdentities(Postprocessor):
    """Assigns predicted identities to individuals.

    Attributes:
        identity_key: Key with which to add predicted identities in the predictions dict
        pose_key: Key for the bodyparts in the predictions dict
    """

    def __init__(self, identity_key: str, pose_key: str) -> None:
        self.identity_key = identity_key
        self.pose_key = pose_key

    def __call__(self, predictions: dict[str, np.ndarray], context: Context) -> tuple[dict[str, np.ndarray], Context]:
        map_ = assign_identity(predictions["bodyparts"], predictions["identity_scores"])
        predictions["bodyparts"] = predictions["bodyparts"][map_]
        predictions["identity_scores"] = predictions["identity_scores"][map_]
        return predictions, context

BboxToCoco

Bases: Postprocessor

Transforms bounding boxes from xyxy to COCO format (xywh)

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class BboxToCoco(Postprocessor):
    """Transforms bounding boxes from xyxy to COCO format (xywh)"""

    def __init__(self, bounding_box_keys: list[str]) -> None:
        super().__init__()
        self.bounding_box_keys = bounding_box_keys

    def __call__(self, predictions: dict[str, np.ndarray], context: Context) -> tuple[dict[str, np.ndarray], Context]:
        for bbox_key in self.bounding_box_keys:
            predictions[bbox_key][:, 2] -= predictions[bbox_key][:, 0]
            predictions[bbox_key][:, 3] -= predictions[bbox_key][:, 1]

        return predictions, context

ComposePostprocessor

Bases: Postprocessor

Class to preprocess an image and turn it into a batch of inputs before running inference.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class ComposePostprocessor(Postprocessor):
    """Class to preprocess an image and turn it into a batch of inputs before running
    inference."""

    def __init__(self, components: list[Postprocessor]) -> None:
        self.components = components

    def __call__(self, predictions: Any, context: Context) -> tuple[Any, Context]:
        for postprocessor in self.components:
            predictions, context = postprocessor(predictions, context)
        return predictions, context

ConcatenateOutputs

Bases: Postprocessor

Checks that there is a single prediction for the image and returns it.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class ConcatenateOutputs(Postprocessor):
    """Checks that there is a single prediction for the image and returns it."""

    def __init__(
        self,
        keys_to_concatenate: dict[str, tuple[str, str]],
        empty_shapes: dict[str, tuple[int, ...]] | None = None,
        create_empty_outputs: bool = False,
    ):
        self.keys_to_concatenate = keys_to_concatenate
        self.empty_shapes = empty_shapes
        self.create_empty_outputs = create_empty_outputs

        if self.create_empty_outputs:
            if not all([k in self.empty_shapes for k in self.keys_to_concatenate]):
                raise ValueError(
                    "You must provide the expected shape for all keys to concatenate"
                    f" when create_empty_outputs is true, found {self.empty_shapes}"
                )

    def __call__(self, predictions: Any, context: Context) -> tuple[dict[str, np.ndarray], Context]:
        if len(predictions) == 0:
            outputs = {name: np.zeros((0, *self.empty_shapes[name])) for name in self.keys_to_concatenate.keys()}
            return outputs, context

        outputs = {}
        for output_name, head_key in self.keys_to_concatenate.items():
            head_name, val_name = head_key
            outputs[output_name] = np.concatenate([p[head_name][val_name] for p in predictions])

        return outputs, context

PadOutputs

Bases: Postprocessor

Pads the outputs to have the maximum number of individuals.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class PadOutputs(Postprocessor):
    """Pads the outputs to have the maximum number of individuals."""

    def __init__(
        self,
        max_individuals: dict[str, int],
        pad_value: int,
        expected_shapes: dict[str, tuple[int, ...]],
    ):
        self.max_individuals = max_individuals
        self.pad_value = pad_value
        self.expected_shapes = expected_shapes

    def __call__(self, predictions: dict[str, np.ndarray], context: Context) -> tuple[dict[str, np.ndarray], Context]:
        for name in predictions:
            output = predictions[name]
            output = np.array(output)  # Normalize all inputs to np.ndarray

            expected_shape = self.expected_shapes.get(name, ())
            expected_ndim = 1 + len(expected_shape)  # individuals_dimension + expected shape for single individual

            # Special handling for empty arrays
            if len(output) == 0:
                output = np.empty((0, *expected_shape), dtype=float)
            elif output.ndim < expected_ndim:
                output = np.reshape(output, (len(output), *expected_shape))

            if name in self.max_individuals and len(output) < self.max_individuals[name]:
                pad_size = self.max_individuals[name] - len(output)
                tail_shape = output.shape[1:]
                padding = self.pad_value * np.ones((pad_size, *tail_shape), dtype=output.dtype)
                output = np.concatenate([output, padding], axis=0)

            predictions[name] = output

        return predictions, context

Postprocessor

Bases: ABC

A post-processor can be called on the output of a model TODO: Documentation

Methods:

Name Description
__call__

Post-processes the outputs of a model into a single prediction.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class Postprocessor(ABC):
    """A post-processor can be called on the output of a model
    TODO: Documentation
    """

    @abstractmethod
    def __call__(self, predictions: Any, context: Context) -> Any:
        """Post-processes the outputs of a model into a single prediction.

        Args:
            predictions: the predictions made by the model on a single image
            context: the context returned by the pre-processor with the image

        Returns:
            a single post-processed prediction
        """
        pass

__call__ abstractmethod

__call__(predictions: Any, context: Context) -> Any

Post-processes the outputs of a model into a single prediction.

Parameters:

Name Type Description Default

predictions

Any

the predictions made by the model on a single image

required

context

Context

the context returned by the pre-processor with the image

required

Returns:

Type Description
Any

a single post-processed prediction

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
@abstractmethod
def __call__(self, predictions: Any, context: Context) -> Any:
    """Post-processes the outputs of a model into a single prediction.

    Args:
        predictions: the predictions made by the model on a single image
        context: the context returned by the pre-processor with the image

    Returns:
        a single post-processed prediction
    """
    pass

PredictKeypointIdentities

Bases: Postprocessor

Assigns predicted identities to keypoints.

The identity maps have shape (h, w, num_ids).

Attributes:

Name Type Description
identity_key

Key with which to add predicted identities in the predictions dict

identity_map_key

Key for the identity maps in the predictions dict

pose_key

Key for the bodyparts in the predictions dict

keep_id_maps

Whether to keep identity heatmaps in the output dictionary. Setting this value to True can be useful for debugging, but can lead to memory issues when running video analysis on long videos.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class PredictKeypointIdentities(Postprocessor):
    """Assigns predicted identities to keypoints.

    The identity maps have shape (h, w, num_ids).

    Attributes:
        identity_key: Key with which to add predicted identities in the predictions dict
        identity_map_key: Key for the identity maps in the predictions dict
        pose_key: Key for the bodyparts in the predictions dict
        keep_id_maps: Whether to keep identity heatmaps in the output dictionary.
            Setting this value to True can be useful for debugging, but can lead to
            memory issues when running video analysis on long videos.
    """

    def __init__(
        self,
        identity_key: str,
        identity_map_key: str,
        pose_key: str,
        keep_id_maps: bool = False,
    ) -> None:
        self.identity_key = identity_key
        self.identity_map_key = identity_map_key
        self.pose_key = pose_key
        self.keep_id_maps = keep_id_maps

    def __call__(self, predictions: dict[str, np.ndarray], context: Context) -> tuple[dict[str, np.ndarray], Context]:
        pose = predictions[self.pose_key]
        num_preds, num_keypoints, _ = pose.shape

        identity_heatmap = predictions[self.identity_map_key]  # (h, w, num_ids)
        h, w, num_ids = identity_heatmap.shape

        id_score_matrix = np.zeros((num_preds, num_keypoints, num_ids))
        for pred_idx, individual_keypoints in enumerate(pose):
            heatmap_indices = np.rint(individual_keypoints).astype(int)
            xs = np.clip(heatmap_indices[:, 0], 0, w - 1)
            ys = np.clip(heatmap_indices[:, 1], 0, h - 1)

            # get the score from each identity heatmap at each predicted keypoint
            for kpt_idx, (x, y) in enumerate(zip(xs, ys, strict=False)):
                id_score_matrix[pred_idx, kpt_idx] = identity_heatmap[y, x, :]

        predictions[self.identity_key] = id_score_matrix
        if not self.keep_id_maps:
            # delete the heatmaps as this saves memory
            id_heatmaps = predictions.pop(self.identity_map_key)
            del id_heatmaps

        return predictions, context

PrepareBackboneFeatures

Bases: Postprocessor

Adds backbone features for each individual and keypoint to the outputs.

Attributes:

Name Type Description
top_down

Whether the model is a top-down model.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class PrepareBackboneFeatures(Postprocessor):
    """Adds backbone features for each individual and keypoint to the outputs.

    Attributes:
        top_down: Whether the model is a top-down model.
    """

    def __init__(self, top_down: bool) -> None:
        self.top_down = top_down

    def __call__(self, predictions: Any, context: Context) -> tuple[Any, Context]:
        if self.top_down:
            input_w, input_h = context["top_down_crop_size"]
        else:
            input_w, input_h = context["image_size"]

        for pred in predictions:
            features: np.ndarray = pred["backbone"]["features"]
            pose: np.ndarray = pred["bodypart"]["poses"]

            # only extract features from valid pose
            mask = ~np.all((pose < 0) | np.isnan(pose), axis=(1, 2))
            pose = pose[mask]
            pred["bodypart"]["poses"] = pose.copy()

            pose = np.nan_to_num(pose, nan=0)

            num_features, h, w = features.shape
            backbone_stride = input_w / w, input_h / h

            num_preds, num_keypoints, _ = pose.shape

            bodypart_features = np.zeros((num_preds, num_keypoints, num_features))
            indices = np.rint(pose[..., :2] / backbone_stride).astype(int)
            indices[..., 0] = np.clip(indices[..., 0], 0, w - 1)
            indices[..., 1] = np.clip(indices[..., 1], 0, h - 1)

            for idv, idv_indices in enumerate(indices):
                for kpt, (x, y) in enumerate(idv_indices):
                    # only assign features if the pose was defined
                    if np.sum(x + y) > 0:
                        bodypart_features[idv, kpt] = features[:, y, x]

            pred["backbone"]["bodypart_features"] = bodypart_features

        return predictions, context

RemoveLowConfidenceBoxes

Bases: Postprocessor

Removes low confidence bounding boxes from detector output before they reach the pose estimator.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class RemoveLowConfidenceBoxes(Postprocessor):
    """Removes low confidence bounding boxes from detector output before they reach the
    pose estimator."""

    def __init__(self, bbox_score_thresh: float):
        super().__init__()
        logging.info("utilizing low confidence bbox filtering")
        self.bbox_score_thresh = bbox_score_thresh

    def __call__(self, predictions: dict[str, np.ndarray], context: Context) -> tuple[dict[str, np.ndarray], Context]:
        above_threshold = predictions["bbox_scores"] >= self.bbox_score_thresh
        keepers = np.where(above_threshold)
        if any(~above_threshold):
            predictions["bboxes"] = predictions["bboxes"][keepers]
            predictions["bbox_scores"] = predictions["bbox_scores"][keepers]
        return predictions, context

RescaleAndOffset

Bases: Postprocessor

Rescales and offsets predictions back to their position in the original image.

This can be done in 3 ways

BBOX_XYWH: the data has shape (num_individuals, 4), in xywh format, and there is a single scale and offset for all bounding boxes (e.g., because the image was resized before being passed to a detector) KEYPOINT: the data has shape (num_individuals, num_keypoints, 2/3), and there is a single scale and offset for all individuals (e.g., because the image was resized before being passed to a BU pose model) KEYPOINT_TD: the data has shape (num_individuals, num_keypoints, 2/3), and there are num_individuals scales and offsets (one for each individual, as TD crops one image per individual)

If no scale and no offsets are given, then this postprocessor simply forwards the predictions and context.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class RescaleAndOffset(Postprocessor):
    """Rescales and offsets predictions back to their position in the original image.

    This can be done in 3 ways:
        BBOX_XYWH: the data has shape (num_individuals, 4), in xywh format, and there
            is a single scale and offset for all bounding boxes (e.g., because the image
            was resized before being passed to a detector)
        KEYPOINT: the data has shape (num_individuals, num_keypoints, 2/3), and there
            is a single scale and offset for all individuals (e.g., because the image
            was resized before being passed to a BU pose model)
        KEYPOINT_TD: the data has shape (num_individuals, num_keypoints, 2/3), and there
            are num_individuals scales and offsets (one for each individual, as TD crops
            one image per individual)

    If no scale and no offsets are given, then this postprocessor simply forwards the
    predictions and context.
    """

    class Mode(Enum):
        BBOX_XYWH = "bbox_xywh"
        KEYPOINT = "keypoint"
        KEYPOINT_TD = "keypoint_td"

    def __init__(
        self,
        keys_to_rescale: list[str],
        mode: RescaleAndOffset.Mode,
    ) -> None:
        super().__init__()
        self.keys_to_rescale = keys_to_rescale
        self.mode = mode

    def __call__(self, predictions: dict[str, np.ndarray], context: Context) -> tuple[dict[str, np.ndarray], Context]:
        if "scales" not in context and "offsets" not in context:
            # no rescaling needed
            return predictions, context

        updated_predictions = {}
        scales, offsets = context["scales"], context["offsets"]
        for name, outputs in predictions.items():
            if name in self.keys_to_rescale:
                if self.mode == self.Mode.BBOX_XYWH:
                    rescaled = outputs.copy()
                    rescaled[:, 0] = outputs[:, 0] * scales[0] + offsets[0]
                    rescaled[:, 1] = outputs[:, 1] * scales[1] + offsets[1]
                    rescaled[:, 2] = outputs[:, 2] * scales[0]
                    rescaled[:, 3] = outputs[:, 3] * scales[1]
                elif self.mode == self.Mode.KEYPOINT:
                    rescaled = outputs.copy()
                    rescaled[..., :2] = outputs[..., :2] * scales + offsets
                else:  # Mode.KEYPOINT_TD
                    if not len(outputs) == len(scales) == len(offsets):
                        raise ValueError(
                            "There must be as many 'scales' and 'offsets' as outputs, found "
                            f"{len(outputs)}, {len(scales)}, {len(offsets)}"
                        )

                    if len(outputs) == 0:
                        rescaled = outputs
                    else:
                        rescaled_individuals = []
                        for output, scale, offset in zip(outputs, scales, offsets, strict=False):
                            output_rescaled = output.copy()
                            output_rescaled[:, :2] = output[:, :2] * scale + offset
                            rescaled_individuals.append(output_rescaled)
                        rescaled = np.stack(rescaled_individuals)

                        # rescoring: https://github.com/amathislab/BUCTD/blob/main/lib/dataset/crowdpose.py#L182-L206
                        if "cond_kpts" in context:
                            kpt_scores = rescaled[:, :, 2].copy()
                            valid_kpt_scores = kpt_scores >= 0.2

                            num_valid_kpts = np.sum(valid_kpt_scores, axis=1)
                            num_valid_kpts[num_valid_kpts == 0] = 1
                            kpt_scores[~valid_kpt_scores] = 0
                            kpt_score_sums = np.sum(kpt_scores, axis=1)
                            idv_scores = kpt_score_sums / num_valid_kpts

                            cond_kpt_scores = np.mean(context["cond_kpts"][:, :, 2], axis=1)

                            rescaled[:, :, 2] = (cond_kpt_scores * idv_scores).reshape(-1, 1)

                updated_predictions[name] = rescaled
            else:
                updated_predictions[name] = outputs.copy()

        return updated_predictions, context

TrimOutputs

Bases: Postprocessor

Ensures all outputs have at most max_individuals detections.

Assumes that the outputs are sorted by decreasing score, such that the first max_individuals predictions are the ones to keep.

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
class TrimOutputs(Postprocessor):
    """Ensures all outputs have at most `max_individuals` detections.

    Assumes that the outputs are sorted by decreasing score, such that the first
    `max_individuals` predictions are the ones to keep.
    """

    def __init__(self, max_individuals: dict[str, int]):
        self.max_individuals = max_individuals

    def __call__(self, predictions: dict[str, np.ndarray], context: Context) -> tuple[dict[str, np.ndarray], Context]:
        for name in predictions:
            output = predictions[name]
            if len(output) > self.max_individuals[name]:
                predictions[name] = output[: self.max_individuals[name]]

        return predictions, context

build_bottom_up_postprocessor

build_bottom_up_postprocessor(
    max_individuals: int,
    num_bodyparts: int,
    num_unique_bodyparts: int,
    with_identity: bool = False,
    with_backbone_features: bool = False,
) -> ComposePostprocessor

Creates a postprocessor for bottom-up pose estimation (or object detection)

Parameters:

Name Type Description Default

max_individuals

int

the maximum number of individuals in a single image

required

num_bodyparts

int

the number of bodyparts output by the model

required

num_unique_bodyparts

int

the number of unique_bodyparts output by the model

required

with_identity

bool

whether the model has an identity head

False

with_backbone_features

bool

When True, the backbone features are extracted from the output and saved in a features key. The PoseModel must have its output_features attribute set to True, or this will raise an Exception.

False

Returns:

Type Description
ComposePostprocessor

A default bottom-up Postprocessor

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
def build_bottom_up_postprocessor(
    max_individuals: int,
    num_bodyparts: int,
    num_unique_bodyparts: int,
    with_identity: bool = False,
    with_backbone_features: bool = False,
) -> ComposePostprocessor:
    """Creates a postprocessor for bottom-up pose estimation (or object detection)

    Args:
        max_individuals: the maximum number of individuals in a single image
        num_bodyparts: the number of bodyparts output by the model
        num_unique_bodyparts: the number of unique_bodyparts output by the model
        with_identity: whether the model has an identity head
        with_backbone_features: When True, the backbone features are extracted from
            the output and saved in a `features` key. The `PoseModel` must have its
            `output_features` attribute set to True, or this will raise an Exception.

    Returns:
        A default bottom-up Postprocessor
    """
    keys_to_concatenate = {"bodyparts": ("bodypart", "poses")}
    empty_shapes = {"bodyparts": (num_bodyparts, 3)}
    keys_to_rescale = ["bodyparts"]

    if num_unique_bodyparts > 0:
        keys_to_concatenate["unique_bodyparts"] = ("unique_bodypart", "poses")
        empty_shapes["unique_bodyparts"] = (num_bodyparts, 3)
        keys_to_rescale.append("unique_bodyparts")

    if with_identity:
        keys_to_concatenate["identity_heatmap"] = ("identity", "heatmap")
        empty_shapes["identity_heatmap"] = (1, 1, max_individuals)

    if with_backbone_features:
        keys_to_concatenate["features"] = ("backbone", "features")
        empty_shapes["features"] = (num_bodyparts, 0, 1)

    components = [
        ConcatenateOutputs(
            keys_to_concatenate=keys_to_concatenate,
            empty_shapes=empty_shapes,
            create_empty_outputs=True,
        ),
    ]

    if with_identity:
        components.append(
            PredictKeypointIdentities(
                identity_key="identity_scores",
                identity_map_key="identity_heatmap",
                pose_key="bodyparts",
                keep_id_maps=False,
            )
        )

    components += [
        RescaleAndOffset(
            keys_to_rescale=keys_to_rescale,
            mode=RescaleAndOffset.Mode.KEYPOINT,
        ),
        PadOutputs(
            max_individuals={
                "bodyparts": max_individuals,
                "identity_scores": max_individuals,
            },
            pad_value=-1,
            expected_shapes={
                "bodyparts": (num_bodyparts, 3),
                "identity_scores": (num_bodyparts, max_individuals),
            },
        ),
    ]

    if with_identity:
        components.append(
            AssignIndividualIdentities(
                identity_key="identity_scores",
                pose_key="bodyparts",
            )
        )

    return ComposePostprocessor(components=components)

build_detector_postprocessor

build_detector_postprocessor(max_individuals: int, min_bbox_score: float | None = None) -> Postprocessor

Creates a postprocessor for top-down pose estimation.

Parameters:

Name Type Description Default

max_individuals

int

the maximum number of detections to keep in a single image

required

min_bbox_score

float | None

the threshold for filtering bounding boxes. Only bboxes with a value higher than this threshold are kept, the rest is removed.

None

Returns:

Type Description
Postprocessor

A default top-down Postprocessor

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
def build_detector_postprocessor(
    max_individuals: int,
    min_bbox_score: float | None = None,
) -> Postprocessor:
    """Creates a postprocessor for top-down pose estimation.

    Args:
        max_individuals: the maximum number of detections to keep in a single image
        min_bbox_score: the threshold for filtering bounding boxes. Only bboxes
            with a value higher than this threshold are kept, the rest is removed.

    Returns:
        A default top-down Postprocessor
    """
    components = [
        ConcatenateOutputs(
            keys_to_concatenate={
                "bboxes": ("detection", "bboxes"),
                "bbox_scores": ("detection", "scores"),
            }
        ),
        TrimOutputs(
            max_individuals={
                "bboxes": max_individuals,
                "bbox_scores": max_individuals,
            },
        ),
        BboxToCoco(bounding_box_keys=["bboxes"]),
        RescaleAndOffset(
            keys_to_rescale=["bboxes"],
            mode=RescaleAndOffset.Mode.BBOX_XYWH,
        ),
    ]
    if min_bbox_score is not None:
        components.append(RemoveLowConfidenceBoxes(min_bbox_score))
    return ComposePostprocessor(components=components)

build_top_down_postprocessor

build_top_down_postprocessor(
    max_individuals: int, num_bodyparts: int, num_unique_bodyparts: int, with_backbone_features: bool = False
) -> Postprocessor

Creates a postprocessor for top-down pose estimation.

Parameters:

Name Type Description Default

max_individuals

int

the maximum number of individuals in a single image

required

num_bodyparts

int

the number of bodyparts output by the model

required

num_unique_bodyparts

int

the number of unique_bodyparts output by the model

required

with_backbone_features

bool

When True, the backbone features are extracted from the output and saved in a features key. The PoseModel must have its output_features attribute set to True, or this will raise an Exception.

False

Returns:

Type Description
Postprocessor

A default top-down Postprocessor

Source code in deeplabcut/pose_estimation_pytorch/data/postprocessor.py
def build_top_down_postprocessor(
    max_individuals: int,
    num_bodyparts: int,
    num_unique_bodyparts: int,
    with_backbone_features: bool = False,
) -> Postprocessor:
    """Creates a postprocessor for top-down pose estimation.

    Args:
        max_individuals: the maximum number of individuals in a single image
        num_bodyparts: the number of bodyparts output by the model
        num_unique_bodyparts: the number of unique_bodyparts output by the model
        with_backbone_features: When True, the backbone features are extracted from
            the output and saved in a `features` key. The `PoseModel` must have its
            `output_features` attribute set to True, or this will raise an Exception.

    Returns:
        A default top-down Postprocessor
    """
    keys_to_concatenate = {"bodyparts": ("bodypart", "poses")}
    empty_shapes = {"bodyparts": (num_bodyparts, 3)}
    keys_to_rescale = ["bodyparts"]
    if num_unique_bodyparts > 0:
        keys_to_concatenate["unique_bodyparts"] = ("unique_bodypart", "poses")
        empty_shapes["unique_bodyparts"] = (num_unique_bodyparts, 3)
        keys_to_rescale.append("unique_bodyparts")

    if with_backbone_features:
        keys_to_concatenate["features"] = ("backbone", "features")
        empty_shapes["features"] = (num_bodyparts, 0, 1)

    return ComposePostprocessor(
        components=[
            ConcatenateOutputs(
                keys_to_concatenate=keys_to_concatenate,
                empty_shapes=empty_shapes,
                create_empty_outputs=True,
            ),
            RescaleAndOffset(
                keys_to_rescale=keys_to_rescale,
                mode=RescaleAndOffset.Mode.KEYPOINT_TD,
            ),
            AddContextToOutput(keys=["bboxes", "bbox_scores"]),
            PadOutputs(
                max_individuals={
                    "bodyparts": max_individuals,
                    "bboxes": max_individuals,
                    "bbox_scores": max_individuals,
                },
                pad_value=-1,
                expected_shapes={
                    "bodyparts": (num_bodyparts, 3),
                    "bboxes": (4,),
                    "bbox_scores": (),  # scalar
                },
            ),
        ]
    )