Changeset - 5fe83c3dfb92
[Not reviewed]
default
1 4 2
Laman - 8 years ago 2017-01-04 21:11:43

splitting core from gui
6 files changed with 182 insertions and 57 deletions:
0 comments (0 inline, 0 general)
src/config.py
Show inline comments
 
import logging
 

	
 

	
 
logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=logging.DEBUG)
 

	
 
class misc:
 
	version=(0,0)
 
	version=(0,0,0)
 

	
 
class gui:
 
	showBigPoints=True
 
	showGrid=False
src/core.py
Show inline comments
 
new file 100644
 
import multiprocessing
 
import PIL
 
from corners import Corners
 
from gui import gui
 

	
 

	
 
class Core:
 
	corners=Corners()
 
	tresW=60.0
 
	tresB=30.0
 

	
 
	@staticmethod
 
	def setCorners(corners):
 
		Core.corners=corners
 

	
 
	@staticmethod
 
	def setTresholds(tresB=None,tresW=None):
 
		if tresB is not None: Core.tresB=tresB
 
		if tresW is not None: Core.tresW=tresW
 

	
 

	
 
msgQueue=multiprocessing.Queue()
 
guiQueue=multiprocessing.Queue()
 
incomingEvent=multiprocessing.Event()
 
guiEvent=multiprocessing.Event()
 

	
 
frame=PIL.Image.open("../images/7.jpg")
 

	
 
guiProc=multiprocessing.Process(name="gui",target=gui,args=(guiQueue,guiEvent,msgQueue,incomingEvent))
 
guiProc.start()
 
guiQueue.put(("setCurrentFrame",(frame,None),dict()))
 
guiEvent.set()
 

	
 
while True:
 
	incomingEvent.wait()
 
	msg=msgQueue.get()
 
	if msgQueue.empty():
 
		incomingEvent.clear()
 
	print(msg)
 

	
 

	
 
# !! always pass object copies, don't share instances
 
"""
 
core
 
====
 
corners
 
grid
 
go
 
imageAnalyzer
 

	
 

	
 
gui
 
===
 
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)
 
"""
 
\ No newline at end of file
src/corners.py
Show inline comments
 
from epoint import *
 
from epoint import EPoint
 
 
 
class Corners:
 
	def __init__(self):
 
		self.corners=[]
 
src/grid.py
Show inline comments
 
import numpy
 
from epoint import *
 
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.
 
#  
src/gui/__init__.py
Show inline comments
 
file renamed from src/gui.py to src/gui/__init__.py
 
import tkinter as tk
 
import threading
 
import tkinter as tk
 
from PIL import ImageTk
 
import PIL
 
 
import config
 
from corners import *
 
from grid import *
 
from image_analyzer import *
 
from go import *
 
from corners import Corners
 
import image_analyzer
 
from go import Go
 
 
 
class Application(tk.Frame):
 
	def __init__(self, master=None):
 
class MainWindow(tk.Frame):
 
	def __init__(self,parent,master=None):
 
		self.parent=parent
 
		self.corners=Corners()
 
 
		self.currentFrame=None
 
		self.boardGrid=None
 
		self.gameState=None
 
 
		self.img=None
 
 
		tk.Frame.__init__(self, master)
 
		self.grid(column=0,row=0)
 
		self.createWidgets()
 
		self._createWidgets()
 
 
	def setCurrentFrame(self,frame,grid):
 
		self.currentFrame=frame
 
		self.boardGrid=grid
 
 
	def createWidgets(self):
 
		w=int(self.imgView['width'])
 
		h=int(self.imgView['height'])
 
		self.img=ImageTk.PhotoImage(frame.resize((w,h),resample=PIL.Image.BILINEAR))
 
 
	def setGameState(self,gameState):
 
		pass
 
 
	def setCallbacks(self,setCorners,setTresholds):
 
		self.cornersCallback=setCorners
 
		self.tresholdsCallback=setTresholds
 
 
	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)
 
 
		# more controls below the board
 
		def _refreshTresholds(_):
 
			self.refreshTresholds()
 
			self.redrawImgView()
 
		self.scaleTresB=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=_refreshTresholds)
 
		self.scaleTresW=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=_refreshTresholds)
 
		self.scaleTresB.set(30.0)
 
		self.scaleTresB=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=self.refreshTresholds)
 
		self.scaleTresW=tk.Scale(self, orient=tk.HORIZONTAL, length=200, from_=0.0, to=100.0, command=self.refreshTresholds)
 
		self.scaleTresB.set(30.0) # !! proper defaults
 
		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)
 
 
			self.parent.sendMsg("setCorners",(self.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.imgOrig.size
 
			origWidth,origHeight=self.currentFrame.size
 
		widthRatio=origWidth/self.img.width()
 
		heightRatio=origHeight/self.img.height()
 
		sizeCoef=max(widthRatio,heightRatio)
 
		tresB=self.scaleTresB.get()
 
		tresW=self.scaleTresW.get()
 
 
			# 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:
 
			for r in range(19):
 
@@ -81,59 +98,61 @@ class Application(tk.Frame):
 
				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:
 
			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)))
 
					((r1,c1),(r2,c2))=image_analyzer.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)
 
 
	## 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()
 
 
	def refreshTresholds(self):
 
			self.boardView.detector.tresB=self.scaleTresB.get()
 
			self.boardView.detector.tresW=self.scaleTresW.get()
 
	def refreshTresholds(self,_):
 
		self.parent.sendMsg("setTresholds",tuple(),{"tresB":self.scaleTresB.get(), "tresW":self.scaleTresW.get()})
 
 
 
