Exemplo n.º 1
0
def main(image, threshold, plot=False):
    '''Thresholds an image by applying a given global threshold level.

    Parameters
    ----------
    image: numpy.ndarray
        image of arbitrary data type that should be thresholded
    threshold: int
        threshold level
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.threshold_manual.Output[Union[numpy.ndarray, str]]
    '''
    logger.info('threshold image at %d', threshold)
    mask = image > threshold

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        outlines = mh.morph.dilate(mh.labeled.bwperim(mask))
        plots = [
            plotting.create_intensity_overlay_image_plot(
                image, outlines, 'ul'),
            plotting.create_mask_image_plot(mask, 'ur')
        ]
        figure = plotting.create_figure(plots,
                                        title='thresholded at %s' % threshold)
    else:
        figure = str()

    return Output(mask, figure)
Exemplo n.º 2
0
def main(image, threshold, plot=False):
    '''Thresholds an image by applying a given global threshold level.

    Parameters
    ----------
    image: numpy.ndarray
        image of arbitrary data type that should be thresholded
    threshold: int
        threshold level
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.threshold_manual.Output[Union[numpy.ndarray, str]]
    '''
    logger.info('threshold image at %d', threshold)
    mask = image > threshold

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        outlines = mh.morph.dilate(mh.labeled.bwperim(mask))
        plots = [
            plotting.create_intensity_overlay_image_plot(
                image, outlines, 'ul'
            ),
            plotting.create_mask_image_plot(mask, 'ur')
        ]
        figure = plotting.create_figure(
            plots, title='thresholded at %s' % threshold
        )
    else:
        figure = str()

    return Output(mask, figure)
Exemplo n.º 3
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)
Exemplo n.º 4
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,
         method,
         kernel_size,
         constant=0,
         min_threshold=None,
         max_threshold=None,
         plot=False):
    '''Thresholds an image with a locally adaptive threshold method.

    Parameters
    ----------
    image: numpy.ndarray
        grayscale image that should be thresholded
    method: str
        thresholding method (options: ``{"crosscorr", "niblack"}``)
    kernel_size: int
        size of the neighbourhood region that's used to calculate the threshold
        value at each pixel position (must be an odd number)
    constant: Union[float, int], optional
        depends on `method`; in case of ``"crosscorr"`` method the constant
        is subtracted from the computed weighted sum per neighbourhood region
        and in case of ``"niblack"`` the constant is multiplied by the
        standard deviation and this term is then subtracted from the mean
        computed per neighbourhood region
    min_threshold: int, optional
        minimal threshold level (default: ``numpy.min(image)``)
    max_threshold: int, optional
        maximal threshold level (default: ``numpy.max(image)``)
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.threshold_adaptive.Output

    Raises
    ------
    ValueError
        when `kernel_size` is not an odd number or when `method` is not valid

    Note
    ----
    Typically requires prior filtering to reduce noise in the image.

    References
    ----------
    .. [1] Niblack, W. 1986: An introduction to Digital Image Processing, Prentice-Hall.
    '''
    if kernel_size % 2 == 0:
        raise ValueError('Argument "kernel_size" must be an odd integer.')
    logger.debug('set kernel size: %d', kernel_size)

    if max_threshold is None:
        max_threshold = np.max(image)
    logger.debug('set maximal threshold: %d', max_threshold)

    if min_threshold is None:
        min_threshold = np.min(image)
    logger.debug('set minimal threshold: %d', min_threshold)

    logger.debug('map image intensities to 8-bit range')
    image_8bit = rescale_to_8bit(image, upper=99.99)

    logger.info('threshold image')
    if method == 'crosscorr':
        thresh_image = cv2.adaptiveThreshold(
            image_8bit,
            maxValue=255,
            adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
            thresholdType=cv2.THRESH_BINARY,
            blockSize=kernel_size,
            C=int(constant))
    elif method == 'niblack':
        thresh_image = cv2.ximgproc.niBlackThreshold(image_8bit,
                                                     maxValue=255,
                                                     type=cv2.THRESH_BINARY,
                                                     blockSize=kernel_size,
                                                     delta=constant)
    else:
        raise ValueError('Arugment "method" can be one of the following:\n'
                         '"crosscorr" or "niblack"')
    # OpenCV treats masks as unsigned integer and not as boolean
    thresh_image = thresh_image > 0

    # Manually fine tune automatic thresholding result
    thresh_image[image < min_threshold] = False
    thresh_image[image > max_threshold] = True

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        outlines = mh.morph.dilate(mh.labeled.bwperim(thresh_image))
        plots = [
            plotting.create_intensity_overlay_image_plot(
                image, outlines, 'ul'),
            plotting.create_mask_image_plot(thresh_image, 'ur')
        ]
        figure = plotting.create_figure(
            plots,
            title='thresholded adaptively with kernel size: %d' % kernel_size)
    else:
        figure = str()

    return Output(thresh_image, figure)
