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 97 insertions and 15 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):
 
@@ -47,12 +50,19 @@ def getVal(graph, subj):
 

	
 
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
 
        self.blurredSamples = {}
 
@@ -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)
 
@@ -156,22 +214,41 @@ class Solver(object):
 
        compiled = {} # dev: { attr: val }
 
        for row in settings:
 
            compiled.setdefault(row[0], {})[row[1]] = row[2]
 
        
 
        layers = []
 

	
 
        for dev, davs in compiled.items():
 
            candidatePics = [] # (distance, path, picColor)
 
            
 
        for (sample, path), s in self.sampleSettings.items():
 
            for d, dav in s.items():
 
                if d not in compiled:
 
                for picDev, picDavs in s.items():
 
                    if picDev != dev:
 
                    continue
 
                requestedAttrs = compiled[d].copy()
 
                picAttrs = dav.copy()
 

	
 
                    requestedAttrs = davs.copy()
 
                    picAttrs = picDavs.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)})
 

	
 
                    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)