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

Obj id bitmask endpoint #308

Merged
merged 24 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
43c6cda
Add endpoint to return bit mask for table with object id column
kkoz Nov 24, 2020
ab9cd8d
Remove logging
kkoz Nov 25, 2020
f178cad
Return errors as JSON; remove unused variables
knabar Aug 3, 2021
ec1921d
Fix rebase issues
kkoz Aug 5, 2021
65d4534
Cast object to int for object id bitmask query
kkoz Aug 12, 2021
984adc8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 12, 2021
919d9e7
Pass lazy parameter correctly to process_table_query
kkoz Aug 12, 2021
ee61917
Fix bitmask endpoint to work if ids are not in ascending order and if…
kkoz Aug 18, 2021
e593f40
flake8
kkoz Aug 19, 2021
a8932cc
Merge from master
kkoz Aug 19, 2021
2353ea5
Disable table download row check for bitmask endpoint
kkoz Aug 19, 2021
61cc201
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 19, 2021
c61839f
Have object id bitmask endpoint handle errors the same as queries
kkoz Aug 25, 2021
e60e249
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 25, 2021
ec2d072
Add documentation for obj_id_bitmask endpoint
kkoz Aug 30, 2021
80522e1
Factor out byte array logic and add unit test
kkoz Aug 30, 2021
71f2680
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 30, 2021
b97dfb3
Use numpy.packbits to create byte array for obj_id_bitmask endpoint
kkoz Aug 31, 2021
d8b16d1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 31, 2021
49177f8
Remove duplicate import numpy
kkoz Aug 31, 2021
223e424
Add docstring for obj_id_bitmask endpoint
kkoz Aug 31, 2021
87e19f1
Add column type checking to obj_id_bitmask
kkoz Aug 31, 2021
0bae56b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Aug 31, 2021
77c46a7
flake8
kkoz Sep 1, 2021
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
20 changes: 20 additions & 0 deletions omeroweb/webgateway/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,25 @@
Get omero table metadata
"""

table_obj_id_bitmask = url(
r"^table/(?P<fileid>\d+)/obj_id_bitmask/$",
views.obj_id_bitmask,
name="webgateway_table_obj_id_bitmask",
)
"""
Get object id bitmask
The user specifies a fileid for an OMERO Table and a query, and
optionally provides a "col_name" query parameter for the column
name to get a bitmask for. By default, "object" is used.
The server will return a bitmask with the nth bit flipped to 1
if the query returns a row where the col_name has a value of n.
The bits returned are 0-indexed.
E.g. if your query returns col_name values of 1, 7, 11, and 12,
you will get back 2 bytes and the bitmask will be 0100000100011000
Note that the 1st, 7th, 11th, and 12th bits are flipped to 1 and
the rest are 0.
"""

object_table_query = url(
r"^table/(?P<objtype>[\w.]+)/(?P<objid>\d+)/query/$",
views.object_table_query,
Expand Down Expand Up @@ -597,6 +616,7 @@
annotations,
table_query,
table_metadata,
table_obj_id_bitmask,
object_table_query,
open_with_options,
]
168 changes: 118 additions & 50 deletions omeroweb/webgateway/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import omero
import omero.clients
from past.builtins import unicode
import numpy

from django.http import (
HttpResponse,
Expand Down Expand Up @@ -2892,38 +2893,16 @@ def _bulk_file_annotations(request, objtype, objid, conn=None, **kwargs):
annotations = login_required()(jsonp(_bulk_file_annotations))


def _table_query(request, fileid, conn=None, query=None, lazy=False, **kwargs):
"""
Query a table specified by fileid
Returns a dictionary with query result if successful, error information
otherwise

@param request: http request; querystring must contain key 'query'
with query to be executed, or '*' to retrieve all rows.
If query is in the format word-number, e.g. "Well-7",
if will be run as (word==number), e.g. "(Well==7)".
This is supported to allow more readable query strings.
@param fileid: Numeric identifier of file containing the table
@param query: The table query. If None, use request.GET.get('query')
E.g. '*' to return all rows.
If in the form 'colname-1', query will be (colname==1)
@param lazy: If True, instead of returning a 'rows' list,
'lazy_rows' will be a generator.
Each gen.next() will return a list of row data
AND 'table' returned MUST be closed.
@param conn: L{omero.gateway.BlitzGateway}
@param **kwargs: offset, limit
@return: A dictionary with key 'error' with an error message
or with key 'data' containing a dictionary with keys
'columns' (an array of column names) and 'rows'
(an array of rows, each an array of values)
"""
if query is None:
query = request.GET.get("query")
if not query:
return dict(error="Must specify query parameter, use * to retrieve all")
col_names = request.GET.getlist("col_names")

def perform_table_query(
conn,
fileid,
query,
col_names,
offset=0,
limit=None,
lazy=False,
check_max_rows=True,
):
ctx = conn.createServiceOptsDict()
ctx.setOmeroGroup("-1")

Expand Down Expand Up @@ -2953,19 +2932,8 @@ def _table_query(request, fileid, conn=None, query=None, lazy=False, **kwargs):
column_names = [col.name for col in cols]
rows = t.getNumberOfRows()

offset = kwargs.get("offset", 0)
limit = kwargs.get("limit", None)
if not offset:
offset = int(request.GET.get("offset", 0))
if not limit:
limit = (
int(request.GET.get("limit"))
if request.GET.get("limit") is not None
else rows
)

range_start = offset
range_size = limit
range_size = limit if limit is not None else rows
range_end = min(rows, range_start + range_size)

if query == "*":
Expand All @@ -2989,12 +2957,13 @@ def _table_query(request, fileid, conn=None, query=None, lazy=False, **kwargs):
except Exception:
return dict(error="Error executing query: %s" % query)

if len(hits) > settings.MAX_TABLE_DOWNLOAD_ROWS:
error = (
"Trying to download %s rows exceeds configured"
" omero.web.max_table_download_rows of %s"
) % (len(hits), settings.MAX_TABLE_DOWNLOAD_ROWS)
return {"error": error, "status": 404}
if check_max_rows:
if len(hits) > settings.MAX_TABLE_DOWNLOAD_ROWS:
error = (
"Trying to download %s rows exceeds configured"
" omero.web.max_table_download_rows of %s"
) % (len(hits), settings.MAX_TABLE_DOWNLOAD_ROWS)
return {"error": error, "status": 404}

def row_generator(table, h):
# hits are all consecutive rows - can load them in batches
Expand Down Expand Up @@ -3041,6 +3010,53 @@ def row_generator(table, h):
t.close()


def _table_query(request, fileid, conn=None, query=None, lazy=False, **kwargs):
"""
Query a table specified by fileid
Returns a dictionary with query result if successful, error information
otherwise

