Category "iTools"


IDL 8.0 introduces an entirely new graphics interface. Currently, there is no name for this system except Graphics (am I supposed to say it capitalized when talking?), so I will refer to them as function graphics because all the routines are functions instead of procedures as in most direct graphics and iTools graphics routines. This new system is easy to use, interactive, programmatically accessible, and fairly complete even in this first release.

I can only just skim the surface of the capabilities of function graphics in this post. See the online help for more details.

To create a plot using function graphics, do:

IDL> p = plot(findgen(10), color=[255, 0, 0])

The plot can be annotated from the GUI interface, but it can also be modified via the return value p, an object which represents the newly created plot. You can get and set properties of the graphics object easily:

IDL> p.color = 'blue' ; or you could use [0, 0, 255]

The plot line automatically changes to blue.

IDL> print, p.color
0 0 255

From the command line, to find all the available properties of one of the graphics object returned from a function graphics routine, just print it:

IDL> print, p
PLOT <21317>
ANTIALIAS = 1
ASPECT_RATIO = 0.0000000
ASPECT_Z = 0.0000000
BACKGROUND_COLOR = 255 255 255
COLOR = 255 0 0
DEPTH_CUE = 0.00000 0.00000
[etc, rest of output omitted for brevity]

By the way, if you want to refer to colors by name in other uses in IDL, you can use the new !color system variable:

IDL> print, !color.blue
0 0 255

You can see a listing of all of the color names available by using HELP on !color:

IDL> help, !color, /structures
** Structure !COLOR, 147 tags, length=441, data length=441:
ALICE_BLUE BYTE Array[3]
ANTIQUE_WHITE BYTE Array[3]
AQUA BYTE Array[3]
AQUAMARINE BYTE Array[3]
[etc, rest of output omitted for brevity]

Note that function graphics can be used programmatically without any graphics windows appearing on the screen:

IDL> c = contour(dist(200), /buffer)
IDL> c->save, 'contour.png'

See the online help on a particular graphics object type to see its available methods, but most seem to have close, convertCoord, copyWindow, getSelect, order, print, refresh, rotate, save, scale, select, and translate.

The WIDGET_WINDOW routine provides a way to add a function graphics window to a widget application.

One of my favorite features in the new graphics API is that LaTeX escape sequences are built-in! For example,

IDL> p = plot(findgen(10), title='Some formula: $\mu = \epsilon^2 + \tau$')

(UPDATE: there is a TEX2IDL routine in the IDL distribution now that can be used independently of the function graphics routines.)

The following functions are part of the function graphics API: BARPLOT, COLORBAR, LEGEND, CONTOUR, ERRORPLOT, IMAGE, MAP, MAPCONTINENTS, MAPGRID, PLOT, PLOT3D, POLARPLOT, SURFACE, VECTOR, AXIS, ELLIPSE, POLYGON, POLYLINE, STREAMLINE, TEXT, and WINDOW.

There is much, much more to be said about function graphics, but hopefully this will get you started exploring. It will take more time to actually do some work using it to determine its shortcomings, but I think it is the most important feature of IDL 8.0.

Stay tuned for one last wrap-up article about IDL 8.0.

One major problem with the iTools is that controlling them via a script can be quite complicated (and I’m not even talking about extending them, i.e., adding new capabilities to them). IDL 7.1 is a major change in the programmatic iTools API: many routines were renamed, several routines were added, and a couple keywords were added.

The main changes to the API which will cause backward compatibility problems are trivial changes to helper routine names: ITSETCURRENT to ISETCURRENT, ITGETCURRENT to IGETCURRENT, ITDELETE to IDELETE, ITRESET to IRESET, ITREGISTER to IREGISTER, and ITRESOLVE to IRESOLVE. The arguments to the routines remained the same.

The main new routine is IOPEN. It uses the registered file readers to open a given file, optionally sending the data directly to an iTool:

IDL> iopen, file_which('endocell.jpg'), /visualize

Third-party developers can add to the registered file-readers which IOPEN will use (but that is extending the iTools and beyond the scope of what we are talking about here). The ISAVE routine was also added to save the contents of an iTools window to an image file, PostScript file, or iTools SAVE file. For example,

