Create VisTrails Package use AutoGeneration

Use the Module AutoGeneation strategy described by VisTrails Developer David Koop for the Matplotlib to wrap functions in a python library as native VisTrails modules.

David’s comments: The basic idea here is to parse the code and documentation and generate modules and ports from this information. For the matplotlib package, we use this strategy to generate initial specifications in XML, allow users to edit these specs (saving a diff so future versions can start by applying the same diff), and then generating the .py files that VisTrails needs. See the parse.py, diff.py, and generate.py scripts in vistrails github for more information. I am hoping to make this a more general development tool in the future, but currently, it has some matplotlib-specific features.

The ultimate goal will be wrapping the python bindings for ASTRA into a VisTrails package.

Example spec XML from the matplotlib package

XML Spec for plot function contour():

<moduleSpec code_ref="matplotlib.pyplot.contour" name="MplContour" output_type="tuple" superclass="MplPlot">


<docstring>

  omitted docstring here........

</docstring>


 <inputPortSpec arg="origin" name="origin" port_type="basic:String">
   <docstring>If None, the first value of Z will correspond to the lower left corner, location (0,0). If 'image', the rc value for image.origin will be used.
   This keyword is not active if X and Y are specified in the call to contour.</docstring>
   <entry_types>['enum']</entry_types>
   <values>[['upper', 'lower', 'image']]</values>
 </inputPortSpec>


 <inputPortSpec arg="linestyles" name="linestyles" port_type="basic:String">
   <docstring>If linestyles is None, the default is 'solid' unless the lines are monochrome.  In that case, negative contours will take their linestyle from the matplotlibrc contour.negative_linestyle setting. linestyles can also be an iterable of the above strings specifying a set of linestyles to be used. If this iterable is shorter than the number of contour levels it will be repeated as necessary.</docstring>
   <entry_types>['enum']</entry_types>
   <values>[['solid', 'dashed', 'dashdot', 'dotted']]</values>
   <defaults>['solid']</defaults>
 </inputPortSpec>


 <inputPortSpec arg="xunits" name="xunits" port_type="basic:String">
   <docstring>Override axis units by specifying an instance of a :class:`matplotlib.units.ConversionInterface`.</docstring>
   <entry_types>['enum']</entry_types>
   <values>[['registered units']]</values>
 </inputPortSpec>


 <inputPortSpec arg="extend" name="extend" port_type="basic:String">
   <docstring>Unless this is 'neither', contour levels are automatically added to one or both ends of the range so that all data are included. These added ranges are then mapped to the special colormap values which default to the ends of the colormap range, but can be set via :meth:`matplotlib.colors.Colormap.set_under` and :meth:`matplotlib.colors.Colormap.set_over` methods.</docstring>
   <entry_types>['enum']</entry_types>
   <values>[['neither', 'both', 'min', 'max']]</values>
 </inputPortSpec>


 <inputPortSpec arg="vmin" name="vmin" port_type="basic:Float">
   <docstring>If not None, either or both of these values will be supplied to the :class:`matplotlib.colors.Normalize` instance, overriding the default color scaling based on levels.</docstring>
 </inputPortSpec>


 <inputPortSpec arg="nchunk" name="nchunk" port_type="basic:Integer">
   <docstring>If 0, no subdivision of the domain. Specify a positive integer to divide the domain into subdomains of roughly nchunk by nchunk points. This may never actually be advantageous, so this option may be removed. Chunking introduces artifacts at the chunk boundaries unless antialiased is False.</docstring>
   <entry_types>['enum']</entry_types>
   <values>[[0]]</values>
 </inputPortSpec>


 <inputPortSpec arg="hatches" name="hatches" port_type="basic:List">
   <docstring>A list of cross hatch patterns to use on the filled areas. If None, no hatching will be added to the contour. Hatching is supported in the PostScript, PDF, SVG and Agg backends only.</docstring>
 </inputPortSpec>


 <inputPortSpec arg="levels" name="levelsSequence" port_type="basic:List">
   <docstring>A list of floating point numbers indicating the level curves to draw; eg to draw just the zero contour pass levels=[0]</docstring>
   <alternateSpec arg="levels" name="levelsScalar" port_type="basic:Float" />
 </inputPortSpec>


 <inputPortSpec arg="linewidths" name="linewidths" port_type="basic:String">
   <docstring>If linewidths is None, the default width in lines.linewidth in matplotlibrc is used.

   If a number, all levels will be plotted with this linewidth.

   If a tuple, different levels will be plotted with different linewidths in the order specified</docstring>
   <entry_types>['enum']</entry_types>
   <values>[['number', 'tuple of numbers']]</values>
 </inputPortSpec>


 <inputPortSpec arg="locator" name="locator" port_type="basic:String">
   <docstring>If locator is None, the default :class:`~matplotlib.ticker.MaxNLocator` is used. The locator is used to determine the contour levels if they are not given explicitly via the V argument.</docstring>
   <entry_types>None</entry_types>
   <values>None</values>
 </inputPortSpec>


 <inputPortSpec arg="colors" name="colors" port_type="basic:Color">
   <docstring>If None, the colormap specified by cmap will be used.

   If a string, like 'r' or 'red', all levels will be plotted in this color.

   If a tuple of matplotlib color args (string, float, rgb, etc), different levels will be plotted in different colors in the order specified.</docstring>
   <translations>translate_color</translations>
   <entry_types>None</entry_types>
   <values>None</values>
 </inputPortSpec>


 <inputPortSpec arg="cmap" name="cmap" port_type="basic:String">
   <docstring>A cm :class:`~matplotlib.colors.Colormap` instance or None. If cmap is None and colors is None, a default Colormap is used.</docstring>
   <entry_types>None</entry_types>
   <values>None</values>
 </inputPortSpec>


 <inputPortSpec arg="yunits" name="yunits" port_type="basic:String">
   <docstring>Override axis units by specifying an instance of a :class:`matplotlib.units.ConversionInterface`.</docstring>
   <entry_types>None</entry_types>
   <values>None</values>
 </inputPortSpec>


 <inputPortSpec arg="extent" name="extent" port_type="basic:String">
   <docstring>If origin is not None, then extent is interpreted as in :func:`matplotlib.pyplot.imshow`: it gives the outer pixel boundaries. In this case, the position of Z[0,0] is the center of the pixel, not a corner. If origin is None, then (x0, y0) is the position of Z[0,0], and (x1, y1) is the position of Z[-1,-1].

   This keyword is not active if X and Y are specified in the call to contour.</docstring>
   <entry_types>['enum']</entry_types>
   <values>[['(x0,x1,y0,y1)']]</values>
 </inputPortSpec>


 <inputPortSpec arg="vmax" name="vmax" port_type="basic:Float">
   <docstring>If not None, either or both of these values will be supplied to the :class:`matplotlib.colors.Normalize` instance, overriding the default color scaling based on levels.</docstring>
 </inputPortSpec>


 <inputPortSpec arg="alpha" name="alpha" port_type="basic:Float">
   <docstring>The alpha blending value</docstring>
 </inputPortSpec>


 <inputPortSpec arg="Z" arg_pos="2" in_args="True" name="Z" port_type="basic:List" required="True" />


 <inputPortSpec arg="antialiased" name="antialiased" port_type="basic:Boolean">
   <docstring>enable antialiasing, overriding the defaults.  For filled contours, the default is True.  For line contours, it is taken from rcParams['lines.antialiased'].</docstring>
   <defaults>[True]</defaults>
 </inputPortSpec>


 <inputPortSpec arg="norm" name="norm" port_type="basic:String">
   <docstring>A :class:`matplotlib.colors.Normalize` instance for scaling data values to colors. If norm is None and colors is None, the default linear scaling is used.</docstring>
   <entry_types>None</entry_types>
   <values>None</values>
 </inputPortSpec>


 <inputPortSpec arg="V" arg_pos="3" in_args="True" name="V" port_type="basic:List" />

 <inputPortSpec arg="Y" arg_pos="1" in_args="True" name="Y" port_type="basic:List" />

 <inputPortSpec arg="X" arg_pos="0" in_args="True" name="X" port_type="basic:List" />

 <inputPortSpec arg="N" arg_pos="4" in_args="True" name="N" port_type="basic:Integer" />

 <outputPortSpec arg="contourSet" compute_name="contourSet" name="contourSet" port_type="MplQuadContourSet" property_key="0" />

 <outputPortSpec arg="lineCollection" compute_name="lineCollections" name="lineCollectionProperties" plural="True" port_type="__property__" property_key="1" property_type="matplotlib.collections.LineCollection" />




