> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ovalbee.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ovalbee.com/_mcp/server.

# Filter annotations

> Removes or keeps individual labels of an OvalBee dataset by class, area share, box aspect, geometry, and object tag. It acts on each matching label and images stay in place, writing the images with their filtered annotations to a destination dataset. To move whole images instead, use Filter images.

## Overview

Removes the labels that match your conditions, or keeps only those, and writes every source image to a destination dataset with its filtered annotations.

The source dataset is never changed, so comparing it with the destination shows exactly what was cut. The badge counts the labels removed in the last run and opens the result.

```mermaid
flowchart LR
  n1["Dataset"] --> n2["Filter annotations"] --> n3["Dataset"]
```

## How it works

Checks every label of every annotation in the source dataset against the conditions you set, combined with **And** or with **Or**.

Drops the labels that match when **Matched labels** is **Remove**, or all the others when it is **Keep**.

Adds every source image to the destination dataset, whether or not any of its labels matched.

Writes one filtered annotation per source annotation, replacing whatever the destination held for those images, and refreshes the destination statistics.

## FAQ

#### How are several conditions combined?

The **Operator** input decides. With **And**, the default, a label matches only when it satisfies every condition you set. With **Or**, one is enough. Conditions left empty do not take part, and a single condition means the same thing either way. Setting no condition at all is a validation error rather than a match-everything run.

A range counts as one condition whether you fill one side of it or both. Split into two inputs they would read as "smaller than the minimum or larger than the maximum" under Or, which is every label there is.

#### What do the operator and the action do together?

Take `Has classes = person` and `Object area` bounded above at `0.05` on a dataset of people and helmets:

|            | And                                                    | Or                                                            |
| ---------- | ------------------------------------------------------ | ------------------------------------------------------------- |
| **Remove** | drops the tiny person labels only                      | drops every person label and every tiny label of any class    |
| **Keep**   | keeps the tiny person labels and drops everything else | keeps every person label and every tiny label, drops the rest |

Keep with narrow conditions is the destructive corner: everything that does not match goes, including untouched labels of other classes.

#### How do the range conditions work?

Fill both sides to bound a range from both ends, fill one side for "at least" or "at most", and leave both empty to switch the condition off.

#### Does this change the source dataset?

No. The destination holds the same images by reference, with new annotations, and the source keeps its own.

#### What if I don't select a destination dataset?

The node writes into its own output dataset, named `Filter annotations` - created on the first run, a new empty version of it on a rerun. A selected destination must differ from the source, otherwise the run fails.

#### What happens to images whose labels do not match?

Every image reaches the destination. With **Remove**, an image without a matching label keeps all of its labels. With **Keep**, it arrives with an empty annotation. Images that have no annotation in the source are added too, without one.

#### What happens when I run the node twice?

Without a selected destination, each rerun writes a new version of the node's own output dataset. With a destination you selected, the annotations it already holds for the source images are replaced by the ones this run produces, and missing images are added. Either way the result depends only on the source dataset and the conditions.

#### How is the object area measured?

As the share of the image the label covers, the same way the [Annotation quality](/node-library/nodes/dataset-ops-annotation-quality) report measures it: the geometry's own area where it has one, and its bounding box otherwise. A label whose annotation carries no image size cannot be measured and never matches an area condition.

#### When should I use Filter images instead?

Use [Filter images](/node-library/nodes/filter-filter-images) to move whole images that have a matching object. Use this node to clean up individual labels while every image stays in place.

## Inputs

**`Source dataset`** `dataset` — required

Dataset whose labels are filtered. Key: `SRC_DATASET`.

---

**`Destination dataset`** `dataset`

Where the images and their filtered labels go. Must differ from the source, and leaving it empty uses the node's own output dataset. Key: `DST_DATASET`.

---

**`Matched labels`** `select` — required, default: remove

Remove drops the matching labels. Keep drops all the others. Key: `MATCHED_LABELS`.

Options:

* Remove (`remove`)
* Keep (`keep`)

---

**`Operator`** `select` — required, default: and

And requires every condition below. Or requires just one of them. Key: `CONDITIONS_OPERATOR`.

Options:

* And (`and`)
* Or (`or`)

---

**`Has classes`** `ontology`

Classes the label must belong to. Empty means any class. Values come from `SRC_DATASET`. Key: `CLASSES`.

---

**`Object area (%)`** `range`

Share of the image the label covers, in percent. Minimum: `0`. Maximum: `100`. Step: `0.5`. Key: `OBJECT_AREA_PERCENT`.

---

**`Object box aspect`** `range`

How stretched the label is: its long side over its short side. 1 is a square. Minimum: `1`. Step: `0.1`. Key: `OBJECT_BOX_ASPECT`.

---

**`Geometry`** `select`

Shape the label is drawn with. Key: `OBJECT_GEOMETRY`.

Options:

* Rectangle (`rectangle`)
* Polygon (`polygon`)
* Bitmap (`bitmap`)
* Alpha mask (`alpha_mask`)
* Point (`point`)
* Line (`line`)
* Graph (`graph`)

