Exemple #1
0
def main(image, clipping_mask, plot=False):
    '''Clips a labeled image using another image as a mask, such that
    intersecting pixels/voxels are set to background.

    Parameters
    ----------
    image: numpy.ndarray
        image that should be clipped
    clipping_mask: numpy.ndarray[numpy.int32 or numpy.bool]
        image that should be used as clipping mask
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.clip_objects.Output

    Raises
    ------
    ValueError
        when `image` and `clipping_mask` don't have the same dimensions
    '''
    if image.shape != clipping_mask.shape:
        raise ValueError(
            '"image" and "clipping_mask" must have the same dimensions')
    clipping_mask = clipping_mask > 0

    clipped_image = image.copy()
    clipped_image[clipping_mask] = 0

    if plot:
        from jtlib import plotting
        if str(image.dtype).startswith('uint'):
            plots = [
                plotting.create_intensity_image_plot(image, 'ul', clip=True),
                plotting.create_mask_image_plot(clipping_mask, 'ur'),
                plotting.create_intensity_image_plot(clipped_image,
                                                     'll',
                                                     clip=True)
            ]
        else:
            n_objects = len(np.unique(image)[1:])
            colorscale = plotting.create_colorscale('Spectral',
                                                    n=n_objects,
                                                    permute=True,
                                                    add_background=True)
            plots = [
                plotting.create_mask_image_plot(image,
                                                'ul',
                                                colorscale=colorscale),
                plotting.create_mask_image_plot(clipping_mask, 'ur'),
                plotting.create_mask_image_plot(clipped_image,
                                                'll',
                                                colorscale=colorscale)
            ]
        figure = plotting.create_figure(plots, title='clipped image')
    else:
        figure = str()

    return Output(clipped_image, figure)
Exemple #2
0
def main(image, plot=False):
    '''Inverts `image`.

    Parameters
    ----------
    image: numpy.ndarray[Union[numpy.uint8, numpy.uint16, numpy.bool, numpy.int32]]
        image that should be inverted
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

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

    Note
    ----
    In case `image` is a label image with type ``numpy.int32`` it is binarized
    (casted to ``numpy.bool``) before inversion.
    '''
    if image.dtype == np.int32:
        logger.info('binarize label image before inversion')
        image = image > 0
    logger.info('invert image')
    inverted_image = np.invert(image)

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        if str(image.dtype).startswith('uint'):
            data = [
                plotting.create_intensity_image_plot(
                    image,
                    'ul',
                    clip=True,
                ),
                plotting.create_intensity_image_plot(
                    inverted_image,
                    'ur',
                    clip=True,
                ),
            ]
        else:
            data = [
                plotting.create_mask_image_plot(
                    image,
                    'ul',
                    clip=True,
                ),
                plotting.create_mask_image_plot(
                    inverted_image,
                    'ur',
                    clip=True,
                ),
            ]
        figure = plotting.create_figure(data,
                                        title='original and inverted image')
    else:
        figure = str()

    return Output(inverted_image, figure)
Exemple #3
0
def main(image, plot=False):
    '''Inverts `image`.

    Parameters
    ----------
    image: numpy.ndarray[Union[numpy.uint8, numpy.uint16, numpy.bool, numpy.int32]]
        image that should be inverted
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

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

    Note
    ----
    In case `image` is a label image with type ``numpy.int32`` it is binarized
    (casted to ``numpy.bool``) before inversion.
    '''
    if image.dtype == np.int32:
        logger.info('binarize label image before inversion')
        image = image > 0
    logger.info('invert image')
    inverted_image = np.invert(image)

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        if str(image.dtype).startswith('uint'):
            data = [
                plotting.create_intensity_image_plot(
                    image, 'ul', clip=True,
                ),
                plotting.create_intensity_image_plot(
                    inverted_image, 'ur', clip=True,
                ),
            ]
        else:
            data = [
                plotting.create_mask_image_plot(
                    image, 'ul', clip=True,
                ),
                plotting.create_mask_image_plot(
                    inverted_image, 'ur', clip=True,
                ),
            ]
        figure = plotting.create_figure(
            data, title='original and inverted image'
        )
    else:
        figure = str()

    return Output(inverted_image, figure)