@param request: http request; querystring must contain key 'query'
with query to be executed, or '*' to retrieve all rows.
If query is in the format word-number, e.g. "Well-7",
if will be run as (word==number), e.g. "(Well==7)".
This is supported to allow more readable query strings.
@param fileid: Numeric identifier of file containing the table
@param query: The table query. If None, use request.GET.get('query')
E.g. '*' to return all rows.
If in the form 'colname-1', query will be (colname==1)
@param lazy: If True, instead of returning a 'rows' list,
'lazy_rows' will be a generator.
Each gen.next() will return a list of row data
AND 'table' returned MUST be closed.
@param conn: L{omero.gateway.BlitzGateway}
@param **kwargs: offset, limit
@return: A dictionary with key 'error' with an error message
or with key 'data' containing a dictionary with keys
'columns' (an array of column names) and 'rows'
(an array of rows, each an array of values)
"""
if query is None:
query = request.GET.get("query")
if not query:
return dict(error="Must specify query parameter, use * to retrieve all")
col_names = request.GET.getlist("col_names")

offset = kwargs.get("offset", 0)
limit = kwargs.get("limit", None)
if not offset:
offset = int(request.GET.get("offset", 0))
if not limit:
limit = (
int(request.GET.get("limit"))
if request.GET.get("limit") is not None
else None
)
return perform_table_query(
conn, fileid, query, col_names, offset=offset, limit=limit, lazy=lazy
)


table_query = login_required()(jsonp(_table_query))


Expand Down Expand Up @@ -3089,6 +3105,58 @@ def _table_metadata(request, fileid, conn=None, query=None, lazy=False, **kwargs
table_metadata = login_required()(jsonp(_table_metadata))


@login_required()
@jsonp
def obj_id_bitmask(request, fileid, conn=None, query=None, lazy=False, **kwargs):
col_name = request.GET.get("col_name", "object")
if query is None:
query = request.GET.get("query")
if not query:
return dict(error="Must specify query parameter, use * to retrieve all")

offset = kwargs.get("offset", 0)
limit = kwargs.get("limit", None)
if not offset:
offset = int(request.GET.get("offset", 0))
if not limit:
limit = (
int(request.GET.get("limit"))
if request.GET.get("limit") is not None
else None
)

rsp_data = perform_table_query(
conn,
fileid,
query,
[col_name],
offset=offset,
limit=limit,
lazy=False,
check_max_rows=False,
)
if "error" in rsp_data:
return rsp_data
data = rowsToByteArray(rsp_data["data"]["rows"])
return HttpResponse(bytes(data), content_type="application/octet-stream")


def rowsToByteArray(rows):
maxval = 0
for obj in rows:
obj_id = int(obj[0])
maxval = max(obj_id, maxval)
bitArray = numpy.zeros(maxval + 1, dtype='uint8')
for obj in rows:
obj_id = int(obj[0])
bitArray[obj_id] = 1
packed = numpy.packbits(bitArray, bitorder='big')
chris-allan marked this conversation as resolved.
Show resolved Hide resolved
data = bytearray()
for val in packed:
data.append(val)
return data


@login_required()
@jsonp
def object_table_query(request, objtype, objid, conn=None, **kwargs):
Expand Down
8 changes: 8 additions & 0 deletions test/unit/test_webgateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from omeroweb.webgateway.webgateway_cache import FileCache, WebGatewayCache
from omeroweb.webgateway.webgateway_cache import WebGatewayTempFile
from omeroweb.webgateway import views
import omero.gateway


Expand Down Expand Up @@ -384,3 +385,10 @@ def testJsonCache(self):
assert self.wcache._json_cache._num_entries != 0
self.wcache.clear()
assert self.wcache._json_cache._num_entries == 0

class TestViews(object):
def testRowstoByteArray(self):
rows = [[1],[2],[7],[11],[12]]
data = views.rowsToByteArray(rows)
assert data[0] == 97 #01100001 First, Second and 7th bits
assert data[1] == 24 #00011000 11th and 12th bits