跳转至

后处理后端

SAHI 的后处理操作(NMS、NMM)可以在三种可互换的后端上运行。合适的后端取决于 您的硬件和已安装的软件包。

后端概览

后端 适用场景 额外依赖
numpy 仅使用 CPU 的环境,预测结果数量较少或中等 无(始终可用)
numba 使用 CPU 且预测结果数量较多;首次调用需要约 1 秒进行 JIT 预热,之后较快 pip install numba
torchvision CUDA GPU 可用;处理大型批次时速度最快 pip install torch torchvision + CUDA

自动检测(默认)

默认情况下,SAHI 会在运行时自动选择最佳可用后端:

  1. torchvision -- 已安装 torchvision 且存在 CUDA GPU 时使用。
  2. numba -- 已安装 numba 软件包时使用。
  3. numpy -- 始终可用,作为最终兜底方案。
from sahi.postprocess.backends import get_postprocess_backend

# 检查解析出的后端(会触发自动检测)
print(get_postprocess_backend())  # 首次执行后处理操作前为 "auto"

强制指定后端

在运行推理前使用 set_postprocess_backend 固定后端:

from sahi.postprocess.backends import set_postprocess_backend

# 强制使用纯 numpy(无额外依赖,可在所有环境中运行)
set_postprocess_backend("numpy")

# 强制使用 numba JIT(安装命令:pip install numba)
set_postprocess_backend("numba")

# 强制使用 torchvision GPU(安装命令:pip install torch torchvision)
set_postprocess_backend("torchvision")

# 恢复自动检测
set_postprocess_backend("auto")

该调用会影响当前进程中后续的所有 NMS/NMM 操作,包括 get_sliced_prediction 内部触发的操作。

示例:为完整推理流程固定后端

from sahi import AutoDetectionModel
from sahi.predict import get_sliced_prediction
from sahi.postprocess.backends import set_postprocess_backend

# 在 CUDA 环境中使用 GPU 加速后处理
set_postprocess_backend("torchvision")

detection_model = AutoDetectionModel.from_pretrained(
    model_type="ultralytics",
    model_path="yolo11n.pt",
    confidence_threshold=0.25,
    device="cuda:0",
)

result = get_sliced_prediction(
    "image.jpg",
    detection_model,
    slice_height=512,
    slice_width=512,
    overlap_height_ratio=0.2,
    overlap_width_ratio=0.2,
)

直接使用后处理函数

三个后端遵循相同的数组约定:使用形状为 (N, 6) 的 numpy 数组,列顺序为 [x1, y1, x2, y2, score, category_id]

NMS(抑制)

import numpy as np
from sahi.postprocess.combine import nms, batched_nms

predictions = np.array([
    [100, 100, 200, 200, 0.95, 0],
    [105, 105, 205, 205, 0.80, 0],
    [300, 300, 400, 400, 0.90, 1],
])

# 全局 NMS:所有类别一起参与比较
keep = nms(predictions, match_metric="IOU", match_threshold=0.5)
print(predictions[keep])

# 按类别执行 NMS:类别 0 和类别 1 分别独立处理
keep = batched_nms(predictions, match_metric="IOU", match_threshold=0.5)
print(predictions[keep])

NMM(合并)

NMM 不会丢弃重叠框,而是将其合并:

from sahi.postprocess.combine import greedy_nmm, nmm, batched_greedy_nmm

# 贪心 NMM:每个保留框只合并与其直接相邻的框(速度快,边界框紧凑)
keep_to_merge = greedy_nmm(predictions, match_metric="IOU", match_threshold=0.5)
# {kept_index: [merged_index, ...], ...}

# 完整 NMM:传递式合并(A 合并 B,B 合并 C,则 A 会获得全部三个框)
keep_to_merge = nmm(predictions, match_metric="IOU", match_threshold=0.5)

# 按类别执行贪心 NMM
keep_to_merge = batched_greedy_nmm(predictions, match_threshold=0.5)

IoS 指标

NMS 和 NMM 都支持 match_metric="IOS"(Intersection over Smaller area, 交集与较小面积之比)。当一个边界框远小于另一个边界框时,该指标非常实用:

keep = nms(predictions, match_metric="IOS", match_threshold=0.5)

后处理类

高级类可以与 SAHI 的 ObjectPrediction 列表集成,并由 get_sliced_prediction 通过 postprocess_type 参数使用:

