If you are new, please see our getting started document: getting_started
Please also see information in the repo README, which
contains similar but complementary information.
Documentation about higher level kwcoco concepts can be found here.
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,
keypoints, annotation tracks, multi-spectral images, and videos (which
represents a generic sequence of images).
A kwcoco file is a “manifest” that serves as a single reference that points to
all images, categories, and annotations in a computer vision dataset. Thus,
when applying an algorithm to a dataset, it is sufficient to have the algorithm
take one dataset parameter: the path to the kwcoco file. Generally a kwcoco
file will live in a “bundle” directory along with the data that it references,
and paths in the kwcoco file will be relative to the location of the kwcoco
file itself.
The main data structure in this model is largely based on the implementation in
https://github.com/cocodataset/cocoapi It uses the same efficient core indexing
data structures, but in our implementation the indexing can be optionally
turned off, functions are silent by default (with the exception of long running
processes, which optionally show progress by default). We support helper
functions that add and remove images, categories, and annotations.
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.
>>> importkwcoco>>> importjson>>> # Create demo data>>> demo=kwcoco.CocoDataset.demo()>>> # Reroot can switch between absolute / relative-paths>>> demo.reroot(absolute=True)>>> # could also use demo.dump / demo.dumps, but this is more explicit>>> text=json.dumps(demo.dataset)>>> withopen('demo.json','w')asfile:>>> file.write(text)>>> # Read from disk>>> self=kwcoco.CocoDataset('demo.json')>>> # Add data>>> cid=self.add_category('Cat')>>> gid=self.add_image('new-img.jpg')>>> aid=self.add_annotation(image_id=gid,category_id=cid,bbox=[0,0,100,100])>>> # Remove data>>> self.remove_annotations([aid])>>> self.remove_images([gid])>>> self.remove_categories([cid])>>> # Look at data>>> importubeltasub>>> print(ub.urepr(self.basic_stats(),nl=1))>>> print(ub.urepr(self.extended_stats(),nl=2))>>> print(ub.urepr(self.boxsize_stats(),nl=3))>>> print(ub.urepr(self.category_annotation_frequency()))>>> # Inspect data>>> # xdoctest: +REQUIRES(module:kwplot)>>> importkwplot>>> kwplot.autompl()>>> self.show_image(gid=1)>>> # Access single-item data via imgs, cats, anns>>> cid=1>>> self.cats[cid]{'id': 1, 'name': 'astronaut', 'supercategory': 'human'}>>> gid=1>>> self.imgs[gid]{'id': 1, 'file_name': '...astro.png', 'url': 'https://i.imgur.com/KXhKM72.png'}>>> aid=3>>> self.anns[aid]{'id': 3, 'image_id': 1, 'category_id': 3, 'line': [326, 369, 500, 500]}>>> # Access multi-item data via the annots and images helper objects>>> aids=self.index.gid_to_aids[2]>>> annots=self.annots(aids)>>> print('annots = {}'.format(ub.urepr(annots,nl=1,sv=1)))annots = <Annots(num=2)>>>> annots.lookup('category_id')[6, 4]>>> annots.lookup('bbox')[[37, 6, 230, 240], [124, 96, 45, 18]]>>> # built in conversions to efficient kwimage array DataStructures>>> print(ub.urepr(annots.detections.data,sv=1)){ 'boxes': <Boxes(xywh, array([[ 37., 6., 230., 240.], [124., 96., 45., 18.]], dtype=float32))>, 'class_idxs': [5, 3], 'keypoints': <PointsList(n=2)>, 'segmentations': <PolygonList(n=2)>,}>>> gids=list(self.imgs.keys())>>> images=self.images(gids)>>> print('images = {}'.format(ub.urepr(images,nl=1,sv=1)))images = <Images(num=3)>>>> images.lookup('file_name')['...astro.png', '...carl.png', '...stars.png']>>> print('images.annots = {}'.format(images.annots))images.annots = <AnnotGroups(n=3, m=3.7, s=3.9)>>>> print('images.annots.cids = {!r}'.format(images.annots.cids))images.annots.cids = [[1, 2, 3, 4, 5, 5, 5, 5, 5], [6, 4], []]
kwcoco.CocoDataset.index - an efficient lookup index into the coco data structure. The index defines its own attributes like anns, cats, imgs, gid_to_aids, file_name_to_img, etc. See CocoIndex for more details on which attributes are available.
kwcoco.CocoDataset.tag - A tag indicating the name of the dataset.
kwcoco.CocoDataset.dataset - raw json data structure. This is the base dictionary that contains {‘annotations’: List, ‘images’: List, ‘categories’: List}
kwcoco.CocoDataset.bundle_dpath - If known, this is the root path that all image file names are relative to. This can also be manually overwritten by the user.
kwcoco.CocoDataset.ensure_category - Like add_category(), but returns the existing category id if it already exists instead of failing. In this case all metadata is ignored.
kwcoco.CocoDataset.ensure_image - Like add_image(),, but returns the existing image id if it already exists instead of failing. In this case all metadata is ignored.
kwcoco.CocoDataset.find_representative_images - Find images that have a wide array of categories. Attempt to find the fewest images that cover all categories using images that contain both a large and small number of annotations.
kwcoco.CocoDataset.load_annot_sample - Reads the chip of an annotation. Note this is much less efficient than using a sampler, but it doesn’t require disk cache.
kwcoco.CocoDataset.subset - Return a subset of the larger coco dataset by specifying which images to port. All annotations in those images will be taken.
kwcoco.CocoDataset.union - Merges multiple CocoDataset items into one. Names and associations are retained, but ids may be different.
kwcoco.CocoDataset.view_sql - Create a cached SQL interface to this dataset suitable for large scale multiprocessing use cases.
# Generate test data
xdoctest-mkwcoco.cli.coco_evalCocoEvalCLI.main
kwcocoeval\--true_dataset=$HOME/.cache/kwcoco/tests/eval/true.mscoco.json\--pred_dataset=$HOME/.cache/kwcoco/tests/eval/pred.mscoco.json\--out_dpath=$HOME/.cache/kwcoco/tests/eval/out\--force_pycocoutils=False\--area_range=all,0-4096,4096-inf
nautilus$HOME/.cache/kwcoco/tests/eval/out
default (dict | None) – overrides the class defaults
cmdline (bool | List[str] | str | dict) – If False, then no command line information is used.
If True, then sys.argv is parsed and used.
If a list of strings that used instead of sys.argv.
If a string, then that is parsed using shlex and used instead
of sys.argv.
If a dictionary grants fine grained controls over the args
passed to Config._read_argv(). Can contain:
strict (bool): defaults to False
argv (List[str]): defaults to None
special_options (bool): defaults to True
autocomplete (bool): defaults to False
Defaults to False.
Note
Avoid setting cmdline parameter here. Instead prefer
to use the cli classmethod to create a command line
aware config instance..
default (dict | None) – overrides the class defaults
cmdline (bool | List[str] | str | dict) – If False, then no command line information is used.
If True, then sys.argv is parsed and used.
If a list of strings that used instead of sys.argv.
If a string, then that is parsed using shlex and used instead
of sys.argv.
If a dictionary grants fine grained controls over the args
passed to Config._read_argv(). Can contain:
strict (bool): defaults to False
argv (List[str]): defaults to None
special_options (bool): defaults to True
autocomplete (bool): defaults to False
Defaults to False.
Note
Avoid setting cmdline parameter here. Instead prefer
to use the cli classmethod to create a command line
aware config instance..
Move a kwcoco file to a new location while maintaining relative paths.
This is equivalent to a regular copy followed by kwcocoreroot followed
by a delete of the original.
Visualize a COCO image using matplotlib or opencv, optionally writing
it to disk
Parameters:
data (object) – filepath, dict, or None
default (dict | None) – overrides the class defaults
cmdline (bool | List[str] | str | dict) – If False, then no command line information is used.
If True, then sys.argv is parsed and used.
If a list of strings that used instead of sys.argv.
If a string, then that is parsed using shlex and used instead
of sys.argv.
If a dictionary grants fine grained controls over the args
passed to Config._read_argv(). Can contain:
strict (bool): defaults to False
argv (List[str]): defaults to None
special_options (bool): defaults to True
autocomplete (bool): defaults to False
Defaults to False.
Note
Avoid setting cmdline parameter here. Instead prefer
to use the cli classmethod to create a command line
aware config instance..
Splits a coco files into two parts base on some criteria.
Useful for generating quick and dirty train/test splits, but in general
users should opt for using kwcocosubset instead to explicitly
construct these splits based on domain knowledge.
Split a single COCO dataset into two sub-datasets.
Parameters:
data (object) – filepath, dict, or None
default (dict | None) – overrides the class defaults
cmdline (bool | List[str] | str | dict) – If False, then no command line information is used.
If True, then sys.argv is parsed and used.
If a list of strings that used instead of sys.argv.
If a string, then that is parsed using shlex and used instead
of sys.argv.
If a dictionary grants fine grained controls over the args
passed to Config._read_argv(). Can contain:
strict (bool): defaults to False
argv (List[str]): defaults to None
special_options (bool): defaults to True
autocomplete (bool): defaults to False
Defaults to False.
Note
Avoid setting cmdline parameter here. Instead prefer
to use the cli classmethod to create a command line
aware config instance..
Create COCO toydata for demo and testing purposes.
Parameters:
data (object) – filepath, dict, or None
default (dict | None) – overrides the class defaults
cmdline (bool | List[str] | str | dict) – If False, then no command line information is used.
If True, then sys.argv is parsed and used.
If a list of strings that used instead of sys.argv.
If a string, then that is parsed using shlex and used instead
of sys.argv.
If a dictionary grants fine grained controls over the args
passed to Config._read_argv(). Can contain:
strict (bool): defaults to False
argv (List[str]): defaults to None
special_options (bool): defaults to True
autocomplete (bool): defaults to False
Defaults to False.
Note
Avoid setting cmdline parameter here. Instead prefer
to use the cli classmethod to create a command line
aware config instance..
Converts the raw camvid format to an MSCOCO based format, ( which lets use
use kwcoco’s COCO backend).
Example
>>> # xdoctest: +REQUIRES(--download)>>> camvid_raw_info=grab_raw_camvid()>>> # test with a reduced set of data>>> delcamvid_raw_info['img_paths'][2:]>>> delcamvid_raw_info['mask_paths'][2:]>>> dset=convert_camvid_raw_to_coco(camvid_raw_info)>>> # xdoctest: +REQUIRES(--show)>>> importkwplot>>> plt=kwplot.autoplt()>>> kwplot.figure(fnum=1,pnum=(1,2,1))>>> dset.show_image(gid=1)>>> kwplot.figure(fnum=1,pnum=(1,2,2))>>> dset.show_image(gid=2)
Generates “toydata” for demo and testing purposes.
Note
The implementation of demodata_toy_img and demodata_toy_dset should be
redone using the tools built for random_video_dset, which have more
extensible implementations.
dpath (str | PathLike | None) – path to the directory that will contain the bundle, (defaults to a
kwcoco cache dir). Ignored if bundle_dpath is given.
fpath (str | PathLike | None) – path to the kwcoco file. The parent will be the bundle if it is not
specified. Should be a descendant of the dpath if specified.
bundle_dpath (str | PathLike | None) – path to the directory that will store images. If specified, dpath
is ignored. If unspecified, a bundle will be written inside
dpath.
aux (bool | None) – if True generates dummy auxiliary channels
verbose (int) – verbosity mode. default=3
use_cache (bool) – if True caches the generated json in the
dpath. Default=True
**kwargs – used for old backwards compatible argument names
gsize - alias for image_size
>>> fromkwcoco.demo.toydata_videoimport*# NOQA>>> multispectral=True>>> dset=random_single_video_dset(num_frames=1,num_tracks=1,multispectral=True)>>> dset._check_json_serializable()>>> dset.dataset['images']>>> assertdset.imgs[1]['auxiliary'][1]['channels']>>> # test that we can render>>> render_toy_dataset(dset,rng=0,dpath=None,renderkw={})
Example
>>> fromkwcoco.demo.toydata_videoimport*# NOQA>>> dset=random_single_video_dset(num_frames=4,num_tracks=1,multispectral=True,multisensor=True,image_size='random',rng=2338)>>> dset._check_json_serializable()>>> assertdset.imgs[1]['auxiliary'][1]['channels']>>> # Print before and after render>>> #print('multisensor-images = {}'.format(ub.urepr(dset.dataset['images'], nl=-2)))>>> #print('multisensor-images = {}'.format(ub.urepr(dset.dataset, nl=-2)))>>> print(ub.hash_data(dset.dataset))>>> # test that we can render>>> render_toy_dataset(dset,rng=0,dpath=None,renderkw={})>>> #print('multisensor-images = {}'.format(ub.urepr(dset.dataset['images'], nl=-2)))>>> # xdoctest: +REQUIRES(--show)>>> importkwplot>>> kwplot.autompl()>>> fromkwcoco.demo.toydata_videoimport_draw_video_sequence# NOQA>>> gids=[1,2,3,4]>>> final=_draw_video_sequence(dset,gids)>>> print('dset.fpath = {!r}'.format(dset.fpath))>>> kwplot.imshow(final)
Generates “toydata” for demo and testing purposes.
Loose image version of the toydata generators.
Note
The implementation of demodata_toy_img and demodata_toy_dset should be
redone using the tools built for random_video_dset, which have more
extensible implementations.
dpath (str | PathLike | None) – path to the directory that will contain the bundle, (defaults to a
kwcoco cache dir). Ignored if bundle_dpath is given.
fpath (str | PathLike | None) – path to the kwcoco file. The parent will be the bundle if it is not
specified. Should be a descendant of the dpath if specified.
bundle_dpath (str | PathLike | None) – path to the directory that will store images. If specified, dpath
is ignored. If unspecified, a bundle will be written inside
dpath.
aux (bool | None) – if True generates dummy auxiliary channels
verbose (int) – verbosity mode. default=3
use_cache (bool) – if True caches the generated json in the
dpath. Default=True
**kwargs – used for old backwards compatible argument names
gsize - alias for image_size
>>> fromkwcoco.demo.toydata_videoimport*# NOQA>>> multispectral=True>>> dset=random_single_video_dset(num_frames=1,num_tracks=1,multispectral=True)>>> dset._check_json_serializable()>>> dset.dataset['images']>>> assertdset.imgs[1]['auxiliary'][1]['channels']>>> # test that we can render>>> render_toy_dataset(dset,rng=0,dpath=None,renderkw={})
Example
>>> fromkwcoco.demo.toydata_videoimport*# NOQA>>> dset=random_single_video_dset(num_frames=4,num_tracks=1,multispectral=True,multisensor=True,image_size='random',rng=2338)>>> dset._check_json_serializable()>>> assertdset.imgs[1]['auxiliary'][1]['channels']>>> # Print before and after render>>> #print('multisensor-images = {}'.format(ub.urepr(dset.dataset['images'], nl=-2)))>>> #print('multisensor-images = {}'.format(ub.urepr(dset.dataset, nl=-2)))>>> print(ub.hash_data(dset.dataset))>>> # test that we can render>>> render_toy_dataset(dset,rng=0,dpath=None,renderkw={})>>> #print('multisensor-images = {}'.format(ub.urepr(dset.dataset['images'], nl=-2)))>>> # xdoctest: +REQUIRES(--show)>>> importkwplot>>> kwplot.autompl()>>> fromkwcoco.demo.toydata_videoimport_draw_video_sequence# NOQA>>> gids=[1,2,3,4]>>> final=_draw_video_sequence(dset,gids)>>> print('dset.fpath = {!r}'.format(dset.fpath))>>> kwplot.imshow(final)
Create toydata_video renderings for a preconstructed coco dataset.
Parameters:
dset (kwcoco.CocoDataset) – A dataset that contains special “renderable” annotations. (e.g.
the demo shapes). Each image can contain special fields that
influence how an image will be rendered.
Currently this process is simple, it just creates a noisy image
with the shapes superimposed over where they should exist as
indicated by the annotations. In the future this may become more
sophisticated.
Each item in dset.dataset[‘images’] will be modified to add
the “file_name” field indicating where the rendered data is writen.
rng (int | None | RandomState) – random state
dpath (str | PathLike | None) – The location to write the images to. If unspecified, it is written
to the rendered folder inside the kwcoco cache directory.
renderkw (dict | None) – See render_toy_image() for details.
Also takes imwrite keywords args only handled in this function.
TODO better docs.
Construct category patterns from either defaults or only with specific
categories. Can accept either an existig category pattern object, a
list of known catnames, or mscoco category dictionaries.
This function is used to populate kpts and sseg information in the
autogenerated coco dataset before rendering. It is redundant with other
functionality.
>>> # hack to allow chip to be None>>> chip=None>>> size=(32,32)>>> cname='superstar'>>> self=CategoryPatterns.coerce()>>> self._from_elem(cname,chip,size)
[ ] _fast_pdist_priority: Look at absolute difference in sibling entropy
when deciding whether to go up or down in the tree.
[ ] medschool applications true-pred matching (applicant proposing) fast
algorithm.
[ ] Maybe looping over truth rather than pred is faster? but it makes you
have to combine pred score / ious, which is weird.
[x] preallocate ndarray and use hstack to build confusion vectors?
doesn’t help
[ ] relevant classes / classes / classes-of-interest we care about needs
to be a first class member of detection metrics.
[ ] Add parameter that allows one prediction to “match” to more than one
truth object. (example: we have a duck detector problem and all the
ducks in a row are annotated as separate object, and we only care about
getting the group)
bias (float, default=0.0) – for computing bounding box overlap, either 1 or 0
gids (List[int], default=None) – which subset of images ids to compute confusion metrics on. If
not specified all images are used.
compat (str, default=’all’) – can be (‘ancestors’ | ‘mutex’ | ‘all’). determines which pred
boxes are allowed to match which true boxes. If ‘mutex’, then
pred boxes can only match true boxes of the same class. If
‘ancestors’, then pred boxes can match true boxes that match or
have a coarser label. If ‘all’, then any pred can match any
true, regardless of its category label.
prioritize (str, default=’iou’) – can be (‘iou’ | ‘class’ | ‘correct’) determines which box to
assign to if mutiple true boxes overlap a predicted box. if
prioritize is iou, then the true box with maximum iou (above
iou_thresh) will be chosen. If prioritize is class, then it will
prefer matching a compatible class above a higher iou. If
prioritize is correct, then ancestors of the true class are
preferred over descendents of the true class, over unreleated
classes.
bg_cidx (int, default=-1) – The index of the background class. The index used in the truth
column when a predicted bounding box does not match any true
bounding box.
classes (List[str] | kwcoco.CategoryTree) – mapping from class indices to class names. Can also contain class
heirarchy information.
ignore_classes (str | List[str]) – class name(s) indicating ignore regions
max_dets (int) – maximum number of detections to consider
Todo
[ ] This is a bottleneck function. An implementation in C / C++ /
Cython would likely improve the overall system.
[ ] Implement crowd truth. Allow multiple predictions to match any
truth objet marked as “iscrowd”.
Returns:
with relevant confusion vectors. This keys of this dict can be
interpreted as columns of a data frame. The txs / pxs columns
represent the indexes of the true / predicted annotations that were
assigned as matching. Additionally each row also contains the true
and predicted class index, the predicted score, the true weight and
the iou of the true and predicted boxes. A txs value of -1 means
that the predicted box was not assigned to a true annotation and a
pxs value of -1 means that the true annotation was not assigne to
any predicted annotation.
Computes a classification report which is a collection of various metrics
commonly used to evaulate classification quality. This can handle binary
and multiclass settings.
Note that this function does not accept probabilities or scores and must
instead act on final decisions. See ovr_classification_report for a
probability based report function using a one-vs-rest strategy.
This emulates the bm(cm) Matlab script [MatlabBM] written by David Powers
that is used for computing bookmaker, markedness, and various other scores
and is based on the paper [PowersMetrics].
Jurman, Riccadonna, Furlanello, (2012). A Comparison of MCC and CEN Error Measures in MultiClass Prediction
Parameters:
y_true (ndarray) – true labels for each item
y_pred (ndarray) – predicted labels for each item
target_names (List | None) – mapping from label to category name
sample_weight (ndarray | None) – weight for each item
verbose (int) – print if True
log (callable | None) – print or logging function
remove_unsupported (bool) – removes categories that have no support.
Defaults to False.
ascii_only (bool) – if True dont use unicode characters.
if the environ ASCII_ONLY is present this is forced to True and
cannot be undone. Defaults to False.
tocombine (List[Measures]) – a list of measures to combine into one
precision (int | None) – If specified rounds thresholds to this precision which can
prevent a RAM explosion when combining a large number of
measures. However, this is a lossy operation and will impact
the underlying scores. NOTE: use growth instead.
growth (int | None) – if specified this limits how much the resulting measures
are allowed to grow by. If None, growth is unlimited.
Otherwise, if growth is ‘max’, the growth is limited to the
maximum length of an input. We might make this more numerical
in the future.
thresh_bins (int | None) – Force this many threshold bins.
>>> # Demonstrate issues that can arrise from choosing a precision>>> # that is too low when combining metrics. Breakpoints>>> # between different metrics can get muddled, but choosing a>>> # precision that is too high can overwhelm memory.>>> fromkwcoco.metrics.confusion_measuresimport*# NOQA>>> base=ub.map_vals(np.asarray,{>>> 'tp_count':[1,1,2,2,2,2,3],>>> 'fp_count':[0,1,1,2,3,4,5],>>> 'fn_count':[1,1,0,0,0,0,0],>>> 'tn_count':[5,4,4,3,2,1,0],>>> 'thresholds':[.0,.0,.0,.0,.0,.0,.0],>>> })>>> # Make tiny offsets to thresholds>>> rng=kwarray.ensure_rng(0)>>> n=len(base['thresholds'])>>> offsets=[>>> sorted(rng.rand(n)*10**-rng.randint(4,7))[::-1]>>> for_inrange(20)>>> ]>>> tocombine=[]>>> foroffsetinoffsets:>>> base_n=base.copy()>>> base_n['thresholds']+=offset>>> measures_n=Measures(base_n).reconstruct()>>> tocombine.append(measures_n)>>> forprecisionin[6,5,2]:>>> combo=Measures.combine(tocombine,precision=precision).reconstruct()>>> print('precision = {!r}'.format(precision))>>> print('combo = {}'.format(ub.urepr(combo,nl=1)))>>> print('num_thresholds = {}'.format(len(combo['thresholds'])))>>> forgrowthin[None,'max','log','root','half']:>>> combo=Measures.combine(tocombine,growth=growth).reconstruct()>>> print('growth = {!r}'.format(growth))>>> print('combo = {}'.format(ub.urepr(combo,nl=1)))>>> print('num_thresholds = {}'.format(len(combo['thresholds'])))>>> #print(combo.counts().pandas())
Example
>>> # Test case: combining a single measures should leave it unchanged>>> fromkwcoco.metrics.confusion_measuresimport*# NOQA>>> measures=Measures.demo(n=40,p_true=0.2,p_error=0.4,p_miss=0.6)>>> df1=measures.counts().pandas().fillna(0)>>> print(df1)>>> tocombine=[measures]>>> combo=Measures.combine(tocombine)>>> df2=combo.counts().pandas().fillna(0)>>> print(df2)>>> assertnp.allclose(df1,df2)
>>> # I am NOT sure if this is correct or not>>> thresh_bins=20>>> combo=Measures.combine(tocombine,thresh_bins=thresh_bins)>>> df4=combo.counts().pandas().fillna(0)>>> print(df4)
Creates a binary representation useful for measuring the performance of
detectors. It is assumed that scores of “positive” classes should be
high and “negative” clases should be low.
Parameters:
negative_classes (List[str | int] | None) – list of negative class names or idxs, by default chooses any
class with a true class index of -1. These classes should
ideally have low scores.
Returns:
BinaryConfusionVectors
Note
The “classlessness” of this depends on the compat=”all” argument
being used when constructing confusion vectors, otherwise it
becomes something like a macro-average because the class
information was used in deciding which true and predicted boxes
were allowed to match.
Creates binary confusion measures for every one-versus-rest category.
Parameters:
stabalize_thresh (int) – if fewer than this many data points inserts dummy stabilization
data so curves can still be drawn. Default to 7.
fp_cutoff (int | None) – maximum number of false positives in the truncated roc curves.
The default None is equivalent to float('inf')
monotonic_ppv (bool) – if True ensures that precision is always increasing as recall
decreases. This is done in pycocotools scoring, but I’m not
sure its a good idea. Default to True.
Stores information about a binary classification problem.
This is always with respect to a specific class, which is given
by cx and classes.
The data DataFrameArray must contain
is_true - if the row is an instance of class classes[cx]
pred_score - the predicted probability of class classes[cx], and
weight - sample weight of the example
stabalize_thresh (int, default=7) – if fewer than this many data points inserts dummy stabalization
data so curves can still be drawn.
fp_cutoff (int | None) – maximum number of false positives in the truncated roc curves.
The default of None is equivalent to float('inf')
monotonic_ppv (bool) – if True ensures that precision is always increasing as recall
decreases. This is done in pycocotools scoring, but I’m not
sure its a good idea.
classes (kwcoco.CategoryTree | None) – the categories to be scored, if unspecified attempts to
determine these from the truth detections
Example
>>> # Demo how to use detection metrics directly given detections only>>> # (no kwcoco file required)>>> fromkwcoco.metricsimportdetect_metrics>>> importkwimage>>> # Setup random true detections (these are just boxes and scores)>>> true_dets=kwimage.Detections.random(3)>>> # Peek at the simple internals of a detections object>>> print('true_dets.data = {}'.format(ub.urepr(true_dets.data,nl=1)))>>> # Create similar but different predictions>>> true_subset=true_dets.take([1,2]).warp(kwimage.Affine.coerce({'scale':1.1}))>>> false_positive=kwimage.Detections.random(3)>>> pred_dets=kwimage.Detections.concatenate([true_subset,false_positive])>>> dmet=DetectionMetrics()>>> dmet.add_predictions(pred_dets,imgname='image1')>>> dmet.add_truth(true_dets,imgname='image1')>>> # Raw confusion vectors>>> cfsn_vecs=dmet.confusion_vectors()>>> print(cfsn_vecs.data.pandas().to_string())>>> # Our scoring definition (derived from confusion vectors)>>> print(dmet.score_kwcoco())>>> # VOC scoring>>> print(dmet.score_voc(bias=0))>>> # Original pycocotools scoring>>> # xdoctest: +REQUIRES(module:pycocotools)>>> print(dmet.score_pycocotools())
Example
>>> dmet=DetectionMetrics.demo(>>> nimgs=100,nboxes=(0,3),n_fp=(0,1),classes=8,score_noise=0.9,hacked=False)>>> print(dmet.score_kwcoco(bias=0,compat='mutex',prioritize='iou')['mAP'])...>>> # NOTE: IN GENERAL NETHARN AND VOC ARE NOT THE SAME>>> print(dmet.score_voc(bias=0)['mAP'])0.8582...>>> #print(dmet.score_coco()['mAP'])
Assigns predicted boxes to the true boxes so we can transform the
detection problem into a classification problem for scoring.
Parameters:
iou_thresh (float | List[float]) – bounding box overlap iou threshold required for assignment
if a list, then return type is a dict. Defaults to 0.5
bias (float) – for computing bounding box overlap, either 1 or 0
Defaults to 0.
gids (List[int] | None) – which subset of images ids to compute confusion metrics on. If
not specified all images are used. Defaults to None.
compat (str) – can be (‘ancestors’ | ‘mutex’ | ‘all’). determines which pred
boxes are allowed to match which true boxes. If ‘mutex’, then
pred boxes can only match true boxes of the same class. If
‘ancestors’, then pred boxes can match true boxes that match or
have a coarser label. If ‘all’, then any pred can match any
true, regardless of its category label. Defaults to all.
prioritize (str) – can be (‘iou’ | ‘class’ | ‘correct’) determines which box to
assign to if mutiple true boxes overlap a predicted box. if
prioritize is iou, then the true box with maximum iou (above
iou_thresh) will be chosen. If prioritize is class, then it will
prefer matching a compatible class above a higher iou. If
prioritize is correct, then ancestors of the true class are
preferred over descendents of the true class, over unreleated
classes. Default to ‘iou’
background_class (str | NoParamType) – Name of the background class. If unspecified we try to
determine it with heuristics. A value of None means there is no
background class.
verbose (int | str) – verbosity flag. Default to ‘auto’. In auto mode,
verbose=1 if len(gids) > 1000.
workers (int) – number of parallel assignment processes. Defaults to 0
track_probs (str) – can be ‘try’, ‘force’, or False. if truthy, we assume
probabilities for multiple classes are available. default=’try’
by default pycocotools computes average precision as the literal
average of computed precisions at 101 uniformly spaced recall
thresholds.
pycocoutils seems to only allow predictions with the same category
as the truth to match those truth objects. This should be the
same as calling dmet.confusion_vectors with compat = mutex
pycocoutils does not take into account the fact that each box often
has a score for each category.
pycocoutils will be incorrect if any annotation has an id of 0
a major difference in the way kwcoco scores versus pycocoutils is
the calculation of AP. The assignment between truth and predicted
detections produces similar enough results. Given our confusion
vectors we use the scikit-learn definition of AP, whereas
pycocoutils seems to compute precision and recall — more or less
correctly — but then it resamples the precision at various
specified recall thresholds (in the accumulate function,
specifically how pr is resampled into the q array). This
can lead to a large difference in reported scores.
pycocoutils also smooths out the precision such that it is
monotonic decreasing, which might not be the best idea.
pycocotools area ranges are inclusive on both ends, that means the
“small” and “medium” truth selections do overlap somewhat.
by default pycocotools computes average precision as the literal
average of computed precisions at 101 uniformly spaced recall
thresholds.
pycocoutils seems to only allow predictions with the same category
as the truth to match those truth objects. This should be the
same as calling dmet.confusion_vectors with compat = mutex
pycocoutils does not take into account the fact that each box often
has a score for each category.
pycocoutils will be incorrect if any annotation has an id of 0
a major difference in the way kwcoco scores versus pycocoutils is
the calculation of AP. The assignment between truth and predicted
detections produces similar enough results. Given our confusion
vectors we use the scikit-learn definition of AP, whereas
pycocoutils seems to compute precision and recall — more or less
correctly — but then it resamples the precision at various
specified recall thresholds (in the accumulate function,
specifically how pr is resampled into the q array). This
can lead to a large difference in reported scores.
pycocoutils also smooths out the precision such that it is
monotonic decreasing, which might not be the best idea.
pycocotools area ranges are inclusive on both ends, that means the
“small” and “medium” truth selections do overlap somewhat.
>>> kwargs={}>>> # Seed the RNG>>> kwargs['rng']=0>>> # Size parameters determine how big the data is>>> kwargs['nimgs']=5>>> kwargs['nboxes']=7>>> kwargs['classes']=11>>> # Noise parameters perterb predictions further from the truth>>> kwargs['n_fp']=3>>> kwargs['box_noise']=0.1>>> kwargs['cls_noise']=0.5>>> dmet=DetectionMetrics.demo(**kwargs)>>> print('dmet.classes = {}'.format(dmet.classes))dmet.classes = <CategoryTree(nNodes=12, maxDepth=3, maxBreadth=4...)>>>> # Can grab kwimage.Detection object for any image>>> print(dmet.true_detections(gid=0))<Detections(4)>>>> print(dmet.pred_detections(gid=0))<Detections(7)>
Example
>>> # Test case with null predicted categories>>> dmet=DetectionMetrics.demo(nimgs=30,null_pred=1,classes=3,>>> nboxes=10,n_fp=3,box_noise=0.1,>>> with_probs=False)>>> dmet.gid_to_pred_dets[0].data>>> dmet.gid_to_true_dets[0].data>>> cfsn_vecs=dmet.confusion_vectors()>>> binvecs_ovr=cfsn_vecs.binarize_ovr()>>> binvecs_per=cfsn_vecs.binarize_classless()>>> measures_per=binvecs_per.measures()>>> measures_ovr=binvecs_ovr.measures()>>> print('measures_per = {!r}'.format(measures_per))>>> print('measures_ovr = {!r}'.format(measures_ovr))>>> # xdoctest: +REQUIRES(--show)>>> importkwplot>>> kwplot.autompl()>>> measures_ovr['perclass'].draw(key='pr',fnum=2)
faster version of sklearn confusion matrix that avoids the
expensive checks and label rectification
Parameters:
y_true (ndarray) – ground truth class label for each sample
y_pred (ndarray) – predicted class label for each sample
n_labels (int) – number of labels
sample_weight (ndarray | None) – weight of each sample
Extended typing ndarray[Any,int|Float]
Returns:
matrix where rows represent real and cols represent pred and the
value at each cell is the total amount of weight
Extended typing ndarray[Shape['*,*'],Int64|Float64]
Faster pure-python versions of sklearn functions that avoid expensive checks
and label rectifications. It is assumed that all labels are consecutive
non-negative integers.
y_score (array, shape = [n_samples]) – Estimated probabilities or decision function
pos_label (int or str, default=None) – The label of the positive class
sample_weight (array-like of shape (n_samples,), default=None) – Sample weights.
Returns:
fps (array, shape = [n_thresholds]) – A count of false positives, at index i being the number of negative
samples assigned a score >= thresholds[i]. The total number of
negative samples is equal to fps[-1] (thus true negatives are given by
fps[-1] - fps).
tps (array, shape = [n_thresholds <= len(np.unique(y_score))]) – An increasing count of true positives, at index i being the number
of positive samples assigned a score >= thresholds[i]. The total
number of positive samples is equal to tps[-1] (thus false negatives
are given by tps[-1] - tps).
API to compute object detection scores using Pascal VOC evaluation method.
To use, add true and predicted detections for each image and then run the
VOC_Metrics.score() function.
Variables:
recs (Dict[int, List[dict]]) – true boxes for each image. maps image ids to a list of records
within that image. Each record is a tlbr bbox, a difficult flag,
and a class name.
cx_to_lines (Dict[int, List]) – VOC formatted prediction preditions. mapping from class index to
all predictions for that category. Each “line” is a list of
[[<imgid>, <score>, <tl_x>, <tl_y>, <br_x>, <br_y>]].
lines (List[list]) – VOC formatted predictions. Each “line” is a list
of [[<imgid>, <score>, <tl_x>, <tl_y>, <br_x>, <br_y>]].
recs (Dict[int, List[dict]) – true boxes for each image.
maps image ids to a list of records within that image.
Each record is a tlbr bbox, a difficult flag, and a class name.
classname (str) – the category to evaluate.
method (str) – code for how the AP is computed.
bias (float) – either 1.0 or 0.0.
Returns:
info about the evaluation containing AP. Contains fp, tp, prec,
rec,
Return type:
Dict
Note
Raw replication of matlab implementation of creating assignments and
the resulting PR-curves and AP. Based on MATLAB code [1].
Stores information about a binary classification problem.
This is always with respect to a specific class, which is given
by cx and classes.
The data DataFrameArray must contain
is_true - if the row is an instance of class classes[cx]
pred_score - the predicted probability of class classes[cx], and
weight - sample weight of the example
stabalize_thresh (int, default=7) – if fewer than this many data points inserts dummy stabalization
data so curves can still be drawn.
fp_cutoff (int | None) – maximum number of false positives in the truncated roc curves.
The default of None is equivalent to float('inf')
monotonic_ppv (bool) – if True ensures that precision is always increasing as recall
decreases. This is done in pycocotools scoring, but I’m not
sure its a good idea.
Creates a binary representation useful for measuring the performance of
detectors. It is assumed that scores of “positive” classes should be
high and “negative” clases should be low.
Parameters:
negative_classes (List[str | int] | None) – list of negative class names or idxs, by default chooses any
class with a true class index of -1. These classes should
ideally have low scores.
Returns:
BinaryConfusionVectors
Note
The “classlessness” of this depends on the compat=”all” argument
being used when constructing confusion vectors, otherwise it
becomes something like a macro-average because the class
information was used in deciding which true and predicted boxes
were allowed to match.
classes (kwcoco.CategoryTree | None) – the categories to be scored, if unspecified attempts to
determine these from the truth detections
Example
>>> # Demo how to use detection metrics directly given detections only>>> # (no kwcoco file required)>>> fromkwcoco.metricsimportdetect_metrics>>> importkwimage>>> # Setup random true detections (these are just boxes and scores)>>> true_dets=kwimage.Detections.random(3)>>> # Peek at the simple internals of a detections object>>> print('true_dets.data = {}'.format(ub.urepr(true_dets.data,nl=1)))>>> # Create similar but different predictions>>> true_subset=true_dets.take([1,2]).warp(kwimage.Affine.coerce({'scale':1.1}))>>> false_positive=kwimage.Detections.random(3)>>> pred_dets=kwimage.Detections.concatenate([true_subset,false_positive])>>> dmet=DetectionMetrics()>>> dmet.add_predictions(pred_dets,imgname='image1')>>> dmet.add_truth(true_dets,imgname='image1')>>> # Raw confusion vectors>>> cfsn_vecs=dmet.confusion_vectors()>>> print(cfsn_vecs.data.pandas().to_string())>>> # Our scoring definition (derived from confusion vectors)>>> print(dmet.score_kwcoco())>>> # VOC scoring>>> print(dmet.score_voc(bias=0))>>> # Original pycocotools scoring>>> # xdoctest: +REQUIRES(module:pycocotools)>>> print(dmet.score_pycocotools())
Example
>>> dmet=DetectionMetrics.demo(>>> nimgs=100,nboxes=(0,3),n_fp=(0,1),classes=8,score_noise=0.9,hacked=False)>>> print(dmet.score_kwcoco(bias=0,compat='mutex',prioritize='iou')['mAP'])...>>> # NOTE: IN GENERAL NETHARN AND VOC ARE NOT THE SAME>>> print(dmet.score_voc(bias=0)['mAP'])0.8582...>>> #print(dmet.score_coco()['mAP'])
Assigns predicted boxes to the true boxes so we can transform the
detection problem into a classification problem for scoring.
Parameters:
iou_thresh (float | List[float]) – bounding box overlap iou threshold required for assignment
if a list, then return type is a dict. Defaults to 0.5
bias (float) – for computing bounding box overlap, either 1 or 0
Defaults to 0.
gids (List[int] | None) – which subset of images ids to compute confusion metrics on. If
not specified all images are used. Defaults to None.
compat (str) – can be (‘ancestors’ | ‘mutex’ | ‘all’). determines which pred
boxes are allowed to match which true boxes. If ‘mutex’, then
pred boxes can only match true boxes of the same class. If
‘ancestors’, then pred boxes can match true boxes that match or
have a coarser label. If ‘all’, then any pred can match any
true, regardless of its category label. Defaults to all.
prioritize (str) – can be (‘iou’ | ‘class’ | ‘correct’) determines which box to
assign to if mutiple true boxes overlap a predicted box. if
prioritize is iou, then the true box with maximum iou (above
iou_thresh) will be chosen. If prioritize is class, then it will
prefer matching a compatible class above a higher iou. If
prioritize is correct, then ancestors of the true class are
preferred over descendents of the true class, over unreleated
classes. Default to ‘iou’
background_class (str | NoParamType) – Name of the background class. If unspecified we try to
determine it with heuristics. A value of None means there is no
background class.
verbose (int | str) – verbosity flag. Default to ‘auto’. In auto mode,
verbose=1 if len(gids) > 1000.
workers (int) – number of parallel assignment processes. Defaults to 0
track_probs (str) – can be ‘try’, ‘force’, or False. if truthy, we assume
probabilities for multiple classes are available. default=’try’
by default pycocotools computes average precision as the literal
average of computed precisions at 101 uniformly spaced recall
thresholds.
pycocoutils seems to only allow predictions with the same category
as the truth to match those truth objects. This should be the
same as calling dmet.confusion_vectors with compat = mutex
pycocoutils does not take into account the fact that each box often
has a score for each category.
pycocoutils will be incorrect if any annotation has an id of 0
a major difference in the way kwcoco scores versus pycocoutils is
the calculation of AP. The assignment between truth and predicted
detections produces similar enough results. Given our confusion
vectors we use the scikit-learn definition of AP, whereas
pycocoutils seems to compute precision and recall — more or less
correctly — but then it resamples the precision at various
specified recall thresholds (in the accumulate function,
specifically how pr is resampled into the q array). This
can lead to a large difference in reported scores.
pycocoutils also smooths out the precision such that it is
monotonic decreasing, which might not be the best idea.
pycocotools area ranges are inclusive on both ends, that means the
“small” and “medium” truth selections do overlap somewhat.
by default pycocotools computes average precision as the literal
average of computed precisions at 101 uniformly spaced recall
thresholds.
pycocoutils seems to only allow predictions with the same category
as the truth to match those truth objects. This should be the
same as calling dmet.confusion_vectors with compat = mutex
pycocoutils does not take into account the fact that each box often
has a score for each category.
pycocoutils will be incorrect if any annotation has an id of 0
a major difference in the way kwcoco scores versus pycocoutils is
the calculation of AP. The assignment between truth and predicted
detections produces similar enough results. Given our confusion
vectors we use the scikit-learn definition of AP, whereas
pycocoutils seems to compute precision and recall — more or less
correctly — but then it resamples the precision at various
specified recall thresholds (in the accumulate function,
specifically how pr is resampled into the q array). This
can lead to a large difference in reported scores.
pycocoutils also smooths out the precision such that it is
monotonic decreasing, which might not be the best idea.
pycocotools area ranges are inclusive on both ends, that means the
“small” and “medium” truth selections do overlap somewhat.
>>> kwargs={}>>> # Seed the RNG>>> kwargs['rng']=0>>> # Size parameters determine how big the data is>>> kwargs['nimgs']=5>>> kwargs['nboxes']=7>>> kwargs['classes']=11>>> # Noise parameters perterb predictions further from the truth>>> kwargs['n_fp']=3>>> kwargs['box_noise']=0.1>>> kwargs['cls_noise']=0.5>>> dmet=DetectionMetrics.demo(**kwargs)>>> print('dmet.classes = {}'.format(dmet.classes))dmet.classes = <CategoryTree(nNodes=12, maxDepth=3, maxBreadth=4...)>>>> # Can grab kwimage.Detection object for any image>>> print(dmet.true_detections(gid=0))<Detections(4)>>>> print(dmet.pred_detections(gid=0))<Detections(7)>
Example
>>> # Test case with null predicted categories>>> dmet=DetectionMetrics.demo(nimgs=30,null_pred=1,classes=3,>>> nboxes=10,n_fp=3,box_noise=0.1,>>> with_probs=False)>>> dmet.gid_to_pred_dets[0].data>>> dmet.gid_to_true_dets[0].data>>> cfsn_vecs=dmet.confusion_vectors()>>> binvecs_ovr=cfsn_vecs.binarize_ovr()>>> binvecs_per=cfsn_vecs.binarize_classless()>>> measures_per=binvecs_per.measures()>>> measures_ovr=binvecs_ovr.measures()>>> print('measures_per = {!r}'.format(measures_per))>>> print('measures_ovr = {!r}'.format(measures_ovr))>>> # xdoctest: +REQUIRES(--show)>>> importkwplot>>> kwplot.autompl()>>> measures_ovr['perclass'].draw(key='pr',fnum=2)
tocombine (List[Measures]) – a list of measures to combine into one
precision (int | None) – If specified rounds thresholds to this precision which can
prevent a RAM explosion when combining a large number of
measures. However, this is a lossy operation and will impact
the underlying scores. NOTE: use growth instead.
growth (int | None) – if specified this limits how much the resulting measures
are allowed to grow by. If None, growth is unlimited.
Otherwise, if growth is ‘max’, the growth is limited to the
maximum length of an input. We might make this more numerical
in the future.
thresh_bins (int | None) – Force this many threshold bins.
>>> # Demonstrate issues that can arrise from choosing a precision>>> # that is too low when combining metrics. Breakpoints>>> # between different metrics can get muddled, but choosing a>>> # precision that is too high can overwhelm memory.>>> fromkwcoco.metrics.confusion_measuresimport*# NOQA>>> base=ub.map_vals(np.asarray,{>>> 'tp_count':[1,1,2,2,2,2,3],>>> 'fp_count':[0,1,1,2,3,4,5],>>> 'fn_count':[1,1,0,0,0,0,0],>>> 'tn_count':[5,4,4,3,2,1,0],>>> 'thresholds':[.0,.0,.0,.0,.0,.0,.0],>>> })>>> # Make tiny offsets to thresholds>>> rng=kwarray.ensure_rng(0)>>> n=len(base['thresholds'])>>> offsets=[>>> sorted(rng.rand(n)*10**-rng.randint(4,7))[::-1]>>> for_inrange(20)>>> ]>>> tocombine=[]>>> foroffsetinoffsets:>>> base_n=base.copy()>>> base_n['thresholds']+=offset>>> measures_n=Measures(base_n).reconstruct()>>> tocombine.append(measures_n)>>> forprecisionin[6,5,2]:>>> combo=Measures.combine(tocombine,precision=precision).reconstruct()>>> print('precision = {!r}'.format(precision))>>> print('combo = {}'.format(ub.urepr(combo,nl=1)))>>> print('num_thresholds = {}'.format(len(combo['thresholds'])))>>> forgrowthin[None,'max','log','root','half']:>>> combo=Measures.combine(tocombine,growth=growth).reconstruct()>>> print('growth = {!r}'.format(growth))>>> print('combo = {}'.format(ub.urepr(combo,nl=1)))>>> print('num_thresholds = {}'.format(len(combo['thresholds'])))>>> #print(combo.counts().pandas())
Example
>>> # Test case: combining a single measures should leave it unchanged>>> fromkwcoco.metrics.confusion_measuresimport*# NOQA>>> measures=Measures.demo(n=40,p_true=0.2,p_error=0.4,p_miss=0.6)>>> df1=measures.counts().pandas().fillna(0)>>> print(df1)>>> tocombine=[measures]>>> combo=Measures.combine(tocombine)>>> df2=combo.counts().pandas().fillna(0)>>> print(df2)>>> assertnp.allclose(df1,df2)
>>> # I am NOT sure if this is correct or not>>> thresh_bins=20>>> combo=Measures.combine(tocombine,thresh_bins=thresh_bins)>>> df4=combo.counts().pandas().fillna(0)>>> print(df4)
Creates binary confusion measures for every one-versus-rest category.
Parameters:
stabalize_thresh (int) – if fewer than this many data points inserts dummy stabilization
data so curves can still be drawn. Default to 7.
fp_cutoff (int | None) – maximum number of false positives in the truncated roc curves.
The default None is equivalent to float('inf')
monotonic_ppv (bool) – if True ensures that precision is always increasing as recall
decreases. This is done in pycocotools scoring, but I’m not
sure its a good idea. Default to True.
>>> # Test case that failed in initial implementation>>> # Due to incorrectly pushing channel selection under the concat>>> fromdelayed_imageimport*# NOQA>>> importkwimage>>> fpath=kwimage.grab_test_image_fpath()>>> base1=DelayedLoad(fpath,channels='r|g|b').prepare()>>> base2=DelayedLoad(fpath,channels='x|y|z').prepare().scale(2)>>> base3=DelayedLoad(fpath,channels='i|j|k').prepare().scale(2)>>> bands=[base2,base1[:,:,0].scale(2).evaluate(),>>> base1[:,:,1].evaluate().scale(2),>>> base1[:,:,2].evaluate().scale(2),base3]>>> delayed=DelayedChannelConcat(bands)>>> delayed=delayed.warp({'scale':2})>>> delayed=delayed[0:100,0:55,[0,2,4]]>>> delayed.write_network_text()>>> delayed.optimize()
Parameters:
parts (List[DelayedArray]) – data to concat
dsize (Tuple[int, int] | None) – size if known a-priori
This method returns a subset of the vision data with only the
specified bands / channels.
Parameters:
channels (List[int] | slice | channel_spec.FusedChannelSpec) – List of integers indexes, a slice, or a channel spec, which is
typically a pipe (|) delimited list of channel codes. See
ChannelSpec for more detials.
Returns:
a delayed vision operation that only operates on the following
channels.
Attempts to “undo” warping for each concatenated channel and returns a
list of delayed operations that are cropped to the right regions.
Typically you will retrain offset, theta, and shear to remove scale.
This ensures the data is spatially aligned up to a scale factor.
Parameters:
remove (List[str]) – if specified, list components of the warping to
remove. Can include: “offset”, “scale”, “shearx”, “theta”.
Typically set this to [“scale”].
retain (List[str]) – if specified, list components of the warping to
retain. Can include: “offset”, “scale”, “shearx”, “theta”.
Mutually exclusive with “remove”. If neither remove or retain
is specified, retain is set to [].
squash_nans (bool) – if True, pure nan channels are squashed into a 1x1 array as
they do not correspond to a real source.
return_warps (bool) – if True, return the transforms we applied. I.e. the transform
from the self to the returned parts.
This is useful when you need to warp objects in the original
space into the jagged space.
Returns:
The List[DelayedImage] are the parts i.e. the new images with the warping undone.
The List[kwimage.Affine]: is the transforms from self to each item in parts
>>> # Test optimize nans>>> fromdelayed_imageimportDelayedNans>>> importkwimage>>> base=DelayedNans(dsize=(100,100),channels='a|b|c')>>> self=base[0:10,0:5]>>> # Should simply return a new nan generator>>> new=self.optimize()>>> self.write_network_text()>>> new.write_network_text()>>> assertlen(new.as_graph().nodes)==1
>>> # Test a case that caused an error in development>>> fromdelayed_image.delayed_nodesimport*# NOQA>>> fromdelayed_imageimportDelayedLoad>>> fpath=kwimage.grab_test_image_fpath()>>> base=DelayedLoad(fpath,channels='r|g|b').prepare()>>> quantization={'quant_max':255,'nodata':0}>>> self=base.get_overview(1).dequantize(quantization)>>> self.write_network_text()>>> opt=self.optimize()
This method returns a subset of the vision data with only the
specified bands / channels.
Parameters:
channels (List[int] | slice | channel_spec.FusedChannelSpec) – List of integers indexes, a slice, or a channel spec, which is
typically a pipe (|) delimited list of channel codes. See
ChannelSpec for more detials.
Returns:
a new delayed load with a fused take channel operation
Attempts to “undo” warping for each concatenated channel and returns a
list of delayed operations that are cropped to the right regions.
Typically you will retrain offset, theta, and shear to remove scale.
This ensures the data is spatially aligned up to a scale factor.
Parameters:
remove (List[str]) – if specified, list components of the warping to
remove. Can include: “offset”, “scale”, “shearx”, “theta”.
Typically set this to [“scale”].
retain (List[str]) – if specified, list components of the warping to
retain. Can include: “offset”, “scale”, “shearx”, “theta”.
Mutually exclusive with “remove”. If neither remove or retain
is specified, retain is set to [].
squash_nans (bool) – if True, pure nan channels are squashed into a 1x1 array as
they do not correspond to a real source.
return_warp (bool) – if True, return the transform we applied.
This is useful when you need to warp objects in the original
space into the jagged space.
SeeAlso:
DelayedChannelConcat.undo_warps
Example
>>> # Test similar to undo_warps, but on each channel separately>>> fromdelayed_image.delayed_nodesimport*# NOQA>>> fromdelayed_image.delayed_leafsimportDelayedLoad>>> fromdelayed_image.delayed_leafsimportDelayedNans>>> importubeltasub>>> importkwimage>>> importkwarray>>> importnumpyasnp>>> # Demo case where we have different channels at different resolutions>>> base=DelayedLoad.demo(channels='r|g|b').prepare().dequantize({'quant_max':255})>>> bandR=base[:,:,0].scale(100/512)[:,:-50].evaluate()>>> bandG=base[:,:,1].scale(300/512).warp({'theta':np.pi/8,'about':(150,150)}).evaluate()>>> bandB=base[:,:,2].scale(600/512)[:150,:].evaluate()>>> bandN=DelayedNans((600,600),channels='N')>>> B0=bandR.scale(6,dsize=(600,600)).optimize()>>> B1=bandG.warp({'theta':-np.pi/8,'about':(150,150)}).scale(2,dsize=(600,600)).optimize()>>> B2=bandB.scale(1,dsize=(600,600)).optimize()>>> vidspace_box=kwimage.Boxes([[-10,-10,270,160]],'ltrb').scale(1/.7).quantize()>>> vidspace_poly=vidspace_box.to_polygons()[0]>>> vidspace_slice=vidspace_box.to_slices()[0]>>> # Test with the padded crop>>> self0=B0.crop(vidspace_slice,wrap=0,clip=0,pad=10).optimize()>>> self1=B1.crop(vidspace_slice,wrap=0,clip=0,pad=10).optimize()>>> self2=B2.crop(vidspace_slice,wrap=0,clip=0,pad=10).optimize()>>> parts=[self0,self1,self2]>>> # Run the undo on each channel>>> undone_scale_parts=[d.undo_warp(remove=['scale'])fordinparts]>>> print('--- Aligned --- ')>>> stackable_aligned=[]>>> fordinparts:>>> d.write_network_text()>>> stackable_aligned.append(d.finalize())>>> print('--- Undone Scale --- ')>>> stackable_undone_scale=[]>>> forundoneinundone_scale_parts:... undone.write_network_text()... stackable_undone_scale.append(undone.finalize())>>> # xdoctest: +REQUIRES(--show)>>> importkwplot>>> kwplot.autompl()>>> canvas0=kwimage.stack_images(stackable_aligned,axis=1,pad=5,bg_value='kw_darkgray')>>> canvas2=kwimage.stack_images(stackable_undone_scale,axis=1,pad=5,bg_value='kw_darkgray')>>> canvas0=kwimage.draw_header_text(canvas0,'Rescaled Channels')>>> canvas2=kwimage.draw_header_text(canvas2,'Native Scale Channels')>>> canvas=kwimage.stack_images([canvas0,canvas2],axis=0,bg_value='kw_darkgray')>>> canvas=kwimage.fill_nans_with_checkers(canvas)>>> kwplot.imshow(canvas)
This is the starting point for most delayed operations. Disk IO is avoided
until the finalize operation is called. Calling prepare can read
image headers if metadata like the image width, height, and number of
channels is not provided, but most operations can be performed while these
are still unknown.
If a gdal backend is available, and the underlying image is in the
appropriate formate (e.g. COG), finalize will return a lazy reference that
enables fast overviews and crops. For image formats that do not allow for
tiling / overviews, then there is no way to avoid reading entire image as
an ndarray.
If metadata like dsize and channels are not provided, then the
prepare() can be used to auto-populate them at the cost of the
disk IO to read image headers.
Parameters:
key (str) – which test image to grab. Valid choices are:
astro - an astronaught
carl - Carl Sagan
paraview - ParaView logo
stars - picture of stars in the sky
channels (str) – if specified, these channels will be stored in the delayed load
metadata. Note: these are not auto-populated. Usually the
key corresponds to 3-channel data,
dsize (None | Tuple[int, int]) – if specified, we will return a variant of the data with the
specific dsize
nodata_method (str | None) – How to handle nodata values in the file itself.
Can be “auto”, “float”, or “ma”.
overviews (None | int) – if specified, will return a variant of the data with overviews
>>> # Check difference between finalize and _finalize>>> fromdelayed_image.delayed_leafsimport*# NOQA>>> self=DelayedLoad.demo().prepare()>>> final_arr=self.finalize()>>> assertisinstance(final_arr,np.ndarray),'finalize should always return an array'>>> final_ref=self._finalize()>>> ifself.lazy_refisnotNotImplemented:>>> assertnotisinstance(final_ref,np.ndarray),(>>> 'A pure load with gdal should return a reference that is '>>> 'similiar to but not quite an array')
Builds the underlying graph structure as a networkx graph with human
readable labels.
Parameters:
fields (str | List[str]) – Add the specified fields as labels. If ‘auto’ then does
somthing “reasonable”. If ‘all’ then shows everything.
TODO: only implemented for “auto” and “all”, implement general
field selection (PR Wanted).
fields (str | List[str]) – Add the specified fields as labels. If ‘auto’ then does
somthing “reasonable”. If ‘all’ then shows everything.
TODO: only implemented for “auto” and “all”, implement general
field selection (PR Wanted).
with_labels (bool) – set to false for no label data
rich (bool | str) – defaults to ‘auto’
vertical_chains (bool) – Defaults to True. Set to false to save vertical space at the
cost of horizontal space.
This is the method that new nodes should overload.
Conceptually this works just like the finalize method with the
exception that it happens at every node in the tree, whereas the public
facing method only happens once, calls this, and is able to do one-time
pre and post operations.
If the underlying image being loaded has precomputed overviews it simply
loads these instead of downsampling the original image, which is more
efficient.
Example
>>> # xdoctest: +REQUIRES(module:osgeo)>>> # Make a complex chain of operations and optimize it>>> fromdelayed_imageimport*# NOQA>>> importkwimage>>> fpath=kwimage.grab_test_image_fpath(overviews=3)>>> dimg=DelayedLoad(fpath,channels='r|g|b').prepare()>>> dimg=dimg.get_overview(1)>>> dimg=dimg.get_overview(1)>>> dimg=dimg.get_overview(1)>>> dopt=dimg.optimize()>>> if1:>>> importnetworkxasnx>>> dimg.write_network_text()>>> dopt.write_network_text()>>> print(ub.urepr(dopt.nesting(),nl=-1,sort=0))>>> final0=dimg._finalize()[:]>>> final1=dopt._finalize()[:]>>> assertfinal0.shape==final1.shape>>> # xdoctest: +REQUIRES(--show)>>> importkwplot>>> kwplot.autompl()>>> kwplot.imshow(final0,pnum=(1,2,1),fnum=1,title='raw')>>> kwplot.imshow(final1,pnum=(1,2,2),fnum=1,title='optimized')
Parameters:
subdata (DelayedArray) – data to operate on
overview (int) – the overview to use (assuming it exists)
transform (ndarray | dict | kwimage.Affine) – a coercable affine matrix. See kwimage.Affine for
details on what can be coerced.
dsize (Tuple[int, int] | str) – The width / height of the output canvas. If ‘auto’, dsize is
computed such that the positive coordinates of the warped image
will fit in the new canvas. In this case, any pixel that maps
to a negative coordinate will be clipped. This has the
property that the input transformation is not modified.
antialias (bool) – if True determines if the transform is downsampling and applies
antialiasing via gaussian a blur. Defaults to False
interpolation (str) – interpolation code or cv2 integer. Interpolation codes are linear,
nearest, cubic, lancsoz, and area. Defaults to “linear”.
noop_eps (float) – This is the tolerance for optimizing a warp away.
If the transform has all of its decomposed parameters (i.e.
scale, rotation, translation, shear) less than this value,
the warp node can be optimized away. Defaults to 0.
>>> # Demo optimization that removes a noop warp>>> fromdelayed_imageimportDelayedLoad>>> importkwimage>>> base=DelayedLoad.demo(channels='r|g|b').prepare()>>> self=base.warp(kwimage.Affine.eye())>>> new=self.optimize()>>> assertlen(self.as_graph().nodes)==2>>> assertlen(new.as_graph().nodes)==1
Example
>>> # Test optimize nans>>> fromdelayed_imageimportDelayedNans>>> importkwimage>>> base=DelayedNans(dsize=(100,100),channels='a|b|c')>>> self=base.warp(kwimage.Affine.scale(0.1))>>> # Should simply return a new nan generator>>> new=self.optimize()>>> assertlen(new.as_graph().nodes)==1
Example
>>> # Test optimize nans>>> fromdelayed_imageimportDelayedLoad>>> importkwimage>>> base=DelayedLoad.demo(channels='r|g|b').prepare()>>> transform=kwimage.Affine.scale(1.0+1e-7)>>> self=base.warp(transform,dsize=base.dsize)>>> # An optimize will not remove a warp if there is any>>> # doubt if it is the identity.>>> new=self.optimize()>>> assertlen(self.as_graph().nodes)==2>>> assertlen(new.as_graph().nodes)==2>>> # But we can specify a threshold where it will>>> self._set_nested_params(noop_eps=1e-6)>>> new=self.optimize()>>> assertlen(self.as_graph().nodes)==2>>> assertlen(new.as_graph().nodes)==1
>>> # xdoctest: +REQUIRES(module:osgeo)>>> fromdelayed_image.delayed_nodesimport*# NOQA>>> fromdelayed_imageimportDelayedLoad>>> importkwimage>>> fpath=kwimage.grab_test_image_fpath(overviews=3)>>> base=DelayedLoad(fpath,channels='r|g|b').prepare()>>> # Case without any operations between the overview and warp>>> self=base.get_overview(1).warp({'scale':4})>>> self.write_network_text()>>> opt=self._opt_absorb_overview()._validate()>>> opt.write_network_text()>>> opt_data=[dforn,dinopt.as_graph().nodes(data=True)]>>> assert'DelayedOverview'notin[d['type']fordinopt_data]>>> # Case with a chain of operations between overview and warp>>> self=base.get_overview(1)[0:101,0:100].warp({'scale':4})>>> self.write_network_text()>>> opt=self._opt_absorb_overview()._validate()>>> opt.write_network_text()>>> opt_data=[dforn,dinopt.as_graph().nodes(data=True)]>>> #assert opt_data[1]['meta']['space_slice'] == (slice(0, 202, None), slice(0, 200, None))>>> assertopt_data[1]['meta']['space_slice']==(slice(0,204,None),slice(0,202,None))>>> # Any sort of complex chain does prevents this optimization>>> # from running.>>> self=base.get_overview(1)[0:101,0:100][0:50,0:50].warp({'scale':4})>>> opt=self._opt_absorb_overview()._validate()>>> opt.write_network_text()>>> opt_data=[dforn,dinopt.as_graph().nodes(data=True)]>>> assert'DelayedOverview'in[d['type']fordinopt_data]
space_slice (Tuple[slice, slice]) – y-slice and x-slice.
chan_idxs (List[int]) – indexes of bands to take
clip (bool) – if True, the slice is interpreted normally, where it won’t go
past the image extent, otherwise slicing into negative regions
or past the image bounds will result in padding. Defaults to
True.
wrap (bool) – if True, negative indexes “wrap around”, otherwise they are
treated as is. Defaults to True.
pad (int | List[Tuple[int, int]]) – if specified, applies extra padding
Applys an affine transformation to the image. See DelayedWarp.
Parameters:
transform (ndarray | dict | kwimage.Affine) – a coercable affine matrix. See kwimage.Affine for
details on what can be coerced.
dsize (Tuple[int, int] | str) – The width / height of the output canvas. If ‘auto’, dsize is
computed such that the positive coordinates of the warped image
will fit in the new canvas. In this case, any pixel that maps
to a negative coordinate will be clipped. This has the
property that the input transformation is not modified.
antialias (bool) – if True determines if the transform is downsampling and applies
antialiasing via gaussian a blur. Defaults to False
interpolation (str) – interpolation code or cv2 integer. Interpolation codes are linear,
nearest, cubic, lancsoz, and area. Defaults to “linear”.
border_value (int | float | str) – if auto will be nan for float and 0 for int.
noop_eps (float) – This is the tolerance for optimizing a warp away.
If the transform has all of its decomposed parameters (i.e.
scale, rotation, translation, shear) less than this value,
the warp node can be optimized away. Defaults to 0.
A dictionary used to define an element of a JSON Schema.
The exact keys/values for the element will depend on the type of element
being described. The SchemaElements defines exactly what these are
for the core elements. (e.g. OBJECT, INTEGER, NULL, ARRAY, ANYOF)
base (dict) – the keys / values this schema must contain
options (dict) – the keys / values this schema may contain
_magic (callable | None) – called when creating an instance of this schema
element. Allows convinience attributes to be converted to the
formal jsonschema specs. TODO: _magic is a terrible name, we
need to rename it with something descriptive.
name (str) – the name of the archive member to read
mode (str) – This is a conceptual parameter that emulates the usual
open mode. Defaults to “rb”, which returns data as raw bytes.
If “r” will decode the bytes into utf8-text.
This function may not be safe, but it has as many mitigation measures
that I know about. This function should be audited and possibly made
even more restricted. The idea is that this should just be used to
evaluate numeric expressions.
A concrete asynchronous executor with a configurable backend.
The type of parallelism (or lack thereof) is configured via the mode
parameter, which can be: “process”, “thread”, or “serial”. This allows the
user to easily enable / disable parallelism or switch between processes and
threads without modifying the surrounding logic.
Abstracts away boilerplate of submitting and collecting jobs
This is a basic wrapper around ubelt.util_futures.Executor that
simplifies the most basic case by 1. keeping track of references to
submitted futures for you and 2. providing an as_completed method to
consume those futures as they are ready.
mode (str) – The backend parallelism mechanism. Can be either thread, serial,
or process. Defaults to ‘thread’.
max_workers (int) – number of workers. If 0, serial is forced. Defaults to 0.
transient (bool) – if True, references to jobs will be discarded as they are
returned by as_completed(). Otherwise the jobs attribute
holds a reference to all jobs ever submitted. Default to False.
concurrent.futures.Future – The completed future object containing the results of a job.
CommandLine
xdoctest-mubelt.util_futuresJobPool.as_completed
Example
>>> importubeltasub>>> pool=ub.JobPool('thread',max_workers=8)>>> text=ub.paragraph(... '''... UDP is a cool protocol, check out the wiki:...... UDP-based Data Transfer Protocol (UDT), is a high-performance... data transfer protocol designed for transferring large... volumetric datasets over high-speed wide area networks. Such... settings are typically disadvantageous for the more common TCP... protocol.... ''')>>> forwordintext.split(' '):... pool.submit(print,word)>>> for_inpool.as_completed():... pass>>> pool.shutdown()
Like JobPool.as_completed(), but executes the result method
of each future and returns only after all processes are complete.
This allows for lower-boilerplate prototyping.
Recurse through json datastructure and find any component that
causes a serialization error. Record the location of these errors
in the datastructure as we recurse through the call tree.
Parameters:
data (object) – data that should be json serializable
quickcheck (bool) – if True, check the entire datastructure assuming
its ok before doing the python-based recursive logic.
Returns:
list of “bad part” dictionaries containing items
’value’ - the value that caused the serialization error
’loc’ - which contains a list of key/indexes that can be used
to lookup the location of the unserializable value.
If the “loc” is a list, then it indicates a rare case where
a key in a dictionary is causing the serialization error.
Return type:
List[Dict]
Example
>>> fromkwcoco.util.util_jsonimport*# NOQA>>> part=ub.ddict(lambda:int)>>> part['foo']=ub.ddict(lambda:int)>>> part['bar']=np.array([1,2,3])>>> part['foo']['a']=1>>> # Create a dictionary with two unserializable parts>>> data=[1,2,{'nest1':[2,part]},{frozenset({'badkey'}):3,2:4}]>>> parts=list(find_json_unserializable(data))>>> print('parts = {}'.format(ub.urepr(parts,nl=1)))>>> # Check expected structure of bad parts>>> assertlen(parts)==2>>> part=parts[1]>>> assertlist(part['loc'])==[2,'nest1',1,'bar']>>> # We can use the "loc" to find the bad value>>> forpartinparts:>>> # "loc" is a list of directions containing which keys/indexes>>> # to traverse at each descent into the data structure.>>> directions=part['loc']>>> curr=data>>> special_flag=False>>> forkeyindirections:>>> ifisinstance(key,list):>>> # special case for bad keys>>> special_flag=True>>> break>>> else:>>> # normal case for bad values>>> curr=curr[key]>>> ifspecial_flag:>>> assertpart['data']incurr.keys()>>> assertpart['data']iskey[1]>>> else:>>> assertpart['data']iscurr
This is a metaclass that overrides the behavior of isinstance and
issubclass when invoked on classes derived from this such that they only
check that the module and class names agree, which are preserved through
module reloads, whereas class instances are not.
This is useful for interactive develoment, but should be removed in
production.
Example
>>> fromkwcoco.util.util_monkeyimport*# NOQA>>> # Illustrate what happens with a reload when using this utility>>> # versus without it.>>> classBase1:>>> ...>>> classDerived1(Base1):>>> ...>>> @Reloadable.add_metaclass>>> classBase2:>>> ...>>> classDerived2(Base2):>>> ...>>> inst1=Derived1()>>> inst2=Derived2()>>> assertisinstance(inst1,Derived1)>>> assertisinstance(inst2,Derived2)>>> # Simulate reload>>> classBase1:>>> ...>>> classDerived1(Base1):>>> ...>>> @Reloadable.add_metaclass>>> classBase2:>>> ...>>> classDerived2(Base2):>>> ...>>> assertnotisinstance(inst1,Derived1)>>> assertisinstance(inst2,Derived2)
Provides train/test indices to split data in train/test sets.
This cross-validation object is a variation of GroupKFold that returns
stratified folds. The folds are made by preserving the percentage of
samples for each class.
This is an old interface and should likely be refactored and modernized.
Parameters:
n_splits (int, default=3) – Number of folds. Must be at least 2.
Truncate a string.
:param string (str): string for modification
:param max_length (int): output string length
:param word_boundary (bool):
:param save_order (bool): if True then word order of output string is like input string
:param separator (str): separator between words
:param trunc_loc (float): fraction of location where to remove the text
trunc_char (str): the character to denote where truncation is starting
Windows is so special. When using msys bash if you pass a path on the CLI
it resolves /c to C:/, but if you have you path as part of a config string,
it doesnt know how to do that, and at that point Python doesn’t handle the
msys style /c paths. This is a hack detects and fixes this in this
location.
name (str) – the name of the archive member to read
mode (str) – This is a conceptual parameter that emulates the usual
open mode. Defaults to “rb”, which returns data as raw bytes.
If “r” will decode the bytes into utf8-text.
A dictionary used to define an element of a JSON Schema.
The exact keys/values for the element will depend on the type of element
being described. The SchemaElements defines exactly what these are
for the core elements. (e.g. OBJECT, INTEGER, NULL, ARRAY, ANYOF)
base (dict) – the keys / values this schema must contain
options (dict) – the keys / values this schema may contain
_magic (callable | None) – called when creating an instance of this schema
element. Allows convinience attributes to be converted to the
formal jsonschema specs. TODO: _magic is a terrible name, we
need to rename it with something descriptive.
data (dict | list | tuple) – the wrapped indexable data
dict_cls (Tuple[type]) – the types that should be considered dictionary mappings for the
purpose of nested iteration. Defaults to dict.
list_cls (Tuple[type]) – the types that should be considered list-like for the purposes
of nested iteration. Defaults to (list,tuple).
Example
>>> importubeltasub>>> # Given Nested Data>>> data={>>> 'foo':{'bar':1},>>> 'baz':[{'biz':3},{'buz':[4,5,6]}],>>> }>>> # Create an IndexableWalker>>> walker=ub.IndexableWalker(data)>>> # We iterate over the data as if it was flat>>> # ignore the <want> string due to order issues on older Pythons>>> # xdoctest: +IGNORE_WANT>>> forpath,valinwalker:>>> print(path)['foo']['baz']['baz', 0]['baz', 1]['baz', 1, 'buz']['baz', 1, 'buz', 0]['baz', 1, 'buz', 1]['baz', 1, 'buz', 2]['baz', 0, 'biz']['foo', 'bar']>>> # We can use "paths" as keys to getitem into the walker>>> path=['baz',1,'buz',2]>>> val=walker[path]>>> assertval==6>>> # We can use "paths" as keys to setitem into the walker>>> assertdata['baz'][1]['buz'][2]==6>>> walker[path]=7>>> assertdata['baz'][1]['buz'][2]==7>>> # We can use "paths" as keys to delitem into the walker>>> assertdata['baz'][1]['buz'][1]==5>>> delwalker[['baz',1,'buz',1]]>>> assertdata['baz'][1]['buz'][1]==7
Example
>>> # Create nested data>>> # xdoctest: +REQUIRES(module:numpy)>>> importnumpyasnp>>> importubeltasub>>> data=ub.ddict(lambda:int)>>> data['foo']=ub.ddict(lambda:int)>>> data['bar']=np.array([1,2,3])>>> data['foo']['a']=1>>> data['foo']['b']=np.array([1,2,3])>>> data['foo']['c']=[1,2,3]>>> data['baz']=3>>> print('data = {}'.format(ub.repr2(data,nl=True)))>>> # We can walk through every node in the nested tree>>> walker=ub.IndexableWalker(data)>>> forpath,valueinwalker:>>> print('walk path = {}'.format(ub.repr2(path,nl=0)))>>> ifpath[-1]=='c':>>> # Use send to prevent traversing this branch>>> got=walker.send(False)>>> # We can modify the value based on the returned path>>> walker[path]='changed the value of c'>>> print('data = {}'.format(ub.repr2(data,nl=True)))>>> assertdata['foo']['c']=='changed the value of c'
Example
>>> # Test sending false for every data item>>> importubeltasub>>> data={1:[1,2,3],2:[1,2,3]}>>> walker=ub.IndexableWalker(data)>>> # Sending false means you wont traverse any further on that path>>> num_iters_v1=0>>> forpath,valueinwalker:>>> print('[v1] walk path = {}'.format(ub.repr2(path,nl=0)))>>> walker.send(False)>>> num_iters_v1+=1>>> num_iters_v2=0>>> forpath,valueinwalker:>>> # When we dont send false we walk all the way down>>> print('[v2] walk path = {}'.format(ub.repr2(path,nl=0)))>>> num_iters_v2+=1>>> assertnum_iters_v1==2>>> assertnum_iters_v2==8
Example
>>> # Test numpy>>> # xdoctest: +REQUIRES(CPython)>>> # xdoctest: +REQUIRES(module:numpy)>>> importubeltasub>>> importnumpyasnp>>> # By default we don't recurse into ndarrays because they>>> # Are registered as an indexable class>>> data={2:np.array([1,2,3])}>>> walker=ub.IndexableWalker(data)>>> num_iters=0>>> forpath,valueinwalker:>>> print('walk path = {}'.format(ub.repr2(path,nl=0)))>>> num_iters+=1>>> assertnum_iters==1>>> # Currently to use top-level ndarrays, you need to extend what the>>> # list class is. This API may change in the future to be easier>>> # to work with.>>> data=np.random.rand(3,5)>>> walker=ub.IndexableWalker(data,list_cls=(list,tuple,np.ndarray))>>> num_iters=0>>> forpath,valueinwalker:>>> print('walk path = {}'.format(ub.repr2(path,nl=0)))>>> num_iters+=1>>> assertnum_iters==3+3*5
Walks through this and another nested data structures and checks if
everything is roughly the same.
Parameters:
other (IndexableWalker | List | Dict) – a nested indexable item to compare against.
rel_tol (float) – maximum difference for being considered “close”, relative to the
magnitude of the input values
abs_tol (float) – maximum difference for being considered “close”, regardless of the
magnitude of the input values
return_info (bool, default=False) – if true, return extra info dict
Returns:
A boolean result if return_info is false, otherwise a tuple of
the boolean result and an “info” dict containing detailed results
indicating what matched and what did not.
Provides train/test indices to split data in train/test sets.
This cross-validation object is a variation of GroupKFold that returns
stratified folds. The folds are made by preserving the percentage of
samples for each class.
This is an old interface and should likely be refactored and modernized.
Parameters:
n_splits (int, default=3) – Number of folds. Must be at least 2.
Recurse through json datastructure and find any component that
causes a serialization error. Record the location of these errors
in the datastructure as we recurse through the call tree.
Parameters:
data (object) – data that should be json serializable
quickcheck (bool) – if True, check the entire datastructure assuming
its ok before doing the python-based recursive logic.
Returns:
list of “bad part” dictionaries containing items
’value’ - the value that caused the serialization error
’loc’ - which contains a list of key/indexes that can be used
to lookup the location of the unserializable value.
If the “loc” is a list, then it indicates a rare case where
a key in a dictionary is causing the serialization error.
Return type:
List[Dict]
Example
>>> fromkwcoco.util.util_jsonimport*# NOQA>>> part=ub.ddict(lambda:int)>>> part['foo']=ub.ddict(lambda:int)>>> part['bar']=np.array([1,2,3])>>> part['foo']['a']=1>>> # Create a dictionary with two unserializable parts>>> data=[1,2,{'nest1':[2,part]},{frozenset({'badkey'}):3,2:4}]>>> parts=list(find_json_unserializable(data))>>> print('parts = {}'.format(ub.urepr(parts,nl=1)))>>> # Check expected structure of bad parts>>> assertlen(parts)==2>>> part=parts[1]>>> assertlist(part['loc'])==[2,'nest1',1,'bar']>>> # We can use the "loc" to find the bad value>>> forpartinparts:>>> # "loc" is a list of directions containing which keys/indexes>>> # to traverse at each descent into the data structure.>>> directions=part['loc']>>> curr=data>>> special_flag=False>>> forkeyindirections:>>> ifisinstance(key,list):>>> # special case for bad keys>>> special_flag=True>>> break>>> else:>>> # normal case for bad values>>> curr=curr[key]>>> ifspecial_flag:>>> assertpart['data']incurr.keys()>>> assertpart['data']iskey[1]>>> else:>>> assertpart['data']iscurr
Truncate a string.
:param string (str): string for modification
:param max_length (int): output string length
:param word_boundary (bool):
:param save_order (bool): if True then word order of output string is like input string
:param separator (str): separator between words
:param trunc_loc (float): fraction of location where to remove the text
trunc_char (str): the character to denote where truncation is starting
For each dataset we create a mapping between each old id and a new id. If
possible and reuse=True we allow the new id to match the old id. After
each dataset is finished we mark all those ids as used and subsequent
new-ids cannot be chosen from that pool.
Parameters:
reuse (bool) – if True we are allowed to reuse ids
as long as they haven’t been used before.
Optional iterable argument provides an initial iterable of values to
initialize the sorted set.
Optional key argument defines a callable that, like the key
argument to Python’s sorted function, extracts a comparison key from
each value. The default, none, compares values directly.
This is a common base for all variants of the Coco Dataset
At the time of writing there is kwcoco.CocoDataset (which is the
dictionary-based backend), and the kwcoco.coco_sql_dataset.CocoSqlDataset,
which is experimental.
The category_tree module defines the 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.
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 (networkx.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.
>>> # The coerce classmethod is the easiest way to create an instance>>> importkwcoco>>> 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)
Parameters:
graph (nx.DiGraph) – either the graph representing a category hierarchy
checks (bool, default=True) – if false, bypass input checks
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.
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
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.
The ChannelSpec and FusedChannelSpec represent a set of channels or bands in an
image. This could be as simple as red|green|blue, or more complex like:
red|green|blue|nir|swir16|swir22.
This functionality has been moved to “delayed_image”.
An implementation and extension of the original MS-COCO API [CocoFormat].
Extends the format to also include line annotations.
The following describes psuedo-code for the high level spec (some of which may
not be have full support in the Python API). A formal json-schema is defined in
kwcoco.coco_schema.
Note
The main object in this file is CocoDataset, which is composed of
several mixin classes. See the class and method documentation for more
details.
For a formal description of the spec see the coco_schema.json, which is generated by :py:mod`kwcoco/coco_schema`.
Todo
[ ] Use ijson (modified to support NaN) to lazilly load pieces of the
dataset in the background or on demand. This will give us faster access
to categories / images, whereas we will always have to wait for
annotations etc…
[X] Should img_root be changed to bundle_dpath?
[ ] Read video data, return numpy arrays (requires API for images)
[ ] Spec for video URI, and convert to frames @ framerate function.
[x] Document channel spec
[x] Document sensor-channel spec
[X] Add remove videos method
[ ] Efficiency: Make video annotations more efficient by only tracking
keyframes, provide an API to obtain a dense or interpolated
annotation on an intermediate frame.
[ ] Efficiency: Allow each section of the kwcoco file to be written as a
separate json file. Perhaps allow genric pointer support? Might get
messy.
[ ] Reroot needs to be redesigned very carefully.
[ ] Allow parts of the kwcoco file to be references to other json files.
[X] Add top-level track table
[ ] Fully transition to integer track ids (in progress)
graph: a directed graph where category names are the nodes,
supercategories define edges, and items in each category dict
(e.g. category id) are added as node properties.
Attempt to transform the input into the intended CocoDataset.
Parameters:
key – this can either be an instance of a CocoDataset, a
string URI pointing to an on-disk dataset, or a special
key for creating demodata.
sqlview (bool | str) – If truthy, will return the dataset as a cached sql view, which
can be quicker to load and use in some instances. Can be given
as a string, which sets the backend that is used: either sqlite
or postgresql. Defaults to False.
**kw – passed to whatever constructor is chosen (if any)
Create a toy coco dataset for testing and demo puposes
Parameters:
key (str) – Either ‘photos’ (default), ‘shapes’, or ‘vidshapes’. There are
also special sufixes that can control behavior.
Basic options that define which flavor of demodata to generate
are: photos, shapes, and vidshapes. A numeric suffix e.g.
vidshapes8 can be specified to indicate the size of the
generated demo dataset. There are other special suffixes that
are available. See the code in this function for explicit
details on what is allowed.
TODO: better documentation for these demo datasets.
As a quick summary: the vidshapes key is the most robust and
mature demodata set, and here are several useful variants of
the vidshapes key.
vidshapes8 - the 8 suffix is the number of videos in this case.
vidshapes8-msi - msi is an alias for multispectral.
vidshapes8-frames5 - generate 8 videos with 5 frames each.
vidshapes2-tracks5 - generate 2 videos with 5 tracks each.
(6) vidshapes2-speed0.1-frames7 - generate 2 videos with 7
frames where the objects move with with a speed of 0.1.
**kwargs – if key is shapes, these arguments are passed to toydata
generation. The Kwargs section of this docstring documents a
subset of the available options. For full details, see
demodata_toy_dset() and random_video_dset().
Notable options are:
bundle_dpath (PathLike): location to write the demo bundle
fpath (PathLike): location to write the demo kwcoco file
Kwargs:
image_size (Tuple[int, int]): width / height size of the images
dpath (str | PathLike):
path to the directory where any generated demo bundles will be
written to. Defaults to using kwcoco cache dir.
aux (bool): if True generates dummy auxiliary channels
>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo('vidshapes1-multispectral',num_frames=5,>>> verbose=0,rng=None)>>> # This is the first use-case of image names>>> assertlen(dset.index.file_name_to_img)==0,(>>> 'the multispectral demo case has no "base" image')>>> assertlen(dset.index.name_to_img)==len(dset.index.imgs)==5>>> dset.remove_images([1])>>> assertlen(dset.index.name_to_img)==len(dset.index.imgs)==4>>> dset.remove_videos([1])>>> assertlen(dset.index.name_to_img)==len(dset.index.imgs)==0
>>> importkwcoco>>> self=kwcoco.CocoDataset.demo()>>> self._build_hashid(hash_pixels=True,verbose=3)...>>> # Shorten hashes for readability>>> importubeltasub>>> walker=ub.IndexableWalker(self.hashid_parts)>>> forpath,valinwalker:>>> ifisinstance(val,str):>>> walker[path]=val[0:8]>>> # Note: this may change in different versions of kwcoco>>> print('self.hashid_parts = '+ub.urepr(self.hashid_parts))>>> print('self.hashid = {!r}'.format(self.hashid[0:8]))self.hashid_parts = { 'annotations': { 'json': 'c1d1b9c3', 'num': 11, }, 'images': { 'pixels': '88e37cc3', 'json': '9b8e8be3', 'num': 3, }, 'categories': { 'json': '82d22e00', 'num': 8, },}self.hashid = 'bf69bf15'
Example
>>> importkwcoco>>> self=kwcoco.CocoDataset.demo()>>> self._build_hashid(hash_pixels=True,verbose=3)>>> self.hashid_parts>>> # Test that when we modify the dataset only the relevant>>> # hashid parts are recomputed.>>> orig=self.hashid_parts['categories']['json']>>> self.add_category('foobar')>>> assert'categories'notinself.hashid_parts>>> self.hashid_parts>>> self.hashid_parts['images']['json']='should not change'>>> self._build_hashid(hash_pixels=True,verbose=3)>>> assertself.hashid_parts['categories']['json']>>> assertself.hashid_parts['categories']['json']!=orig>>> assertself.hashid_parts['images']['json']=='should not change'
Called whenever the coco dataset is modified. It is possible to specify
which parts were modified so unmodified parts can be reused next time
the hash is constructed.
Todo
[ ] Rename to _notify_modification — or something like that
The idea is to cache the hashid when we are sure that the dataset was
loaded from a file and has not been modified. We can record the
modification time of the file (because we know it hasn’t changed in
memory), and use that as a key to the cache. If the modification time
on the file is different than the one recorded in the cache, we know
the cache could be invalid, so we recompute the hashid.
Modify the prefix of the image/data paths onto a new image/data root.
Parameters:
new_root (str | PathLike | None) – New image root. If unspecified the current
self.bundle_dpath is used. If old_prefix and new_prefix are
unspecified, they will attempt to be determined based on the
current root (which assumes the file paths exist at that root)
and this new root. Defaults to None.
old_prefix (str | None) – If specified, removes this prefix from file names.
This also prevents any inferences that might be made via
“new_root”. Defaults to None.
new_prefix (str | None) – If specified, adds this prefix to the file names.
This also prevents any inferences that might be made via
“new_root”. Defaults to None.
absolute (bool) – if True, file names are stored as absolute paths, otherwise
they are relative to the new image root. Defaults to False.
check (bool) – if True, checks that the images all exist.
Defaults to True.
safe (bool) – if True, does not overwrite values until all checks pass.
Defaults to True.
annot_ids (List[int] | None) – annotation ids to reference, if unspecified all annotations are
returned. An alias is “aids”, which may be removed in the future.
image_id (int | None) – return all annotations that belong to this image id.
Mutually exclusive with other arguments.
An alias is “gids”, which may be removed in the future.
track_id (int | None) – return all annotations that belong to this track.
mutually exclusive with other arguments.
An alias is “trackid”, which may be removed in the future.
video_ids (List[int] | None) – video ids to reference, if unspecified all videos are returned.
The vidids argument is an alias. Mutually exclusive with
other args.
names (List[str] | None) – lookup videos by their name.
Mutually exclusive with other args.
[ ] This conflicts with what should be the property that
should redirect to index.videos, we should resolve this
somehow. E.g. all other main members of the index (anns, imgs,
cats) have a toplevel dataset property, we don’t have one for
videos because the name we would pick conflicts with this.
**config – schema (default=True): if True, validate the json-schema
unique (default=True): if True, validate unique secondary keys
missing (default=True): if True, validate registered files exist
corrupted (default=False): if True, validate data in registered files
channels (default=True):
if True, validate that channels in auxiliary/asset items
are all unique.
require_relative (default=False):
if True, causes validation to fail if paths are
non-portable, i.e. all paths must be relative to the
bundle directory. if>0, paths must be relative to bundle
root. if>1, paths must be inside bundle root.
img_attrs (default=’warn’):
if truthy, check that image attributes contain width and
height entries. If ‘warn’, then warn if they do not exist.
If ‘error’, then fail.
verbose (default=1): verbosity flag
workers (int): number of workers for parallel checks. defaults to 0
fastfail (default=False): if True raise errors immediately
Returns:
result containing keys -
status (bool): False if any errors occurred
errors (List[str]): list of all error messages
missing (List): List of any missing images
corrupted (List): List of any corrupted images
>>> importkwcoco>>> self=kwcoco.CocoDataset.demo('shapes8')>>> self.draw_image(1)>>> # Now you can dump the annotated image to disk / whatever>>> # xdoctest: +REQUIRES(--show)>>> importkwplot>>> kwplot.autompl()>>> kwplot.imshow(canvas)
file_name (str | None) – relative or absolute path to image.
if not given, then “name” must be specified and we will
expect that “auxiliary” assets are eventually added.
id (None | int) – ADVANCED. Force using this image id.
name (str) – a unique key to identify this image
width (int) – base width of the image
height (int) – base height of the image
channels (ChannelSpec) – specification of base channels.
Only relevant if file_name is given.
auxiliary (List[Dict]) – specification of auxiliary assets.
See CocoImage.add_asset() for details
video_id (int) – id of parent video, if applicable
frame_index (int) – frame index in parent video
timestamp (number | str) – timestamp of frame index
warp_img_to_vid (Dict) – this transform is used to align
the image to a video if it belongs to one.
**kw – stores arbitrary key/value pairs in this new image
Adds an auxiliary / asset item to the image dictionary.
Parameters:
gid (int) – The image id to add the auxiliary/asset item to.
file_name (str | None) – The name of the file relative to the bundle directory. If
unspecified, imdata must be given.
channels (str | kwcoco.FusedChannelSpec) – The channel code indicating what each of the bands represents.
These channels should be disjoint wrt to the existing data in
this image (this is not checked).
**kwargs – See CocoImage.add_asset() for more details
Adds an auxiliary / asset item to the image dictionary.
Parameters:
gid (int) – The image id to add the auxiliary/asset item to.
file_name (str | None) – The name of the file relative to the bundle directory. If
unspecified, imdata must be given.
channels (str | kwcoco.FusedChannelSpec) – The channel code indicating what each of the bands represents.
These channels should be disjoint wrt to the existing data in
this image (this is not checked).
**kwargs – See CocoImage.add_asset() for more details
image_id (int) – image_id the annotation is added to.
category_id (int | None) – category_id for the new annotation
bbox (list | kwimage.Boxes) – bounding box in xywh format
segmentation (Dict | List | Any) – keypoints in some
accepted format, see kwimage.Mask.to_coco() and
kwimage.MultiPolygon.to_coco().
Extended types: MaskLike | MultiPolygonLike.
keypoints (Any) – keypoints in some accepted
format, see kwimage.Keypoints.to_coco().
Extended types: KeypointsLike.
id (None | int) – Force using this annotation id. Typically you
should NOT specify this. A new unused id will be chosen and
returned.
track_id (int | str | None) – Some value used to associate annotations that belong to the
same “track”. In the future we may remove support for strings.
**kw – stores arbitrary key/value pairs in this new image,
Common respected key/values include but are not limited to the
following:
score : float
prob : List[float]
weight (float): a weight, usually used to indicate if a ground
truth annotation is difficult / important. This generalizes
standard “is_hard” or “ignore” attributes in other formats.
caption (str): a text caption for this annotation
>>> importkwimage>>> importkwcoco>>> self=kwcoco.CocoDataset.demo()>>> new_det=kwimage.Detections.random(1,segmentations=True,keypoints=True)>>> # kwimage datastructures have methods to convert to coco recognized formats>>> new_ann_data=list(new_det.to_coco(style='new'))[0]>>> image_id=1>>> aid=self.add_annotation(image_id,**new_ann_data)>>> # Lookup the annotation we just added>>> ann=self.index.anns[aid]>>> print('ann = {}'.format(ub.urepr(ann,nl=-2)))
Example
>>> # Attempt to add annot without a category or bbox>>> importkwcoco>>> self=kwcoco.CocoDataset.demo()>>> image_id=1>>> aid=self.add_annotation(image_id)>>> assertNoneinself.index.cid_to_aids
Faster less-safe multi-item alternative to add_annotation.
We assume the annotations are well formatted in kwcoco compliant
dictionaries, including the “id” field. No validation checks are
made when calling this function.
Parameters:
anns (List[Dict]) – list of annotation dictionaries
We assume the images are well formatted in kwcoco compliant
dictionaries, including the “id” field. No validation checks are
made when calling this function.
Note
THIS FUNCTION WAS DESIGNED FOR SPEED, AS SUCH IT DOES NOT CHECK IF
THE IMAGE-IDs or FILE_NAMES ARE DUPLICATED AND WILL BLINDLY ADD
DATA EVEN IF IT IS BAD. THE SINGLE IMAGE VERSION IS SLOWER BUT
SAFER.
THIS FUNCTION WAS DESIGNED FOR SPEED, AS SUCH IT DOES NOT CHECK IF
THE IMAGE-IDs or FILE_NAMES ARE DUPLICATED AND WILL BLINDLY ADD
DATA EVEN IF IT IS BAD. THE SINGLE IMAGE VERSION IS SLOWER BUT
SAFER.
The main coco dataset class with a json dataset backend.
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, gid_to_aids,
file_name_to_img, etc. See CocoIndex for more details
on which attributes are available.
fpath (PathLike | None) – if known, this stores the filepath the dataset was loaded from
tag (str | None) – A tag indicating the name of the dataset.
bundle_dpath (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.
>>> fromkwcoco.coco_datasetimportdemo_coco_data>>> importkwcoco>>> importubeltasub>>> # Returns a coco json structure>>> dataset=demo_coco_data()>>> # Pass the coco json structure to the API>>> self=kwcoco.CocoDataset(dataset,tag='demo')>>> # Now you can access the data using the index and helper methods>>> #>>> # Start by looking up an image by it's COCO id.>>> image_id=1>>> img=self.index.imgs[image_id]>>> print(ub.urepr(img,nl=1,sort=1)){ 'file_name': 'astro.png', 'id': 1, 'url': 'https://i.imgur.com/KXhKM72.png',}>>> #>>> # Use the (gid_to_aids) index to lookup annotations in the iamge>>> annotation_id=sorted(self.index.gid_to_aids[image_id])[0]>>> ann=self.index.anns[annotation_id]>>> print(ub.urepr((ub.udict(ann)-{'segmentation'}).sorted_keys(),nl=1)){ 'bbox': [10, 10, 360, 490], 'category_id': 1, 'id': 1, 'image_id': 1, 'keypoints': [247, 101, 2, 202, 100, 2],}>>> #>>> # Use annotation category id to look up that information>>> category_id=ann['category_id']>>> cat=self.index.cats[category_id]>>> print('cat = {}'.format(ub.urepr(cat,nl=1,sort=1)))cat = { 'id': 1, 'name': 'astronaut', 'supercategory': 'human',}>>> #>>> # Now play with some helper functions, like extended statistics>>> extended_stats=self.extended_stats()>>> # xdoctest: +IGNORE_WANT>>> print('extended_stats = {}'.format(ub.urepr(extended_stats,nl=1,precision=2,sort=1)))extended_stats = { 'annots_per_img': {'mean': 3.67, 'std': 3.86, 'min': 0.00, 'max': 9.00, 'nMin': 1, 'nMax': 1, 'shape': (3,)}, 'imgs_per_cat': {'mean': 0.88, 'std': 0.60, 'min': 0.00, 'max': 2.00, 'nMin': 2, 'nMax': 1, 'shape': (8,)}, 'cats_per_img': {'mean': 2.33, 'std': 2.05, 'min': 0.00, 'max': 5.00, 'nMin': 1, 'nMax': 1, 'shape': (3,)}, 'annots_per_cat': {'mean': 1.38, 'std': 1.49, 'min': 0.00, 'max': 5.00, 'nMin': 2, 'nMax': 1, 'shape': (8,)}, 'imgs_per_video': {'empty_list': True},}>>> # You can "draw" a raster of the annotated image with cv2>>> canvas=self.draw_image(2)>>> # Or if you have matplotlib you can "show" the image with mpl objects>>> # xdoctest: +REQUIRES(--show)>>> frommatplotlibimportpyplotasplt>>> fig=plt.figure()>>> ax1=fig.add_subplot(1,2,1)>>> self.show_image(gid=2)>>> ax2=fig.add_subplot(1,2,2)>>> ax2.imshow(canvas)>>> ax1.set_title('show with matplotlib')>>> ax2.set_title('draw with cv2')>>> plt.show()
Parameters:
data (str | PathLike | dict | None) – Either a filepath to a coco json file, or a dictionary
containing the actual coco json structure. For a more generally
coercable constructor see func:CocoDataset.coerce.
tag (str | None) – Name of the dataset for display purposes, and does not
influence behavior of the underlying data structure, although
it may be used via convinience methods. We attempt to
autopopulate this via information in data if available.
If unspecfied and data is a filepath this becomes the
basename.
bundle_dpath (str | None) – the root of the dataset that images / external data will be
assumed to be relative to. If unspecfied, we attempt to
determine it using information in data. If data is a
filepath, we use the dirname of that path. If data is a
dictionary, we look for the “img_root” key. If unspecfied and
we fail to introspect then, we fallback to the current working
directory.
img_root (str | None) – deprecated alias for bundle_dpath
workers (int | str) – number of worker threads / processes.
Can also accept coerceable workers.
mode (str) – thread, process, or serial. Defaults to process.
verbose (int) – verbosity level
postprocess (Callable | None) – A function taking one arg (the loaded dataset) to run on the
loaded kwcoco dataset in background workers. This can be more
efficient when postprocessing is independent per kwcoco file.
ordered (bool) – if True yields datasets in the same order as given. Otherwise
results are yielded as they become available. Defaults to True.
**kwargs – arguments passed to the constructor
Yields:
CocoDataset
SeeAlso:
load_multiple - like this function but is a strict file-path-only loader
fpaths (List[str | PathLike]) – list of paths to multiple coco files to be loaded
workers (int) – number of worker threads / processes
mode (str) – thread, process, or serial. Defaults to process.
verbose (int) – verbosity level
postprocess (Callable | None) – A function taking one arg (the loaded dataset) to run on the
loaded kwcoco dataset in background workers and returns the
modified dataset. This can be more efficient when
postprocessing is independent per kwcoco file.
ordered (bool) – if True yields datasets in the same order as given. Otherwise
results are yielded as they become available. Defaults to True.
**kwargs – arguments passed to the constructor
Yields:
CocoDataset
SeeAlso:
coerce_multiple - like this function but accepts general
Loads multiple coco datasets and unions the result
Note
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) – number of worker threads / processes
verbose (int) – verbosity level
mode (str) – thread, process, or serial
union (str | bool) – 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. Default=’try’
Note
This may be deprecated. Use load_multiple or coerce_multiple and
then manually perform the union.
file (PathLike | IO | None) – Where to write the data. Can either be a path to a file or an
open file pointer / stream. If unspecified, it will be written
to the current fpath property.
indent (int | str | None) – indentation for the json file. See
json.dump() for details.
newlines (bool) – if True, each annotation, image, category gets its own line.
temp_file (bool | str) – Argument to safer.open(). Ignored if file is not a
PathLike object. Defaults to ‘auto’, which is False on Windows
and True everywhere else.
compress (bool | str) – if True, dumps the kwcoco file as a compressed zipfile.
In this case a literal IO file object must be opened in binary
write mode. If auto, then it will default to False unless
it can introspect the file name and the name ends with .zip
Example
>>> importkwcoco>>> importubeltasub>>> dpath=ub.Path.appdir('kwcoco/demo/dump').ensuredir()>>> dset=kwcoco.CocoDataset.demo()>>> dset.fpath=dpath/'my_coco_file.json'>>> # Calling dump writes to the current fpath attribute.>>> dset.dump()>>> assertdset.dataset==kwcoco.CocoDataset(dset.fpath).dataset>>> assertdset.dumps()==dset.fpath.read_text()>>> #>>> # Using compress=True can save a lot of space and it>>> # is transparent when reading files via CocoDataset>>> dset.dump(compress=True)>>> assertdset.dataset==kwcoco.CocoDataset(dset.fpath).dataset>>> assertdset.dumps()!=dset.fpath.read_text(errors='replace')
Example
>>> importkwcoco>>> importubeltasub>>> # Compression auto-defaults based on the file name.>>> dpath=ub.Path.appdir('kwcoco/demo/dump').ensuredir()>>> dset=kwcoco.CocoDataset.demo()>>> fpath1=dset.fpath=dpath/'my_coco_file.zip'>>> dset.dump()>>> fpath2=dset.fpath=dpath/'my_coco_file.json'>>> dset.dump()>>> assertfpath1.read_bytes()[0:8]!=fpath2.read_bytes()[0:8]
Merges multiple CocoDataset items into one. Names and
associations are retained, but ids may be different.
Parameters:
*others – a series of CocoDatasets that we will merge.
Note, if called as an instance method, the “self” instance
will be the first item in the “others” list. But if called
like a classmethod, “others” will be empty by default.
disjoint_tracks (bool) – if True, we will assume track-ids are disjoint and if two
datasets share the same track-id, we will disambiguate them.
Otherwise they will be copied over as-is. Defaults to True.
remember_parent (bool) – if True, videos and images will save information about their
parent in the “union_parent” field.
**kwargs – constructor options for the new merged CocoDataset
>>> # 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.urepr(merged.imgs,nl=1)))>>> assertset(merged.imgs)&set([1,2,3,4,5])==set(merged.imgs)
Create a cached SQL interface to this dataset suitable for large scale
multiprocessing use cases.
Parameters:
force_rewrite (bool) – if True, forces an update to any existing cache file on disk
memory (bool) – if True, the database is constructed in memory.
backend (str) – sqlite or postgresql
sql_db_fpath (str | PathLike | None) – overrides the database uri
Note
This view cache is experimental and currently depends on the
timestamp of the file pointed to by self.fpath. In other words
dont use this on in-memory datasets.
This contains several non-standard fields, which help ensure robustness of
functions tested with this data. For more compliant demodata see the
kwcoco.demodata submodule.
Abstracts the evaluation process to execute on two coco datasets.
This can be run as a standalone script where the user specifies the paths
to the true and predited dataset explicitly, or this can be used by a
higher level script that produces the predictions and then sends them to
this evaluator.
Executes the main evaluation logic. Performs assignments between
detections to make DetectionMetrics object, then creates per-item and
ovr confusion vectors, and performs various threshold-vs-confusion
analyses.
Defines the CocoImage class which is an object oriented way of manipulating
data pointed to by a COCO image dictionary.
Notably this provides the .imdelay method for delayed image loading ( which
enables things like fast loading of subimage-regions / coarser scales in images
that contain tiles / overviews - e.g. Cloud Optimized Geotiffs or COGs (Medical
image formats may be supported in the future).
Todo
This file no longer is only images, it has logic for generic single-class
objects. It should be refactored into coco_objects0d.py or something.
An object-oriented representation of a coco image.
It provides helper methods that are specific to a single image.
This operates directly on a single coco image dictionary, but it can
optionally be connected to a parent dataset, which allows it to use
CocoDataset methods to query about relationships and resolve pointers.
This is different than the Images class in coco_object1d, which is just a
vectorized interface to multiple objects.
First, try to find the auxiliary image that has with the smallest
distortion to the base image (if known via warp_aux_to_img)
Second, break ties by using the largest image if w / h is known
Last, if previous information not available use the first
auxiliary image.
Parameters:
requires (List[str] | None) – list of attribute that must be non-None to consider an object
as the primary one.
as_dict (bool) – if True the return type is a raw dictionary. Otherwise use a newer
object-oriented wrapper that should be duck-type swappable.
In the future this default will change to False.
with_bundle (bool) – If True, prepends the bundle dpath to fully specify the path.
Otherwise, just returns the registered string in the file_name
attribute of each asset. Defaults to True.
Adds an auxiliary / asset item to the image dictionary.
This operation can be done purely in-memory (the default), or the image
data can be written to a file on disk (via the imwrite=True flag).
Parameters:
file_name (str | PathLike | None) – The name of the file relative to the bundle directory. If
unspecified, imdata must be given.
channels (str | kwcoco.FusedChannelSpec | None) – The channel code indicating what each of the bands represents.
These channels should be disjoint wrt to the existing data in
this image (this is not checked).
imdata (ndarray | None) – The underlying image data this auxiliary item represents. If
unspecified, it is assumed file_name points to a path on disk
that will eventually exist. If imdata, file_name, and the
special imwrite=True flag are specified, this function will
write the data to disk.
warp_aux_to_img (kwimage.Affine | None) – The transformation from this auxiliary space to image space.
If unspecified, assumes this item is related to image space by
only a scale factor.
width (int | None) – Width of the data in auxiliary space (inferred if unspecified)
height (int | None) – Height of the data in auxiliary space (inferred if unspecified)
imwrite (bool) – If specified, both imdata and file_name must be specified, and
this will write the data to disk. Note: it it recommended that
you simply call imwrite yourself before or after calling this
function. This lets you better control imwrite parameters.
image_id (int | None) – An asset dictionary contains an image-id, but it should not
be specified here. If it is, then it must agree with this
image’s id.
**kw – stores arbitrary key/value pairs in this new asset.
Todo
[ ] Allow imwrite to specify an executor that is used to
return a Future so the imwrite call does not block.
The delayed load can load a subset of channels, and perform lazy
warping operations. If the underlying data is in a tiled format this
can reduce the amount of disk IO needed to read the data if only a
small crop or lower resolution view of the data is needed.
Note
This method is experimental and relies on the delayed load
proof-of-concept.
Parameters:
gid (int) – image id to load
channels (kwcoco.FusedChannelSpec) – specific channels to load.
if unspecified, all channels are loaded.
space (str) – can either be “image” for loading in image space, or
“video” for loading in video space.
resolution (None | str | float) – If specified, applies an additional scale factor to the result
such that the data is loaded at this specified resolution.
This requires that the image / video has a registered
resolution attribute and that its units agree with this
request.
Todo
[ ] This function could stand to have a better name. Maybe imread
with a delayed=True flag? Or maybe just delayed_load?
>>> # TODO: should only select the "red" channel>>> dset=kwcoco.CocoDataset.demo('shapes8')>>> delayed=CocoImage(dset.imgs[gid],dset).imdelay(channels='r')
>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo()>>> coco_img=dset.coco_image(1)>>> # Test case where nothing is registered in the dataset>>> delayed=coco_img.imdelay()>>> final=delayed.finalize()>>> assertfinal.shape==(512,512,3)
>>> # Test that delay works when imdata is stored in the image>>> # dictionary itself.>>> fromkwcoco.coco_imageimport*# NOQA>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo('vidshapes8-multispectral')>>> coco_img=dset.coco_image(1)>>> imdata=np.random.rand(6,6,5)>>> imdata[:]=np.arange(5)[None,None,:]>>> channels=kwcoco.FusedChannelSpec.coerce('Aux:5')>>> coco_img.add_auxiliary_item(imdata=imdata,channels=channels)>>> delayed=coco_img.imdelay(channels='B1|Aux:2:4')>>> final=delayed.finalize()
Example
>>> # Test delay when loading in asset space>>> fromkwcoco.coco_imageimport*# NOQA>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo('vidshapes8-msi-multisensor')>>> coco_img=dset.coco_image(1)>>> stream1=coco_img.channels.streams()[0]>>> stream2=coco_img.channels.streams()[1]>>> asset_delayed=coco_img.imdelay(stream1,space='asset')>>> img_delayed=coco_img.imdelay(stream1,space='image')>>> vid_delayed=coco_img.imdelay(stream1,space='video')>>> #>>> aux_imdata=asset_delayed.as_xarray().finalize()>>> img_imdata=img_delayed.as_xarray().finalize()>>> assertaux_imdata.shape!=img_imdata.shape>>> # Cannot load multiple asset items at the same time in>>> # asset space>>> importpytest>>> fused_channels=stream1|stream2>>> fromdelayed_image.delayed_nodesimportCoordinateCompatibilityError>>> withpytest.raises(CoordinateCompatibilityError):>>> aux_delayed2=coco_img.imdelay(fused_channels,space='asset')
Example
>>> # Test loading at a specific resolution.>>> fromkwcoco.coco_imageimport*# NOQA>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo('vidshapes8-msi-multisensor')>>> coco_img=dset.coco_image(1)>>> coco_img.img['resolution']='1 meter'>>> img_delayed1=coco_img.imdelay(space='image')>>> vid_delayed1=coco_img.imdelay(space='video')>>> # test with unitless request>>> img_delayed2=coco_img.imdelay(space='image',resolution=3.1)>>> vid_delayed2=coco_img.imdelay(space='video',resolution='3.1 meter')>>> np.ceil(img_delayed1.shape[0]/3.1)==img_delayed2.shape[0]>>> np.ceil(vid_delayed1.shape[0]/3.1)==vid_delayed2.shape[0]>>> # test with unitless data>>> coco_img.img['resolution']=1>>> img_delayed2=coco_img.imdelay(space='image',resolution=3.1)>>> vid_delayed2=coco_img.imdelay(space='video',resolution='3.1 meter')>>> np.ceil(img_delayed1.shape[0]/3.1)==img_delayed2.shape[0]>>> np.ceil(vid_delayed1.shape[0]/3.1)==vid_delayed2.shape[0]
Lightweight reference to a set of object (e.g. annotations, images) that
allows for convenient property access.
Types:
ObjT = Ann | Img | Cat # can be one of these types
ObjectList1D gives us access to a List[ObjT]
Example
>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo()>>> # Both annots and images are object lists>>> self=dset.annots()>>> self=dset.images()>>> # can call with a list of ids or not, for everything>>> self=dset.annots([1,2,11])>>> self=dset.images([1,2,3])>>> self.lookup('id')>>> self.lookup(['id'])
Parameters:
ids (List[int]) – list of ids
dset (CocoDataset) – parent dataset
key (str) – main object name (e.g. ‘images’, ‘annotations’)
Each item represents a set of annotations that corresopnds with something
(i.e. belongs to a particular image).
Example
>>> fromkwcoco.coco_objects1dimportImageGroups>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo('photos')>>> images=dset.images()>>> # Requesting the "annots" property from a Images object>>> # will return an AnnotGroups object>>> group:AnnotGroups=images.annots>>> # Printing the group gives info on the mean/std of the number>>> # of items per group.>>> print(group)<AnnotGroups(n=3, m=3.7, s=3.9)...>>>> # Groups are fairly restrictive, they dont provide property level>>> # access in many cases, but the lookup method is available>>> print(group.lookup('id'))[[1, 2, 3, 4, 5, 6, 7, 8, 9], [10, 11], []]>>> print(group.lookup('image_id'))[[1, 1, 1, 1, 1, 1, 1, 1, 1], [2, 2], []]>>> print(group.lookup('category_id'))[[1, 2, 3, 4, 5, 5, 5, 5, 5], [6, 4], []]
Parameters:
groups (List[ObjectList1D]) – list of object lists
Image groups are vectorized lists of other Image objects.
Each item represents a set of images that corresopnds with something (i.e.
belongs to a particular video).
Example
>>> fromkwcoco.coco_objects1dimportImageGroups>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo('vidshapes8')>>> videos=dset.videos()>>> # Requesting the "images" property from a Videos object>>> # will return an ImageGroups object>>> group:ImageGroups=videos.images>>> # Printing the group gives info on the mean/std of the number>>> # of items per group.>>> print(group)<ImageGroups(n=8, m=2.0, s=0.0)...>>>> # Groups are fairly restrictive, they dont provide property level>>> # access in many cases, but the lookup method is available>>> print(group.lookup('id'))[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14], [15, 16]]>>> print(group.lookup('video_id'))[[1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 6], [7, 7], [8, 8]]>>> print(group.lookup('frame_index'))[[0, 1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1], [0, 1]]
Parameters:
groups (List[ObjectList1D]) – list of object lists
>>> # Test the multispectral image defintino>>> importcopy>>> dataset=dset.copy().dataset>>> img=dataset['images'][0]>>> img.pop('file_name')>>> importpytest>>> withpytest.raises(jsonschema.ValidationError):>>> COCO_SCHEMA.validate(dataset)>>> importpytest>>> img['auxiliary']=[{'file_name':'foobar'}]>>> withpytest.raises(jsonschema.ValidationError):>>> COCO_SCHEMA.validate(dataset)>>> img['name']='asset-only images must have a name'>>> COCO_SCHEMA.validate(dataset)
Finally got a baseline implementation of an SQLite backend for COCO
datasets. This mostly plugs into my existing tools (as long as only read
operations are used; haven’t impelmented writing yet) by duck-typing the
dict API.
This solves the issue of forking and then accessing nested dictionaries in
the JSON-style COCO objects. (When you access the dictionary Python will
increment a reference count which triggers copy-on-write for whatever
memory page that data happened to live in. Non-contiguous access had the
effect of excessive memory copies).
For “medium sized” datasets its quite a bit slower. Running through a torch
DataLoader with 4 workers for 10,000 images executes at a rate of 100Hz but
takes 850MB of RAM. Using the duck-typed SQL backend only uses 500MB (which
includes the cost of caching), but runs at 45Hz (which includes the benefit
of caching).
However, once I scale up to 100,000 images I start seeing benefits. The
in-memory dictionary interface chugs at 1.05HZ, and is taking more than 4GB
of memory at the time I killed the process (eta was over an hour). The SQL
backend ran at 45Hz and took about 3 minutes and used about 2.45GB of memory.
Without a cache, SQL runs at 30HZ and takes 400MB for 10,000 images, and
for 100,000 images it gets 30Hz with 1.1GB. There is also a much larger startup
time. I’m not exactly sure what it is yet, but its probably some preprocessing
I’m doing.
Using a LRU cache we get 45Hz and 1.05GB of memory, so that’s a clear win. We
do need to be sure to disable the cache if we ever implement write mode.
I’d like to be a bit faster on the medium sized datasets (I’d really like
to avoid caching rows, which is why the speed is currently
semi-reasonable), but I don’t think I can do any better than this because
single-row lookup time is O(log(N)) for sqlite, whereas its O(1) for
dictionaries. (I wish sqlite had an option to create a hash-table index for
a table, but I dont think it does). I optimized as many of the dictionary
operations as possible (for instance, iterating through keys, values, and
items should be O(N) instead of O(N log(N))), but the majority of the
runtime cost is in the single-row lookup time.
There are a few questions I still have if anyone has insight:
Say I want to select a subset of K rows from a table with N entries,
and I have a list of all of the rowids that I want. Is there any
way to do this better than O(Klog(N))? I tried using a
SELECTcolFROMtableWHEREidIN(?,?,?,?,...) filling in
enough ? as there are rows in my subset. I’m not sure what the
complexity of using a query like this is. I’m not sure what the IN
implementation looks like. Can this be done more efficiently by
with a temporary table and a JOIN?
There really is no way to do O(1) row lookup in sqlite right?
Is there a way in PostgreSQL or some other backend sqlalchemy
supports?
Duck-types an SQL table as a dictionary of dictionaries.
The key is specified by an indexed column (by default it is the id
column). The values are dictionaries containing all data for that row.
Note
With SQLite indexes are B-Trees so lookup is O(log(N)) and not O(1) as
will regular dictionaries. Iteration should still be O(N), but
databases have much more overhead than Python dictionaries.
Parameters:
session (sqlalchemy.orm.session.Session) – the sqlalchemy session
cls (Type) – the declarative sqlalchemy table class
keyattr (Column) – the indexed column to use as the keys
ignore_null (bool) – if True, ignores any keys set to NULL, otherwise
NULL keys are allowed.
>>> # xdoctest: +REQUIRES(module:sqlalchemy)>>> fromkwcoco.coco_sql_datasetimport*# NOQA>>> importkwcoco>>> # Test the variant of the SqlDictProxy where we ignore None keys>>> # This is the case for name_to_img and file_name_to_img>>> dct_dset=kwcoco.CocoDataset.demo('shapes1')>>> dct_dset.add_image(name='no_file_image1')>>> dct_dset.add_image(name='no_file_image2')>>> dct_dset.add_image(name='no_file_image3')>>> sql_dset=dct_dset.view_sql(memory=True)>>> assertlen(dct_dset.index.imgs)==4>>> assertlen(dct_dset.index.file_name_to_img)==1>>> assertlen(dct_dset.index.name_to_img)==3>>> assertlen(sql_dset.index.imgs)==4>>> assertlen(sql_dset.index.file_name_to_img)==1>>> assertlen(sql_dset.index.name_to_img)==3
Similar to SqlDictProxy, but maps ids to groups of other ids.
Simulates a dictionary that maps ids of a parent table to all ids of
another table corresponding to rows where a specific column has that parent
id.
The items in the group can be sorted by the order_attr if
specified. The order_attr can belong to another table
if parent_order_id and self_order_id are specified.
For example, imagine two tables: images with one column (id) and
annotations with two columns (id, image_id). This class can help provide a
mpaping from each image.id to a Set[annotation.id] where those
annotation rows have annotation.image_id = image.id.
>>> # xdoctest: +REQUIRES(module:sqlalchemy)>>> fromkwcoco.coco_sql_datasetimport*# NOQA>>> importkwcoco>>> # Test the group sorted variant of this by using vidid_to_gids>>> # where the "gids" must be sorted by the image frame indexes>>> dct_dset=kwcoco.CocoDataset.demo('vidshapes1')>>> dct_dset.add_image(name='frame-index-order-demo1',frame_index=-30,video_id=1)>>> dct_dset.add_image(name='frame-index-order-demo2',frame_index=10,video_id=1)>>> dct_dset.add_image(name='frame-index-order-demo3',frame_index=3,video_id=1)>>> dct_dset.add_video(name='empty-video1')>>> dct_dset.add_video(name='empty-video2')>>> dct_dset.add_video(name='empty-video3')>>> sql_dset=dct_dset.view_sql(memory=True)>>> orig=dct_dset.index.vidid_to_gids>>> proxy=sql_dset.index.vidid_to_gids>>> fromkwcoco.util.util_jsonimportindexable_allclose>>> assertindexable_allclose(orig,dict(proxy))>>> items=list(proxy.items())>>> vals=list(proxy.values())>>> keys=list(proxy.keys())>>> assertlen(keys)==len(vals)>>> assertdict(zip(keys,vals))==dict(items)
Parameters:
session (sqlalchemy.orm.session.Session) – the sqlalchemy session
valattr (InstrumentedAttribute) – The column to lookup as a value
keyattr (InstrumentedAttribute) – The column to use as a key
parent_keyattr (InstrumentedAttribute | None) – The column of the table corresponding to the key. If
unspecified the column in the indexed table is used which may
be less efficient.
order_attr (InstrumentedAttribute | None) – This is the attribute that the returned results will be ordered
by
order_id (InstrumentedAttribute | None) – if order_attr belongs to another table, then this must be a
column of the value table that corresponds to the primary key
of the table used for ordering (e.g. when ordering annotations
by image frame index, this must be the annotation image id)
Temporary function to deal with URI. Modern tools seem to use RFC 3968
URIs, but sqlalchemy uses RFC 1738. Attempt to gracefully handle special
cases. With a better understanding of the above specs, this function may be
able to be written more eloquently.
Provides an API nearly identical to kwcoco.CocoDatabase, but uses
an SQL backend data store. This makes it robust to copy-on-write memory
issues that arise when forking, as discussed in [1].
Note
By default constructing an instance of the CocoSqlDatabase does not
create a connection to the databse. Use the connect() method to
open a connection.
Hack to reconstruct the original name. Makes assumptions about how
naming is handled elsewhere. There should be centralized logic about
how to construct side-car names that can be queried for inversed like
this.
Compatibility with the way the exiting cached hashid in the coco
dataset is used. Both of these functions are private and subject to
change (and need optimization).
Helper for understanding the time differences between backends
Note
post_iterate ensures that all of the returned data is looked at by the
python interpreter. Makes this a more fair comparison because python
can just return pointers to the data, but only in the case where most
of the data will touched. For one attribute lookups it is not a good
test.
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.
catToImgs returns integer image ids but imgToAnns returns annotation
dictionaries.
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.
kw18 does not contain complete information, and as such
the returned coco dataset may need to be augmented.
Parameters:
image_paths (Dict[int, str] | None) – if specified, maps frame numbers to image file paths.
video_name (str | None) – if specified records the name of the video this kw18 belongs to
Todo
[X] allow kwargs to specify path to frames / videos
Example
>>> fromkwcoco.kw18importKW18>>> importubeltasub>>> importkwimage>>> importkwcoco>>> # Prep test data - autogen a demo kw18 and write it to disk>>> dpath=ub.Path.appdir('kwcoco/kw18').ensuredir()>>> kw18_fpath=ub.Path(dpath)/'test.kw18'>>> KW18.demo().dump(kw18_fpath)>>> #>>> # Load the kw18 file>>> self=KW18.load(kw18_fpath)>>> # Pretend that these image correspond to kw18 frame numbers>>> frame_names=kwcoco.CocoDataset.demo('shapes8').images().lookup('file_name')>>> frame_ids=sorted(set(self['frame_number']))>>> image_paths=dict(zip(frame_ids,frame_names))>>> #>>> # Convert the kw18 to kwcoco and specify paths to images>>> coco_dset=self.to_coco(image_paths=image_paths,video_name='dummy.mp4')>>> #>>> # Now we can draw images>>> canvas=coco_dset.draw_image(1)>>> # xdoctest: +REQUIRES(--draw)>>> kwimage.imwrite('foo.jpg',canvas)>>> # Draw all iamges>>> forgidincoco_dset.imgs.keys():>>> canvas=coco_dset.draw_image(gid)>>> fpath=dpath/'gid_{}.jpg'.format(gid)>>> print('write fpath = {!r}'.format(fpath))>>> kwimage.imwrite(fpath,canvas)
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,
keypoints, annotation tracks, multi-spectral images, and videos (which
represents a generic sequence of images).
A kwcoco file is a “manifest” that serves as a single reference that points to
all images, categories, and annotations in a computer vision dataset. Thus,
when applying an algorithm to a dataset, it is sufficient to have the algorithm
take one dataset parameter: the path to the kwcoco file. Generally a kwcoco
file will live in a “bundle” directory along with the data that it references,
and paths in the kwcoco file will be relative to the location of the kwcoco
file itself.
The main data structure in this model is largely based on the implementation in
https://github.com/cocodataset/cocoapi It uses the same efficient core indexing
data structures, but in our implementation the indexing can be optionally
turned off, functions are silent by default (with the exception of long running
processes, which optionally show progress by default). We support helper
functions that add and remove images, categories, and annotations.
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.
>>> importkwcoco>>> importjson>>> # Create demo data>>> demo=kwcoco.CocoDataset.demo()>>> # Reroot can switch between absolute / relative-paths>>> demo.reroot(absolute=True)>>> # could also use demo.dump / demo.dumps, but this is more explicit>>> text=json.dumps(demo.dataset)>>> withopen('demo.json','w')asfile:>>> file.write(text)>>> # Read from disk>>> self=kwcoco.CocoDataset('demo.json')>>> # Add data>>> cid=self.add_category('Cat')>>> gid=self.add_image('new-img.jpg')>>> aid=self.add_annotation(image_id=gid,category_id=cid,bbox=[0,0,100,100])>>> # Remove data>>> self.remove_annotations([aid])>>> self.remove_images([gid])>>> self.remove_categories([cid])>>> # Look at data>>> importubeltasub>>> print(ub.urepr(self.basic_stats(),nl=1))>>> print(ub.urepr(self.extended_stats(),nl=2))>>> print(ub.urepr(self.boxsize_stats(),nl=3))>>> print(ub.urepr(self.category_annotation_frequency()))>>> # Inspect data>>> # xdoctest: +REQUIRES(module:kwplot)>>> importkwplot>>> kwplot.autompl()>>> self.show_image(gid=1)>>> # Access single-item data via imgs, cats, anns>>> cid=1>>> self.cats[cid]{'id': 1, 'name': 'astronaut', 'supercategory': 'human'}>>> gid=1>>> self.imgs[gid]{'id': 1, 'file_name': '...astro.png', 'url': 'https://i.imgur.com/KXhKM72.png'}>>> aid=3>>> self.anns[aid]{'id': 3, 'image_id': 1, 'category_id': 3, 'line': [326, 369, 500, 500]}>>> # Access multi-item data via the annots and images helper objects>>> aids=self.index.gid_to_aids[2]>>> annots=self.annots(aids)>>> print('annots = {}'.format(ub.urepr(annots,nl=1,sv=1)))annots = <Annots(num=2)>>>> annots.lookup('category_id')[6, 4]>>> annots.lookup('bbox')[[37, 6, 230, 240], [124, 96, 45, 18]]>>> # built in conversions to efficient kwimage array DataStructures>>> print(ub.urepr(annots.detections.data,sv=1)){ 'boxes': <Boxes(xywh, array([[ 37., 6., 230., 240.], [124., 96., 45., 18.]], dtype=float32))>, 'class_idxs': [5, 3], 'keypoints': <PointsList(n=2)>, 'segmentations': <PolygonList(n=2)>,}>>> gids=list(self.imgs.keys())>>> images=self.images(gids)>>> print('images = {}'.format(ub.urepr(images,nl=1,sv=1)))images = <Images(num=3)>>>> images.lookup('file_name')['...astro.png', '...carl.png', '...stars.png']>>> print('images.annots = {}'.format(images.annots))images.annots = <AnnotGroups(n=3, m=3.7, s=3.9)>>>> print('images.annots.cids = {!r}'.format(images.annots.cids))images.annots.cids = [[1, 2, 3, 4, 5, 5, 5, 5, 5], [6, 4], []]
kwcoco.CocoDataset.index - an efficient lookup index into the coco data structure. The index defines its own attributes like anns, cats, imgs, gid_to_aids, file_name_to_img, etc. See CocoIndex for more details on which attributes are available.
kwcoco.CocoDataset.tag - A tag indicating the name of the dataset.
kwcoco.CocoDataset.dataset - raw json data structure. This is the base dictionary that contains {‘annotations’: List, ‘images’: List, ‘categories’: List}
kwcoco.CocoDataset.bundle_dpath - If known, this is the root path that all image file names are relative to. This can also be manually overwritten by the user.
kwcoco.CocoDataset.ensure_category - Like add_category(), but returns the existing category id if it already exists instead of failing. In this case all metadata is ignored.
kwcoco.CocoDataset.ensure_image - Like add_image(),, but returns the existing image id if it already exists instead of failing. In this case all metadata is ignored.
kwcoco.CocoDataset.find_representative_images - Find images that have a wide array of categories. Attempt to find the fewest images that cover all categories using images that contain both a large and small number of annotations.
kwcoco.CocoDataset.load_annot_sample - Reads the chip of an annotation. Note this is much less efficient than using a sampler, but it doesn’t require disk cache.
kwcoco.CocoDataset.subset - Return a subset of the larger coco dataset by specifying which images to port. All annotations in those images will be taken.
This is a common base for all variants of the Coco Dataset
At the time of writing there is kwcoco.CocoDataset (which is the
dictionary-based backend), and the kwcoco.coco_sql_dataset.CocoSqlDataset,
which is experimental.
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 (networkx.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.
>>> # The coerce classmethod is the easiest way to create an instance>>> importkwcoco>>> 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)
Parameters:
graph (nx.DiGraph) – either the graph representing a category hierarchy
checks (bool, default=True) – if false, bypass input checks
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.
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
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.
Parse and extract information about network input channel specs for
early or late fusion networks.
Behaves like a dictionary of FusedChannelSpec objects
Todo
[ ] Rename to something that indicates this is a collection of
FusedChannelSpec? MultiChannelSpec?
Note
This class name and API is in flux and subject to change.
Note
The pipe (‘|’) character represents an early-fused input stream, and
order matters (it is non-communative).
The comma (‘,’) character separates different inputs streams/branches
for a multi-stream/branch network which will be lated fused. Order does
not matter
>>> # Case where we have to break up early fused data>>> importnumpyasnp>>> dims=(40,40)>>> item={>>> 'rgb|disparity':np.random.rand(4,*dims),>>> 'flowx':np.random.rand(1,*dims),>>> 'flowy':np.random.rand(1,*dims),>>> }>>> # Complex Case>>> self=ChannelSpec('rgb,disparity,rgb|disparity,rgb|disparity|flowx|flowy,flowx|flowy,flowx,disparity')>>> inputs=self.encode(item)>>> input_shapes=ub.map_vals(lambdax:x.shape,inputs)>>> print('input_shapes = {}'.format(ub.urepr(input_shapes,nl=1)))
The main coco dataset class with a json dataset backend.
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, gid_to_aids,
file_name_to_img, etc. See CocoIndex for more details
on which attributes are available.
fpath (PathLike | None) – if known, this stores the filepath the dataset was loaded from
tag (str | None) – A tag indicating the name of the dataset.
bundle_dpath (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.
>>> fromkwcoco.coco_datasetimportdemo_coco_data>>> importkwcoco>>> importubeltasub>>> # Returns a coco json structure>>> dataset=demo_coco_data()>>> # Pass the coco json structure to the API>>> self=kwcoco.CocoDataset(dataset,tag='demo')>>> # Now you can access the data using the index and helper methods>>> #>>> # Start by looking up an image by it's COCO id.>>> image_id=1>>> img=self.index.imgs[image_id]>>> print(ub.urepr(img,nl=1,sort=1)){ 'file_name': 'astro.png', 'id': 1, 'url': 'https://i.imgur.com/KXhKM72.png',}>>> #>>> # Use the (gid_to_aids) index to lookup annotations in the iamge>>> annotation_id=sorted(self.index.gid_to_aids[image_id])[0]>>> ann=self.index.anns[annotation_id]>>> print(ub.urepr((ub.udict(ann)-{'segmentation'}).sorted_keys(),nl=1)){ 'bbox': [10, 10, 360, 490], 'category_id': 1, 'id': 1, 'image_id': 1, 'keypoints': [247, 101, 2, 202, 100, 2],}>>> #>>> # Use annotation category id to look up that information>>> category_id=ann['category_id']>>> cat=self.index.cats[category_id]>>> print('cat = {}'.format(ub.urepr(cat,nl=1,sort=1)))cat = { 'id': 1, 'name': 'astronaut', 'supercategory': 'human',}>>> #>>> # Now play with some helper functions, like extended statistics>>> extended_stats=self.extended_stats()>>> # xdoctest: +IGNORE_WANT>>> print('extended_stats = {}'.format(ub.urepr(extended_stats,nl=1,precision=2,sort=1)))extended_stats = { 'annots_per_img': {'mean': 3.67, 'std': 3.86, 'min': 0.00, 'max': 9.00, 'nMin': 1, 'nMax': 1, 'shape': (3,)}, 'imgs_per_cat': {'mean': 0.88, 'std': 0.60, 'min': 0.00, 'max': 2.00, 'nMin': 2, 'nMax': 1, 'shape': (8,)}, 'cats_per_img': {'mean': 2.33, 'std': 2.05, 'min': 0.00, 'max': 5.00, 'nMin': 1, 'nMax': 1, 'shape': (3,)}, 'annots_per_cat': {'mean': 1.38, 'std': 1.49, 'min': 0.00, 'max': 5.00, 'nMin': 2, 'nMax': 1, 'shape': (8,)}, 'imgs_per_video': {'empty_list': True},}>>> # You can "draw" a raster of the annotated image with cv2>>> canvas=self.draw_image(2)>>> # Or if you have matplotlib you can "show" the image with mpl objects>>> # xdoctest: +REQUIRES(--show)>>> frommatplotlibimportpyplotasplt>>> fig=plt.figure()>>> ax1=fig.add_subplot(1,2,1)>>> self.show_image(gid=2)>>> ax2=fig.add_subplot(1,2,2)>>> ax2.imshow(canvas)>>> ax1.set_title('show with matplotlib')>>> ax2.set_title('draw with cv2')>>> plt.show()
Parameters:
data (str | PathLike | dict | None) – Either a filepath to a coco json file, or a dictionary
containing the actual coco json structure. For a more generally
coercable constructor see func:CocoDataset.coerce.
tag (str | None) – Name of the dataset for display purposes, and does not
influence behavior of the underlying data structure, although
it may be used via convinience methods. We attempt to
autopopulate this via information in data if available.
If unspecfied and data is a filepath this becomes the
basename.
bundle_dpath (str | None) – the root of the dataset that images / external data will be
assumed to be relative to. If unspecfied, we attempt to
determine it using information in data. If data is a
filepath, we use the dirname of that path. If data is a
dictionary, we look for the “img_root” key. If unspecfied and
we fail to introspect then, we fallback to the current working
directory.
img_root (str | None) – deprecated alias for bundle_dpath
workers (int | str) – number of worker threads / processes.
Can also accept coerceable workers.
mode (str) – thread, process, or serial. Defaults to process.
verbose (int) – verbosity level
postprocess (Callable | None) – A function taking one arg (the loaded dataset) to run on the
loaded kwcoco dataset in background workers. This can be more
efficient when postprocessing is independent per kwcoco file.
ordered (bool) – if True yields datasets in the same order as given. Otherwise
results are yielded as they become available. Defaults to True.
**kwargs – arguments passed to the constructor
Yields:
CocoDataset
SeeAlso:
load_multiple - like this function but is a strict file-path-only loader
fpaths (List[str | PathLike]) – list of paths to multiple coco files to be loaded
workers (int) – number of worker threads / processes
mode (str) – thread, process, or serial. Defaults to process.
verbose (int) – verbosity level
postprocess (Callable | None) – A function taking one arg (the loaded dataset) to run on the
loaded kwcoco dataset in background workers and returns the
modified dataset. This can be more efficient when
postprocessing is independent per kwcoco file.
ordered (bool) – if True yields datasets in the same order as given. Otherwise
results are yielded as they become available. Defaults to True.
**kwargs – arguments passed to the constructor
Yields:
CocoDataset
SeeAlso:
coerce_multiple - like this function but accepts general
Loads multiple coco datasets and unions the result
Note
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) – number of worker threads / processes
verbose (int) – verbosity level
mode (str) – thread, process, or serial
union (str | bool) – 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. Default=’try’
Note
This may be deprecated. Use load_multiple or coerce_multiple and
then manually perform the union.
file (PathLike | IO | None) – Where to write the data. Can either be a path to a file or an
open file pointer / stream. If unspecified, it will be written
to the current fpath property.
indent (int | str | None) – indentation for the json file. See
json.dump() for details.
newlines (bool) – if True, each annotation, image, category gets its own line.
temp_file (bool | str) – Argument to safer.open(). Ignored if file is not a
PathLike object. Defaults to ‘auto’, which is False on Windows
and True everywhere else.
compress (bool | str) – if True, dumps the kwcoco file as a compressed zipfile.
In this case a literal IO file object must be opened in binary
write mode. If auto, then it will default to False unless
it can introspect the file name and the name ends with .zip
Example
>>> importkwcoco>>> importubeltasub>>> dpath=ub.Path.appdir('kwcoco/demo/dump').ensuredir()>>> dset=kwcoco.CocoDataset.demo()>>> dset.fpath=dpath/'my_coco_file.json'>>> # Calling dump writes to the current fpath attribute.>>> dset.dump()>>> assertdset.dataset==kwcoco.CocoDataset(dset.fpath).dataset>>> assertdset.dumps()==dset.fpath.read_text()>>> #>>> # Using compress=True can save a lot of space and it>>> # is transparent when reading files via CocoDataset>>> dset.dump(compress=True)>>> assertdset.dataset==kwcoco.CocoDataset(dset.fpath).dataset>>> assertdset.dumps()!=dset.fpath.read_text(errors='replace')
Example
>>> importkwcoco>>> importubeltasub>>> # Compression auto-defaults based on the file name.>>> dpath=ub.Path.appdir('kwcoco/demo/dump').ensuredir()>>> dset=kwcoco.CocoDataset.demo()>>> fpath1=dset.fpath=dpath/'my_coco_file.zip'>>> dset.dump()>>> fpath2=dset.fpath=dpath/'my_coco_file.json'>>> dset.dump()>>> assertfpath1.read_bytes()[0:8]!=fpath2.read_bytes()[0:8]
Merges multiple CocoDataset items into one. Names and
associations are retained, but ids may be different.
Parameters:
*others – a series of CocoDatasets that we will merge.
Note, if called as an instance method, the “self” instance
will be the first item in the “others” list. But if called
like a classmethod, “others” will be empty by default.
disjoint_tracks (bool) – if True, we will assume track-ids are disjoint and if two
datasets share the same track-id, we will disambiguate them.
Otherwise they will be copied over as-is. Defaults to True.
remember_parent (bool) – if True, videos and images will save information about their
parent in the “union_parent” field.
**kwargs – constructor options for the new merged CocoDataset
>>> # 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.urepr(merged.imgs,nl=1)))>>> assertset(merged.imgs)&set([1,2,3,4,5])==set(merged.imgs)
Create a cached SQL interface to this dataset suitable for large scale
multiprocessing use cases.
Parameters:
force_rewrite (bool) – if True, forces an update to any existing cache file on disk
memory (bool) – if True, the database is constructed in memory.
backend (str) – sqlite or postgresql
sql_db_fpath (str | PathLike | None) – overrides the database uri
Note
This view cache is experimental and currently depends on the
timestamp of the file pointed to by self.fpath. In other words
dont use this on in-memory datasets.
An object-oriented representation of a coco image.
It provides helper methods that are specific to a single image.
This operates directly on a single coco image dictionary, but it can
optionally be connected to a parent dataset, which allows it to use
CocoDataset methods to query about relationships and resolve pointers.
This is different than the Images class in coco_object1d, which is just a
vectorized interface to multiple objects.
First, try to find the auxiliary image that has with the smallest
distortion to the base image (if known via warp_aux_to_img)
Second, break ties by using the largest image if w / h is known
Last, if previous information not available use the first
auxiliary image.
Parameters:
requires (List[str] | None) – list of attribute that must be non-None to consider an object
as the primary one.
as_dict (bool) – if True the return type is a raw dictionary. Otherwise use a newer
object-oriented wrapper that should be duck-type swappable.
In the future this default will change to False.
with_bundle (bool) – If True, prepends the bundle dpath to fully specify the path.
Otherwise, just returns the registered string in the file_name
attribute of each asset. Defaults to True.
Adds an auxiliary / asset item to the image dictionary.
This operation can be done purely in-memory (the default), or the image
data can be written to a file on disk (via the imwrite=True flag).
Parameters:
file_name (str | PathLike | None) – The name of the file relative to the bundle directory. If
unspecified, imdata must be given.
channels (str | kwcoco.FusedChannelSpec | None) – The channel code indicating what each of the bands represents.
These channels should be disjoint wrt to the existing data in
this image (this is not checked).
imdata (ndarray | None) – The underlying image data this auxiliary item represents. If
unspecified, it is assumed file_name points to a path on disk
that will eventually exist. If imdata, file_name, and the
special imwrite=True flag are specified, this function will
write the data to disk.
warp_aux_to_img (kwimage.Affine | None) – The transformation from this auxiliary space to image space.
If unspecified, assumes this item is related to image space by
only a scale factor.
width (int | None) – Width of the data in auxiliary space (inferred if unspecified)
height (int | None) – Height of the data in auxiliary space (inferred if unspecified)
imwrite (bool) – If specified, both imdata and file_name must be specified, and
this will write the data to disk. Note: it it recommended that
you simply call imwrite yourself before or after calling this
function. This lets you better control imwrite parameters.
image_id (int | None) – An asset dictionary contains an image-id, but it should not
be specified here. If it is, then it must agree with this
image’s id.
**kw – stores arbitrary key/value pairs in this new asset.
Todo
[ ] Allow imwrite to specify an executor that is used to
return a Future so the imwrite call does not block.
The delayed load can load a subset of channels, and perform lazy
warping operations. If the underlying data is in a tiled format this
can reduce the amount of disk IO needed to read the data if only a
small crop or lower resolution view of the data is needed.
Note
This method is experimental and relies on the delayed load
proof-of-concept.
Parameters:
gid (int) – image id to load
channels (kwcoco.FusedChannelSpec) – specific channels to load.
if unspecified, all channels are loaded.
space (str) – can either be “image” for loading in image space, or
“video” for loading in video space.
resolution (None | str | float) – If specified, applies an additional scale factor to the result
such that the data is loaded at this specified resolution.
This requires that the image / video has a registered
resolution attribute and that its units agree with this
request.
Todo
[ ] This function could stand to have a better name. Maybe imread
with a delayed=True flag? Or maybe just delayed_load?
>>> # TODO: should only select the "red" channel>>> dset=kwcoco.CocoDataset.demo('shapes8')>>> delayed=CocoImage(dset.imgs[gid],dset).imdelay(channels='r')
>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo()>>> coco_img=dset.coco_image(1)>>> # Test case where nothing is registered in the dataset>>> delayed=coco_img.imdelay()>>> final=delayed.finalize()>>> assertfinal.shape==(512,512,3)
>>> # Test that delay works when imdata is stored in the image>>> # dictionary itself.>>> fromkwcoco.coco_imageimport*# NOQA>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo('vidshapes8-multispectral')>>> coco_img=dset.coco_image(1)>>> imdata=np.random.rand(6,6,5)>>> imdata[:]=np.arange(5)[None,None,:]>>> channels=kwcoco.FusedChannelSpec.coerce('Aux:5')>>> coco_img.add_auxiliary_item(imdata=imdata,channels=channels)>>> delayed=coco_img.imdelay(channels='B1|Aux:2:4')>>> final=delayed.finalize()
Example
>>> # Test delay when loading in asset space>>> fromkwcoco.coco_imageimport*# NOQA>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo('vidshapes8-msi-multisensor')>>> coco_img=dset.coco_image(1)>>> stream1=coco_img.channels.streams()[0]>>> stream2=coco_img.channels.streams()[1]>>> asset_delayed=coco_img.imdelay(stream1,space='asset')>>> img_delayed=coco_img.imdelay(stream1,space='image')>>> vid_delayed=coco_img.imdelay(stream1,space='video')>>> #>>> aux_imdata=asset_delayed.as_xarray().finalize()>>> img_imdata=img_delayed.as_xarray().finalize()>>> assertaux_imdata.shape!=img_imdata.shape>>> # Cannot load multiple asset items at the same time in>>> # asset space>>> importpytest>>> fused_channels=stream1|stream2>>> fromdelayed_image.delayed_nodesimportCoordinateCompatibilityError>>> withpytest.raises(CoordinateCompatibilityError):>>> aux_delayed2=coco_img.imdelay(fused_channels,space='asset')
Example
>>> # Test loading at a specific resolution.>>> fromkwcoco.coco_imageimport*# NOQA>>> importkwcoco>>> dset=kwcoco.CocoDataset.demo('vidshapes8-msi-multisensor')>>> coco_img=dset.coco_image(1)>>> coco_img.img['resolution']='1 meter'>>> img_delayed1=coco_img.imdelay(space='image')>>> vid_delayed1=coco_img.imdelay(space='video')>>> # test with unitless request>>> img_delayed2=coco_img.imdelay(space='image',resolution=3.1)>>> vid_delayed2=coco_img.imdelay(space='video',resolution='3.1 meter')>>> np.ceil(img_delayed1.shape[0]/3.1)==img_delayed2.shape[0]>>> np.ceil(vid_delayed1.shape[0]/3.1)==vid_delayed2.shape[0]>>> # test with unitless data>>> coco_img.img['resolution']=1>>> img_delayed2=coco_img.imdelay(space='image',resolution=3.1)>>> vid_delayed2=coco_img.imdelay(space='video',resolution='3.1 meter')>>> np.ceil(img_delayed1.shape[0]/3.1)==img_delayed2.shape[0]>>> np.ceil(vid_delayed1.shape[0]/3.1)==vid_delayed2.shape[0]
Provides an API nearly identical to kwcoco.CocoDatabase, but uses
an SQL backend data store. This makes it robust to copy-on-write memory
issues that arise when forking, as discussed in [1].
Note
By default constructing an instance of the CocoSqlDatabase does not
create a connection to the databse. Use the connect() method to
open a connection.
Hack to reconstruct the original name. Makes assumptions about how
naming is handled elsewhere. There should be centralized logic about
how to construct side-car names that can be queried for inversed like
this.
Compatibility with the way the exiting cached hashid in the coco
dataset is used. Both of these functions are private and subject to
change (and need optimization).
A specific type of channel spec with only one early fused stream.
The channels in this stream are non-communative
Behaves like a list of atomic-channel codes
(which may represent more than 1 channel), normalized codes always
represent exactly 1 channel.
Note
This class name and API is in flux and subject to change.
Todo
A special code indicating a name and some number of bands that that
names contains, this would primarilly be used for large numbers of
channels produced by a network. Like:
resnet_d35d060_L5:512
or
resnet_d35d060_L5[:512]
might refer to a very specific (hashed) set of resnet parameters
with 512 bands
maybe we can do something slicly like:
resnet_d35d060_L5[A:B]
resnet_d35d060_L5:A:B
Do we want to “just store the code” and allow for parsing later?
Or do we want to ensure the serialization is parsed before we
construct the data structure?
>>> # xdoctest: +REQUIRES(module:lark)>>> fromdelayed_imageimportSensorChanSpec>>> a=SensorChanSpec.coerce('Cam1:(red,blue)')>>> b=SensorChanSpec.coerce('Cam2:(blue,green)')>>> c=(a+b).concise()>>> print(c)(Cam1,Cam2):blue,Cam1:red,Cam2:green>>> # Note the importance of parenthesis in the previous example>>> # otherwise channels will be assigned to `*` the generic sensor.>>> a=SensorChanSpec.coerce('Cam1:red,blue')>>> b=SensorChanSpec.coerce('Cam2:blue,green')>>> c=(a+b).concise()>>> print(c)(*,Cam2):blue,*:green,Cam1:red