Examples

generation

"""
Example script that creates a Cinema store using VTK.
The parameters are phi, theta (camera positions) and
contour. This script will create a contour.json file
as well as a bunch of images named based on the
filename_patter = {phi}/{theta}/{contour}.png
where the {} scope denotes variables that are replaced
by parameter values for each image.
You can view the output of this script using the
Cinema QtViewer using something like this:
>> python ~/Work/Cinema/qt-viewer/Cinema.py ./contour.json
"""

import cinema_python.adaptors.vtk.vtk_explorers as vtk_explorers
import cinema_python.database.store as store
import cinema_python.database.file_store as file_store
import vtk

# set up the visualization

rw = vtk.vtkRenderWindow()
rw.SetSize(800, 800)
r = vtk.vtkRenderer()
rw.AddRenderer(r)

s = vtk.vtkRTAnalyticSource()
s.SetWholeExtent(-50, 50, -50, 50, -50, 50)

cf = vtk.vtkContourFilter()
cf.SetInputConnection(s.GetOutputPort())
cf.SetInputArrayToProcess(
    0, 0, 0, "vtkDataObject::FIELD_ASSOCIATION_POINTS", "RTData")
cf.SetNumberOfContours(1)
cf.SetValue(0, 200)
cf.ComputeScalarsOn()
m = vtk.vtkPolyDataMapper()
m.SetInputConnection(cf.GetOutputPort())
a = vtk.vtkActor()
a.SetMapper(m)
r.AddActor(a)

rw.Render()
r.ResetCamera()

# Create a new Cinema store
cs = file_store.FileStore("./contour.json")
cs.filename_pattern = "{phi}/{theta}/{contour}.png"

# These are the parameters that we will have in the store
cs.add_parameter("phi", store.make_parameter('phi', range(0, 200, 40)))
cs.add_parameter(
    "theta", store.make_parameter('theta', range(-180, 200, 40)))
cs.add_parameter("contour", store.make_parameter('contour', [160, 200]))

# These objects are responsible of change VTK parameters during exploration
con = vtk_explorers.Contour('contour', cf, 'SetValue')
cam = vtk_explorers.Camera([0, 0, 0], [0, 1, 0], 300.0, r.GetActiveCamera())

# Let's create the store
e = vtk_explorers.ImageExplorer(
    cs, ['contour', 'phi', 'theta'], [cam, con], rw)
e.explore()

# Now let's load from the store

cs2 = file_store.FileStore("./contour.json")
cs2.load()

docs = []
for doc in cs2.find({'theta': -180, 'phi': 0, 'contour': 160}):
    print doc
    docs.append(doc.data)

print doc

analysis

"""
Example script that analyzes a store. First it loads it, then asks what the
variable parameters are, picks one of them, uses that to search in
the database while doing some simple processing on the matching documents.
"""

import cinema_python.database.file_store as file_store
import PIL.Image
import sys


def demonstrate_analyze(fname):
    cs = file_store.FileStore(fname)
    cs.load()

    print "PARAMETERS ARE"
    for parameter in cs.parameter_list:
        print parameter
        print cs.get_parameter(parameter)['values']

    print "ONE PARAMETER'S FIRST VALUE IS"
    param = cs.parameter_list.keys()[0]
    val = cs.get_parameter(param)['values'][0]
    print val

    print "HISTOGRAMS OF MATCHING RECORDS FOR", param, "=", val, "ARE"
    for doc in cs.find({param: val}):
        print doc.descriptor
        image = PIL.Image.fromarray(doc.data)
        print image.histogram()

if len(sys.argv) != 2:
    print "Usage: python demo.py /path/to/info.json"
    exit(0)

demonstrate_analyze(sys.argv[1])

viewing

"""
Example script that shows one object from a cinema spec B/C store.
"""

import cinema_python.database.file_store as file_store
import cinema_python.images.querymaker_specb as qmsb
import cinema_python.images.compositor as compositor
import cinema_python.images.lookup_tables as luts
import PIL.Image
import sys

# todo:
# fail gracefully, or use specA translator, if not a specB/C store
# use pv_explorer to make something predictable