from sahi.postprocess.combine import NMSPostprocess, NMMPostprocess, GreedyNMMPostprocess

# NMS:保留最佳边界框,丢弃其余边界框
postprocessor = NMSPostprocess(
    match_threshold=0.5,
    match_metric="IOU",
    class_agnostic=True,   # False 表示按类别处理
)
filtered = postprocessor(object_prediction_list)

# 贪心 NMM:合并重叠边界框(速度快)
postprocessor = GreedyNMMPostprocess(match_threshold=0.5)
merged = postprocessor(object_prediction_list)

# 完整 NMM:传递式合并
postprocessor = NMMPostprocess(match_threshold=0.5)
merged = postprocessor(object_prediction_list)

传入 class_agnostic=False 后,每个后处理器都会按类别独立运行,因此 "car" 预测结果不会抑制 "person" 预测结果。

API 参考

sahi.postprocess.backends

Postprocessing backend selection and auto-detection.

Usage

from sahi.postprocess.backends import set_postprocess_backend, get_postprocess_backend

set_postprocess_backend("numba") # force numba set_postprocess_backend("auto") # auto-detect best available

Functions:

get_postprocess_backend()

Return the currently configured backend name (may be "auto").

Source code in sahi/postprocess/backends.py
def get_postprocess_backend() -> str:
    """Return the currently configured backend name (may be "auto")."""
    return _backend

resolve_backend()

Resolve "auto" to a concrete backend, caching the result.

When the backend is set to "auto", detection follows this priority:

  1. torchvision -- selected if both torchvision and a CUDA GPU are available (GPU-accelerated NMS).
  2. numba -- selected if the numba package is installed (JIT-compiled loops, faster than pure numpy for large prediction counts).
  3. numpy -- always available as the fallback (pure numpy, no extra dependencies).

If the backend was explicitly set via set_postprocess_backend, that value is returned directly without auto-detection.

Returns:

Type Description
str

One of "numpy", "numba", or "torchvision".

Source code in sahi/postprocess/backends.py
def resolve_backend() -> str:
    """Resolve "auto" to a concrete backend, caching the result.

    When the backend is set to "auto", detection follows this priority:

    1. **torchvision** -- selected if both torchvision and a CUDA GPU are
       available (GPU-accelerated NMS).
    2. **numba** -- selected if the numba package is installed (JIT-compiled
       loops, faster than pure numpy for large prediction counts).
    3. **numpy** -- always available as the fallback (pure numpy,
       no extra dependencies).

    If the backend was explicitly set via ``set_postprocess_backend``, that
    value is returned directly without auto-detection.

    Returns:
        One of "numpy", "numba", or "torchvision".
    """
    global _resolved_cache
    if _resolved_cache is not None:
        return _resolved_cache

    if _backend != "auto":
        _resolved_cache = _backend
        return _backend

    # Auto-detect: prefer torchvision on GPU, then numba, then numpy
    if is_available("torchvision"):
        try:
            import torch

            if torch.cuda.is_available():
                _resolved_cache = "torchvision"
                return _resolved_cache
        except ImportError:
            pass

    if is_available("numba"):
        _resolved_cache = "numba"
        return _resolved_cache

    _resolved_cache = "numpy"
    return _resolved_cache

set_postprocess_backend(name)

Set the postprocessing backend.

Call once at startup before running any inference. This function is not thread-safe.

Parameters:

Name Type Description Default
name
str

One of "auto", "numpy", "numba", "torchvision".

required
Source code in sahi/postprocess/backends.py
def set_postprocess_backend(name: str) -> None:
    """Set the postprocessing backend.

    Call once at startup before running any inference.  This function is
    **not** thread-safe.

    Args:
        name: One of "auto", "numpy", "numba", "torchvision".
    """
    global _backend, _resolved_cache
    if name not in VALID_BACKENDS:
        raise ValueError(f"Unknown backend {name!r}. Choose from {VALID_BACKENDS}")
    _backend = name
    _resolved_cache = None  # force re-resolve
    # Invalidate the dispatch function cache in combine module
    try:
        from sahi.postprocess.combine import _dispatch_cache

        _dispatch_cache.clear()
    except ImportError:
        pass

sahi.postprocess.combine

Postprocessing strategies for combining predictions from sliced inference.

Classes

GreedyNMMPostprocess

Bases: NMMPostprocess

