Changeset - a261a4bc97a0
[Not reviewed]
default
0 4 0
Drew Perttula - 8 years ago 2017-05-29 06:09:50
drewp@bigasterisk.com
straighten out LightSample schema
Ignore-this: 6ed550d64787303a9e8960cf8103f34c
4 files changed with 27 insertions and 18 deletions:
0 comments (0 inline, 0 general)
light9/paint/capture.py
Show inline comments
 
import os
 
from rdflib import URIRef
 
from light9 import showconfig
 
from light9.rdfdb.patch import Patch
 
from light9.namespaces import L9
 
from light9.namespaces import L9, RDF
 
from light9.paint.solve import loadNumpy
 

	
 
def writeCaptureDescription(graph, ctx, uri, dev, relOutPath, settingsSubgraphCache, settings):
 
    graph.patch(Patch(addQuads=settings.statements(
 
        uri, ctx=ctx,
 
        settingRoot=URIRef('/'.join([showconfig.showUri(), 'capture', dev.rsplit('/')[1]])),
 
        settingsSubgraphCache=settingsSubgraphCache)))
 
    graph.patch(Patch(addQuads=[
 
        (dev, L9['capture'], uri, ctx),
 
        (uri, RDF.type, L9['LightSample'], ctx),
 
        (uri, L9['imagePath'], URIRef('/'.join([showconfig.showUri(), relOutPath])), ctx),
 
        ]))
 
    
 
class CaptureLoader(object):
 
    def __init__(self, graph):
 
        self.graph = graph
 
        
 
    def loadImage(self, pic, thumb=(100, 100)):
 
        ip = self.graph.value(pic, L9['imagePath'])
 
        if not ip.startswith(showconfig.show()):
 
            raise ValueError(repr(ip))
 
        diskPath = os.path.join(showconfig.root(), ip[len(self.show):])
 
        return loadNumpy(diskPath, thumb)
 
        
 
    def devices(self):
 
        """devices for which we have any captured data"""
 

	
 
    def capturedSettings(self, device):
 
        """list of (pic, settings) we know for this device"""
 
        
light9/paint/solve.py
Show inline comments
 
@@ -8,111 +8,110 @@ import logging
 

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

	
 
log = logging.getLogger('solve')
 

	
 
# 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)
 
    a.shape = h, w, 4
 
    a = a.transpose((1, 0, 2))
 
    return a[:w,:h,:3]
 

	
 
def numpyFromPil(img):
 
    return scipy.misc.fromimage(img, mode='RGB').transpose((1, 0, 2))
 

	
 
def loadNumpy(path, thumb=(100, 100)):
 
    img = Image.open(path)
 
    img.thumbnail(thumb)
 
    return numpyFromPil(img)
 

	
 
def saveNumpy(path, img):
 
    # maybe this should only run if log level is debug?
 
    scipy.misc.imsave(path, img.transpose((1, 0, 2)))
 

	
 
def scaledHex(h, scale):
 
    rgb = parseHex(h)
 
    rgb8 = (rgb * scale).astype(numpy.uint8)
 
    return '#%02x%02x%02x' % tuple(rgb8)
 
    
 
def colorRatio(col1, col2):
 
    rgb1 = parseHex(col1)
 
    rgb2 = parseHex(col2)
 
    def div(x, y):
 
        if y == 0:
 
            return 0
 
        return round(x / y, 3)
 
    return tuple([div(a, b) for a, b in zip(rgb1, rgb2)])
 

	
 
def brightest(img):
 
    return numpy.amax(img, axis=(0, 1))
 

	
 

	
 
