def test_whole_pixel_shifts(self): self.check_skip() L = 21 dims = (L, L + 2) # avoid square images in tests pos = [7, 13] expected = np.array([pos]) image = np.ones(dims, dtype='uint8') draw_feature(image, pos, 15) guess = np.array([[6, 13]]) actual = tp.feature.refine(image, image, 6, guess, characterize=False, engine=self.engine)[:, :2][:, ::-1] assert_allclose(actual, expected, atol=0.1) guess = np.array([[7, 12]]) actual = tp.feature.refine(image, image, 6, guess, characterize=False, engine=self.engine)[:, :2][:, ::-1] assert_allclose(actual, expected, atol=0.1) guess = np.array([[7, 14]]) actual = tp.feature.refine(image, image, 6, guess, characterize=False, engine=self.engine)[:, :2][:, ::-1] assert_allclose(actual, expected, atol=0.1) guess = np.array([[6, 12]]) actual = tp.feature.refine(image, image, 6, guess, characterize=False, engine=self.engine)[:, :2][:, ::-1] assert_allclose(actual, expected, atol=0.1)
def test_separation_fast(self): separation = 20 for angle in np.arange(0, 360, 15): im = np.zeros((128, 128), dtype=np.uint8) pos = [[64, 64], [64 + separation * np.sin(angle/180*np.pi), 64 + separation * np.cos(angle/180*np.pi)]] # setup features: features with equal signal will always be # detected by a grey dilation, so make them unequal draw_feature(im, pos[0], 3, 240) draw_feature(im, pos[1], 3, 250) # find both of them f = grey_dilation(im, separation - 1, precise=False) assert_coordinates_close(f, pos, atol=1) # find only the brightest if angle in [45, 135, 225, 315]: # for unprecise, a too small square kernel is used, which is # perfect for 45-degree angles f = grey_dilation(im, separation + 1, precise=False) assert_coordinates_close(f, pos[1:], atol=1) else: # but too small by a factor of sqrt(ndim) for 90-degree angles f = grey_dilation(im, separation*np.sqrt(2) + 1, precise=False) assert_coordinates_close(f, pos[1:], atol=1)
def test_separation_fast(self): separation = 20 for angle in np.arange(0, 360, 15): im = np.zeros((128, 128), dtype=np.uint8) pos = [[64, 64], [ 64 + separation * np.sin(angle / 180 * np.pi), 64 + separation * np.cos(angle / 180 * np.pi) ]] # setup features: features with equal signal will always be # detected by a grey dilation, so make them unequal draw_feature(im, pos[0], 3, 240) draw_feature(im, pos[1], 3, 250) # find both of them f = grey_dilation(im, separation - 1, precise=False) assert_coordinates_close(f, pos, atol=1) # find only the brightest if angle in [45, 135, 225, 315]: # for unprecise, a too small square kernel is used, which is # perfect for 45-degree angles f = grey_dilation(im, separation + 1, precise=False) assert_coordinates_close(f, pos[1:], atol=1) else: # but too small by a factor of sqrt(ndim) for 90-degree angles f = grey_dilation(im, separation * np.sqrt(2) + 1, precise=False) assert_coordinates_close(f, pos[1:], atol=1)
def test_reject_peaks_near_edge(self): """Check whether local maxima near the edge of the image are properly rejected, so as not to cause crashes later.""" N = 30 diameter = 9 y = np.arange(20, 10*N + 1, 20) x = np.linspace(diameter / 2. - 2, diameter * 1.5, len(y)) cols = ['y', 'x'] expected = DataFrame(np.vstack([y, x]).T, columns=cols) dims = (y.max() - y.min() + 5*diameter, int(4 * diameter) - 2) image = np.ones(dims, dtype='uint8') for ypos, xpos in expected[['y', 'x']].values: draw_feature(image, [ypos, xpos], 27, max_value=100) def locate(image, **kwargs): return tp.locate(image, diameter, 1, preprocess=False, engine=self.engine, **kwargs)[cols] # All we care about is that this doesn't crash actual = locate(image) assert len(actual) # Check the other sides actual = locate(np.fliplr(image)) assert len(actual) actual = locate(image.transpose()) assert len(actual) actual = locate(np.flipud(image.transpose())) assert len(actual)
def test_one_centered_gaussian_3D_anisotropic(self): self.check_skip() L = 21 dims = (L, L + 2, L + 4) # avoid square images in tests pos = [7, 13, 9] cols = ['z', 'y', 'x'] expected = DataFrame(np.asarray(pos).reshape(1, -1), columns=cols) image = np.ones(dims, dtype='uint8') draw_feature(image, pos, (21, 27, 27)) actual = tp.locate(image, (7, 9, 9), 1, preprocess=False, engine=self.engine)[cols] assert_allclose(actual, expected, atol=0.1)
def test_float_image(self): separation = 20 angle = 45 im = np.zeros((128, 128), dtype=np.float64) pos = [[64, 64], [64 + separation * np.sin(angle/180*np.pi), 64 + separation * np.cos(angle/180*np.pi)]] # setup features: features with equal signal will always be # detected by a grey dilation, so make them unequal draw_feature(im, pos[0], 3, 240) draw_feature(im, pos[1], 3, 250) # find both of them f = grey_dilation(im, separation - 1, precise=False) assert_coordinates_close(f, pos, atol=1)
def test_size_anisotropic(self): # The separate columns 'size_x' and 'size_y' reflect the radii of # gyration in the two separate directions. self.check_skip() L = 101 SIZE = 5 dims = (L, L + 2) # avoid square images in tests pos = [50, 55] for ar in [1.1, 1.5, 2]: image = np.zeros(dims, dtype='uint8') draw_feature(image, pos, [int(SIZE*8*ar), SIZE*8], rg=0.25) f = tp.locate(image, [int(SIZE*4*ar) * 2 - 1, SIZE*8 - 1], 1, preprocess=False, engine=self.engine) assert_allclose(f['size_x'], SIZE, rtol=0.1) assert_allclose(f['size_y'], SIZE*ar, rtol=0.1)
def test_float_image(self): separation = 20 angle = 45 im = np.zeros((128, 128), dtype=np.float64) pos = [[64, 64], [ 64 + separation * np.sin(angle / 180 * np.pi), 64 + separation * np.cos(angle / 180 * np.pi) ]] # setup features: features with equal signal will always be # detected by a grey dilation, so make them unequal draw_feature(im, pos[0], 3, 240) draw_feature(im, pos[1], 3, 250) # find both of them f = grey_dilation(im, separation - 1, precise=False) assert_coordinates_close(f, pos, atol=1)
def test_separation_anisotropic(self): separation = (10, 20) for angle in np.arange(0, 360, 15): im = np.zeros((128, 128), dtype=np.uint8) pos = [[64, 64], [64 + separation[0] * np.sin(angle/180*np.pi), 64 + separation[1] * np.cos(angle/180*np.pi)]] # setup features: features with equal signal will always be # detected by a grey dilation, so make them unequal draw_feature(im, pos[0], 3, 240) draw_feature(im, pos[1], 3, 250) # find both of them f = grey_dilation(im, (9, 19)) assert_coordinates_close(f, pos, atol=1) # find only the brightest f = grey_dilation(im, (11, 21)) assert_coordinates_close(f, pos[1:], atol=1)
def test_separation(self): separation = 20 for angle in np.arange(0, 360, 15): im = np.zeros((128, 128), dtype=np.uint8) pos = [[64, 64], [64 + separation * np.sin(angle/180*np.pi), 64 + separation * np.cos(angle/180*np.pi)]] # setup features: features with equal signal will always be # detected by grey_dilation_legacy, so make them unequal draw_feature(im, pos[0], 3, 240) draw_feature(im, pos[1], 3, 250) # find both of them f = grey_dilation_legacy(im, separation - 1) assert_coordinates_close(f, pos, atol=1) # find only the brightest f = grey_dilation_legacy(im, separation + 1) assert_coordinates_close(f, pos[1:], atol=1)
def test_minmass_maxsize(self): # Test the mass- and sizebased filtering here on 4 different features. self.check_skip() L = 64 dims = (L, L + 2) cols = ['y', 'x'] PRECISION = 1 # we are not testing for subpx precision here image = np.zeros(dims, dtype=np.uint8) pos1 = np.array([15, 20]) pos2 = np.array([40, 40]) pos3 = np.array([25, 50]) pos4 = np.array([35, 15]) draw_feature(image, pos1, size=2.5) draw_feature(image, pos2, size=5) draw_feature(image, pos3, size=0.8) draw_feature(image, pos4, size=3.2) # filter on mass actual = tp.locate(image, 15, engine=self.engine, preprocess=False, minmass=6500, separation=10)[cols] actual = pandas_sort(actual, cols) expected = pandas_sort(DataFrame([pos2, pos4], columns=cols), cols) assert_allclose(actual, expected, atol=PRECISION) # filter on size actual = tp.locate(image, 15, engine=self.engine, preprocess=False, maxsize=3.0, separation=10)[cols] actual = pandas_sort(actual, cols) expected = pandas_sort(DataFrame([pos1, pos3], columns=cols), cols) assert_allclose(actual, expected, atol=PRECISION) # filter on both mass and size actual = tp.locate(image, 15, engine=self.engine, preprocess=False, minmass=600, maxsize=4.0, separation=10)[cols] actual = pandas_sort(actual, cols) expected = pandas_sort(DataFrame([pos1, pos4], columns=cols), cols) assert_allclose(actual, expected, atol=PRECISION)
def get_image(self, noise=0, signal_dev=0., size_dev=0., separation=None, N=None): if N is None: N = self.repeats if separation is None: separation = self.separation margin = self.separation Nsqrt = int(N**(1 / self.ndim) + 0.9999) pos = np.meshgrid(*[np.arange(0, s * Nsqrt, s) for s in separation], indexing='ij') pos = np.array([p.ravel() for p in pos], dtype=np.float).T[:N] + margin pos += (np.random.random(pos.shape) - 0.5 ) #randomize subpixel location shape = tuple(np.max(pos, axis=0).astype(np.int) + margin) if signal_dev > 0: signal = self.signal * np.random.uniform(1 - signal_dev, 1 + signal_dev, N) else: signal = np.repeat(self.signal, N) if size_dev > 0: size = np.array([self.size]) * np.random.uniform( 1 - size_dev, 1 + size_dev, (N, 1)) else: size = np.repeat([self.size], N, axis=0) image = np.zeros(shape, dtype=self.dtype) for _pos, _signal, _size in zip(pos, signal, size): draw_feature(image, _pos, _size, _signal, self.feat_func, **self.feat_kwargs) if self.isotropic: size = size[:, 0] if noise > 0: image = image + np.random.poisson(noise, shape) if image.max() <= 255: image = image.astype(np.uint8) return image, (pos, signal, size)
def test_ep_anisotropic(self): # The separate columns 'ep_x' and 'ep_y' reflect the static errors # in the two separate directions. The error in the direction with the # smallest mask size should be lowest; their ratio is equal to the # mask aspect ratio. self.check_skip() L = 101 SIZE = 5 dims = (L, L + 2) # avoid square images in tests pos = [50, 55] noise = 0.2 for ar in [1.1, 1.5, 2]: # sizeY / sizeX image = np.random.randint(0, int(noise*255), dims).astype('uint8') draw_feature(image, pos, [int(SIZE*8*ar), SIZE*8], max_value=int((1-noise)*255)) f = tp.locate(image, [int(SIZE*4*ar) * 2 - 1, SIZE*8 - 1], threshold=int(noise*64), topn=1, engine=self.engine).loc[0] assert_allclose(f['ep_y'] / f['ep_x'], ar, rtol=0.1)
def test_size(self): # To draw Gaussians with radii 2, 3, 5, and 7 px, we supply the draw # function with rg=0.25. This means that the radius of gyration will be # one fourth of the max radius in the draw area, which is diameter/2. # The 'size' comes out to be within 3%, which is because of the # pixelation of the Gaussian. # The IDL code has mistake in this area, documented here: # http://www.physics.emory.edu/~weeks/idl/radius.html self.check_skip() L = 101 dims = (L, L + 2) # avoid square images in tests for pos in [[50, 55], [50.2, 55], [50.5, 55]]: for SIZE in [2, 3, 5, 7]: image = np.zeros(dims, dtype='uint8') draw_feature(image, pos, SIZE*8, rg=0.25) actual = tp.locate(image, SIZE*8 - 1, 1, preprocess=False, engine=self.engine)['size'] assert_allclose(actual, SIZE, rtol=0.1)
def test_eccentricity(self): # Eccentricity (elongation) is measured with good accuracy and # ~0.02 precision, as long as the mask is large enough to cover # the whole object. self.check_skip() L = 501 dims = (L + 2, L) # avoid square images in tests pos = [50, 55] cols = ['y', 'x'] ECC = 0 image = np.ones(dims, dtype='uint8') draw_feature(image, pos, 27, ecc=ECC) actual = tp.locate(image, 21, 1, preprocess=False, engine=self.engine)['ecc'] expected = ECC assert_allclose(actual, expected, atol=0.02) ECC = 0.2 image = np.ones(dims, dtype='uint8') draw_feature(image, pos, 27, ecc=ECC) actual = tp.locate(image, 21, 1, preprocess=False, engine=self.engine)['ecc'] expected = ECC assert_allclose(actual, expected, atol=0.1) ECC = 0.5 image = np.ones(dims, dtype='uint8') draw_feature(image, pos, 27, ecc=ECC) actual = tp.locate(image, 21, 1, preprocess=False, engine=self.engine)['ecc'] expected = ECC assert_allclose(actual, expected, atol=0.1)
def test_separation_anisotropic(self): separation = (10, 20) for angle in np.arange(0, 360, 15): im = np.zeros((128, 128), dtype=np.uint8) pos = [[64, 64], [ 64 + separation[0] * np.sin(angle / 180 * np.pi), 64 + separation[1] * np.cos(angle / 180 * np.pi) ]] # setup features: features with equal signal will always be # detected by a grey dilation, so make them unequal draw_feature(im, pos[0], 3, 240) draw_feature(im, pos[1], 3, 250) # find both of them f = grey_dilation(im, (9, 19)) assert_coordinates_close(f, pos, atol=1) # find only the brightest f = grey_dilation(im, (11, 21)) assert_coordinates_close(f, pos[1:], atol=1)
def test_separation(self): separation = 20 for angle in np.arange(0, 360, 15): im = np.zeros((128, 128), dtype=np.uint8) pos = [[64, 64], [ 64 + separation * np.sin(angle / 180 * np.pi), 64 + separation * np.cos(angle / 180 * np.pi) ]] # setup features: features with equal signal will always be # detected by grey_dilation_legacy, so make them unequal draw_feature(im, pos[0], 3, 240) draw_feature(im, pos[1], 3, 250) # find both of them f = grey_dilation_legacy(im, separation - 1) assert_coordinates_close(f, pos, atol=1) # find only the brightest f = grey_dilation_legacy(im, separation + 1) assert_coordinates_close(f, pos[1:], atol=1)
def test_uncertainty_failure(self): """When the uncertainty ("ep") calculation results in a nonsense negative value, it should return NaN instead. """ self.check_skip() L = 21 dims = (L, L + 2) # avoid square images in tests pos = np.array([7, 13]) cols = ['x', 'y'] expected = DataFrame(pos[::-1].reshape(1, -1), columns=cols) image = 100*np.ones(dims, dtype='uint8') # For a feature to have a negative uncertainty, its integrated mass # must be less than if it were not there at all (replaced # with the average background intensity). So our feature will be a # small bright spot surrounded by a dark annulus. draw_feature(image, pos, 6, max_value=-100) draw_feature(image, pos, 4, max_value=200) actual = tp.locate(image, 9, 1, preprocess=False, engine=self.engine) assert np.allclose(actual[['x', 'y']], expected[['x', 'y']]) assert np.isnan(np.asscalar(actual.ep))
def get_image(self, noise=0, signal_dev=0., size_dev=0., separation=None, N=None): if N is None: N = self.repeats if separation is None: separation = self.separation margin = self.separation Nsqrt = int(N**(1/self.ndim) + 0.9999) pos = np.meshgrid(*[np.arange(0, s * Nsqrt, s) for s in separation], indexing='ij') pos = np.array([p.ravel() for p in pos], dtype=np.float).T[:N] + margin pos += (np.random.random(pos.shape) - 0.5) #randomize subpixel location shape = tuple(np.max(pos, axis=0).astype(np.int) + margin) if signal_dev > 0: signal = self.signal * np.random.uniform(1-signal_dev, 1+signal_dev, N) else: signal = np.repeat(self.signal, N) if size_dev > 0: size = np.array([self.size]) * np.random.uniform(1-size_dev, 1+size_dev, (N, 1)) else: size = np.repeat([self.size], N, axis=0) image = np.zeros(shape, dtype=self.dtype) for _pos, _signal, _size in zip(pos, signal, size): draw_feature(image, _pos, _size, _signal, self.feat_func, **self.feat_kwargs) if self.isotropic: size = size[:, 0] if noise > 0: image = image + np.random.poisson(noise, shape) if image.max() <= 255: image = image.astype(np.uint8) return image, (pos, signal, size)
def test_minmass_maxsize(self): # Test the mass- and sizebased filtering here on 4 different features. self.check_skip() L = 64 dims = (L, L + 2) cols = ['y', 'x'] PRECISION = 1 # we are not testing for subpx precision here image = np.zeros(dims, dtype=np.uint8) pos1 = np.array([15, 20]) pos2 = np.array([40, 40]) pos3 = np.array([25, 45]) pos4 = np.array([35, 15]) draw_feature(image, pos1, 15) draw_feature(image, pos2, 30) draw_feature(image, pos3, 5) draw_feature(image, pos4, 20) # filter on mass actual = tp.locate(image, 15, engine=self.engine, preprocess=False, minmass=6500)[cols] actual = actual.sort(cols) expected = DataFrame([pos2, pos4], columns=cols).sort(cols) assert_allclose(actual, expected, atol=PRECISION) # filter on size actual = tp.locate(image, 15, engine=self.engine, preprocess=False, maxsize=3.0)[cols] actual = actual.sort(cols) expected = DataFrame([pos1, pos3], columns=cols).sort(cols) assert_allclose(actual, expected, atol=PRECISION) # filter on both mass and size actual = tp.locate(image, 15, engine=self.engine, preprocess=False, minmass=600, maxsize=4.0)[cols] actual = actual.sort(cols) expected = DataFrame([pos1, pos4], columns=cols).sort(cols) assert_allclose(actual, expected, atol=PRECISION)