diff --git a/bin/captureDevice b/bin/captureDevice
new file mode 100644
--- /dev/null
+++ b/bin/captureDevice
@@ -0,0 +1,137 @@
+#!bin/python
+from __future__ import division
+from rdflib import URIRef, Literal
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, Deferred
+
+import logging
+import optparse
+import os
+import time
+import traceback
+import treq
+import cyclone.web, cyclone.websocket, cyclone.httpclient
+from greplin import scales
+
+from run_local import log
+from lib.cycloneerr import PrettyErrorHandler
+
+from light9.namespaces import L9
+from light9 import networking, showconfig
+from light9.rdfdb.syncedgraph import SyncedGraph
+from light9.paint.capture import writeCaptureDescription
+from light9.greplin_cyclone import StatsForCyclone
+from light9.effect.settings import DeviceSettings
+from light9.effect.sequencer import sendToCollector
+
+stats = scales.collection('/webServer', scales.PmfStat('setAttr'))
+
+class Attrs(PrettyErrorHandler, cyclone.web.RequestHandler):
+ def put(self):
+ with stats.setAttr.time():
+ client, clientSession, settings, sendTime = parseJsonMessage(self.request.body)
+ self.set_status(202)
+
+
+class Camera(object):
+ def __init__(self, imageUrl):
+ self.imageUrl = imageUrl
+
+ def takePic(self, uri, writePath):
+ log.info('takePic %s', uri)
+ return treq.get(self.imageUrl).addCallbacks(lambda r: self._done(writePath, r), log.error)
+
+ @inlineCallbacks
+ def _done(self, writePath, response):
+ jpg = yield response.content()
+ try:
+ os.makedirs(os.path.dirname(writePath))
+ except OSError:
+ pass
+ with open(writePath, 'w') as out:
+ out.write(jpg)
+ log.info('wrote %s', writePath)
+
+
+
+settleTime = .5
+
+camera = Camera('http://dash:8200/picamserve/pic?res=480&resize=480&rotation=180&iso=800&redgain=1.6&bluegain=2&shutter=60000')
+
+def launch(graph):
+
+ def steps(a, b, n):
+ return [round(a + (b - a) * i / n, 5) for i in range(n)]
+
+ startTime = time.time()
+ toGather = []
+
+ row = 0
+ for ry in steps(0.85, .92, 6):
+ xSteps = steps(.24, .45, 12)
+ if row % 2:
+ xSteps.reverse()
+ row += 1
+ for rx in xSteps:
+ toGather.append(DeviceSettings(graph, [
+ (L9['device/moving1'], L9['rx'], rx),
+ (L9['device/moving1'], L9['ry'], ry),
+ ]))
+
+ numPics = [0]
+ settingsCache = set()
+ @inlineCallbacks
+ def step():
+ if not toGather:
+ reactor.stop()
+ return
+ settings = toGather.pop()
+
+ log.info('move to %r', settings)
+ yield sendToCollector(client='captureDevice', session='main', settings=settings)
+
+ d = Deferred()
+ reactor.callLater(settleTime, d.callback, None)
+ yield d
+ dev = settings.devices()[0]
+
+ devTail = dev.rsplit('/')[-1]
+ captureId = 'cap%s' % (int(startTime) - 1495170000)
+ picId = 'pic%s' % numPics[0]
+ path = '/'.join(['capture', devTail, captureId, picId])
+ ctx = URIRef('/'.join([showconfig.showUri(), 'capture', devTail, captureId, 'index']))
+ uri = URIRef('/'.join([showconfig.showUri(), 'capture', devTail, captureId, picId]))
+
+ relOutPath = path + '.jpg'
+
+ yield camera.takePic(uri, os.path.join(showconfig.root(), relOutPath))
+ numPics[0] += 1
+
+ writeCaptureDescription(graph, ctx, uri, dev, relOutPath, settingsCache, settings)
+
+ reactor.callLater(0, step)
+ step().addErrback(log.error)
+
+ reactor.listenTCP(networking.captureDevice.port,
+ cyclone.web.Application(handlers=[
+ (r'/()', cyclone.web.StaticFileHandler,
+ {"path" : "light9/web", "default_filename" : "captureDevice.html"}),
+ (r'/stats', StatsForCyclone),
+ ]),
+ interface='::')
+ log.info('serving http on %s', networking.captureDevice.port)
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option("-v", "--verbose", action="store_true",
+ help="logging.DEBUG")
+ (options, args) = parser.parse_args()
+ log.setLevel(logging.DEBUG if options.verbose else logging.INFO)
+
+ graph = SyncedGraph(networking.rdfdb.url, "captureDevice")
+
+ graph.initiallySynced.addCallback(lambda _: launch(graph)).addErrback(log.error)
+ reactor.run()
+
+if __name__ == '__main__':
+ main()
diff --git a/light9/networking.py b/light9/networking.py
--- a/light9/networking.py
+++ b/light9/networking.py
@@ -36,6 +36,7 @@ class ServiceAddress(object):
def path(self, more):
return self.url + str(more)
+captureDevice = ServiceAddress(L9['captureDevice'])
curveCalc = ServiceAddress(L9['curveCalc'])
dmxServer = ServiceAddress(L9['dmxServer'])
dmxServerZmq = ServiceAddress(L9['dmxServerZmq'])
diff --git a/light9/paint/capture.py b/light9/paint/capture.py
new file mode 100644
--- /dev/null
+++ b/light9/paint/capture.py
@@ -0,0 +1,34 @@
+import os
+from rdflib import URIRef
+from light9 import showconfig
+from light9.rdfdb.patch import Patch
+from light9.namespaces import L9
+from light9.paint.solve import loadNumPy
+
+def writeCaptureDescription(graph, ctx, uri, dev, relOutPath, settingsSubgraphCache, settings):
+ settings.addStatements(
+ uri, ctx=ctx,
+ settingRoot=URIRef('/'.join([showconfig.showUri(), 'capture', dev.rsplit('/')[1]])),
+ settingsSubgraphCache=settingsSubgraphCache)
+ graph.patch(Patch(addQuads=[
+ (dev, L9['capture'], uri, 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"""
+
diff --git a/light9/paint/solve.py b/light9/paint/solve.py
--- a/light9/paint/solve.py
+++ b/light9/paint/solve.py
@@ -1,7 +1,6 @@
from __future__ import division
from light9.namespaces import RDF, L9, DEV
from PIL import Image
-import decimal
import numpy
import scipy.misc, scipy.ndimage, scipy.optimize
import cairo
@@ -18,6 +17,11 @@ def numpyFromCairo(surface):
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):
scipy.misc.imsave(path, img.transpose((1, 0, 2)))
diff --git a/show/dance2017/networking.n3 b/show/dance2017/networking.n3
--- a/show/dance2017/networking.n3
+++ b/show/dance2017/networking.n3
@@ -6,6 +6,7 @@ show:dance2017 :networking sh:netHome .
sh:netHome
:webServer ;
:patchReceiverUpdateHost "dash";
+ :captureDevice ;
:curveCalc ;
:collector ;
:collectorZmq ;
@@ -14,13 +15,14 @@ sh:netHome
:keyboardComposer ;
:musicPlayer ;
:oscDmxServer ;
- :paintServer ;
- :picamserve ;
+ :paintServer ;
+ :picamserve ;
:rdfdb ;
:subComposer ;
:subServer ;
:vidref .
+:captureDevice :urlPath "captureDevice" .
:curveCalc :urlPath "curveCalc" .
:dmxServer :urlPath "dmxServer" .
:effectEval :urlPath "effectEval" .