def get_segmentations(experiment_id, mapobject_type_id):
    """
    .. http:get:: /api/experiments/(string:experiment_id)/mapobject_types/(string:mapobject_type_id)/segmentations

        Get segmentations for each
        :class:`Mapobject <tmlib.model.mapobject.Mapobject>` contained within
        the specified :class:`Site <tmlib.models.site.Site>`. Segmentations are
        provided in form of a 2D labeled array, where pixel values encode
        object identity with unsigned integer values and background pixels are
        zero.

        **Example response**:

        .. sourcecode:: http

            HTTP/1.1 200 OK
            Content-Type: application/json

            {
                "data": [
                   [1205, 7042, 4438, 7446, 3213, 8773, 5445, 9884, 8326, 6357],
                   [4663, 2740, 9954, 5187,  309, 8029, 4502, 4927, 5259, 1802],
                   [8117, 8489, 8495, 1194, 9788, 8182, 5431, 9969, 5931, 6490],
                   [7974, 3990, 8892, 1866, 7890, 1147, 9630, 3687, 1565, 3613],
                   [3977, 7318, 5252, 3270, 6746,  822, 7035, 5184, 7808, 4013],
                   [4380, 6719, 5882, 4279, 7996, 2139, 1760, 2548, 3753, 5511],
                   [7829, 8825,  224, 1192, 9296, 1663, 5213, 9040,  463, 9080],
                   [6922, 6781, 9776, 9002, 6992, 8887, 9672, 8500, 1085,  563]
                ]
            }

        :reqheader Authorization: JWT token issued by the server
        :statuscode 200: no error
        :statuscode 400: malformed request

        :query plate_name: name of the plate (required)
        :query well_name: name of the well (required)
        :query well_pos_x: x-coordinate of the site within the well (required)
        :query well_pos_y: y-coordinate of the site within the well (required)
        :query tpoint: time point (required)
        :query zplane: z-plane (required)

    """
    plate_name = request.args.get('plate_name')
    well_name = request.args.get('well_name')
    well_pos_x = request.args.get('well_pos_x', type=int)
    well_pos_y = request.args.get('well_pos_y', type=int)
    zplane = request.args.get('zplane', type=int)
    tpoint = request.args.get('tpoint', type=int)
    align = is_true(request.args.get('align'))

    logger.info(
        'get segmentations for mapobject type %d of experiment %d at '
        'plate "%s", well "%s", well position %d/%d, zplane %d, time point %d',
        mapobject_type_id, experiment_id, plate_name, well_name, well_pos_y,
        well_pos_x, zplane, tpoint)

    with tm.utils.MainSession() as session:
        experiment = session.query(tm.ExperimentReference).get(experiment_id)
        experiment_name = experiment.name

    with tm.utils.ExperimentSession(experiment_id) as session:
        site = session.query(tm.Site).\
            join(tm.Well).\
            join(tm.Plate).\
            filter(
                tm.Plate.name == plate_name,
                tm.Well.name == well_name,
                tm.Site.x == well_pos_x, tm.Site.y == well_pos_y
            ).\
            one()
        mapobject_type = session.query(tm.MapobjectType).\
            get(mapobject_type_id)
        polygons = mapobject_type.get_segmentations_per_site(site.id,
                                                             tpoint=tpoint,
                                                             zplane=zplane)
        if len(polygons) == 0:
            raise ResourceNotFoundError(tm.MapobjectSegmentation, request.args)

        if align:
            y_offset, x_offset = site.aligned_offset
            height = site.aligned_height
            width = site.aligned_width
        else:
            y_offset, x_offset = site.offset
            height = site.height
            width = site.width

    img = SegmentationImage.create_from_polygons(polygons, y_offset, x_offset,
                                                 (height, width))
    return jsonify(data=img.array.tolist())