Exemple #4
0
def main(intensity_image, min_value=None, max_value=None, plot=False):
    '''Rescales an image between `min_value` and `max_value`.

    Parameters
    ----------
    intensity_image: numpy.ndarray[Union[numpy.uint8, numpy.uint16]]
        grayscale image
    min: int, optional
        grayscale value to be set as zero in rescaled image (default:
        ``False``)
    max: int, optional
        grayscale value to be set as max in rescaled image (default:
        ``False``)
    plot: bool, optional
        whether a figure should be created (default: ``False``)
    '''

    rescaled_image = np.zeros(shape=intensity_image.shape, dtype=np.int32)

    if min_value is not None:
        logger.info('subtract min_value %s', min_value)
        rescaled_image = intensity_image.astype(np.int32) - min_value
        rescaled_image[rescaled_image < 0] = 0
    else:
        rescaled_image = intensity_image

    if max_value is not None:
        logger.info('set max_value %s', max_value)

        max_for_type = np.iinfo(intensity_image.dtype).max
        rescaled_image = rescaled_image.astype(
            np.float32) / max_value * max_for_type
        rescaled_image[rescaled_image > max_for_type] = max_for_type

    rescaled_image = rescaled_image.astype(intensity_image.dtype)

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        plots = [
            plotting.create_intensity_image_plot(intensity_image,
                                                 'ul',
                                                 clip=True),
            plotting.create_intensity_image_plot(rescaled_image,
                                                 'ur',
                                                 clip=True)
        ]
        figure = plotting.create_figure(plots, title='rescaled image')
    else:
        figure = str()

    return Output(rescaled_image, figure)
Exemple #5
0
def main(image, method='max', plot=False):
    '''Projects an image along the last dimension using the given `method`.

    Parameters
    ----------
    image: numpy.ndarray[Union[numpy.uint8, numpy.uint16]]
        grayscale image
    method: str, optional
        method used for projection
        (default: ``"max"``, options: ``{"max", "sum"}``)
    plot: bool, optional
        whether a figure should be created (default: ``False``)
    '''
    logger.info('project image using "%s" method', method)
    func = projections[method]
    projected_image = func(image, axis=-1)

    projected_image = projected_image.astype(image.dtype)

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        plots = [
	    plotting.create_intensity_image_plot(
                projected_image, 'ul', clip=True
            )
	]
        figure = plotting.create_figure(plots, title='projection image')
    else:
        figure = str()

    return Output(projected_image, figure)
Exemple #6
0
def main(image, mask, plot=False, plot_type='objects'):

    mask_image = np.copy(image)
    mask_image[mask == 0] = 0

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        if plot_type == 'objects':
            colorscale = plotting.create_colorscale('Spectral',
                                                    n=image.max(),
                                                    permute=True,
                                                    add_background=True)
            data = [
                plotting.create_mask_image_plot(mask,
                                                'ul',
                                                colorscale=colorscale),
                plotting.create_mask_image_plot(mask_image,
                                                'ur',
                                                colorscale=colorscale)
            ]
            figure = plotting.create_figure(data, title='Masked label image')
        elif plot_type == 'intensity':
            data = [
                plotting.create_mask_image_plot(mask, 'ul'),
                plotting.create_intensity_image_plot(mask_image, 'ur')
            ]
            figure = plotting.create_figure(data,
                                            title='Masked intensity image')
    else:
        figure = str()

    return Output(mask_image, figure)
Exemple #7
0
def main(image, mask, plot=False, plot_type='objects'):

    mask_image = np.copy(image)
    mask_image[mask == 0] = 0

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        if plot_type == 'objects':
            colorscale = plotting.create_colorscale(
                'Spectral', n=image.max(), permute=True, add_background=True
            )
            data = [
                plotting.create_mask_image_plot(
                    mask, 'ul', colorscale=colorscale
                ),
                plotting.create_mask_image_plot(
                    mask_image, 'ur', colorscale=colorscale
                )
            ]
            figure = plotting.create_figure(
                data,
                title='Masked label image'
            )
        elif plot_type == 'intensity':
            data = [
                plotting.create_mask_image_plot(
                    mask, 'ul'
                ),
                plotting.create_intensity_image_plot(
                    mask_image, 'ur'
                )
            ]
            figure = plotting.create_figure(
                data,
                title='Masked intensity image'
            )
    else:
        figure = str()

    return Output(mask_image, figure)
Exemple #8
0
def main(image, filter_name, filter_size, plot=False):
    '''Smoothes (blurs) `image`.

    Parameters
    ----------
    image: numpy.ndarray
        grayscale image that should be smoothed
    filter_name: str
        name of the filter kernel that should be applied
        (options: ``{"avarage", "gaussian", "median", "bilateral"}``)
    filter_size: int
        size of the kernel
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

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

    Raises
    ------
    ValueError
        when `filter_name` is not
        ``"avarage"``, ``"gaussian"``, ``"median"`` or ``"bilateral"``
    '''
    se = np.ones((filter_size, filter_size))
    if filter_name == 'average':
        logger.info('apply "average" filter')
        smoothed_image = mh.mean_filter(image, se)
    elif filter_name == 'gaussian':
        logger.info('apply "gaussian" filter')
        smoothed_image = mh.gaussian_filter(image, filter_size)
    elif filter_name == 'median':
        logger.info('apply "median" filter')
        smoothed_image = mh.median_filter(image, se)
    elif filter_name == 'bilateral':
        smoothed_image = cv2.bilateralFilter(
            image.astype(np.float32), d=0,
            sigmaColor=filter_size, sigmaSpace=filter_size
        ).astype(image.dtype)
    else:
        raise ValueError(
            'Arugment "filter_name" can be one of the following:\n'
            '"average", "gaussian", "median" or "bilateral"'
        )
    smoothed_image = smoothed_image.astype(image.dtype)

    if plot:
        logger.info('create plot')
        from jtlib import plotting
        clip_value = np.percentile(image, 99.99)
        data = [
            plotting.create_intensity_image_plot(
                image, 'ul', clip=True, clip_value=clip_value
            ),
            plotting.create_intensity_image_plot(
                smoothed_image, 'ur', clip=True, clip_value=clip_value
            ),
        ]
        figure = plotting.create_figure(
            data,
            title='Smoothed with "{0}" filter (kernel size: {1})'.format(
                filter_name, filter_size
            )
        )
    else:
        figure = str()

    return Output(smoothed_image, 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 #10
0
def main(image, output_type='16-bit', plot=False):
    '''Converts an arbitrary Image to an IntensityImage

    Parameters
    ----------
    image: numpy.ndarray
        image to be converted
    output_type: numpy.ndarray
        output data type
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.convert_to_intensity.Output
    '''
    if output_type == '8-bit':
        bit_depth = np.uint8
        max_value = pow(2, 8)
    elif output_type == '16-bit':
        bit_depth = np.uint16
        max_value = pow(2, 16)
    else:
        logger.warn('unrecognised requested output data-type %s, using 16-bit', output_type)
        bit_depth = np.uint16
        max_value = pow(2, 16)

    if image.dtype == np.int32:
        logger.info('Converting label image to intensity image')
        if (np.amax(image) < max_value):
            intensity_image = image.astype(dtype=bit_depth)
        else:
            logger.warn(
                '%d objects in input label image exceeds maximum (%d)',
                np.amax(image),
                max_value
            )
            intensity_image = image
    else:
        logger.info('Converting non-label image to intensity image')
        intensity_image = image.astype(dtype=bit_depth)

    if plot:
        from jtlib import plotting
        n_objects = len(np.unique(image)[1:])
        colorscale = plotting.create_colorscale(
            'Spectral', n=n_objects, permute=True, add_background=True
        )
        plots = [
            plotting.create_mask_image_plot(
                image, 'ul', colorscale=colorscale
            ),
            plotting.create_intensity_image_plot(
                intensity_image, 'ur'
            )
        ]
        figure = plotting.create_figure(plots, title='convert_to_intensity_image')
    else:
        figure = str()

    return Output(intensity_image, figure)
def main(image_1, image_2, weight_1, weight_2, plot=False):
    '''Combines `image_1` with `image_2`.

    Parameters
    ----------
    input_mask_1: numpy.ndarray[numpy.uint8 or numpy.uint16]
        2D unsigned integer array
    input_mask_2: numpy.ndarray[numpy.uint8 or numpy.uint16]
        2D unsigned integer array
    weight_1: int
        weight for `image_1`
    weight_2: int
        weight for `image_2`

    Returns
    -------
    jtmodules.combine_channels.Output

    Raises
    ------
    ValueError
        when `weight_1` or `weight_2` are not positive integers
    ValueError
        when `image_1` and `image_2` don't have the same dimensions
        and data type and if they don't have unsigned integer type
    '''
    if not isinstance(weight_1, int):
        raise TypeError('Weight #1 must have integer type.')
    if not isinstance(weight_2, int):
        raise TypeError('Weight #2 must have integer type.')
    if weight_1 < 1:
        raise ValueError('Weight #1 must be a positive integer.')
    if weight_2 < 1:
        raise ValueError('Weight #2 must be a positive integer.')
    logger.info('weight for first image: %d', weight_1)
    logger.info('weight for second image: %d', weight_2)

    if image_1.shape != image_2.shape:
        raise ValueError('The two images must have identical dimensions.')
    if image_1.dtype != image_2.dtype:
        raise ValueError('The two images must have identical data type.')

    if image_1.dtype == np.uint8:
        max_val = 2**8 - 1
    elif image_1.dtype == np.uint16:
        max_val = 2**16 - 1
    else:
        raise ValueError('The two images must have unsigned integer type.')

    logger.info('cast images to type float for arythmetics')
    img_1 = mh.stretch(image_1, 0, 1, float)
    img_2 = mh.stretch(image_2, 0, 1, float)
    logger.info('combine images using the provided weights')
    combined_image = img_1 * weight_1 + img_2 * weight_2
    logger.info('cast combined image back to correct data type')
    combined_image = mh.stretch(combined_image, 0, max_val, image_1.dtype)

    if plot:
        from jtlib import plotting
        plots = [
            plotting.create_intensity_image_plot(image_1, 'ul'),
            plotting.create_intensity_image_plot(image_2, 'ur'),
            plotting.create_intensity_image_plot(combined_image, 'll')
        ]
        figure = plotting.create_figure(plots, title='combined image')
    else:
        figure = str()

    return Output(combined_image, figure)
def main(image, mask, threshold=150, bead_size=2, superpixel_size=4,
         close_surface=False, close_disc_size=8, 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 (default: ``150``)
    bead_size: int, optional
        minimal size of bead (default: ``2``)
    superpixel_size: int, optional
        size of superpixels for searching the 3D position of a bead
    close_surface: bool, optional
        whether the interpolated surface should be morphologically closed
    close_disc_size: int, optional
        size in pixels of the disc used to morphologically close the
        interpolated surface
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

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

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

    logger.debug('mask beads inside cell')
    beads_outside_cell = np.copy(image)
    for iz in range(n_slices):
        beads_outside_cell[mask > 0, iz] = 0

    logger.debug('search for 3D position of beads outside cell')
    slide = np.argmax(beads_outside_cell, axis=2)
    slide[slide > np.percentile(slide[mask == 0], 20)] = 0

    logger.debug('determine surface of slide')
    slide_coordinates = array_to_coordinate_list(slide)
    bottom_surface = fit_plane(subsample_coordinate_list(
        slide_coordinates, 2000)
    )

    logger.debug('detect_beads in 2D')
    mip = np.max(image, axis=-1)
    try:
        # TODO: use LOG filter???
        beads, beads_centroids = detect_blobs(
            image=mip, mask=np.invert(mask > 0), threshold=threshold,
            min_area=bead_size
        )
    except:
        logger.warn('detect_blobs failed, returning empty volume image')
        volume_image = np.zeros(shape=mask.shape, dtype=image.dtype)
        figure = str()
        return Output(volume_image, figure)

    n_beads = np.count_nonzero(beads_centroids)
    logger.info('found %d beads on cells', n_beads)

    if n_beads == 0:
        logger.warn('empty volume image')
        volume_image = np.zeros(shape=mask.shape, dtype=image.dtype)
    else:
        logger.debug('locate beads in 3D')
        beads_coords_3D = locate_in_3D(
            image=image, mask=beads_centroids,
            bin_size=superpixel_size
        )

        logger.info('interpolate cell surface')
        volume_image = interpolate_surface(
            coords=beads_coords_3D,
            output_shape=np.shape(image[:, :, 1]),
            method='linear'
        )

        volume_image = volume_image.astype(image.dtype)

        if (close_surface is True):
            import mahotas as mh
            logger.info('morphological closing of cell surface')
            volume_image = mh.close(volume_image,
                                    Bc=mh.disk(close_disc_size))
        volume_image[mask == 0] = 0

    if plot:
        logger.debug('convert bottom surface plane to image for plotting')
        bottom_surface_image = np.zeros(slide.shape, dtype=np.uint8)
        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(
                mip, 'ul', clip=True
            ),
            plotting.create_intensity_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 #13
0
def main(image, clipping_mask, plot=False):
    '''Clips a labeled image using another image as a mask, such that
    intersecting pixels/voxels are set to background.

    Parameters
    ----------
    image: numpy.ndarray
        image that should be clipped
    clipping_mask: numpy.ndarray[numpy.int32 or numpy.bool]
        image that should be used as clipping mask
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

    Returns
    -------
    jtmodules.clip_objects.Output

    Raises
    ------
    ValueError
        when `image` and `clipping_mask` don't have the same dimensions
    '''
    if image.shape != clipping_mask.shape:
        raise ValueError(
            '"image" and "clipping_mask" must have the same dimensions'
        )
    clipping_mask = clipping_mask > 0

    clipped_image = image.copy()
    clipped_image[clipping_mask] = 0

    if plot:
        from jtlib import plotting
        if str(image.dtype).startswith('uint'):
            plots = [
                plotting.create_intensity_image_plot(
                    image, 'ul', clip=True
                ),
                plotting.create_mask_image_plot(
                    clipping_mask, 'ur'
                ),
                plotting.create_intensity_image_plot(
                    clipped_image, 'll', clip=True
                )
            ]
        else:
            n_objects = len(np.unique(image)[1:])
            colorscale = plotting.create_colorscale(
                'Spectral', n=n_objects, permute=True, add_background=True
            )
            plots = [
                plotting.create_mask_image_plot(
                    image, 'ul', colorscale=colorscale
                ),
                plotting.create_mask_image_plot(
                    clipping_mask, 'ur'
                ),
                plotting.create_mask_image_plot(
                    clipped_image, 'll', colorscale=colorscale
                )
            ]
        figure = plotting.create_figure(plots, title='clipped image')
    else:
        figure = str()

    return Output(clipped_image, figure)
def main(image,
         mask,
         threshold=150,
         bead_size=2,
         superpixel_size=4,
         close_surface=False,
         close_disc_size=8,
         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 (default: ``150``)
    bead_size: int, optional
        minimal size of bead (default: ``2``)
    superpixel_size: int, optional
        size of superpixels for searching the 3D position of a bead
    close_surface: bool, optional
        whether the interpolated surface should be morphologically closed
    close_disc_size: int, optional
        size in pixels of the disc used to morphologically close the
        interpolated surface
    plot: bool, optional
        whether a plot should be generated (default: ``False``)

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

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

    logger.debug('mask beads inside cell')
    beads_outside_cell = np.copy(image)
    for iz in range(n_slices):
        beads_outside_cell[mask > 0, iz] = 0

    logger.debug('search for 3D position of beads outside cell')
    slide = np.argmax(beads_outside_cell, axis=2)
    slide[slide > np.percentile(slide[mask == 0], 20)] = 0

    logger.debug('determine surface of slide')
    slide_coordinates = array_to_coordinate_list(slide)
    bottom_surface = fit_plane(
        subsample_coordinate_list(slide_coordinates, 2000))

    logger.debug('detect_beads in 2D')
    mip = np.max(image, axis=-1)
    try:
        # TODO: use LOG filter???
        beads, beads_centroids = detect_blobs(image=mip,
                                              mask=np.invert(mask > 0),
                                              threshold=threshold,
                                              min_area=bead_size)
    except:
        logger.warn('detect_blobs failed, returning empty volume image')
        volume_image = np.zeros(shape=mask.shape, dtype=image.dtype)
        figure = str()
        return Output(volume_image, figure)

    n_beads = np.count_nonzero(beads_centroids)
    logger.info('found %d beads on cells', n_beads)

    if n_beads == 0:
        logger.warn('empty volume image')
        volume_image = np.zeros(shape=mask.shape, dtype=image.dtype)
    else:
        logger.debug('locate beads in 3D')
        beads_coords_3D = locate_in_3D(image=image,
                                       mask=beads_centroids,
                                       bin_size=superpixel_size)

        logger.info('interpolate cell surface')
        volume_image = interpolate_surface(coords=beads_coords_3D,
                                           output_shape=np.shape(image[:, :,
                                                                       1]),
                                           method='linear')

        volume_image = volume_image.astype(image.dtype)

        if (close_surface is True):
            import mahotas as mh
            logger.info('morphological closing of cell surface')
            volume_image = mh.close(volume_image, Bc=mh.disk(close_disc_size))
        volume_image[mask == 0] = 0

    if plot:
        logger.debug('convert bottom surface plane to image for plotting')
        bottom_surface_image = np.zeros(slide.shape, dtype=np.uint8)
        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(mip, 'ul', clip=True),
            plotting.create_intensity_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)