Geometry API Reference¶
oriented_det.geometry
¶
Geometry primitives and conversion helpers for oriented detection.
Polygon
dataclass
¶
Immutable polygon helper with a small but focused API.
Source code in oriented_det/geometry/poly.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | |
QBox
dataclass
¶
Simple quadrilateral box with geometry helpers.
Source code in oriented_det/geometry/qbox.py
RBox
dataclass
¶
Immutable representation of a rotated rectangle.
Source code in oriented_det/geometry/rbox.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | |
from_polygon(polygon)
classmethod
¶
Create RBox from a quadrilateral polygon.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
polygon
|
Polygon
|
Polygon with exactly 4 points |
required |
Returns:
| Type | Description |
|---|---|
'RBox'
|
RBox representing the polygon |
Raises:
| Type | Description |
|---|---|
ValueError
|
If polygon doesn't have exactly 4 points |
Source code in oriented_det/geometry/rbox.py
from_xyxytheta(x1, y1, x2, y2, theta)
classmethod
¶
Create RBox from axis-aligned bounding box and rotation angle.
This is useful when you have an axis-aligned box (x1, y1, x2, y2) and a rotation angle theta.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x1
|
float
|
X coordinate of the top-left corner of the axis-aligned box. |
required |
y1
|
float
|
Y coordinate of the top-left corner of the axis-aligned box. |
required |
x2
|
float
|
X coordinate of the bottom-right corner of the axis-aligned box. |
required |
y2
|
float
|
Y coordinate of the bottom-right corner of the axis-aligned box. |
required |
theta
|
float
|
Rotation angle in radians. |
required |
Returns:
| Type | Description |
|---|---|
'RBox'
|
RBox with center at the center of the bounding box, |
'RBox'
|
dimensions from the bounding box, and the specified angle |
Example
Axis-aligned box from (10, 20) to (50, 60) rotated 30°¶
rbox = RBox.from_xyxytheta(10, 20, 50, 60, math.radians(30))
rbox.cx = 30, rbox.cy = 40, rbox.width = 40, rbox.height = 40¶
Source code in oriented_det/geometry/rbox.py
is_valid(min_size=1.0)
¶
Check if this RBox is valid (not degenerated).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
min_size
|
float
|
Minimum width and height in pixels (default: 1.0) |
1.0
|
Returns:
| Type | Description |
|---|---|
bool
|
True if RBox is valid, False if degenerated |
Source code in oriented_det/geometry/rbox.py
normalize_le90(rbox)
¶
Normalize RBox angle to [-π/2, π/2) using le90 convention.
The "le90" (long edge 90°) convention ensures that: - The angle is in [-π/2, π/2) - Width is always >= height (swaps dimensions if needed) - This makes boxes with the same orientation have the same representation
The algorithm: 1. First normalize angle to [-π/2, π/2) by adding/subtracting integer multiples of π (without swapping width/height). 2. If width < height, swap dimensions and adjust angle by π/2. 3. Normalize angle again while preserving width >= height.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
rbox
|
RBox
|
Input RBox to normalize |
required |
Returns:
| Type | Description |
|---|---|
RBox
|
Normalized RBox with angle in [-π/2, π/2) and width >= height |
Example
rbox = RBox(100, 100, 50, 100, math.pi) # width < height, angle = π normalized = normalize_le90(rbox)
normalized.width = 100, normalized.height = 50, normalized.angle ≈ -π/2¶
Source code in oriented_det/geometry/rbox.py
Important Notes¶
Angle Convention¶
The geometry module uses a full circle angle representation by default:
- Range: Angles are normalized to
(-π, π]interval - Direction: Positive angles rotate counter-clockwise (mathematical convention)
- Origin: Top-left corner of the image (x-axis right, y-axis down)
- Normalization: Angles are automatically normalized using
atan2(sin, cos)to handle periodicity
le90 Convention¶
For DOTA compatibility, use the normalize_le90() function to convert to the "long edge 90°" convention:
- Range:
[-π/2, π/2) - Constraint: Width is always >= height (dimensions are swapped if needed)
- Usage: Ensures boxes with the same orientation have the same representation
from oriented_det.geometry import RBox, normalize_le90
import math
rbox = RBox(100, 100, 50, 100, math.pi) # width < height, angle = π
normalized = normalize_le90(rbox)
# normalized.width = 100, normalized.height = 50, normalized.angle ≈ -π/2
Round-trip Conversions¶
All conversions between Polygon, QBox, and RBox are designed to be lossless within floating-point precision:
from oriented_det.geometry import RBox, transforms
import math
rbox = RBox(10.0, -3.0, 5.0, 2.0, math.radians(30))
qbox = transforms.rbox_to_qbox(rbox)
recovered = transforms.qbox_to_rbox(qbox)
assert abs(recovered.cx - rbox.cx) < 1e-6
assert abs(recovered.angle - rbox.angle) < 1e-6
DOTA Polygon Format¶
When working with DOTA annotations, polygons use 8 coordinates:
The loader automatically:
1. Parses polygons from annotation files
2. Converts to QBox (normalizes point order, ensures counter-clockwise)
3. Converts to RBox (computes center, dimensions, angle)
Examples¶
Creating Geometric Primitives¶
from oriented_det.geometry import Polygon, QBox, RBox
from math import radians
# Polygon from points
poly = Polygon([(0, 0), (2, 0), (2, 1), (0, 1)])
# RBox with rotation
rbox = RBox(cx=512, cy=384, width=120, height=48, angle=radians(20))
# QBox from 4 points
qbox = QBox([(0, 0), (4, 0), (4, 2), (0, 2)])
Geometric Operations¶
# Translation
translated = poly.translate(dx=10, dy=20)
# Rotation around origin
rotated = poly.rotate(radians=0.5, origin=(0, 0))
# Ensure counter-clockwise orientation
ccw_poly = poly.ensure_orientation(clockwise=False)
Conversions¶
from oriented_det.geometry import transforms
# RBox ↔ QBox
qbox = transforms.rbox_to_qbox(rbox)
rbox = transforms.qbox_to_rbox(qbox)
# RBox ↔ Polygon
polygon = transforms.rbox_to_polygon(rbox)
rbox = transforms.polygon_to_rbox(polygon)
# QBox ↔ Polygon
polygon = transforms.qbox_to_polygon(qbox)
qbox = transforms.polygon_to_qbox(polygon)