Skip to content

COCO

Load and query COCO-format datasets.

from hotcoco import COCO

coco = COCO("instances_val2017.json")
use hotcoco::COCO;
use std::path::Path;

let coco = COCO::new(Path::new("instances_val2017.json"))?;

Constructor

COCO(annotation_file: str | None = None)
Parameter Type Default Description
annotation_file str | None None Path to a COCO JSON annotation file. None creates an empty instance.
COCO::new(annotation_file: &Path) -> Result<Self, Box<dyn Error>>
COCO::from_dataset(dataset: Dataset) -> Self
Parameter Type Description
annotation_file &Path Path to a COCO JSON annotation file
dataset Dataset A pre-built Dataset struct (for from_dataset)

Properties

dataset

The full dataset with images, annotations, and categories.

coco = COCO("instances_val2017.json")
print(len(coco.dataset["images"]))       # 5000
print(len(coco.dataset["annotations"]))  # 36781
let coco = COCO::new(Path::new("instances_val2017.json"))?;
println!("{}", coco.dataset.images.len());       // 5000
println!("{}", coco.dataset.annotations.len());  // 36781

Methods

get_ann_ids

Get annotation IDs matching the given filters. All filters are ANDed together.

get_ann_ids(
    img_ids: list[int] = [],
    cat_ids: list[int] = [],
    area_rng: list[float] | None = None,
    iscrowd: bool | None = None,
) -> list[int]
Parameter Type Default Description
img_ids list[int] [] Filter by image IDs (empty = all)
cat_ids list[int] [] Filter by category IDs (empty = all)
area_rng list[float] | None None Filter by area range [min, max]
iscrowd bool | None None Filter by crowd flag
ann_ids = coco.get_ann_ids(img_ids=[42], cat_ids=[1])

camelCase alias

Also available as getAnnIds().

fn get_ann_ids(
    &self,
    img_ids: &[u64],
    cat_ids: &[u64],
    area_rng: Option<[f64; 2]>,
    is_crowd: Option<bool>,
) -> Vec<u64>
Parameter Type Description
img_ids &[u64] Filter by image IDs (empty = all)
cat_ids &[u64] Filter by category IDs (empty = all)
area_rng Option<[f64; 2]> Filter by area range [min, max]
is_crowd Option<bool> Filter by crowd flag
let ann_ids = coco.get_ann_ids(&[42], &[1], None, None);

get_cat_ids

Get category IDs matching the given filters.

get_cat_ids(
    cat_nms: list[str] = [],
    sup_nms: list[str] = [],
    cat_ids: list[int] = [],
) -> list[int]
Parameter Type Default Description
cat_nms list[str] [] Filter by category names
sup_nms list[str] [] Filter by supercategory names
cat_ids list[int] [] Filter by category IDs
cat_ids = coco.get_cat_ids(cat_nms=["person", "dog"])

camelCase alias

Also available as getCatIds().

fn get_cat_ids(&self, cat_nms: &[&str], sup_nms: &[&str], cat_ids: &[u64]) -> Vec<u64>
let cat_ids = coco.get_cat_ids(&["person", "dog"], &[], &[]);

get_img_ids

Get image IDs matching the given filters.

get_img_ids(
    img_ids: list[int] = [],
    cat_ids: list[int] = [],
) -> list[int]
Parameter Type Default Description
img_ids list[int] [] Filter by image IDs
cat_ids list[int] [] Filter by category IDs (images containing these categories)
img_ids = coco.get_img_ids(cat_ids=[1])

camelCase alias

Also available as getImgIds().

fn get_img_ids(&self, img_ids: &[u64], cat_ids: &[u64]) -> Vec<u64>
let img_ids = coco.get_img_ids(&[], &[1]);

load_anns

Load annotations by their IDs.

load_anns(ids: list[int]) -> list[dict]

Returns annotation dicts with keys like id, image_id, category_id, bbox, area, segmentation, iscrowd.

anns = coco.load_anns([101, 102, 103])
print(anns[0]["bbox"])  # [x, y, width, height]

camelCase alias

Also available as loadAnns().

fn load_anns(&self, ids: &[u64]) -> Vec<&Annotation>

Returns references to Annotation structs.

let anns = coco.load_anns(&[101, 102, 103]);
println!("{:?}", anns[0].bbox);  // [x, y, width, height]

load_cats

Load categories by their IDs.

