Changeset - 5d2dcae1a7c6
[Not reviewed]
default
0 8 0
drewp@bigasterisk.com - 8 years ago 2017-06-10 02:02:33
drewp@bigasterisk.com
paint can now do best matches on multiple lights at once
Ignore-this: 3b8a333264edc9532f8c4ff48d92ac17
8 files changed with 206 insertions and 20 deletions:
0 comments (0 inline, 0 general)
bin/paintserver
Show inline comments
 
@@ -8,66 +8,89 @@ from light9.greplin_cyclone import Stats
 
from light9.rdfdb.syncedgraph import SyncedGraph
 
from light9 import networking, showconfig
 
from greplin import scales
 
import optparse, sys, logging
 
import cyclone.web
 
from rdflib import URIRef
 
from light9.rdfdb import clientsession
 
import light9.paint.solve
 
from lib.cycloneerr import PrettyErrorHandler
 
from light9.namespaces import RDF, L9, DEV
 

	
 

	
 

	
 

	
 
class Solve(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def post(self):
 
        painting = json.loads(self.request.body)
 
        with self.settings.stats.solve.time():
 
            img = self.settings.solver.draw(painting)
 
            sample, sampleDist = self.settings.solver.bestMatch(img)
 
            sample, sampleDist = self.settings.solver.bestMatch(img, device=DEV['aura2'])
 
            with self.settings.graph.currentState() as g:
 
                bestPath = g.value(sample, L9['imagePath']).replace(L9[''], '')
 
            #out = solver.solve(painting)
 
            #layers = solver.simulationLayers(out)
 
            
 
        self.write(json.dumps({
 
            'bestMatch': {'uri': sample, 'path': bestPath, 'dist': sampleDist},
 
        #    'layers': layers,
 
        #    'out': out,
 
        }))
 

	
 
    def reloadSolver(self):
 
        reload(light9.paint.solve)
 
        self.settings.solver = light9.paint.solve.Solver(self.settings.graph)
 
        self.settings.solver.loadSamples()
 

	
 
class BestMatches(PrettyErrorHandler, cyclone.web.RequestHandler):
 
    def post(self):
 
        body = json.loads(self.request.body)
 
        painting = body['painting']
 
        devs = [URIRef(d) for d in body['devices']]
 
        with self.settings.stats.solve.time():
 
            img = self.settings.solver.draw(painting)
 
            outSettings = self.settings.solver.bestMatches(img, devs)
 
            self.write(json.dumps({
 
                'settings': outSettings.asList()
 
                }))
 
        
 
class App(object):
 
    def __init__(self, show, session):
 
        self.show = show
 
        self.session = session
 

	
 
        self.graph = SyncedGraph(networking.rdfdb.url, "paintServer")
 
        self.graph.initiallySynced.addCallback(self.launch)
 
        self.graph.initiallySynced.addCallback(self.launch).addErrback(log.error)
 
        
 
        self.stats = scales.collection('/', scales.PmfStat('solve'),
 
                                       )
 
       
 
    def launch(self, *args):
 

	
 
        self.solver = light9.paint.solve.Solver(self.graph, sessions=[L9['show/dance2017/capture/moving1/cap961804']])
 
        self.solver = light9.paint.solve.Solver(self.graph, sessions=[
 
            L9['show/dance2017/capture/aura1/cap1876596'],
 
            L9['show/dance2017/capture/aura2/cap1876792'],
 
            L9['show/dance2017/capture/aura3/cap1877057'],
 
            L9['show/dance2017/capture/aura4/cap1877241'],
 
            L9['show/dance2017/capture/aura5/cap1877406'],
 
            L9['show/dance2017/capture/q1/cap1874255'],
 
            L9['show/dance2017/capture/q2/cap1873665'],
 
            L9['show/dance2017/capture/q3/cap1876223'],
 
        ])
 
        self.solver.loadSamples()
 
        
 
        self.cycloneApp = cyclone.web.Application(handlers=[
 
            (r'/stats', StatsForCyclone),
 
            (r'/solve', Solve),
 
            (r'/bestMatches', BestMatches),
 
        ],
 
                                                  debug=True,
 
                                                  graph=self.graph,
 
                                                  solver=self.solver,
 
                                                  stats=self.stats)
 
        reactor.listenTCP(networking.paintServer.port, self.cycloneApp)
 
        log.info("listening on %s" % networking.paintServer.port)
 

	
 

	
 
if __name__ == "__main__":
 
    parser = optparse.OptionParser()
 
    parser.add_option('--show',
light9/effect/settings.py
Show inline comments
 
@@ -7,26 +7,29 @@ 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
 
import logging
 
log = logging.getLogger('settings')
 

	
 
def parseHex(h):
 
    if h[0] != '#': raise ValueError(h)
 
    return [int(h[i:i+2], 16) for i in 1, 3, 5]
 

	
 
def parseHexNorm(h):
 
    return [x / 255 for x in parseHex(h)]
 
    
 
def toHex(rgbFloat):
 
    return '#%02x%02x%02x' % tuple(int(v * 255) for v in rgbFloat)
 
    return '#%02x%02x%02x' % tuple(max(0, min(255, 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()
 
    if isinstance(ret, decimal.Decimal):
 
        ret = float(ret)
 
    return ret
 

	
 
class _Settings(object):
 
    """
 
    default values are 0 or '#000000'. Internal rep must not store zeros or some
 
    comparisons will break.
 
@@ -74,24 +77,45 @@ class _Settings(object):
 
    @classmethod
 
    def fromList(cls, graph, others):
 
        out = cls(graph, [])
 
        for s in others:
 
            if not isinstance(s, cls):
 
                raise TypeError(s)
 
            for row in s.asList(): # could work straight from s._compiled
 
                if row[0] is None:
 
                    raise TypeError('bad row %r' % (row,))
 
                out._compiled.setdefault(row[0], {})[row[1]] = row[2]
 
        out._delZeros()
 
        return out
 

	
 
    @classmethod
 
    def fromBlend(cls, graph, others):
 
        """others is a list of (weight, Settings) pairs"""
 
        out = cls(graph, [])
 
        for weight, s in others:
 
            if not isinstance(s, cls):
 
                raise TypeError(s)
 
            for row in s.asList(): # could work straight from s._compiled
 
                if row[0] is None:
 
                    raise TypeError('bad row %r' % (row,))
 
                dd = out._compiled.setdefault(row[0], {})
 

	
 
                if isinstance(row[2], basestring):
 
                    prev = parseHexNorm(dd.get(row[1], '#000000'))
 
                    newVal = toHex(prev + weight * numpy.array(parseHexNorm(row[2])))
 
                else:
 
                    newVal = dd.get(row[1], 0) + weight * row[2]
 
                dd[row[1]] = newVal
 
        out._delZeros()
 
        return out
 
        
 
    def _zeroForAttr(self, attr):
 
        if attr == L9['color']:
 
            return '#000000'
 
        return 0.0
 

	
 
    def _delZeros(self):
 
        for dev, av in self._compiled.items():
 
            for attr, val in av.items():
 
                if val == self._zeroForAttr(attr):
 
                    del av[attr]
 
            if not av:
 
@@ -108,58 +132,58 @@ class _Settings(object):
 
        return self._compiled == other._compiled
 

	
 
    def __ne__(self, other):
 
        return not self == other
 

	
 
    def __nonzero__(self):
 
        return bool(self._compiled)
 
        
 
    def __repr__(self):
 
        words = []
 
        def accum():
 
            for dev, av in self._compiled.iteritems():
 
                for attr, val in av.iteritems():
 
                for attr, val in sorted(av.iteritems()):
 
                    words.append('%s.%s=%s' % (dev.rsplit('/')[-1],
 
                                               attr.rsplit('/')[-1],
 
                                               val))
 
                    if len(words) > 5:
 
                        words.append('...')
 
                        return
 
        accum()
 
        return '<%s %s>' % (self.__class__.__name__, ' '.join(words))
 
        
 

	
 
    def getValue(self, dev, attr):
 
        return self._compiled.get(dev, {}).get(attr, self._zeroForAttr(attr))
 

	
 
    def _vectorKeys(self, deviceAttrFilter=None):
 
        """stable order of all the dev,attr pairs for this type of settings"""
 
        raise NotImplementedError
 

	
 
    def asList(self):
 
        """old style list of (dev, attr, val) tuples"""
 
        out = []
 
        for dev, av in self._compiled.iteritems():
 
            for attr, val in av.iteritems():
 
                out.append((dev, attr, val))
 
        return out
 

	
 
    def devices(self):
 
        return self._compiled.keys()
 
        
 
    def toVector(self, deviceAttrFilter=None):
 
        out = []
 
        for dev, attr in self._vectorKeys(deviceAttrFilter):
 
            v = self.getValue(dev, attr)
 
            if attr == L9['color']:
 
                out.extend([x / 255 for x in parseHex(v)])
 
                out.extend(parseHexNorm(v))
 
            else:
 
                out.append(v)
 
        return out
 

	
 
    def byDevice(self):
 
        for dev, av in self._compiled.iteritems():
 
            yield dev, self.__class__._fromCompiled(self.graph, {dev: av})
 

	
 
    def ofDevice(self, dev):
 
        return self.__class__._fromCompiled(self.graph,
 
                                            {dev: self._compiled.get(dev, {})})
 
        
light9/effect/settings_test.py
Show inline comments
 
import unittest
 
from rdflib import Literal
 
from light9.rdfdb.patch import Patch
 
from light9.rdfdb.localsyncedgraph import LocalSyncedGraph
 
from light9.namespaces import RDF, L9, DEV
 
from light9.effect.settings import DeviceSettings
 

	
 
             
 
        
 
class TestDeviceSettings(unittest.TestCase):
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3',
 
                                             'test/cam/bg.n3'])
 

	
 
    def testToVectorZero(self):
 
        ds = DeviceSettings(self.graph, [])
 
        self.assertEqual([0] * 30, ds.toVector())
 

	
 
    def testEq(self):
 
        s1 = DeviceSettings(self.graph, [
 
            (L9['light1'], L9['attr1'], 0.5),
 
@@ -103,12 +104,46 @@ class TestDeviceSettings(unittest.TestCa
 
        
 
    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))
 
        
 

	
 
L1 = L9['light1']
 
ZOOM = L9['zoom']
 
class TestFromBlend(unittest.TestCase):
 
    def setUp(self):
 
        self.graph = LocalSyncedGraph(files=['test/cam/lightConfig.n3',
 
                                             'test/cam/bg.n3'])
 
    def testSingle(self):
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]),
 
            DeviceSettings.fromBlend(self.graph, [
 
                (1, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))]))
 

	
 
    def testScale(self):
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [(L1, ZOOM, 0.1)]),
 
            DeviceSettings.fromBlend(self.graph, [
 
                (.2, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)]))]))
 

	
 
    def testMixFloats(self):
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [(L1, ZOOM, 0.4)]),
 
            DeviceSettings.fromBlend(self.graph, [
 
                (.2, DeviceSettings(self.graph, [(L1, ZOOM, 0.5)])),
 
                (.3, DeviceSettings(self.graph, [(L1, ZOOM, 1.0)])),
 
            ]))
 

	
 
    def testMixColors(self):
 
        self.assertEqual(
 
            DeviceSettings(self.graph, [(L1, ZOOM, '#503000')]),
 
            DeviceSettings.fromBlend(self.graph, [
 
                (.25, DeviceSettings(self.graph, [(L1, ZOOM, '#800000')])),
 
                (.5, DeviceSettings(self.graph, [(L1, ZOOM, '#606000')])),
 
            ]))
light9/paint/solve.py
Show inline comments
 
@@ -58,92 +58,149 @@ class ImageDist(object):
 
        return 1 - numpy.dot(self.a, b) / self.d
 

	
 
class ImageDistAbs(object):
 
    def __init__(self, img1):
 
        self.a = img1
 
        self.maxDist = img1.shape[0] * img1.shape[1] * img1.shape[2] * 255
 

	
 
    def distanceTo(self, img2):
 
        return numpy.sum(numpy.absolute(self.a - img2), axis=None) / self.maxDist
 

	
 
        
 
class Solver(object):
 
    def __init__(self, graph, sessions=None, imgSize=(100, 75)):
 
    def __init__(self, graph, sessions=None, imgSize=(100, 53)):
 
        self.graph = graph
 
        self.sessions = sessions # URIs of capture sessions to load
 
        self.imgSize = imgSize
 
        self.samples = {} # uri: Image array (float 0-255)
 
        self.fromPath = {} # imagePath: image array
 
        self.path = {} # sample: path
 
        self.blurredSamples = {}
 
        self.sampleSettings = {} # (uri, path): DeviceSettings
 
        self.sampleSettings = {} # sample: DeviceSettings
 
        self.samplesForDevice = {} # dev : [(sample, img)]
 
        
 
    def loadSamples(self):
 
        """learn what lights do from images"""
 

	
 
        log.info('loading...')
 

	
 
        with self.graph.currentState() as g:
 
            for sess in self.sessions:
 
                for cap in g.objects(sess, L9['capture']):
 
                    self._loadSample(g, cap)
 
        log.info('loaded %s samples', len(self.samples))
 

	
 
    def _loadSample(self, g, samp):
 
        pathUri = g.value(samp, L9['imagePath'])
 
        self.samples[samp] = self.fromPath[pathUri] = loadNumpy(pathUri.replace(L9[''], '')).astype(float)
 
        self.blurredSamples[samp] = self._blur(self.samples[samp])
 
        img = loadNumpy(pathUri.replace(L9[''], '')).astype(float)
 
        settings = DeviceSettings.fromResource(self.graph, samp)
 
        
 
        self.samples[samp] = img
 
        self.fromPath[pathUri] = img
 
        self.blurredSamples[samp] = self._blur(img)
 

	
 
        key = (samp, pathUri)
 
        self.sampleSettings[key] = DeviceSettings.fromResource(self.graph, samp)
 
        self.path[samp] = pathUri
 
        assert samp not in self.sampleSettings
 
        self.sampleSettings[samp] = settings
 
        devs = settings.devices()
 
        if len(devs) == 1:
 
            self.samplesForDevice.setdefault(devs[0], []).append((samp, img))
 
        
 
    def _blur(self, img):
 
        return scipy.ndimage.gaussian_filter(img, 10, 0, mode='nearest')
 

	
 
    def draw(self, painting):
 
        return self._draw(painting, self.imgSize[0], self.imgSize[1])
 
        
 
    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) # ?
 
        ctx.set_line_width(w / 15) # ?
 
        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 bestMatch(self, img):
 
    def bestMatch(self, img, device=None):
 
        """the one sample that best matches this image"""
 
        #img = self._blur(img)
 
        results = []
 
        dist = ImageDist(img)
 
        for uri, img2 in sorted(self.samples.items()):
 
        if device is None:
 
            items = self.samples.items()
 
        else:
 
            items = self.samplesForDevice[device]
 
        for uri, img2 in sorted(items):
 
            if img.shape != img2.shape:
 
                log.warn("mismatch %s %s", img.shape, img2.shape)
 
                continue
 
            results.append((dist.distanceTo(img2), uri, img2))
 
        results.sort()
 
        topDist, topUri, topImg = results[0]
 
        print 'tops2'
 
        for row in results[:4]:
 
            print '%.5f' % row[0], row[1][-20:], self.sampleSettings[row[1]]
 
        
 
       
 
        #saveNumpy('/tmp/best_in.png', img)
 
        #saveNumpy('/tmp/best_out.png', topImg)
 
        #saveNumpy('/tmp/mult.png', topImg / 255 * img)
 
        return topUri, topDist
 

	
 
    def bestMatches(self, img, devices=None):
 
        """settings for the given devices that point them each
 
        at the input image"""
 
        dist = ImageDist(img)
 
        devSettings = []
 
        for dev in devices:
 
            results = []
 
            for samp, img2 in self.samplesForDevice[dev]:
 
                results.append((dist.distanceTo(img2), samp))
 
            results.sort()
 
            
 
            s = self.blendResults([(d, self.sampleSettings[samp])
 
                                   for d, samp in results[:8]])
 
            devSettings.append(s)
 
        return DeviceSettings.fromList(self.graph, devSettings)
 

	
 
    def blendResults(self, results):
 
        """list of (dist, settings)"""
 
        
 
        dists = [d for d, sets in results]
 
        hi = max(dists)
 
        lo = min(dists)
 
        n = len(results)
 
        remappedDists = [1 - (d - lo) / (hi - lo) * n / (n + 1)
 
                         for d in dists]
 
        total = sum(remappedDists)
 
        
 
        #print 'blend'
 
        #for o,n in zip(dists, remappedDists):
 
        #    print o,n, n / total
 
        blend = DeviceSettings.fromBlend(
 
            self.graph,
 
            [(d / total, sets) for
 
             d, (_, sets) in zip(remappedDists, results)])
 
        return blend
 
        
 
    def solve(self, painting):
 
        """
 
        given strokes of colors on a photo of the stage, figure out the
 
        best light DeviceSettings to match the image
 
        """
 
        pic0 = self.draw(painting).astype(numpy.float)
 
        pic0Blur = self._blur(pic0)
 
        saveNumpy('/tmp/sample_paint_%s.png' % len(painting['strokes']),
 
                  pic0Blur)
 
        sampleDist = {}
 
        dist = ImageDist(pic0Blur)
 
@@ -217,25 +274,26 @@ class Solver(object):
 
        
 
    def simulationLayers(self, settings):
 
        """
 
        how should a simulation preview approximate the light settings
 
        (device attribute values) by combining photos we have?
 
        """
 
        assert isinstance(settings, DeviceSettings)
 
        layers = []
 

	
 
        for dev, devSettings in settings.byDevice():
 
            requestedColor = devSettings.getValue(dev, L9['color'])
 
            candidatePics = [] # (distance, path, picColor)
 
            for (sample, path), s in self.sampleSettings.items():
 
            for sample, s in self.sampleSettings.items():
 
                path = self.path[sample]
 
                otherDevSettings = s.ofDevice(dev)
 
                if not otherDevSettings:
 
                    continue
 
                dist = devSettings.distanceTo(otherDevSettings)
 
                log.info('  candidate pic %s %s dist=%s', sample, path, dist)
 
                candidatePics.append((dist, path, s.getValue(dev, 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]
 
            log.info('  device best d=%g path=%s color=%s', bestDist, bestPath, bestPicColor)
 
            
light9/web/paint/paint-elements.coffee
Show inline comments
 
log = console.log
 

	
 
class Painting
 
  constructor: (@svg) ->
 
    @strokes = []
 

	
 
  setSize: (@size) ->
 

	
 
  startStroke: (pos, color) ->
 
    stroke = new Stroke(pos, color, @size)
 
    stroke.appendElem(@svg)
 
    @strokes.push(stroke)
 
    return stroke
 

	
 
@@ -167,25 +169,63 @@ Polymer
 
      catch
 
        null
 
      try
 
        return @graph.floatValue(s, pred)
 
      catch
 
        null
 
    throw new Error("no value for #{s}")
 
    
 
Polymer
 
  is: "light9-paint"
 
  properties: {
 
    painting: { type: Object }
 
    client: { type: Object }
 
    graph: { type: Object }
 
  }
 

	
 
  ready: () ->
 
    # couldn't make it work to bind to painting's notifyPath events
 
    @$.canvas.addEventListener('paintingChanged', @paintingChanged.bind(@))
 
    @$.solve.addEventListener('response', @onSolve.bind(@))
 

	
 
    @clientSendThrottled = _.throttle(@client.send.bind(@client), 60)
 
    @bestMatchPending = false
 
    
 
  paintingChanged: (ev) ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    @painting = ev.detail
 
    @$.solve.body = JSON.stringify(@painting.getDoc())
 
    @$.solve.generateRequest()
 
    #@$.solve.generateRequest()
 

	
 
    @$.bestMatches.body = JSON.stringify({
 
      painting: @painting.getDoc(),
 
      devices: [
 
        U('dev:aura1'), U('dev:aura2'), U('dev:aura3'), U('dev:aura4'), U('dev:aura5'),
 
        U('dev:q1'), U('dev:q2'), U('dev:q3'),
 
        ]})
 

	
 
    send = =>
 
      @$.bestMatches.generateRequest().completes.then (r) =>
 
        @clientSendThrottled(r.response.settings)
 
        if @bestMatchPending
 
          @bestMatchPending = false
 
          send()
 
    
 
    if @$.bestMatches.loading
 
      @bestMatchPending = true
 
    else
 
      send()
 

	
 
  onSolve: (response) ->
 
    U = (x) => @graph.Uri(x)
 

	
 
    sample = @$.solve.lastResponse.bestMatch.uri
 
    settingsList = []
 
    for s in @graph.objects(sample, U(':setting'))
 
      try
 
        v = @graph.floatValue(s, U(':value'))
 
      catch
 
        v = @graph.stringValue(s, U(':scaledValue'))
 
      row = [@graph.uriValue(s, U(':device')), @graph.uriValue(s, U(':deviceAttr')), v]
 
      settingsList.push(row)
 
    @client.send(settingsList)
light9/web/paint/paint-elements.html
Show inline comments
 
<script src="/lib/underscore/underscore-min.js"></script>
 
<link rel="import" href="/lib/polymer/polymer.html">
 
<link rel="import" href="/lib/iron-resizable-behavior/iron-resizable-behavior.html">
 
<link rel="import" href="/lib/iron-ajax/iron-ajax.html">
 
<link rel="import" href="/lib/paper-radio-group/paper-radio-group.html">
 
<link rel="import" href="/lib/paper-radio-button/paper-radio-button.html">
 
<link rel="import" href="paint-report-elements.html">
 
<link rel="import" href="../rdfdb-synced-graph.html">
 
<link rel="import" href="../light9-collector-client.html">
 

	
 

	
 
<dom-module id="light9-paint-canvas">
 
  <template>
 
    <style>
 
     :host {
 
         display: block;
 
     }
 
     #parent {
 
         position: relative;
 
         height: 500px;
 
     }
 
@@ -89,23 +90,27 @@
 
    </div>
 
  </template>
 
 
 
</dom-module>
 

	
 
<dom-module id="light9-paint">
 
  <template>
 
    <rdfdb-synced-graph graph="{{graph}}"></rdfdb-synced-graph>
 

	
 
    <light9-paint-canvas id="canvas" bg="bg2.jpg" painting="{{painting}}"></light9-paint-canvas>
 

	
 
    <iron-ajax id="solve" method="POST" url="../paintServer/solve" last-response="{{solve}}"></iron-ajax>
 

	
 
    <iron-ajax id="bestMatches" method="POST" url="../paintServer/bestMatches"></iron-ajax>
 
    
 
    <div>To collector: <light9-collector-client self="{{client}}"></light9-collector-client></div>
 

	
 
    <light9-simulation graph="{{graph}}" solution="{{solve}}" layers="{{layers}}"></light9-simulation>
 
  </template>
 
</dom-module>
 

	
 
<script src="/lib/N3.js-pull61/browser/n3-browser.js"></script>
 
<script src="/lib/shortcut/index.js"></script>
 
<script src="/lib/underscore/underscore-min.js"></script>
 
<script src="/lib/async/dist/async.js"></script>
 

	
 
<script src="paint-elements.js"></script>
light9/web/paint/paint-report-elements.html
Show inline comments
 
@@ -17,25 +17,25 @@
 
     [draggable=true]:hover {
 
         box-shadow: 0 0 20px yellow;
 
     }
 
         
 
     
 
    </style>
 

	
 
    <div id="solutions">
 
      <div id="single-light">
 
        <div>Single pic best match:</div>
 

	
 
        <!-- drag this img to make an effect out of just it -->
 
        <light9-capture-image name="mac2" path="{{solution.bestMatch.path}}"></light9-capture-image>
 
        <light9-capture-image name="lighhtnamehere" path="{{solution.bestMatch.path}}"></light9-capture-image>
 

	
 
        <div>Error: {{solution.bestMatch.dist}}</div>
 
        
 
        <light9-device-settings graph="{{graph}}" subj="{{solution.bestMatch.uri}}"></light9-device-settings>
 
      </div>
 

	
 
      <!-- existing effect best match? -->
 
      
 
      <div id="multi-light">
 
        Created from multiple lights:
 

	
 
        <div id="breakdown">
light9/web/rdfdb-synced-graph.html
Show inline comments
 
@@ -16,21 +16,22 @@
 
  <script src="rdfdbclient.js"></script>
 
  <script src="graph.js"></script>
 
  <script>
 
   Polymer({
 
       is: "rdfdb-synced-graph",
 
       properties: {
 
           graph: {type: Object, notify: true},
 
           status: {type: String, notify: true}
 
       },
 
       ready: function() {
 
           this.graph = new SyncedGraph('/rdfdb/syncedGraph', {
 
               '': 'http://light9.bigasterisk.com/',
 
               'dev': 'http://light9.bigasterisk.com/device/',
 
               'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
 
               'rdfs': 'http://www.w3.org/2000/01/rdf-schema#',
 
               'xsd': 'http://www.w3.org/2001/XMLSchema#',
 
           }, function(s) { this.status = s; }.bind(this));
 
           window.graph = this.graph;
 
       }
 
   });
 
  </script>
 
</dom-module>
0 comments (0 inline, 0 general)