# HG changeset patch # User Drew Perttula # Date 2017-05-22 06:42:37 # Node ID 14508266a00a9ec6d1479e8dce8551ba3dea4890 # Parent 7f1852dc830436e19db7420c50e625b6fa855eda more work on solver api updates. Ignore-this: 52382237d531582cbf64e7a95acf4547 diff --git a/light9/effect/settings.py b/light9/effect/settings.py --- a/light9/effect/settings.py +++ b/light9/effect/settings.py @@ -1,14 +1,23 @@ +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() @@ -18,12 +27,12 @@ def getVal(graph, subj): 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 @@ -50,14 +59,26 @@ class _Settings(object): @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] @@ -81,7 +102,7 @@ class _Settings(object): 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: @@ -91,7 +112,7 @@ class _Settings(object): 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""" @@ -111,7 +132,12 @@ class _Settings(object): 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): @@ -123,14 +149,9 @@ class _Settings(object): {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): """ diff --git a/light9/effect/settings_test.py b/light9/effect/settings_test.py --- a/light9/effect/settings_test.py +++ b/light9/effect/settings_test.py @@ -12,7 +12,7 @@ class TestDeviceSettings(unittest.TestCa 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, [ @@ -53,17 +53,20 @@ class TestDeviceSettings(unittest.TestCa 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): @@ -75,22 +78,33 @@ class TestDeviceSettings(unittest.TestCa 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)) + diff --git a/light9/paint/solve.py b/light9/paint/solve.py --- a/light9/paint/solve.py +++ b/light9/paint/solve.py @@ -5,7 +5,7 @@ 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. @@ -27,13 +27,6 @@ def loadNumpy(path, thumb=(100, 100)): 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) @@ -66,7 +59,7 @@ class Solver(object): 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): @@ -117,7 +110,7 @@ class Solver(object): brightestSample = brightest(self.samples[sample]) if max(brightest0) < 1 / 255: - return [] + return DeviceSettings(self.graph, []) scale = brightest0 / brightestSample @@ -138,19 +131,6 @@ class Solver(object): 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) diff --git a/light9/paint/solve_test.py b/light9/paint/solve_test.py --- a/light9/paint/solve_test.py +++ b/light9/paint/solve_test.py @@ -7,7 +7,8 @@ from light9.effect.settings import Devic 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 @@ -33,7 +34,8 @@ class TestSolveBrute(TestSolve): 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() @@ -72,7 +74,8 @@ class TestSimulationLayers(unittest.Test 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):