Changeset - 9bfd2303f011
[Not reviewed]
default
0 5 1
Drew Perttula - 8 years ago 2017-05-28 07:48:04
drewp@bigasterisk.com
fix bestMatch solve method
Ignore-this: 2e7f5af86e2e54b36dfb27a6b52e26a5
6 files changed with 47 insertions and 11 deletions:
0 comments (0 inline, 0 general)
light9/effect/settings.py
Show inline comments
 
@@ -6,13 +6,14 @@ or output attrs (dmx channel).
 
"""
 
import decimal
 
import numpy
 
from rdflib import URIRef, Literal
 
from light9.namespaces import RDF, L9, DEV
 
from light9.rdfdb.patch import Patch
 

	
 
import logging
 
log = logging.getLogger('settings')
 

	
 
def parseHex(h):
 
    if h[0] != '#': raise ValueError(h)
 
    return [int(h[i:i+2], 16) for i in 1, 3, 5]
 

	
 
def toHex(rgbFloat):
 
@@ -93,13 +94,15 @@ class _Settings(object):
 
            raise TypeError("can't compare %r to %r" % (self.__class__, other.__class__))
 
        return self._compiled == other._compiled
 

	
 
    def __ne__(self, other):
 
        return not self == other
 

	
 

	
 
    def __nonzero__(self):
 
        return bool(self._compiled)
 
        
 
    def __repr__(self):
 
        words = []
 
        def accum():
 
            for dev, av in self._compiled.iteritems():
 
                for attr, val in av.iteritems():
 
                    words.append('%s.%s=%s' % (dev.rsplit('/')[-1],
 
@@ -148,12 +151,13 @@ class _Settings(object):
 
        return self.__class__._fromCompiled(self.graph,
 
                                            {dev: self._compiled.get(dev, {})})
 
        
 
    def distanceTo(self, other):
 
        diff = numpy.array(self.toVector()) - other.toVector()
 
        d = numpy.linalg.norm(diff, ord=None)
 
        log.info('distanceTo %r - %r = %g', self, other, d)
 
        return d
 

	
 
    def statements(self, subj, ctx, settingRoot, settingsSubgraphCache):
 
        """
 
        settingRoot can be shared across images (or even wider if you want)
 
        """
light9/effect/settings_test.py
Show inline comments
 
@@ -27,12 +27,16 @@ class TestDeviceSettings(unittest.TestCa
 
        self.assertFalse(s1 != s2)
 

	
 
    def testMissingFieldsEqZero(self):
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [(L9['aura1'], L9['rx'], 0),]),
 
            DeviceSettings(self.graph, []))
 

	
 
    def testFalseIfZero(self):
 
        self.assertTrue(DeviceSettings(self.graph, [(L9['aura1'], L9['rx'], 0.1)]))
 
        self.assertFalse(DeviceSettings(self.graph, []))
 
        
 
    def testFromResource(self):
 
        ctx = L9['']
 
        self.graph.patch(Patch(addQuads=[
 
            (L9['foo'], L9['setting'], L9['foo_set0'], ctx),
 
            (L9['foo_set0'], L9['device'], L9['light1'], ctx),
light9/paint/solve.py
Show inline comments
 
from __future__ import division
 
from light9.namespaces import RDF, L9, DEV
 
from PIL import Image
 
import numpy
 
import scipy.misc, scipy.ndimage, scipy.optimize
 
import cairo
 
import logging
 

	
 
from light9.effect.settings import DeviceSettings, parseHex, toHex
 

	
 
log = logging.getLogger('solve')
 

	
 
# numpy images in this file are (x, y, c) layout.
 

	
 
def numpyFromCairo(surface):
 
    w, h = surface.get_width(), surface.get_height()
 
    a = numpy.frombuffer(surface.get_data(), numpy.uint8)
 
    a.shape = h, w, 4
 
@@ -22,23 +25,28 @@ def numpyFromPil(img):
 
def loadNumpy(path, thumb=(100, 100)):
 
    img = Image.open(path)
 
    img.thumbnail(thumb)
 
    return numpyFromPil(img)
 

	
 
def saveNumpy(path, img):
 
    # maybe this should only run if log level is debug?
 
    scipy.misc.imsave(path, img.transpose((1, 0, 2)))
 

	
 
def scaledHex(h, scale):
 
    rgb = parseHex(h)
 
    rgb8 = (rgb * scale).astype(numpy.uint8)
 
    return '#%02x%02x%02x' % tuple(rgb8)
 
    
 
def colorRatio(col1, col2):
 
    rgb1 = parseHex(col1)
 
    rgb2 = parseHex(col2)
 
    return tuple([round(a / b, 3) for a, b in zip(rgb1, rgb2)])
 
    def div(x, y):
 
        if y == 0:
 
            return 0
 
        return round(x / y, 3)
 
    return tuple([div(a, b) for a, b in zip(rgb1, rgb2)])
 

	
 
def brightest(img):
 
    return numpy.amax(img, axis=(0, 1))
 

	
 

	
 
class Solver(object):
 
@@ -72,13 +80,13 @@ class Solver(object):
 
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
 
        ctx = cairo.Context(surface)
 
        ctx.rectangle(0, 0, w, h)
 
        ctx.fill()
 
        
 
        ctx.set_line_cap(cairo.LINE_CAP_ROUND)
 
        ctx.set_line_width(20) # ?
 
        ctx.set_line_width(w / 5) # ?
 
        for stroke in painting['strokes']:
 
            for pt in stroke['pts']:
 
                op = ctx.move_to if pt is stroke['pts'][0] else ctx.line_to
 
                op(pt[0] * w, pt[1] * h)
 

	
 
            r,g,b = parseHex(stroke['color'])
 
@@ -90,21 +98,22 @@ class Solver(object):
 

	
 

	
 
    def _imgDist(self, a, b):
 
        return numpy.sum(numpy.absolute(a - b), axis=None)
 
        
 
    def bestMatch(self, img):
 
        """the one sample that best matches this image"""
 
        results = []
 
        for uri, img2 in self.samples.iteritems():
 
            results.append((self._imgDist(img, img2), uri, img2))
 
        results.sort()
 
        print 'results:'
 
        for r in results:
 
            print r
 
        saveNumpy('/tmp/bestsamp.png', results[-1][2])
 
        return results[-1][1]
 
        log.info('results:')
 
        for d,u,i in results:
 
            log.info('%s %g', u, d)
 
        saveNumpy('/tmp/bestsamp.png', results[0][2])
 
        return results[0][1]
 
        
 
    def solve(self, painting):
 
        """
 
        given strokes of colors on a photo of the stage, figure out the
 
        best light DeviceSettings to match the image
 
        """
 
@@ -189,17 +198,22 @@ class Solver(object):
 
        layers = []
 

	
 
        for dev, devSettings in settings.byDevice():
 
            requestedColor = devSettings.getValue(dev, L9['color'])
 
            candidatePics = [] # (distance, path, picColor)
 
            for (sample, path), s in self.sampleSettings.items():
 
                dist = devSettings.distanceTo(s.ofDevice(dev))
 
                otherDevSettings = s.ofDevice(dev)
 
                if not otherDevSettings:
 
                    continue
 
                dist = devSettings.distanceTo(otherDevSettings)
 
                log.info('  candidate pic %s %s dist=%s', sample, path, dist)
 
                candidatePics.append((dist, path, s.getValue(dev, L9['color'])))
 
            candidatePics.sort()
 
            # we could even blend multiple top candidates, or omit all
 
            # of them if they're too far
 
            bestDist, bestPath, bestPicColor = candidatePics[0]
 

	
 
            log.info('  device best d=%g path=%s color=%s', bestDist, bestPath, bestPicColor)
 
            
 
            layers.append({'path': bestPath,
 
                           'color': colorRatio(requestedColor, bestPicColor)})
 
        
 
        return layers
light9/paint/solve_test.py
Show inline comments
 
@@ -84,6 +84,19 @@ class TestCombineImages(unittest.TestCas
 
            {'path': 'bg2-a.jpg', 'color': (.888, 0, .3)},
 
        ])
 
        solve.saveNumpy('/tmp/t.png', out)
 
        golden = solve.loadNumpy('show/dance2017/cam/test/layers_out1.png')
 
        numpy.testing.assert_array_equal(golden, out)
 

	
 
class TestBestMatch(unittest.TestCase):
 
    def setUp(self):
 
        graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
 
                                        'show/dance2017/cam/test/bg.n3'])
 
        self.solver = solve.Solver(graph)
 
        self.solver.loadSamples()
 
        
 
    def testRightSide(self):
 
        drawingOnRight = {"strokes":[{"pts":[[0.875,0.64],[0.854,0.644]],
 
                                      "color":"#aaaaaa"}]}
 
        drawImg = self.solver.draw(drawingOnRight)
 
        match = self.solver.bestMatch(drawImg)
 
        self.assertEqual(L9['sample5'], match)
show/dance2017/cam/test/lightConfig.n3
Show inline comments
 
new file 100644
show/dance2017/deviceClass.n3
Show inline comments
 
@@ -15,12 +15,13 @@
 
  rdfs:comment "0=none, 1=fastest" .
 
:goboSpeed          a :DeviceAttr; :dataType :scalar ;
 
  rdfs:comment "0=stopped, 1=rotate the fastest".
 
:quantumGoboChoice  a :DeviceAttr; :dataType :choice;
 
  :choice :open, :spider, :windmill, :limbo, :brush, :whirlpool, :stars .
 

	
 

	
 
:SimpleDimmer a :DeviceClass;
 
  :deviceAttr :brightness;
 
  :attr
 
    [ :outputAttr :level; :dmxOffset 0 ] .
 

	
 
:ChauvetColorStrip a :DeviceClass;
0 comments (0 inline, 0 general)