Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rois pagination #422

Merged
merged 9 commits into from
Feb 19, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions omero_figure/templates/figure/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -309,8 +309,9 @@ <h4>ROIs:</h4>
<!-- ROIs loaded here... -->
</tbody>
</table>
<!-- Additional message depends on whether we have any ROIs or not -->
<p id='cropRoiMessage'></p>
<button style="float: right" class="btn btn-sm btn-default loadRoiRects">Load More...</button>
<!-- Additional message/status -->
<p style="color: #999; margin: 10px" id='cropRoiMessage'></p>
</div>
<div class="col-xs-8">
<div id="cropViewer">
Expand Down
3 changes: 3 additions & 0 deletions omero_figure/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
url(r'^roiCount/(?P<image_id>[0-9]+)/$', views.roi_count,
name='figure_roiCount'),

url(r'^roiRectangles/(?P<image_id>[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'),

Expand Down
45 changes: 45 additions & 0 deletions omero_figure/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -543,6 +544,50 @@ 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):
jburel marked this conversation as resolved.
Show resolved Hide resolved
"""
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)
total_count = result[0][0].val

return JsonResponse({'data': json_data,
'meta': {'totalCount': total_count}})


@login_required()
def roi_count(request, image_id, conn=None, **kwargs):
"""
Expand Down
56 changes: 41 additions & 15 deletions src/js/views/crop_modal_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ var CropModalView = Backbone.View.extend({

roiTemplate: JST["src/templates/modal_dialogs/crop_modal_roi.html"],

model:FigureModel,
model: FigureModel,

roisPageSize: 200,
roisPage: 0,
roisCount: 0,
// not all these ROIs will contain Rects
roisLoaded: 0,
// Rectangles from ROIs
roiRects: [],

initialize: function() {

Expand Down Expand Up @@ -40,8 +48,11 @@ 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.roisPage = 0;
self.loadRoiRects();
// ...along with ROIs from clipboard or on this image in the figure
self.showClipboardFigureRois();
});
Expand Down Expand Up @@ -89,6 +100,7 @@ var CropModalView = Backbone.View.extend({

events: {
"click .roiPickMe": "roiPicked",
"click .loadRoiRects": "loadRoiRects",
"mousedown svg": "mousedown",
"mousemove svg": "mousemove",
"mouseup svg": "mouseup",
Expand Down Expand Up @@ -318,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
Expand All @@ -336,22 +348,29 @@ 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);
},

// 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 = BASE_WEBFIGURE_URL + 'roiRectangles/' + 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
Expand Down Expand Up @@ -407,7 +426,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,
Expand All @@ -420,12 +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]";
self.renderRois(rects, ".roisFromOMERO", msg);
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);
});
},
Expand Down Expand Up @@ -490,7 +517,7 @@ var CropModalView = Backbone.View.extend({
html += this.roiTemplate(json);
}
if (html.length === 0) {
html = "<tr><td colspan='3'>" + msg + "</td></tr>";
html = "<tr><td colspan='3' style='color: #999'>" + msg + "</td></tr>";
}
$(target + " tbody", this.$el).html(html);

Expand All @@ -514,7 +541,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,
Expand Down
10 changes: 9 additions & 1 deletion src/js/views/roi_loader_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down Expand Up @@ -163,7 +166,12 @@ 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,
totalCount: this.totalCount,
count: this.collection.length,
});
this.$el.html(html);

return this;
Expand Down
41 changes: 31 additions & 10 deletions src/js/views/roi_modal_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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",
},
Expand Down Expand Up @@ -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))
Expand All @@ -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) {
Expand Down
11 changes: 11 additions & 0 deletions src/templates/modal_dialogs/roi_modal_roi.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,14 @@
</tr>

<% }) %>

<tr>
<td colspan="4" style="color: #999; vertical-align: middle">
Loaded <%= count %> / <%= totalCount %> ROIs
</td>
<td>
<button style="float: right; <% if (!showLoadMore){ %>display:none<% } %>" class="loadMoreRois btn btn-success btn-sm">
Load more...
</button>
</td>
</tr>