Module vipy.pyramid
Expand source code Browse git
import vipy
import numpy as np
import copy
vipy.util.try_import('torch')
import torch
class GaussianPyramid():
"""vipy.pyramid.GaussianPyramid() class"""
def __init__(self, im=None, tensor=None):
assert im is not None or tensor is not None
assert im is None or isinstance(im, vipy.image.Image)
assert tensor is None or (torch.is_tensor(tensor) and tensor.ndim == 4)
g = ((1.0/np.sqrt(2*np.pi))*np.exp(-0.5*(np.array([-2,-1,0,1,2])**2))).astype(np.float32)
G = torch.from_numpy(np.outer(g,g)).repeat( (3,1,1,1) )
self._G = G
self._band = []
self._pad = torch.nn.ReflectionPad2d(2)
x = im.float().torch(order='NCHW') if im is not None else tensor
for k in range(int(np.log2(min(x.shape[2], x.shape[3]))-2)):
y = torch.nn.functional.conv2d(self._pad(x), self._G, groups=3) # anti-aliasing
self._band.append(y) # lowpass
x = y[:,:,::2,::2] # downsample
self._band.append(x) # lowpass
def __len__(self):
return len(self._band)
def __iter__(self):
for k in range(len(self)):
yield self[k]
def __getitem__(self, k):
assert k>=0 and k<len(self)
return vipy.image.Image.fromtorch(self._band[k])
def band(self, k):
return self[k]
def show(self, mindim=256):
return vipy.visualize.montage([im.maxsquare() for im in self], mindim, mindim).show()
class LaplacianPyramid():
"""vipy.pyramid.LaplacianPyramid() class"""
def __init__(self, im, pad='zero'):
g = (1.0/np.sqrt(2*np.pi))*np.exp(-0.5*(np.array([-2,-1,0,1,2])**2))
G = torch.from_numpy(np.outer(g,g).astype(np.float32))
self._G = G.repeat( (3,1,1,1) )
self._Ce = torch.sum(G[::2,::2]) # filter coefficient sum, even elements only
self._Co = torch.sum(G[1::2,1::2]) # filter coefficient sum, odd elements only
self._band = []
self._im = im
if pad == 'zero':
self._pad = torch.nn.ZeroPad2d(2) # introduces lowpass corner boundary artifact on reconstruction
self._gain = 1.6 # rescale to approximately correct boundary artifact
elif pad == 'reflect':
self._pad = torch.nn.ReflectionPad2d(2) # introduces lowpass gain boundary artifact on reconstruction
self._gain = 1.4 # rescale to approximately correct boundary artifact
else:
raise ValueError('unknown padding "%s" - must be ["zero", "reflect"]' % pad)
if isinstance(im, vipy.image.Image):
x = im.float().torch(order='NCHW')
for k in range(int(np.log2(im.mindim())-1)):
y = torch.nn.functional.conv2d(self._pad(x), self._G, groups=3) # anti-aliasing
self._band.append(x-y) # bandpass
x = y[:,:,::2,::2] # even downsample
self._band.append(x) # lowpass
elif isinstance(im, list):
assert all([torch.is_tensor(b) for b in im])
self._band = im
self._im = self[0].clone().zeros() # no source image available
else:
raise ValueError('invalid input')
def __repr__(self):
return '<vipy.pyramid.LaplacianPyramid: scales=%d, channels=%d, height=%d, width=%d>' % (self.scales(), self.channels(), self.height(), self.width())
def __len__(self):
return len(self._band)
def __iter__(self):
for k in range(len(self)):
yield self[k]
def __getitem__(self, k):
assert k>=0 and k<len(self)
return vipy.image.Image.fromtorch(self._band[k])
def band(self, k):
return self[k]
def reconstruct(self):
x = self._band[-1]
for b in reversed(self._band[0:-1]):
xz = torch.zeros(1, b.shape[1], b.shape[2], b.shape[3])
xz[:,:,::2,::2] = x # odd zero interpolate (even signal)
xu = torch.nn.functional.conv2d(self._pad(xz), (1.0/self._Ce)*self._G, groups=3) # upsample, rescale using sum of non-zeroed kernel elements for even locations
xu[:,:,1::2,1::2] *= (self._Ce/self._Co) # upsample, rescale using sum of non-zeroed kernel elements for odd locations
x = xu + b # reconstruction
im = vipy.image.Image.fromtorch((x * self._gain).clamp(0, 255)) # final rescale due to padding boundary artifact (can also use im.mat2gray)
return im.array(im.numpy().astype(np.uint8)).colorspace('rgb')
def show(self, mindim=256):
return vipy.visualize.montage([im.maxsquare().resize(mindim, mindim, interp='nearest') for im in self], mindim, mindim).show()
def height(self):
return self._im.height()
def width(self):
return self._im.width()
def channels(self):
return self._im.channels()
def scales(self):
return len(self)
def tensor(self, interp='nearest'):
return torch.stack([im.resize(self.height(), self.width(), interp=interp).torch(order='CHW') for im in self]) # scales() x channels() x height() x width()
@staticmethod
def fromtensor(bands):
"""Convert a S*CxHxW torch tensor back to LaplacianPyramid"""
assert torch.is_tensor(bands) and bands.ndim == 4
(S,C,H,W) = bands.shape
return LaplacianPyramid([vipy.image.Image.fromtorch(b).resize(H//(2**i), W//(2**i), interp='nearest').torch(order='NCHW') for (i,b) in enumerate(bands)])
class Foveation(LaplacianPyramid):
def __init__(self, im, mode='log-circle', s=None):
super().__init__(im)
(H,W) = (im.height(), im.width())
allowable_modes = ['gaussian', 'linear-circle', 'linear-square', 'log-circle']
if mode == 'gaussian':
s = s*(W/2) if s is not None else W/2
G = vipy.math.gaussian2d([W,H], [s,s], 2*H, 2*W)
M = np.repeat(G[:,:,np.newaxis], 3, axis=2)
thresholds = np.arange(0, float(np.max(G)), np.max(G)/len(self))
masks = [vipy.image.Image(array=255*np.array(M>=t).astype(np.uint8)).blur(16).mat2gray(min=0) for t in thresholds]
elif mode == 'linear-circle':
s = s*2.0 if s is not None else 2.0
masks = [vipy.calibration.imcircle(W, H, s*(d/2), 2*W, 2*H, 3).rgb().blur(16).mat2gray(min=0) for d in np.arange(max(H,W), 0, -max(H,W)/len(self))]
elif mode == 'log-circle':
s = s*0.125 if s is not None else 0.125
masks = [vipy.calibration.imcircle(W, H, (s*(d/2))**2, 2*W, 2*H, 3).rgb().blur(16).mat2gray(min=0) for d in np.arange(max(H,W), 0, -max(H,W)/len(self))]
elif mode == 'linear-square':
s = s*2.0 if s is not None else 2.0
masks = [vipy.image.Image(array=vipy.calibration.square(W,H,s*(d/2),2*W,2*H,3).astype(np.float32), colorspace='float') for d in np.arange(max(H,W), 0, -max(H,W)/len(self))]
else:
raise ValueError('invalid mode "%s" - must be in %s' % (mode, str(allowable_modes)))
self._immasks = masks
self._masks = [m.torch(order='NCHW') for m in masks]
def __call__(self, tx=0, ty=0, sx=1.0, sy=1.0):
(C, H,W) = (self.channels(), self.height(), self.width())
theta = torch.FloatTensor([[sx,0,tx],[0,sy,ty]]).repeat( (1, 1, 1) )
G = torch.nn.functional.affine_grid(theta, (1, C, H, W), align_corners=False)
blend = [GaussianPyramid(tensor=torch.nn.functional.grid_sample(m, G, align_corners=False)) for m in self._masks]
pyr = copy.deepcopy(self)
pyr._band[:-1] = [torch.mul(w[k].torch(order='NCHW'), b) for (k, (w,b)) in enumerate(zip(reversed(blend), pyr._band[:-1]))]
return pyr.reconstruct()
def foveate(self, tx=0, ty=0):
"""Foveate the input image at location (tx, ty) in scaled image coordinates where (0,0) is the center and (1,1) is the upper left"""
return self.__call__(tx=tx, ty=ty, sx=1.0, sy=1.0)
def visualize(self):
"""Show the fovea density"""
imgmask = self._immasks[0].clone().mat2gray().numpy()
for (im,c) in zip(self._immasks, range(0, 255, len(self))):
img = im.clone().mat2gray(min=0).numpy()
imgmask += c*img
return vipy.image.Image(array=imgmask)
Classes
class Foveation (im, mode='log-circle', s=None)
-
vipy.pyramid.LaplacianPyramid() class
Expand source code Browse git
class Foveation(LaplacianPyramid): def __init__(self, im, mode='log-circle', s=None): super().__init__(im) (H,W) = (im.height(), im.width()) allowable_modes = ['gaussian', 'linear-circle', 'linear-square', 'log-circle'] if mode == 'gaussian': s = s*(W/2) if s is not None else W/2 G = vipy.math.gaussian2d([W,H], [s,s], 2*H, 2*W) M = np.repeat(G[:,:,np.newaxis], 3, axis=2) thresholds = np.arange(0, float(np.max(G)), np.max(G)/len(self)) masks = [vipy.image.Image(array=255*np.array(M>=t).astype(np.uint8)).blur(16).mat2gray(min=0) for t in thresholds] elif mode == 'linear-circle': s = s*2.0 if s is not None else 2.0 masks = [vipy.calibration.imcircle(W, H, s*(d/2), 2*W, 2*H, 3).rgb().blur(16).mat2gray(min=0) for d in np.arange(max(H,W), 0, -max(H,W)/len(self))] elif mode == 'log-circle': s = s*0.125 if s is not None else 0.125 masks = [vipy.calibration.imcircle(W, H, (s*(d/2))**2, 2*W, 2*H, 3).rgb().blur(16).mat2gray(min=0) for d in np.arange(max(H,W), 0, -max(H,W)/len(self))] elif mode == 'linear-square': s = s*2.0 if s is not None else 2.0 masks = [vipy.image.Image(array=vipy.calibration.square(W,H,s*(d/2),2*W,2*H,3).astype(np.float32), colorspace='float') for d in np.arange(max(H,W), 0, -max(H,W)/len(self))] else: raise ValueError('invalid mode "%s" - must be in %s' % (mode, str(allowable_modes))) self._immasks = masks self._masks = [m.torch(order='NCHW') for m in masks] def __call__(self, tx=0, ty=0, sx=1.0, sy=1.0): (C, H,W) = (self.channels(), self.height(), self.width()) theta = torch.FloatTensor([[sx,0,tx],[0,sy,ty]]).repeat( (1, 1, 1) ) G = torch.nn.functional.affine_grid(theta, (1, C, H, W), align_corners=False) blend = [GaussianPyramid(tensor=torch.nn.functional.grid_sample(m, G, align_corners=False)) for m in self._masks] pyr = copy.deepcopy(self) pyr._band[:-1] = [torch.mul(w[k].torch(order='NCHW'), b) for (k, (w,b)) in enumerate(zip(reversed(blend), pyr._band[:-1]))] return pyr.reconstruct() def foveate(self, tx=0, ty=0): """Foveate the input image at location (tx, ty) in scaled image coordinates where (0,0) is the center and (1,1) is the upper left""" return self.__call__(tx=tx, ty=ty, sx=1.0, sy=1.0) def visualize(self): """Show the fovea density""" imgmask = self._immasks[0].clone().mat2gray().numpy() for (im,c) in zip(self._immasks, range(0, 255, len(self))): img = im.clone().mat2gray(min=0).numpy() imgmask += c*img return vipy.image.Image(array=imgmask)
Ancestors
Methods
def foveate(self, tx=0, ty=0)
-
Foveate the input image at location (tx, ty) in scaled image coordinates where (0,0) is the center and (1,1) is the upper left
Expand source code Browse git
def foveate(self, tx=0, ty=0): """Foveate the input image at location (tx, ty) in scaled image coordinates where (0,0) is the center and (1,1) is the upper left""" return self.__call__(tx=tx, ty=ty, sx=1.0, sy=1.0)
def visualize(self)
-
Show the fovea density
Expand source code Browse git
def visualize(self): """Show the fovea density""" imgmask = self._immasks[0].clone().mat2gray().numpy() for (im,c) in zip(self._immasks, range(0, 255, len(self))): img = im.clone().mat2gray(min=0).numpy() imgmask += c*img return vipy.image.Image(array=imgmask)
Inherited members
class GaussianPyramid (im=None, tensor=None)
-
vipy.pyramid.GaussianPyramid() class
Expand source code Browse git
class GaussianPyramid(): """vipy.pyramid.GaussianPyramid() class""" def __init__(self, im=None, tensor=None): assert im is not None or tensor is not None assert im is None or isinstance(im, vipy.image.Image) assert tensor is None or (torch.is_tensor(tensor) and tensor.ndim == 4) g = ((1.0/np.sqrt(2*np.pi))*np.exp(-0.5*(np.array([-2,-1,0,1,2])**2))).astype(np.float32) G = torch.from_numpy(np.outer(g,g)).repeat( (3,1,1,1) ) self._G = G self._band = [] self._pad = torch.nn.ReflectionPad2d(2) x = im.float().torch(order='NCHW') if im is not None else tensor for k in range(int(np.log2(min(x.shape[2], x.shape[3]))-2)): y = torch.nn.functional.conv2d(self._pad(x), self._G, groups=3) # anti-aliasing self._band.append(y) # lowpass x = y[:,:,::2,::2] # downsample self._band.append(x) # lowpass def __len__(self): return len(self._band) def __iter__(self): for k in range(len(self)): yield self[k] def __getitem__(self, k): assert k>=0 and k<len(self) return vipy.image.Image.fromtorch(self._band[k]) def band(self, k): return self[k] def show(self, mindim=256): return vipy.visualize.montage([im.maxsquare() for im in self], mindim, mindim).show()
Methods
def band(self, k)
-
Expand source code Browse git
def band(self, k): return self[k]
def show(self, mindim=256)
-
Expand source code Browse git
def show(self, mindim=256): return vipy.visualize.montage([im.maxsquare() for im in self], mindim, mindim).show()
class LaplacianPyramid (im, pad='zero')
-
vipy.pyramid.LaplacianPyramid() class
Expand source code Browse git
class LaplacianPyramid(): """vipy.pyramid.LaplacianPyramid() class""" def __init__(self, im, pad='zero'): g = (1.0/np.sqrt(2*np.pi))*np.exp(-0.5*(np.array([-2,-1,0,1,2])**2)) G = torch.from_numpy(np.outer(g,g).astype(np.float32)) self._G = G.repeat( (3,1,1,1) ) self._Ce = torch.sum(G[::2,::2]) # filter coefficient sum, even elements only self._Co = torch.sum(G[1::2,1::2]) # filter coefficient sum, odd elements only self._band = [] self._im = im if pad == 'zero': self._pad = torch.nn.ZeroPad2d(2) # introduces lowpass corner boundary artifact on reconstruction self._gain = 1.6 # rescale to approximately correct boundary artifact elif pad == 'reflect': self._pad = torch.nn.ReflectionPad2d(2) # introduces lowpass gain boundary artifact on reconstruction self._gain = 1.4 # rescale to approximately correct boundary artifact else: raise ValueError('unknown padding "%s" - must be ["zero", "reflect"]' % pad) if isinstance(im, vipy.image.Image): x = im.float().torch(order='NCHW') for k in range(int(np.log2(im.mindim())-1)): y = torch.nn.functional.conv2d(self._pad(x), self._G, groups=3) # anti-aliasing self._band.append(x-y) # bandpass x = y[:,:,::2,::2] # even downsample self._band.append(x) # lowpass elif isinstance(im, list): assert all([torch.is_tensor(b) for b in im]) self._band = im self._im = self[0].clone().zeros() # no source image available else: raise ValueError('invalid input') def __repr__(self): return '<vipy.pyramid.LaplacianPyramid: scales=%d, channels=%d, height=%d, width=%d>' % (self.scales(), self.channels(), self.height(), self.width()) def __len__(self): return len(self._band) def __iter__(self): for k in range(len(self)): yield self[k] def __getitem__(self, k): assert k>=0 and k<len(self) return vipy.image.Image.fromtorch(self._band[k]) def band(self, k): return self[k] def reconstruct(self): x = self._band[-1] for b in reversed(self._band[0:-1]): xz = torch.zeros(1, b.shape[1], b.shape[2], b.shape[3]) xz[:,:,::2,::2] = x # odd zero interpolate (even signal) xu = torch.nn.functional.conv2d(self._pad(xz), (1.0/self._Ce)*self._G, groups=3) # upsample, rescale using sum of non-zeroed kernel elements for even locations xu[:,:,1::2,1::2] *= (self._Ce/self._Co) # upsample, rescale using sum of non-zeroed kernel elements for odd locations x = xu + b # reconstruction im = vipy.image.Image.fromtorch((x * self._gain).clamp(0, 255)) # final rescale due to padding boundary artifact (can also use im.mat2gray) return im.array(im.numpy().astype(np.uint8)).colorspace('rgb') def show(self, mindim=256): return vipy.visualize.montage([im.maxsquare().resize(mindim, mindim, interp='nearest') for im in self], mindim, mindim).show() def height(self): return self._im.height() def width(self): return self._im.width() def channels(self): return self._im.channels() def scales(self): return len(self) def tensor(self, interp='nearest'): return torch.stack([im.resize(self.height(), self.width(), interp=interp).torch(order='CHW') for im in self]) # scales() x channels() x height() x width() @staticmethod def fromtensor(bands): """Convert a S*CxHxW torch tensor back to LaplacianPyramid""" assert torch.is_tensor(bands) and bands.ndim == 4 (S,C,H,W) = bands.shape return LaplacianPyramid([vipy.image.Image.fromtorch(b).resize(H//(2**i), W//(2**i), interp='nearest').torch(order='NCHW') for (i,b) in enumerate(bands)])
Subclasses
Static methods
def fromtensor(bands)
-
Convert a S*CxHxW torch tensor back to LaplacianPyramid
Expand source code Browse git
@staticmethod def fromtensor(bands): """Convert a S*CxHxW torch tensor back to LaplacianPyramid""" assert torch.is_tensor(bands) and bands.ndim == 4 (S,C,H,W) = bands.shape return LaplacianPyramid([vipy.image.Image.fromtorch(b).resize(H//(2**i), W//(2**i), interp='nearest').torch(order='NCHW') for (i,b) in enumerate(bands)])
Methods
def band(self, k)
-
Expand source code Browse git
def band(self, k): return self[k]
def channels(self)
-
Expand source code Browse git
def channels(self): return self._im.channels()
def height(self)
-
Expand source code Browse git
def height(self): return self._im.height()
def reconstruct(self)
-
Expand source code Browse git
def reconstruct(self): x = self._band[-1] for b in reversed(self._band[0:-1]): xz = torch.zeros(1, b.shape[1], b.shape[2], b.shape[3]) xz[:,:,::2,::2] = x # odd zero interpolate (even signal) xu = torch.nn.functional.conv2d(self._pad(xz), (1.0/self._Ce)*self._G, groups=3) # upsample, rescale using sum of non-zeroed kernel elements for even locations xu[:,:,1::2,1::2] *= (self._Ce/self._Co) # upsample, rescale using sum of non-zeroed kernel elements for odd locations x = xu + b # reconstruction im = vipy.image.Image.fromtorch((x * self._gain).clamp(0, 255)) # final rescale due to padding boundary artifact (can also use im.mat2gray) return im.array(im.numpy().astype(np.uint8)).colorspace('rgb')
def scales(self)
-
Expand source code Browse git
def scales(self): return len(self)
def show(self, mindim=256)
-
Expand source code Browse git
def show(self, mindim=256): return vipy.visualize.montage([im.maxsquare().resize(mindim, mindim, interp='nearest') for im in self], mindim, mindim).show()
def tensor(self, interp='nearest')
-
Expand source code Browse git
def tensor(self, interp='nearest'): return torch.stack([im.resize(self.height(), self.width(), interp=interp).torch(order='CHW') for im in self]) # scales() x channels() x height() x width()
def width(self)
-
Expand source code Browse git
def width(self): return self._im.width()