Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
7c632bb
Add /wells/ ordered by column, row NOT name
will-moore Jan 18, 2017
1843443
Blitz buildQuery() remove lower(obj.%s) from order_by
will-moore Jan 18, 2017
f26b10e
ObjectsView order_by uses 'obj.'
will-moore Jan 18, 2017
02bd39b
Blitz Well.getQueryString() supports filter by 'plate'
will-moore Jan 18, 2017
e630df5
Filter Wells by ?plate=id or /plates/id/wells
will-moore Jan 18, 2017
51bc36c
Add wells_url to /plates/:id/ json
will-moore Jan 18, 2017
7b785cc
Blitz Well.getQueryString() supports 'load_images'
will-moore Jan 18, 2017
9139d69
Blitz Well.getQueryString() supports load_pixels & load_channels
will-moore Jan 18, 2017
6fec387
add_data(urls={}) can be used with specified urls
will-moore Jan 18, 2017
bf22ebb
Add WellView with add_data() to add urls to Images
will-moore Jan 18, 2017
43f5971
flake8 fixes
will-moore Jan 18, 2017
b4fe99f
Use 'url:wells' for wells in Plate json
will-moore Jan 19, 2017
f2d4fbf
Load images for /wells/ and pixels for /wells/:id
will-moore Jan 19, 2017
af784b9
Also add 'url:image' to Images in /wells/
will-moore Jan 19, 2017
9b8d9a0
New test_api_wells.py tests lising /wells/ and /wells/:id/
will-moore Jan 19, 2017
d645be8
Blitz Well getQueryString() also loads plateAcquisition with wellSample
will-moore Jan 20, 2017
ff4a187
flake8 fixes
will-moore Jan 22, 2017
7a5ca34
Blitz Well _getQueryString uses 'left outer join' for plateAcquisition
will-moore Jan 23, 2017
d474461
Added 'url:well' test to test_api_containers.py
will-moore Jan 23, 2017
88637ca
Merge remote-tracking branch 'origin/develop' into api_wells
will-moore Jan 23, 2017
3bc0096
flake8 fixes
will-moore Jan 23, 2017
ff3711b
Blitz fixes for PR #5047
will-moore Jan 24, 2017
e02844b
test_api fixes for PR #5047
will-moore Jan 24, 2017
84ad329
Add PlateAcquisition to test_api_wells.py
will-moore Jan 24, 2017
7664049
flake* and pep8 fixes for test_api_errors.py
will-moore Jan 24, 2017
2314276
Blitz flake8 fixes
will-moore Jan 24, 2017
256ce26
flake8 fix
will-moore Jan 25, 2017
1c0dbc6
test_plate_wells() tests plates with & without plate_acquisition
will-moore Jan 25, 2017
8797960
Blitz getPixelsQuery() helper
will-moore Jan 25, 2017
3a90c5c
Typos in web api tests
will-moore Jan 25, 2017
78a5a94
Blitz flake8 fix
will-moore Jan 25, 2017
852c3ed
Temp use chris' well-wellsample-image branch of omero-marshal
will-moore Jan 27, 2017
024a188
Don't use urls={} in ApiView.add_data()
will-moore Jan 30, 2017
8eb3ad2
Revert "Temp use chris' well-wellsample-image branch of omero-marshal"
will-moore Feb 12, 2017
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
68 changes: 58 additions & 10 deletions components/tools/OmeroPy/src/omero/gateway/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,23 @@ def fileread_gen(fin, fsize, bufsize):
fin.close()


def getPixelsQuery(imageName):
"""Helper for building Query for Images or Wells & Images"""
return (' left outer join fetch %s.pixels as pixels'
' left outer join fetch pixels.pixelsType' % imageName)


def getChannelsQuery():
"""Helper for building Query for Images or Wells & Images"""
return (' join fetch pixels.channels as channels'
' join fetch channels.logicalChannel as logicalChannel'
' left outer join fetch '
' logicalChannel.photometricInterpretation'
' left outer join fetch logicalChannel.illumination'
' left outer join fetch logicalChannel.mode'
' left outer join fetch logicalChannel.contrastMethod')


class OmeroRestrictionWrapper (object):

