Local (Browser-Based) Analysis Tasks and Plugins

### Adding Local (Browser-Based) Analysis Tasks and Plugins to JS9

JS9 supports the ability to perform local analysis, i.e. tasks executed in your browser (not on the back-end server). This is accomplished by offering access to the image data and region information via a JS9 Public API. Obviously, all of JS9 JavaScript code is available to you within a web page, but the public API is designed to stable and well-documented.

### Adding Simple Analysis Tasks to Your Web Page

For simple analysis (i.e., no mouse or keyboard event processing), it often is sufficient to define GUI elements directly on the web page, adding Javascript code to interact with these elements and call the public JS9 functions as needed. In particular, the JS9.GetImage() function will return a handle for the currently displayed image and the the various JS9.Region functions allow you to manipulate regions. These routines are described in the JS9 Public API.

The js9onchange.html demo web page illustrates these simple capabilities. It shows how to run a task every time a region changes, i.e. when it is moved, resized, or deleted. This is accomplished by setting the JS9.Regions.opts.onchange parameter to a function (if you are setting via JavaScript) or a string routine name (if you are setting via json in the prefs file):

        // via JavaScript
        JS9.Regions.opts.onchange = myRegionChanged;

or:

        // from within js9prefs.js
        { ... "Regions.opts":  {"onchange": "myRegionChanged"} ... }

The calling sequence of the xeqonchange function is:

        function myRegionChanged(im, xreg)

where im is the image handle of the currently displayed image, and xreg is an object containing information about the changed region:

      function myRegionChanged(im, xreg){
        var obj, v;
        obj = JS9.GetImageData(true, {display: im});
        # get data value at the center of the region
        v = obj.data[Math.floor(xreg.y-0.5) * obj.width + Math.floor(xreg.x-0.5)];
        console.log("cen: %s,%s val: %s", xreg.x.toFixed(2), xreg.y.toFixed(2), v);
      };

Note that you can turn off region change processing for individual regions or for all regions by means of the xeqonchange menu option in a given region's context menu or the global Regions menu, respectively.

The js9onchange.html demo web page also shows how server-side JS9 analysis tasks can be executed directly from the web page using HTML elements (buttons, forms, etc.) instead of the Analysis menu. See Server-side Analysis Tasks for more information.

### Adding Plugins to Your Web Page

For anything other than the simplest analysis, you can write a JS9 Plugin module. A plugin is a Javascript module that contains a constructor function to create a plugin GUI, and, optionally, one or more event callbacks to process desired events. When the file is loaded, the plugin should call RegisterPlugin() to make itself known to JS9. Plugin modules should be stored in the js9/plugins directory (or in a sub-directory therein) and should be loaded into JS9-enabled web pages after js9.js itself is loaded.

To create instances of the plugin GUI, the constructor function gets called for each plugin div defined on the web page. The this context is seeded with a few JS9 properties, including:

- this.div: the DOM element representing the div for this plugin
- this.divjq: the jquery object representing the div for this plugin
- this.id: the id of the div (or the plugin name as a default)
- this.winType: "div" (in-page div element) or "light" (from view menu)
- this.winHandle: handle returned from light window create routine
- this.display: the display object associated with this plugin
- this.plugin: plugin class object (contains user opts in opts sub-object)
- this.status: "active" or "inactive" or undefined

The most important of these properties is the div property specifying the DOM element of the containing div. You add your GUI (HTML elements) to this.div and use the events to control the action. The type property tells you whether the instance is being created for an in-page div element or from the View menu, in case configuration is different for these cases. The plugintest.js module provides a fairly complete example and should be studied in conjunction with this help page.

A JS9 plugin is registered at the end of the module using the JS9.RegisterPlugin() public routine:

      JS9.RegisterPlugin(xclass, xname, func, opts)

where:

- xclass: class of the plugin
- xname: name of plugin
- func: the constructor function
- opts: an object containing plugin options

The xclass and xname values together make up the class of the div element. Thus, if the plugin xclass is "JS9" and the plugin xname is "Menubar", then the div which will hold the menubar has a class of "JS9Menubar". Note that these strings must adhere to CSS grammar rules, e.g., start with a letter, followed by letters, numbers, dashes and underscores, no spaces allowed.

This class/name separation has been made so that multiple plugins can be written as part of one class and processed together as part of that class, e.g., displayed together in the View and Help menus.

The optional opts object contains the following optional elements:

- divArgs: [arg1, arg2, ...] arguments passed to div-based constructor
- menuItem: menuname in specified menu (default is View menu)
- menu: add plugin to this menu (only View or Analysis right now!)
- winDims: [width, height]
- winTitle: string
- toolbarHTML: string
- toolbarSeparate: boolean
- help: path html file for help, relative to js9/plugins directory

The opts object allows you to specify callback functions that get triggered by various JS9 events. The calling sequence of these functions depends on the callback type. The defined callback types and their standard callbacks are:

- image: function(im)
  - onimageload: an image is loaded
  - onimageclose: an image is closed
  - onimagedisplay: an image is (re)displayed
  - onimagerefresh: an image is refreshed (i.e., new data is supplied)
  - onplugindisplay: a plugin window is re-displayed
  - onrawdata: low-level support for when the raw data array is produced
- mouse: function(im, ipos, evt)
  - onmouseup: mouse up events
  - onmousedown: mouse down events
  - onmousemove: mouse move events
  - onmouseover: mouse over events
  - onmouseout: mouse out events
  - onclick: click events (down/up with only tiny movement)
- keypress: function(im, ipos, evt)
  - onkeydown: keydown events
  - onkeypress: keypress events (keydown + keyup)
