Changeset - a5174ca22652
[Not reviewed]
default
0 2 0
Drew Perttula - 8 years ago 2017-05-03 22:17:48
drewp@bigasterisk.com
WIP solver
Ignore-this: aa599483d7ccb0708f6ca88dacbce016
2 files changed with 103 insertions and 21 deletions:
0 comments (0 inline, 0 general)
light9/paint/solve.py
Show inline comments
 
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.
 

	
 
def numpyFromCairo(surface):
 
    w, h = surface.get_width(), surface.get_height()
 
@@ -22,12 +22,15 @@ 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):
 
@@ -46,12 +49,19 @@ def getVal(graph, subj):
 
    return ret
 

	
 
def loadNumpy(path, thumb=(100, 100)):
 
    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):
 
        self.graph = graph
 
        self.samples = {} # uri: Image array
 
        self.fromPath = {} # basename: image array
 
@@ -135,12 +145,60 @@ class Solver(object):
 
                if attr == L9['color']:
 
                    val = scaledHex(val, scale)
 
                out.append((g.value(obj, L9['device']), attr, val))
 
                           
 
        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)
 
        for layer in layers:
 
            colorScaled = self.fromPath[layer['path']] * layer['color']
 
            out += colorScaled.astype(numpy.uint16)
 
@@ -153,25 +211,44 @@ class Solver(object):
 
        (device attribute values) by combining photos we have?
 
        """
 

	
 
        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
light9/paint/solve_test.py
Show inline comments
 
@@ -6,27 +6,32 @@ from light9.rdfdb.localsyncedgraph impor
 

	
 
class TestSolve(unittest.TestCase):
 
    def setUp(self):
 
        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([
 
            (DEV['aura1'], L9['color'], u"#ffffff"),
 
            (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):
 
        graph = LocalSyncedGraph(files=['show/dance2017/cam/test/bg.n3'])
 
        self.solver = solve.Solver(graph)
 
        self.solver.loadSamples()
 
@@ -36,13 +41,13 @@ class TestSimulationLayers(unittest.Test
 

	
 
    def testPerfect1Match(self):
 
        layers = self.solver.simulationLayers(settings=[
 
            (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=[
 
            (DEV['aura1'], L9['color'], u"#304050"),
 
            (DEV['aura1'], L9['rx'], 0.5 ),
 
            (DEV['aura1'], L9['ry'], 0.573)])
0 comments (0 inline, 0 general)