Postprocessor using Greedy Non-Maximum Merging (NMM).

Similar to NMM but uses a greedy strategy: each kept prediction only merges boxes that directly overlap with it (no transitive merging). This is faster than full NMM and produces tighter merged boxes.

Source code in sahi/postprocess/combine.py
class GreedyNMMPostprocess(NMMPostprocess):
    """Postprocessor using Greedy Non-Maximum Merging (NMM).

    Similar to NMM but uses a greedy strategy: each kept prediction only
    merges boxes that directly overlap with it (no transitive merging).
    This is faster than full NMM and produces tighter merged boxes.
    """

    _agnostic_func = staticmethod(greedy_nmm)
    _batched_func = staticmethod(batched_greedy_nmm)

LSNMSPostprocess

Bases: PostprocessPredictions

Postprocessor using Locality-Sensitive NMS from the lsnms package.

Uses a spatial index for fast neighbor lookup, making it efficient for large numbers of predictions. Only supports IoU metric (not IoS). Requires the lsnms package (pip install lsnms>0.3.1).

Note

This postprocessor is experimental and not recommended for production use.

Source code in sahi/postprocess/combine.py
class LSNMSPostprocess(PostprocessPredictions):
    """Postprocessor using Locality-Sensitive NMS from the ``lsnms`` package.

    Uses a spatial index for fast neighbor lookup, making it efficient for
    large numbers of predictions. Only supports IoU metric (not IoS).
    Requires the ``lsnms`` package (``pip install lsnms>0.3.1``).

    Note:
        This postprocessor is experimental and not recommended for
        production use.
    """

    def __call__(self, object_predictions: list[ObjectPrediction]) -> list[ObjectPrediction]:
        """Apply Locality-Sensitive NMS to suppress overlapping predictions.

        Args:
            object_predictions: List of ObjectPrediction instances to suppress.

        Returns:
            List of suppressed ObjectPrediction instances.

        Raises:
            ModuleNotFoundError: If the lsnms package is not installed.
            NotImplementedError: If match_metric is not "IOU".
        """
        try:
            from lsnms import nms
        except ModuleNotFoundError:
            raise ModuleNotFoundError(
                'Please run "pip install lsnms>0.3.1" to install lsnms first for lsnms utilities.'
            )

        if self.match_metric == "IOS":
            raise NotImplementedError(f"match_metric={self.match_metric} is not supported for LSNMSPostprocess")

        logger.warning("LSNMSPostprocess is experimental and not recommended to use.")

        object_prediction_list = ObjectPredictionList(object_predictions)
        preds_np = object_prediction_list.tonumpy()

        boxes = preds_np[:, :4]
        scores = preds_np[:, 4]
        class_ids = preds_np[:, 5].astype("uint8")

        keep = nms(
            boxes, scores, iou_threshold=self.match_threshold, class_ids=None if self.class_agnostic else class_ids
        )

        selected = object_prediction_list[keep].tolist()
        if not isinstance(selected, list):
            selected = [selected]
        return selected
Methods:
__call__(object_predictions)

Apply Locality-Sensitive NMS to suppress overlapping predictions.

Parameters:

Name Type Description Default
object_predictions list[ObjectPrediction]

List of ObjectPrediction instances to suppress.

required

Returns:

Type Description
list[ObjectPrediction]

List of suppressed ObjectPrediction instances.

Raises:

Type Description
ModuleNotFoundError

If the lsnms package is not installed.

NotImplementedError

If match_metric is not "IOU".

Source code in sahi/postprocess/combine.py
def __call__(self, object_predictions: list[ObjectPrediction]) -> list[ObjectPrediction]:
    """Apply Locality-Sensitive NMS to suppress overlapping predictions.

    Args:
        object_predictions: List of ObjectPrediction instances to suppress.

    Returns:
        List of suppressed ObjectPrediction instances.

    Raises:
        ModuleNotFoundError: If the lsnms package is not installed.
        NotImplementedError: If match_metric is not "IOU".
    """
    try:
        from lsnms import nms
    except ModuleNotFoundError:
        raise ModuleNotFoundError(
            'Please run "pip install lsnms>0.3.1" to install lsnms first for lsnms utilities.'
        )

    if self.match_metric == "IOS":
        raise NotImplementedError(f"match_metric={self.match_metric} is not supported for LSNMSPostprocess")

    logger.warning("LSNMSPostprocess is experimental and not recommended to use.")

    object_prediction_list = ObjectPredictionList(object_predictions)
    preds_np = object_prediction_list.tonumpy()

    boxes = preds_np[:, :4]
    scores = preds_np[:, 4]
    class_ids = preds_np[:, 5].astype("uint8")

    keep = nms(
        boxes, scores, iou_threshold=self.match_threshold, class_ids=None if self.class_agnostic else class_ids
    )

    selected = object_prediction_list[keep].tolist()
    if not isinstance(selected, list):
        selected = [selected]
    return selected

