FashionGAN / netdissect /actviz.py
fiesty-bear
Initial Commit
6064c9d
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))