---

**`Object tag`** `string`

Tag the label carries. Write key, or key=value to require a value. Key: `OBJECT_TAG`.

---

## Outputs

**`Dataset`** `dataset`

ID of the destination dataset holding the images and their filtered annotations. Shown as an artifact. Key: `DATASET`.

---

**`Last Removed Labels Count`** `string`

Short badge with the number of labels removed in the latest run. Shown on the node as a badge. Selecting the badge opens `DATASET`. Key: `LAST_REMOVED_LABELS_COUNT`.

---

## JSON config

Machine-readable node interface for automation and advanced usage.

#### Show JSON

```json
{
  "name": "Filter annotations",
  "description": "Removes or keeps individual labels of an OvalBee dataset by class, area share, box aspect, geometry, and object tag. It acts on each matching label and images stay in place, writing the images with their filtered annotations to a destination dataset. To move whole images instead, use Filter images.",
  "category": "Filter",
  "namespace": "ovalbee",
  "templateKey": "filter/filter_annotations",
  "version": "v1",
  "inputs": [
    {
      "key": "SRC_DATASET",
      "label": "Source dataset",
      "type": "dataset",
      "description": "Dataset whose labels are filtered.",
      "required": true,
      "default": null,
      "visibleWhen": null,
      "options": {
        "creatable": false
      }
    },
    {
      "key": "DST_DATASET",
      "label": "Destination dataset",
      "type": "dataset",
      "description": "Where the images and their filtered labels go. Must differ from the source, and leaving it empty uses the node's own output dataset.",
      "required": false,
      "default": null,
      "visibleWhen": null
    },
    {
      "key": "MATCHED_LABELS",
      "label": "Matched labels",
      "type": "select",
      "description": "Remove drops the matching labels. Keep drops all the others.",
      "required": true,
      "default": "remove",
      "visibleWhen": null,
      "options": {
        "options": [
          {
            "label": "Remove",
            "value": "remove"
          },
          {
            "label": "Keep",
            "value": "keep"
          }
        ]
      }
    },
    {
      "key": "CONDITIONS_OPERATOR",
      "label": "Operator",
      "type": "select",
      "description": "And requires every condition below. Or requires just one of them.",
      "required": true,
      "default": "and",
      "visibleWhen": null,
      "options": {
        "options": [
          {
            "label": "And",
            "value": "and"
          },
          {
            "label": "Or",
            "value": "or"
          }
        ]
      }
    },
    {
      "key": "CLASSES",
      "label": "Has classes",
      "type": "ontology",
      "description": "Classes the label must belong to. Empty means any class.",
      "required": false,
      "default": null,
      "visibleWhen": null,
      "options": {
        "ref": "SRC_DATASET",
        "selectable": true
      }
    },
    {
      "key": "OBJECT_AREA_PERCENT",
      "label": "Object area (%)",
      "type": "range",
      "description": "Share of the image the label covers, in percent.",
      "required": false,
      "default": null,
      "visibleWhen": null,
      "options": {
        "min": 0,
        "max": 100,
        "step": 0.5
      }
    },
    {
      "key": "OBJECT_BOX_ASPECT",
      "label": "Object box aspect",
      "type": "range",
      "description": "How stretched the label is: its long side over its short side. 1 is a square.",
      "required": false,
      "default": null,
      "visibleWhen": null,
      "options": {
        "min": 1,
        "step": 0.1
      }
    },
    {
      "key": "OBJECT_GEOMETRY",
      "label": "Geometry",
      "type": "select",
      "description": "Shape the label is drawn with.",
      "required": false,
      "default": null,
      "visibleWhen": null,
      "options": {
        "options": [
          {
            "label": "Rectangle",
            "value": "rectangle"
          },
          {
            "label": "Polygon",
            "value": "polygon"
          },
          {
            "label": "Bitmap",
            "value": "bitmap"
          },
          {
            "label": "Alpha mask",
            "value": "alpha_mask"
          },
          {
            "label": "Point",
            "value": "point"
          },
          {
            "label": "Line",
            "value": "line"
          },
          {
            "label": "Graph",
            "value": "graph"
          }
        ]
      }
    },
    {
      "key": "OBJECT_TAG",
      "label": "Object tag",
      "type": "string",
      "description": "Tag the label carries. Write key, or key=value to require a value.",
      "required": false,
      "default": null,
      "visibleWhen": null
    }
  ],
  "outputs": [
    {
      "key": "DATASET",
      "label": "Dataset",
      "type": "dataset",
      "description": "ID of the destination dataset holding the images and their filtered annotations.",
      "artifact": true,
      "badge": false,
      "badgeOpens": null,
      "hidden": false
    },
    {
      "key": "LAST_REMOVED_LABELS_COUNT",
      "label": "Last Removed Labels Count",
      "type": "string",
      "description": "Short badge with the number of labels removed in the latest run.",
      "artifact": false,
      "badge": true,
      "badgeOpens": "DATASET",
      "hidden": false
    }
  ]
}
```