diff options
Diffstat (limited to 'silx/third_party/modest_image.py')
-rw-r--r-- | silx/third_party/modest_image.py | 322 |
1 files changed, 0 insertions, 322 deletions
diff --git a/silx/third_party/modest_image.py b/silx/third_party/modest_image.py deleted file mode 100644 index 3a64d1a..0000000 --- a/silx/third_party/modest_image.py +++ /dev/null @@ -1,322 +0,0 @@ -""" -Taken from https://github.com/ChrisBeaumont/mpl-modest-image, -commit 0545d2c58970bed9ac366193eaf771cc3247d250 - -Copyright (c) 2013 Chris Beaumont - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the "Software"), -to deal in the Software without restriction, including without limitation -the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the -Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. - -Modification of Chris Beaumont's mpl-modest-image package to allow the use of -set_extent. -""" -from __future__ import print_function, division - -import matplotlib -rcParams = matplotlib.rcParams - -import matplotlib.image as mi -import matplotlib.colors as mcolors -import matplotlib.cbook as cbook -from matplotlib.transforms import IdentityTransform, Affine2D - -import numpy as np - -IDENTITY_TRANSFORM = IdentityTransform() - - -class ModestImage(mi.AxesImage): - - """ - Computationally modest image class. - - ModestImage is an extension of the Matplotlib AxesImage class - better suited for the interactive display of larger images. Before - drawing, ModestImage resamples the data array based on the screen - resolution and view window. This has very little affect on the - appearance of the image, but can substantially cut down on - computation since calculations of unresolved or clipped pixels - are skipped. - - The interface of ModestImage is the same as AxesImage. However, it - does not currently support setting the 'extent' property. There - may also be weird coordinate warping operations for images that - I'm not aware of. Don't expect those to work either. - """ - - def __init__(self, *args, **kwargs): - self._full_res = None - self._full_extent = kwargs.get('extent', None) - super(ModestImage, self).__init__(*args, **kwargs) - self.invalidate_cache() - - def set_data(self, A): - """ - Set the image array - - ACCEPTS: numpy/PIL Image A - """ - self._full_res = A - self._A = A - - if self._A.dtype != np.uint8 and not np.can_cast(self._A.dtype, - np.float): - raise TypeError("Image data can not convert to float") - - if (self._A.ndim not in (2, 3) or - (self._A.ndim == 3 and self._A.shape[-1] not in (3, 4))): - raise TypeError("Invalid dimensions for image data") - - self.invalidate_cache() - - def invalidate_cache(self): - self._bounds = None - self._imcache = None - self._rgbacache = None - self._oldxslice = None - self._oldyslice = None - self._sx, self._sy = None, None - self._pixel2world_cache = None - self._world2pixel_cache = None - - def set_extent(self, extent): - self._full_extent = extent - self.invalidate_cache() - mi.AxesImage.set_extent(self, extent) - - def get_array(self): - """Override to return the full-resolution array""" - return self._full_res - - @property - def _pixel2world(self): - - if self._pixel2world_cache is None: - - # Pre-compute affine transforms to convert between the 'world' - # coordinates of the axes (what is shown by the axis labels) to - # 'pixel' coordinates in the underlying array. - - extent = self._full_extent - - if extent is None: - - self._pixel2world_cache = IDENTITY_TRANSFORM - - else: - - self._pixel2world_cache = Affine2D() - - self._pixel2world.translate(+0.5, +0.5) - - self._pixel2world.scale((extent[1] - extent[0]) / self._full_res.shape[1], - (extent[3] - extent[2]) / self._full_res.shape[0]) - - self._pixel2world.translate(extent[0], extent[2]) - - self._world2pixel_cache = None - - return self._pixel2world_cache - - @property - def _world2pixel(self): - if self._world2pixel_cache is None: - self._world2pixel_cache = self._pixel2world.inverted() - return self._world2pixel_cache - - def _scale_to_res(self): - """ - Change self._A and _extent to render an image whose resolution is - matched to the eventual rendering. - """ - - # Find out how we need to slice the array to make sure we match the - # resolution of the display. We pass self._world2pixel which matters - # for cases where the extent has been set. - x0, x1, sx, y0, y1, sy = extract_matched_slices(axes=self.axes, - shape=self._full_res.shape, - transform=self._world2pixel) - - # Check whether we've already calculated what we need, and if so just - # return without doing anything further. - if (self._bounds is not None and - sx >= self._sx and sy >= self._sy and - x0 >= self._bounds[0] and x1 <= self._bounds[1] and - y0 >= self._bounds[2] and y1 <= self._bounds[3]): - return - - # Slice the array using the slices determined previously to optimally - # match the display - self._A = self._full_res[y0:y1:sy, x0:x1:sx] - self._A = cbook.safe_masked_invalid(self._A) - - # We now determine the extent of the subset of the image, by determining - # it first in pixel space, and converting it to the 'world' coordinates. - - # See https://github.com/matplotlib/matplotlib/issues/8693 for a - # demonstration of why origin='upper' and extent=None needs to be - # special-cased. - - if self.origin == 'upper' and self._full_extent is None: - xmin, xmax, ymin, ymax = x0 - .5, x1 - .5, y1 - .5, y0 - .5 - else: - xmin, xmax, ymin, ymax = x0 - .5, x1 - .5, y0 - .5, y1 - .5 - - xmin, ymin, xmax, ymax = self._pixel2world.transform([(xmin, ymin), (xmax, ymax)]).ravel() - - mi.AxesImage.set_extent(self, [xmin, xmax, ymin, ymax]) - # self.set_extent([xmin, xmax, ymin, ymax]) - - # Finally, we cache the current settings to avoid re-computing similar - # arrays in future. - self._sx = sx - self._sy = sy - self._bounds = (x0, x1, y0, y1) - - self.changed() - - def draw(self, renderer, *args, **kwargs): - if self._full_res.shape is None: - return - self._scale_to_res() - super(ModestImage, self).draw(renderer, *args, **kwargs) - - -def main(): - from time import time - import matplotlib.pyplot as plt - x, y = np.mgrid[0:2000, 0:2000] - data = np.sin(x / 10.) * np.cos(y / 30.) - - f = plt.figure() - ax = f.add_subplot(111) - - # try switching between - artist = ModestImage(ax, data=data) - - ax.set_aspect('equal') - artist.norm.vmin = -1 - artist.norm.vmax = 1 - - ax.add_artist(artist) - - t0 = time() - plt.gcf().canvas.draw() - t1 = time() - - print("Draw time for %s: %0.1f ms" % (artist.__class__.__name__, - (t1 - t0) * 1000)) - - plt.show() - - -def imshow(axes, X, cmap=None, norm=None, aspect=None, - interpolation=None, alpha=None, vmin=None, vmax=None, - origin=None, extent=None, shape=None, filternorm=1, - filterrad=4.0, imlim=None, resample=None, url=None, **kwargs): - """Similar to matplotlib's imshow command, but produces a ModestImage - - Unlike matplotlib version, must explicitly specify axes - """ - if not axes._hold: - axes.cla() - if norm is not None: - assert(isinstance(norm, mcolors.Normalize)) - if aspect is None: - aspect = rcParams['image.aspect'] - axes.set_aspect(aspect) - im = ModestImage(axes, cmap=cmap, norm=norm, interpolation=interpolation, - origin=origin, extent=extent, filternorm=filternorm, - filterrad=filterrad, resample=resample, **kwargs) - - im.set_data(X) - im.set_alpha(alpha) - axes._set_artist_props(im) - - if im.get_clip_path() is None: - # image does not already have clipping set, clip to axes patch - im.set_clip_path(axes.patch) - - # if norm is None and shape is None: - # im.set_clim(vmin, vmax) - if vmin is not None or vmax is not None: - im.set_clim(vmin, vmax) - elif norm is None: - im.autoscale_None() - - im.set_url(url) - - # update ax.dataLim, and, if autoscaling, set viewLim - # to tightly fit the image, regardless of dataLim. - im.set_extent(im.get_extent()) - - axes.images.append(im) - im._remove_method = lambda h: axes.images.remove(h) - - return im - - -def extract_matched_slices(axes=None, shape=None, extent=None, - transform=IDENTITY_TRANSFORM): - """Determine the slice parameters to use, matched to the screen. - - :param ax: Axes object to query. It's extent and pixel size - determine the slice parameters - - :param shape: Tuple of the full image shape to slice into. Upper - boundaries for slices will be cropped to fit within - this shape. - - :rtype: tulpe of x0, x1, sx, y0, y1, sy - - Indexing the full resolution array as array[y0:y1:sy, x0:x1:sx] returns - a view well-matched to the axes' resolution and extent - """ - - # Find extent in display pixels (this gives the resolution we need - # to sample the array to) - ext = (axes.transAxes.transform([(1, 1)]) - axes.transAxes.transform([(0, 0)]))[0] - - # Find the extent of the axes in 'world' coordinates - xlim, ylim = axes.get_xlim(), axes.get_ylim() - - # Transform the limits to pixel coordinates - ind0 = transform.transform([min(xlim), min(ylim)]) - ind1 = transform.transform([max(xlim), max(ylim)]) - - def _clip(val, lo, hi): - return int(max(min(val, hi), lo)) - - # Determine the range of pixels to extract from the array, including a 5 - # pixel margin all around. We ensure that the shape of the resulting array - # will always be at least (1, 1) even if there is really no overlap, to - # avoid issues. - y0 = _clip(ind0[1] - 5, 0, shape[0] - 1) - y1 = _clip(ind1[1] + 5, 1, shape[0]) - x0 = _clip(ind0[0] - 5, 0, shape[1] - 1) - x1 = _clip(ind1[0] + 5, 1, shape[1]) - - # Determine the strides that can be used when extracting the array - sy = int(max(1, min((y1 - y0) / 5., np.ceil(abs((ind1[1] - ind0[1]) / ext[1]))))) - sx = int(max(1, min((x1 - x0) / 5., np.ceil(abs((ind1[0] - ind0[0]) / ext[0]))))) - - return x0, x1, sx, y0, y1, sy - - -if __name__ == "__main__": - main() |