def show_something(fname):
    cs = file_store.FileStore(fname)
    cs.load()

    defobject = cs.get_parameter('vis')['default']
    print "LOOKING AT OBJECT ", defobject

    # now find parameters that are needed for this object and choose values
    indep, field, dep = cs.parameters_for_object(defobject)

    # the object itself
    request = {}
    request['vis'] = set([defobject])

    # independent parameters (time, camera etc)
    for x in indep:
        defval = cs.get_parameter(x)['default']
        try:
            request[x] = set([defval])
        except TypeError:
            # happens with pose's which are unhashable lists
            request[x] = defval

    # dependent parameters, filter settings etc
    for x in dep:
        defval = cs.get_parameter(x)['default']
        request[x] = set([defval])

    # an array to color by
    defval = cs.get_parameter(field)['default']
    request[field] = set([defval])

    print "DEFAULT SETTINGS ARE: "
    print "{"
    for k in list(request):
        print " '{}': {},".format(k, request[k])
    print "}"

    # now hand that over to the maker so it can make up a series
    # of queries that return the required rasters that go into
    # the object we've selected
    qm = qmsb.QueryMaker_SpecB()
    qm.setStore(cs)
    res = qm.translateQuery(request)

    print "RASTERS ARE KEPT IN", res

    # now setup the deferred renderer
    compo = compositor.Compositor_SpecB()
    compo.enableLighting(True)
    compo.set_background_color([0, 0, 0])

    # make up a color transfer function
    cmaps = []
    luts.add_rainbow(cmaps)
    lut = cmaps[0]
    glut = luts.LookupTable()
    glut.name = "Rainbow"
    glut.colorSpace = "RGB"
    glut.ingest(lut['RGBPoints'])
    lstruct = {
        defobject: {'colorLut': glut, 'geometryColor': [255, 255, 255]}}
    compo.setColorDefinitions(lstruct)
    print "COLOR TRANSFER FUNCTION TO USE", lstruct

    # ask it to render
    image = compo.render(res)

    # now we should have an RGB image in numpy, show it on screen
    im = PIL.Image.fromarray(image)
    im.show()
    print "If you see an empty window, most likely default values are off"

if len(sys.argv) != 2:
    print "Usage: python demo.py /path/to/info.json"
    exit(0)

show_something(sys.argv[1])

web service

"""
Example script showing how to provide a database viewing service over the web.
Besides cinema, this depends on bottle.py (see bottlepy.org)
To try it:
python web_service.py -fn /location/of/an/info.json
browser to localhost:8080/cinemaviewer
"""

from bottle import response, route, run
import cinema_python.database.file_store as file_store
import cinema_python.images.compositor as compositor
import cinema_python.images.lookup_tables as luts
import cinema_python.images.querymaker_specb as qmsb
import json
import numpy
import PIL
import StringIO
import sys

fname = None
for i in range(0, len(sys.argv)-1):
    if sys.argv[i] == "-fn":
        fname = sys.argv[i+1]
if fname is None:
    print ("Usage:", sys.argv[0], "-fn cinemastore/info.json")
    sys.exit(0)

cs = file_store.FileStore(fname)
cs.load()


@route('/speclevel')
def speclevel():
    """
    Entry point for web page to see what type of store we serve.
    """
    if cs.get_version_major() == 1:
        return "C"
    return "A"


@route('/cameramodel')
def cameramodel():
    """
    Entry point to ask what the camera model is, and thus what
    interactors to make. todo: we are not yet doing anything with this
    in the example below.
    """
    return cs.get_camera_model()


@route('/parameters')
def parameters():
    """
    Entry point to get the list of parameters are present in the store.
    """
    return cs.parameter_list


def __index_query_to_values(query):
    """
    Translate indexes in query to values.
    """
    # print ("QSTR", query)
    q = json.loads(query)
    transq = {}
    for k, v in q.iteritems():
        p = cs.get_parameter(k)['values']
        transq[k] = p[v[0]]
    # print ("TRANSQ ", transq)
    return transq


