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, 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)
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)
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)
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)
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)
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)
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)
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)
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)