26.5. Analysis plug-ins

26.5.1. The AbstractAnalysisPlugin class
26.5.2. The AnalysisFilterPlugin interface

A plug-in becomes an analysis plug-in simply by returning Plugin.MainType.ANALYZE from the Plugin.getMainType() method. The information returned from InteractivePlugin.getGuiContexts() should include: [Item.BIOASSAYSET, Type.ITEM] or [Item.DERIVEDBIOASSAYSET, Type.ITEM] since web client doesn't look for analysis plug-ins in most other places. If the plug-in can work on a subset of the bioassays it may also include [Item.BIOASSAY, Type.LIST] among the contexts. This will make it possible for a user to select bioassays from the list and then invoke the plug-in. The following code examples are taken from an analysis plug-in that is used in an experiment context.

private static final Set<GuiContext> guiContexts = 
   Collections.singleton(new GuiContext(Item.BIOASSAYSET, GuiContext.Type.ITEM));

public Set<GuiContext> getGuiContexts()
{
   return guiContexts;
}

If the plug-in depends on a specific raw data type or on the number of channels, it should check that the current bioassayset is of the correct type in the InteractivePlugin.isInContext() method. It is also a good idea to check if the current user has permission to use the current experiment. This permission is needed to create new bioassaysets or other data belonging to the experiment.

public boolean isInContext(GuiContext context, Object item)
{
   if (item == null)
   {
      message = "The object is null";
   }
   else if (!(item instanceof BioAssaySet))
   {
      message = "The object is not a BioAssaySet: " + item;
   }
   else
   {
      BioAssaySet bas = (BioAssaySet)item;
      int channels = bas.getRawDataType().getChannels();
      if (channels != 2)
      {
         message = "This plug-in requires 2-channel data, not " + channels + "-channel.";
      }
      else
      {
         Experiment e = bas.getExperiment();
         e.checkPermission(Permission.USE);
      }
   }
}

The plug-in should always include a parameter asking for the current bioassay set when the InteractivePlugin.getRequestInformation() is called with command = Request.COMMAND_CONFIGURE_JOB.

private static final RequestInformation configurePlugin;
private RequestInformation configureJob;
private PluginParameter<BioAssaySet> bioAssaySetParameter;

public RequestInformation getRequestInformation(GuiContext context, String command) 
   throws BaseException
{
   RequestInformation requestInformation = null;
   if (command.equals(Request.COMMAND_CONFIGURE_PLUGIN))
   {
      requestInformation = getConfigurePlugin(context);
   }
   else if (command.equals(Request.COMMAND_CONFIGURE_JOB))
   {
      requestInformation = getConfigureJob(context);
   }
   return requestInformation;
}

private RequestInformation getConfigureJob(GuiContext context)
{
   if (configureJob == null)
   {
      bioAssaySetParameter; = new PluginParameter<BioAssaySet>(
         "bioAssaySet",
         "Bioassay set",
         "The bioassay set used as the source for this analysis plugin",
         new ItemParameterType<BioAssaySet>(BioAssaySet.class, null, true, 1, null)
      );

      List<PluginParameter<?>> parameters = new ArrayList<PluginParameter<?>>();
      parameters.add(bioAssaySetParameter);
      // Add more plug-in-specific parameters here...
		
      configureJob = new RequestInformation(
         Request.COMMAND_CONFIGURE_JOB,
         "Configure job",
         "Set parameter for plug-in execution",
         parameters
      );
   }
   return configureJob;
}

Of course, the InteractivePlugin.configure() method needs to validate and store the bioassay set parameter as well:

