LTPDA Extension Modules


As of Version 2.4, LTPDA now supports extension modules. This should allow users to extend LTPDA to provide more specific functionalities for their own context.



What is an Extension Module?

Extension modules are a collection of class methods, new user classes, built-in models, utility functions, examples, source files, and unit tests. All extension modules have the following structure on disk:

My_Module/
|-- README.txt
|-- classes
|   |-- README_classes.txt
|-- examples
|-- functions
|   |-- README_functions.txt
|-- jar
|   |-- README_jar.txt
|-- models
|   |-- README_models.txt
|-- moduleinfo.xml
|-- pipelines
|   |-- README_pipelines.txt
|-- tests
    |-- README_tests.txt
    |-- classes
    |   |-- README_class_tests.txt
    |-- models
        |-- README_model_tests.txt
  
The file moduleinfo.xml contains the name and version of the module. It is not necessary that the module name and the containing directory are the same, though they may be. To build an extension module, see Section Building your own Extension Module. The various directories and their uses are described in the following sections.

classes

The classes directory can contain either new LTPDA user classes (see Section New User Classes) or by adding methods to existing LTPDA user classes (see Section Adding New Methods).

examples

The examples directory is meant to contain useful examples for users.

jar

Since MATLAB is built on top of java, one useful way to extend the functionalities is to create java classes and methods, or even graphical user interfaces. The LTPDA startup script (ltpda_startup) will take care of properly installing any jar files (java archive files) contained within this directory.

models

Here you should place any built-in models, either for existing LTPDA user classes, or for new user classes defined in this module. The LTPDA built-in model framework looks in this directory (and any sub-directories) for built-in models.

pipelines

This directory is meant to hold any LTPDA pipelines or analysis workflows which you want to distribute to users.

tests

The tests directory should contain unit-tests for all new class methods, user classes and built-in models in this module. For help in writing unit tests see Section Unit Tests.



Building your own Extension Module

Building your own extension modules starts by preparing the directory structure on disk. For this we have a convenient utility method:

    >> utils.modules.buildModule('/some/path/', 'My_Module')


Installing Extension Modules

Installing an LTPDA Extension Module is straightforward. Start the LTPDA Preferences:

    >> LTPDAprefs

Select the 'Extensions' tab. Click the 'Browse' button to locate the module directory on disk, or directly type the path to the module in the input text field. Click the 'plus' button to add this extension to the list. You should see some activity on the MATLAB terminal as LTPDA will start installing any extension methods for existing user classes. Removing an extension module is just a case of selecting the module in the list and clicking the 'minus' button.

Note: after installing an extension module, in order to make new methods available to the workbench, you need to rebuild the library from the workbench menu: "Tools -> Rebuild LTPDA Library". This will take a couple of minutes, but afterwards the new methods from the extension module should be available on the workbench.



Adding New Methods

New methods can be added to existing LTPDA user classes. For example, you can add a new method to the Analysis Object class (ao) by creating a sub-directory of the classes directory called ao then put your new method in there. For example, suppose we want to create a new AO method called myCalibration. We create a directory ao in My_Module/classes then add the new file myCalibration.m to that directory. In order for the new method to work, the LTPDA startup function ltpda_startup copies all methods for core LTPDA user classes in to their correct class directories. In order to write a correct LTPDA user-class method, it is recommended to look at some examples, such as ao/abs, ao/average, or ao/psd. You can also extend other existing user classes in the same way. Just make a directory of the correct class name (remember to leave off the @ from the directory name; this is not supposed to be a 'normal' MATLAB class directory) and put your new methods in there.



New User Classes

This is an advanced topic, and it is assumed that you are familiary with creating MATLAB classes already. To get familiar, read the MATLAB documentation on Object-Oriented Programming in the user manual.

You can create new user classes in the classes directory. These follow MATLAB rules for classes, i.e., the directory name begins with a @ and contains a constructor file with the same name. For example, suppose we want to create a new user class which stores trigger events from some experiment. Each trigger event has the following properties: a trigger time, an amplitude, and a frequency. We would create a new directory under classes called @Trigger and an associated constructor file like this:

    >> cd 'My_Module/classes'
    >> mkdir @Trigger
    >> edit @Trigger/Trigger.m

The constructor file should declare that this new Trigger class is a subclass of the LTPDA user-object base class ltpda_uoh.

% TRIGGER constructor for Trigger class.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% DESCRIPTION: TRIGGER constructor for Trigger class.
%
% CONSTRUCTOR:
%
%       t = Trigger()       - creates an empty trigger object
%
% Parameter Sets
%
% VERSION:     $Id$
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

classdef Trigger < ltpda_uoh
  
  %---------- Public (read/write) Properties  ----------
  properties
    time      = time();
    amplitude = [];
    frequency = [];
  end
    
  methods
    function obj = Trigger(varargin)
      
      import utils.const.*
      utils.helper.msg(msg.PROC3, 'running %s/%s', mfilename('class'), mfilename);
      
      % do some initialisation of the object
      
    end % End constructor
    
  end
  
end

Various methods need to be defined by any new user class, in particular, the following abstract methods need to be created:

