def test_cache_works(self): with TestDir() as test_dir: image = np.ones((100, 100, 3), dtype=np.uint8) image_path = osp.join(test_dir, 'image.jpg') save_image(image_path, image) caching_loader = lazy_image(image_path, cache=True) self.assertTrue(caching_loader() is caching_loader()) non_caching_loader = lazy_image(image_path, cache=False) self.assertFalse(non_caching_loader() is non_caching_loader())
def test_cache_works(self): with TestDir() as test_dir: image = np.ones((100, 100, 3), dtype=np.uint8) image = Image.fromarray(image).convert('RGB') image_path = osp.join(test_dir.path, 'image.jpg') image.save(image_path) caching_loader = lazy_image(image_path, cache=None) self.assertTrue(caching_loader() is caching_loader()) non_caching_loader = lazy_image(image_path, cache=False) self.assertFalse(non_caching_loader() is non_caching_loader())
def __init__(self, data: Union[bytes, Callable[[str], bytes], None] = None, *, path: Optional[str] = None, ext: Optional[str] = None, size: Optional[Tuple[int, int]] = None): if not isinstance(data, bytes): assert path or callable(data), "Image can not be empty" assert data is None or callable(data) if path and osp.isfile(path) or data: data = lazy_image(path, loader=data) self._bytes_data = data if ext is None and path is None and isinstance(data, bytes): ext = self._guess_ext(data) super().__init__(path=path, ext=ext, size=size, data=lambda _: decode_image(self.get_bytes())) if data is None: # We don't expect decoder to produce images from nothing, # otherwise using this class makes no sense. We undefine # data to avoid using default image loader for loading binaries # from the path, when no data is provided. self._data = None
def _load_items(self, subset): labels = self._categories.setdefault(AnnotationType.label, LabelCategories()) path = osp.join(self._path, subset) images = [i for i in find_images(path, recursive=True)] for image_path in sorted(images): item_id = osp.splitext(osp.relpath(image_path, path))[0] if Ade20k2017Path.MASK_PATTERN.fullmatch(osp.basename(item_id)): continue item_annotations = [] item_info = self._load_item_info(image_path) for item in item_info: label_idx = labels.find(item['label_name'])[0] if label_idx is None: labels.add(item['label_name']) mask_path = osp.splitext(image_path)[0] + '_seg.png' if not osp.isfile(mask_path): log.warning("Can't find mask for image: %s" % image_path) part_level = 0 max_part_level = max([p['part_level'] for p in item_info]) for part_level in range(max_part_level + 1): if not osp.exists(mask_path): log.warning('Can`t find part level %s mask for %s' \ % (part_level, image_path)) continue mask = lazy_image(mask_path, loader=self._load_instance_mask) mask = CompiledMask(instance_mask=mask) for v in item_info: if v['part_level'] != part_level: continue label_id = labels.find(v['label_name'])[0] instance_id = v['id'] attributes = {k: True for k in v['attributes']} item_annotations.append( Mask(label=label_id, image=mask.lazy_extract(instance_id), id=instance_id, attributes=attributes, z_order=part_level, group=instance_id)) mask_path = osp.splitext(image_path)[0] \ + '_parts_%s.png' % (part_level + 1) self._items.append( DatasetItem(item_id, subset=subset, image=image_path, annotations=item_annotations))
def __init__(self, data: Union[np.ndarray, Callable[[str], np.ndarray], None] = None, *, path: Optional[str] = None, ext: Optional[str] = None, size: Optional[Tuple[int, int]] = None) -> None: """ Creates an image. Any combination of the `data`, `path` and `size` is possible, but at least one of these arguments must be provided. The `ext` parameter cannot be used as a single argument for construction. Args: data - Image pixels or a function to retrieve them. The expected image shape is (H, W [, C]). If a function is provided, it must accept image path as the first argument. path - Image path ext - Image extension. Cannot be used together with `path`. It can be used for saving with a custom extension - in that case, the image need to have the `data` and `ext` fields defined. size - A pair (H, W), which represents image size. """ if size is not None: assert len(size) == 2 and 0 < size[0] and 0 < size[1], \ f"Invalid image size info '{size}'" size = tuple(map(int, size)) self._size = size # (H, W) if path is None: path = '' elif path: path = path.replace('\\', '/') self._path = path if ext: assert not path, "Can't specify both 'path' and 'ext' for image" if not ext.startswith('.'): ext = '.' + ext ext = ext.lower() else: ext = None self._ext = ext if not isinstance(data, np.ndarray): assert path or callable(data) or size, "Image can not be empty" assert data is None or callable(data), \ f"Image data has unexpected type '{type(data)}'" if data or path and osp.isfile(path): data = lazy_image(path, loader=data) self._data = data
def _get(self, item, subset_name): image = None image_path = osp.join(self._path, VocPath.IMAGES_DIR, item + VocPath.IMAGE_EXT) if osp.isfile(image_path): image = lazy_image(image_path) annotations = self._get_annotations(item, subset_name) return DatasetItem(annotations=annotations, id=item, subset=subset_name, image=image)
def test_cache_fifo_displacement(self): capacity = 2 cache = ImageCache(capacity) loaders = [lazy_image(None, loader=lambda p: object(), cache=cache) for _ in range(capacity + 1)] first_request = [loader() for loader in loaders[1 : ]] loaders[0]() # pop something from the cache second_request = [loader() for loader in loaders[2 : ]] second_request.insert(0, loaders[1]()) matches = sum([a is b for a, b in zip(first_request, second_request)]) self.assertEqual(matches, len(first_request) - 1)
def _get(self, index, subset_name): item = self._annotations[subset_name]['items'][index] item_id = item.get('id') image_path = osp.join(self._path, DatumaroPath.IMAGES_DIR, item_id + DatumaroPath.IMAGE_EXT) image = None if osp.isfile(image_path): image = lazy_image(image_path) annotations = self._load_annotations(item) return DatasetItem(id=item_id, subset=subset_name, annotations=annotations, image=image)
def __init__(self, url): super().__init__() items = [] for (dirpath, _, filenames) in os.walk(url): for name in filenames: path = osp.join(dirpath, name) if self._is_image(path): item_id = Task.get_image_frame(path) item = datumaro.DatasetItem(id=item_id, image=lazy_image(path)) items.append((item.id, item)) items = sorted(items, key=lambda e: e[0]) items = OrderedDict(items) self._items = items self._subsets = None
def _load_panoptic_ann(self, ann, parsed_annotations=None): if parsed_annotations is None: parsed_annotations = [] # For the panoptic task, each annotation struct is a per-image # annotation rather than a per-object annotation. mask_path = osp.join(self._mask_dir, ann['file_name']) mask = lazy_image(mask_path, loader=self._load_pan_mask) mask = CompiledMask(instance_mask=mask) for segm_info in ann['segments_info']: cat_id = self._get_label_id(segm_info) segm_id = segm_info['id'] attributes = {'is_crowd': bool(segm_info['iscrowd'])} parsed_annotations.append( Mask(image=mask.lazy_extract(segm_id), label=cat_id, id=segm_id, group=segm_id, attributes=attributes)) return parsed_annotations
def _load_panoptic_items(self, config): items = {} images_info = { img['id']: { 'path': osp.join(self._images_dir, img['file_name']), 'height': img.get('height'), 'width': img.get('width') } for img in config['images'] } for item_ann in config['annotations']: item_id = item_ann['image_id'] image = None if images_info.get(item_id): image = Image( path=images_info[item_id]['path'], size=self._get_image_size(images_info[item_id]) ) annotations = [] mask_path = osp.join(self._annotations_dir, MapillaryVistasPath.PANOPTIC_DIR, item_ann['file_name']) mask = lazy_image(mask_path, loader=self._load_pan_mask) mask = CompiledMask(instance_mask=mask) for segment_info in item_ann['segments_info']: cat_id = self._get_label_id(segment_info) segment_id = segment_info['id'] attributes = { 'is_crowd': bool(segment_info['iscrowd']) } annotations.append(Mask(image=mask.lazy_extract(segment_id), label=cat_id, id=segment_id, group=segment_id, attributes=attributes)) items[item_id] = DatasetItem(id=item_id, subset=self._subset, annotations=annotations, image=image) self._load_polygons(items) return items.values()
def _parse_tfrecord_file(cls, filepath, subset, images_dir): dataset = tf.data.TFRecordDataset(filepath) features = { 'image/filename': tf.io.FixedLenFeature([], tf.string), 'image/source_id': tf.io.FixedLenFeature([], tf.string), 'image/height': tf.io.FixedLenFeature([], tf.int64), 'image/width': tf.io.FixedLenFeature([], tf.int64), 'image/encoded': tf.io.FixedLenFeature([], tf.string), 'image/format': tf.io.FixedLenFeature([], tf.string), # use varlen to avoid errors when this field is missing 'image/key/sha256': tf.io.VarLenFeature(tf.string), # Object boxes and classes. 'image/object/bbox/xmin': tf.io.VarLenFeature(tf.float32), 'image/object/bbox/xmax': tf.io.VarLenFeature(tf.float32), 'image/object/bbox/ymin': tf.io.VarLenFeature(tf.float32), 'image/object/bbox/ymax': tf.io.VarLenFeature(tf.float32), 'image/object/class/label': tf.io.VarLenFeature(tf.int64), 'image/object/class/text': tf.io.VarLenFeature(tf.string), 'image/object/mask': tf.io.VarLenFeature(tf.string), } dataset_labels = OrderedDict() labelmap_path = osp.join(osp.dirname(filepath), DetectionApiPath.LABELMAP_FILE) if osp.exists(labelmap_path): with open(labelmap_path, 'r', encoding='utf-8') as f: labelmap_text = f.read() dataset_labels.update({ label: id - 1 for label, id in cls._parse_labelmap(labelmap_text).items() }) dataset_items = [] for record in dataset: parsed_record = tf.io.parse_single_example(record, features) frame_id = parsed_record['image/source_id'].numpy().decode('utf-8') frame_filename = \ parsed_record['image/filename'].numpy().decode('utf-8') frame_height = tf.cast(parsed_record['image/height'], tf.int64).numpy().item() frame_width = tf.cast(parsed_record['image/width'], tf.int64).numpy().item() frame_image = parsed_record['image/encoded'].numpy() xmins = tf.sparse.to_dense( parsed_record['image/object/bbox/xmin']).numpy() ymins = tf.sparse.to_dense( parsed_record['image/object/bbox/ymin']).numpy() xmaxs = tf.sparse.to_dense( parsed_record['image/object/bbox/xmax']).numpy() ymaxs = tf.sparse.to_dense( parsed_record['image/object/bbox/ymax']).numpy() label_ids = tf.sparse.to_dense( parsed_record['image/object/class/label']).numpy() labels = tf.sparse.to_dense( parsed_record['image/object/class/text'], default_value=b'').numpy() masks = tf.sparse.to_dense(parsed_record['image/object/mask'], default_value=b'').numpy() for label, label_id in zip(labels, label_ids): label = label.decode('utf-8') if not label: continue if label_id <= 0: continue if label in dataset_labels: continue dataset_labels[label] = label_id - 1 item_id = osp.splitext(frame_filename)[0] annotations = [] for shape_id, shape in enumerate( np.dstack((labels, xmins, ymins, xmaxs, ymaxs))[0]): label = shape[0].decode('utf-8') mask = None if len(masks) != 0: mask = masks[shape_id] if mask is not None: if isinstance(mask, bytes): mask = lazy_image(mask, decode_image) annotations.append( Mask(image=mask, label=dataset_labels.get(label))) else: x = clamp(shape[1] * frame_width, 0, frame_width) y = clamp(shape[2] * frame_height, 0, frame_height) w = clamp(shape[3] * frame_width, 0, frame_width) - x h = clamp(shape[4] * frame_height, 0, frame_height) - y annotations.append( Bbox(x, y, w, h, label=dataset_labels.get(label))) image_size = None if frame_height and frame_width: image_size = (frame_height, frame_width) image_params = {} if frame_image: image_params['data'] = frame_image if frame_filename: image_params['path'] = osp.join(images_dir, frame_filename) image = None if image_params: image = ByteImage(**image_params, size=image_size) dataset_items.append( DatasetItem(id=item_id, subset=subset, image=image, annotations=annotations, attributes={'source_id': frame_id})) return dataset_items, dataset_labels
def test_global_cache_is_accessible(self): loader = lazy_image(None, loader=lambda p: object()) self.assertTrue(loader() is loader()) self.assertEqual(ImageCache.get_instance().size(), 1)
def _load_annotations(self, item): parsed = item['annotations'] loaded = [] for ann in parsed: ann_id = ann.get('id') ann_type = AnnotationType[ann['type']] attributes = ann.get('attributes') group = ann.get('group') if ann_type == AnnotationType.label: label_id = ann.get('label_id') loaded.append(LabelObject(label=label_id, id=ann_id, attributes=attributes, group=group)) elif ann_type == AnnotationType.mask: label_id = ann.get('label_id') mask_id = str(ann.get('mask_id')) mask_path = osp.join(self._path, DatumaroPath.ANNOTATIONS_DIR, DatumaroPath.MASKS_DIR, mask_id + DatumaroPath.MASK_EXT) mask = None if osp.isfile(mask_path): mask_cat = self._categories.get(AnnotationType.mask) if mask_cat is not None: mask = lazy_mask(mask_path, mask_cat.inverse_colormap) else: mask = lazy_image(mask_path) loaded.append(MaskObject(label=label_id, image=mask, id=ann_id, attributes=attributes, group=group)) elif ann_type == AnnotationType.polyline: label_id = ann.get('label_id') points = ann.get('points') loaded.append(PolyLineObject(points, label=label_id, id=ann_id, attributes=attributes, group=group)) elif ann_type == AnnotationType.polygon: label_id = ann.get('label_id') points = ann.get('points') loaded.append(PolygonObject(points, label=label_id, id=ann_id, attributes=attributes, group=group)) elif ann_type == AnnotationType.bbox: label_id = ann.get('label_id') x, y, w, h = ann.get('bbox') loaded.append(BboxObject(x, y, w, h, label=label_id, id=ann_id, attributes=attributes, group=group)) elif ann_type == AnnotationType.points: label_id = ann.get('label_id') points = ann.get('points') loaded.append(PointsObject(points, label=label_id, id=ann_id, attributes=attributes, group=group)) elif ann_type == AnnotationType.caption: caption = ann.get('caption') loaded.append(CaptionObject(caption, id=ann_id, attributes=attributes, group=group)) else: raise NotImplementedError() return loaded
def _load_items(self, subset): labels = self._categories.setdefault(AnnotationType.label, LabelCategories()) path = osp.join(self._path, subset) images = [i for i in find_images(path, recursive=True)] for image_path in sorted(images): item_id = osp.splitext(osp.relpath(image_path, path))[0] if Ade20k2020Path.MASK_PATTERN.fullmatch(osp.basename(item_id)): continue item_annotations = [] item_info = self._load_item_info(image_path) for item in item_info: label_idx = labels.find(item['label_name'])[0] if label_idx is None: labels.add(item['label_name']) mask_path = osp.splitext(image_path)[0] + '_seg.png' max_part_level = max([p['part_level'] for p in item_info]) for part_level in range(max_part_level + 1): if not osp.exists(mask_path): log.warning('Can`t find part level %s mask for %s' \ % (part_level, image_path)) continue mask = lazy_image(mask_path, loader=self._load_class_mask) mask = CompiledMask(instance_mask=mask) classes = {(v['class_idx'], v['label_name']) for v in item_info if v['part_level'] == part_level} for class_idx, label_name in classes: label_id = labels.find(label_name)[0] item_annotations.append( Mask(label=label_id, id=class_idx, image=mask.lazy_extract(class_idx), group=class_idx, z_order=part_level)) mask_path = osp.splitext(image_path)[0] \ + '_parts_%s.png' % (part_level + 1) for item in item_info: instance_path = osp.join(osp.dirname(image_path), item['instance_mask']) if not osp.isfile(instance_path): log.warning('Can`t find instance mask: %s' % instance_path) continue mask = lazy_image(instance_path, loader=self._load_instance_mask) mask = CompiledMask(instance_mask=mask) label_id = labels.find(item['label_name'])[0] instance_id = item['id'] attributes = {k: True for k in item['attributes']} polygon_points = item['polygon_points'] item_annotations.append( Mask(label=label_id, image=mask.lazy_extract(1), id=instance_id, attributes=attributes, z_order=item['part_level'], group=instance_id)) if (len(item['polygon_points']) % 2 == 0 \ and 3 <= len(item['polygon_points']) // 2): item_annotations.append( Polygon(polygon_points, label=label_id, attributes=attributes, id=instance_id, z_order=item['part_level'], group=instance_id)) self._items.append( DatasetItem(item_id, subset=subset, image=image_path, annotations=item_annotations))
def _make_image_loader(self, item_id): return lazy_image(item_id, lambda item_id: self._image_loader(item_id, self))
def _load_masks(self, items_by_id, normalized_coords): label_categories = self._categories[AnnotationType.label] for mask_path in self._glob_annotations( '*' + OpenImagesPath.MASK_DESCRIPTION_FILE_SUFFIX ): with self._open_csv_annotation(mask_path) as mask_reader: for mask_description in mask_reader: mask_path = mask_description['MaskPath'] if _RE_INVALID_PATH_COMPONENT.fullmatch(mask_path): raise UnsupportedMaskPathError(item_id=item.id, mask_path=mask_path) image_id = mask_description['ImageID'] item = items_by_id.get(image_id) if item is None: item = items_by_id.setdefault( image_id, self._add_item(image_id, self._get_subset_name(mask_path)) ) label_name = mask_description['LabelName'] label_index, _ = label_categories.find(label_name) if label_index is None: raise UndefinedLabel( item_id=item.id, subset=item.subset, label_name=label_name, severity=Severity.error) if item.has_image and item.image.has_size: image_size = item.image.size elif self._image_meta.get(item.id): image_size = self._image_meta.get(item.id) else: log.warning( "Can't decode mask for item '%s' due to missing image file", item.id) continue attributes = {} # The box IDs are rather useless, because the _box_ annotations # don't include them, so they cannot be used to match masks to boxes. # However, it is still desirable to record them, because they are # included in the mask file names, so in order to save each mask to the # file it was loaded from when saving in-places, we need to know # the original box ID. box_id = mask_description['BoxID'] if _RE_INVALID_PATH_COMPONENT.fullmatch(box_id): raise UnsupportedBoxIdError( item_id=item.id, box_id=box_id) attributes['box_id'] = box_id group = 0 box_coord_fields = ('BoxXMin', 'BoxXMax', 'BoxYMin', 'BoxYMax') # The original OID has box coordinates for all masks, but # a dataset converted from another dataset might not. if all(mask_description[f] for f in box_coord_fields): # Try to find the box annotation corresponding to the # current mask. mask_box_coords = np.array([ float(mask_description[field]) for field in box_coord_fields ]) for annotation in item.annotations: if ( annotation.type is AnnotationType.bbox and annotation.label == label_index ): # In the original OID, mask box coordinates are stored # with 6 digit precision, hence the tolerance. if np.allclose( mask_box_coords, normalized_coords[id(annotation)], rtol=0, atol=1e-6, ): group = annotation.group if mask_description['PredictedIoU']: attributes['predicted_iou'] = float(mask_description['PredictedIoU']) item.annotations.append(Mask( image=lazy_image( osp.join( self._dataset_dir, OpenImagesPath.MASKS_DIR, item.subset, mask_path, ), loader=functools.partial( self._load_and_resize_mask, size=image_size), ), label=label_index, attributes=attributes, group=group, ))
def lazy_mask(path, inverse_colormap=None): return lazy_image(path, lambda path: load_mask(path, inverse_colormap))
def lazy_mask(path, inverse_colormap=None): return lazy_image(path, partial(load_mask, inverse_colormap=inverse_colormap))