Exemple #1
0
def main(mask,
         intensity_image,
         min_area,
         max_area,
         min_cut_area,
         max_circularity,
         max_convexity,
         plot=False,
         selection_test_mode=False):
    '''Detects clumps in `mask` given criteria provided by the user
    and cuts them along the borders of watershed regions, which are determined
    based on the distance transform of `mask`.

    Parameters
    ----------
    mask: numpy.ndarray[Union[numpy.int32, numpy.bool]]
        2D binary or labele image encoding potential clumps
    intensity_image: numpy.ndarray[numpy.uint8 or numpy.uint16]
        2D grayscale image with intensity values of the objects that should
        be detected
    min_area: int
        minimal area an object must have to be considered a clump
    max_area: int
        maximal area an object can have to be considered a clump
    min_cut_area: int
        minimal area an object must have
        (useful to prevent cuts that would result in too small objects)
    max_circularity: float
        maximal circularity an object can have to be considerd a clump
    max_convexity: float
        maximal convexity an object can have to be considerd a clump
    plot: bool, optional
        whether a plot should be generated
    selection_test_mode: bool, optional
        whether, instead of the normal plot, heatmaps should be generated that
        display values of the selection criteria *area*, *circularity* and
        *convexity* for each individual object in `mask` as well as
        the selected "clumps" based on the criteria provided by the user

    Returns
    -------
    jtmodules.separate_clumps.Output
    '''

    separated_mask = separate_clumped_objects(mask, min_cut_area, min_area,
                                              max_area, max_circularity,
                                              max_convexity)

    if plot:
        from jtlib import plotting
        if selection_test_mode:
            logger.info('create plot for selection test mode')
            labeled_mask, n_objects = mh.label(mask)
            f = Morphology(labeled_mask)
            values = f.extract()
            area_img = create_feature_image(values['Morphology_Area'].values,
                                            labeled_mask)
            convexity_img = create_feature_image(
                values['Morphology_Convexity'].values, labeled_mask)
            circularity_img = create_feature_image(
                values['Morphology_Circularity'].values, labeled_mask)
            area_colorscale = plotting.create_colorscale(
                'Greens',
                n_objects,
                add_background=True,
                background_color='white')
            circularity_colorscale = plotting.create_colorscale(
                'Blues',
                n_objects,
                add_background=True,
                background_color='white')
            convexity_colorscale = plotting.create_colorscale(
                'Reds',
                n_objects,
                add_background=True,
                background_color='white')
            plots = [
                plotting.create_float_image_plot(area_img,
                                                 'ul',
                                                 colorscale=area_colorscale),
                plotting.create_float_image_plot(
                    convexity_img, 'ur', colorscale=convexity_colorscale),
                plotting.create_float_image_plot(
                    circularity_img, 'll', colorscale=circularity_colorscale),
                plotting.create_mask_image_plot(clumps_mask, 'lr'),
            ]
            figure = plotting.create_figure(
                plots,
                title=('Selection criteria: "area" (green), "convexity" (red) '
                       'and "circularity" (blue)'))
        else:
            logger.info('create plot')

            cut_mask = (mask > 0) - (separated_mask > 0)
            clumps_mask = np.zeros(mask.shape, bool)
            initial_objects_label_image, n_initial_objects = mh.label(mask > 0)
            for i in range(1, n_initial_objects + 1):
                index = initial_objects_label_image == i
                if len(np.unique(separated_mask[index])) > 1:
                    clumps_mask[index] = True

            n_objects = len(np.unique(separated_mask[separated_mask > 0]))
            colorscale = plotting.create_colorscale('Spectral',
                                                    n=n_objects,
                                                    permute=True,
                                                    add_background=True)
            outlines = mh.morph.dilate(mh.labeled.bwperim(separated_mask > 0))
            cutlines = mh.morph.dilate(mh.labeled.bwperim(cut_mask))
            plots = [
                plotting.create_mask_image_plot(separated_mask,
                                                'ul',
                                                colorscale=colorscale),
                plotting.create_intensity_overlay_image_plot(
                    intensity_image, outlines, 'ur'),
                plotting.create_mask_overlay_image_plot(
                    clumps_mask, cutlines, 'll')
            ]
            figure = plotting.create_figure(plots, title='separated clumps')
    else:
        figure = str()

    return Output(separated_mask, figure)