load_cats(ids: list[int]) -> list[dict]

Returns category dicts with keys id, name, supercategory.

cats = coco.load_cats([1, 2, 3])
print(cats[0]["name"])  # "person"

camelCase alias

Also available as loadCats().

fn load_cats(&self, ids: &[u64]) -> Vec<&Category>
let cats = coco.load_cats(&[1, 2, 3]);
println!("{}", cats[0].name);  // "person"

load_imgs

Load images by their IDs.

load_imgs(ids: list[int]) -> list[dict]

Returns image dicts with keys like id, file_name, width, height.

imgs = coco.load_imgs([42])
print(f"{imgs[0]['width']}x{imgs[0]['height']}")

camelCase alias

Also available as loadImgs().

fn load_imgs(&self, ids: &[u64]) -> Vec<&Image>
let imgs = coco.load_imgs(&[42]);
println!("{}x{}", imgs[0].width, imgs[0].height);

load_res

Load detection results into a new COCO object. Images and categories are copied from the ground truth. Missing fields (area, segmentation) are computed automatically.

load_res(res: str | list[dict] | np.ndarray) -> COCO

Three input formats are accepted:

JSON file path:

coco_dt = coco_gt.load_res("detections.json")

List of dicts (in-memory results):

coco_dt = coco_gt.load_res([
    {"image_id": 42, "category_id": 1, "bbox": [10, 20, 100, 80], "score": 0.95},
])

NumPy array — shape (N, 7) with columns [image_id, x, y, w, h, score, category_id], or (N, 6) with category_id defaulting to 1. Array must be float64. Matches pycocotools loadNumpyAnnotations convention:

arr = np.array([[42, 10, 20, 100, 80, 0.95, 1]], dtype=np.float64)
coco_dt = coco_gt.load_res(arr)

camelCase alias

Also available as loadRes().

// From a file
fn load_res(&self, res_file: &Path) -> Result<COCO, Box<dyn Error>>

// From in-memory annotations
fn load_res_anns(&self, anns: Vec<Annotation>) -> Result<COCO, Box<dyn Error>>
let coco_dt = coco_gt.load_res(Path::new("detections.json"))?;
let coco_dt = coco_gt.load_res_anns(my_annotations)?;

Tip

load_res automatically computes missing fields: area from bounding boxes or segmentation masks, and polygon segmentations from bbox results. This matches pycocotools behavior.


ann_to_rle

Convert an annotation to RLE format.

ann_to_rle(ann: dict) -> dict

Returns an RLE dict with "counts" (str) and "size" ([h, w]).

ann = coco.load_anns([101])[0]
rle = coco.ann_to_rle(ann)
print(rle.keys())  # dict_keys(['counts', 'size'])

camelCase alias

Also available as annToRLE().

fn ann_to_rle(&self, ann: &Annotation) -> Option<Rle>

Returns an Rle struct with h, w, and counts fields.

let ann = &coco.load_anns(&[101])[0];
if let Some(rle) = coco.ann_to_rle(ann) {
    println!("{}x{}", rle.h, rle.w);
}

ann_to_mask

Convert an annotation to a binary mask.

ann_to_mask(ann: dict) -> numpy.ndarray

Returns a binary mask of shape (h, w), dtype uint8.

ann = coco.load_anns([101])[0]
mask = coco.ann_to_mask(ann)
print(mask.shape)  # (height, width)

camelCase alias

Also available as annToMask().

fn ann_to_mask(&self, ann: &Annotation) -> Option<Vec<u8>>

Returns a flat Vec<u8> in column-major order (h * w pixels).

let ann = &coco.load_anns(&[101])[0];
if let Some(mask) = coco.ann_to_mask(ann) {
    println!("pixels: {}", mask.len());
}

stats

Compute dataset health-check statistics: annotation counts, image dimensions, annotation area distribution, and per-category breakdowns.

stats() -> dict

Returns a dict with the following structure:

Key Type Description
image_count int Total number of images
annotation_count int Total number of annotations
category_count int Number of categories
crowd_count int Number of crowd annotations (iscrowd=1)
per_category list[dict] Per-category stats, sorted by ann_count descending
image_width dict Width summary stats (min, max, mean, median)
image_height dict Height summary stats
annotation_area dict Area summary stats

Each per_category entry has keys id, name, ann_count, img_count, crowd_count.

s = coco.stats()
print(s["image_count"])        # 5000
print(s["annotation_count"])   # 36781

