kwcoco package¶
Subpackages¶
- kwcoco.cli package
- kwcoco.demo package
- kwcoco.metrics package
- Submodules
- kwcoco.metrics.assignment module
- kwcoco.metrics.clf_report module
- kwcoco.metrics.confusion_vectors module
- kwcoco.metrics.detect_metrics module
- kwcoco.metrics.drawing module
- kwcoco.metrics.functional module
- kwcoco.metrics.sklearn_alts module
- kwcoco.metrics.util module
- kwcoco.metrics.voc_metrics module
- Module contents
- Submodules
- kwcoco.util package
Submodules¶
Module contents¶
The Kitware COCO module defines a variant of the Microsoft COCO format, originally developed for the “collected images in context” object detection challenge. We are backwards compatible with the original module, but we also have improved implementations in several places, including segmentations and keypoints.
The kwcoco.CocoDataset
class is capable of dynamic addition and removal
of categories, images, and annotations. Has better support for keypoints and
segmentation formats than the original COCO format. Despite being written in
Python, this data structure is reasonably efficient.
-
class
kwcoco.
CocoDataset
(data=None, tag=None, img_root=None, autobuild=True)[source]¶ Bases:
ubelt.util_mixins.NiceRepr
,kwcoco.coco_dataset.MixinCocoAddRemove
,kwcoco.coco_dataset.MixinCocoStats
,kwcoco.coco_dataset.MixinCocoAttrs
,kwcoco.coco_dataset.MixinCocoDraw
,kwcoco.coco_dataset.MixinCocoExtras
,kwcoco.coco_dataset.MixinCocoIndex
,kwcoco.coco_dataset.MixinCocoDepricate
Notes
- A keypoint annotation
- {
- “image_id” : int, “category_id” : int, “keypoints” : [x1,y1,v1,…,xk,yk,vk], “score” : float,
} Note that
v[i]
is a visibility flag, where v=0: not labeled,v=1: labeled but not visible, and v=2: labeled and visible.- A bounding box annotation
- {
- “image_id” : int, “category_id” : int, “bbox” : [x,y,width,height], “score” : float,
}
- We also define a non-standard “line” annotation (which
- our fixup scripts will interpret as the diameter of a circle to convert into a bounding box)
- A line* annotation (note this is a non-standard field)
- {
- “image_id” : int, “category_id” : int, “line” : [x1,y1,x2,y2], “score” : float,
}
Lastly, note that our datasets will sometimes specify multiple bbox, line, and/or, keypoints fields. In this case we may also specify a field roi_shape, which denotes which field is the “main” annotation type.
Variables: - dataset (Dict) – raw json data structure. This is the base dictionary that contains {‘annotations’: List, ‘images’: List, ‘categories’: List}
- index (CocoIndex) – an efficient lookup index into the coco data
structure. The index defines its own attributes like
anns
,cats
,imgs
, etc. SeeCocoIndex
for more details on which attributes are available. - fpath (PathLike | None) – if known, this stores the filepath the dataset was loaded from
- tag (str) – A tag indicating the name of the dataset.
- img_root (PathLike | None) – If known, this is the root path that all image file names are relative to. This can also be manually overwritten by the user.
- hashid (str | None) – If computed, this will be a hash uniquely identifing the dataset.
To ensure this is computed see
_build_hashid()
.
References
http://cocodataset.org/#format http://cocodataset.org/#download
- CommandLine:
- python -m kwcoco.coco_dataset CocoDataset –show
Example
>>> dataset = demo_coco_data() >>> self = CocoDataset(dataset, tag='demo') >>> # xdoctest: +REQUIRES(--show) >>> self.show_image(gid=2) >>> from matplotlib import pyplot as plt >>> plt.show()
-
classmethod
from_image_paths
(gpaths, img_root=None)[source]¶ Constructor from a list of images paths
Example
>>> coco_dset = CocoDataset.from_image_paths(['a.png', 'b.png']) >>> assert coco_dset.n_images == 2
-
classmethod
from_coco_paths
(fpaths, max_workers=0, verbose=1, mode='thread', union='try')[source]¶ Constructor from multiple coco file paths.
Loads multiple coco datasets and unions the result
Notes
if the union operation fails, the list of individually loaded files is returned instead.
Parameters: - fpaths (List[str]) – list of paths to multiple coco files to be loaded and unioned.
- max_workers (int, default=0) – number of worker threads / processes
- verbose (int) – verbosity level
- mode (str) – thread, process, or serial
- union (str | bool, default=’try’) – If True, unions the result datasets after loading. If False, just returns the result list. If ‘try’, then try to preform the union, but return the result list if it fails.
-
copy
()[source]¶ Deep copies this object
Example
>>> from kwcoco.coco_dataset import * >>> self = CocoDataset.demo() >>> new = self.copy() >>> assert new.imgs[1] is new.dataset['images'][0] >>> assert new.imgs[1] == self.dataset['images'][0] >>> assert new.imgs[1] is not self.dataset['images'][0]
-
dumps
(indent=None, newlines=False)[source]¶ Writes the dataset out to the json format
Parameters: newlines (bool) – if True, each annotation, image, category gets its own line Notes
- Using newlines=True is similar to:
- print(ub.repr2(dset.dataset, nl=2, trailsep=False)) However, the above may not output valid json if it contains ndarrays.
Example
>>> from kwcoco.coco_dataset import * >>> import json >>> self = CocoDataset.demo() >>> text = self.dumps(newlines=True) >>> print(text) >>> self2 = CocoDataset(json.loads(text), tag='demo2') >>> assert self2.dataset == self.dataset >>> assert self2.dataset is not self.dataset
>>> text = self.dumps(newlines=True) >>> print(text) >>> self2 = CocoDataset(json.loads(text), tag='demo2') >>> assert self2.dataset == self.dataset >>> assert self2.dataset is not self.dataset
- Ignore:
- for k in self2.dataset:
- if self.dataset[k] == self2.dataset[k]:
- print(‘YES: k = {!r}’.format(k))
- else:
- print(‘NO: k = {!r}’.format(k))
self2.dataset[‘categories’] self.dataset[‘categories’]
-
dump
(file, indent=None, newlines=False)[source]¶ Writes the dataset out to the json format
Parameters: - file (PathLike | FileLike) – Where to write the data. Can either be a path to a file or an open file pointer / stream.
- newlines (bool) – if True, each annotation, image, category gets its own line.
Example
>>> import tempfile >>> from kwcoco.coco_dataset import * >>> self = CocoDataset.demo() >>> file = tempfile.NamedTemporaryFile('w') >>> self.dump(file) >>> file.seek(0) >>> text = open(file.name, 'r').read() >>> print(text) >>> file.seek(0) >>> dataset = json.load(open(file.name, 'r')) >>> self2 = CocoDataset(dataset, tag='demo2') >>> assert self2.dataset == self.dataset >>> assert self2.dataset is not self.dataset
>>> file = tempfile.NamedTemporaryFile('w') >>> self.dump(file, newlines=True) >>> file.seek(0) >>> text = open(file.name, 'r').read() >>> print(text) >>> file.seek(0) >>> dataset = json.load(open(file.name, 'r')) >>> self2 = CocoDataset(dataset, tag='demo2') >>> assert self2.dataset == self.dataset >>> assert self2.dataset is not self.dataset
-
union
(*others, **kwargs)[source]¶ Merges multiple
CocoDataset
items into one. Names and associations are retained, but ids may be different.Parameters: - self – note that
union()
can be called as an instance method or a class method. If it is a class method, then this is the class type, otherwise the instance will also be unioned withothers
. - *others – a series of CocoDatasets that we will merge
- **kwargs – constructor options for the new merged CocoDataset
Returns: a new merged coco dataset
Return type: Example
>>> # Test union works with different keypoint categories >>> dset1 = CocoDataset.demo('shapes1') >>> dset2 = CocoDataset.demo('shapes2') >>> dset1.remove_keypoint_categories(['bot_tip', 'mid_tip', 'right_eye']) >>> dset2.remove_keypoint_categories(['top_tip', 'left_eye']) >>> dset_12a = CocoDataset.union(dset1, dset2) >>> dset_12b = dset1.union(dset2) >>> dset_21 = dset2.union(dset1) >>> def add_hist(h1, h2): >>> return {k: h1.get(k, 0) + h2.get(k, 0) for k in set(h1) | set(h2)} >>> kpfreq1 = dset1.keypoint_annotation_frequency() >>> kpfreq2 = dset2.keypoint_annotation_frequency() >>> kpfreq_want = add_hist(kpfreq1, kpfreq2) >>> kpfreq_got1 = dset_12a.keypoint_annotation_frequency() >>> kpfreq_got2 = dset_12b.keypoint_annotation_frequency() >>> assert kpfreq_want == kpfreq_got1 >>> assert kpfreq_want == kpfreq_got2
>>> # Test disjoint gid datasets >>> import kwcoco >>> dset1 = kwcoco.CocoDataset.demo('shapes3') >>> for new_gid, img in enumerate(dset1.dataset['images'], start=10): >>> for aid in dset1.gid_to_aids[img['id']]: >>> dset1.anns[aid]['image_id'] = new_gid >>> img['id'] = new_gid >>> dset1.index.clear() >>> dset1._build_index() >>> # ------ >>> dset2 = kwcoco.CocoDataset.demo('shapes2') >>> for new_gid, img in enumerate(dset2.dataset['images'], start=100): >>> for aid in dset2.gid_to_aids[img['id']]: >>> dset2.anns[aid]['image_id'] = new_gid >>> img['id'] = new_gid >>> dset1.index.clear() >>> dset2._build_index() >>> others = [dset1, dset2] >>> merged = kwcoco.CocoDataset.union(*others) >>> print('merged = {!r}'.format(merged)) >>> print('merged.imgs = {}'.format(ub.repr2(merged.imgs, nl=1))) >>> assert set(merged.imgs) & set([10, 11, 12, 100, 101]) == set(merged.imgs)
>>> # Test data is not preserved >>> dset2 = kwcoco.CocoDataset.demo('shapes2') >>> dset1 = kwcoco.CocoDataset.demo('shapes3') >>> others = (dset1, dset2) >>> cls = self = kwcoco.CocoDataset >>> merged = cls.union(*others) >>> print('merged = {!r}'.format(merged)) >>> print('merged.imgs = {}'.format(ub.repr2(merged.imgs, nl=1))) >>> assert set(merged.imgs) & set([1, 2, 3, 4, 5]) == set(merged.imgs)
Todo
- [ ] are supercategories broken?
- [ ] reuse image ids where possible
- [ ] reuse annotation / category ids where possible
- [ ] disambiguate track-ids
- [x] disambiguate video-ids
- self – note that
-
subset
(gids, copy=False, autobuild=True)[source]¶ Return a subset of the larger coco dataset by specifying which images to port. All annotations in those images will be taken.
Parameters: - gids (List[int]) – image-ids to copy into a new dataset
- copy (bool, default=False) – if True, makes a deep copy of all nested attributes, otherwise makes a shallow copy.
- autobuild (bool, default=True) – if True will automatically build the fast lookup index.
Example
>>> self = CocoDataset.demo() >>> gids = [1, 3] >>> sub_dset = self.subset(gids) >>> assert len(self.gid_to_aids) == 3 >>> assert len(sub_dset.gid_to_aids) == 2
Example
>>> self = CocoDataset.demo() >>> sub1 = self.subset([1]) >>> sub2 = self.subset([2]) >>> sub3 = self.subset([3]) >>> others = [sub1, sub2, sub3] >>> rejoined = CocoDataset.union(*others) >>> assert len(sub1.anns) == 9 >>> assert len(sub2.anns) == 2 >>> assert len(sub3.anns) == 0 >>> assert rejoined.basic_stats() == self.basic_stats()
-
class
kwcoco.
CategoryTree
(graph=None)[source]¶ Bases:
ubelt.util_mixins.NiceRepr
Wrapper that maintains flat or hierarchical category information.
Helps compute softmaxes and probabilities for tree-based categories where a directed edge (A, B) represents that A is a superclass of B.
Notes
There are three basic properties that this object maintains:
- name:
- Alphanumeric string names that should be generally descriptive. Using spaces and special characters in these names is discouraged, but can be done.
- id:
- The integer id of a category should ideally remain consistent. These are often given by a dataset (e.g. a COCO dataset).
- index:
- Contigous zero-based indices that indexes the list of categories. These should be used for the fastest access in backend computation tasks.
Variables: - idx_to_node (List[str]) – a list of class names. Implicitly maps from index to category name.
- id_to_node (Dict[int, str]) – maps integer ids to category names
- node_to_id (Dict[str, int]) – maps category names to ids
- node_to_idx (Dict[str, int]) – maps category names to indexes
- graph (nx.Graph) – a Graph that stores any hierarchy information. For standard mutually exclusive classes, this graph is edgeless. Nodes in this graph can maintain category attributes / properties.
- idx_groups (List[List[int]]) – groups of category indices that share the same parent category.
Example
>>> from kwcoco.category_tree import * >>> graph = nx.from_dict_of_lists({ >>> 'background': [], >>> 'foreground': ['animal'], >>> 'animal': ['mammal', 'fish', 'insect', 'reptile'], >>> 'mammal': ['dog', 'cat', 'human', 'zebra'], >>> 'zebra': ['grevys', 'plains'], >>> 'grevys': ['fred'], >>> 'dog': ['boxer', 'beagle', 'golden'], >>> 'cat': ['maine coon', 'persian', 'sphynx'], >>> 'reptile': ['bearded dragon', 't-rex'], >>> }, nx.DiGraph) >>> self = CategoryTree(graph) >>> print(self) <CategoryTree(nNodes=22, maxDepth=6, maxBreadth=4...)>
Example
>>> # The coerce classmethod is the easiest way to create an instance >>> import kwcoco >>> kwcoco.CategoryTree.coerce(['a', 'b', 'c']) <CategoryTree(nNodes=3, nodes=['a', 'b', 'c']) ... >>> kwcoco.CategoryTree.coerce(4) <CategoryTree(nNodes=4, nodes=['class_1', 'class_2', 'class_3', ... >>> kwcoco.CategoryTree.coerce(4)
-
classmethod
from_mutex
(nodes, bg_hack=True)[source]¶ Parameters: nodes (List[str]) – or a list of class names (in which case they will all be assumed to be mutually exclusive) Example
>>> print(CategoryTree.from_mutex(['a', 'b', 'c'])) <CategoryTree(nNodes=3, ...)>
-
classmethod
from_json
(state)[source]¶ Parameters: state (Dict) – see __getstate__ / __json__ for details
-
classmethod
from_coco
(categories)[source]¶ Create a CategoryTree object from coco categories
Parameters: List[Dict] – list of coco-style categories
-
classmethod
coerce
(data, **kw)[source]¶ Attempt to coerce data as a CategoryTree object.
This is primarily useful for when the software stack depends on categories being represent
This will work if the input data is a specially formatted json dict, a list of mutually exclusive classes, or if it is already a CategoryTree. Otherwise an error will be thrown.
Parameters: - data (object) – a known representation of a category tree.
- **kwargs – input type specific arguments
Returns: self
Return type: Raises: - TypeError - if the input format is unknown
- ValueError - if kwargs are not compatible with the input format
Example
>>> import kwcoco >>> classes1 = kwcoco.CategoryTree.coerce(3) # integer >>> classes2 = kwcoco.CategoryTree.coerce(classes1.__json__()) # graph dict >>> classes3 = kwcoco.CategoryTree.coerce(['class_1', 'class_2', 'class_3']) # mutex list >>> classes4 = kwcoco.CategoryTree.coerce(classes1.graph) # nx Graph >>> classes5 = kwcoco.CategoryTree.coerce(classes1) # cls >>> # xdoctest: +REQUIRES(module:ndsampler) >>> import ndsampler >>> classes6 = ndsampler.CategoryTree.coerce(3) >>> classes7 = ndsampler.CategoryTree.coerce(classes1) >>> classes8 = kwcoco.CategoryTree.coerce(classes6)
-
classmethod
demo
(key='coco', **kwargs)[source]¶ Parameters: key (str) – specify which demo dataset to use. Can be ‘coco’ (which uses the default coco demo data). Can be ‘btree’ which creates a binary tree and accepts kwargs
‘r’ and ‘h’ for branching-factor and height.
- CommandLine:
- xdoctest -m ~/code/kwcoco/kwcoco/category_tree.py CategoryTree.demo
Example
>>> from kwcoco.category_tree import * >>> self = CategoryTree.demo() >>> print('self = {}'.format(self)) self = <CategoryTree(nNodes=10, maxDepth=2, maxBreadth=4...)>
-
id_to_idx
¶ >>> import kwcoco >>> self = kwcoco.CategoryTree.demo() >>> self.id_to_idx[1]
Type: Example
-
idx_to_id
¶ >>> import kwcoco >>> self = kwcoco.CategoryTree.demo() >>> self.idx_to_id[0]
Type: Example
-
idx_to_ancestor_idxs
¶ memoization decorator for a method that respects args and kwargs
References
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
Example
>>> import ubelt as ub >>> closure = {'a': 'b', 'c': 'd'} >>> incr = [0] >>> class Foo(object): >>> @memoize_method >>> def foo_memo(self, key): >>> value = closure[key] >>> incr[0] += 1 >>> return value >>> def foo(self, key): >>> value = closure[key] >>> incr[0] += 1 >>> return value >>> self = Foo() >>> assert self.foo('a') == 'b' and self.foo('c') == 'd' >>> assert incr[0] == 2 >>> print('Call memoized version') >>> assert self.foo_memo('a') == 'b' and self.foo_memo('c') == 'd' >>> assert incr[0] == 4 >>> assert self.foo_memo('a') == 'b' and self.foo_memo('c') == 'd' >>> print('Counter should no longer increase') >>> assert incr[0] == 4 >>> print('Closure changes result without memoization') >>> closure = {'a': 0, 'c': 1} >>> assert self.foo('a') == 0 and self.foo('c') == 1 >>> assert incr[0] == 6 >>> assert self.foo_memo('a') == 'b' and self.foo_memo('c') == 'd' >>> print('Constructing a new object should get a new cache') >>> self2 = Foo() >>> self2.foo_memo('a') >>> assert incr[0] == 7 >>> self2.foo_memo('a') >>> assert incr[0] == 7
-
idx_to_descendants_idxs
¶ memoization decorator for a method that respects args and kwargs
References
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
Example
>>> import ubelt as ub >>> closure = {'a': 'b', 'c': 'd'} >>> incr = [0] >>> class Foo(object): >>> @memoize_method >>> def foo_memo(self, key): >>> value = closure[key] >>> incr[0] += 1 >>> return value >>> def foo(self, key): >>> value = closure[key] >>> incr[0] += 1 >>> return value >>> self = Foo() >>> assert self.foo('a') == 'b' and self.foo('c') == 'd' >>> assert incr[0] == 2 >>> print('Call memoized version') >>> assert self.foo_memo('a') == 'b' and self.foo_memo('c') == 'd' >>> assert incr[0] == 4 >>> assert self.foo_memo('a') == 'b' and self.foo_memo('c') == 'd' >>> print('Counter should no longer increase') >>> assert incr[0] == 4 >>> print('Closure changes result without memoization') >>> closure = {'a': 0, 'c': 1} >>> assert self.foo('a') == 0 and self.foo('c') == 1 >>> assert incr[0] == 6 >>> assert self.foo_memo('a') == 'b' and self.foo_memo('c') == 'd' >>> print('Constructing a new object should get a new cache') >>> self2 = Foo() >>> self2.foo_memo('a') >>> assert incr[0] == 7 >>> self2.foo_memo('a') >>> assert incr[0] == 7
-
idx_pairwise_distance
¶ memoization decorator for a method that respects args and kwargs
References
http://code.activestate.com/recipes/577452-a-memoize-decorator-for-instance-methods/
Example
>>> import ubelt as ub >>> closure = {'a': 'b', 'c': 'd'} >>> incr = [0] >>> class Foo(object): >>> @memoize_method >>> def foo_memo(self, key): >>> value = closure[key] >>> incr[0] += 1 >>> return value >>> def foo(self, key): >>> value = closure[key] >>> incr[0] += 1 >>> return value >>> self = Foo() >>> assert self.foo('a') == 'b' and self.foo('c') == 'd' >>> assert incr[0] == 2 >>> print('Call memoized version') >>> assert self.foo_memo('a') == 'b' and self.foo_memo('c') == 'd' >>> assert incr[0] == 4 >>> assert self.foo_memo('a') == 'b' and self.foo_memo('c') == 'd' >>> print('Counter should no longer increase') >>> assert incr[0] == 4 >>> print('Closure changes result without memoization') >>> closure = {'a': 0, 'c': 1} >>> assert self.foo('a') == 0 and self.foo('c') == 1 >>> assert incr[0] == 6 >>> assert self.foo_memo('a') == 'b' and self.foo_memo('c') == 'd' >>> print('Constructing a new object should get a new cache') >>> self2 = Foo() >>> self2.foo_memo('a') >>> assert incr[0] == 7 >>> self2.foo_memo('a') >>> assert incr[0] == 7
-
is_mutex
()[source]¶ Returns True if all categories are mutually exclusive (i.e. flat)
If true, then the classes may be represented as a simple list of class names without any loss of information, otherwise the underlying category graph is necessary to preserve all knowledge.
Todo
- [ ] what happens when we have a dummy root?
-
num_classes
¶
-
class_names
¶
-
category_names
¶
-
cats
¶ Returns a mapping from category names to category attributes.
If this category tree was constructed from a coco-dataset, then this will contain the coco category attributes.
Returns: Dict[str, Dict[str, object]] Example
>>> from kwcoco.category_tree import * >>> self = CategoryTree.demo() >>> print('self.cats = {!r}'.format(self.cats))
-
show
()[source]¶ - Ignore:
>>> import kwplot >>> kwplot.autompl() >>> from kwcoco import category_tree >>> self = category_tree.CategoryTree.demo() >>> self.show()
python -c “import kwplot, kwcoco, graphid; kwplot.autompl(); graphid.util.show_nx(kwcoco.category_tree.CategoryTree.demo().graph); kwplot.show_if_requested()” –show