Commit 3d856562 authored by David DeMarle's avatar David DeMarle
Browse files

update the documentation

provide a top level query example to go along with the
top level generate example we already have

also fill in docymentation and copyright for all public
api
parent 177aa834
Pipeline #12896 passed with stage
......@@ -119,4 +119,42 @@ e = vtk_explorers.ImageExplorer(cs, ['offset','phi', 'theta'], [cam, g], rw)
e.explore()
```
The cinema module is also the recommended path for working with cinema stores that have been generated. The following code demonstrates the API for opening a store, searching for elements within it, and doing some trivial analysis of them.
```python
from cinema_python import cinema_store
import PIL.Image
import sys
def demonstrate_analyze(fname):
"""
this demonstrates traversing an existing cinema store and doing some analysi
(in this case just printing the contents) on each item"""
cs = cinema_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])
```
#==============================================================================
# Copyright (c) 2015, Kitware Inc., Los Alamos National Laboratory
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may
# be used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#==============================================================================
"""
Interface to OpenEXR library.
TODO: fold this into raster_wrangler.
"""
import OpenEXR as oe
import Imath as im
import numpy as np
class OexrCompression:
NONE = 0
RLE = 1
......
#==============================================================================
# Copyright (c) 2015, Kitware Inc., Los Alamos National Laboratory
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may
# be used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#==============================================================================
"""
API for creating images that correspond to a set of queries.
Used by viewer primarily.
"""
import abc
import cinema_store
from LayerSpec import *
......
#==============================================================================
# Copyright (c) 2015, Kitware Inc., Los Alamos National Laboratory
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its contributors may
# be used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#==============================================================================
"""
Creating images that correspond to a set of queries by compositing the results together.
Used by viewer primarily.
"""
from QueryTranslator import QueryTranslator
import cinema_store
from LayerSpec import *
......
......@@ -204,6 +204,12 @@ class Store(object):
return "RGB"
def determine_type(self, desc):
"""
Given a descriptor this figures out what type of data it holds.
It works by first looking into the __type_specs for registered
relationships and if that fails returning the registered default
type for the store.
"""
#try any assigned mappings (for example color='depth' then 'Z')
for typename, checks in self.__type_specs.items():
for check in checks:
......@@ -218,6 +224,8 @@ class Store(object):
@property
def parameter_associations(self):
""" paremeter associations establish a dependency relationship between
different parameters. """
return self.__parameter_associations
def _set_parameter_associations(self, val):
......@@ -242,6 +250,7 @@ class Store(object):
self.__metadata.update(keyval)
def get_version_major(self):
""" major version information corresponds with store type """
if self.metadata == None or not 'type' in self.metadata:
return -1
if self.metadata['type'] == 'parametric-image-stack':
......@@ -250,11 +259,13 @@ class Store(object):
return 1
def get_version_minor(self):
""" minor version information corresponds to larger changes in a store type """
if self.metadata == None or not 'version' in self.metadata:
return 0
return int(self.metadata['version'].split('.')[0])
def get_version_patch(self):
""" patch version information corresponds to slight changes in a store type """
if self.metadata == None or not 'version' in self.metadata:
return 0
return int(self.metadata['version'].split('.')[1])
......@@ -399,13 +410,16 @@ class Store(object):
def islayer(self, name):
return (self.parameter_list[name]['role'] == 'layer') if (name in self.parameter_list and 'role' in self.parameter_list[name]) else False
def add_sublayer(self, name, properties, parent_layer, parents_value):
def add_control(self, name, properties):
"""
An example of a layer is an isocontour display. An example of a sublayer
is the particular isovalues for the isocontour.
A control is a togglable parameter for a filter. Examples include:
isovalue, offset.
"""
self.add_layer(name, properties)
self.assign_parameter_dependence(name, parent_layer, parents_value)
properties['role'] = 'control'
self.add_parameter(name, properties)
def iscontrol(self, name):
return (self.parameter_list[name]['role'] == 'control') if (name in self.parameter_list and 'role' in self.parameter_list[name]) else False
def add_field(self, name, properties, parent_layer, parents_values):
"""
......@@ -420,17 +434,6 @@ class Store(object):
def isfield(self, name):
return (self.parameter_list[name]['role'] == 'field') if (name in self.parameter_list and 'role' in self.parameter_list[name]) else False
def add_control(self, name, properties):
"""
A control is a togglable parameter for a filter. Examples include:
isovalue, offset.
"""
properties['role'] = 'control'
self.add_parameter(name, properties)
def iscontrol(self, name):
return (self.parameter_list[name]['role'] == 'control') if (name in self.parameter_list and 'role' in self.parameter_list[name]) else False
def parameters_for_object(self, obj):
"""
Given <obj>, an element of the layer <vis>, this method returns:
......@@ -594,6 +597,9 @@ class FileStore(Store):
consisting of parameter names enclosed in '{' and '}' and
separated by spacers. "/" spacer characters produce sub
directories.
Composite type stores ignore the file name pattern other than
the extension.
"""
return self.__filename_pattern
......@@ -612,9 +618,6 @@ class FileStore(Store):
def _get_filename(self, desc):
dirname = os.path.dirname(self.__dbfilename)
#print self.__dbfilename
#print desc
#print self.filename_pattern
#find filename modulo any dependent parameters
fixed = self.filename_pattern.format(**desc)
......@@ -633,7 +636,6 @@ class FileStore(Store):
#this one follows the graph and thus keeps related things, like
#all the rasters (fields) for a particular object (layer), close
#to one another
#TODO: optimize this
keys = [k for k in sorted(desc)]
ordered_keys = []
while len(keys):
......@@ -672,6 +674,8 @@ class FileStore(Store):
return fullpath
def insert(self, document):
"""overridden to write file for the document after parent
makes a record of it in the store"""
super(FileStore, self).insert(document)
fname = self._get_filename(document.descriptor)
......@@ -710,14 +714,13 @@ class FileStore(Store):
return doc
def find(self, q=None):
""" overridden to implement parent API with files"""
q = q if q else dict()
target_desc = q
#print "->>> store::find(): target_desc-> ", target_desc
for possible_desc in self.iterate(fixedargs=target_desc):
if possible_desc == {}:
yield None
#print "->>> store::find() possible_desc: ", possible_desc
filename = self._get_filename(possible_desc)
#optimization - cache and reuse to avoid file load
if filename in self.cached_files:
......@@ -729,23 +732,22 @@ class FileStore(Store):
yield fcontent
def get(self, q):
#print "GET", q
""" optimization of find()[0] for an important case where caller
knows exactly what to retrieve."""
filename = self._get_filename(q)
#print "FILE", filename
#optimization - cache and reuse to avoid file load
if filename in self.cached_files:
#print "HIT"
return self.cached_files[filename]
#print "MISS"
fcontent = self._load_data(filename, q)
#print fcontent
#todo: shouldn't be unbounded size
self.cached_files[filename] = fcontent
return fcontent
class SingleFileStore(Store):
"""Implementation of a store based on a single volume file (image stack)."""
"""Implementation of a store based on a single volume file (image stack).
NOTE: This class is limited to parametric-image-stack type stores,
currently unmaintained and may go away in the near future."""
def __init__(self, dbfilename=None):
super(SingleFileStore, self).__init__()
......@@ -798,9 +800,9 @@ class SingleFileStore(Store):
slices = slices * numvals
return slices
def compute_sliceindex(self, descriptor):
#find position of descriptor within the set of slices
#TODO: algorithm is dumb, but consisent with find (which is also dumb)
def _compute_sliceindex(self, descriptor):
"""find position of descriptor within the set of slices
TODO: algorithm is dumb, but consisent with find (which is also dumb)"""
args = []
values = []
ordered = sorted(self.parameter_list.keys())
......@@ -820,8 +822,9 @@ class SingleFileStore(Store):
index = index + 1
def get_sliceindex(self, document):
""" returns the location of one document within the stack"""
desc = self.get_complete_descriptor(document.descriptor)
index = self.compute_sliceindex(desc)
index = self._compute_sliceindex(desc)
return index
def _insertslice(self, vol_file, index, document):
......@@ -849,6 +852,7 @@ class SingleFileStore(Store):
self._needWrite = True
def insert(self, document):
"""overridden to store data within a volume after parent makes a note of it"""
super(SingleFileStore, self).insert(document)
index = self.get_sliceindex(document)
......@@ -861,7 +865,6 @@ class SingleFileStore(Store):
self._insertslice(vol_file, index, document)
def _load_slice(self, q, index, desc):
if not self._volume:
import vtk
dirname = os.path.dirname(self.__dbfilename)
......@@ -891,7 +894,9 @@ class SingleFileStore(Store):
return doc
def find(self, q=None):
#TODO: algorithm is dumb, but consisent with compute_sliceindex (which is also dumb)
"""Overridden to search for documentts withing the stack.
TODO: algorithm is dumb, but consisent with compute_sliceindex
(which is also dumb)"""
q = q if q else dict()
args = []
values = []
......@@ -914,6 +919,13 @@ class SingleFileStore(Store):
def make_parameter(name, values, **kwargs):
""" define a new parameter that will be added to a store.
Primarily takes a name and an array of potential values.
May also be given a default value from inside the array.
May also be given a typechoice to help the UI which is required to be one of
'list', 'range', 'option' or 'hidden'.
May also bve given a user friendly label.
"""
default = kwargs['default'] if 'default' in kwargs else values[0]
if not default in values:
raise RuntimeError, "Invalid default, must be one of %s" % str(values)
......@@ -933,8 +945,13 @@ def make_parameter(name, values, **kwargs):
return properties
def make_field(name, _values, **kwargs):
#specialization of make_parameters for parameters that define fields
#in this case the values is a list of name, type pairs
"""specialization of make_parameters for parameters that define
fields(aka color inputs).
In this case the values is a list of name, type pairs where types must be one
of 'rgb', 'depth', 'value', or 'luminance'
May also be given an set of valueRanges, which have min and max values for named
'value' type color selections.
"""
values = _values.keys()
img_types = _values.values()
......@@ -948,9 +965,6 @@ def make_field(name, _values, **kwargs):
raise RuntimeError, "Invalid default, must be one of %s" % str(values)
typechoice = 'hidden'
valid_types = ['list','range','option','hidden']
if not typechoice in valid_types:
raise RuntimeError, "Invalid typechoice, must be one of %s" % str(valid_types)
label = kwargs['label'] if 'label' in kwargs else name
......
......@@ -40,8 +40,9 @@ from paraview import numpy_support as numpy_support
class ImageExplorer(explorers.Explorer):
"""
An explorer that connects a paraview script's views to a store
and makes it save new images into the store.
An Explorer that connects a ParaView script's views to a store.
Basically it iterates over the parameters and for each unique combination
it tells ParaView to make an image and saved the result into the store.
"""
def __init__(self,
cinema_store, parameters, tracks,
......@@ -74,6 +75,8 @@ class ImageExplorer(explorers.Explorer):
self.rgb2grey = rgb2grey
def insert(self, document):
"""overridden to use paraview to generate an image and create a
the document for it"""
if not self.view:
return
if self.CaptureDepth:
......@@ -135,6 +138,8 @@ class ImageExplorer(explorers.Explorer):
super(ImageExplorer, self).insert(document)
def setDrawMode(self, choice, **kwargs):
""" helper for Color tracks so that they can cause ParaView to
render in the right mode."""
if choice == 'color':
self.view.StopCaptureValues()
if self.UsingGL2:
......@@ -179,7 +184,7 @@ class ImageExplorer(explorers.Explorer):
class Camera(explorers.Track):
"""
A track that connects a paraview script's camera to the phi and theta tracks.
A track that connects a ParaView script's camera to the phi and theta tracks.
This allows the creation of spherical camera stores where the user can
view the data from many points around it.
"""
......@@ -195,6 +200,7 @@ class Camera(explorers.Track):
self.view = view
def execute(self, document):
"""moves camera into position for the current phi, theta value"""
import math
theta = document.descriptor['theta']
phi = document.descriptor['phi']
......@@ -234,9 +240,8 @@ class Camera(explorers.Track):
class Slice(explorers.Track):
"""
A track that connects slice filters to a scalar valued parameter.
A track that connects a slice filter to a scalar valued parameter.
"""
def __init__(self, parameter, filt):
super(Slice, self).__init__()
......@@ -253,9 +258,8 @@ class Slice(explorers.Track):
class Contour(explorers.Track):
"""
A track that connects contour filters to a scalar valued parameter.
A track that connects a contour filter to a scalar valued parameter.
"""
def __init__(self, parameter, filt):
super(Contour, self).__init__()
self.parameter = parameter
......@@ -272,9 +276,8 @@ class Contour(explorers.Track):
class Clip(explorers.Track):
"""
A track that connects clip filters to a scalar valued parameter.
A track that connects a clip filter to a scalar valued parameter.
"""
def __init__(self, argument, clip):
super(Clip, self).__init__()
self.argument = argument
......@@ -292,25 +295,26 @@ class Clip(explorers.Track):
class Templated(explorers.Track):
"""
A track that connects any type of filter to a scalar valued
'control' parameter.
parameter. To use pass in a source proxy (aka filter)
and the name of method (aka property) to be called on it.
"""
def __init__(self, parameter, filt, control):
def __init__(self, parameter, filt, methodName):
explorers.Track.__init__(self)
self.parameter = parameter
self.filt = filt
self.control = control
self.methodName = methodName
def execute(self, doc):
o = doc.descriptor[self.parameter]
self.filt.SetPropertyWithName(self.control,[o])
self.filt.SetPropertyWithName(self.methodName,[o])
class ColorList():
"""
A helper that creates a dictionary of color controls for ParaView. The Color track takes in
a color name from the Explorer and looks up into a ColorList to determine exactly what
needs to be set to apply the color.
A helper that creates a dictionary of color controls for ParaView.
The Color track takes in a color name from the Explorer and looks
up into a ColorList to determine exactly what needs to be set to
apply the color.
"""
def __init__(self):
self._dict = {}
......@@ -339,7 +343,7 @@ class ColorList():
class Color(explorers.Track):
"""
A track that connects a parameter to a choice of surface rendered color maps.
A track that connects a parameter to color controls.
"""
def __init__(self, parameter, colorlist, rep):
super(Color, self).__init__()
......@@ -349,6 +353,9 @@ class Color(explorers.Track):
self.imageExplorer = None
def execute(self, doc):
"""tells ParaView to color the object we've been assigned
using the color definition we've been given that corresponds
to the value we've been assigned to watch in the doc.descriptor"""
if not self.parameter in doc.descriptor:
return
if self.rep == None:
......
......@@ -27,6 +27,11 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#==============================================================================
"""
Module that looks at a ParaView pipeline and automatically creates a cinema
store that ranges over all of the variables that we know how to control and later
show.
"""
import cinema_store
import paraview
import pv_explorers
......@@ -35,6 +40,7 @@ from itertools import imap
import numpy as np
def record_visibility():
"""at start of run, record the current paraview state so we can return to it"""
proxies = []
view_info = {}
......@@ -71,6 +77,7 @@ def record_visibility():
return proxies
def restore_visibility(proxies):
"""at end of run, return to a previously recorded paraview state"""
view_proxy = paraview.simple.GetActiveView()
for listElt in proxies:
......@@ -152,6 +159,7 @@ def inspect(skip_invisible=True):
return pxies
def get_pipeline():
"""sanitizes the pipeline graph"""
proxies = inspect(skip_invisible=False)
for proxy in proxies:
source = paraview.simple.FindSource(proxy['name'])
......@@ -171,7 +179,9 @@ def get_pipeline():
return proxies
def float_limiter(x):
#a shame, but needed to make sure python, java and (directory/file)name agree
"""a shame, but needed to make sure python, javascript and
(directory/file)name agree. TODO: This can go away now that
we use name=index instead of name=value filenames."""
if isinstance(x, (float)):
#return '%6f' % x #arbitrarily chose 6 decimal places
return '%.6e' % x #arbitrarily chose 6 significant digits
......@@ -183,6 +193,7 @@ def float_limiter(x):
explorerDir = {}
def add_filter_value(name, cs, userDefinedValues):
"""creates controls for the filters that we know how to manipulate"""
source = paraview.simple.FindSource(name)
# plane offset generator (for Slice or Clip)
......@@ -239,6 +250,7 @@ def add_filter_value(name, cs, userDefinedValues):
explorerDir[name] = pv_explorers.Contour(name, source)
def filter_has_parameters(name):
"""see if this proxy is one we know how to make controls for"""
source = paraview.simple.FindSource(name)
return any(imap(lambda filter: isinstance(source, filter),
[paraview.simple.servermanager.filters.Clip,
......@@ -246,6 +258,7 @@ def filter_has_parameters(name):
paraview.simple.servermanager.filters.Contour]))
def add_control_and_colors(name, cs):
"""add parameters that change the settings and color of a filter"""
source = paraview.simple.FindSource(name)
#make up list of color options
fields = {'depth':'depth','luminance':'luminance'}
......@@ -388,7 +401,8 @@ def testexplore(cs):
def explore(cs, proxies, iSave=True, currentTime=None):
"""
Takes in the store, which contains only the list of parameters,
Runs a pipeline through all the changes we know how to make and saves off
images into the store for each one.
"""
# import pv_explorers
import explorers
......@@ -490,6 +504,8 @@ def explore(cs, proxies, iSave=True, currentTime=None):
e.explore({'time':float_limiter(t)})
def record(csname="/tmp/test_pv/info.json"):
"""A stripped down top level entry point to create a store from a paraview session.
TODO: removed this."""
paraview.simple.Render()
view = paraview.simple.GetActiveView()
camera = view.GetActiveCamera()
......
......@@ -105,18 +105,21 @@ class RasterWrangler(object):
self.backends.add("VTK")