for cat in s["per_category"][:5]:
    print(f"{cat['name']}: {cat['ann_count']} annotations")
fn stats(&self) -> DatasetStats

Returns a DatasetStats struct with fields mirroring the Python dict.

let s = coco.stats();
println!("{} images", s.image_count);
println!("{} annotations", s.annotation_count);
for cat in &s.per_category {
    println!("{}: {} anns", cat.name, cat.ann_count);
}

Dataset Operations

The following methods reshape or subset a dataset, returning a new COCO object. The original is never modified. See the Dataset Operations guide for worked examples.


filter

Subset the dataset by category, image, and/or annotation area. All criteria are ANDed.

filter(
    cat_ids: list[int] | None = None,
    img_ids: list[int] | None = None,
    area_rng: list[float] | None = None,
    drop_empty_images: bool = True,
) -> COCO
Parameter Type Default Description
cat_ids list[int] | None None Keep only these category IDs
img_ids list[int] | None None Keep only these image IDs
area_rng list[float] | None None Area range [min, max] (inclusive)
drop_empty_images bool True Remove images with no matching annotations
person_id = coco.get_cat_ids(cat_nms=["person"])[0]
people = coco.filter(cat_ids=[person_id])
medium  = coco.filter(area_rng=[1024.0, 9216.0])
fn filter(
    &self,
    cat_ids: Option<&[u64]>,
    img_ids: Option<&[u64]>,
    area_rng: Option<[f64; 2]>,
    drop_empty_images: bool,
) -> Dataset

Returns a Dataset; wrap with COCO::from_dataset() to re-index.

let people = COCO::from_dataset(coco.filter(Some(&[1]), None, None, true));

merge

Merge a list of datasets into one. All datasets must share the same category taxonomy. Image and annotation IDs are remapped to be globally unique.

Raises ValueError (Python) or returns Err (Rust) if taxonomies differ.

COCO.merge(datasets: list[COCO]) -> COCO  # classmethod
Parameter Type Description
datasets list[COCO] Two or more COCO objects with identical category sets
batch1 = COCO("batch1.json")
batch2 = COCO("batch2.json")
combined = COCO.merge([batch1, batch2])
fn merge(datasets: &[&Dataset]) -> Result<Dataset, String>
let combined = COCO::from_dataset(
    COCO::merge(&[&ds1, &ds2]).expect("incompatible taxonomies")
);

split

Split the dataset into train/val (or train/val/test) subsets. Images are shuffled deterministically; annotations follow their images. All splits share the full category list.

split(
    val_frac: float = 0.2,
    test_frac: float | None = None,
    seed: int = 42,
) -> tuple[COCO, COCO] | tuple[COCO, COCO, COCO]
Parameter Type Default Description
val_frac float 0.2 Fraction of images for validation
test_frac float | None None Fraction for a test set; omit for a two-way split
seed int 42 Random seed for reproducibility
train, val = coco.split(val_frac=0.2)
train, val, test = coco.split(val_frac=0.15, test_frac=0.15)
fn split(
    &self,
    val_frac: f64,
    test_frac: Option<f64>,
    seed: u64,
) -> (Dataset, Dataset, Option<Dataset>)
let (train, val, _) = coco.split(0.2, None, 42);
let train = COCO::from_dataset(train);

sample

Draw a random subset of images with their annotations. The sample is deterministic for the same seed.

sample(
    n: int | None = None,
    frac: float | None = None,
    seed: int = 42,
) -> COCO
Parameter Type Default Description
n int | None None Exact number of images to sample
frac float | None None Fraction of images to sample
seed int 42 Random seed for reproducibility

Provide either n or frac, not both.

subset = coco.sample(n=500, seed=0)
subset = coco.sample(frac=0.1, seed=0)
fn sample(&self, n: Option<usize>, frac: Option<f64>, seed: u64) -> Dataset
let subset = COCO::from_dataset(coco.sample(Some(500), None, 0));

save

Serialize the dataset to a COCO-format JSON file.

save(path: str) -> None
coco.filter(cat_ids=[1]).sample(n=500, seed=0).save("person_sample.json")

save is a Python-only convenience method. In Rust, serialize with serde_json:

use std::fs::File;
use std::io::BufWriter;

let file = BufWriter::new(File::create("output.json")?);
serde_json::to_writer_pretty(file, &coco.dataset)?;