:py:mod:`kwcoco.category_tree` ============================== .. py:module:: kwcoco.category_tree .. autoapi-nested-parse:: The :mod:`category_tree` module defines the :class:`CategoryTree` class, which is used for maintaining flat or hierarchical category information. The kwcoco version of this class only contains the datastructure and does not contain any torch operations. See the ndsampler version for the extension with torch operations. Module Contents --------------- Classes ~~~~~~~ .. autoapisummary:: kwcoco.category_tree.CategoryTree .. py:class:: CategoryTree(graph=None, checks=True) Bases: :py:obj:`ubelt.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. .. note:: There are three basic properties that this object maintains: .. code:: node: Alphanumeric string names that should be generally descriptive. Using spaces and special characters in these names is discouraged, but can be done. This is the COCO category "name" attribute. For categories this may be denoted as (name, node, cname, catname). id: The integer id of a category should ideally remain consistent. These are often given by a dataset (e.g. a COCO dataset). This is the COCO category "id" attribute. For categories this is often denoted as (id, cid). index: Contigous zero-based indices that indexes the list of categories. These should be used for the fastest access in backend computation tasks. Typically corresponds to the ordering of the channels in the final linear layer in an associated model. For categories this is often denoted as (index, cidx, idx, or cx). :ivar idx_to_node: a list of class names. Implicitly maps from index to category name. :vartype idx_to_node: List[str] :ivar id_to_node: maps integer ids to category names :vartype id_to_node: Dict[int, str] :ivar node_to_id: maps category names to ids :vartype node_to_id: Dict[str, int] :ivar node_to_idx: maps category names to indexes :vartype node_to_idx: Dict[str, int] :ivar 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. :vartype graph: networkx.Graph :ivar idx_groups: groups of category indices that share the same parent category. :vartype idx_groups: List[List[int]] .. rubric:: 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) .. rubric:: Example >>> # The coerce classmethod is the easiest way to create an instance >>> import kwcoco >>> kwcoco.CategoryTree.coerce(['a', 'b', 'c']) >> kwcoco.CategoryTree.coerce(4) >> kwcoco.CategoryTree.coerce(4) .. py:method:: copy(self) .. py:method:: from_mutex(cls, nodes, bg_hack=True) :classmethod: :Parameters: **nodes** (*List[str]*) -- or a list of class names (in which case they will all be assumed to be mutually exclusive) .. rubric:: Example >>> print(CategoryTree.from_mutex(['a', 'b', 'c'])) .. py:method:: from_json(cls, state) :classmethod: :Parameters: **state** (*Dict*) -- see __getstate__ / __json__ for details .. py:method:: from_coco(cls, categories) :classmethod: Create a CategoryTree object from coco categories :Parameters: **List[Dict]** -- list of coco-style categories .. py:method:: coerce(cls, data, **kw) :classmethod: 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 :rtype: CategoryTree :raises TypeError - if the input format is unknown: :raises ValueError - if kwargs are not compatible with the input format: .. rubric:: 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) .. py:method:: demo(cls, key='coco', **kwargs) :classmethod: :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. Can be 'btree2', which is the same as btree but returns strings .. rubric:: CommandLine .. code-block:: bash xdoctest -m ~/code/kwcoco/kwcoco/category_tree.py CategoryTree.demo .. rubric:: Example >>> from kwcoco.category_tree import * >>> self = CategoryTree.demo() >>> print('self = {}'.format(self)) self = .. py:method:: to_coco(self) Converts to a coco-style data structure :Yields: *Dict* -- coco category dictionaries .. py:method:: id_to_idx(self) .. rubric:: Example >>> import kwcoco >>> self = kwcoco.CategoryTree.demo() >>> self.id_to_idx[1] .. py:method:: idx_to_id(self) .. rubric:: Example >>> import kwcoco >>> self = kwcoco.CategoryTree.demo() >>> self.idx_to_id[0] .. py:method:: idx_to_ancestor_idxs(self, include_self=True) Mapping from a class index to its ancestors :Parameters: **include_self** (*bool, default=True*) -- if True includes each node as its own ancestor. .. py:method:: idx_to_descendants_idxs(self, include_self=False) Mapping from a class index to its descendants (including itself) :Parameters: **include_self** (*bool, default=False*) -- if True includes each node as its own descendant. .. py:method:: idx_pairwise_distance(self) Get a matrix encoding the distance from one class to another. Distances * from parents to children are positive (descendants), * from children to parents are negative (ancestors), * between unreachable nodes (wrt to forward and reverse graph) are nan. .. py:method:: __len__(self) .. py:method:: __iter__(self) .. py:method:: __getitem__(self, index) .. py:method:: __contains__(self, node) .. py:method:: __json__(self) .. rubric:: Example >>> import pickle >>> self = CategoryTree.demo() >>> print('self = {!r}'.format(self.__json__())) .. py:method:: __getstate__(self) Serializes information in this class .. rubric:: Example >>> from kwcoco.category_tree import * >>> import pickle >>> self = CategoryTree.demo() >>> state = self.__getstate__() >>> serialization = pickle.dumps(self) >>> recon = pickle.loads(serialization) >>> assert recon.__json__() == self.__json__() .. py:method:: __setstate__(self, state) .. py:method:: __nice__(self) .. py:method:: is_mutex(self) 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? .. py:method:: num_classes(self) :property: .. py:method:: class_names(self) :property: .. py:method:: category_names(self) :property: .. py:method:: cats(self) :property: 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]] .. rubric:: Example >>> from kwcoco.category_tree import * >>> self = CategoryTree.demo() >>> print('self.cats = {!r}'.format(self.cats)) .. py:method:: index(self, node) Return the index that corresponds to the category name .. py:method:: _build_index(self) construct lookup tables .. py:method:: show(self) .. py:method:: forest_str(self) .. py:method:: normalize(self) Applies a normalization scheme to the categories. Note: this may break other tasks that depend on exact category names. :returns: CategoryTree .. rubric:: Example >>> from kwcoco.category_tree import * # NOQA >>> import kwcoco >>> orig = kwcoco.CategoryTree.demo('animals_v1') >>> self = kwcoco.CategoryTree(nx.relabel_nodes(orig.graph, str.upper)) >>> norm = self.normalize()