From 134de4472d3f2fa913944770595de9221dd27fdf Mon Sep 17 00:00:00 2001 From: Thomas B Thompson Date: Tue, 4 Jan 2011 00:04:05 -0500 Subject: worked on profiling, made a bunch of changes, huge speedup! --- LightInstallation.py | 2 +- Profile.py | 4 ++++ TestProfile.py | 34 +++++++++++++++++++++++++++++++ config/Outdoor.xml | 3 ++- inputs/PygameInput.py | 2 ++ logger/loggingConfig.ini | 4 ++-- operationscore/Input.py | 3 ++- operationscore/Renderer.py | 4 ++-- operationscore/SmootCoreObject.py | 3 +-- operationscore/ThreadedSmootCoreObject.py | 14 +++++++++++++ pixelcore/Pixel.py | 21 ++++++++++++++----- pixelcore/Screen.py | 17 ++++++++++++++-- pixelevents/DecayEvent.py | 22 +++++++++++++------- pixelmappers/GaussianMapper.py | 3 ++- util/ColorOps.py | 14 ++++++++++--- 15 files changed, 123 insertions(+), 27 deletions(-) create mode 100644 Profile.py create mode 100644 TestProfile.py create mode 100644 operationscore/ThreadedSmootCoreObject.py diff --git a/LightInstallation.py b/LightInstallation.py index 07fa8fc..168983b 100644 --- a/LightInstallation.py +++ b/LightInstallation.py @@ -100,7 +100,7 @@ class LightInstallation: #self.screen.allOn() lastLoopTime = clock.time() refreshInterval = 30 - runCount = 10000 + runCount = 150 while runCount > 0: runCount -= 1 loopStart = clock.time() diff --git a/Profile.py b/Profile.py new file mode 100644 index 0000000..45ccc35 --- /dev/null +++ b/Profile.py @@ -0,0 +1,4 @@ +import cProfile +from LightInstallation import main +command = """main(['', 'config/Outdoor.xml'])""" +cProfile.runctx(command, globals(), locals(), filename="smootlight.profile") diff --git a/TestProfile.py b/TestProfile.py new file mode 100644 index 0000000..eccb884 --- /dev/null +++ b/TestProfile.py @@ -0,0 +1,34 @@ +import cProfile +from LightInstallation import main +numiter = 10000000 +def main1(): + for i in xrange(0,numiter): + if 'abc' == 'def': + pass + if 'abc' == 'abc': + pass + +def main2(): + for i in xrange(0,numiter): + if 1 == 2: + pass + if 1 == 1: + pass + +x = [1,2,3] +a = [] +def abc1(): + for i in xrange(0,numiter): + a = min(4, 255) + b = min(257, 255) + +def abc2(): + for i in xrange(0,numiter): + a = 4 if 4 < 255 else 255 + b = 257 if 257 < 255 else 255 +command = """abc1()""" +cProfile.runctx(command, globals(), locals()) + +command = """abc2()""" +cProfile.runctx(command, globals(), locals()) + diff --git a/config/Outdoor.xml b/config/Outdoor.xml index b34e18a..67958d7 100644 --- a/config/Outdoor.xml +++ b/config/Outdoor.xml @@ -20,6 +20,7 @@ gaussmap 20 + 0.01 3 1 @@ -186,7 +187,7 @@ colorchange decay - True + False diff --git a/inputs/PygameInput.py b/inputs/PygameInput.py index 6779a20..a39c089 100644 --- a/inputs/PygameInput.py +++ b/inputs/PygameInput.py @@ -13,6 +13,8 @@ class PygameInput(Input): return for event in pygame.event.get(): if event.type is KEYDOWN: + if event.key == 301: + exit() self.respond({Strings.LOCATION: (5,5),'Key': event.key}) if event.type is MOUSEBUTTONDOWN: self.respond({Strings.LOCATION: pygame.mouse.get_pos()}) diff --git a/logger/loggingConfig.ini b/logger/loggingConfig.ini index ee35749..6727c26 100644 --- a/logger/loggingConfig.ini +++ b/logger/loggingConfig.ini @@ -18,7 +18,7 @@ level = INFO handlers = console [logger_smoot_light] -level = INFO +level = DEBUG handlers = file qualname = smoot_light propagate = 0 @@ -40,7 +40,7 @@ formatter = generic [handler_file] class = FileHandler args = ('/var/log/smoot_light/main.log', 'a') -level = INFO +level = DEBUG formatter = generic [handler_exception] diff --git a/operationscore/Input.py b/operationscore/Input.py index 62c4682..6b56cd5 100644 --- a/operationscore/Input.py +++ b/operationscore/Input.py @@ -1,5 +1,6 @@ import threading,time from operationscore.SmootCoreObject import * +from operationscore.ThreadedSmootCoreObject import ThreadedSmootCoreObject #Abstract class for inputs. Inheriting classes should call "respond" to raise #their event. Inheriting classes MUST define sensingLoop. Called at the #interval specified in RefreshInterval while the input is active. For example, if you are writing @@ -7,7 +8,7 @@ from operationscore.SmootCoreObject import * #Inheriting classes MAY define inputInit. This is called before the loop #begins. import pdb -class Input(SmootCoreObject): +class Input(ThreadedSmootCoreObject): #Event scope is a function pointer the function that will get called when #an Parent is raised. def __init__(self, argDict): diff --git a/operationscore/Renderer.py b/operationscore/Renderer.py index 8e31f8b..88da606 100644 --- a/operationscore/Renderer.py +++ b/operationscore/Renderer.py @@ -3,8 +3,8 @@ #Inheriting classes may define initRenderer which is called after the dictionary #is pulled from config. #TODO: multithreaded-rendering -from operationscore.SmootCoreObject import * -class Renderer(SmootCoreObject): +from operationscore.ThreadedSmootCoreObject import * +class Renderer(ThreadedSmootCoreObject): def init(self): self.initRenderer() threading.Thread.__init__(self) diff --git a/operationscore/SmootCoreObject.py b/operationscore/SmootCoreObject.py index 9784aab..7e3c8bd 100644 --- a/operationscore/SmootCoreObject.py +++ b/operationscore/SmootCoreObject.py @@ -2,12 +2,11 @@ import pdb import threading import thread import util.Config as configGetter -class SmootCoreObject(threading.Thread): +class SmootCoreObject(object): def __init__(self, argDict, skipValidation = False): self.argDict = argDict self.validateArgs(self.className()+'.params') self.lock = thread.allocate_lock() - threading.Thread.__init__(self) self.init() #call init of inheriting class # self.__setitem__ = self.argDict.__setitem__ # self.__getitem__ = self.argDict.__getitem__ diff --git a/operationscore/ThreadedSmootCoreObject.py b/operationscore/ThreadedSmootCoreObject.py new file mode 100644 index 0000000..90611bc --- /dev/null +++ b/operationscore/ThreadedSmootCoreObject.py @@ -0,0 +1,14 @@ +import pdb +import threading +import thread +import util.Config as configGetter +from operationscore.SmootCoreObject import SmootCoreObject +class ThreadedSmootCoreObject(SmootCoreObject, threading.Thread): + def __init__(self, argDict, skipValidation = False): + self.argDict = argDict + self.validateArgs(self.className()+'.params') + self.lock = thread.allocate_lock() + threading.Thread.__init__(self) + self.init() #call init of inheriting class + # self.__setitem__ = self.argDict.__setitem__ + # self.__getitem__ = self.argDict.__getitem__ diff --git a/pixelcore/Pixel.py b/pixelcore/Pixel.py index f66d0bb..9f5cc85 100644 --- a/pixelcore/Pixel.py +++ b/pixelcore/Pixel.py @@ -1,7 +1,7 @@ import util.ColorOps as color import pdb from pixelevents.StepEvent import * -import util.TimeOps as clock +import util.TimeOps as timeops #Pixel keeps a queue of events (PixelEvent objects) (actually a dictionary #keyed by event time). Every time is state is #requested, it processes all the members of its queue. If a member returns none, @@ -10,12 +10,15 @@ import util.TimeOps as clock class Pixel: radius = 2 timeOff = -1 + def __init__(self, location): self.location = location self.events = {} self.memState = None + def turnOn(self): self.turnOnFor(-1) + #Turn the light white for 'time' ms. Really only meant for testing. Use #processInput instead. Also, you shouldn't use this anyway. You should be #using the input method on the screen! @@ -23,34 +26,42 @@ class Pixel: event = StepEvent.generate(time, (255,255,255)) #TODO: Move color to self.processInput(event, 0) #arg + #Add a pixelEvent to the list of active events def processInput(self,pixelEvent,zindex): #consider migrating arg to dict - self.events[clock.time()] = (zindex, pixelEvent) + self.events[timeops.time()] = (zindex, pixelEvent) + def clearAllEvents(self): self.events = {} + #Combines all PixelEvents currently active and computes the current color of #the pixel. def invalidateState(self): self.memState = None + def state(self): if self.memState != None: return self.memState - if len(self.events) == 0: + if self.events == []: return (0,0,0) deadEvents = [] - currentTime = clock.time() + currentTime = timeops.time() resultingColor = (0,0,0) + colors = [] for eventTime in self.events: #TODO: right color weighting code (zindex,event) = self.events[eventTime] eventResult = event.state(currentTime-eventTime) if eventResult != None: - resultingColor = color.combineColors(eventResult, resultingColor) + colors.append(eventResult) else: deadEvents.append(eventTime) + + resultingColor = color.combineColors(colors) [self.events.pop(event) for event in deadEvents] resultingColor = [int(round(c)) for c in resultingColor] self.memState = tuple(resultingColor) return tuple(resultingColor) + def __str__(self): return 'Loc: ' + str(self.location) diff --git a/pixelcore/Screen.py b/pixelcore/Screen.py index da03ad2..b002896 100644 --- a/pixelcore/Screen.py +++ b/pixelcore/Screen.py @@ -6,6 +6,8 @@ import util.Search as Search import util.ComponentRegistry as compReg import util.Strings as Strings import itertools +import sys +from logger import main_log #Class representing a collection of Pixels grouped into PixelStrips. Needs a #PixelMapper, currently set via setMapper by may be migrated into the argDict. class Screen: @@ -15,28 +17,35 @@ class Screen: self.xSortedPixels = [] self.xPixelLocs = [] sizeValid = False + def addStrip(self, lS): self.pixelStrips.append(lS) self.sizeValid = False #keep track of whether or not our screen size has #been invalidated by adding more pixels self.computeXSortedPixels() + #Returns (pixelIndex, pixel). Does a binary search. def pixelsInRange(self, minX, maxX): minIndex = Search.find_ge(self.xPixelLocs, minX) maxIndex = Search.find_le(self.xPixelLocs, maxX)+1 return self.xSortedPixels[minIndex:maxIndex] + def computeXSortedPixels(self): for pixel in self: self.xSortedPixels.append((pixel.location[0], pixel)) self.xSortedPixels.sort() self.xPixelLocs = [p[0] for p in self.xSortedPixels] + def render(self, surface): [lS.render(surface) for lS in self.pixelStrips] + def allOn(self): [lS.allOn(-1) for lS in self.pixelStrips] + def __iter__(self): #the iterator of all our pixel strips chained togther return itertools.chain(*[strip.__iter__() for strip in \ self.pixelStrips]) #the * operator breaks the list into args + #increment time -- This processes all queued responses. Responses generated #during this period are added to the queue that will be processed on the next #time step. @@ -46,14 +55,15 @@ class Screen: for response in tempQueue: self.processResponse(response) [p.invalidateState() for p in self] + #public def respond(self, responseInfo): self.responseQueue.append(responseInfo) + def getSize(self): if self.sizeValid: return self.size - (minX, minY, maxX, maxY) = (10**10,10**10,-10**10,-10*10) #TODO: don't - #be lazy + (minX, minY, maxX, maxY) = (sys.maxint,sys.maxint,-sys.maxint,-sys.maxint) for light in self: (x,y) = light.location @@ -65,6 +75,7 @@ class Screen: self.size = (0,0, maxX, maxY) self.sizeValid = True return (0, 0, maxX+100, maxY+100) #TODO: cleaner + #private def processResponse(self, responseInfo): #we need to make a new dict for #each to prevent interference @@ -78,6 +89,8 @@ class Screen: #if type(mapper) != type(PixelMapper): # raise Exception('No default mapper specified.') pixelWeightList = mapper.mapEvent(responseInfo['Location'], self) + main_log.debug(str(len(pixelWeightList))) + main_log.debug(pixelWeightList) PixelEvent.addPixelEventIfMissing(responseInfo) for (pixel, weight) in pixelWeightList: pixel.processInput(responseInfo['PixelEvent'].scale(weight), 0) #TODO: z-index diff --git a/pixelevents/DecayEvent.py b/pixelevents/DecayEvent.py index 0b4c820..c1166d6 100644 --- a/pixelevents/DecayEvent.py +++ b/pixelevents/DecayEvent.py @@ -3,14 +3,22 @@ import math from util.ColorOps import * class DecayEvent(PixelEvent): def initEvent(self): - self['Coefficient'] = abs(self['Coefficient']) - def state(self,timeDelay): + self.coefficient = float(abs(self['Coefficient'])) if self['DecayType'] == 'Exponential': - decay = math.exp(timeDelay*-1*self['Coefficient']) - if self['DecayType'] == 'Proportional': - decay = float(self['Coefficient']) / timeDelay - color = multiplyColor(self['Color'], decay) - return color if sum(color) > 5 else None + self.decayType = 1 + else: + self.decayType = 2 + self.color = self['Color'] + + #SUBVERTING DESIGN FOR THE SAKE OF EFFICIENCY -- RUSSELL COHEN (2011-01-03-23:18) + def state(self,timeDelay): + if self.decayType == 1: + decay = math.exp(timeDelay*-1*self.coefficient) + if self.decayType == 2: + decay = self.coefficient / timeDelay + color = multiplyColor(self.color, decay) + return color if (color[0] + color[1] + color[2]) > 5 else None + @staticmethod def generate(decayType, coefficient, color): args = {'DecayType': decayType, 'Coefficient':coefficient, 'Color':color} diff --git a/pixelmappers/GaussianMapper.py b/pixelmappers/GaussianMapper.py index 8755acf..8fdf16b 100644 --- a/pixelmappers/GaussianMapper.py +++ b/pixelmappers/GaussianMapper.py @@ -9,5 +9,6 @@ class GaussianMapper(PixelMapper): pixelDist = Geo.dist(pixel.location, eventLocation) if pixelDist < self['CutoffDist']: w = Geo.gaussian(pixelDist, self['Height'], 0, self['Width']) - returnPixels.append((pixel, w)) + if w > self['MinWeight']: + returnPixels.append((pixel, w)) return returnPixels diff --git a/util/ColorOps.py b/util/ColorOps.py index b0d64a7..da1e704 100644 --- a/util/ColorOps.py +++ b/util/ColorOps.py @@ -4,8 +4,16 @@ def randomColor(): def chooseRandomColor(colorList): return random.choice(colorList) def safeColor(c): - return [min(channel,255) for channel in c] -def combineColors(c1,c2): - return safeColor([c1[i]+c2[i] for i in range(min(len(c1),len(c2)))]) + c[0] = c[0] if c[0] < 255 else 255 + c[1] = c[1] if c[1] < 255 else 255 + c[2] = c[2] if c[2] < 255 else 255 + return c +def combineColors(colors): + result = [0,0,0] + for c in colors: + result[0] += c[0] + result[1] += c[1] + result[2] += c[2] + return safeColor(result) def multiplyColor(color, percent): return safeColor([channel*(percent) for channel in color]) -- cgit v1.2.3