Mercurial > repos > imgteam > spot_detection_2d
diff spot_detection_2d.py @ 5:e8c9e104e109 draft default tip
planemo upload for repository https://github.com/BMCV/galaxy-image-analysis/tree/master/tools/spot_detection_2d/ commit f1b9207ec23c0af1681c929281bcbf1d0638368e
author | imgteam |
---|---|
date | Wed, 25 Sep 2024 08:19:30 +0000 |
parents | 4645b356bf3b |
children |
line wrap: on
line diff
--- a/spot_detection_2d.py Mon Nov 13 22:12:30 2023 +0000 +++ b/spot_detection_2d.py Wed Sep 25 08:19:30 2024 +0000 @@ -1,88 +1,128 @@ """ Copyright 2021-2022 Biomedical Computer Vision Group, Heidelberg University. -Author: Qi Gao (qi.gao@bioquant.uni-heidelberg.de) +Authors: +- Qi Gao (qi.gao@bioquant.uni-heidelberg.de) +- Leonid Kostrykin (leonid.kostrykin@bioquant.uni-heidelberg.de) Distributed under the MIT license. See file LICENSE for detail or copy at https://opensource.org/licenses/MIT - """ import argparse -import imageio +import giatools.io import numpy as np import pandas as pd -from skimage.feature import peak_local_max -from skimage.filters import gaussian, laplace +import scipy.ndimage as ndi +from numpy.typing import NDArray +from skimage.feature import blob_dog, blob_doh, blob_log + +blob_filters = { + 'dog': blob_dog, + 'doh': blob_doh, + 'log': blob_log, +} -def getbr(xy, img, nb, firstn): - ndata = xy.shape[0] - br = np.empty((ndata, 1)) - for j in range(ndata): - br[j] = np.NaN - if not np.isnan(xy[j, 0]): - timg = img[xy[j, 1] - nb - 1:xy[j, 1] + nb, xy[j, 0] - nb - 1:xy[j, 0] + nb] - br[j] = np.mean(np.sort(timg, axis=None)[-firstn:]) - return br +def mean_intensity(img: NDArray, y: int, x: int, radius: int) -> float: + assert img.ndim == 2 + assert radius >= 0 + if radius == 0: + return float(img[y, x]) + else: + mask = np.ones(img.shape, bool) + mask[y, x] = False + mask = (ndi.distance_transform_edt(mask) <= radius) + return img[mask].mean() -def spot_detection(fn_in, fn_out, frame_1st=1, frame_end=0, - typ_filter='Gauss', ssig=1, th=10, - typ_br='smoothed', bd=10): - ims_ori = imageio.mimread(fn_in, format='TIFF') - ims_smd = np.zeros((len(ims_ori), ims_ori[0].shape[0], ims_ori[0].shape[1]), dtype='float64') - if frame_end == 0 or frame_end > len(ims_ori): - frame_end = len(ims_ori) +def spot_detection( + fn_in: str, + fn_out: str, + frame_1st: int, + frame_end: int, + filter_type: str, + min_scale: float, + max_scale: float, + abs_threshold: float, + rel_threshold: float, + boundary: int, +) -> None: - for i in range(frame_1st - 1, frame_end): - ims_smd[i, :, :] = gaussian(ims_ori[i].astype('float64'), sigma=ssig) - ims_smd_max = np.max(ims_smd) + # Load the single-channel 2-D input image (or stack thereof) + stack = giatools.io.imread(fn_in) - txyb_all = np.array([]).reshape(0, 4) - for i in range(frame_1st - 1, frame_end): - tmp = np.copy(ims_smd[i, :, :]) - if typ_filter == 'LoG': - tmp = laplace(tmp) + # Normalize input image so that it is a stack of images (possibly a stack of a single image) + assert stack.ndim in (2, 3) + if stack.ndim == 2: + stack = stack.reshape(1, *stack.shape) + + # Slice the stack + assert frame_1st >= 1 + assert frame_end >= 0 + stack = stack[frame_1st - 1:] + if frame_end > 0: + stack = stack[:-frame_end] - tmp[tmp < th * ims_smd_max / 100] = 0 - coords = peak_local_max(tmp, min_distance=1) - idx_to_del = np.where((coords[:, 0] <= bd) | (coords[:, 0] >= tmp.shape[0] - bd) | - (coords[:, 1] <= bd) | (coords[:, 1] >= tmp.shape[1] - bd)) - coords = np.delete(coords, idx_to_del[0], axis=0) - xys = coords[:, ::-1] + # Select the blob detection filter + assert filter_type.lower() in blob_filters.keys() + blob_filter = blob_filters[filter_type.lower()] + + # Perform blob detection on each image of the stack + detections = list() + for img_idx, img in enumerate(stack): + blobs = blob_filter(img, threshold=abs_threshold, threshold_rel=rel_threshold, min_sigma=min_scale, max_sigma=max_scale) + for blob in blobs: + y, x, scale = blob + + # Skip the detection if it is too close to the boundary of the image + if y < boundary or x < boundary or y >= img.shape[0] - boundary or x >= img.shape[1] - boundary: + continue - if typ_br == 'smoothed': - intens = getbr(xys, ims_smd[i, :, :], 0, 1) - elif typ_br == 'robust': - intens = getbr(xys, ims_ori[i], 1, 4) - else: - intens = getbr(xys, ims_ori[i], 0, 1) + # Add the detection to the list of detections + radius = scale * np.sqrt(2) * 2 + intensity = mean_intensity(img, round(y), round(x), round(radius)) + detections.append( + { + 'frame': img_idx + 1, + 'pos_x': round(x), + 'pos_y': round(y), + 'scale': scale, + 'radius': radius, + 'intensity': intensity, + } + ) - txyb = np.concatenate(((i + 1) * np.ones((xys.shape[0], 1)), xys, intens), axis=1) - txyb_all = np.concatenate((txyb_all, txyb), axis=0) - - df = pd.DataFrame() - df['FRAME'] = txyb_all[:, 0].astype(int) - df['POS_X'] = txyb_all[:, 1].astype(int) - df['POS_Y'] = txyb_all[:, 2].astype(int) - df['INTENSITY'] = txyb_all[:, 3] + # Build and save dataframe + df = pd.DataFrame.from_dict(detections) df.to_csv(fn_out, index=False, float_format='%.2f', sep="\t") if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Spot detection") - parser.add_argument("fn_in", help="Name of input image sequence (stack)") - parser.add_argument("fn_out", help="Name of output file to save the coordinates and intensities of detected spots") - parser.add_argument("frame_1st", type=int, help="Index for the starting frame to detect spots (1 for first frame of the stack)") - parser.add_argument("frame_end", type=int, help="Index for the last frame to detect spots (0 for the last frame of the stack)") - parser.add_argument("filter", help="Detection filter") - parser.add_argument("ssig", type=float, help="Sigma of the Gaussian for noise suppression") - parser.add_argument("thres", type=float, help="Percentage of the global maximal for thresholding candidate spots") - parser.add_argument("typ_intens", help="smoothed or robust (for measuring the intensities of spots)") - parser.add_argument("bndy", type=int, help="Number of pixels (Spots close to image boundaries will be ignored)") + + parser.add_argument("fn_in", help="Name of input image or image stack.") + parser.add_argument("fn_out", help="Name of output file to write the detections into.") + parser.add_argument("frame_1st", type=int, help="Index for the starting frame to detect spots (1 for first frame of the stack).") + parser.add_argument("frame_end", type=int, help="Index for the last frame to detect spots (0 for the last frame of the stack).") + parser.add_argument("filter_type", help="Detection filter") + parser.add_argument("min_scale", type=float, help="The minimum scale to consider for multi-scale detection.") + parser.add_argument("max_scale", type=float, help="The maximum scale to consider for multi-scale detection.") + parser.add_argument("abs_threshold", type=float, help=( + "Filter responses below this threshold will be ignored. Only filter responses above this thresholding will be considered as blobs. " + "This threshold is ignored if the relative threshold (below) corresponds to a higher response.") + ) + parser.add_argument("rel_threshold", type=float, help=( + "Same as the absolute threshold (above), but as a fraction of the overall maximal filter response of an image. " + "This threshold is ignored if it corresponds to a response below the absolute threshold.") + ) + parser.add_argument("boundary", type=int, help="Width of image boundaries (in pixel) where spots will be ignored.") + args = parser.parse_args() spot_detection(args.fn_in, args.fn_out, frame_1st=args.frame_1st, frame_end=args.frame_end, - typ_filter=args.filter, ssig=args.ssig, th=args.thres, - typ_br=args.typ_intens, bd=args.bndy) + filter_type=args.filter_type, + min_scale=args.min_scale, max_scale=args.max_scale, + abs_threshold=args.abs_threshold, rel_threshold=args.rel_threshold, + boundary=args.boundary)