diff --git a/Packages/testing/regression.py b/Packages/testing/regression.py index b9cd2cdeb3..25255fdc0d 100644 --- a/Packages/testing/regression.py +++ b/Packages/testing/regression.py @@ -20,13 +20,15 @@ def init(*args, **kwargs): testingDir = os.path.join(os.path.dirname(__file__), "..") sys.path.append(testingDir) - vcsinst = vcs.init(*args, **kwargs) - vcsinst.setantialiasing(0) - vcsinst.drawlogooff() - if ((('bg' in kwargs and kwargs['bg']) or ('bg' not in kwargs)) and ('geometry' not in kwargs)): + vcsinst = vcs.init(*args, **dict(kwargs, bg=1)) vcsinst.setbgoutputdimensions(1200, 1091, units="pixels") + else: + vcsinst = vcs.init(*args, **dict(kwargs, bg=0)) + + vcsinst.setantialiasing(0) + vcsinst.drawlogooff() return vcsinst def run(vcsinst, fname, baseline=sys.argv[1], threshold=defaultThreshold): diff --git a/Packages/vcs/vcs/VCS_validation_functions.py b/Packages/vcs/vcs/VCS_validation_functions.py index 7595d900e3..2968d7832f 100644 --- a/Packages/vcs/vcs/VCS_validation_functions.py +++ b/Packages/vcs/vcs/VCS_validation_functions.py @@ -243,6 +243,18 @@ def checkListOfNumbers(self, name, value, minvalue=None, return list(value) +def checkInStringList(self, name, value, options): + checkName(self, name, value) + if value not in options: + self.checkedRaise( + self, + value, + ValueError, + 'Invalid value ' + value + '. Valid options are: ' + + ','.join(self.scaleoptions)) + return value + + def checkFont(self, name, value): if (value is None): pass diff --git a/Packages/vcs/vcs/vcsvtk/vectorpipeline.py b/Packages/vcs/vcs/vcsvtk/vectorpipeline.py index 0badc60b48..642884bc6c 100644 --- a/Packages/vcs/vcs/vcsvtk/vectorpipeline.py +++ b/Packages/vcs/vcs/vcsvtk/vectorpipeline.py @@ -19,6 +19,8 @@ def _plotInternal(self): # Preserve time and z axis for plotting these inof in rendertemplate projection = vcs.elements["projection"][self._gm.projection] taxis = self._originalData1.getTime() + scaleFactor = 1.0 + if self._originalData1.ndim > 2: zaxis = self._originalData1.getAxis(-3) else: @@ -84,24 +86,80 @@ def _plotInternal(self): arrow.SetOutputPointsPrecision(vtk.vtkAlgorithm.DOUBLE_PRECISION) arrow.FilledOff() + polydata = self._vtkPolyDataFilter.GetOutput() + vectors = polydata.GetPointData().GetVectors() + + if self._gm.scaletype == 'constant' or\ + self._gm.scaletype == 'constantNNormalize' or\ + self._gm.scaletype == 'constantNLinear': + scaleFactor = scale * 2.0 * self._gm.scale + else: + scaleFactor = 1.0 + glyphFilter = vtk.vtkGlyph2D() - glyphFilter.SetInputConnection(self._vtkPolyDataFilter.GetOutputPort()) + glyphFilter.SetInputData(polydata) glyphFilter.SetInputArrayToProcess(1, 0, 0, 0, "vector") glyphFilter.SetSourceConnection(arrow.GetOutputPort()) glyphFilter.SetVectorModeToUseVector() # Rotate arrows to match vector data: glyphFilter.OrientOn() + glyphFilter.ScalingOn() - # Scale to vector magnitude: glyphFilter.SetScaleModeToScaleByVector() - glyphFilter.SetScaleFactor(scale * 2.0 * self._gm.scale) - # These are some unfortunately named methods. It does *not* clamp the - # scale range to [min, max], but rather remaps the range - # [min, max] --> [0, 1]. - glyphFilter.ClampingOn() - glyphFilter.SetRange(0.01, 1.0) + if self._gm.scaletype == 'normalize' or self._gm.scaletype == 'linear' or\ + self._gm.scaletype == 'constantNNormalize' or self._gm.scaletype == 'constantNLinear': + + # Find the min and max vector magnitudes + maxNorm = vectors.GetMaxNorm() + + if maxNorm == 0: + maxNorm = 1.0 + + if self._gm.scaletype == 'normalize' or self._gm.scaletype == 'constantNNormalize': + scaleFactor /= maxNorm + + if self._gm.scaletype == 'linear' or self._gm.scaletype == 'constantNLinear': + minNorm = None + maxNorm = None + + noOfComponents = vectors.GetNumberOfComponents() + for i in range(0, vectors.GetNumberOfTuples()): + norm = vtk.vtkMath.Norm(vectors.GetTuple(i), noOfComponents) + + if (minNorm is None or norm < minNorm): + minNorm = norm + if (maxNorm is None or norm > maxNorm): + maxNorm = norm + + if maxNorm == 0: + maxNorm = 1.0 + + scalarArray = vtk.vtkDoubleArray() + scalarArray.SetNumberOfComponents(1) + scalarArray.SetNumberOfValues(vectors.GetNumberOfTuples()) + + oldRange = maxNorm - minNorm + oldRange = 1.0 if oldRange == 0.0 else oldRange + + # New range min, max. + newRangeValues = self._gm.scalerange + newRange = newRangeValues[1] - newRangeValues[0] + + for i in range(0, vectors.GetNumberOfTuples()): + norm = vtk.vtkMath.Norm(vectors.GetTuple(i), noOfComponents) + newValue = (((norm - minNorm) * newRange) / oldRange) + newRangeValues[0] + scalarArray.SetValue(i, newValue) + polydata.GetPointData().SetScalars(scalarArray) + + # Scale to vector magnitude: + # NOTE: Currently we compute our own scaling factor since VTK does + # it by clamping the values > max to max and values < min to min + # and not remap the range. + glyphFilter.SetScaleModeToScaleByScalar() + + glyphFilter.SetScaleFactor(scaleFactor) mapper = vtk.vtkPolyDataMapper() diff --git a/Packages/vcs/vcs/vector.py b/Packages/vcs/vcs/vector.py index acea94c047..2c49bfd30d 100755 --- a/Packages/vcs/vcs/vector.py +++ b/Packages/vcs/vcs/vector.py @@ -131,7 +131,7 @@ def process_src(nm, code): class Gv(object): """ - Class: Gv # Vector + Class: Gv # Vector Description of Gv Class: The vector graphics method displays a vector plot of a 2D vector field. Vectors @@ -145,76 +145,76 @@ class Gv(object): entry. Other Useful Functions: - a=vcs.init() # Constructor - a.show('vector') # Show predefined vector graphics methods - a.show('line') # Show predefined VCS line objects - a.setcolormap("AMIP") # Change the VCS color Map - a.vector(s1, s2, v,'default') # Plot data 's1', and 's2' with vector 'v' + a=vcs.init() # Constructor + a.show('vector') # Show predefined vector graphics methods + a.show('line') # Show predefined VCS line objects + a.setcolormap("AMIP") # Change the VCS color Map + a.vector(s1, s2, v,'default') # Plot data 's1', and 's2' with vector 'v' and 'default' template - a.update() # Updates the VCS Canvas at user's request - a.mode=1, or 0 # If 1, then automatic update, else if + a.update() # Updates the VCS Canvas at user's request + a.mode=1, or 0 # If 1, then automatic update, else if 0, then use update function to update the VCS Canvas. Example of Use: a=vcs.init() To Create a new instance of vector use: - vc=a.createvector('new','quick') # Copies content of 'quick' to 'new' - vc=a.createvector('new') # Copies content of 'default' to 'new' + vc=a.createvector('new','quick') # Copies content of 'quick' to 'new' + vc=a.createvector('new') # Copies content of 'default' to 'new' To Modify an existing vector use: vc=a.getvector('AMIP_psl') - vc.list() # Will list all the vector attribute values - vc.projection='linear' # Can only be 'linear' + vc.list() # Will list all the vector attribute values + vc.projection='linear' # Can only be 'linear' lon30={-180:'180W',-150:'150W',0:'Eq'} vc.xticlabels1=lon30 vc.xticlabels2=lon30 - vc.xticlabels(lon30, lon30) # Will set them both + vc.xticlabels(lon30, lon30) # Will set them both vc.xmtics1='' vc.xmtics2='' - vc.xmtics(lon30, lon30) # Will set them both + vc.xmtics(lon30, lon30) # Will set them both vc.yticlabels1=lat10 vc.yticlabels2=lat10 - vc.yticlabels(lat10, lat10) # Will set them both + vc.yticlabels(lat10, lat10) # Will set them both vc.ymtics1='' vc.ymtics2='' - vc.ymtics(lat10, lat10) # Will set them both + vc.ymtics(lat10, lat10) # Will set them both vc.datawc_y1=-90.0 vc.datawc_y2=90.0 vc.datawc_x1=-180.0 vc.datawc_x2=180.0 - vc.datawc(-90, 90, -180, 180) # Will set them all + vc.datawc(-90, 90, -180, 180) # Will set them all xaxisconvert='linear' yaxisconvert='linear' - vc.xyscale('linear', 'area_wt') # Will set them both + vc.xyscale('linear', 'area_wt') # Will set them both Specify the line style: - vc.line=0 # Same as vc.line='solid' - vc.line=1 # Same as vc.line='dash' - vc.line=2 # Same as vc.line='dot' - vc.line=3 # Same as vc.line='dash-dot' - vc.line=4 # Same as vc.line='long-dot' + vc.line=0 # Same as vc.line='solid' + vc.line=1 # Same as vc.line='dash' + vc.line=2 # Same as vc.line='dot' + vc.line=3 # Same as vc.line='dash-dot' + vc.line=4 # Same as vc.line='long-dot' Specify the line color of the vectors: - vc.linecolor=16 # Color range: 16 to 230, default line color is black - vc.linewidth=1 # Width range: 1 to 100, default size is 1 + vc.linecolor=16 # Color range: 16 to 230, default line color is black + vc.linewidth=1 # Width range: 1 to 100, default size is 1 Specify the vector scale factor: - vc.scale=2.0 # Can be an integer or float + vc.scale=2.0 # Can be an integer or float Specify the vector alignment: - vc.alignment=0 # Same as vc.alignment='head' - vc.alignment=1 # Same as vc.alignment='center' - vc.alignment=2 # Same as vc.alignment='tail' + vc.alignment=0 # Same as vc.alignment='head' + vc.alignment=1 # Same as vc.alignment='center' + vc.alignment=2 # Same as vc.alignment='tail' Specify the vector type: - vc.type=0 # Same as vc.type='arrow head' - vc.type=1 # Same as vc.type='wind barbs' - vc.type=2 # Same as vc.type='solid arrow head' + vc.type=0 # Same as vc.type='arrow head' + vc.type=1 # Same as vc.type='wind barbs' + vc.type=2 # Same as vc.type='solid arrow head' Specify the vector reference: - vc.reference=4 # Can be an integer or float + vc.reference=4 # Can be an integer or float """ __slots__ = [ 'name', @@ -244,6 +244,9 @@ class Gv(object): 'type', 'reference', 'colormap', + 'scaleoptions', + 'scaletype', + 'scalerange', '_name', '_xaxisconvert', '_yaxisconvert', @@ -270,9 +273,13 @@ class Gv(object): '_type', '_reference', '_colormap', + '_scaleoptions', + '_scaletype', + '_scalerange', ] colormap = VCS_validation_functions.colormap + scaleoptions = ('off', 'constant', 'normalize', 'linear', 'constantNNormalize', 'constantNLinear') def _getname(self): return self._name @@ -528,6 +535,30 @@ def _setalignment(self, value): self._alignment = value alignment = property(_getalignment, _setalignment) + def _getscaletype(self): + return self._scaletype + + def _setscaletype(self, value): + value = VCS_validation_functions.checkInStringList(self, + 'scaletype', + value, + self.scaleoptions) + self._scaletype = value + scaletype = property(_getscaletype, _setscaletype) + + def _getscalerange(self): + return self._scalerange + + def _setscalerange(self, value): + value = VCS_validation_functions.checkListOfNumbers(self, + 'scalerange', + value, + minvalue=0.0, + minelements=2, + maxelements=2) + self._scalerange = value + scalerange = property(_getscalerange, _setscalerange) + def __init__(self, Gv_name, Gv_name_src='default'): # # ########################################################### @@ -568,6 +599,8 @@ def __init__(self, Gv_name, Gv_name_src='default'): self._datawc_timeunits = "days since 2000" self._datawc_calendar = 135441 self._colormap = None + self._scaletype = self.scaleoptions[4] + self._scalerange = [0.1, 1.0] else: if isinstance(Gv_name_src, Gv): Gv_name_src = Gv_name_src.name @@ -583,7 +616,9 @@ def __init__(self, Gv_name, Gv_name_src='default'): 'datawc_x2', 'xaxisconvert', 'yaxisconvert', 'line', 'linecolor', 'linewidth', 'datawc_timeunits', 'datawc_calendar', 'colormap', - 'scale', 'alignment', 'type', 'reference']: + 'scale', 'alignment', 'type', 'reference', 'scaletype', + 'scalerange']: + setattr(self, att, getattr(src, att)) # Ok now we need to stick in the elements vcs.elements["vector"][Gv_name] = self @@ -660,6 +695,8 @@ def list(self): print "alignment = ", self.alignment print "type = ", self.type print "reference = ", self.reference + print "scaletype = ", self.scaletype + print "scalerange = ", self.scalerange ########################################################################## # # @@ -798,6 +835,9 @@ def script(self, script_filename=None, mode=None): fp.write("%s.linecolor = %s\n" % (unique_name, self.linecolor)) fp.write("%s.linewidth = %s\n" % (unique_name, self.linewidth)) fp.write("%s.scale = %s\n" % (unique_name, self.scale)) + fp.write("%s.scaletype = %s\n" % (unique_name, self.scaletype)) + fp.write("%s.scalerange = %s\n" % (unique_name, self.scalerange)) + fp.write("%s.scaleoptions = %s\n" % (unique_name, self.scaleoptions)) fp.write("%s.alignment = '%s'\n" % (unique_name, self.alignment)) fp.write("%s.type = '%s'\n" % (unique_name, self.type)) fp.write("%s.reference = %s\n\n" % (unique_name, self.reference)) @@ -814,5 +854,5 @@ def script(self, script_filename=None, mode=None): ############################################################################### -# END OF FILE # +# END OF FILE # ############################################################################### diff --git a/testing/vcs/CMakeLists.txt b/testing/vcs/CMakeLists.txt index 09910f4a3e..bee8b9a45f 100644 --- a/testing/vcs/CMakeLists.txt +++ b/testing/vcs/CMakeLists.txt @@ -904,6 +904,16 @@ cdat_add_test(test_vcs_settings_color_name_rgba ${cdat_SOURCE_DIR}/testing/vcs/test_vcs_vectors_robinson_wrap.py "${BASELINE_DIR}/test_vcs_vectors_robinson_wrap.png" ) + cdat_add_test(test_vcs_vectors_scale_options + "${PYTHON_EXECUTABLE}" + ${cdat_SOURCE_DIR}/testing/vcs/test_vcs_vectors_scale_options.py + "${BASELINE_DIR}/test_vcs_vectors_scale_options_off.png" + "${BASELINE_DIR}/test_vcs_vectors_scale_options_constant.png" + "${BASELINE_DIR}/test_vcs_vectors_scale_options_linear.png" + "${BASELINE_DIR}/test_vcs_vectors_scale_options_normalize.png" + "${BASELINE_DIR}/test_vcs_vectors_scale_options_constantNLinear.png" + "${BASELINE_DIR}/test_vcs_vectors_scale_options_constantNNormalize.png" + ) endif() endif() diff --git a/testing/vcs/test_vcs_dump_json.json b/testing/vcs/test_vcs_dump_json.json index b79b1319c3..d408449871 100644 --- a/testing/vcs/test_vcs_dump_json.json +++ b/testing/vcs/test_vcs_dump_json.json @@ -1,6 +1,6 @@ { "G1d": { - "Charles.Doutriaux": { + "vcs_instance": { "colormap": null, "datawc_calendar": 135441, "datawc_timeunits": "days since 2000", @@ -29,7 +29,7 @@ } }, "Gfb": { - "Charles.Doutriaux": { + "vcs_instance": { "boxfill_type": "linear", "color_1": 16, "color_2": 239, @@ -70,7 +70,7 @@ } }, "Gfi": { - "Charles.Doutriaux": { + "vcs_instance": { "colormap": null, "datawc_calendar": 135441, "datawc_timeunits": "days since 2000", @@ -110,7 +110,7 @@ } }, "Gfm": { - "Charles.Doutriaux": { + "vcs_instance": { "colormap": null, "datawc_calendar": 135441, "datawc_timeunits": "days since 2000", @@ -153,7 +153,7 @@ } }, "Gi": { - "Charles.Doutriaux": { + "vcs_instance": { "angle": [ 35.0 ], @@ -210,8 +210,50 @@ "yticlabels2": "*" } }, + "Gv": { + "vcs_instance": { + "alignment": "center", + "colormap": null, + "datawc_calendar": 135441, + "datawc_timeunits": "days since 2000", + "datawc_x1": 1e+20, + "datawc_x2": 1e+20, + "datawc_y1": 1e+20, + "datawc_y2": 1e+20, + "line": null, + "linecolor": null, + "linewidth": null, + "projection": "linear", + "reference": 1e+20, + "scale": 1.0, + "scaleoptions": [ + "off", + "constant", + "normalize", + "linear", + "constantNNormalize", + "constantNLinear" + ], + "scalerange": [ + 0.1, + 1.0 + ], + "scaletype": "constantNNormalize", + "type": "arrows", + "xaxisconvert": "linear", + "xmtics1": "", + "xmtics2": "", + "xticlabels1": "*", + "xticlabels2": "*", + "yaxisconvert": "linear", + "ymtics1": "", + "ymtics2": "", + "yticlabels1": "*", + "yticlabels2": "*" + } + }, "P": { - "Charles.Doutriaux": { + "vcs_instance": { "box1": { "line": "default", "priority": 1, @@ -579,7 +621,7 @@ } }, "Proj": { - "Charles.Doutriaux": { + "vcs_instance": { "parameters": [ 1e+20, 1e+20, @@ -601,7 +643,7 @@ } }, "Tf": { - "Charles.Doutriaux": { + "vcs_instance": { "color": [ 1 ], @@ -632,7 +674,7 @@ } }, "Tl": { - "Charles.Doutriaux": { + "vcs_instance": { "color": [ 1 ], @@ -662,7 +704,7 @@ } }, "Tm": { - "Charles.Doutriaux": { + "vcs_instance": { "color": [ 1 ], @@ -691,7 +733,7 @@ } }, "To": { - "Charles.Doutriaux": { + "vcs_instance": { "angle": 0, "halign": 0, "height": 14, @@ -700,7 +742,7 @@ } }, "Tt": { - "Charles.Doutriaux": { + "vcs_instance": { "backgroundcolor": 0, "backgroundopacity": 0, "color": 1, diff --git a/testing/vcs/test_vcs_dump_json.py b/testing/vcs/test_vcs_dump_json.py index aca6215b89..421606c4d8 100644 --- a/testing/vcs/test_vcs_dump_json.py +++ b/testing/vcs/test_vcs_dump_json.py @@ -1,33 +1,35 @@ import filecmp import vcs,numpy,os,sys -src=sys.argv[1] +src = sys.argv[1] if os.path.exists("test_vcs_dump_json.json"): os.remove("test_vcs_dump_json.json") -b = vcs.createboxfill("Charles.Doutriaux") +b = vcs.createboxfill("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.createisofill("Charles.Doutriaux") +b = vcs.createisofill("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.createisoline("Charles.Doutriaux") +b = vcs.createisoline("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.createmeshfill("Charles.Doutriaux") +b = vcs.createmeshfill("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.create1d("Charles.Doutriaux") +b = vcs.create1d("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.createfillarea("Charles.Doutriaux") +b = vcs.createfillarea("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.createtext("Charles.Doutriaux") +b = vcs.createvector("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.createline("Charles.Doutriaux") +b = vcs.createtext("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.createmarker("Charles.Doutriaux") +b = vcs.createline("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.createtemplate("Charles.Doutriaux") +b = vcs.createmarker("vcs_instance") b.script("test_vcs_dump_json","a") -b = vcs.createprojection("Charles.Doutriaux") +b = vcs.createtemplate("vcs_instance") +b.script("test_vcs_dump_json","a") +b = vcs.createprojection("vcs_instance") b.script("test_vcs_dump_json","a") -assert(filecmp.cmp("test_vcs_dump_json.json",src)) +assert(filecmp.cmp("test_vcs_dump_json.json", src)) diff --git a/testing/vcs/test_vcs_vectors_scale_options.py b/testing/vcs/test_vcs_vectors_scale_options.py new file mode 100644 index 0000000000..32898d129f --- /dev/null +++ b/testing/vcs/test_vcs_vectors_scale_options.py @@ -0,0 +1,59 @@ +import sys, cdms2, vcs, testing.regression as regression + +data = cdms2.open(vcs.sample_data+"/clt.nc") +v = data['v'][...,::10,::10] +u = data['u'][...,::10,::10] + +canvas = regression.init() +gv = vcs.createvector() + +gv.scaletype = 'off' +canvas.plot(u, v, gv) +outFilename = 'test_vcs_vectors_scale_options_off.png' +canvas.png(outFilename) +ret = regression.check_result_image(outFilename, sys.argv[1]) +canvas.clear() + +v = data['v'][...,::4,::4] +u = data['u'][...,::4,::4] +gv.scaletype = 'constant' +gv.scale = 0.1 +canvas.plot(u, v, gv) +outFilename = 'test_vcs_vectors_scale_options_constant.png' +canvas.png(outFilename) +ret += regression.check_result_image(outFilename, sys.argv[2]) +canvas.clear() + +v = data['v'] +u = data['u'] +gv.scale = 1.0 + +gv.scaletype = 'linear' +canvas.plot(u, v, gv) +outFilename = 'test_vcs_vectors_scale_options_linear.png' +canvas.png(outFilename) +ret += regression.check_result_image(outFilename, sys.argv[3]) +canvas.clear() + +gv.scaletype = 'normalize' +canvas.plot(u, v, gv) +outFilename = 'test_vcs_vectors_scale_options_normalize.png' +canvas.png(outFilename) +ret += regression.check_result_image(outFilename, sys.argv[4]) +canvas.clear() + +gv.scaletype = 'constantNLinear' +canvas.plot(u, v, gv) +outFilename = 'test_vcs_vectors_scale_options_constantNLinear.png' +canvas.png(outFilename) +ret += regression.check_result_image(outFilename, sys.argv[5]) +canvas.clear() + +gv.scaletype = 'constantNNormalize' +canvas.plot(u, v, gv) +outFilename = 'test_vcs_vectors_scale_options_constantNNormalize.png' +canvas.png(outFilename) +ret += regression.check_result_image(outFilename, sys.argv[6]) +canvas.clear() + +sys.exit(ret)