class Solver(object):
 
    def __init__(self, graph):
 
        self.graph = graph
 
        self.samples = {} # uri: Image array
 
        self.fromPath = {} # basename: image array
 
        self.fromPath = {} # imagePath: image array
 
        self.blurredSamples = {}
 
        self.sampleSettings = {} # (uri, path): DeviceSettings
 
        
 
    def loadSamples(self):
 
        """learn what lights do from images"""
 

	
 
        with self.graph.currentState() as g:
 
            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)
 
                pathUri = g.value(samp, L9['imagePath'])
 
                self.samples[samp] = self.fromPath[pathUri] = loadNumpy(pathUri.replace(L9[''], ''))
 
                self.blurredSamples[samp] = self._blur(self.samples[samp])
 
                
 
                key = (samp, g.value(samp, L9['path']).toPython().encode('utf8'))
 
                key = (samp, pathUri)
 
                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):
 
        return self._draw(painting, 100, 48)
 
        
 
    def _draw(self, painting, w, h):
 
        surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, w, h)
 
        ctx = cairo.Context(surface)
 
        ctx.rectangle(0, 0, w, h)
 
        ctx.fill()
 
        
 
        ctx.set_line_cap(cairo.LINE_CAP_ROUND)
 
        ctx.set_line_width(w / 5) # ?
 
        for stroke in painting['strokes']:
 
            for pt in stroke['pts']:
 
                op = ctx.move_to if pt is stroke['pts'][0] else ctx.line_to
 
                op(pt[0] * w, pt[1] * h)
 

	
 
            r,g,b = parseHex(stroke['color'])
 
            ctx.set_source_rgb(r / 255, g / 255, b / 255)
 
            ctx.stroke()
 
        
 
        surface.write_to_png('/tmp/surf.png')
 
        return numpyFromCairo(surface)
 

	
 

	
 
    def _imgDist(self, a, b):
 
        return numpy.sum(numpy.absolute(a - b), axis=None)
 
        
 
    def bestMatch(self, img):
 
        """the one sample that best matches this image"""
 
        results = []
 
        for uri, img2 in self.samples.iteritems():
 
            results.append((self._imgDist(img, img2), uri, img2))
 
        results.sort()
 
        log.info('results:')
 
        for d,u,i in results:
 
            log.info('%s %g', u, d)
 
        saveNumpy('/tmp/bestsamp.png', results[0][2])
 
        return results[0][1]
 
        
 
    def solve(self, painting):
 
        """
 
        given strokes of colors on a photo of the stage, figure out the
 
        best light DeviceSettings to match the image
light9/paint/solve_test.py
Show inline comments
 
import unittest
 
import numpy.testing
 
import solve
 
from rdflib import Namespace
 
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/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': []})
 
        self.assertEqual(DeviceSettings(self.graph, []), devAttrs)
 

	
 
    def testSingleLightCloseMatch(self):
 
        devAttrs = self.solveMethod({'strokes': [{'pts': [[224, 141],
 
                                                 [223, 159]],
 
                                         'color': '#ffffff'}]})
 
        self.assertEqual(DeviceSettings(self.graph, [
 
            (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
 
        
 
CAM_TEST = Namespace('http://light9.bigasterisk.com/show/dance2017/cam/test/')
 
        
 
class TestSimulationLayers(unittest.TestCase):
 
    def setUp(self):
 
        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(
 
            [],
 
            self.solver.simulationLayers(settings=DeviceSettings(self.graph, [])))
 

	
 
    def testPerfect1Match(self):
 
        layers = self.solver.simulationLayers(settings=DeviceSettings(self.graph, [
 
            (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': CAM_TEST['bg2-d.jpg'], 'color': (1., 1., 1.)}], layers)
 

	
 
    def testPerfect1MatchTinted(self):
 
        layers = self.solver.simulationLayers(settings=DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['color'], u"#304050"),
 
            (DEV['aura1'], L9['rx'], 0.5 ),
 
            (DEV['aura1'], L9['ry'], 0.573)]))
 
        self.assertEqual([{'path': 'bg2-d.jpg', 'color': (.188, .251, .314)}], layers)
 
        self.assertEqual([{'path': CAM_TEST['bg2-d.jpg'], 'color': (.188, .251, .314)}], layers)
 
        
 
    def testPerfect2Matches(self):
 
        layers = self.solver.simulationLayers(settings=DeviceSettings(self.graph, [
 
            (DEV['aura1'], L9['color'], u"#ffffff"),
 
            (DEV['aura1'], L9['rx'], 0.5 ),
 
            (DEV['aura1'], L9['ry'], 0.573),
 
            (DEV['aura2'], L9['color'], u"#ffffff"),
 
            (DEV['aura2'], L9['rx'], 0.7 ),
 
            (DEV['aura2'], L9['ry'], 0.573),
 
        ]))
 
        self.assertItemsEqual([
 
            {'path': 'bg2-d.jpg', 'color': (1, 1, 1)},
 
            {'path': 'bg2-f.jpg', 'color': (1, 1, 1)},
 
            {'path': CAM_TEST['bg2-d.jpg'], 'color': (1, 1, 1)},
 
            {'path': CAM_TEST['bg2-f.jpg'], 'color': (1, 1, 1)},
 
                      ], layers)
 

	
 
class TestCombineImages(unittest.TestCase):
 
    def setUp(self):
 
        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)},
 
            {'path': CAM_TEST['bg2-d.jpg'], 'color': (.2, .2, .3)},
 
            {'path': CAM_TEST['bg2-a.jpg'], 'color': (.888, 0, .3)},
 
        ])
 
        solve.saveNumpy('/tmp/t.png', out)
 
        golden = solve.loadNumpy('show/dance2017/cam/test/layers_out1.png')
 
        numpy.testing.assert_array_equal(golden, out)
 

	
 
class TestBestMatch(unittest.TestCase):
 
    def setUp(self):
 
        graph = LocalSyncedGraph(files=['show/dance2017/cam/test/lightConfig.n3',
 
                                        'show/dance2017/cam/test/bg.n3'])
 
        self.solver = solve.Solver(graph)
 
        self.solver.loadSamples()
 
        
 
    def testRightSide(self):
 
        drawingOnRight = {"strokes":[{"pts":[[0.875,0.64],[0.854,0.644]],
 
                                      "color":"#aaaaaa"}]}
 
        drawImg = self.solver.draw(drawingOnRight)
 
        match = self.solver.bestMatch(drawImg)
 
        self.assertEqual(L9['sample5'], match)
show/dance2017/cam/test/bg.n3
Show inline comments
 
@prefix : <http://light9.bigasterisk.com/> .
 
@prefix dev: <http://light9.bigasterisk.com/device/> .
 
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
 
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
 
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
 
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
 

	
 
@prefix set: <http://light9.bigasterisk.com/setting/> .
 

	
 

	
 
:sample0 a :LightSample; :path "bg2-a.jpg"; :setting 
 
:sample0 a :LightSample; :imagePath <http://light9.bigasterisk.com/show/dance2017/cam/test/bg2-a.jpg>;
 
 :setting 
 
[ :device dev:aura1; :deviceAttr :color;  :scaledValue "#ffffff" ],
 
[ :device dev:aura1; :deviceAttr :rx;     :value 0.2 ],
 
[ :device dev:aura1; :deviceAttr :ry;     :value 0.573 ] .
 

	
 
:sample1 a :LightSample; :path "bg2-b.jpg"; :setting 
 
:sample1 a :LightSample; :imagePath <http://light9.bigasterisk.com/show/dance2017/cam/test/bg2-b.jpg>;
 
 :setting 
 
[ :device dev:aura1; :deviceAttr :color;  :scaledValue "#ffffff" ],
 
[ :device dev:aura1; :deviceAttr :rx;     :value 0.3 ],
 
[ :device dev:aura1; :deviceAttr :ry;     :value 0.573 ] .
 

	
 
:sample2 a :LightSample; :path "bg2-c.jpg"; :setting 
 
:sample2 a :LightSample; :imagePath <http://light9.bigasterisk.com/show/dance2017/cam/test/bg2-c.jpg>;
 
 :setting 
 
[ :device dev:aura1; :deviceAttr :color;  :scaledValue "#ffffff" ],
 
[ :device dev:aura1; :deviceAttr :rx;     :value 0.4 ],
 
[ :device dev:aura1; :deviceAttr :ry;     :value 0.573 ] .
 

	
 
:sample3 a :LightSample; :path "bg2-d.jpg"; :setting 
 
:sample3 a :LightSample; :imagePath <http://light9.bigasterisk.com/show/dance2017/cam/test/bg2-d.jpg>;
 
 :setting 
 
[ :device dev:aura1; :deviceAttr :color;  :scaledValue "#ffffff" ],
 
[ :device dev:aura1; :deviceAttr :rx;     :value 0.5 ],
 
[ :device dev:aura1; :deviceAttr :ry;     :value 0.573 ] .
 

	
 
:sample4 a :LightSample; :path "bg2-e.jpg"; :setting 
 
:sample4 a :LightSample; :imagePath <http://light9.bigasterisk.com/show/dance2017/cam/test/bg2-e.jpg>;
 
 :setting 
 
[ :device dev:aura1; :deviceAttr :color;  :scaledValue "#ffffff" ],
 
[ :device dev:aura1; :deviceAttr :rx;     :value 0.6 ],
 
[ :device dev:aura1; :deviceAttr :ry;     :value 0.573 ] .
 

	
 
# note: different device
 
:sample5 a :LightSample; :path "bg2-f.jpg"; :setting 
 
:sample5 a :LightSample; :imagePath <http://light9.bigasterisk.com/show/dance2017/cam/test/bg2-f.jpg>;
 
 :setting 
 
[ :device dev:aura2; :deviceAttr :color;  :scaledValue "#ffffff" ],
 
[ :device dev:aura2; :deviceAttr :rx;     :value 0.7 ],
 
[ :device dev:aura2; :deviceAttr :ry;     :value 0.573 ] .
 

	
0 comments (0 inline, 0 general)