Files @ 5fe83c3dfb92
Branch filter:

Location: OneEye/src/gui/__init__.py

Laman
splitting core from gui
import threading
import tkinter as tk
from PIL import ImageTk
import PIL

import config
from corners import Corners
import image_analyzer
from go import Go


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()

	def setCurrentFrame(self,frame,grid):
		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))

	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.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
		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.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.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:
			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 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)))
					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):
		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.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.boardGrid=None

		tk.Canvas.__init__(self, master)
		self.configure(width=360,height=360,background="#ffcc00")

		self.drawGrid()

		self.grid()

	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()

	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)


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()

	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()