Ujjwal123 commited on
Commit
c68e701
1 Parent(s): 1f5168b

added queing mechanism

Browse files
Files changed (3) hide show
  1. extractpuzzle.py +0 -787
  2. main.py +64 -63
  3. requirements.txt +0 -2
extractpuzzle.py DELETED
@@ -1,787 +0,0 @@
1
- import cv2
2
- import numpy as np
3
- import math
4
- from sklearn.linear_model import LinearRegression
5
- import pytesseract
6
- import re
7
-
8
-
9
- pytesseract.pytesseract.tesseract_cmd = "/usr/bin/tesseract"
10
-
11
- def first_preprocessing(image):
12
- gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
13
- canny = cv2.Canny(gray,75,25)
14
- contours,hierarchies = cv2.findContours(canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
15
- sorted_contours = sorted(contours,key = cv2.contourArea,reverse = True)
16
- largest_contour = sorted_contours[0]
17
- box = cv2.boundingRect(sorted_contours[0])
18
- x = box[0]
19
- y = box[1]
20
- w = box[2]
21
- h = box[3]
22
- result = cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 255), -1)
23
- return result
24
-
25
- def remove_head(image):
26
- custom_config = r'--oem 3 --psm 6' # Tesseract OCR configuration
27
- detected_text = pytesseract.image_to_string(image, config=custom_config)
28
- lines = detected_text.split('\n')
29
-
30
- # Find the first line containing some text
31
- line_index = 0
32
- for i, line in enumerate(lines):
33
- if line.strip() != '':
34
- line_index = i
35
- break
36
- first_newline_idx = detected_text.find('\n')
37
- result = cv2.rectangle(image, (0, line_index), (image.shape[1], first_newline_idx), (255,255,255), thickness=cv2.FILLED)
38
- return result
39
-
40
- def second_preprocessing(image):
41
- gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)
42
- canny = cv2.Canny(gray,75,25)
43
- contours,hierarchies = cv2.findContours(canny,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
44
- sorted_contours = sorted(contours,key = cv2.contourArea,reverse = True)
45
- largest_contour = sorted_contours[0]
46
- box2 = cv2.boundingRect(sorted_contours[0])
47
- x = box2[0]
48
- y = box2[1]
49
- w = box2[2]
50
- h = box2[3]
51
- result2 = cv2.rectangle(image, (x, y), (x + w, y + h), (255, 255, 255), -1)
52
- return result2
53
-
54
- def find_vertical_profile(image):
55
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
56
- _, binary = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
57
- vertical_profile = np.sum(binary, axis=0)
58
- return vertical_profile
59
-
60
- def detect_steepest_changes(projection_profile, threshold=0.4, start_idx=0, min_valley_width=10, min_search_width=50):
61
- differences = np.diff(projection_profile)
62
- change_points = np.where(np.abs(differences) > threshold * np.max(np.abs(differences)))[0]
63
- left_boundaries = []
64
- right_boundaries = []
65
-
66
- for idx in change_points:
67
- if idx <= start_idx:
68
- continue
69
-
70
- if idx - start_idx >= min_search_width:
71
- decreasing_profile = projection_profile[idx:]
72
- if np.any(decreasing_profile > 0):
73
- right_boundary = idx + np.argmin(decreasing_profile)
74
- right_boundaries.append(right_boundary)
75
- else:
76
- continue
77
- valley_start = max(start_idx, idx - min_valley_width)
78
- valley_start = valley_start-40
79
- valley_end = min(idx + min_valley_width, len(projection_profile) - 1)
80
- valley = valley_start + np.argmin(projection_profile[valley_start:valley_end])
81
- left_boundaries.append(valley)
82
-
83
- break
84
-
85
- return left_boundaries, right_boundaries
86
-
87
- def crop_text_columns(image, projection_profile, threshold=0.4):
88
- start_idx = 0
89
- text_columns = []
90
-
91
- while True:
92
- left_boundaries, right_boundaries = detect_steepest_changes(projection_profile, threshold, start_idx)
93
- if not left_boundaries or not right_boundaries:
94
- break
95
- left = left_boundaries[0]
96
- right = right_boundaries[0]
97
- text_column = image[:, left:right]
98
- text_columns.append(text_column)
99
-
100
- start_idx = right
101
-
102
- return text_columns
103
-
104
-
105
- def parse_clues(clue_text):
106
- lines = clue_text.split('\n')
107
- clues = {}
108
- number = None
109
- column = 0
110
- for line in lines:
111
- if "column separation" in line:
112
- column += 1
113
- continue
114
- pattern = r"^(\d+(?:\.\d+)?)\s*(.+)" # Updated pattern to handle decimal point numbers for clues
115
- match = re.search(pattern, line)
116
- if match:
117
- number = float(match.group(1)) # Convert the matched number to float if there is a decimal point
118
- if number not in clues:
119
- clues[number] = [column,match.group(2).strip()]
120
- else:
121
- continue
122
- elif number is None:
123
- continue
124
- elif clues[number][0] != column:
125
- continue
126
- else:
127
- clues[number][1] += " " + line.strip() # Append to the previous clue if it's a multiline clue
128
-
129
- return clues
130
-
131
- def parse_crossword_clues(text):
132
- # Check if "Down" clues are present
133
- match = re.search(r'[dD][oO][wW][nN]\n', text)
134
- if match:
135
- across_clues, down_clues = re.split(r'[dD][oO][wW][nN]\n', text)
136
- else:
137
- # If "Down" clues are not present, set down_clues to an empty string
138
- across_clues, down_clues = text, ""
139
-
140
- across = parse_clues(across_clues)
141
- down = parse_clues(down_clues)
142
-
143
- return across, down
144
-
145
-
146
- def classify_text(filtered_columns):
147
- text = ""
148
- custom_config = r'--oem 3 --psm 6'
149
- for i, column in enumerate(filtered_columns):
150
- column2 = cv2.cvtColor(column, cv2.COLOR_BGR2RGB)
151
- scale_factor = 2.0 # You can adjust this value
152
-
153
- # Calculate the new dimensions after scaling
154
- new_width = int(column2.shape[1] * scale_factor)
155
- new_height = int(column2.shape[0] * scale_factor)
156
-
157
- # Resize the image using OpenCV
158
- scaled_image = cv2.resize(column2, (new_width, new_height), interpolation=cv2.INTER_LINEAR)
159
-
160
- # Apply image enhancement techniques
161
- denoised_image = cv2.fastNlMeansDenoising(scaled_image, None, h=10, templateWindowSize=7, searchWindowSize=21)
162
- enhanced_image = cv2.cvtColor(denoised_image, cv2.COLOR_BGR2GRAY) # Convert to grayscale # Apply histogram equalization
163
- detected_text = pytesseract.image_to_string(enhanced_image, config=custom_config)
164
- # print(detected_text)
165
- text+=detected_text
166
- across_clues, down_clues = parse_crossword_clues(text)
167
- return across_clues,down_clues
168
-
169
- def get_text(image):
170
- image = cv2.cvtColor(image,cv2.COLOR_GRAY2BGR)
171
- result = first_preprocessing(image)
172
- result1 = remove_head(result)
173
- result2 = second_preprocessing(result1)
174
- vertical_profile = find_vertical_profile(result2)
175
- combined_columns = crop_text_columns(result2,vertical_profile)
176
- across,down = classify_text(combined_columns)
177
- return across,down
178
-
179
-
180
- ################################ Grid Extraction begins here ###########################
181
- ########################################################################################
182
-
183
-
184
- # for applying non max suppression of the contours
185
- def calculate_iou(image, contour1, contour2):
186
- # Create masks for each contour
187
- mask1 = np.zeros_like(image, dtype=np.uint8)
188
- cv2.drawContours(mask1, [contour1], -1, 255, thickness=cv2.FILLED)
189
-
190
- mask2 = np.zeros_like(image, dtype=np.uint8)
191
- cv2.drawContours(mask2, [contour2], -1, 255, thickness=cv2.FILLED)
192
-
193
- # Find the intersection between the two masks
194
- intersection = cv2.bitwise_and(mask1, mask2)
195
-
196
- # Calculate the intersection area
197
- intersection_area = cv2.countNonZero(intersection)
198
-
199
- # Calculate the union area (Not the accurate one but works alright XD !)
200
- union_area = cv2.contourArea(cv2.convexHull(np.concatenate((contour1, contour2))))
201
-
202
- # Calculate the IoU
203
- iou = intersection_area / union_area
204
- return iou
205
-
206
- # remove overlapping contours, non square and not quardatic contours
207
- # this check every contour with every other contour so be careful
208
- def filter_contours(img_gray2, contours, iou_threshold = 0.6, asp_ratio = 1,tolerance = 0.5):
209
- # Remove overlapping contours, removing that are not square
210
- filtered_contours = []
211
- epsilon = 0.02
212
- for contour in contours:
213
-
214
- # Approximate the contour to reduce the number of points
215
- epsilon_multiplier = epsilon * cv2.arcLength(contour, True)
216
- approximated_contour = cv2.approxPolyDP(contour, epsilon_multiplier, True)
217
-
218
- # find the aspect ratio of the contour, if it is close to 1 then keep it otherwise discard
219
- _,_,w,h = cv2.boundingRect(approximated_contour)
220
- if(abs(float(w)/h - asp_ratio) > tolerance ): continue
221
-
222
- # Calculate the IoU with all existing contours
223
- iou_values = [calculate_iou(img_gray2,np.array(approximated_contour), np.array(existing_contour)) for existing_contour in filtered_contours]
224
-
225
- # If the IoU value with all existing contours is below the threshold, add the current contour
226
- if not any(iou_value > iou_threshold for iou_value in iou_values):
227
- filtered_contours.append(approximated_contour)
228
-
229
- return filtered_contours
230
-
231
- # https://stackoverflow.com/questions/383480/intersection-of-two-lines-defined-in-rho-theta-parameterization/383527#383527
232
- # Define the parametricIntersect function
233
- def parametricIntersect(r1, t1, r2, t2):
234
- ct1 = np.cos(t1)
235
- st1 = np.sin(t1)
236
- ct2 = np.cos(t2)
237
- st2 = np.sin(t2)
238
- d = ct1 * st2 - st1 * ct2
239
- if d != 0.0:
240
- x = int((st2 * r1 - st1 * r2) / d)
241
- y = int((-ct2 * r1 + ct1 * r2) / d)
242
- return x, y
243
- else:
244
- return None
245
-
246
- # Group the coordinate to a list such that each point in a list may belong to a line
247
- def group_lines(coordinates,axis=0,threshold=10):
248
- sorted_coordinates = list(sorted(coordinates,key=lambda x: x[axis]))
249
- groups = []
250
- current_group = []
251
-
252
- for i in range(len(sorted_coordinates)):
253
- if i!=0 and abs(current_group[0][axis] - sorted_coordinates[i][axis]) > threshold: # condition to change the group
254
- if len(current_group) > 4:
255
- groups.append(current_group)
256
- current_group = []
257
- current_group.append(sorted_coordinates[i]) # condition to append to the group
258
- if(len(current_group) > 4):
259
- groups.append(current_group)
260
- return groups
261
-
262
- # Use the Grouped Lines to Fit a line using Linear Regression
263
- def fit_lines(grouped_lines,is_horizontal = False):
264
- actual_lines = []
265
- for coordinates in grouped_lines:
266
- # Converting into numpy array
267
- coordinates_arr = np.array(coordinates)
268
- # Separate the x and y coordinates
269
- x = coordinates_arr[:, 0]
270
- y = coordinates_arr[:, 1]
271
- # Fit a linear regression model
272
- regressor = LinearRegression()
273
- regressor.fit(y.reshape(-1, 1), x)
274
- # Get the slope and intercept of the fitted line
275
- slope = regressor.coef_[0]
276
- intercept = regressor.intercept_
277
-
278
- if(is_horizontal):
279
- intercept = np.mean(y)
280
- actual_lines.append((slope,intercept))
281
-
282
- return actual_lines
283
-
284
- # Calculates difference between two consecutive elements in an array
285
- def average_distance(arr):
286
- n = len(arr)
287
- distance_sum = 0
288
-
289
- for i in range(n - 1):
290
- distance_sum += abs(arr[i+1] - arr[i])
291
-
292
- average = distance_sum / (n - 1)
293
- return average
294
-
295
- # If two adjacent lines are near than some threshold, then merge them
296
- # Returns Results in y = mx + b from
297
- def average_out_similar_lines(lines_m_c,lines_coord,del_threshold,is_horizontal=False):
298
- averaged_lines = []
299
- i = 0
300
- while(i < len(lines_m_c) - 1):
301
-
302
- _, intercept1 = lines_m_c[i]
303
- _, intercept2 = lines_m_c[i + 1]
304
-
305
- if abs(intercept2 - intercept1) < del_threshold:
306
- new_points = np.array(lines_coord[i] + lines_coord[i+1][:-1])
307
- # Separate the x and y coordinates
308
- x = new_points[:, 0]
309
- y = new_points[:, 1]
310
-
311
- # Fit a linear regression model
312
- regressor = LinearRegression()
313
- regressor.fit(y.reshape(-1, 1), x)
314
-
315
- # Get the slope and intercept of the fitted line
316
- slope = regressor.coef_[0]
317
- intercept = regressor.intercept_
318
-
319
- if(is_horizontal):
320
- intercept = np.mean(y)
321
- averaged_lines.append((slope,intercept))
322
- i+=2
323
- else:
324
- averaged_lines.append(lines_m_c[i])
325
- i+=1
326
- if(i < len(lines_m_c)):
327
- averaged_lines.append(lines_m_c[i])
328
-
329
- return averaged_lines
330
-
331
- # If two adjacent lines are near than some threshold, then merge them
332
- # Returns Results in normalized vector form
333
- def average_out_similar_lines1(lines_m_c,lines_coord,del_threshold):
334
- averaged_lines = []
335
- i = 0
336
- while(i < len(lines_m_c) - 1):
337
-
338
- _, intercept1 = lines_m_c[i]
339
- _, intercept2 = lines_m_c[i + 1]
340
-
341
- if abs(intercept2 - intercept1) < del_threshold:
342
- new_points = np.array(lines_coord[i] + lines_coord[i+1][:-1])
343
- coordinates = np.array(new_points)
344
- points = coordinates[:, None, :].astype(np.int32)
345
- # Fit a line using linear regression
346
- [vx, vy, x, y] = cv2.fitLine(points, cv2.DIST_L2, 0, 0.01, 0.01)
347
- averaged_lines.append((vx, vy, x, y))
348
- i+=2
349
- else:
350
- new_points = np.array(lines_coord[i])
351
-
352
- coordinates = np.array(new_points)
353
- points = coordinates[:, None, :].astype(np.int32)
354
- # Fit a line using linear regression
355
- [vx, vy, x, y] = cv2.fitLine(points, cv2.DIST_L2, 0, 0.01, 0.01)
356
- averaged_lines.append((vx, vy, x, y))
357
- i+=1
358
- if(i < len(lines_m_c)):
359
- new_points = np.array(lines_coord[i])
360
- coordinates = np.array(new_points)
361
- points = coordinates[:, None, :].astype(np.int32)
362
- # Fit a line using linear regression
363
- [vx, vy, x, y] = cv2.fitLine(points, cv2.DIST_L2, 0, 0.01, 0.01)
364
- averaged_lines.append((vx, vy, x, y))
365
-
366
- return averaged_lines
367
-
368
- def get_square_color(image, box):
369
-
370
- # Determine the size of the square region
371
- square_size = (box[1][0] - box[0][0]) / 3
372
-
373
- # Determine the coordinates of the square region inside the box
374
- top_left = (box[0][0] + square_size, box[0][1] + square_size)
375
- bottom_right = (box[0][0] + square_size*2, box[0][1] + square_size*2)
376
-
377
- # Extract the square region from the image
378
- square_region = image[int(top_left[1]):int(bottom_right[1]), int(top_left[0]):int(bottom_right[0])]
379
-
380
- # Calculate the mean pixel value of the square region
381
- mean_value = np.mean(square_region)
382
-
383
- # Determine whether the square region is predominantly black or white
384
- if mean_value < 128:
385
- square_color = "."
386
- else:
387
- square_color = " "
388
-
389
- return square_color
390
-
391
- # accepts image in grayscale
392
- def extract_grid(image):
393
-
394
- # Apply Gaussian blur to reduce noise and improve edge detection
395
- blurred = cv2.GaussianBlur(image, (3, 3), 0)
396
- # Apply Canny edge detection
397
- edges = cv2.Canny(blurred, 50, 150)
398
-
399
- # Apply dilation to connect nearby edges and make them more contiguous
400
- kernel = np.ones((5, 5), np.uint8)
401
- dilated = cv2.dilate(edges, kernel, iterations=1)
402
-
403
- # # Applying canny edge detector
404
- # detecting contours on the canny image
405
- contours, _ = cv2.findContours(dilated, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
406
-
407
- # sorting the contours by the descending order area of the contour
408
- sorted_contours = list(sorted(contours, key=cv2.contourArea,reverse=True))
409
- # filtering out the top 10 largest by applying NMS and only selecting square ones (Apsect ratio 1)
410
- filtered_contours = filter_contours(image, sorted_contours[0:10],iou_threshold=0.6,asp_ratio=1,tolerance=0.2)
411
-
412
- # largest Contour Extraction
413
- largest_contour = []
414
- if(len(filtered_contours)):
415
- largest_contour = filtered_contours[0]
416
- else:
417
- largest_contour = sorted_contours[0]
418
-
419
- # --- Performing Perspective warp of the largest contour ---
420
- coordinates_list = []
421
-
422
- if(largest_contour.shape != (4,1,2)):
423
- largest_contour = cv2.convexHull(largest_contour)
424
- if(largest_contour.shape != (4,1,2)):
425
- rect = cv2.minAreaRect(largest_contour)
426
- largest_contour = cv2.boxPoints(rect)
427
- largest_contour = largest_contour.astype('int')
428
-
429
- coordinates_list = largest_contour.reshape(4, 2).tolist()
430
-
431
- # Convert coordinates_list to a numpy array
432
- coordinates_array = np.array(coordinates_list)
433
-
434
- # Find the convex hull of the points
435
- hull = cv2.convexHull(coordinates_array)
436
-
437
- # Find the extreme points of the convex hull
438
- extreme_points = np.squeeze(hull)
439
-
440
- # Sort the extreme points by their x and y coordinates to determine the order
441
- sorted_points = extreme_points[np.lexsort((extreme_points[:, 1], extreme_points[:, 0]))]
442
-
443
- # Extract top left, bottom right, top right, and bottom left points
444
- tl = sorted_points[0]
445
- tr = sorted_points[1]
446
- bl = sorted_points[2]
447
- br = sorted_points[3]
448
-
449
- if(tr[1] < tl[1]):
450
- tl,tr = tr,tl
451
- if(br[1] < bl[1]):
452
- bl,br = br,bl
453
-
454
- # Define pts1
455
- pts1 = [tl, bl, tr, br]
456
-
457
- # Calculate the bounding rectangle coordinates
458
- x, y, w, h = 0,0,400,400
459
- # Define pts2 as the corners of the bounding rectangle
460
- pts2 = [[3, 3], [400, 3], [3, 400], [400, 400]]
461
-
462
- # Calculate the perspective transformation matrix
463
- matrix = cv2.getPerspectiveTransform(np.float32(pts1), np.float32(pts2))
464
-
465
- # Apply the perspective transformation to the cropped_image
466
- transformed_img = cv2.warpPerspective(image, matrix, (403, 403))
467
- cropped_image = transformed_img.copy()
468
-
469
- # if the largest contour was not exactly quadilateral
470
-
471
- # -- Performing Hough Transform --
472
-
473
- similarity_threshold = math.floor(w/30) # Thresholds for filtering Similar Hough Lines
474
-
475
- # Applying Gaussian Blur to reduce noice and improve dege detection
476
- blurred = cv2.GaussianBlur(cropped_image, (5, 5), 0)
477
- # Perform Canny edge detection on the GrayScale Image
478
- edges = cv2.Canny(blurred, 50, 150)
479
- lines = cv2.HoughLines(edges, 1, np.pi/180, 200)
480
-
481
- # Filter out similar lines
482
- filtered_lines = []
483
- for line in lines:
484
- for r_theta in lines:
485
- arr = np.array(r_theta[0], dtype=np.float64)
486
- rho, theta = arr
487
- is_similar = False
488
- for filtered_line in filtered_lines:
489
- filtered_rho, filtered_theta = filtered_line
490
- # similarity threshold is 10
491
- if abs(rho - filtered_rho) < similarity_threshold and abs(theta - filtered_theta) < np.pi/180 * similarity_threshold:
492
- is_similar = True
493
- break
494
- if not is_similar:
495
- filtered_lines.append((rho, theta))
496
-
497
- # Filter out the horizontal and the vertical lines
498
- horizontal_lines = []
499
- vertical_lines = []
500
- for rho, theta in filtered_lines:
501
- a = np.cos(theta)
502
- b = np.sin(theta)
503
- x0 = a * rho
504
- y0 = b * rho
505
- x1 = int(x0 + 1000 * (-b))
506
- y1 = int(y0 + 1000 * (a))
507
- x2 = int(x0 - 1000 * (-b))
508
- y2 = int(y0 - 1000 * (a))
509
-
510
- slope = (y2 - y1) / (x2 - x1 + 0.0001)
511
- # do taninv(0.17) it is nearly equal to 10
512
- if( abs(slope) <= 0.18 ):
513
- horizontal_lines.append((rho,theta))
514
- elif (abs(slope) > 6):
515
- vertical_lines.append((rho,theta))
516
-
517
- # Find the intersection points of horizontal and vertical lines
518
- hough_corners = []
519
- for h_rho, h_theta in horizontal_lines:
520
- for v_rho, v_theta in vertical_lines:
521
- x, y = parametricIntersect(h_rho, h_theta, v_rho, v_theta)
522
- if x is not None and y is not None:
523
- hough_corners.append((x, y))
524
-
525
- # -- Performing Harris Corner Detection --
526
-
527
- # Create CLAHE object with specified clip limit
528
- clahe = cv2.createCLAHE(clipLimit=3, tileGridSize=(8, 8))
529
- clahe_image = clahe.apply(cropped_image)
530
-
531
- # harris corner detection for CLHAE IMAGE
532
- dst = cv2.cornerHarris(clahe_image,2,3,0.04)
533
- ret,dst = cv2.threshold(dst,0.1*dst.max(),255,0)
534
- dst = np.uint8(dst)
535
- dst = cv2.dilate(dst,None)
536
- ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)
537
- criteria = (cv2.TERM_CRITERIA_EPS+cv2.TermCriteria_MAX_ITER,100,0.001)
538
- harris_corners = cv2.cornerSubPix(clahe_image,np.float32(centroids),(5,5),(-1,-1),criteria)
539
-
540
- drawn_image = cv2.cvtColor(cropped_image, cv2.COLOR_GRAY2BGR)
541
- for i in harris_corners:
542
- x,y = i
543
- image2 = cv2.circle(drawn_image, (int(x),int(y)), radius=0, color=(0, 0, 255), thickness=3)
544
-
545
- # -- Using Regression Model to approximate horizontal and vertical Lines
546
-
547
- # reducing to 0 decimal places
548
- corners1 = list(map(lambda coord: (round(coord[0], 0), round(coord[1], 0)), harris_corners))
549
-
550
- # adding the corners obtained from hough transform
551
- corners1 += hough_corners
552
-
553
- # removing the duplicate corners
554
- corners_no_dup = list(set(corners1))
555
-
556
- min_cell_width = w/30
557
- min_cell_height = h/30
558
-
559
- # grouping coordinates into probabale array that could fit a horizontal and vertical lien
560
- vertical_lines = group_lines(corners_no_dup,0,min_cell_height)
561
- horizontal_lines = group_lines(corners_no_dup,1,min_cell_height)
562
-
563
- actual_vertical_lines = fit_lines(vertical_lines)
564
- actual_horizontal_lines = fit_lines(horizontal_lines,is_horizontal=True)
565
-
566
-
567
- # Lines obtained from above method are not appropriate, we have to refine them
568
-
569
- x_probable = [i[1] for i in actual_horizontal_lines] # looking at the intercepts
570
- y_probable = [i[1] for i in actual_vertical_lines]
571
-
572
- del_x_avg = average_distance(x_probable)
573
- del_y_avg = average_distance(y_probable)
574
-
575
- averaged_horizontal_lines1 = [] # This step here is fishy and needs refinement
576
- averaged_vertical_lines1 = []
577
- multiplier = 0.95
578
- i = 0
579
- while(1):
580
- averaged_horizontal_lines = average_out_similar_lines(actual_horizontal_lines,horizontal_lines,del_y_avg*multiplier,is_horizontal=True)
581
- averaged_vertical_lines = average_out_similar_lines(actual_vertical_lines,vertical_lines,del_x_avg*multiplier,is_horizontal=False)
582
- i += 1
583
- if(i >= 20 or len(averaged_horizontal_lines) == len(averaged_vertical_lines)):
584
- break
585
- else:
586
- multiplier -= 0.05
587
-
588
- averaged_horizontal_lines1 = average_out_similar_lines1(actual_horizontal_lines,horizontal_lines,del_y_avg*multiplier)
589
- averaged_vertical_lines1 = average_out_similar_lines1(actual_vertical_lines,vertical_lines,del_x_avg*multiplier)
590
-
591
-
592
- # plotting the lines to image to find the intersection points
593
- drawn_image6 = np.ones_like(cropped_image)*255
594
- for vx,vy,cx,cy in averaged_horizontal_lines1 + averaged_vertical_lines1:
595
- w = cropped_image.shape[1]
596
- cv2.line(drawn_image6, (int(cx-vx*w), int(cy-vy*w)), (int(cx+vx*w), int(cy+vy*w)), (0, 0, 255),1,cv2.LINE_AA)
597
-
598
- # -- Finding Intersection points --
599
-
600
- # Applying Harris Corner Detection to find the intersection points
601
- mesh_image = drawn_image6.copy()
602
- dst = cv2.cornerHarris(mesh_image,2,3,0.04)
603
-
604
- ret,dst = cv2.threshold(dst,0.1*dst.max(),255,0)
605
- dst = np.uint8(dst)
606
- dst = cv2.dilate(dst,None)
607
- ret, labels, stats, centroids = cv2.connectedComponentsWithStats(dst)
608
- criteria = (cv2.TERM_CRITERIA_EPS+cv2.TermCriteria_MAX_ITER,100,0.001)
609
- harris_corners = cv2.cornerSubPix(mesh_image,np.float32(centroids),(5,5),(-1,-1),criteria)
610
- drawn_image = cv2.cvtColor(drawn_image6, cv2.COLOR_GRAY2BGR)
611
- harris_corners = list(sorted(harris_corners[1:],key = lambda x : x[1]))
612
-
613
- # -- Finding out the grid color --
614
-
615
-
616
- grayscale = cropped_image.copy()
617
- # Perform adaptive thresholding to obtain binary image
618
- _, binary = cv2.threshold(grayscale, 128, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)
619
-
620
- # Perform morphological operations to remove small text regions
621
- kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3))
622
- binary = cv2.morphologyEx(binary, cv2.MORPH_ELLIPSE, kernel, iterations=1)
623
-
624
- # Invert the binary image
625
- inverted_binary = cv2.bitwise_not(binary)
626
-
627
- # Restore the image by blending the inverted binary image with the grayscale image
628
- restored_image = cv2.bitwise_or(inverted_binary, grayscale)
629
-
630
- # Apply morphological opening to remove small black dots
631
- kernel_opening = np.ones((3, 3), np.uint8)
632
- opened_image = cv2.morphologyEx(restored_image, cv2.MORPH_OPEN, kernel_opening, iterations=1)
633
-
634
- # Apply morphological closing to further refine the restored image
635
- kernel_closing = np.ones((5, 5), np.uint8)
636
- refined_image = cv2.morphologyEx(opened_image, cv2.MORPH_CLOSE, kernel_closing, iterations=1)
637
-
638
- # finding out the grid corner
639
- grid = []
640
- grid_nums = []
641
- across_clue_num = []
642
- down_clue_num = []
643
-
644
- sorted_corners = np.array(list(sorted(harris_corners,key=lambda x:x[1])))
645
- if(len(sorted_corners) == len(averaged_horizontal_lines1) * len(averaged_vertical_lines1)):
646
- sorted_corners_grouped = []
647
- for i in range(0,len(sorted_corners),len(averaged_vertical_lines1)):
648
- temp_arr = sorted_corners[i:i+len(averaged_vertical_lines1)]
649
- temp_arr = list(sorted(temp_arr,key=lambda x: x[0]))
650
- sorted_corners_grouped.append(temp_arr)
651
-
652
- for h_line_idx in range(0,len(sorted_corners_grouped)-1):
653
- for corner_idx in range(0,len(sorted_corners_grouped[h_line_idx])-1):
654
- # grabbing the four box coordinates
655
- box = [sorted_corners_grouped[h_line_idx][corner_idx],sorted_corners_grouped[h_line_idx][corner_idx+1],
656
- sorted_corners_grouped[h_line_idx+1][corner_idx],sorted_corners_grouped[h_line_idx+1][corner_idx+1]]
657
- grid.append(get_square_color(refined_image,box))
658
-
659
- grid_formatted = []
660
- for i in range(0, len(grid), len(averaged_vertical_lines1) - 1):
661
- grid_formatted.append(grid[i:i + len(averaged_vertical_lines1) - 1])
662
-
663
-
664
- # if (x,y) is present in these array the cell (x,y) is already accounted as a part of answer of across or down
665
- in_horizontal = []
666
- in_vertical = []
667
-
668
- num = 0
669
-
670
-
671
-
672
- for x in range(0, len(averaged_vertical_lines1) - 1):
673
- for y in range(0, len(averaged_horizontal_lines1) - 1):
674
-
675
- # if the cell is black there's no need to number
676
- if grid_formatted[x][y] == '.':
677
- grid_nums.append(0)
678
- continue
679
-
680
- # if the cell is part of both horizontal and vertical cell then there's no need to number
681
- horizontal_presence = (x, y) in in_horizontal
682
- vertical_presence = (x, y) in in_vertical
683
-
684
- # present in both 1 1
685
- if horizontal_presence and vertical_presence:
686
- grid_nums.append(0)
687
- continue
688
-
689
- # present in one i.e 1 0
690
- if not horizontal_presence and vertical_presence:
691
- horizontal_length = 0
692
- temp_horizontal_arr = []
693
- # iterate in x direction until the end of the grid or until a black box is found
694
- while x + horizontal_length < len(averaged_horizontal_lines1) - 1 and grid_formatted[x + horizontal_length][y] != '.':
695
- temp_horizontal_arr.append((x + horizontal_length, y))
696
- horizontal_length += 1
697
- # if horizontal length is greater than 1, then append the temp_horizontal_arr to in_horizontal array
698
- if horizontal_length > 1:
699
- in_horizontal.extend(temp_horizontal_arr)
700
- num += 1
701
- across_clue_num.append(num)
702
- grid_nums.append(num)
703
- continue
704
- grid_nums.append(0)
705
- # present in one 1 0
706
- if not vertical_presence and horizontal_presence:
707
- # do the same for vertical
708
- vertical_length = 0
709
- temp_vertical_arr = []
710
- # iterate in y direction until the end of the grid or until a black box is found
711
- while y + vertical_length < len(averaged_vertical_lines1) - 1 and grid_formatted[x][y+vertical_length] != '.':
712
- temp_vertical_arr.append((x, y+vertical_length))
713
- vertical_length += 1
714
- # if vertical length is greater than 1, then append the temp_vertical_arr to in_vertical array
715
- if vertical_length > 1:
716
- in_vertical.extend(temp_vertical_arr)
717
- num += 1
718
- down_clue_num.append(num)
719
- grid_nums.append(num)
720
- continue
721
- grid_nums.append(0)
722
-
723
- if(not horizontal_presence and not vertical_presence):
724
-
725
- horizontal_length = 0
726
- temp_horizontal_arr = []
727
- # iterate in x direction until the end of the grid or until a black box is found
728
- while x + horizontal_length < len(averaged_horizontal_lines1) - 1 and grid_formatted[x + horizontal_length][y] != '.':
729
- temp_horizontal_arr.append((x + horizontal_length, y))
730
- horizontal_length += 1
731
- # if horizontal length is greater than 1, then append the temp_horizontal_arr to in_horizontal array
732
-
733
- # do the same for vertical
734
- vertical_length = 0
735
- temp_vertical_arr = []
736
- # iterate in y direction until the end of the grid or until a black box is found
737
- while y + vertical_length < len(averaged_vertical_lines1) - 1 and grid_formatted[x][y+vertical_length] != '.':
738
- temp_vertical_arr.append((x, y+vertical_length))
739
- vertical_length += 1
740
- # if vertical length is greater than 1, then append the temp_vertical_arr to in_vertical array
741
-
742
- if horizontal_length > 1 and horizontal_length > 1:
743
- in_horizontal.extend(temp_horizontal_arr)
744
- in_vertical.extend(temp_vertical_arr)
745
- num += 1
746
- across_clue_num.append(num)
747
- down_clue_num.append(num)
748
- grid_nums.append(num)
749
- elif vertical_length > 1:
750
- in_vertical.extend(temp_vertical_arr)
751
- num += 1
752
- down_clue_num.append(num)
753
- grid_nums.append(num)
754
- elif horizontal_length > 1:
755
- in_horizontal.extend(temp_horizontal_arr)
756
- num += 1
757
- across_clue_num.append(num)
758
- grid_nums.append(num)
759
- else:
760
- grid_nums.append(0)
761
-
762
-
763
- size = { 'rows' : len(averaged_horizontal_lines1)-1,
764
- 'cols' : len(averaged_vertical_lines1)-1,
765
- }
766
-
767
- dict = {
768
- 'size' : size,
769
- 'grid' : grid,
770
- 'gridnums': grid_nums,
771
- 'across_nums': down_clue_num,
772
- 'down_nums' : across_clue_num,
773
- 'clues':{
774
- 'across' : [],
775
- 'down': []
776
- }
777
- }
778
-
779
- return dict
780
-
781
- if __name__ == "__main__":
782
- img = cv2.imread("D:\\D\\Major Project files\\opencv\\movie.png",0)
783
- down = extract_grid(img)
784
- print(down)
785
- # img = Image.open("chalena3.jpg")
786
- # img_gray = img.convert("L")
787
- # print(extract_grid(img_gray))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
main.py CHANGED
@@ -1,26 +1,17 @@
1
- from fastapi import FastAPI,UploadFile,File,status,HTTPException,Request
2
  import os