def main(image, mask, threshold=25,
         mean_size=6, min_size=10,
         filter_type='log_2d',
         minimum_bead_intensity=150,
         z_step=0.333, pixel_size=0.1625,
         alpha=0, plot=False):
    '''Converts an image stack with labelled cell surface to a cell
    `volume` image

    Parameters
    ----------
    image: numpy.ndarray[Union[numpy.uint8, numpy.uint16]]
        grayscale image in which beads should be detected (3D)
    mask: numpy.ndarray[Union[numpy.int32, numpy.bool]]
        binary or labeled image of cell segmentation (2D)
    threshold: int, optional
        intensity of bead in filtered image (default: ``25``)
    mean_size: int, optional
        mean size of bead (default: ``6``)
    min_size: int, optional
        minimal number of connected voxels per bead (default: ``10``)
    filter_type: str, optional
        filter used to emphasise the beads in 3D
        (options: ``log_2d`` (default) or ``log_3d``)
    minimum_bead_intensity: int, optional
        minimum intensity in the original image of an identified bead
        centre. Use to filter low intensity beads.
    z_step: float, optional
        distance between consecutive z-planes (um) (default: ``0.333``)
    pixel_size: float, optional
        size of pixel (um) (default: ``0.1625``)
    alpha: float, optional
        value of parameter for 3D alpha shape calculation
        (default: ``0``, no vertex filtering performed)
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.generate_volume_image.Output
    '''

    # Check that there are cells identified in image
    if (np.max(mask) > 0):
        volume_image_calculated = True

        n_slices = image.shape[-1]
        logger.debug('input image has z-dimension %d', n_slices)

        # Remove high intensity pixels
        detect_image = image.copy()
        p = np.percentile(detect_image, 99.9)
        detect_image[detect_image > p] = p

        # Perform LoG filtering in 3D to emphasise beads
        if filter_type == 'log_2d':
            logger.info('using stacked 2D LoG filter to detect beads')
            f = -1 * log_2d(size=mean_size, sigma=float(mean_size - 1) / 3)
            filt = np.stack([f for _ in range(mean_size)], axis=2)

        elif filter_type == 'log_3d':
            logger.info('using 3D LoG filter to detect beads')
            filt = -1 * log_3d(mean_size, (float(mean_size - 1) / 3,
                                           float(mean_size - 1) / 3,
                                           4 * float(mean_size - 1) / 3))
        else:
            logger.info('using unfiltered image to detect beads')

        if filter_type == 'log_2d' or filter_type == 'log_3d':
            logger.debug('convolve image with filter kernel')
            detect_image = mh.convolve(detect_image.astype(float), filt)
            detect_image[detect_image < 0] = 0

        logger.debug('threshold beads')
        labeled_beads, n_labels = mh.label(detect_image > threshold)
        logger.info('detected %d beads', n_labels)

        logger.debug('remove small beads')
        sizes = mh.labeled.labeled_size(labeled_beads)
        too_small = np.where(sizes < min_size)
        labeled_beads = mh.labeled.remove_regions(labeled_beads, too_small)
        mh.labeled.relabel(labeled_beads, inplace=True)
        logger.info(
            '%d beads remain after removing small beads', np.max(labeled_beads)
        )

        logger.debug('localise beads in 3D')
        localised_beads = localise_bead_maxima_3D(
            image, labeled_beads, minimum_bead_intensity
        )

        logger.debug('mask beads inside cells')
        '''NOTE: localised_beads.coordinate image is used only for beads
        outside cells and can therefore be modified here. For beads
        inside cells, localised_beads.coordinates are used instead.
        '''
        # expand mask to ensure slide-beads are well away from cells
        slide = localised_beads.coordinate_image
        expand_mask = mh.dilate(
            A=mask > 0,
            Bc=np.ones([25,25], bool)
        )
        slide[expand_mask] = 0

        logger.debug('determine coordinates of slide surface')
        try:
            bottom_surface = slide_surface_params(slide)
        except InvalidSlideError:
            logger.error('slide surface calculation is invalid' +
                         ' returning empty volume image')
            volume_image = np.zeros(shape=image[:,:,0].shape,
                                    dtype=image.dtype)
            figure = str()
            return Output(volume_image, figure)

        logger.debug('subtract slide surface to get absolute bead coordinates')
        bead_coords_abs = []
        for i in range(len(localised_beads.coordinates)):
            bead_height = (
                localised_beads.coordinates[i][2] -
                plane(localised_beads.coordinates[i][0],
                      localised_beads.coordinates[i][1],
                      bottom_surface.x)
            )
            if bead_height > 0:
                bead_coords_abs.append(
                    (localised_beads.coordinates[i][0],
                     localised_beads.coordinates[i][1],
                     bead_height)
                )

        logger.debug('convert absolute bead coordinates to image')
        coord_image_abs = coordinate_list_to_array(
            bead_coords_abs, shape=image[:,:,0].shape, dtype=np.float32
        )

        filtered_coords_global = filter_vertices_per_cell_alpha_shape(
            coord_image_abs=coord_image_abs,
            mask=mask,
            alpha=alpha,
            z_step=z_step,
            pixel_size=pixel_size
        )

        logger.info('interpolate cell surface')
        volume_image = interpolate_surface(
            coords=np.asarray(filtered_coords_global, dtype=np.uint16),
            output_shape=np.shape(image[:,:,0]),
            method='linear'
        )

        volume_image = volume_image.astype(image.dtype)

        logger.debug('set regions outside mask to zero')
        volume_image[mask == 0] = 0

    else:
        logger.warn(
            'no objects in input mask, skipping cell volume calculation.'
        )
        volume_image_calculated = False
        volume_image = np.zeros(shape=image[:,:,0].shape, dtype=image.dtype)

    if (plot and volume_image_calculated):
        logger.debug('convert bottom surface plane to image for plotting')
        dt = np.dtype(float)
        bottom_surface_image = np.zeros(slide.shape, dtype=dt)
        for ix in range(slide.shape[0]):
            for iy in range(slide.shape[1]):
                bottom_surface_image[ix, iy] = plane(
                    ix, iy, bottom_surface.x)
        logger.info('create plot')
        from jtlib import plotting
        plots = [
            plotting.create_intensity_image_plot(
                np.max(image, axis=-1), 'ul', clip=True
            ),
            plotting.create_float_image_plot(
                bottom_surface_image, 'll', clip=True
            ),
            plotting.create_intensity_image_plot(
                volume_image, 'ur', clip=True
            )

        ]
        figure = plotting.create_figure(
            plots, title='Convert stack to volume image'
        )
    else:
        figure = str()

    return Output(volume_image, figure)
