Spaces:
Runtime error
Runtime error
# coding=utf-8 | |
# Copyright 2021 The Deeplab2 Authors. | |
# | |
# Licensed under the Apache License, Version 2.0 (the "License"); | |
# you may not use this file except in compliance with the License. | |
# You may obtain a copy of the License at | |
# | |
# http://www.apache.org/licenses/LICENSE-2.0 | |
# | |
# Unless required by applicable law or agreed to in writing, software | |
# distributed under the License is distributed on an "AS IS" BASIS, | |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
# See the License for the specific language governing permissions and | |
# limitations under the License. | |
"""Utility functions for the visualizer.""" | |
from absl import logging | |
import matplotlib.pyplot as plt | |
import numpy as np | |
import PIL | |
import tensorflow as tf | |
from deeplab2.data import coco_constants | |
# Amount of color perturbation added to colormap. | |
_COLOR_PERTURBATION = 60 | |
def bit_get(val, idx): | |
"""Gets the bit value. | |
Args: | |
val: Input value, int or numpy int array. | |
idx: Which bit of the input val. | |
Returns: | |
The "idx"-th bit of input val. | |
""" | |
return (val >> idx) & 1 | |
def create_pascal_label_colormap(): | |
"""Creates a label colormap used in PASCAL VOC segmentation benchmark. | |
Returns: | |
A colormap for visualizing segmentation results. | |
""" | |
colormap = np.zeros((512, 3), dtype=int) | |
ind = np.arange(512, dtype=int) | |
for shift in reversed(list(range(8))): | |
for channel in range(3): | |
colormap[:, channel] |= bit_get(ind, channel) << shift | |
ind >>= 3 | |
return colormap | |
def create_rgb_from_instance_map(instance_map): | |
"""Creates an RGB image from an instance map for visualization. | |
To assign a color to each instance, if the maximum value of the instance | |
labels is smaller than the maximum allowed value of Pascal's colormap, we use | |
Pascal's colormap. Otherwise, we use random and non-repeated colors. | |
Args: | |
instance_map: Numpy array of shape `[height, width]`, the instance map. | |
Returns: | |
instance_image: Numpy array of shape `[height, width, 3]`, the visualized | |
RGB instance image. | |
""" | |
# pylint: disable=protected-access | |
if np.max(instance_map) < 512: | |
colormap = create_pascal_label_colormap() | |
instance_image = colormap[instance_map] | |
else: | |
np.random.seed(0) | |
used_colors = [(0, 0, 0)] | |
instanc_map_shape = instance_map.shape | |
instance_image = np.zeros([instanc_map_shape[0], instanc_map_shape[1], 3], | |
np.uint8) | |
instance_ids = np.unique(instance_map) | |
for instance_id in instance_ids: | |
# We preserve the id "0" for stuff. | |
if instance_id == 0: | |
continue | |
r = np.random.randint(0, 256, dtype=np.uint8) | |
g = np.random.randint(0, 256, dtype=np.uint8) | |
b = np.random.randint(0, 256, dtype=np.uint8) | |
while (r, g, b) in used_colors: | |
r = np.random.randint(0, 256, dtype=np.uint8) | |
g = np.random.randint(0, 256, dtype=np.uint8) | |
b = np.random.randint(0, 256, dtype=np.uint8) | |
instance_image[instance_map == instance_id, :] = (r, g, b) | |
used_colors.append((r, g, b)) | |
instance_image[instance_map == 0, :] = (0, 0, 0) | |
return instance_image | |
def _generate_color(used_colors): | |
""""Generates a non-repeated color. | |
This function first uses the pascal colormap to generate the color. If more | |
colors are requested, it randomly generates a non-repeated color. | |
Args: | |
used_colors: A list, where each element is a tuple in the format of | |
(r, g, b). | |
Returns: | |
A tuple representing a color in the format of (r, g, b). | |
A list, which is the updated `used_colors` with the returned color tuple | |
appended to it. | |
""" | |
pascal_colormap = create_pascal_label_colormap() | |
if len(used_colors) < len(pascal_colormap): | |
color = tuple(pascal_colormap[len(used_colors)]) | |
else: | |
r = np.random.randint(0, 256, dtype=np.uint8) | |
g = np.random.randint(0, 256, dtype=np.uint8) | |
b = np.random.randint(0, 256, dtype=np.uint8) | |
while (r, g, b) in used_colors: | |
r = np.random.randint(0, 256, dtype=np.uint8) | |
g = np.random.randint(0, 256, dtype=np.uint8) | |
b = np.random.randint(0, 256, dtype=np.uint8) | |
color = (r, g, b) | |
used_colors.append(color) | |
return color, used_colors | |
def overlay_heatmap_on_image(heatmap, | |
input_image, | |
dpi=80.0, | |
add_color_bar=False): | |
"""Overlays a heatmap on top of an image. | |
Args: | |
heatmap: A numpy array (float32) of shape `[height, width]`, | |
which is the heatmap of keypoints. | |
input_image: A numpy array (float32 or uint8) of shape | |
`[height, width, 3]`, which is an image and all the pixel values are in | |
the range of [0.0, 255.0]. | |
dpi: Float, the dpi of the output image. | |
add_color_bar: Boolean, whether to add a colorbar to the output image. | |
Returns: | |
A numpy array (uint8) of the same shape as the `input_image`. | |
""" | |
# Generate the cmap. | |
cmap = plt.cm.Reds | |
# pylint: disable=protected-access | |
cmap._init() | |
# pylint: disable=protected-access | |
cmap._lut[:, -1] = np.linspace(0, 1.0, 259) | |
# Plot. | |
image = input_image.astype(np.float32) / 255.0 | |
image_height, image_width, _ = image.shape | |
fig, ax = plt.subplots( | |
1, | |
1, | |
facecolor='white', | |
figsize=(image_width / dpi, image_height / dpi), | |
dpi=dpi) | |
grid_y, grid_x = np.mgrid[0:image_height, 0:image_width] | |
cb = ax.contourf(grid_x, grid_y, heatmap, 10, cmap=cmap) | |
ax.imshow(image) | |
ax.grid(False) | |
plt.axis('off') | |
if add_color_bar: | |
plt.colorbar(cb) | |
fig.subplots_adjust(bottom=0) | |
fig.subplots_adjust(top=1) | |
fig.subplots_adjust(right=1) | |
fig.subplots_adjust(left=0) | |
# Get the output image. | |
fig.canvas.draw() | |
# pylint: disable=protected-access | |
output_image = np.array(fig.canvas.renderer._renderer)[:, :, :-1] | |
plt.close() | |
return output_image | |
# pylint: disable=invalid-name | |
def make_colorwheel(): | |
"""Generates a color wheel for optical flow visualization. | |
Reference implementation: | |
https://github.com/tomrunia/OpticalFlow_Visualization | |
Returns: | |
flow_image: A numpy array of output image. | |
""" | |
RY = 15 | |
YG = 6 | |
GC = 4 | |
CB = 11 | |
BM = 13 | |
MR = 6 | |
ncols = RY + YG + GC + CB + BM + MR | |
colorwheel = np.zeros((ncols, 3)) | |
col = 0 | |
# RY | |
colorwheel[0:RY, 0] = 255 | |
colorwheel[0:RY, 1] = np.floor(255 * np.arange(0, RY) / RY) | |
col = col + RY | |
# YG | |
colorwheel[col:col + YG, 0] = 255 - np.floor(255 * np.arange(0, YG) / YG) | |
colorwheel[col:col + YG, 1] = 255 | |
col = col + YG | |
# GC | |
colorwheel[col:col + GC, 1] = 255 | |
colorwheel[col:col + GC, 2] = np.floor(255 * np.arange(0, GC) / GC) | |
col = col + GC | |
# CB | |
colorwheel[col:col+CB, 1] = 255 - np.floor(255*np.arange(CB)/CB) | |
colorwheel[col:col+CB, 2] = 255 | |
col = col+CB | |
# BM | |
colorwheel[col:col + BM, 2] = 255 | |
colorwheel[col:col + BM, 0] = np.floor(255 * np.arange(0, BM) / BM) | |
col = col + BM | |
# MR | |
colorwheel[col:col+MR, 2] = 255 - np.floor(255*np.arange(MR)/MR) | |
colorwheel[col:col+MR, 0] = 255 | |
return colorwheel | |
# pylint: enable=invalid-name | |
def flow_compute_color(u, v): | |
"""Computes color for 2D flow field. | |
Reference implementation: | |
https://github.com/tomrunia/OpticalFlow_Visualization | |
Args: | |
u: A numpy array of horizontal flow. | |
v: A numpy array of vertical flow. | |
Returns: | |
flow_image: A numpy array of output image. | |
""" | |
flow_image = np.zeros((u.shape[0], u.shape[1], 3), np.uint8) | |
colorwheel = make_colorwheel() # shape [55x3] | |
ncols = colorwheel.shape[0] | |
rad = np.sqrt(np.square(u) + np.square(v)) | |
a = np.arctan2(-v, -u) / np.pi | |
fk = (a + 1) / 2 * (ncols - 1) | |
k0 = np.floor(fk).astype(np.int32) | |
k1 = k0 + 1 | |
k1[k1 == ncols] = 0 | |
f = fk - k0 | |
for i in range(colorwheel.shape[1]): | |
tmp = colorwheel[:, i] | |
color0 = tmp[k0] / 255.0 | |
color1 = tmp[k1] / 255.0 | |
color = (1 - f) * color0 + f * color1 | |
idx = (rad <= 1) | |
color[idx] = 1 - rad[idx] * (1 - color[idx]) | |
color[~idx] = color[~idx] * 0.75 | |
# The order is RGB. | |
ch_idx = i | |
flow_image[:, :, ch_idx] = np.floor(255 * color) | |
return flow_image | |
def flow_to_color(flow_uv, clip_flow=None): | |
"""Applies color to 2D flow field. | |
Reference implementation: | |
https://github.com/tomrunia/OpticalFlow_Visualization | |
Args: | |
flow_uv: A numpy array of flow with shape [Height, Width, 2]. | |
clip_flow: A float to clip the maximum value for the flow. | |
Returns: | |
flow_image: A numpy array of output image. | |
Raises: | |
ValueError: Input flow does not have dimension equals to 3. | |
ValueError: Input flow does not have shape [H, W, 2]. | |
""" | |
if flow_uv.ndim != 3: | |
raise ValueError('Input flow must have three dimensions.') | |
if flow_uv.shape[2] != 2: | |
raise ValueError('Input flow must have shape [H, W, 2].') | |
if clip_flow is not None: | |
flow_uv = np.clip(flow_uv, 0, clip_flow) | |
u = flow_uv[:, :, 0] | |
v = flow_uv[:, :, 1] | |
rad = np.sqrt(np.square(u) + np.square(v)) | |
rad_max = np.max(rad) | |
epsilon = 1e-5 | |
u = u / (rad_max + epsilon) | |
v = v / (rad_max + epsilon) | |
return flow_compute_color(u, v) | |
def squeeze_batch_dim_and_convert_to_numpy(input_dict): | |
for key in input_dict: | |
input_dict[key] = tf.squeeze(input_dict[key], axis=0).numpy() | |
return input_dict | |
def create_cityscapes_label_colormap(): | |
"""Creates a label colormap used in CITYSCAPES segmentation benchmark. | |
Returns: | |
A colormap for visualizing segmentation results. | |
""" | |
colormap = np.zeros((256, 3), dtype=np.uint8) | |
colormap[0] = [128, 64, 128] | |
colormap[1] = [244, 35, 232] | |
colormap[2] = [70, 70, 70] | |
colormap[3] = [102, 102, 156] | |
colormap[4] = [190, 153, 153] | |
colormap[5] = [153, 153, 153] | |
colormap[6] = [250, 170, 30] | |
colormap[7] = [220, 220, 0] | |
colormap[8] = [107, 142, 35] | |
colormap[9] = [152, 251, 152] | |
colormap[10] = [70, 130, 180] | |
colormap[11] = [220, 20, 60] | |
colormap[12] = [255, 0, 0] | |
colormap[13] = [0, 0, 142] | |
colormap[14] = [0, 0, 70] | |
colormap[15] = [0, 60, 100] | |
colormap[16] = [0, 80, 100] | |
colormap[17] = [0, 0, 230] | |
colormap[18] = [119, 11, 32] | |
return colormap | |
def create_motchallenge_label_colormap(): | |
"""Creates a label colormap used in MOTChallenge-STEP benchmark. | |
Returns: | |
A colormap for visualizing segmentation results. | |
""" | |
colormap = np.zeros((256, 3), dtype=np.uint8) | |
colormap[0] = [244, 35, 232] | |
colormap[1] = [70, 70, 70] | |
colormap[2] = [107, 142, 35] | |
colormap[3] = [70, 130, 180] | |
colormap[4] = [220, 20, 60] | |
colormap[5] = [255, 0, 0] | |
colormap[6] = [119, 11, 32] | |
return colormap | |
def create_coco_label_colormap(): | |
"""Creates a label colormap used in COCO dataset. | |
Returns: | |
A colormap for visualizing segmentation results. | |
""" | |
# Obtain the dictionary mapping original category id to contiguous ones. | |
coco_categories = coco_constants.get_coco_reduced_meta() | |
colormap = np.zeros((256, 3), dtype=np.uint8) | |
for category in coco_categories: | |
colormap[category['id']] = category['color'] | |
return colormap | |
def label_to_color_image(label, colormap_name='cityscapes'): | |
"""Adds color defined by the colormap derived from the dataset to the label. | |
Args: | |
label: A 2D array with integer type, storing the segmentation label. | |
colormap_name: A string specifying the name of the dataset. Used for | |
choosing the right colormap. Currently supported: 'cityscapes', | |
'motchallenge'. (Default: 'cityscapes') | |
Returns: | |
result: A 2D array with floating type. The element of the array | |
is the color indexed by the corresponding element in the input label | |
to the cityscapes colormap. | |
Raises: | |
ValueError: If label is not of rank 2 or its value is larger than color | |
map maximum entry. | |
""" | |
if label.ndim != 2: | |
raise ValueError('Expect 2-D input label. Got {}'.format(label.shape)) | |
if np.max(label) >= 256: | |
raise ValueError( | |
'label value too large: {} >= 256.'.format(np.max(label))) | |
if colormap_name == 'cityscapes': | |
colormap = create_cityscapes_label_colormap() | |
elif colormap_name == 'motchallenge': | |
colormap = create_motchallenge_label_colormap() | |
elif colormap_name == 'coco': | |
colormap = create_coco_label_colormap() | |
else: | |
raise ValueError('Could not find a colormap for dataset %s.' % | |
colormap_name) | |
return colormap[label] | |
def save_parsing_result(parsing_result, | |
label_divisor, | |
thing_list, | |
save_dir, | |
filename, | |
id_to_colormap=None, | |
colormap_name='cityscapes'): | |
"""Saves the parsing results. | |
The parsing result encodes both semantic segmentation and instance | |
segmentation results. In order to visualize the parsing result with only | |
one png file, we adopt the following procedures, similar to the | |
`visualization.py` provided in the COCO panoptic segmentation evaluation | |
codes. | |
1. Pixels predicted as `stuff` will take the same semantic color defined | |
in the colormap. | |
2. Pixels of a predicted `thing` instance will take similar semantic color | |
defined in the colormap. For example, `car` class takes blue color in | |
the colormap. Predicted car instance 1 will then be colored with the | |
blue color perturbed with a small amount of RGB noise. | |
Args: | |
parsing_result: The numpy array to be saved. The data will be converted | |
to uint8 and saved as png image. | |
label_divisor: Integer, encoding the semantic segmentation and instance | |
segmentation results as value = semantic_label * label_divisor + | |
instance_label. | |
thing_list: A list containing the semantic indices of the thing classes. | |
save_dir: String, the directory to which the results will be saved. | |
filename: String, the image filename. | |
id_to_colormap: An optional mapping from track ID to color. | |
colormap_name: A string specifying the dataset to choose the corresponding | |
color map. Currently supported: 'cityscapes', 'motchallenge'. (Default: | |
'cityscapes'). | |
Raises: | |
ValueError: If parsing_result is not of rank 2 or its value in semantic | |
segmentation result is larger than color map maximum entry. | |
ValueError: If provided colormap_name is not supported. | |
Returns: | |
If id_to_colormap is passed, the updated id_to_colormap will be returned. | |
""" | |
if parsing_result.ndim != 2: | |
raise ValueError('Expect 2-D parsing result. Got {}'.format( | |
parsing_result.shape)) | |
semantic_result = parsing_result // label_divisor | |
instance_result = parsing_result % label_divisor | |
colormap_max_value = 256 | |
if np.max(semantic_result) >= colormap_max_value: | |
raise ValueError('Predicted semantic value too large: {} >= {}.'.format( | |
np.max(semantic_result), colormap_max_value)) | |
height, width = parsing_result.shape | |
colored_output = np.zeros((height, width, 3), dtype=np.uint8) | |
if colormap_name == 'cityscapes': | |
colormap = create_cityscapes_label_colormap() | |
elif colormap_name == 'motchallenge': | |
colormap = create_motchallenge_label_colormap() | |
elif colormap_name == 'coco': | |
colormap = create_coco_label_colormap() | |
else: | |
raise ValueError('Could not find a colormap for dataset %s.' % | |
colormap_name) | |
# Keep track of used colors. | |
used_colors = set() | |
if id_to_colormap is not None: | |
used_colors = set([tuple(val) for val in id_to_colormap.values()]) | |
np_state = None | |
else: | |
# Use random seed 0 in order to reproduce the same visualization. | |
np_state = np.random.RandomState(0) | |
unique_semantic_values = np.unique(semantic_result) | |
for semantic_value in unique_semantic_values: | |
semantic_mask = semantic_result == semantic_value | |
if semantic_value in thing_list: | |
# For `thing` class, we will add a small amount of random noise to its | |
# correspondingly predefined semantic segmentation colormap. | |
unique_instance_values = np.unique(instance_result[semantic_mask]) | |
for instance_value in unique_instance_values: | |
instance_mask = np.logical_and(semantic_mask, | |
instance_result == instance_value) | |
if id_to_colormap is not None: | |
if instance_value in id_to_colormap: | |
colored_output[instance_mask] = id_to_colormap[instance_value] | |
continue | |
random_color = perturb_color( | |
colormap[semantic_value], | |
_COLOR_PERTURBATION, | |
used_colors, | |
random_state=np_state) | |
colored_output[instance_mask] = random_color | |
if id_to_colormap is not None: | |
id_to_colormap[instance_value] = random_color | |
else: | |
# For `stuff` class, we use the defined semantic color. | |
colored_output[semantic_mask] = colormap[semantic_value] | |
used_colors.add(tuple(colormap[semantic_value])) | |
pil_image = PIL.Image.fromarray(colored_output.astype(dtype=np.uint8)) | |
with tf.io.gfile.GFile('{}/{}.png'.format(save_dir, filename), mode='w') as f: | |
pil_image.save(f, 'PNG') | |
if id_to_colormap is not None: | |
return id_to_colormap | |
def perturb_color(color, | |
noise, | |
used_colors=None, | |
max_trials=50, | |
random_state=None): | |
"""Pertrubs the color with some noise. | |
If `used_colors` is not None, we will return the color that has | |
not appeared before in it. | |
Args: | |
color: A numpy array with three elements [R, G, B]. | |
noise: Integer, specifying the amount of perturbing noise. | |
used_colors: A set, used to keep track of used colors. | |
max_trials: An integer, maximum trials to generate random color. | |
random_state: An optional np.random.RandomState. If passed, will be used to | |
generate random numbers. | |
Returns: | |
A perturbed color that has not appeared in used_colors. | |
""" | |
for _ in range(max_trials): | |
if random_state is not None: | |
random_color = color + random_state.randint( | |
low=-noise, high=noise + 1, size=3) | |
else: | |
random_color = color + np.random.randint(low=-noise, | |
high=noise+1, | |
size=3) | |
random_color = np.maximum(0, np.minimum(255, random_color)) | |
if used_colors is None: | |
return random_color | |
elif tuple(random_color) not in used_colors: | |
used_colors.add(tuple(random_color)) | |
return random_color | |
logging.warning('Using duplicate random color.') | |
return random_color | |
def save_annotation(label, | |
save_dir, | |
filename, | |
add_colormap=True, | |
normalize_to_unit_values=False, | |
scale_values=False, | |
colormap_name='cityscapes'): | |
"""Saves the given label to image on disk. | |
Args: | |
label: The numpy array to be saved. The data will be converted | |
to uint8 and saved as png image. | |
save_dir: String, the directory to which the results will be saved. | |
filename: String, the image filename. | |
add_colormap: Boolean, add color map to the label or not. | |
normalize_to_unit_values: Boolean, normalize the input values to [0, 1]. | |
scale_values: Boolean, scale the input values to [0, 255] for visualization. | |
colormap_name: A string specifying the dataset to choose the corresponding | |
color map. Currently supported: 'cityscapes', 'motchallenge'. (Default: | |
'cityscapes'). | |
""" | |
# Add colormap for visualizing the prediction. | |
if add_colormap: | |
colored_label = label_to_color_image(label, colormap_name) | |
else: | |
colored_label = label | |
if normalize_to_unit_values: | |
min_value = np.amin(colored_label) | |
max_value = np.amax(colored_label) | |
range_value = max_value - min_value | |
if range_value != 0: | |
colored_label = (colored_label - min_value) / range_value | |
if scale_values: | |
colored_label = 255. * colored_label | |
pil_image = PIL.Image.fromarray(colored_label.astype(dtype=np.uint8)) | |
with tf.io.gfile.GFile('%s/%s.png' % (save_dir, filename), mode='w') as f: | |
pil_image.save(f, 'PNG') | |