3
  from Crossword_inf import Crossword
4
  from BPSolver_inf import BPSolver
5
  from Strict_json import json_CA_json_converter
6
 
 
7
  from fastapi.middleware.cors import CORSMiddleware
8
- import aiofiles
9
- import cv2
10
- from extractpuzzle import extract_grid,get_text
11
-
12
- MODEL_PATH = os.path.join("Inference_components","dpr_biencoder_trained_500k.bin")
13
- ANS_TSV_PATH = os.path.join("Inference_components","all_answer_list.tsv")
14
- DENSE_EMBD_PATH = os.path.join("Inference_components","embeddings_all_answers_json_0*")
15
 
16
  MODEL_PATH_DISTIL = os.path.join("Inference_components","distilbert_EPOCHs_7_COMPLETE.bin")
17
  ANS_TSV_PATH_DISTIL = os.path.join("Inference_components","all_answer_list.tsv")
18
  DENSE_EMBD_PATH_DISTIL = os.path.join("Inference_components","distilbert_7_epochs_embeddings.pkl")
19
 
20
-
21
  app = FastAPI()
22
- # for reading images in chunk
23
- CHUNK_SIZE = 1024 * 1024 * 2
24
 
25
  app.add_middleware(
26
  CORSMiddleware,
@@ -29,65 +20,75 @@ app.add_middleware(
29
  allow_headers=["*"],
30
  allow_credentials=True,
31
  )
32
-
33
- @app.get("/")
34
- async def index():
35
- return {"message": "Hello World"}
36
-
37
- @app.post("/solve")
38
- async def solve(request: Request):
39
- json = await request.json()
40
- puzzle = json_CA_json_converter(json, False)
41
- crossword = Crossword(puzzle)
42
- solver = BPSolver(crossword, model_path = MODEL_PATH_DISTIL,
43
- ans_tsv_path = ANS_TSV_PATH_DISTIL,
44
- dense_embd_path = DENSE_EMBD_PATH_DISTIL,
45
- max_candidates = 40000,
46
- model_type = 'distilbert')
47
- solution = solver.solve(num_iters = 100, iterative_improvement_steps = 0)
48
- return solution, solver.evaluate(solution)
49
-
50
- @app.post("/parseImage/")
51
- async def upload(file: UploadFile = File(...)):
52
-
53
- try:
54
- filepath = os.path.join('./', os.path.basename(file.filename))
55
- async with aiofiles.open(filepath, 'wb') as f:
56
- while chunk := await file.read(CHUNK_SIZE):
57
- await f.write(chunk)
58
- except Exception:
59
- raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
60
- detail='There was an error uploading the file')
61
- finally:
62
- await file.close()
63
 