def add_segmentations(experiment_id, mapobject_type_id):
    """
    .. http:post:: /api/experiments/(string:experiment_id)/mapobject_types/(string:mapobject_type_id)/segmentations

        Provide segmentations in form of a labeled 2D pixels array
        for a given :class:`Site <tmlib.models.site.Site>`.
        A :class:`Mapobject <tmlib.models.mapobject.Mapobject>` and
        :class:`MapobjectSegmentation <tmlib.models.mapobject.MapobjectSegmentation>`
        will be created for each labeled connected pixel component in *image*.

        :reqheader Authorization: JWT token issued by the server
        :statuscode 200: no error
        :statuscode 400: malformed request

        :query npz_file: npz file containing the segmentation image "segmentation" (required)
        :query plate_name: name of the plate (required)
        :query well_name: name of the well (required)
        :query well_pos_x: x-coordinate of the site within the well (required)
        :query well_pos_y: y-coordinate of the site within the well (required)
        :query tpoint: time point (required)
        :query zplane: z-plane (required)

    """
    data = request.get_json()
    plate_name = data.get('plate_name')
    well_name = data.get('well_name')
    well_pos_x = int(data.get('well_pos_x'))
    well_pos_y = int(data.get('well_pos_y'))
    zplane = int(data.get('zplane'))
    tpoint = int(data.get('tpoint'))
    align = is_true(request.args.get('align'))  # TODO

    logger.info(
        'add segmentations for mapobject type %d of experiment %d at '
        'plate "%s", well "%s", well position %d/%d, zplane %d, time point %d',
        mapobject_type_id, experiment_id, plate_name, well_name, well_pos_y,
        well_pos_x, zplane, tpoint)

    npz_file = base64.b64decode(data.get('npz_file'))
    pixels = np.load(BytesIO(npz_file))["segmentation"]
    array = np.array(pixels, dtype=np.int32)
    labels = np.unique(array[array > 0])
    n_objects = len(labels)

    with tm.utils.ExperimentSession(experiment_id) as session:
        segmentation_layer = session.get_or_create(
            tm.SegmentationLayer,
            mapobject_type_id=mapobject_type_id,
            tpoint=tpoint,
            zplane=zplane)
        segmentation_layer_id = segmentation_layer.id

        site = session.query(tm.Site).\
            join(tm.Well).\
            join(tm.Plate).\
            filter(
                tm.Plate.name == plate_name, tm.Well.name == well_name,
                tm.Site.y == well_pos_y, tm.Site.x == well_pos_x
            ).\
            one()

        if align:
            y_offset, x_offset = site.aligned_offset
            if array.shape != site.aligned_image_size:
                raise MalformedRequestError('Image has wrong dimensions')
        else:
            y_offset, x_offset = site.offset
            if array.shape != site.image_size:
                raise MalformedRequestError('Image has wrong dimensions')
        site_id = site.id

        metadata = SegmentationImageMetadata(mapobject_type_id, site_id,
                                             tpoint, zplane)
        image = SegmentationImage(array, metadata)

        existing_segmentations_map = dict(
            session.query(
                tm.MapobjectSegmentation.label,
                tm.MapobjectSegmentation.mapobject_id
            ).\
            join(tm.Mapobject).\
            filter(
                tm.MapobjectSegmentation.label.in_(labels.tolist()),
                tm.Mapobject.mapobject_type_id == mapobject_type_id,
                tm.MapobjectSegmentation.partition_key == site_id
            ).\
            all()
        )

    with tm.utils.ExperimentSession(experiment_id, False) as session:
        segmentations = list()
        for label, polygon in image.extract_polygons(y_offset, x_offset):
            mapobject = tm.Mapobject(site_id, mapobject_type_id)
            if label in existing_segmentations_map:
                # A parent mapobject with the same label may already exist,
                # because it got already created for another zplane/tpoint.
                # The segmentation for the given zplane/tpoint must not yet
                # exist, however. This will lead to an error upon insertion.
                mapobject.id = existing_segmentations_map[label]
            else:
                session.add(mapobject)
                session.flush()
            s = tm.MapobjectSegmentation(
                partition_key=site_id,
                mapobject_id=mapobject.id,
                geom_polygon=polygon,
                geom_centroid=polygon.centroid,
                segmentation_layer_id=segmentation_layer_id,
                label=label)
            segmentations.append(s)
        session.bulk_ingest(segmentations)

    return jsonify(message='ok')
Exemple #3
0
def get_channel_image_file(experiment_id, channel_id):
    """
    .. http:get:: /api/experiments/(string:experiment_id)/channels/(string:channel_id)/image-files

        Get a specific image belonging to a channel.

        **Example response**:

        .. sourcecode:: http

            HTTP/1.1 200 OK
            Content-Type: image/png

        :query plate_name: name of the plate (required)
        :query cycle_index: cycle's index (required)
        :query well_name: name of the well (required)
        :query well_pos_x: x-coordinate of the site within the well (optional)
        :query well_pos_y: y-coordinate of the site within the well (optional)
        :query tpoint: time point (required)
        :query zplane: z-plane (required)
        :query illumcorr: correct image for illumination artifacts (optional)
        :query align: align image relative to reference cycle (optional)

        :reqheader Authorization: JWT token issued by the server
        :statuscode 200: no error
        :statuscode 404: no matching image found
        :statuscode 400: not all query parameters provided

    """
    logger.info(
        'get image of channel %d from experiment %d', channel_id, experiment_id
    )
    plate_name = request.args.get('plate_name')
    well_name = request.args.get('well_name')
    x = request.args.get('well_pos_x', type=int)
    y = request.args.get('well_pos_y', type=int)
    cycle_index = request.args.get('cycle_index', type=int)
    tpoint = request.args.get('tpoint', type=int)
    zplane = request.args.get('zplane', type=int)
    illumcorr = is_true(request.args.get('correct'))
    align = is_true(request.args.get('align'))
    with tm.utils.MainSession() as session:
        experiment = session.query(tm.ExperimentReference).get(experiment_id)
        experiment_name = experiment.name
    with tm.utils.ExperimentSession(experiment_id) as session:
        site_id = session.query(tm.Site.id).\
            join(tm.Well).\
            join(tm.Plate).\
            filter(
                tm.Plate.name == plate_name,
                tm.Well.name == well_name,
                tm.Site.x == x, tm.Site.y == y
            ).\
            one()[0]
        channel = session.query(tm.Channel).get(channel_id)
        channel_name = channel.name
        image_file = session.query(tm.ChannelImageFile).\
            join(tm.Cycle).\
            filter(
                tm.Cycle.index == cycle_index,
                tm.ChannelImageFile.site_id == site_id,
                tm.ChannelImageFile.channel_id == channel_id,
                tm.ChannelImageFile.tpoint == tpoint,
                tm.ChannelImageFile.zplane == zplane
            ).\
            one()
        img = image_file.get()
        if illumcorr:
            # TODO: cache in Redis for a limited amount of time to not having to
            # load the file repeatedly when user downloads multiple files of the
            # same channel
            logger.info('correct image for illumination artefacts')
            illumstats_file = session.query(tm.IllumstatsFile).\
                filter_by(channel_id=channel_id).\
                one_or_none()
            if illumstats_file is None:
                raise ResourceNotFoundError(
                    'No illumination statistics file found for channel %d'
                    % channel_id
                )
            stats = illumstats_file.get()
            img = img.correct(stats)
    if align:
        img = img.align()

    pixels = img.png_encode()
    f = StringIO()
    f.write(pixels)
    f.seek(0)
    filename = '%s_%s_%s_y%.3d_x%.3d_z%.3d_t%.3d_%s.png' % (
        experiment_name, plate_name, well_name, y, x, zplane, tpoint,
        channel_name
    )
    return send_file(
        f,
        attachment_filename=secure_filename(filename),
        mimetype='image/png',
        as_attachment=True
    )
