:py:mod:`kwcoco.coco_image` =========================== .. py:module:: kwcoco.coco_image Module Contents --------------- Classes ~~~~~~~ .. autoapisummary:: kwcoco.coco_image.CocoImage kwcoco.coco_image.CocoAsset Functions ~~~~~~~~~ .. autoapisummary:: kwcoco.coco_image._delay_load_imglike Attributes ~~~~~~~~~~ .. autoapisummary:: kwcoco.coco_image.profile .. py:data:: profile .. py:class:: CocoImage(img, dset=None) Bases: :py:obj:`ubelt.NiceRepr` 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. .. rubric:: Example >>> import kwcoco >>> dset1 = kwcoco.CocoDataset.demo('shapes8') >>> dset2 = kwcoco.CocoDataset.demo('vidshapes8-multispectral') >>> self = CocoImage(dset1.imgs[1], dset1) >>> print('self = {!r}'.format(self)) >>> print('self.channels = {}'.format(ub.repr2(self.channels, nl=1))) >>> self = CocoImage(dset2.imgs[1], dset2) >>> print('self.channels = {}'.format(ub.repr2(self.channels, nl=1))) >>> self.primary_asset() .. py:method:: from_gid(cls, dset, gid) :classmethod: .. py:method:: bundle_dpath(self) :property: .. py:method:: video(self) :property: Helper to grab the video for this image if it exists .. py:method:: detach(self) Removes references to the underlying coco dataset, but keeps special information such that it wont be needed. .. py:method:: __nice__(self) .. rubric:: Example >>> from kwcoco.coco_image import * # NOQA >>> import kwcoco >>> with ub.CaptureStdout() as cap: ... dset = kwcoco.CocoDataset.demo('shapes8') >>> self = CocoImage(dset.dataset['images'][0], dset) >>> print('self = {!r}'.format(self)) >>> dset = kwcoco.CocoDataset.demo() >>> self = CocoImage(dset.dataset['images'][0], dset) >>> print('self = {!r}'.format(self)) .. py:method:: stats(self) .. py:method:: __getitem__(self, key) Proxy getter attribute for underlying `self.img` dictionary .. py:method:: keys(self) Proxy getter attribute for underlying `self.img` dictionary .. py:method:: get(self, key, default=ub.NoParam) Proxy getter attribute for underlying `self.img` dictionary .. py:method:: channels(self) :property: .. py:method:: num_channels(self) :property: .. py:method:: dsize(self) :property: .. py:method:: primary_image_filepath(self, requires=None) .. py:method:: primary_asset(self, requires=None) Compute a "main" image asset. .. rubric:: Notes Uses a heuristic. * 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]*) -- list of attribute that must be non-None to consider an object as the primary one. .. todo:: - [ ] Add in primary heuristics .. rubric:: Example >>> import kwarray >>> from kwcoco.coco_image import * # NOQA >>> rng = kwarray.ensure_rng(0) >>> def random_auxiliary(name, w=None, h=None): >>> return {'file_name': name, 'width': w, 'height': h} >>> self = CocoImage({ >>> 'auxiliary': [ >>> random_auxiliary('1'), >>> random_auxiliary('2'), >>> random_auxiliary('3'), >>> ] >>> }) >>> assert self.primary_asset()['file_name'] == '1' >>> self = CocoImage({ >>> 'auxiliary': [ >>> random_auxiliary('1'), >>> random_auxiliary('2', 3, 3), >>> random_auxiliary('3'), >>> ] >>> }) >>> assert self.primary_asset()['file_name'] == '2' .. py:method:: iter_image_filepaths(self) .. py:method:: iter_asset_objs(self) Iterate through base + auxiliary dicts that have file paths :Yields: *dict* -- an image or auxiliary dictionary .. py:method:: find_asset_obj(self, channels) Find the asset dictionary with the specified channels .. py:method:: add_auxiliary_item(self, file_name=None, channels=None, imdata=None, warp_aux_to_img=None, width=None, height=None, imwrite=False) Adds an auxiliary 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 | 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). * **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*) -- 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*) -- Width of the data in auxiliary space (inferred if unspecified) * **height** (*int*) -- 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. .. todo:: - [ ] Allow imwrite to specify an executor that is used to return a Future so the imwrite call does not block. .. rubric:: Example >>> from kwcoco.coco_image import * # NOQA >>> import kwcoco >>> dset = kwcoco.CocoDataset.demo('vidshapes8-multispectral') >>> coco_img = dset.coco_image(1) >>> imdata = np.random.rand(32, 32, 5) >>> channels = kwcoco.FusedChannelSpec.coerce('Aux:5') >>> coco_img.add_auxiliary_item(imdata=imdata, channels=channels) .. py:method:: delay(self, channels=None, space='image', bundle_dpath=None) Perform a delayed load on the data in this image. 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** (*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. .. todo:: - [X] Currently can only take all or none of the channels from each base-image / auxiliary dict. For instance if the main image is r|g|b you can't just select g|b at the moment. - [X] The order of the channels in the delayed load should match the requested channel order. - [X] TODO: add nans to bands that don't exist or throw an error - [ ] This function could stand to have a better name. Maybe imread with a delayed=True flag? Or maybe just delayed_load? .. rubric:: Example >>> from kwcoco.coco_image import * # NOQA >>> import kwcoco >>> gid = 1 >>> # >>> dset = kwcoco.CocoDataset.demo('vidshapes8-multispectral') >>> self = CocoImage(dset.imgs[gid], dset) >>> delayed = self.delay() >>> print('delayed = {!r}'.format(delayed)) >>> print('delayed.finalize() = {!r}'.format(delayed.finalize())) >>> print('delayed.finalize() = {!r}'.format(delayed.finalize(as_xarray=True))) >>> # >>> dset = kwcoco.CocoDataset.demo('shapes8') >>> delayed = dset.delayed_load(gid) >>> print('delayed = {!r}'.format(delayed)) >>> print('delayed.finalize() = {!r}'.format(delayed.finalize())) >>> print('delayed.finalize() = {!r}'.format(delayed.finalize(as_xarray=True))) >>> crop = delayed.delayed_crop((slice(0, 3), slice(0, 3))) >>> crop.finalize() >>> crop.finalize(as_xarray=True) >>> # TODO: should only select the "red" channel >>> dset = kwcoco.CocoDataset.demo('shapes8') >>> delayed = CocoImage(dset.imgs[gid], dset).delay(channels='r') >>> import kwcoco >>> gid = 1 >>> # >>> dset = kwcoco.CocoDataset.demo('vidshapes8-multispectral') >>> delayed = dset.delayed_load(gid, channels='B1|B2', space='image') >>> print('delayed = {!r}'.format(delayed)) >>> print('delayed.finalize() = {!r}'.format(delayed.finalize(as_xarray=True))) >>> delayed = dset.delayed_load(gid, channels='B1|B2|B11', space='image') >>> print('delayed = {!r}'.format(delayed)) >>> print('delayed.finalize() = {!r}'.format(delayed.finalize(as_xarray=True))) >>> delayed = dset.delayed_load(gid, channels='B8|B1', space='video') >>> print('delayed = {!r}'.format(delayed)) >>> print('delayed.finalize() = {!r}'.format(delayed.finalize(as_xarray=True))) >>> delayed = dset.delayed_load(gid, channels='B8|foo|bar|B1', space='video') >>> print('delayed = {!r}'.format(delayed)) >>> print('delayed.finalize() = {!r}'.format(delayed.finalize(as_xarray=True))) .. rubric:: Example >>> import kwcoco >>> dset = kwcoco.CocoDataset.demo() >>> coco_img = dset.coco_image(1) >>> # Test case where nothing is registered in the dataset >>> delayed = coco_img.delay() >>> final = delayed.finalize() >>> assert final.shape == (512, 512, 3) .. rubric:: Example >>> # Test that delay works when imdata is stored in the image >>> # dictionary itself. >>> from kwcoco.coco_image import * # NOQA >>> import kwcoco >>> 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.delay(channels='B1|Aux:2:4') >>> final = delayed.finalize() .. rubric:: Example >>> # Test delay when loading in auxiliary space >>> from kwcoco.coco_image import * # NOQA >>> import kwcoco >>> 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] >>> aux_delayed = coco_img.delay(stream1, space='auxiliary') >>> img_delayed = coco_img.delay(stream1, space='image') >>> vid_delayed = coco_img.delay(stream1, space='video') >>> # >>> aux_imdata = aux_delayed.finalize() >>> img_imdata = img_delayed.finalize() >>> assert aux_imdata.shape != img_imdata.shape >>> # Cannot load multiple auxiliary items at the same time in >>> # auxiliary space >>> import pytest >>> fused_channels = stream1 | stream2 >>> with pytest.raises(kwcoco.exceptions.CoordinateCompatibilityError): >>> aux_delayed2 = coco_img.delay(fused_channels, space='auxiliary') .. py:method:: valid_region(self, space='image') If this image has a valid polygon, return it in image, or video space .. py:method:: warp_vid_from_img(self) .. py:method:: warp_img_from_vid(self) .. py:method:: _annot_segmentation(self, ann, space='video') .. py:class:: CocoAsset Bases: :py:obj:`object` Represents one 2D image file relative to a parent img. Could be a single asset, or an image with sub-assets, but sub-assets are ignored here. Initially we called these "auxiliary" items, but I think we should change their name to "assets", which better maps with STAC terminology. .. py:method:: __getitem__(self, key) Proxy getter attribute for underlying `self.obj` dictionary .. py:method:: keys(self) Proxy getter attribute for underlying `self.obj` dictionary .. py:method:: get(self, key, default=ub.NoParam) Proxy getter attribute for underlying `self.obj` dictionary .. py:function:: _delay_load_imglike(bundle_dpath, obj)