Creating And Modifying Tools

Edit Action Button-3 Pop-Up Menu
Creating A New Tool
Description of Tool Arguments
Description of VariableList
Additional Smart Tool Methods
Creating a Smart Tool from an Existing File
Smart Script Class Library


Creating and Modifying Tools

Now we will look behind the scenes to see how Smart Tools work, how they can be modified and created. The following diagram depicts the Smart Tool architecture within the GFESuite software. It shows Smart Tools as meteorological algorithms which modify the Forecast database. These algorithms are written in a simple, intuitive language called Python and have access to Numerical Models, Observations, and Topography data. The Smart Script Library is a set of convenient library functions available to simplify the job of tools. Through it, Smart Tools access data, create soundings, and perform unit conversion. The GFESuite software locates the tool, sets up information to be passed to it, and calls it to obtain revised grid values.


In the following sections, we will see how to create and modify tools and procedures through the Edit Action Dialog. We will demonstrate the use of the Smart Tool Framework and Smart Script Library through a set of exercises and tutorials. The following sections assume that you have knowledge of Python and its Numpy extension which are covered in the GFESuite Python Tutorial and Programming Guidelines.


Edit Action Button 3 Pop-up Menu

In AWIPS1, the Edit Actions dialog was used to access new tool creation functionality. Pressing MB3 over each of the Smart Tools listed in the Edit Actions Dialog opens a pop-up menu. This menu is described in the Editable Listbox description. However, in AWIPS2, new tools are created via the Localization Perspective. This perspective will open when you select GFE -> Define Smart Tools from the Menu bar.

Creating a New Tool

        Exercise Tool-1 -- Creating a New Tool

The following exercises will show you how to create and modify tools. We will build a tool to produce SnowAmt grid values. We will proceed in steps, building more functionality into the Tool at each step.

Exercise Tool-1 -- Creating a new tool

Create a Smart Tool to yield a SnowAmt equal to the current QPF value multiplied by 10.
  1. Select New... on the MB3 popup over the Smart Tools folder. The New Tool Dialog appears into which you will enter:
  1. Press OK and a Python Editor will appear with a template Python module for your Tool.
  1. Remove the sample arguments from the "execute" definition, leave the "self" argument, and enter the argument, QPF in the parentheses. When the tool executes, this argument will contain a Numeric Array representing the Fcst QPF grid.
  2. Type in a description of your tool below the "execute" definition. This description will appear when you click MB3 --> Info over your new tool.
  3. Insert a line to multiply the QPF values by 10:
  4.     SnowAmt = QPF * 10
    This line produces a Numeric Array named "SnowAmt" which has values 10 times the corresponding QPF values.
  5. Save the Python module. Select File-->Save.
  6. Execute the new tool to see that it works. If you encounter errors when executing your tool, a dialog will appear to help you locate the problem. After successfully executing the tool, the QPF grid appears. Examine the data by sampling the QPF values and the SnowAmt values. If your results seem confusing, check to see that the QPF and SnowAmt grids are aligned in the Grid Manager. If not, you will be getting time averaged QPF values over multiple grids.
  7. Close the Python Editor. Select File-->Close.

Description of Tool Arguments

