Skip to content
Permalink
master
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
'''
* Team Id: eYRCPlus-PS1#2678
* Author List: Avick Dutta, Arpan Das, Subhendu Hazra, Asesh Basu
* Filename: task6_code.py
* Theme: Puzzle Solver Robot (GLCD) - eYRCPlus
* Functions: sortContoursByArea, sortContoursByPosition, cropDigits, digitCount, detectDigit, imgToNumber,
* cropBiggestContourAsImage, drawBorderAroundDigits, play, matcher, generate_output, substract_list,
* get_result_set, puzzle, generate_traversal_path
* Global Variables: None
'''
import numpy as np
import cv2
from operator import itemgetter
import itertools
from serial import Serial
######################### Task1 Functions start #######################
#######################################################################
######################### my functions ##########################
# sorting contours by area (bigger first)
'''
* Function Name: sortContoursByArea
* Input: contours -- is a list of contours as input argument #0,
* rev -- (reverse) is a boolean as input argument #1 : default True for bigger first
* Output: contours_ret -- is a list of sorted contours
* Logic: Iterate throught all contours and check for area of each one. Then we store area and coutour
* pair in array variable area_list. Then we sort it by area using sorted function. After that
* we append only contours from area_list to contours_ret. Then we return contours_ret
* Example Call: sortContoursByArea(contours, True)
'''
def sortContoursByArea (contours, rev=True):
area_list = []
for contour in contours:
item = [cv2.contourArea(contour), contour]
area_list.append(item)
area_list = sorted(area_list, key=itemgetter(0), reverse=rev) # 0=area, 1=contour
contours_ret = []
for i in range(0, len(area_list)):
contours_ret.append(area_list[i][1])
return contours_ret
# sortContoursByArea end
# sorting contours by position
'''
* Function Name: sortContoursByPosition
* Input: contours -- is a list of contours as input argument #0,
* rev -- (reverse) is a boolean as input argument #1 : default True for bigger first,
* sortBy -- (sort by direction) is a string as input argument #2 : default 'x'
* Output: contours_ret -- is a list of sorted contours
* Logic: We iterate through contours and get positions of each one. We append 3-tuple i.e.
* x-coordinate, y-coordinate, and contour in array variable position_list.
* Now, if sortBy is "x", then we sort position_list by x-coordinate with given rev
* else, we sort position_list by y-coordinate with given rev. After that, we iterate
* through position_list and append contours only from it to array variable contours_ret.
* Then we return contours_ret
* Example Call: sortContoursByPosition(contours, True, 'x')
'''
def sortContoursByPosition (contours, rev=True, sortBy='x'):
position_list = []
for contour in contours:
# finding centroid of a particular contour
M = cv2.moments(contour)
try:
cx = int(M["m10"]/M["m00"])
cy = int(M["m01"]/M["m00"])
except: pass
item = [cx, cy, contour]
position_list.append(item)
if sortBy == "x": position_list = sorted(position_list, key=itemgetter(0), reverse=rev) # 0=cx, 1=cy
else: position_list = sorted(position_list, key=itemgetter(1), reverse=rev) # 0=cx, 1=cy
contours_ret = []
for i in range(0, len(position_list)):
contours_ret.append(position_list[i][2])
return contours_ret
# sortContoursByPosition end
'''
* Function Name: cropDigits
* Input: img -- is an image as input argument #0,
* Output: returns crops -- is a 2d list containing cropped digits
* Logic: We convert the input image to grey scale and fetch contours from it. Then we crop output
* the actial digit with rectangular shape by using cv2.boundingRect() function and append
* the cropped portion to array variable crops. Then we return crops.
* Example Call: cropDigits(img, 1)
'''
# crop digits from an image with digits ony
def cropDigits (img):
# getting contours
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) # cv2.THRESH_BINARY = 0
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contours = sortContoursByPosition(contours)
# crop digit and store them into list crops
crops = []
for contour in contours:
x, y, w, h = cv2.boundingRect(contour)
crops.append(img[y:y+h, x:x+w])
return crops
# cropDigits end
# count number of digit from a image (only with digits)
'''
* Function Name: digitCount
* Input: img -- is an image as input argument #0
* Output: contour_count -- is an integer, returns number of contours
* Logic: If img is a string i.e. image's file name, then we read the image, else its already an image.
* Then, if the image is extreme black or extreme white i.e. the image is blank, then return 0.
* Then we convert the input image to grey scale and fetch contours from it. After that, we count
* the number of elements on variable contours return it.
* Example Call: digitCount(img)
'''
def digitCount (img):
if type(img) == str: img = cv2.imread(img)
h, w, c = img.shape
# handling blank images
if np.sum(img[:, :, :]) == 0 or np.sum(img[:, :, :]) == h*w*c*255: return 0
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) # cv2.THRESH_BINARY = 0
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contour_count = len(contours)
return contour_count
# blankOrOneOrTwoDigits end
# detect a single digit from a image (only with one digit)
'''
* Function Name: detectDigit
* Input: img1 -- is an image as input argument #0
* Output: retVal -- is an integer, which is the recogized digit
* Logic: If img is a string i.e. image's file name, then we read the image, else its already an image.
* Note that img1 is needle and img2 is haystack. We store height and width in img1 in h1 and w1 respectively.
* We iterate through variable i in range 0-9 and indented process below is for each iteration. (Please see the
* directory "data" which has numbers stored as images with the number as file name. This is the memory of the program).
* We read the image with file name and stores height and width in variables h2 and w2
* respectively. Then we calculate average height and width and store them into hAvg and wAvg respectively.
* After that we resize both the images with average height and width i.e. hAvg and wAvg respectively. Then
* we calculates the image difference by XOR-ing two images using cv2.bitwise_xor() method and store the
* result into variable diff. Then we sum up all pixels of diff (black=0, white=255) and store the result
* in the variable sumOfDiff. After that we append pair of i and sumOfDiff into array matched_digit_list.
* After all abobe process, we sort the matched_digit_list in ascending order and stores the first element i.e.
* the least difference into variable retval and then we return retval.
*
* Example Call: detectDigit (img)
'''
def detectDigit (img1): # img1 = needle, img2 = haystack
if type(img1) == str: img1 = cv2.imread(img1)
h1, w1, _ = img1.shape
matched_digit_list = [] # it will contain list of index and difference magnitude
for i in range (0, 10):
img2 = cv2.imread("data/%d.png" % i)
h2, w2, _ = img2.shape
# resize both image of their average resolution
hAvg, wAvg = (h1+h2)/2, (w1+w2)/2
img1 = cv2.resize(img1, (wAvg, hAvg))
img2 = cv2.resize(img2, (wAvg, hAvg))
#diff = img1 - img2 # img2 - img1 not working
diff = cv2.bitwise_xor(img1, img2) # Bitwise-AND mask and original image
# applying morph: erosion operation
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
diff = cv2.erode(diff, kernel, iterations=1)
# applying median blur
diff = cv2.medianBlur(diff, 3)
# calculating sum of exclusive different pixels
sumOfDiff = np.sum(diff[:, :, :])
#print sumOfDiff
matched_digit_list.append([i, sumOfDiff])
# end for
matched_digit_list = sorted(matched_digit_list, key=itemgetter(1)) # sort asc
retval = matched_digit_list[0][0] # get most least difference
return retval # return most matched
# end detectDigit
# comverts image containing 2 digits number to equivalent integer
# detect and convert image to number (with any no of digits)
# 1. count digits -> 2. crops them -> 3. detect each digit -> 4. calculate total value
'''
* Function Name: imgToNumber
* Input: img -- is an image as input argument #0
* Output: retval -- is an integer
* Logic: This function comverts image containing 2 digits number to equivalent integer. At first we check
* if img is a string i.e. image's file name, then we read the image, else its already an image.
* Then we count digits using function digitCount() and store the result in the variable digCnt.
* After that we crop img using function cropDigits() and store the result in variable d. Then we iterate i
* in range from 0 to digCnt and repeat the indented steps below.
* We detect i-th digit from array variable d ans stores current digit in the variable thisDigit.
* Then we append thisDigit * 10^i to integer variable retval.
* int variable retval will be returned.
* Example Call: imgToNumber(img)
'''
def imgToNumber (img):
if type(img) == str: img = cv2.imread(img)
h, w, c = img.shape
digCnt = digitCount(img) # 1. count digits
if digCnt == 0: return None
d = cropDigits(img) # 2. crops them
# 4. calculate total value
retval = 0
for i in range(0, digCnt):
thisDigit = detectDigit(d[i]) # 3. detect each digit
retval += thisDigit * pow(10, i) # because 123 = 3*(10^0) + 2*(10^1) + 1*(10^2)
return retval
# imgToNumber end
# crop and get the biggest external contour from an image
'''
* Function Name: cropBiggestContourAsImage
* Input: img -- is an image as input argument #0
* extraPadding -- (extra padding) is an integer as input argument #1 : default 0,
* cropWidth -- (crop by width) is a boolean as input argument #2,
* cropHeight -- (crop by height) is a boolean as input argument #3,
* howMany -- (how many biggest contour to be returned) is an integer as input argument #4,
* threshMode -- (threshholding mode) is an integer as input argument #5
* Output: crops -- is a 2d list containing cropped digits
* Logic: If img is a string i.e. image's file name, then we read the image, else its already an image.
* Then, if the image is extreme black or extreme white i.e. the image is blank, then return the img itself.
* Then we convert the input image to grey scale and fetch contours from it and also count number of contours
* and store it to variable contour_count. Then we sort contours by area using sortContoursByArea() function.
* If howMany > contour_count, then we print error and return them img itself.
* Now we iterate i in range from 0 to howMany and repeat the indenteded steps below:
* We get top-left co-ordinate of i'th contour using cv2.boundingRect() method.
* Then we add some padding according to boolean variables cropWidth and cropHeight and append the
* cropped image to array variable crops.
* All after above process, we return crops.
* Example Call: cropBiggestContourAsImage(img, extraPadding, threshMode=cv2.THRESH_OTSU)
'''
def cropBiggestContourAsImage (img, extraPadding=0, cropWidth=True, cropHeight=True, howMany=1, threshMode=cv2.THRESH_OTSU): # find the biggest contour using
if type(img) == str: img = cv2.imread(img)
h, w, c = img.shape
# handling blank images
if np.sum(img[:, :, :]) == 0 or np.sum(img[:, :, :]) == h*w*c*255: return [img]
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, threshMode) # cv2.THRESH_BINARY = 0
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
contour_count = len(contours)
contours = sortContoursByArea(contours) # need sortContoursByArea() but if digit, need sortContoursByPosition()
if howMany > contour_count:
print "Error: howMany should be less than contour_count!"
return [img]
crops = []
for i in range (0, howMany):
x, y, wd, ht = cv2.boundingRect(contours[i])
if cropWidth == True and cropHeight == True: crops.append(img[y-extraPadding:y+ht+extraPadding, x-extraPadding:x+wd+extraPadding])
elif cropWidth == True and cropHeight == False: crops[i].append(img[:, x-extraPadding:x+wd+extraPadding])
elif cropWidth == False and cropHeight == True: crops[i].append(img[y-extraPadding:y+ht+extraPadding, :])
else: crops[i].append(img)
return crops
# cropBiggestContourAsImage end
# draw borders around digits for image
'''
* Function Name: drawBorderAroundDigits
* Input: img -- is an image as input argument #0,
* color -- is a rgb color tuple as input argument #1,
* borderWidth -- (border width) is an integer as input argument #2,
* extraPadding -- (extra padding) is an integer as input argument #3 : default 0
* Output: img -- is an image
* Logic: Invert the img, crop out the biggest contour and store back to img. Then invert back the img.
* Then we convert the input image to grey scale and fetch contours from it. Then we iterate
* through each contour and follow repeat the indented steps below.
* Get contour area by using cv2.contourArea() method and if the area > 400 or area < 2800
* then we draw green contour border of 2 pixels using cv2.drawContours() method
* All after above process, we return img
* Example Call: drawBorderAroundDigits(original, (0, 255, 0), 2, extraPadding=10)
'''
def drawBorderAroundDigits (img, color, borderWidth, extraPadding=0):
cv2.bitwise_not(img, img) # invert the image
img = cropBiggestContourAsImage(img, extraPadding, threshMode=cv2.THRESH_OTSU)[0]
cv2.bitwise_not(img, img) # invert the image back
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU) # cv2.THRESH_BINARY = 0
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
for i, contour in enumerate(contours):
area = cv2.contourArea(contour)
areaLowerBound = int(round((img.size/3) * 0.11083/100)) # 400 with ratio of current test image size
areaUpperBound = int(round((img.size/3) * 0.77581/100)) # 2800 with ratio of current test image size
if area > areaLowerBound and area < areaUpperBound:
cv2.drawContours(img, contours, i, color, 2)
return img
# drawBorderAroundDigits end
#################################################################
'''
* Function Name: play
* Input: img -- a single test image as input argument
* Output: No_pos_D1 -- List containing detected numbers in Division D1,
* No_pos_D2 -- List of pair [grid number, value]
* Logic: At first we backup the img into variable orinal and invert img using cv2.bitwise_not()
* Then we calculated the required padding and store it into variable padding. After that we get
* the biggest contour from img and store it into variable arena. Then we get height and width
* of arena and store them into variables h and w respectively. After that we draw a vertical
* line to separate D1 and D2 and the line has distance 42% from left of the arena.
* Then we applied morphing i.e. arode the arena.
* Then we convert the image arena to grey scale and fetch contours from it. Then we reverse contours.
* Then we iterate through each contour and follow repeat the indented steps below. (i=iteration counter)
* if i > 37, then break the loop
* calculate contour area and store it into variable area
* get area bound i.e. allowed area for contours (size of a box) and store it into variable areaBound
* if area < areaBound, then skip the rest part of cussrent iteration
* Then we get crop and cut the digit only, and convert it into actual integer number and store it
* to variable digit.
* Now, if i >= 0 and i <= 11, then append digit to array variable No_pos_D1
* else if i >= 13 and i <= 36, then do as indented steps below:
* If digit is valid, then append pair of i-13 and digit to variable pair.
* After that append variable pair to array variable No_pos_D2.
* i = i + 1
* After that, draw 2px green border around digits using function drawBorderAroundDigits and show img
* At the end, return No_pos_D1 and No_pos_D2
* Example Call: play(img)
'''
def play (img):
original = img.copy() # making backup of original img
cv2.bitwise_not(img, img) # invert the image
padding = int(round((img.size/3) * 0.00138/100)) # 5px with ratio of current test image size
arena = cropBiggestContourAsImage(img, extraPadding=padding)[0] # get the biggest external contour
h, w, _ = arena.shape
# draw line to separate D1 and D2 (image, start, end, color, thickness)
cv2.line(arena, (int(w*42/100), 0), (int(w*42/100), h), (0, 0, 0), 1) # for extraPadding=30
# apply morphing: erode arena
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (3, 3))
erosion = cv2.erode(arena, kernel, iterations=1) # erosion
arena = erosion
## fetch divisions start
gray = cv2.cvtColor(arena, cv2.COLOR_BGR2GRAY)
ret, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_OTSU)
contours, hierarchy = cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
contours.reverse()
arena_for_show = arena.copy() # backup
No_pos_D1 = [] # format for D1 = [1, 2, 1, 3, 7, 6, 2, 9, 8, 1, 5, 4]
No_pos_D2 = [] # format for D2 = [ [5,10], [6,11], [15,15] ]
i=0
for contour in contours:
# value of i: 0-11=12 boxes of D1, 12=whole D1, 13-36=24 boxes of D2, 37=whole D2
if i > 37: break
area = cv2.contourArea(contour)
# get area bound i.e. allowed area for contours (size of a box)
#areaBound = int(round((arena.size/3) * 0.69268/100)) # 2500px with ratio of current test image size
areaBound = int(round((arena.size/3) * 1/100)) # 2500px with ratio of current test image size
if area < areaBound: continue
x, y, w, h= cv2.boundingRect(contour)
np = int(round((arena.size/3) * 0.00138/100)) # negative padding 5px with ratio of current test image size
box = arena[y+np:y+h-np, x+np:x+w-np, :].copy() # h, w, c
digit = imgToNumber(box)
if i >= 0 and i <= 11: # get left div i.e. D1
try: # getting error, some contour h=0 w=0 coming sometimes
No_pos_D1.append(digit)
## export single digits as file to build the memory/template
# crop = cropBiggestContourAsImage(box)[0];
# cv2.imwrite("data12/"+str(i)+".png", crop)
except: pass
# you may uncomment below and imshow("show", arena_for_show) to check the operational img
#cv2.putText(arena_for_show, str(i), (x+60, y+80), 0, 0.5, (255, 255, 255), 1, cv2.CV_AA)
elif i>=13 and i <= 36: # get right div i.e. D2
try: # getting error, some contour h=0 w=0 coming sometimes
if not(digit == None): # if the digit is not none
pair = [i-13, digit]
No_pos_D2.append(pair)
except: pass
# you may uncomment below and imshow("show", arena_for_show) to check the operational img
#cv2.putText(arena_for_show, str(i-13), (x+60, y+80), 0, 0.5, (255, 255, 255), 1, cv2.CV_AA)
i += 1
# end for
#cv2.imshow("arena_for_show", arena_for_show)
## drawing contours border around the digits and showing output image
padding = int(round((arena.size/3) * 0.00692/100)) # 25px with ratio of current test image size
img = drawBorderAroundDigits(original, (0, 255, 0), 2, extraPadding=padding)
cv2.imshow("contour", img)
# returning D1, D2 lists
#No_pos_D1 = "D1 = " + str(No_pos_D1)
#No_pos_D2 = "D2 = " + str(No_pos_D2)
return No_pos_D1, No_pos_D2
######################### Task1 Functions end #########################
#######################################################################
######################### Task2 Functions start #######################
#######################################################################
########################################### My functions ###############################################
########################################################################################################
'''
* Function Name: matcher
* Input: Given_array -- is an integer list as input argument #0,
* Required_number -- is an integer number as input argument #1 : default True for bigger first,
* operands -- is an integer number as input argument #2 : default 1
* Output: matcher.results -- is a static list of integer numbers : initialized with []
* Logic: If matcher.results is not empty or operands > 12, then return
* Generate all unique possible combinations of Given_array with operands
* Then iterating through each combinations, repeat the following indented steps below:
* sum = 0
* iterating throught range from 0 to operands, taking i as iteration counter, repeat dedention below:
* sum = sum + i-th combination
* if sum is equals to required number, then append conbination to array variable matcher.results
* If matcher.results is empty, then recursively call function matcher() with operand+1
* else sort matcher.results in reverse order.
* Example Call: matcher(D1, number)
'''
def matcher (Given_array, Required_number, operands=1): ################################################
## recursion breaking condition: (matcher.results is not empty or operands > 12)
if (matcher.results or operands > 12): return
## generating all possible combinations
combinations = list(itertools.combinations(Given_array, operands)) # returns combinations as list of tuples
combinations = list(set(combinations)) # make combinations unique by convert to set and then list
combinations = [list(x) for x in combinations] # convert tuples into lists
for combination in combinations: # iterate through every distinct combinations
sum = 0
for i in range(0, operands): sum += combination[i] # calculating sum of n digit combinations
if (sum == Required_number): matcher.results.append(combination) # append matched to results
## if no result found i.e. matcher.results is empty for n operands, then recursively try with n+1
if (not matcher.results): matcher(Given_array, Required_number, operands+1)
else: matcher.results.sort(reverse=True)
matcher.results = [] # static variable of matcher() function
# end of matcher #######################################################################################
'''
* Function Name: generate_output
* Input: number -- is an integer numbers as input argument #0,
* operands -- is an integer list as input argument #1
* Output: output -- is a string with format x = y + z
* Logic: Concatenate number following " = " to initially blank string variable output.
* If operand is not empty, then iterate through range from 0 to no of elements in operands and repeat indention below:
* concatenate i-th operand from operands array to output string.
* If i is not equal to no of elements in operands - 1 then concatenete " + " to output string.
* else concatenate "Incomplete" to output string.
* Return th output string variable.
* Example Call:
'''
def generate_output (number, operands): ################################################################
output = str(number) + " = "
if (operands): # if operand is not empty
# forming the output strnig from operands list
for i in range(0, len(operands)):
output += str(operands[i])
if not(i == len(operands)-1): output += " + "
else:
output += "Incomplete"
return output
# end of generate_output ###############################################################################
# substract lit b from list a
'''
* Function Name: substract_list
* Input: a -- is an integer list as input argument #0,
* b -- is an integer list as input argument #1
* Output: a -- is an integer list
* Logic: Remove x from a for each x found in b.
* Example Call: substract_list(D1, result)
'''
def substract_list (a, b): #############################################################################
try:
[a.remove(x) for x in b]
except Exception as e:
pass
#print e.args, e.message
return a
# end of substract_list ################################################################################
'''
* Function Name: get_result_set
* Input: D1 -- is an integer list as input argument #0,
* D2_values -- is an integer list as input argument #1
* Output: result_set -- is an integer list in form of [x, y, z] if x = y + z,
* priority -- is an integer which is the priority of a result set by no of operand used and incomplete.
* Logic: Iterate through each number present in D2_values and repeat the indented steps below:
* Call matcher(D1, number)
* If matcher.results is not empty then set variable result = matcher.results and reset matcher.results
* If result is found, then remove the result from D1
* Make array of one element i.e. number and result and append the pair into result_set (result_set
* will have lists that each list contains list thatthe first element is the number [0], and rest of
* others [1:]are the operands used the make the sum of that number)
* Initialize new variable operand_used = 0, incomplete = 0, priority = 0, priority_one_var = 0,
* priority_two_or_more_var = 0, i.e. all equals to 0 initially.
* Now, iterate through each result present in result_set and follow the indented steps below:
* operand_used = operand_used + number of elements in result - 1
* If number of elements in result is equals to 1, then incomplete = incomplete + 1 (i.e. no = incomplete (no operand))
* Else if number of elements in result is equals to 2, then priority_one_var = priority_one_var + 1 (i.e. no = x (one operand))
* Else priority_two_or_more_var = priority_two_or_more_var + 1 (i.e. no = x+y+... (two or more operands))
* operand_used = 12 - operand_used (complement it as lower is better (12 = no of element in D1))
* incomplete = number of element in D2_values - incomplete (complement it too as lower is better)
* incomplete = incomplete * incomplete (giving more -ve priority: as using more operands is better than leave incomplete)
* Then finally priority = (incomplete * operand_used) - priority_one_var + priority_two_or_more_var
* All after abobe process, we return result_set and priority.
* Example Call: get_result_set(list(D1), combination)
'''
def get_result_set (D1, D2_values): ####################################################################
## getting result set for all different combinations of D2
result_set = []
for number in D2_values: # getting each values of D2
try:
result = [] # stores the best result
## find result
matcher(D1, number)
if (matcher.results):
result = matcher.results[0]
matcher.results = []
## remove result from D1
if result:
D1 = substract_list(D1, result)
# result_set will have lists that each list contains list that
# the first element is the number [0], and rest of others [1:]
# are the operands used the make the sum of that number
result_set.append([number] + result)
#print generate_output(number, result)
except RuntimeError as re: print re.args, re.message
# end for loop
## calculate priority of the result set
operand_used = 0
incomplete = 0
priority = 0
priority_one_var = 0 # priority decrementer
priority_two_or_more_var = 0 # priority incrementer
for result in result_set:
operand_used += len(result)-1
if (len(result) == 1): incomplete += 1 # no = incomplete (no operand)
elif (len(result) == 2): priority_one_var += 1 # no = x (one operand)
else: priority_two_or_more_var += 1 # no = x+y+... (two or more operands)
# end for loop
operand_used = 12 - operand_used # complement it as lower is better (12 = no of element in D1)
incomplete = len(D2_values)-incomplete # complement it too as lower is better
incomplete *= incomplete # giving more -ve priority: as using more operands is better than leave incomplete
priority = (incomplete * operand_used) - priority_one_var + priority_two_or_more_var
return result_set, priority
# end of get_result_set ################################################################################
########################################################################################################
'''
* Function Name: puzzle
* Input: D1 -- is an integer list as input argument #0,
* D2 -- is an integer list as input argument #1
* Output: retVal -- is a 2D integer list
* Logic: Sort D1 in desc order. Then get only values without positions from D2 and sort by descending order.
* Then we get all possible combinations of D2. Initially we set integer variable max_priority = 0.
* Now we iterate through each combination in D2_combinations and do the indented steps below:
* get result_set, priority from function get_result_set(list(D1), combination)
* If priority >= max_priority, then set max_priority = priority and best_result_set = result_set.
* Then sorting back best_result_set by original positions of D2 though it doesn't matter as e-yantra said.
* Now, iterate through each result in results_sorted and taking i as iterator counter, repeat as indented follows:
* Append result to retVal
* After all the above process, return retVal.
* Example Call: puzzle(D1, D2)
'''
def puzzle (D1, D2):
## sort D1 in desc order
D1.sort(reverse=True)
## get only values without positions from D2 and sort by DESC order
D2_values = []
#D2_pos_value_pair = []
for i in xrange(1, len(D2), 2): # getting each values of D2
D2_values.append(D2[i])
#D2_pos_value_pair.append([D2[i], D2[i-1]])
## get all possible combinations of D2
# returns combinations as list of tuples
D2_combinations = list(itertools.permutations(D2_values))
# convert them into list of lists
D2_combinations = [list(x) for x in D2_combinations]
## get through all combinations -> calculate priority -> choose result with highest priority
max_priority = 0
best_result_set = []
for combination in D2_combinations:
result_set, priority = get_result_set(list(D1), combination)
if (priority >= max_priority):
max_priority = priority
best_result_set = result_set
## sorting back best_result_set by original positions of D2 though it doesn't matter as e-yantra said
# best_result_set.sort(key=lambda x: D2_values.index(x[0])) # this cant sort if duplicate found on D2
results_sorted = []
for i in range(len(D2_values)): # iterate through every value of D2_values
j = 0 # index for D2
# until best_result_set become empty, find first occurance and append to results_sorted
while (len(best_result_set) > 0):
if (D2_values[i] == best_result_set[j][0]): # if match found
results_sorted.append(best_result_set[j]) # append it to results_sorted
del best_result_set[j] # delete j-th result from best_result_set
break # break after first occurance
j += 1
# end while
## print best_result_set
retVal = []
for i, result in enumerate(results_sorted):
#print generate_output(result[0], result[1:])
retVal.append(result)
return retVal
# end of puzzle
######################### Task2 Functions end #########################
#######################################################################
######################### Task4 Functions start #######################
#######################################################################
'''
* Function Name: generate_traversal_path
* Input: D1 -- is an integer list as input argument #0,
* D2_1D -- is an integer list as input argument #1,
* results -- is a 2D integer list as input argument #3
* Output: output_str -- is a string vaariable which stores the traversal path
* Logic: Prioritize D1 to get nearest number among ambigious numbers by manually sorting position-value pair in D1
* and store the result pairs into D1_prioritized_kv. Then get only values from D1_prioritized_kv and store
* the result into D1_prioritized_v. Now we need to make values invalid, so the keys i.e. positions will be
* only valid. Copy D2_1D into D2_copy. Then iterate through D2_copy, for each element present in D2_copy and
* taking i as iteration counter repeat as indented steps follows:
* if i is even i.e. it a value, then set i-th element of D2_copy to -1 to make it invalid.
* Now we generate solution se in this form: D2 position, D2 value, D1 position, D1 value, D1 position, D1
* value... (rest operands in D1); D2 value, D1 position, D1 value, D1 position, D1 value...; ... (rest
* solution set in D2, D1). e.g. - 1,16,3,8,10,8;10,14,1,9,2,5;23,10,6,7,7,3. To do that,
* terate through each element i.e. solution in results taking i as iteration counter repeat indention below:
* Iterate through each number present in each solution taking j as iteration counter repeat indention below:
* If j is equal to 0, then do as indention below:
* Get the position index of first occurance of y in D2_copy and stor it into variable pos.
* Concatenate pos-1 th element of D2_1D into output_str following a comma.
* Concatenate y into output_str.
* Set -1 to pos th element in D2_1D to make a used number invalid.
* Set -1 to pos th element in D2_copy mark the current D2 number as used so that the robot will not fill it again.
* Else, do as indention below:
* Get the position index of first occurance of y in D1_prioritized_v and stor it into variable pos.
* Get the actual position from D1_prioritized_kv and store it into variable actual_pos.
* Concatenate actual_pos into output_str following a comma then concatenate y into it.
* Set -1 to pos th element in D1_prioritized_v to make a used number invalid.
* If j is not equal to (number of elements in x) - 1, then concatenate comma into output_str.
* If j is not equal to (number of elements in result) - 1, then concatenate semicolon into output_str.
* Now we are going to generating the traversal path of the robot in comma separated format i.e. D1 position, D1 value,
* D2 position, D2 value, D1 position, D1 value ... e.g. - 3,8,1,16,10,8,1,16,1,9,10,14,2,5,10,14,6,7,23,10,7,3,23,10.
* To do that, we split output_str by semicolon and save the returned array into variable results_array and reset output_str.
* Now, we iterate through each result present in results_array and do the indented steps below:
* We split result by comma and save the returned array back into variable result. Initialize variable j = 0.
* We iterate through each number present in result[2:] i.e. 2nd element to last element if result, follow indention below:
* If j is even do as following indented steps:
* Concatenate 2+j th element of result into output_str following a comma.
* Concatenate 2+j+1 th element of result into output_str following a comma.
* Concatenate 0 th element of result into output_str following a comma.
* Concatenate 1 st element of result into output_str following a comma.
* j = j + 1
* After that removing the last comma from output_str and return output_str.
* Example Call: generate_traversal_path(D1, D2_1D, results)
'''
def generate_traversal_path (D1, D2_1D, results):
output_str = ""
# prioritize D1 to get nearest number among ambigious numbers
D1_prioritized_kv = [] # holds manually sorted position-value pair
D1_prioritized_kv.append([7, D1[7]])
D1_prioritized_kv.append([11, D1[11]])
D1_prioritized_kv.append([10, D1[10]])
D1_prioritized_kv.append([6, D1[6]])
D1_prioritized_kv.append([3, D1[3]])
D1_prioritized_kv.append([2, D1[2]])
D1_prioritized_kv.append([9, D1[9]])
D1_prioritized_kv.append([5, D1[5]])
D1_prioritized_kv.append([1, D1[1]])
D1_prioritized_kv.append([8, D1[8]])
D1_prioritized_kv.append([4, D1[4]])
D1_prioritized_kv.append([0, D1[0]])
# get only values from D1_prioritized_kv
D1_prioritized_v = []
for x in D1_prioritized_kv:
D1_prioritized_v.append(x[1])
# we need to make values invalid, so the keys i.e. positions will be only valid
D2_copy = list(D2_1D) # backing up D2_1D
for i, x in enumerate(D2_copy):
if (i%2 == 0): D2_copy[i] = -1
# generate solution se in this form: D2 position, D2 value, D1 position, D1 value, D1 position, D1 value... (rest operands in D1);
# D2 value, D1 position, D1 value, D1 position, D1 value...; ... (rest solution set in D2, D1)
# e.g. - 1,16,3,8,10,8;10,14,1,9,2,5;23,10,6,7,7,3
for i, x in enumerate(results):
for j, y in enumerate(x):
if (j == 0): # number of D2
pos = D2_copy.index(y)
output_str += str(D2_1D[pos-1])
output_str += ','
output_str += (str(y))
D2_1D[pos] = -1
D2_copy[pos] = -1 # mark the current D2 number as used so that the robot will not fill it again
else: # operands from D1
pos = D1_prioritized_v.index(y)
actual_pos = D1_prioritized_kv[pos][0]
output_str += str(actual_pos)
output_str += ','
output_str += (str(y))
D1_prioritized_v[pos] = -1;
if (j != len(x)-1): output_str += ','
if (i != len(results)-1): output_str += ';'
#print "\noutput_str: ", output_str
# generating the traversal path of the robot in comma separated format
# D1 position, D1 value, D2 position, D2 value, D1 position, D1 value ...
# e.g. - 3,8,1,16,10,8,1,16,1,9,10,14,2,5,10,14,6,7,23,10,7,3,23,10
results_array = output_str.split(";")
output_str = ""
for result in results_array:
result = result.split(",")
j=0
for num in result[2:]:
if j%2 == 0:
output_str += result[2+j] + "," # concatenating D1 position
output_str += result[2+j+1] + "," # concatenating D1 value
output_str += result[0] + "," # concatenating D2 position
output_str += result[1] + "," # concatenating D2 value
j += 1
#output_str += "-1," # to indicate completion of a number in D2
output_str = output_str[:-1] # removing the last comma
return output_str
# end of generate_traversal_path
######################### Task4 Functions end #########################
#######################################################################
'''
* Function Name: main
* Input: "Bonus/Puzzle Solver 1.jpg" as image path and the COM PORT number with which the robot is connected.
* Output: D1, D2
* Logic: We read the image and pass it to the play() function to do OCR and store the results in variable D1 and D2.
* Then we convert 2d list into 1d list i.e. from D2 to D2_1D.
* Now we run our puzzle solver algorithm i.e. function puzzle() store the result into variable results.
* Then we generate output that will be sent over serial communication terminal software or pyserial to the robot
* before pressing the boot key i.e. interrupt key using generate_traversal_path() function.
* Example Call: Called automatically by the Operating System.
'''
if __name__ == "__main__":
img = cv2.imread('Bonus/Puzzle Solver 1.jpg')
com_port = 5 # COM PORT
D1, D2 = play(img)
print 'D1 =', D1
print 'D2 =', D2
# converting 2d list into 1d list
D2_1D = []
for i in D2:
for j in i:
D2_1D.append(j)
D2_1D
results = puzzle(list(D1), D2_1D)
# this string will be sent over serial communication terminal software or pyserial to the robot
# before pressing the boot key i.e. interrupt key
output_str = generate_traversal_path(D1, D2_1D, results)
#print '\nResults =', results
#print '\nString to be sent: ', output_str
try:
# send output_str to the robot using pyserial
ser = Serial("com"+str(com_port), 9600) # parameters: comport, baudrate
ser.write(output_str) #send file
except:
print "\nError: \tCould not open COM Port COM"+str(com_port)
print "\tPlease connect the robot to the PC via USB to Serial cable and retry"
cv2.waitKey(0)
cv2.destroyAllWindows()