Exemple #3
0
def main(mask, feature, lower_threshold=None, upper_threshold=None, plot=False):
    '''Filters objects (connected components) based on the specified
    value range for a given `feature`.

    Parameters
    ----------
    mask: numpy.ndarray[Union[numpy.bool, numpy.int32]]
        image that should be filtered
    feature: str
        name of the feature based on which the image should be filtered
        (options: ``{"area", "eccentricity", "circularity", "convecity"}``)
    lower_threshold:
        minimal `feature` value objects must have
        (default: ``None``; type depends on the chosen `feature`)
    upper_threshold:
        maximal `feature` value objects must have
        (default: ``None``; type depends on the chosen `feature`)
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.filter_objects.Output

    Raises
    ------
    ValueError
        when both `lower_threshold` and `upper_threshold` are ``None``
    ValueError
        when value of `feature` is not one of the supported features

    '''
    if lower_threshold is None and upper_threshold is None:
        raise ValueError(
            'Argument "lower_threshold" or "upper_threshold" must be provided. '
        )
    if feature not in SUPPORTED_FEATURES:
        raise ValueError(
            'Argument "feature" must be one of the following: "%s".'
            % '", "'.join(SUPPORTED_FEATURES)
        )

    name = 'Morphology_{0}'.format(feature.capitalize())

    labeled_image = mh.label(mask > 0)[0]
    f = Morphology(labeled_image)
    measurement = f.extract()[name]
    values = measurement.values

    feature_image = create_feature_image(values, labeled_image)
    if not measurement.empty:
        if lower_threshold is None:
            lower_threshold = np.min(values)
        if upper_threshold is None:
            upper_threshold = np.max(values)
        logger.info(
            'keep objects with "%s" values in the range [%d, %d]',
            feature, lower_threshold, upper_threshold
        )

        condition_image = np.logical_or(
            feature_image < lower_threshold, feature_image > upper_threshold
        )
        filtered_mask = labeled_image.copy()
        filtered_mask[condition_image] = 0
    else:
        logger.warn('no objects detected in image')
        filtered_mask = labeled_image

    mh.labeled.relabel(filtered_mask, inplace=True)

    if plot:
        from jtlib import plotting
        plots = [
            plotting.create_mask_image_plot(mask, 'ul'),
            plotting.create_float_image_plot(feature_image, 'ur'),
            plotting.create_mask_image_plot(filtered_mask, 'll'),
        ]
        n_removed = (
            len(np.unique(labeled_image)) - len(np.unique(filtered_mask))
        )
        figure = plotting.create_figure(
            plots,
            title='Filtered for feature "{0}": {1} objects removed'.format(
                feature, n_removed
            )
        )
    else:
        figure = str()

    return Output(filtered_mask, figure)