@route('/get/<query>')
def get(query):
    """
    Entry point for page to request a specific result from the store.
    query comes in a a set of key:index pairs and this returns images.
    todo: cinema can store more than just images, for example depth
    and value rasters and potentially other things, but this function
    blindly returns pngs. That's fine for specA, but if we make the
    client smart enough to do its own compositing, we'll need to fix
    that for example.
    """
    transq = __index_query_to_values(query)

    # get the result out of the database
    # todo: fail gracefully on bad query where raster is None
    document = cs.get(transq)
    raster = document.data

    # convert the result into a string of byte to return to browser
    imageslice = numpy.flipud(raster)
    pimg = PIL.Image.fromarray(imageslice)
    output = StringIO.StringIO()

    # encode as PNG in memory
    format = 'PNG'
    pimg.save(output, format)
    contents = output.getvalue()
    # let client know that we're returning an image
    response.set_header('Content-type', 'image/png')
    return contents


def __index_query_to_values_C(query):
    """
    Differs from __index_query_to_values_A in that we expect to to
    to pass in multiple items for some keys, not just one.
    """
    # print ("QSTR", query)
    q = json.loads(query)
    transq = {}
    for k, v in q.iteritems():
        p = cs.get_parameter(k)['values']
        if k == 'pose':
            transq[k] = p[v[0]]
        else:
            vals = []
            for r in range(0, len(v)):
                vals.append(p[v[r]])
            transq[k] = vals
    # print ("TRANSQ ", transq)
    return transq


@route('/getcomposite/<query>')
def getcomposite(query):
    """
    Entry point for rendered results from a specB/C store.
    """
    transq = __index_query_to_values_C(query)

    # turn the set of choices into a stack or raster producing queries
    qm = qmsb.QueryMaker_SpecB()
    qm.setStore(cs)
    res = qm.translateQuery(transq)

    # setup the deferred renderer to put the rasters together into a final
    # image
    compo = compositor.Compositor_SpecB()
    compo.enableLighting(True)
    compo.set_background_color([0, 0, 0])

    # make up a color transfer function
    # todo: let user choose which one
    # todo: let user make new ones
    cmaps = []
    luts.add_rainbow(cmaps)
    lut = cmaps[0]
    glut = luts.LookupTable()
    glut.name = "Rainbow"
    glut.colorSpace = "RGB"
    glut.ingest(lut['RGBPoints'])
    lstruct = {}
    # more correct to look for the one param with annotation 'layer' but
    # whatever we'll just get 'vis' since paraview names it that.
    objects = cs.get_parameter('vis')['values']
    for v in objects:
        # assign color choices to each potential object in the scene
        lstruct[v] = {'colorLut': glut, 'geometryColor': [255, 255, 255]}
    compo.setColorDefinitions(lstruct)

    # tell the compositor to make the final image
    pimg = compo.render(res)

    # translate the numpy result into a png to send to browser
    im = PIL.Image.fromarray(pimg)
    output = StringIO.StringIO()
    format = 'PNG'
    im.save(output, format)

    # return it
    contents = output.getvalue()
    response.set_header('Content-type', 'image/png')
    return contents