NMMPostprocess

Bases: PostprocessPredictions

Postprocessor using Non-Maximum Merging (NMM) with transitive merging.

Instead of discarding overlapping detections, merges their bounding boxes, masks, and scores. Uses non-greedy transitive merging: if A overlaps B and B overlaps C, all three are merged even if A does not directly overlap C.

Source code in sahi/postprocess/combine.py
class NMMPostprocess(PostprocessPredictions):
    """Postprocessor using Non-Maximum Merging (NMM) with transitive merging.

    Instead of discarding overlapping detections, merges their bounding
    boxes, masks, and scores. Uses non-greedy transitive merging: if A
    overlaps B and B overlaps C, all three are merged even if A does not
    directly overlap C.
    """

    _agnostic_func = staticmethod(nmm)
    _batched_func = staticmethod(batched_nmm)

    def __call__(self, object_predictions: list[ObjectPrediction]) -> list[ObjectPrediction]:
        """Apply Non-Maximum Merging to merge overlapping predictions.

        Args:
            object_predictions: List of ObjectPrediction instances to merge.

        Returns:
            List of merged ObjectPrediction instances.
        """
        object_prediction_list = ObjectPredictionList(object_predictions)
        preds_np = object_prediction_list.tonumpy()
        func = self._agnostic_func if self.class_agnostic else self._batched_func
        keep_to_merge = func(preds_np, match_threshold=self.match_threshold, match_metric=self.match_metric)
        return _apply_merge(object_prediction_list, keep_to_merge, self.match_metric, self.match_threshold)
Methods:
__call__(object_predictions)

Apply Non-Maximum Merging to merge overlapping predictions.

Parameters:

Name Type Description Default
object_predictions list[ObjectPrediction]

List of ObjectPrediction instances to merge.

required

Returns:

Type Description
list[ObjectPrediction]

List of merged ObjectPrediction instances.

Source code in sahi/postprocess/combine.py
def __call__(self, object_predictions: list[ObjectPrediction]) -> list[ObjectPrediction]:
    """Apply Non-Maximum Merging to merge overlapping predictions.

    Args:
        object_predictions: List of ObjectPrediction instances to merge.

    Returns:
        List of merged ObjectPrediction instances.
    """
    object_prediction_list = ObjectPredictionList(object_predictions)
    preds_np = object_prediction_list.tonumpy()
    func = self._agnostic_func if self.class_agnostic else self._batched_func
    keep_to_merge = func(preds_np, match_threshold=self.match_threshold, match_metric=self.match_metric)
    return _apply_merge(object_prediction_list, keep_to_merge, self.match_metric, self.match_threshold)

NMSPostprocess

Bases: PostprocessPredictions

Postprocessor using Non-Maximum Suppression (NMS).

Keeps the highest-scored prediction among overlapping boxes and discards the rest. Does not merge bounding boxes or masks.

Source code in sahi/postprocess/combine.py
class NMSPostprocess(PostprocessPredictions):
    """Postprocessor using Non-Maximum Suppression (NMS).

    Keeps the highest-scored prediction among overlapping boxes and
    discards the rest. Does not merge bounding boxes or masks.
    """

    def __call__(self, object_predictions: list[ObjectPrediction]) -> list[ObjectPrediction]:
        """Apply Non-Maximum Suppression to suppress overlapping predictions.

        Args:
            object_predictions: List of ObjectPrediction instances to suppress.

        Returns:
            List of suppressed ObjectPrediction instances.
        """
        object_prediction_list = ObjectPredictionList(object_predictions)
        preds_np = object_prediction_list.tonumpy()
        func = nms if self.class_agnostic else batched_nms
        keep = func(preds_np, match_threshold=self.match_threshold, match_metric=self.match_metric)

        selected = object_prediction_list[keep].tolist()
        if not isinstance(selected, list):
            selected = [selected]
        return selected