Exemple #4
0
def main(image,
         mask,
         threshold=1,
         min_area=3,
         mean_area=5,
         max_area=1000,
         clip_percentile=99.999,
         plot=False):
    '''Detects blobs in `image` using an implementation of
    `SExtractor <http://www.astromatic.net/software/sextractor>`_ [1].
    The `image` is first convolved with a Laplacian of Gaussian filter of size
    `mean_area` to enhance blob-like structures. The enhanced image is
    then thresholded at `threshold` level and connected pixel components are
    subsequently deplended.

    Parameters
    ----------
    image: numpy.ndarray[Union[numpy.uint8, numpy.uint16]]
        grayscale image in which blobs should be detected
    mask: numpy.ndarray[Union[numpy.int32, numpy.bool]]
        binary or labeled image that specifies pixel regions of interest
        in which blobs should be detected
    threshold: int, optional
        threshold level for pixel values in the convolved image
        (default: ``1``)
    min_area: int, optional
        minimal size a blob is allowed to have (default: ``3``)
    mean_area: int, optional
        estimated average size of a blob (default: ``5``)
    max_area: int, optional
        maximal size a blob is allowed to have to be subject to deblending;
        no attempt will be made to deblend blobs larger than `max_area`
        (default: ``100``)
    clip_percentile: float, optional
        clip intensity values in `image` above the given percentile; this may
        help in attenuating artifacts
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.detect_blobs.Output[Union[numpy.ndarray, str]]

    References
    ----------
    .. [1] Bertin, E. & Arnouts, S. 1996: SExtractor: Software for source
    extraction, Astronomy & Astrophysics Supplement 317, 393
    '''

    logger.info('detect blobs above threshold {0}'.format(threshold))

    detect_image = image.copy()

    p = np.percentile(image, clip_percentile)
    detect_image[image > p] = p

    # Enhance the image for blob detection by convoling it with a LOG filter
    f = -1 * log_2d(size=mean_area, sigma=float(mean_area - 1) / 3)
    detect_image = mh.convolve(detect_image.astype(float), f)
    detect_image[detect_image < 0] = 0

    # Mask regions of too big blobs
    pre_blobs = mh.label(detect_image > threshold)[0]
    bad_blobs, n_bad = mh.labeled.filter_labeled(pre_blobs, min_size=max_area)
    logger.info(
        'remove {0} blobs because they are bigger than {1} pixels'.format(
            n_bad, max_area))
    detect_mask = np.invert(mask > 0)
    detect_mask[bad_blobs > 0] = True
    detect_image[bad_blobs > 0] = 0

    logger.info('deblend blobs')
    blobs, centroids = detect_blobs(image=detect_image,
                                    mask=detect_mask,
                                    threshold=threshold,
                                    min_area=min_area)

    n = len(np.unique(blobs[blobs > 0]))

    logger.info('{0} blobs detected'.format(n))

    if plot:
        logger.info('create plot')
        from jtlib import plotting

        colorscale = plotting.create_colorscale('Spectral',
                                                n=n,
                                                permute=True,
                                                add_background=True)
        plots = [
            plotting.create_float_image_plot(detect_image, 'ul', clip=True),
            plotting.create_mask_image_plot(blobs, 'ur', colorscale=colorscale)
        ]
        figure = plotting.create_figure(
            plots,
            title=('detected #{0} blobs above threshold {1}'
                   ' in LOG filtered image'.format(n, threshold)))
    else:
        figure = str()

    return Output(centroids, blobs, figure)
