Changeset - 14508266a00a
[Not reviewed]
default
0 4 0
Drew Perttula - 8 years ago 2017-05-22 06:42:37
drewp@bigasterisk.com
more work on solver api updates.
Ignore-this: 52382237d531582cbf64e7a95acf4547
4 files changed with 70 insertions and 52 deletions:
0 comments (0 inline, 0 general)
light9/effect/settings.py
Show inline comments
 
from __future__ import division
 
"""
 
Data structure and convertors for a table of (device,attr,value)
 
rows. These might be effect attrs ('strength'), device attrs ('rx'),
 
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
 

	
 

	
 
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):
 
    return '#%02x%02x%02x' % tuple(int(v * 255) for v in rgbFloat)
 

	
 
def getVal(graph, subj):
 
    lit = graph.value(subj, L9['value']) or graph.value(subj, L9['scaledValue'])
 
    ret = lit.toPython()
 
    if isinstance(ret, decimal.Decimal):
 
        ret = float(ret)
 
    return ret
 

	
 
class _Settings(object):
 
    """
 
    default values are 0. Internal rep must not store zeros or some
 
    default values are 0 or '#000000'. Internal rep must not store zeros or some
 
    comparisons will break.
 
    """
 
    def __init__(self, graph, settingsList):
 
        self.graph = graph # for looking up all possible attrs
 
        self._compiled = {} # dev: { attr: val }
 
        self._compiled = {} # dev: { attr: val }; val is number or colorhex
 
        for row in settingsList:
 
            self._compiled.setdefault(row[0], {})[row[1]] = row[2]
 
        # self._compiled may not be final yet- see _fromCompiled
 
        self._delZeros()
 
        
 
    @classmethod
 
@@ -47,20 +56,32 @@ class _Settings(object):
 
                settingsList.append((d, da, v))
 
        return cls(graph, settingsList)
 

	
 
    @classmethod
 
    def fromVector(cls, graph, vector):
 
        compiled = {}
 
        for (d, a), v in zip(cls(graph, [])._vectorKeys(), vector):
 
        i = 0
 
        for (d, a) in cls(graph, [])._vectorKeys():
 
            if a == L9['color']:
 
                v = toHex(vector[i:i+3])
 
                i += 3
 
            else:
 
                v = vector[i]
 
                i += 1
 
            compiled.setdefault(d, {})[a] = v
 
        return cls._fromCompiled(graph, compiled)
 

	
 
    def _zeroForAttr(self, attr):
 
        if attr == L9['color']:
 
            return '#000000'
 
        return 0
 

	
 
    def _delZeros(self):
 
        for dev, av in self._compiled.items():
 
            for attr, val in av.items():
 
                if val == 0:
 
                if val == self._zeroForAttr(attr):
 
                    del av[attr]
 
            if not av:
 
                del self._compiled[dev]
 
        
 
    def __hash__(self):
 
        itemed = tuple([(d, tuple([(a, v) for a, v in sorted(av.items())]))
 
@@ -78,23 +99,23 @@ class _Settings(object):
 

	
 
    def __repr__(self):
 
        words = []
 
        def accum():
 
            for dev, av in self._compiled.iteritems():
 
                for attr, val in av.iteritems():
 
                    words.append('%s.%s=%g' % (dev.rsplit('/')[-1],
 
                    words.append('%s.%s=%s' % (dev.rsplit('/')[-1],
 
                                               attr.rsplit('/')[-1],
 
                                               val))
 
                    if len(words) > 5:
 
                        words.append('...')
 
                        return
 
        accum()
 
        return '<%s %s>' % (self.__class__.__name__, ' '.join(words))
 
        
 
    def getValue(self, dev, attr):
 
        return self._compiled.get(dev, {}).get(attr, 0)
 
        return self._compiled.get(dev, {}).get(attr, self._zeroForAttr(attr))
 

	
 
    def _vectorKeys(self):
 
        """stable order of all the dev,attr pairs for this type of settings"""
 
        raise NotImplementedError
 

	
 
    def asList(self):
 
@@ -108,32 +129,32 @@ class _Settings(object):
 
    def devices(self):
 
        return self._compiled.keys()
 
        
 
    def toVector(self):
 
        out = []
 
        for dev, attr in self._vectorKeys():
 
            out.append(self._compiled.get(dev, {}).get(attr, 0))
 
            # color components may need to get spread out
 
            v = self.getValue(dev, attr)
 
            if attr == L9['color']:
 
                out.extend([x / 255 for x in parseHex(v)])
 
            else:
 
                out.append(v)
 
        return out
 

	
 
    def byDevice(self):
 
        for dev, av in self._compiled.iteritems():
 
            yield dev, self.__class__._fromCompiled(self.graph, {dev: av})
 

	
 
    def ofDevice(self, dev):
 
        return self.__class__._fromCompiled(self.graph,
 
                                            {dev: self._compiled.get(dev, {})})
 
        
 
    def distanceTo(self, other):
 
        raise NotImplementedError
 
        dist = 0
 
        for key in set(attrs1).union(set(attrs2)):
 
            if key not in attrs1 or key not in attrs2:
 
                dist += 999
 
            else:
 
                dist += abs(attrs1[key] - attrs2[key])
 
        return dist
 
        diff = numpy.array(self.toVector()) - other.toVector()
 
        d = numpy.linalg.norm(diff, ord=None)
 
        return d
 

	
 
    def statements(self, subj, ctx, settingRoot, settingsSubgraphCache):
 
        """
 
        settingRoot can be shared across images (or even wider if you want)
 
        """
 
        # ported from live.coffee
light9/effect/settings_test.py
Show inline comments
 
@@ -9,13 +9,13 @@ class TestDeviceSettings(unittest.TestCa
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
 
                                             'show/dance2017/cam/test/bg.n3'])
 

	
 
    def testToVectorZero(self):
 
        ds = DeviceSettings(self.graph, [])
 
        self.assertEqual([0] * 20, ds.toVector())
 
        self.assertEqual([0] * 30, ds.toVector())
 

	
 
    def testEq(self):
 
        s1 = DeviceSettings(self.graph, [
 
            (L9['light1'], L9['attr1'], 0.5),
 
            (L9['light1'], L9['attr2'], 0.3),
 
        ])
 
@@ -50,47 +50,61 @@ class TestDeviceSettings(unittest.TestCa
 
            (L9['light1'], L9['speed'], 0.2),
 
        ]), s)
 

	
 
    def testToVector(self):
 
        v = DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['rx'], 0.5),
 
            (DEV['aura1'], L9['color'], '#00ff00'),
 
        ]).toVector()
 
        self.assertEqual(
 
            [0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], v)
 
            [0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 
            v)
 
        
 
    def testFromVector(self):
 
        s = DeviceSettings.fromVector(
 
            self.graph,
 
            [0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
 
            [0, 1, 0, 0.5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
 
        
 
        self.assertEqual(DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['rx'], 0.5),
 
            (DEV['aura1'], L9['color'], '#00ff00'),
 
        ]), s)                            
 

	
 
    def testAsList(self):
 
        sets = [
 
            (L9['light1'], L9['attr2'], 0.3),
 
            (L9['light1'], L9['attr1'], 0.5),
 
        ]
 
        self.assertItemsEqual(sets, DeviceSettings(self.graph, sets).asList())
 

	
 
    def testDevices(self):
 
        s = DeviceSettings(self.graph, [
 
            (L9['aura1'], L9['rx'], 0),
 
            (L9['aura2'], L9['rx'], 0.1),
 
            (DEV['aura1'], L9['rx'], 0),
 
            (DEV['aura2'], L9['rx'], 0.1),
 
            ])
 
        # aura1 is all defaults (zeros), so it doesn't get listed
 
        self.assertItemsEqual([L9['aura2']], s.devices())
 
        self.assertItemsEqual([DEV['aura2']], s.devices())
 

	
 
    def testAddStatements(self):
 
        s = DeviceSettings(self.graph, [
 
            (L9['aura2'], L9['rx'], 0.1),
 
            (DEV['aura2'], L9['rx'], 0.1),
 
            ])
 
        stmts = s.statements(L9['foo'], L9['ctx1'], L9['s_'], set())
 
        self.maxDiff=None
 
        self.assertItemsEqual([
 
            (L9['foo'], L9['setting'], L9['s_set8011962'], L9['ctx1']),
 
            (L9['s_set8011962'], L9['device'], L9['aura2'], L9['ctx1']),
 
            (L9['s_set8011962'], L9['deviceAttr'], L9['rx'], L9['ctx1']),
 
            (L9['s_set8011962'], L9['value'], Literal(0.1), L9['ctx1']),
 
            (L9['foo'], L9['setting'], L9['s_set4350023'], L9['ctx1']),
 
            (L9['s_set4350023'], L9['device'], DEV['aura2'], L9['ctx1']),
 
            (L9['s_set4350023'], L9['deviceAttr'], L9['rx'], L9['ctx1']),
 
            (L9['s_set4350023'], L9['value'], Literal(0.1), L9['ctx1']),
 
        ], stmts)
 
        
 
    def testDistanceTo(self):
 
        s1 = DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['rx'], 0.1),
 
            (DEV['aura1'], L9['ry'], 0.6),
 
        ])
 
        s2 = DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['rx'], 0.3),
 
            (DEV['aura1'], L9['ry'], 0.3),
 
        ])
 
        self.assertEqual(0.36055512754639896, s1.distanceTo(s2))
 
        
light9/paint/solve.py
Show inline comments
 
@@ -2,13 +2,13 @@ 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
 

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

	
 
# 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)
 
@@ -24,19 +24,12 @@ def loadNumpy(path, thumb=(100, 100)):
 
    img.thumbnail(thumb)
 
    return numpyFromPil(img)
 

	
 
def saveNumpy(path, img):
 
    scipy.misc.imsave(path, img.transpose((1, 0, 2)))
 

	
 
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):
 
    return '#%02x%02x%02x' % tuple(int(v * 255) for v in rgbFloat)
 

	
 
def scaledHex(h, scale):
 
    rgb = parseHex(h)
 
    rgb8 = (rgb * scale).astype(numpy.uint8)
 
    return '#%02x%02x%02x' % tuple(rgb8)
 
    
 
def colorRatio(col1, col2):
 
@@ -63,13 +56,13 @@ class Solver(object):
 
            for samp in g.subjects(RDF.type, L9['LightSample']):
 
                base = g.value(samp, L9['path']).toPython()
 
                path = 'show/dance2017/cam/test/%s' % base
 
                self.samples[samp] = self.fromPath[base] = loadNumpy(path)
 
                self.blurredSamples[samp] = self._blur(self.samples[samp])
 
                
 
                key = (samp, g.value(samp, L9['path']).toPython())
 
                key = (samp, g.value(samp, L9['path']).toPython().encode('utf8'))
 
                self.sampleSettings[key] = DeviceSettings.fromResource(self.graph, samp)
 

	
 
    def _blur(self, img):
 
        return scipy.ndimage.gaussian_filter(img, 10, 0, mode='nearest')
 
                
 
    def draw(self, painting, w, h):
 
@@ -114,13 +107,13 @@ class Solver(object):
 

	
 
        # this is wrong; some wrong-alignments ought to be dimmer than full
 
        brightest0 = brightest(pic0)
 
        brightestSample = brightest(self.samples[sample])
 
        
 
        if max(brightest0) < 1 / 255:
 
            return []
 
            return DeviceSettings(self.graph, [])
 

	
 
        scale = brightest0 / brightestSample
 

	
 
        s = DeviceSettings.fromResource(self.graph, sample)
 
        # missing color scale, but it was wrong to operate on all devs at once
 
        return s
 
@@ -136,25 +129,12 @@ class Solver(object):
 
            (DEV['aura1'], L9['ry'], [slice(.573, .573+1, 1)]),
 
            (DEV['aura1'], L9['color'], [slice(0, 1 + colorStep, colorStep),
 
                                         slice(0, 1 + colorStep, colorStep),
 
                                         slice(0, 1 + colorStep, colorStep)]),
 
        ]
 

	
 
        def settingsFromVector(x):
 
            settings = []
 

	
 
            xLeft = x.tolist()
 
            for dev, attr, _ in dims:
 
                if attr == L9['color']:
 
                    rgb = (xLeft.pop(), xLeft.pop(), xLeft.pop())
 
                    settings.append((dev, attr, toHex(rgb)))
 
                else:
 
                    settings.append((dev, attr, xLeft.pop()))
 
            return DeviceSettings(self.graph, settings)
 

	
 
        
 
        def drawError(x):
 
            settings = DeviceSettings.fromVector(self.graph, x)
 
            preview = self.combineImages(self.simulationLayers(settings))
 
            saveNumpy('/tmp/x_%s.png' % abs(hash(settings)), preview)
 
            
 
            diff = preview.astype(numpy.float) - pic0
light9/paint/solve_test.py
Show inline comments
 
@@ -4,13 +4,14 @@ import solve
 
from light9.namespaces import RDF, L9, DEV
 
from light9.rdfdb.localsyncedgraph import LocalSyncedGraph
 
from light9.effect.settings import DeviceSettings
 

	
 
class TestSolve(unittest.TestCase):
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(files=['show/dance2017/cam/test/bg.n3'])
 
        self.graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
 
                                             'show/dance2017/cam/test/bg.n3'])
 
        self.solver = solve.Solver(self.graph)
 
        self.solver.loadSamples()
 
        self.solveMethod = self.solver.solve
 

	
 
    def testBlack(self):
 
        devAttrs = self.solveMethod({'strokes': []})
 
@@ -30,13 +31,14 @@ class TestSolveBrute(TestSolve):
 
    def setUp(self):
 
        super(TestSolveBrute, self).setUp()
 
        self.solveMethod = self.solver.solveBrute
 
        
 
class TestSimulationLayers(unittest.TestCase):
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(files=['show/dance2017/cam/test/bg.n3'])
 
        self.graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
 
                                             'show/dance2017/cam/test/bg.n3'])
 
        self.solver = solve.Solver(self.graph)
 
        self.solver.loadSamples()
 
        
 
    def testBlack(self):
 
        self.assertEqual(
 
            [],
 
@@ -69,13 +71,14 @@ class TestSimulationLayers(unittest.Test
 
            {'path': 'bg2-d.jpg', 'color': (1, 1, 1)},
 
            {'path': 'bg2-f.jpg', 'color': (1, 1, 1)},
 
                      ], layers)
 

	
 
class TestCombineImages(unittest.TestCase):
 
    def setUp(self):
 
        graph = LocalSyncedGraph(files=['show/dance2017/cam/test/bg.n3'])
 
        graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
 
                                        'show/dance2017/cam/test/bg.n3'])
 
        self.solver = solve.Solver(graph)
 
        self.solver.loadSamples()
 
    def test(self):
 
        out = self.solver.combineImages(layers=[
 
            {'path': 'bg2-d.jpg', 'color': (.2, .2, .3)},
 
            {'path': 'bg2-a.jpg', 'color': (.888, 0, .3)},
0 comments (0 inline, 0 general)