Methods:
__call__(object_predictions)

Apply Non-Maximum Suppression to suppress overlapping predictions.

Parameters:

Name Type Description Default
object_predictions list[ObjectPrediction]

List of ObjectPrediction instances to suppress.

required

Returns:

Type Description
list[ObjectPrediction]

List of suppressed ObjectPrediction instances.

Source code in sahi/postprocess/combine.py
def __call__(self, object_predictions: list[ObjectPrediction]) -> list[ObjectPrediction]:
    """Apply Non-Maximum Suppression to suppress overlapping predictions.

    Args:
        object_predictions: List of ObjectPrediction instances to suppress.

    Returns:
        List of suppressed ObjectPrediction instances.
    """
    object_prediction_list = ObjectPredictionList(object_predictions)
    preds_np = object_prediction_list.tonumpy()
    func = nms if self.class_agnostic else batched_nms
    keep = func(preds_np, match_threshold=self.match_threshold, match_metric=self.match_metric)

    selected = object_prediction_list[keep].tolist()
    if not isinstance(selected, list):
        selected = [selected]
    return selected

PostprocessPredictions

Bases: ABC

Abstract base class for postprocessing object prediction lists.

Subclasses implement a specific strategy (NMS, NMM, greedy NMM, etc.) to reduce overlapping detections produced by sliced inference.

Parameters:

Name Type Description Default
match_threshold
float

Minimum overlap value (IoU or IoS) to consider two predictions as matching.

0.5
match_metric
str

Overlap metric, "IOU" or "IOS".

'IOU'
class_agnostic
bool

If True, apply postprocessing across all categories. If False, apply per category independently.

True
Source code in sahi/postprocess/combine.py
class PostprocessPredictions(ABC):
    """Abstract base class for postprocessing object prediction lists.

    Subclasses implement a specific strategy (NMS, NMM, greedy NMM, etc.)
    to reduce overlapping detections produced by sliced inference.

    Args:
        match_threshold: Minimum overlap value (IoU or IoS) to consider
            two predictions as matching.
        match_metric: Overlap metric, "IOU" or "IOS".
        class_agnostic: If True, apply postprocessing across all
            categories. If False, apply per category independently.
    """

    def __init__(
        self,
        match_threshold: float = 0.5,
        match_metric: str = "IOU",
        class_agnostic: bool = True,
    ) -> None:
        """Initialize the postprocessor with configuration parameters.

        Args:
            match_threshold: Minimum overlap value (IoU or IoS) to consider
                two predictions as matching.
            match_metric: Overlap metric, "IOU" or "IOS".
            class_agnostic: If True, apply postprocessing across all
                categories. If False, apply per category independently.
        """
        self.match_threshold = match_threshold
        self.class_agnostic = class_agnostic
        self.match_metric = match_metric

    @abstractmethod
    def __call__(self, predictions: list[ObjectPrediction]) -> list[ObjectPrediction]:
        """Apply postprocessing to the list of predictions.

        Args:
            predictions: List of ObjectPrediction instances to postprocess.

        Returns:
            List of postprocessed ObjectPrediction instances.
        """
        pass
Methods:
__call__(predictions) abstractmethod

Apply postprocessing to the list of predictions.

Parameters:

Name Type Description Default
predictions list[ObjectPrediction]

List of ObjectPrediction instances to postprocess.

required

Returns:

Type Description
list[ObjectPrediction]

List of postprocessed ObjectPrediction instances.

Source code in sahi/postprocess/combine.py
@abstractmethod
def __call__(self, predictions: list[ObjectPrediction]) -> list[ObjectPrediction]:
    """Apply postprocessing to the list of predictions.

    Args:
        predictions: List of ObjectPrediction instances to postprocess.

    Returns:
        List of postprocessed ObjectPrediction instances.
    """
    pass
__init__(match_threshold=0.5, match_metric='IOU', class_agnostic=True)

Initialize the postprocessor with configuration parameters.

Parameters:

Name Type Description Default
match_threshold float

Minimum overlap value (IoU or IoS) to consider two predictions as matching.

0.5
match_metric str

Overlap metric, "IOU" or "IOS".

'IOU'
class_agnostic bool

If True, apply postprocessing across all categories. If False, apply per category independently.

