Files
logtd-ComfyUI-InstanceDiffu…/utils/decode_item.py
T
2024-02-23 14:54:01 -07:00

424 lines
15 KiB
Python

# Directly taken from InstanceDiffusion repo
import torch
import random
import base64
import numpy as np
from io import BytesIO
from collections import Counter
from PIL import Image, ImageDraw
import base64
from skimage import measure
# import nltk
# from nltk.corpus import stopwords
def decode_base64_to_pillow(image_b64):
return Image.open(BytesIO(base64.b64decode(image_b64))).convert('RGB')
def decode_tensor_from_string(arr_str, use_tensor=True):
arr = np.frombuffer(base64.b64decode(arr_str), dtype='float32')
if use_tensor:
arr = torch.from_numpy(arr)
return arr
def close_contour(contour):
if not np.array_equal(contour[0], contour[-1]):
contour = np.vstack((contour, contour[0]))
return contour
# convert binay mask to polygon format
def binary_mask_to_polygon(binary_mask, tolerance=0):
"""Converts a binary mask to COCO polygon representation
Args:
binary_mask: a 2D binary numpy array where '1's represent the object
tolerance: Maximum distance from original points of polygon to approximated
polygonal chain. If tolerance is 0, the original coordinate array is returned.
"""
polygons = []
# pad mask to close contours of shapes which start and end at an edge
padded_binary_mask = np.pad(
binary_mask, pad_width=1, mode='constant', constant_values=0)
contours = measure.find_contours(padded_binary_mask, tolerance)
polygons = []
# print(contours)
for contour in contours:
contour = close_contour(contour)
contour = measure.approximate_polygon(contour, tolerance)
if len(contour) < 3:
continue
contour = np.flip(contour, axis=1)
segmentation = contour.ravel().tolist()
# after padding and subtracting 1 we may get -0.5 points in our segmentation
segmentation = [0 if i < 0 else i for i in segmentation]
polygons.append(segmentation)
return polygons
def sample_random_points_from_mask(mask, k):
mask = mask[:, :, 0]
# Find the coordinates of non-zero pixels in the binary mask
nonzero_coords = np.transpose(np.nonzero(mask))
# Randomly sample 'k' points
# return all zeros if there is no non-zero pixel
if len(nonzero_coords) == 0:
xy_points = [0 for _ in range(k * 2)]
return xy_points
# randomly sample with replacement if there are not enough non-zero pixels
if len(nonzero_coords) < k and len(nonzero_coords) > 0:
random_indices = np.random.choice(len(nonzero_coords), k, replace=True)
# randomly sample withiout replacement if there are enough non-zero pixels
else:
random_indices = np.random.choice(
len(nonzero_coords), k, replace=False)
sampled_points = nonzero_coords[random_indices]
# order the points by their distance to (0, 0)
# center = np.array([mask.shape[0] // 2, mask.shape[1] // 2])
center = np.array([0, 0])
sampled_points = sorted(sampled_points, key=lambda x: np.linalg.norm(
np.array(x) - center)) # np.linalg.norm
# concatenate x and y coordinates and return them as a list
# [x1,y1,x2,y2,...,x_k,y_k]
xy_points = []
for x in sampled_points:
xy_points.append(float(x[1]))
xy_points.append(float(x[0]))
return xy_points
# convert numpy array of bool mask to float mask
def binary_mask_to_int(binary_mask):
return binary_mask.astype(np.int32)
# uniformly sample points from the mask
def sample_sparse_points(binary_mask, k, return_2d=False):
# Find the coordinates of non-zero pixels in the binary mask
nonzero_coords = np.array(np.nonzero(binary_mask))
if len(nonzero_coords) == 0:
xy_points = [0 for _ in range(k * 2)]
return xy_points
# Calculate the total number of non-zero pixels
num_nonzero_pixels = len(nonzero_coords)
xy_points = []
if k >= num_nonzero_pixels:
for x in nonzero_coords:
xy_points.append(float(x[1]))
xy_points.append(float(x[0]))
for _ in range(k - num_nonzero_pixels):
xy_points.append(nonzero_coords[-1][1])
xy_points.append(nonzero_coords[-1][0])
return nonzero_coords
# Calculate the number of points to sample in each dimension
num_points_per_dim = int(np.sqrt(k))
# Calculate the step size to ensure equal spacing
step_size = max(1, num_nonzero_pixels // (num_points_per_dim ** 2))
# Sample points with equal spacing
sampled_points = nonzero_coords[::step_size][:k]
if return_2d:
sampled_points = [(x[1], x[0]) for x in sampled_points]
else:
for x in sampled_points:
xy_points.append(float(x[1]))
xy_points.append(float(x[0]))
return xy_points
def sample_uniform_sparse_points(binary_mask, k):
# binary_mask = binary_mask[:,:,0]
# Step 1: Get the indices of '1' values in the binary mask
foreground_indices = np.argwhere(binary_mask == 1)
if len(foreground_indices) == 0:
return []
selected_points = []
if len(foreground_indices) < k:
# randomly sample with replacement if there are not enough non-zero pixels
for i in range(k):
random_point = random.choice(foreground_indices)
selected_points.append((random_point[1], random_point[0]))
else:
# rank the points by their distance to the mean of the foreground_indices
center = np.mean(foreground_indices, axis=0)
# print(center)
foreground_indices = sorted(
foreground_indices, key=lambda x: np.linalg.norm(x - center)) # np.linalg.norm
# Calculate the number of points to select from each segment
points_per_segment = len(foreground_indices) // k
# Step 2: Randomly select one point from each segment
# print(k)
for i in range(k):
segment_points = foreground_indices[i *
points_per_segment: (i + 1) * points_per_segment]
# choose the middle point in each segment
random_point = segment_points[len(segment_points) // 2]
# random_point = random.choice(segment_points)
selected_points.append((random_point[1], random_point[0]))
return selected_points
def sample_sparse_points_from_mask(mask, k):
n_points = k
n_polygons = n_points // 2 # half points should be sampled from the polygons
mask = mask[:, :, 0]
# sample sparse points from the polygons (boundary)
polygons = binary_mask_to_polygon(mask, tolerance=0.0)
# concatenate polygons to a single list
polygons_single = []
for polygon in polygons:
polygons_single += polygon
if len(polygons_single) != 0:
# uniformly sample points from the polygon
polygons_single = np.array(polygons_single).reshape(-1, 2)
indexes = np.linspace(0, polygons_single.shape[0] - 1, n_polygons)
indexes = list([int(i) for i in indexes])
polygons_single = polygons_single[indexes]
sampled_polygons = [(x[0], x[1]) for x in polygons_single]
else:
return None
# sample sparse points from the mask
n_inside_points = n_points - len(sampled_polygons)
inside_points = sample_uniform_sparse_points(mask, n_inside_points)
# combine inside_points and sampled_polygons
xy_points = inside_points + sampled_polygons
# order the points by their distance to (0, 0)
center = np.array([0, 0])
xy_points = sorted(xy_points, key=lambda x: np.linalg.norm(
np.array(x) - center)) # np.linalg.norm
# return the sampled points
sampled_points = []
for x in xy_points:
sampled_points.append(x[0])
sampled_points.append(x[1])
return sampled_points
def get_polygons_from_mask(mask, tolerance=0, n_polygon_points=256):
mask = binary_mask_to_int(mask)
return_polygons = True
if return_polygons:
# convert float mask to polygons
polygons = binary_mask_to_polygon(mask[:, :, 0], tolerance=tolerance)
# return all zeros if there is no polygon
if len(polygons) == 0:
polygons = [0 for _ in range(n_polygon_points * 2)]
return polygons
# concatenate polygons to a single list
polygon = []
for p in polygons:
polygon += p
# uniformly sample points the polygon
polygon = np.array(polygon).reshape(-1, 2)
indexes = np.linspace(0, polygon.shape[0] - 1, n_polygon_points)
indexes = [int(i) for i in indexes]
polygon = polygon[indexes].reshape(-1)
return polygon
else:
sampled_points = sample_sparse_points(mask, n_polygon_points)
return sampled_points
def decode_item(item):
# convert string to dict
if "image" in item and isinstance(item['image'], Image.Image):
return item
item['image'] = decode_base64_to_pillow(item['image'])
segs = []
for anno in item['annos']:
anno['image_embedding_before'] = decode_tensor_from_string(
anno['image_embedding_before'])
anno['text_embedding_before'] = decode_tensor_from_string(
anno['text_embedding_before'])
anno['image_embedding_after'] = decode_tensor_from_string(
anno['image_embedding_after'])
anno['text_embedding_after'] = decode_tensor_from_string(
anno['text_embedding_after'])
if "blip_clip_embeddings" in anno:
anno['blip_clip_embeddings'] = decode_tensor_from_string(
anno['blip_clip_embeddings'])
if 'mask' in anno:
# sample k random points from the mask
n_scribble_points = 20
rle = anno['mask']
binary_mask = decodeToBinaryMask(rle)
segs.append(binary_mask)
if "scribbles" in anno:
anno['scribbles'] = anno["scribbles"]
else:
anno['scribbles'] = sample_random_points_from_mask(
binary_mask, n_scribble_points)
# convert mask to polygon
n_polygon_points = 256
polygons = sample_sparse_points_from_mask(
binary_mask, k=n_polygon_points)
if polygons != None:
anno['polygons'] = polygons
else:
anno['polygons'] = [0 for _ in range(n_polygon_points * 2)]
if len(segs) > 0:
item['segs'] = np.stack(segs).astype(np.float32).squeeze()
return item
def check_unique(images, fields):
for field in fields:
temp_list = []
for img_info in images:
temp_list.append(img_info[field])
assert len(set(temp_list)) == len(temp_list), field
def clean_data(data):
for data_info in data:
data_info.pop("original_img_id", None)
data_info.pop("original_id", None)
# sentence id for each image (multiple sentences for one image)
data_info.pop("sentence_id", None)
data_info.pop("dataset_name", None)
data_info.pop("data_source", None)
data_info["data_id"] = data_info.pop("id")
def clean_annotations(annotations):
for anno_info in annotations:
anno_info.pop("iscrowd", None)
anno_info.pop("category_id", None)
anno_info.pop("area", None)
anno_info["data_id"] = anno_info.pop("image_id")
def draw_box(img, boxes):
draw = ImageDraw.Draw(img)
for box in boxes:
draw.rectangle([box[0], box[1], box[2], box[3]],
outline="red", width=2) # x0 y0 x1 y1
return img
def xyhw2xyxy(box):
x0, y0, w, h = box
return [x0, y0, x0 + w, y0 + h]
def make_a_sentence_count_nums(obj_names):
# count the number of duplicated strings in the list
# ["dog", "dog", "cat"]
obj_names = dict(Counter(obj_names))
# {'dog': 2, 'cat': 1}
caption = ""
for item in obj_names:
caption += str(obj_names[item]) + " " + item + ", "
return caption[:-2]
def make_a_sentence(obj_names, clean=False):
if clean:
obj_names = [name[:-6] if ("-other" in name)
else name for name in obj_names]
caption = ""
tokens_positive = []
for obj_name in obj_names:
start_len = len(caption)
caption += obj_name
end_len = len(caption)
caption += ", "
tokens_positive.append(
# in real caption, positive tokens can be disjoint, thus using list of list
[[start_len, end_len]]
)
caption = caption[:-2] # remove last ", "
return caption # , tokens_positive
def mask_for_random_drop_text_or_image_feature(masks, random_drop_embedding):
"""
input masks tell how many valid grounding tokens for this image
e.g., 1,1,1,1,0,0,0,0,0,0...
If random_drop_embedding=both. we will random drop either image or
text feature for each token,
but we always make sure there is at least one feature used.
In other words, the following masks are not valid
(because for the second obj, no feature at all):
image: 1,0,1,1,0,0,0,0,0
text: 1,0,0,0,0,0,0,0,0
if random_drop_embedding=image. we will random drop image feature
and always keep the text one.
"""
N = masks.shape[0]
if random_drop_embedding == 'both':
temp_mask = torch.ones(2, N)
for i in range(N):
if random.uniform(0, 1) < 0.5: # else keep both features
# randomly choose to drop image or text feature
idx = random.sample([0, 1], 1)[0]
temp_mask[idx, i] = 0
image_masks = temp_mask[0] * masks
text_masks = temp_mask[1] * masks
if random_drop_embedding == 'image':
image_masks = masks * (torch.rand(N) > 0.5) * 1
text_masks = masks
return image_masks, text_masks
def project(x, projection_matrix):
"""
x (Batch*768) should be the penultimate feature of CLIP (before projection)
projection_matrix (768*768) is the CLIP projection matrix, which should be weight.data of Linear layer
defined in CLIP (out_dim, in_dim), thus we need to apply transpose below.
this function will return the CLIP feature (without normalziation)
"""
return x @ torch.transpose(projection_matrix, 0, 1)
def inv_project(y, projection_matrix):
"""
y (Batch*768) should be the CLIP feature (after projection)
projection_matrix (768*768) is the CLIP projection matrix, which should be weight.data of Linear layer
defined in CLIP (out_dim, in_dim).
this function will return the CLIP penultimate feature.
Note: to make sure getting the correct penultimate feature, the input y should not be normalized.
If it is normalized, then the result will be scaled by CLIP feature norm, which is unknown.
"""
return y @ torch.transpose(torch.linalg.inv(projection_matrix), 0, 1)