formats¶
Readers and writers for the five annotation formats, behind hbb2obb-convert and hbb2obb-view. Every format reads into, and writes from, one representation: a FrameAnnotations per frame holding a Box per object, with four corners in absolute pixels. A horizontal box is a Box whose corners are axis-aligned. The guide has a worked example.
from pathlib import Path
from hbb2obb import formats
frames, names, fmt = formats.read_set(Path("data/labels_obb_gt"), sizes=formats.image_sizes(Path("data/images")))
box = frames[0].boxes[0]
print(box.cls, box.quad, box.xyxy, box.is_axis_aligned)
Representation¶
Box
dataclass
¶
A single annotation: four corners in absolute pixel coordinates.
Source code in hbb2obb/formats.py
FrameAnnotations
dataclass
¶
canonicalize
¶
canonicalize(frames: Iterable[FrameAnnotations], precision: int = DEFAULT_PRECISION) -> List[FrameAnnotations]
Round every quad once, so that all output formats derive from one source.
This is what keeps the integer formats consistent with each other and with the envelope of the
box they came from. Rounding is monotonic, so min(round(x)) == round(min(x)) holds only when
both sides round the same values; deriving one format from full precision and another from a
rounded canonical is what makes them disagree on boxes that land exactly on a half pixel.
Source code in hbb2obb/formats.py
Whole sets¶
read_set
¶
read_set(source: Path, fmt: Optional[str] = None, names: Optional[Sequence[str]] = None, sizes: Optional[Dict[str, Tuple[int, int]]] = None, default_size: Optional[Tuple[int, int]] = None) -> Tuple[List[FrameAnnotations], List[str], str]
Read a whole annotation set. Returns its frames, the class names, and the detected format.
source is a directory of per-frame files, or the .json file itself for COCO. Image sizes
come from sizes (keyed by frame stem), from default_size, or from the format itself
where it records them; they are needed to denormalize relative YOLO coordinates and to write
the formats that declare an image size.
Source code in hbb2obb/formats.py
write_set
¶
write_set(frames: Sequence[FrameAnnotations], dest: Path, fmt: str, names: Sequence[str], kind: str, normalize: bool = False, precision: Optional[int] = None, dota_ext: str = DOTA_EXT, image_ext: str = '.jpg', coco_name: Optional[str] = None, identity: Optional[CocoIdentity] = None, **kwargs) -> List[Path]
Write a whole annotation set in one format. Returns the paths written.
Source code in hbb2obb/formats.py
verify
¶
verify(sets: Dict[str, List[FrameAnnotations]], reference: str = 'yolo') -> List[str]
Check that several readings of the same annotations agree, and report every disagreement.
The reference format carries full precision and the others are one common rounding of it, so
the comparison is exact equality after rounding rather than a tolerance. difficult is only
compared between the formats that can express it.
Source code in hbb2obb/formats.py
detect_format
¶
Guess the format of a label file or of a directory of them, raising if it cannot.
A directory is resolved in ALL_FORMATS order rather than in file order, so that YOLO wins
wherever a set ships its canonical files beside derived ones: the derived formats are rounded
and cannot carry a confidence, so reading them by alphabetical accident loses information.
Source code in hbb2obb/formats.py
formats_present
¶
Every format holding label files under a directory, in ALL_FORMATS order.
A published set usually ships the same boxes more than once: the canonical YOLO files, a DOTA copy beside them, a COCO record at the subset root. This names all of them, so that a reader can say which one it picked and offer the rest. For a single file it is the one format that file is, and for anything unreadable it is empty.
Source code in hbb2obb/formats.py
COCO identifiers¶
CocoIdentity
dataclass
¶
Identifiers and record metadata lifted from an existing COCO file, to be re-used by a new one.
A converted set is usually a derivative: one box per box of some source annotation, in the same
order. write_coco numbers images and annotations 1..N, which breaks the join back to
that source, and it has no way to know what record the output belongs to, so it writes an empty
info and licenses. Reading them off the source file is what keeps a derived release
traceable, and the match is positional: box k of a frame is the same object in both files.
info is read here for a caller that wants it, but write_coco does not carry it into the
output: it describes the record being written, and a derivative is not its source.
Source code in hbb2obb/formats.py
read_coco_identity
¶
read_coco_identity(path: Path) -> CocoIdentity
Read a COCO file for its identifiers and record metadata, not its geometry.
Annotations are grouped by frame in the order the file lists them, which is the order
write_coco writes and the order a one-box-per-box conversion preserves.
Source code in hbb2obb/formats.py
check_coco_identity
¶
check_coco_identity(identity: CocoIdentity, frames: Sequence[FrameAnnotations], names: Sequence[str]) -> List[str]
Whether identity really describes frames, row for row. Returns the problems found.
Checked before anything is written, because a positional map applied to the wrong rows produces a file that is wrong in a way nothing downstream can detect.
Source code in hbb2obb/formats.py
Images¶
image_size
¶
Read an image's (width, height) from its header, without decoding it.
Only the size is ever needed here, to denormalize relative coordinates and to fill in the formats that declare one, and decoding a directory of 4K frames to learn it is pure waste. Falls back to OpenCV for anything the three header layouts below do not cover.
A JPEG's Exif orientation is applied, because cv2.imread applies it too: the size reported
here has to be the size of the frame the boxes were fitted in, or every coordinate normalized
against it is divided by the wrong dimension.