True
Source code in sahi/postprocess/combine.py
def __init__(
    self,
    match_threshold: float = 0.5,
    match_metric: str = "IOU",
    class_agnostic: bool = True,
) -> None:
    """Initialize the postprocessor with configuration parameters.

    Args:
        match_threshold: Minimum overlap value (IoU or IoS) to consider
            two predictions as matching.
        match_metric: Overlap metric, "IOU" or "IOS".
        class_agnostic: If True, apply postprocessing across all
            categories. If False, apply per category independently.
    """
    self.match_threshold = match_threshold
    self.class_agnostic = class_agnostic
    self.match_metric = match_metric

Functions:

batched_greedy_nmm(predictions, match_metric='IOU', match_threshold=0.5)

Apply greedy non-maximum merging independently per category.

Parameters:

Name Type Description Default
predictions
ndarray

Array of shape (N, 6) with columns [x1, y1, x2, y2, score, category_id].

required
match_metric
str

Overlap metric, "IOU" or "IOS".

'IOU'
match_threshold
float

Minimum overlap to merge a lower-scored box.

0.5

Returns:

Type Description
dict[int, list[int]]

Dict mapping each kept index to a list of indices merged into it.

Source code in sahi/postprocess/combine.py
def batched_greedy_nmm(
    predictions: np.ndarray,
    match_metric: str = "IOU",
    match_threshold: float = 0.5,
) -> dict[int, list[int]]:
    """Apply greedy non-maximum merging independently per category.

    Args:
        predictions: Array of shape (N, 6) with columns
            [x1, y1, x2, y2, score, category_id].
        match_metric: Overlap metric, "IOU" or "IOS".
        match_threshold: Minimum overlap to merge a lower-scored box.

    Returns:
        Dict mapping each kept index to a list of indices merged into it.
    """
    return _batched_apply(predictions, greedy_nmm, match_metric, match_threshold)  # type: ignore[return-value]

batched_nmm(predictions, match_metric='IOU', match_threshold=0.5)

Apply non-maximum merging (non-greedy, transitive) independently per category.

Parameters:

Name Type Description Default
predictions
ndarray

Array of shape (N, 6) with columns [x1, y1, x2, y2, score, category_id].

required
match_metric
str

Overlap metric, "IOU" or "IOS".

'IOU'
match_threshold
float

Minimum overlap to merge a lower-scored box.

0.5

Returns:

Type Description
dict[int, list[int]]

Dict mapping each kept index to a list of indices merged into it.

Source code in sahi/postprocess/combine.py
def batched_nmm(
    predictions: np.ndarray,
    match_metric: str = "IOU",
    match_threshold: float = 0.5,
) -> dict[int, list[int]]:
    """Apply non-maximum merging (non-greedy, transitive) independently per category.

    Args:
        predictions: Array of shape (N, 6) with columns
            [x1, y1, x2, y2, score, category_id].
        match_metric: Overlap metric, "IOU" or "IOS".
        match_threshold: Minimum overlap to merge a lower-scored box.

    Returns:
        Dict mapping each kept index to a list of indices merged into it.
    """
    return _batched_apply(predictions, nmm, match_metric, match_threshold)  # type: ignore[return-value]

batched_nms(predictions, match_metric='IOU', match_threshold=0.5)

Apply non-maximum suppression independently per category.

Parameters:

Name Type Description Default
predictions
ndarray

Array of shape (N, 6) with columns [x1, y1, x2, y2, score, category_id].

required
match_metric
str

Overlap metric, "IOU" or "IOS".

'IOU'
match_threshold
float

Minimum overlap to suppress a lower-scored box.

0.5

Returns:

Type Description
list[int]

List of indices of the kept predictions, sorted by score descending.

Source code in sahi/postprocess/combine.py
def batched_nms(
    predictions: np.ndarray,
    match_metric: str = "IOU",
    match_threshold: float = 0.5,
) -> list[int]:
    """Apply non-maximum suppression independently per category.

    Args:
        predictions: Array of shape (N, 6) with columns
            [x1, y1, x2, y2, score, category_id].
        match_metric: Overlap metric, "IOU" or "IOS".
        match_threshold: Minimum overlap to suppress a lower-scored box.

    Returns:
        List of indices of the kept predictions, sorted by score descending.
    """
    return _batched_apply(predictions, nms, match_metric, match_threshold)  # type: ignore[return-value]

