Category "Widgets"


This program is an example of using timer events to do a task in the background while allowing the the user interface to still respond to events. This technique requires the background task to be split into parts which are short enough for each part to complete quickly enough for a user to not notice a delay if he begins to interact with the interface. In other words, if each part of the task takes 0.1 seconds, then there is a potential delay of 0.1 seconds before the user interface will respond.

mg_timer_demo screenshot

Here are the source and docs for the demo.

I’m going to use the normal “pstate” technique for passing data to my event handlers and standard techniques for manipulating object graphics interactively without explaining them. I’m only going to talk about the “new” part: timer events to handle a background task.

First, we need a widget that won’t generate events normally. We could use one we already have, but I usually create one specifically to do the timer events. Since bases don’t appear in the interface, I often use something like:

timer = widget_base(toolbar, uname='timer')

Next, we’ll need a few fields in our state structure to store information related to the timer events.

state = { oview: oview, $
          owindow: owindow, $
          otrack: otrack, $
          time: 0.1, $
          t: 0L, $
          stop: 1B $
        }

The time field holds the time value between timer events. Make sure this is long enough to accomplish a step in your task. The t field is a counter. We’ll rotate the surface one degree each time the timer goes off and stop after 360 rotations; using the counter to mark our position. The stop field indicates if the “stop” button has been hit (the initial condition is as if the “stop” button was just hit).

The event handler code for the timer event is fairly straightforward. Here is the case for timer events:

'timer' : begin
    if ((*pstate).stop) then return
    if (++(*pstate).t gt 360) then return
    omodel = (*pstate).oview->getByName('model')
    omodel->rotate, [0, 1, 0], 1
    (*pstate).owindow->draw, (*pstate).oview
    widget_control, event.id, timer=(*pstate).time
  end

If the stop field is set, then we don’t do anything. If the counter (which we increment) has gone past 360 we also don’t do anything. Otherwise, rotate the model by a degree around the y-axis. Finally, set the timer to go off again in (*pstate).time seconds.

Another choice for handling background tasks is to use the new IDL_IDLBridge class. With the IDL_IDLBridge class technique, the task would not have to be broken down into small subtasks, but there are more difficulties with overhead and passing data. This will be the subject of a future article.

A couple weeks ago, I wrote a demo program to view JPEG 2000 images as a “regular” widget program. Now I want to rewrite the same program as an “object widget,” in other words write methods of a class instead of normal functions and procedures. You need to already understand the basics of object-oriented and widget programming in IDL to follow along with this example. The following files are needed for this program: mgtilejp2__define.pro (doc), mgobjectwidget__define.pro (doc), mg_object_event_handler.pro , and mg_object_cleanup.pro.

JPEG 2000 tile viewer as an object widget

This new program will have exactly the same functionality as the old program. So what are the advantages of writing the program as an object? Of course it’s a matter of preference and some will already have a preference to use or avoid objects. Here are my thoughts about the advantages of this approach:

  1. Cleaner: state/pstate doesn’t need to be passed around.
  2. Better encapsulation when multiple programs are interacting with each other and passing messages to each other. In other words, one program doesn’t need to know internal details about the other program in order to pass a message to it.

New architecture

There are several techniques to make “object widgets.” I found the following technique simple yet still has the advantages of object-oriented programming. The steps to making a simple object widget:

  1. The member variables of the object hold what used to be in the state or pstate.
  2. The init method is the widget creation part of the program. One important item: place self in the UVALUE of the top-level base. This will make our scheme for event handling work.
  3. XMANAGER can’t call a method for an event handler or cleanup routine, so we will trick it. Instead write a simple two line event handler which pulls out the UVALUE of the top-level base (which is our object widget reference) and calls the real event handler method of the object. This will mean that our object widget will only have a single event handler that handles all events (but possibly dispatches events to other routines). The same must be done for the cleanup routine.

Once you’ve done this, it’s fairly easy to modify the structure a bit for your own purposes.

Code changes

Here’s what I had to do to make MG_TILEJP2 into an object widget:

  1. Change the names of the routines from mg_tilejp2_refresh to mgtilejp2::refresh. mgtilejp2 becomes mgtilejp2::init. Make sure to change it to a function and return 1.
  2. Add MGTILEJP2__DEFINE that simply names the class, inherits from MGObjectWidget, and creates the member variables with the same name and type as the fields of the old state variable.
  3. Convert creating pstate and putting it into the tlb’s UVALUE into putting self into the tlb’s UVALUE. Get rid of