Exemple #4
0
def get_channel_image_file(experiment_id, channel_id):
    """
    .. http:get:: /api/experiments/(string:experiment_id)/channels/(string:channel_id)/image-files

        Get a specific image belonging to a channel.

        **Example response**:

        .. sourcecode:: http

            HTTP/1.1 200 OK
            Content-Type: image/png

        :query plate_name: name of the plate (required)
        :query cycle_index: cycle's index (required)
        :query well_name: name of the well (required)
        :query well_pos_x: x-coordinate of the site within the well (optional)
        :query well_pos_y: y-coordinate of the site within the well (optional)
        :query tpoint: time point (required)
        :query zplane: z-plane (required)
        :query illumcorr: correct image for illumination artifacts (optional)
        :query align: align image relative to reference cycle (optional)

        :reqheader Authorization: JWT token issued by the server
        :statuscode 200: no error
        :statuscode 404: no matching image found
        :statuscode 400: not all query parameters provided

    """
    logger.info('get image of channel %d from experiment %d', channel_id,
                experiment_id)
    plate_name = request.args.get('plate_name')
    well_name = request.args.get('well_name')
    x = request.args.get('well_pos_x', type=int)
    y = request.args.get('well_pos_y', type=int)
    cycle_index = request.args.get('cycle_index', type=int)
    tpoint = request.args.get('tpoint', type=int)
    zplane = request.args.get('zplane', type=int)
    illumcorr = is_true(request.args.get('correct'))
    align = is_true(request.args.get('align'))
    with tm.utils.MainSession() as session:
        experiment = session.query(tm.ExperimentReference).get(experiment_id)
        experiment_name = experiment.name
    with tm.utils.ExperimentSession(experiment_id) as session:
        site_id = session.query(tm.Site.id).\
            join(tm.Well).\
            join(tm.Plate).\
            filter(
                tm.Plate.name == plate_name,
                tm.Well.name == well_name,
                tm.Site.x == x, tm.Site.y == y
            ).\
            one()[0]
        channel = session.query(tm.Channel).get(channel_id)
        channel_name = channel.name
        image_file = session.query(tm.ChannelImageFile).\
            join(tm.Cycle).\
            filter(
                tm.Cycle.index == cycle_index,
                tm.ChannelImageFile.site_id == site_id,
                tm.ChannelImageFile.channel_id == channel_id,
                tm.ChannelImageFile.tpoint == tpoint,
                tm.ChannelImageFile.zplane == zplane
            ).\
            one()
        img = image_file.get()
        if illumcorr:
            # TODO: cache in Redis for a limited amount of time to not having to
            # load the file repeatedly when user downloads multiple files of the
            # same channel
            logger.info('correct image for illumination artefacts')
            illumstats_file = session.query(tm.IllumstatsFile).\
                filter_by(channel_id=channel_id).\
                one_or_none()
            if illumstats_file is None:
                raise ResourceNotFoundError(
                    'No illumination statistics file found for channel %d' %
                    channel_id)
            stats = illumstats_file.get()
            img = img.correct(stats)
    if align:
        img = img.align()

    pixels = img.png_encode()
    f = StringIO()
    f.write(pixels)
    f.seek(0)
    filename = '%s_%s_%s_y%.3d_x%.3d_z%.3d_t%.3d_%s.png' % (
        experiment_name, plate_name, well_name, y, x, zplane, tpoint,
        channel_name)
    return send_file(f,
                     attachment_filename=secure_filename(filename),
                     mimetype='image/png',
                     as_attachment=True)