Exemplo n.º 6
0
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)
Exemplo n.º 7
0
def main(image, method, kernel_size, constant=0,
        min_threshold=None, max_threshold=None, plot=False):
    '''Thresholds an image with a locally adaptive threshold method.

    Parameters
    ----------
    image: numpy.ndarray
        grayscale image that should be thresholded
    method: str
        thresholding method (options: ``{"crosscorr", "niblack"}``)
    kernel_size: int
        size of the neighbourhood region that's used to calculate the threshold
        value at each pixel position (must be an odd number)
    constant: Union[float, int], optional
        depends on `method`; in case of ``"crosscorr"`` method the constant
        is subtracted from the computed weighted sum per neighbourhood region
        and in case of ``"niblack"`` the constant is multiplied by the
        standard deviation and this term is then subtracted from the mean
        computed per neighbourhood region
    min_threshold: int, optional
        minimal threshold level (default: ``numpy.min(image)``)
    max_threshold: int, optional
        maximal threshold level (default: ``numpy.max(image)``)
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.threshold_adaptive.Output

    Raises
    ------
    ValueError
        when `kernel_size` is not an odd number or when `method` is not valid

    Note
    ----
    Typically requires prior filtering to reduce noise in the image.

    References
    ----------
    .. [1] Niblack, W. 1986: An introduction to Digital Image Processing, Prentice-Hall.
    '''
    if kernel_size % 2 == 0:
        raise ValueError('Argument "kernel_size" must be an odd integer.')
    logger.debug('set kernel size: %d', kernel_size)

    if max_threshold is None:
        max_threshold = np.max(image)
    logger.debug('set maximal threshold: %d', max_threshold)

    if min_threshold is None:
        min_threshold = np.min(image)
    logger.debug('set minimal threshold: %d', min_threshold)

    logger.debug('map image intensities to 8-bit range')
    image_8bit = rescale_to_8bit(image, upper=99.99)

    logger.info('threshold image')
    if method == 'crosscorr':
        thresh_image = cv2.adaptiveThreshold(
            image_8bit, maxValue=255,
            adaptiveMethod=cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
            thresholdType=cv2.THRESH_BINARY,
            blockSize=kernel_size, C=int(constant)
        )
    elif method == 'niblack':
        thresh_image = cv2.ximgproc.niBlackThreshold(
            image_8bit, maxValue=255, type=cv2.THRESH_BINARY,
            blockSize=kernel_size, delta=constant
        )
    else:
        raise ValueError(
            'Arugment "method" can be one of the following:\n'
            '"crosscorr" or "niblack"'
        )
    # OpenCV treats masks as unsigned integer and not as boolean
    thresh_image = thresh_image > 0

    # Manually fine tune automatic thresholding result
    thresh_image[image < min_threshold] = False
    thresh_image[image > max_threshold] = True

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        outlines = mh.morph.dilate(mh.labeled.bwperim(thresh_image))
        plots = [
            plotting.create_intensity_overlay_image_plot(
                image, outlines, 'ul'
            ),
            plotting.create_mask_image_plot(thresh_image, 'ur')
        ]
        figure = plotting.create_figure(
            plots,
            title='thresholded adaptively with kernel size: %d' % kernel_size
        )
    else:
        figure = str()

    return Output(thresh_image, figure)
