Skip to content

[ENH]: Add OrientedRectangleSelector (interactive rotated rectangle selector) #30442

@saranmahadev

Description

@saranmahadev

Problem

Matplotlib’s current RectangleSelector only supports axis-aligned rectangles. Many domains (image annotation, computer vision, OCR, remote sensing, microscopy, UI layout tools) need oriented/rotated bounding boxes with interactive resize/rotate/translate. Today, users hack around this with PolygonSelector (4 points) or custom event handlers, which leads to inconsistent UX, no angle snapping, and a lot of duplicated code.

Proposed solution

Introduce a new widget: OrientedRectangleSelector, an interactive selector that behaves like RectangleSelector but supports arbitrary rotation. It provides:

  • Corner and edge handles for resizing (with optional aspect-ratio lock).
  • A rotation handle with optional angle snapping.
  • Dragging the center to translate.
  • Live callbacks during interaction and a final onselect on release.
  • Visual feedback (cursors, handles) and optional blitting for performance.

This mirrors the mental model of RectangleSelector while adding rotation.

High-level API

from matplotlib.widgets import OrientedRectangleSelector

ors = OrientedRectangleSelector(
    ax,
    onselect=None,              # called on mouse release with final params
    onmove_callback=None,       # called during interaction with live params
    *,
    useblit=False,
    button=None,
    minspanx=0, minspany=0,
    spancoords="data",
    maxdist=10,                 # handle hit test (px)
    snap_angle=True,
    angle_snap_increment=1.0,   # degrees
    maintain_aspect_ratio=False,
    min_size=0.02,              # in data units
    initial_center=(0.5, 0.5),
    initial_width=0.3,
    initial_height=0.2,
    initial_angle=0.0,          # degrees
    handle_props=None,          # dict for corner/edge/rotate/center styles
    line_props=None,            # rectangle edge props
    state_modifier_keys=None,   # {'rotate': 'shift', 'aspect_ratio': 'control', 'snap': 'alt'}
)

Returned/Callback params

{
    "center": np.ndarray([cx, cy]),
    "width": float,
    "height": float,
    "angle": float,             # degrees
    "corners": np.ndarray(shape=(4,2)),  # BL, BR, TR, TL in data coords
    "area": float
}

Programmatic control

ors.set_rectangle(center=(x, y), width=w, height=h, angle=a)
params = ors.get_rectangle_params()
ors.update_properties({"snap_angle": False, "min_size": 0.05})

Example usage

fig, ax = plt.subplots()
ax.imshow(img, cmap="gray")

def onmove(params):
    # live feedback (e.g., show crop/metrics)
    pass

def ondone(params):
    print(params["center"], params["width"], params["height"], params["angle"])

ors = OrientedRectangleSelector(ax, onselect=ondone, onmove_callback=onmove,
                                snap_angle=True, angle_snap_increment=1.0)
plt.show()

UX details

  • Handles: 4 corners, 4 edge midpoints, 1 rotation handle, 1 center “+”.
  • Cursors: move/resize/rotate cursors based on hover target.
  • Snapping: configurable granularity (default 1°; can be 15° etc.).
  • Constraints: configurable minimum size; easy to add “keep inside axes” later.
Image

Implementation notes

A reference implementation is attached (ready to adapt to Matplotlib conventions):

  • Uses a Rectangle patch drawn at the origin and transformed via Affine2D (rotate + translate) for numerical stability.
  • Hit-testing in data space; handle detection uses pixel tolerance.
  • Optional blitting for smooth interaction.
  • Clean separation of interaction modes: translate, rotate, resize-corner, resize-edge.
  • Public methods mirror RectangleSelector where possible; extras are additive.

Backward compatibility

No breakage. New widget, opt-in. Naming follows RectangleSelector. Consider adding an alias RotatedRectangleSelector for searchability.

Performance

Blitting support keeps interaction smooth; complexity is similar to existing selectors. Handle count is fixed (small); transform math is minimal.

Testing

  • Unit tests for:
    • Local↔world transform correctness.
    • Angle snapping correctness.
    • Aspect-ratio lock.
    • Min-size constraint.
  • Image tests for handle placement and rotation rendering.
  • Event simulation tests (press/motion/release) to validate callbacks.

Documentation

  • User guide with interactive examples.
  • API reference with detailed parameter descriptions.
  • Changelog for version updates.

[I am ready to do that!]

Future extensions (non-blocking)

  • Constrain rectangle within axes/view limits.
  • Keyboard nudging & precise numeric entry.
  • Multi-rectangle manager as a helper (selection, add/remove).
  • Snapping to guide lines / other artists.

Note: I have more ideas for this widget and ready to take full ownership and I can implement them.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions