Exemple #1
0
    def _to_coco(dmet):
        """
        Convert to a coco representation of truth and predictions
        """
        import kwcoco
        true = kwcoco.CocoDataset()
        pred = kwcoco.CocoDataset()

        for node in dmet.classes:
            # cid = dmet.classes.graph.node[node]['id']
            cid = dmet.classes.index(node)
            supercategory = list(dmet.classes.graph.pred[node])
            if len(supercategory) == 0:
                supercategory = None
            else:
                assert len(supercategory) == 1
                supercategory = supercategory[0]
            true.add_category(node, id=cid, supercategory=supercategory)
            pred.add_category(node, id=cid, supercategory=supercategory)

        for imgname, gid in dmet._imgname_to_gid.items():
            true.add_image(imgname, id=gid)
            pred.add_image(imgname, id=gid)

        idx_to_id = {
            idx: dmet.classes.index(node)
            for idx, node in enumerate(dmet.classes.idx_to_node)
        }

        for gid, pred_dets in dmet.gid_to_pred_dets.items():
            pred_boxes = pred_dets.boxes
            if 'scores' in pred_dets.data:
                pred_scores = pred_dets.scores
            else:
                pred_scores = np.ones(len(pred_dets))
            pred_cids = list(ub.take(idx_to_id, pred_dets.class_idxs))
            pred_xywh = pred_boxes.to_xywh().data.tolist()
            for bbox, cid, score in zip(pred_xywh, pred_cids, pred_scores):
                pred.add_annotation(gid, cid, bbox=bbox, score=score)

        for gid, true_dets in dmet.gid_to_true_dets.items():
            true_boxes = true_dets.boxes
            if 'weights' in true_dets.data:
                true_weights = true_dets.weights
            else:
                true_weights = np.ones(len(true_boxes))
            true_cids = list(ub.take(idx_to_id, true_dets.class_idxs))
            true_xywh = true_boxes.to_xywh().data.tolist()
            for bbox, cid, weight in zip(true_xywh, true_cids, true_weights):
                true.add_annotation(gid, cid, bbox=bbox, weight=weight)

        return pred, true
Exemple #2
0
 def _ensure_coco(coco):
     # Map a file path or an in-memory dataset to a CocoDataset
     import kwcoco
     import six
     from os.path import exists
     if coco is None:
         return None
     elif isinstance(coco, six.string_types):
         fpath = _rectify_fpath(coco)
         if exists(fpath):
             with ub.Timer(
                     'read kwcoco dataset: fpath = {!r}'.format(fpath)):
                 coco = kwcoco.CocoDataset(fpath, autobuild=False)
             print('building kwcoco index')
             coco._build_index()
         else:
             if not coco.lower().startswith('special:'):
                 import warnings
                 warnings.warn('warning start dataset codes with special:')
                 code = coco
             else:
                 code = coco.lower()[len('special:'):]
             coco = kwcoco.CocoDataset.demo(code)
     else:
         # print('live dataset')
         assert isinstance(coco, kwcoco.CocoDataset)
     return coco
Exemple #3
0
def make_pycocotools_compliant(fpath):
    import kwcoco
    import kwimage
    import ubelt as ub

    print('Reading fpath = {!r}'.format(fpath))
    dset = kwcoco.CocoDataset(fpath)

    dset._ensure_imgsize(workers=8)

    for ann in ub.ProgIter(dset.dataset['annotations'], desc='update anns'):
        if 'iscrowd' not in ann:
            ann['iscrowd'] = False

        if 'ignore' not in ann:
            ann['ignore'] = ann.get('weight', 1.0) < .5

        if 'area' not in ann:
            # Use segmentation if available
            if 'segmentation' in ann:
                poly = kwimage.Polygon.from_coco(ann['segmentation'][0])
                ann['area'] = float(poly.to_shapely().area)
            else:
                x, y, w, h = ann['bbox']
                ann['area'] = w * h

    dset.dump(dset.fpath, newlines=True)
Exemple #4
0
def convert(in_path, out_path):
    from bioharn.io.viame_csv import ViameCSV

    dset = kwcoco.CocoDataset()

    # TODO: ability to map image ids to agree with another coco file
    csv = ViameCSV(in_path)
    csv.extend_coco(dset=dset)
    dset.dump(out_path, newlines=True)
Exemple #5
0
def test_union_with_aux():
    from os.path import join
    test_img1 = {
        'id': 1,
        'name': 'foo',
        'file_name': 'subdir/images/foo.png',
        'auxiliary': [{
            'channels': 'ir',
            'file_name': 'subdir/aux/foo.png',
        }]
    }

    test_img2 = {
        'id': 1,
        'name': 'bar',
        'file_name': 'images/bar.png',
        'auxiliary': [{
            'channels': 'ir',
            'file_name': 'aux/foo.png',
        }]
    }

    import kwcoco
    dset1 = kwcoco.CocoDataset()
    dset1.add_image(**test_img1)
    dset1.fpath = join('.', 'dset1', 'data.kwcoco.json')

    dset2 = kwcoco.CocoDataset()
    dset2.add_image(**test_img2)
    dset2.fpath = join('.', 'subdir/dset2', 'data.kwcoco.json')

    combo = kwcoco.CocoDataset.union(dset1, dset2)

    assert combo.get_image_fpath(1) == dset1.get_image_fpath(1)
    assert combo.get_image_fpath(1, channels='ir') == dset1.get_image_fpath(
        1, channels='ir')

    assert combo.get_image_fpath(2) == dset2.get_image_fpath(1)
    assert combo.get_image_fpath(2, channels='ir') == dset2.get_image_fpath(
        1, channels='ir')
def getting_started_existing_dataset():
    """
    If you want to start using the Python API. Just open IPython and try:
    """

    import kwcoco
    dset = kwcoco.CocoDataset('<DATASET_NAME>/data.kwcoco.json')

    """
    You can access category, image, and annotation infomation using the index:
    """
    cat = dset.index.cats[1]
    print('The First Category = {!r}'.format(cat))

    img = dset.index.imgs[1]
    print('The First Image = {!r}'.format(img))

    ann = dset.index.anns[1]
    print('The First Annotation = {!r}'.format(ann))
    def main(cls, cmdline=True, **kw):
        """
        CommandLine:
            xdoctest -m make_coco_from_masks.py MakeCocoFromMasksCLI.main

        Example:
            >>> import ubelt as ub
            >>> cls = MakeCocoFromMasksCLI
            >>> cmdline = False
            >>> dpath = _make_intmask_demodata()
            >>> kw = {'src': join(dpath, '*.png'), 'dst': 'masks.mscoco.json'}
            >>> fpath = cls.main(cmdline, **kw)
            >>> # Check validity
            >>> import kwcoco
            >>> dset = kwcoco.CocoDataset(fpath)
            >>> dset._check_integrity()
            >>> # Ensure we can convert to kwimage and access segmentations
            >>> dset.annots().detections.data['segmentations'][0].data
        """
        import kwcoco
        config = cls.CLIConfig(kw, cmdline=cmdline)
        print('config = {}'.format(ub.repr2(dict(config), nl=2)))
        serialization_method = config['serialization']

        # Initialize an empty COCO dataset
        coco_dset = kwcoco.CocoDataset()

        # Path to each mask object
        mask_fpaths = config['src']
        for mask_fpath in mask_fpaths:
            # I assume each mask corresponds to a single image, but I dont know
            # what those images should be at the moment. TODO: if this is going
            # to be a real script, then we should find a nice way of specifying
            # the correspondences between masks and the images to which they
            # belong. For now, I'm going to use the mask itself as a dummy
            # value.

            img_fpath = mask_fpath
            image_id = coco_dset.add_image(file_name=img_fpath)

            # Parse the mask file, and add each object as a new annotation
            multi_mask = kwimage.imread(mask_fpath)
            # I recall there is a better opencv of splitting these sort of
            # masks up into binary masks, maybe it was a connected-component
            # function? I guess it depends if you want disconnected objects
            # represented as separte annotations or not.  I'm just going to do
            # the naive thing for now.
            obj_idxs = np.setdiff1d(np.unique(multi_mask), [0])
            for obj_idx in obj_idxs:
                bin_data = (multi_mask == obj_idx).astype(np.uint8)

                # Create a kwimage object which has nice `to_coco` methods
                bin_mask = kwimage.Mask(bin_data, format='c_mask')

                # We can either save in our coco file as a raster RLE style
                # mask, or we can use a vector polygon style mask. Either of
                # the resulting coco_sseg objects is a valid value for an
                # annotation's "segmentation" field.
                if serialization_method == 'raster':
                    sseg = bin_mask
                    coco_sseg = sseg.to_coco(style='new')
                elif serialization_method == 'vector':
                    bin_poly = bin_mask.to_multi_polygon()
                    sseg = bin_poly
                    coco_sseg = sseg.to_coco(style='new')

                # Now we add this segmentation to the coco dataset as a new
                # annotation. The annotation needs to know which image it
                # belongs to, and ideally it has a category and a bounding box
                # as well.

                # We can make up a dummy category (note that ensure_category
                # will not fail if there is a duplicate entry but add_category
                # will)
                category_id = coco_dset.ensure_category(
                    'class_{}'.format(obj_idx))

                # We can use the kwimage sseg object to get the bounding box
                # FIXME: apparently the MultiPolygon object doesnt implement
                # `to_boxes`, at the moment, so force an inefficient conversion
                # back to a mask as a hack and use its to_boxes method.
                # Technically, the bounding box should not be required, but its
                # a good idea to always include it.
                bbox = list(
                    sseg.to_mask(
                        dims=multi_mask.shape).to_boxes().to_coco())[0]

                METHOD1 = False
                if METHOD1:
                    # We could just add it diretly like this
                    # FIXME: it should be ok to add an annotation without a
                    # category, but it seems like a recent change in kwcoco has
                    # broken that. That will be fixed in the future.
                    annot_id = coco_dset.add_annotation(
                        image_id=image_id,
                        category_id=category_id,
                        bbox=bbox,
                        segmentation=coco_sseg)
                else:
                    # But lets do it as if we were adding a segmentation to an
                    # existing dataset. In this case we access the
                    # CocoDataset's annotation index structure.
                    #
                    # First add the basic annotation
                    annot_id = coco_dset.add_annotation(
                        image_id=image_id, category_id=category_id, bbox=bbox)
                    # Then use the annotation id to look up its coco-dictionary
                    # representation and simply add the segmentation field
                    ann = coco_dset.anns[annot_id]
                    ann['segmentation'] = coco_sseg

        # You dont have to set the fpath attr, but I tend to like it
        coco_dset.fpath = config['dst']
        print('Writing to fpath = {}'.format(ub.repr2(coco_dset.fpath, nl=1)))
        coco_dset.dump(coco_dset.fpath, newlines=True)
        return coco_dset.fpath
Exemple #8
0
def load_kwcoco_dataset(kwcoco_dataset_id):
    logger.info('Starting KWCOCO ETL routine')
    ds_entry = KWCOCOArchive.objects.get(id=kwcoco_dataset_id)
    if not ds_entry.name:
        ds_entry.name = os.path.basename(ds_entry.spec_file.name)
        ds_entry.save(update_fields=[
            'name',
        ])

    # TODO: add a setting like this:
    workdir = getattr(settings, 'GEODATA_WORKDIR', None)
    with tempfile.TemporaryDirectory(dir=workdir) as tmpdir:

        if ds_entry.image_set:
            # Delete all previously existing data
            # This should cascade to all the annotations
            for image in ds_entry.image_set.images.all():
                image.file.delete()
        else:
            ds_entry.image_set = ImageSet()
        ds_entry.image_set.name = ds_entry.name
        ds_entry.image_set.save()
        ds_entry.save(update_fields=[
            'image_set',
        ]  # noqa: E231
                      )

        # Unarchive the images locally so we can import them when loading the spec
        # Images could come from a URL, so this is optional
        if ds_entry.image_archive:
            with ds_entry.image_archive.file as file_obj:
                logger.info(
                    f'The KWCOCO image archive: {ds_entry.image_archive}')
                # Place images in a local directory and keep track of root path
                # Unzip the contents to the working dir
                with zipfile.ZipFile(file_obj, 'r') as zip_ref:
                    zip_ref.extractall(tmpdir)
                logger.info(f'The extracted KWCOCO image archive: {tmpdir}')
        else:
            pass
            # TODO: how should we download data from specified URLs?

        # Load the KWCOCO JSON spec and make annotations on the images
        with ds_entry.spec_file.yield_local_path() as file_path:
            ds = kwcoco.CocoDataset(str(file_path))
            # Set the root dir to where the images were extracted / the temp dir
            # If images are coming from URL, they will download to here
            ds.img_root = tmpdir
            # Iterate over images and create an ImageMeta from them.
            # Any images in the archive that aren't listed in the JSON will be deleted
            for imgid in ds.imgs.keys():
                ak = ds.index.gid_to_aids[imgid]
                img = ds.imgs[imgid]
                anns = [ds.anns[k] for k in ak]
                # Create the Image entry to track each image's location
                image_file_abs_path = os.path.join(ds.img_root,
                                                   img['file_name'])
                name = os.path.basename(image_file_abs_path)
                file = ChecksumFile()
                file.file.save(name, open(image_file_abs_path, 'rb'))
                image, _ = Image.objects.get_or_create(file=file)
                # Add ImageMeta to ImageSet
                ds_entry.image_set.images.add(image)
                # Create annotations that link to that ImageMeta
                for ann in anns:
                    annotation_entry = Annotation()
                    annotation_entry.image = image
                    try:
                        annotation_entry.label = ds.cats[
                            ann['category_id']]['name']
                    except KeyError:
                        pass
                    # annotation_entry.annotator =
                    # annotation_entry.notes =
                    _fill_annotation_segmentation(annotation_entry, ann)
    logger.info('Done with KWCOCO ETL routine')
Exemple #9
0
def convert_camvid_raw_to_coco(camvid_raw_info):
    """
    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
        >>> del camvid_raw_info['img_paths'][2:]
        >>> del camvid_raw_info['mask_paths'][2:]
        >>> dset = convert_camvid_raw_to_coco(camvid_raw_info)
        >>> # xdoctest: +REQUIRES(--show)
        >>> import kwplot
        >>> 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)
    """
    import re
    import kwimage
    import kwcoco
    print('Converting CamVid to MS-COCO format')

    dset_root, img_paths, label_path, mask_paths = ub.take(
        camvid_raw_info,
        'dset_root, img_paths, label_path, mask_paths'.split(', '))

    img_infos = {
        'img_fname': img_paths,
        'mask_fname': mask_paths,
    }
    keys = list(img_infos.keys())
    next_vals = list(zip(*img_infos.values()))
    image_items = [{k: v for k, v in zip(keys, vals)} for vals in next_vals]

    dataset = {
        'img_root': dset_root,
        'images': [],
        'categories': [],
        'annotations': [],
    }

    lines = ub.readfrom(label_path).split('\n')
    lines = [line for line in lines if line]
    for line in lines:
        color_text, name = re.split('\t+', line)
        r, g, b = map(int, color_text.split(' '))
        color = (r, g, b)

        # Parse the special camvid format
        cid = (r << 16) + (g << 8) + (b << 0)
        cat = {
            'id': cid,
            'name': name,
            'color': color,
        }
        dataset['categories'].append(cat)

    for gid, img_item in enumerate(image_items, start=1):
        img = {
            'id': gid,
            'file_name': img_item['img_fname'],
            # nonstandard image field
            'segmentation': img_item['mask_fname'],
        }
        dataset['images'].append(img)

    dset = kwcoco.CocoDataset(dataset)
    dset.rename_categories({'Void': 'background'})

    assert dset.name_to_cat['background']['id'] == 0
    dset.name_to_cat['background'].setdefault('alias', []).append('Void')

    if False:
        _define_camvid_class_hierarcy(dset)

    if 1:
        # TODO: Binarize CCs (and efficiently encode if possible)
        import numpy as np

        bad_info = []
        once = False

        # Add images
        dset.remove_annotations(list(dset.index.anns.keys()))
        for gid, img in ub.ProgIter(dset.imgs.items(),
                                    desc='parse label masks'):
            mask_fpath = join(dset_root, img['segmentation'])

            rgb_mask = kwimage.imread(mask_fpath, space='rgb')
            r, g, b = rgb_mask.T.astype(np.int64)
            cid_mask = np.ascontiguousarray(rgb_to_cid(r, g, b).T)

            cids = set(np.unique(cid_mask)) - {0}

            for cid in cids:
                if cid not in dset.cats:
                    if gid == 618:
                        # Handle a known issue with image 618
                        c_mask = (cid == cid_mask).astype(np.uint8)
                        total_bad = c_mask.sum()
                        if total_bad < 32:
                            if not once:
                                print(
                                    'gid 618 has a few known bad pixels, ignoring them'
                                )
                                once = True
                            continue
                        else:
                            raise Exception('more bad pixels than expected')
                    else:
                        raise Exception(
                            'UNKNOWN cid = {!r} in gid={!r}'.format(cid, gid))

                    # bad_rgb = cid_to_rgb(cid)
                    # print('bad_rgb = {!r}'.format(bad_rgb))
                    # print('WARNING UNKNOWN cid = {!r} in gid={!r}'.format(cid, gid))
                    # bad_info.append({
                    #     'gid': gid,
                    #     'cid': cid,
                    # })
                else:
                    ann = {
                        'category_id': cid,
                        'image_id': gid
                        # 'segmentation': mask.to_coco()
                    }
                    assert cid in dset.cats
                    c_mask = (cid == cid_mask).astype(np.uint8)
                    mask = kwimage.Mask(c_mask, 'c_mask')

                    box = kwimage.Boxes([mask.get_xywh()], 'xywh')
                    # box = mask.to_boxes()

                    ann['bbox'] = ub.peek(box.to_coco())
                    ann['segmentation'] = mask.to_coco()
                    dset.add_annotation(**ann)

        if 0:
            bad_cids = [i['cid'] for i in bad_info]
            print(sorted([c['color'] for c in dataset['categories']]))
            print(sorted(set([cid_to_rgb(i['cid']) for i in bad_info])))

            gid = 618
            img = dset.imgs[gid]
            mask_fpath = join(dset_root, img['segmentation'])
            rgb_mask = kwimage.imread(mask_fpath, space='rgb')
            r, g, b = rgb_mask.T.astype(np.int64)
            cid_mask = np.ascontiguousarray(rgb_to_cid(r, g, b).T)
            cid_hist = ub.dict_hist(cid_mask.ravel())

            bad_cid_hist = {}
            for cid in bad_cids:
                bad_cid_hist[cid] = cid_hist.pop(cid)

            import kwplot
            kwplot.autompl()
            kwplot.imshow(rgb_mask)

    if 0:
        import kwplot
        plt = kwplot.autoplt()
        plt.clf()
        dset.show_image(1)

        import xdev
        gid_list = list(dset.imgs)
        for gid in xdev.InteractiveIter(gid_list):
            dset.show_image(gid)
            xdev.InteractiveIter.draw()

    dset._build_index()
    dset._build_hashid()
    return dset
Exemple #10
0
def grab_coco_camvid():
    """
    Example:
        >>> # xdoctest: +REQUIRES(--download)
        >>> dset = grab_coco_camvid()
        >>> print('dset = {!r}'.format(dset))
        >>> # xdoctest: +REQUIRES(--show)
        >>> import kwplot
        >>> plt = kwplot.autoplt()
        >>> plt.clf()
        >>> dset.show_image(gid=1)

    Ignore:
        import xdev
        gid_list = list(dset.imgs)
        for gid in xdev.InteractiveIter(gid_list):
            dset.show_image(gid)
            xdev.InteractiveIter.draw()
    """
    import kwcoco
    cache_dpath = ub.ensure_app_cache_dir('kwcoco', 'camvid')
    coco_fpath = join(cache_dpath, 'camvid.mscoco.json')

    # Need to manually bump this if you make a change to loading
    SCRIPT_VERSION = 'v4'

    # Ubelt's stamp-based caches are super cheap and let you take control of
    # the data format.
    stamp = ub.CacheStamp('camvid_coco',
                          cfgstr=SCRIPT_VERSION,
                          dpath=cache_dpath,
                          product=coco_fpath,
                          hasher='sha1',
                          verbose=3)
    if stamp.expired():
        camvid_raw_info = grab_raw_camvid()
        dset = convert_camvid_raw_to_coco(camvid_raw_info)
        with ub.Timer('dumping MS-COCO dset to: {}'.format(coco_fpath)):
            dset.dump(coco_fpath)
        # Mark this process as completed by saving a small file containing the
        # hash of the "product" you are stamping.
        stamp.renew()

    # We can also cache the index build step independently. This uses
    # ubelt.Cacher, which is pickle based, and writes the actual object to
    # disk. Each type of caching has its own uses and tradeoffs.
    cacher = ub.Cacher('prebuilt-coco',
                       cfgstr=SCRIPT_VERSION,
                       dpath=cache_dpath,
                       verbose=3)
    dset = cacher.tryload()
    if dset is None:
        print('Reading coco_fpath = {!r}'.format(coco_fpath))
        dset = kwcoco.CocoDataset(coco_fpath, tag='camvid')
        # Directly save the file to disk.
        dset._build_index()
        dset._build_hashid()
        cacher.save(dset)

    camvid_dset = dset
    print('Loaded camvid_dset = {!r}'.format(camvid_dset))
    return camvid_dset
Exemple #11
0
    def to_coco(self, image_paths=None, video_name=None):
        """
        Translates a kw18 files to a CocoDataset.

        Note:
            kw18 does not contain complete information, and as such
            the returned coco dataset may need to be augmented.

        Args:
            image_paths (Dict[int, str], default=None):
                if specified, maps frame numbers to image file paths.

            video_name (str, default=None):
                if specified records the name of the video this kw18 belongs to

        TODO:
            - [X] allow kwargs to specify path to frames / videos

        Example:
            >>> from kwcoco.kw18 import KW18
            >>> from os.path import join
            >>> import ubelt as ub
            >>> import kwimage
            >>> # Prep test data - autogen a demo kw18 and write it to disk
            >>> dpath = ub.ensure_app_cache_dir('kwcoco/kw18')
            >>> kw18_fpath = join(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 = [kwimage.grab_test_image_fpath(k) for k in kwimage.grab_test_image.keys()]
            >>> 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
            >>> for gid in coco_dset.imgs.keys():
            >>>     canvas = coco_dset.draw_image(gid)
            >>>     fpath = join(dpath, 'gid_{}.jpg'.format(gid))
            >>>     print('write fpath = {!r}'.format(fpath))
            >>>     kwimage.imwrite(fpath, canvas)
        """
        import kwcoco
        import ubelt as ub
        dset = kwcoco.CocoDataset()

        # kw18s don't have category names, so use ids as proxies
        unique_category_ids = sorted(set(self['object_type_id']))
        for cid in unique_category_ids:
            dset.ensure_category('class_{}'.format(cid), id=cid)

        unique_frame_idxs = ub.argunique(self['frame_number'])

        # kw18 files correspond to one video
        vidid = 1
        dset.add_video(id=vidid, name='unknown_kw18_video')

        # Index frames of the video
        for idx in unique_frame_idxs:
            frame_num = self['frame_number'][idx]
            timestamp = self['timestamp'][idx]
            if image_paths and frame_num in image_paths:
                file_name = image_paths[frame_num]
            else:
                file_name = '<unknown_image_{}>'.format(frame_num)
            dset.add_image(id=frame_num,
                           file_name=file_name,
                           video_id=vidid,
                           frame_index=frame_num,
                           timestamp=timestamp)

        for rx, row in self.iterrows():
            tl_x = row['img_bbox_tl_x']
            tl_y = row['img_bbox_tl_y']
            br_x = row['img_bbox_br_x']
            br_y = row['img_bbox_br_y']
            w = br_x - tl_x
            h = br_y - tl_y
            bbox = [tl_x, tl_y, w, h]

            world_loc = (row['world_loc_x'], row['world_loc_y'],
                         row['world_loc_z'])
            velocity = (row['velocity_x'], row['velocity_y'])

            kw = {}
            if 'confidence' in row:
                kw['score'] = row['confidence']

            dset.add_annotation(id=rx,
                                image_id=row['frame_number'],
                                category_id=row['object_type_id'],
                                track_id=row['track_id'],
                                bbox=bbox,
                                area=row['area'],
                                velocity=velocity,
                                world_loc=world_loc,
                                **kw)
        return dset
Exemple #12
0
def coco_from_viame_csv(csv_fpaths, images=None):
    @ub.memoize
    def lazy_image_list():
        if images is None:
            raise Exception('must specify where the image root is')
        if isdir(images):
            image_dpath = images
            all_gpaths = []
            import os
            for root, ds, fs in os.walk(image_dpath):
                IMG_EXT = {'png', 'jpg', 'jpeg', 'tif', 'tiff'}
                gpaths = [join(root, f) for f in fs if f.split('.')[-1].lower() in IMG_EXT]
                if len(gpaths) > 1 and len(ds) != 0:
                    raise Exception('Images must be in a leaf directory')
                if len(all_gpaths) > 0:
                    raise Exception('Images cannot be nested ATM')
                all_gpaths += gpaths
            all_gpaths = sorted(all_gpaths)
        else:
            raise NotImplementedError

        return all_gpaths

    indexed_images = None

    import kwcoco
    dset = kwcoco.CocoDataset()
    for csv_fpath in csv_fpaths:
        with open(csv_fpath, 'r') as file:
            text = file.read()
        lines = [line.strip() for line in text.split('\n')]
        lines = [line for line in lines if line and not line.startswith('#')]
        for line in lines:
            parts = line.split(',')
            tid = int(parts[0])
            gname = parts[1]
            frame_index = int(parts[2])

            if gname == '':
                if len(dset.imgs) == 0 or indexed_images:
                    # I GUESS WE ARE SUPPOSED TO GUESS WHAT IMAGE IS WHICH
                    if not indexed_images:
                        indexed_images = lazy_image_list()
                    try:
                        gname = indexed_images[frame_index]
                    except IndexError:
                        continue
                else:
                    # Also, VIAME-CSV lets the annotations run longer than the
                    # image sequence, so account for that.
                    # Skip this annotation
                    continue

            tl_x, tl_y, br_x, br_y = map(float, parts[3:7])
            w = br_x - tl_x
            h = br_y - tl_y
            bbox = [tl_x, tl_y, w, h]
            score = float(parts[7])
            target_len = float(parts[8])

            rest = parts[9:]
            catparts = []
            rest_iter = iter(rest)
            for p in rest_iter:
                if p.startswith('('):
                    catparts.append(p)

            final_parts = list(rest_iter)
            if final_parts:
                raise NotImplementedError

            catnames = rest[0::2]
            catscores = list(map(float, rest[1::2]))

            cat_to_score = ub.dzip(catnames, catscores)
            if cat_to_score:
                catname = ub.argmax(cat_to_score)
                cid = dset.ensure_category(name=catname)
            else:
                cid = None

            gid = dset.ensure_image(file_name=gname, frame_index=frame_index)
            kw = {}
            if target_len >= 0:
                kw['target_len'] = target_len
            if score >= 0:
                kw['score'] = score

            dset.add_annotation(
                image_id=gid, category_id=cid, track_id=tid, bbox=bbox, **kw
            )
    return dset
Exemple #13
0
def test_frames_are_in_order():
    import kwcoco
    import ubelt as ub
    import random

    def is_sorted(x):
        return x == sorted(x)

    total_frames = 30
    total_videos = 4
    max_frames_per_video = 20

    # Seed rng for reproducibility
    rng = random.Random(926960862)

    # Initialize empty dataset
    dset = kwcoco.CocoDataset()

    # Add some number of videos
    vidid_pool = [
        dset.add_video('vid_{:03d}'.format(vididx))
        for vididx in range(total_videos)
    ]
    vidid_to_frame_pool = {
        vidid: ub.oset(range(max_frames_per_video))
        for vidid in vidid_pool
    }

    # Add some number of frames to the videos in a random order
    for imgidx in range(total_frames):
        vidid = rng.choice(vidid_pool)
        frame_pool = vidid_to_frame_pool[vidid]
        assert frame_pool, 'ran out of frames'
        frame_index = rng.choice(frame_pool)
        frame_pool.remove(frame_index)

        name = 'img_{:03d}'.format(imgidx)
        dset.add_image(video_id=vidid, frame_index=frame_index, name=name)

    # Test that our image ids are always ordered by frame ids
    vidid_to_gids = dset.index.vidid_to_gids
    gids_were_in_order = []
    for vidid, gids in vidid_to_gids.items():
        gids_were_in_order.append(is_sorted(gids))
        frame_idxs = [dset.imgs[gid]['frame_index'] for gid in gids]

        # Note: this check is always valid
        assert is_sorted(frame_idxs), (
            'images in vidid_to_gids must be sorted by frame_index')

    # Note: this check has a chance of failing for other params / seeds
    assert not all(gids_were_in_order), (
        'the probability we randomly have ordered image ids is low, '
        'and 0 when we seed the rng')

    try:
        import sqlalchemy  # NOQA
    except Exception:
        pass
    else:
        # Test that the sql view works too
        sql_dset = dset.view_sql(memory=True)

        vidid_to_gids = dict(sql_dset.index.vidid_to_gids)
        gids_were_in_order = []
        for vidid, gids in vidid_to_gids.items():
            gids_were_in_order.append(is_sorted(gids))
            frame_idxs = [dset.imgs[gid]['frame_index'] for gid in gids]

            # Note: this check is always valid
            assert is_sorted(frame_idxs), (
                'images in vidid_to_gids must be sorted by frame_index')

        # Note: this check has a chance of failing for other params / seeds
        assert not all(gids_were_in_order), (
            'the probability we randomly have ordered image ids is low, '
            'and 0 when we seed the rng')
Exemple #14
0
def eval_detections_cli(**kw):
    """
    CommandLine:
        xdoctest -m ~/code/netharn/netharn/metrics/detect_metrics.py eval_detections_cli
    """
    import scriptconfig as scfg
    import kwcoco

    class EvalDetectionCLI(scfg.Config):
        default = {
            'true': scfg.Path(None, help='true coco dataset'),
            'pred': scfg.Path(None, help='predicted coco dataset'),
            'out_dpath': scfg.Path('./out', help='output directory')
        }
        pass

    config = EvalDetectionCLI()
    cmdline = kw.pop('cmdline', True)
    config.load(kw, cmdline=cmdline)

    true_coco = kwcoco.CocoDataset(config['true'])
    pred_coco = kwcoco.CocoDataset(config['pred'])

    from netharn.metrics.detect_metrics import DetectionMetrics
    dmet = DetectionMetrics.from_coco(true_coco, pred_coco)

    voc_info = dmet.score_voc()

    cls_info = voc_info['perclass'][0]
    tp = cls_info['tp']
    fp = cls_info['fp']
    fn = cls_info['fn']

    tpr = cls_info['tpr']
    ppv = cls_info['ppv']
    fp = cls_info['fp']

    # Compute the MCC as TN->inf
    thresh = cls_info['thresholds']

    # https://erotemic.wordpress.com/2019/10/23/closed-form-of-the-mcc-when-tn-inf/
    mcc_lim = tp / (np.sqrt(fn + tp) * np.sqrt(fp + tp))
    f1 = 2 * (ppv * tpr) / (ppv + tpr)

    draw = False
    if draw:

        mcc_idx = mcc_lim.argmax()
        f1_idx = f1.argmax()

        import kwplot
        plt = kwplot.autoplt()

        kwplot.multi_plot(
            xdata=thresh,
            ydata=mcc_lim,
            xlabel='threshold',
            ylabel='mcc*',
            fnum=1,
            pnum=(1, 4, 1),
            title='MCC*',
            color=['blue'],
        )
        plt.plot(thresh[mcc_idx], mcc_lim[mcc_idx], 'r*', markersize=20)
        plt.plot(thresh[f1_idx], mcc_lim[f1_idx], 'k*', markersize=20)

        kwplot.multi_plot(
            xdata=fp,
            ydata=tpr,
            xlabel='fp (fa)',
            ylabel='tpr (pd)',
            fnum=1,
            pnum=(1, 4, 2),
            title='ROC',
            color=['blue'],
        )
        plt.plot(fp[mcc_idx], tpr[mcc_idx], 'r*', markersize=20)
        plt.plot(fp[f1_idx], tpr[f1_idx], 'k*', markersize=20)

        kwplot.multi_plot(
            xdata=tpr,
            ydata=ppv,
            xlabel='tpr (recall)',
            ylabel='ppv (precision)',
            fnum=1,
            pnum=(1, 4, 3),
            title='PR',
            color=['blue'],
        )
        plt.plot(tpr[mcc_idx], ppv[mcc_idx], 'r*', markersize=20)
        plt.plot(tpr[f1_idx], ppv[f1_idx], 'k*', markersize=20)

        kwplot.multi_plot(
            xdata=thresh,
            ydata=f1,
            xlabel='threshold',
            ylabel='f1',
            fnum=1,
            pnum=(1, 4, 4),
            title='F1',
            color=['blue'],
        )
        plt.plot(thresh[mcc_idx], f1[mcc_idx], 'r*', markersize=20)
        plt.plot(thresh[f1_idx], f1[f1_idx], 'k*', markersize=20)
