# Interface for selecting multiple bounding boxes

**URL:** https://support.prodi.gy/t/interface-for-selecting-multiple-bounding-boxes/7557
**Category:** Uncategorized
**Tags:** enhancement, usage, custom
**Created:** [May 6, 2025, 8:55am UTC](https://support.prodi.gy/t/interface-for-selecting-multiple-bounding-boxes/7557 "2025-05-06T08:55:10Z")
**Posts on this page:** 1
**Showing post:** 2

<div class="post-metadata">

### Author: ![magdaaniol](https://sea2.discourse-cdn.com/flex020/user_avatar/support.prodi.gy/magdaaniol/32/2787_2.png) [@magdaaniol](https://support.prodi.gy/u/magdaaniol)
#### Post date: [May 6, 2025, 1:54pm UTC](https://support.prodi.gy/t/interface-for-selecting-multiple-bounding-boxes/7557/2 "2025-05-06T13:54:32Z")

</div>

Hi @tuomo_h!

I imagine that clickable bounding boxes would be ideal for this use case. This is actually a feature that we're [currently working on](https://support.prodi.gy/t/implementing-clickable-pdf-annotations-in-pdf-spans-manual/7519/3).

In the meantime adding a "GROUP" label for annotators to draw a bounding box around the items they want to group simplifies the immediate UI challenge a whole lot:  
 ![cats_groups](https://us1.discourse-cdn.com/flex020/uploads/prodigy/original/2X/7/718f3a775482ff669af4ff9788030b2567ced198.gif)

You could then use the `before_db` callback to process the spans and find the ones contained in the GROUP box. Since the `rectangular` and `freehand` bounding boxes do not have the centers calculated, I used `shapely` package to quickly check the containment. If all your boxes are rectangular you don't really need it:

```python
# your custom recipe with image_manual UI
import uuid
from shapely.geometry import Polygon, Point

def process_span_groups(annotation):
        """
        Adds a unique ID to each span and resolves "GROUP" labeled spans
        into a "span_groups" field, handling rectangles, polygons, and freehand.

        Args:
            annotation (dict): The annotated example.

        Returns:
            dict: The updated annotation dictionary with unique span IDs and the "span_groups" field.
        """
        if "spans" not in annotation:
            return annotation

        # Add a unique ID to each span
        for span in annotation["spans"]:
            span["id"] = uuid.uuid4().hex

        group_spans = [span for span in annotation["spans"] if span.get("label") == "GROUP"]
        individual_spans = [span for span in annotation["spans"] if span.get("label") != "GROUP"]

        span_groups = []
        for group_span in group_spans:
            contained_span_ids = []
            group_points = group_span.get("points")
            group_type = group_span.get("type")

            if not group_points:
                continue # Skip if the group span has no points

            group_geom = Polygon(group_points)
         

            for individual_span in individual_spans:
                individual_points = individual_span.get("points")
                individual_type = individual_span.get("type")

                if not individual_points:
                    continue

                try:
                    if individual_type in ["rect", "polygon", "freehand"]:
                        # For simplicity, we'll check if the *center* of the bounding box
                        # of the individual span falls within the group span.
                        # For polygons and freehand, calculating the exact bounding box center.
                        if individual_type == "rect":
                            center_x = individual_span["x"] + individual_span["width"] / 2
                            center_y = individual_span["y"] + individual_span["height"] / 2
                        else:
                            min_x = min(p[0] for p in individual_points)
                            max_x = max(p[0] for p in individual_points)
                            min_y = min(p[1] for p in individual_points)
                            max_y = max(p[1] for p in individual_points)
                            center_x = (min_x + max_x) / 2
                            center_y = (min_y + max_y) / 2

                        individual_center = Point(center_x, center_y)
                        if group_geom.contains(individual_center):
                            contained_span_ids.append(individual_span["id"])

                    else:
                        print(f"Warning: Unknown individual span type '{individual_type}'. Skipping.")
                        continue
                except Exception as e:
                    print(f"Error processing individual span with Shapely: {e}. Skipping.")
                    continue

            if contained_span_ids:
                span_groups.append({
                    "label": "GROUP",
                    "color": group_span.get("color"),
                    "spans": contained_span_ids
                })
        if span_groups:
            annotation["span_groups"] = span_groups

        return annotation

```

and then in `before_db` callback:

```python
def before_db(examples: List[TaskType]) -> List[TaskType]:
    for eg in examples:
        if remove_base64 and eg["image"].startswith("data:"):
            eg["image"] = eg.get("path")
        # Process the groups if they exist in the submitted data
        process_span_groups(eg) 
        if "span_groups" in eg:
            # You could add validation or transformation logic here
            pass
     return examples

```

This should then result in the following DB record:

```bash
"span_groups": [
    {
      "label": "GROUP",
      "color": "springgreen",
      "spans": [
        "bec0e831b1a64974a5521c099ae26b89",
        "d1712ee4e05b4ba4b7e6bdc17e0b66cb",
        "34ac6ec3322e4cec92d7ca4e099b09f5"
      ]
    }

```

Where spans are IDs you've assigned to each annotated span.

This of course assumes that's it's practical to draw such GROUP boxes and the centers are a good indicator of containment. If it is, it would simplify the challenge a lot.

---

_[View the full topic](https://support.prodi.gy/t/interface-for-selecting-multiple-bounding-boxes/7557)._
