Changeset - 8af597b8f819
[Not reviewed]
default
0 5 0
Laman - 8 years ago 2017-01-06 20:25:01

further refactoring, got processes passing messages, restored computing grid and analyzing image
5 files changed with 93 insertions and 66 deletions:
0 comments (0 inline, 0 general)
src/core.py
Show inline comments
 
import multiprocessing
 
import logging as log
 
import PIL
 
from corners import Corners
 
from gui import gui
 
from image_analyzer import ImageAnalyzer
 
from go import Go
 

	
 

	
 
class Core:
 
	corners=Corners()
 
	tresW=60.0
 
	tresB=30.0
 
	def __init__(self):
 
		self.grid=None
 
		self.go=Go()
 
		self.detector=ImageAnalyzer()
 
		self.tresW=60.0
 
		self.tresB=30.0
 

	
 
	@staticmethod
 
	def setCorners(corners):
 
		Core.corners=corners
 
		self._msgQueue=multiprocessing.Queue()
 
		self._guiQueue=multiprocessing.Queue()
 
		self._incomingEvent=multiprocessing.Event()
 
		self._guiEvent=multiprocessing.Event()
 

	
 
	@staticmethod
 
	def setTresholds(tresB=None,tresW=None):
 
		if tresB is not None: Core.tresB=tresB
 
		if tresW is not None: Core.tresW=tresW
 
		self._frame=PIL.Image.open("../images/7.jpg")
 

	
 
		self._guiProc=multiprocessing.Process(name="gui", target=gui, args=(self._guiQueue,self._guiEvent,self._msgQueue,self._incomingEvent))
 
		self._guiProc.start()
 
		self._guiQueue.put(("setCurrentFrame",(self._frame,),dict()))
 
		self._guiEvent.set()
 

	
 
msgQueue=multiprocessing.Queue()
 
guiQueue=multiprocessing.Queue()
 
incomingEvent=multiprocessing.Event()
 
guiEvent=multiprocessing.Event()
 
	def setCorners(self,corners):
 
		self.detector.setGridCorners(corners)
 
		self.detector.analyze(self._frame)
 
		for r in self.detector.board: log.info(r)
 

	
 
frame=PIL.Image.open("../images/7.jpg")
 
	def setTresholds(self,tresB=None,tresW=None):
 
		if tresB is not None: self.tresB=tresB
 
		if tresW is not None: self.tresW=tresW
 

	
 
guiProc=multiprocessing.Process(name="gui",target=gui,args=(guiQueue,guiEvent,msgQueue,incomingEvent))
 
guiProc.start()
 
guiQueue.put(("setCurrentFrame",(frame,None),dict()))
 
guiEvent.set()
 
	def listen(self):
 
		while True:
 
			self._incomingEvent.wait()
 
			msg=self._msgQueue.get()
 
			if self._msgQueue.empty():
 
				self._incomingEvent.clear()
 
			log.info(msg)
 
			self._handleEvent(msg)
 

	
 
while True:
 
	incomingEvent.wait()
 
	msg=msgQueue.get()
 
	if msgQueue.empty():
 
		incomingEvent.clear()
 
	print(msg)
 
	def _handleEvent(self,e):
 
		actions={"setCorners":self.setCorners, "setTresholds":self.setTresholds}
 
		(actionName,args,kwargs)=e
 

	
 
		return actions[actionName](*args,**kwargs)
 

	
 
# !! always pass object copies, don't share instances
 
core=Core()
 
core.listen()
 

	
 
"""
 
core
 
====
 
corners
 
grid
 
go
 
imageAnalyzer
 

	
 

	
 
gui
 
===
 
corners
 

	
 
a) keeps references to important objects and uses them
 
b) gets and sets all relevant data through method calls with core
 

	
 
GUI
 
<- addCorner(corner)
 
-> redrawImgView(img,grid)
 
<- refreshTresholds(tresB,tresW)
 

	
 
BoardView
 
-> redrawState(go)
 

	
 

	
 
core-gui: just pass messages with relevant data (!! always pass object copies, don't share instances)
 
"""
 
\ No newline at end of file
src/go.py
Show inline comments
 
