Spaces:
Build error
Build error
import os | |
import numpy | |
from scipy.interpolate import RectBivariateSpline | |
def activation_visualization(image, data, level, alpha=0.5, source_shape=None, | |
crop=False, zoom=None, border=2, negate=False, return_mask=False, | |
**kwargs): | |
""" | |
Makes a visualiztion image of activation data overlaid on the image. | |
Params: | |
image The original image. | |
data The single channel feature map. | |
alpha The darkening to apply in inactive regions of the image. | |
level The threshold of activation levels to highlight. | |
""" | |
if len(image.shape) == 2: | |
# Puff up grayscale image to RGB. | |
image = image[:,:,None] * numpy.array([[[1, 1, 1]]]) | |
surface = activation_surface(data, target_shape=image.shape[:2], | |
source_shape=source_shape, **kwargs) | |
if negate: | |
surface = -surface | |
level = -level | |
if crop: | |
# crop to source_shape | |
if source_shape is not None: | |
ch, cw = ((t - s) // 2 for s, t in zip( | |
source_shape, image.shape[:2])) | |
image = image[ch:ch+source_shape[0], cw:cw+source_shape[1]] | |
surface = surface[ch:ch+source_shape[0], cw:cw+source_shape[1]] | |
if crop is True: | |
crop = surface.shape | |
elif not hasattr(crop, '__len__'): | |
crop = (crop, crop) | |
if zoom is not None: | |
source_rect = best_sub_rect(surface >= level, crop, zoom, | |
pad=border) | |
else: | |
source_rect = (0, surface.shape[0], 0, surface.shape[1]) | |
image = zoom_image(image, source_rect, crop) | |
surface = zoom_image(surface, source_rect, crop) | |
mask = (surface >= level) | |
# Add a yellow border at the edge of the mask for contrast | |
result = (mask[:, :, None] * (1 - alpha) + alpha) * image | |
if border: | |
edge = mask_border(mask)[:,:,None] | |
result = numpy.maximum(edge * numpy.array([[[200, 200, 0]]]), result) | |
if not return_mask: | |
return result | |
mask_image = (1 - mask[:, :, None]) * numpy.array( | |
[[[0, 0, 0, 255 * (1 - alpha)]]], dtype=numpy.uint8) | |
if border: | |
mask_image = numpy.maximum(edge * numpy.array([[[200, 200, 0, 255]]]), | |
mask_image) | |
return result, mask_image | |
def activation_surface(data, target_shape=None, source_shape=None, | |
scale_offset=None, deg=1, pad=True): | |
""" | |
Generates an upsampled activation sample. | |
Params: | |
target_shape Shape of the output array. | |
source_shape The centered shape of the output to match with data | |
when upscaling. Defaults to the whole target_shape. | |
scale_offset The amount by which to scale, then offset data | |
dimensions to end up with target dimensions. A pair of pairs. | |
deg Degree of interpolation to apply (1 = linear, etc). | |
pad True to zero-pad the edge instead of doing a funny edge interp. | |
""" | |
# Default is that nothing is resized. | |
if target_shape is None: | |
target_shape = data.shape | |
# Make a default scale_offset to fill the image if there isn't one | |
if scale_offset is None: | |
scale = tuple(float(ts) / ds | |
for ts, ds in zip(target_shape, data.shape)) | |
offset = tuple(0.5 * s - 0.5 for s in scale) | |
else: | |
scale, offset = (v for v in zip(*scale_offset)) | |
# Now we adjust offsets to take into account cropping and so on | |
if source_shape is not None: | |
offset = tuple(o + (ts - ss) / 2.0 | |
for o, ss, ts in zip(offset, source_shape, target_shape)) | |
# Pad the edge with zeros for sensible edge behavior | |
if pad: | |
zeropad = numpy.zeros( | |
(data.shape[0] + 2, data.shape[1] + 2), dtype=data.dtype) | |
zeropad[1:-1, 1:-1] = data | |
data = zeropad | |
offset = tuple((o - s) for o, s in zip(offset, scale)) | |
# Upsample linearly | |
ty, tx = (numpy.arange(ts) for ts in target_shape) | |
sy, sx = (numpy.arange(ss) * s + o | |
for ss, s, o in zip(data.shape, scale, offset)) | |
levels = RectBivariateSpline( | |
sy, sx, data, kx=deg, ky=deg)(ty, tx, grid=True) | |
# Return the mask. | |
return levels | |
def mask_border(mask, border=2): | |
"""Given a mask computes a border mask""" | |
from scipy import ndimage | |
struct = ndimage.generate_binary_structure(2, 2) | |
erosion = numpy.ones((mask.shape[0] + 10, mask.shape[1] + 10), dtype='int') | |
erosion[5:5+mask.shape[0], 5:5+mask.shape[1]] = ~mask | |
for _ in range(border): | |
erosion = ndimage.binary_erosion(erosion, struct) | |
return ~mask ^ erosion[5:5+mask.shape[0], 5:5+mask.shape[1]] | |
def bounding_rect(mask, pad=0): | |
"""Returns (r, b, l, r) boundaries so that all nonzero pixels in mask | |
have locations (i, j) with t <= i < b, and l <= j < r.""" | |
nz = mask.nonzero() | |
if len(nz[0]) == 0: | |
# print('no pixels') | |
return (0, mask.shape[0], 0, mask.shape[1]) | |
(t, b), (l, r) = [(max(0, p.min() - pad), min(s, p.max() + 1 + pad)) | |
for p, s in zip(nz, mask.shape)] | |
return (t, b, l, r) | |
def best_sub_rect(mask, shape, max_zoom=None, pad=2): | |
"""Finds the smallest subrectangle containing all the nonzeros of mask, | |
matching the aspect ratio of shape, and where the zoom-up ratio is no | |
more than max_zoom""" | |
t, b, l, r = bounding_rect(mask, pad=pad) | |
height = max(b - t, int(round(float(shape[0]) * (r - l) / shape[1]))) | |
if max_zoom is not None: | |
height = int(max(round(float(shape[0]) / max_zoom), height)) | |
width = int(round(float(shape[1]) * height / shape[0])) | |
nt = min(mask.shape[0] - height, max(0, (b + t - height) // 2)) | |
nb = nt + height | |
nl = min(mask.shape[1] - width, max(0, (r + l - width) // 2)) | |
nr = nl + width | |
return (nt, nb, nl, nr) | |
def zoom_image(img, source_rect, target_shape=None): | |
"""Zooms pixels from the source_rect of img to target_shape.""" | |
import warnings | |
from scipy.ndimage import zoom | |
if target_shape is None: | |
target_shape = img.shape | |
st, sb, sl, sr = source_rect | |
source = img[st:sb, sl:sr] | |
if source.shape == target_shape: | |
return source | |
zoom_tuple = tuple(float(t) / s | |
for t, s in zip(target_shape, source.shape[:2]) | |
) + (1,) * (img.ndim - 2) | |
with warnings.catch_warnings(): | |
warnings.simplefilter('ignore', UserWarning) # "output shape of zoom" | |
target = zoom(source, zoom_tuple) | |
assert target.shape[:2] == target_shape, (target.shape, target_shape) | |
return target | |
def scale_offset(dilations): | |
if len(dilations) == 0: | |
return (1, 0) | |
scale, offset = scale_offset(dilations[1:]) | |
kernel, stride, padding = dilations[0] | |
scale *= stride | |
offset *= stride | |
offset += (kernel - 1) / 2.0 - padding | |
return scale, offset | |
def choose_level(feature_map, percentile=0.8): | |
''' | |
Chooses the top 80% level (or whatever the level chosen). | |
''' | |
data_range = numpy.sort(feature_map.flatten()) | |
return numpy.interp( | |
percentile, numpy.linspace(0, 1, len(data_range)), data_range) | |
def dilations(modulelist): | |
result = [] | |
for module in modulelist: | |
settings = tuple(getattr(module, n, d) | |
for n, d in (('kernel_size', 1), ('stride', 1), ('padding', 0))) | |
settings = (((s, s) if not isinstance(s, tuple) else s) | |
for s in settings) | |
if settings != ((1, 1), (1, 1), (0, 0)): | |
result.append(zip(*settings)) | |
return zip(*result) | |
def grid_scale_offset(modulelist): | |
'''Returns (yscale, yoffset), (xscale, xoffset) given a list of modules''' | |
return tuple(scale_offset(d) for d in dilations(modulelist)) | |