64
- img_array = cv2.imread(filepath,0)
65
-
66
- grid_data = {}
67
- clue_data = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
 
 
 
 
69
 
70
- try: # try extracting the grid from the image
71
- # dict = { 'size' : size, 'grid' : grid, 'gridnums': grid_nums, 'across_nums': down_clue_num,'down_nums' : across_clue_num }
72
- grid_data = extract_grid(img_array)
73
- grid_data['gridExtractionStatus'] = "Passed"
74
- except Exception as e:
75
- grid_data['gridExtractionStatus'] = "Failed"
76
 
77
-
78
- try: # try extracting clues
79
- acrossClues, downClues = get_text(img_array) # { number : [column_of_projection_profile,extracted_text]}
80
- clue_data['across'] = acrossClues
81
- clue_data['down'] = downClues
82
- clue_data['gridExtractionStatus'] = "Passed"
83
- except Exception as e:
84
- grid_data['ClueExtractionStatus'] = "Failed"
85
 
86
- grid_data.update(clue_data)
87
 
88
- return grid_data
 
 
 
 
 
 
 
 
89
 
90
-
 
 
91
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
 
 
 
 
 
 
93
 
 
1
+ from fastapi import FastAPI,Request
2
  import os
3
  from Crossword_inf import Crossword
4
  from BPSolver_inf import BPSolver
