From c48c512cf63a72ea6009196736dfd4df8a46e2f0 Mon Sep 17 00:00:00 2001 From: Dev Singh Date: Fri, 25 Sep 2020 02:06:30 +0000 Subject: [PATCH] Implement fitting to circle using LSC and HyperFit (#45) * chore: add pylint to devcontainer Signed-off-by: Dev Singh * feat: init LSC fitting cuda and cpu-based LSC fitting using cupy and numpy Signed-off-by: Dev Singh * docs: add changelog entry and module to class list Signed-off-by: Dev Singh * docs: fix typo in comment Signed-off-by: Dev Singh * fix: only import cupy if cuda available Signed-off-by: Dev Singh * fix: move to own file, abandon cupy Signed-off-by: Dev Singh * fix: remove numba dep Signed-off-by: Dev Singh * deps: remove cupy dep Signed-off-by: Dev Singh * feat: add tests Signed-off-by: Dev Singh * fix: correct indentation Signed-off-by: Dev Singh * fix: variable names Signed-off-by: Dev Singh * fix: add self when refering to coords Signed-off-by: Dev Singh * fix: numpy ordering Signed-off-by: Dev Singh * docs: remove version bump, nomaintain add notice that module is not actively maintained, may be removed in future release Signed-off-by: Dev Singh * fix: remove hyperfit as not being impled Signed-off-by: Dev Singh --- .devcontainer/devcontainer.json | 2 +- analysis-master/test_analysis.py | 6 +- analysis-master/tra_analysis/fits.py | 85 ++++++++++++++++++++++ analysis-master/tra_analysis/regression.py | 12 +-- 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 analysis-master/tra_analysis/fits.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 53b442b5..a1f40089 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -24,5 +24,5 @@ "ms-python.python", "waderyan.gitblame" ], - "postCreateCommand": "apt install vim -y ; pip install -r data-analysis/requirements.txt ; pip install -r analysis-master/requirements.txt ; pip install tra-analysis" + "postCreateCommand": "apt install vim -y ; pip install -r data-analysis/requirements.txt ; pip install -r analysis-master/requirements.txt ; pip install pylint ; pip install tra-analysis" } \ No newline at end of file diff --git a/analysis-master/test_analysis.py b/analysis-master/test_analysis.py index ffb7e88f..d281b5ef 100644 --- a/analysis-master/test_analysis.py +++ b/analysis-master/test_analysis.py @@ -1,8 +1,11 @@ from tra_analysis import analysis as an from tra_analysis import metrics +from tra_analysis import fits def test_(): test_data_linear = [1, 3, 6, 7, 9] + x_data_circular = [] + y_data_circular = [] y_data_ccu = [1, 3, 7, 14, 21] y_data_ccd = [1, 5, 7, 8.5, 8.66] test_data_scrambled = [-32, 34, 19, 72, -65, -11, -43, 6, 85, -17, -98, -26, 12, 20, 9, -92, -40, 98, -78, 17, -20, 49, 93, -27, -24, -66, 40, 84, 1, -64, -68, -25, -42, -46, -76, 43, -3, 30, -14, -34, -55, -13, 41, -30, 0, -61, 48, 23, 60, 87, 80, 77, 53, 73, 79, 24, -52, 82, 8, -44, 65, 47, -77, 94, 7, 37, -79, 36, -94, 91, 59, 10, 97, -38, -67, 83, 54, 31, -95, -63, 16, -45, 21, -12, 66, -48, -18, -96, -90, -21, -83, -74, 39, 64, 69, -97, 13, 55, 27, -39] @@ -28,4 +31,5 @@ def test_(): assert all(a == b for a, b in zip(an.Sort().shellsort(test_data_scrambled), test_data_sorted)) assert all(a == b for a, b in zip(an.Sort().bubblesort(test_data_scrambled), test_data_sorted)) assert all(a == b for a, b in zip(an.Sort().cyclesort(test_data_scrambled), test_data_sorted)) - assert all(a == b for a, b in zip(an.Sort().cocktailsort(test_data_scrambled), test_data_sorted)) \ No newline at end of file + assert all(a == b for a, b in zip(an.Sort().cocktailsort(test_data_scrambled), test_data_sorted)) + assert fits.CircleFit(x=[0,0,-1,1], y=[1, -1, 0, 0]).LSC() == (0.0, 0.0, 1.0, 0.0) \ No newline at end of file diff --git a/analysis-master/tra_analysis/fits.py b/analysis-master/tra_analysis/fits.py new file mode 100644 index 00000000..00935cb3 --- /dev/null +++ b/analysis-master/tra_analysis/fits.py @@ -0,0 +1,85 @@ +# Titan Robotics Team 2022: CPU fitting models +# Written by Dev Singh +# Notes: +# this module is cuda-optimized (as appropriate) and vectorized (except for one small part) +# setup: + +__version__ = "0.0.1" + +# changelog should be viewed using print(analysis.fits.__changelog__) +__changelog__ = """changelog: + 0.0.1: + - initial release, add circle fitting with LSC +""" + +__author__ = ( + "Dev Singh " +) + +__all__ = [ + 'CircleFit' +] + +import numpy as np + +class CircleFit: + """Class to fit data to a circle using the Least Square Circle (LSC) method""" + # For more information on the LSC method, see: + # http://www.dtcenter.org/sites/default/files/community-code/met/docs/write-ups/circle_fit.pdf + def __init__(self, x, y, xy=None): + self.ournp = np #todo: implement cupy correctly + if type(x) == list: + x = np.array(x) + if type(y) == list: + y = np.array(y) + if type(xy) == list: + xy = np.array(xy) + if xy != None: + self.coords = xy + else: + # following block combines x and y into one array if not already done + self.coords = self.ournp.vstack(([x.T], [y.T])).T + def calc_R(x, y, xc, yc): + """Returns distance between center and point""" + return self.ournp.sqrt((x-xc)**2 + (y-yc)**2) + def f(c, x, y): + """Returns distance between point and circle at c""" + Ri = calc_R(x, y, *c) + return Ri - Ri.mean() + def LSC(self): + """Fits given data to a circle and returns the center, radius, and variance""" + x = self.coords[:, 0] + y = self.coords[:, 1] + # guessing at a center + x_m = self.ournp.mean(x) + y_m = self.ournp.mean(y) + + # calculation of the reduced coordinates + u = x - x_m + v = y - y_m + + # linear system defining the center (uc, vc) in reduced coordinates: + # Suu * uc + Suv * vc = (Suuu + Suvv)/2 + # Suv * uc + Svv * vc = (Suuv + Svvv)/2 + Suv = self.ournp.sum(u*v) + Suu = self.ournp.sum(u**2) + Svv = self.ournp.sum(v**2) + Suuv = self.ournp.sum(u**2 * v) + Suvv = self.ournp.sum(u * v**2) + Suuu = self.ournp.sum(u**3) + Svvv = self.ournp.sum(v**3) + + # Solving the linear system + A = self.ournp.array([ [ Suu, Suv ], [Suv, Svv]]) + B = self.ournp.array([ Suuu + Suvv, Svvv + Suuv ])/2.0 + uc, vc = self.ournp.linalg.solve(A, B) + + xc_1 = x_m + uc + yc_1 = y_m + vc + + # Calculate the distances from center (xc_1, yc_1) + Ri_1 = self.ournp.sqrt((x-xc_1)**2 + (y-yc_1)**2) + R_1 = self.ournp.mean(Ri_1) + # calculate residual error + residu_1 = self.ournp.sum((Ri_1-R_1)**2) + return (xc_1, yc_1, R_1, residu_1) \ No newline at end of file diff --git a/analysis-master/tra_analysis/regression.py b/analysis-master/tra_analysis/regression.py index e9d8199a..82e8fbb1 100644 --- a/analysis-master/tra_analysis/regression.py +++ b/analysis-master/tra_analysis/regression.py @@ -1,8 +1,9 @@ # Titan Robotics Team 2022: CUDA-based Regressions Module +# Not actively maintained, may be removed in future release # Written by Arthur Lu & Jacob Levine # Notes: # this module has been automatically inegrated into analysis.py, and should be callable as a class from the package -# this module is cuda-optimized and vectorized (except for one small part) +# this module is cuda-optimized (as appropriate) and vectorized (except for one small part) # setup: __version__ = "0.0.4" @@ -25,7 +26,7 @@ __changelog__ = """ __author__ = ( "Jacob Levine ", - "Arthur Lu " + "Arthur Lu ", ) __all__ = [ @@ -40,14 +41,15 @@ __all__ = [ 'ExpRegKernel', 'SigmoidalRegKernelArthur', 'SGDTrain', - 'CustomTrain' + 'CustomTrain', + 'CircleFit' ] import torch global device -device = "cuda:0" if torch.torch.cuda.is_available() else "cpu" +device = "cuda:0" if torch.cuda.is_available() else "cpu" #todo: document completely @@ -217,4 +219,4 @@ def CustomTrain(self, kernel, optim, data, ground, loss=torch.nn.MSELoss(), iter ls=loss(pred,ground_cuda) ls.backward() optim.step() - return kernel \ No newline at end of file + return kernel