def main(mask, intensity_image, min_area, max_area,
        min_cut_area, max_circularity, max_convexity,
        plot=False, selection_test_mode=False):
    '''Detects clumps in `mask` given criteria provided by the user
    and cuts them along the borders of watershed regions, which are determined
    based on the distance transform of `mask`.

    Parameters
    ----------
    mask: numpy.ndarray[Union[numpy.int32, numpy.bool]]
        2D binary or labele image encoding potential clumps
    intensity_image: numpy.ndarray[numpy.uint8 or numpy.uint16]
        2D grayscale image with intensity values of the objects that should
        be detected
    min_area: int
        minimal area an object must have to be considered a clump
    max_area: int
        maximal area an object can have to be considered a clump
    min_cut_area: int
        minimal area an object must have
        (useful to prevent cuts that would result in too small objects)
    max_circularity: float
        maximal circularity an object can have to be considerd a clump
    max_convexity: float
        maximal convexity an object can have to be considerd a clump
    plot: bool, optional
        whether a plot should be generated
    selection_test_mode: bool, optional
        whether, instead of the normal plot, heatmaps should be generated that
        display values of the selection criteria *area*, *circularity* and
        *convexity* for each individual object in `mask` as well as
        the selected "clumps" based on the criteria provided by the user

    Returns
    -------
    jtmodules.separate_clumps.Output
    '''

    separated_mask = separate_clumped_objects(
        mask, min_cut_area, min_area, max_area,
        max_circularity, max_convexity
    )

    if plot:
        from jtlib import plotting
        if selection_test_mode:
            logger.info('create plot for selection test mode')
            labeled_mask, n_objects = mh.label(mask)
            f = Morphology(labeled_mask)
            values = f.extract()
            area_img = create_feature_image(
                values['Morphology_Area'].values, labeled_mask
            )
            convexity_img = create_feature_image(
                values['Morphology_Convexity'].values, labeled_mask
            )
            circularity_img = create_feature_image(
                values['Morphology_Circularity'].values, labeled_mask
            )
            area_colorscale = plotting.create_colorscale(
                'Greens', n_objects,
                add_background=True, background_color='white'
            )
            circularity_colorscale = plotting.create_colorscale(
                'Blues', n_objects,
                add_background=True, background_color='white'
            )
            convexity_colorscale = plotting.create_colorscale(
                'Reds', n_objects,
                add_background=True, background_color='white'
            )
            plots = [
                plotting.create_float_image_plot(
                    area_img, 'ul', colorscale=area_colorscale
                ),
                plotting.create_float_image_plot(
                    convexity_img, 'ur', colorscale=convexity_colorscale
                ),
                plotting.create_float_image_plot(
                    circularity_img, 'll', colorscale=circularity_colorscale
                ),
                plotting.create_mask_image_plot(
                    clumps_mask, 'lr'
                ),
            ]
            figure = plotting.create_figure(
                plots,
                title=(
                    'Selection criteria: "area" (green), "convexity" (red) '
                    'and "circularity" (blue)'
                )
            )
        else:
            logger.info('create plot')

            cut_mask = (mask > 0) - (separated_mask > 0)
            clumps_mask = np.zeros(mask.shape, bool)
            initial_objects_label_image, n_initial_objects = mh.label(mask > 0)
            for i in range(1, n_initial_objects+1):
                index = initial_objects_label_image == i
                if len(np.unique(separated_mask[index])) > 1:
                    clumps_mask[index] = True

            n_objects = len(np.unique(separated_mask[separated_mask > 0]))
            colorscale = plotting.create_colorscale(
                'Spectral', n=n_objects, permute=True, add_background=True
            )
            outlines = mh.morph.dilate(mh.labeled.bwperim(separated_mask > 0))
            cutlines = mh.morph.dilate(mh.labeled.bwperim(cut_mask))
            plots = [
                plotting.create_mask_image_plot(
                    separated_mask, 'ul', colorscale=colorscale
                ),
                plotting.create_intensity_overlay_image_plot(
                    intensity_image, outlines, 'ur'
                ),
                plotting.create_mask_overlay_image_plot(
                    clumps_mask, cutlines, 'll'
                )
            ]
            figure = plotting.create_figure(
                plots, title='separated clumps'
            )
    else:
        figure = str()

    return Output(separated_mask, figure)