widget_control, event.top, get_uvalue=pstate

in the event handler.

  1. Fix up passing around pstate since it’s no longer needed. Fix (*pstate).owindow to self.owindow.
  2. Added a cleanupWidgets method, fixed up cleanup method, and MG_TILEJP2_EVENT to mgtilejp2::handleEvents.

See previous post about tiling JPEG 2000 images with IDLgrImage.

UPDATED 10/25/13: Updated links of library source code.

IDL 6.2 added several new capabilities to the IDLgrImage class in the object graphics system. One of them is the ability to support large images using tiling. (Another is significantly faster rendering time in typical cases.)

mg_tilejp2 demo

The new tiling features allow portions of the image to be read from a data source as needed. For applications that zoom using data sources with an image pyramid, level of detail rendering is used. For example, if a thumbnail of the entire image is needed, only a low resolution image is requested from the data source and rendered by IDLgrImage.

The mechanics of how this works requires a bit of communication between the destination object (IDLgrWindow, IDLgrBuffer, IDLgrClipboard or IDLgrPrinter support it) and the IDLgrImage object using a few new methods (most notably, queryRequiredTiles for the destination objects and setTileDatafor IDLgrImage).

Running the demo

The demo program is a minimal tiling image viewer which supports scrolling (click and drag as well as with the arrow keys) and zooming (page up and page down). To test it out:

  1. First download the source code (mg_tilejp2.pro and mg_tilejp2_demo.pro).
  2. Change directories in IDL to a directory that you want to save the JPEG 2000 data file in.
  3. You need to run MG_TILEJP2_DEMO at least the first time you run the demo code in order for the JPEG2000 image to be created. After that, you can run MG_TILEJP2 directly.

How it works

The basic steps are pretty simple:

  1. set a few properties on the IDLgrImage object when you create it,
  2. query the destination object with the new queryRequiredTiles method for any new tile data to read, and
  3. set the tile data with the new setTileData method of IDLgrImage.

First, there a few new properties of IDLgrImage that you can now set. The TILING keyword must be set and TILED_IMAGE_DIMENSIONS must specify the full size of the image. The IDLgrImage initialization in the demo program looks like:

oimage = obj_new('IDLgrImage', name='image', order=1, $
                 /tiling, $
                 tile_show_boundaries=0, $
                 tile_level_mode=1, $ ; automatic mode for image pyramid
                 tiled_image_dimensions=imageDims, $
                 tile_dimensions=jp2TileDims)

The size of each tile is set through the TILE_DIMENSIONS keyword; the default value is [1024, 1024]. The keyword TILE_SHOW_BOUNDARIES can be set to show the boundaries of the tiles (handy when debugging). Finally, TILE_LEVEL_MODE specifies whether the application specificies the level through the TILE_CURRENT_LEVEL property (0) or if it done automatically (1). Since our demo program has a data source with an image pyramid, we can use automatic.

The guts of the tile handling is in the MG_TILEJP2_REFRESH routine that is called whenever the image needs to be redrawn (usually because the VEWPLANE_RECT of the view has been modified). Our refresh routinen is a typical example of filling in the tile data from a JPEG 2000 file:

pro mg_tilejp2_refresh, pstate
  compile_opt strictarr

  oimage = (*pstate).oview->getByName('model/image')
  reqTiles = (*pstate).owindow->queryRequiredTiles((*pstate).oview, $
                                                   oimage, $
                                                   count=nTiles)

  if (nTiles gt 0) then widget_control, /hourglass

  for t = 0L, nTiles - 1L do begin
    subrect = [reqTiles[t].x, reqTiles[t].y, $
               reqTiles[t].width, reqTiles[t].height]

    level = reqTiles[t].level
    scale = ishft(1, level) ; scale = 2^level
    subrect *= scale

    ojp2 = obj_new('IDLffJPEG2000', (*pstate).jp2filename, $
                   persistent=0)
    tileData = ojp2->getData(region=subrect, $
                             discard_levels=level, $
                             order=1)
    obj_destroy, ojp2
    oimage->setTileData, reqTiles[t], tileData, no_free=0
  endfor
  (*pstate).owindow->draw, (*pstate).oview
  widget_control, hourglass=0
end

Note how nicely the output of IDLgrWindow::queryRequiredTiles meshes with the keywords of IDLffJPEG2000::getData and the parameters of IDLgrImage::setTileData.

The rest is, as they say, details—that is, if you have a basic knowledge of object graphics and widget programming.