@@ -3,13 +3,13 @@
 
	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=[[Go.EMPTY]*self.boardSize for x in range(boardSize)]
 
 
	## Executes a move.
 
	#
 
	#  Doesn't check for kos. Suicide not allowed.
 
	#
 
	#  @param color Go.BLACK or Go.WHITE
src/grid.py
Show inline comments
 
import numpy
 
from epoint import EPoint
 
 
 
## Projective transformation of a point with a matrix A.
 
#  
 
#  Takes a point as a horizontal vector and multiplies it transposed with A from left.
 
#  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()
 
 
 
@@ -18,17 +18,18 @@ class Grid:
 
	#
 
	#  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.
 
	#  @param corners list of EPoints in ABCD order per corners.Corners.canonizeOrder().
 
	# !! Needs a check for proper initialization.
 
	def __init__(self,corners):
 
		# ad
 
		# bc
 
		a,b,c,d=[c.toProjective() for c in corners.corners]
 
		a,b,c,d=[c.toProjective() for c in 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
 
@@ -57,15 +58,15 @@ class Grid:
 
			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))
 
 
	def stoneSizeAt(self,r,c,sizeCoef):
 
	def stoneSizeAt(self,r,c):
 
		intersection=self.intersections[r][c]
 
 
		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)
 
		if c>0: width=intersection.x - self.intersections[r][c-1].x
 
		else: width=self.intersections[r][c+1].x - intersection.x
 
		if r>0: height=intersection.y - self.intersections[r-1][c].y
 
		else: height=self.intersections[r+1][c].y - intersection.y
 
 
		return (width,height)
src/gui/__init__.py
Show inline comments
 
import threading
 
import logging as log
 
import tkinter as tk
 
from PIL import ImageTk
 
import PIL
 
 
import config
 
from epoint import EPoint
 
from corners import Corners
 
import image_analyzer
 
from go import Go
 
from grid import Grid
 
 
 
class MainWindow(tk.Frame):
 
	def __init__(self,parent,master=None):
 
		self.parent=parent
 
		self.corners=Corners()
 
 
		self.currentFrame=None
 
		self.boardGrid=None
 
		self._boardGrid=None
 
		self.gameState=None
 
 
		self.img=None
 
		self._imgSizeCoef=1
 
		self._imgShift=EPoint(0,0)
 
 
		tk.Frame.__init__(self, master)
 
		self.grid(column=0,row=0)
 
		self._createWidgets()
 
 
	def setCurrentFrame(self,frame,grid):
 
	def setCurrentFrame(self,frame):
 
		self.currentFrame=frame
 
		self.boardGrid=grid
 
 
		w=int(self.imgView['width'])
 
		h=int(self.imgView['height'])
 
		self.img=ImageTk.PhotoImage(frame.resize((w,h),resample=PIL.Image.BILINEAR))
 
 
		wo,ho=self.currentFrame.size # o for original
 
		widthRatio=wo/w
 
		heightRatio=ho/h
 
		self._imgSizeCoef=max(widthRatio,heightRatio)
 
		# shift compensates possible horizontal or vertical empty margins from unmatching aspect ratios
 
		self._imgShift=EPoint(wo-w*self._imgSizeCoef,ho-h*self._imgSizeCoef)/2
 
 
	def setGameState(self,gameState):
 
		pass
 
 
	def setCallbacks(self,setCorners,setTresholds):
 
		self.cornersCallback=setCorners
 
		self.tresholdsCallback=setTresholds
 