Exemplo n.º 8
0
def main(primary_label_image, intensity_image, contrast_threshold,
        min_threshold=None, max_threshold=None, plot=False):
    '''Detects secondary objects in an image by expanding the primary objects
    encoded in `primary_label_image`. The outlines of secondary objects are
    determined based on the watershed transform of `intensity_image` using the
    primary objects in `primary_label_image` as seeds.

    Parameters
    ----------
    primary_label_image: numpy.ndarray[numpy.int32]
        2D labeled array encoding primary objects, which serve as seeds for
        watershed transform
    intensity_image: numpy.ndarray[numpy.uint8 or numpy.uint16]
        2D grayscale array that serves as gradient for watershed transform;
        optimally this image is enhanced with a low-pass filter
    contrast_threshold: int
        contrast threshold for automatic separation of forground from background
        based on locally adaptive thresholding (when ``0`` threshold defaults
        to `min_threshold` manual thresholding)
    min_threshold: int, optional
        minimal foreground value; pixels below `min_threshold` are considered
        background
    max_threshold: int, optional
        maximal foreground value; pixels above `max_threshold` are considered
        foreground
    plot: bool, optional
        whether a plot should be generated

    Returns
    -------
    jtmodules.segment_secondary.Output

    Note
    ----
    Setting `min_threshold` and `max_threshold` to the same value reduces
    to manual thresholding.
    '''
    if np.any(primary_label_image == 0):
        has_background = True
    else:
        has_background = False

    if not has_background:
        secondary_label_image = primary_label_image
    else:
        # A simple, fixed threshold doesn't work for SE stains. Therefore, we
        # use adaptive thresholding to determine background regions,
        # i.e. regions in the intensity_image that should not be covered by
        # secondary objects.
        n_objects = len(np.unique(primary_label_image[1:]))
        logger.info(
            'primary label image has %d objects',
            n_objects - 1
        )
        # SB: Added a catch for images with no primary objects
        # note that background is an 'object'
        if n_objects > 1:
            # TODO: consider using contrast_treshold as input parameter
            background_mask = mh.thresholding.bernsen(
                intensity_image, 5, contrast_threshold
            )
            if min_threshold is not None:
                logger.info(
                    'set lower threshold level to %d', min_threshold
                )
                background_mask[intensity_image < min_threshold] = True

            if max_threshold is not None:
                logger.info(
                    'set upper threshold level to %d', max_threshold
                )
                background_mask[intensity_image > max_threshold] = False
            # background_mask = mh.morph.open(background_mask)
            background_label_image = mh.label(background_mask)[0]
            background_label_image[background_mask] += n_objects

            logger.info('detect secondary objects via watershed transform')
            secondary_label_image = expand_objects_watershed(
                primary_label_image, background_label_image, intensity_image
            )
        else:
            logger.info('skipping secondary segmentation')
            secondary_label_image = np.zeros(
                primary_label_image.shape, dtype=np.int32
            )

    n_objects = len(np.unique(secondary_label_image)[1:])
    logger.info('identified %d objects', n_objects)

    if plot:
        from jtlib import plotting
        colorscale = plotting.create_colorscale(
            'Spectral', n=n_objects, permute=True, add_background=True
        )
        outlines = mh.morph.dilate(mh.labeled.bwperim(secondary_label_image > 0))
        plots = [
            plotting.create_mask_image_plot(
                primary_label_image, 'ul', colorscale=colorscale
                ),
            plotting.create_mask_image_plot(
                secondary_label_image, 'ur', colorscale=colorscale
            ),
            plotting.create_intensity_overlay_image_plot(
                intensity_image, outlines, 'll'
            )
        ]
        figure = plotting.create_figure(plots, title='secondary objects')
    else:
        figure = str()

    return Output(secondary_label_image, figure)
