A ship in DOTA is four corners, written as eight coordinates. The detector stores four things: a center, a long side, a short side, and one angle.
That convention is called le90. Width is the long edge. The angle of that edge sits between −90° and 90°. A vertical ship is −90°. Every Hub name ending in _le90_ uses this.
How those eight coordinates become those four numbers is below.

Five numbers, and why they are not unique Link to heading
An RBox is (cx, cy, width, height, angle).
The image origin is the top-left corner. +x points right. +y points down. The angle is radians. A small positive angle swings the width edge from +x toward +y. On the screen that is down and to the right, so the box looks clockwise. The same rotation matrix is counter-clockwise when y points up, which is why the geometry notes still say “mathematical counter-clockwise.” Training does not use that y-up helper. It uses the image-space flips and rotates in oriented_det.data, then writes the box back to le90.
A rectangle does not care which way is the bow. Turning it by 180° is the same pixels: (w, h, θ) and (w, h, θ+π) draw one box. Swapping the sides and adding 90° is the same pixels again: (w, h, θ) and (h, w, θ+π/2). A regression head that is free to emit either form spends capacity on a tie. le90 cuts the tie.
The rule Link to heading
normalize_le90 does two things, in this order:
- Fold the angle into [-π/2, π/2) by steps of π. A half-turn does not swap the sides.
- If width is shorter than height, swap them and add π/2, then fold the angle back into the same interval.
After that, width is the long edge (or either edge, when the box is square), and the angle is the heading of that long edge. The interval is closed on the left and open on the right. A vertical long edge is −90°, not +90°. Both +π/2 and −π/2 land on −π/2.
Two parameter vectors for one 80×20 box at 30°:
| Stored before normalize | After normalize_le90 |
|---|---|
| width 80, height 20, angle +30° | 80 × 20 at +30° |
| width 20, height 80, angle +120° | 80 × 20 at +30° |
The docstring case is the vertical one. RBox(100, 100, 50, 100, π) — short side called “width,” angle 180° — becomes width 100, height 50, angle −π/2.
Loaders call this on the way into the batch. Flips and random rotate call it again on the way out. A checkpoint and a label file only line up when both sides have been through the same function.
From eight DOTA coordinates Link to heading
A DOTA line is a quad, then a class, then a difficult flag:
x1, y1, x2, y2, x3, y3, x4, y4, category, difficult
The corners walk the perimeter. They are not required to be a perfect rectangle, and they are not required to start on the long edge. The loader builds a QBox:
- The polygon is wound counter-clockwise in the shoelace sense (positive area with the usual formula). On an image, that vertex order is the one the library calls counter-clockwise.
- The first vertex is the top-most point. A tie breaks to the left-most.
- The angle is
atan2of that first edge. - Width is the mean length of that edge and the edge opposite it. Height is the mean of the other pair.
A skewed quad becomes a rectangle at that step. The original eight coordinates are not what the loss sees. Then normalize_le90 runs, so the long edge is width even if the quad’s first edge was the short one.
HRSC XML mbox angles, FAIR1M polygons, SSDD corners, and HRSID polygons take the same path. HRSID outlines with more than four points become a minimum-area rectangle first, then le90. The HRSID note is that conversion; this post is the angle rule underneath it.
Flips have to keep the long edge Link to heading
A horizontal mirror sends θ to −θ. A vertical mirror sends θ to π − θ. Both can push the angle outside [-90°, 90°) or leave the short side in the width slot, so the flipped box is normalized again.
The diagonal flip is the one that does not touch the angle. MMRotate mirrors the center and returns. oriented-det used to apply π − θ there. That is a real heading error on every diagonally flipped ship, and it was fixed before the v0.3 zoo. The v0.3.1 note records the patch. If you implement your own aug outside oriented_det.data, copy flip_diagonal: move (cx, cy), leave θ, then normalize_le90.
geometry.transforms.rotate is the other trap. It is rbox math in a y-up frame. Rotating a PIL image with that angle, without negating it, turns the pixels one way and the boxes the other. The training collate uses apply_rotate_to_image / apply_rotate_to_rboxes and normalizes afterward.
What does not belong in a le90 slot Link to heading
OpenCV minAreaRect. Its angle and its choice of which side is width are a different convention, historically in a range near [-90°, 0°]. Pasting (width, height, angle) from that call into a DOTA recipe rotates the box by a quarter turn on some headings. Convert the four corners through QBox, then normalize_le90.
le135. MMRotate also defines a long-edge box whose angle sits in [-45°, 135°). oriented-det recipes and Hub weights are le90. A le135 checkpoint is not a drop-in for odet train.
Bow versus stern. le90 identifies a rectangle, not a directed heading. A ship at 20° and the same ship labeled 200° are one box. If the product needs a vector, that is another head.
Where you meet it Link to heading
The string le90 in oriented_rcnn_dota_le90_1x and in rotated_rtmdet_dota_le90_3x is this convention, not a dataset version. Regression targets, the decoded boxes in predictions.json, and the pre-NMS ONNX graph all speak it. The ONNX walkthrough consumes those boxes with a rotated NMS in Python; the angle in the graph output is still le90. v0.4.0 uses the same five numbers.
Square-ish objects hide the swap. Ships, harbors, and bridges do not. If two overlays disagree by about 90° and the long side has swapped, you are looking at a missed normalize_le90, not at a bad detector.
Links Link to heading
- Geometry user guide ·
normalize_le90 - Tutorial 02 — boxes, angles, NMS
- Lessons learned on DOTA — a few degrees on a thin box is an IoU failure
- Previous: OSSDD — Sentinel-1 ships · ONNX export
- Next: Oriented mAP (22 Oct) — what to report once the boxes are in one convention