5
  from Strict_json import json_CA_json_converter
6
 
7
+ import asyncio
8
  from fastapi.middleware.cors import CORSMiddleware
 
 
 
 
 
 
 
9
 
10
  MODEL_PATH_DISTIL = os.path.join("Inference_components","distilbert_EPOCHs_7_COMPLETE.bin")
11
  ANS_TSV_PATH_DISTIL = os.path.join("Inference_components","all_answer_list.tsv")
12
  DENSE_EMBD_PATH_DISTIL = os.path.join("Inference_components","distilbert_7_epochs_embeddings.pkl")
13
 
 
14
  app = FastAPI()
 
 
15
 
16
  app.add_middleware(
17
  CORSMiddleware,
 
20
  allow_headers=["*"],
21
  allow_credentials=True,
22
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
 
24
+ async def solve_puzzle(json):
25
+ puzzle = json_CA_json_converter(json, False)
26
+ crossword = Crossword(puzzle)
27
+
28
+ # Perform asynchronous operations using asyncio.gather or asyncio.create_task
29
+ async def solve_async():
30
+ return await asyncio.to_thread(BPSolver, crossword, model_path=MODEL_PATH_DISTIL,
31
+ ans_tsv_path=ANS_TSV_PATH_DISTIL,
32
+ dense_embd_path=DENSE_EMBD_PATH_DISTIL,
33
+ max_candidates=40000,
34
+ model_type='distilbert')
35
+
36
+ solver = await solve_async()
37
+
38
+ # Run solve method asynchronously
39
+ async def solve_method_async():
40
+ return await asyncio.to_thread(solver.solve, num_iters=100, iterative_improvement_steps=0)
41
 
42
+ solution = await solve_method_async()
43
+ evaluation = await asyncio.to_thread(solver.evaluate, solution)
44
+
45
+ return solution, evaluation
46
 
 
 
 
 
 
 
47
 
48
+ fifo_queue = asyncio.Queue()
 
 
 
 
 
 
 
49
 
50
+ jobs = {}
51
 
52
+ async def worker():
53
+ while True:
54
+ print(f"Worker got a job: (size of remaining queue: {fifo_queue.qsize()})")
55
+ job_id, job, args, future = await fifo_queue.get()
56
+ jobs[job_id]["status"] = "processing"
57
+ result = await job(*args)
58
+ jobs[job_id]["result"] = result
59
+ jobs[job_id]["status"] = "completed"
60
+ future.set_result(job_id)
61
 
62
+ @app.on_event("startup")
63
+ async def on_start_up():
64
+ asyncio.create_task(worker())
65
 
66
+ @app.post("/solve")
67
+ async def solve(request: Request):
68
+ json = await request.json()
69
+ future = asyncio.Future()
70
+ job_id = id(future)
71
+ jobs[job_id]= {"status":"queued"}
72
+ await fifo_queue.put((job_id, solve_puzzle, [json], future))
73
+ return {"job_id": job_id}
74
+
75
+ @app.get("/result/{job_id}")
76
+ async def get_result(job_id: int):
77
+ if job_id in jobs:
78
+ returnVal = {}
79
+ returnVal = {**jobs[job_id]}
80
+ if(jobs[job_id]["status"]=="queued"):
81
+ queue_size = fifo_queue.qsize()
82
+ for index, (queued_job_id, _, _, _) in enumerate(fifo_queue._queue):
83
+ if job_id == queued_job_id:
84
+ returnVal["queue_status"] = f"Enqueued In : {index + 1}/{queue_size}"
85
+ return returnVal
86
+ return {"error": "Job not found or completed"}
87
 
88
+ @app.get("/")
89
+ async def home():
90
+ return {
91
+ "Success" : "True",
92
+ "Message" : "Pong"
93
+ }
94
 
requirements.txt CHANGED
@@ -7,9 +7,7 @@ transformers==4.35.2
7
  wordsegment==1.3.1
8
  torch==2.1.1
9
  faiss-cpu==1.7.4
10
- aiofiles==23.2.1
11
  python-multipart
12
- opencv-python-headless==4.6.0.66
13
  pytesseract==0.3.10
14
  scikit-learn==1.3.2
15
 
 
7
  wordsegment==1.3.1
8
  torch==2.1.1
9
  faiss-cpu==1.7.4
 
10
  python-multipart
 
11
  pytesseract==0.3.10
12
  scikit-learn==1.3.2
13