IDL> iplot, findgen(11)
IDL> isave, 'test.ps'

The output format is determined by the extension of the filename.

Properties of iTool components can be set easily:

IDL> iplot, findgen(11)
IDL> isetproperty, 'plot', color=[255, 0, 0]

They can also be retrieved with `IGETPROPERTY.

The IPUTDATA and IGETDATA routines make it much more convenient to get at the data in an iTool. For example, the following grabs the data from an iTool and plots using PLOT:

IDL> iplot, findgen(11)
IDL> igetdata, 'plot', data
IDL> plot, data

Data for an iTool can also be changed:

IDL> iputdata, 'plot', 2. * findgen(11)

The iTool detects the change and automatically updates its display. The iTools equivalent to CONVERT_COORD, ICONVERTCOORD, was also added to convert between coordinate systems.

New routines also allow the current iTool window to be annotated:

IDL> iplot, findgen(11), /scatter, sym_index=1
IDL> itext, 'important point', 5., 3., /data, font_size=9
IDL>        ipolyline, [[4.1, 3.9], [4.9, 3.2]], /data, arrow_style=2, $
IDL>        arrow_size=0.025

For drawing, IELLIPSE, IPOLYGON, IPOLYLINE, and ITEXT were added.

The IROTATE, ITRANSLATE, ISCALE, and IZOOM routines were also added to manipulate the current iTool window.

Updating to IDL 7.1 will probably mean making changes to any code that programmatically controls iTools. But a lot of things that required getting into the guts of the iTools framework before now can be done with a single command.

MGitWriteSparkline is an example of a file writer for the iTools system. It creates a PNG file representing a sparkline for vector data. Sparklines are intense, word-like graphics like Boulder temperature highs 55. Readers and writers are some of the simplest components to add to an iTool and a good place to start learning how to create custom iTools.

MGitWriteSparkline dialog

In order to run this demo, you will need the the MGitWriteSparkline class (doc), the calling program (doc), and the original sparklines routine (doc and see this article about the sparklines routine).

The calling program

The calling program, MG_SPARKWRITER_DEMO, is fairly simple.

iplot, randomu(seed, 50)
id = itGetCurrent(tool=oplot)
oplot->registerFileWriter, 'Sparkline writer', 'MGitWriteSparkline', $
                           description='PNG representation of a sparkline'

It creates an iPlot with some random data, gets the object reference for the iPlot, and registers the file writer with it.

Methods of MGitWriteSparkline

I’ll list all the methods of MGitWriteSparkline with simple descriptions. Most of these methods will be straightforward to write for those with experience with how properties work in the iTools or object graphics systems.

function mgitwritesparkline::init, _ref_extra=e
pro mgitwritesparkline::cleanup

The init method is responsible for registering and initializing properties of the class. This will be discussed in the properties section below. The cleanup method just calls the parent IDLitWriter::cleanup method since we don’t allocate any resources that need to be freed.

pro mgitwritesparkline::getProperty, width=width, height=height, $
                                     color=color, $
                                     transparent_background=transparent_background, $
                                     background=background, $
                                     use_range_band=use_range_band, $
                                     band_color=band_color, $
                                     endpoint_color=endpoint_color, $
                                     _ref_extra=e

pro mgitwritesparkline::setProperty, width=width, height=height, $
                                     color=color, $
                                     transparent_background=transparent_background, $
                                     background=background, $
                                     use_range_band=use_range_band, $
                                     band_color=band_color, $
                                     endpoint_color=endpoint_color, $
                                     _ref_extra=e

In the init, setProperty, and getProperty methods make sure to pass along keywords not expicitly declared to their corresponding IDLitWriter methods. We’ll discuss how to deal with the properties and their attributes in more detail in a section below.

function mgitwritesparkline::setData, oPlotData

This method is special for IDLitWriter subclasses and it is where the real work is done. We’ll discuss it in more detail in a section below.

Properties of MGitWriteSparkline

A property is created in the init method with registerProperty. The name of the property is the positional parameter argument while the NAME gives a human readable version. An optional second position parameter gives the data type of the property. If not present then one of the type keywords must be used. Here, the COLOR keyword indicates the type. Also, the SENSITIVE keyword indicates the user interface should not allow this property to be modified. (The aspects of the property here corresponding to these keywords are called attributes.)

self->registerProperty, 'background', name='Background', $
                        description='Background color of the plot', $
                        /color, sensitive=0

Later, in the getProperty method, a straightforward test using ARG_PRESENT indicates whether the stored value should be passed back. Here we have stored each property in an instance variable of the same name in the object.

if (arg_present(background)) then background = self.background

The setProperty method is more complicated because the value of the TRANSPARENT_BACKGROUND property indicates whether the BACKGROUND property is needed (and hence if it can be set in the user interface). The relevant part for our property BACKGROUND is:

if (n_elements(transparent_background) gt 0) then begin
  self.transparent_background = transparent_background
  self->setPropertyAttribute, 'background', $
                              sensitive=~transparent_background
endif
if (n_elements(background) gt 0) then self.background = background

We use the setPropertyAttribute method to change attributes like SENSITIVE or HIDE.

MGitWriteSparkline::setData method

Finally, the setData method is responsible for getting an iTools data object and creating the output file. The filename of this file is found by using the getFilename method:

filename = self->getFilename()
if (filename eq '') then return, 0

The return value for this function is a status code: 0 for failure, 1 for success.

if (~obj_valid(oPlotData)) then begin
  self->errorMessage, ['Invalid plot data object'], title='Error', $
                      severity=2
  return, 0
endif

The oPlotData parameter is an iTools data object containing the data to be written (i.e. selected by the user in the writing dialog). Here, only type IDLVECTOR data can be used to create a sparkline.

odata = oPlotData->getByType('IDLVECTOR', count=nplots)
if (nplots eq 0) then begin
  self->errorMessage, ['Invalid data provided to file writer'], $
                      title='Error', /severity
endif

The odata return value of the getByType method could contain an array of data objects. Here, we’ll only consider the first one, pulling the data out of it with the getData method.

odata = odata[0]
result = odata->getData(y)
if (result eq 0) then begin
  self->errorMessage, ['Error retrieving plot data'], $
                      title='Error', severity=2
endif

The y variable now holds the data to be written by the mg_sparkline routine.

mg_sparkline, filename, y, xsize=self.width, ysize=self.height, $
              color=self.color, $
              background=keyword_set(self.transparent_background) $
                ? undefined $
                : self.background, $
              band_color=keyword_set(self.use_range_band) $
                ? self.band_color $
                : undefined, $
              endpoint_color=self.endpoint_color

The undefined variable is actually undefined, i.e. n_elements(undefined) eq 0. It is needed here to indicate that we are not passing a value to a particular keyword.

return, 1B

Finally, return 1B to indicate success.

Every component in the iTools has a unique identifier placing it in a hierarchy of components for the tool. This identifier is necessary to programmatically use the component. So how do you find the identifier of some component? The standard technique is to use the IDLitTool::findIdentifiers method. Alternatively, MG_ITBROWSER allows interactive browsing of all the identifiers (and their properties) of an iTool. This is very useful for learning and exploring the components that make up an iTool.

mg_itbrowser

Here is the source code and docs for MG_ITBROWSER.

Once you are done exploring, you’ll want to use the IDLitTool::findIdentifiers method to find identifiers in your code. This method can find identifiers given a search string containing wildcards. For example, to find the “Statistics” operation in an iTool:

IDL> print, otool->findIdentifiers('*statistics*')
/TOOLS/PLOT TOOL/OPERATIONS/OPERATIONS/STATISTICS

There are keywords to narrow the search to a particular class of components (operations, manipulators, visualizations, etc.)

UPDATED 2/1/2007: Fixed a bug that incorrectly displayed hierarchies with multiple windows.

When rendering object graphics atoms with transparency, the order the atoms are rendered (i.e. the order the atoms are created and added to the hierarchy) determines what can be seen. You want to draw the atoms from back to front when the front items are transparent. The same principle holds true for iTools since they use object graphics.

Rendering order

If you add atoms to a model in the wrong order, you can always change the order with the IDL_Container::move method. With the iTools, you can use the BringToFront, SendToBack, BringForward, and SendBackward operations. These are available from the “Edit” menu or the context menu for a visualization. You can use these operations with code as well. First create a surface iTool and get its object reference (we’ll assume data has been read in already).

isurface, d
id = itGetCurrent(tool=otool)

To make the surface transparent, we’ll get its identifier and use the IDLitTool::doSetProperty method.

surfID = otool->findIdentifiers('*surface', /visualization)
result = otool->doSetProperty(surfID[0], 'Transparency', 50)
otool->commitActions

The IDLitTool::commitActions method tells the iTool that it should put any actions (like our property change) into the undo/redo buffer and refresh the window. Now, we’re ready to add a second data set.

iplot, x, y, z, /overplot

Finally, we find the identifier for the SendToBack operation and do it.

sendToBackID = otool->findIdentifiers('*sendtoback', /operations)
result = otool->doAction(sendToBackID)

Unlike doSetProperty, the IDLitTool::doAction does not need commitActions for its action to be added to the undo/redo buffer.

Here’s what the results look like:

Polylines in front (added last); you can’t see through the supposedly transparent surface. Polylines sent to back; now you can partially see the polyline behind the surface.
Polyline in front Polyline in back

Here are the source code and docs for the demo.

The iTools are easy to use interactively (O.K., easy once you learn some of the conventions and jargon). But they also have the ability to be extended and controlled programmatically. I have given a couple examples of extending an iTool with new operations (data operation, general operation). I’ll post more extending iTools visualizations, file readers/writers, manipulators, user interface services, and custom user interfaces in the weeks to follow. But programmatically controlling an iTool is both fairly easy and extremely useful, especially in situations where a visualization needs to be created which further needs to be explored interactively.

Isosurfaces in iVolume

Here is the source and documentation for the demo.

An iTool’s object reference is necessary to do most actions with a tool. Analogous to direct graphics, after an iTool is created it is the “current tool.” So to create an iVolume tool and get its object reference:

ivolume, vol
id = itGetCurrent(tool=otool)

Now id holds the string identifier of the tool and otool holds the object reference of the tool.

One of the most useful methods on the tool object is findIdentifiers. This searches through the identifiers of all the components of the iTool (though is can be limited, as it is here, to a subset of all components). Here the visualizations are searched for the volume visualization just created:

vol_id = otool->findIdentifiers('*DATA SPACE/VOLUME*', $
                                /visualizations)
ovol = otool->getByIdentifier(vol_id)

Then given the identifier, the getByIdentifier gets the object reference of the component. For components of a tool, object references are necessary for “low level” operations and identifiers are needed for “high level” operations. For example, properties of a component can be changed using either an identifier or an object reference for the component. If the identifier is used (as an argument of IDLitTool::doSetProperty) then the changing of the property is automatically placed in the undo/redo system. If the object reference is used directly (with its setProperty method), then the property is changed, but nothing else is done.

The heart of the routine is in the creation of the isosurfaces:

for i = 0, n_elements(isovalues) - 1 do begin
  oIsoOp->setProperty, _isovalue0=isovalues[i]
  result = otool->doAction(iso_op_id)
  otool->commitActions
  ovol->select
  endfor

The one trick demonstrated in this example is that operations always operate on the current selection and creating an isosurface will automatically select the isosurface itself upon completion. So after the first isosurface is created, you must explicitly select the original volume data before making another isosurface.

The ITPROPERTYREPORT routine called at the end is an example program contained in the documentation of the iTools (in the examples/doc/itools/ directory). It can be a handy tool when exploring, developing, and debugging iTools code.

For simple operations, subclassing IDLitDataOperation is an easy way to quickly add an operation to the iTools system. See previous post “Creating an iTool data operation” for more information about IDLitDataOperation. If you want to do anything more complicated than a numeric operation on the dependent variable of the currently selected visualization, then you want to subclass IDLitOperation. We will examine a fairly simple subclass of IDLitOperation, MGitOpDerivative, which calculates the derivative of data in a plot visualization.

Here are the files for this demo:

MGitOpDerivative__define.pro (doc)

A subclass of IDLitOperation which can calculate a derivative for IDLVECTOR data parameters in IDLitVisPlot visualizations.

MGitOpDerivative_demo.pro (doc)

Program which creates an iTool, loads a sample data set, and registers the above operation for it.

The old standards: init, cleanup, getProperty, setProperty methods

It is very important that the init method calls IDLitOperation::init with the TYPES keyword and pass along it’s own keywords with _EXTRA.

if (~self->IDLitOperation::init(types=['IDLVector'], $
                                name='Derivative', $
                                icon='plot', $
                                _extra=e)) then return, 0B

The cleanup, getProperty, and setProperty methods do not need to be overridden from those inherited from IDLitOperation.

The doAction method

The doAction method is called when the operation is performed by the user. The doAction method does the following:

  1. Finds IDLitVisPlot objects in the currently selected items of the iTool.
  2. Saves initial y data of the selected visualizations in an IDLitCommandSet so that the operation can be undone later (done in recordInitialValues)
  3. Do the derivatives (in doDerivative).
  4. Saves the final y data of the visualizations in the same IDLitCommandSet so that the operation can be redone later (done in recordFinalValues)
  5. Return the IDLitCommandSet that the initial and final values are stored in.

The real work is done in mgitopderivative::doDerivative. Here the plot parameter is an IDLitVisPlot object. The X and Y parameters of this object contain the data.

pro mgitopderivative::doDerivative, plot
  compile_opt strictarr

  xdata = plot->getParameter('X')
  ydata = plot->getParameter('Y')
  x = xdata->getData(x)
  y = ydata->getData(y)
  yprime = deriv(x, y)
  result = ydata->setData(yprime)
end

The same kind of code will be used later to change the y values, using IDLitData::setData method instead of IDLitData::getData.

Undo/Redo

Unlike when subclassing IDLitDataOperation, we are responsible for storing information to enable undo/redo. This information is stored in an IDLitCommandSet object. For each visualization acted on, an IDLitCommand is created, data is added to it, and it is added to the command set. Here is a snippet from recordInitialvalues:

ocmd = obj_new('IDLitCommand', $
               target_identifier=targets[i]->getFullIdentifier())
ydata = targets[i]->getParameter('Y')
result = ydata->getData(y)
result = ocmd->addItem('INITIAL_Y', y)
oCmdSet->add, ocmd

recordFinalValues does the same thing, but names the item 'FINAL_Y'. The undoOperation and redoOperation methods simply use this information to set the data of the Y parameter back to the stored value.

Registering the operation

Creating the iTool and registering the operation is straightforward. It is necessary to register the operation so that the tool knows about it. It is possible to globally register the operation, but we will register it with a single iTool in our example. Use itGetCurrent to find the object reference for the iTool and IDLitTool::registerOperation to register the operation. This is done in MGITOPDERIVATIVE_DEMO:

iplot, x, y
id = itGetCurrent(tool=otool)
otool->registerOperation, 'Derivative', 'mgitopderivative', $
                          identifier='Operations/Derivative'

The ability to extend the itools is one of their main strengths. This can be adding a button for an operation (like our example here), the capability to read/write new file types, new visualizations, or new ways to manipulate visualizations. Plus, the user interface can be customized to various levels. Toolbars and panels can be added to the standard configuration, but a totally custom interface can be created also. Capabilities can also be removed to provide a simpler interface for a specific task.

Screenshot of using MGitOpAdaptHistEqual

This example adds an adaptive histogram equalization operation to IIMAGE.

There are two files for this demo program: mgitopadapthistequal__define.pro (doc) to define the operation and mg_dataop_demo.pro (doc) to setup using the operation. To run the demo, make sure both files are compiled or in your path and run MG_DATAOP_DEMO.

You’ll probably want to look at the documentation for the parent classes of our class: IDLitComponent, IDLitIMessaging, IDLitOperation, and IDLitDataOperation during the course of this tutorial.

Define the member variables

The member variables will include all the properties of the operation, plus whatever else is needed to be stored during the life of the operation. The properties will be registered in the init method using the names defined here.

The init and cleanup methods

The init method has several jobs:

  1. call IDLitDataOperation::init with the proper keywords (TYPES and _EXTRA),
  2. register properties, and
  3. initialize member variables (properties).

As usual for a subclass’ init method, the first task is to call the parent’s init method:

if (~self->idlitdataoperation::init(types=['IDLARRAY2D'], $
                                    name='Adapt Hist Eq', $
                                    icon='image', $
                                    reversible_operation=0B, $
                                    expensive_operation=0B, $
                                    _extra=e)) then return, 0L

Note that it is important that it get passed _EXTRA. The TYPES determines which itools data types the operation will act on. The REVERSIBLE_OPERATION and EXPENSIVE_OPERATION keywords relate to the undo/redo system. REVERSIBLE_OPERATION determines what happens when the user undo’s the operation. If REVERSIBLE_OPERATION is set, then the undoExecute method is called. If REVERSIBLE_OPERATION is not set, then the itools will cache data before the operation is executed so that it can be restored when it is undone. EXPENSIVE_OPERATION determines the behavoir after the operation is undone. If EXPENSIVE_OPERATION is set, then the result of the operation is cached so it can be redone without having to calculate it again. If not set it must be calculated again via the execute method if it is redone.

self->registerProperty, 'clip', /float, $
                        description='Slope limit of histogram', $
                        name='Clip', $
                        sensitive=1

Register properties of the operation via IDLitComponent::registerProperty (IDLitComponent is a parent class of IDLitDataOperation). The positional parameter is the member variable name. The type is specified using the INTEGER and FLOAT keywords (others are available, though not corresponding to all the IDL types), but could be specifed via a code as the second positional parameter.

The cleanup method simply calls IDLitDataOperation::cleanup.

The setProperty and getProperty methods

The setProperty and getProperty methods are fairly standard. They must provide access to all registered properties and must pass keywords on to IDLitDataOperation::setProperty and IDLitDataOperation::getProperty.

The doExecuteUI, execute, and undoExecute methods

The doExecuteUI method is called to present a GUI for the user to modify properties, see a preview, etc. before the operation is executed. It gets the tool object reference, does a quick sanity check, and does a UI service:

otool = self->getTool()
if (~otool) then return, 0L
return, otool->doUIService('OperationPreview', self)

Documented predefined choices for services are: ‘PropertySheet’ or ‘OperationPreview’. You can also write your own UI service. Note that this method will not be called if the SHOW_EXECUTION_UI property of the operation is not set.

The execute method actually does the calculations of the operation. The function accepts a single positional parameter which is an input for the original data and an output for the operated upon data. This is the statement that actually does the adaptive histogram equalization:

data = adapt_hist_equal(bytscl(data), $
                        clip=self.clip, $
                        nregions=self.nregions, $
                        top=self.top)

The status of the execution is returned (1 for success, 0 for failure).

The undoExecute method is not needed for this operation because REVERSIBLE_OPERATION is not set.

Using the operation

MG_DATAOP_DEMO puts MGitOpAdaptHistEqual into IIMAGE and loads some default data. The itool is created with a data set in the standard way:

f = filepath('endocell.jpg', subdir=['examples', 'data'])
endo = read_image(f)
iimage, endo
id = itGetCurrent(tool=otool)

Here, ITGETCURRENT gets the itool identifier and object reference. We only need the object reference, but are required to get the identifier. Be sure to use ITGETCURRENT right after creating the itool, because if another itool is created or even becames the current window in the OS, it will be the current itool.

otool->registerOperation, 'AdaptHistEqual', $
                          'MGitOpAdaptHistEqual', $
                          identifier='Operations/AdaptHistEqual', $
                          icon='image'

This registers the operation on our itool. The positional parameters give a name for our operation and the classname that defines the operation. The IDENTIFIER keyword locates the operation in the menu system. For instance, the identifier ‘File/AdaptHistEqual’ would put this in the File menu of the itool. More usefully, ‘Operations/My Operations/AdaptHistEqual’ would create a new submenu in the Operations menu and put this operation in it. The ICON keyword specifies an icon for the operation in the operation browser. In this case, icon='image' is equivalent to

icon = filepath('image.bmp', subdir=['examples', 'data'])

Hopefully, this will provide a template for further operations. I hope to provide more demos/templates/tutorials for other methods of extending the itools including file readers, file writers, general operations, manipulators, visualizations, UI services, user interface panels, and full-blown user interfaces.