Exemple #1
0
    def collect_job_output(self, batch):
        '''Creates :class:`MapobjectType <tmlib.models.mapobject.MapobjectType>`
        instances for :class:`Site <tmlib.models.site.Site>`,
        :class:`Well <tmlib.models.well.Well>`,
        and :class:`Plate <tmlib.models.plate.Plate>` types and creates for each
        object of these classes an instance of
        :class:`Mapobject <tmlib.models.mapobject.Mapobject>` and
        :class:`MapobjectSegmentation <tmlib.models.mapobject.MapobjectSegmentation>`.
        This allows visualizing these objects on the map and using them
        for efficient spatial queries.

        Parameters
        ----------
        batch: dict
            job description
        '''
        mapobject_mappings = {
            'Plates': tm.Plate,
            'Wells': tm.Well,
            'Sites': tm.Site
        }
        for name, cls in mapobject_mappings.iteritems():
            with tm.utils.ExperimentSession(self.experiment_id,
                                            transaction=False) as session:
                logger.info(
                    'create static mapobject type "%s" for reference type "%s"',
                    name, cls.__name__)
                mapobject_type = session.get_or_create(
                    tm.MapobjectType,
                    name=name,
                    experiment_id=self.experiment_id,
                    ref_type=cls.__name__)
                mapobject_type_id = mapobject_type.id
                segmentation_layer = session.get_or_create(
                    tm.SegmentationLayer, mapobject_type_id=mapobject_type_id)

                logger.info('create individual mapobjects of type "%s"', name)
                segmentations = dict()
                for obj in session.query(cls):
                    logger.debug('create mapobject for reference object #%d',
                                 obj.id)
                    if name == 'Sites':
                        # We need to account for the "multiplexing" edge case.
                        offset = obj.aligned_offset
                        image_size = obj.aligned_image_size
                    else:
                        offset = obj.offset
                        image_size = obj.image_size
                    # First element: x axis
                    # Second element: inverted (!) y axis
                    # We further subtract one pixel such that the polygon
                    # defines the exact boundary of the objects. This is
                    # crucial for testing whether other objects intersect with
                    # the border.
                    ul = (offset[1] + 1, -1 * (offset[0] + 1))
                    ll = (ul[0], ul[1] - (image_size[0] - 3))
                    ur = (ul[0] + image_size[1] - 3, ul[1])
                    lr = (ll[0] + image_size[1] - 3, ll[1])
                    # Closed circle with coordinates sorted counter-clockwise
                    contour = np.array([ur, ul, ll, lr, ur])
                    polygon = shapely.geometry.Polygon(contour)
                    segmentations[obj.id] = {
                        'segmentation_layer_id': segmentation_layer.id,
                        'polygon': polygon
                    }

                logger.debug('delete existing mapobjects of type "%s"', name)
                session.query(tm.Mapobject).\
                    filter_by(mapobject_type_id=mapobject_type_id).\
                    delete()
                logger.debug('add new mapobjects of type "%s"', name)
                for key, value in segmentations.iteritems():
                    mapobject = tm.Mapobject(
                        partition_key=key, mapobject_type_id=mapobject_type_id)
                    session.add(mapobject)
                    session.flush()
                    logger.debug('add mapobject #%d', mapobject.id)
                    mapobject_segmentation = tm.MapobjectSegmentation(
                        partition_key=key,
                        mapobject_id=mapobject.id,
                        geom_polygon=value['polygon'],
                        geom_centroid=value['polygon'].centroid,
                        segmentation_layer_id=value['segmentation_layer_id'],
                    )
                    session.add(mapobject_segmentation)
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 _save_pipeline_outputs(self, store, assume_clean_state):
        logger.info('save pipeline outputs')
        objects_output = self.project.pipe.description.output.objects
        for item in objects_output:
            as_polygons = item.as_polygons
            store['objects'][item.name].save = True
            store['objects'][item.name].represent_as_polygons = as_polygons

        with tm.utils.ExperimentSession(self.experiment_id, False) as session:
            layer = session.query(tm.ChannelLayer).first()
            mapobject_type_ids = dict()
            segmentation_layer_ids = dict()
            objects_to_save = dict()
            feature_ids = collections.defaultdict(dict)
            for obj_name, segm_objs in store['objects'].iteritems():
                if segm_objs.save:
                    logger.info('objects of type "%s" are saved', obj_name)
                    objects_to_save[obj_name] = segm_objs
                else:
                    logger.info('objects of type "%s" are not saved', obj_name)
                    continue
                logger.debug('add object type "%s"', obj_name)
                mapobject_type = session.get_or_create(
                    tm.MapobjectType,
                    experiment_id=self.experiment_id,
                    name=obj_name,
                    ref_type=tm.Site.__name__)
                mapobject_type_ids[obj_name] = mapobject_type.id
                # Create a feature values entry for each segmented object at
                # each time point.
                logger.info('add features for objects of type "%s"', obj_name)
                for feature_name in segm_objs.measurements[0].columns:
                    logger.debug('add feature "%s"', feature_name)
                    feature = session.get_or_create(
                        tm.Feature,
                        name=feature_name,
                        mapobject_type_id=mapobject_type_ids[obj_name],
                        is_aggregate=False)
                    feature_ids[obj_name][feature_name] = feature.id

                for (t, z), plane in segm_objs.iter_planes():
                    segmentation_layer = session.get_or_create(
                        tm.SegmentationLayer,
                        mapobject_type_id=mapobject_type.id,
                        tpoint=t,
                        zplane=z)

                    segmentation_layer_ids[(obj_name, t, z)] = \
                        segmentation_layer.id

            site = session.query(tm.Site).get(store['site_id'])
            y_offset, x_offset = site.aligned_offset

            mapobject_ids = dict()
            for obj_name, segm_objs in objects_to_save.iteritems():
                if not assume_clean_state:
                    # Delete existing mapobjects for this site, which were
                    # generated in a previous run of the same pipeline. In case
                    # they were passed as inputs don't delete them.
                    inputs = self.project.pipe.description.input.objects
                    if obj_name not in inputs:
                        logger.info(
                            'delete segmentations for existing mapobjects of '
                            'type "%s"', obj_name)
                        session.query(tm.Mapobject).\
                            filter_by(
                                mapobject_type_id=mapobject_type_ids[obj_name],
                                partition_key=store['site_id']
                            ).\
                            delete()

                # Create a mapobject for each segmented object, i.e. each
                # pixel component having a unique label.
                logger.info('add objects of type "%s"', obj_name)
                # TODO: Can we avoid these multiple loops?
                # Is the bottleneck inserting objects into the db or Python?
                mapobjects = [
                    tm.Mapobject(
                        partition_key=store['site_id'],
                        mapobject_type_id=mapobject_type_ids[obj_name])
                    for _ in segm_objs.labels
                ]
                logger.info('insert objects into database')
                # FIXME: does this update the id attribute?
                session.bulk_ingest(mapobjects)
                session.flush()
                mapobject_ids = {
                    label: mapobjects[i].id
                    for i, label in enumerate(segm_objs.labels)
                }

                # Create a polygon and/or point for each segmented object
                # based on the cooridinates of their contours and centroids,
                # respectively.
                logger.info('add segmentations for objects of type "%s"',
                            obj_name)
                mapobject_segmentations = list()
                if segm_objs.represent_as_polygons:
                    logger.debug('represent segmented objects as polygons')
                    iterator = segm_objs.iter_polygons(y_offset, x_offset)
                    for t, z, label, polygon in iterator:
                        logger.debug(
                            'add segmentation for object #%d at '
                            'tpoint %d and zplane %d', label, t, z)
                        if polygon.is_empty:
                            logger.warn(
                                'object #%d of type %s doesn\'t have a polygon',
                                label, obj_name)
                            # TODO: Shall we rather raise an Exception here???
                            # At the moment we remove the corresponding
                            # mapobjects in the collect phase.
                            continue
                        mapobject_segmentations.append(
                            tm.MapobjectSegmentation(
                                partition_key=store['site_id'],
                                label=label,
                                geom_polygon=polygon,
                                geom_centroid=polygon.centroid,
                                mapobject_id=mapobject_ids[label],
                                segmentation_layer_id=segmentation_layer_ids[(
                                    obj_name, t, z)],
                            ))
                else:
                    logger.debug('represent segmented objects only as points')
                    iterator = segm_objs.iter_points(y_offset, x_offset)
                    for t, z, label, centroid in iterator:
                        logger.debug(
                            'add segmentation for object #%d at '
                            'tpoint %d and zplane %d', label, t, z)
                        mapobject_segmentations.append(
                            tm.MapobjectSegmentation(
                                partition_key=store['site_id'],
                                label=label,
                                geom_polygon=None,
                                geom_centroid=centroid,
                                mapobject_id=mapobject_ids[label],
                                segmentation_layer_id=segmentation_layer_ids[(
                                    obj_name, t, z)]))
                logger.info('insert segmentations into database')
                session.bulk_ingest(mapobject_segmentations)

                logger.info('add feature values for objects of type "%s"',
                            obj_name)
                logger.debug('round feature values to 6 decimals')
                feature_values = list()
                for t, data in enumerate(segm_objs.measurements):
                    data = data.round(6)  # single!
                    if data.empty:
                        logger.warn('empty measurement at time point %d', t)
                        continue
                    elif data.shape[0] < len(mapobject_ids):
                        # We clean up these objects in the collect phase.
                        logger.error('missing feature values')
                    elif data.shape[0] > len(mapobject_ids):
                        # Not sure this could happen.
                        logger.error('too many feature values')
                    column_lut = feature_ids[obj_name]
                    for label, c in data.rename(columns=column_lut).iterrows():
                        logger.debug(
                            'add values for mapobject #%d at time point %d',
                            label, t)
                        values = dict(
                            zip(c.index.astype(str), c.values.astype(str)))
                        feature_values.append(
                            tm.FeatureValues(partition_key=store['site_id'],
                                             mapobject_id=mapobject_ids[label],
                                             tpoint=t,
                                             values=values))
                logger.debug('insert feature values into db table')
                session.bulk_ingest(feature_values)