- shape: function(im, xreg)
  - on[layer]change: a shapes changes in a layer (e.g., "onregionschange" for region changes)

You can globally toggle execution of all plugin callbacks by setting the value of the JS9.globalOpts.xeqPlugins property to true or false.

In addition to these standard callbacks, JS9 also supports an extended set of image callbacks that get triggered when internal JS9 routines are called. These callbacks are only active if the JS9.globalOpts.extendedPlugins property is set to true (which is the default).

- onsetpan: called when pan value is set by im.setPan()
- onsetzoom called when zoom value is set by im.setZoom()
- onsetwcssys called when wcs system is set by im.setWCSSys()
- onsetunits called when wcs units is set by im.setWCSUnits()
- onsetcolormap called when colormap is set by im.setColormap()
- onsetscale called when scale is set by im.setScale()
- onrawdatalayer called when a raw data layer is created or modified by im.rawDataLayer()
- onfilterrgbimage called when an RGB image is filtered by im.filterRGBImage()
- onchangecontrastbias called when the "change contrast/bias" mouse/touch gesture changes the contrast/bias

Finally, JS9 supports the onregionsmove callback if the JS9.globalOpts.intensivePlugins property is set to true (the default is false). Executing this callback requires that the region parameters be updated continually as the mouse moves. Such an update is a relatively intensive operation, so the intensivePlugins property should only be enabled if you need it.

Please let us know if you require a callback to be associated with another JS9 function.

Each callback function gets executed when the specified event takes place. The im object is the JS9 image handle, which can be passed to the display parameter of public API calls to specify the JS9 display (necessary when there is more than one display on the page);

      function onimdisplay(im){
        arr = JS9.GetRegions({display: im});
      }

The ipos object contains the one-indexed image position of the mouse:

      function onmousemove(im, ipos){
        var obj, v;
        obj = JS9.GetImageData(true, {display: im});
        # get data value at the mouse position
        v = obj.data[Math.floor(ipos.y-0.5) * obj.width + Math.floor(ipos.x-0.5)];
        console.log("ipos=%s,%s val=%s", ipos.x.toFixed(2), ipos.y.toFixed(2), v);
      };

The evt object is the jQuery event that triggered the callback.

The on[layer]change callback gets passed the same xreg object as is used for the JS9.GetRegions() routine and the JS9.Regions.opts.onchange parameter:

      function myRegionChanged(im, xreg){
        var obj, v;
        obj = JS9.GetImageData(true, {display: im});
        # get data value at the center of the region
        v = obj.data[Math.floor(xreg.y-0.5) * obj.width + Math.floor(xreg.x-0.5)];
        console.log("cen: %s,%s val: %s", xreg.x.toFixed(2), xreg.y.toFixed(2), v);
      };

Holding down the Shift key (where available) will turn off execution of all mouse event callbacks. This allows you to move the mouse to a specific location, and then move away without triggering a callback. The xeqonchange menu option in the Regions menu and each region's context menu can be used to turn off execution of the region onchange callback. The former does this globally, the latter only for the specific region.

The divArgs is an array specifying arguments to pass to the constructor function, when a div element is found in the web page. For example, the Panner and Magnifier plugins pass a width and height to the constructor in this way.

The menuItem is a string which specifies the name that will be used in the View menu when a plugin div is not found on the web page. If no menuItem is specified, the plugin will not appear in the View menu.

Furthermore, the menu property can be used to change the menu in which the above-mentioned menuItem plugin will be placed. For example, an Imexam plugin situates its plugins in the Analysis menu instead of the View menu. NB: At the moment, only the View and Analysis menus are supported for placement.

The winDims is an array specifying the width and height of the light window that will get created for this plugin via the View menu. If the winDims dimensions is specified as [0, 0], the plugin is virtual and is active all the time. This is useful in cases where you simply want to define callback routines for one or more JS9 actions. See the example below.

The winTitle is a string specifying the title of the light window. The default is no title.

You can add HTML (e.g., command buttons) to the light window titlebar by putting an HTML string into the toolbarHTML property. The panner and magnifier use this technique to add zoom buttons. You also can add HTML onto a web page plugin div, but in this case, where the HTML is placed depends on the value of the toolbarSeparate property. If the value of toolbarSeparate is true, then a titlebar is created above the plugin and the HTML is added to it. Otherwise, the HTML is placed in top of the plugin itself.

The help property specifies the pathname of an html file that will be placed in the Help menu. This pathname should be relative to the plugins directory.

Within a callback, you can use the JS9 Public API to manipulate data, as detailed in the JS9 Public API.

A final example: if you want changes of contrast/bias in one JS9 display to trigger similar changes in all other displays, you can do this:

    JS9.RegisterPlugin("MyPlugins", "allconstrastbias",
    		   function(){return;},
    		   {onchangecontrastbias: function(im){
    		       var i, tim, obj;
    		       JS9.globalOpts.xeqPlugins = false;
    		       obj = JS9.GetColormap({display: im});
    		       for(i=0; i<JS9.displays.length; i++){
    			   tim = JS9.displays[i].image;
    			   if( tim && (tim !== im) ){
    			       JS9.SetColormap(obj.contrast, obj.bias,
    					      {display: tim});
    			   }
    		       }
    		       JS9.globalOpts.xeqPlugins = true;
    		   },
    		   winDims: [0, 0]});

Here, a virtual plugin is created with no user interface. It thus is active all of the time. The plugin defines a single action that gets triggered when the contrast/bias is changed. This action changes the contrast/bias of all other displays. Note that the JS9.globalOpts.xeqPlugins property is set to false temporarily to avoid recursion.

##### Last updated: July 10, 2018