OMERO.web 'Open with' & Filtering

Tuesday Group Meeting
28th June, 2016

Will Moore

Talk outline

  • 'Open With' #4630
    • Simple case (image viewer)
    • Customisation with JavaScript
  • Filering of images (and other objects)? #4694
  • What about React.js? #4413

Open With: Image Viewer

  • Existing functionality allows to set viewer #4480
  • 'Open With' allows multiple viewers (and more) #4630
    $ omero config append omero.web.open_with '["My viewer", "url_name"]'
  • By default 'Open with' only enabled when single image selected.
  • Uses reverse(url_name) to try to generate url.
  • Opens selected image in new window with
    E.g. /webtest/multi_plane_stack/?image=4420

More options


$ omero config append omero.web.open_with '["OMERO.figure", "new_figure",
    {"objects": ["images", "dataset"],
     "target": "tab"
    }]'

Include JavaScript


$ omero config append omero.web.open_with '["OMERO.figure", "new_figure",
    {"script": "figure/openwith.js", "target": "tab"}]'
  • Includes /figure/templates/figure/openwith.js
  • Script can add JavaScript handlers to 'Open With'

// openwith.js
var isEnabled = function(selected) {
    // return boolean - should 'Open With' be enabled
    // with the currently 'selected' objects?
    return true;
};

var handleAction = function(selected, url) {
    // handle selected objects
};

// helpers for applying handlers to Open With 'OMERO.figure'
OME.setOpenWithEnabledHandler("OMERO.figure", isEnabled);
OME.setOpenWithActionHandler("OMERO.figure", handleAction);

OMERO.figure script example

// openwith.js
var isEnabled = function(selected) {
    var allImagesSmall = selected.reduce(function(prev, s){
        // We don't support 'Big' images over 3k x 3k
        var smallImg = (s.type === 'image' && s.sizeX * s.sizeY < 9000000);
        // We support OMERO.figure files (annotations with correct namespace)
        var figureFile = (s.type === 'fileannotation' &&
            s.ns === 'omero.web.figure.json');
        return prev & (smallImg || figureFile);
    }, true);
    return allImagesSmall;
};

var handleAction = function(selected, url) {
    // if we have a figure file, handle it
    if (selected[0].type === "fileannotation") {
        // url is for figure/new/ but for files we want figure/file/:id
        window.open(url.replace('new', 'file') + selected[0].id + "/",
                    '_blank');
        return false;
    }
    // Otherwise, return true and default behaviour will handle it...
    return true;
};

OME.setOpenWithEnabledHandler("OMERO.figure", isEnabled);
OME.setOpenWithActionHandler("OMERO.figure", handleAction);

Genbank script example

// openwith.js
var isEnabled = function(selected) {
    // Only enabled for single objects...
    if (selected.length !== 1) return false;
    // ... where name is a number (E.g. Gene ID)
    var name = selected[0].name;
    return (parseInt(name, 10) == name);
};

var handleAction = function(selected, url) {
    // Url includes selected 'name' which is an Gene ID
    var name = selected[0].name;
    // E.g. open http://www.ncbi.nlm.nih.gov/protein/000397
    window.open(url + name, 'new');
};

OME.setOpenWithEnabledHandler("GenBank Protein", isEnabled);
OME.setOpenWithActionHandler("GenBank Protein", handleAction);

What else is needed?

  • Chris: 'Open with' available from top of right panel
  • Josh:
    • selection of one or more key-value pairs for execution
    • auto-discovery of plugins & javascripts (from third parties)
    • planning for the first breaking change (can we support 2 versions at once?)
    • scalability: what happens if I want to generate a heatmap from several thousand images (Will this require POST'ing json to the plugins?)

Filtering Images #4694

  • Initially a "quick fix" to add Rating to current filtering
  • BUT, currently we only filter on a single page (200 images)
  • Filtering would be much more powerful if we filter server-side...

Filtering Images (React.js branch) #4413

  • jsTree applies filter when loading images E.g. webclient/api/images/?id=10851&page=1&filter=dv
  • Use iQuery to do projection count and load data using 2 queries
  • Returns image data and 'count'
  • 
    {
        'images': [
            {'id': 123, 'name': 'myImage.dv'...}
        ],
        'count': 3152
    }
                        

Problems

  • iQuery is getting complex (custom projection AND filtering)
  • Need lots more filters: Rating, Tags, Comments etc.
  • Updating of centre panel is tricky (without React.js)
  • Evaluating React.js: Centre Panel

    • Centre panel populated from jsTree data: #4242
      • Uses unscore.js template to create HTML
    • Bug: right panel scrolls on selection change #4297:
      # If it's just a selection change,
      # simply highlight (don't re-render whole panel)
      if (parentId === newParentId
          && event.type !== "load_node"
          && event.type !== "delete_node") {
       
          highlightSelectedThumbs(selected);
          return;
      }
    • Bug: Also re-render on copy & paste #4336:
       if (parentId === newParentId
      +    && event.type !== "copy_node"
      +    && event.type !== "create_node"
           && event.type !== "load_node"
           && event.type !== "delete_node"
           && event.type !== "refreshThumb" {

    Using React.js for centre panel #4413

    • Don't have to worry about what changes are expected from different events
    • Simply declare how jsTree data maps to centre panel HTML
      • E.g. image selected -> class 'ui-selected'
      • E.g. iconSize -> img width
    < ImageIcon >
    
    render: function() {
        var image = this.props.image,
            cls = [];
        if (image.selected) {cls.push('ui-selected')};
        if (image.fsSelected) {cls.push('fs-selected')};
        return (
            < li className={"row " + cls.join(" ")} >
                < img width={this.props.iconSize + "px"}
            ...
    

    Questions?