</moduleSpec>

Example Template

VisTrails use the Mako Templates for Python to generate python code from a XML spec document.

A simplest example of using the Mako template:

from mako.template import Template
print Template("hello ${data}!").render(data="world")

To learn more about Mako library, see their documentation page.

For the template used to generate matplotlib plot functions, see the Matplotlib plot template

Example generated python module

class MplContour(MplPlot):

 _input_ports = [
           ("origin", "basic:String",
            {'entry_types': "['enum']", 'docstring': "If None, the first value of Z will correspond to the lower left corner, location (0,0). If 'image', the rc value for image.origin will be used.\n\nThis keyword is not active if X and Y are specified in the call to contour.", 'values': "[['upper', 'lower', 'image']]", 'optional': True}),
           ("linestyles", "basic:String",
            {'entry_types': "['enum']", 'docstring': "If linestyles is None, the default is 'solid' unless the lines are monochrome.  In that case, negative contours will take their linestyle from the matplotlibrc contour.negative_linestyle setting.\n\nlinestyles can also be an iterable of the above strings specifying a set of linestyles to be used. If this iterable is shorter than the number of contour levels it will be repeated as necessary.", 'values': "[['solid', 'dashed', 'dashdot', 'dotted']]", 'optional': True, 'defaults': "['solid']"}),
           ("xunits", "basic:String",
            {'entry_types': "['enum']", 'docstring': 'Override axis units by specifying an instance of a :class:`matplotlib.units.ConversionInterface`.', 'values': "[['registered units']]", 'optional': True}),
           ("extend", "basic:String",
            {'entry_types': "['enum']", 'docstring': "Unless this is 'neither', contour levels are automatically added to one or both ends of the range so that all data are included. These added ranges are then mapped to the special colormap values which default to the ends of the colormap range, but can be set via :meth:`matplotlib.colors.Colormap.set_under` and :meth:`matplotlib.colors.Colormap.set_over` methods.", 'values': "[['neither', 'both', 'min', 'max']]", 'optional': True}),
           ("vmin", "basic:Float",
            {'optional': True, 'docstring': 'If not None, either or both of these values will be supplied to the :class:`matplotlib.colors.Normalize` instance, overriding the default color scaling based on levels.'}),
           ("nchunk", "basic:Integer",
            {'entry_types': "['enum']", 'docstring': 'If 0, no subdivision of the domain. Specify a positive integer to divide the domain into subdomains of roughly nchunk by nchunk points. This may never actually be advantageous, so this option may be removed. Chunking introduces artifacts at the chunk boundaries unless antialiased is False.', 'values': '[[0]]', 'optional': True}),
           ("hatches", "basic:List",
            {'optional': True, 'docstring': 'A list of cross hatch patterns to use on the filled areas. If None, no hatching will be added to the contour. Hatching is supported in the PostScript, PDF, SVG and Agg backends only.'}),
           ("levelsSequence", "basic:List",
            {'optional': True, 'docstring': 'A list of floating point numbers indicating the level curves to draw; eg to draw just the zero contour pass levels=[0]'}),
           ("levelsScalar", "basic:Float",
            {'docstring': 'A list of floating point numbers indicating the level curves to draw; eg to draw just the zero contour pass levels=[0]', 'optional': True}),
           ("linewidths", "basic:String",
            {'entry_types': "['enum']", 'docstring': 'If linewidths is None, the default width in lines.linewidth in matplotlibrc is used.\n\nIf a number, all levels will be plotted with this linewidth.\n\nIf a tuple, different levels will be plotted with different linewidths in the order specified', 'values': "[['number', 'tuple of numbers']]", 'optional': True}),
           ("locator", "basic:String",
            {'optional': True, 'docstring': 'If locator is None, the default :class:`~matplotlib.ticker.MaxNLocator` is used. The locator is used to determine the contour levels if they are not given explicitly via the V argument.'}),
           ("colors", "basic:Color",
            {'optional': True, 'docstring': "If None, the colormap specified by cmap will be used.\n\nIf a string, like 'r' or 'red', all levels will be plotted in this color.\n\nIf a tuple of matplotlib color args (string, float, rgb, etc), different levels will be plotted in different colors in the order specified."}),
           ("cmap", "basic:String",
            {'optional': True, 'docstring': 'A cm :class:`~matplotlib.colors.Colormap` instance or None. If cmap is None and colors is None, a default Colormap is used.'}),
           ("yunits", "basic:String",
            {'optional': True, 'docstring': 'Override axis units by specifying an instance of a :class:`matplotlib.units.ConversionInterface`.'}),
           ("extent", "basic:String",
            {'entry_types': "['enum']", 'docstring': 'If origin is not None, then extent is interpreted as in :func:`matplotlib.pyplot.imshow`: it gives the outer pixel boundaries. In this case, the position of Z[0,0] is the center of the pixel, not a corner. If origin is None, then (x0, y0) is the position of Z[0,0], and (x1, y1) is the position of Z[-1,-1].\n\nThis keyword is not active if X and Y are specified in the call to contour.', 'values': "[['(x0,x1,y0,y1)']]", 'optional': True}),
           ("vmax", "basic:Float",
            {'optional': True, 'docstring': 'If not None, either or both of these values will be supplied to the :class:`matplotlib.colors.Normalize` instance, overriding the default color scaling based on levels.'}),
           ("alpha", "basic:Float",
            {'optional': True, 'docstring': 'The alpha blending value'}),
           ("Z", "basic:List",
            {}),
           ("antialiased", "basic:Boolean",
            {'optional': True, 'docstring': "enable antialiasing, overriding the defaults.  For filled contours, the default is True.  For line contours, it is taken from rcParams['lines.antialiased'].", 'defaults': '[True]'}),
           ("norm", "basic:String",
            {'optional': True, 'docstring': 'A :class:`matplotlib.colors.Normalize` instance for scaling data values to colors. If norm is None and colors is None, the default linear scaling is used.'}),
           ("V", "basic:List",
            {'optional': True}),
           ("Y", "basic:List",
            {'optional': True}),
           ("X", "basic:List",
            {'optional': True}),
           ("N", "basic:Integer",
            {'optional': True}),
           ("lineCollectionProperties", "MplLineCollectionProperties",
            {}),
     ]

 _output_ports = [
     ("self", "(MplContour)"),
           ("contourSet", "MplQuadContourSet",
             {}),
     ]


 def compute(self):
     super(MplContour, self).compute()
     # get args into args, kwargs
     # write out translations
     args = []
     if self.has_input('X'):
         val = self.get_input('X')
         args.append(val)
     if self.has_input('Y'):
         val = self.get_input('Y')
         args.append(val)
     val = self.get_input('Z')
     args.append(val)
     if self.has_input('V'):
         val = self.get_input('V')
         args.append(val)
     if self.has_input('N'):
         val = self.get_input('N')
         args.append(val)

     kwargs = {}
     if self.has_input('origin'):
         val = self.get_input('origin')
         kwargs['origin'] = val
     if self.has_input('linestyles'):
         val = self.get_input('linestyles')
         kwargs['linestyles'] = val
     if self.has_input('xunits'):
         val = self.get_input('xunits')
         kwargs['xunits'] = val
     if self.has_input('extend'):
         val = self.get_input('extend')
         kwargs['extend'] = val
     if self.has_input('vmin'):
         val = self.get_input('vmin')
         kwargs['vmin'] = val
     if self.has_input('nchunk'):
         val = self.get_input('nchunk')
         kwargs['nchunk'] = val
     if self.has_input('hatches'):
         val = self.get_input('hatches')
         kwargs['hatches'] = val
     if self.has_input('levelsSequence'):
         val = self.get_input('levelsSequence')
         kwargs['levels'] = val
     elif self.has_input('levelsScalar'):
         val = self.get_input('levelsScalar')
         kwargs['levels'] = val
     if self.has_input('linewidths'):
         val = self.get_input('linewidths')
         kwargs['linewidths'] = val
     if self.has_input('locator'):
         val = self.get_input('locator')
         kwargs['locator'] = val
     if self.has_input('colors'):
         val = self.get_input('colors')
         val = translate_color(val)
         kwargs['colors'] = val
     if self.has_input('cmap'):
         val = self.get_input('cmap')
         kwargs['cmap'] = val
     if self.has_input('yunits'):
         val = self.get_input('yunits')
         kwargs['yunits'] = val
     if self.has_input('extent'):
         val = self.get_input('extent')
         kwargs['extent'] = val
     if self.has_input('vmax'):
         val = self.get_input('vmax')
         kwargs['vmax'] = val
     if self.has_input('alpha'):
         val = self.get_input('alpha')
         kwargs['alpha'] = val
     if self.has_input('antialiased'):
         val = self.get_input('antialiased')
         kwargs['antialiased'] = val
     if self.has_input('norm'):
         val = self.get_input('norm')
         kwargs['norm'] = val

     if self.has_input("N") and self.has_input("V"):
         del args[-1]
     contour_set = matplotlib.pyplot.contour(*args, **kwargs)
     output = (contour_set, contour_set.collections)
     contourSet = output[0]
     lineCollections = output[1]
     self.set_output('contourSet', contourSet)
     if self.has_input('lineCollectionProperties'):
         properties = self.get_input('lineCollectionProperties')
         if lineCollections is not None:
             properties.update_props(lineCollections)