def main(mask,
         intensity_image,
         min_area,
         max_area,
         min_cut_area,
         max_circularity,
         max_convexity,
         plot=False,
         selection_test_mode=False,
         selection_test_show_remaining=False,
         trimming=True):
    '''Detects clumps in `mask` given criteria provided by the user
    and cuts them along the borders of watershed regions, which are determined
    based on the distance transform of `mask`.

    Parameters
    ----------
    mask: numpy.ndarray[Union[numpy.int32, numpy.bool]]
        2D binary or labele image encoding potential clumps
    intensity_image: numpy.ndarray[numpy.uint8 or numpy.uint16]
        2D grayscale image with intensity values of the objects that should
        be detected
    min_area: int
        minimal area an object must have to be considered a clump
    max_area: int
        maximal area an object can have to be considered a clump
    min_cut_area: int
        minimal area an object must have
        (useful to prevent cuts that would result in too small objects)
    max_circularity: float
        maximal circularity an object can have to be considerd a clump
    max_convexity: float
        maximal convexity an object can have to be considerd a clump
    plot: bool, optional
        whether a plot should be generated
    selection_test_mode: bool, optional
        whether, instead of the normal plot, heatmaps should be generated that
        display values of the selection criteria *area*, *circularity* and
        *convexity* for each individual object in `mask` as well as
        the selected "clumps" based on the criteria provided by the user
    selection_test_show_remaining: bool, optional
        whether the selection test plot should be made on the remaining image
        after the cuts were performed (helps to see why some objects were not
        cut, especially if there are complicated clumps that require multiple
        cuts). Defaults to false, thus showing the values in the original image
    trimming: bool
        some cuts may create a tiny third object. If this boolean is true, 
        tertiary objects < trimming_threshold (10) pixels will be removed

    Returns
    -------
    jtmodules.separate_clumps.Output
    '''

    separated_label_image = separate_clumped_objects(mask,
                                                     min_cut_area,
                                                     min_area,
                                                     max_area,
                                                     max_circularity,
                                                     max_convexity,
                                                     allow_trimming=trimming)

    if plot:
        from jtlib import plotting

        clumps_mask = np.zeros(mask.shape, bool)
        initial_objects_label_image, n_initial_objects = mh.label(mask > 0)
        for n in range(1, n_initial_objects + 1):
            obj = (initial_objects_label_image == n)
            if len(np.unique(separated_label_image[obj])) > 1:
                clumps_mask[obj] = True

        cut_mask = (mask > 0) & (separated_label_image == 0)
        cutlines = mh.morph.dilate(mh.labeled.bwperim(cut_mask))

        if selection_test_mode:
            logger.info('create plot for selection test mode')

            # Check if selection_test_show_remaining is active
            # If so, show values on processed image, not original
            if selection_test_show_remaining:
                labeled_mask, n_objects = mh.label(separated_label_image > 0)
                logger.info('Selection test mode plot with processed image')
            else:
                labeled_mask, n_objects = mh.label(mask)
            f = Morphology(labeled_mask)
            values = f.extract()
            area_img = create_feature_image(values['Morphology_Area'].values,
                                            labeled_mask)
            convexity_img = create_feature_image(
                values['Morphology_Convexity'].values, labeled_mask)
            circularity_img = create_feature_image(
                values['Morphology_Circularity'].values, labeled_mask)
            plots = [
                plotting.create_float_image_plot(area_img, 'ul'),
                plotting.create_float_image_plot(convexity_img, 'ur'),
                plotting.create_float_image_plot(circularity_img, 'll'),
                plotting.create_mask_overlay_image_plot(
                    clumps_mask, cutlines, 'lr'),
            ]
            figure = plotting.create_figure(
                plots,
                title=('Selection criteria:'
                       ' "area" (top left),'
                       ' "convexity" (top-right),'
                       ' and "circularity" (bottom-left);'
                       ' cuts made (bottom right).'))
        else:
            logger.info('create plot')

            n_objects = len(
                np.unique(separated_label_image[separated_label_image > 0]))
            colorscale = plotting.create_colorscale('Spectral',
                                                    n=n_objects,
                                                    permute=True,
                                                    add_background=True)
            outlines = mh.morph.dilate(
                mh.labeled.bwperim(separated_label_image > 0))
            plots = [
                plotting.create_mask_image_plot(separated_label_image,
                                                'ul',
                                                colorscale=colorscale),
                plotting.create_intensity_overlay_image_plot(
                    intensity_image, outlines, 'ur'),
                plotting.create_mask_overlay_image_plot(
                    clumps_mask, cutlines, 'll')
            ]
            figure = plotting.create_figure(plots, title='separated clumps')
    else:
        figure = str()

    return Output(separated_label_image, figure)
