Convex hull / concave hull for multiple clusters in data - matlab

I have done a lot of reading on drawing polygons around clusters and realized convhull maybe the best way forward. Basically I am looking for a elastic like polygon to wrap around my cluster points.
My data is matrix consisting of x (1st column) and y(2nd column) points which are grouped in clusters (3rd column). I have 700 such clusters hence not feasible to plot each separately.
Is there a way to perform convhull for each cluster separately and then plot each of them on a single chart.
EDIT
Code I have written until now which isn't able to run convex hull on each individual cluster...
[ndata, text, alldata] = xlsread(fullfile(source_dir));
[~, y] = sort(ndata(:,end));
As = ndata(y,:);
lon = As(:,1);
lat = As(:,2);
cluster = As(:,3);
%% To find number of points in a cluster (repetitions)
rep = zeros(size(cluster));
for j = 1:length(cluster)
rep(j) = sum(cluster==cluster(j));
end
%% Less than 3 points in a cluster are filtered out
x = lon (rep>3);
y = lat (rep>3);
z = cluster (rep>3);
%% convex hull for each cluster plotted ....hold....then display all.
figure
hold on
clusters = unique(z);
for i = 1:length(z)
k=convhull(x(z==clusters(i)), y(z==clusters(i)));
plot(x, y, 'b.'); %# plot cluster points
plot(x(k),y(k),'r-'); %# plots only k indices, giving the convex hull
end
Below is an image of what is being displayed;
If this question has already been asked I apologize for repetition but please do direct me to the answer you'll see fit.
Please can anyone help with this, however trivial I'm really struggling!

