:py:mod:`kwarray.util_slider` ============================= .. py:module:: kwarray.util_slider Module Contents --------------- Classes ~~~~~~~ .. autoapisummary:: kwarray.util_slider.SlidingWindow kwarray.util_slider.Stitcher Functions ~~~~~~~~~ .. autoapisummary:: kwarray.util_slider._slices1d Attributes ~~~~~~~~~~ .. autoapisummary:: kwarray.util_slider.torch .. py:data:: torch .. py:class:: SlidingWindow(shape, window, overlap=None, stride=None, keepbound=False, allow_overshoot=False) Bases: :py:obj:`ubelt.NiceRepr` Slide a window of a certain shape over an array with a larger shape. This can be used for iterating over a grid of sub-regions of 2d-images, 3d-volumes, or any n-dimensional array. Yields slices of shape `window` that can be used to index into an array with shape `shape` via numpy / torch fancy indexing. This allows for fast fast iteration over subregions of a larger image. Because we generate a grid-basis using only shapes, the larger image does not need to be in memory as long as its width/height/depth/etc... :Parameters: * **shape** (*Tuple[int, ...]*) -- shape of source array to slide across. * **window** (*Tuple[int, ...]*) -- shape of window that will be slid over the larger image. * **overlap** (*float, default=0*) -- a number between 0 and 1 indicating the fraction of overlap that parts will have. Specifying this is mutually exclusive with `stride`. Must be `0 <= overlap < 1`. * **stride** (*int, default=None*) -- the number of cells (pixels) moved on each step of the window. Mutually exclusive with overlap. * **keepbound** (*bool, default=False*) -- if True, a non-uniform stride will be taken to ensure that the right / bottom of the image is returned as a slice if needed. Such a slice will not obey the overlap constraints. (Defaults to False) * **allow_overshoot** (*bool, default=False*) -- if False, we will raise an error if the window doesn't slide perfectly over the input shape. :ivar basis_shape - shape of the grid corresponding to the number of strides: the sliding window will take. :ivar basis_slices - slices that will be taken in every dimension: :Yields: *Tuple[slice, ...]* -- slices used for numpy indexing, the number of slices in the tuple .. rubric:: Notes For each dimension, we generate a basis (which defines a grid), and we slide over that basis. .. rubric:: Example >>> from kwarray.util_slider import * # NOQA >>> shape = (10, 10) >>> window = (5, 5) >>> self = SlidingWindow(shape, window) >>> for i, index in enumerate(self): >>> print('i={}, index={}'.format(i, index)) i=0, index=(slice(0, 5, None), slice(0, 5, None)) i=1, index=(slice(0, 5, None), slice(5, 10, None)) i=2, index=(slice(5, 10, None), slice(0, 5, None)) i=3, index=(slice(5, 10, None), slice(5, 10, None)) .. rubric:: Example >>> from kwarray.util_slider import * # NOQA >>> shape = (16, 16) >>> window = (4, 4) >>> self = SlidingWindow(shape, window, overlap=(.5, .25)) >>> print('self.stride = {!r}'.format(self.stride)) self.stride = [2, 3] >>> list(ub.chunks(self.grid, 5)) [[(0, 0), (0, 1), (0, 2), (0, 3), (0, 4)], [(1, 0), (1, 1), (1, 2), (1, 3), (1, 4)], [(2, 0), (2, 1), (2, 2), (2, 3), (2, 4)], [(3, 0), (3, 1), (3, 2), (3, 3), (3, 4)], [(4, 0), (4, 1), (4, 2), (4, 3), (4, 4)], [(5, 0), (5, 1), (5, 2), (5, 3), (5, 4)], [(6, 0), (6, 1), (6, 2), (6, 3), (6, 4)]] .. rubric:: Example >>> # Test shapes that dont fit >>> # When the window is bigger than the shape, the left-aligned slices >>> # are returend. >>> self = SlidingWindow((3, 3), (12, 12), allow_overshoot=True, keepbound=True) >>> print(list(self)) [(slice(0, 12, None), slice(0, 12, None))] >>> print(list(SlidingWindow((3, 3), None, allow_overshoot=True, keepbound=True))) [(slice(0, 3, None), slice(0, 3, None))] >>> print(list(SlidingWindow((3, 3), (None, 2), allow_overshoot=True, keepbound=True))) [(slice(0, 3, None), slice(0, 2, None)), (slice(0, 3, None), slice(1, 3, None))] .. py:method:: __nice__(self) .. py:method:: _compute_stride(self, overlap, stride, shape, window) Ensures that stride hasoverlap the correct shape. If stride is not provided, compute stride from desired overlap. .. py:method:: __len__(self) .. py:method:: _iter_basis_frac(self) .. py:method:: __iter__(self) .. py:method:: __getitem__(self, index) Get a specific item by its flat (raveled) index .. rubric:: Example >>> from kwarray.util_slider import * # NOQA >>> window = (10, 10) >>> shape = (20, 20) >>> self = SlidingWindow(shape, window, stride=5) >>> itered_items = list(self) >>> assert len(itered_items) == len(self) >>> indexed_items = [self[i] for i in range(len(self))] >>> assert itered_items[0] == self[0] >>> assert itered_items[-1] == self[-1] >>> assert itered_items == indexed_items .. py:method:: grid(self) :property: Generate indices into the "basis" slice for each dimension. This enumerates the nd indices of the grid. :Yields: Tuple[int, ...] .. py:method:: slices(self) :property: Generate slices for each window (equivalent to iter(self)) .. rubric:: Example >>> shape = (220, 220) >>> window = (10, 10) >>> self = SlidingWindow(shape, window, stride=5) >>> list(self)[41:45] [(slice(0, 10, None), slice(205, 215, None)), (slice(0, 10, None), slice(210, 220, None)), (slice(5, 15, None), slice(0, 10, None)), (slice(5, 15, None), slice(5, 15, None))] >>> print('self.overlap = {!r}'.format(self.overlap)) self.overlap = [0.5, 0.5] .. py:method:: centers(self) :property: Generate centers of each window :Yields: *Tuple[float, ...]* -- the center coordinate of the slice .. rubric:: Example >>> shape = (4, 4) >>> window = (3, 3) >>> self = SlidingWindow(shape, window, stride=1) >>> list(zip(self.centers, self.slices)) [((1.0, 1.0), (slice(0, 3, None), slice(0, 3, None))), ((1.0, 2.0), (slice(0, 3, None), slice(1, 4, None))), ((2.0, 1.0), (slice(1, 4, None), slice(0, 3, None))), ((2.0, 2.0), (slice(1, 4, None), slice(1, 4, None)))] >>> shape = (3, 3) >>> window = (2, 2) >>> self = SlidingWindow(shape, window, stride=1) >>> list(zip(self.centers, self.slices)) [((0.5, 0.5), (slice(0, 2, None), slice(0, 2, None))), ((0.5, 1.5), (slice(0, 2, None), slice(1, 3, None))), ((1.5, 0.5), (slice(1, 3, None), slice(0, 2, None))), ((1.5, 1.5), (slice(1, 3, None), slice(1, 3, None)))] .. py:class:: Stitcher(stitcher, shape, device='numpy') Bases: :py:obj:`ubelt.NiceRepr` Stitches multiple possibly overlapping slices into a larger array. This is used to invert the SlidingWindow. For semenatic segmentation the patches are probability chips. Overlapping chips are averaged together. :Parameters: **shape** (*tuple*) -- dimensions of the large image that will be created from the smaller pixels or patches. .. todo:: - [ ] Look at the old "add_fast" code in the netharn version and see if it is worth porting. This code is kept in the dev folder in ../dev/_dev_slider.py .. rubric:: Example >>> from kwarray.util_slider import * # NOQA >>> import sys >>> # Build a high resolution image and slice it into chips >>> highres = np.random.rand(5, 200, 200).astype(np.float32) >>> target_shape = (1, 50, 50) >>> slider = SlidingWindow(highres.shape, target_shape, overlap=(0, .5, .5)) >>> # Show how Sticher can be used to reconstruct the original image >>> stitcher = Stitcher(slider.input_shape) >>> for sl in list(slider): ... chip = highres[sl] ... stitcher.add(sl, chip) >>> assert stitcher.weights.max() == 4, 'some parts should be processed 4 times' >>> recon = stitcher.finalize() .. py:method:: __nice__(stitcher) .. py:method:: add(stitcher, indices, patch, weight=None) Incorporate a new (possibly overlapping) patch or pixel using a weighted sum. :Parameters: * **indices** (*slice or tuple*) -- typically a Tuple[slice] of pixels or a single pixel, but this can be any numpy fancy index. * **patch** (*ndarray*) -- data to patch into the bigger image. * **weight** (*float or ndarray*) -- weight of this patch (default to 1.0) .. py:method:: average(stitcher) Averages out contributions from overlapping adds using weighted average :returns: ndarray: the stitched image :rtype: out .. py:method:: finalize(stitcher, indices=None) Averages out contributions from overlapping adds :Parameters: **indices** (*None | slice | tuple*) -- if None, finalize the entire block, otherwise only finalize a subregion. :returns: ndarray: the stitched image :rtype: final .. py:function:: _slices1d(margin, stop, step=None, start=0, keepbound=False, check=True) Helper to generates slices in a single dimension. :Parameters: * **margin** (*int*) -- the length of the slice (window) * **stop** (*int*) -- the length of the image dimension * **step** (*int, default=None*) -- the length of each step / distance between slices * **start** (*int, default=0*) -- starting point (in most cases set this to 0) * **keepbound** (*bool*) -- if True, a non-uniform step will be taken to ensure that the right / bottom of the image is returned as a slice if needed. Such a slice will not obey the overlap constraints. (Defaults to False) * **check** (*bool*) -- if True an error will be raised if the window does not cover the entire extent from start to stop, even if keepbound is True. :Yields: *slice* -- slice in one dimension of size (margin) .. rubric:: Example >>> stop, margin, step = 2000, 360, 360 >>> keepbound = True >>> strides = list(_slices1d(margin, stop, step, keepbound, check=False)) >>> assert all([(s.stop - s.start) == margin for s in strides]) .. rubric:: Example >>> stop, margin, step = 200, 46, 7 >>> keepbound = True >>> strides = list(_slices1d(margin, stop, step, keepbound=False, check=True)) >>> starts = np.array([s.start for s in strides]) >>> stops = np.array([s.stop for s in strides]) >>> widths = stops - starts >>> assert np.all(np.diff(starts) == step) >>> assert np.all(widths == margin) .. rubric:: Example >>> import pytest >>> stop, margin, step = 200, 36, 7 >>> with pytest.raises(ValueError): ... list(_slices1d(margin, stop, step))