Example #1
0
    def upload_annotation(self, predicted_data, project_id):
        self.cj.job.update(progress=95, statusComment="Uploading annotations")

        annotations = AnnotationCollection()
        components = ObjectFinder(predicted_data).find_components()
        locations = []
        for component in components:
            location = Polygon(component[0], component[1])

            if location.is_valid:
                locations.append(location)
            else:
                fixed = fix_geometry(location)

                if fixed.is_valid and not fixed.is_empty:
                    locations.append(fixed)

        for idx, loc in enumerate(locations):
            if not loc.is_valid:
                fixed = fix_geometry(loc)
                if fixed.is_valid and not fixed.is_empty:
                    loc[idx] = fixed

        annotations.extend([
            create_annotation_from_location(loc, self.image_instance.id,
                                            self.image_instance.height,
                                            project_id) for loc in locations
        ])

        annotations.save(chunk=20)
def extract_tiled_annotations(in_tiles, out_path, nj, label_merging=False):
    """
    in_images: iterable
        List of BiaflowsTile
    out_path: str
        Path of output tiles
    nj: BiaflowsJob
        A BIAflows job object
    label_merging: bool
        True for merging only polygons having the same label. False for merging based on geometry only
    """
    # regroup tiles by original images
    grouped_tiles = defaultdict(list)
    for in_tile in in_tiles:
        grouped_tiles[in_tile.in_image.original_filename].append(in_tile)

    default_tile_builder = DefaultTileBuilder()
    annotations = AnnotationCollection()
    for tiles in grouped_tiles.values():
        # recreate the topology
        in_image = tiles[0].in_image
        topology = BiaflowsSldcImage(in_image, is_2d=True).tile_topology(
            default_tile_builder,
            max_width=nj.flags["tile_width"],
            max_height=nj.flags["tile_height"],
            overlap=nj.flags["tile_overlap"])

        # extract polygons for each tile
        ids, polygons, labels = list(), list(), list()
        label = -1
        for tile in tiles:
            out_tile_path = os.path.join(out_path, tile.filename)
            slices = mask_to_objects_2d(imread(out_tile_path),
                                        offset=tile.tile.abs_offset[::-1])
            ids.append(tile.tile.identifier)
            polygons.append([s.polygon for s in slices])
            labels.append([s.label for s in slices])
            # save label for use after merging
            if len(slices) > 0:
                label = slices[0]

        # merge
        merged = SemanticMerger(tolerance=1).merge(
            ids, polygons, topology, labels=labels if label_merging else None)
        if label_merging:
            merged = merged[0]
        annotations.extend([
            create_annotation_from_slice(
                _slice=AnnotationSlice(p, label),
                id_image=in_image.object.id,
                image_height=in_image.object.height,
                id_project=nj.project.id,
            ) for p in merged
        ])
    return annotations
def mask_convert(mask,
                 image,
                 project_id,
                 mask_2d_fn,
                 mask_3d_fn,
                 track_prefix,
                 upload_group_id=False):
    """Generic function to convert a mask into an annotation collection

    Parameters
    ----------
    mask: ndarray
    image: ImageInstance
    project_id: int
    mask_2d_fn: callable
    mask_3d_fn: callable
    track_prefix: str
    upload_group_id: bool

    Returns
    -------
    tracks: TrackCollection
        Tracks, which have been saved
    annotations: AnnotationCollection
        Annotation which have NOT been saved
    """
    tracks = TrackCollection()
    annotations = AnnotationCollection()
    if mask.ndim == 2:
        slices = mask_2d_fn(mask)
        annotations.extend([
            create_annotation_from_slice(s,
                                         image.id,
                                         image.height,
                                         project_id,
                                         upload_group_id=upload_group_id)
            for s in slices
        ])
    elif mask.ndim == 3:
        slices = mask_3d_fn(mask)
        depth_to_slice = get_depth_to_slice(image)
        for obj_id, obj in enumerate(slices):
            track, curr_annotations = create_track_from_slices(
                image,
                obj,
                label=obj_id,
                depth2slice=depth_to_slice,
                track_prefix=track_prefix,
                id_project=project_id,
                upload_group_id=upload_group_id)
            tracks.append(track)
            annotations.extend(curr_annotations)
    else:
        raise ValueError("Only supports 2D or 3D output images...")
    return tracks, annotations
def extract_annotations_prttrk(out_path, in_image, project_id, track_prefix,
                               **kwargs):
    """
    Parameters:
    -----------
    out_path: str
    in_image: BiaflowsCytomineInput
    project_id: int
    name_prefix: str
    kwargs: dict
    """

    image = in_image.object
    path = os.path.join(out_path, in_image.filename)
    data, dim_order, _ = imread(path, return_order=True)
    ndim = get_dimensionality(dim_order)

    if ndim != 3:
        raise ValueError(
            "Annotation extraction for object tracking does not support masks with more than 3 dims..."
        )

    slices = mask_to_points_3d(data, time=True, assume_unique_labels=True)
    time_to_image = get_depth_to_slice(image)

    tracks = TrackCollection()
    annotations = AnnotationCollection()
    for slice_group in slices:
        curr_tracks, curr_annots = create_tracking_from_slice_group(
            image,
            slice_group,
            slice2point=lambda _slice: _slice.polygon,
            depth2slice=time_to_image,
            id_project=project_id,
            upload_object=False,
            track_prefix=track_prefix + "-particle",
            upload_group_id=True)
        tracks.extend(curr_tracks)
        annotations.extend(curr_annots)

    return tracks, annotations
def upload_data(problemclass,
                nj,
                inputs,
                out_path,
                monitor_params=None,
                **kwargs):
    """Upload annotations or any other related results to the server.

    Parameters
    ----------
    problemclass: str
        The problem class
    nj: CytomineJob|BiaflowsJob
        The CytomineJob or BiaflowsJob object. Ignored if do_export is True.
    inputs: list
        Input data as returned by the prepare_data
    out_path: str
        Output path
    monitor_params: dict|None
        A dictionnary of parameters to be passed to the data upload loop monitor.
    kwargs: dict
        Additional parameters for:
        * ObjDet/SptCnt: see function 'extract_annotations_objdet'
        * ObjSeg: see function 'extract_annotations_objseg'
    """
    if "is_2d" in kwargs:
        warnings.warn(
            "As of version 0.9.3, the 'is_2d' parameter is not needed anymore in function 'upload_data' and "
            "is now ignored.", DeprecationWarning)
    if not nj.flags["do_upload_annotations"]:
        return
    if nj.flags["tiling"] and (problemclass != CLASS_OBJSEG
                               and problemclass != CLASS_PIXCLA):
        print(
            "Annot. upload is only supported for one of {ObjSeg, PixCla} in 2D when tiling is enabled.. skipping !"
        )
        return
    if monitor_params is None:
        monitor_params = dict()

    annotations = AnnotationCollection()

    if nj.flags["tiling"]:
        annotations.extend(
            extract_tiled_annotations(
                inputs,
                out_path,
                nj,
                label_merging=problemclass == CLASS_PIXCLA))
    else:
        if problemclass == CLASS_OBJSEG:
            extract_fn = extract_annotations_objseg
        elif problemclass == CLASS_PIXCLA:
            extract_fn = extract_annotations_pixcla
        elif problemclass == CLASS_OBJDET or problemclass == CLASS_SPTCNT or problemclass == CLASS_LNDDET:
            extract_fn = extract_annotations_objdet
        elif problemclass == CLASS_LOOTRC or problemclass == CLASS_TRETRC:
            extract_fn = extract_annotations_lootrc
        elif problemclass == CLASS_PRTTRK:
            extract_fn = extract_annotations_prttrk
        elif problemclass == CLASS_OBJTRK:
            extract_fn = extract_annotations_objtrk
        else:
            raise NotImplementedError(
                "Upload data does not support problem class '{}' yet.".format(
                    problemclass))

        tracks = TrackCollection()
        monitor_params["prefix"] = "Extract masks/points/... from output data"
        for in_image in nj.monitor(inputs, **monitor_params):
            curr_tracks, curr_annots = extract_fn(out_path,
                                                  in_image,
                                                  nj.project.id,
                                                  track_prefix=str(nj.job.id),
                                                  **kwargs)
            tracks.extend(curr_tracks)
            annotations.extend(curr_annots)

    nj.job.update(
        statusComment="Upload extracted annotations (total: {})".format(
            len(annotations)))
    annotations.save(chunk=20, n_workers=min(4, cpu_count() * 2))
def extract_annotations_objtrk(out_path, in_image, project_id, track_prefix,
                               **kwargs):
    """
    out_path: str
    in_image: BiaflowsCytomineInput
    project_id: int
    track_prefix: str
    kwargs: dict
    """
    image = in_image.object
    path = os.path.join(out_path, in_image.filename)
    data, dim_order, _ = imread(path, return_order=True)
    ndim = get_dimensionality(dim_order)

    if ndim < 3:
        raise ValueError(
            "Object tracking should be at least 3D (only {} spatial dimension(s) found)"
            .format(ndim))

    tracks = TrackCollection()
    annotations = AnnotationCollection()

    if ndim == 3:
        slices = mask_to_objects_3d(data, time=True, assume_unique_labels=True)
        time_to_image = get_depth_to_slice(image)

        for slice_group in slices:
            curr_tracks, curr_annots = create_tracking_from_slice_group(
                image,
                slice_group,
                slice2point=lambda _slice: _slice.polygon.centroid,
                depth2slice=time_to_image,
                id_project=project_id,
                upload_object=True,
                upload_group_id=True,
                track_prefix=track_prefix + "-object")
            tracks.extend(curr_tracks)
            annotations.extend(curr_annots)
    elif ndim == 4:
        objects = mask_to_objects_3dt(mask=data)
        depths_to_image = get_depth_to_slice(image, depth=("time", "depth"))
        # TODO add tracking lines one way or another
        for time_steps in objects:
            label = time_steps[0][0].label
            track = Track(name="{}-{}".format(track_prefix, label),
                          id_image=image.id,
                          color=DEFAULT_COLOR).save()
            Property(track, key="label", value=label).save()
            annotations.extend([
                Annotation(location=change_referential(
                    p=slice.polygon, height=image.height).wkt,
                           id_image=image.id,
                           id_project=project_id,
                           id_tracks=[track.id],
                           slice=depths_to_image[(slice.time, slice.depth)].id)
                for slices in time_steps for slice in slices
            ])

            tracks.append(track)

    else:
        raise ValueError(
            "Annotation extraction for object tracking does not support masks with more than 4 dims..."
        )

    return tracks, annotations
def extract_annotations_objdet(out_path,
                               in_image,
                               project_id,
                               track_prefix,
                               is_csv=False,
                               generate_mask=False,
                               result_file_suffix=".tif",
                               has_headers=False,
                               parse_fn=None,
                               **kwargs):
    """
    Parameters:
    -----------
    out_path: str
    in_image: BiaflowsCytomineInput
    project_id: int
    is_csv: bool
        True if the output data are stored in a csv file
    generate_mask: bool
        If result is in a CSV, True for generating a mask based on the points in the csv. Ignored if is_csv is False.
        The mask file is generated in out_path with the name "{in_image.id}.png".
    result_file_suffix: str
        Suffix of the result filename (prefix being the image id).
    has_headers: bool
        True if the csv contains some headers (ignored if is_csv is False)
    parse_fn: callable
        A function for extracting coordinates from the csv file (already separated) line.
    track_prefix: str
    kwargs: dict
    """
    image = in_image.object
    file = str(image.id) + result_file_suffix
    path = os.path.join(out_path, file)

    tracks = TrackCollection()
    annotations = AnnotationCollection()
    if not os.path.isfile(path):
        print("No output file at '{}' for image with id:{}.".format(
            path, image.id),
              file=sys.stderr)
        return annotations

    # whether the points are stored in a csv or a mask
    if is_csv:  # only 2d
        if parse_fn is None:
            raise ValueError(
                "parse_fn shouldn't be 'None' when result file is a CSV.")
        points = csv_to_points(path,
                               has_headers=has_headers,
                               parse_fn=parse_fn)
        annotations.extend([
            create_annotation_from_slice(point,
                                         image.id,
                                         image.height,
                                         project_id,
                                         upload_group_id=False)
            for point in points
        ])

        if generate_mask:
            image, dim_order, _ = imread(in_image.filepath, return_order=True)
            mask = slices_to_mask(points, image.shape).squeeze()
            imwrite_ome(os.path.join(out_path, in_image.filename),
                        mask,
                        dim_order=dim_order)
    else:
        # points stored in a mask
        data, dim_order, _ = imread(path)
        tracks, annotations = mask_convert(
            data,
            image,
            project_id,
            mask_2d_fn=mask_to_points_2d,
            mask_3d_fn=lambda m: mask_to_points_3d(
                m, time=False, assume_unique_labels=False),
            track_prefix=track_prefix + "-object",
            upload_group_id=get_dimensionality(dim_order) > 2)

    return tracks, annotations