This document describes how a user of OpenDX can extend Data Explorer's functionality in various ways. Example applications and programs discussed in this document were available with Data Explorer Version 3.1.4B.
Open Visualization Data Explorer
Chapter 1. Overview: What You Need to Know to Extend Data Explorer Functionality
Chapter 2. Building Applications with the Supervise Modules, DXLink, and DXCallModule
- Adding Function for use in the Data Explorer VPE or Scripting Language
- Writing a Stand-alone Program Using the Data Explorer Data Model
- Writing a Stand-alone Program Using Data Explorer Modules
- Controlling the Data Explorer Executive or User Interface from a Separate Program
- Defining Custom Interaction Modes
- The Supervise Tools: Overview
- Software Rendering and the Supervise Tools
- Hardware Rendering and the Supervise Tools
- Using the Supervise Tools
- Using the Supervise Tools in a Visual Program
- Using the Supervise Tools with a Separate Graphical User Interface using DXLink and DXCallModule
- The Events Output, Picking, and UserInteractors
- The Events Output and Picking
- UserInteractors, Default and User-Defined
- Default UserInteractors
- Building your Own UserInteractors
Chapter 1. Overview: What You Need to Know to Extend Data Explorer Functionality
The Data Explorer libraries allow you to extend Data Explorer's functionality in a variety of ways. Depending on the task you want to accomplish, the way you use the libraries will vary."DX Architecture"shows the Data Explorer architecture.
|DX Architecture (click on figure to expand)
"Applications" shows several possible uses of Data Explorer functionality.
|Applications (click on figure to expand)
The Data Explorer User Interface and Data Explorer Executive are the two programs you use when you create and execute visual programs. Underlying the Data Explorer Executive is a collection of modules, which in turn use the Data Explorer data model to manipulate all of the objects in a program (data sets, isosurfaces, images, etc.). The Data Explorer architecture is described in more detail in Chapter 1. "Overview" in IBM Visualization Data Explorer User's Guide.
Adding Function for use in the Data Explorer VPE or Scripting Language
Suppose you want to accomplish a task in the Visual Program Editor or scripting language which no built-in Data Explorer module accomplishes. The first thing you should investigate is whether existing modules may be used together in a macro to accomplish that task. For example, the Compute module, along with various structuring modules, such as Mark, Unmark, and Replace, can perform a variety of data transformation tasks. Similarly, many data filtering operations are available within the Filter module. When you create a macro, you can define its interface (inputs and outputs) so that it will present a configuration dialog box to the user just like any of the built-in modules in Data Explorer. You can arrage to have your macro or macros loaded automatically each time Data Explorer starts. (ref to appropriate place in user guide)
If, however, either because using macros is not as efficient as you want, or if the necessary function cannot be provided using existing modules, you may want to consider adding your own module to Data Explorer. For example, you may want to write a special-purpose data import module, which reads your particular data format and outputs a Data Explorer Object for further processing within the Visual Program Editor. Or you may want to write a data filtering or smoothing module which performs more specific operations on your data than is available using existing modules. In this case your module will appear just like any other Data Explorer module, as in "Writing a Module" in "Applications"
The chapters "Writing a Simple Module" through "Making a Module Work" in the Data Explorer Programmer's Reference show you a number of examples of modules, including import filters and various data manipulation modules. These examples are supported by .c files and makefiles in /usr/lpp/dx/samples/program_guide. These examples use the libDXlite.a library, which contains all of the data model routines of Data Explorer. To use these routines, you should be familiar with the material in Chapter 12 "Working with Data Model Objects" in the Data Explorer Programmer's Reference, which discusses how to use the Data Explorer data model routines. Other, higher level routines, such as invalid data handling, hash support, and entry points for the Data Explorer module set are available in the libDXcallm.a library. If you wish to incorporate routines from the libDXcallm.a library, simply change the makefiles to link to this library instead of to libDXlite.a. All of the routines in libDXcallm.a are described in Appendix C. "Data Explorer Library Routines", while the subset of routines available in libDXlite.a is listed in Appendix B. "Data Explorer Data Model Library: DXlite Routines".
When you write a module for use in Data Explorer, the Data Explorer Executive is still the process which "owns main". Your module is simply incorporated into Data Explorer. Your module can be directly built in to the Data Explorer Executive (inboard module), run as a separate process (outboard module), or loaded into the Data Explorer executive at runtime (runtime-loadable module). Each of these is discussed in Chapter 11. "Making a Module Work" in the Data Explorer Programmer's Reference. Using runtime-loadable modules is in general the preferred option for portability and efficiency reasons.
"Inboard, Outboard, and Runtime Loadable Modules " illustrates the different options.
|Inboard, Outboard, and Runtime Loadable Modules (click on figure to expand)
Writing a Stand-alone Program Using the Data Explorer Data Model
You may also want to write a stand-alone program which uses the Data Explorer data model. For example, you may want to write a data filter which processes data, writing it out to a file in Data Explorer format using DXExportDX. In this case, your stand-alone program "owns main" and simply links in Data Explorer data model routines, which are listed in Appendix B. "Data Explorer Data Model Library: DXlite Routines", and discussed in Chapter 12. "Working with Data Model Objects". Graphically, this is represented by the lower "User Program" in DX Architecture, which embeds Data Explorer data model routines into the user's program, and by the leftmost "Standalone Application" in Applications.
Writing a Stand-alone Program Using Data Explorer Modules
You may want to write a stand-alone program which directly uses Data Explorer modules. You would link to the libDXcallm.a library, and use DXCallModule to call individual Data Explorer modules. In this case, as with the previous one, your stand-alone program "owns main". Note that you can do complete visualization programs in this way, for example, from Import to Isosurface to Display from within your own program. However, you will not be getting the functionality of the Data Explorer Executive in this case, including cache management, and control of execution order. You will, in addition, be responsible for deleting objects when you are finished using them. Note that with the SuperviseWindow and SuperviseState modules (see SuperviseWindow and SuperviseState in IBM Visualization Data Explorer User's Reference), direct interaction within the Image window is available without the Image tool. Graphically, this is represented by the rightmost "Standalone Application" in Applications.
Examples of stand-alone programs including .c files and makefiles which use the CallModule library can be found in /usr/lpp/dx/samples/callmodule. Graphically, these are represented by the lower "User Program" in "DX Architecture" which embeds Data Explorer module routines into the user's program. Further discussion of details involved in creating CallModule applications can be found in"Using the Supervise Tools with a Separate Graphical User Interface using DXLink and DXCallModule" and "DXCallModule".
which embeds Data Explorer module routines into the user's program. Further discussion of details involved in creating CallModule applications can be found in"Using the Supervise Tools with a Separate Graphical User Interface using DXLink and DXCallModule" and "DXCallModule".
Controlling the Data Explorer Executive or User Interface from a Separate Program
You may want to write a program which controls the Data Explorer Executive. For example, you could write your own user interface, providing a custom "look and feel", and send Data Explorer script language commands to the Data Explorer executive to process data and compute images. In this case you would get all of the functionality provided by the executive (cache management, control of execution order, and object management). You could also directly control the Data Explorer User Interface from a separate program, loading and executing visual programs. For example, you may wish to fire up Data Explorer with a "canned" visualization program once a simulation is complete, with parameters within the visual program preset to particular values.
Graphically, both of these are represented by the upper "User Program" in DX Architecture, and by "Application Controlling DX" in Applications, which show the application controlling the Data Explorer Executive or User Interface. The libDXL.a library (DXLink) provides this functionality, and is discussed in "Using the Supervise Tools with a Separate Graphical User Interface using DXLink and DXCallModule", and "The DXLink Developer's Toolkit" in the Data Explorer Programmer's Reference.. Examples of DXLink programs can be found in /usr/lpp/dx/samples/dxlink.
Defining Custom Interaction Modes
With the functionality provided by SuperviseWindow and SuperviseState (see SuperviseWindow and SuperviseState in IBM Visualization Data Explorer User's Reference), you can define exactly what should occur for given mouse or keyboard events. This functionality is available both within the Visual Program Editor and scripting language, and also from your own stand-alone application, if you so desire. In this way your program does not need the Image tool (which is provided only within the Data Explorer User Interface) in order to provide direct user interaction in the image window. Thus a custom GUI communicating only with the Data Explorer Executive can implement all of the user-interaction provided by the Data Explorer User Interface. Examples of custom direct interactors can be found in /usr/lpp/dx/samples/supervise; while these examples are demonstrated using the Data Explorer User Interface, there is no necessity that they do so, as all of the modules used in these examples (SuperviseWindow, SuperviseState, and Display, in particular) are available directly from the Data Explorer Executive.
Chapter 2. Building Applications with the Supervise Modules, DXLink, and DXCallModule
This part discusses how you can build applications which use and extend Data Explorer functionality. In many cases, such applications will not use the Data Explorer User Interface, but will rather build a custom graphical user interface appropriate to the particular application. The Supervise modules (see the SuperviseState and SuperviseWindow manual pages in the IBM Visualization Data Explorer User's Reference) will often be used in such a case to provide user interaction with the image, as the "Image Tool" is available only with the Data Explorer User Interface.
Data Explorer modules (including the Supervise modules) can be accessed from a separate graphical user interface in two ways: DXLink and DXCallModule. The differences between these approaches, along with examples of the use of these methods, are discussed in the following chapters. The DXLink library routines themselves are discussed in "The DXLink Developer's Toolkit" in the Data Explorer Programmer's Reference,, while DXCallModule is discussed in "DXCallModule".
Some examples of possible applications and how they would typically be implemented:
This chapter is primarily concerned with the most complex one of these, the third application. Of course the first two can be thought of a simpler subsets of the last one.
- A simple stand-alone program which calls several Data Explorer modules, producing as output a rendered postscript image on disk (stand-alone program using DXCallModule; see the example /usr/lpp/dx/samples/callmodule/renderimage.c)
- A custom graphical user interface which allows a user to choose a visualization from a set of options, and which then loads specific visual programs, together with their associated Data Explorer control panels, for the user to execute (stand-alone program communicating with the Data Explorer User Interface and Executive using DXLink; see the example /usr/lpp/dx/samples/dxlink/demoapp.c)
- A graphical user interface with custom widgets to control visualization parameters and an embedded image window which allows user interaction with the image. (stand-alone program, either communicating with the Data Explorer Executive using DXLink, or calling modules individually using DXCallModule; see the examples /usr/lpp/dx/samples/dxlink/xapp_supervise.c and /usr/lpp/dx/samples/callmodule/xapp_supervise.c)
Note that if you are interested in simply adding a module to the Data Explorer system, you should see the chapters concerned with writing modules in the Data Explorer Programmer's Reference.
You may want to review "Overview: What You Need to Know to Extend Data Explorer Functionality" to make sure you understand the function of the Data Explorer Executive, the Data Explorer User Interface, and the Data Explorer API to the library routines. Depending on which approach you use (DXLink communicating with the Executive, DXLink communicating with the User Interface, or DXCallModule), you will have the benefits of different portions of Data Explorer.
1. The Supervise Tools: Overview
SuperviseWindow and SuperviseState give the application developer direct control over the effect of mouse and keyboard events in a window containing an image. In addition, the use of these tools enables the application developer to embed an image within another window; for example, within a custom graphical user interface. These tools have a number of input and output parameters, all of which are described in detail in IBM Visualization Data Explorer User's Reference; the discussion here is intended rather to give an overview of how these tools work together to provide the application developer with great flexibility in offering users interaction with the image.
SuperviseWindow creates and owns a window in which an image is displayed. Because SuperviseWindow owns the window, it can watch the window for events, such as mouse motions or keyboard clicks. You can specify the "parent" of the window created by SuperviseWindow as something other than the root window, which allows the window to be nested within either other image windows or within a custom graphical user interface.
SuperviseWindow outputs a window identifier for the window it creates, along with the current size of the window and an "events" structure, which encodes information about mouse and keyboard actions in the window. The window identifier, the size, and (typically) the events structure should be passed to the SuperviseState module. SuperviseState is the tool which causes changes to be made to the object in the window or the camera used to view it based on whatever events take place in the window (For the case of hardware rendering, some details are different, as will be discussed in "Hardware Rendering and the Supervise Tools"). For example, a click of the left mouse may result in the object "touched" being highlighted, a drag of the left mouse may then "drag" the highlighted object, a drag of the right mouse may change the camera angle resulting in a "rotation" of the object, while keyboard events may cause a caption to be placed in the scene. The actions which occur as a result of specific keyboard or mouse events are completely at the discretion of the application developer, who implements these actions using UserInteractors, which are discussed in "UserInteractors, Default and User-Defined". If the developer does not provide any UserInteractors, then a built-in, default set of interactors is used instead. SuperviseState has a "mode" parameter, which allows multiple modes to be created; thus left mouse clicks can have different interpretations depending on the current setting of the mode parameter.
Among other inputs, you must pass to SuperviseState an initial object and an initial camera. These will be used for the first rendering into the image window. The initial object and camera will later be modified by the UserInteractors based on events in the window. SuperviseState outputs a possibly modified object, a possibly modified camera, a window identifier, and an events encoding. The object, camera, and window identifier should be passed directly to the Display module, which will render the object using the camera and then display the result in the specified window. The events output of SuperviseState encodes all those window events which were not handled by a UserInteractor within SuperviseState. These events may then, for example, be intrepreted by modules with a visual program, if desired.
The precise behavior of the Supervise modules and Display differs somewhat depending on whether software or hardware rendering is being used. In both cases, Data Explorer must be in execute-on-change mode (or the equivalent, in the case of using CallModule to access these modules, as will be discussed later) in order for events to be responded to.
The Supervise tools and Display use the cache to maintain information about the current state of the object and camera. An important thing to remember about the way these tools work is that, except for the first execution, if the "resetObject" parameter to SuperviseState is not set to 1, the object displayed is dependent on what comes out of the cache, not what comes into SuperviseState. Thus if you change the object input to SuperviseState, that change will not be reflected in the output image unless you either set resetObject to 1 or you flush the cache. Similarly, the "resetCamera" input must be set to 1 in order for the camera coming into SuperviseState to be used in rendering the image.
Picking is a topic which needs some separate discussion, as in order to implement picking using the Supervise tools, you will need a solid understanding of both the process of picking and the events output of SuperviseWindow. See "The Events Output, Picking, and UserInteractors".
Software Rendering and the Supervise Tools
A simplified figure showing the Supervise modules and Display when software rendering is being used is shown in "Software Rendering". The cache is used to storcurrent state of both the object being viewed and the camera.
|Software Rendering (click on figure to expand)
When first executed, SuperviseWindow creates an initial window. The object and defaultCamera inputs to SuperviseState are passed along to Display. Display renders the object with the camera and displays the result in the window as specified by the "where" parameter (the window identifier). Display will also store the object and the camera in the cache. Now if any event occurs in the window, such as a mouse click, SuperviseWindow will notice it, execute, and output a new events parameter. SuperviseState will then execute. SuperviseState will retrieve the appropriate object and camera, and will then perform the specified operation, depending on which interactor is to be used (identified by the "mode" parameter to SuperviseState), for example rotation, on the object or camera.
Which object or camera is manipulated by SuperviseState depends on the setting of the resetCamera and resetObject parameters of SuperviseState. If set to 0, then SuperviseState retrieves the object and camera from the cache. If reset Camera is set to 1, then the defaultCamera as passed to SuperviseState is used. Similarly, if resetObject is set to 1, then the object as passed to SuperviseState is used, rather than the object stored in the cache.
After modifying the object or camera, SuperviseState passes them to Display, which renders the object and displays the image in the specified window. Display also stores the new camera and object in the cache.
Hardware Rendering and the Supervise Tools
A simplified figure showing the Supervise modules and Display when hardware rendering is being used is shown in "Hardware Rendering". When SuperviseState is executed, it either passes out the default object and camera, or retrieves the current object and camera from the cache and passes those out, depending on whether it is the first execution and the settings of resetObject and resetCamera. When Display executes, it uses the object and camera passed in from SuperviseState. However, an important difference from the case of software rendering is that during button-down execution, SuperviseState does not execute UserInteractors based on events which occur in the window; rather Display does. Whenever events occur in the window, Display notices them and performs the appropriate action, storing and retrieving the object and the camera in the cache. Display knows which "mode" is currently set because SuperviseState adds the mode as an attribute to the object passed out. Thus you should be aware that if resetCamera or resetObject are set to 1 in SuperviseState, the object and camera will not in fact be reset until the mouse button is released and SuperviseState is re-executed.
|Hardware Rendering (click on figure to expand)
In addition, any events not handled by the UserInteractors are passed (behind the scenes) from Display to SuperviseWindow, which outputs them as its events output. Note that this means that the events output of SuperviseWindow will differ between the hardware and software cases: in the hardware case, it will encode only those events not handled by a UserInteractor, while in the software case it will encode all events. The events output of SuperviseState, will, on the other hand, be the same in the two cases, since it will encode only unhandled events.
In the typical case, Display will handle all events which occur, and SuperviseState will not need to handle any, since any events which come into SuperviseState from SuperviseWindow will contain only those that are intentionally not handled by the UserInteractor.
A side-effect of this behavior is that unhandled events from one UserInteractor mode may later be unintentionally interpreted by a different UserInteractor mode which does handle the particular event. For example, suppose that UserInteractor mode 0 ignores left mouse clicks, but UserInteractor mode 1 handles them. If, while in mode 0 the user clicks the left mouse, the UserInteractor (correctly) ignores the event, and the unhandled event is passed to SuperviseWindow (behind the scenes) where it appears on the events output. If the user then changes to mode 1, this event will be interpreted on the next execution within SuperviseState by UserInteractor mode 1. Since this is presumably not the intended behavior, the user can either choose not to wire the events output of SuperviseWindow to SuperviseState (it's not necessary to, since Display will execute the UserInteractors based on events in the window), or to handle all events within each interactor by at least setting the mask to request all events, even if no action is taken for some of them.
2. Using the Supervise Tools
There are a variety of ways you can use the Supervise Tools:
Each of these uses of the Supervise tools will be discussed in turn.
- At the simplest level, you can use SuperviseWindow and SuperviseState in a visual program instead of using the Image tool. If you do not provide any custom UserInteractors, the built-in UserInteractors will be used. Alternatively, you can write your own UserInteractors (see "UserInteractors, Default and User-Defined") You might use the Supervise Tools in this way, for example, to create interactive panels of images.
- You could use the Supervise tools with Display, and control their behavior from an entirely custom graphical user interface, with no need for the Data Explorer user interface. You may use the built-in UserInteractors or provide your own. Your graphical user interface could communicate with Data Explorer either using the DXCallModule interface or using DXLink. The DXLink routines are discussed in detail in "The DXLink Developer's Toolkit" in the Data Explorer Programmer's Reference, while the use of DXCallModule is discussed in "DXCallModule", and the DXCallModule routines themselves are described on (the DXCallModule manual page in the programmer's reference). Some of the important differences between these approaches are discussed in "Using the Supervise Tools with a Separate Graphical User Interface using DXLink and DXCallModule".
Using the Supervise Tools in a Visual Program
Several examples of using the Supervise tools in a visual program can be found in /usr/lpp/dx/samples/SUPERVISE. The basic layout is shown in "supervise.net". The "mode" parameter to SuperviseState is set using a Selector interactor to one of 0, 1, or 2, to select one of the three built-in default UserInteractors (see"UserInteractors, Default and User-Defined"). (If the mode is set to -1, no interactors are enabled.) An initial object is passed to the "object" parameter of SuperviseState, along with an initial camera, in this case simply provided by the AutoCamera module. The choice of hardware or software rendering is made using the Options module (see the manual page for Options in the Data Explorer User Reference). The advantages of using the Supervise tools are only really found in more complex applications than the one shown in "supervise.net", which offers little advantage over the Image tool if you are using the Data Explorer User Interface (unless you write your own UserInteractors). For example, by using the fact that Supervise windows can be nested within other windows, you can create interactive windows-within-windows. The example "inset.net" shows the basic layout for creating nested windows. Two sets of SuperviseWindow/SuperviseState/Display are used, with one of the Supervise Window modules being passed as its parent input the window identifier from the other SuperviseWindow module. This sample can be found in /usr/lpp/dx/samples/programs/InsetImageSimple.net, while another sample can be found in /usr/lpp/dx/samples/programs/InsetImage.net. The latter sample uses a slightly more general macro to achieve the same result.
|supervise.net (click on figure to expand)
|inset.net (click on figure to expand)
Using the Supervise Tools with a Separate Graphical User Interface using DXLink and DXCallModule
One of the most important applications of the Supervise tools is to allow user interaction with images without the Data Explorer graphical user interface. Without the Supervise tools, user interaction is available only by using the Image tool, which is a DX user interface tool. However, with the Supervise tools, user interaction can be built into a custom gui, using Data Explorer only to do the data processing and rendering. There are two ways to approach using Data Explorer in this way: DXCallModule and DXLink.
The DXCallModule library allows you to call any of the Data Explorer modules from within a stand-alone program. Thus you could write your own gui, and, based on user choices in the custom interface, call particular Data Explorer modules. These modules can include the Supervise modules and Display. You can instruct the Display module to display the rendered image into your own gui, and you can either use the default UserInteractors provided with Data Explorer or define your own (see "UserInteractors, Default and User-Defined").
The DXLink API is another way for you to accomplish the same goal. In this case, you communicate directly with the Data Explorer executive to, for example, load and execute macros or visual programs, which in turn call Data Explorer modules. You can also send script language commands directly to the Data Explorer Executive. Again, you can use the built-in UserInteractors or define your own.
What is the difference, then, in these two approaches? The fundamental difference is that when you use DXCallModule, your program "owns main". It is your program which completely controls the order of execution of modules. Your program would also be responsible for any optimizations as far as not executing modules unless necessary (i.e. not executing modules whose inputs have not changed, or whose outputs are unnecessary). For example, if the user resizes the image window in the gui, it is necessary to rerender the image, but it is not necessary to recreate the object (for example, importing the data and running Isosurface on the data field). It would be the responsibility of your program to keep track of which modules needed to be run and which didn't.
On the other hand, when you use DXLink, the Data Explorer executive is a separate process, which "owns main" itself. You tell it what macro (set of tools) to run, and you tell it various inputs (such as data file names or isosurface values), and the Data Explorer executive decides which modules need to be run, and in what order they will be run. (Further details of the use of DXLink can be found in "The DXLink Developer's Toolkit" in the Data Explorer Programmer's Reference,, while detailed discussion of the use of DXCallModule can be found in "DXCallModule".)
You can accomplish the same goals using either of these approaches. The choice really depends on how much control you want to have over exactly how modules get executed in order to create the visualization. It is generally easier to create efficient applications using DXLink, both because you can use easily-created visual programs as the macros, and because you have the advantages of the Data Explorer Executive's intelligent flow management. However, you may prefer the tight control and lower overhead which is possible using DXCallModule. An example of a simple user interface with a few options has been created using both of these approaches. An example using DXCallModule can be found in /usr/lpp/dx/samples/callmodule/xapp_supervise.c. See the ReadMe file in the samples/callmodule directory for information on how to compile and run the example. An example using DXLink can be found in /usr/lpp/dx/samples/dxlink/xapp_supervise.c. Again, see the ReadMe file in the samples/dxlink directory for information on how to compile and run the example.
Both of these examples use the built-in rotation UserInteractor, although of course you could write your own UserInteractor (see "UserInteractors, Default and User-Defined"). The creation of the user interface is identical in both of these programs. Also, in both programs the basic sequence of modules is the same and is as follows:
The differences are as follows:
- Import a data file as specified by the user in the gui
- Create an Isosurface of the imported data
- Call SuperviseWindow to create the window, passing it the window id of the custom gui as the "parent" parameter
- Call SuperviseState, passing it the isosurface and a default camera. SuperviseState will act on events (such as dragging to rotate the isosurface)
- Call the Options module to set the rendering mode to hardware or software (based on user input in the gui)
- Call Display to render the isosurface and display it in the gui
- Defining the "macro": In the DXCallModule example, the subroutine DoIt contains the calls to Import, Isosurface, etc. Parameters which are specified via the gui (such as the data file name) are set using the calls DXModSetObjectInput, etc., before calling DXCallModule on the appropriate module, e.g. Import. In the case of the DXLink example, a simple macro is first defined and sent to the Data Explorer executive. This macro contains the modules listed above, along with calls to DXLInputNamed to allow specific inputs to be set in callbacks when particular buttons are pressed in the gui. For example, when the user changes the rendering mode, a simple call is made to send a new value to the DXLInputNamed tool which has been given the name "renderingmode". Then the macro is re-executed. (Note that because in this DXLink example the macro is defined using the scripting language, rather than as a visual program, it would also have been possible to directly send the parameter values using the DXLink routine DXLSend. DXLInputNamed is strictly necessary only when you are using a visual program, or .net file, to define the macro to be executed. DXLInputNamed is necessary when you are using a visual program because there would be no other way to easily associate a sent value with a particular module input. DXLInputNamed allows you to define a variable name to make this association.)
- Optimization: In the DXCallModule example, the subroutine DoIt contains some intelligence which causes it to call different sets of modules depending on which button was changed in the gui. For example, when the user changes the data file, all the modules are called, while if the user just changes the rendering mode between hardware and software, only Options and Display are called. This optimization is unnecessary in the case of DXLink, since the Data Explorer executive will do this optimization automatically. Thus in the DXLink example, it is simply necessary to change the parameter value and recall the macro; the Executive will ensure that only the necessary modules are run.
- The event loop: In the DXCallModule example, an X-event loop is created by a call to XtAppMainLoop. In order to ensure that the Supervise modules properly respond to mouse clicks or drags in the image window, and that changes to widgets are acted upon by the Data Explorer modules, a work process is added to the X event loop, called XCheckRIH. This routine gets called regularly whenever the application is not busy doing other things. XCheckRIH first calls DXCheckRIH to see if any registered input handlers need to be called (this is what ensures that the Supervise modules get called in response to mouse or keyboard clicks), and then calls the macro DoIt, which checks to see what modules need to be run. SuperviseWindow is always called by DoIt; if any events are output by SuperviseWindow (indicating that mouse or keyboard events occurred), or if any of the variables set by widgets (such as the filename) have changed, then the appropriate Data Explorer modules are called. In contrast, in the DXLink example, the event loop is managed entirely by Data Explorer, and DX behaves just as it would if you were running a visual program. From the DXLink program, you can load visual programs, set parameter values, cause executions, put Data Explorer in execute-on-change mode, or take Data Explorer out of execute-on-change mode.
- Callbacks: What happens inside callbacks for the widgets in the custom gui is different in these two examples. In the DXCallModule example, each callback sets one or more global variables. For example, if the data file is changed, the flag "changedfilename" is set to 1. The global variables are checked in the event loop worker routine, and if any of them are set, indicating that modules need to be re-called, the appropriate calls are made. Thus the callbacks do not, on their own, initiate the calls to Data Explorer; they merely set the conditions for the Data Explorer routines to be called the next time the worker routine is executed. In contrast, the callbacks in the DXLink example do directly initiate execution of Data Explorer. For example, when the data file name is changed, first execute-on-change mode is turned off. (This simply allows multiple changes to be made without triggering an execution.) Then the new filename is sent to Data Explorer, and the resetCamera and resetObject inputs to SuperviseState are set to 1 (recall that unless these inputs are set to 1, SuperviseState will continue to use the object and camera from the cache, which will be appropriate for the last object viewed, not the new one). The previously defined macro is executed once, then the resetCamera and resetObject parameters are set back to 0, and Data Explorer is placed in execute-on-change mode again, so that mouse events in the window can continue to be watched for and actied upon.
3. The Events Output, Picking, and UserInteractors
We will now discuss some of the technical details associated with the Supervise modules: the structure of the events output of SuperviseWindow, the implementation of Picking when using the Supervise modules, and UserInteractors, both the built-in default ones and how you would define your own UserInteractors.
The Events Output and Picking
As discussed in "UserInteractors, Default and User-Defined", UserInteractors can be defined (or the default ones can be used) to act on "events" which occur in the image window. These events can be either mouse clicks or keyboard presses. A UserInteractor modifies either the object being viewed, the camera, or both. What if you are interested in doing picking (using the mouse to select a particular point or object and then have something happen at that point or to that object)?
First let's review the process of picking when you are using the Image tool in the Data Explorer graphical user interface. Recall that picking means using the mouse to select a point on the surface of an object in a window. When you are using the Image tool, this is a built-in interaction mode, available from the View Control dialog. Because the user interface owns the window in which picking occurs, it has access to both the object in the window and the camera which was used to view it. This information is passed along with the x, y location of the pick (behind the scenes, and invisibly from the user's point of view) to the Pick module, which is placed in the visual program.
Clearly Pick needs both the object and the camera in order to translate an x,y location in pixels in the image window to a three-dimensional point located on the object being viewed.
The output of Pick is then available for the user to do whatever he or she wants with it. The output of Pick consists of a pick structure, which has "positions" and "data" which are immediately available to, for example, place text glyphs at the picked points. The pick structure also has identifying information about how the pick point can be accessed with in object hierarchy. This additional information is useful for a module which you might write to perform more complex operations on picks (see "Using the Pick Structure" in the Data Explorer Programmer's Reference).
There are no built-in UserInteractors which perform any kind of picking operations, primarily because defining a "default" pick operation would be somewhat arbitrary. Thus if you want to use picking with the Supervise modules, you must do it in one of two ways. You can do it either within the visual program, or within your own UserInteractor, which you write yourself. Here we will discuss the first of these options; the second will be discussed in"Building your Own UserInteractors".
The first difference in using the Pick tool with the Supervise modules instead of with Image is that the Data Explorer User Interface can no longer give Pick the necessary information to do picking: the object, camera, and x,y location of the pick. This is because the user interface does not own the image window (SuperviseWindow does), and in fact, there may be no Data Explorer user interface at all. Thus this information must be passed to Pick by you, the application developer. Pick has three inputs available for this information to be passed in. All of these inputs are hidden by default in the Data Explorer User Interface, but can be accessed using the Expand button in Pick's configuration dialog box.
The x,y pixel location of the pick or picks should be passed to the locations input of Pick. This information is available within the events structure as output by SuperviseState. The object being viewed and the camera being used should be passed from SuperviseState to the camera and object inputs of Pick respectively.
As mentioned above, the events output of SuperviseWindow or SuperviseState encodes mouse or keyboard events. It is an array of integer 8-vectors where the first five integers represent the following (the remaining three are reserved for future use):
- state or keypress, depending on event
- kstate (need more description here)
- event is one of DXEVENT_LEFT, DXEVENT_MIDDLE, DXEVENT_RIGHT, or DXEVENT_KEYPRESS.
- x and y are the pixel locations of the event.
- For event = DXEVENT_LEFT, DXEVENT_MIDDLE, DXEVENT_RIGHT, the final integer is "state", which is one of BUTTON_UP, BUTTON_MOTION, or BUTTON_DOWN.
- For event = DXEVENT_KEYPRESS, the fourth integer is "keypress" which is the character which was pressed, and the fifth is "kstate" which is whatever it is..
- Note that
Typically you would use the events output of SuperviseState rather than the events output of SuperviseWindow as input to Pick, as the events output of SuperviseState includes only those events which were not handled by a UserInteractor. (An exception would be if you wanted, for example, to use the action of a pick to change the UserInteractor mode. An example of this can be found in the sample UserInteractor in /usr/lpp/dx/samples/complexdemo.) It is not necessary for you to extract the x,y pixel location from the events structure before passing it to Pick; Pick is able to extract the appropriate information itself, and also pays attention only to button-down events. If however, you are interested in explicitly watching for particular mouse-buttons or particular types of event, then you can do so by pulling apart the events structure. The macro /usr/lpp/dx/samples/macros/GetEventsMacro.net does this.
- #define DXEVENT_LEFT 0x01
- #define DXEVENT_MIDDLE 0x02
- #define DXEVENT_RIGHT 0x04
- #define DXEVENT_KEYPRESS 0x08
- #define BUTTON_UP 1
- #define BUTTON_DOWN 2
- #define BUTTON_MOTION 3
Once you have the pick structure as output by Pick, you can do what you like with it. As a simple example, you can pass it to AutoGlyph to create a text glyph of the data value at the picked point. Then you need to collect the text glyph into the image being displayed. Here is where some complication occurs. If you simply Collect the object output of SuperviseState with the text glyph and pass the result to Display, Display will render and display the collected group, and store the collected group in the cache. Now suppose you pick again. On the following execution, the object output of SuperviseState will be the group containing both the original object and the first text glyph, which will now be collected with the second text glyph. So you will display, and cache, a group containing a group and a text glyph. Each text glyph will be added to the previous one in an ever-broadening hierarchy of groups. There's nothing inherently wrong about this, but what if you want to display only the last text glyph, rather than the last text glyph and all of the previous text glyphs as well?
You can instead always pass to Display a group containing two fields: the original object and the text glyph field. Instead of collecting the latest text glyph with the object output of SuperviseState (which includes the previous text glyph as well), first extract only the "data" field from the output of SuperviseState, and collect that with the latest text glyph. This procedure is demonstrated in the sample program /usr/lpp/dx/samples/programs/SupervisePicking.net. This discussion is meant primarily to alert you to the fact that an understanding of how objects and cameras are cached and retrieved is necessary in order to use the Supervise modules effectively. You may want to review the material in "Software Rendering and the Supervise Tools" and "Hardware Rendering and the Supervise Tools". In addition, if you are interested in more complex applications based on picking, you should refer to "Using the Pick Structure" in the Data Explorer Programmer's reference.
You may also want to look at the example visual programs found in /usr/lpp/dx/samples/supervise/imagedemo, which show how you can do picking within an image, to for example, extract the color of a picked pixel, or create a subset image based on two picked corner points in an original image.
UserInteractors, Default and User-Defined
UserInteractors are used by SuperviseState (and Display, in button-down execution within the X-event loop when you are using hardware rendering) to define what should happen to the object or camera based on mouse or keyboard events in the window. There are three built-in default UserInteractors which are used if you do not provide your own. Which UserInteractor is used depends on the setting of the mode parameter to SuperviseState. Setting mode to -1 (which is the default value) means that no UserInteractor is in use.
If you do not specify any UserInteractors (see "Other Environment Variables" in the Data Explorer User Guide), then three default UserInteractors are used. Rotation (mode 0) is the same as the standard left-button rotation interaction of the Image tool. Pan (mode 1) operates differently than the Data Explorer pan mode of the Image tool; in this case you simply drag on the object to move it in the desired direction. Zoom (mode 2) operates as follows: drag upward to zoom in; drag downward to zoom out.
Building your Own UserInteractors
A UserInteractors consists of a specification of the following routines, the contents of which must be provided by the application developer. Note that the UserInteractor does not call these routines; Data Explorer calls them at the appropriate time. The basic way UserInteractors work is as follows. An Initialization routine allocates a private handle, which will be available from all the routines which make up the interactor. This handle stores whatever information is necessary for the interactor; for example, camera parameters, the renderable object, the window size, etc. Other interactor routines serve to fill the handle with current information, operate on the contents of the handle based on events, or communicate the courrent contents of the handle back to Data Explorer. The location of the custom interactor object files is specified using the DX_USER_INTERACTOR_FILE environment variable (see "Other Environment Variables" in User Guide). Example UserInteractors may be found in the subdirectories of /usr/lpp/dx/samples/supervise.
void *InitMode(Object args, int width, int height, int *mask)
- Given an object containing args (which come in as an optional input to SuperviseState and the interpretation of which is free for the application developer to specify), and the current width and height of the window, returns a handle that is passed into all the other UserInteractor routines. This handle should be of a size to contain whatever information will be necessary for the UserInteractor to do its work. For example, if the camera is going to be modified by the UserInteractor, then space should be allocated in the handle to hold all of the camera parameters. This routine also sets the value of a mask to reflect which events the particular interactor is interested in (for example, only left or right buttons). Once mask is set, only those events which have been specified as being interesting to the interactor will cause the interactor to be called. The set of possible masks is:
void EndMode(void *handle)
- Frees the space for the handle allocated in InitMode.
void SetCamera(void *handle, float *to, float *from, float *up, int projection, float
fov, float width)
- Passes the current camera information from Data Explorer into the interactor, which stores it in its private handle. The UserInteractor does not call this function, but if it is going to modify the camera, must provide its contents, which should extract whatever camera information it is interested in and put it into its private handle. Note that if the UserInteractor is going to modify the camera, the handle must retain the entire camera state so that it can be passed back later in GetCamera().
void SetRenderable(void *handle, Object object)
- Passes the current object from Data Explorer into the interactor, which stores it in its private handle. The UserInteractor does not call this function, but if it is going to modify the object, must provide its contents. Note that if the interactor is going to change the object, the object must be retained in the handle so that it can be passed back later in GetObject.
int GetCamera(void *handle, float *to, float *from, float *up, int *projection, float
*fov, float *width)
- Passes updated camera information from the interactor's handle back to Data Explorer. The UserInteractor does not call this routine itself, but if it modifies the camera, it must provide its contents so that Data Explorer can call it to obtain the current camera (as possibly modified by the UserInteractor). If the interactor has not updated the camera information, this routine should return 0; otherwise it should set ALL the inputs and return 1.
int GetRenderable(void *handle, Object *obj)
- Passes updated object information from the interactor's handle back to Data Explorer. The UserInteractor does not call this routine itself, but if the UserInteractor modifies the object it must provide its contents so that Data Explorer can call it to obtain the current object (as possibly modified by the UserInteractor). If the interactor has not updated the object, this routine returns 0, otherwise it should set obj to point to the updated object and return 1.
void EventHandler(void *handle, DXEvent *event)
A DXEvent is of type:
- Event handler. Receives the event in *event. This routine then performs whatever action is indicated by the event using the information stored in the hande, which may include the current camera, object, image size, etc.. The updated object or camera should then be put into the private handle, where it will be available to Data Explorer via GetCamera and GetRenderable.
and where DXAnyEvent, DXMouseevent, and DXKeyPressEvent are of type:
event is one of DXEVENT_LEFT, DXEVENT_MIDDLE, DXEVENT_RIGHT, or DXEVENT_KEYPRESS; x and y are the pixel location of the event; state is one of BUTTON_DOWN, BUTTON_MOTION, or BUTTON_UP; and key is the key that was pressed.
As discussed previously, SuperviseState has a mode parameter which allows one of several UserInteractors to be invoked at any given time. These modes are specified by setting up an interactor table of type UserInteractor, where
typedef void *(*InitMode)(Object args, int width, int height, int *mask);
typedef void (*EndMode)(void *handle);
typedef void (*SetCamera)(void *handle, float *to, float *from, float *up,
int projection, float fov, float width);
typedef int (*GetCamera)(void *handle, float *to, float *from, float *up,
int *projection, float *fov, float *width);
typedef void (*SetRenderable)(void *handle, Object obj);
typedef int (*GetRenderable)(void *handle, Object *obj);
typedef void (*EventHandler)(void *handler, DXEvent *event);
typedef struct _userInteractor UserInteractor;
An example of a very simple custom interactor, which pays attention only to left-mouse button events to zoom the camera in or out, can be found in /usr/lpp/dx/samples/supervise/simpledemo. This UserInteractor does not modify the object being viewed; it modifies only the camera.
If you are interested in doing picking with your Supervise modules, you can either do it within the visual program as discussed in "The Events Output, Picking, and UserInteractors", or you can do it within your own custom UserInteractor. An example of the latter can be found in /usr/lpp/dx/samples/supervise/mediumdemo. In this example, DxCallModule is used to call the Pick tool from within the UserInteractor. Pick returns the location of the picked point, and then AutoGlyph is used to create a text glyph of the data value at that point. The UserInteractor modifies the object (rather than the camera) by adding the text glyph to it, showing the data value at the picked location. This example also shows how you might add a caption at a position on the screen where an user starts typing characters.
A more complex example, which modifies both the camera and the object, can be found in /usr/lpp/dx/samples/supervise/complexdemo. This example can move objects within a displayed group independently (by modifying the transform at the top of the specified object), and can add captions to a location in the scene as specified by a mouse click, as well as move captions once they are placed.
The DXCallModule access routines enable the programmer to call Data Explorer modules through an interface similar to the scripting language. Modules are called by name, and parameters are specified as name-value pairs, freeing the programmer from having to supply values for all possible parameters. Optional parameters use the same defaults as they would if executed directly by the executive. (If other parameters are added in subsequent releases, the call remains upwardly compatible.)
Data Explorer modules can be called by inboard, outboard, and runtime loadable modules used within Data Explorer. They can also be called by stand-alone programs (for examples, see /usr/lpp/dx/samples/callmodule). Such stand-alone programs may include, for example, simple data processing and rendering programs which produce an image file as output, or an elaborate custom graphical user interface which allows user interactivity with an image. This later application is discussed in "Using the Supervise Tools with a Separate Graphical User Interface using DXLink and DXCallModule".
Briefly, this is how DXCallModule is used:
Note that Objects passed as input parameters to DXCallModule will be deleted when the called module is finished. For example, if you first call Import to import a data field, and then pass that field as an input to the Isosurface module, the original imported data field will be deleted once Isosurface is called. If you want to use the Object later (for example, as input to another module, say MapToPlane) then you must call DXReference on the Object (the data field) before calling DXCallModule for Isosurface. You are then responsible for calling DXDelete on the Object when you are finished with it.
- The programmer first declares a ModuleInput array and a ModuleOutput array. These should be declared large enough to handle as many inputs or outputs as might be necessary for the modules to be called. For example, if a particular module will require 8 inputs to be set, then declare a ModuleInput structure as ModuleInput MyModuleInputStructure.
- Next, the values of the required input parameters are set using the routine DXModSetObjectInput. This routine takes as input the address of one of the elements of the ModuleInput array, the name of the parameter, and the Data Explorer Object which should be passed to that parameter. For example, if you are specifying an isovalue, the third parameter must be a Data Explorer Array (type float or int, category real, rank 0, or rank 1 and shape 1) containing the isovalue; it cannot simply be a floating point value. For this reason, you may find it helpful to use the convenience routines DXModSetFloatInput, DXModSetIntegerInput, or DXModSetStringInput, which automatically create Data Explorer Objects for you from specified floating point values, integers, or character strings, respectively.
- DXModSetObjectInput, or the other convenience routines, are called for other parameters of the given module until all necessary parameters have been specified. Note that if you prefer, you need not specify parameters by name; if the name is unspecified, then parameters are assumed to be specified positionally, that is, in order. In this case, however, you must specify all parameters up to the last one set to a non-default value, even if you want to use default values for some of the earlier parameters (in which case the value should be specified as a null input).
- DXModSetObjectOutput is called, passing in the address of an element of the ModuleOutput array, the name of the output parameter (recall that some modules have more than one output), and the address of an Object into which the result of the module should be placed.
- DXCallModule is called, passing in the name of the module (for example "Isosurface"), the number of input parameters, the ModuleInput array, the number of output parameters, and the ModuleOutput array. DXCallModule returns OK or ERROR depending on whether the module returns an error or not.
- In addition, if DXCallModule is being used in a stand-alone program or outboard module (as opposed to an inboard module or runtime-loaded module), the programmer must call DXInitModules() before calling any of the other DXCallModule routines, and it is necessary to link to the library libDXcallm.a.
The following simple example calls the Slab module:
Error Slab1(Object toBeSlabbed, int dimension, int position, Object *slabbedObject)
The following modules cannot be called by DXCallModule:
DXModSetObjectInput(&in, "input", toBeSlabbed);
DXModSetIntegerInput(&in, "dimension", dimension);
DXModSetIntegerInput(&in, "position", position);
DXModSetObjectOutput(&out, "output", SlabbedObject);
result = DXCallModule("Slab", 3, in, 1, out);
Interactors (because these are Data Explorer User Interface tools):
Flow Control (because these tools reside in the Executive, which is not active when you are using DXCallModule. The function of these tools can typically be accomplished simply by programming within the DXCallModule program.
Interface Control (because these tools control behavior of the Data Explorer User Interface, which is not active when you are using DXCallModule)
Special (because these are Data Explorer User Interface tools)
DXLink (because these tools are used only when you are writing a DXLink program)