Changeset - a1b8c202099c
[Not reviewed]
default
0 7 0
Laman - 8 years ago 2016-11-23 22:31:36

reformated from spaces to tabs indentation
7 files changed with 425 insertions and 437 deletions:
0 comments (0 inline, 0 general)
.hgignore
Show inline comments
 
^src/__pycache__/
 
__pycache__/
 
^\.idea/
 
^images/
src/corners.py
Show inline comments
 
@@ -2,92 +2,92 @@
 
 
 
class Corners:
 
  def __init__(self):
 
    self.corners=[]
 
  
 
  ## Adds a new corner if there are less than four, replaces the closest otherwise.
 
  def add(self,x,y):
 
    a=EPoint(x,y)
 
    # for i,c in enumerate(self.corners): # move an improperly placed point
 
      # if a.dist(c)<20:
 
        # self.corners[i]=a
 
        # return
 
    
 
    if len(self.corners)<4: # add a new corner
 
      self.corners.append(a)
 
    
 
    if len(self.corners)<4: 
 
      return
 
    
 
    index,minDist=0,float('inf') # replace the corner closest to the clicked point
 
    for i,c in enumerate(self.corners):
 
      if a.dist(c)<minDist:
 
        index,minDist=i,a.dist(c)
 
    
 
    self.corners[index]=a
 
  
 
  
 
  ## Computes twice the area of the triangle formed by points a,b,c.
 
  #  
 
  #  @return positive value for points oriented counter-clockwise, negative for clockwise, zero for degenerate cases.
 
  def _doubleTriangleArea(a,b,c):
 
    return (a.x-b.x)*(c.y-a.y)-(c.x-a.x)*(a.y-b.y)
 
  
 
  
 
  def _slope(a,b):
 
    if(b.x==a.x): return float("inf")
 
    return (b.y-a.y)/(b.x-a.x)
 
	def __init__(self):
 
		self.corners=[]
 
 
	## Adds a new corner if there are less than four, replaces the closest otherwise.
 
	def add(self,x,y):
 
		a=EPoint(x,y)
 
		# for i,c in enumerate(self.corners): # move an improperly placed point
 
			# if a.dist(c)<20:
 
				# self.corners[i]=a
 
				# return
 
 
		if len(self.corners)<4: # add a new corner
 
			self.corners.append(a)
 
 
		if len(self.corners)<4:
 
			return
 
 
		index,minDist=0,float('inf') # replace the corner closest to the clicked point
 
		for i,c in enumerate(self.corners):
 
			if a.dist(c)<minDist:
 
				index,minDist=i,a.dist(c)
 
 
		self.corners[index]=a
 
 
 
	## Computes twice the area of the triangle formed by points a,b,c.
 
	#
 
	#  @return positive value for points oriented counter-clockwise, negative for clockwise, zero for degenerate cases.
 
	def _doubleTriangleArea(a,b,c):
 
		return (a.x-b.x)*(c.y-a.y)-(c.x-a.x)*(a.y-b.y)
 
 
 
	def _slope(a,b):
 
		if(b.x==a.x): return float("inf")
 
		return (b.y-a.y)/(b.x-a.x)
 
 
 
    
 
  ## Order the corners (0,1,2,3) so they make a quadrangle with vertices KLMN in counter-clockwise order, K being in the upper left.
 
  #  
 
  #  For four points ABCD, there are 24 possible permutations corresponding to the desired KLMN.
 
  #  When we relax the condition of K being the upper left one, we get six groups of four equivalent permutations. KLMN ~ LMNK ~ MNKL ~ NKLM.
 
  #  
 
  #  We determine which of the points' triplets are oriented clockwise and which counter-clockwise (minus/plus in the table below)
 
  #  and swap them so that all triangles turn counter-clockwise.
 
  #  
 
  #  xxxx -> KLMN | ABC | ABD | ACD | BCD | index | swap
 
  #  ------------ | :-: | :-: | :-: | :-: | ----: | ----
 
  #  A BCD        |  +  |  +  |  +  |  +  |    15 | 0
 
  #  A BDC        |  +  |  +  |  -  |  -  |    12 | CD
 
  #  A CBD        |  -  |  +  |  +  |  -  |     6 | BC
 
  #  A CDB        |  -  |  -  |  +  |  +  |     3 | AB
 
  #  A DBC        |  +  |  -  |  -  |  +  |     9 | AD
 
  #  A DCB        |  -  |  -  |  -  |  -  |     0 | BD
 
  #  
 
  #  For every non-degenerate quadrangle, there must be 1-3 edges going right-left (from a higher to a lower x coordinate).
 
  #  From these pick the one with the lowest slope (dy/dx) and declare its ending point the upper left corner. For the same slope pick the one further left.
 
  #  
 
  #  @return True for a convex quadrangle, False for concave and degenerate cases.
 
  def canonizeOrder(self):
 
    if len(self.corners)!=4: return False # erroneus call
 
    
 
    a,b,c,d=self.corners
 
    abc=Corners._doubleTriangleArea(a,b,c)
 
    abd=Corners._doubleTriangleArea(a,b,d)
 
    acd=Corners._doubleTriangleArea(a,c,d)
 
    bcd=Corners._doubleTriangleArea(b,c,d)
 
    
 
    if any(x==0 for x in (abc,abd,acd,bcd)): return False # collinear degenerate
 
    
 
    swaps=[(1,3),(0,1),(1,2),(0,3),(2,3),(0,0)]
 
    index=(8 if abc>0 else 0)|(4 if abd>0 else 0)|(2 if acd>0 else 0)|(1 if bcd>0 else 0)
 
    if index%3!=0: return False # concave degenerate
 
    swap=swaps[index//3]
 
    
 
    self.corners[swap[0]], self.corners[swap[1]] = self.corners[swap[1]], self.corners[swap[0]] # counter-clockwise order
 
    
 
    kIndex=None
 
    lowestSlope=float("inf")
 
    
 
    for i,corner in enumerate(self.corners): # find the NK edge: going right-left with the lowest slope, secondarily the one going down
 
      ii=(i+1)%4
 
      slope=abs(Corners._slope(corner,self.corners[ii]))
 
      if corner.x>self.corners[ii].x and (slope<lowestSlope or (slope==lowestSlope and corner.y<self.corners[ii].y)):
 
        kIndex=ii
 
        lowestSlope=slope
 
    
 
    self.corners=self.corners[kIndex:]+self.corners[:kIndex] # rotate the upper left corner to the first place
 
        
 
    return True # success
 
	## Order the corners (0,1,2,3) so they make a quadrangle with vertices KLMN in counter-clockwise order, K being in the upper left.
 
	#
 
	#  For four points ABCD, there are 24 possible permutations corresponding to the desired KLMN.
 
	#  When we relax the condition of K being the upper left one, we get six groups of four equivalent permutations. KLMN ~ LMNK ~ MNKL ~ NKLM.
 
	#
 
	#  We determine which of the points' triplets are oriented clockwise and which counter-clockwise (minus/plus in the table below)
 
	#  and swap them so that all triangles turn counter-clockwise.
 
	#
 
	#  xxxx -> KLMN | ABC | ABD | ACD | BCD | index | swap
 
	#  ------------ | :-: | :-: | :-: | :-: | ----: | ----
 
	#  A BCD        |  +  |  +  |  +  |  +  |    15 | 0
 
	#  A BDC        |  +  |  +  |  -  |  -  |    12 | CD
 
	#  A CBD        |  -  |  +  |  +  |  -  |     6 | BC
 
	#  A CDB        |  -  |  -  |  +  |  +  |     3 | AB
 
	#  A DBC        |  +  |  -  |  -  |  +  |     9 | AD
 
	#  A DCB        |  -  |  -  |  -  |  -  |     0 | BD
 
	#
 
	#  For every non-degenerate quadrangle, there must be 1-3 edges going right-left (from a higher to a lower x coordinate).
 
	#  From these pick the one with the lowest slope (dy/dx) and declare its ending point the upper left corner. For the same slope pick the one further left.
 
	#
 
	#  @return True for a convex quadrangle, False for concave and degenerate cases.
 
	def canonizeOrder(self):
 
		if len(self.corners)!=4: return False # erroneus call
 
 
		a,b,c,d=self.corners
 
		abc=Corners._doubleTriangleArea(a,b,c)
 
		abd=Corners._doubleTriangleArea(a,b,d)
 
		acd=Corners._doubleTriangleArea(a,c,d)
 
		bcd=Corners._doubleTriangleArea(b,c,d)
 
 
		if any(x==0 for x in (abc,abd,acd,bcd)): return False # collinear degenerate
 
 
		swaps=[(1,3),(0,1),(1,2),(0,3),(2,3),(0,0)]
 
		index=(8 if abc>0 else 0)|(4 if abd>0 else 0)|(2 if acd>0 else 0)|(1 if bcd>0 else 0)
 
		if index%3!=0: return False # concave degenerate
 
		swap=swaps[index//3]
 
 
		self.corners[swap[0]], self.corners[swap[1]] = self.corners[swap[1]], self.corners[swap[0]] # counter-clockwise order
 
 
		kIndex=None
 
		lowestSlope=float("inf")
 
 
		for i,corner in enumerate(self.corners): # find the NK edge: going right-left with the lowest slope, secondarily the one going down
 
			ii=(i+1)%4
 
			slope=abs(Corners._slope(corner,self.corners[ii]))
 
			if corner.x>self.corners[ii].x and (slope<lowestSlope or (slope==lowestSlope and corner.y<self.corners[ii].y)):
 
				kIndex=ii
 
				lowestSlope=slope
 
 
		self.corners=self.corners[kIndex:]+self.corners[:kIndex] # rotate the upper left corner to the first place
 
 
		return True # success
src/epoint.py
Show inline comments
 
@@ -3,67 +3,67 @@
 
 
## Euclidean 2D plane point: (x,y).
 
class EPoint:
 
  def __init__(self,x,y):
 
    self.x=x
 
    self.y=y
 
    
 
  def fromTuple(tup): return EPoint(tup[0],tup[1])
 
  
 
  def fromProjective(point):
 
    if point.item(0)==0: return None
 
    return EPoint(point.item(1)/point.item(0),point.item(2)/point.item(0))
 
    
 
  def toProjective(self):
 
    return (1,self.x,self.y)
 
  
 
  def dist(self,a):
 
    return math.sqrt((self.x-a.x)**2+(self.y-a.y)**2)
 
    
 
  def __add__(self,a):
 
    return EPoint(self.x+a.x,self.y+a.y)
 
    
 
  def __sub__(self,a):
 
    return EPoint(self.x-a.x,self.y-a.y)
 
    
 
  def __mul__(self,k):
 
    return EPoint(self.x*k,self.y*k)
 
    
 
  def __rmul__(self,k):
 
    return self*k
 
    
 
  def __truediv__(self,k):
 
    return EPoint(self.x/k,self.y/k)
 
    
 
  def __floordiv__(self,k):
 
    return EPoint(self.x//k,self.y//k)
 
    
 
  def __iadd__(self,a):
 
    self.x+=a.x
 
    self.y+=a.y
 
    return self
 
    
 
  def __isub__(self,a):
 
    self.x-=a.x
 
    self.y-=a.y
 
    return self
 
  
 
  def __imul__(self,k):
 
    self.x*=k
 
    self.y*=k
 
    return self
 
    
 
  def __itruediv__(self,k):
 
    self.x/=k
 
    self.y/=k
 
    return self
 
    
 
  def __ifloordiv__(self,k):
 
    self.x//=k
 
    self.y//=k
 
    return self
 
    
 
  def __neg__(self):
 
    return EPoint(-self.x,-self.y)
 
    
 
  def __str__(self): return "({0},{1})".format(round(self.x,3),round(self.y,3))
 
  def __repr__(self): return "EPoint({0},{1})".format(round(self.x,3),round(self.y,3))
 
	def __init__(self,x,y):
 
		self.x=x
 
		self.y=y
 
 
	def fromTuple(tup): return EPoint(tup[0],tup[1])
 
 
	def fromProjective(point):
 
		if point.item(0)==0: return None
 
		return EPoint(point.item(1)/point.item(0),point.item(2)/point.item(0))
 
 
	def toProjective(self):
 
		return (1,self.x,self.y)
 
 
	def dist(self,a):
 
		return math.sqrt((self.x-a.x)**2+(self.y-a.y)**2)
 
 
	def __add__(self,a):
 
		return EPoint(self.x+a.x,self.y+a.y)
 
 
	def __sub__(self,a):
 
		return EPoint(self.x-a.x,self.y-a.y)
 
 
	def __mul__(self,k):
 
		return EPoint(self.x*k,self.y*k)
 
 
	def __rmul__(self,k):
 
		return self*k
 
 
	def __truediv__(self,k):
 
		return EPoint(self.x/k,self.y/k)
 
 
	def __floordiv__(self,k):
 
		return EPoint(self.x//k,self.y//k)
 
 
	def __iadd__(self,a):
 
		self.x+=a.x
 
		self.y+=a.y
 
		return self
 
 
	def __isub__(self,a):
 
		self.x-=a.x
 
		self.y-=a.y
 
		return self
 
 
	def __imul__(self,k):
 
		self.x*=k
 
		self.y*=k
 
		return self
 
 
	def __itruediv__(self,k):
 
		self.x/=k
 
		self.y/=k
 
		return self
 
 
	def __ifloordiv__(self,k):
 
		self.x//=k
 
		self.y//=k
 
		return self
 
 
	def __neg__(self):
 
		return EPoint(-self.x,-self.y)
 
 
	def __str__(self): return "({0},{1})".format(round(self.x,3),round(self.y,3))
 
	def __repr__(self): return "EPoint({0},{1})".format(round(self.x,3),round(self.y,3))
src/go.py
Show inline comments
 
class Go:
 
  EMPTY=0
 
  BLACK=1
 
  WHITE=-1
 
  
 
  ## Initializes self.board to a list[r][c]=Go.EMPTY.
 
  def __init__(self,boardSize=19):
 
    self.boardSize=boardSize
 
    self.board=[[Go.EMPTY]*self.boardSize for x in self.board]
 
  
 
  ## Executes a move.
 
  #  
 
  #  Doesn't check for kos. Suicide not allowed.
 
  #  
 
  #  @param color Go.BLACK or Go.WHITE
 
  #  @return True on success, False on failure (illegal move)
 
  def move(self,color,row,col):
 
    if self.board[row][col]!=Go.EMPTY: return False
 
	EMPTY=0
 
	BLACK=1
 
	WHITE=-1
 
 
	## Initializes self.board to a list[r][c]=Go.EMPTY.
 
	def __init__(self,boardSize=19):
 
		self.boardSize=boardSize
 
		self.board=[[Go.EMPTY]*self.boardSize for x in self.board]
 
 
    self.board[row][col]=color
 
	## Executes a move.
 
	#
 
	#  Doesn't check for kos. Suicide not allowed.
 
	#
 
	#  @param color Go.BLACK or Go.WHITE
 
	#  @return True on success, False on failure (illegal move)
 
	def move(self,color,row,col):
 
		if self.board[row][col]!=Go.EMPTY: return False
 
 
		self.board[row][col]=color
 
 
		# capture neighbors
 
		for r,c in ((-1,0),(1,0),(0,-1),(0,1)):
 
			self.temp=[[False]*self.boardSize for x in self.board]
 
			if not self._floodFill(-color,row+r,col+c): self._remove()
 
 
    # capture neighbors
 
    for r,c in ((-1,0),(1,0),(0,-1),(0,1)):
 
      self.temp=[[False]*self.boardSize for x in self.board]
 
      if not self._floodFill(-color,row+r,col+c): self._remove()
 
    
 
    # check for suicide
 
    self.temp=[[False]*self.boardSize for x in self.board]
 
    if not self._floodFill(color,row,col):
 
      self.board[row][col]=Go.EMPTY
 
      return False
 
    return True
 
    
 
  ## Checks for liberties of a stone at given coordinates.
 
  #  
 
  #  The stone's group is marked with True in self.temp, ready for capture if needed. Recursively called for stone's neighbors.
 
  #  
 
  #  @return True if alive, False if captured
 
  def _floodFill(self,color,row,col):
 
    if col<0 or col>=self.boardSize or row<0 or row>=self.boardSize: return False # out of range
 
    if self.temp[row][col]: return False # already visited
 
    if self.board[row][col]==Go.EMPTY: return True # found a liberty
 
    if self.board[row][col]!=color: return False # opponent's stone
 
    self.temp[row][col]=True # set visited
 
    return self._floodFill(color,row,col-1) or self._floodFill(color,row,col+1) or self._floodFill(color,row-1,col) or self._floodFill(color,row+1,col) # check neighbors
 
  
 
  ## Removes stones at coordinates marked with True in self.temp.
 
  def _remove(self):
 
    for r in range(self.boardSize):
 
      for c in range(self.boardSize):
 
        if self.temp[r][c]: self.board[r][c]=Go.EMPTY
 
		# check for suicide
 
		self.temp=[[False]*self.boardSize for x in self.board]
 
		if not self._floodFill(color,row,col):
 
			self.board[row][col]=Go.EMPTY
 
			return False
 
		return True
 
 
	## Checks for liberties of a stone at given coordinates.
 
	#
 
	#  The stone's group is marked with True in self.temp, ready for capture if needed. Recursively called for stone's neighbors.
 
	#
 
	#  @return True if alive, False if captured
 
	def _floodFill(self,color,row,col):
 
		if col<0 or col>=self.boardSize or row<0 or row>=self.boardSize: return False # out of range
 
		if self.temp[row][col]: return False # already visited
 
		if self.board[row][col]==Go.EMPTY: return True # found a liberty
 
		if self.board[row][col]!=color: return False # opponent's stone
 
		self.temp[row][col]=True # set visited
 
		return self._floodFill(color,row,col-1) or self._floodFill(color,row,col+1) or self._floodFill(color,row-1,col) or self._floodFill(color,row+1,col) # check neighbors
 
 
	## Removes stones at coordinates marked with True in self.temp.
 
	def _remove(self):
 
		for r in range(self.boardSize):
 
			for c in range(self.boardSize):
 
				if self.temp[r][c]: self.board[r][c]=Go.EMPTY
src/grid.py
Show inline comments
 
@@ -8,76 +8,64 @@ from epoint import *
 
#  
 
#  @return transformed point as a numpy.array
 
def transformPoint(point,A):
 
  return (A*numpy.matrix(point).transpose()).getA1()
 
	return (A*numpy.matrix(point).transpose()).getA1()
 
 
 
class Grid:
 
  ## Creates a Grid from the provided Corners object.
 
  #  
 
  #  Finds the vanishing points of the board lines (corner points define perspectively transformed parallel lines). The vanishing points define the image horizon.
 
  #  
 
  #  The horizon can be used to construct a matrix for affine rectification of the image (restoring parallel lines parallelism). We transform the corner points by this matrix,
 
  #  interpolate them to get proper intersections' coordinates and then transform these back to get their placement at the original image.
 
  #  
 
  #  The result is stored in grid.intersections, a boardSize*boardSize list with [row][column] coordinates.
 
  #  
 
  #  @param corners a properly initialized Corners object. !! Needs a check for the proper initialization.
 
  def __init__(self,corners):
 
    # ad
 
    # bc
 
    a,b,c,d=[c.toProjective() for c in corners.corners]
 
    
 
    p1=numpy.cross(a,b)
 
    p2=numpy.cross(c,d)
 
    vanish1=numpy.cross(p1,p2)
 
    # !! 32 bit int can overflow. keeping it reasonably small. might want to use a cleaner solution
 
    vanish1=EPoint.fromProjective(vanish1).toProjective() # !! EPoint fails with point in infinity
 
    
 
    p3=numpy.cross(a,d)
 
    p4=numpy.cross(b,c)
 
    vanish2=numpy.cross(p3,p4)
 
    vanish2=EPoint.fromProjective(vanish2).toProjective()
 
    
 
    horizon=numpy.cross(vanish1,vanish2)
 
	## Creates a Grid from the provided Corners object.
 
	#
 
	#  Finds the vanishing points of the board lines (corner points define perspectively transformed parallel lines). The vanishing points define the image horizon.
 
	#
 
	#  The horizon can be used to construct a matrix for affine rectification of the image (restoring parallel lines parallelism). We transform the corner points by this matrix,
 
	#  interpolate them to get proper intersections' coordinates and then transform these back to get their placement at the original image.
 
	#
 
	#  The result is stored in grid.intersections, a boardSize*boardSize list with [row][column] coordinates.
 
	#
 
	#  @param corners a properly initialized Corners object. !! Needs a check for the proper initialization.
 
	def __init__(self,corners):
 
		# ad
 
		# bc
 
		a,b,c,d=[c.toProjective() for c in corners.corners]
 
 
    horizon=EPoint.fromProjective(horizon).toProjective()
 
    
 
    rectiMatrix=numpy.matrix([horizon,[0,1,0],[0,0,1]])
 
    rectiMatrixInv=numpy.linalg.inv(rectiMatrix)
 
    
 
    
 
    affineCorners=[EPoint.fromProjective(transformPoint(x,rectiMatrix)) for x in (a,b,c,d)]
 
    x=[affineCorners[0]-affineCorners[3],affineCorners[1]-affineCorners[2],affineCorners[0]-affineCorners[1],affineCorners[3]-affineCorners[2]]
 
    
 
    self.intersections=[]
 
    boardSize=19
 
    for r in range(boardSize):
 
      self.intersections.append([None]*boardSize)
 
      rowStart=(affineCorners[0]*(boardSize-1-r)+affineCorners[1]*r) / (boardSize-1)
 
      rowEnd=(affineCorners[3]*(boardSize-1-r)+affineCorners[2]*r) / (boardSize-1)
 
      
 
      for c in range(boardSize):
 
        affineIntersection=(rowStart*(boardSize-1-c)+rowEnd*c) / (boardSize-1)
 
        self.intersections[r][c]=EPoint.fromProjective(transformPoint(affineIntersection.toProjective(),rectiMatrixInv))
 
		p1=numpy.cross(a,b)
 
		p2=numpy.cross(c,d)
 
		vanish1=numpy.cross(p1,p2)
 
		# !! 32 bit int can overflow. keeping it reasonably small. might want to use a cleaner solution
 
		vanish1=EPoint.fromProjective(vanish1).toProjective() # !! EPoint fails with point in infinity
 
 
  def stoneSizeAt(self,r,c,sizeCoef):
 
    intersection=self.intersections[r][c]
 
		p3=numpy.cross(a,d)
 
		p4=numpy.cross(b,c)
 
		vanish2=numpy.cross(p3,p4)
 
		vanish2=EPoint.fromProjective(vanish2).toProjective()
 
 
    if c>0: width=sizeCoef*(intersection.x-self.intersections[r][c-1].x)
 
    else: width=sizeCoef*(self.intersections[r][c+1].x-intersection.x)
 
    if r>0: height=sizeCoef*(intersection.y-self.intersections[r-1][c].y)
 
    else: height=sizeCoef*(self.intersections[r+1][c].y-intersection.y)
 
		horizon=numpy.cross(vanish1,vanish2)
 
 
    return (width,height)
 
		horizon=EPoint.fromProjective(horizon).toProjective()
 
 
		rectiMatrix=numpy.matrix([horizon,[0,1,0],[0,0,1]])
 
		rectiMatrixInv=numpy.linalg.inv(rectiMatrix)
 
 
 
# from corners import Corners
 
# corn=Corners()
 
		affineCorners=[EPoint.fromProjective(transformPoint(x,rectiMatrix)) for x in (a,b,c,d)]
 
		x=[affineCorners[0]-affineCorners[3],affineCorners[1]-affineCorners[2],affineCorners[0]-affineCorners[1],affineCorners[3]-affineCorners[2]]
 
 
		self.intersections=[]
 
		boardSize=19
 
		for r in range(boardSize):
 
			self.intersections.append([None]*boardSize)
 
			rowStart=(affineCorners[0]*(boardSize-1-r)+affineCorners[1]*r) / (boardSize-1)
 
			rowEnd=(affineCorners[3]*(boardSize-1-r)+affineCorners[2]*r) / (boardSize-1)
 
 
# corn.add(106,86)
 
# corn.add(57,321)
 
# corn.add(416,320)
 
# corn.add(365,86)
 
# corn.add(365,88)
 
			for c in range(boardSize):
 
				affineIntersection=(rowStart*(boardSize-1-c)+rowEnd*c) / (boardSize-1)
 
				self.intersections[r][c]=EPoint.fromProjective(transformPoint(affineIntersection.toProjective(),rectiMatrixInv))
 
 
	def stoneSizeAt(self,r,c,sizeCoef):
 
		intersection=self.intersections[r][c]
 
 
# x=Grid(corn)
 
		if c>0: width=sizeCoef*(intersection.x-self.intersections[r][c-1].x)
 
		else: width=sizeCoef*(self.intersections[r][c+1].x-intersection.x)
 
		if r>0: height=sizeCoef*(intersection.y-self.intersections[r-1][c].y)
 
		else: height=sizeCoef*(self.intersections[r+1][c].y-intersection.y)
 
 
		return (width,height)
src/gui.py
Show inline comments
 
@@ -11,153 +11,153 @@ showGrid=False
 
 
 
class Application(tk.Frame):
 
  def __init__(self, master=None):
 
    self.corners=Corners()
 
    self.boardGrid=None
 
    
 
    tk.Frame.__init__(self, master)
 
    self.grid(column=0,row=0)
 
    self.createWidgets()
 
	def __init__(self, master=None):
 
		self.corners=Corners()
 
		self.boardGrid=None
 
 
		tk.Frame.__init__(self, master)
 
		self.grid(column=0,row=0)
 
		self.createWidgets()
 
 
	def createWidgets(self):
 
		# a captured frame with overlay graphics
 
		self.imgView=tk.Canvas(self)
 
		self.imgView.configure(width=480,height=360)
 
		self.imgOrig=PIL.Image.open("../images/7.jpg")
 
 
		self.img=ImageTk.PhotoImage(self.imgOrig.resize((int(self.imgView['width']),int(self.imgView['height'])),resample=PIL.Image.BILINEAR))
 
 
		self.imgView.bind('<1>',lambda e: self.addCorner(e.x,e.y))
 
 
		self.imgView.grid(column=0,row=0)
 
 
  def createWidgets(self):
 
    # a captured frame with overlay graphics
 
    self.imgView=tk.Canvas(self)
 
    self.imgView.configure(width=480,height=360)
 
    self.imgOrig=PIL.Image.open("../images/7.jpg")
 
    
 
    self.img=ImageTk.PhotoImage(self.imgOrig.resize((int(self.imgView['width']),int(self.imgView['height'])),resample=PIL.Image.BILINEAR))
 
    
 
    self.imgView.bind('<1>',lambda e: self.addCorner(e.x,e.y))
 
    
 
    self.imgView.grid(column=0,row=0)
 
    
 
    # board with detected stones
 
    self.boardView=BoardView(self)
 
    self.boardView.grid(column=1,row=0)
 
		# board with detected stones
 
		self.boardView=BoardView(self)
 
		self.boardView.grid(column=1,row=0)
 
 
    # more controls below the board
 
    self.scaleTresB=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=lambda x: self.redrawImgView())
 
    self.scaleTresW=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=lambda x: self.redrawImgView())
 
    self.scaleTresB.set(30.0)
 
    self.scaleTresW.set(60.0)
 
    self.scaleTresB.grid(column=0,row=1,columnspan=2)
 
    self.scaleTresW.grid(column=0,row=2,columnspan=2)
 
		# more controls below the board
 
		self.scaleTresB=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=lambda x: self.redrawImgView())
 
		self.scaleTresW=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=lambda x: self.redrawImgView())
 
		self.scaleTresB.set(30.0)
 
		self.scaleTresW.set(60.0)
 
		self.scaleTresB.grid(column=0,row=1,columnspan=2)
 
		self.scaleTresW.grid(column=0,row=2,columnspan=2)
 
 
		# render everything
 
		self.redrawImgView()
 
 
	## Stores a grid corner located at x,y coordinates.
 
	def addCorner(self,x,y):
 
		self.corners.add(x,y)
 
		if self.corners.canonizeOrder():
 
			self.boardGrid=Grid(self.corners)
 
			self.boardView.setBoardGrid(self.boardGrid)
 
 
    # render everything
 
    self.redrawImgView()
 
  
 
  ## Stores a grid corner located at x,y coordinates.
 
  def addCorner(self,x,y):
 
    self.corners.add(x,y)
 
    if self.corners.canonizeOrder():
 
      self.boardGrid=Grid(self.corners)
 
      self.boardView.setBoardGrid(self.boardGrid)
 
    
 
    self.redrawImgView()
 
    
 
  ## Redraws the current image and its overlay.
 
  def redrawImgView(self):
 
    self.imgView.create_image(2,2,anchor="nw",image=self.img)
 
		self.redrawImgView()
 
 
	## Redraws the current image and its overlay.
 
	def redrawImgView(self):
 
		self.imgView.create_image(2,2,anchor="nw",image=self.img)
 
 
    origWidth,origHeight=self.imgOrig.size
 
    widthRatio=origWidth/self.img.width()
 
    heightRatio=origHeight/self.img.height()
 
    sizeCoef=max(widthRatio,heightRatio)
 
    tresB=self.scaleTresB.get()
 
    tresW=self.scaleTresW.get()
 
		origWidth,origHeight=self.imgOrig.size
 
		widthRatio=origWidth/self.img.width()
 
		heightRatio=origHeight/self.img.height()
 
		sizeCoef=max(widthRatio,heightRatio)
 
		tresB=self.scaleTresB.get()
 
		tresW=self.scaleTresW.get()
 
 
		for corner in self.corners.corners:
 
			self.markPoint(corner.x,corner.y)
 
 
    for corner in self.corners.corners:
 
      self.markPoint(corner.x,corner.y)
 
    
 
    if self.boardGrid!=None and showGrid:
 
      for r in range(19):
 
        a=self.boardGrid.intersections[r][0]
 
        b=self.boardGrid.intersections[r][-1]
 
        self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00')
 
      for c in range(19):
 
        a=self.boardGrid.intersections[0][c]
 
        b=self.boardGrid.intersections[-1][c]
 
        self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00')
 
		if self.boardGrid!=None and showGrid:
 
			for r in range(19):
 
				a=self.boardGrid.intersections[r][0]
 
				b=self.boardGrid.intersections[r][-1]
 
				self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00')
 
			for c in range(19):
 
				a=self.boardGrid.intersections[0][c]
 
				b=self.boardGrid.intersections[-1][c]
 
				self.imgView.create_line(a.x,a.y,b.x,b.y,fill='#00ff00')
 
 
    if self.boardGrid!=None and showBigPoints:
 
      for r in range(19):
 
        for c in range(19):
 
          ((r1,c1),(r2,c2))=self.boardView.detector.relevantRect(self.boardGrid.intersections[r][c],*(self.boardGrid.stoneSizeAt(r,c,sizeCoef)))
 
          self.imgView.create_rectangle(r1,c1,r2,c2,outline="#00ffff")
 
    
 
    self.imgView.grid()
 
    
 
    shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2
 
    
 
    self.boardView.redrawState(self.imgOrig,sizeCoef,shift,tresB,tresW)
 
    
 
  ## Marks a point at the image with a green cross. Used for corners.
 
  def markPoint(self,x,y):
 
    self.imgView.create_line(x-3,y-3,x+4,y+4,fill="#00ff00")
 
    self.imgView.create_line(x-3,y+3,x+4,y-4,fill="#00ff00")
 
    
 
    self.imgView.grid()
 
		if self.boardGrid!=None and showBigPoints:
 
			for r in range(19):
 
				for c in range(19):
 
					((r1,c1),(r2,c2))=self.boardView.detector.relevantRect(self.boardGrid.intersections[r][c],*(self.boardGrid.stoneSizeAt(r,c,sizeCoef)))
 
					self.imgView.create_rectangle(r1,c1,r2,c2,outline="#00ffff")
 
 
		self.imgView.grid()
 
 
		shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2
 
 
		self.boardView.redrawState(self.imgOrig,sizeCoef,shift,tresB,tresW)
 
 
	## Marks a point at the image with a green cross. Used for corners.
 
	def markPoint(self,x,y):
 
		self.imgView.create_line(x-3,y-3,x+4,y+4,fill="#00ff00")
 
		self.imgView.create_line(x-3,y+3,x+4,y-4,fill="#00ff00")
 
 
		self.imgView.grid()
 
 
 