I would iterate through all the clusters and do what you already written, and use the hold on option to accumulate all the plots in the same plot. Something like this:
% Generate three clouds of points in 2D:
c1 = bsxfun(#plus, 0.5 * randn(50,2), [1 3]);
c2 = bsxfun(#plus, 0.6 * randn(20,2), [0 0]);
c3 = bsxfun(#plus, 0.4 * randn(20,2), [1 1]);
data = [c1, ones(50,1); ...
c2, 2*ones(20,1); ...
c3, 3*ones(20,1)];
% Plot the data points with different colors
clf
plot(c1(:,1), c1(:,2),'r+', 'LineWidth', 2);
hold on
plot(c2(:,1), c2(:,2),'k+', 'LineWidth', 2);
plot(c3(:,1), c3(:,2),'b+', 'LineWidth', 2);
x = data(:,1);
y = data(:,2);
cluster = data(:,3);
clusters = unique(cluster);
for i = 1:length(clusters)
px = x(cluster == clusters(i));
py = y(cluster == clusters(i));
if length(px) > 2
k = convhull(px, py);
plot(px(k), py(k), '-');
end
end
It gives the following result:

Related

How to superimpose two contour maps onto each other in matlab?

I have two contour maps in Matlab and each of the two maps has a single curve specifying a single Z-value. I want to super impose the two contour maps so that I can find the single solution where the two z-value curves intersect. How could I go about super imposing the two contour maps?
% the two contour maps are coded the exact same way, but with different z-values
x = 0.05:0.05:1;
y = 0.0:0.05:1;
[X, Y] = meshgrid(x, y);
% Z-value data is copied from excel and pasted into an array
Z = [data]
contourf(X, Y, Z);
pcolor(X, Y, Z); hold on
shading interp
title();
xlabel();
ylabel();
colorbar
val = %z-value to plot onto colormap
tol = %tolerance
idxZval = (Z <= val+tol) & (Z >= val-tol);
plot(X(idxZval), Y(idxZval))[enter image description here][1]
The end result you seek is possible using contourc or using contour specifying the same contours (isolines).
This answer extends this answer in Approach 1 using contourc and provides a simple solution with contour in Approach 2.
You might ask "Why Approach 1 when Approach 2 is so simple?"
Approach 1 provides a way to directly access the individual isolines in the event you require a numerical approach to searching for intersections.
Approach 1
Example Data:
% MATLAB R2018b
x = 0:0.01:1;
y = 0:0.01:1;
[X,Y] = meshgrid(x,y);
Z = sqrt(X.^3+Y); % Placeholder 1
W = sqrt(X.*Y + X.^2 + Y.^(2/3)); % Placeholder 2
Overlay Single Isoline from 2 Contour Plots
Mimicking this answer and using
v = [.5 0.75 .85 1]; % Values of Z to plot isolines
we can visualize these two functions, Z, and W, respectively.
We can overlay the isolines since they share the same (x,y) domain. For example, they both equal 0.8 as displayed below.
val = 0.8; % Isoline value to plot (for Z & W)
Ck = contourc(x,y,Z,[val val]);
Ck2 = contourc(x,y,W,[val val]);
figure, hold on, box on
plot(Ck(1,2:end),Ck(2,2:end),'k-','LineWidth',2,'DisplayName',['Z = ' num2str(val)])
plot(Ck2(1,2:end),Ck2(2,2:end),'b-','LineWidth',2,'DisplayName',['W = ' num2str(val)])
legend('show')
Overlay Multiple Isolines from 2 Contour Plots
We can also do this for more isolines at a time.
v = [1 0.5]; % Isoline values to plot (for Z & W)
figure, hold on, box on
for k = 1:length(v)
Ck = contourc(x,y,Z,[v(k) v(k)]);
Ck2 = contourc(x,y,W,[v(k) v(k)]);
p(k) = plot(Ck(1,2:end),Ck(2,2:end),'k-','LineWidth',2,'DisplayName',['Z = ' num2str(v(k))]);
p2(k) = plot(Ck2(1,2:end),Ck2(2,2:end),'b-','LineWidth',2,'DisplayName',['W = ' num2str(v(k))]);
end
p(2).LineStyle = '--';
p2(2).LineStyle = '--';
legend('show')
Approach 2
Without making it pretty...
% Single Isoline
val = 1.2;
contour(X,Y,Z,val), hold on
contour(X,Y,W,val)
% Multiple Isolines
v = [.5 0.75 .85 1];
contour(X,Y,Z,v), hold on
contour(X,Y,W,v)
It is straightforward to clean these up for presentation. If val is a scalar (single number), then c1 = contour(X,Y,Z,val); and c2 = contour(X,Y,W,val) gives access to the isoline for each contour plot.

PCA biplot - Matlab

Based on to answers
Unable to create an array from a table,
Coloring The Dots in biPlot Chart I wrote the code below.
How can I change the legends to show the clusters = Tb.class (iris species) and how can I convex hull each group?
Code:
clc;
clear;
close all;
Tb = webread('https://datahub.io/machine-learning/iris/r/iris.csv');
clusters = Tb.class;
X = [Tb.sepallength Tb.sepalwidth Tb.petallength Tb.petalwidth ];
Z = zscore(X); % Standardized data
[coefs,score] = pca(Z);
vbls = {'sepallength','sepalwidth','petallength','petalwidth'};
h=biplot(coefs(:,1:2),'Scores',score(:,1:2),'VarLabels',vbls);
hID = get(h, 'tag');
% Isolate handles to scatter points
hPt = h(strcmp(hID,'obsmarker'));
% Identify cluster groups
grp = findgroups(clusters); %r2015b or later - leave comment if you need an alternative
grp(isnan(grp)) = max(grp(~isnan(grp)))+1;
grpID = 1:max(grp);
% assign colors and legend display name
clrMap = lines(length(unique(grp))); % using 'lines' colormap
for i = 1:max(grp)
set(hPt(grp==i), 'Color', clrMap(i,:), 'DisplayName', sprintf('Cluster %d', grpID(i)))
end
% add legend to identify cluster
[~, unqIdx] = unique(grp);
legend(hPt(unqIdx))
1) For the legend you can simply use the cluster names as follows:
unqClusters = unique(clusters);
for i = 1:max(grp)
% Original legend showing "Cluster n"
%set(hPt(grp==i), 'Color', clrMap(i,:), 'DisplayName', sprintf('Cluster %d', grpID(i)))
% New legend showing the flower class name
set(hPt(grp==i), 'Color', clrMap(i,:), 'DisplayName', sprintf('%s', unqClusters{i}))
end
2) For the convex hull part, you could use the contours of the 2D normal distribution.
Here is a code that works on the first two raw PC scores, that you would need to adapt to the standardization performed by the biplot() function.
The code essentially goes over each cluster, computes the mean and covariance of the first two PC values, and uses these values as the parameters of the 2D normal distribution whose PDF is computed on a meshgrid. Finally a level value of the 2D PDF is selected for plotting, which results in an ellipse covering most of the points in the cluster.
%% Convex hulls around each cluster (ellipses based on 2D normal distribution)
% Execution parameters:
% Number of points used for the meshgrid to generate the Normal PDF
resolution = 100;
% Factor of PC range by which to extend the min and max PC values
% on which the normal PDF is computed
% (adjust this value so that the ellipse around the points is drawn completely)
factor = 2;
% Variables to store the location and scale parameters of each cluster
mu = cell(1,3);
Sigma = cell(1,3);
% Do the calculations for each cluster and plot points and ellipses on a single plot
figure
hold on
for g = grpID
% Score (PC) values to analyze
scoregrp = score(grp==g,1:2);
mu{g} = mean(scoregrp);
Sigma{g} = cov(scoregrp);
% PC1 and PC2 values on which to compute the Normal PDF
pcvalues = cell(1,2);
for c = 1:length(pcvalues)
pcrange = range(scoregrp(:,c));
pcmin = min(scoregrp(:,c)) - factor*pcrange;
pcmax = max(scoregrp(:,c)) + factor*pcrange;
pcstep = pcrange / resolution;
pcvalues{c} = pcmin:pcstep:pcmax;
end
% Meshgrid generated from the above PC values
[XPoints, YPoints] = meshgrid(pcvalues{1}, pcvalues{2});
XYPoints = [XPoints(:) YPoints(:)];
npoints = size(XYPoints, 1);
% Variables used repeatedly in the computation of the normal PDF
detSigma = det(Sigma{g});
invSigma = inv(Sigma{g});
% Normal PDF on the meshgrid
% Note: the PDF is computed via a loop instead of matrix operation
% due to memory constraints
pdfnormal = nan(1, npoints);
den = sqrt(2*pi*detSigma);
for i = 1:npoints
point = XYPoints(i,:) - mu{g};
pdfnormal(i) = exp( -0.5 * point * invSigma * point' ) / den;
end
% Reshape the PDF to 2D matrix so that we can plot the contours
pdfnormal2D = reshape(pdfnormal, length(pcvalues{1}), length(pcvalues{2}));
% Value to plot as ellipse where the normal falls at about the value
% of the standard normal at two standard deviations
pdfvalue2Std = pdf('norm', 2);
% Plot!
plot(scoregrp(:,1), scoregrp(:,2), '.', 'Color', clrMap(g,:))
contour(XPoints, YPoints, pdfnormal2D, pdfvalue2Std, 'LineWidth', 2, ...
'Color', clrMap(g,:))
end
xlabel('PC1')
ylabel('PC2')
which generates the following graph:

undo rotation of measured vector components after affine transformation of coordinates

Problem
I have a vector field with three dimensions. Each direction of the vector I have to sample seperately and therefore the three grids are slightly unaligned within the measured field (the sample moves, not the measurement grid).
When I realign my measurements my vector components are no longer orthogonal to each other since each has a different transformation. Because the realignment also has a translation, I think each sample point has a slightly different rotation. Basically I want to rotate my sample points but keep my vector directions and make sure they are orthogonal.
Question
How do I correctly 'unrotate' my vectors?
Example
Example MatLab code (note that in my real data I only want to perform the correction in MatLab, I dont perform the transformation in MatLab, just creating fake data to give an example):
n1=10; n2=10; n3=10; %10x10x10 samples for each direction / vector component measurement
sig=5; %smoothness of measured vector field
vec = struct; A = struct; %define structs
%define measurement grid
[vec(1).X,vec(2).X,vec(3).X] = ndgrid(1:n1,1:n2,1:n3); figure;
for itrans = 1:3
vec(itrans).x = imgaussfilt3(rand(n1,n2,n3), sig);%make random smooth vector field
t = rand(1,3); %random translations
r = rand(1,3)*2*pi/50; %random pitch roll yaw
%seperate rotation matrices
R1 = [1, 0, 0;...
0, cos(r(1)), sin(r(1));...
0,-sin(r(1)), cos(r(1))];
R2 = [cos(r(2)), 0,-sin(r(2));...
0, 1, 0;...
sin(r(2)), 0, cos(r(2))];
R3 = [cos(r(3)), sin(r(3)), 0;...
-sin(r(3)), cos(r(3)), 0;...
0, 0, 1];
%make affine component matrices
T = eye(4); T(1,4) = t(1); T(2,4) = t(2); T(3,4) = t(3);%make translation matrix
R = R1*R2*R3; R(4,4) = 1;%combine rotations
S = eye(4); %no skew or scaling
%compose affine transformation
A(itrans).mat = T*R*S;
%apply transformation to coordinates and plot alignment
X = [vec(1).X(:)'; vec(2).X(:)'; vec(3).X(:)';ones(1,numel(vec(3).X))]; XX = A(itrans).mat*X;
subplot(1,3,itrans); scatter3(vec(1).X(:),vec(2).X(:),vec(3).X(:)); hold on; title('displacement measurement 3'); scatter3(XX(1,:)',XX(2,:)',XX(3,:)', 'r'); legend('original', 'displaced')
end
%apply tranformation to data
for itrans = 1:3
vec(itrans).xtrans = interp3(vec(itrans).x ,XX(1,:)',XX(2,:)',XX(3,:)','cubic',0);
end
%plot new vectors
figure; subplot(1,2,1); quiver3(vec(1).X,vec(2).X,vec(3).X,vec(1).x,vec(2).x,vec(3).x); title('original field')
subplot(1,2,2); quiver3(vec(1).X,vec(2).X,vec(3).X,vec(1).xtrans,vec(2).xtrans,vec(3).xtrans); legend('displaced field')
The three realigned coordinates, each component of the vector field has been translated and rotated slightly differently.
The original field, each component of one vector was not really measured at the same location, which I correct for by transforming the coordinates and then interpolating my measurements.
The transformed field, each component of one vector is no longer really along the axis it represents, and they are no longer orthogonal to each other.
Trying to show the problem in 2d with paint. Each arrow shows a measured component, each cross shows a coordinate system. The two blue arrows are the two components that I measure, the two gray arrows are my result after realigning my measurements, the two orange arrows are what I need after somehow 'unrotating' and combining them.
This isn't a very complete answer, and it probably isn't an answer that merits votes; but, at the time the answer is given, there aren't any others, so I had better post what I know.
If your problem were an academic problem, then it would be a graduate-level academic vector-algebra problem. Someone may already have prefabricated a neat formula for it; but, lacking such a formula, if I were in your place, before rotating, I might try to reset all measurements on a regular orthonormal coordinate grid. (I recently instructed a junior-level electromagnetics course in which I had the students do something vaguely similar, but simpler, to discretize Laplace's equation.) Before resetting, one would need to expand each orthonormal component of the continuous function in a multidimensional Taylor series, then fit the nearby measured values to the undetermined coefficients of the series.... In other words, the analysis before you might be painful.
Splines work on a related idea.
Your comment mentioned an affine analysis. Unfortunately, it is not obvious to me how that would help.
Fortunately, once you had done the analysis and coding, the associated calculations should be pretty quick for the computer to complete.
This answer doesn't answer anything, of course. Given 16 hours or so, I could probably do the analysis; then given another 40 hours or so, I could probably come up with some code—by which time I might discover where in the literature someone had already solved the problem in a neater, simpler way. Good luck.
I calculated the displacement for each sampling point D = X - XX and then calculated the rotation of that field with curl(). That showed a constant rotation for all locations and all directions. So probably I was wrong in assuming that I needed a different correction for each sampling point.
Then its probably possible to invert/transpose the rotation matrix and apply it to the measurement (instead of the coordinates of the measurement). Then add the three resulting components of the three measurements together would then give the corrected data.
I added that code to the loop and plotted the displacement and the curl:
n1=10; n2=10; n3=10; sig=5; vec = struct; A = struct; D = struct; C = struct; [vec(1).X,vec(2).X,vec(3).X] = ndgrid(1:n1,1:n2,1:n3); fig1 = figure; fig2 = figure; fig3 = figure;
for itrans = 1:3
t = rand(1,3); r = rand(1,3)*2*pi/50; vec(itrans).x = imgaussfilt3(rand(n1,n2,n3), sig);
R1 = [1, 0, 0; 0, cos(r(1)), sin(r(1)); 0,-sin(r(1)), cos(r(1))];
R2 = [cos(r(2)), 0,-sin(r(2)); 0, 1, 0; sin(r(2)), 0, cos(r(2))];
R3 = [cos(r(3)), sin(r(3)), 0; -sin(r(3)), cos(r(3)), 0; 0, 0, 1];
%make affine component matrices
T = eye(4); T(1,4) = t(1); T(2,4) = t(2); T(3,4) = t(3); R = R1*R2*R3; R(4,4) = 1;S = eye(4);
A(itrans).mat = T*R*S;
%apply transformation to coordinates and plot alignment
X = [vec(1).X(:)'; vec(2).X(:)'; vec(3).X(:)';ones(1,numel(vec(3).X))]; XX = A(itrans).mat*X;
dcenter = sqrt(sum((X(1:3,:)-5).^2)); dcenter = dcenter./max(dcenter(:));
figure(fig1); subplot(1,3,itrans); scatter3(vec(1).X(:),vec(2).X(:),vec(3).X(:)); hold on; title('displacement measurement 3'); scatter3(XX(1,:)',XX(2,:)',XX(3,:)', 'r'); legend('original', 'displaced')
%calculate and plot displacement due to transformation
D(itrans).d = X-XX;
figure(fig2); subplot(1,3,itrans); q = quiver3(vec(1).X(:)',vec(2).X(:)',vec(3).X(:)',D(itrans).d(1,:),D(itrans).d(2,:),D(itrans).d(3,:)); title(['displacement of each voxel measurement ' num2str(itrans)])
currentColormap = colormap(gca); [~, ~, ind] = histcounts(dcenter, size(currentColormap, 1)); cmap = uint8(ind2rgb(ind(:), currentColormap) * 255); cmap(:,:,4) = 255; cmap = permute(repmat(cmap, [1 3 1]), [2 1 3]); set(q.Head, 'ColorBinding', 'interpolated', 'ColorData', reshape(cmap(1:3,:,:), [], 4).'); set(q.Tail, 'ColorBinding', 'interpolated', 'ColorData', reshape(cmap(1:2,:,:), [], 4).');
%calculate and plot rotational part of displacement field
[C(itrans).x1,C(itrans).x2,C(itrans).x3,C(itrans).av] = curl(reshape(vec(1).X(:)', n1, n2, n3),reshape(vec(2).X(:)', n1, n2, n3),reshape(vec(3).X(:)', n1, n2, n3),reshape(D(itrans).d(1,:), n1, n2, n3),reshape(D(itrans).d(2,:), n1, n2, n3),reshape(D(itrans).d(3,:), n1, n2, n3));
figure(fig3); subplot(1,3,itrans); q = quiver3(vec(1).X(:)',vec(2).X(:)',vec(3).X(:)',C(itrans).x1(:)',C(itrans).x2(:)',C(itrans).x3(:)'); title(['curl of each voxel measurement ' num2str(itrans)])
currentColormap = colormap(gca); [~, ~, ind] = histcounts(dcenter, size(currentColormap, 1)); cmap = uint8(ind2rgb(ind(:), currentColormap) * 255); cmap(:,:,4) = 255; cmap = permute(repmat(cmap, [1 3 1]), [2 1 3]); set(q.Head, 'ColorBinding', 'interpolated', 'ColorData', reshape(cmap(1:3,:,:), [], 4).'); set(q.Tail, 'ColorBinding', 'interpolated', 'ColorData', reshape(cmap(1:2,:,:), [], 4).');
%rotation is constant, so not changed by translation or position of sample?
end
Displacement of vectors (color as a function of how close to the edge).
Rotational part of displacement.
Which is equal everywhere:
unique(C(1).x1(:))
ans =
0.1617
0.1617
0.1617
0.1617
So I think I can just apply R' to each measurement and then add them together as the corrected data.
rotmeas = zeros(3, numel(vec(1).x));
for itrans = 1:3
%apply transformation to coordinates and interpolate
vec(itrans).xtrans = interp3(vec(itrans).x(:) ,XX(1,:)',XX(2,:)',XX(3,:)','cubic',0);
%get inverse rotation
Rinv = A(itrans).mat(1:3,1:3)'
curmeas = vec(itrans).xtrans;
compmeas = zeros(3, numel(vec(1).x));
compmeas(itrans,:) = curmeas;
%apply inverse rotation to spread old component over new axes
rotmeas = rotmeas + Rinv*compmeas;
end
Then just reshape it to an array again.
for itrans = 1:3
vec(itrans).xcor = reshape(rotmeas(itrans,:), n1,n2,n3);
end
Now I think the three data sets are aligned and the vector components are orthogonal and aligned with the new coordinate system. Just not sure how to check that. It seems easy to mix up the different components of the measurements and the dimensions of the rotations.

Nearest point between two clusters Matlab

I have a set of clusters consisting of 3D points. I want to get the nearest two points from each two clusters.
For example: I have 5 clusters C1 to C5 consisting of a 3D points. For C1 and C2 there are two points Pc1 "point in C1" and Pc2 "point in C2" that are the closet two points between the two clusters C1 and C2, same between C1 and C3..C5 and same between C2 and C3..C5 and so on. After that I'll have 20 points representing the nearest points between the different clusters.
The second thing is that I want to connect this points together if the distance between each of them and the other is less than a certain distance "threshold".
So I'm asking if anyone could please advise me
Update:
Thanks Amro for your answer, I've updated it to CIDX=kmeans(X, K,'distance','cityblock', 'replicates',5); to solve the empty cluster error. But another error appeared "pdistmex Out of memory. Type HELP MEMORY for your options." So I've checked your answer here: Out of memory error while using clusterdata in MATLAB and updated your code as below but the problem now is that there is now an indexing error in this code mn = min(min(D(idx1,idx2))); I'm asking if there is a workaround for this error?
Code used:
%function single_linkage(depth,clrr)
X = randn(5000,3);
%X=XX;
% clr = clrr;
K=7;
clr = jet(K);
%// cluster into K=4
K = 7;
%CIDX = kmeans(X,K);
%// pairwise distances
SUBSET_SIZE = 1000; %# subset size
ind = randperm(size(X,1));
data = X(ind(1:SUBSET_SIZE), :);
D = squareform(pdist(data));
subs = 1:size(D,1);
CIDX=kmeans(D, K,'distance','sqEuclidean', 'replicates',5);
centers = zeros(K, size(data,2));
for i=1:size(data,2)
centers(:,i) = accumarray(CIDX, data(:,i), [], #mean);
end
%# calculate distance of each instance to all cluster centers
D = zeros(size(X,1), K);
for k=1:K
D(:,k) = sum( bsxfun(#minus, X, centers(k,:)).^2, 2);
end
%D=squareform(D);
%# assign each instance to the closest cluster
[~,clustIDX] = min(D, [], 2);
%// for each pair of clusters
cpairs = nchoosek(1:K,2);
pairs = zeros(size(cpairs));
dists = zeros(size(cpairs,1),1);
for i=1:size(cpairs,1)
%// index of points assigned to each of the two cluster
idx1 = (clustIDX == cpairs(i,1));
idx2 = (clustIDX == cpairs(i,2));
%// shortest distance between the two clusters
mn = min(min(D(idx1,idx2)));
dists(i) = mn;
%// corresponding pair of points with the minimum distance
[r,c] = find(D(idx1,idx2)==mn);
s1 = subs(idx1); s2 = subs(idx2);
pairs(i,:) = [s1(r) s2(c)];
end
%// filter pairs by keeping only those whose distances is below a threshold
thresh = inf;
cpairs(dist>thresh,:) = [];
%// plot 3D points color-coded by clusters
figure('renderer','zbuffer')
%clr = lines(K);
h = zeros(1,K);
for i=1:K
h(i) = line(X(CIDX==i,1), X(CIDX==i,2), X(CIDX==i,3), ...
'Color',clr(i,:), 'LineStyle','none', 'Marker','.', 'MarkerSize',5);
end
legend(h, num2str((1:K)', 'C%d')) %'
view(3), axis vis3d, grid on
%// mark and connect nearest points between each pair of clusters
for i=1:size(pairs,1)
line(X(pairs(i,:),1), X(pairs(i,:),2), X(pairs(i,:),3), ...
'Color','k', 'LineStyle','-', 'LineWidth',3, ...
'Marker','o', 'MarkerSize',10);
end
What you are asking for sounds similar to what single-linkage clustering does at each step; from the bottoms-up, clusters separated by the shortest distance are combined.
Anyway below is the brute-force way of solving this. I'm sure there are more efficient implementations, but this one is easy to implement.
%// data of 3D points
X = randn(5000,3);
%// cluster into K=4
K = 4;
CIDX = kmeans(X,K);
%// pairwise distances
D = squareform(pdist(X));
subs = 1:size(X,1);
%// for each pair of clusters
cpairs = nchoosek(1:K,2);
pairs = zeros(size(cpairs));
dists = zeros(size(cpairs,1),1);
for i=1:size(cpairs,1)
%// index of points assigned to each of the two cluster
idx1 = (CIDX == cpairs(i,1));
idx2 = (CIDX == cpairs(i,2));
%// shortest distance between the two clusters
mn = min(min(D(idx1,idx2)));
dists(i) = mn;
%// corresponding pair of points with the minimum distance
[r,c] = find(D(idx1,idx2)==mn);
s1 = subs(idx1); s2 = subs(idx2);
pairs(i,:) = [s1(r) s2(c)];
end
%// filter pairs by keeping only those whose distances is below a threshold
thresh = inf; %// use your threshold value instead
cpairs(dists>thresh,:) = [];
%// plot 3D points color-coded by clusters
figure('renderer','zbuffer')
clr = lines(K);
h = zeros(1,K);
for i=1:K
h(i) = line(X(CIDX==i,1), X(CIDX==i,2), X(CIDX==i,3), ...
'Color',clr(i,:), 'LineStyle','none', ...
'Marker','.', 'MarkerSize',5);
end
legend(h, num2str((1:K)', 'C%d')) %'
view(3), axis vis3d, grid on
%// mark and connect nearest points between each pair of clusters
for i=1:size(pairs,1)
line(X(pairs(i,:),1), X(pairs(i,:),2), X(pairs(i,:),3), ...
'Color','k', 'LineStyle','-', 'LineWidth',3, ...
'Marker','o', 'MarkerSize',10);
end
Note that in the above example the data is randomly generated and not very interesting, so it is hard to see the connected nearest points.
Just for fun, here is another result where I simply replaced the min-distance by the max-distance between pair of clusters (similar to complete-linkage clustering), i.e use:
mx = max(max(D(idx1,idx2)));
instead of the previous:
mn = min(min(D(idx1,idx2)));
which shows how we connect the farthest points between each pair of clusters. This visualization is a bit more interesting in my opinion :)

surfnorm function more efficient way Matlab

After constructing the point cloud I want to get the normal of each point and I used the built-in matlab function surfnorm but its takes a lot of processing time. So if anyone could assist me do this a better and more efficient way.
I wonder if the following code would help you. There are three steps here.
Create 500 randomly spaced points (x,y), and compute a corresponding value z (the height of the surface) for which I chose a sinc like function
Resample the random points using the TriScatteredInterp function - this permits me to obtain points on an evenly sampled grid that "roughly correspond" to the initial surface
Compute the normal to "some points" on that grid (since there are 480x640 points, computing the normal at every point would just create an impossibly dense "forest of vectors"; by sampling "every 10th point" you can actually see what you are doing
The code I used was as follows:
randomX = rand(1,500);
randomY = rand(1,500);
r = 5*sqrt(randomX.^2 + randomY.^2);
randomZ = sin(r) ./ r;
% resample the data:
[xx yy] = meshgrid(linspace(0,1,640), linspace(0,1,480));
F = TriScatteredInterp(randomX(:), randomY(:), randomZ(:));
zz = F(xx, yy);
%% at each point, the normal is cross product of vectors to neighbors
xyz=reshape([xx yy zz],[size(xx) 3]);
xv = 10:30:479; yv = 10:30:639; % points at which to compute normals
dx = xyz(xv, yv+1, :) - xyz(xv, yv, :);
dy = xyz(xv+1, yv, :) - xyz(xv, yv, :);
normVecs = cross(dx, dy); % here we compute the normals.
normVecs = normVecs ./ repmat(sqrt(sum(normVecs.^2, 3)), [1 1 3]);
figure;
quiver3(xx(xv, yv), yy(xv, yv), zz(xv, yv), ...
normVecs(:,:,1), normVecs(:,:,2), normVecs(:,:,3));
axis equal
view([56 22]);
And the resulting plot: