Changeset - 3798475f45c1
[Not reviewed]
default
6 5 7
Laman - 7 years ago 2017-12-05 00:27:22

refactoring: moving files around
12 files changed with 20 insertions and 21 deletions:
0 comments (0 inline, 0 general)
src/analyzer/__init__.py
Show inline comments
 
file renamed from src/imageanalyzer.py to src/analyzer/__init__.py
 
import logging as log
 
from grid import Grid
 
from go import exportBoard
 
import go
 
from .grid import Grid
 
from go.core import exportBoard, EMPTY,BLACK,WHITE
 

	
 

	
 
class ImageAnalyzer:
 

	
 
	def __init__(self,tresB=30,tresW=60):
 
		self.board=[[go.EMPTY]*19 for r in range(19)]
 
		self.board=[[EMPTY] * 19 for r in range(19)]
 
		self.grid=None
 

	
 
		self.tresB=tresB
 
		self.tresW=tresW
 

	
 
	# let's not concern ourselves with sizecoef and shift here anymore. we want corners to come already properly recomputed
 
	def analyze(self,image):
 
		if self.grid==None:
 
			log.info("ImageAnalyzer.analyze() aborted: no grid available.")
 
			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,r,c,intersection,*(self.grid.stoneSizeAt(r,c)))
 

	
 
		log.info("board analyzed:\n%s", exportBoard(self.board))
 
		return True
 

	
 
	def analyzePoint(self,image,row,col,imageCoords,stoneWidth,stoneHeight):
 
		b=w=e=0
 

	
 
		((x1,y1),(x2,y2))=relevantRect(imageCoords,stoneWidth,stoneHeight)
 

	
 
		for y in range(y1,y2+1):
 
			for x in range(x1,x2+1):
 
				try:
 
					red,green,blue=image.getpixel((x,y))
 
				except IndexError: continue
 

	
 
				I=(red+green+blue)/255/3
 
				m=min(red,green,blue)
 
				S=1-m/I if I!=0 else 0
 
				if 100*I<self.tresB: b+=1
 
				elif 100*I>self.tresW: w+=1
 
				else: e+=1
 

	
 
		log.debug("(%d,%d) ... (b=%d,w=%d,e=%d)", row, col, b, w, e)
 

	
 
		if b>=w and b>=e: return go.BLACK
 
		if w>=b and w>=e: return go.WHITE
 
		return go.EMPTY
 
		if b>=w and b>=e: return BLACK
 
		if w>=b and w>=e: return WHITE
 
		return EMPTY
 

	
 
	def setGridCorners(self,corners):
 
		self.grid=Grid(corners)
 

	
 

	
 
def relevantRect(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))
src/analyzer/corners.py
Show inline comments
 
file renamed from src/corners.py to src/analyzer/corners.py
 
from epoint import EPoint
 
from .epoint import EPoint
 

	
 

	
 
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)
src/analyzer/epoint.py
Show inline comments
 
file renamed from src/epoint.py to src/analyzer/epoint.py
src/analyzer/grid.py
Show inline comments
 
file renamed from src/grid.py to src/analyzer/grid.py
 
import numpy
 
from epoint import EPoint
 
from .epoint import EPoint
 

	
 

	
 
## Projective transformation of a point with a matrix A.
 
#  
 
#  Takes a point as a horizontal vector, transposes it and multiplies with A from left.
 
#  
 
#  @return transformed point as a numpy.array
 
def transformPoint(point,A):
 
	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 list of EPoints in ABCD order per corners.Corners.canonizeOrder().
 
	# !! Needs a check for proper initialization.
 
	def __init__(self,corners):
src/core.py
Show inline comments
 
import os
 
import multiprocessing
 
import threading
 
import logging as log
 
import PIL
 
from util import MsgQueue
 
from gui import gui
 
from imageanalyzer import ImageAnalyzer
 
from go import Go, isLegalPosition
 
from analyzer import ImageAnalyzer
 
from go.core import Go, isLegalPosition
 
from statebag import StateBag
 
import config as cfg
 

	
 

	
 
