Context Navigation

Introduction

MindSeer, designed and created by Eider Moore, is a data visualization program implemented in Java. It currently supports a variety of data types including volumetric data, 3D surfaces, and 3D point data. Furthermore, it was designed to support easy development of plug-ins for handling additional data types and visualization techniques. This document is intended to provide an overview on what is entailed in creating a plug-in for MindSeer.

Classes & Interfaces to Override & Implement

Creating a plug-in for handling a new data type or a new visualization method may require implementing at least one of the classes and interfaces listed below. Depending on the complexity or existing support for that new ability, a developer may have to implement all of the main components; however, it remains a good idea to check if certain elements of the new task are at least partially supported in order to maximize code reuse. This section lists each of the main interfaces and abstract base classes that a plug-in may need to implement. Most of the interfaces have a partial default implementation which can handle returning default values for less commonly overridden methods.

Implementations of the ​CreateFileFilter interface declare the types of supported files. They also link decoders and encoders to file types through static calls to the factory-class ​CodecFactory. There are several useful file type helpers in the edu.washington.biostr.sig.
vault.files package for defining these types.

Implementations of the Decoder interface are designed to load a specified file, or a group of files, into a single, corresponding instance of the Data class. Decoders are called upon to load these files to respond to user or programmatic requests to load a file group that has not already been loaded or is not currently in memory. Each type of Decoder is only expected to handle at least one file type (or group of file types).

Implementations of the Encoder interface are designed to write an instance of the Data class out to a single file or a group of files. Encoders are called upon to save the files at a user’s request. Encoders may also be used to save the data to a temporary disk cache during low memory conditions, if the programmer deems the encoder sufficiently fast. Each type of Encoder is only expected to handle at least one Data type.

The Data class is designed to store a single set of data; for example, an instance of the VolumeData? class allocates a large array for storing volumetric data. Data can be mutable or static; however, implementers must account for the fact that even if the data is used in multiple viewers or used by multiple users at a given instant, only one instance of the Data object exists for a given group of files. Thus the data can be shared among multiple views. This is done to reduce loading times and memory impact for repeated load requests.

Implementations of the ​DataView interface handle the multi-user situation. While only one instance of the Data object may exist for a given file group, each of the users’ viewers uses its own instance of the ​DataView. ​DataViews are designed to provide a layer between the source data and the viewer. They also handle changed parameter events and store any necessary rendering attributes. The minimum implementation of a new visualization method of an existing data type will likely implement this interface.

Implementations of the DataController? interface provide a user interface for manipulating the settings for a particular ​DataView instance. These controls will appear on the Data tab. GUI events are processed through the Listener interface’s method somethingHappened(Event) and, after processing, should be dispatched to the appropriate ​DataView. Generally, controls for this type of controller should only manipulate the ​DataView instance. If no such controls are to be presented to the user, then use an instance of NullController?.

The ViewPort? class is designed to provide a visual representation of a collection of data sets. It creates and uses a DataModel?, described below, to manage the storage of each of the data views present in the viewer. In general, this class should only handle ViewPort?-related events and rendering procedures.

Implementations of the DataModel? interface manage the storage of DataView? instances. It should also make appropriate calls upon the DataViews? to tell them when they are added or removed from the model to ensure that proper initialization and cleanup occurs within the DataView?. The may assist the ViewPort? in collecting some rendering information as it may be a more efficient. In general, there is a one-to-one link between a ViewPort? instance and a DataModel? instance; DataModels? are not shared among ViewPorts?.

Implementations of the ViewPortController? interface provide a user interface for manipulating the settings for a particular ViewPort? instance. These controls will appear on the View tab. GUI events are processed through the Listener interface’s method somethingHappened(Event) and, after processing, should be dispatched to the appropriate ViewPort?. Generally, controls for this type of controller should only manipulate the ViewPort? instance. If no such controls are to be presented, then no declaration of a class implementing ViewPortController? is necessary.

Many computational tasks may take a long time to complete. Often these tasks respond to user input. In order to avoid tying up the event handling thread, these long tasks should be relegated to a class implementing the LongTask? interface. The basic structure of a long task is to first do an extended computation, then update the DataView? and the user interface. To ensure serial behavior of the updates, the two updates will occur on a two threads assigned to that particular DataView? and that DataView?’s controller respectively. In general, implementations of this interface need only be implemented as inner classes to the class that needs the long task completed. Despite this allowance, the doWork(…) method should not make any references to the outer class’s instance variables, as those values may change asynchronously.

Registering New Classes

Because MindSeer is implemented using a factory-style of programming, existing code can immediately access new implementations of existing abstract classes and interfaces simply by registering the new code.
In the resources folder, there is a file named mindseer.xml. It contains a listing of all registered CreateFileFilters? and all mappings from Data to ​DataView, ViewPort? to ViewPortControllers?, and ​DataView to default ViewPort?. Many examples are already listed in this file, and there are XML comments provided in the file to explain the XML schema used.

Helpers

In order to make implementation of current code and future plug-ins easier, there are many helper classes, usually affixed with the word “Helper” at the end of the class name. These provide some supplementary and useful implementation for performing various tasks that span across the MindSeer code base.
One of the helpers, named class Helper, provides easy controls for dispatching LongTasks?. Additionally, it provides getter methods for the timer, which displays the status of a long operation, and the session, for loading files and issuing other main UI events.
Another helpful class is the SharingDataView?<D extends Data, S> class, which derives from the ​DataView interface. It provides support for data views that need to know of the existence of other identically typed data views in their model. While it is possible to extract this information without using this class, using this class makes it easy to use an auxiliary class for managing these relationships. The VolumePlaneView? and ColorizedVolumeSurface3DView classes are good examples of using this helper class.

Summary

To conclude, it is relatively easy to create a simple plug-in to MindSeer. More complicated plug-ins may require writing code over many of the interfaces and the creation of several supporting classes. In any case, there are many examples of how to go about creating an addition to the program within the source code. The plain text handler and 2D image handler are both good examples of writing a simple plug-in that spans all of the necessary classes for handling a completely new data type. Additionally, many of the necessary interfaces and supporting classes are supplemented with ​JavaDoc to aid in understanding their functionality. By examining this framework, future developers should be able to extend MindSeer with ease.