Exemplo n.º 9
0
def main(primary_label_image, intensity_image, contrast_threshold,
        min_threshold=None, max_threshold=None, plot=False):
    '''Detects secondary objects in an image by expanding the primary objects
    encoded in `primary_label_image`. The outlines of secondary objects are
    determined based on the watershed transform of `intensity_image` using the
    primary objects in `primary_label_image` as seeds.

    Parameters
    ----------
    primary_label_image: numpy.ndarray[numpy.int32]
        2D labeled array encoding primary objects, which serve as seeds for
        watershed transform
    intensity_image: numpy.ndarray[numpy.uint8 or numpy.uint16]
        2D grayscale array that serves as gradient for watershed transform;
        optimally this image is enhanced with a low-pass filter
    contrast_threshold: int
        contrast threshold for automatic separation of forground from background
        based on locally adaptive thresholding (when ``0`` threshold defaults
        to `min_threshold` manual thresholding)
    min_threshold: int, optional
        minimal foreground value; pixels below `min_threshold` are considered
        background
    max_threshold: int, optional
        maximal foreground value; pixels above `max_threshold` are considered
        foreground
    plot: bool, optional
        whether a plot should be generated

    Returns
    -------
    jtmodules.segment_secondary.Output

    Note
    ----
    Setting `min_threshold` and `max_threshold` to the same value reduces
    to manual thresholding.
    '''
    if np.any(primary_label_image == 0):
        has_background = True
    else:
        has_background = False

    if not has_background:
        secondary_label_image = primary_label_image
    else:
        # A simple, fixed threshold doesn't work for SE stains. Therefore, we
        # use adaptive thresholding to determine background regions,
        # i.e. regions in the intensity_image that should not be covered by
        # secondary objects.
        n_objects = len(np.unique(primary_label_image[1:]))
        logger.info(
            'primary label image has %d objects',
            n_objects - 1
        )
        # SB: Added a catch for images with no primary objects
        # note that background is an 'object'
        if n_objects > 1:
            # TODO: consider using contrast_treshold as input parameter
            background_mask = mh.thresholding.bernsen(
                intensity_image, 5, contrast_threshold
            )
            if min_threshold is not None:
                logger.info(
                    'set lower threshold level to %d', min_threshold
                )
                background_mask[intensity_image < min_threshold] = True

            if max_threshold is not None:
                logger.info(
                    'set upper threshold level to %d', max_threshold
                )
                background_mask[intensity_image > max_threshold] = False
            # background_mask = mh.morph.open(background_mask)
            background_label_image = mh.label(background_mask)[0]
            background_label_image[background_mask] += n_objects

            logger.info('detect secondary objects via watershed transform')
            secondary_label_image = expand_objects_watershed(
                primary_label_image, background_label_image, intensity_image
            )
        else:
            logger.info('skipping secondary segmentation')
            secondary_label_image = np.zeros(
                primary_label_image.shape, dtype=np.int32
            )

    n_objects = len(np.unique(secondary_label_image)[1:])
    logger.info('identified %d objects', n_objects)

    if plot:
        from jtlib import plotting
        colorscale = plotting.create_colorscale(
            'Spectral', n=n_objects, permute=True, add_background=True
        )
        outlines = mh.morph.dilate(mh.labeled.bwperim(secondary_label_image > 0))
        plots = [
            plotting.create_mask_image_plot(
                primary_label_image, 'ul', colorscale=colorscale
                ),
            plotting.create_mask_image_plot(
                secondary_label_image, 'ur', colorscale=colorscale
            ),
            plotting.create_intensity_overlay_image_plot(
                intensity_image, outlines, 'll'
            )
        ]
        figure = plotting.create_figure(plots, title='secondary objects')
    else:
        figure = str()

    return Output(secondary_label_image, figure)