@route('/cinemaviewer')
def cinemaviewer():
    """
    Main entry point that end users care about. Point a browser at this and
    it will create a page that lets you see and interact with a cinema store.
    """

    # todo: the html and javascript and eventually css should be in
    # in their own files. But for now, just encoding the page as text.
    return ("""
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Sample of Web delivery out of cinema_python.</title>
  <link rel="stylesheet"
        href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <script src="https://code.jquery.com/jquery-1.12.4.js"></script>
  <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
  <script>
  // what we learn about the store
  var level = "A";
  var model = "static";
  var parameters;

  // holds the query that we make.
  var query = {};

  onload = function () {
    // when page loads, first figure out what the store has
    // spec type, so we can make appropriate requests
    $.get("http://localhost:8080/speclevel",
          function(data) {
            level = data;
          });

    // camera model so we can set up interactors
    // todo: this is unused so far
    $.get("http://localhost:8080/cameramodel",
          function(data) {
            model = data;
          });

    // this of parameters
    $.getJSON("http://localhost:8080/parameters", {format:"json"}).done(
      function(data) {
        //we now know what the parameters are, make GUI and run
        parameters = data;
        update_UI();
      }
    );
  };

  update_UI = function() {
    // make up GUI corresponding to parameters int the store
    // at the end (and whenever a widget changes, we call render to show
    // the result.

    var gui = document.getElementById('gui');
    for (var key in parameters) {
      var br = document.createElement("br");
      gui.appendChild(br);

      query[key] = [0]; // todo: use default instead of 0
      if (('role' in parameters[key]) &&
          (parameters[key]['role'] == 'field')) {
         // color components need special treatment
         var label = document.createTextNode(key);
         gui.appendChild(label);

         br = document.createElement("br");
         gui.appendChild(br);

         var numitems = parameters[key]['values'].length;
         var types = parameters[key]['types']
         var first = -1;
         for (var idx = 0; idx < numitems; idx++) {
           if (types[idx] != "depth" && types[idx] != "luminance") {
             // for color components, have to skip over depth and luminance
             // which user can't see directly

             if (first == -1)
               {
                 first = idx;
               }
             var btn = document.createElement('button');
             btn.innerHTML = parameters[key]['values'][idx];
             btn.id = key+"_"+idx;
             gui.appendChild(btn);
             $(btn).click( function () {
               var key = this.id.split("_")[0];
               var idx = parseInt(this.id.split("_")[1]);
               query[key] = [idx];
               render();
             });
           }

           //todo: add a colorlut chooser
         }
         query[key] = [first];
         continue;
      }

      var innerDiv = document.createElement('div');
      // label
      var label = document.createTextNode(key);
      gui.appendChild(label);

      var innerDiv = document.createElement('div');
      innerDiv.id = key;
      gui.appendChild(innerDiv);
      var numitems = parameters[key]['values'].length;

      if (parameters[key]['type'] == 'option') {
        // combobox
        for (var idx = 0; idx < numitems; idx++) {
          var btn = document.createElement('button');
          btn.innerHTML = parameters[key]['values'][idx];
          btn.id = key+"_"+idx;
          gui.appendChild(btn);
          // todo: make button look down when on
          $(btn).click( function () {
            var key = this.id.split("_")[0];
            var idx = parseInt(this.id.split("_")[1]);
            var location = query[key].indexOf(idx);
            if (location == -1) {
               query[key].push(idx);
            } else {
               query[key].splice(location, 1);
            }
            render();
          });
        }
      } else {
        // slider
        // todo: display chosen value as text
        var nextSlider = $(innerDiv).slider({
          max : numitems-1,
          change: function(event, ui) {
            var key = $(ui.handle).parent().attr('id');
            var idx = ui.value;
            query[key] = [idx];
            // call render whenever released to update view
            render();
          }
        });

        // play button
        var btn = document.createElement('button');
        btn.innerHTML = "play";
        btn.id = "play_"+key;
        gui.appendChild(btn)
        $(btn).click( function () {
          var key = this.id.split("_")[1];
          var numitems = parameters[key]['values'].length;
          var now = query[key][0];
          if (now == numitems-1) {
            now = -1;
          }
          if (now < numitems-1) {
            now = now + 1;
            query[key] = [now];
            render();
            //call self to continue
            //todo: update slider appropriately
            //todo: rather than fixed timeout of 130msec, call(_this)
            //as soon as and only when render() refreshes image
            if (now < numitems-1) {
              var fn = arguments.callee;
              var _this = this;
              setTimeout(function(){fn.call(_this);}, 100);
            }
          }
        });
      }

    }
    // call render on first draw
    render();
  };

  render = function() {
    // ask the server (view the get or getcomposite entry point) for the
    // data corresponding to our query, and show it in the view_window image
    //
    // todo: cache results and reuse rather than hit server every time.
    var q_str1 = "http://localhost:8080/get/";
    if (level == "C") {
      q_str1 = "http://localhost:8080/getcomposite/";
    }
    var q_str2 = JSON.stringify(query);
    var elem = document.getElementById('view_window');
    elem.src = q_str1+q_str2;
  };
  </script>
</head>
<body>

<div id="gui"></div>
<br>
<img src="" id="view_window" >
</div>

</body>
</html>
""")


run(host='localhost', port=8080, debug=False)