## Handles and presents the game state as detected by the program.
 
class BoardView(tk.Canvas):
 
  def __init__(self, master=None):
 
    self.detector=ImageAnalyzer()
 
    self.boardGrid=None
 
    
 
    tk.Canvas.__init__(self, master)
 
    self.configure(width=360,height=360,background="#ffcc00")
 
	def __init__(self, master=None):
 
		self.detector=ImageAnalyzer()
 
		self.boardGrid=None
 
 
		tk.Canvas.__init__(self, master)
 
		self.configure(width=360,height=360,background="#ffcc00")
 
 
		self.drawGrid()
 
 
		self.grid()
 
 
    self.drawGrid()
 
    
 
    self.grid()
 
	## Redraws and reananalyzes the board view.
 
	#
 
	# @param tresB upper intensity treshold for a pixel to be considered black, [0-100]
 
	# @param tresW lower intensity treshold for a pixel to be considered white, [0-100]
 
	def redrawState(self,img,sizeCoef,shift,tresB,tresW):
 
		self.create_rectangle(0,0,360,360,fill="#ffcc00")
 
		self.drawGrid()
 
 
  ## Redraws and reananalyzes the board view.
 
  #
 
  # @param tresB upper intensity treshold for a pixel to be considered black, [0-100]
 
  # @param tresW lower intensity treshold for a pixel to be considered white, [0-100]
 
  def redrawState(self,img,sizeCoef,shift,tresB,tresW):
 
    self.create_rectangle(0,0,360,360,fill="#ffcc00")
 
    self.drawGrid()
 
		self.detector.analyze(img,tresB,tresW,sizeCoef,shift)
 
 
		for r,row in enumerate(self.detector.board):
 
			for c,point in enumerate(row):
 
				self.drawStone(r,c,point)
 
 
		self.grid()
 
 
    self.detector.analyze(img,tresB,tresW,sizeCoef,shift)
 
    
 
    for r,row in enumerate(self.detector.board):
 
      for c,point in enumerate(row):
 
        self.drawStone(r,c,point)
 
        
 
    self.grid()
 
    
 
  def drawGrid(self):
 
    for i in range(19):
 
      self.create_line(18,18*(i+1),360-18,18*(i+1),fill="#000000") # rows
 
      self.create_line(18*(i+1),18,18*(i+1),360-18,fill="#000000") # cols
 
      
 
    self.drawStars()
 
      
 
  def drawStars(self):
 
    for r in range(4,19,6):
 
      for c in range(4,19,6):
 
        self.create_oval(r*18-2,c*18-2,r*18+2,c*18+2,fill='#000000')
 
      
 
  ## Draws a stone at provided coordinates.
 
  #  
 
  #  For an unknown color draws nothing and returns False.
 
  #  
 
  #  @param r row coordinate, [0-18], counted from top
 
  #  @param c column coordinate, [0-18], counted from left
 
  #  @param color color indicator, Go.BLACK or Go.WHITE
 
  def drawStone(self,r,c,color):
 
    if color==Go.BLACK: hexCode='#000000'
 
    elif color==Go.WHITE: hexCode='#ffffff'
 
    else: return False
 
    r+=1
 
    c+=1
 
    self.create_oval(c*18-9,r*18-9,c*18+9,r*18+9,fill=hexCode)
 
    
 
  def setBoardGrid(self,boardGrid):
 
    self.boardGrid=boardGrid
 
    self.detector.setGrid(boardGrid)
 
	def drawGrid(self):
 
		for i in range(19):
 
			self.create_line(18,18*(i+1),360-18,18*(i+1),fill="#000000") # rows
 
			self.create_line(18*(i+1),18,18*(i+1),360-18,fill="#000000") # cols
 
 
		self.drawStars()
 
 
	def drawStars(self):
 
		for r in range(4,19,6):
 
			for c in range(4,19,6):
 
				self.create_oval(r*18-2,c*18-2,r*18+2,c*18+2,fill='#000000')
 
 
	## Draws a stone at provided coordinates.
 
	#
 
	#  For an unknown color draws nothing and returns False.
 
	#
 
	#  @param r row coordinate, [0-18], counted from top
 
	#  @param c column coordinate, [0-18], counted from left
 
	#  @param color color indicator, Go.BLACK or Go.WHITE
 
	def drawStone(self,r,c,color):
 
		if color==Go.BLACK: hexCode='#000000'
 
		elif color==Go.WHITE: hexCode='#ffffff'
 
		else: return False
 
		r+=1
 
		c+=1
 
		self.create_oval(c*18-9,r*18-9,c*18+9,r*18+9,fill=hexCode)
 
 
	def setBoardGrid(self,boardGrid):
 
		self.boardGrid=boardGrid
 
		self.detector.setGrid(boardGrid)
 
 
 