Exemplo n.º 10
0
def main(image,
         correction_factor=1,
         min_threshold=None,
         max_threshold=None,
         plot=False):
    '''Thresholds an image by applying an automatically determined global
    threshold level using
    `Otsu's method <https://en.wikipedia.org/wiki/Otsu%27s_method>`_.

    Additional parameters allow correction of the calculated threshold
    level or restricting it to a defined range. This may be useful to prevent
    extreme levels in case the `image` contains artifacts. Setting
    `min_threshold` and `max_threshold` to the same value results in a
    manual thresholding.

    Parameters
    ----------
    image: numpy.ndarray[numpy.uint8 or numpy.unit16]
        grayscale image that should be thresholded
    correction_factor: int, optional
        value by which the calculated threshold level will be multiplied
        (default: ``1``)
    min_threshold: int, optional
        minimal threshold level (default: ``numpy.min(image)``)
    max_threshold: int, optional
        maximal threshold level (default: ``numpy.max(image)``)
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.threshold_otsu.Output[Union[numpy.ndarray, str]]
    '''
    if max_threshold is None:
        max_threshold = np.max(image)
    logger.debug('set maximal threshold: %d', max_threshold)

    if min_threshold is None:
        min_threshold = np.min(image)
    logger.debug('set minimal threshold: %d', min_threshold)
    logger.debug('set threshold correction factor: %.2f', correction_factor)

    threshold = mh.otsu(image)
    logger.info('calculated threshold level: %d', threshold)

    corr_threshold = threshold * correction_factor
    logger.info('corrected threshold level: %d', corr_threshold)

    if corr_threshold > max_threshold:
        logger.info('set threshold level to maximum: %d', max_threshold)
        corr_threshold = max_threshold
    elif corr_threshold < min_threshold:
        logger.info('set threshold level to minimum: %d', min_threshold)
        corr_threshold = min_threshold

    logger.info('threshold image at %d', corr_threshold)
    mask = image > corr_threshold

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        outlines = mh.morph.dilate(mh.labeled.bwperim(mask))
        plots = [
            plotting.create_intensity_overlay_image_plot(
                image, outlines, 'ul'),
            plotting.create_mask_image_plot(mask, 'ur')
        ]
        figure = plotting.create_figure(plots,
                                        title='thresholded at %s' %
                                        corr_threshold)
    else:
        figure = str()

    return Output(mask, figure)
Exemplo n.º 11
0
def main(image, correction_factor=1, min_threshold=None, max_threshold=None,
        plot=False):
    '''Thresholds an image by applying an automatically determined global
    threshold level using
    `Otsu's method <https://en.wikipedia.org/wiki/Otsu%27s_method>`_.

    Additional parameters allow correction of the calculated threshold
    level or restricting it to a defined range. This may be useful to prevent
    extreme levels in case the `image` contains artifacts. Setting
    `min_threshold` and `max_threshold` to the same value results in a
    manual thresholding.

    Parameters
    ----------
    image: numpy.ndarray[numpy.uint8 or numpy.unit16]
        grayscale image that should be thresholded
    correction_factor: int, optional
        value by which the calculated threshold level will be multiplied
        (default: ``1``)
    min_threshold: int, optional
        minimal threshold level (default: ``numpy.min(image)``)
    max_threshold: int, optional
        maximal threshold level (default: ``numpy.max(image)``)
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.threshold_otsu.Output[Union[numpy.ndarray, str]]
    '''
    if max_threshold is None:
        max_threshold = np.max(image)
    logger.debug('set maximal threshold: %d', max_threshold)

    if min_threshold is None:
        min_threshold = np.min(image)
    logger.debug('set minimal threshold: %d', min_threshold)
    logger.debug('set threshold correction factor: %.2f', correction_factor)

    threshold = mh.otsu(image)
    logger.info('calculated threshold level: %d', threshold)

    corr_threshold = threshold * correction_factor
    logger.info('corrected threshold level: %d', corr_threshold)

    if corr_threshold > max_threshold:
        logger.info('set threshold level to maximum: %d', max_threshold)
        corr_threshold = max_threshold
    elif corr_threshold < min_threshold:
        logger.info('set threshold level to minimum: %d', min_threshold)
        corr_threshold = min_threshold

    logger.info('threshold image at %d', corr_threshold)
    mask = image > corr_threshold

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        outlines = mh.morph.dilate(mh.labeled.bwperim(mask))
        plots = [
            plotting.create_intensity_overlay_image_plot(
                image, outlines, 'ul'
            ),
            plotting.create_mask_image_plot(mask, 'ur')
        ]
        figure = plotting.create_figure(
            plots, title='thresholded at %s' % corr_threshold
        )
    else:
        figure = str()

    return Output(mask, figure)