Changeset - 7f6fac7f6d8e
[Not reviewed]
default
0 6 0
Laman - 6 years ago 2018-12-04 17:17:22

enhanced logging
6 files changed with 46 insertions and 7 deletions:
0 comments (0 inline, 0 general)
src/analyzer/__init__.py
Show inline comments
 
import logging as log
 
import logging
 

	
 
from .grid import Grid
 
from util import BLACK,WHITE,EMPTY, exportBoard
 

	
 
log=logging.getLogger(__name__)
 

	
 

	
 
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
 

	
 
	# 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 BLACK
 
		if w>=b and w>=e: return WHITE
 
		return EMPTY
 

	
src/config.py
Show inline comments
 
import os
 
import logging
 
import logging.config
 
import json
 

	
 

	
 
srcDir=os.path.dirname(__file__)
 

	
 
logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', level=logging.DEBUG)
 
with open(os.path.join(srcDir, "..", "config.json")) as f:
 
	cfgFile=json.load(f)
 

	
 

	
 
logCfg = cfgFile.get("logging") or {
 
	"version": 1,
 
	"handlers": {
 
		"file": {
 
			"level": "DEBUG",
 
			"class": "logging.FileHandler",
 
			"filename": "/tmp/oneeye.log",
 
			"formatter": "default"
 
		},
 
		"console": {
 
			"level": "DEBUG",
 
			"class": "logging.StreamHandler",
 
			"formatter": "default"
 
		}
 
	},
 
	"root": {
 
		"handlers": ["console","file"],
 
		"level": "DEBUG"
 
	},
 
	"formatters": {
 
		"default": {
 
			"format": "%(asctime)s %(levelname)s: %(message)s",
 
			"datefmt": "%Y-%m-%d %H:%M:%S"
 
		}
 
	}
 
}
 

	
 
logging.config.dictConfig(logCfg)
 

	
 

	
 
class misc:
 
	file=cfgFile["misc"]
 
	version=(0,0,0)
 
	defaultImage=file.get("defaultImage", 0)
 

	
 
	_imgDir=file.get("imgDir","../images")
 
	imgDir=_imgDir if os.path.isabs(_imgDir) else os.path.join(srcDir,_imgDir)
 

	
 

	
 
class gui:
 
	file=cfgFile["gui"]
 
	showBigPoints=file.get("showBigPoints", False)
 
	showGrid=file.get("showGrid", True)
src/core.py
Show inline comments
 
import os
 
import multiprocessing
 
import threading
 
import logging as log
 
import logging
 
import PIL
 
from util import MsgQueue
 
from gui import gui
 
from analyzer import ImageAnalyzer
 
from go.core import Go, isLegalPosition
 
from statebag import StateBag
 
import config as cfg
 

	
 
log=logging.getLogger(__name__)
 

	
 

	
 
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):
 
		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.analyze()
 

	
 
	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(),))
 
		self.analyze()
 

	
 
	def analyze(self):
 
		if self.detector.analyze(self._frame):
 
			if isLegalPosition(self.detector.board):
 
				state=self.states.pushState(self.detector.board)
 
				rec=[]
 
				if state:
 
					rec=state.exportRecord()
 
					log.debug("progressive game record: %s",rec)
 
				self._guiMessages.send("setGameState", (self.detector.board,rec))
 

	
 
				self.go.transitionMove(self.detector.board)
 
				log.debug("conservative game record: %s",self.go._record)
src/go/core.py
Show inline comments
 
import logging as log
 
import logging
 

	
 
from util import EMPTY,BLACK,WHITE, colorNames,hashBoard,diffHash
 
from .helperboard import HelperBoard
 
from .gamerecord import GameRecord
 

	
 
log=logging.getLogger(__name__)
 

	
 
PASS=(99,99)
 

	
 

	
 
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.toMove=BLACK
 

	
 
		# utility field to allow undoing moves. but only useful right after doMove. this class doesn't need it, so it is not kept correct at other times
 
		self.captures=[None]*4
 
		self.captureCount=0
 

	
 
		self._helper=HelperBoard(self.board)
 
		self._freshHash=hashBoard(self.board) # always reflecting current state of the board
 
		self._hashes=[self._freshHash]
 
		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 doMove(self,color,r,c):
 
		if color!=self.toMove: log.warning("move by %s out of order",colorNames[color])
 
		if (r,c)==PASS:
 
			self.toMove*=-1 # pass
 
			self._hashes.append(self._freshHash)
 
			return True
 
		if self.board[r][c]!=EMPTY: return False
 

	
 
		self.board[r][c]=color
 
		self._freshHash^=diffHash(r,c,EMPTY,color)
 

	
 
		# capture neighbors
 
		self.captureCount=0
 
		for dr,dc in ((-1,0),(1,0),(0,-1),(0,1)):
 
			self._helper.clear()
 
			if not self._helper.floodFill(-color,r+dr,c+dc,EMPTY):
 
				self.captures[self.captureCount]=(r+dr,c+dc)
 
				self.captureCount+=1
 
				self._remove()
 

	
 
		# check for suicide and prevent it
 
		self._helper.clear()
src/gui/imgview.py
Show inline comments
 
import logging as log
 
import logging
 

	
 
from PIL import ImageTk
 

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

	
 
log=logging.getLogger(__name__)
 

	
 

	
 
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.resize((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))=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):
src/util.py
Show inline comments
 
import random
 
import multiprocessing
 
import logging as log
 
import logging
 

	
 
log=logging.getLogger(__name__)
 

	
 
EMPTY=0
 
BLACK=1
 
WHITE=-1
 

	
 
colorNames={BLACK:"B",WHITE:"W"}
 

	
 

	
 
class MsgQueue:
 
	def __init__(self,handler=None):
 
		self._queue=multiprocessing.Queue()
 
		self._event=multiprocessing.Event()
 
		self._handleEvent=handler
 

	
 
	def send(self,actionName,args=tuple(),kwargs=None):
 
		if kwargs is None: kwargs=dict()
 
		self._queue.put((actionName,args,kwargs))
 
		self._event.set()
 

	
 
	def listen(self,handleEvent=None):
 
		if handleEvent is not None: self._handleEvent=handleEvent
 

	
 
		while True:
 
			self._event.wait()
 
			msg=self._queue.get()
 
			if self._queue.empty():
 
				self._event.clear()
 
			log.info(msg)
 
			if msg[0]=="!kill": break
 
			self._handleEvent(msg)
 

	
 
	def setHandler(self,handler):
 
		self._handleEvent=handler
 

	
 

	
 
rand=random.Random()
 
rand.seed(361)
 
zobristNums=tuple(
 
	tuple(
 
		tuple(rand.getrandbits(32) for i in range(3)) for c in range(19)
 
	) for r in range(19)
 
)
 

	
 
def hashBoard(board):
 
	res=0
 
	for (r,row) in enumerate(board):
 
		for (c,item) in enumerate(row):
 
			res^=zobristNums[r][c][item+1]
0 comments (0 inline, 0 general)