Source code for kwcoco.compat_dataset

"""
A wrapper around the basic kwcoco dataset with a pycocotools API.

We do not recommend using this API because it has some idiosyncrasies, where
names can be missleading and APIs are not always clear / efficient: e.g.

(1) catToImgs returns integer image ids but imgToAnns returns annotation
    dictionaries.

(2) showAnns takes a dictionary list as an argument instead of an integer list

The cool thing is that this extends the kwcoco API so you can drop this for
compatibility with the old API, but you still get access to all of the kwcoco
API including dynamic addition / removal of categories / annotations / images.
"""
from kwcoco.coco_dataset import CocoDataset
import itertools as it
import ubelt as ub
import numpy as np


[docs] class COCO(CocoDataset): """ A wrapper around the basic kwcoco dataset with a pycocotools API. Example: >>> from kwcoco.compat_dataset import * # NOQA >>> import kwcoco >>> basic = kwcoco.CocoDataset.demo('shapes8') >>> self = COCO(basic.dataset) >>> self.info() >>> print('self.imgToAnns = {!r}'.format(self.imgToAnns[1])) >>> print('self.catToImgs = {!r}'.format(self.catToImgs)) """ def __init__(self, annotation_file=None, **kw): if annotation_file is not None: if 'data' in kw: raise ValueError('cannot specify data and annotation file') if 'data' in kw and annotation_file is None: annotation_file = kw.pop('data') super().__init__(annotation_file, **kw)
[docs] def createIndex(self): self._build_index()
[docs] def info(self): """ Print information about the annotation file. """ for key, value in self.dataset.get('info', {}).items(): print('{}: {}'.format(key, value))
@property def imgToAnns(self): from scriptconfig.dict_like import DictLike class ProxyImgToAnns(DictLike): def __init__(self, parent): self.parent = parent def getitem(self, gid): aids = self.parent.index.gid_to_aids[gid] anns = list(ub.take(self.parent.index.anns, aids)) return anns def keys(self): return self.parent.index.gid_to_aids.keys() imgToAnns = ProxyImgToAnns(parent=self) return imgToAnns @property def catToImgs(self): """ unlike the name implies, this actually goes from category to image ids Name retained for backward compatibility """ catToImgs = self.index.cid_to_gids return catToImgs
[docs] def getAnnIds(self, imgIds=[], catIds=[], areaRng=[], iscrowd=None): """ Get ann ids that satisfy given filter conditions. default skips that filter Args: imgIds (List[int]): get anns for given imgs catIds (List[int]): get anns for given cats areaRng (List[float]): get anns for given area range (e.g. [0 inf]) iscrowd (bool | None): get anns for given crowd label (False or True) Returns: List[int]: integer array of ann ids Example: >>> from kwcoco.compat_dataset import * # NOQA >>> import kwcoco >>> self = COCO(kwcoco.CocoDataset.demo('shapes8').dataset) >>> self.getAnnIds() >>> self.getAnnIds(imgIds=1) >>> self.getAnnIds(imgIds=[1]) >>> self.getAnnIds(catIds=[3]) """ imgIds = imgIds if ub.iterable(imgIds) else [imgIds] catIds = catIds if ub.iterable(catIds) else [catIds] if len(imgIds) == len(catIds) == len(areaRng) == 0: anns = self.dataset['annotations'] else: if not len(imgIds) == 0: lists = [self.imgToAnns[imgId] for imgId in imgIds if imgId in self.imgToAnns] anns = list(it.chain.from_iterable(lists)) else: anns = self.dataset['annotations'] anns = anns if len(catIds) == 0 else [ ann for ann in anns if ann['category_id'] in catIds] anns = anns if len(areaRng) == 0 else [ ann for ann in anns if ann['area'] > areaRng[0] and ann['area'] < areaRng[1]] if iscrowd is not None: ids = [ann['id'] for ann in anns if ann['iscrowd'] == iscrowd] else: ids = [ann['id'] for ann in anns] return ids
[docs] def getCatIds(self, catNms=[], supNms=[], catIds=[]): """ filtering parameters. default skips that filter. Args: catNms (List[str]): get cats for given cat names supNms (List[str]): get cats for given supercategory names catIds (List[int]): get cats for given cat ids Returns: List[int]: integer array of cat ids Example: >>> from kwcoco.compat_dataset import * # NOQA >>> import kwcoco >>> self = COCO(kwcoco.CocoDataset.demo('shapes8').dataset) >>> self.getCatIds() >>> self.getCatIds(catNms=['superstar']) >>> self.getCatIds(supNms=['raster']) >>> self.getCatIds(catIds=[3]) """ catNms = catNms if ub.iterable(catNms) else [catNms] supNms = supNms if ub.iterable(supNms) else [supNms] catIds = catIds if ub.iterable(catIds) else [catIds] cats = self.dataset['categories'] if catNms or supNms or catIds: cats = self.dataset['categories'] if catNms: cats = [cat for cat in cats if cat['name'] in catNms] if supNms: cats = [ cat for cat in cats if cat.get( 'supercategory', None) in supNms] if catIds: cats = [cat for cat in cats if cat['id'] in catIds] ids = [cat['id'] for cat in cats] return ids
[docs] def getImgIds(self, imgIds=[], catIds=[]): ''' Get img ids that satisfy given filter conditions. Args: imgIds (List[int]) : get imgs for given ids catIds (List[int]) : get imgs with all given cats Returns: List[int]: integer array of img ids Example: >>> from kwcoco.compat_dataset import * # NOQA >>> import kwcoco >>> self = COCO(kwcoco.CocoDataset.demo('shapes8').dataset) >>> self.getImgIds(imgIds=[1, 2]) >>> self.getImgIds(catIds=[3, 6, 7]) >>> self.getImgIds(catIds=[3, 6, 7], imgIds=[1, 2]) ''' imgIds = imgIds if ub.iterable(imgIds) else [imgIds] catIds = catIds if ub.iterable(catIds) else [catIds] if not imgIds: valid_gids = set(self.imgs.keys()) else: valid_gids = set(imgIds) if catIds: hascat_gids = set() for aids in ub.take(self.index.cid_to_aids, catIds): hascat_gids |= set(self.annots(aids).lookup('image_id')) valid_gids &= hascat_gids return sorted(valid_gids)
[docs] def loadAnns(self, ids=[]): """ Load anns with the specified ids. Args: ids (List[int]) : integer ids specifying anns Returns: List[dict]: loaded ann objects """ if isinstance(ids, int): return self.anns[ids] return list(ub.take(self.anns, ids))
[docs] def loadCats(self, ids=[]): """ Load cats with the specified ids. Args: ids (List[int]) : integer ids specifying cats Returns: List[dict]: loaded cat objects """ if isinstance(ids, int): return self.cats[ids] return list(ub.take(self.cats, ids))
[docs] def loadImgs(self, ids=[]): """ Load anns with the specified ids. Args: ids (List[int]) : integer ids specifying img Returns: List[dict]: loaded img objects """ if isinstance(ids, int): return self.imgs[ids] return list(map(self.load_image, ids))
[docs] def showAnns(self, anns, draw_bbox=False): """ Display the specified annotations. Args: anns (List[Dict]): annotations to display """ aids = [ann['id'] for ann in anns] self.show_image(aids=aids, show_boxes=draw_bbox)
# raise NotImplementedError
[docs] def loadRes(self, resFile): """ Load result file and return a result api object. Args: resFile (str): file name of result file Returns: object: res result api object """ import json import time import copy res = COCO() res.dataset['images'] = [img for img in self.dataset['images']] print('Loading and preparing results...') tic = time.time() if isinstance(resFile, str): anns = json.load(open(resFile)) elif isinstance(resFile, np.ndarray): anns = self.loadNumpyAnnotations(resFile) else: anns = resFile assert isinstance(anns, list), 'results in not an array of objects' annsImgIds = [ann['image_id'] for ann in anns] assert set(annsImgIds) == (set(annsImgIds) & set(self.getImgIds())), \ 'Results do not correspond to current coco set' if len(anns): if 'caption' in anns[0]: imgIds = set([img['id'] for img in res.dataset['images']]) & set( [ann['image_id'] for ann in anns]) res.dataset['images'] = [ img for img in res.dataset['images'] if img['id'] in imgIds] for id, ann in enumerate(anns): ann['id'] = id + 1 elif 'bbox' in anns[0] and not anns[0]['bbox'] == []: res.dataset['categories'] = copy.deepcopy( self.dataset['categories']) for id, ann in enumerate(anns): bb = ann['bbox'] x1, x2, y1, y2 = [bb[0], bb[0] + bb[2], bb[1], bb[1] + bb[3]] if 'segmentation' not in ann: ann['segmentation'] = [[x1, y1, x1, y2, x2, y2, x2, y1]] ann['area'] = bb[2] * bb[3] ann['id'] = id + 1 ann['iscrowd'] = 0 elif 'segmentation' in anns[0]: res.dataset['categories'] = copy.deepcopy( self.dataset['categories']) for id, ann in enumerate(anns): # now only support compressed RLE format as segmentation # results raise NotImplementedError('havent ported mask results yet') # ann['area'] = maskUtils.area(ann['segmentation']) # if 'bbox' not in ann: # ann['bbox'] = maskUtils.toBbox(ann['segmentation']) ann['id'] = id + 1 ann['iscrowd'] = 0 elif 'keypoints' in anns[0]: res.dataset['categories'] = copy.deepcopy( self.dataset['categories']) for id, ann in enumerate(anns): s = ann['keypoints'] x = s[0::3] y = s[1::3] x0, x1, y0, y1 = np.min(x), np.max(x), np.min(y), np.max(y) ann['area'] = (x1 - x0) * (y1 - y0) ann['id'] = id + 1 ann['bbox'] = [x0, y0, x1 - x0, y1 - y0] print('DONE (t={:0.2f}s)'.format(time.time() - tic)) res.dataset['annotations'] = anns res.createIndex() return res
[docs] def download(self, tarDir=None, imgIds=[]): ''' Download COCO images from mscoco.org server. Args: tarDir (str | PathLike | None): COCO results directory name imgIds (list): images to be downloaded ''' if tarDir is not None: self.reroot(tarDir) if not imgIds: imgIds = None self._ensure_image_data(gids=imgIds)
[docs] def loadNumpyAnnotations(self, data): """ Convert result data from a numpy array [Nx7] where each row contains {imageID,x1,y1,w,h,score,class} Args: data (numpy.ndarray) Returns: List[Dict]: annotations (python nested list) """ print('Converting ndarray to lists...') assert isinstance(data, np.ndarray) assert data.shape[1] == 7 N = data.shape[0] ann = [] for i in ub.ProgIter(range(N)): ann += [{ 'image_id': int(data[i, 0]), 'bbox': [data[i, 1], data[i, 2], data[i, 3], data[i, 4]], 'score': data[i, 5], 'category_id': int(data[i, 6]), }] return ann
[docs] def annToRLE(self, ann): """ Convert annotation which can be polygons, uncompressed RLE to RLE. Returns: kwimage.Mask Note: * This requires the C-extensions for kwimage to be installed (i.e. ``pip install kwimage_ext``) due to the need to interface with the bytes RLE format. Example: >>> from kwcoco.compat_dataset import * # NOQA >>> import kwcoco >>> self = COCO(kwcoco.CocoDataset.demo('shapes8').dataset) >>> try: >>> rle = self.annToRLE(self.anns[1]) >>> except NotImplementedError: >>> import pytest >>> pytest.skip('missing kwimage c-extensions') >>> else: >>> assert len(rle['counts']) > 2 >>> # xdoctest: +REQUIRES(module:pycocotools) >>> self.conform(legacy=True) >>> orig = self._aspycoco().annToRLE(self.anns[1]) """ from kwimage.structs.segmentation import _coerce_coco_segmentation aid = ann['id'] ann = self.anns[aid] t = self.imgs[ann['image_id']] h, w = t['height'], t['width'] data = ann['segmentation'] dims = (h, w) sseg = _coerce_coco_segmentation(data, dims) try: rle = sseg.to_mask(dims=dims).to_bytes_rle().data except NotImplementedError: raise NotImplementedError(( 'kwimage does not seem to have required ' 'c-extensions for bytes RLE')) return rle
[docs] def annToMask(self, ann): """ Convert annotation which can be polygons, uncompressed RLE, or RLE to binary mask. Returns: ndarray: binary mask (numpy 2D array) Note: The mask is returned as a fortran (F-style) array with the same dimensions as the parent image. Ignore: >>> from kwcoco.compat_dataset import * # NOQA >>> import kwcoco >>> self = COCO(kwcoco.CocoDataset.demo('shapes8').dataset) >>> mask = self.annToMask(self.anns[1]) >>> # xdoctest: +REQUIRES(module:pycocotools) >>> self.conform(legacy=True) >>> orig = self._aspycoco().annToMask(self.anns[1]) >>> # xdoctest: +REQUIRES(--show) >>> import kwplot >>> kwplot.autompl() >>> diff = kwimage.normalize((compat_mask - orig_mask).astype(np.float32)) >>> kwplot.imshow(diff) >>> kwplot.show_if_requested() """ from kwimage.structs.segmentation import _coerce_coco_segmentation aid = ann['id'] ann = self.anns[aid] data = ann['segmentation'] dims = None sseg = _coerce_coco_segmentation(data, dims) try: mask = sseg.to_mask() except Exception: img = self.imgs[ann['image_id']] dims = (img['height'], img['width']) mask = sseg.to_mask(dims=dims) m = mask.to_fortran_mask().data return m