"""
Creating images that correspond to a set of queries by compositing the
results together. Used by viewer primarily.
"""
import itertools as it
from querymaker import QueryMaker
import layer_rasters
import copy
[docs]class QueryMaker_SpecB(QueryMaker):
'''
This class translates UI queries into a set of Spec-B LayerRasters
instances (multi-image). This set of LayerRasters can be composited
through the Compositor class (compositor.py).
'''
def __init__(self):
super(QueryMaker_SpecB, self).__init__()
def _QueryMaker__createBaseLayerFromQuery(self, query):
'''
Creates a base LayerRasters containing the values that are shared
(global) by the set of user-selected layers (e.g. current time,
camera, etc.). This base LayerRasters will be used as an initial
template for all of the LayerRasters.
These queries are assumed to have a single value. Queries containing
multiple values need to be added separately in __generateQueriedLayers
(...).
'''
baseLayer = layer_rasters.LayerRasters()
parameter_list = self.store().parameter_list
for name in query.keys():
if (name in parameter_list) and (
"role" not in parameter_list[name]):
values = query[name]
if name == 'pose':
v = list(iter(values))
else:
# assumed to have a single value
v = list(iter(values))[0]
baseLayer.addToBaseQuery({name: v})
return baseLayer
def __generateQueriedLayers(self, query, globalBaseLayer):
'''
Creates all the LayerRasters instances, uses baseLayer as a common
template (__createLayer(..) creates copies of it).
Iterates over the "vis" parameters (assumes only those available items
are to be displayed) and creates a LayerRasters for each based on all
its necessary parameters. This method heavily relies on
cinema_store::parameters_for_object() in order to resolve all the
necessary parameters.
visQuery is the top-level query (vis parameter), parQueries are
queries of the mid-level parent filters wich control values affect the
final parameter node, ownQueries account for the control
values of the parameter itself.
'''
createdLayers = []
# every visible pipeline item
for paramName in (query["vis"] if ("vis" in query) else []):
relatedParams = self.store().parameters_for_object(paramName)
fieldName = relatedParams[1]
dependeeControls = relatedParams[2]
visQuery = {"vis": paramName}
# pop its own name from the dependee list, create specific queries
ownQueries = []
if paramName in dependeeControls:
dependeeControls.pop(dependeeControls.index(paramName))
ownQueries = self.__generateSpecificBaseQueries(
query, paramName)
# add the field to the list for the case when there is no controls
# but only field value arrays
if len(ownQueries) == 0:
ownQueries += self.__generateSpecificBaseQueries(
query, fieldName)
# set up each of the parent control queries (shared by each
# target query)
parQueries = []
for control in dependeeControls:
# TODO: make the [] brackets default to __genSpecificQueries
parQueries += [
self.__generateSpecificBaseQueries(query, control)]
if len(parQueries) > 0:
allQueries = self.__traverseQueryTree(parQueries, ownQueries)
else:
allQueries = ownQueries
for targetq in allQueries:
totalBaseQueries = [visQuery] + (
[targetq] if isinstance(targetq, dict) else list(targetq))
layer = self.__createLayer(
query, globalBaseLayer, totalBaseQueries, fieldName)
layer.setCustomizationName(paramName)
createdLayers.append(layer)
return createdLayers
def __traverseQueryTree(self, parQueries, ownQueries):
'''
Assembles iterable objects with all of the controls parameter
combinations (including both the parent control values and its own
control values). Uses map to apply the my_list customized list
initializer and itertools.chain.from_iterable to chain together each
layer's list of parameters to obtain a final list with all
of the chained combinations.
parQueries (parent control queries):
[[{'Ctrl1' : a}, {'Ctrl1' : b}, {'Ctrl1' : c}, ...],
[{'Ctrl2' : a}, {'Ctrl2' : b}, {'Ctrl2' : c}, ...], ...]
ownQueries (parameter's own control values):
[{'param' : a}, {'param' : b}, {'param' : c}, ...]
output (all of the combinations)**:
[[{'Ctrl1' : a}, {'Ctrl2' : a}, {'param' : a}],
[{'Ctrl1' : a}, {'Ctrl2' : a}, {'param' : b}],
[{'Ctrl1' : a}, {'Ctrl2' : a}, {'param' : c}], ...]
Note(**): The elements of the output list are itertool objects which
need to be evaluated by initializing a list (or any other iterable).
Warning: this function assumes ownQueries and parQueries contain
at least one element. The caller should ensure this is always true.
Note: See itertools docs for more information.
(https://docs.python.org/3/library/itertools.html#itertools.product)'''
def my_list(iterable_var):
'''
This is a customized list initializer which gives special
treatment to dict types (e.g. queries). All iterables are
initialized with list() while dictionaries are appended to a list.
The intention is to be able to pass this function to map() and
obtain a list of lists which is very simple to chain
together with itertools.chain.from_iterable().'''
if isinstance(iterable_var, dict):
return [iterable_var]
else:
return list(iterable_var)
# Compute the combinations between parent controls. it.product gives
# the inner product between the two lists and returns sets which are
# then converted back to lists. The resulting list-elements are
# concatenated into a single list.
current = map(my_list, parQueries.pop(0))
for nextQuery in parQueries:
current = map(
my_list, it.product(current, map(my_list, nextQuery)))
current = map(it.chain.from_iterable, current)
# Force it.chain evaluation to avoid recursive referencing to a
# single generator when doing the last product (ln. 170), as gens
# evaluate only once.
current = map(my_list, current)
# Compute the combinations of the parent controls with the
# parameter's own control values
allQueries = map(
my_list, it.product(current, map(my_list, ownQueries)))
allQueries = map(it.chain.from_iterable, allQueries)
return allQueries
def __generateSpecificBaseQueries(self, query, control):
'''
Creates a set of queries based on its input parameters. These are all
specific to a particular LayerRasters or group of LayerRasters.
'''
return [{control: value} for value in (
query[control] if (control in query) else [])]
def __createLayer(self, query, baseLayer, otherBaseQueries, fieldName):
'''
Creates a new layer by copying a 'baseLayer' and then including
additional base queries ('otherBaseQueries'). It then adds all the fbo
components specified in a field parameter ('fieldName') to this layer
for later compositing.
'''
newLayer = copy.deepcopy(baseLayer)
for bq in otherBaseQueries:
newLayer.addToBaseQuery(bq)
try:
fieldParameter = self.store().parameter_list[fieldName]
fieldValues = fieldParameter["values"]
fieldTypes = fieldParameter["types"]
fieldRanges = fieldParameter["valueRanges"] if (
"valueRanges" in fieldParameter) else {}
for index, value in enumerate(fieldValues):
# TODO: when "rgb" a hack might be needed to add a single
# color when several available
# Note 4th condition: includes a query[] value if the
# fieldName has been queried (necessary
# for compatibility SpecB-testcase1
if (fieldTypes[index] == "depth" or
fieldTypes[index] == "luminance" or
fieldTypes[index] == "rgb" or
((value in query[fieldName])
if (fieldName in query) else False)):
# Actual value ranges will be used to normalize color lut
# if set
# Ensure value rasters are float and not invertible
if "value_mode" in self.store().metadata:
self.__floatValueRasters = self.store().metadata[
"value_mode"] == 2
else:
self.__floatValueRasters = False
if (value in fieldRanges) and self.__floatValueRasters:
newLayer.setValueRange(fieldRanges[value])
imageType = self.store().determine_type({fieldName: value})
# TODO imageType parameter can be resolved with the
# other two within ::addQuery()
newLayer.addQuery(imageType, fieldName, value)
except KeyError:
raise KeyError(
"The field parameter ", fieldName, " is not well defined!",
"\n Types: ", fieldTypes,
"\n Values: ", fieldValues,
"\n Query: ", query,
"\n Value: ", value, " INDEX: ", index)
return newLayer
[docs] def translateQuery(self, query, colorDefinitions={}):
'''
Receives a UI query and returns a set of LayerRasters consisting of all
the different layers to composite. See the base class documentation
for detail.
'''
# Create a common base LayerRasters (time and camera).
baseLayer = self._QueryMaker__createBaseLayerFromQuery(query)
# Create all the LayerRasters (TODO cache layers and other db info to
# avoid re-creating them every time).
allLayers = self.__generateQueriedLayers(query, baseLayer)
self._QueryMaker__loadLayers(allLayers)
return allLayers
[docs] def supportsLayering(self):
return True