@@ -64,47 +75,44 @@ class MainWindow(tk.Frame):
 
		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.parent.sendMsg("setCorners",(self.corners,))
 
			# transform corners from show coordinates to real coordinates
 
			log.debug(self.corners.corners)
 
			self._boardGrid=Grid(self.corners.corners)
 
			corners=[(c*self._imgSizeCoef+self._imgShift) for c in self.corners.corners]
 
			self.parent.sendMsg("setCorners",(corners,))
 
		# 	self.boardGrid=Grid(self.corners)
 
		# 	self.boardView.setBoardGrid(self.boardGrid)
 
		#
 
		self.redrawImgView()
 
 
	## Redraws the current image and its overlay.
 
	def redrawImgView(self):
 
		if self.currentFrame and self.img:
 
			self.imgView.create_image(2,2,anchor="nw",image=self.img)
 
 
			origWidth,origHeight=self.currentFrame.size
 
			widthRatio=origWidth/self.img.width()
 
			heightRatio=origHeight/self.img.height()
 
			sizeCoef=max(widthRatio,heightRatio)
 
 
			# shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2
 
 
		for corner in self.corners.corners:
 
			self.markPoint(corner.x,corner.y)
 
 
		if self.boardGrid!=None and config.gui.showGrid:
 
		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]
 
				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]
 
				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 config.gui.showBigPoints:
 
		if self._boardGrid!=None and config.gui.showBigPoints:
 
			for r in range(19):
 
				for c in range(19):
 
					((r1,c1),(r2,c2))=image_analyzer.relevantRect(self.boardGrid.intersections[r][c],*(self.boardGrid.stoneSizeAt(r,c,sizeCoef)))
 
					((r1,c1),(r2,c2))=image_analyzer.relevantRect(self._boardGrid.intersections[r][c], *(self._boardGrid.stoneSizeAt(r, c)))
 
					self.imgView.create_rectangle(r1,c1,r2,c2,outline="#00ffff")
 
 
		self.imgView.grid()
 
 
	## Marks a point at the image with a green cross. Used for corners.
 
	def markPoint(self,x,y):
 
@@ -118,13 +126,12 @@ class MainWindow(tk.Frame):
 
 
 
## 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")
 
 
		self.drawGrid()
 
 
@@ -207,23 +214,23 @@ class GUI:
 
	def _listen(self,incomingQueue,incomingEvent):
 
		while True:
 
			incomingEvent.wait()
 
			msg=incomingQueue.get()
 
			if incomingQueue.empty():
 
				incomingEvent.clear()
 
			print(msg)
 
			log.info(msg)
 
			self._handleEvent(msg)
 
 
	def _handleEvent(self,e):
 
		actions={"setCurrentFrame": self._frameHandler}
 
		actions={"setCurrentFrame":self._frameHandler}
 
		(actionName,args,kwargs)=e
 
 
		return actions[actionName](*args,**kwargs)
 
 
	def _frameHandler(self,newFrame,grid):
 
		self.mainWindow.setCurrentFrame(newFrame,grid)
 
	def _frameHandler(self,newFrame):
 
		self.mainWindow.setCurrentFrame(newFrame)
 
		self.root.event_generate("<<redrawImgView>>")
 
 
	def _stateHandler(self,e):
 
		pass
 
 
gui=GUI()
src/image_analyzer.py
Show inline comments
 
import logging
 
import logging as log
 
from grid import Grid
 
from go import Go
 
 
 
class ImageAnalyzer:
 
 
	def __init__(self,tresB=30,tresW=60):
 
		self.board=[[Go.EMPTY]*19 for r in range(19)]
 
		self.grid=None
 
 
		self.tresB=tresB
 
		self.tresW=tresW
 
 
	def analyze(self,image,sizeCoef,shift):
 
	# 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: 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*sizeCoef+shift,*(self.grid.stoneSizeAt(r,c,sizeCoef)))
 
				self.board[r][c]=self.analyzePoint(image,r,c,intersection,*(self.grid.stoneSizeAt(r,c)))
 
 
	def analyzePoint(self,image,row,col,imageCoords,stoneWidth,stoneHeight):
 
		b=w=e=0
 
 
		((x1,y1),(x2,y2))=relevantRect(imageCoords,stoneWidth,stoneHeight)
 
 
@@ -33,20 +35,20 @@ class ImageAnalyzer:
 
				m=min(red,green,blue)
 
				S=1-m/I
 
				if 100*I<self.tresB: b+=1
 
				elif 100*I>self.tresW: w+=1
 
				else: e+=1
 
 
		logging.debug("(%d,%d) ... (b=%d,w=%d,e=%d)", row, col, b, w, e)
 
		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
 
 
	def setGrid(self,grid):
 
		self.grid=grid
 
	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
0 comments (0 inline, 0 general)