greedy_nmm(predictions, match_metric='IOU', match_threshold=0.5)

Greedy non-maximum merging for axis-aligned bounding boxes.

Instead of discarding overlapping boxes, merges them into the highest-scored box. Dispatches to the resolved backend.

Parameters:

Name Type Description Default
predictions
ndarray

Array of shape (N, 6) with columns [x1, y1, x2, y2, score, category_id].

required
match_metric
str

Overlap metric, "IOU" or "IOS".

'IOU'
match_threshold
float

Minimum overlap to merge a lower-scored box.

0.5

Returns:

Type Description
dict[int, list[int]]

Dict mapping each kept index to a list of indices merged into it.

Source code in sahi/postprocess/combine.py
def greedy_nmm(
    predictions: np.ndarray,
    match_metric: str = "IOU",
    match_threshold: float = 0.5,
) -> dict[int, list[int]]:
    """Greedy non-maximum merging for axis-aligned bounding boxes.

    Instead of discarding overlapping boxes, merges them into the
    highest-scored box. Dispatches to the resolved backend.

    Args:
        predictions: Array of shape (N, 6) with columns
            [x1, y1, x2, y2, score, category_id].
        match_metric: Overlap metric, "IOU" or "IOS".
        match_threshold: Minimum overlap to merge a lower-scored box.

    Returns:
        Dict mapping each kept index to a list of indices merged into it.
    """
    return _dispatch("greedy_nmm")(predictions, match_metric, match_threshold)

nmm(predictions, match_metric='IOU', match_threshold=0.5)

Non-maximum merging (non-greedy, transitive) for axis-aligned bounding boxes.

Unlike greedy NMM, this variant allows transitive merging: if box A merges with B and B merges with C, all three are merged together. Dispatches to the resolved backend.

Parameters:

Name Type Description Default
predictions
ndarray

Array of shape (N, 6) with columns [x1, y1, x2, y2, score, category_id].

required
match_metric
str

Overlap metric, "IOU" or "IOS".

'IOU'
match_threshold
float

Minimum overlap to merge a lower-scored box.

0.5

Returns:

Type Description
dict[int, list[int]]

Dict mapping each kept index to a list of indices merged into it.

Source code in sahi/postprocess/combine.py
def nmm(
    predictions: np.ndarray,
    match_metric: str = "IOU",
    match_threshold: float = 0.5,
) -> dict[int, list[int]]:
    """Non-maximum merging (non-greedy, transitive) for axis-aligned bounding boxes.

    Unlike greedy NMM, this variant allows transitive merging: if box A
    merges with B and B merges with C, all three are merged together.
    Dispatches to the resolved backend.

    Args:
        predictions: Array of shape (N, 6) with columns
            [x1, y1, x2, y2, score, category_id].
        match_metric: Overlap metric, "IOU" or "IOS".
        match_threshold: Minimum overlap to merge a lower-scored box.

    Returns:
        Dict mapping each kept index to a list of indices merged into it.
    """
    return _dispatch("nmm")(predictions, match_metric, match_threshold)

nms(predictions, match_metric='IOU', match_threshold=0.5)

Non-maximum suppression for axis-aligned bounding boxes.

Dispatches to the resolved backend (numpy, numba, or torchvision).

Parameters:

Name Type Description Default
predictions
ndarray

Array of shape (N, 6) with columns [x1, y1, x2, y2, score, category_id].

required
match_metric
str

Overlap metric, "IOU" or "IOS".

'IOU'
match_threshold
float

Minimum overlap to suppress a lower-scored box.

0.5

Returns:

Type Description
list[int]

List of indices of the kept predictions, sorted by score descending.

Source code in sahi/postprocess/combine.py
def nms(
    predictions: np.ndarray,
    match_metric: str = "IOU",
    match_threshold: float = 0.5,
) -> list[int]:
    """Non-maximum suppression for axis-aligned bounding boxes.

    Dispatches to the resolved backend (numpy, numba, or torchvision).

    Args:
        predictions: Array of shape (N, 6) with columns
            [x1, y1, x2, y2, score, category_id].
        match_metric: Overlap metric, "IOU" or "IOS".
        match_threshold: Minimum overlap to suppress a lower-scored box.

    Returns:
        List of indices of the kept predictions, sorted by score descending.
    """
    if len(predictions) == 0:
        return []
    return _dispatch("nms")(predictions, match_metric, match_threshold)