## Handles and presents the game state as detected by the program.
 
class BoardView(tk.Canvas):
 
	def __init__(self, master=None):
 
		self.detector=ImageAnalyzer()
 
		# self.detector=ImageAnalyzer()
 
		self.boardGrid=None
 
 
		tk.Canvas.__init__(self, master)
 
		self.configure(width=360,height=360,background="#ffcc00")
 
 
		self.drawGrid()
 
 
		self.grid()
 
 
	## Redraws and reananalyzes the board view.
 
	def redrawState(self,img,sizeCoef,shift):
 
		self.create_rectangle(0,0,360,360,fill="#ffcc00")
 
		self.drawGrid()
 
 
		self.detector.analyze(img,sizeCoef,shift)
 
 
		for r,row in enumerate(self.detector.board):
 
	def redrawState(self,gameState):
 
		for r,row in enumerate(gameState):
 
			for c,point in enumerate(row):
 
				self.drawStone(r,c,point)
 
 
		self.grid()
 
 
	## Redraws and reananalyzes the board view.
 
	# def redrawState(self,img,sizeCoef,shift):
 
	# 	self.create_rectangle(0,0,360,360,fill="#ffcc00")
 
	# 	self.drawGrid()
 
	#
 
	# 	self.detector.analyze(img,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()
 
@@ -155,15 +174,56 @@ class BoardView(tk.Canvas):
 
		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)
 
 
class GUI:
 
	def __init__(self):
 
		self.root = tk.Tk()
 
		self.root.title("OneEye {0}.{1}.{2}".format(*config.misc.version))
 
 
		self.outcomingQueue=None
 
		self.outcomingEvent=None
 
 
		self.mainWindow = MainWindow(self,master=self.root)
 
 
		self.root.bind("<<redrawImgView>>", lambda e: self.mainWindow.redrawImgView())
 
		self.root.bind("<<receiveState>>", lambda e: print("fired receiveState"))
 
 
	def __call__(self,incomingQueue,incomingEvent,outcomingQueue,outcomingEvent):
 
		self.outcomingQueue=outcomingQueue
 
		self.outcomingEvent=outcomingEvent
 
 
		self.listenerThread=threading.Thread(target=lambda: self._listen(incomingQueue,incomingEvent))
 
		self.listenerThread.start()
 
 
		self.mainWindow.mainloop()
 
 
root = tk.Tk()
 
root.title("OneEye {0}.{1}".format(*config.misc.version))
 
app = Application(master=root)
 
app.mainloop()
 
	def sendMsg(self,actionName,args=tuple(),kwargs=dict()):
 
		self.outcomingQueue.put((actionName,args,kwargs))
 
		self.outcomingEvent.set()
 
 
	def _listen(self,incomingQueue,incomingEvent):
 
		while True:
 
			incomingEvent.wait()
 
			msg=incomingQueue.get()
 
			if incomingQueue.empty():
 
				incomingEvent.clear()
 
			print(msg)
 
			self._handleEvent(msg)
 
 
	def _handleEvent(self,e):
 
		actions={"setCurrentFrame": self._frameHandler}
 
		(actionName,args,kwargs)=e
 
 
		return actions[actionName](*args,**kwargs)
 
 
	def _frameHandler(self,newFrame,grid):
 
		self.mainWindow.setCurrentFrame(newFrame,grid)
 
		self.root.event_generate("<<redrawImgView>>")
 
 
	def _stateHandler(self,e):
 
		pass
 
 
gui=GUI()
src/image_analyzer.py
Show inline comments
 
import logging
 
from go import *
 
from go import Go
 
 
 
class ImageAnalyzer:
 
 
	def __init__(self,tresB=30,tresW=60):
 
		self.board=[[Go.EMPTY]*19 for r in range(19)]
 
@@ -20,13 +20,13 @@ class ImageAnalyzer:
 
 
				self.board[r][c]=self.analyzePoint(image,r,c,intersection*sizeCoef+shift,*(self.grid.stoneSizeAt(r,c,sizeCoef)))
 
 
	def analyzePoint(self,image,row,col,imageCoords,stoneWidth,stoneHeight):
 
		b=w=e=0
 
 
		((x1,y1),(x2,y2))=self.relevantRect(imageCoords,stoneWidth,stoneHeight)
 
		((x1,y1),(x2,y2))=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
 
@@ -39,16 +39,17 @@ class ImageAnalyzer:
 
		logging.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 relevantRect(self,imageCoords,stoneWidth,stoneHeight):
 
	def setGrid(self,grid):
 
		self.grid=grid
 
 
 
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))
 
 
	def setGrid(self,grid):
 
		self.grid=grid
0 comments (0 inline, 0 general)