Changeset - 6f9ec51a8142
[Not reviewed]
default
0 6 0
Laman - 6 years ago 2018-12-12 22:28:59

moving responsibilities
GUI got its own ImageAnalyzer instance for doing previews
accordingly changed APIs to keep only useful messages
ImageAnalyzer params wrapped in a separate class
6 files changed with 81 insertions and 65 deletions:
0 comments (0 inline, 0 general)
src/analyzer/__init__.py
Show inline comments
 
@@ -3,19 +3,34 @@
 
from .grid import Grid
 
from util import BLACK,WHITE,EMPTY, exportBoard
 

	
 
log=logging.getLogger(__name__)
 

	
 

	
 
class Params:
 
	def __init__(self):
 
		self.tresB=30
 
		self.tresW=60
 
		self.corners=[]
 

	
 
	def copy(self):
 
		res=Params()
 
		res.tresB=self.tresB
 
		res.tresW=self.tresW
 
		res.corners=[p*1 for p in self.corners] # *1 forces copy
 
		return res
 

	
 

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

	
 
		self.tresB=tresB
 
		self.tresW=tresW
 
		self.params=Params()
 
		self.params.tresB=tresB
 
		self.params.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
 
@@ -40,23 +55,29 @@ class ImageAnalyzer:
 
					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
 
				if 100*I<self.params.tresB: b+=1
 
				elif 100*I>self.params.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 BLACK
 
		if w>=b and w>=e: return WHITE
 
		return EMPTY
 

	
 
	def setGridCorners(self,corners):
 
	def setParams(self,params):
 
		self.params=params.copy()
 
		if len(params.corners)==4:
 
			self.grid=Grid(self.params.corners)
 

	
 
	def setCorners(self,corners):
 
		self.params.corners=[p*1 for p in corners]
 
		self.grid=Grid(corners)
 

	
 

	
 
def relevantRect(imageCoords,stoneWidth,stoneHeight):
 
	x=int(imageCoords.x)
 
	y=int(imageCoords.y)
src/analyzer/framecache.py
Show inline comments
 
from collections import deque
 

	
 

	
 
class FrameCache:
 
	def __init__(self,capacity=600): # 600 is enough for 10 min of 1 fps
 
		self._capacity=capacity
 
		self._cache=deque()
 
		self._keys=deque()
 
		self._values=dict()
 
		self._i=0
 

	
 
	def put(self,frame):
 
		if len(self._cache)>=self._capacity:
 
			self._cache.popleft()
 
		self._cache.append(frame)
 
	def put(self,item):
 
		self._i+=1
 
		if len(self._keys)>=self._capacity:
 
			k=self._keys.popleft()
 
			del self._values[k]
 
		self._values[self._i]=item
 
		self._keys.append(self._i)
 
		return self._i
 

	
 
	def get(self,k):
 
		return self._values.get(k)
 

	
 
	def getRelative(self,k):
 
		return (self._keys[k], self._values[self._keys[k]])
src/core.py
Show inline comments
 
import os
 
import multiprocessing
 
import threading
 
import logging
 
import PIL
 
from util import MsgQueue
 
from gui import gui
 