Exemple #15
0
def convert_voc_to_coco(dpath=None):
    # TODO: convert segmentation information

    classes = [
        'aeroplane', 'bicycle', 'bird', 'boat', 'bottle', 'bus', 'car', 'cat',
        'chair', 'cow', 'diningtable', 'dog', 'horse', 'motorbike', 'person',
        'pottedplant', 'sheep', 'sofa', 'train', 'tvmonitor'
    ]
    devkit_dpath = ensure_voc_data(dpath=dpath)
    root = out_dpath = dirname(devkit_dpath)

    dsets = []

    d = _convert_voc_split(devkit_dpath, classes, 'train', 2012, root)
    dsets.append(d)

    d = _convert_voc_split(devkit_dpath, classes, 'train', 2007, root)
    dsets.append(d)

    d = _convert_voc_split(devkit_dpath, classes, 'val', 2012, root)
    dsets.append(d)

    d = _convert_voc_split(devkit_dpath, classes, 'val', 2007, root)
    dsets.append(d)

    d = _convert_voc_split(devkit_dpath, classes, 'test', 2007, root)
    dsets.append(d)

    if 0:
        import xdev
        xdev.view_directory(out_dpath)

    def reroot_imgs(dset, root):
        for img in dset.imgs.values():
            img['file_name'] = relpath(img['file_name'], root)

    import kwcoco
    t1 = kwcoco.CocoDataset(join(out_dpath, 'voc-train-2007.mscoco.json'))
    t2 = kwcoco.CocoDataset(join(out_dpath, 'voc-train-2012.mscoco.json'))

    v1 = kwcoco.CocoDataset(join(out_dpath, 'voc-val-2007.mscoco.json'))
    v2 = kwcoco.CocoDataset(join(out_dpath, 'voc-val-2012.mscoco.json'))

    t = kwcoco.CocoDataset.union(t1, t2)
    t.tag = 'voc-train'
    t.fpath = join(root, t.tag + '.mscoco.json')

    v = kwcoco.CocoDataset.union(v1, v2)
    v.tag = 'voc-val'
    v.fpath = join(root, v.tag + '.mscoco.json')

    tv = kwcoco.CocoDataset.union(t1, t2, v1, v2)
    tv.tag = 'voc-trainval'
    tv.fpath = join(root, tv.tag + '.mscoco.json')

    print('t.fpath = {!r}'.format(t.fpath))
    t.dump(t.fpath, newlines=True)
    print('v.fpath = {!r}'.format(v.fpath))
    v.dump(v.fpath, newlines=True)
    print('tv.fpath = {!r}'.format(tv.fpath))
    tv.dump(tv.fpath, newlines=True)
    if 0:
        tv.img_root = root
        import kwplot
        kwplot.autompl()
        tv.show_image(2)

    dsets = {
        'train': t,
        'vali': v,
        'trainval': tv,
    }
    return dsets
Exemple #16
0
def draw_true_and_pred_boxes(true_fpath, pred_fpath, gid, viz_fpath):
    """
    How do you generally visualize gt and predicted bounding boxes together?

    Example:
        >>> import kwcoco
        >>> import ubelt as ub
        >>> from os.path import join
        >>> from kwcoco.demo.perterb import perterb_coco
        >>> # Create a working directory
        >>> dpath = ub.ensure_app_cache_dir('kwcoco/examples/draw_true_and_pred_boxes')
        >>> # Lets setup some dummy true data
        >>> true_dset = kwcoco.CocoDataset.demo('shapes2')
        >>> true_dset.fpath = join(dpath, 'true_dset.kwcoco.json')
        >>> true_dset.dump(true_dset.fpath, newlines=True)
        >>> # Lets setup some dummy predicted data
        >>> pred_dset = perterb_coco(true_dset, box_noise=100, rng=421)
        >>> pred_dset.fpath = join(dpath, 'pred_dset.kwcoco.json')
        >>> pred_dset.dump(pred_dset.fpath, newlines=True)
        >>> #
        >>> # We now have our true and predicted data, lets visualize
        >>> true_fpath = true_dset.fpath
        >>> pred_fpath = pred_dset.fpath
        >>> print('dpath = {!r}'.format(dpath))
        >>> print('true_fpath = {!r}'.format(true_fpath))
        >>> print('pred_fpath = {!r}'.format(pred_fpath))
        >>> # Lets choose an image id to visualize and a path to write to
        >>> gid = 1
        >>> viz_fpath = join(dpath, 'viz_{}.jpg'.format(gid))
        >>> # The answer to the question is in the logic of this function
        >>> draw_true_and_pred_boxes(true_fpath, pred_fpath, gid, viz_fpath)
    """
    import kwimage
    import kwcoco
    true_dset = kwcoco.CocoDataset(true_fpath)
    pred_dset = kwcoco.CocoDataset(pred_fpath)

    if __debug__:
        # I hope your image ids are aligned between datasets
        true_img = true_dset.imgs[gid]
        pred_img = pred_dset.imgs[gid]
        assert true_img['file_name'] == pred_img['file_name']

    # Get the true/pred annotation dictionaries from the chosen image
    true_aids = true_dset.index.gid_to_aids[gid]
    pred_aids = pred_dset.index.gid_to_aids[gid]
    true_anns = [true_dset.index.anns[aid] for aid in true_aids]
    pred_anns = [pred_dset.index.anns[aid] for aid in pred_aids]

    # Create Detections from the coco annotation dictionaries
    true_dets = kwimage.Detections.from_coco_annots(true_anns, dset=true_dset)
    pred_dets = kwimage.Detections.from_coco_annots(pred_anns, dset=pred_dset)

    print('true_dets.boxes = {!r}'.format(true_dets.boxes))
    print('pred_dets.boxes = {!r}'.format(pred_dets.boxes))

    # Load the image
    fpath = true_dset.get_image_fpath(gid)
    canvas = kwimage.imread(fpath)

    # Use kwimage.Detections draw_on method to modify the image
    canvas = true_dets.draw_on(canvas, color='green')
    canvas = pred_dets.draw_on(canvas, color='blue')

    kwimage.imwrite(viz_fpath, canvas)