Exemple #7
0
def main(image, mask, threshold=1, min_area=3, mean_area=5, max_area=1000,
        clip_percentile=99.999, plot=False):
    '''Detects blobs in `image` using an implementation of
    `SExtractor <http://www.astromatic.net/software/sextractor>`_ [1].
    The `image` is first convolved with a Laplacian of Gaussian filter of size
    `mean_area` to enhance blob-like structures. The enhanced image is
    then thresholded at `threshold` level and connected pixel components are
    subsequently deplended.

    Parameters
    ----------
    image: numpy.ndarray[Union[numpy.uint8, numpy.uint16]]
        grayscale image in which blobs should be detected
    mask: numpy.ndarray[Union[numpy.int32, numpy.bool]]
        binary or labeled image that specifies pixel regions of interest
        in which blobs should be detected
    threshold: int, optional
        threshold level for pixel values in the convolved image
        (default: ``1``)
    min_area: int, optional
        minimal size a blob is allowed to have (default: ``3``)
    mean_area: int, optional
        estimated average size of a blob (default: ``5``)
    max_area: int, optional
        maximal size a blob is allowed to have to be subject to deblending;
        no attempt will be made to deblend blobs larger than `max_area`
        (default: ``100``)
    clip_percentile: float, optional
        clip intensity values in `image` above the given percentile; this may
        help in attenuating artifacts
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.detect_blobs.Output[Union[numpy.ndarray, str]]

    References
    ----------
    .. [1] Bertin, E. & Arnouts, S. 1996: SExtractor: Software for source
    extraction, Astronomy & Astrophysics Supplement 317, 393
    '''

    logger.info('detect blobs above threshold {0}'.format(threshold))

    detect_image = image.copy()

    p = np.percentile(image, clip_percentile)
    detect_image[image > p] = p

    # Enhance the image for blob detection by convoling it with a LOG filter
    f = -1 * log_2d(size=mean_area, sigma=float(mean_area - 1)/3)
    detect_image = mh.convolve(detect_image.astype(float), f)
    detect_image[detect_image < 0] = 0

    # Mask regions of too big blobs
    pre_blobs = mh.label(detect_image > threshold)[0]
    bad_blobs, n_bad = mh.labeled.filter_labeled(pre_blobs, min_size=max_area)
    logger.info(
        'remove {0} blobs because they are bigger than {1} pixels'.format(
            n_bad, max_area
        )
    )
    detect_mask = np.invert(mask > 0)
    detect_mask[bad_blobs > 0] = True
    detect_image[bad_blobs > 0] = 0

    logger.info('deblend blobs')
    blobs, centroids = detect_blobs(
        image=detect_image, mask=detect_mask, threshold=threshold,
        min_area=min_area
    )

    n = len(np.unique(blobs[blobs>0]))

    logger.info('{0} blobs detected'.format(n))

    if plot:
        logger.info('create plot')
        from jtlib import plotting

        colorscale = plotting.create_colorscale(
            'Spectral', n=n, permute=True, add_background=True
        )
        plots = [
            plotting.create_float_image_plot(
                detect_image, 'ul', clip=True
            ),
            plotting.create_mask_image_plot(
                blobs, 'ur', colorscale=colorscale
            )
        ]
        figure = plotting.create_figure(
            plots,
            title=(
                'detected #{0} blobs above threshold {1}'
                ' in LOG filtered image'.format(n, threshold)
            )
        )
    else:
        figure = str()

    return Output(centroids, blobs, figure)