How to write custom actions for TrackMate

Extending TrackMate
TrackMate can be extended with new modules covering about everything it does, thanks to several nice features of SciJava. These tutorials explain how to do so. They are best read in order.


Introduction.

Actions were my crude solution to the problem of adding random features to TrackMate without having to change the GUI too much. Adding buttons or dialogs or extra panels is cumbersome and I thought it would complexify the GUI, which is meant to be simple. A TrackMate action is a lazy workaround for this problem. You must keep in mind that is a placeholder for random feature ideas, and provided a quick and dirty way to test them.

A TrackMate action takes the shape of an item in a drop-down list in the last panel of the GUI. It can do more or less anything, since we pass everything to the action, even a reference to the GUI itself. Thanks to the SciJava discovery mechanism, we do not have to worry about adding it on the GUI: it will automatically be listed in the action list. The drawback of this simplicity is that you cannot use it to provide elaborated user interaction mechanisms, such as the ones you can find in a view.

In this tutorial, we will use it to launch the event logger we created in the previous tutorial of this series. If you remember, we saw in the last paragraph how to use the visible = false parameter the SciJava annotation to hide it from the view menu. Hereby preventing the user to access it. No problem, we will now build an action that will launch it as a supplementary view.


The TrackMateActionFactory interface.

Again, the action behavior and its integration in TrackMate are split in two classes. The behavior is described by the TrackMateAction interface. The integration mechanism is controlled by the TrackMateActionFactory interface, which extends the TrackMateModule interface.

SciJava parameters recap.

There is not much to say about the factory itself. Ii is the class that must be annotated with

@Plugin( type = TrackMateActionFactory.class )

All the SciJava annotation parameters apply, and they have the following meaning:

  • The enabled = true/false parameter is used to control whether the action is enabled or not. A disabled action is not even instantiated.
  • The visible = true/false parameter determines whether the action is listed in the action panel. If false, the action factory is instantiated but the corresponding action will not be listed in the panel, preventing any use.
  • The priority = double parameter is used here just to determine the order in which the action items appear in the list. High priorities are listed last.


Action factory methods.

As of TrackMate version 2.2.0 (March 2014), actions are the only TrackMate modules that use the getIcon() method. The icon is then displayed in the action list, next to the action name. That's it for the TrackMateModule part.

The method specific to actions is more interesting:

@Override
public TrackMateAction create( final TrackMateGUIController controller )

This means that when we create our specific action, we have access to the some of GUI context through the controller. We therefore have to check its API to know what we can get. It gives us access to:

  • The GUI window itself (public TrackMateWizard getGUI()), that we can use as parent for dialogs, wild live GUI editing...
  • The trackmate plugin (public TrackMate getPlugin()), hereby the model and settings objects.
  • The selection model (public SelectionModel getSelectionModel())
  • Even the GUI model (public TrackMateGUIModel getGuimodel())
  • And all the providers that manage the modules of TrackMate.

So you can pretty well mess stuff with the controller, but it gives us access to mainly everything. In our case, we do not need much. Here is the code for our simple event logger launcher:

package plugin.trackmate.examples.action;

import javax.swing.ImageIcon;

import org.scijava.plugin.Plugin;

import fiji.plugin.trackmate.action.TrackMateAction;
import fiji.plugin.trackmate.action.TrackMateActionFactory;
import fiji.plugin.trackmate.gui.TrackMateGUIController;

@Plugin( type = TrackMateActionFactory.class )
public class LaunchEventLoggerActionFactory implements TrackMateActionFactory
{

	private static final String INFO_TEXT = "<html>This action will launch a new event logger, that uses the ImageJ log window to append TrackMate events.</html>";

	private static final String KEY = "LAUNCH_EVENT_LOGGER";

	private static final String NAME = "Launch the event logger";

	@Override
	public String getInfoText()
	{
		return INFO_TEXT;
	}

	@Override
	public ImageIcon getIcon()
	{
		return null; // No icon for this one.
	}

	@Override
	public String getKey()
	{
		return KEY;
	}

	@Override
	public String getName()
	{
		return NAME;
	}

	@Override
	public TrackMateAction create( final TrackMateGUIController controller )
	{
		return new LaunchEventLoggerAction( controller.getPlugin().getModel(), controller.getSelectionModel() );
	}

}

Nothing complicated.


The TrackMateAction interface.

This interface is just made of two methods:

public void execute(TrackMate trackmate);

public void setLogger(Logger logger);

The execute method is the one triggered by the user when he clicks the Execute button. It receives a TrackMate instance that can be of use. In our case, as you saw in the factory class, we got the model and selection model through the controller.

The other method is used to pass a logger instance that is specific to the action panel in the GUI. All messages and updates sent to this logger will be shown on the action panel.

Here is how this translates simply in a simple launcher:

package plugin.trackmate.examples.action;

import plugin.trackmate.examples.view.EventLoggerView;
import fiji.plugin.trackmate.Logger;
import fiji.plugin.trackmate.Model;
import fiji.plugin.trackmate.SelectionModel;
import fiji.plugin.trackmate.TrackMate;
import fiji.plugin.trackmate.action.TrackMateAction;

public class LaunchEventLoggerAction implements TrackMateAction
{

	private final SelectionModel selectionModel;

	private final Model model;

	private Logger logger;

	public LaunchEventLoggerAction( final Model model, final SelectionModel selectionModel )
	{
		this.model = model;
		this.selectionModel = selectionModel;
	}

	@Override
	public void execute( final TrackMate trackmate )
	{
		logger.log( "Launching a new event logger..." );
		final EventLoggerView view = new EventLoggerView( model, selectionModel );
		view.render();
		logger.log( " Done.\n" );
	}

	@Override
	public void setLogger( final Logger logger )
	{
		this.logger = logger;
	}
}


Wrapping up

And here are the results:

TrackMate CustomAction 1.png

You can imagine a lot of applications for Actions. Since they give you access to most of the plugin context, you can basically plug anything there. The one limitation is that it does not fit perfectly in the existing GUI: actions just appear as items in a drop-down list. But in most cases it does not matter much. Actions are very useful to quickly graft a piece of new functionality on TrackMate.

This concludes this tutorial, which was pretty quick and simple. This is unfortunately the last time in this series that things are simple and short. The next tutorial will be about implementing a custom detector, which will turn to be quite complicated for apparently wrong reasons. See you there!