|
|
|
import copy |
|
import logging |
|
import types |
|
from collections import UserDict |
|
from typing import List |
|
|
|
from detectron2.utils.logger import log_first_n |
|
|
|
__all__ = ["DatasetCatalog", "MetadataCatalog", "Metadata"] |
|
|
|
|
|
class _DatasetCatalog(UserDict): |
|
""" |
|
A global dictionary that stores information about the datasets and how to obtain them. |
|
|
|
It contains a mapping from strings |
|
(which are names that identify a dataset, e.g. "coco_2014_train") |
|
to a function which parses the dataset and returns the samples in the |
|
format of `list[dict]`. |
|
|
|
The returned dicts should be in Detectron2 Dataset format (See DATASETS.md for details) |
|
if used with the data loader functionalities in `data/build.py,data/detection_transform.py`. |
|
|
|
The purpose of having this catalog is to make it easy to choose |
|
different datasets, by just using the strings in the config. |
|
""" |
|
|
|
def register(self, name, func): |
|
""" |
|
Args: |
|
name (str): the name that identifies a dataset, e.g. "coco_2014_train". |
|
func (callable): a callable which takes no arguments and returns a list of dicts. |
|
It must return the same results if called multiple times. |
|
""" |
|
assert callable(func), "You must register a function with `DatasetCatalog.register`!" |
|
assert name not in self, "Dataset '{}' is already registered!".format(name) |
|
self[name] = func |
|
|
|
def get(self, name): |
|
""" |
|
Call the registered function and return its results. |
|
|
|
Args: |
|
name (str): the name that identifies a dataset, e.g. "coco_2014_train". |
|
|
|
Returns: |
|
list[dict]: dataset annotations. |
|
""" |
|
try: |
|
f = self[name] |
|
except KeyError as e: |
|
raise KeyError( |
|
"Dataset '{}' is not registered! Available datasets are: {}".format( |
|
name, ", ".join(list(self.keys())) |
|
) |
|
) from e |
|
return f() |
|
|
|
def list(self) -> List[str]: |
|
""" |
|
List all registered datasets. |
|
|
|
Returns: |
|
list[str] |
|
""" |
|
return list(self.keys()) |
|
|
|
def remove(self, name): |
|
""" |
|
Alias of ``pop``. |
|
""" |
|
self.pop(name) |
|
|
|
def __str__(self): |
|
return "DatasetCatalog(registered datasets: {})".format(", ".join(self.keys())) |
|
|
|
__repr__ = __str__ |
|
|
|
|
|
DatasetCatalog = _DatasetCatalog() |
|
DatasetCatalog.__doc__ = ( |
|
_DatasetCatalog.__doc__ |
|
+ """ |
|
.. automethod:: detectron2.data.catalog.DatasetCatalog.register |
|
.. automethod:: detectron2.data.catalog.DatasetCatalog.get |
|
""" |
|
) |
|
|
|
|
|
class Metadata(types.SimpleNamespace): |
|
""" |
|
A class that supports simple attribute setter/getter. |
|
It is intended for storing metadata of a dataset and make it accessible globally. |
|
|
|
Examples: |
|
:: |
|
# somewhere when you load the data: |
|
MetadataCatalog.get("mydataset").thing_classes = ["person", "dog"] |
|
|
|
# somewhere when you print statistics or visualize: |
|
classes = MetadataCatalog.get("mydataset").thing_classes |
|
""" |
|
|
|
|
|
|
|
name: str = "N/A" |
|
|
|
_RENAMED = { |
|
"class_names": "thing_classes", |
|
"dataset_id_to_contiguous_id": "thing_dataset_id_to_contiguous_id", |
|
"stuff_class_names": "stuff_classes", |
|
} |
|
|
|
def __getattr__(self, key): |
|
if key in self._RENAMED: |
|
log_first_n( |
|
logging.WARNING, |
|
"Metadata '{}' was renamed to '{}'!".format(key, self._RENAMED[key]), |
|
n=10, |
|
) |
|
return getattr(self, self._RENAMED[key]) |
|
|
|
|
|
if len(self.__dict__) > 1: |
|
raise AttributeError( |
|
"Attribute '{}' does not exist in the metadata of dataset '{}'. Available " |
|
"keys are {}.".format(key, self.name, str(self.__dict__.keys())) |
|
) |
|
else: |
|
raise AttributeError( |
|
f"Attribute '{key}' does not exist in the metadata of dataset '{self.name}': " |
|
"metadata is empty." |
|
) |
|
|
|
def __setattr__(self, key, val): |
|
if key in self._RENAMED: |
|
log_first_n( |
|
logging.WARNING, |
|
"Metadata '{}' was renamed to '{}'!".format(key, self._RENAMED[key]), |
|
n=10, |
|
) |
|
setattr(self, self._RENAMED[key], val) |
|
|
|
|
|
try: |
|
oldval = getattr(self, key) |
|
assert oldval == val, ( |
|
"Attribute '{}' in the metadata of '{}' cannot be set " |
|
"to a different value!\n{} != {}".format(key, self.name, oldval, val) |
|
) |
|
except AttributeError: |
|
super().__setattr__(key, val) |
|
|
|
def as_dict(self): |
|
""" |
|
Returns all the metadata as a dict. |
|
Note that modifications to the returned dict will not reflect on the Metadata object. |
|
""" |
|
return copy.copy(self.__dict__) |
|
|
|
def set(self, **kwargs): |
|
""" |
|
Set multiple metadata with kwargs. |
|
""" |
|
for k, v in kwargs.items(): |
|
setattr(self, k, v) |
|
return self |
|
|
|
def get(self, key, default=None): |
|
""" |
|
Access an attribute and return its value if exists. |
|
Otherwise return default. |
|
""" |
|
try: |
|
return getattr(self, key) |
|
except AttributeError: |
|
return default |
|
|
|
|
|
class _MetadataCatalog(UserDict): |
|
""" |
|
MetadataCatalog is a global dictionary that provides access to |
|
:class:`Metadata` of a given dataset. |
|
|
|
The metadata associated with a certain name is a singleton: once created, the |
|
metadata will stay alive and will be returned by future calls to ``get(name)``. |
|
|
|
It's like global variables, so don't abuse it. |
|
It's meant for storing knowledge that's constant and shared across the execution |
|
of the program, e.g.: the class names in COCO. |
|
""" |
|
|
|
def get(self, name): |
|
""" |
|
Args: |
|
name (str): name of a dataset (e.g. coco_2014_train). |
|
|
|
Returns: |
|
Metadata: The :class:`Metadata` instance associated with this name, |
|
or create an empty one if none is available. |
|
""" |
|
assert len(name) |
|
r = super().get(name, None) |
|
if r is None: |
|
r = self[name] = Metadata(name=name) |
|
return r |
|
|
|
def list(self): |
|
""" |
|
List all registered metadata. |
|
|
|
Returns: |
|
list[str]: keys (names of datasets) of all registered metadata |
|
""" |
|
return list(self.keys()) |
|
|
|
def remove(self, name): |
|
""" |
|
Alias of ``pop``. |
|
""" |
|
self.pop(name) |
|
|
|
def __str__(self): |
|
return "MetadataCatalog(registered metadata: {})".format(", ".join(self.keys())) |
|
|
|
__repr__ = __str__ |
|
|
|
|
|
MetadataCatalog = _MetadataCatalog() |
|
MetadataCatalog.__doc__ = ( |
|
_MetadataCatalog.__doc__ |
|
+ """ |
|
.. automethod:: detectron2.data.catalog.MetadataCatalog.get |
|
""" |
|
) |
|
|