Spaces:
Running
Running
""" | |
Code adapted from https://github.com/rpautrat/SuperPoint | |
Module used to generate geometrical synthetic shapes | |
""" | |
import math | |
import cv2 as cv | |
import numpy as np | |
import shapely.geometry | |
from itertools import combinations | |
random_state = np.random.RandomState(None) | |
def set_random_state(state): | |
global random_state | |
random_state = state | |
def get_random_color(background_color): | |
"""Output a random scalar in grayscale with a least a small contrast | |
with the background color.""" | |
color = random_state.randint(256) | |
if abs(color - background_color) < 30: # not enough contrast | |
color = (color + 128) % 256 | |
return color | |
def get_different_color(previous_colors, min_dist=50, max_count=20): | |
"""Output a color that contrasts with the previous colors. | |
Parameters: | |
previous_colors: np.array of the previous colors | |
min_dist: the difference between the new color and | |
the previous colors must be at least min_dist | |
max_count: maximal number of iterations | |
""" | |
color = random_state.randint(256) | |
count = 0 | |
while np.any(np.abs(previous_colors - color) < min_dist) and count < max_count: | |
count += 1 | |
color = random_state.randint(256) | |
return color | |
def add_salt_and_pepper(img): | |
"""Add salt and pepper noise to an image.""" | |
noise = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8) | |
cv.randu(noise, 0, 255) | |
black = noise < 30 | |
white = noise > 225 | |
img[white > 0] = 255 | |
img[black > 0] = 0 | |
cv.blur(img, (5, 5), img) | |
return np.empty((0, 2), dtype=np.int) | |
def generate_background( | |
size=(960, 1280), | |
nb_blobs=100, | |
min_rad_ratio=0.01, | |
max_rad_ratio=0.05, | |
min_kernel_size=50, | |
max_kernel_size=300, | |
): | |
"""Generate a customized background image. | |
Parameters: | |
size: size of the image | |
nb_blobs: number of circles to draw | |
min_rad_ratio: the radius of blobs is at least min_rad_size * max(size) | |
max_rad_ratio: the radius of blobs is at most max_rad_size * max(size) | |
min_kernel_size: minimal size of the kernel | |
max_kernel_size: maximal size of the kernel | |
""" | |
img = np.zeros(size, dtype=np.uint8) | |
dim = max(size) | |
cv.randu(img, 0, 255) | |
cv.threshold(img, random_state.randint(256), 255, cv.THRESH_BINARY, img) | |
background_color = int(np.mean(img)) | |
blobs = np.concatenate( | |
[ | |
random_state.randint(0, size[1], size=(nb_blobs, 1)), | |
random_state.randint(0, size[0], size=(nb_blobs, 1)), | |
], | |
axis=1, | |
) | |
for i in range(nb_blobs): | |
col = get_random_color(background_color) | |
cv.circle( | |
img, | |
(blobs[i][0], blobs[i][1]), | |
np.random.randint(int(dim * min_rad_ratio), int(dim * max_rad_ratio)), | |
col, | |
-1, | |
) | |
kernel_size = random_state.randint(min_kernel_size, max_kernel_size) | |
cv.blur(img, (kernel_size, kernel_size), img) | |
return img | |
def generate_custom_background( | |
size, background_color, nb_blobs=3000, kernel_boundaries=(50, 100) | |
): | |
"""Generate a customized background to fill the shapes. | |
Parameters: | |
background_color: average color of the background image | |
nb_blobs: number of circles to draw | |
kernel_boundaries: interval of the possible sizes of the kernel | |
""" | |
img = np.zeros(size, dtype=np.uint8) | |
img = img + get_random_color(background_color) | |
blobs = np.concatenate( | |
[ | |
np.random.randint(0, size[1], size=(nb_blobs, 1)), | |
np.random.randint(0, size[0], size=(nb_blobs, 1)), | |
], | |
axis=1, | |
) | |
for i in range(nb_blobs): | |
col = get_random_color(background_color) | |
cv.circle(img, (blobs[i][0], blobs[i][1]), np.random.randint(20), col, -1) | |
kernel_size = np.random.randint(kernel_boundaries[0], kernel_boundaries[1]) | |
cv.blur(img, (kernel_size, kernel_size), img) | |
return img | |
def final_blur(img, kernel_size=(5, 5)): | |
"""Gaussian blur applied to an image. | |
Parameters: | |
kernel_size: size of the kernel | |
""" | |
cv.GaussianBlur(img, kernel_size, 0, img) | |
def ccw(A, B, C, dim): | |
"""Check if the points are listed in counter-clockwise order.""" | |
if dim == 2: # only 2 dimensions | |
return (C[:, 1] - A[:, 1]) * (B[:, 0] - A[:, 0]) > (B[:, 1] - A[:, 1]) * ( | |
C[:, 0] - A[:, 0] | |
) | |
else: # dim should be equal to 3 | |
return (C[:, 1, :] - A[:, 1, :]) * (B[:, 0, :] - A[:, 0, :]) > ( | |
B[:, 1, :] - A[:, 1, :] | |
) * (C[:, 0, :] - A[:, 0, :]) | |
def intersect(A, B, C, D, dim): | |
"""Return true if line segments AB and CD intersect""" | |
return np.any( | |
(ccw(A, C, D, dim) != ccw(B, C, D, dim)) | |
& (ccw(A, B, C, dim) != ccw(A, B, D, dim)) | |
) | |
def keep_points_inside(points, size): | |
"""Keep only the points whose coordinates are inside the dimensions of | |
the image of size 'size'""" | |
mask = ( | |
(points[:, 0] >= 0) | |
& (points[:, 0] < size[1]) | |
& (points[:, 1] >= 0) | |
& (points[:, 1] < size[0]) | |
) | |
return points[mask, :] | |
def get_unique_junctions(segments, min_label_len): | |
"""Get unique junction points from line segments.""" | |
# Get all junctions from segments | |
junctions_all = np.concatenate((segments[:, :2], segments[:, 2:]), axis=0) | |
if junctions_all.shape[0] == 0: | |
junc_points = None | |
line_map = None | |
# Get all unique junction points | |
else: | |
junc_points = np.unique(junctions_all, axis=0) | |
# Generate line map from points and segments | |
line_map = get_line_map(junc_points, segments) | |
return junc_points, line_map | |
def get_line_map(points: np.ndarray, segments: np.ndarray) -> np.ndarray: | |
"""Get line map given the points and segment sets.""" | |
# create empty line map | |
num_point = points.shape[0] | |
line_map = np.zeros([num_point, num_point]) | |
# Iterate through every segment | |
for idx in range(segments.shape[0]): | |
# Get the junctions from a single segement | |
seg = segments[idx, :] | |
junction1 = seg[:2] | |
junction2 = seg[2:] | |
# Get index | |
idx_junction1 = np.where((points == junction1).sum(axis=1) == 2)[0] | |
idx_junction2 = np.where((points == junction2).sum(axis=1) == 2)[0] | |
# label the corresponding entries | |
line_map[idx_junction1, idx_junction2] = 1 | |
line_map[idx_junction2, idx_junction1] = 1 | |
return line_map | |
def get_line_heatmap(junctions, line_map, size=[480, 640], thickness=1): | |
"""Get line heat map from junctions and line map.""" | |
# Make sure that the thickness is 1 | |
if not isinstance(thickness, int): | |
thickness = int(thickness) | |
# If the junction points are not int => round them and convert to int | |
if not junctions.dtype == np.int: | |
junctions = (np.round(junctions)).astype(np.int) | |
# Initialize empty map | |
heat_map = np.zeros(size) | |
if junctions.shape[0] > 0: # If empty, just return zero map | |
# Iterate through all the junctions | |
for idx in range(junctions.shape[0]): | |
# if no connectivity, just skip it | |
if line_map[idx, :].sum() == 0: | |
continue | |
# Plot the line segment | |
else: | |
# Iterate through all the connected junctions | |
for idx2 in np.where(line_map[idx, :] == 1)[0]: | |
point1 = junctions[idx, :] | |
point2 = junctions[idx2, :] | |
# Draw line | |
cv.line(heat_map, tuple(point1), tuple(point2), 1.0, thickness) | |
return heat_map | |
def draw_lines(img, nb_lines=10, min_len=32, min_label_len=32): | |
"""Draw random lines and output the positions of the pair of junctions | |
and line associativities. | |
Parameters: | |
nb_lines: maximal number of lines | |
""" | |
# Set line number and points placeholder | |
num_lines = random_state.randint(1, nb_lines) | |
segments = np.empty((0, 4), dtype=np.int) | |
points = np.empty((0, 2), dtype=np.int) | |
background_color = int(np.mean(img)) | |
min_dim = min(img.shape) | |
# Convert length constrain to pixel if given float number | |
if isinstance(min_len, float) and min_len <= 1.0: | |
min_len = int(min_dim * min_len) | |
if isinstance(min_label_len, float) and min_label_len <= 1.0: | |
min_label_len = int(min_dim * min_label_len) | |
# Generate lines one by one | |
for i in range(num_lines): | |
x1 = random_state.randint(img.shape[1]) | |
y1 = random_state.randint(img.shape[0]) | |
p1 = np.array([[x1, y1]]) | |
x2 = random_state.randint(img.shape[1]) | |
y2 = random_state.randint(img.shape[0]) | |
p2 = np.array([[x2, y2]]) | |
# Check the length of the line | |
line_length = np.sqrt(np.sum((p1 - p2) ** 2)) | |
if line_length < min_len: | |
continue | |
# Check that there is no overlap | |
if intersect(segments[:, 0:2], segments[:, 2:4], p1, p2, 2): | |
continue | |
col = get_random_color(background_color) | |
thickness = random_state.randint(min_dim * 0.01, min_dim * 0.02) | |
cv.line(img, (x1, y1), (x2, y2), col, thickness) | |
# Only record the segments longer than min_label_len | |
seg_len = math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2) | |
if seg_len >= min_label_len: | |
segments = np.concatenate([segments, np.array([[x1, y1, x2, y2]])], axis=0) | |
points = np.concatenate([points, np.array([[x1, y1], [x2, y2]])], axis=0) | |
# If no line is drawn, recursively call the function | |
if points.shape[0] == 0: | |
return draw_lines(img, nb_lines, min_len, min_label_len) | |
# Get the line associativity map | |
line_map = get_line_map(points, segments) | |
return {"points": points, "line_map": line_map} | |
def check_segment_len(segments, min_len=32): | |
"""Check if one of the segments is too short (True means too short).""" | |
point1_vec = segments[:, :2] | |
point2_vec = segments[:, 2:] | |
diff = point1_vec - point2_vec | |
dist = np.sqrt(np.sum(diff**2, axis=1)) | |
if np.any(dist < min_len): | |
return True | |
else: | |
return False | |
def draw_polygon(img, max_sides=8, min_len=32, min_label_len=64): | |
"""Draw a polygon with a random number of corners and return the position | |
of the junctions + line map. | |
Parameters: | |
max_sides: maximal number of sides + 1 | |
""" | |
num_corners = random_state.randint(3, max_sides) | |
min_dim = min(img.shape[0], img.shape[1]) | |
rad = max(random_state.rand() * min_dim / 2, min_dim / 10) | |
# Center of a circle | |
x = random_state.randint(rad, img.shape[1] - rad) | |
y = random_state.randint(rad, img.shape[0] - rad) | |
# Convert length constrain to pixel if given float number | |
if isinstance(min_len, float) and min_len <= 1.0: | |
min_len = int(min_dim * min_len) | |
if isinstance(min_label_len, float) and min_label_len <= 1.0: | |
min_label_len = int(min_dim * min_label_len) | |
# Sample num_corners points inside the circle | |
slices = np.linspace(0, 2 * math.pi, num_corners + 1) | |
angles = [ | |
slices[i] + random_state.rand() * (slices[i + 1] - slices[i]) | |
for i in range(num_corners) | |
] | |
points = np.array( | |
[ | |
[ | |
int(x + max(random_state.rand(), 0.4) * rad * math.cos(a)), | |
int(y + max(random_state.rand(), 0.4) * rad * math.sin(a)), | |
] | |
for a in angles | |
] | |
) | |
# Filter the points that are too close or that have an angle too flat | |
norms = [ | |
np.linalg.norm(points[(i - 1) % num_corners, :] - points[i, :]) | |
for i in range(num_corners) | |
] | |
mask = np.array(norms) > 0.01 | |
points = points[mask, :] | |
num_corners = points.shape[0] | |
corner_angles = [ | |
angle_between_vectors( | |
points[(i - 1) % num_corners, :] - points[i, :], | |
points[(i + 1) % num_corners, :] - points[i, :], | |
) | |
for i in range(num_corners) | |
] | |
mask = np.array(corner_angles) < (2 * math.pi / 3) | |
points = points[mask, :] | |
num_corners = points.shape[0] | |
# Get junction pairs from points | |
segments = np.zeros([0, 4]) | |
# Used to record all the segments no matter we are going to label it or not. | |
segments_raw = np.zeros([0, 4]) | |
for idx in range(num_corners): | |
if idx == (num_corners - 1): | |
p1 = points[idx] | |
p2 = points[0] | |
else: | |
p1 = points[idx] | |
p2 = points[idx + 1] | |
segment = np.concatenate((p1, p2), axis=0) | |
# Only record the segments longer than min_label_len | |
seg_len = np.sqrt(np.sum((p1 - p2) ** 2)) | |
if seg_len >= min_label_len: | |
segments = np.concatenate((segments, segment[None, ...]), axis=0) | |
segments_raw = np.concatenate((segments_raw, segment[None, ...]), axis=0) | |
# If not enough corner, just regenerate one | |
if (num_corners < 3) or check_segment_len(segments_raw, min_len): | |
return draw_polygon(img, max_sides, min_len, min_label_len) | |
# Get junctions from segments | |
junctions_all = np.concatenate((segments[:, :2], segments[:, 2:]), axis=0) | |
if junctions_all.shape[0] == 0: | |
junc_points = None | |
line_map = None | |
else: | |
junc_points = np.unique(junctions_all, axis=0) | |
# Get the line map | |
line_map = get_line_map(junc_points, segments) | |
corners = points.reshape((-1, 1, 2)) | |
col = get_random_color(int(np.mean(img))) | |
cv.fillPoly(img, [corners], col) | |
return {"points": junc_points, "line_map": line_map} | |
def overlap(center, rad, centers, rads): | |
"""Check that the circle with (center, rad) | |
doesn't overlap with the other circles.""" | |
flag = False | |
for i in range(len(rads)): | |
if np.linalg.norm(center - centers[i]) < rad + rads[i]: | |
flag = True | |
break | |
return flag | |
def angle_between_vectors(v1, v2): | |
"""Compute the angle (in rad) between the two vectors v1 and v2.""" | |
v1_u = v1 / np.linalg.norm(v1) | |
v2_u = v2 / np.linalg.norm(v2) | |
return np.arccos(np.clip(np.dot(v1_u, v2_u), -1.0, 1.0)) | |
def draw_multiple_polygons( | |
img, | |
max_sides=8, | |
nb_polygons=30, | |
min_len=32, | |
min_label_len=64, | |
safe_margin=5, | |
**extra | |
): | |
"""Draw multiple polygons with a random number of corners | |
and return the junction points + line map. | |
Parameters: | |
max_sides: maximal number of sides + 1 | |
nb_polygons: maximal number of polygons | |
""" | |
segments = np.empty((0, 4), dtype=np.int) | |
label_segments = np.empty((0, 4), dtype=np.int) | |
centers = [] | |
rads = [] | |
points = np.empty((0, 2), dtype=np.int) | |
background_color = int(np.mean(img)) | |
min_dim = min(img.shape[0], img.shape[1]) | |
# Convert length constrain to pixel if given float number | |
if isinstance(min_len, float) and min_len <= 1.0: | |
min_len = int(min_dim * min_len) | |
if isinstance(min_label_len, float) and min_label_len <= 1.0: | |
min_label_len = int(min_dim * min_label_len) | |
if isinstance(safe_margin, float) and safe_margin <= 1.0: | |
safe_margin = int(min_dim * safe_margin) | |
# Sequentially generate polygons | |
for i in range(nb_polygons): | |
num_corners = random_state.randint(3, max_sides) | |
min_dim = min(img.shape[0], img.shape[1]) | |
# Also add the real radius | |
rad = max(random_state.rand() * min_dim / 2, min_dim / 9) | |
rad_real = rad - safe_margin | |
# Center of a circle | |
x = random_state.randint(rad, img.shape[1] - rad) | |
y = random_state.randint(rad, img.shape[0] - rad) | |
# Sample num_corners points inside the circle | |
slices = np.linspace(0, 2 * math.pi, num_corners + 1) | |
angles = [ | |
slices[i] + random_state.rand() * (slices[i + 1] - slices[i]) | |
for i in range(num_corners) | |
] | |
# Sample outer points and inner points | |
new_points = [] | |
new_points_real = [] | |
for a in angles: | |
x_offset = max(random_state.rand(), 0.4) | |
y_offset = max(random_state.rand(), 0.4) | |
new_points.append( | |
[ | |
int(x + x_offset * rad * math.cos(a)), | |
int(y + y_offset * rad * math.sin(a)), | |
] | |
) | |
new_points_real.append( | |
[ | |
int(x + x_offset * rad_real * math.cos(a)), | |
int(y + y_offset * rad_real * math.sin(a)), | |
] | |
) | |
new_points = np.array(new_points) | |
new_points_real = np.array(new_points_real) | |
# Filter the points that are too close or that have an angle too flat | |
norms = [ | |
np.linalg.norm(new_points[(i - 1) % num_corners, :] - new_points[i, :]) | |
for i in range(num_corners) | |
] | |
mask = np.array(norms) > 0.01 | |
new_points = new_points[mask, :] | |
new_points_real = new_points_real[mask, :] | |
num_corners = new_points.shape[0] | |
corner_angles = [ | |
angle_between_vectors( | |
new_points[(i - 1) % num_corners, :] - new_points[i, :], | |
new_points[(i + 1) % num_corners, :] - new_points[i, :], | |
) | |
for i in range(num_corners) | |
] | |
mask = np.array(corner_angles) < (2 * math.pi / 3) | |
new_points = new_points[mask, :] | |
new_points_real = new_points_real[mask, :] | |
num_corners = new_points.shape[0] | |
# Not enough corners | |
if num_corners < 3: | |
continue | |
# Segments for checking overlap (outer circle) | |
new_segments = np.zeros((1, 4, num_corners)) | |
new_segments[:, 0, :] = [new_points[i][0] for i in range(num_corners)] | |
new_segments[:, 1, :] = [new_points[i][1] for i in range(num_corners)] | |
new_segments[:, 2, :] = [ | |
new_points[(i + 1) % num_corners][0] for i in range(num_corners) | |
] | |
new_segments[:, 3, :] = [ | |
new_points[(i + 1) % num_corners][1] for i in range(num_corners) | |
] | |
# Segments to record (inner circle) | |
new_segments_real = np.zeros((1, 4, num_corners)) | |
new_segments_real[:, 0, :] = [new_points_real[i][0] for i in range(num_corners)] | |
new_segments_real[:, 1, :] = [new_points_real[i][1] for i in range(num_corners)] | |
new_segments_real[:, 2, :] = [ | |
new_points_real[(i + 1) % num_corners][0] for i in range(num_corners) | |
] | |
new_segments_real[:, 3, :] = [ | |
new_points_real[(i + 1) % num_corners][1] for i in range(num_corners) | |
] | |
# Check that the polygon will not overlap with pre-existing shapes | |
if intersect( | |
segments[:, 0:2, None], | |
segments[:, 2:4, None], | |
new_segments[:, 0:2, :], | |
new_segments[:, 2:4, :], | |
3, | |
) or overlap(np.array([x, y]), rad, centers, rads): | |
continue | |
# Check that the the edges of the polygon is not too short | |
if check_segment_len(new_segments_real, min_len): | |
continue | |
# If the polygon is valid, append it to the polygon set | |
centers.append(np.array([x, y])) | |
rads.append(rad) | |
new_segments = np.reshape(np.swapaxes(new_segments, 0, 2), (-1, 4)) | |
segments = np.concatenate([segments, new_segments], axis=0) | |
# Only record the segments longer than min_label_len | |
new_segments_real = np.reshape(np.swapaxes(new_segments_real, 0, 2), (-1, 4)) | |
points1 = new_segments_real[:, :2] | |
points2 = new_segments_real[:, 2:] | |
seg_len = np.sqrt(np.sum((points1 - points2) ** 2, axis=1)) | |
new_label_segment = new_segments_real[seg_len >= min_label_len, :] | |
label_segments = np.concatenate([label_segments, new_label_segment], axis=0) | |
# Color the polygon with a custom background | |
corners = new_points_real.reshape((-1, 1, 2)) | |
mask = np.zeros(img.shape, np.uint8) | |
custom_background = generate_custom_background( | |
img.shape, background_color, **extra | |
) | |
cv.fillPoly(mask, [corners], 255) | |
locs = np.where(mask != 0) | |
img[locs[0], locs[1]] = custom_background[locs[0], locs[1]] | |
points = np.concatenate([points, new_points], axis=0) | |
# Get all junctions from label segments | |
junctions_all = np.concatenate( | |
(label_segments[:, :2], label_segments[:, 2:]), axis=0 | |
) | |
if junctions_all.shape[0] == 0: | |
junc_points = None | |
line_map = None | |
else: | |
junc_points = np.unique(junctions_all, axis=0) | |
# Generate line map from points and segments | |
line_map = get_line_map(junc_points, label_segments) | |
return {"points": junc_points, "line_map": line_map} | |
def draw_ellipses(img, nb_ellipses=20): | |
"""Draw several ellipses. | |
Parameters: | |
nb_ellipses: maximal number of ellipses | |
""" | |
centers = np.empty((0, 2), dtype=np.int) | |
rads = np.empty((0, 1), dtype=np.int) | |
min_dim = min(img.shape[0], img.shape[1]) / 4 | |
background_color = int(np.mean(img)) | |
for i in range(nb_ellipses): | |
ax = int(max(random_state.rand() * min_dim, min_dim / 5)) | |
ay = int(max(random_state.rand() * min_dim, min_dim / 5)) | |
max_rad = max(ax, ay) | |
x = random_state.randint(max_rad, img.shape[1] - max_rad) # center | |
y = random_state.randint(max_rad, img.shape[0] - max_rad) | |
new_center = np.array([[x, y]]) | |
# Check that the ellipsis will not overlap with pre-existing shapes | |
diff = centers - new_center | |
if np.any(max_rad > (np.sqrt(np.sum(diff * diff, axis=1)) - rads)): | |
continue | |
centers = np.concatenate([centers, new_center], axis=0) | |
rads = np.concatenate([rads, np.array([[max_rad]])], axis=0) | |
col = get_random_color(background_color) | |
angle = random_state.rand() * 90 | |
cv.ellipse(img, (x, y), (ax, ay), angle, 0, 360, col, -1) | |
return np.empty((0, 2), dtype=np.int) | |
def draw_star(img, nb_branches=6, min_len=32, min_label_len=64): | |
"""Draw a star and return the junction points + line map. | |
Parameters: | |
nb_branches: number of branches of the star | |
""" | |
num_branches = random_state.randint(3, nb_branches) | |
min_dim = min(img.shape[0], img.shape[1]) | |
# Convert length constrain to pixel if given float number | |
if isinstance(min_len, float) and min_len <= 1.0: | |
min_len = int(min_dim * min_len) | |
if isinstance(min_label_len, float) and min_label_len <= 1.0: | |
min_label_len = int(min_dim * min_label_len) | |
thickness = random_state.randint(min_dim * 0.01, min_dim * 0.025) | |
rad = max(random_state.rand() * min_dim / 2, min_dim / 5) | |
x = random_state.randint(rad, img.shape[1] - rad) | |
y = random_state.randint(rad, img.shape[0] - rad) | |
# Sample num_branches points inside the circle | |
slices = np.linspace(0, 2 * math.pi, num_branches + 1) | |
angles = [ | |
slices[i] + random_state.rand() * (slices[i + 1] - slices[i]) | |
for i in range(num_branches) | |
] | |
points = np.array( | |
[ | |
[ | |
int(x + max(random_state.rand(), 0.3) * rad * math.cos(a)), | |
int(y + max(random_state.rand(), 0.3) * rad * math.sin(a)), | |
] | |
for a in angles | |
] | |
) | |
points = np.concatenate(([[x, y]], points), axis=0) | |
# Generate segments and check the length | |
segments = np.array([[x, y, _[0], _[1]] for _ in points[1:, :]]) | |
if check_segment_len(segments, min_len): | |
return draw_star(img, nb_branches, min_len, min_label_len) | |
# Only record the segments longer than min_label_len | |
points1 = segments[:, :2] | |
points2 = segments[:, 2:] | |
seg_len = np.sqrt(np.sum((points1 - points2) ** 2, axis=1)) | |
label_segments = segments[seg_len >= min_label_len, :] | |
# Get all junctions from label segments | |
junctions_all = np.concatenate( | |
(label_segments[:, :2], label_segments[:, 2:]), axis=0 | |
) | |
if junctions_all.shape[0] == 0: | |
junc_points = None | |
line_map = None | |
# Get all unique junction points | |
else: | |
junc_points = np.unique(junctions_all, axis=0) | |
# Generate line map from points and segments | |
line_map = get_line_map(junc_points, label_segments) | |
background_color = int(np.mean(img)) | |
for i in range(1, num_branches + 1): | |
col = get_random_color(background_color) | |
cv.line( | |
img, | |
(points[0][0], points[0][1]), | |
(points[i][0], points[i][1]), | |
col, | |
thickness, | |
) | |
return {"points": junc_points, "line_map": line_map} | |
def draw_checkerboard_multiseg( | |
img, | |
max_rows=7, | |
max_cols=7, | |
transform_params=(0.05, 0.15), | |
min_label_len=64, | |
seed=None, | |
): | |
"""Draw a checkerboard and output the junctions + line segments | |
Parameters: | |
max_rows: maximal number of rows + 1 | |
max_cols: maximal number of cols + 1 | |
transform_params: set the range of the parameters of the transformations | |
""" | |
if seed is None: | |
global random_state | |
else: | |
random_state = np.random.RandomState(seed) | |
background_color = int(np.mean(img)) | |
min_dim = min(img.shape) | |
if isinstance(min_label_len, float) and min_label_len <= 1.0: | |
min_label_len = int(min_dim * min_label_len) | |
# Create the grid | |
rows = random_state.randint(3, max_rows) # number of rows | |
cols = random_state.randint(3, max_cols) # number of cols | |
s = min((img.shape[1] - 1) // cols, (img.shape[0] - 1) // rows) | |
x_coord = np.tile(range(cols + 1), rows + 1).reshape(((rows + 1) * (cols + 1), 1)) | |
y_coord = np.repeat(range(rows + 1), cols + 1).reshape(((rows + 1) * (cols + 1), 1)) | |
# points are the grid coordinates | |
points = s * np.concatenate([x_coord, y_coord], axis=1) | |
# Warp the grid using an affine transformation and an homography | |
alpha_affine = np.max(img.shape) * ( | |
transform_params[0] + random_state.rand() * transform_params[1] | |
) | |
center_square = np.float32(img.shape) // 2 | |
min_dim = min(img.shape) | |
square_size = min_dim // 3 | |
pts1 = np.float32( | |
[ | |
center_square + square_size, | |
[center_square[0] + square_size, center_square[1] - square_size], | |
center_square - square_size, | |
[center_square[0] - square_size, center_square[1] + square_size], | |
] | |
) | |
pts2 = pts1 + random_state.uniform( | |
-alpha_affine, alpha_affine, size=pts1.shape | |
).astype(np.float32) | |
affine_transform = cv.getAffineTransform(pts1[:3], pts2[:3]) | |
pts2 = pts1 + random_state.uniform( | |
-alpha_affine / 2, alpha_affine / 2, size=pts1.shape | |
).astype(np.float32) | |
perspective_transform = cv.getPerspectiveTransform(pts1, pts2) | |
# Apply the affine transformation | |
points = np.transpose( | |
np.concatenate((points, np.ones(((rows + 1) * (cols + 1), 1))), axis=1) | |
) | |
warped_points = np.transpose(np.dot(affine_transform, points)) | |
# Apply the homography | |
warped_col0 = np.add( | |
np.sum(np.multiply(warped_points, perspective_transform[0, :2]), axis=1), | |
perspective_transform[0, 2], | |
) | |
warped_col1 = np.add( | |
np.sum(np.multiply(warped_points, perspective_transform[1, :2]), axis=1), | |
perspective_transform[1, 2], | |
) | |
warped_col2 = np.add( | |
np.sum(np.multiply(warped_points, perspective_transform[2, :2]), axis=1), | |
perspective_transform[2, 2], | |
) | |
warped_col0 = np.divide(warped_col0, warped_col2) | |
warped_col1 = np.divide(warped_col1, warped_col2) | |
warped_points = np.concatenate([warped_col0[:, None], warped_col1[:, None]], axis=1) | |
warped_points_float = warped_points.copy() | |
warped_points = warped_points.astype(int) | |
# Fill the rectangles | |
colors = np.zeros((rows * cols,), np.int32) | |
for i in range(rows): | |
for j in range(cols): | |
# Get a color that contrast with the neighboring cells | |
if i == 0 and j == 0: | |
col = get_random_color(background_color) | |
else: | |
neighboring_colors = [] | |
if i != 0: | |
neighboring_colors.append(colors[(i - 1) * cols + j]) | |
if j != 0: | |
neighboring_colors.append(colors[i * cols + j - 1]) | |
col = get_different_color(np.array(neighboring_colors)) | |
colors[i * cols + j] = col | |
# Fill the cell | |
cv.fillConvexPoly( | |
img, | |
np.array( | |
[ | |
( | |
warped_points[i * (cols + 1) + j, 0], | |
warped_points[i * (cols + 1) + j, 1], | |
), | |
( | |
warped_points[i * (cols + 1) + j + 1, 0], | |
warped_points[i * (cols + 1) + j + 1, 1], | |
), | |
( | |
warped_points[(i + 1) * (cols + 1) + j + 1, 0], | |
warped_points[(i + 1) * (cols + 1) + j + 1, 1], | |
), | |
( | |
warped_points[(i + 1) * (cols + 1) + j, 0], | |
warped_points[(i + 1) * (cols + 1) + j, 1], | |
), | |
] | |
), | |
col, | |
) | |
label_segments = np.empty([0, 4], dtype=np.int) | |
# Iterate through rows | |
for row_idx in range(rows + 1): | |
# Include all the combination of the junctions | |
# Iterate through all the combination of junction index in that row | |
multi_seg_lst = [ | |
np.array( | |
[ | |
warped_points_float[id1, 0], | |
warped_points_float[id1, 1], | |
warped_points_float[id2, 0], | |
warped_points_float[id2, 1], | |
] | |
)[None, ...] | |
for (id1, id2) in combinations( | |
range(row_idx * (cols + 1), (row_idx + 1) * (cols + 1), 1), 2 | |
) | |
] | |
multi_seg = np.concatenate(multi_seg_lst, axis=0) | |
label_segments = np.concatenate((label_segments, multi_seg), axis=0) | |
# Iterate through columns | |
for col_idx in range(cols + 1): # for 5 columns, we will have 5 + 1 edges | |
# Include all the combination of the junctions | |
# Iterate throuhg all the combination of junction index in that column | |
multi_seg_lst = [ | |
np.array( | |
[ | |
warped_points_float[id1, 0], | |
warped_points_float[id1, 1], | |
warped_points_float[id2, 0], | |
warped_points_float[id2, 1], | |
] | |
)[None, ...] | |
for (id1, id2) in combinations( | |
range(col_idx, col_idx + ((rows + 1) * (cols + 1)), cols + 1), 2 | |
) | |
] | |
multi_seg = np.concatenate(multi_seg_lst, axis=0) | |
label_segments = np.concatenate((label_segments, multi_seg), axis=0) | |
label_segments_filtered = np.zeros([0, 4]) | |
# Define image boundary polygon (in x y manner) | |
image_poly = shapely.geometry.Polygon( | |
[ | |
[0, 0], | |
[img.shape[1] - 1, 0], | |
[img.shape[1] - 1, img.shape[0] - 1], | |
[0, img.shape[0] - 1], | |
] | |
) | |
for idx in range(label_segments.shape[0]): | |
# Get the line segment | |
seg_raw = label_segments[idx, :] | |
seg = shapely.geometry.LineString([seg_raw[:2], seg_raw[2:]]) | |
# The line segment is just inside the image. | |
if seg.intersection(image_poly) == seg: | |
label_segments_filtered = np.concatenate( | |
(label_segments_filtered, seg_raw[None, ...]), axis=0 | |
) | |
# Intersect with the image. | |
elif seg.intersects(image_poly): | |
# Check intersection | |
try: | |
p = np.array(seg.intersection(image_poly).coords).reshape([-1, 4]) | |
# If intersect with eact one point | |
except: | |
continue | |
segment = p | |
label_segments_filtered = np.concatenate( | |
(label_segments_filtered, segment), axis=0 | |
) | |
else: | |
continue | |
label_segments = np.round(label_segments_filtered).astype(np.int) | |
# Only record the segments longer than min_label_len | |
points1 = label_segments[:, :2] | |
points2 = label_segments[:, 2:] | |
seg_len = np.sqrt(np.sum((points1 - points2) ** 2, axis=1)) | |
label_segments = label_segments[seg_len >= min_label_len, :] | |
# Get all junctions from label segments | |
junc_points, line_map = get_unique_junctions(label_segments, min_label_len) | |
# Draw lines on the boundaries of the board at random | |
nb_rows = random_state.randint(2, rows + 2) | |
nb_cols = random_state.randint(2, cols + 2) | |
thickness = random_state.randint(min_dim * 0.01, min_dim * 0.015) | |
for _ in range(nb_rows): | |
row_idx = random_state.randint(rows + 1) | |
col_idx1 = random_state.randint(cols + 1) | |
col_idx2 = random_state.randint(cols + 1) | |
col = get_random_color(background_color) | |
cv.line( | |
img, | |
( | |
warped_points[row_idx * (cols + 1) + col_idx1, 0], | |
warped_points[row_idx * (cols + 1) + col_idx1, 1], | |
), | |
( | |
warped_points[row_idx * (cols + 1) + col_idx2, 0], | |
warped_points[row_idx * (cols + 1) + col_idx2, 1], | |
), | |
col, | |
thickness, | |
) | |
for _ in range(nb_cols): | |
col_idx = random_state.randint(cols + 1) | |
row_idx1 = random_state.randint(rows + 1) | |
row_idx2 = random_state.randint(rows + 1) | |
col = get_random_color(background_color) | |
cv.line( | |
img, | |
( | |
warped_points[row_idx1 * (cols + 1) + col_idx, 0], | |
warped_points[row_idx1 * (cols + 1) + col_idx, 1], | |
), | |
( | |
warped_points[row_idx2 * (cols + 1) + col_idx, 0], | |
warped_points[row_idx2 * (cols + 1) + col_idx, 1], | |
), | |
col, | |
thickness, | |
) | |
# Keep only the points inside the image | |
points = keep_points_inside(warped_points, img.shape[:2]) | |
return {"points": junc_points, "line_map": line_map} | |
def draw_stripes_multiseg( | |
img, | |
max_nb_cols=13, | |
min_len=0.04, | |
min_label_len=64, | |
transform_params=(0.05, 0.15), | |
seed=None, | |
): | |
"""Draw stripes in a distorted rectangle | |
and output the junctions points + line map. | |
Parameters: | |
max_nb_cols: maximal number of stripes to be drawn | |
min_width_ratio: the minimal width of a stripe is | |
min_width_ratio * smallest dimension of the image | |
transform_params: set the range of the parameters of the transformations | |
""" | |
# Set the optional random seed (most for debugging) | |
if seed is None: | |
global random_state | |
else: | |
random_state = np.random.RandomState(seed) | |
background_color = int(np.mean(img)) | |
# Create the grid | |
board_size = ( | |
int(img.shape[0] * (1 + random_state.rand())), | |
int(img.shape[1] * (1 + random_state.rand())), | |
) | |
# Number of cols | |
col = random_state.randint(5, max_nb_cols) | |
cols = np.concatenate( | |
[board_size[1] * random_state.rand(col - 1), np.array([0, board_size[1] - 1])], | |
axis=0, | |
) | |
cols = np.unique(cols.astype(int)) | |
# Remove the indices that are too close | |
min_dim = min(img.shape) | |
# Convert length constrain to pixel if given float number | |
if isinstance(min_len, float) and min_len <= 1.0: | |
min_len = int(min_dim * min_len) | |
if isinstance(min_label_len, float) and min_label_len <= 1.0: | |
min_label_len = int(min_dim * min_label_len) | |
cols = cols[ | |
(np.concatenate([cols[1:], np.array([board_size[1] + min_len])], axis=0) - cols) | |
>= min_len | |
] | |
# Update the number of cols | |
col = cols.shape[0] - 1 | |
cols = np.reshape(cols, (col + 1, 1)) | |
cols1 = np.concatenate([cols, np.zeros((col + 1, 1), np.int32)], axis=1) | |
cols2 = np.concatenate( | |
[cols, (board_size[0] - 1) * np.ones((col + 1, 1), np.int32)], axis=1 | |
) | |
points = np.concatenate([cols1, cols2], axis=0) | |
# Warp the grid using an affine transformation and a homography | |
alpha_affine = np.max(img.shape) * ( | |
transform_params[0] + random_state.rand() * transform_params[1] | |
) | |
center_square = np.float32(img.shape) // 2 | |
square_size = min(img.shape) // 3 | |
pts1 = np.float32( | |
[ | |
center_square + square_size, | |
[center_square[0] + square_size, center_square[1] - square_size], | |
center_square - square_size, | |
[center_square[0] - square_size, center_square[1] + square_size], | |
] | |
) | |
pts2 = pts1 + random_state.uniform( | |
-alpha_affine, alpha_affine, size=pts1.shape | |
).astype(np.float32) | |
affine_transform = cv.getAffineTransform(pts1[:3], pts2[:3]) | |
pts2 = pts1 + random_state.uniform( | |
-alpha_affine / 2, alpha_affine / 2, size=pts1.shape | |
).astype(np.float32) | |
perspective_transform = cv.getPerspectiveTransform(pts1, pts2) | |
# Apply the affine transformation | |
points = np.transpose(np.concatenate((points, np.ones((2 * (col + 1), 1))), axis=1)) | |
warped_points = np.transpose(np.dot(affine_transform, points)) | |
# Apply the homography | |
warped_col0 = np.add( | |
np.sum(np.multiply(warped_points, perspective_transform[0, :2]), axis=1), | |
perspective_transform[0, 2], | |
) | |
warped_col1 = np.add( | |
np.sum(np.multiply(warped_points, perspective_transform[1, :2]), axis=1), | |
perspective_transform[1, 2], | |
) | |
warped_col2 = np.add( | |
np.sum(np.multiply(warped_points, perspective_transform[2, :2]), axis=1), | |
perspective_transform[2, 2], | |
) | |
warped_col0 = np.divide(warped_col0, warped_col2) | |
warped_col1 = np.divide(warped_col1, warped_col2) | |
warped_points = np.concatenate([warped_col0[:, None], warped_col1[:, None]], axis=1) | |
warped_points_float = warped_points.copy() | |
warped_points = warped_points.astype(int) | |
# Fill the rectangles and get the segments | |
color = get_random_color(background_color) | |
# segments_debug = np.zeros([0, 4]) | |
for i in range(col): | |
# Fill the color | |
color = (color + 128 + random_state.randint(-30, 30)) % 256 | |
cv.fillConvexPoly( | |
img, | |
np.array( | |
[ | |
(warped_points[i, 0], warped_points[i, 1]), | |
(warped_points[i + 1, 0], warped_points[i + 1, 1]), | |
(warped_points[i + col + 2, 0], warped_points[i + col + 2, 1]), | |
(warped_points[i + col + 1, 0], warped_points[i + col + 1, 1]), | |
] | |
), | |
color, | |
) | |
segments = np.zeros([0, 4]) | |
row = 1 # in stripes case | |
# Iterate through rows | |
for row_idx in range(row + 1): | |
# Include all the combination of the junctions | |
# Iterate through all the combination of junction index in that row | |
multi_seg_lst = [ | |
np.array( | |
[ | |
warped_points_float[id1, 0], | |
warped_points_float[id1, 1], | |
warped_points_float[id2, 0], | |
warped_points_float[id2, 1], | |
] | |
)[None, ...] | |
for (id1, id2) in combinations( | |
range(row_idx * (col + 1), (row_idx + 1) * (col + 1), 1), 2 | |
) | |
] | |
multi_seg = np.concatenate(multi_seg_lst, axis=0) | |
segments = np.concatenate((segments, multi_seg), axis=0) | |
# Iterate through columns | |
for col_idx in range(col + 1): # for 5 columns, we will have 5 + 1 edges. | |
# Include all the combination of the junctions | |
# Iterate throuhg all the combination of junction index in that column | |
multi_seg_lst = [ | |
np.array( | |
[ | |
warped_points_float[id1, 0], | |
warped_points_float[id1, 1], | |
warped_points_float[id2, 0], | |
warped_points_float[id2, 1], | |
] | |
)[None, ...] | |
for (id1, id2) in combinations( | |
range(col_idx, col_idx + (row * col) + 2, col + 1), 2 | |
) | |
] | |
multi_seg = np.concatenate(multi_seg_lst, axis=0) | |
segments = np.concatenate((segments, multi_seg), axis=0) | |
# Select and refine the segments | |
segments_new = np.zeros([0, 4]) | |
# Define image boundary polygon (in x y manner) | |
image_poly = shapely.geometry.Polygon( | |
[ | |
[0, 0], | |
[img.shape[1] - 1, 0], | |
[img.shape[1] - 1, img.shape[0] - 1], | |
[0, img.shape[0] - 1], | |
] | |
) | |
for idx in range(segments.shape[0]): | |
# Get the line segment | |
seg_raw = segments[idx, :] | |
seg = shapely.geometry.LineString([seg_raw[:2], seg_raw[2:]]) | |
# The line segment is just inside the image. | |
if seg.intersection(image_poly) == seg: | |
segments_new = np.concatenate((segments_new, seg_raw[None, ...]), axis=0) | |
# Intersect with the image. | |
elif seg.intersects(image_poly): | |
# Check intersection | |
try: | |
p = np.array(seg.intersection(image_poly).coords).reshape([-1, 4]) | |
# If intersect at exact one point, just continue. | |
except: | |
continue | |
segment = p | |
segments_new = np.concatenate((segments_new, segment), axis=0) | |
else: | |
continue | |
segments = (np.round(segments_new)).astype(np.int) | |
# Only record the segments longer than min_label_len | |
points1 = segments[:, :2] | |
points2 = segments[:, 2:] | |
seg_len = np.sqrt(np.sum((points1 - points2) ** 2, axis=1)) | |
label_segments = segments[seg_len >= min_label_len, :] | |
# Get all junctions from label segments | |
junctions_all = np.concatenate( | |
(label_segments[:, :2], label_segments[:, 2:]), axis=0 | |
) | |
if junctions_all.shape[0] == 0: | |
junc_points = None | |
line_map = None | |
# Get all unique junction points | |
else: | |
junc_points = np.unique(junctions_all, axis=0) | |
# Generate line map from points and segments | |
line_map = get_line_map(junc_points, label_segments) | |
# Draw lines on the boundaries of the stripes at random | |
nb_rows = random_state.randint(2, 5) | |
nb_cols = random_state.randint(2, col + 2) | |
thickness = random_state.randint(min_dim * 0.01, min_dim * 0.011) | |
for _ in range(nb_rows): | |
row_idx = random_state.choice([0, col + 1]) | |
col_idx1 = random_state.randint(col + 1) | |
col_idx2 = random_state.randint(col + 1) | |
color = get_random_color(background_color) | |
cv.line( | |
img, | |
( | |
warped_points[row_idx + col_idx1, 0], | |
warped_points[row_idx + col_idx1, 1], | |
), | |
( | |
warped_points[row_idx + col_idx2, 0], | |
warped_points[row_idx + col_idx2, 1], | |
), | |
color, | |
thickness, | |
) | |
for _ in range(nb_cols): | |
col_idx = random_state.randint(col + 1) | |
color = get_random_color(background_color) | |
cv.line( | |
img, | |
(warped_points[col_idx, 0], warped_points[col_idx, 1]), | |
(warped_points[col_idx + col + 1, 0], warped_points[col_idx + col + 1, 1]), | |
color, | |
thickness, | |
) | |
# Keep only the points inside the image | |
# points = keep_points_inside(warped_points, img.shape[:2]) | |
return {"points": junc_points, "line_map": line_map} | |
def draw_cube( | |
img, | |
min_size_ratio=0.2, | |
min_label_len=64, | |
scale_interval=(0.4, 0.6), | |
trans_interval=(0.5, 0.2), | |
): | |
"""Draw a 2D projection of a cube and output the visible juntions. | |
Parameters: | |
min_size_ratio: min(img.shape) * min_size_ratio is the smallest | |
achievable cube side size | |
scale_interval: the scale is between scale_interval[0] and | |
scale_interval[0]+scale_interval[1] | |
trans_interval: the translation is between img.shape*trans_interval[0] | |
and img.shape*(trans_interval[0] + trans_interval[1]) | |
""" | |
# Generate a cube and apply to it an affine transformation | |
# The order matters! | |
# The indices of two adjacent vertices differ only of one bit (Gray code) | |
background_color = int(np.mean(img)) | |
min_dim = min(img.shape[:2]) | |
min_side = min_dim * min_size_ratio | |
lx = min_side + random_state.rand() * 2 * min_dim / 3 # dims of the cube | |
ly = min_side + random_state.rand() * 2 * min_dim / 3 | |
lz = min_side + random_state.rand() * 2 * min_dim / 3 | |
cube = np.array( | |
[ | |
[0, 0, 0], | |
[lx, 0, 0], | |
[0, ly, 0], | |
[lx, ly, 0], | |
[0, 0, lz], | |
[lx, 0, lz], | |
[0, ly, lz], | |
[lx, ly, lz], | |
] | |
) | |
rot_angles = random_state.rand(3) * 3 * math.pi / 10.0 + math.pi / 10.0 | |
rotation_1 = np.array( | |
[ | |
[math.cos(rot_angles[0]), -math.sin(rot_angles[0]), 0], | |
[math.sin(rot_angles[0]), math.cos(rot_angles[0]), 0], | |
[0, 0, 1], | |
] | |
) | |
rotation_2 = np.array( | |
[ | |
[1, 0, 0], | |
[0, math.cos(rot_angles[1]), -math.sin(rot_angles[1])], | |
[0, math.sin(rot_angles[1]), math.cos(rot_angles[1])], | |
] | |
) | |
rotation_3 = np.array( | |
[ | |
[math.cos(rot_angles[2]), 0, -math.sin(rot_angles[2])], | |
[0, 1, 0], | |
[math.sin(rot_angles[2]), 0, math.cos(rot_angles[2])], | |
] | |
) | |
scaling = np.array( | |
[ | |
[scale_interval[0] + random_state.rand() * scale_interval[1], 0, 0], | |
[0, scale_interval[0] + random_state.rand() * scale_interval[1], 0], | |
[0, 0, scale_interval[0] + random_state.rand() * scale_interval[1]], | |
] | |
) | |
trans = np.array( | |
[ | |
img.shape[1] * trans_interval[0] | |
+ random_state.randint( | |
-img.shape[1] * trans_interval[1], img.shape[1] * trans_interval[1] | |
), | |
img.shape[0] * trans_interval[0] | |
+ random_state.randint( | |
-img.shape[0] * trans_interval[1], img.shape[0] * trans_interval[1] | |
), | |
0, | |
] | |
) | |
cube = trans + np.transpose( | |
np.dot( | |
scaling, | |
np.dot( | |
rotation_1, np.dot(rotation_2, np.dot(rotation_3, np.transpose(cube))) | |
), | |
) | |
) | |
# The hidden corner is 0 by construction | |
# The front one is 7 | |
cube = cube[:, :2] # project on the plane z=0 | |
cube = cube.astype(int) | |
points = cube[1:, :] # get rid of the hidden corner | |
# Get the three visible faces | |
faces = np.array([[7, 3, 1, 5], [7, 5, 4, 6], [7, 6, 2, 3]]) | |
# Get all visible line segments | |
segments = np.zeros([0, 4]) | |
# Iterate through all the faces | |
for face_idx in range(faces.shape[0]): | |
face = faces[face_idx, :] | |
# Brute-forcely expand all the segments | |
segment = np.array( | |
[ | |
np.concatenate((cube[face[0]], cube[face[1]]), axis=0), | |
np.concatenate((cube[face[1]], cube[face[2]]), axis=0), | |
np.concatenate((cube[face[2]], cube[face[3]]), axis=0), | |
np.concatenate((cube[face[3]], cube[face[0]]), axis=0), | |
] | |
) | |
segments = np.concatenate((segments, segment), axis=0) | |
# Select and refine the segments | |
segments_new = np.zeros([0, 4]) | |
# Define image boundary polygon (in x y manner) | |
image_poly = shapely.geometry.Polygon( | |
[ | |
[0, 0], | |
[img.shape[1] - 1, 0], | |
[img.shape[1] - 1, img.shape[0] - 1], | |
[0, img.shape[0] - 1], | |
] | |
) | |
for idx in range(segments.shape[0]): | |
# Get the line segment | |
seg_raw = segments[idx, :] | |
seg = shapely.geometry.LineString([seg_raw[:2], seg_raw[2:]]) | |
# The line segment is just inside the image. | |
if seg.intersection(image_poly) == seg: | |
segments_new = np.concatenate((segments_new, seg_raw[None, ...]), axis=0) | |
# Intersect with the image. | |
elif seg.intersects(image_poly): | |
try: | |
p = np.array(seg.intersection(image_poly).coords).reshape([-1, 4]) | |
except: | |
continue | |
segment = p | |
segments_new = np.concatenate((segments_new, segment), axis=0) | |
else: | |
continue | |
segments = (np.round(segments_new)).astype(np.int) | |
# Only record the segments longer than min_label_len | |
points1 = segments[:, :2] | |
points2 = segments[:, 2:] | |
seg_len = np.sqrt(np.sum((points1 - points2) ** 2, axis=1)) | |
label_segments = segments[seg_len >= min_label_len, :] | |
# Get all junctions from label segments | |
junctions_all = np.concatenate( | |
(label_segments[:, :2], label_segments[:, 2:]), axis=0 | |
) | |
if junctions_all.shape[0] == 0: | |
junc_points = None | |
line_map = None | |
# Get all unique junction points | |
else: | |
junc_points = np.unique(junctions_all, axis=0) | |
# Generate line map from points and segments | |
line_map = get_line_map(junc_points, label_segments) | |
# Fill the faces and draw the contours | |
col_face = get_random_color(background_color) | |
for i in [0, 1, 2]: | |
cv.fillPoly(img, [cube[faces[i]].reshape((-1, 1, 2))], col_face) | |
thickness = random_state.randint(min_dim * 0.003, min_dim * 0.015) | |
for i in [0, 1, 2]: | |
for j in [0, 1, 2, 3]: | |
col_edge = ( | |
col_face + 128 + random_state.randint(-64, 64) | |
) % 256 # color that constrats with the face color | |
cv.line( | |
img, | |
(cube[faces[i][j], 0], cube[faces[i][j], 1]), | |
(cube[faces[i][(j + 1) % 4], 0], cube[faces[i][(j + 1) % 4], 1]), | |
col_edge, | |
thickness, | |
) | |
return {"points": junc_points, "line_map": line_map} | |
def gaussian_noise(img): | |
"""Apply random noise to the image.""" | |
cv.randu(img, 0, 255) | |
return {"points": None, "line_map": None} | |