diff --git a/components/tools/OmeroPy/src/omero/gateway/__init__.py b/components/tools/OmeroPy/src/omero/gateway/__init__.py index beaa744421d..3ad2246fa33 100644 --- a/components/tools/OmeroPy/src/omero/gateway/__init__.py +++ b/components/tools/OmeroPy/src/omero/gateway/__init__.py @@ -158,6 +158,13 @@ def getChannelsQuery(): ' left outer join fetch logicalChannel.contrastMethod') +def add_plate_filter(clauses, params, opts): + """Helper for adding 'plate' to filtering clauses and parameters.""" + if opts is not None and 'plate' in opts: + clauses.append('obj.plate.id = :pid') + params.add('pid', rlong(opts['plate'])) + + class OmeroRestrictionWrapper (object): def canDownload(self): @@ -6038,6 +6045,21 @@ class _PlateAcquisitionWrapper (BlitzObjectWrapper): OMERO_CLASS = 'PlateAcquisition' + @classmethod + def _getQueryString(cls, opts=None): + """ + Extend base query to handle filtering of PlateAcquisitions by Plate. + Returns a tuple of (query, clauses, params). + Supported opts: 'plate': to filter by Plate + + :param opts: Dictionary of optional parameters. + :return: Tuple of string, list, ParametersI + """ + query, clauses, params = super( + _PlateAcquisitionWrapper, cls)._getQueryString(opts) + add_plate_filter(clauses, params, opts) + return (query, clauses, params) + def getName(self): name = super(_PlateAcquisitionWrapper, self).getName() if name is None: @@ -6099,16 +6121,19 @@ def _getQueryString(cls, opts=None): """ 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 opts is None: + opts = {} + add_plate_filter(clauses, params, opts) + load_images = opts.get('load_images') + load_pixels = opts.get('load_pixels') + load_channels = opts.get('load_channels') + if 'plateacquisition' in opts: + clauses.append('plateAcquisition.id = :plateAcq') + params.add('plateAcq', rlong(opts['plateacquisition'])) + load_images = True + if 'wellsample_index' in opts: + clauses.append('index(wellSamples) = :wellsample_index') + params.add('wellsample_index', rint(opts['wellsample_index'])) 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"\ diff --git a/components/tools/OmeroWeb/omeroweb/api/api_query.py b/components/tools/OmeroWeb/omeroweb/api/api_query.py index dd08b8fe47a..d6e3cbe1e7d 100644 --- a/components/tools/OmeroWeb/omeroweb/api/api_query.py +++ b/components/tools/OmeroWeb/omeroweb/api/api_query.py @@ -32,6 +32,31 @@ DEFAULT_LIMIT = max(1, api_settings.API_LIMIT) +def get_wellsample_indices(conn, plate_id=None, plateacquisition_id=None): + """ + Return min and max WellSample index for a Plate OR PlateAcquisition + + @param conn: BlitzGateway + @param plate_id: Plate ID + @param plateacquisition_id: PlateAcquisition ID + @return A dict of parent_id: child_count + """ + ctx = deepcopy(conn.SERVICE_OPTS) + ctx.setOmeroGroup(-1) + params = ParametersI() + query = "select minIndex(ws), maxIndex(ws) from Well well " \ + "join well.wellSamples ws" + if plate_id is not None: + query += " where well.plate.id=:plate_id " + params.add('plate_id', wrap(plate_id)) + elif plateacquisition_id is not None: + query += " where ws.plateAcquisition.id=:plateacquisition_id" + params.add('plateacquisition_id', wrap(plateacquisition_id)) + result = conn.getQueryService().projection(query, params, ctx) + result = [r for r in unwrap(result)[0] if r is not None] + return result + + def get_child_counts(conn, link_class, parent_ids): """ Count child links for the specified parent_ids. diff --git a/components/tools/OmeroWeb/omeroweb/api/urls.py b/components/tools/OmeroWeb/omeroweb/api/urls.py index fe04b93a5cb..5261364e90f 100644 --- a/components/tools/OmeroWeb/omeroweb/api/urls.py +++ b/components/tools/OmeroWeb/omeroweb/api/urls.py @@ -176,6 +176,44 @@ GET all wells, using omero-marshal to generate json """ +api_plate_plateacquisitions = url( + r'^v(?P%s)/m/plates/' + '(?P[0-9]+)/plateacquisitions/$' % versions, + views.PlateAcquisitionsView.as_view(), + name='api_plate_plateacquisitions') +""" +GET PlateAcquisitions in Plate, using omero-marshal to generate json +""" + +api_plateacquisition = url( + r'^v(?P%s)/m/plateacquisitions/' + '(?P[0-9]+)/$' % versions, + views.PlateAcquisitionView.as_view(), + name='api_plateacquisition') +""" +Well url to GET or DELETE a single Well +""" + +api_plateacquisition_wellsampleindex_wells = url( + r'^v(?P%s)/m/plateacquisitions/' + '(?P[0-9]+)/wellsampleindex/' + '(?P[0-9]+)/wells/$' % versions, + views.WellsView.as_view(), + name='api_plateacquisition_wellsampleindex_wells') +""" +GET Wells from a single Index in PlateAcquisition +""" + +api_plate_wellsampleindex_wells = url( + r'^v(?P%s)/m/plates/' + '(?P[0-9]+)/wellsampleindex/' + '(?P[0-9]+)/wells/$' % versions, + views.WellsView.as_view(), + name='api_plate_wellsampleindex_wells') +""" +GET Wells from a single Index in Plate +""" + api_plate_wells = url( r'^v(?P%s)/m/plates/' '(?P[0-9]+)/wells/$' % versions, @@ -185,6 +223,15 @@ GET Wells in Plate, using omero-marshal to generate json """ +api_plateacquisition_wells = url( + r'^v(?P%s)/m/plateacquisitions/' + '(?P[0-9]+)/wells/$' % versions, + views.WellsView.as_view(), + name='api_plateacquisition_wells') +""" +GET Wells in Plate, using omero-marshal to generate json +""" + api_well = url( r'^v(?P%s)/m/wells/(?P[0-9]+)/$' % versions, views.WellView.as_view(), @@ -215,6 +262,11 @@ api_screen_plates, api_plate, api_wells, + api_plate_plateacquisitions, + api_plateacquisition, + api_plateacquisition_wellsampleindex_wells, + api_plate_wellsampleindex_wells, api_plate_wells, + api_plateacquisition_wells, api_well, ) diff --git a/components/tools/OmeroWeb/omeroweb/api/views.py b/components/tools/OmeroWeb/omeroweb/api/views.py index 4c7add6389e..faf8a265ec1 100644 --- a/components/tools/OmeroWeb/omeroweb/api/views.py +++ b/components/tools/OmeroWeb/omeroweb/api/views.py @@ -28,7 +28,7 @@ import traceback import json -from api_query import query_objects, get_child_counts +from api_query import query_objects, get_child_counts, get_wellsample_indices from omero_marshal import get_encoder, get_decoder, OME_SCHEMA_URL from omero import ValidationException from omeroweb.connector import Server @@ -125,7 +125,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, urls=None, **kwargs): + def add_data(self, marshalled, request, conn, urls=None, **kwargs): """ Post-process marshalled object to add any extra data. @@ -179,7 +179,7 @@ def get(self, request, object_id, conn=None, **kwargs): ch_count = counts[object_id] if object_id in counts else 0 marshalled['omero:childCount'] = ch_count - self.add_data(marshalled, request, self.urls, **kwargs) + self.add_data(marshalled, request, conn, self.urls, **kwargs) return {'data': marshalled} def delete(self, request, object_id, conn=None, **kwargs): @@ -264,9 +264,59 @@ class PlateView(ObjectView): # Urls to add to marshalled object. See ProjectsView for more details urls = { 'url:wells': {'name': 'api_plate_wells', - 'kwargs': {'plate_id': 'OBJECT_ID'}} + 'kwargs': {'plate_id': 'OBJECT_ID'}}, + 'url:plateacquisitions': {'name': 'api_plate_plateacquisitions', + 'kwargs': {'plate_id': 'OBJECT_ID'}}, } + def add_data(self, marshalled, request, conn, urls=None, **kwargs): + """Add min/max WellSampleIndex.""" + marshalled = super(PlateView, self).add_data(marshalled, request, conn, + urls=urls, **kwargs) + idx = get_wellsample_indices(conn, marshalled['@id']) + marshalled['omero:wellsampleIndex'] = idx + + # Add link to Wells for each WellSample index in this Plate + ws_urls = [] + if len(idx) == 2: + for ws_index in range(idx[0], idx[1]+1): + version = kwargs['api_version'] + extra = {'plate_id': marshalled['@id'], + 'index': ws_index} + url = build_url(request, 'api_plate_wellsampleindex_wells', + version, **extra) + ws_urls.append(url) + marshalled['url:wellsampleindex_wells'] = ws_urls + + return marshalled + + +class PlateAcquisitionView(ObjectView): + """Handles GET for /plates/:plate_id/plateacquisitions.""" + + OMERO_TYPE = 'PlateAcquisition' + + def add_data(self, marshalled, request, conn, urls=None, **kwargs): + """Add min/max WellSampleIndex.""" + marshalled = super(PlateAcquisitionView, self).add_data( + marshalled, request, conn, urls=urls, **kwargs) + idx = get_wellsample_indices(conn, + plateacquisition_id=marshalled['@id']) + marshalled['omero:wellsampleIndex'] = idx + + # Add link to Wells for each WellSample index in this PlateAcquisition + ws_urls = [] + for ws_index in range(idx[0], idx[1]+1): + version = kwargs['api_version'] + extra = {'plateacquisition_id': marshalled['@id'], + 'index': ws_index} + url = build_url(request, + 'api_plateacquisition_wellsampleindex_wells', + version, **extra) + ws_urls.append(url) + marshalled['url:wellsampleindex_wells'] = ws_urls + return marshalled + class WellView(ObjectView): """Handle access to an individual Well to GET or DELETE it.""" @@ -282,9 +332,9 @@ def get_opts(self, request): opts['load_pixels'] = True return opts - def add_data(self, marshalled, request, urls=None, **kwargs): + def add_data(self, marshalled, request, conn, urls=None, **kwargs): """Add 'url:image' to any 'Image' in 'WellSamples'.""" - marshalled = super(WellView, self).add_data(marshalled, request, + marshalled = super(WellView, self).add_data(marshalled, request, conn, urls=urls, **kwargs) image_urls = { 'url:image': {'name': 'api_image', @@ -294,7 +344,8 @@ def add_data(self, marshalled, request, urls=None, **kwargs): # 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) + self.add_data(ws['Image'], request, conn, + image_urls, **kwargs) return marshalled @@ -330,7 +381,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, self.urls, **kwargs) + self.add_data(m, request, conn, self.urls, **kwargs) return marshalled @@ -429,7 +480,9 @@ def get_opts(self, request, **kwargs): 'url:wells': {'name': 'api_plate_wells', 'kwargs': {'plate_id': 'OBJECT_ID'}}, 'url:plate': {'name': 'api_plate', - 'kwargs': {'object_id': 'OBJECT_ID'}} + 'kwargs': {'object_id': 'OBJECT_ID'}}, + 'url:plateacquisitions': {'name': 'api_plate_plateacquisitions', + 'kwargs': {'plate_id': 'OBJECT_ID'}}, } @@ -461,6 +514,49 @@ def get_opts(self, request, **kwargs): return opts +class PlateAcquisitionsView(ObjectsView): + """Handles GET for /plates/:plate_id/plateacquisitions.""" + + OMERO_TYPE = 'PlateAcquisition' + + # Urls to add to marshalled object. See ProjectsView for more details + urls = { + 'url:plateacquisition': {'name': 'api_plateacquisition', + 'kwargs': {'object_id': 'OBJECT_ID'}}, + } + + def get_opts(self, request, **kwargs): + """Add extra parameters to the opts dict.""" + opts = super(PlateAcquisitionsView, self).get_opts(request, **kwargs) + opts['order_by'] = 'lower(obj.name)' + # at /plates/:plate_id/plateacquisitions/ we have 'plate_id' in kwargs + if 'plate_id' in kwargs: + opts['plate'] = long(kwargs['plate_id']) + return opts + + def add_data(self, marshalled, request, conn, urls=None, **kwargs): + """Add min/max WellSampleIndex.""" + marshalled = super(PlateAcquisitionsView, self).add_data( + marshalled, request, conn, urls=urls, **kwargs) + idx = get_wellsample_indices(conn, + plateacquisition_id=marshalled['@id']) + marshalled['omero:wellsampleIndex'] = idx + + # Add link to Wells for each WellSample index in this PlateAcquisition + ws_urls = [] + for ws_index in range(idx[0], idx[1]+1): + version = kwargs['api_version'] + extra = {'plateacquisition_id': marshalled['@id'], + 'index': ws_index} + url = build_url(request, + 'api_plateacquisition_wellsampleindex_wells', + version, **extra) + ws_urls.append(url) + marshalled['url:wellsampleindex_wells'] = ws_urls + + return marshalled + + class WellsView(ObjectsView): """Handles GET for /wells/ to list available Images.""" @@ -479,18 +575,23 @@ def get_opts(self, request, **kwargs): # at /plates/:plate_id/wells/ we have 'plate_id' in kwargs if 'plate_id' in kwargs: opts['plate'] = long(kwargs['plate_id']) + elif 'plateacquisition_id' in kwargs: + opts['plateacquisition'] = long(kwargs['plateacquisition_id']) else: # filter by query /wells/?plate=:id plate = getIntOrDefault(request, 'plate', None) if plate is not None: opts['plate'] = plate + # When filtering by plate or plateacquisition, can filter by ws index + if 'index' in kwargs: + opts['wellsample_index'] = int(kwargs['index']) # Listing Wells, load Images opts['load_images'] = True return opts - def add_data(self, marshalled, request, urls=None, **kwargs): + def add_data(self, marshalled, request, conn, urls=None, **kwargs): """Add 'url:image' to any 'Image' in 'WellSamples'.""" - marshalled = super(WellsView, self).add_data(marshalled, request, + marshalled = super(WellsView, self).add_data(marshalled, request, conn, urls=urls, **kwargs) image_urls = { 'url:image': {'name': 'api_image', @@ -500,7 +601,8 @@ def add_data(self, marshalled, request, urls=None, **kwargs): # 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) + self.add_data(ws['Image'], request, conn, + image_urls, **kwargs) return marshalled diff --git a/components/tools/OmeroWeb/test/integration/test_api_containers.py b/components/tools/OmeroWeb/test/integration/test_api_containers.py index 4b5c6fef5d1..124c0ad0a7c 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_containers.py +++ b/components/tools/OmeroWeb/test/integration/test_api_containers.py @@ -353,6 +353,9 @@ def test_datasets_plates(self, user1, dtype, child_count, object_url = "%sm/%ss/%s/" % (base_url, dtype.lower(), children[0].id.val) rsp = _get_response_json(django_client, object_url, payload) + if dtype == 'Plate': + # When we get a single Plate, expect this (not when listing plates) + ds_or_pl_children = [{'omero:wellsampleIndex': [0, 0]}] assert_objects(conn, [rsp['data']], [children[0]], dtype=dtype, extra=ds_or_pl_children) @@ -363,7 +366,7 @@ def test_datasets_plates(self, user1, dtype, child_count, 'childCount': str(child_count).lower()} rsp = _get_response_json(django_client, request_url, payload) extra = None - if ds_or_pl_children is not None: + if ds_or_pl_children is not None and len(ds_or_pl_children) > 1: extra = ds_or_pl_children[0:limit] assert_objects(conn, rsp['data'], children[0:limit], dtype=dtype, extra=extra) @@ -373,7 +376,7 @@ def test_datasets_plates(self, user1, dtype, child_count, 'offset': 0} payload['offset'] = limit # page 2 rsp = _get_response_json(django_client, request_url, payload) - if ds_or_pl_children is not None: + if ds_or_pl_children is not None and len(ds_or_pl_children) > 1: extra = ds_or_pl_children[limit:limit * 2] assert_objects(conn, rsp['data'], children[limit:limit * 2], dtype=dtype, extra=extra) @@ -455,7 +458,19 @@ def test_spw_urls(self, user1, screen_plates): assert_objects(conn, plates_json, plates, dtype='Plate', extra=extra) # View single plate rsp = _get_response_json(client, plates_json[0]['url:plate'], {}) - assert_objects(conn, [rsp['data']], plates[0:1], dtype='Plate') + plate_json = rsp['data'] + minMaxIndex = [0, 0] + links = [] + for idx in range(minMaxIndex[0], minMaxIndex[1]+1): + l = build_url(client, 'api_plate_wellsampleindex_wells', + {'api_version': version, + 'plate_id': plate_json['@id'], + 'index': idx}) + links.append(l) + extra = [{'url:wellsampleindex_wells': links, + 'omero:wellsampleIndex': minMaxIndex}] + assert_objects(conn, [plate_json], plates[0:1], dtype='Plate', + extra=extra) # List wells of first plate wells_url = plates_json[0]['url:wells'] diff --git a/components/tools/OmeroWeb/test/integration/test_api_wells.py b/components/tools/OmeroWeb/test/integration/test_api_wells.py index 643a399a0f7..96292c7429c 100644 --- a/components/tools/OmeroWeb/test/integration/test_api_wells.py +++ b/components/tools/OmeroWeb/test/integration/test_api_wells.py @@ -23,8 +23,11 @@ from django.core.urlresolvers import reverse from omeroweb.api import api_settings import pytest -from test_api_projects import get_update_service, \ - get_connection, marshal_objects +from test_api_projects import cmp_name_insensitive, \ + get_connection, \ + get_update_service, \ + marshal_objects +from test_api_containers import build_url from omero.model import ImageI, \ LengthI, \ PlateAcquisitionI, \ @@ -49,10 +52,10 @@ def cmp_column_row(x, y): return sort_by_column -def remove_urls(marshalled): +def remove_urls(marshalled, keys=[]): """Traverse a dict (Well) removing 'url:' values.""" for key, val in marshalled.items(): - if key.startswith('url:'): + if key.startswith('url:') and key not in keys: del(marshalled[key]) # We only traverse paths where we know urls are elif key == 'Image': @@ -85,13 +88,15 @@ def assert_objects(conn, json_objects, omero_ids_objects, dtype="Project", expected = marshal_objects(objs) assert len(json_objects) == len(expected) for i, o1, o2 in zip(range(len(expected)), json_objects, expected): + dont_remove = [] if extra is not None and i < len(extra): o2.update(extra[i]) + dont_remove = extra[i].keys() # We dump to json and re-load (same as test data). This means that # unicode has been handled in same way, e.g. Pixel size symbols. o2 = json.loads(json.dumps(o2)) - # remove any urls from json (tested elsewhere) - remove_urls(o1) + # remove urls from json + remove_urls(o1, dont_remove) assert o1 == o2 @@ -105,25 +110,28 @@ def user1(self): group = self.new_group(perms='rwra--') return self.new_client_and_user(group=group) - def create_plate_wells(self, user1, rows, cols, with_plate_acq=True): + def create_plate_wells(self, user1, rows, cols, plateacquisitions=1): """Return Plate with Wells.""" updateService = get_update_service(user1) plate = PlateI() plate.name = rstring('plate') plate = updateService.saveAndReturnObject(plate) - # Single PlateAcquisition for plate - if with_plate_acq: + # PlateAcquisitions for plate + plate_acqs = [] + for p in range(plateacquisitions): plate_acq = PlateAcquisitionI() - plate_acq.name = rstring('plateacquisition') + plate_acq.name = rstring('plateacquisition_%s' % p) plate_acq.description = rstring('plateacquisition_description') plate_acq.maximumFieldCount = rint(3) plate_acq.startTime = rtime(1L) plate_acq.endTime = rtime(2L) plate_acq.plate = PlateI(plate.id.val, False) plate_acq = updateService.saveAndReturnObject(plate_acq) + plate_acqs.append(plate_acq) # Create Wells for plate + ref_frame = UnitsLength.REFERENCEFRAME for row in range(rows): for col in range(cols): # create Well @@ -133,19 +141,23 @@ def create_plate_wells(self, user1, rows, cols, with_plate_acq=True): well.plate = PlateI(plate.id.val, False) # Only wells in first Column have well-samples etc. if col == 0: - # Have 3 images/well-samples in these wells - for i in range(3): - image = self.create_test_image( - size_x=5, size_y=5, session=user1[0].getSession()) - ws = WellSampleI() - ws.image = ImageI(image.id, False) - ws.well = well - ws.posX = LengthI(i * 10, UnitsLength.REFERENCEFRAME) - ws.posY = LengthI(i, UnitsLength.REFERENCEFRAME) - if with_plate_acq: - ws.setPlateAcquisition( - PlateAcquisitionI(plate_acq.id.val, False)) - well.addWellSample(ws) + # Have 3 images/well-samples per plateacquisition + # (if no plateacquisitions, create 3 well-samples without) + for p in range(max(1, plateacquisitions)): + for i in range(3): + image = self.create_test_image( + size_x=5, size_y=5, + session=user1[0].getSession()) + ws = WellSampleI() + ws.image = ImageI(image.id, False) + ws.well = well + ws.posX = LengthI(i * 10, ref_frame) + ws.posY = LengthI(i, ref_frame) + if p < len(plate_acqs): + ws.setPlateAcquisition( + PlateAcquisitionI(plate_acqs[p].id.val, + False)) + well.addWellSample(ws) updateService.saveObject(well) return plate @@ -156,7 +168,7 @@ def small_plate(self, user1): Two wells are created, but only the first has any Images (3). """ - return self.create_plate_wells(user1, 1, 2, with_plate_acq=False) + return self.create_plate_wells(user1, 1, 2, 0) @pytest.fixture() def bigger_plate(self, user1): @@ -168,6 +180,16 @@ def bigger_plate(self, user1): """ return self.create_plate_wells(user1, 2, 3) + @pytest.fixture() + def multi_acquisition_plate(self, user1): + """ + Create a bigger plate with 2 plate_acquisitions. + + Six wells are created, but only wells in the first + column have any Images (3 fields in each of 2 Acquisitions). + """ + return self.create_plate_wells(user1, 2, 3, 2) + def test_plate_wells(self, user1, small_plate, bigger_plate): """Test listing of Wells in a Plate.""" conn = get_connection(user1) @@ -198,9 +220,81 @@ def test_plate_wells(self, user1, small_plate, bigger_plate): assert 'Image' in well_sample assert ('PlateAcquisition' in well_sample) == with_acq assert 'Pixels' not in well_sample['Image'] - + extra = [{'url:well': build_url(django_client, 'api_well', + {'object_id': w.id.val, + 'api_version': version})} + for w in wells] assert_objects(conn, rsp['data'], wells, dtype='Well', - opts={'load_images': True}) + opts={'load_images': True}, extra=extra) + + def test_plate_index_wells(self, user1, multi_acquisition_plate): + """ + Test filtering of Wells by Plate/PlateAcquisition AND index. + + Browse urls Plate -> PlateAcquisitions -> Wells + OR Plate -> Wells (filtering by Index) + """ + conn = get_connection(user1) + user_name = conn.getUser().getName() + client = self.new_django_client(user_name, user_name) + version = api_settings.API_VERSIONS[-1] + + plate_id = multi_acquisition_plate.id.val + plate_url = reverse('api_plate', + kwargs={'object_id': plate_id, + 'api_version': version}) + + rsp = _get_response_json(client, plate_url, {}) + plate_json = rsp['data'] + + # Construct the urls we expect... + plate_acq_link = build_url(client, 'api_plate_plateacquisitions', + {'plate_id': plate_id, + 'api_version': version}) + well_link = build_url(client, 'api_plate_wells', + {'plate_id': plate_id, + 'api_version': version}) + index_links = [] + plate = conn.getObject('Plate', plate_id) + idx = plate.getNumberOfFields() + for i in range(idx[0], idx[1]+1): + l = build_url(client, 'api_plate_wellsampleindex_wells', + {'api_version': version, + 'plate_id': plate_id, + 'index': i}) + index_links.append(l) + # ...and compare plate json: + assert_objects(conn, [plate_json], [multi_acquisition_plate], + dtype='Plate', + extra=[{'url:plateacquisitions': plate_acq_link, + 'url:wellsampleindex_wells': index_links, + 'url:wells': well_link, + 'omero:wellsampleIndex': list(idx)}]) + + # Browse to /plate/:id/plateacquisitions/ + rsp = _get_response_json(client, plate_acq_link, {}) + plate_acq_json = rsp['data'] + + # Construct data & urls we expect... + pas = list(plate.listPlateAcquisitions()) + pas.sort(cmp_name_insensitive) + paq_ids = [p.id for p in pas] + extra = [] + for p, plate_acq in enumerate(pas): + index_links = [] + for i in range(p * 3, (p + 1) * 3): + l = build_url(client, + 'api_plateacquisition_wellsampleindex_wells', + {'api_version': version, + 'plateacquisition_id': plate_acq.id, + 'index': i}) + index_links.append(l) + extra.append({'url:wellsampleindex_wells': index_links, + 'omero:wellsampleIndex': [p * 3, (p + 1) * 3 - 1]}) + # ...and compare + assert_objects(conn, plate_acq_json, paq_ids, + dtype="PlateAcquisition", + extra=extra) def test_well(self, user1, small_plate): """Test loading a single Well, with or without WellSamples."""