def canDownload(self):
Expand Down Expand Up @@ -3096,6 +3113,7 @@ def buildQuery(self, obj_type, ids=None, params=None, attributes=None,
:param opts: Dict of additional options for filtering or
defining extra data to load.
offset, limit and owner for all objects.
Also 'order_by': 'obj.name' to order results.
Additional opts handled by _getQueryString()
E.g. 'childCount', or filter Dataset by 'project'
:return: (query, params, wrapper)
Expand Down Expand Up @@ -3171,9 +3189,9 @@ def buildQuery(self, obj_type, ids=None, params=None, attributes=None,
if clauses:
query += " where " + (" and ".join(clauses))

# Order by...
# Order by... e.g. 'lower(obj.name)' or 'obj.column, obj.row' for wells
if order_by is not None:
query += " order by lower(obj.%s), obj.id" % order_by
query += " order by %s, obj.id" % order_by

return (query, baseParams, wrapper)

Expand Down Expand Up @@ -6053,6 +6071,42 @@ def __reset__(self):
"""
self._childcache = None

@classmethod
def _getQueryString(cls, opts=None):
"""
Extend base query to handle filtering of Wells by Plate.
Returns a tuple of (query, clauses, params).
Supported opts: 'plate': <plate_id> to filter by Plate
'load_images': <bool> to load wellSamples and images

:param opts: Dictionary of optional parameters.
:return: Tuple of string, list, ParametersI
"""
query, clauses, params = super(
_WellWrapper, cls)._getQueryString(opts)
if opts is not None and 'plate' in opts:
clauses.append('obj.plate.id = :pid')
params.add('pid', rlong(opts['plate']))
load_images = False
load_pixels = False
load_channels = False
if opts is not None:
load_images = opts.get('load_images')
load_pixels = opts.get('load_pixels')
load_channels = opts.get('load_channels')
if load_images or load_pixels or load_channels:
# NB: Using left outer join, we may get Wells with no Images
query += " left outer join fetch obj.wellSamples as wellSamples"\
" left outer join fetch wellSamples.image as image"\
" left outer join fetch wellSamples.plateAcquisition"\
" as plateAcquisition"
if load_pixels or load_channels:
query += getPixelsQuery("image")
if load_channels:
query += getChannelsQuery()

return (query, clauses, params)

def __loadedHotSwap__(self):
query = ("select well from Well as well "
"join fetch well.details.creationEvent "
Expand Down Expand Up @@ -7140,15 +7194,9 @@ def _getQueryString(cls, opts=None):
orphaned = opts.get('orphaned')
if load_pixels or load_channels:
# We use 'left outer join', since we still want images if no pixels
query += ' left outer join fetch obj.pixels pixels' \
' left outer join fetch pixels.pixelsType'
query += getPixelsQuery("obj")
if load_channels:
query += ' join fetch pixels.channels as channels' \
' join fetch channels.logicalChannel as logicalChannel' \
' left outer join fetch logicalChannel.photometricInterpretation' \
' left outer join fetch logicalChannel.illumination' \
' left outer join fetch logicalChannel.mode' \
' left outer join fetch logicalChannel.contrastMethod'
query += getChannelsQuery()
if orphaned:
clauses.append(
"""
Expand Down
27 changes: 27 additions & 0 deletions components/tools/OmeroWeb/omeroweb/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,30 @@
Plate url to GET or DELETE a single Plate
"""

api_wells = url(r'^v(?P<api_version>%s)/m/wells/$' % versions,
views.WellsView.as_view(),
name='api_wells')
"""
GET all wells, using omero-marshal to generate json
"""

api_plate_wells = url(
r'^v(?P<api_version>%s)/m/plates/'
'(?P<plate_id>[0-9]+)/wells/$' % versions,
views.WellsView.as_view(),
name='api_plate_wells')
"""
GET Wells in Plate, using omero-marshal to generate json
"""

api_well = url(
r'^v(?P<api_version>%s)/m/wells/(?P<object_id>[0-9]+)/$' % versions,
views.WellView.as_view(),
name='api_well')
"""
Well url to GET or DELETE a single Well
"""

urlpatterns = patterns(
'',
api_versions,
Expand All @@ -190,4 +214,7 @@
api_plates,
api_screen_plates,
api_plate,
api_wells,
api_plate_wells,
api_well,
)
127 changes: 110 additions & 17 deletions components/tools/OmeroWeb/omeroweb/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ def dispatch(self, *args, **kwargs):
"""Wrap other methods to add decorators."""
return super(ApiView, self).dispatch(*args, **kwargs)

def add_data(self, marshalled, request, **kwargs):
def add_data(self, marshalled, request, urls=None, **kwargs):
"""
Post-process marshalled object to add any extra data.

Expand All @@ -133,15 +133,16 @@ def add_data(self, marshalled, request, **kwargs):
"""
object_id = marshalled['@id']
version = kwargs['api_version']
for key, args in self.urls.items():
name = args['name']
kwargs = args['kwargs'].copy()
# If kwargs has 'OBJECT_ID' placeholder, we replace with id
for k, v in kwargs.items():
if v == 'OBJECT_ID':
kwargs[k] = object_id
url = build_url(request, name, version, **kwargs)
marshalled[key] = url
if urls is not None:
for key, args in urls.items():
name = args['name']
kwargs = args['kwargs'].copy()
# If kwargs has 'OBJECT_ID' placeholder, we replace with id
for k, v in kwargs.items():
if v == 'OBJECT_ID':
kwargs[k] = object_id
url = build_url(request, name, version, **kwargs)
marshalled[key] = url
return marshalled


Expand All @@ -161,7 +162,7 @@ def get(self, request, object_id, conn=None, **kwargs):
object_id))
encoder = get_encoder(obj._obj.__class__)
marshalled = encoder.encode(obj._obj)
self.add_data(marshalled, request, **kwargs)
self.add_data(marshalled, request, self.urls, **kwargs)
return marshalled

def delete(self, request, object_id, conn=None, **kwargs):
Expand Down Expand Up @@ -214,7 +215,6 @@ class ImageView(ObjectView):
def get_opts(self, request):
"""Add support for load_pixels and load_channels."""
opts = super(ImageView, self).get_opts(request)
opts['orphaned'] = request.GET.get('orphaned', False) == 'true'
# for single image, we always load channels
opts['load_channels'] = True
return opts
Expand All @@ -237,6 +237,40 @@ class PlateView(ObjectView):

OMERO_TYPE = 'Plate'

# Urls to add to marshalled object. See ProjectsView for more details
urls = {
'url:wells': {'name': 'api_plate_wells',
'kwargs': {'plate_id': 'OBJECT_ID'}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if that is placeholder why OBJECT_ID not just None ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OBJECT_ID is a unique identifier that this should be replaced with the ID of the current object. It's possible that other IDs could be used in future. Using None doesn't explicitly say what the placeholder should be replaced with.

}


class WellView(ObjectView):
"""Handle access to an individual Well to GET or DELETE it."""

OMERO_TYPE = 'Well'

def get_opts(self, request):
"""Add support for load_images."""
opts = super(WellView, self).get_opts(request)
# for single well, we load images with pixels
opts['load_pixels'] = True
return opts

def add_data(self, marshalled, request, urls=None, **kwargs):
"""Add 'url:image' to any 'Image' in 'WellSamples'."""
marshalled = super(WellView, self).add_data(marshalled, request,
urls=urls, **kwargs)
image_urls = {
'url:image': {'name': 'api_image',
'kwargs': {'object_id': 'OBJECT_ID'}},
}
if 'WellSamples' in marshalled:
# For each WellSample, add image urls to Image
for ws in marshalled['WellSamples']:
if 'Image' in ws:
self.add_data(ws['Image'], request, image_urls, **kwargs)
return marshalled


class ObjectsView(ApiView):
"""Base class for listing objects."""
Expand All @@ -258,7 +292,6 @@ def get_opts(self, request, **kwargs):
'owner': owner,
'orphaned': orphaned,
'child_count': child_count,
'order_by': 'name', # NB: will break if object has no name
}
return opts

Expand All @@ -271,7 +304,7 @@ def get(self, request, conn=None, **kwargs):
marshalled = query_objects(conn, self.OMERO_TYPE, group,
opts, normalize)
for m in marshalled['data']:
self.add_data(m, request, **kwargs)
self.add_data(m, request, self.urls, **kwargs)
return marshalled


Expand All @@ -280,6 +313,12 @@ class ProjectsView(ObjectsView):

OMERO_TYPE = 'Project'

def get_opts(self, request, **kwargs):
"""Add extra parameters to the opts dict."""
opts = super(ProjectsView, self).get_opts(request, **kwargs)
opts['order_by'] = 'lower(obj.name)'
return opts

# To add a url to marshalled object add to this dict
# 'name' is url name, kwargs are passed to reverse()
# If any kwargs values are 'OBJECT_ID' then this placeholder will be
Expand All @@ -298,8 +337,9 @@ class DatasetsView(ObjectsView):
OMERO_TYPE = 'Dataset'

def get_opts(self, request, **kwargs):
"""Add filtering by 'project' to the opts dict."""
"""Add extra parameters to the opts dict."""
opts = super(DatasetsView, self).get_opts(request, **kwargs)
opts['order_by'] = 'lower(obj.name)'
# at /projects/:project_id/datasets/ we have 'project_id' in kwargs
if 'project_id' in kwargs:
opts['project'] = long(kwargs['project_id'])
Expand All @@ -324,6 +364,12 @@ class ScreensView(ObjectsView):

OMERO_TYPE = 'Screen'

def get_opts(self, request, **kwargs):
"""Add extra parameters to the opts dict."""
opts = super(ScreensView, self).get_opts(request, **kwargs)
opts['order_by'] = 'lower(obj.name)'
return opts

# Urls to add to marshalled object. See ProjectsView for more details
urls = {
'url:plates': {'name': 'api_screen_plates',
Expand All @@ -339,8 +385,9 @@ class PlatesView(ObjectsView):
OMERO_TYPE = 'Plate'

def get_opts(self, request, **kwargs):
"""Add filtering by 'screen' to the opts dict."""
"""Add extra parameters to the opts dict."""
opts = super(PlatesView, self).get_opts(request, **kwargs)
opts['order_by'] = 'lower(obj.name)'
# at /screens/:screen_id/plates/ we have 'screen_id' in kwargs
if 'screen_id' in kwargs:
opts['screen'] = long(kwargs['screen_id'])
Expand All @@ -353,6 +400,8 @@ def get_opts(self, request, **kwargs):

# Urls to add to marshalled object. See ProjectsView for more details
urls = {
'url:wells': {'name': 'api_plate_wells',
'kwargs': {'plate_id': 'OBJECT_ID'}},
'url:plate': {'name': 'api_plate',
'kwargs': {'object_id': 'OBJECT_ID'}}
}
Expand All @@ -370,8 +419,9 @@ class ImagesView(ObjectsView):
}

def get_opts(self, request, **kwargs):
"""Add filtering by 'dataset' and other params to the opts dict."""
"""Add extra parameters to the opts dict."""
opts = super(ImagesView, self).get_opts(request, **kwargs)
opts['order_by'] = 'lower(obj.name)'
# at /datasets/:dataset_id/images/ we have 'dataset_id' in kwargs
if 'dataset_id' in kwargs:
opts['dataset'] = long(kwargs['dataset_id'])
Expand All @@ -385,6 +435,49 @@ def get_opts(self, request, **kwargs):
return opts


class WellsView(ObjectsView):
"""Handles GET for /wells/ to list available Images."""

OMERO_TYPE = 'Well'

# Urls to add to marshalled object. See ProjectsView for more details
urls = {
'url:well': {'name': 'api_well',
'kwargs': {'object_id': 'OBJECT_ID'}},
}

def get_opts(self, request, **kwargs):
"""Add extra parameters to the opts dict."""
opts = super(WellsView, self).get_opts(request, **kwargs)
opts['order_by'] = 'obj.column, obj.row'
# at /plates/:plate_id/wells/ we have 'plate_id' in kwargs
if 'plate_id' in kwargs:
opts['plate'] = long(kwargs['plate_id'])
else:
# filter by query /wells/?plate=:id
plate = getIntOrDefault(request, 'plate', None)
if plate is not None:
opts['plate'] = plate
# Listing Wells, load Images
opts['load_images'] = True
return opts

def add_data(self, marshalled, request, urls=None, **kwargs):
"""Add 'url:image' to any 'Image' in 'WellSamples'."""
marshalled = super(WellsView, self).add_data(marshalled, request,
urls=urls, **kwargs)
image_urls = {
'url:image': {'name': 'api_image',
'kwargs': {'object_id': 'OBJECT_ID'}},
}
if 'WellSamples' in marshalled:
# For each WellSample, add image urls to Image
for ws in marshalled['WellSamples']:
if 'Image' in ws:
self.add_data(ws['Image'], request, image_urls, **kwargs)
return marshalled


class SaveView(View):
"""
This view provides 'Save' functionality for all types of objects.
Expand Down
Loading