public void configure(GuiContext context, Request request, Response response)
{
   String command = request.getCommand();
   try
   {
      if (command.equals(Request.COMMAND_CONFIGURE_PLUGIN))
      {
         // Validate and store configuration parameters
         response.setDone("Plugin configuration complete");
      }
      else if (command.equals(Request.COMMAND_CONFIGURE_JOB))
      {
         List<Throwable> errors = 
            validateRequestParameters(configureJob.getParameters(), request);
         if (errors != null)
         {
            response.setError(errors.size() +
               " invalid parameter(s) were found in the request", errors);
            return;
         }
         storeValue(job, request, bioAssaySetParameter);
         // Store other plugin-specific parameters
			
         response.setDone("Job configuration complete", Job.ExecutionTime.SHORT);
      }
   }
   catch (Throwable ex)
   {
      // Never throw exception, always set response!
      response.setError(ex.getMessage(), Arrays.asList(ex));
   }
}

Now, the typical Plugin.run() method loads the specfied bioassay set and its spot data. It may do some filtering and recalculation of the spot intensity value(s). In most cases it will store the result as a child bioassay set with one bioassay for each bioassay in the parent bioassay set. Here is an example, which just copies the intensity values, while removing those with a negative value in either channel.

public void run(Request request, Response response, ProgressReporter progress)
{
   DbControl dc = sc.newDbControl();
   try
   {
      BioAssaySet source = (BioAssaySet)job.getParameter("bioAssaySet");
      // Reload with current DbControl
      source = BioAssaySet.getById(dc, source.getId());
      int channels = source.getRawDataType().getChannels();
      
      // Create transformation and new bioassay set
      Job j = Job.getById(dc, job.getId());
      Transformation t = source.newTransformation(j);
      t.setName("Copy spot intensities >= 0");
      dc.saveItem(t);

      BioAssaySet result = t.newProduct(null, "new", true);
      result.setName("After: Copying spot intensities");
      dc.saveItem(result);

      // Get query for source data
      DynamicSpotQuery query = source.getSpotData();
      
      // Do not return spots with intensities < 0
      for (int ch = 1; ch <= channels; ++ch)
      {
         query.restrict(
      	    Restrictions.gteq(
      	       Dynamic.column(VirtualColumn.channel(ch)),
      	       Expressions.integer(0)
      	    )
      	 );
      }
      
      // Create batcher and copy data
      SpotBatcher batcher = result.getSpotBatcher();
      int spotsCopied = batcher.insert(query);
      batcher.close();
      
      // Commit and return
      dc.commit();
      response.setDone("Copied " + spotsCopied + " spots.");
   }
   catch (Throwable t)
   {
      response.setError(t.getMessage(), Arrays.asList(t));
   }
   finally
   {
      if (dc != null) dc.close();
   }
}

See Section 29.5, “The Dynamic API” for more examples of using the analysis API.

26.5.1. The AbstractAnalysisPlugin class

This class is an abstract base class. It is a useful class for most analysis plug-ins used in an experiment context to inherit from. Its main purpose is to define PluginParameter objects that are commonly used in analysis plug-ins. This includes:

  • The source bioassay set: getSourceBioAssaySetParameter(), getCurrentBioAssaySet(), getSourceBioAssaySet()

  • The optional restriction of which bioassays to use. All bioassays in a bioassay set will be used if this parameter is empty. This is useful when the plugin only should run on a subset of bioassays in a bioassay set: getSourceBioAssaysParameter(), getSourceBioAssays()

  • The name and description of the child bioassay set that is going to be created by the plug-in: getChildNameParameter(), getChildDescriptionParameter()

  • The name and description of the transformation that represents the execution of the plug-in: getTransformationNameParameter(), getTransformationName()

26.5.2. The AnalysisFilterPlugin interface

The net.sf.basedb.core.plugin.AnalysisFilterPlugin is a tagging interface, with no methods, that all analysis plug-ins that only filters data should implement. The benefit is that they will be linked from the Filter bioassay set button and not just the Run analysis button. They will also get a different icon in the experiment outline to make filtering transformations appear different from other transformations.

The interface exists purely for making the user interaction better. There is no harm in not implementing it since the plug-in will always appear in from the Run analysis button. On the other hand, it doesn't cost anything to implement the interface since it doesn't have any methods.