How do i correctly fit a line and force it through a particular point in an image? - matlab

I am to fit planes through various points in an image, but I am having issues with forcing the line through a particular point in the image. This happens particularly when the line is 90 degrees.
My code is as follows:
I = [3 3 3 3 3 2 2
3 3 3 3 2 2 2
3 3 3 3 2 2 2
3 3 1 2 2 2 2
1 1 1 2 2 2 2
1 1 1 1 1 2 2
1 1 1 1 1 1 1];
% force the line through point p
p = [3,3];
% points to fit plane through
edgeA = [3,3.5; 3,4; 2.5,4; 2,4; 1.5,4];
edgeB = [3.5,3; 4,3; 4.5,3; 5,3];
% fit a plane through p and edgeA
xws = [p(2), edgeA(:,2)']';
yws = [p(1), edgeA(:,1)']';
Cws = [xws ones(size(xws))];
dws = yws;
Aeqws = [p(2) 1];
beqws = [p(1)];
planefitA = lsqlin(Cws ,dws,[],[],Aeqws, beqws);
% fit a plane through p and edgeB
xwn = [p(2), edgeB(:,2)']';
ywn = [p(1), edgeB(:,1)']';
Cwn = [xwn ones(size(xwn))];
dwn = ywn;
Aeqwn = [p(2) 1];
beqwn = [p(1)];
planefitB = lsqlin(Cwn ,dwn,[],[],Aeqwn, beqwn);
%%%%% plot the fitted planes:
xAxis = linspace(0, size(I, 2), 12);
%obtain linear curve
fA = planefitA(1)*xAxis + planefitA(2);
fB = planefitB(1)*xAxis + planefitB(2);
%plot the fitted curve
RI = imref2d(size(I),[0 size(I, 2)],[0 size(I, 1)]);
figure, imshow(I, RI, [], 'InitialMagnification','fit')
grid on;
hold on;
plot(xAxis,fA, 'Color', 'b', 'linewidth', 2);
plot(xAxis,fB, 'Color', 'r', 'linewidth', 2);
All the points in edgeB fall on a 90 degrees line. However, the function ends up fitting a wrong line through those points. I know this because using
planefitB = polyfit([p(2), edgeB(:,2)'], [p(1), edgeB(:,1)'], 1);
works for this particular line but the problem is that i have these process repeated so many times at different locations in my image, hence i do not know how to suggest polyfit when the line would be 90 degrees.
Please, any ideas/suggestions on how i could make this work? Many thanks.

This amounts to the least squares solution of only the angle of the line. The offset is fixed by the fact that it has to go through (3,3). The easiest way to express this is by offsetting your data points by the known crossing. That is, subtract (3,3) from your data points, and fit the best m for y=mx, the b being fixed to 0.
For the non-vertical case, you can use a classic least-squares formulation, but don't augment the constant 1 into the Vandermonde matrix:
slope = (edgeA(:,2) - p(2)) \ (edgeA(:,1) - p(1));
This gives exactly the same answer as your constrained lsq solution.
Now for the vertical line: A non-vertical line can be expressed in the standard functional form of y=mx, where the least squares formulation implicitly assumes an independent and a dependent variable. A vertical line doesn't follow that, so the only general choice is a "Total Least Squares" formulation, where errors in both variables are considered, rather than just the residuals in the dependent (y) variable.
The simplest way to write this is to choose a and b to minimize ax - by, in the least squares sense. [x_k -y_k]*[a b].' should be as close to a zero vector as possible. This is the vector closest to the null space of the [x -y] matrix, which can be computed with the svd. Swapping columns and fudging signs lets us just use svd directly:
[u s v] = svd(bsxfun(#minus, edgeA, p));
The last column of v is the closest to the null space, so mapping back to your x/y definitions, (edgeA-p)*v(:,2) is the line, so the y multiplier is in the top position, and the x in the lower, with a sign flip. To convert to y=mx form, just divide:
slope = -v(2,2)/v(1,2);
Note that this answer will be quite a bit different than the normal least squares answer, since you are treating the residuals differently. Also, the final step of computing "slope" won't work in the vertical case for the reasons we've already discussed (it produces Inf), so you are probably better off leaving the line as a normalized 2-vector, which won't have any corner cases.

Related

Plotting circles with complex numbers in MATLAB

I want to make a figure in MATLAB as described in the following image
What I did is the following:
x = [1 2 3];
y = [2 2 4];
radius = [1 1.2 2.2];
theta = [-pi 0 pi];
figure;
scatter(x,y,radius)
How do I add an angle theta to the plot to represent a complex number z = radius.*exp(1j*theta) at every spacial coordinates?
Technically speaking, those are only circles if x and y axes are scaled equally. That is because scatter always plots circles, independently of the scales (and they remain circles if you zoom in nonuniformly. + you have the problem with the line, which should indicate the angle...
You can solve both issues by drawing the circles:
function plotCirc(x,y,r,theta)
% calculate "points" where you want to draw approximate a circle
ang = 0:0.01:2*pi+.01;
xp = r*cos(ang);
yp = r*sin(ang);
% calculate start and end point to indicate the angle (starting at math=0, i.e. right, horizontal)
xt = x + [0 r*sin(theta)];
yt = y + [0 r*cos(theta)];
% plot with color: b-blue
plot(x+xp,y+yp,'b', xt,yt,'b');
end
having this little function, you can call it to draw as many circles as you want
x = [1 2 3];
y = [2 2 4];
radius = [1 1.2 2.2];
theta = [-pi 0 pi];
figure
hold on
for i = 1:length(x)
plotCirc(x(i),y(i),radius(i),theta(i))
end
I went back over scatter again, and it looks like you can't get that directly from the function. Hopefully there's a clean built-in way to do this, and someone else will chime in with it, but as a backup plan, you can just add the lines yourself.
You'd want a number of lines that's the same as the length of your coordinate set, from the center point to the edge at the target angle, and fortunately 'line' does multiple lines if you feed it a matrix.
You could just tack this on to the end of your code to get the angled line:
x_lines = [x; x + radius.*cos(theta)];
y_lines = [y; y + radius.*sin(theta)];
line(x_lines, y_lines, 'Color', 'b')
I had to assign the color specifically, since otherwise 'line' makes each new line cycle through the default colors, but that also means you could easily change the line color to stand out more. There's also no center dot, but that'd just be a second scatter plot with tiny radius. Should plot most of what you're looking for, at least.
(My version of Matlab is old enough that scatter behaves differently, so I can only check the line part, but they have the right length and location.)
Edit: Other answer makes a good point on whether scatter is appropriate here. Probably better to draw the circle too.

How to plot a Diagonal Histogram in Matlab

Given scatter data, or a matrix, I would like to generate a nice plot such as the one shown below, with all 3 histograms and a colored matrix. I'm specifically interested in the diagonal histogram, which ideally, would correspond to the diagonals of a matrix:
Source figure: www.med.upenn.edu/mulab/jpst.html
The existing command scatterhist is not that powerful to generate this type of graph. Any ideas?
Thanks!
EDIT:
Following #Cris Luengo's hints, I came up with the following code which does some first work at the inclined histogram: WORK IN PROGRESS (HELP WELCOME)!!
b = [0 1 2 3 4 5 6 7 8 9 10];
h = [0.33477 0.40166 0.20134 0.053451 0.008112 0.000643 2.7e-05 0 0 0 0];
wid = 0.25; bb = sort([b-wid b-wid b+wid b+wid]);
kk = [zeros(numel(h),1) h(:) h(:) zeros(numel(h),1)];
kk = reshape(kk',[1,numel(kk)]);
pp=patch(bb,kk,'b');axis([-.5 5 0 .5])
set(gca,'CameraUpVector',[-1,.08,0]);axis square
EDIT 2: Using rotation
phi = pi/4;
R = [cos(phi),-sin(phi);sin(phi),cos(phi)];
rr = [bb' kk'] * R;
bb = rr(:,1); kk = rr(:,2);
patch(bb,kk,'b'); axis([-.5 3 -4 .5])
Here is a recipe to plot the diagonal histogram, if you can do that I’m sure you can figure out the rest too.
Compute the histogram, the bin counts are h, the bin centers are b.
Build a coordinate matrix, attaching the coordinates of a point on the x-axis at the left and right ends of the histogram:
coords = [b(:),h(:)];
coords = [coord;b(end),0;b(1),0];
Using patch you can now plot the histogram as follows:
patch(coords(1,:),coords(2,:));
To plot a rotated histogram you can simply multiply the coords matrix with a rotation matrix, before using patch:
phi = pi/4;
R = [cos(phi),-sin(phi);sin(phi),cos(phi)];
coords = R * coords;
You might need to shift the plot to place it at the right location w.r.t. the other elements.
I recommend that you place all these graphic elements in the same axes object; you can set the axes’ visibility to 'off' so that it works only as a canvas for the other elements.
It will be a bit of work to get everything placed as in the plot you show, but none of it is difficult. Use the low-level image, line,patch and text to place those types of elements, don’t try to use the higher-level plotting functions such as plot, they don’t provide any benefits over the low-level ones in this case.

How can I plot filled rectangles as a backdrop for a desired target in MATLAB?

I have two datasets, one of which is a target position, and the other is the actual position. I would like to plot the target with a +/- acceptable range and then overlay with the actual. This question is only concerning the target position however.
I have unsuccessfully attempted the built in area, fill, and rectangle functions. Using code found on stackoverflow here, it is only correct in certain areas.
For example
y = [1 1 1 2 1 1 3 3 1 1 1 1 1 1 1]; % Target datum
y1 = y+1; %variation in target size
y2 = y-1;
t = 1:15;
X=[t,fliplr(t)]; %create continuous x value array for plotting
Y=[y1,fliplr(y2)]; %create y values for out and then back
fill(X,Y,'b');
The figure produced looks like this:
I would prefer it to be filled within the red boxes drawn on here:
Thank you!
If you would just plot a function y against x, then you could use a stairs plot. Luckily for us, you can use the stairs function like:
[xs,ys] = stairs(x,y);
to create the vectors xs, ys which generate a stairs-plot when using the plot function. We can now use these vectors to generate the correct X and Y vectors for the fill function. Note that stairs generates column vectors, so we have to transpose them first:
y = [1 1 1 2 1 1 3 3 1 1 1 1 1 1 1]; % Target datum
y1 = y+1; %variation in target size
y2 = y-1;
t = 1:15;
[ts,ys1] = stairs(t,y1);
[ts,ys2] = stairs(t,y2);
X=[ts.',fliplr(ts.')]; %create continuous x value array for plotting
Y=[ys1.',fliplr(ys2.')]; %create y values for out and then back
fill(X,Y,'b');
Again, thank you hbaderts. You answered my question perfectly, however when I applied it to the large data set I needed for, I obtained this image
https://dl.dropboxusercontent.com/u/37982601/stair%20fill.png
I think it is because the fill function connects vertices to fill?
In any case, for the potential solution of another individual, combined your suggested code with the stair function and used the area function.
By plotting them on top of one another and setting the color of the lower area to be white, it appears as the rectangular figures I was after.
%sample code. produces image similar to o.p.
y = [1 1 1 2 1 1 3 3 1 1 1 1 1 1 1];
y1 = y+1;
y2 = y-1;
t = 1:15;
[ts,ys1] = stairs(t,y1);
[ts,ys2] = stairs(t,y2);
area(ts,ys1,'FaceColor','b','EdgeColor','none')
hold on
area(ts,ys2,'FaceColor','w','EdgeColor','none')
https://dl.dropboxusercontent.com/u/37982601/stair%20area.png
Thanks again for your help and for pointing me in the right direction!

drow cumulative distribution function in matlab

I have two vectors of the same size. The first one can have any different numbers with any order, the second one is decreasing (but can have the same elements) and consists of only positive integers. For example:
a = [7 8 13 6];
b = [5 2 2 1];
I would like to plot them in the following way: on the x axis I have points from a vector and on the y axis I have the sum of elements from vector b before this points divided by the sum(b). Therefore I will have points:
(7; 0.5) - 0.5 = 5/(5+2+2+1)
(8; 0.7) - 0.7 = (5+2)/(5+2+2+1)
(13; 0.9) ...
(6; 1) ...
I assume that this explanation might not help, so I included the image
Because this looks to me as a cumulative distribution function, I tried to find luck with cdfplot but with no success.
I have another option is to draw the image by plotting each line segment separately, but I hope that there is a better way of doing this.
I find the values on the x axis a little confusing. Leaving that aside for the moment, I think this does what you want:
b = [5 2 2 1];
stairs(cumsum(b)/sum(b));
set(gca,'Ylim',[0 1])
And if you really need those values on the x axis, simply rename the ticks of that axis:
a = [7 8 13 6];
set(gca,'xtick',1:length(b),'xticklabel',a)
Also grid on will add grid to the plot

How do I generate pair of random points in a circle using Matlab?

Let a circle of known radius be plotted in MATLAB.
Assume a pair of random points whose location has to be determined in terms of coordinates (x1,y1) (x2,y2)..(xn,yn). Pairs should be close to each other. For example T1 and R1 should be near.
As shown in figure, there are four random pairs (T1,R1)..(T4,R4).
There coordinates need to be determined wrt to center (0,0).
How can I generate this in MATLAB?
The simplest approach to pick a point from a uniform distribution over a circle with reduce R is using Gibbs sampling. Here is the code:
function [x y] = circular uniform (R)
while true
x = 2*R*rand() - R
y = 2*R*rand() - R
if (x*x + y*y) > R*R
return
end
end
The loop runs 4/π times on average.
(Complete edit after the question was edited).
To complete this task, I think that you need to combine the different approaches that have been mentioned before your edit:
To generate the centers T1,T2,T3,... in the green torus, use the polar coordinates. (Edit: this turned out to be wrong, rejection sampling must also be used here, otherwise, the distribution is not uniform!)
To generate the points R1,R2,R3,... in the circle around T1,T2,T3,... but still in the torus, use the rejection sampling.
With these ingredients, you should be able to do everything you need. Here is the code I wrote:
d=859.23;
D=1432.05;
R=100;
N=400;
% Generate the angle
theta = 2*pi*rand(N,1);
% Generate the radius
r = d + (D-d)*rand(N,1);
% Get the centers of the circles
Tx = r.*cos(theta);
Ty = r.*sin(theta);
% Generate the R points
Rx=zeros(N,1);
Ry=zeros(N,1);
for i=1:N
while true
% Try
alpha = 2*pi*rand();
rr = R*rand();
Rx(i) = Tx(i) + rr*cos(alpha);
Ry(i) = Ty(i) + rr*sin(alpha);
% Check if in the correct zone
if ( (Rx(i)*Rx(i) + Ry(i)*Ry(i) > d*d) && (Rx(i)*Rx(i) + Ry(i)*Ry(i) < D*D) )
break
end
end
end
% Display
figure(1);
clf;
angle=linspace(0,2*pi,1000);
plot( d*cos(angle), d*sin(angle),'-b');
hold on;
plot( D*cos(angle), D*sin(angle),'-b');
for i=1:N
plot(Tx(i),Ty(i),'gs');
plot(Rx(i),Ry(i),'rx');
plot([Tx(i) Rx(i)],[Ty(i) Ry(i)],'-k');
end
hold off;
let R be radious of (0;0) centered circle.
(x,y) : x^2+y^2<=R^2 (LE) to be inside the circle
x = rand()*2*R - R;
y should be in interval (-sqrt(R^2 - x^2);+sqrt(R^2 - x^2))
so, let it be
y = rand()*sqrt(R^2 - x^2)*2-sqrt(R^2 - x^2);
Hope, that's right, i have no matlab to test.
Hope, you'll manage to find close pairs your self.
Ok, i'll spend a bit more time for a hint.
To find a random number k in interval [a,b] use
k = rand()*(b-a)+a
Now it should really help if i still remember the matlab syntaxis. Good luck.
Here is a low quality solution that is very easy to use with uniformly distributed points. Assuming the number of points is small efficiency should not be a concern, if you want better quality you can use something more powerfull than nearest neighbor:
While you have less than n points: Generate a random point
If it is in the circle, store it else go to step 1
While there are unpaired points: check which point is nearest to the first unpaired point, make them a pair
As a result most pairs should be good, but some can be really really bad. I would recommend you to try it and perhaps add a step 4 with k-opt or some other local search if required. And if you really have little points (e.g. less than 20) you can of course just calculate all distances and find the optimum matching.
If you don't really care about the uniform distribution, here is an even easier solution:
While you have less than n points: Generate a random point
If it is in the circle, store it else go to step 1
For each of these points, generate a point near it
If it is in the circle, store it else go to step 3
Generate the random points as #PheuVerg suggested (with a slight vectorized tweak)
n = 8; %must be even!
x = rand(n, 1)*2*R - R;
y = rand(n, 1).*sqrt(R^2 - x.^2).*2-sqrt(R^2 - x.^2);
Then use kmeans clustering to get n/2 centers
[~ c] = kmeans([x y], n/2);
now you have to loop through each center and find it's distance to each point
dists = zeros(n, n/2);
for cc = 1:n/2
for pp = 1:n
dists(pp, cc) = sqrt((c(cc,1) - x(pp))^2 + (c(cc,2) - y(pp))^2);
end
end
now you must find the smallest 2 values for each columns of dists
[sorted, idx] = sort(dists);
so now the top two rows of each column are the two nearest points. But there could be clashes! i.e. points that are nearest to two different centers. So for repeated values you have to loop through and choose swap for the point that will give you the smallest extra distance.
Example data:
x =
0.7894
-0.7176
-0.5814
0.0708
0.5198
-0.2299
0.2245
-0.8941
y =
-0.0800
-0.3339
0.0012
0.9765
-0.4135
0.5733
-0.1867
0.2094
sorted =
0.1870 0 0 0.1555
0.2895 0.5030 0.5030 0.2931
0.3145 1.1733 0.6715 0.2989
1.0905 1.1733 0.7574 0.7929
1.1161 1.2326 0.8854 0.9666
1.2335 1.2778 1.0300 1.2955
1.2814 1.4608 1.2106 1.3051
1.4715 1.5293 1.2393 1.5209
idx =
5 4 6 3
7 6 4 2
1 3 3 8
6 7 8 6
3 8 7 7
2 1 2 4
4 5 1 5
8 2 5 1
So now it's clear that 5 and 7 are pairs, and that 3 and 2 are pairs. But 4 and 6 are both repeated. (in this case it is clear that they are pairs too I guess!) but what I would suggest is to leave point 4 with center 2 and point 6 with center 3. Then we start at column 2 and see the next available point is 8 with a distance of 1.2326. This would leave point 1 paired with point 6 but then it's distance from the center is 1.2106. Had we paired point 6 with 8 and point 4 with 1 we would have got distances of 0.7574 and 1.2778 respectively which is actually less total distance. So finding 'close' pairs is easy but finding the set of pairs with the globally smallest minimum is hard! This solutions gets you something decent quite easily but fi you need the global best then I'm afraid you have quite a bit of work to do still :(
Finally let me add some visualisation. First lets (manually) create a vector that shows which points are paired:
I = [1 2 2 1 3 4 3 4];
Remember that that will depend on your data! Now you can plot is nicely like this:
gscatter(x, y, I)
Hope this gets you close and that you can eliminate the manual pairing of mine at the end by yourself. It shouldn't be too hard to get a crude solution.