Exemple #17
0
def _convert_voc_split(devkit_dpath, classes, split, year, root):
    """
    split, year = 'train', 2012
    split, year = 'train', 2007
    """
    import kwcoco
    import xml.etree.ElementTree as ET
    dset = kwcoco.CocoDataset(tag='voc-{}-{}'.format(split, year))

    for catname in classes:
        dset.add_category(catname)

    gpaths, apaths = _read_split_paths(devkit_dpath, split, year)

    KNOWN = {
        'object', 'segmented', 'size', 'source', 'filename', 'folder', 'owner'
    }

    for gpath, apath in ub.ProgIter(zip(gpaths, apaths)):
        tree = ET.parse(apath)
        troot = tree.getroot()

        top_level = list(troot)

        unknown = {e.tag for e in top_level} - KNOWN
        assert not unknown

        img = {
            'file_name': relpath(gpath, root),
            'width': int(tree.find('size').find('width').text),
            'height': int(tree.find('size').find('height').text),
            'depth': int(tree.find('size').find('depth').text),
            'segmented': int(tree.find('segmented').text),
            'source':
            {elem.tag: elem.text
             for elem in list(tree.find('source'))},
        }

        assert img.pop('depth') == 3

        owner = tree.find('owner')
        if owner is not None:
            img['owner'] = {elem.tag: elem.text for elem in list(owner)}

        gid = dset.add_image(**img)

        for obj in tree.findall('object'):
            bbox = obj.find('bndbox')
            # Make pixel indexes 0-based
            x1 = float(bbox.find('xmin').text) - 1
            y1 = float(bbox.find('ymin').text) - 1
            x2 = float(bbox.find('xmax').text) - 1
            y2 = float(bbox.find('ymax').text) - 1

            diffc = obj.find('difficult')
            difficult = 0 if diffc is None else int(diffc.text)

            catname = obj.find('name').text.lower().strip()
            w = x2 - x1
            h = y2 - y1
            ann = {
                'bbox': [x1, y1, w, h],
                'category_name': catname,
                'difficult': difficult,
                'weight': 1.0 - difficult,
            }
            cid = dset._alias_to_cat(ann.pop('category_name'))['id']
            dset.add_annotation(image_id=gid, category_id=cid, **ann)

    dset.dump(join(root, dset.tag + '.mscoco.json'), newlines=True)
    return dset
Exemple #18
0
def main(**kw):
    """
    CommandLine:
        python $HOME/code/bioharn/dev/kwcoco_to_viame_csv.py \
            --src /data/public/Aerial/US_ALASKA_MML_SEALION/2007/sealions_2007_v9.kwcoco.json \
            --dst /data/public/Aerial/US_ALASKA_MML_SEALION/2007/sealions_2007_v9.viame.csv
    """
    config = ConvertConfig(default=kw, cmdline=True)

    import kwcoco
    import kwimage
    import ubelt as ub
    coco_dset = kwcoco.CocoDataset(config['src'])

    csv_rows = []
    for gid, img in ub.ProgIter(coco_dset.imgs.items(), total=coco_dset.n_images):
        gname = img['file_name']
        aids = coco_dset.gid_to_aids[gid]

        frame_index = img.get('frame_index', 0)
        # vidid = img.get('video_id', None)

        for aid in aids:
            ann = coco_dset.anns[aid]
            cat = coco_dset.cats[ann['category_id']]
            catname = cat['name']

            # just use annotation id if no tracks
            tid = ann.get('track_id', aid)
            # tracked_aids = tid_to_aids.get(tid, [aid])
            # track_len = len(tracked_aids)

            tl_x, tl_y, br_x, br_y = kwimage.Boxes([ann['bbox']], 'xywh').toformat('tlbr').data[0].tolist()

            score = ann.get('score', 1)

            row = [
                 tid,             # 1 - Detection or Track Unique ID
                 gname,           # 2 - Video or Image String Identifier
                 frame_index,     # 3 - Unique Frame Integer Identifier
                 round(tl_x, 3),  # 4 - TL-x (top left of the image is the origin: 0,0
                 round(tl_y, 3),  # 5 - TL-y
                 round(br_x, 3),  # 6 - BR-x
                 round(br_y, 3),  # 7 - BR-y
                 score,           # 8 - Auxiliary Confidence (how likely is this actually an object)
                 -1,              # 9 - Target Length
                 catname,         # 10+ - category name
                 score,           # 11+ - category score
            ]

            # Optional fields
            for kp in ann.get('keypoints', []):
                if 'keypoint_category_id' in kp:
                    cname = coco_dset._resolve_to_kpcat(kp['keypoint_category_id'])['name']
                elif 'category_name' in kp:
                    cname = kp['category_name']
                elif 'category' in kp:
                    cname = kp['category']
                else:
                    raise Exception(str(kp))
                kp_x, kp_y = kp['xy']
                row.append('(kp) {} {} {}'.format(
                    cname, round(kp_x, 3), round(kp_y, 3)))

            note_fields = [
                'box_source',
                'changelog',
                'color',
            ]
            for note_key in note_fields:
                if note_key in ann:
                    row.append('(note) {}: {}'.format(note_key, repr(ann[note_key]).replace(',', '<comma>')))

            row = list(map(str, row))
            for item in row:
                if ',' in row:
                    print('BAD row = {!r}'.format(row))
                    raise Exception('comma is in a row field')

            row_str = ','.join(row)
            csv_rows.append(row_str)

    csv_text = '\n'.join(csv_rows)
    dst_fpath = config['dst']
    print('dst_fpath = {!r}'.format(dst_fpath))
    with open(dst_fpath, 'w') as file:
        file.write(csv_text)