The Catalyst Library
QUESTION: I've heard you mention the Catalyst Library numerous times on the IDL newsgroup, but I can't find any reference to it on your web page. Where is it? How can I download it? How can I find out more about it?
ANSWER: My colleague, Dave Burridge, and I have been working on the Catalyst Library off and on since 2003 in our spare time. It has evolved to the point where we use it extensively in our own IDL programming applications, but we have not found the time to write a Users Guide for it. We hope (as always!) to do that soon. We have, however, trained other people to use the Library and we find that anyone who understands IDL widget programming has absolutely no difficulty learning to use the Catalyst Library itself. In fact, if anything, the consistent user interface makes using the Library even easier than widget programming in general. For this reason, we have decided to release the Library as an Open Source project, with the expectation that wider use of the Library will encourage us, as well as others, to provide better documentation and functionality. The entire source code for the Library is now available.
For people who are interested in exploring the Catalyst Library, we have put together an example application that illustrates many of the features of the Catalyst Library. Simply download and install both the Coyote and Catalyst libraries and type “catalyst ”at your IDL prompt.
A Catalyst program, named AnnotateWindow, is also avaiable. This program allows you to annotate or make distance or angle measurement on any IDL graphics window or image. The final annotated window can be same in a variety of file formats, including JPEG, TIFF, and PostScript. The application is written in IDL and is available for your to see how easy it is to write Catalyst applications.
Restrictions and Notes:
- You will need IDL 6.2 to run the Catalyst Library programs.
- The program assumes a 24-bit TRUECOLOR display. Note, many, many people are running 24-bit DIRECTCOLOR displays because this is the default visual for most operating systems. The DIRECTCOLOR display is never a good idea for an IDL application that works with colors, since it makes color handling impossible to understand or explain. You can learn more about this and how to set IDL up in 24-bit TRUECOLOR mode in the article Configuring IDL on X Window Systems.
- Odd results have been reported on X-Windows machines that allow the window manager to do backing store. If you run the program and think "An idiot wrote this program!", try setting DEVICE, RETAIN=2 before you restore and run the program. (Note, this is especially a problem on LINUX machines. You might want to think of adding this command to your IDL start-up file if window retention is a problem for you. We think we have fixed this problem from within Catalyst itself.)
- The test application was written to test Catalyst functionality, NOT widget functionality. So, for example, I am not generating TLB resize events. I certainly could, nothing prevents me from doing so, but I am not.
- There are certainly bugs in the program and certain features are missing or not yet implemented. I appreciate hearing about whatever you find. I'll try to fix it or implement it as soon as I can.
What is the Catalyst Library?
The Library is a collection of objects (about 80 at the moment), written in IDL, that form a framework for building IDL applications. In this sense, it is similar to the iTool object library provided in IDL, although we think its fundamental advantage over the iTool library is its utter simplicity and ease of use. Writing a Catalyst Library application is no more difficult, and often a great deal easier because of built-in functionality, than writing an IDL widget program. And, unlike the iTool library, you have a choice as to whether you wish to use object graphics or direct graphics for displaying your data. In our own programming work, we find we use the simple direct graphics routines (standard PLOT, CONTOUR, and TV commands) nearly 95 percent of the time. This makes the Library accessible to anyone who has ever used IDL. You don't have to be a computer science nerd to figure it out!
As a framework, the Catalyst Library is something you build IDL applications and programs with, not a program itself. It is, if you like, the foundation of a larger application. What it provides is a consistent user interface and approach to building applications. You don't have to think about how each and every program is going to be designed. You simply have to think about what objects are needed to make your application work, and build those. What we give you are the building blocks for doing so.
In addition, the framework provides a common structure for handling low-level functionality like error handling, widget event processing, object messaging, keyword inheritance, object clean-up, and program documentation. But perhaps the biggest advantage of the Library is that it virtually eliminates the problem of data handling getting out of control in large widget applications. Objects, because of their ability to encapsulate data, make it dead simple to write large applications that are easy to maintain and easy to extend in the future. Dave and I have found we can build large applications with the Catalyst Library in a fraction of the time it used to take us, and that the programs we write are significantly more robust than the widget programs we wrote previously.
Important Features of the Catalyst Library
Here are brief overviews of what we consider to be some of the most important features of the Library.
Object HierarchyThe notion of a hierarch is a powerful one in computer programming. We think, for example, of a widget hierarchy and widget events passing up and down such a hierarchy. We decided to use the idea of a hierarchy in the Catalyst Library, because we could see that it provided significant advantages to us in terms of object communication. Specifically, every object in the Catalyst Library is sub-classed from and IDL_Container object. That is to say, each object can contain other objects. We take full advantage of this in a number of ways.
One way we use the concept of an object hierarchy is in the way we interpret a DRAW method. Nearly every object in the Library has a DRAW method associated with it. However, the DRAW method is interpreted differently depending upon the object. For example, the DRAW method of a widget object object realizes the widget; the DRAW method of a coordinate object establishes a data coordinate system for the graphics system; the DRAW method of a data object might display the object in a graphics window, etc. In the Catalyst Library, a single call to the DRAW method of the application object starts the application running, and the the DRAW method call propigates throughout the object hierarchy so that objects are initialized, graphics windows, are created, and data gets displayed in the windows, all automatically. If we wish to display something in a draw widget object, for example, we simply write a DRAW method for the object we wish to display and pop it into a draw widget object container. It automatically gets drawn when the DRAW method of the draw widget object is called because of the way DRAW methods propogate in the object hierarchy. (This can go on to any arbitray level, so if we want to draw objects--say annotations--on an image object, we place the annotation objects in the image object container, and place the image object in a draw widget container, etc.)
Widgets as Objects
A vital part of the Catalyst Library for us was finding a way to incorporate widget event processing, which normally is external to objects, into our object methods. I have to say, I think we hit on exactly the right solution. We have made objects of every widget in the IDL system, as well as created a number of handy new compound widget objects (such as the Statusbar object). Event handling is straightforward and efficient. Events can be redirected to other objects (even non-widget objects!) and events naturally propogate up the object hierarchy in the same way they normally propagate up the widget hierarchy. The ID, TOP, and HANDLER fields of event structures have been replaced (and enhanced) with similar fields containing references to objects incorporating similar functionality, so that you never have to worry about leaving the object paradigm. The entire left side of the Catalyst program illustrates widgets as objects. Note, too, the Field Widgets and StatusBar objects at the bottom of the Catalyst program. These are examples of compound widget objects.
Coordinates and Data
Objects are inherently data-centric, and we carry on this tradition. Each piece of data in the Catalyst Library, whether scalar, vector, or array, can have its own coordinate system associated with it. This is powerful because it allows us to maintain a conversation between a data object and a display object. In other words, a display object (a draw widget, say) can ask a data object (say an image object), "Did I just click inside you? And if I did, could you please tell me where I clicked in your coordinate system and could you tell me the value of the data at that location." You see this capability illustrated in the display windows of the Catalyst program, which report this information back to the Statusbar object at the bottom of the application. Image data objects always have this capability, even if they are in map projections.
Most objects have built-in control panels, similar to the one for the TextLine object in the figure below. Control panels can either be embedded in other widget objects, or they can pop-up in their own top-level base object as in the figure. All objects in the Catalyst Library are sub-classed IDLgrComponent objects, so they can take advantage of PropertySheet widget objects. In many applications, right-clicking an object will call up that object's control panel. You see this illustrated in the Annotation tab of the Catalyst program.
Event handling is typically not enough in large applications because you still have the problem of how to communicate in event handlers to objects that can't be seen in the context of the event handler. Thus, there has to be some means of communicating from one object to another without knowing ahead of time which objects are going to exist in the application.
We have solved this problem in the Catalyst Library by implementing a system, built into each and every object, for passing object messages. For example, a ColorBar object might want to broadcast a COLORS_HAVE_CHANGED message whenever the user interacts with the color bar and changes the colors. Perhaps you have five image objects on display, but only two of them care about this message. They simply register their interest with the ColorBar object. When the ColorBar changes its colors, it looks in its "message" container for objects who wish to be notified. If those objects have registered for the COLORS_HAVE_CHANGED message, then that message is sent to the MessageHandler method of those objects. This is similar, if not identical, to the way a widget event message is sent to the EventHandler method of an object. Data can also be passed with the message. For example, the ColorBar object might send the new color table vectors as part of the COLORS_HAVE_CHANGED message.The Catalyst Library also comes with utiltiy routines for tracking objects down in the object hierarchy when you don't know exactly where they are, or if they even exist. You can seach for objects by name, by type, etc.
Error Handling, Logging, and Debugging
The Catalyst Library handles errors in a consistent and natural way. Errors that occur deep in the object hierarchy do not result in a slew of error messages as they propagate back up the object hierarchy, but you get a single error notification with a sensible traceback message pointing to the location in your program where the initial error occurred. There are no hidden and silent error handlers to cause you hours of grief and confusion, as in other object libraries.
Errors can be generated with three levels of detail: Terse, Normal, and Verbose. And error messages can be sent to the display, to a log file, or to both. Easily configured Catalyst global variables (singleton objects) can control error handling options, or you can turn options on or off for individual objects. The later is especially important during debugging operations.
Catalyst objects keep track of when they enter and exit object methods, and you can obtain a printout of this activiity, which is useful for debugging object operations.
User interactions are objects that, when set it place, take over event handling for a draw widget, produce some kind of user interaction (typically they allow the user to draw some type of region of interest, but they can do other things as well), and then alert another object of the result of the interaction. The Catalyst Library comes with interactions for drawing rectangles of various sorts, ellipses, straight-line polygons, and freehand polygons. You can see this kind of interaction illustrated in the Medical Image tab of the Catalyst program. Interactions typically allow themselves to be dragged around on the window after they are created for final positioning.
Annotating Graphics Displays
The Catalyst Library comes with a special type of user interaction object, called an AnnotationInteraction, that is used for producing annotations on top of display windows. At the moment this is a direct graphics only capability, but we plan to extend the concept to object graphics windows as well. While it is written in direct graphics, I have never seen anything like it in any other IDL direct graphics program.
It allows annotations, including text, to be created directly on top of an IDL graphics window. (If you are creating text, be sure to hit the carriage return when you are finished typing to "enter" the text and create the new text object. Otherwise, we can't tell when you are finished typing!) Once created, the annotations can be immediately dragged around and positioned at any location in the graphics window. We allow text, boxes, ellipses, arrows, and polygons to be created. All can be created with filled or non-filled background colors. (Right click on any selected object to access the object's Control Panel and change it's properties.)
Objects can be grouped (shift-left-click to select multiple objects, then right-click inside any selected object to get a Group context menu), deleted, and shifted forward and back in their container so that one object is displayed property on top of another. (The Arrow object can also be rotated by first selecting it and then grabbing one of the selection handles near either end of the arrow to drag and rotate.) The Catalyst Library has the concept of a Layer object, which can contain graphical objects, like annotations. The Annotation tab in the Catalyst program shows the AnnotationInteraction object with a Layer object. The annotation layer may be saved, turned on and off, and a different annotation layer can be opened later on. This provides a great deal of flexibility for annotating graphical displays.
Hardcopy Output of Graphics Displays
Because of the hierarchical nature of the Catalyst Library, displaying graphics simply means calling the Draw method of the graphics window, which will call the Draw methods of all the objects in that container, and so on, down through the hierarchy, until the entire hierarchy is drawn. This makes it easy to send output to the PostScript device, for example. Simply set up the PostScript device and call the Draw method on the graphics window. It is similarly easy to take a snapshot of whatever is in the draw widget window. Thus, all draw widgets in our Catalyst Library can create their own hardcopy output in JPEG, TIFF, PNG, BMP, and (with some minor qualifications) PostScript files.
Object Memory Clean-up
The hierarchical nature of the Catalyst Library, and the fact that all objects in the library are sub-classed IDL_Container objects makes it easy to clean up the objects you create. Simply by destroying the application object, most objects used in an application are automatically cleaned up and destroyed automatically. But with container objects, sometimes an object in a container is destroyed when it shouldn't be. For example, you might have an image object and you want to see it displayed in two different draw widget objects. You would add the single image to both draw widget objects. But if one of the draw widgets was destroyed, you would not want the image object to be destroyed.
The Catalyst Library uses reference counting to make sure that objects get destroyed only when no other object is interested in them. In adding the image to two different containers, the reference count on the image will have been set to 2. In the example above, destroying the first draw widget would decrease the reference count on the image by one. But the image itself is not destroyed until its reference count goes to 0. Both draw widgets will have to be destroyed to destroy the image object.
What Applications are Appropriate for the Catalyst Library?
The Catalyst Library can be used for nearly any kind of IDL application. We have used the library to build a large meteorological application using satellite images and a great many image overlays, several different kinds of medical image applications, and a flight controller application that did not involve much graphical display at all. Because most of the large application we have used the Catalyst Library for used images, the library has evolved a number of image-specific objects. For example, we have created ImageFrame and ImageStack objects to allow us to work with and display 3D medical image data sets.
You can find Catalyst application programs among the programs listed in the Coyote Library, and we expect more to be available soon.
Copyright © 2005-2008 David W. Fanning
Last Updated 20 October 2005