@@ -24,62 +23,40 @@ class Core:
 
		self.states=StateBag()
 

	
 
		self._ownMessages=MsgQueue(self._handleEvent)
 
		self._guiMessages=MsgQueue()
 
		self._vidMessages=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._frame=None
 

	
 
		self._guiProc=multiprocessing.Process(name="gui", target=gui, args=(self._guiMessages,self._ownMessages))
 
		self._guiProc.start()
 
		self._vidProc=multiprocessing.Process(
 
			name="video",
 
			target=capVideo,
 
			args=(cfg.misc.video, self._vidMessages,self._ownMessages)
 
		)
 
		self._vidProc.start()
 

	
 
	def setCorners(self,corners):
 
		self.detector.setGridCorners(corners)
 
		self.analyze()
 

	
 
	def setTresholds(self,tresB=None,tresW=None):
 
		if tresB is not None: self.detector.tresB=tresB
 
		if tresW is not None: self.detector.tresW=tresW
 
		self.preview()
 

	
 
	def relativeFrame(self,step):
 
		self._imgIndex=(self._imgIndex+step)%len(self._imgs)
 
		imgPath=os.path.join(cfg.misc.imgDir,self._imgs[self._imgIndex])
 
		self._frame=PIL.Image.open(imgPath)
 
		self._guiMessages.send("setCurrentFrame",(self._frame.copy(),gui.PREVIEW))
 
		self.preview()
 
	def showFrame(self,key):
 
		frame=self._cache.get(key)
 
		if frame is None:
 
			(key,frame)=self._cache.getRelative(10)
 
		self._guiMessages.send("setCurrentFrame", (frame.copy(), gui.PREVIEW, key))
 

	
 
	def putFrame(self,frame):
 
		img=PIL.Image.fromarray(frame)
 
		self._cache.put(img)
 
		self._guiMessages.send("setCurrentFrame",(img,gui.PREVIEW)) # !! RECORDING
 
		# self.analyze()
 
		self._frame=PIL.Image.fromarray(frame)
 
		k=self._cache.put(self._frame)
 
		self._guiMessages.send("setCurrentFrame", (self._frame, gui.RECORDING, k))
 
		self.analyze()
 

	
 
	def sendParams(self):
 
		params={
 
			"tresB": self.detector.tresB,
 
			"tresW": self.detector.tresW
 
		}
 
		self._guiMessages.send("setParams",tuple(),params)
 
		self._guiMessages.send("setParams",(self.detector.params.copy(),))
 

	
 
	def setParams(self,tresB=0,tresW=0):
 
		self.detector.tresB=tresB
 
		self.detector.tresW=tresW
 

	
 
	def preview(self):
 
		if self.detector.analyze(self._frame):
 
			self._guiMessages.send("setGameState", (self.detector.board,[]))
 
	def setParams(self,params):
 
		self.detector.setParams(params)
 

	
 
	def analyze(self):
 
		if self.detector.analyze(self._frame):
 
			if isLegalPosition(self.detector.board):
 
				state=self.states.pushState(self.detector.board)
 
				rec=[]
 
@@ -102,14 +79,12 @@ class Core:
 
		self._vidMessages.send("shutDown")
 
		self._vidProc.join()
 
		self._ownMessages.send("!kill",("core",))
 

	
 
	def _handleEvent(self,e):
 
		actions={
 
			"setCorners": self.setCorners,
 
			"setTresholds": self.setTresholds,
 
			"prevFrame": lambda: self.relativeFrame(-1),
 
			"nextFrame": lambda: self.relativeFrame(1),
 
			"putFrame": self.putFrame,
 
			"fetchParams": self.sendParams,
 
			"setParams": self.setParams
 
		}
src/gui/__init__.py
Show inline comments
 
import logging
 
import threading
 
import tkinter as tk
 

	
 
import config
 
from analyzer import ImageAnalyzer
 
from .mainwindow import MainWindow
 
from .boardview import BoardView
 
from .settings import Settings
 

	
 
log=logging.getLogger(__name__)
 

	
 
@@ -16,12 +17,15 @@ class GUI:
 

	
 
	def __init__(self):
 
		self.root = tk.Tk()
 
		self.root.title("OneEye {0}.{1}.{2}".format(*config.misc.version))
 
		self.root.option_add('*tearOff',False) # for menu
 

	
 
		self.detector=ImageAnalyzer()
 
		self._frameKey=0
 

	
 
		self._ownMessages=None
 
		self._coreMessages=None
 

	
 
		self._state=GUI.SETUP
 

	
 
		self.mainWindow = MainWindow(self, master=self.root)
 
