-
Notifications
You must be signed in to change notification settings - Fork 2
/
utils.py
425 lines (337 loc) · 15.9 KB
/
utils.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
'''
General Utility Files
- Consisting of helper functions
for supporting visualisations
of model results
'''
import os
import math
import cv2
import copy
import numpy as np
from empatches import EMPatches
from einops import rearrange
from plantcv import plantcv as pcv
global mean , std
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
def merge_images_horizontally(img1, img2, img3):
assert img1.shape == img2.shape==img3.shape , "Error merging the images"
# Resize images if necessary to make them the same size
if img1.shape != img2.shape:
img2 = cv2.resize(img2, img1.shape[:2])
if img1.shape != img3.shape:
img3 = cv2.resize(img3, img1.shape[:2])
# Combine the images horizontally
merged_img = np.hstack((img1, img2, img3))
return merged_img
def imvisualize(settings,imdeg, imgt, impred, ind, epoch=0,threshold=0.4):
"""
Visualize the predicted images along with the degraded and clean gt ones
Args:
imdeg (tensor): degraded image
imgt (tensor): gt clean image
impred (tensor): prediced cleaned image
ind (str): index of images (name)
epoch (str): current epoch
setting (str): experiment name
"""
# unnormalize data
imdeg = imdeg.numpy()
imgt = imgt.numpy()
impred = impred.numpy()
impred = np.squeeze(impred, axis=0)
imgt = np.squeeze(imgt, axis=0)
imdeg = np.squeeze(imdeg, axis=0)
imdeg = np.transpose(imdeg, (1, 2, 0))
imgt = np.transpose(imgt, (1, 2, 0))
impred = np.transpose(impred, (1, 2, 0))
# Only for the input image
for ch in range(3):
imdeg[:,:,ch] = (imdeg[:,:,ch] *std[ch]) + mean[ch]
# avoid taking values of pixels outside [0, 1]
impred[np.where(impred>1.0)] = 1
impred[np.where(impred<0.0)] = 0
# thresholding now
# binarize the predicted image taking 0.5 as threshold
impred = (impred>threshold)*1
# Change to 0-255 range
imdeg=imdeg*255
imgt=imgt*255
impred=impred*255
impred= impred.astype(np.uint8)
# save images
if not settings['enabledWandb']:
base_dir = os.path.join(settings['visualisation_folder'],'epoch_{}'.format(epoch))
epoch=str(epoch)
os.makedirs(base_dir,exist_ok=True)
imdeg_ = imdeg[:,:,0].reshape(*imdeg.shape[:-1], 1)
out = merge_images_horizontally(imdeg_,imgt,impred)
cv2.imwrite(os.path.join(base_dir,str(ind)+'_combined.png'),out)
return imdeg,imgt,impred
def preprocess(deg_img):
deg_img = (np.array(deg_img) /255).astype('float32')
# normalize data
mean = [0.485, 0.456, 0.406]
std = [0.229, 0.224, 0.225]
out_deg_img = np.zeros([3, *deg_img.shape[:-1]])
for i in range(3):
out_deg_img[i] = (deg_img[:,:,i] - mean[i]) / std[i]
return out_deg_img
def readFullImage(path,PDIM=256,DIM=256,OVERLAP=0.25):
input_patches=[]
emp = EMPatches()
try:
img = cv2.imread(path)
img = preprocess(img)
img = np.transpose(img)
img_patches, indices = emp.extract_patches(img,patchsize=PDIM,overlap=OVERLAP)
for i,patch in enumerate(img_patches):
resized=[DIM,DIM]
if patch.shape[0]!= DIM or patch.shape[1]!= DIM :
resized=[patch.shape[0],patch.shape[1]]
patch = cv2.resize(patch,(DIM,DIM),interpolation = cv2.INTER_AREA)
# cv2_imshow(patch)
patch = np.asarray(patch,dtype=np.float32)
patch = np.transpose(patch)
patch= np.expand_dims(patch,axis=0)
sample={'img':patch,'resized':resized}
input_patches.append(sample)
except Exception as exp :
print('ImageReading Error ! :{}'.format(exp))
return None,None
return input_patches,indices
'''
Reconstruct from pred_pixels to patches
'''
def reconstruct(pred_pixel_values,patch_size,target_shape,image_size):
rec_patches = copy.deepcopy(pred_pixel_values)
output_image = rearrange(rec_patches, 'b (h w) (p1 p2 c) -> b c (h p1) (w p2)',p1 = patch_size, p2 = patch_size, h=image_size[0]//patch_size)
output_image = output_image.cpu().numpy().squeeze()
output_image = output_image.T
# Resizing to get desired output
output_image = cv2.resize(output_image,target_shape, interpolation = cv2.INTER_AREA)
# Basic Thresholding
output_image[np.where( output_image>1)] = 1
output_image[np.where( output_image<0)] = 0
return output_image
'''
Calculate patch/image wise PSNR given two input
'''
def computePSNR(gt_img, pred_img, PIXEL_MAX=None):
# Detach from computation graph, move calculations to CPU
pred_img = pred_img.detach().cpu()
gt_img = gt_img.detach().cpu()
# Convert torch tensor to number arrays
pred_img = pred_img.numpy()[0]
gt_img = gt_img.numpy()[0]
# calculate Pixel Max and normalise it
if not PIXEL_MAX:
PIXEL_MAX = np.max(gt_img)
gt_img = gt_img/PIXEL_MAX
# Convert prediction values to 0 and 1
pred_img[np.where(pred_img>1)] = 1
pred_img[np.where(pred_img<0)] = 0
pred_img = (pred_img>0.5)*1
# Calculate MSE
mse = np.mean( (pred_img - gt_img) ** 2 )
if (mse == 0):
return (100)
# Calculate PSNR
p = (20 * math.log10(PIXEL_MAX / math.sqrt(mse)))
return p
def polygon_to_distance_mask(pmask,threshold=60):
# Read the polygon mask image as a binary image
polygon_mask = cv2.cvtColor(pmask,cv2.COLOR_BGR2GRAY)
# Ensure that the mask is binary (0 or 255 values)
_, polygon_mask = cv2.threshold(polygon_mask, 128, 255, cv2.THRESH_BINARY)
# Compute the distance transform
distance_mask = cv2.distanceTransform(polygon_mask, cv2.DIST_L2, cv2.DIST_MASK_5)
# Normalize the distance values to 0-255 range
distance_mask = cv2.normalize(distance_mask, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)
# Threshold the image
src = copy.deepcopy(distance_mask)
src[src<threshold]=0
src[src>=threshold]=255
src = np.uint8(src)
return src
def average_coordinates(hull):
# Calculate the average x and y coordinates of all points in the hull/contour.
# Format : [[x1,y1], [x2,y2],[x3,y3]...[xn,yn]]
num_points = len(hull)
avg_x = sum(pt[0][0] for pt in hull) / num_points
avg_y = sum(pt[0][1] for pt in hull) / num_points
return avg_x, avg_y
# You send set of clean contours to this function
# you obtain a list of hulls and merge the ones on the same horizontal level.
def combine_hulls_on_same_level(contours,tolerance=50):
combined_hulls = []
hulls = [cv2.convexHull(np.array(contour)) for contour in contours]
# Sort the hulls by the average y-coordinate of all points
sorted_hulls = sorted(hulls, key=lambda hull: average_coordinates(hull)[1])
# Starting with 0th contour
current_combined_hull = sorted_hulls[0]
# Starts from 1st contour and keeps track of which hull is merging and breaks
for hull in sorted_hulls[1:]:
# Check if the current hull is on the same horizontal level as the combined hull
if abs(average_coordinates(hull)[1] - average_coordinates(current_combined_hull)[1]) < tolerance:
# Merge the hulls by extending the current_combined_hull with hull (combining points)
current_combined_hull = np.vstack((current_combined_hull, hull))
else:
# Hull is on a different level, add the current combined hull to the result
combined_hulls.append(current_combined_hull)
current_combined_hull = hull
# Add the last combined hull
combined_hulls.append(current_combined_hull)
# Returning them as hulls again
nethulls = [cv2.convexHull(np.array(contour,dtype=np.int32)) for contour in combined_hulls]
# finalHulls = [ hull.reshape((-1,2)).tolist() for hull in nethulls ]
return nethulls
# Text Dilation
def text_dilate(image, kernel_size, iterations=1):
# Create a structuring element (kernel) for dilation
kernel = np.ones((kernel_size, kernel_size), np.uint8)
# Perform dilation
dilated_image = cv2.dilate(image, kernel, iterations=iterations)
return dilated_image
# Horizontal Dilation
def horizontal_dilation(image, kernel_width=5,iterations=1):
# Create a horizontal kernel for dilation
kernel = np.ones((1, kernel_width), np.uint8)
# Perform dilation
dilated_image = cv2.dilate(image, kernel, iterations)
return dilated_image
'''
Generates Scribble given a polygon inputs and img dimensions (pmask)
'''
from scipy.interpolate import interp1d
def uniformly_sampled_line(points):
num_points = min(len(points),100)
# Separate x and y coordinates from the given points
x_coords, y_coords = zip(*points)
# Calculate the cumulative distance along the original line
distances = np.cumsum(np.sqrt(np.diff(x_coords) ** 2 + np.diff(y_coords) ** 2))
distances = np.insert(distances, 0, 0) # Add the initial point (0, 0) distance
# Create a linear interpolation function for x and y coordinates
interpolate_x = interp1d(distances, x_coords, kind='linear')
interpolate_y = interp1d(distances, y_coords, kind='linear')
# Calculate new uniformly spaced distances
new_distances = np.linspace(0, distances[-1], num_points)
# Interpolate new x and y coordinates using the uniformly spaced distances
new_x_coords = interpolate_x(new_distances)
new_y_coords = interpolate_y(new_distances)
# Create a list of new points
new_points = [[np.int32(new_x_coords[i]), np.int32(new_y_coords[i])] for i in range(num_points)]
return new_points
def draw_line_through_midpoints(box_points):
# Ensure points are ordered clockwise starting from the top-left corner
x_coords, y_coords = zip(*box_points)
top_left_index = np.argmin(x_coords + y_coords)
ordered_points = box_points[top_left_index:] + box_points[:top_left_index]
# Calculate midpoints of the opposite sides
top_midpoint = ((ordered_points[0][0] + ordered_points[1][0]) / 2, (ordered_points[0][1] + ordered_points[1][1]) / 2)
bottom_midpoint = ((ordered_points[2][0] + ordered_points[3][0]) / 2, (ordered_points[2][1] + ordered_points[3][1]) / 2)
left_midpoint = ((ordered_points[0][0] + ordered_points[3][0]) / 2, (ordered_points[0][1] + ordered_points[3][1]) / 2)
right_midpoint = ((ordered_points[1][0] + ordered_points[2][0]) / 2, (ordered_points[1][1] + ordered_points[2][1]) / 2)
return [int(left_midpoint[0]), int(left_midpoint[1])], [int(right_midpoint[0]), int(right_midpoint[1])]
def draw_line_through_midpoints_of_obb(polygon_points):
# Compute the oriented bounding box (OBB)
rect = cv2.minAreaRect(np.array(polygon_points, dtype=np.float32))
# Get the four corner points of the OBB
box_points = cv2.boxPoints(rect)
box_points = np.int0(box_points).tolist()
# Sort the points to ensure they are arranged from top-left to bottom-right
x_coords, y_coords = zip(*box_points)
top_left_index = np.argmin(x_coords + y_coords)
ordered_points = box_points[top_left_index:] + box_points[:top_left_index]
# Calculate midpoints of the opposite sides
top_midpoint = ((ordered_points[0][0] + ordered_points[1][0]) / 2, (ordered_points[0][1] + ordered_points[1][1]) / 2)
bottom_midpoint = ((ordered_points[2][0] + ordered_points[3][0]) / 2, (ordered_points[2][1] + ordered_points[3][1]) / 2)
left_midpoint = ((ordered_points[0][0] + ordered_points[3][0]) / 2, (ordered_points[0][1] + ordered_points[3][1]) / 2)
right_midpoint = ((ordered_points[1][0] + ordered_points[2][0]) / 2, (ordered_points[1][1] + ordered_points[2][1]) / 2)
# Determine the orientation (horizontal or vertical)
is_horizontal = abs(ordered_points[0][0] - ordered_points[1][0]) > abs(ordered_points[0][1] - ordered_points[3][1])
if is_horizontal:
return [ [int(left_midpoint[0]), int(left_midpoint[1])], [int(right_midpoint[0]), int(right_midpoint[1])]]
else:
return [ [int(top_midpoint[0]), int(top_midpoint[1])] , [int(bottom_midpoint[0]), int(bottom_midpoint[1])] ]
def find_index_of_max_length_list(list_of_2d_lists):
return max(enumerate(list_of_2d_lists), key=lambda x: len(x[1]), default=(None, None))[0]
def calculate_average_orientation(points):
# Calculate the centroid (mean) of the points
centroid_x = np.mean([point[0] for point in points])
centroid_y = np.mean([point[1] for point in points])
# Calculate the orientation (angle) relative to the centroid
orientations = [np.arctan2(point[1] - centroid_y, point[0] - centroid_x) for point in points]
# Calculate the average orientation
average_orientation = np.mean(orientations)
return average_orientation,centroid_x,centroid_y
def remove_oriented_and_sharp_turn_points(points, angle_threshold_deg):
filtered_points = []
average_orientation,centroid_x,centroid_y = calculate_average_orientation(points)
angle_threshold_rad = np.radians(angle_threshold_deg)
for point in points:
# Calculate the orientation (angle) of the point relative to the centroid
point_orientation = np.arctan2(point[1] - centroid_y, point[0] - centroid_x)
# Calculate the angular difference between the point's orientation and the average orientation
angle_difference = abs(point_orientation - average_orientation)
# Check if the angle difference is below the threshold (not oriented or sharp turn)
if angle_difference <= angle_threshold_rad:
filtered_points.append(point)
return filtered_points
def find_corner_points_polygon(points):
if not points:
return None, None
# Sort the points based on their x-coordinate
sorted_points = sorted(points, key=lambda point: point[0])
leftmost_point = list(sorted_points[0])
rightmost_point = list(sorted_points[-1])
return leftmost_point, rightmost_point
def generateScribble(H, W, polygon, isBox=False):
# Generate Canvas
canvas = np.zeros((H,W))
# Mark the polygon on the canvas
if isBox is False:
leftmost_point, rightmost_point = draw_line_through_midpoints_of_obb(polygon)
else:
leftmost_point,rightmost_point = draw_line_through_midpoints(polygon)
poly_arr = np.asarray(polygon,dtype=np.int32).reshape((-1,1,2))
canvas = cv2.fillPoly(canvas,[poly_arr],(255,255,255))
# Scribble generation
skeleton = pcv.morphology.skeletonize(canvas)
pruned_skeleton,_,segment_objects = pcv.morphology.prune(skel_img=skeleton,size=100)
index = find_index_of_max_length_list(segment_objects)
scribble = np.asarray(segment_objects[index],dtype=np.int32).reshape((-1,2))
scribble=scribble.tolist()
scribble = sorted(scribble, key=lambda point: point[0])
scribble = remove_oriented_and_sharp_turn_points(scribble, angle_threshold_deg=30)
# return scribble
if leftmost_point is not None and rightmost_point is not None :
scribble.append(leftmost_point)
scribble.append(rightmost_point)
scribble = sorted(scribble, key=lambda point: point[0])
return scribble
# New helper function
def deformat(listofpoints):
# Input : [[[x1,y1],[[x2,y2]],[[x3,y3]]....]
# Output : [ [x1,y1], [x2,y2],[x3,y3]....]
output = [ pt[0].tolist() for pt in listofpoints ]
return output
# Extracts final contours with a specific area threshold
def cleanImageFindContours(patch,threshold):
try:
patch = np.uint8(patch)
patch = cv2.cvtColor(patch,cv2.COLOR_BGR2GRAY)
contours, hierarchy = cv2.findContours(patch,cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
if len(contours)<1:
print('No contours in the raw image!')
return patch
# Else sort them
cntsSorted = sorted(contours, key=lambda x: cv2.contourArea(x),reverse=True)
areaList = [cv2.contourArea(c) for c in cntsSorted]
maxArea = max(areaList)
sortedContours = [deformat(c) for c in cntsSorted if cv2.contourArea(c)>np.int32(threshold*maxArea)]
return sortedContours
except Exception as exp :
print('Error in figuring out the clean contours : {} '.format(exp))
return None