Exemple #5
0
def get_segmentations(experiment_id, mapobject_type_id):
    """
    .. http:get:: /api/experiments/(string:experiment_id)/mapobject_types/(string:mapobject_type_id)/segmentations

        Get segmentations for each
        :class:`Mapobject <tmlib.model.mapobject.Mapobject>` contained within
        the specified :class:`Site <tmlib.models.site.Site>`. Segmentations are
        provided in form of a 2D labeled array, where pixel values encode
        object identity with unsigned integer values and background pixels are
        zero.

        **Example response**:

        .. sourcecode:: http

            HTTP/1.1 200 OK
            Content-Type: application/json

            {
                "data": [
                   [1205, 7042, 4438, 7446, 3213, 8773, 5445, 9884, 8326, 6357],
                   [4663, 2740, 9954, 5187,  309, 8029, 4502, 4927, 5259, 1802],
                   [8117, 8489, 8495, 1194, 9788, 8182, 5431, 9969, 5931, 6490],
                   [7974, 3990, 8892, 1866, 7890, 1147, 9630, 3687, 1565, 3613],
                   [3977, 7318, 5252, 3270, 6746,  822, 7035, 5184, 7808, 4013],
                   [4380, 6719, 5882, 4279, 7996, 2139, 1760, 2548, 3753, 5511],
                   [7829, 8825,  224, 1192, 9296, 1663, 5213, 9040,  463, 9080],
                   [6922, 6781, 9776, 9002, 6992, 8887, 9672, 8500, 1085,  563]
                ]
            }

        :reqheader Authorization: JWT token issued by the server
        :statuscode 200: no error
        :statuscode 400: malformed request

        :query plate_name: name of the plate (required)
        :query well_name: name of the well (required)
        :query well_pos_x: x-coordinate of the site within the well (required)
        :query well_pos_y: y-coordinate of the site within the well (required)
        :query tpoint: time point (required)
        :query zplane: z-plane (required)

    """
    plate_name = request.args.get('plate_name')
    well_name = request.args.get('well_name')
    well_pos_x = request.args.get('well_pos_x', type=int)
    well_pos_y = request.args.get('well_pos_y', type=int)
    zplane = request.args.get('zplane', type=int)
    tpoint = request.args.get('tpoint', type=int)
    align = is_true(request.args.get('align'))

    logger.info(
        'get segmentations for mapobject type %d of experiment %d at '
        'plate "%s", well "%s", well position %d/%d, zplane %d, time point %d',
        mapobject_type_id, experiment_id, plate_name, well_name, well_pos_y,
        well_pos_x, zplane, tpoint
    )

    with tm.utils.MainSession() as session:
        experiment = session.query(tm.ExperimentReference).get(experiment_id)
        experiment_name = experiment.name

    with tm.utils.ExperimentSession(experiment_id) as session:
        site = session.query(tm.Site).\
            join(tm.Well).\
            join(tm.Plate).\
            filter(
                tm.Plate.name == plate_name,
                tm.Well.name == well_name,
                tm.Site.x == well_pos_x, tm.Site.y == well_pos_y
            ).\
            one()
        mapobject_type = session.query(tm.MapobjectType).\
            get(mapobject_type_id)
        polygons = mapobject_type.get_segmentations_per_site(
            site.id, tpoint=tpoint, zplane=zplane
        )
        if len(polygons) == 0:
            raise ResourceNotFoundError(tm.MapobjectSegmentation, request.args)

        if align:
            y_offset, x_offset = site.aligned_offset
            height = site.aligned_height
            width = site.aligned_width
        else:
            y_offset, x_offset = site.offset
            height = site.height
            width = site.width

    img = SegmentationImage.create_from_polygons(
        polygons, y_offset, x_offset, (height, width)
    )
    return jsonify(data=img.array.tolist())
Exemple #6
0
def add_segmentations(experiment_id, mapobject_type_id):
    """
    .. http:post:: /api/experiments/(string:experiment_id)/mapobject_types/(string:mapobject_type_id)/segmentations

        Provide segmentations in form of a labeled 2D pixels array
        for a given :class:`Site <tmlib.models.site.Site>`.
        A :class:`Mapobject <tmlib.models.mapobject.Mapobject>` and
        :class:`MapobjectSegmentation <tmlib.models.mapobject.MapobjectSegmentation>`
        will be created for each labeled connected pixel component in *image*.

        :reqheader Authorization: JWT token issued by the server
        :statuscode 200: no error
        :statuscode 400: malformed request

        :query npz_file: npz file containing the segmentation image "segmentation" (required)
        :query plate_name: name of the plate (required)
        :query well_name: name of the well (required)
        :query well_pos_x: x-coordinate of the site within the well (required)
        :query well_pos_y: y-coordinate of the site within the well (required)
        :query tpoint: time point (required)
        :query zplane: z-plane (required)

    """
    data = request.get_json()
    plate_name = data.get('plate_name')
    well_name = data.get('well_name')
    well_pos_x = int(data.get('well_pos_x'))
    well_pos_y = int(data.get('well_pos_y'))
    zplane = int(data.get('zplane'))
    tpoint = int(data.get('tpoint'))
    align = is_true(request.args.get('align')) # TODO

    logger.info(
        'add segmentations for mapobject type %d of experiment %d at '
        'plate "%s", well "%s", well position %d/%d, zplane %d, time point %d',
        mapobject_type_id, experiment_id, plate_name, well_name, well_pos_y,
        well_pos_x, zplane, tpoint
    )

    npz_file = base64.b64decode(data.get('npz_file'))
    pixels = np.load(BytesIO(npz_file))["segmentation"]
    array = np.array(pixels, dtype=np.int32)
    labels = np.unique(array[array > 0])
    n_objects = len(labels)

    with tm.utils.ExperimentSession(experiment_id) as session:
        segmentation_layer = session.get_or_create(
            tm.SegmentationLayer,
            mapobject_type_id=mapobject_type_id, tpoint=tpoint, zplane=zplane
        )
        segmentation_layer_id = segmentation_layer.id

        site = session.query(tm.Site).\
            join(tm.Well).\
            join(tm.Plate).\
            filter(
                tm.Plate.name == plate_name, tm.Well.name == well_name,
                tm.Site.y == well_pos_y, tm.Site.x == well_pos_x
            ).\
            one()

        if align:
            y_offset, x_offset = site.aligned_offset
            if array.shape != site.aligned_image_size:
                raise MalformedRequestError('Image has wrong dimensions')
        else:
            y_offset, x_offset = site.offset
            if array.shape != site.image_size:
                raise MalformedRequestError('Image has wrong dimensions')
        site_id = site.id

        metadata = SegmentationImageMetadata(
            mapobject_type_id, site_id, tpoint, zplane
        )
        image = SegmentationImage(array, metadata)

        existing_segmentations_map = dict(
            session.query(
                tm.MapobjectSegmentation.label,
                tm.MapobjectSegmentation.mapobject_id
            ).\
            join(tm.Mapobject).\
            filter(
                tm.MapobjectSegmentation.label.in_(labels.tolist()),
                tm.Mapobject.mapobject_type_id == mapobject_type_id,
                tm.MapobjectSegmentation.partition_key == site_id
            ).\
            all()
        )

    with tm.utils.ExperimentSession(experiment_id, False) as session:
        segmentations = list()
        for label, polygon in image.extract_polygons(y_offset, x_offset):
            mapobject = tm.Mapobject(site_id, mapobject_type_id)
            if label in existing_segmentations_map:
                # A parent mapobject with the same label may already exist,
                # because it got already created for another zplane/tpoint.
                # The segmentation for the given zplane/tpoint must not yet
                # exist, however. This will lead to an error upon insertion.
                mapobject.id = existing_segmentations_map[label]
            else:
                session.add(mapobject)
                session.flush()
            s = tm.MapobjectSegmentation(
                partition_key=site_id, mapobject_id=mapobject.id,
                geom_polygon=polygon, geom_centroid=polygon.centroid,
                segmentation_layer_id=segmentation_layer_id, label=label
            )
            segmentations.append(s)
        session.bulk_ingest(segmentations)

    return jsonify(message='ok')