diff --git a/src/epicell.m b/src/@epicell/epicell.m similarity index 85% rename from src/epicell.m rename to src/@epicell/epicell.m index 3f2d48f6a23ff49146bddbb21c9777136e7d94e7..204013355ee61537a660e576c08922592e034dc3 100644 --- a/src/epicell.m +++ b/src/@epicell/epicell.m @@ -75,7 +75,7 @@ classdef epicell end end - %% Static methods: compute final properties value. + %% Private static methods: compute final properties value. methods ( Access = private, Hidden = true, Static = true ) function p = centered_points( p ) @@ -142,8 +142,34 @@ classdef epicell p = p - repmat( c, size( p, 1 ), 1 ); % Fit a plane to these points. [ ~, ~, v ] = svd( p ); - E = rot2eulerZXZ( v ); + E = epicell.rot2eulerZXZ( v ); end + + + + end + + %% Public static methods: utilities. + + methods ( Access = public, Hidden = false, Static = true ) + % Convert euler angles to rotation matrix. + R = euleurZXZ2rot( E ) + + % Convert rotation matrix to euler angles. + [ E, E_deg ] = rot2eulerZXZ( R ) + + % Fit a 2D ellipse to a set of 2D points. + [ f, Q ] = fit_ellipse_2d( p, method ) + + % Fit a 2D ellipse to a set of 3D points. + [ f3d, v ] = fit_ellipse_3d( p, E, method ) + + % Plot a 2D ellipse in 3D. + h = plot_ellipse_3d( f3d, v, npoints ) + + % Plot an ellipse in XY plane. + h = plot_ellipse_2d( f, npoints ) + end end diff --git a/src/euleurZXZ2rot.m b/src/@epicell/euleurZXZ2rot.m similarity index 100% rename from src/euleurZXZ2rot.m rename to src/@epicell/euleurZXZ2rot.m diff --git a/src/fit_ellipse_2d.m b/src/@epicell/fit_ellipse_2d.m similarity index 100% rename from src/fit_ellipse_2d.m rename to src/@epicell/fit_ellipse_2d.m diff --git a/src/@epicell/fit_ellipse_3d.m b/src/@epicell/fit_ellipse_3d.m new file mode 100644 index 0000000000000000000000000000000000000000..fe153c4e77dc465255c1a1af02e13bf3f5492228 --- /dev/null +++ b/src/@epicell/fit_ellipse_3d.m @@ -0,0 +1,44 @@ +function [ f3d, v ] = fit_ellipse_3d( p, E, method ) +%FIT_ELLIPSE_3D Fit a 2D ellipse to a set of 3D points. +% The fit requires (or compute) the Euler angles of the plane fitted +% through the opints, so that we can project them on this plane. We then +% make a 2D ellipse fit on the projected points. This turns to be much +% more robust than a 3D fit, and also closely match our configuration. +% +% This function returns f3d = [ x0 y0 z0 a b theta ]. These are the +% cartesian parameters of the ellipse in the rotated plane. The ellipse +% semi-major axis and semi-minor axis are always so that a >= b and theta +% measure the angle of the semi-major axis with respect to the X axis (in +% the rotated plane). +% +% v is the transformation matrix that rotates the 3D points close to the +% XY plane. It will be used to plot the ellipse in 3D. + +% Greatly inspired from https://stackoverflow.com/questions/29051168 +% /data-fitting-an-ellipse-in-3d-space + + if nargin < 3 + method = 'direct'; + end + + c = mean( p ); + p = p - repmat( c, size( p, 1 ), 1 ); + + % Fit a plane to these points. + if nargin < 2 + [ ~, ~, v ] = svd( p ); + else + v = euleurZXZ2rot( E ); + end + + % Rotate the points into the principal axes frame. + pt = p * v; + + f = epicell.fit_ellipse_2d( pt( :, 1:2 ), method ); + + f( 1 ) = f( 1 ) + c( 1 ); + f( 2 ) = f( 2 ) + c( 2 ); + f3d = [ f(1:2) c(3) f(3:5) ]; + +end + diff --git a/src/plot_ellipse_2d.m b/src/@epicell/plot_ellipse_2d.m similarity index 100% rename from src/plot_ellipse_2d.m rename to src/@epicell/plot_ellipse_2d.m diff --git a/src/plot_ellipse_3d.m b/src/@epicell/plot_ellipse_3d.m similarity index 100% rename from src/plot_ellipse_3d.m rename to src/@epicell/plot_ellipse_3d.m diff --git a/src/rot2eulerZXZ.m b/src/@epicell/rot2eulerZXZ.m similarity index 94% rename from src/rot2eulerZXZ.m rename to src/@epicell/rot2eulerZXZ.m index 7ef58205c0fd9af352de9c1f5dac058a4865cb61..1e4c3e712ddf32ecf37c649fea554036a0b51ffe 100644 --- a/src/rot2eulerZXZ.m +++ b/src/@epicell/rot2eulerZXZ.m @@ -1,4 +1,4 @@ -function[E,E_deg]=rot2eulerZXZ(R) +function[ E, E_deg ] = rot2eulerZXZ( R ) %ROT2EULERZXZ Convert rotation matrix to ZX'Z' Euler angles. r13 = R( 1, 3 ); diff --git a/src/fit_ellipse.m b/src/fit_ellipse.m deleted file mode 100644 index 4b60f7a5bf09a4856f04c98cb920403e558162fd..0000000000000000000000000000000000000000 --- a/src/fit_ellipse.m +++ /dev/null @@ -1,137 +0,0 @@ -function f = fit_ellipse( p, E ) -%FIT_ELLIPSE Fit a 2D ellipse to the 3D points. -% The fit requires the Euler angles of the plane fitted through the -% opints, so that we can project them on this plane. We then make a 2D -% ellipse fit on the projected points. This turns to be much more robust -% than a 3D fit, and also closely match our configuration. - -% Greatly inspired from -% https://stackoverflow.com/questions/29051168/data-fitting-an-ellipse-in-3d-space - - % Fit a plane to these points. - if nargin < 2 - [ ~, ~, v ] = svd( p ); - else - v = euleurZXZ2rot( E ); - end - - % Rotate the points into the principal axes frame. - p = p * v; - - % Direct ellipse fit. -% A = direct_ellipse_fit( p( :, 1:2 ) ); - A = taubin_ellipse_fit( p( :, 1:2 ) ); - f = quadratic_to_cartesian( A ); - - %% Subfunctions - - function f = quadratic_to_cartesian( A ) - % Equations taken from Wolfram website. - - a = A(1); - b = A(2); - c = A(3); - d = A(4); - f = A(5); - g = A(6); - - x0 = ( c * d - b * f ) / ( b^2 - a * c ); - y0 = ( a * f - b * d ) / ( b^2 - a * c ); - - l1 = sqrt( 2*(a*f^2+c*d^2+g*b^2-2*b*d*f-a*c*g) / ((b^2-a*c)*(sqrt((a-c)^2+4*b^2)-(a+c)))); - l2 = sqrt( 2*(a*f^2+c*d^2+g*b^2-2*b*d*f-a*c*g) / ((b^2-a*c)*(-sqrt((a-c)^2+4*b^2)-(a+c)))); - - if b == 0 && a < c - phi = 0; - elseif b == 0 && a > c - phi = 0.5*pi; - elseif b ~= 0 && a < c - phi = 0.5* acot((a-c)/(2*b)); - else - phi = 0.5*pi + 0.5* acot((a-c)/(2*b)); - end - - f = [ x0 y0 l1 l2 phi ]; - - end - - function A = direct_ellipse_fit(XY) %#ok<DEFNU> - % Direct ellipse fit, proposed in article - % A. W. Fitzgibbon, M. Pilu, R. B. Fisher - % "Direct Least Squares Fitting of Ellipses" - % IEEE Trans. PAMI, Vol. 21, pages 476-480 (1999) - % - % Adapted from https://fr.mathworks.com/matlabcentral/fileexchange/22684-ellipse-fit-direct-method - - centroid = mean(XY); % the centroid of the data set - D1 = [(XY(:,1)-centroid(1)).^2, (XY(:,1)-centroid(1)).*(XY(:,2)-centroid(2)),... - (XY(:,2)-centroid(2)).^2]; - D2 = [XY(:,1)-centroid(1), XY(:,2)-centroid(2), ones(size(XY,1),1)]; - S1 = D1'*D1; - S2 = D1'*D2; - S3 = D2'*D2; - T = -inv(S3)*S2'; - M = S1 + S2*T; - M = [M(3,:)./2; -M(2,:); M(1,:)./2]; - [evec,eval] = eig(M); %#ok<ASGLU> - cond = 4*evec(1,:).*evec(3,:)-evec(2,:).^2; - A1 = evec(:,find(cond>0)); %#ok<FNDSB> - A = [A1; T*A1]; - A4 = A(4)-2*A(1)*centroid(1)-A(2)*centroid(2); - A5 = A(5)-2*A(3)*centroid(2)-A(2)*centroid(1); - A6 = A(6)+A(1)*centroid(1)^2+A(3)*centroid(2)^2+... - A(2)*centroid(1)*centroid(2)-A(4)*centroid(1)-A(5)*centroid(2); - A(4) = A4; A(5) = A5; A(6) = A6; - A = A/norm(A); - end - - function A = taubin_ellipse_fit(XY) - % Ellipse fit by Taubin's Method published in - % G. Taubin, "Estimation Of Planar Curves, Surfaces And Nonplanar - % Space Curves Defined By Implicit Equations, With - % Applications To Edge And Range Image Segmentation", - % IEEE Trans. PAMI, Vol. 13, pages 1115-1138, (1991) - % - % Input: XY(n,2) is the array of coordinates of n points x(i)=XY(i,1), y(i)=XY(i,2) - % - % Output: A = [a b c d e f]' is the vector of algebraic - % parameters of the fitting ellipse: - % ax^2 + bxy + cy^2 +dx + ey + f = 0 - % the vector A is normed, so that ||A||=1 - % - % Among fast non-iterative ellipse fitting methods, - % this is perhaps the most accurate and robust - % - % Note: this method fits a quadratic curve (conic) to a set of points; - % if points are better approximated by a hyperbola, this fit will - % return a hyperbola. To fit ellipses only, use "Direct Ellipse Fit". - - centroid = mean(XY); % the centroid of the data set - Z = [(XY(:,1)-centroid(1)).^2, (XY(:,1)-centroid(1)).*(XY(:,2)-centroid(2)),... - (XY(:,2)-centroid(2)).^2, XY(:,1)-centroid(1), XY(:,2)-centroid(2), ones(size(XY,1),1)]; - M = Z'*Z/size(XY,1); - P = [M(1,1)-M(1,6)^2, M(1,2)-M(1,6)*M(2,6), M(1,3)-M(1,6)*M(3,6), M(1,4), M(1,5); - M(1,2)-M(1,6)*M(2,6), M(2,2)-M(2,6)^2, M(2,3)-M(2,6)*M(3,6), M(2,4), M(2,5); - M(1,3)-M(1,6)*M(3,6), M(2,3)-M(2,6)*M(3,6), M(3,3)-M(3,6)^2, M(3,4), M(3,5); - M(1,4), M(2,4), M(3,4), M(4,4), M(4,5); - M(1,5), M(2,5), M(3,5), M(4,5), M(5,5)]; - Q = [4*M(1,6), 2*M(2,6), 0, 0, 0; - 2*M(2,6), M(1,6)+M(3,6), 2*M(2,6), 0, 0; - 0, 2*M(2,6), 4*M(3,6), 0, 0; - 0, 0, 0, 1, 0; - 0, 0, 0, 0, 1]; - [V,D] = eig(P,Q); - [Dsort,ID] = sort(diag(D)); %#ok<ASGLU> - A = V(:,ID(1)); - A = [A; -A(1:3)'*M(1:3,6)]; - A4 = A(4)-2*A(1)*centroid(1)-A(2)*centroid(2); - A5 = A(5)-2*A(3)*centroid(2)-A(2)*centroid(1); - A6 = A(6)+A(1)*centroid(1)^2+A(3)*centroid(2)^2+... - A(2)*centroid(1)*centroid(2)-A(4)*centroid(1)-A(5)*centroid(2); - A(4) = A4; A(5) = A5; A(6) = A6; - A = A/norm(A); - end % Taubin - - -end - diff --git a/src/fit_ellipse_3d.m b/src/fit_ellipse_3d.m deleted file mode 100644 index 78ece117e5a6a6732fb9613809addf3c05e357fa..0000000000000000000000000000000000000000 --- a/src/fit_ellipse_3d.m +++ /dev/null @@ -1,38 +0,0 @@ -function [ f3d, v ] = fit_ellipse_3d( p, E, method ) -%FIT_ELLIPSE_3D Fit a 2D ellipse on the 3D points. -% The fit requires the Euler angles of the plane fitted through the -% opints, so that we can project them on this plane. We then make a 2D -% ellipse fit on the projected points. This turns to be much more robust -% than a 3D fit, and also closely match our configuration. -% This function returns f3d = [ x0 y0 z0 a b theta ] -% and v, the ransformation matrix that rotates the 3D points close to the -% XY plane. It will be used to plot the ellipse in 3D. - -% Greatly inspired from https://stackoverflow.com/questions/29051168 -% /data-fitting-an-ellipse-in-3d-space - - if nargin < 3 - method = 'direct'; - end - - c = mean( p ); - p = p - repmat( c, size( p, 1 ), 1 ); - - % Fit a plane to these points. - if nargin < 2 - [ ~, ~, v ] = svd( p ); - else - v = euleurZXZ2rot( E ); - end - - % Rotate the points into the principal axes frame. - pt = p * v; - - f = fit_ellipse_2d( pt( :, 1:2 ), method ); - - f( 1 ) = f( 1 ) + c( 1 ); - f( 2 ) = f( 2 ) + c( 2 ); - f3d = [ f(1:2) c(3) f(3:5) ]; - -end -