@@ -58,39 +62,45 @@ class GUI:
 
		self.mainWindow.setRecording()
 
		self.root.bind("<Left>",lambda e: None)
 
		self.root.bind("<Right>",lambda e: None)
 
		if self.settings:
 
			self.settings.destroy()
 
			self.settings=None
 
		self.sendParams()
 

	
 
	def sendParams(self):
 
		self.sendMsg("setParams",(self.detector.params.copy(),))
 

	
 
	def _handleEvent(self,e):
 
		actions={
 
			"setCurrentFrame": self._frameHandler,
 
			"setGameState": self._stateHandler,
 
			"setParams": self._paramsHandler
 
		}
 
		(actionName,args,kwargs)=e
 

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

	
 
	def _frameHandler(self,newFrame,type):
 
		if self._state!=type:
 
	def _frameHandler(self,newFrame,type,key):
 
		if self._state!=type and self.mainWindow.imgView.isSet():
 
			log.info("ignored setCurrentFrame event, wrong type")
 
			return
 
		self._frameKey=key
 
		self.mainWindow.setCurrentFrame(newFrame)
 
		self.root.event_generate("<<redrawImgView>>")
 

	
 
	def _stateHandler(self,gameState,moves):
 
		labels={(row,col):(i+1) for (i,(c,row,col)) in enumerate(moves)}
 
		self.mainWindow.boardView.redrawState(gameState,labels)
 

	
 
	def _paramsHandler(self,**params):
 
	def _paramsHandler(self,params):
 
		if not self.settings:
 
			log.warning("received 'setParams' message while settings is '%s'",str(self.settings))
 
			return
 
		self.settings.setParams(**params)
 
		self.detector.setParams(params)
 
		self.settings.setParams(params)
 

	
 

	
 
gui=GUI()
 

	
 
"""
 
# setup #
src/gui/imgview.py
Show inline comments
 
@@ -63,13 +63,13 @@ class ImgView(ResizableCanvas):
 
		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._parent.detector.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")
 
@@ -78,12 +78,15 @@ class ImgView(ResizableCanvas):
 
	def setUp(self):
 
		self.bind('<1>',lambda e: self.addCorner(e.x,e.y))
 

	
 
	def setRecording(self):
 
		self.bind('<1>',lambda e: None)
 

	
 
	def isSet(self):
 
		return self._img is not None
 

	
 
	def _onResize(self,event):
 
		w=self._width
 
		super()._onResize(event)
 
		self._corners.scale(self._width/w)
 
		if len(self._corners.corners)==4:
 
			self._boardGrid=Grid(self._corners.corners)
src/gui/settings.py
Show inline comments
 
@@ -37,22 +37,17 @@ class Settings(tk.Toplevel,MsgMixin):
 
		self.confirmButton=tk.Button(self.buttonFrame,text="OK",command=self.sendParams)
 
		self.cancelButton=tk.Button(self.buttonFrame,text="Cancel",command=lambda: self.destroy())
 
		self.confirmButton.pack(side=LEFT)
 
		self.cancelButton.pack(side=LEFT)
 

	
 
	def refreshTresholds(self,_):
 
		self.parent.sendMsg("setTresholds",tuple(),self._compileParams())
 
		params=self.parent.detector.params
 
		params.tresB=self.scaleTresB.get()
 
		params.tresW=self.scaleTresW.get()
 

	
 
	def setParams(self,tresB=0,tresW=0):
 
		self.scaleTresB.set(tresB)
 
		self.scaleTresW.set(tresW)
 
	def setParams(self,params):
 
		self.scaleTresB.set(params.tresB)
 
		self.scaleTresW.set(params.tresW)
 

	
 
	def sendParams(self):
 
		self.parent.sendMsg("setParams",tuple(),self._compileParams())
 
		self.parent.sendParams()
 
		self.destroy()
 

	
 
	def _compileParams(self):
 
		params={
 
			"tresB": self.scaleTresB.get(),
 
			"tresW": self.scaleTresW.get()
 
		}
 
		return params
0 comments (0 inline, 0 general)