root = tk.Tk()
src/image_analyzer.py
Show inline comments
 
@@ -2,47 +2,47 @@
 
 
 
class ImageAnalyzer:
 
  
 
  def __init__(self):
 
    self.board=[[Go.EMPTY]*19 for r in range(19)]
 
    self.grid=None
 
  
 
  def analyze(self,image,tresB,tresW,sizeCoef,shift):
 
    if self.grid==None: return False
 
    
 
    for r in range(19):
 
      for c in range(19):
 
        intersection=self.grid.intersections[r][c]
 
        
 
        self.board[r][c]=self.analyzePoint(image,intersection*sizeCoef+shift,*(self.grid.stoneSizeAt(r,c,sizeCoef)),tresB,tresW)
 
  
 
  def analyzePoint(self,image,imageCoords,stoneWidth,stoneHeight,tresB,tresW):
 
    b=w=e=0
 
 
	def __init__(self):
 
		self.board=[[Go.EMPTY]*19 for r in range(19)]
 
		self.grid=None
 
 
	def analyze(self,image,tresB,tresW,sizeCoef,shift):
 
		if self.grid==None: return False
 
 
    ((x1,y1),(x2,y2))=self.relevantRect(imageCoords,stoneWidth,stoneHeight)
 
		for r in range(19):
 
			for c in range(19):
 
				intersection=self.grid.intersections[r][c]
 
 
				self.board[r][c]=self.analyzePoint(image,intersection*sizeCoef+shift,*(self.grid.stoneSizeAt(r,c,sizeCoef)),tresB,tresW)
 
 
	def analyzePoint(self,image,imageCoords,stoneWidth,stoneHeight,tresB,tresW):
 
		b=w=e=0
 
 
		((x1,y1),(x2,y2))=self.relevantRect(imageCoords,stoneWidth,stoneHeight)
 
 
    for y in range(y1,y2+1):
 
      for x in range(x1,x2+1):
 
        red,green,blue=image.getpixel((x,y))
 
        
 
        I=(red+green+blue)/255/3
 
        m=min(red,green,blue)
 
        S=1-m/I
 
        if 100*I<tresB: b+=1
 
        elif 100*I>tresW: w+=1
 
        else: e+=1
 
        
 
    if b>=w and b>=e: return Go.BLACK
 
    if w>=b and w>=e: return Go.WHITE
 
    return Go.EMPTY
 
		for y in range(y1,y2+1):
 
			for x in range(x1,x2+1):
 
				red,green,blue=image.getpixel((x,y))
 
 
				I=(red+green+blue)/255/3
 
				m=min(red,green,blue)
 
				S=1-m/I
 
				if 100*I<tresB: b+=1
 
				elif 100*I>tresW: w+=1
 
				else: e+=1
 
 
  def relevantRect(self,imageCoords,stoneWidth,stoneHeight):
 
    x=int(imageCoords.x)
 
    y=int(imageCoords.y)
 
    xmax=max(int(stoneWidth*2//7), 2) # !! optimal parameters subject to further research
 
    ymax=max(int(stoneHeight*2//7), 2)
 
		if b>=w and b>=e: return Go.BLACK
 
		if w>=b and w>=e: return Go.WHITE
 
		return Go.EMPTY
 
 
    return ((x-xmax,y-ymax), (x+xmax,y+ymax))
 
    
 
  def setGrid(self,grid):
 
    self.grid=grid
 
	def relevantRect(self,imageCoords,stoneWidth,stoneHeight):
 
		x=int(imageCoords.x)
 
		y=int(imageCoords.y)
 
		xmax=max(int(stoneWidth*2//7), 2) # !! optimal parameters subject to further research
 
		ymax=max(int(stoneHeight*2//7), 2)
 
 
		return ((x-xmax,y-ymax), (x+xmax,y+ymax))
 
 
	def setGrid(self,grid):
 
		self.grid=grid
0 comments (0 inline, 0 general)