From 70ae6c7af260205e31facc0027327a4902b80180 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 29 Jan 2021 15:09:46 +0000 Subject: [PATCH 1/9] Add Load More... button to ROIs dialog list --- src/js/views/roi_loader_view.js | 8 +++- src/js/views/roi_modal_view.js | 41 ++++++++++++++----- .../modal_dialogs/roi_modal_roi.html | 6 +++ 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/js/views/roi_loader_view.js b/src/js/views/roi_loader_view.js index 6e404c50a..4c3317723 100644 --- a/src/js/views/roi_loader_view.js +++ b/src/js/views/roi_loader_view.js @@ -7,7 +7,10 @@ var RoiLoaderView = Backbone.View.extend({ shapeTemplate: JST["src/templates/modal_dialogs/roi_modal_shape.html"], initialize: function(options) { + this.render = _.debounce(this.render); + this.totalCount = options.totalCount; this.panel = options.panel; + this.listenTo(this.collection, "add", this.render); }, events: { @@ -163,7 +166,10 @@ var RoiLoaderView = Backbone.View.extend({ // remove any rois from above with no shapes json = json.filter(function(j) {return j}); - var html = this.template({'rois': json}); + var html = this.template({ + rois: json, + showLoadMore: this.totalCount > this.collection.length, + }); this.$el.html(html); return this; diff --git a/src/js/views/roi_modal_view.js b/src/js/views/roi_modal_view.js index 8d44909b9..3e1d2e634 100644 --- a/src/js/views/roi_modal_view.js +++ b/src/js/views/roi_modal_view.js @@ -16,11 +16,20 @@ var RoiModalView = Backbone.View.extend({ // This gets populated when dialog loads omeroRoiCount: 0, roisLoaded: false, + roisPageSize: 500, + roisPage: 0, initialize: function() { var self = this; + // We create a new Model and RoiLoaderView. + // Then listen for selection events etc coming from RoiLoaderView + this.Rois = new RoiList(); + this.listenTo(this.Rois, "change:selection", this.showTempShape); // mouseover shape + this.listenTo(this.Rois, "shape_add", this.addShapeFromOmero); + this.listenTo(this.Rois, "shape_click", this.showShapePlane); + // We manually bind Mousetrap keyboardEvents to body so as // not to clash with the global keyboardEvents in figure_view.js // Bind to 'body' instead of #roiModal since this didn't always work with @@ -97,6 +106,7 @@ var RoiModalView = Backbone.View.extend({ "click .deleteShape": "deleteShapes", "click .selectAll": "selectAllShapes", "click .loadRois": "loadRois", + "click .loadMoreRois": "loadMoreRois", "click .revert_theZ": "revertTheZ", "click .revert_theT": "revertTheT", }, @@ -131,22 +141,16 @@ var RoiModalView = Backbone.View.extend({ // hide button and tip $(".loadRois", this.$el).prop('disabled', true); $("#roiModalTip").hide(); - + this.roisPage = 0; var iid = this.m.get('imageId'); - // We create a new Model and RoiLoaderView. - // Then listen for selection events etc coming from RoiLoaderView - var Rois = new RoiList(); - this.listenTo(Rois, "change:selection", this.showTempShape); // mouseover shape - this.listenTo(Rois, "shape_add", this.addShapeFromOmero); - this.listenTo(Rois, "shape_click", this.showShapePlane); - var roiUrl = ROIS_JSON_URL + '?image=' + iid + '&limit=500'; + var roiUrl = ROIS_JSON_URL + '?image=' + iid + '&limit=' + this.roisPageSize; $.getJSON(roiUrl, function(data){ - Rois.set(data.data); + this.Rois.set(data.data); $(".loadRois", this.$el).prop('disabled', false); $("#roiModalRoiList table").empty(); this.roisLoaded = true; this.renderToolbar(); - var roiLoaderView = new RoiLoaderView({collection: Rois, panel: this.m}); + var roiLoaderView = new RoiLoaderView({ collection: this.Rois, panel: this.m, totalCount: this.omeroRoiCount}); $("#roiModalRoiList table").append(roiLoaderView.el); roiLoaderView.render(); }.bind(this)) @@ -156,6 +160,23 @@ var RoiModalView = Backbone.View.extend({ }); }, + loadMoreRois: function (event) { + event.preventDefault(); + $(event.target).prop('disabled', true); + var iid = this.m.get('imageId'); + this.roisPage += 1; + var offset = this.roisPageSize * this.roisPage; + var roiUrl = ROIS_JSON_URL + '?image=' + iid + '&limit=' + this.roisPageSize + '&offset=' + offset; + $.getJSON(roiUrl, function (data) { + // causes RoiLoaderView to re-render + this.Rois.add(data.data); + }.bind(this)) + .fail(function (jqxhr, textStatus, error) { + console.log("fail", arguments); + alert("Failed to load ROIS: " + textStatus + ", " + error); + }); + }, + showShapePlane: function(args) { var shapeJson = args[0]; if (shapeJson) { diff --git a/src/templates/modal_dialogs/roi_modal_roi.html b/src/templates/modal_dialogs/roi_modal_roi.html index 07fa8c312..3401b664e 100644 --- a/src/templates/modal_dialogs/roi_modal_roi.html +++ b/src/templates/modal_dialogs/roi_modal_roi.html @@ -48,3 +48,9 @@ <% }) %> + +style="display:none" <% } %> > + + + + From ae9ad6109731fc494917b613257d41498f8c9b1f Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 29 Jan 2021 15:26:06 +0000 Subject: [PATCH 2/9] Show number of ROIs loaded / total --- src/js/views/roi_loader_view.js | 2 ++ src/templates/modal_dialogs/roi_modal_roi.html | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/js/views/roi_loader_view.js b/src/js/views/roi_loader_view.js index 4c3317723..922619f52 100644 --- a/src/js/views/roi_loader_view.js +++ b/src/js/views/roi_loader_view.js @@ -169,6 +169,8 @@ var RoiLoaderView = Backbone.View.extend({ var html = this.template({ rois: json, showLoadMore: this.totalCount > this.collection.length, + totalCount: this.totalCount, + count: this.collection.length, }); this.$el.html(html); diff --git a/src/templates/modal_dialogs/roi_modal_roi.html b/src/templates/modal_dialogs/roi_modal_roi.html index 3401b664e..a9b7106c1 100644 --- a/src/templates/modal_dialogs/roi_modal_roi.html +++ b/src/templates/modal_dialogs/roi_modal_roi.html @@ -49,8 +49,13 @@ <% }) %> -style="display:none" <% } %> > - - + + + Loaded <%= count %> / <%= totalCount %> ROIs + + + From 340afcac878488097987996bb64338ae16f51033 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 29 Jan 2021 17:18:06 +0000 Subject: [PATCH 3/9] ROIs pagination for crop dialog --- omero_figure/templates/figure/index.html | 5 +-- src/js/views/crop_modal_view.js | 39 ++++++++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/omero_figure/templates/figure/index.html b/omero_figure/templates/figure/index.html index d85603279..74eb93e03 100644 --- a/omero_figure/templates/figure/index.html +++ b/omero_figure/templates/figure/index.html @@ -309,8 +309,9 @@

ROIs:

- -

+ + +

diff --git a/src/js/views/crop_modal_view.js b/src/js/views/crop_modal_view.js index 236e589f5..22b1190de 100644 --- a/src/js/views/crop_modal_view.js +++ b/src/js/views/crop_modal_view.js @@ -6,7 +6,15 @@ var CropModalView = Backbone.View.extend({ roiTemplate: JST["src/templates/modal_dialogs/crop_modal_roi.html"], - model:FigureModel, + model: FigureModel, + + roisPageSize: 10, + roisPage: 0, + roisCount: 0, + // not all these ROIs will contain Rects + roisLoaded: 0, + // Rectangles from ROIs + roiRects: [], initialize: function() { @@ -40,8 +48,10 @@ var CropModalView = Backbone.View.extend({ // disable submit until user chooses a region/ROI self.enableSubmit(false); - // Load ROIs from OMERO... - self.loadRois(); + // Reset ROIs from OMERO... + self.roiRects = []; + self.roisLoaded = 0; + self.loadRoiRects(); // ...along with ROIs from clipboard or on this image in the figure self.showClipboardFigureRois(); }); @@ -89,6 +99,7 @@ var CropModalView = Backbone.View.extend({ events: { "click .roiPickMe": "roiPicked", + "click .loadRoiRects": "loadRoiRects", "mousedown svg": "mousedown", "mousemove svg": "mousemove", "mouseup svg": "mouseup", @@ -341,17 +352,24 @@ var CropModalView = Backbone.View.extend({ }, // Load Rectangles from OMERO and render them - loadRois: function() { + loadRoiRects: function(event) { + if (event) { + event.preventDefault(); + } var self = this, iid = self.m.get('imageId'); - $.getJSON(ROIS_JSON_URL + '?image=' + iid, function(rsp){ + var offset = this.roisPageSize * this.roisPage; + var url = ROIS_JSON_URL + '?image=' + iid + '&limit=' + self.roisPageSize + '&offset=' + offset; + $.getJSON(url, function(rsp){ data = rsp.data; + self.roisLoaded += data.length; + self.roisPage += 1; + self.roisCount = rsp.meta.totalCount; // get a representative Rect from each ROI. // Include a z and t index, trying to pick current z/t if ROI includes a shape there var currT = self.m.get('theT'), currZ = self.m.get('theZ'); - var rects = [], - cachedRois = {}, // roiId: shapes (z/t dict) + var cachedRois = {}, // roiId: shapes (z/t dict) roi, roiId, shape, theT, theZ, z, t, rect, tkeys, zkeys, minT, maxT, shapes; // dict of all shapes by z & t index @@ -407,7 +425,7 @@ var CropModalView = Backbone.View.extend({ z = zkeys[(zkeys.length/2)>>0] } shape = shapes[t][z] - rects.push({'theZ': shape.TheZ, + self.roiRects.push({'theZ': shape.TheZ, 'theT': shape.TheT, 'x': shape.X, 'y': shape.Y, @@ -421,7 +439,9 @@ var CropModalView = Backbone.View.extend({ } // Show ROIS from OMERO... var msg = "[No rectangular ROIs found on this image in OMERO]"; - self.renderRois(rects, ".roisFromOMERO", msg); + self.renderRois(self.roiRects, ".roisFromOMERO", msg); + + $("#cropRoiMessage").html(`Loaded ${self.roisLoaded} / ${self.roisCount} ROIs`); self.cachedRois = cachedRois; }).error(function(){ @@ -514,7 +534,6 @@ var CropModalView = Backbone.View.extend({ render: function() { var scale = this.zoom / 100, - roi = this.currentROI, w = this.m.get('orig_width'), h = this.m.get('orig_height'); var newW = w * scale, From 3c29293003ae32e4e862cb526abf65622130669e Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 29 Jan 2021 17:20:39 +0000 Subject: [PATCH 4/9] Use page size of 200 for crop dialog --- src/js/views/crop_modal_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/js/views/crop_modal_view.js b/src/js/views/crop_modal_view.js index 22b1190de..a96d2209a 100644 --- a/src/js/views/crop_modal_view.js +++ b/src/js/views/crop_modal_view.js @@ -8,7 +8,7 @@ var CropModalView = Backbone.View.extend({ model: FigureModel, - roisPageSize: 10, + roisPageSize: 200, roisPage: 0, roisCount: 0, // not all these ROIs will contain Rects From 74560102ba53fa2a021bd6178c90be0e880cd06a Mon Sep 17 00:00:00 2001 From: William Moore Date: Sun, 31 Jan 2021 23:33:25 +0000 Subject: [PATCH 5/9] Improve user messages for crop loading rects --- omero_figure/templates/figure/index.html | 2 +- src/js/views/crop_modal_view.js | 17 ++++++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/omero_figure/templates/figure/index.html b/omero_figure/templates/figure/index.html index 74eb93e03..dbf1fd562 100644 --- a/omero_figure/templates/figure/index.html +++ b/omero_figure/templates/figure/index.html @@ -309,7 +309,7 @@

ROIs:

- +

diff --git a/src/js/views/crop_modal_view.js b/src/js/views/crop_modal_view.js index a96d2209a..c87069a90 100644 --- a/src/js/views/crop_modal_view.js +++ b/src/js/views/crop_modal_view.js @@ -51,6 +51,7 @@ var CropModalView = Backbone.View.extend({ // Reset ROIs from OMERO... self.roiRects = []; self.roisLoaded = 0; + self.roisPage = 0; self.loadRoiRects(); // ...along with ROIs from clipboard or on this image in the figure self.showClipboardFigureRois(); @@ -329,7 +330,7 @@ var CropModalView = Backbone.View.extend({ } }); } - var msg = "[No Regions copied to clipboard]"; + var msg = "No Regions copied to clipboard"; this.renderRois(clipboardRects, ".roisFromClipboard", msg); // Show Rectangles from panels in figure @@ -347,7 +348,7 @@ var CropModalView = Backbone.View.extend({ }); } }); - msg = "[No Rectangular ROIs on selected panel in figure]"; + msg = "No Rectangular ROIs on selected panel in figure"; this.renderRois(figureRois, ".roisFromFigure", msg); }, @@ -438,14 +439,20 @@ var CropModalView = Backbone.View.extend({ 'zEnd': zkeys[zkeys.length-1]}); } // Show ROIS from OMERO... - var msg = "[No rectangular ROIs found on this image in OMERO]"; + var msg = "No rectangular ROIs found on this image in OMERO"; self.renderRois(self.roiRects, ".roisFromOMERO", msg); + if (self.roisLoaded < self.roisCount) { + // Show the 'Load' button if more are available + $(".loadRoiRects", this.$el).show(); + } else { + $(".loadRoiRects", this.$el).hide(); + } $("#cropRoiMessage").html(`Loaded ${self.roisLoaded} / ${self.roisCount} ROIs`); self.cachedRois = cachedRois; }).error(function(){ - var msg = "[No rectangular ROIs found on this image in OMERO]"; + var msg = "No rectangular ROIs found on this image in OMERO"; self.renderRois([], ".roisFromOMERO", msg); }); }, @@ -510,7 +517,7 @@ var CropModalView = Backbone.View.extend({ html += this.roiTemplate(json); } if (html.length === 0) { - html = "" + msg + ""; + html = "" + msg + ""; } $(target + " tbody", this.$el).html(html); From 7e05c402dfce169230fb4cb392d209f082dddbf8 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 2 Feb 2021 17:19:50 +0000 Subject: [PATCH 6/9] Load ONLY Rectangle ROIs for crop dialog --- omero_figure/urls.py | 3 +++ omero_figure/views.py | 43 +++++++++++++++++++++++++++++++++ src/js/views/crop_modal_view.js | 2 +- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/omero_figure/urls.py b/omero_figure/urls.py index f4823ddab..115075f75 100644 --- a/omero_figure/urls.py +++ b/omero_figure/urls.py @@ -76,6 +76,9 @@ url(r'^roiCount/(?P[0-9]+)/$', views.roi_count, name='figure_roiCount'), + url(r'^roiRectangles/(?P[0-9]+)/$', views.roi_rectangles, + name='figure_roiRectangles'), + # POST to change figure to new group with ann_id and group_id url(r'chgrp/$', views.chgrp, name='figure_chgrp'), diff --git a/omero_figure/views.py b/omero_figure/views.py index 9a5899bbd..8b4b95e97 100644 --- a/omero_figure/views.py +++ b/omero_figure/views.py @@ -35,6 +35,7 @@ from omero.model.enums import UnitsLength from omero.cmd import ERR, OK import omero +from omero_marshal import get_encoder from io import BytesIO @@ -543,6 +544,48 @@ def unit_conversion(request, value, from_unit, to_unit, conn=None, **kwargs): return HttpResponse(json.dumps(rsp), content_type='application/json') +@login_required() +def roi_rectangles(request, image_id, conn=None, **kwargs): + """ + Load ROIs that have Rectangles and marshal with omero_marshal + + Returns similar JSON to /api/ rois, with meta.totalCount + Supports pagination with ?offset and ?limit + """ + + params = omero.sys.ParametersI() + params.addLong('image_id', image_id) + limit = request.GET.get('limit') + offset = request.GET.get('offset', 0) + if limit is not None: + params.page(int(offset), int(limit)) + query = """select roi from Roi roi join fetch + roi.details.owner as owner join fetch roi.details.creationEvent + left outer join fetch roi.shapes as shapes + where roi.image.id = :image_id + and shapes.class = Rectangle + order by roi.id""" + + rois = conn.getQueryService().findAllByQuery( + query, params, conn.SERVICE_OPTS) + + json_data = [] + for roi in rois: + encoder = get_encoder(roi.__class__) + json_data.append(encoder.encode(roi)) + + count_query = """select count(distinct roi) from Roi roi + left outer join roi.shapes as shapes + where roi.image.id = :image_id + and shapes.class = Rectangle""" + params = omero.sys.ParametersI() + params.addLong('image_id', image_id) + result = conn.getQueryService().projection(count_query, params, conn.SERVICE_OPTS) + totalCount = result[0][0].val + + return JsonResponse({'data': json_data, 'meta': {'totalCount': totalCount}}) + + @login_required() def roi_count(request, image_id, conn=None, **kwargs): """ diff --git a/src/js/views/crop_modal_view.js b/src/js/views/crop_modal_view.js index c87069a90..00d830aea 100644 --- a/src/js/views/crop_modal_view.js +++ b/src/js/views/crop_modal_view.js @@ -360,7 +360,7 @@ var CropModalView = Backbone.View.extend({ var self = this, iid = self.m.get('imageId'); var offset = this.roisPageSize * this.roisPage; - var url = ROIS_JSON_URL + '?image=' + iid + '&limit=' + self.roisPageSize + '&offset=' + offset; + var url = BASE_WEBFIGURE_URL + 'roiRectangles/' + iid + '/?limit=' + self.roisPageSize + '&offset=' + offset; $.getJSON(url, function(rsp){ data = rsp.data; self.roisLoaded += data.length; From 8f34e3d68ce2d2845e82897e3e482977d08a7999 Mon Sep 17 00:00:00 2001 From: William Moore Date: Tue, 2 Feb 2021 22:35:33 +0000 Subject: [PATCH 7/9] flake8 fixes --- omero_figure/views.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/omero_figure/views.py b/omero_figure/views.py index 8b4b95e97..bb308975d 100644 --- a/omero_figure/views.py +++ b/omero_figure/views.py @@ -580,10 +580,12 @@ def roi_rectangles(request, image_id, conn=None, **kwargs): and shapes.class = Rectangle""" params = omero.sys.ParametersI() params.addLong('image_id', image_id) - result = conn.getQueryService().projection(count_query, params, conn.SERVICE_OPTS) - totalCount = result[0][0].val + result = conn.getQueryService().projection(count_query, params, + conn.SERVICE_OPTS) + total_count = result[0][0].val - return JsonResponse({'data': json_data, 'meta': {'totalCount': totalCount}}) + return JsonResponse({'data': json_data, + 'meta': {'totalCount': total_count}}) @login_required() From 10e2da36d7b917c73445454794f86b37117188b6 Mon Sep 17 00:00:00 2001 From: William Moore Date: Fri, 5 Feb 2021 15:32:13 +0000 Subject: [PATCH 8/9] Add pagination controls above ROIs in Edit ROIs dialog --- omero_figure/templates/figure/index.html | 15 ++-- src/js/views/roi_modal_view.js | 85 ++++++++++++++----- .../modal_dialogs/roi_modal_roi.html | 11 --- .../shapes/shape_toolbar_template.html | 2 +- 4 files changed, 73 insertions(+), 40 deletions(-) diff --git a/omero_figure/templates/figure/index.html b/omero_figure/templates/figure/index.html index dbf1fd562..5fda10e1b 100644 --- a/omero_figure/templates/figure/index.html +++ b/omero_figure/templates/figure/index.html @@ -151,13 +151,16 @@
-
-
-
+
+ + +
+
+
+
+
+

-

-

-
0) { %> title="Load <%=omeroRoiCount%> ROIs from OMERO" <% } else { %> From 12a9be8319f90f6d8f4f87c8d0e51cdcbe517ded Mon Sep 17 00:00:00 2001 From: William Moore Date: Mon, 15 Feb 2021 22:49:06 +0000 Subject: [PATCH 9/9] Fix suggestions from PR 422 --- src/js/views/roi_modal_view.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/js/views/roi_modal_view.js b/src/js/views/roi_modal_view.js index 31dd9ada1..3f1342139 100644 --- a/src/js/views/roi_modal_view.js +++ b/src/js/views/roi_modal_view.js @@ -481,8 +481,10 @@ var RoiModalView = Backbone.View.extend({ return; } var pageCount = Math.ceil(this.omeroRoiCount / this.roisPageSize); - var html = ` - ${ this.omeroRoiCount} ROIs: page ${ this.roisPage + 1}/${pageCount} + var html = `${ this.omeroRoiCount} ROIs` + // Only show pagination controls if needed + if (pageCount > 1) { + html += `: page ${ this.roisPage + 1}/${pageCount}
- -
` + } else { + html += ``; + } $("#roiPageControls").html(html).show(); },