Method name Description

attachToDom

Defines how the object is serialized to an XML DOM.

char

Creates a character representation of the object, typically for use in display.

copy

Makes a deep or shallow copy of the object, depending on the passed argument.

display

Defines how the object is displayed on the MATLAB terminal.

fromDom

Constructs an object from an XML DOM.

loadobj

This function is called is MATLAB is unable to load an object from a MAT file, for example if the class structure changes between versions. This gives an opportunity to update the loaded structure before trying to create an object from it. For more details, see MATLAB's help topic >>help loadobj

update_struct

Define rules how to update structure representation of an object between versions.

generateConstructorPlist

Given an instance of the user object, this method generates a plist which can be used to construct an object with the same properties.

In most cases, copying these methods from an existing LTPDA user class, for example, ao, is a good start.

In addition to defining these abstract methods, you typically need to overload some static methods (which we usually place inside the class constructor file). The following code fragment shows the necessary methods needed to complete our Trigger example.

  methods (Static)
    
    % This provides the hook for the command <class>.getBuiltInModels. 
    function mdls = getBuiltInModels(varargin)
      mdls = ltpda_uo.getBuiltInModels('Trigger');
    end
    
    % Here we typically return the CVS version or some other version string 
    function out = VEROUT()
      out = '$Id$';
    end
    
    % This provides the hook for the command <class>.getInfo. 
    function ii = getInfo(varargin)
      ii = utils.helper.generic_getInfo(varargin{:}, 'Trigger');
    end
    
    % Here we return a list of parameter sets that this constructor can handle. 
    function out = SETS()
      out = [SETS@ltpda_uoh, ...
        {'Default'}             ...
        ];
    end
    
    
    % This returns a parameter list for a given parameter set. 
    % The use of the MATLAB 'persistent' keyword means we don't repeatedly build the same plist.
    function plout = getDefaultPlist(set)
      persistent pl;
      persistent lastset;
      if exist('pl', 'var')==0 || isempty(pl) || ~strcmp(lastset, set)
        pl = Trigger.buildplist(set);
        lastset = set;
      end
      plout = pl;
    end
    
    % This builds a parameter list for the given set name. 
    function out = buildplist(set)
      
      if ~utils.helper.ismember(lower(Trigger.SETS), lower(set))
        error('### Unknown set [%s]', set);
      end
      
      out = plist();
      out = Trigger.addGlobalKeys(out);
      out = buildplist@ltpda_uoh(out, set);
      
      % Build the requested parameter list.
      switch lower(set)
        case 'Default'
          % time
          p = param({'time','The time of the trigger. Give either a string representation or a time object.'}, paramValue.EMPTY_STRING);
          out.append(p);
          
          % amplitude
          p = param({'amplitude','The trigger amplitude.'}, paramValue.EMPTY_DOUBLE);
          out.append(p);
          
          % frequency
          p = param({'frequency','The trigger frequency.'}, paramValue.EMPTY_DOUBLE);
          out.append(p);
          
      end
    end % function out = getDefaultPlist(varargin)
    
    % This creates arrays of the given size containing empty Trigger objects.
    function obj = initObjectWithSize(n,m)
      obj = Trigger.newarray([n m]);
      for ii = 1:numel(obj)
        obj(ii).UUID = char(java.util.UUID.randomUUID);
      end
    end
    
  end % End static methods
  
  methods (Static, Access=protected)
    
    % Global keys are added to all parameter lists so that properties common to all
    % user objects can be set.
    function pl = removeGlobalKeys(pl)
      pl.remove('name');
      pl.remove('description');
    end
    
    function pl = addGlobalKeys(pl)
      % Name
      p = param({'Name','The name of the constructed trigger object.'}, paramValue.STRING_VALUE('None'));
      pl.append(p);
      
      % Description
      p = param({'Description','The description of the constructed trigger object.'}, paramValue.EMPTY_STRING);
      pl.append(p);      
    end
    
  end % End static, private methods
  
  methods (Static = true, Hidden = true)
    varargout = loadobj(varargin)
    varargout = update_struct(varargin);
  end
  
  methods
    varargout = char(varargin)
    varargout = display(varargin)
    varargout = copy(varargin)
  end
  
  methods (Hidden = true)
    varargout = attachToDom(varargin)
  end
  
  methods (Access = protected)
    obj = fromStruct(obj, a_struct)
    varargout = fromDom(varargin)
  end


Unit Tests

LTPDA provides a unit test framework that aims to make it easy to test your new methods, classes and built-in models. Unit tests are methods of a unit test class. These unit test classes should inherit from one of the base classes ltpda_utp or ltpda_builtin_model_utp. The directory ltpda_toolbox/ltpda/classes/tests/ contains these test classes and examples for testing classes and built-in models. To run the unit tests you can use the unit test runner class ltpda_test_runner. For example,

  >> ltpda_test_runner.RUN_ALL_TESTS
  >> ltpda_test_runner.RUN_TESTS('@my_class_tests')
  >> ltpda_test_runner.RUN_TESTS('@my_class_tests', 'test_a_particular_method_feature')



©LTP Team