Here is a complete description of the various arguments that you can receive into your Smart Tool methods. To receive a particular argument, simply list it between the parentheses in the method definition. For example:
    def execute(self, QPF, Topo, SnowAmt_DeltaValue, FzLevel, varDict):
  • <weName> -- the value for a given weather element. If you want to base the new value for an element on its current one, you must include it in the argument list. You may also include other weather elements. A Numeric Array representing the grid values for the weather element is returned. Weather element values are as follows:

    • Scalar value: floating point values,
    • Vector value: two-tuple of magnitude and direction numeric arrays, for example:

    •    mag = Wind[0]
         dir = Wind[1]
    • Weather value: A Wx argument represents a 2-tuple:
      • wxValues : numerical grid of byte values
      • keys : list of "ugly strings" where the index of the ugly string corresponds to the byte value in the wxValues grid.


    Weather elements do not need to be loaded in the GFE to be accessible to Smart Tools. The weather element is assumed to come from the Fcst database unless you specify a more complete name of the form:

           elementName_level_siteID_type_model_modeltime

    The "type" is an empty string for GFE Fcst data and is "D2D" for D2D data.

           MaxT_SFC_BOU__NAM12_Mar2912  :              gets MaxT from the March 29 12Z NAM12 run created by GFE initialization.
           MaxT_SFC_BOU_D2D_NAM12_Mar2912  :  gets MaxT from the March 29 12Z original NAM12 run from D2D.

    If you omit the "modeltime", the most recent model run will be selected. For example:

           MaxT_SFC_BOU__NAM12  :              gets MaxT from the most recent NAM run created by GFE initialization.
           MaxT_SFC_BOU_D2D_NAM12 :   gets MaxT from the most recent original NAM12 run from D2D.
           rh_MB300_BOU_D2D_GFS40 :    gets rh from the most recent original GFS40 run from D2D.

    For a list of D2D elements and descriptions click here.

    If you request a weather element value other than the one you are editing, it is possible that the system will encounter multiple corresponding grids. For example, if you are editing a 6-hour QPF grid, there may be multiple Wind grids that fall within the QPF time range. In this case, the system will automatically return the time-weighted average value of the multiple grids. This means that the values in a 3-hour Wind grid will be weighted 3 times more heavily than those in a 1-hour grid when calculating the average value. Only the portion of the grid that overlaps the edited grid will be counted in the time weighting. Time-weighted average for a Weather-type element is defined as the combination of all weather values encountered.

    Smart Script Class Library:   For more advanced methods of accessing grids in your Smart Tool, see the section below on the Smart Script Class Library. In this library you will find more sophisticated processing. For example, if you are not satisfied with a time-weighted average of grids over a time range, you can get a sum, max/min or even the actual grid values themselves. Or, if you do not know ahead of time which model you will want to access, you can create a call at execution time to get the grids you want.

  • Topo -- this will give you the topography information in the form of a numeric array of elevation values.
  • variableElement-- the numeric array for the editable weather element in the GFE.
  • WEname -- the name of the element being edited. This is handy if your tool works on "variableElement". WEname will be the element edited for this run of the tool e.g. T, Wind, Td, etc.
  • <weName>_PickUpValue -- the PickUp Value for the given element where <weName> could be "variableElement", e.g. MaxT_PickUpValue, variableElement_PickUpValue
  • <weName>_DeltaValue -- the Delta Value for the given element
  • <weName>_FuzzValue -- the Fuzz Value for the given element
  • GridTimeRange -- The time range for the current grid being modified. Example:

  • def execute(self, GridTimeRange):
        gridStartTime = GridTimeRange.startTime()
        hour = gridStartTime.hour()
        if hour == 18:
            # Now you know the grid is valid at 18Z

      There are many methods for working with time ranges. Here's the basic methods:

    •     GridTimeRange.startTime()
    •     GridTimeRange.endTime()
    •     GridTimeRange.duration() # In seconds

    The start and end times are TimeRange objects.

    Here's an example of creating your own time range relative to the given GridTimeRange:

    def execute(self, GridTimeRange):
       import TimeRange
       start = GridTimeRange.startTime()
       end = GridTimeRange.endTime()
       timeRange2 = TimeRange.TimeRange(start - 24 *3600, end - 24 *3600)

       T0 = self.getGrids("Fcst", "T","SFC", timeRange2, noDataError=0)
       if T0 is None:
          self.noData()

    NOTE: Values supplied to the tool are initial values only i.e. the values of the variables at the beginning of Smart Tool execution. Changes made by the Smart Tool will not be reflected in the values of the variables passed to the tool.

    Exercise Tool-2 -- Modifying a Tool

    Modify your SnowAmt_LearningTool to base its new value on temperature values. Use the following table:

    T
    Multiply QPF by
    < 20
    18
    between 20 and 25
    14
    over 25
    10
    1. Select Open... on the MB3 popup over the SnowAmt_LearningTool at USER level (drill-down). This opens the Python Editor in which you can make changes.
    2. You will need the corresponding T grid, so add this as an argument to your method. Modify the Python code with where statments to implement the Temperature-based table.
    3. Save the file. You can try your Tool without closing the editor or restarting the GFE. The changes are effective as soon as you Save. Verify that the tool worked by Sampling the SnowAmt and QPF grid values.

    Description of VariableList

    Exercise Tool-3 -- Using VariableLists
    Creating VariableLists "On-the-fly"

    The VariableList defines values to be supplied to a Tool by the user. It consists of a list of tuples defining each variable and resides in the Python file for the Tool. (Make sure it appears before or after the Tool Class, not within it!) These variables will be solicited from the user prior to executing the Tool.
    For example, suppose we want to let the user set threshold values prior to executing our tool named MyTool. Then in the Python module for MyTool, we list:

    VariableList = [
                ("Threshold Value1", 10, "numeric"),
                ("Threshold Value2", 20, "numeric")
        ]
    The values, 10 and 20, will be used as the default values. The method call within MyTool must then include the argument, "varDict", and can access the values as follows:
        def execute(self, QPF, Wind, QPF_DeltaValue, x, y, varDict):
             # Get the values for the variables:
            threshold1 = varDict["Threshold Value1"]
            threshold2 = varDict["Threshold Value2"]
    The Variable tuple in the VariableList is in the format:
      ("Variable Name", default value, variable type, optional list)
    Variables can be of many types:
    • "numeric" -- will ensure the user enters a number
    • "alphaNumeric" -- will accept any characters and will automatically convert the string to a number if possible
    • "radio" -- a set of Radio buttons i.e. one and only one choice must be made by the user among a list of values. In this case, a list of values must be supplied.
    • "check" -- a set of Check buttons i.e. the user may select zero of more values from a list. In this case a list of values must be supplied and the default value is a list of values to be checked "On" initially. The returned value for such a variable in varDict is a list of values that were checked "On" when the user selected "OK."
    • "scale" -- a slider-bar type Scale. In this case, a list containing the minimum and maximum values must be supplied along with the default value. You can also specify a resolution if you wish. The default is 1.
    • "model" -- gives a list of GFE Surface model runs e.g. NAM12, GFS40, RUC80
    • "D2D_model" -- give a list of D2D model runs e.g. NAM12, GFS40, RUC80
    • "label" -- simply displays text given in the "Variable Name" slot.
    • "scrollbar" -- adds a scrollbar to the VariableList dialog. This is intended for dialogs that have become too large for the screen.  A desired height for the dialog must be given.  For example:
    •                ("", 500, "scrollbar")
      will add a scrollbar to the dialog and set the dialog height to 500 pixels. Only one scrollbar entry is processed per VariableList dialog.

    Exercise Tool-3 -- Using VariableLists

    Modify your SnowAmt_LearningTool to adjust the QPF only in areas above a user-given elevation. To perform this exercise, we will need to define a variable for the user to input at execution time. Remember to include "varDict" in your argument list to get the value for the user's variable. You will also need to access the variable, Topo.

    Click here for Answer to Exercise Tool-3.


    Creating VariableLists "On-the-fly"

    It is possible to create VariableLists and display dialogs from a Smart Tool or Procedure in the event that the VariableList entries are dependent on run-time values. To do so, you must import the "ProcessVariableList" class and use it to display your dialog. The following example illustrates how this might be done:

          ToolType = "numeric"
          WeatherElementEdited = "variableElement"
          ScreenList = ["SCALAR"]
          from numpy import *

          import ProcessVariableList
          import SmartScript

          class Tool (SmartScript.SmartScript):
               def __init__(self, dbss):
                   SmartScript.SmartScript.__init__(self, dbss)

               def preProcessTool(self):
                   # This is necessary so that we show the
                   # dialog once per tool, not once per grid.
                   self._dialogShown = 0

               def execute(self, variableElement_GridInfo):
                   if self._dialogShown == 0:
                      minval=variableElement_GridInfo.minLimit()
                      maxval=variableElement_GridInfo.maxLimit()
                      variableList=[ ("Modify value to:",0,"scale",[minval,maxval])]
                      varDict = {}
                      processVarList = ProcessVariableList.ProcessVariableList(
                                                    "Title", variableList, varDict,  parent = self.eaMgr().root())
                      status = processVarList.status()
                      if status != "Ok":
                            self.cancel()
                      self._modifyValue = varDict["Modify value to:"]
                      self._dialogShown = 1

                   variableElement = self._empty +  self._modifyValue
                   return variableElement


    Additional Smart Tool Methods

    Reserved Methods
    Creating Your Own Methods
    Naming Conventions

    Your Smart Tool class can contain methods in addition to the mandatory "execute" method.

    Reserved Methods

    Some methods have reserved names and meanings. To understand the framework, recall that a Smart Tool may operate over a time range which may include multiple grids. The mandatory "execute" method is called for each grid to be modified. Sometimes, you may wish to set up information before or after grid-by-grid processing occurs. If so, you may use the following methods:
    • preProcessTool:   Called once at beginning of the Tool, before any grids are processed.
    • postProcessTool:   Called once at end of the Tool, after all grids are processed.
    Additional methods, "preProcessGrid" and "postProcessGrid" are reserved for Point-based Tools. Of course, reserved methods will be called only if you supply them in your tool. They are included as comments in the Smart Tool template that appears when you create a new tool and are documented there. The possible arguments to these methods will vary according to the logic of when they are called.

    Creating Your Own Methods

    You may write your own methods and call them from within others. The only tricky part is in using the funny "self" variable. Here's an example:

    import SmartScript
    class Tool (SmartScript.SmartScript):
       def __init__(self, dbss):
          SmartScript.SmartScript.__init__(self, dbss)
     
        def execute(self, QPF, T):
            "Tool to calculate SnowAmt"
     
            # Determine new value
            SnowRatio = self._getSnowRatio(T)
            SnowAmt = QPF * SnowRatio
     
            # Return the new value
            return SnowAmt
     
        def _getSnowRatio(self, T):
             SnowRatio = where( less(T, 20), 18,
                                                  where( less(T, 21), 14, 10)
             return SnowRatio
    Notice that we did two things:
    • Included "self" as the first argument in the definition of our new method, _getSnowRatio.
    • Used the prefix "self" when calling the method WITHOUT putting "self" in the argument list.

    Naming Conventions

    To help distinguish the source of methods and variables, Smart Tools (and Procedures) follow these naming conventions:
  • User-defined Smart Tool and Procedure methods and variables, which need to span multiple methods, are preceeded by an underscore: e.g self._modifyValue, def _myMethod.
  • SmartScript library methods do not have a preceeding underscore: e.g. self.getGrids.
  • The Reserved methods, e.g. execute, preProcessTool, do not have a preceeding underscore.
  • Utility methods (see Utility section), are preceeded by an underscore.
  • These conventions are important to follow not only for clarity, but to insure that you do not inadvertently override an existing library method by giving your method or variable a duplicate name.

    Creating a Smart Tool from an Existing File

    Suppose you have an existing smart tool file. You might receive a file from a repository, from a colleague, or find one in the examples directories of your release. How can you add it to your Edit Action Dialog?
    1. From the Localization Perspective Smart Tool folder, Select MB3 --> New...
    2. Enter the name of the Tool and the weather element it modifies.
    3. Select OK and a template for the Tool should appear.
    4. Open another window with the file that contains the existing tool via Select File-->Open and choose the file.
    5. Copy and Paste the existing smart tool into the new smart tool window and Save it. Select File-->Save.
    6. Close the Python windows. Select File-->Close.

    Smart Script Class Library

    Exercise SmartScript-1 -- Accessing Grids Directly
    Exercise SmartScript-2 -- Accessing Variable Grids Directly
    Exercise SmartScript-3 -- Making and Accessing Soundings
    Exercise SmartScript-4 -- Making and Accessing Soundings
    Exercise SmartScript-5 -- Making and Accessing Soundings
    Exercise SmartScript-6 -- Creating Elements On-the-fly
    Exercise SmartScript-7 -- Working with Weather and Discrete
    Exercise SmartScript-8 -- Working with Weather and Discrete
    Exercise SmartScript-9 -- Translating between Smart Initialization and Smart Tools
    Smart Script Class Library documentation.

    Notice that all the Smart Tools we've created begin with the following lines:

      import SmartScript
       
      class Tool (SmartScript.SmartScript):
         def __init__(self, dbss):
              SmartScript.SmartScript.__init__(self, dbss)
    These lines define a class, Tool, that "inherits" from an existing SmartScript class. This means that all the methods in the SmartScript class are available to your tool. As an example, there is a method in the SmartScript class named "convertFtoK" which converts Fahrenheit degrees to Kelvin. You would call it from within one of your tool methods as follows:
    def execute(self, T):
            degreesK = self.convertFtoK(T)
    Notice that we used the prefix, "self." to tell the system that this method belongs to my class (which inherited it from the SmartScript class). Procedures begin in a similar way with a class, Procedure, instead of Tool, and the same methods are available to them.

    The SmartScript class contains methods for:

  • accessing grids directly (This means that it is not necessary to list all the grids you need in the Smart Tool argument list.),
  • creating a sounding and accessing values from it based on elevation,
  • error handling,
  • unit conversion,
  • "procedure" commands e.g. copy, interpolate, createFromScratch, accessing named time ranges and named edit areas, and calling Smart Tools.

  • Exercise SmartScript-1 -- Accessing Grids Directly

    In this exercise, we will access grids directly from the Smart Tool instead of from the argument list.
    1. Study the "getGrids" command in the Smart Script Library.
    2. Return to the Smart Tools Window and create a new smart tool which edits QPF.  Access both the current QPF value and the  most recent  D2D NAM12 tp (total precipitation) value directly using the "getGrids" method. If the current QPF value is zero, assign the tp value to it. Otherwise, return it as is.
    3. Run and test your tool to make sure it works.
    Click here for Answer to Exercise SmartScript-1.

    Exercise SmartScript-2 -- Accessing Variable Grids Directly

    Often, you will want to choose the model you want to work with at run-time.  You can use a variable list to get the model and then access the grids directly.
    1. Modify your tool from Exercise SmartScript-1 to have a variable D2D model from which to get the "tp" value.
    2. Run and test your tool to make sure it works.
    Click here for Answer to Exercise SmartScript-2.

    Exercise SmartScript-3 -- Making and Accessing Soundings

    The SmartScript library allows you to make a sounding of numpy grids. The method, "makeNumericSounding" returns two numerical "cubes" -- one cube for the gh height values for a given set of levels and one cube for the weather parameter values. In this exercise, we will create a sounding from model data and use Topo information to determine the surface temperature. You will use the "makeNumericSounding" method to get gh and t numerical cubes. You will then have to "walk up" the cubes assigning values directly from the t weather parameter at ground height. For a more information, see GFESuite Python Tutorial -- Looking Up the Columns of a Cube.
    1. In the SmartScript class, find the method, "makeNumericSounding". Read the documentation to learn how to call this method.
    2. Find the Unit Conversion methods section and examine the methods available.
    3. Create a new smart tool which edits T. Allow the user to set the D2D model using a VariableList. Assign values from the sounding made from the D2D model grids using the Topo information. You will have to convert Topo from feet to meters and temperature from K to F.
    4. Run and test your tool to make sure it works.
    Click here for Answer to Exercise SmartScript-3.

    Exercise SmartScript-4 -- Making and Accessing Soundings

    To help you understand the concept of Numerical logical statements, rewrite the tool from Exercise SmartScript-3 and simplify the "where" statement by using some logical statements. For more information, see the discussion of Simple and Compound statements in GFESuite Python Tutorial and Programming Guidelines -- Style Suggestions.

    Click here for Answer to Exercise SmartScript-4.


    Exercise SmartScript-5 -- Creating Elements "On-the-Fly"

    It is now possible to create "temporary" weather elements from a Smart Tool and add grids to them. These elements appear in the GridManager but cannot be saved. So if you unload them or shut down the GFE, they are gone. They can be accessed by subsequent Smart Tools.
    Suppose we want to create a temporary RH element from T an Td. Look at the documentation for the SmartScript method, "createGrid." The first time this is called during a GFE session a new model and element can be created. Subsequent calls will simply add grids to the element.

    Now, try your hand at writing a tool to create a new "TempRH" element. Select your own model name and use the equations in the RH_Tool (found in the Edit Action Dialog). When you create your tool, select "None" for the element edited. Use the "ScreenList" that appears in the tool template to specify how it will be displayed within the Edit Action Dialog. Instead of returning the resulting grid, your tool will call the "createGrid" command.

    Now you should be able to access this element in other tools with the "getGrids" command using your model name, element name, and "SFC" for the level. For example:

         RH = self.getGrids("TempModel","TempRH", "SFC",GridTimeRange)

    Click here for Answer to Exercise SmartScript-5.


    Exercise SmartScript-6 -- Working with Weather/Discrete

    Weather and Discrete are similar to each other. They both are represented as a 2-tuple:
    • wxValues : numerical grid of byte values
    • keys : list of "ugly strings" where the index of the ugly string corresponds to the byte value in the wxValues grid for the WEATHER type of grid, and a list of "strings" where the index of the string corresponds to the byte value in the wxValues grid for the DISCRETE type of grid.
    For a complete description, see Working with Weather Data in the GFESuite Python Tutorial and Working with Discrete Data in the GFESuite Python Tutorial. Two particularily useful functions are getIndex() and wxMask() for Weather data, and getIndex() and discreteMask() for Discrete data.

    Use the SmartScript method "getIndex" to assign weather values based on PoP according to the following criteria:

  • where PoP < 15,  assign "<NoCov>:<NoWx>:<NoInten>:<NoVis>:"
  • where PoP >=15 and PoP < 35, assign "Chc:R:-:<NoVis>:"
  • where PoP >=35 and PoP < 55, assign "Sct:RW:m:<NoVis>:"
  • where PoP >= 55, assign "Wide:R:+:<NoVis>:"
  • Click here for Answer to Exercise SmartScript-6.

    Here is an example of a Discrete smart tool that converts all gridpoints that have a value of <None> to "BL.W":

    def execute(self, Hazards):
            "Sample tool to put in Blizzard Warnings (VTEC code BL.W)"

            # Determine new value
            grid, key = Hazards
            indexBlizzard = self.getIndex("BL.W", key)
            indexNone = self.getIndex("<None>", key)
            mask = equal(grid, indexNone)
            grid = where(mask, indexBlizzard, grid)
     
             # Return the new value
            return grid, key


    Exercise SmartScript-7 -- Working with Weather/Discrete

    Weather and Discrete are similar to each other.  They both are represented as a 2-tuple of (grid, keys).

    Use the SmartScript method, "wxMask()" to assign PoP values based on Wx coverage according to the following criteria:

  • where coverage is "<NoCov>", assign 0
  • where coverage is "Chc", assign 25
  • where coverage is "Sct", assign 55
  • where coverage is "Wide", assign 80

  • Exercise SmartScript-8 -- Translating Between Smart Initialization and Smart Tools

    Overview of the correlation between Smart Initialization and Smart Tools. For the most part, the code is similar. However, the Smart Tools have access the sounding cubes through the "getNumericSounding" method. Smart Tools are also enhanced to extrapolate and interpolate, but the Smart Initialization code works as well. In general, when going between Smart Initialization and Smart Tools, follow these tips:
    • In a Smart Tool, use "getNumericSounding" to get the sounding value cubes.
    • In a Smart Tool, use "interpolateValues" instead of the Smart Initialization method, "linear". Notice the order of arguments is different.
    • In a Smart Tool, the Topo argument is in feet while the Smart Initialization topo argument has been converted to meters.