Unverified Commit b4de1e0a authored by Jonathan Crall's avatar Jonathan Crall
Browse files

Improve speed of delayed load

parent 66da111d
Pipeline #251002 failed with stages
in 20 minutes and 46 seconds
......@@ -11,6 +11,9 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
* Add `.images` to `Videos` 1D object.
* Initial `copy_assets` behavior for kwcoco subset.
### Changed
* Improved speed of repeated calls to FusedChannelSpec.coerce and normalize
## Version 0.2.12 - Released 2021-09-22
......
......@@ -154,13 +154,16 @@ class FusedChannelSpec(BaseChannelSpec):
"""
_alias_lut = {
'rgb': 'r|g|b',
'rgba': 'r|g|b|a',
'dxdy': 'dx|dy',
'fxfy': 'fx|fy',
'rgb': ['r', 'g', 'b'],
'rgba': ['r', 'g', 'b', 'a'],
'dxdy': ['dx', 'dy'],
'fxfy': ['fx', 'fy'],
}
_size_lut = {k: v.count('|') + 1 for k, v in _alias_lut.items()}
# Efficiency memorization of coerced string codes
_memo = {}
_size_lut = {k: len(v) for k, v in _alias_lut.items()}
def __init__(self, parsed, _is_normalized=False):
self.parsed = parsed
......@@ -170,7 +173,7 @@ class FusedChannelSpec(BaseChannelSpec):
def __len__(self):
import warnings
if not self._is_normalized:
warnings.warn(ub.paragraph(
text = ub.paragraph(
'''
Length Definition for unormalized FusedChannelSpec is in flux.
......@@ -179,7 +182,8 @@ class FusedChannelSpec(BaseChannelSpec):
atomic codes. Currently it returns the number "unnormalized"
atomic codes. Normalizing the FusedChannelSpec object or using
"numel" will supress this warning.
'''))
''')
warnings.warn(text)
return len(self.parsed)
def __getitem__(self, index):
......@@ -219,13 +223,20 @@ class FusedChannelSpec(BaseChannelSpec):
>>> FusedChannelSpec.coerce(3)
>>> FusedChannelSpec.coerce(FusedChannelSpec(['a']))
"""
try:
# Efficiency hack
return cls._memo[data]
except KeyError:
pass
if isinstance(data, list):
self = cls(data)
elif isinstance(data, str):
self = cls.parse(data)
cls._memo[data] = self
elif isinstance(data, int):
# we know the number of channels, but not their names
self = cls(['u{}'.format(i) for i in range(data)])
cls._memo[data] = self
elif isinstance(data, cls):
self = data
elif isinstance(data, ChannelSpec):
......@@ -240,7 +251,7 @@ class FusedChannelSpec(BaseChannelSpec):
raise TypeError('unknown type {}'.format(type(data)))
return self
@ub.memoize_method
# @ub.memoize_method
def normalize(self):
"""
Replace aliases with explicit single-band-per-code specs
......@@ -263,9 +274,11 @@ class FusedChannelSpec(BaseChannelSpec):
return self
norm_parsed = []
needed_normalization = False
for v in self.parsed:
if v in self._alias_lut:
norm_parsed.extend(self._alias_lut.get(v).split('|'))
norm_parsed.extend(self._alias_lut.get(v))
needed_normalization = True
else:
if ':' in v:
root, *slice_args = v.split(':')
......@@ -280,11 +293,15 @@ class FusedChannelSpec(BaseChannelSpec):
raise NotImplementedError
for idx in range(start, stop, step):
norm_parsed.append('{}.{}'.format(root, idx))
needed_normalization = True
else:
norm_parsed.append(v)
# self._alias_lut.get(v, v).split('|')
# norm_parsed = list(ub.flatten(
# for v in self.parsed))
if not needed_normalization:
# If we went through the normalized process and we didn't need it
# update ourself so we don't redo the work.
self._is_normalized = True
return self
normed = FusedChannelSpec(norm_parsed, _is_normalized=True)
return normed
......@@ -315,7 +332,7 @@ class FusedChannelSpec(BaseChannelSpec):
size_list = []
for v in self.parsed:
if v in self._alias_lut:
num = self._alias_lut.get(v).count('|') + 1
num = len(self._alias_lut.get(v))
else:
if ':' in v:
root, *slice_args = v.split(':')
......
......@@ -371,38 +371,20 @@ class MixinCocoAccessors(object):
>>> print('delayed = {!r}'.format(delayed))
>>> print('delayed.finalize() = {!r}'.format(delayed.finalize(as_xarray=True)))
"""
from kwcoco.util.util_delayed_poc import DelayedLoad, DelayedChannelConcat
from kwcoco.util.util_delayed_poc import DelayedChannelConcat
from kwimage.transform import Affine
from kwcoco.channel_spec import FusedChannelSpec
bundle_dpath = self.bundle_dpath
requested = channels
if requested is not None:
requested = FusedChannelSpec.coerce(requested)
def _delay_load_imglike(obj):
info = {}
fname = obj.get('file_name', None)
channels_ = obj.get('channels', None)
if channels_ is not None:
channels_ = FusedChannelSpec.coerce(channels_).normalize()
info['channels'] = channels_
width = obj.get('width', None)
height = obj.get('height', None)
if height is not None and width is not None:
info['dsize'] = dsize = (width, height)
else:
info['dsize'] = None
if fname is not None:
info['fpath'] = fpath = join(bundle_dpath, fname)
info['chan'] = DelayedLoad(fpath, channels=channels_, dsize=dsize)
return info
img = self.index.imgs[gid]
# obj = img
info = img_info = _delay_load_imglike(img)
chan_list = []
# Get info about the primary image and check if its channels are
# requested (if it even has any)
info = img_info = self._delay_load_imglike(img)
if info.get('chan', None) is not None:
include_flag = requested is None
if not include_flag:
......@@ -412,15 +394,14 @@ class MixinCocoAccessors(object):
chan_list.append(info.get('chan', None))
for aux in img.get('auxiliary', []):
info = _delay_load_imglike(aux)
aux_to_img = Affine.coerce(aux.get('warp_aux_to_img', None))
chan = info['chan']
info = self._delay_load_imglike(aux)
include_flag = requested is None
if not include_flag:
if requested.intersection(info['channels']):
include_flag = True
if include_flag:
aux_to_img = Affine.coerce(aux.get('warp_aux_to_img', None))
chan = info['chan']()
chan = chan.delayed_warp(
aux_to_img, dsize=img_info['dsize'])
chan_list.append(chan)
......@@ -454,6 +435,28 @@ class MixinCocoAccessors(object):
return delayed
def _delay_load_imglike(self, obj):
from kwcoco.util.util_delayed_poc import DelayedLoad
from kwcoco.channel_spec import FusedChannelSpec
info = {}
fname = obj.get('file_name', None)
channels_ = obj.get('channels', None)
if channels_ is not None:
channels_ = FusedChannelSpec.coerce(channels_)
channels_ = channels_.normalize()
info['channels'] = channels_
width = obj.get('width', None)
height = obj.get('height', None)
if height is not None and width is not None:
info['dsize'] = dsize = (width, height)
else:
info['dsize'] = None
if fname is not None:
bundle_dpath = self.bundle_dpath
info['fpath'] = fpath = join(bundle_dpath, fname)
info['chan'] = lambda: DelayedLoad(fpath, channels=channels_, dsize=dsize)
return info
def load_image(self, gid_or_img, channels=None):
"""
Reads an image from disk and
......
......@@ -270,6 +270,7 @@ class DelayedImageOperation(DelayedVisionOperation):
Operations that pertain only to images
"""
@profile
def delayed_crop(self, region_slices):
"""
Create a new delayed image that performs a crop in the transformed
......@@ -442,6 +443,7 @@ class DelayedIdentity(DelayedImageOperation):
# Hack
yield DelayedWarp(self, Affine(None), dsize=self.dsize)
@profile
def finalize(self):
final = self.sub_data
final = kwarray.atleast_nd(final, 3, front=False)
......@@ -499,6 +501,7 @@ class DelayedNans(DelayedImageOperation):
# hack
yield DelayedWarp(self, Affine(None), dsize=self.dsize)
@profile
def finalize(self, **kwargs):
if 'dsize' in kwargs:
shape = tuple(kwargs['dsize'])[::-1] + (self.num_bands,)
......@@ -676,6 +679,7 @@ class DelayedLoad(DelayedImageOperation):
def fpath(self):
return self.meta.get('fpath', None)
@profile
def finalize(self, **kwargs):
final = self.cache.get('final', None)
if final is None:
......@@ -878,6 +882,7 @@ class DelayedLoad(DelayedImageOperation):
return new
@ub.memoize
def have_gdal():
try:
from osgeo import gdal
......@@ -1005,6 +1010,7 @@ class LazyGDalFrameFile(ub.NiceRepr):
from os.path import basename
return '.../' + basename(self.fpath)
@profile
def __getitem__(self, index):
"""
References:
......@@ -1226,6 +1232,7 @@ class DelayedFrameConcat(DelayedVideoOperation):
w, h = self.dsize
return (self.num_frames, h, w, self.num_bands)
@profile
def finalize(self, **kwargs):
"""
Execute the final transform
......@@ -1395,6 +1402,7 @@ class DelayedChannelConcat(DelayedImageOperation):
w, h = self.dsize
return (h, w, self.num_bands)
@profile
def finalize(self, **kwargs):
"""
Execute the final transform
......@@ -1751,6 +1759,7 @@ class DelayedWarp(DelayedImageOperation):
leaf = DelayedWarp(sub_data, transform, dsize=dsize)
yield leaf
@profile
def finalize(self, transform=None, dsize=None, interpolation='linear',
**kwargs):
"""
......@@ -1920,6 +1929,7 @@ class DelayedCrop(DelayedImageOperation):
def children(self):
yield self.sub_data
@profile
def finalize(self, **kwargs):
if hasattr(self.sub_data, 'finalize'):
return self.sub_data.finalize(**kwargs)[self.sub_slices]
......@@ -1930,6 +1940,7 @@ class DelayedCrop(DelayedImageOperation):
raise NotImplementedError('cant look at leafs through crop atm')
@profile
def _compute_leaf_subcrop(root_region_bounds, tf_leaf_to_root):
r"""
Given a region in a "root" image and a trasnform between that "root" and
......@@ -1954,7 +1965,9 @@ def _compute_leaf_subcrop(root_region_bounds, tf_leaf_to_root):
"""
# Transform the region bounds into the sub-image space
tf_root_to_leaf = np.asarray(Affine.coerce(tf_leaf_to_root).inv())
tf_leaf_to_root = Affine.coerce(tf_leaf_to_root)
tf_root_to_leaf = tf_leaf_to_root.inv()
tf_root_to_leaf = tf_root_to_leaf.__array__()
leaf_region_bounds = root_region_bounds.warp(tf_root_to_leaf)
leaf_region_box = leaf_region_bounds.bounding_box().to_ltrb()
......@@ -1972,7 +1985,7 @@ def _compute_leaf_subcrop(root_region_bounds, tf_leaf_to_root):
crop_offset = leaf_crop_box.data[0, 0:2]
root_offset = root_region_bounds.exterior.data.min(axis=0)
tf_root_to_newroot = Affine.affine(offset=root_offset).inv().matrix
tf_root_to_newroot = Affine.affine(offset=-root_offset).matrix
tf_newleaf_to_leaf = Affine.affine(offset=crop_offset).matrix
# Resample the smaller region to align it with the root region
......@@ -2053,19 +2066,19 @@ def _devcheck_corner():
# cropped-leaf-space not just the leaf-space, so we invert the implicit
# crop
tf_crop_to_leaf = Affine.affine(offset=crop_offset)
tf_crop_to_leaf = Affine.translate(offset=crop_offset)
# tf_newroot_to_root = Affine.affine(offset=region_box.data[0, 0:2])
tf_root_to_newroot = Affine.affine(offset=region_box.data[0, 0:2]).inv()
tf_root_to_newroot = Affine.translate(offset=region_box.data[0, 0:2]).inv()
tf_crop_to_leaf = Affine.affine(offset=crop_offset)
tf_crop_to_leaf = Affine.translate(offset=crop_offset)
tf_crop_to_newroot = tf_root_to_newroot @ tf_leaf_to_root @ tf_crop_to_leaf
tf_newroot_to_crop = tf_crop_to_newroot.inv()
# tf_leaf_to_crop
# tf_corner_offset = Affine.affine(offset=offset_xy)
# tf_corner_offset = Affine.translate(offset=offset_xy)
subpixel_offset = Affine.affine(offset=offset_xy).matrix
subpixel_offset = Affine.translate(offset=offset_xy).matrix
tf_crop_to_leaf = subpixel_offset
# tf_crop_to_root = tf_leaf_to_root @ tf_crop_to_leaf
# tf_root_to_crop = np.linalg.inv(tf_crop_to_root)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment