changeset 1528:a5174ca22652

WIP solver Ignore-this: aa599483d7ccb0708f6ca88dacbce016
author Drew Perttula <drewp@bigasterisk.com>
date Wed, 03 May 2017 22:17:48 +0000
parents 951fc2051045
children abe692d0a811
files light9/paint/solve.py light9/paint/solve_test.py
diffstat 2 files changed, 103 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/light9/paint/solve.py	Mon May 01 03:47:39 2017 +0000
+++ b/light9/paint/solve.py	Wed May 03 22:17:48 2017 +0000
@@ -1,9 +1,9 @@
 from __future__ import division
-from light9.namespaces import RDF, L9
+from light9.namespaces import RDF, L9, DEV
 from PIL import Image
 import decimal
 import numpy
-import scipy.misc, scipy.ndimage
+import scipy.misc, scipy.ndimage, scipy.optimize
 import cairo
 
 # numpy images in this file are (x, y, c) layout.
@@ -25,6 +25,9 @@
     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)
@@ -49,6 +52,13 @@
     img = Image.open(path)
     img.thumbnail(thumb)
     return numpyFromPil(img)
+
+
+class Settings(object):
+    def toVector(self):
+    def fromVector(self):
+    def distanceTo(self, other):
+        
     
 class Solver(object):
     def __init__(self, graph):
@@ -138,6 +148,54 @@
                            
         return out
 
+    def solveBrute(self, painting):
+        pic0 = self.draw(painting, 100, 48).astype(numpy.float)
+
+        colorSteps = 3
+        colorStep = 1. / colorSteps
+
+        dims = [
+            (DEV['aura1'], L9['rx'], [slice(.2, .7+.1, .1)]),
+            (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 settings
+
+        
+        def drawError(x):
+            settings = settingsFromVector(x)
+            preview = self.combineImages(self.simulationLayers(settings))
+            saveNumpy('/tmp/x_%s.png' % abs(hash(tuple(settings))), preview)
+            
+            diff = preview.astype(numpy.float) - pic0
+            out = scipy.sum(abs(diff))
+            
+            #print 'measure at', x, 'drawError=', out
+            return out
+            
+        x0, fval, grid, Jout = scipy.optimize.brute(
+            drawError,
+            sum([s for dev, da, s in dims], []),
+            finish=None,
+            disp=True,
+            full_output=True)
+        if fval > 30000:
+            raise ValueError('solution has error of %s' % fval)
+        return settingsFromVector(x0)
+        
     def combineImages(self, layers):
         """make a result image from our self.samples images"""
         out = (self.fromPath.itervalues().next() * 0).astype(numpy.uint16)
@@ -156,22 +214,41 @@
         compiled = {} # dev: { attr: val }
         for row in settings:
             compiled.setdefault(row[0], {})[row[1]] = row[2]
-        
+
         layers = []
 
-        for (sample, path), s in self.sampleSettings.items():
-            for d, dav in s.items():
-                if d not in compiled:
-                    continue
-                requestedAttrs = compiled[d].copy()
-                picAttrs = dav.copy()
-                del requestedAttrs[L9['color']]
-                del picAttrs[L9['color']]
-                if requestedAttrs == picAttrs:
-                    requestedColor = compiled[d][L9['color']]
-                    picColor = dav[L9['color']]
-                    layers.append({'path': path,
-                                   'color': colorRatio(requestedColor,
-                                                       picColor)})
+        for dev, davs in compiled.items():
+            candidatePics = [] # (distance, path, picColor)
+            
+            for (sample, path), s in self.sampleSettings.items():
+                for picDev, picDavs in s.items():
+                    if picDev != dev:
+                        continue
+
+                    requestedAttrs = davs.copy()
+                    picAttrs = picDavs.copy()
+                    del requestedAttrs[L9['color']]
+                    del picAttrs[L9['color']]
+
+                    dist = attrDistance(picAttrs, requestedAttrs)
+                    candidatePics.append((dist, path, picDavs[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]
+
+            requestedColor = davs[L9['color']]
+            layers.append({'path': bestPath,
+                           'color': colorRatio(requestedColor, bestPicColor)})
         
         return layers
+
+
+def attrDistance(attrs1, attrs2):
+    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
--- a/light9/paint/solve_test.py	Mon May 01 03:47:39 2017 +0000
+++ b/light9/paint/solve_test.py	Wed May 03 22:17:48 2017 +0000
@@ -9,13 +9,14 @@
         graph = LocalSyncedGraph(files=['show/dance2017/cam/test/bg.n3'])
         self.solver = solve.Solver(graph)
         self.solver.loadSamples()
+        self.solveMethod = self.solver.solve
 
     def testBlack(self):
-        devAttrs = self.solver.solve({'strokes': []})
+        devAttrs = self.solveMethod({'strokes': []})
         self.assertEqual([], devAttrs)
 
     def testSingleLightCloseMatch(self):
-        devAttrs = self.solver.solve({'strokes': [{'pts': [[224, 141],
+        devAttrs = self.solveMethod({'strokes': [{'pts': [[224, 141],
                                                  [223, 159]],
                                          'color': '#ffffff'}]})
         self.assertItemsEqual([
@@ -23,7 +24,11 @@
             (DEV['aura1'], L9['rx'], 0.5 ),
             (DEV['aura1'], L9['ry'], 0.573),
         ], devAttrs)
-        
+
+class TestSolveBrute(TestSolve):
+    def setUp(self):
+        super(TestSolveBrute, self).setUp()
+        self.solveMethod = self.solver.solveBrute
         
 class TestSimulationLayers(unittest.TestCase):
     def setUp(self):
@@ -39,7 +44,7 @@
             (DEV['aura1'], L9['color'], u"#ffffff"),
             (DEV['aura1'], L9['rx'], 0.5 ),
             (DEV['aura1'], L9['ry'], 0.573)])
-        self.assertEqual([{'path': 'bg2-d.jpg', 'color': (1, 1, 1)}], layers)
+        self.assertEqual([{'path': 'bg2-d.jpg', 'color': (1., 1., 1.)}], layers)
 
     def testPerfect1MatchTinted(self):
         layers = self.solver.simulationLayers(settings=[