def denoised(self, noise_level, noise_size, smoothing_size=None, threshold=None): image = self.noisy_image(noise_level) return bandpass(image, noise_size, smoothing_size, threshold)
def preprocess(raw_image, noise_size=None, smoothing_size=None, threshold=None): if noise_size is not None: image = bandpass(raw_image, noise_size, smoothing_size, threshold) # Coerce the image into integer type. Rescale to fill dynamic range. if np.issubdtype(raw_image.dtype, np.integer): dtype = raw_image.dtype else: dtype = np.uint8 scale_factor = scalefactor_to_gamut(image, dtype) image = scale_to_gamut(image, dtype, scale_factor) elif np.issubdtype(raw_image.dtype, np.integer): # Do nothing when image is already of integer type scale_factor = 1. image = raw_image else: # Coerce the image into uint8 type. Rescale to fill dynamic range. scale_factor = scalefactor_to_gamut(raw_image, np.uint8) image = scale_to_gamut(raw_image, np.uint8, scale_factor) try: frame_no = raw_image.frame_no except AttributeError: frame_no = None return Frame(image, frame_no, metadata=dict(scale_factor=scale_factor))
from __future__ import division import nose from numpy.testing.utils import assert_allclose from trackpy.preprocessing import (bandpass, legacy_bandpass, legacy_bandpass_fftw) from trackpy.artificial import gen_nonoverlapping_locations, draw_spots from trackpy.tests.common import StrictTestCase pos = gen_nonoverlapping_locations((512, 512), 200, 20) frame = draw_spots((512, 512), pos, 20, noise_level=100) margin = 11 bp_scipy = bandpass(frame, 3, 11)[margin:-margin, margin:-margin] def test_legacy_bandpass(): lbp_numpy = legacy_bandpass(frame, 3, 11)[margin:-margin, margin:-margin] assert_allclose(lbp_numpy, bp_scipy, atol=1.1) def test_legacy_bandpass_fftw(): try: import pyfftw except ImportError: raise nose.SkipTest("pyfftw not installed. Skipping.") lbp_fftw = legacy_bandpass_fftw(frame, 3, 11)[margin:-margin, margin:-margin] assert_allclose(lbp_fftw, bp_scipy, atol=1.1) if __name__ == '__main__': import nose nose.runmodule(argv=[__file__, '-vvs', '-x', '--pdb', '--pdb-failure'],
def setUp(self): pos = gen_nonoverlapping_locations((512, 512), 200, 20) self.frame = draw_spots((512, 512), pos, 20, noise_level=100) self.margin = 11 self.bp_scipy = bandpass(self.frame, 2, 11)[self.margin:-self.margin, self.margin:-self.margin]
def locate(raw_image, diameter, minmass=100., maxsize=None, separation=None, noise_size=1, smoothing_size=None, threshold=1, invert=False, percentile=64, topn=None, preprocess=True, max_iterations=10, filter_before=True, filter_after=True, characterize=True, engine='auto'): """Locate Gaussian-like blobs of a given approximate size. Preprocess the image by performing a band pass and a threshold. Locate all peaks of brightness, characterize the neighborhoods of the peaks and take only those with given total brightnesss ("mass"). Finally, refine the positions of each peak. Parameters ---------- image : image array (any dimensions) diameter : feature size in px minmass : minimum integrated brightness Default is 100, but a good value is often much higher. This is a crucial parameter for elminating spurrious features. maxsize : maximum radius-of-gyration of brightness, default None separation : feature separation, in pixels Default is the feature diameter + 1. noise_size : width of Gaussian blurring kernel, in pixels Default is 1. smoothing_size : size of boxcar smoothing, in pixels Default is the same is feature separation. threshold : Clip bandpass result below this value. Default 1; use 8 for 16-bit images. invert : Set to True if features are darker than background. False by default. percentile : Features must have a peak brighter than pixels in this percentile. This helps eliminate spurrious peaks. topn : Return only the N brightest features above minmass. If None (default), return all features above minmass. Returns ------- DataFrame([x, y, mass, size, ecc, signal]) where mass means total integrated brightness of the blob, size means the radius of gyration of its Gaussian-like profile, and ecc is its eccentricity (1 is circular). Other Parameters ---------------- preprocess : Set to False to turn out bandpass preprocessing. max_iterations : integer max number of loops to refine the center of mass, default 10 filter_before : boolean Use minmass (and maxsize, if set) to eliminate spurrious features based on their estimated mass and size before refining position. True by default for performance. filter_after : boolean Use final characterizations of mass and size to elminate spurrious features. True by default. characterize : boolean Compute "extras": eccentricity, signal, ep. True by default. engine : {'auto', 'python', 'numba'} See Also -------- batch : performs location on many images in batch Notes ----- This is an implementation of the Crocker-Grier centroid-finding algorithm. [1]_ References ---------- .. [1] Crocker, J.C., Grier, D.G. http://dx.doi.org/10.1006/jcis.1996.0217 """ # Validate parameters and set defaults. if not diameter & 1: raise ValueError("Feature diameter must be an odd number. Round up.") if not separation: separation = int(diameter) + 1 radius = int(diameter)//2 if smoothing_size is None: smoothing_size = diameter raw_image = np.squeeze(raw_image) shape = raw_image.shape # Check whether the image looks suspiciously like a color image. if 3 in shape or 4 in shape: dim = raw_image.ndim warnings.warn("I am interpreting the image as {0}-dimensional. " "If it is actually a {1}-dimensional color image, " "convert it to grayscale first.".format(dim, dim-1)) if preprocess: if invert: # It is tempting to do this in place, but if it is called multiple # times on the same image, chaos reigns. max_value = np.iinfo(raw_image.dtype).max raw_image = raw_image ^ max_value image = bandpass(raw_image, noise_size, smoothing_size, threshold) else: image = raw_image.copy() # Coerce the image into integer type. Rescale to fill dynamic range. if np.issubdtype(raw_image.dtype, np.integer): dtype = raw_image.dtype else: dtype = np.int8 image = scale_to_gamut(image, dtype) # Set up a DataFrame for the final results. if image.ndim < 4: coord_columns = ['x', 'y', 'z'][:image.ndim] else: coord_columns = map(lambda i: 'x' + str(i), range(image.ndim)) char_columns = ['mass'] if characterize: char_columns += ['size', 'ecc', 'signal'] columns = coord_columns + char_columns # The 'ep' column is joined on at the end, so we need this... if characterize: all_columns = columns + ['ep'] else: all_columns = columns # Find local maxima. coords = local_maxima(image, radius, separation, percentile) count_maxima = coords.shape[0] if count_maxima == 0: return DataFrame(columns=all_columns) # Proactively filter based on estimated mass/size before # refining positions. if filter_before: approx_mass = np.empty(count_maxima) # initialize to avoid appending for i in range(count_maxima): approx_mass[i] = estimate_mass(image, radius, coords[i]) condition = approx_mass > minmass if maxsize is not None: approx_size = np.empty(count_maxima) for i in range(count_maxima): approx_size[i] = estimate_size(image, radius, coords[i], approx_mass[i]) condition &= approx_size < maxsize coords = coords[condition] count_qualified = coords.shape[0] if count_qualified == 0: warnings.warn("No maxima survived mass- and size-based prefiltering.") return DataFrame(columns=all_columns) # Refine their locations and characterize mass, size, etc. refined_coords = refine(raw_image, image, radius, coords, max_iterations, engine, characterize) # Filter again, using final ("exact") mass -- and size, if set. MASS_COLUMN_INDEX = image.ndim SIZE_COLUMN_INDEX = image.ndim + 1 exact_mass = refined_coords[:, MASS_COLUMN_INDEX] if filter_after: condition = exact_mass > minmass if maxsize is not None: exact_size = refined_coords[:, SIZE_COLUMN_INDEX] condition &= exact_size < maxsize refined_coords = refined_coords[condition] exact_mass = exact_mass[condition] # used below by topn count_qualified = refined_coords.shape[0] if count_qualified == 0: warnings.warn("No maxima survived mass- and size-based filtering.") return DataFrame(columns=all_columns) if topn is not None and count_qualified > topn: if topn == 1: # special case for high performance and correct shape refined_coords = refined_coords[np.argmax(exact_mass)] refined_coords = refined_coords.reshape(1, -1) else: refined_coords = refined_coords[np.argsort(exact_mass)][-topn:] f = DataFrame(refined_coords, columns=columns) # Estimate the uncertainty in position using signal (measured in refine) # and noise (measured here below). if characterize: black_level, noise = uncertainty.measure_noise( raw_image, diameter, threshold) f['signal'] -= black_level ep = uncertainty.static_error(f, noise, diameter, noise_size) f = f.join(ep) # If this is a pims Frame object, it has a frame number. # Tag it on; this is helpful for parallelization. if hasattr(raw_image, 'frame_no') and raw_image.frame_no is not None: f['frame'] = raw_image.frame_no return f
def locate(raw_image, diameter, minmass=100., maxsize=None, separation=None, noise_size=1, smoothing_size=None, threshold=None, invert=False, percentile=64, topn=None, preprocess=True, max_iterations=10, filter_before=True, filter_after=True, characterize=True, engine='auto'): """Locate Gaussian-like blobs of a given approximate size. Preprocess the image by performing a band pass and a threshold. Locate all peaks of brightness, characterize the neighborhoods of the peaks and take only those with given total brightnesss ("mass"). Finally, refine the positions of each peak. Parameters ---------- image : image array (any dimensions) diameter : feature size in px minmass : minimum integrated brightness Default is 100, but a good value is often much higher. This is a crucial parameter for elminating spurrious features. maxsize : maximum radius-of-gyration of brightness, default None separation : feature separation, in pixels Default is the feature diameter + 1. noise_size : width of Gaussian blurring kernel, in pixels Default is 1. smoothing_size : size of boxcar smoothing, in pixels Default is the same is feature separation. threshold : Clip bandpass result below this value. Default None, passed through to bandpass. invert : Set to True if features are darker than background. False by default. percentile : Features must have a peak brighter than pixels in this percentile. This helps eliminate spurrious peaks. topn : Return only the N brightest features above minmass. If None (default), return all features above minmass. Returns ------- DataFrame([x, y, mass, size, ecc, signal]) where mass means total integrated brightness of the blob, size means the radius of gyration of its Gaussian-like profile, and ecc is its eccentricity (1 is circular). Other Parameters ---------------- preprocess : Set to False to turn out bandpass preprocessing. max_iterations : integer max number of loops to refine the center of mass, default 10 filter_before : boolean Use minmass (and maxsize, if set) to eliminate spurrious features based on their estimated mass and size before refining position. True by default for performance. filter_after : boolean Use final characterizations of mass and size to elminate spurrious features. True by default. characterize : boolean Compute "extras": eccentricity, signal, ep. True by default. engine : {'auto', 'python', 'numba'} See Also -------- batch : performs location on many images in batch Notes ----- This is an implementation of the Crocker-Grier centroid-finding algorithm. [1]_ References ---------- .. [1] Crocker, J.C., Grier, D.G. http://dx.doi.org/10.1006/jcis.1996.0217 """ # Validate parameters and set defaults. if not diameter & 1: raise ValueError("Feature diameter must be an odd number. Round up.") if not separation: separation = int(diameter) + 1 radius = int(diameter) // 2 if smoothing_size is None: smoothing_size = diameter raw_image = np.squeeze(raw_image) shape = raw_image.shape # Check whether the image looks suspiciously like a color image. if 3 in shape or 4 in shape: dim = raw_image.ndim warnings.warn("I am interpreting the image as {0}-dimensional. " "If it is actually a {1}-dimensional color image, " "convert it to grayscale first.".format(dim, dim - 1)) if preprocess: if invert: # It is tempting to do this in place, but if it is called multiple # times on the same image, chaos reigns. if np.issubdtype(raw_image.dtype, np.integer): max_value = np.iinfo(raw_image.dtype).max raw_image = raw_image ^ max_value else: # To avoid degrading performance, assume gamut is zero to one. # Have you ever encountered an image of unnormalized floats? raw_image = 1 - raw_image image = bandpass(raw_image, noise_size, smoothing_size, threshold) else: image = raw_image.copy() # Coerce the image into integer type. Rescale to fill dynamic range. if np.issubdtype(raw_image.dtype, np.integer): dtype = raw_image.dtype else: dtype = np.int8 image = scale_to_gamut(image, dtype) # Set up a DataFrame for the final results. if image.ndim < 4: coord_columns = ['x', 'y', 'z'][:image.ndim] else: coord_columns = map(lambda i: 'x' + str(i), range(image.ndim)) char_columns = ['mass'] if characterize: char_columns += ['size', 'ecc', 'signal'] columns = coord_columns + char_columns # The 'ep' column is joined on at the end, so we need this... if characterize: all_columns = columns + ['ep'] else: all_columns = columns # Find local maxima. coords = local_maxima(image, radius, separation, percentile) count_maxima = coords.shape[0] if count_maxima == 0: return DataFrame(columns=all_columns) # Proactively filter based on estimated mass/size before # refining positions. if filter_before: approx_mass = np.empty(count_maxima) # initialize to avoid appending for i in range(count_maxima): approx_mass[i] = estimate_mass(image, radius, coords[i]) condition = approx_mass > minmass if maxsize is not None: approx_size = np.empty(count_maxima) for i in range(count_maxima): approx_size[i] = estimate_size(image, radius, coords[i], approx_mass[i]) condition &= approx_size < maxsize coords = coords[condition] count_qualified = coords.shape[0] if count_qualified == 0: warnings.warn("No maxima survived mass- and size-based prefiltering.") return DataFrame(columns=all_columns) # Refine their locations and characterize mass, size, etc. refined_coords = refine(raw_image, image, radius, coords, max_iterations, engine, characterize) # Filter again, using final ("exact") mass -- and size, if set. MASS_COLUMN_INDEX = image.ndim SIZE_COLUMN_INDEX = image.ndim + 1 exact_mass = refined_coords[:, MASS_COLUMN_INDEX] if filter_after: condition = exact_mass > minmass if maxsize is not None: exact_size = refined_coords[:, SIZE_COLUMN_INDEX] condition &= exact_size < maxsize refined_coords = refined_coords[condition] exact_mass = exact_mass[condition] # used below by topn count_qualified = refined_coords.shape[0] if count_qualified == 0: warnings.warn("No maxima survived mass- and size-based filtering.") return DataFrame(columns=all_columns) if topn is not None and count_qualified > topn: if topn == 1: # special case for high performance and correct shape refined_coords = refined_coords[np.argmax(exact_mass)] refined_coords = refined_coords.reshape(1, -1) else: refined_coords = refined_coords[np.argsort(exact_mass)][-topn:] f = DataFrame(refined_coords, columns=columns) # Estimate the uncertainty in position using signal (measured in refine) # and noise (measured here below). if characterize: black_level, noise = uncertainty.measure_noise(raw_image, diameter, threshold) f['signal'] -= black_level ep = uncertainty.static_error(f, noise, diameter, noise_size) f = f.join(ep) # If this is a pims Frame object, it has a frame number. # Tag it on; this is helpful for parallelization. if hasattr(raw_image, 'frame_no') and raw_image.frame_no is not None: f['frame'] = raw_image.frame_no return f