class Core:
 
	def __init__(self):
 
		self.grid=None
 
		self.go=Go()
 
		self.detector=ImageAnalyzer()
 
		self.states=StateBag()
 

	
 
		self._ownMessages=MsgQueue(self._handleEvent)
 
		self._guiMessages=MsgQueue()
 

	
 
		self._imgs=sorted(os.listdir(cfg.misc.imgDir))
 
		self._imgIndex=cfg.misc.defaultImage
 
		imgPath=os.path.join(cfg.misc.imgDir,self._imgs[self._imgIndex])
 
		self._frame=PIL.Image.open(imgPath)
 

	
 
		self._guiProc=multiprocessing.Process(name="gui", target=gui, args=(self._guiMessages,self._ownMessages))
 
		self._guiProc.start()
 
		self.relativeFrame(0)
 

	
 
	def setCorners(self,corners):
src/go/__init__.py
Show inline comments
 
new file 100644
src/go/core.py
Show inline comments
 
file renamed from src/go.py to src/go/core.py
 
import logging as log
 

	
 
from util import EMPTY,BLACK,WHITE,colorNames
 
from gamerecord import GameRecord
 
from .gamerecord import GameRecord
 

	
 

	
 
class Go:
 
	## Initializes self.board to a list[r][c]=EMPTY.
 
	def __init__(self,boardSize=19):
 
		self.boardSize=boardSize
 
		self.board=[[EMPTY]*boardSize for x in range(boardSize)]
 
		self._temp=[[EMPTY]*boardSize for x in range(boardSize)]
 
		self.toMove=BLACK
 
		self._record=GameRecord()
 

	
 
	## Executes a move.
 
	#
 
	#  Doesn't check for kos. Suicide not allowed.
 
	#
 
	#  @param color BLACK or WHITE
 
	#  @return True on success, False on failure (illegal move)
 
	def move(self,color,row,col):
 
		if color!=self.toMove: log.warning("move by %s out of order",colorNames[color])
 
		if self.board[row][col]!=EMPTY: return False
 

	
 
		self.board[row][col]=color
 

	
 
		# capture neighbors
src/go/gamerecord.py
Show inline comments
 
file renamed from src/gamerecord.py to src/go/gamerecord.py
src/gui/boardview.py
Show inline comments
 
from .resizablecanvas import ResizableCanvas
 
from go import BLACK,WHITE
 
from go.core import BLACK,WHITE
 

	
 

	
 
## Handles and presents the game state as detected by the program.
 
class BoardView(ResizableCanvas):
 
	def __init__(self, master=None):
 
		super().__init__(master)
 

	
 
		self.configure(width=360,height=360,background="#ffcc00")
 

	
 
		self._padding=18
 
		self._cellWidth=(self._width-2*self._padding)/18
 
		self._cellHeight=(self._height-2*self._padding)/18
 

	
 
		self._drawGrid()
 

	
 
	def redrawState(self,gameState):
 
		self.delete("black","white")
 
		for r,row in enumerate(gameState):
 
			for c,point in enumerate(row):
 
				self._drawStone(r, c, point)
 

	
 
	def _drawGrid(self):
 
		padding=self._padding
 
		for i in range(19):
src/gui/imgview.py
Show inline comments
 
import logging as log
 

	
 
from PIL import ImageTk
 

	
 
import config
 
from .resizablecanvas import ResizableCanvas
 
from corners import Corners
 
from epoint import EPoint
 
from grid import Grid
 
import imageanalyzer
 
from analyzer.corners import Corners
 
from analyzer.epoint import EPoint
 
from analyzer.grid import Grid
 
import analyzer
 

	
 

	
 
class ImgView(ResizableCanvas):
 
	def __init__(self,master=None,parent=None):
 
		super().__init__(master)
 

	
 
		self._parent=parent
 
		self._corners=Corners()
 
		self._boardGrid=None
 

	
 
		self._img=None
 
		self._tkImg=None
 

	
 
		self.configure(width=480,height=360)
 
		self.bind('<1>',lambda e: self.addCorner(e.x,e.y))
 

	
 
	## Redraws the current image and its overlay.
 
	def redraw(self):
 
		self.delete("all")
 

	
 
		if self._img:
 
			img=self._img.copy()
 
			img.thumbnail((int(self._width),int(self._height)))
 
			self._tkImg=ImageTk.PhotoImage(img) # just to save the image from garbage collector
 
			self.create_image(self._width//2, self._height//2, anchor="center", image=self._tkImg)
 

	
 
		for corner in self._corners.corners:
 
			self.markPoint(corner.x,corner.y)
 

	
 
		if self._boardGrid!=None and config.gui.showGrid:
 
			for r in range(19):
 
				a=self._boardGrid.intersections[r][0]
 
				b=self._boardGrid.intersections[r][-1]
 
				self.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.create_line(a.x,a.y,b.x,b.y,fill='#00ff00')
 

	
 
		if self._boardGrid!=None and config.gui.showBigPoints:
 
			for r in range(19):
 
				for c in range(19):
 
					((r1,c1),(r2,c2))=imageanalyzer.relevantRect(self._boardGrid.intersections[r][c], *(self._boardGrid.stoneSizeAt(r, c)))
 
					((r1,c1),(r2,c2))=analyzer.relevantRect(self._boardGrid.intersections[r][c], *(self._boardGrid.stoneSizeAt(r, c)))
 
					self.create_rectangle(r1,c1,r2,c2,outline="#00ffff")
 

	
 
	def setImg(self,img):
 
		self._img=img
 

	
 
	## Stores a grid corner located at x,y coordinates.
 
	def addCorner(self,x,y):
 
		self._corners.add(x,y)
 
		log.debug("click on %d,%d",x,y)
 
		if self._corners.canonizeOrder():
 
			# transform corners from show coordinates to real coordinates
 
			log.debug(self._corners.corners)
 
			self._boardGrid=Grid(self._corners.corners)
 
			corners=[self._transformPoint(c) for c in self._corners.corners]
 
			self._parent.sendMsg("setCorners",(corners,))
 

	
 
		self.redraw()
 

	
 
	## Marks a point at the image with a green cross. Used for corners.
 
	def markPoint(self,x,y):
 
		self.create_line(x-3,y-3,x+4,y+4,fill="#00ff00")
 
		self.create_line(x-3,y+3,x+4,y-4,fill="#00ff00")
 

	
 
	def _onResize(self,event):
src/statebag.py
Show inline comments
 
"""Theory:
 
We have a sequence S of valid board states s_1, ..., s_n.
 
We search for a picked subsequence S_p of length k and m additional moves M such that:
 
- S_p augmented by M forms a valid sequence of moves
 
- k-m is maximal for S_p picked from S
 
It is relatively cheap to add new items to S.
 

	
 
User can change detector parameters, presumably in case the previous don't fit the (current) situation.
 
In such a case we assume the new parameters are correct from the current position onwards.
 
But the change might have been appropriate even earlier (before the user detected and fixed an error).
 
So we try to find the correct crossover point like this:
 
- construct a sequence S' = s'_i, ..., s'_n by reanalyzing the positions with a new set of parameters, where s_i is the point of previous user intervention or s_0
 
- for each s'_j:
 
	- try to append it to S[:j]
 
	- try to append it to S'[:j]
 
	- remember the better variant
 
- linearize the fork back by discarding s'_j-s preceding the crossover and s_j-s following the crossover
 
"""
 

	
 
from util import BLACK,WHITE,EMPTY
 
from go import transitionSequence
 
from util import EMPTY
 
from go.core import transitionSequence
 

	
 

	
 
## Crude lower bound on edit distance between states.
 
def estimateDistance(diff):
 
	additions=sum(1 for d in diff if d[2]=="+")
 
	deletions=sum(1 for d in diff if d[2]=="-")
 
	replacements=len(diff)-additions-deletions
 
	if additions>0: return additions+replacements
 
	elif replacements==0 and deletions>0: return 2 # take n, return 1
 
	return replacements # ???
 

	
 

	
 
class BoardState:
 
	def __init__(self,board):
 
		self._board=tuple(tuple(x for x in row) for row in board)
 
		self.prev=None
 
		self._next=None
 
		self.moves=[]
 
		self.weight=0
 
		self.diff2Prev=None
 

	
 
	def __iter__(self): return iter(self._board)
 

	
 
	def __getitem__(self,key): return self._board[key]
src/tests/testGo.py
Show inline comments
 
from unittest import TestCase
 

	
 
from go import isLegalPosition
 
from go.core import isLegalPosition
 

	
 

	
 
class TestLegal(TestCase):
 
	def testLegal(self):
 
		board=[
 
			[0,0,0,0,0],
 
			[1,1,1,1,1],
 
			[-1,-1,0,1,0],
 
			[0,0,0,-1,-1],
 
			[0,-1,0,-1,0]
 
		]
 
		self.assertTrue(isLegalPosition(board))
 

	
 
	def testIllegal(self):
 
		board=[
 
			[0,1,0,0,0],
 
			[1,-1,1,0,0],
 
			[0,1,0,0,0],
 
			[0,0,0,0,0],
 
			[0,0,0,0,0]
 
		]
 
		self.assertFalse(isLegalPosition(board))
0 comments (0 inline, 0 general)