def test_larger_contour(): locus = pyefd.calculate_dc_coefficients(contour_1) coeffs = pyefd.elliptic_fourier_descriptors(contour_1, order=50) number_of_points = contour_1.shape[0] reconstruction = pyefd.reconstruct_contour(coeffs, locus, number_of_points) hausdorff_distance, _, _ = directed_hausdorff(contour_1, reconstruction) assert hausdorff_distance < 0.4
def create_contour(mask): """Turn as mask into a contour polygon. The polygon is smoothed via Fourier descriptors Parameters ---------- mask : 2D numpy array mask of a region Returns ------- recon : Nx2 numpy array coordinates of contour """ # extract contour by recovering contour of filled mask reg = skimage.measure.regionprops_table( skimage.morphology.label(mask), properties=( "label", "area", "filled_area", "eccentricity", "extent", "filled_image", "coords", "bbox", ), ) regp = pd.DataFrame(reg).sort_values(by="filled_area", ascending=False) new_mask2 = np.zeros(mask.shape) new_mask2[regp.iloc[0]["bbox-0"]:regp.iloc[0]["bbox-2"], regp.iloc[0]["bbox-1"]:regp. iloc[0]["bbox-3"], ] = regp.iloc[0].filled_image area = regp.iloc[0].filled_area contour = skimage.measure.find_contours(new_mask2, 0.8) sel_contour = contour[np.argmax([len(x) for x in contour])] # calculate a smoothed verions using a reconstruction via Fourier descriptors coeffs = elliptic_fourier_descriptors(sel_contour, order=100, normalize=False) coef0 = calculate_dc_coefficients(sel_contour) recon = reconstruct_contour(coeffs, locus=coef0, num_points=1500) return recon, area
def test_fit_1(): n = 300 locus = pyefd.calculate_dc_coefficients(contour_1) coeffs = pyefd.elliptic_fourier_descriptors(contour_1, order=20) t = np.linspace(0, 1.0, n) xt = np.ones((n, )) * locus[0] yt = np.ones((n, )) * locus[1] for n in pyefd._range(coeffs.shape[0]): xt += (coeffs[n, 0] * np.cos(2 * (n + 1) * np.pi * t)) + \ (coeffs[n, 1] * np.sin(2 * (n + 1) * np.pi * t)) yt += (coeffs[n, 2] * np.cos(2 * (n + 1) * np.pi * t)) + \ (coeffs[n, 3] * np.sin(2 * (n + 1) * np.pi * t)) assert True
def test_reconstruct_simple_contour(): simple_polygon = np.array([[1., 1.], [0., 1.], [0., 0.], [1., 0.], [1., 1.]]) number_of_points = simple_polygon.shape[0] locus = pyefd.calculate_dc_coefficients(simple_polygon) coeffs = pyefd.elliptic_fourier_descriptors(simple_polygon, order=30) reconstruction = pyefd.reconstruct_contour(coeffs, locus, number_of_points) # with only 2 decimal accuracy it is a bit of a course test, but it will do # directly comparing the two polygons will only work here, because efd coefficients will be cycle-consistent np.testing.assert_array_almost_equal(simple_polygon, reconstruction, decimal=2) hausdorff_distance, _, _ = directed_hausdorff(reconstruction, simple_polygon) assert hausdorff_distance < 0.01
def test_fit_1(): n = 300 locus = pyefd.calculate_dc_coefficients(contour_1) coeffs = pyefd.elliptic_fourier_descriptors(contour_1, order=20) t = np.linspace(0, 1.0, n) xt = np.ones((n,)) * locus[0] yt = np.ones((n,)) * locus[1] for n in pyefd._range(coeffs.shape[0]): xt += (coeffs[n, 0] * np.cos(2 * (n + 1) * np.pi * t)) + \ (coeffs[n, 1] * np.sin(2 * (n + 1) * np.pi * t)) yt += (coeffs[n, 2] * np.cos(2 * (n + 1) * np.pi * t)) + \ (coeffs[n, 3] * np.sin(2 * (n + 1) * np.pi * t)) assert True
def test_centroid(self): geom1 = 'POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))' geom1 = gv.vectorize_wkt(geom1) with self.subTest('It does not accept a numpy ndarray'): with self.assertRaises(AssertionError): centroid(geom1) with self.subTest('It rejects 3D geometries'): with self.assertRaises(AssertionError): centroid(torch.rand((10, 3))) with self.subTest('Our stand-in centroid function does the same as pyefd'): geom2 = 'POLYGON((1 1, 0 1, 0 0, 1 0, 1 1))' geom2 = gv.vectorize_wkt(geom2) coords_batch = geom2[:, :2] coords_batch = coords_batch.reshape(1, geom2.shape[0], 2) polygon2_tensor = torch.from_numpy(coords_batch) pyefd_centroid = pyefd.calculate_dc_coefficients(coords_batch[0]) pytorch_centroid = centroid(polygon2_tensor) np.testing.assert_array_almost_equal(pyefd_centroid, pytorch_centroid[0]) with self.subTest('It correctly calculates centroids for batches'): geom2 = 'POLYGON((1 1, 0 1, 0 0, 1 0, 1 1))' geom2 = gv.vectorize_wkt(geom2) coords_batch = geom2[:, :2] coords_batch = coords_batch.reshape(1, geom2.shape[0], 2) polygon2_tensor = torch.from_numpy(coords_batch) batch_size = 6 batch = polygon2_tensor.repeat(batch_size, 1, 1) multiply_range = torch.range(1., 6., dtype=batch.dtype).reshape((batch_size, 1, 1)) batch = batch * multiply_range reference_centroids = np.arange(1, batch_size + 1) reference_centroids = reference_centroids.reshape(batch_size, 1) * 0.5 reference_centroids = reference_centroids.repeat(2, axis=1) batch_centroids = centroid(batch) np.testing.assert_array_almost_equal(reference_centroids, batch_centroids.numpy())
def test_locus(): locus = pyefd.calculate_dc_coefficients(contour_1) np.testing.assert_array_almost_equal(locus, np.mean(contour_1, axis=0), decimal=0)
"""Elliptic Fourier Descriptors parametrizing a closed countour (in red)""" import vedo, pyefd shapes = vedo.load(vedo.dataurl+'timecourse1d.npy') sh = shapes[55] sr = vedo.Line(sh).mirror('x').reverse() sm = vedo.merge(sh, sr).c('red5').lw(3) pts = sm.points()[:,(0,1)] rlines = [] for order in range(5,30, 5): coeffs = pyefd.elliptic_fourier_descriptors(pts, order=order, normalize=False) a0, c0 = pyefd.calculate_dc_coefficients(pts) rpts = pyefd.reconstruct_contour(coeffs, locus=(a0,c0), num_points=400) color = vedo.colorMap(order, "Blues", 5,30) rline = vedo.Line(rpts).lw(3).c(color) rlines.append(rline) sm.z(0.1) # move it on top so it's visible vedo.show(sm, *rlines, __doc__, axes=1, bg='k', size=(1190, 630), zoom=1.8)
# create fourier descriptors and save statistics normalized_coefficients = np.zeros((dataset.n_images, n_harmonics, 4)) errors = np.zeros(dataset.n_images) for idx in range(dataset.n_images): im = dataset.get_image(idx).squeeze().numpy() contour = get_contour(im, threshold=threshold) if contour is not None: # elliptical Fourier descriptor's coefficients. coeffs = pyefd.elliptic_fourier_descriptors(contour, order=n_harmonics) # normalize coeffs normalized_coeffs, L = pyefd.normalize_efd(deepcopy(coeffs)) # recon_contour if len(contour) > 0: locus = pyefd.calculate_dc_coefficients(contour) else: locus = (0, 0) recon_contour = pyefd.reconstruct_contour(coeffs, locus=locus, num_points=300) # error measure error = calc_contours_distance(contour, recon_contour) / (2 * L) * 100 normalized_coefficients[idx] = normalized_coeffs errors[idx] = error print('{} harmonics: error {}(mean); {}(std); {} (min); {}(max); {}(90p)'. format(n_harmonics, errors.mean(), errors.std(), errors.min(), errors.max(), np.percentile(errors, 90)))
# diamond # rCos = np.array([0.5, 0.427, 0.0, 0.0732]) # zSin = np.array([0.0, 0.427, 0.0, -0.0732]) # D # rCos = np.array([3.0, 0.991, 0.136]) # zSin = np.array([0.0, 1.409, -0.118]) # belt # rCos = np.array([3.0, 0.453, 0.0, 0.0 ]) # zSin = np.array([0.0, 0.6 , 0.0, 0.196]) # ellipse # rCos = np.array([3.0, 1.0]) # zSin = np.array([0.0, 3.0]) # evaluate curve geometry given as Fourier coefficients above n = 300 contour = np.zeros([n,2]) theta = np.linspace(0.0, 2.0*np.pi, n) mMax = len(rCos) for m in range(mMax): contour[:,0] += rCos[m] * np.cos(m*theta) contour[:,1] += zSin[m] * np.sin(m*theta) # apply pyefd to get elliptic Fourier descriptors coeffs = pyefd.elliptic_fourier_descriptors(contour) a0, c0 = pyefd.calculate_dc_coefficients(contour) pyefd.plot_efd(coeffs, locus=(a0,c0), contour=contour)