MATLAB calculation on specific interval, shifted across data. - matlab

So, I'm working with a spread sheet in which each row has four important columns: x position (0-7000 units), y position (0-7000 units), theta angle (0-90), and phi angle (0-360). I have written the script I need to convert the theta and phi into Cartesian coordinates, then to calculate the eigenvalues for the entire data set as a whole, with no relation to position.
Now, what I'd like to do, is take a section, say 200 units wide, starting at the left of the (x,y) spread, and do the same calculation for all the data that falls into that area (x = 0 to 200), and record the eigenvalues at the center of that section (x=100), then shift the area say, 20 units to the right and repeat, and so on until the 200 unit wide area has shifted entirely across the 7000 unit spread.
Basically, I'm looking to do a sort of moving average where the eigenvalue calculation is my filter, and the width of the area of interest determines the degree of smoothing. I just haven't figured out how to write a loop that advances the 200 unit wide area of interest and can spit out a value at the center of it's current location.
I can supply the code I have to calculate Cartesian components and eigenvalues, although I don't think it's very relevant to what I'm trying to do. This mostly seems to be an issue of having no idea what to search for on the interwebs, so if someone can even point me in the right direction I'd appreciate it.

Hope the below code segment will give you an idea.
clc; clear all;
in = rand(20,1); % assume a 20*1 matrix
w = 5; % range from middle
j = 2; % jump
for i = 1+w:j:size(in,1)-w
in(i,2) = mean(in(i-w:i+w,1));
fprintf('\ni = %d\trange = %d:%d\tavg = %f', i, i-w, i+w, in(i,2));
end
Following is the output.
i = 6 range = 1:11 avg = 0.528908
i = 8 range = 3:13 avg = 0.501255
i = 10 range = 5:15 avg = 0.531217
i = 12 range = 7:17 avg = 0.557450
i = 14 range = 9:19 avg = 0.570374

Related

How do I divide a bounding box into sub boxes in Matlab

I have a bound box and points located inside it in different areas and positions.
I want to divide that box into sub boxes according to the dividing point DP[x,y] into box [ax ay bx by] that may locate close to each other.
Here are examples for illustrating the idea:
I tried to calculate and make it by my hand but they are hundreds, I stuck. Is there any Matlab function to do that?
My question is: How to divide a bounding box into sub boxes according to the specific points?
I used the function point2bbox described here, but it does not a suitable solution in this case because the points are not in the corners of the box.
Here is an example of what I want to do:
bound box = [341 91 24 74]
x = 341, y = 91, width = 24 , height = 74
DP1(349,49) , DP2 (360,70)
points to calculated are: a1(349,91),a2(350,91),a2(360,91), and a3(361,91)
The corners points of big box are: A(341,91), B(365,91), C(341,17), and D(365,17)
bbox1 = [341 91 8 74], bbox2 = [350 91 10 74], and bbox3 =[361 91 4 74].
see the picture below for more clarifying:
Could you suggest any idea to do that, please? I appreciate any help.
Here are the solution steps of my question to divide the Big box to three boxes. Maybe it could help another one.
%% Big box values in [ax,ay,bx,by]
bound = [341,91 ,365,17];
%% Two Divide Points are: [349,49,360,70]in form [x1,y1,x2,y2]
% store as struct
DP =struct('dividePoint1',{349,49},'dividePoint2',{360,70});
DP2 = [DP(1,1).dividePoint1;DP(1,1).dividePoint2];
DP1x = DP(1,1).dividePoint1;
DP1y = DP(1,2).dividePoint1;
DP2x = DP(1,1).dividePoint2;
DP2y = DP(1,2).dividePoint2;
%% Put the two points in one vector
DPmat = [DP1x DP1y;DP2x DP2y];
%% Sort Points
DPSorted = sort(DPmat,'ascend');
%% convert bound to 4 values [ax,ay,bx,by]
axBound =bound(1);ayBound = bound(2);bxBound = bound(3);byBound = bound(4);
%% Initial empty x-axis value
XsValues = {};
%% loop to divide two values to four values DP1,DP1+1,DP2,DP2+1
for k = 1:(numel(DP2))
boxes = [DPSorted(k,1),DPSorted(k,1)+1];
XsValues = [XsValues;boxes];
end
%% rearrang the points
% convert x-axis values to matrix
xBoxes = cell2mat(XsValues);
xValues = [xBoxes(1,:),xBoxes(2,:)];
subBox1 = [axBound,ayBound,xValues(1),byBound];
subBox2 = [xValues(2),ayBound,xValues(3),byBound];
subBox3 = [xBoxes(4),ayBound,bxBound,byBound];
%% all subBoxes in one matrix
AllBoxes = [subBox1;subBox2;subBox3];
I appreciate all of your help.
If it need improve you can help us also.
Well, another in my opinion clearer option is using mat2cell: https://www.mathworks.com/help/matlab/ref/mat2cell.html
Suppose your matrix origMat is a 22x74 matrix, that you want to split it to three matrices sized 8x74, 10x74 and 4x74 - as in your example.
subboxes = mat2cell(origMat, [8,10,4]); % This gives you 3x1 cell array containing your matrices 8x74, 10x74 and 4x74.
If the question is how to get those numbers 8, 10 and 4, well, in your example, you would simply use DP1(1)-A(1); DP2(1)-DP1(1); B(1)-DP2(1).
Now, for a bit more general case. Suppose you have a matrix origMat. We have DP = L*2 array of division points on the matrix, and we want to divide original matrix to smaller boxes both horizontally and vertically according to those division points. In your example you would also split matrix horizontally to 21, 21 and 32 points from top to bottom. This solution ignores top left corner point position of the original matrix as it is easy to shift all points by some offset if required.
origMat = rand(74,22); %74 row 22 col matrix.
DP = [21,8; 42,18]; % 2 division points at row 21, col 8 and row 42, col 18.
[S1, S2] = size(origMat);
SDPy = sort(DP(:,1));
SDPx = sort(DP(:,2));
% widths are to first division point, then distances between division points, then from last division point to the edge of original matrix.
widths = [SDPx(1), SDPx(2:end)-SDPx(1:end-1), S2-SDPx(end)];
heights = [SDPy(1), SDPy(2:end)-SDPy(1:end-1), S1-SDPy(end)];
subboxes = mat2cell(origMat, heights, widths);
Duplicates in x or y will lead to some 0xN or Nx0 matrices. Remove duplicated sorted entries or points at 0 or S1/S2 in that direction to avoid that if it is undesirable.
If you have division points beyond the edges of original matrix, this code won't work. How to handle points beyond the edges mainly depends what you want to do:
If you want to remove problematic points entirely, you should check DP list and delete rows where x or y are out of bounds.
If you want to keep division in the ok dimension and just ignore the out of bounds part, remove all sorted elements that are out of bounds.
Differences are mainly conceptual - do you want to divide some internal small bounding box (inside a much larger image) only by points inside it, or also by coordinates outside? This depends on application.

Fourier transform for fiber alignment

I'm working on an application to determine from an image the degree of alignment of a fiber network. I've read several papers on this issue and they basically do this:
Find the 2D discrete Fourier transform (DFT = F(u,v)) of the image (gray, range 0-255)
Find the Fourier Spectrum (FS = abs(F(u,v))) and the Power Spectrum (PS = FS^2)
Convert spectrum to polar coordinates and divide it into 1º intervals.
Calculate number-averaged line intensities (FI) for each interval (theta), that is, the average of all the intensities (pixels) forming "theta" degrees with respect to the horizontal axis.
Transform FI(theta) to cartesian coordinates
Cxy(theta) = [FI*cos(theta), FI*sin(theta)]
Find eigenvalues (lambda1 and lambda2) of the matrix Cxy'*Cxy
Find alignment index as alpha = 1 - lamda2/lambda1
I've implemented this in MATLAB (code below), but I'm not sure whether it is ok since point 3 and 4 are not really clear for me (I'm getting similar results to those of the papers, but not in all cases). For instance, in point 3, "spectrum" is referring to FS or to PS?. And in point 4, how should this average be done? are all the pixels considered? (even though there are more pixels in the diagonal).
rgb = imread('network.tif');%513x513 pixels
im = rgb2gray(rgb);
im = imrotate(im,-90);%since FFT space is rotated 90º
FT = fft2(im) ;
FS = abs(FT); %Fourier spectrum
PS = FS.^2; % Power spectrum
FS = fftshift(FS);
PS = fftshift(PS);
xoffset = (513-1)/2;
yoffset = (513-1)/2;
% Avoid low frequency points
x1 = 5;
y1 = 0;
% Maximum high frequency pixels
x2 = 255;
y2 = 0;
for theta = 0:pi/180:pi
% Transposed rotation matrix
Rt = [cos(theta) sin(theta);
-sin(theta) cos(theta)];
% Find radial lines necessary for improfile
xy1_rot = Rt * [x1; y1] + [xoffset; yoffset];
xy2_rot = Rt * [x2; y2] + [xoffset; yoffset];
plot([xy1_rot(1) xy2_rot(1)], ...
[xy1_rot(2) xy2_rot(2)], ...
'linestyle','none', ...
'marker','o', ...
'color','k');
prof = improfile(F,[xy1_rot(1) xy2_rot(1)],[xy1_rot(2) xy2_rot(2)]);
i = i + 1;
FI(i) = sum(prof(:))/length(prof);
Cxy(i,:) = [FI(i)*cos(theta), FI(i)*sin(theta)];
end
C = Cxy'*Cxy;
[V,D] = eig(C)
lambda2 = D(1,1);
lambda1 = D(2,2);
alpha = 1 - lambda2/lambda1
Figure: A) original image, B) plot of log(P+1), C) polar plot of FI.
My main concern is that when I choose an artificial image perfectly aligned (attached figure), I get alpha = 0.91, and it should be exactly 1.
Any help will be greatly appreciated.
PD: those black dots in the middle plot are just the points used by improfile.
I believe that there are a couple sources of potential error here that are leading to you not getting a perfect alpha value.
Discrete Fourier Transform
You have discrete imaging data which forces you to take a discrete Fourier transform which inevitably (depending on the resolution of the input data) have some accuracy issues.
Binning vs. Sampling Along a Line
The way that you have done the binning is that you literally drew a line (rotated by a particular angle) and sampled the image along that line using improfile. Using improfile performs interpolation of your data along that line introducing yet another potential source of error. The default is nearest neighbor interpolation which in the example shown below can cause multiple "profiles" to all pick up the same points.
This was with a rotation of 1-degree off-vertical when technically you'd want those peaks to only appear for a perfectly vertical line. It is clear to see how this sort of interpolation of the Fourier spectrum can lead to a spread around the "correct" answer.
Data Undersampling
Similar to Nyquist sampling in the Fourier domain, sampling in the spatial domain has some requirements as well.
Imagine for a second that you wanted to use 45-degree bin widths instead of the 1-degree. Your approach would still sample along a thin line and use that sample to represent 45-degrees worth or data. Clearly, this is a gross under-sampling of the data and you can imagine that the result wouldn't be very accurate.
It becomes more and more of an issue the further you get from the center of the image since the data in this "bin" is really pie wedge shaped and you're approximating it with a line.
A Potential Solution
A different approach to binning would be to determine the polar coordinates (r, theta) for all pixel centers in the image. Then to bin the theta components into 1-degree bins. Then sum all of the values that fall into that bin.
This has several advantages:
It removes the undersampling that we talked about and draws samples from the entire "pie wedge" regardless of the sampling angle.
It ensures that each pixel belongs to one and only one angular bin
I have implemented this alternate approach in the code below with some false horizontal line data and am able to achieve an alpha value of 0.988 which I'd say is pretty good given the discrete nature of the data.
% Draw a bunch of horizontal lines
data = zeros(101);
data([5:5:end],:) = 1;
fourier = fftshift(fft2(data));
FS = abs(fourier);
PS = FS.^2;
center = fliplr(size(FS)) / 2;
[xx,yy] = meshgrid(1:size(FS,2), 1:size(FS, 1));
coords = [xx(:), yy(:)];
% De-mean coordinates to center at the middle of the image
coords = bsxfun(#minus, coords, center);
[theta, R] = cart2pol(coords(:,1), coords(:,2));
% Convert to degrees and round them to the nearest degree
degrees = mod(round(rad2deg(theta)), 360);
degreeRange = 0:359;
% Band pass to ignore high and low frequency components;
lowfreq = 5;
highfreq = size(FS,1)/2;
% Now average everything with the same degrees (sum over PS and average by the number of pixels)
for k = degreeRange
ps_integral(k+1) = mean(PS(degrees == k & R > lowfreq & R < highfreq));
fs_integral(k+1) = mean(FS(degrees == k & R > lowfreq & R < highfreq));
end
thetas = deg2rad(degreeRange);
Cxy = [ps_integral.*cos(thetas);
ps_integral.*sin(thetas)]';
C = Cxy' * Cxy;
[V,D] = eig(C);
lambda2 = D(1,1);
lambda1 = D(2,2);
alpha = 1 - lambda2/lambda1;

How to create random points alongside a complex polyline?

I would like to populate random points on a 2D plot, in such a way that the points fall in proximity of a "C" shaped polyline.
I managed to accomplish this for a rather simple square shaped "C":
This is how I did it:
% Marker color
c = 'k'; % Black
% Red "C" polyline
xl = [8,2,2,8];
yl = [8,8,2,2];
plot(xl,yl,'r','LineWidth',2);
hold on;
% Axis settings
axis equal;
axis([0,10,0,10]);
set(gca,'xtick',[],'ytick',[]);
step = 0.05; % Affects point quantity
coeff = 0.9; % Affects point density
% Top Horizontal segment
x = 2:step:9.5;
y = 8 + coeff*randn(size(x));
scatter(x,y,'filled','MarkerFaceColor',c);
% Vertical segment
y = 1.5:step:8.5;
x = 2 + coeff*randn(size(y));
scatter(x,y,'filled','MarkerFaceColor',c);
% Bottom Horizontal segment
x = 2:step:9.5;
y = 2 + coeff*randn(size(x));
scatter(x,y,'filled','MarkerFaceColor',c);
hold off;
As you can see in the code, for each segment of the polyline I generate the scatter point coordinates artificially using randn.
For the previous example, splitting the polyline into segments and generating the points manually is fine. However, what if I wanted to experiment with a more sophisticated "C" shape like this one:
Note that with my current approach, when the geometric complexity of the polyline increases so does the coding effort.
Before going any further, is there a better approach for this problem?
A simpler approach, which generalizes to any polyline, is to run a loop over the segments. For each segment, r is its length, and m is the number of points to be placed along that segment (it closely corresponds to the prescribed step size, with slight deviation in case the step size does not evenly divide the length). Note that both x and y are subject to random perturbation.
for n = 1:numel(xl)-1
r = norm([xl(n)-xl(n+1), yl(n)-yl(n+1)]);
m = round(r/step) + 1;
x = linspace(xl(n), xl(n+1), m) + coeff*randn(1,m);
y = linspace(yl(n), yl(n+1), m) + coeff*randn(1,m);
scatter(x,y,'filled','MarkerFaceColor',c);
end
Output:
A more complex example, using coeff = 0.4; and xl = [8,4,2,2,6,8];
yl = [8,6,8,2,4,2];
If you think this point cloud is too thin near the endpoints, you can artifically lengthen the first and last segments before running the loop. But I don't see the need: it makes sense that the fuzzied curve is thinning out at the extremities.
With your original approach, two places with the same distance to a line can sampled with a different probability, especially at the corners where two lines meet. I tried to fix this rephrasing the random experiment. The random experiment my code does is: "Pick a random point. Accept it with a probability of normpdf(d)<rand where d is the distance to the next line". This is a rejection sampling strategy.
xl = [8,4,2,2,6,8];
yl = [8,6,8,2,4,2];
resolution=50;
points_to_sample=200;
step=.5;
sigma=.4; %lower value to get points closer to the line.
xmax=(max(xl)+2);
ymax=(max(yl)+2);
dist=zeros(xmax*resolution+1,ymax*resolution+1);
x=[];
y=[];
for n = 1:numel(xl)-1
r = norm([xl(n)-xl(n+1), yl(n)-yl(n+1)]);
m = round(r/step) + 1;
x = [x,round(linspace(xl(n)*resolution+1, xl(n+1)*resolution+1, m*resolution))];
y = [y,round(linspace(yl(n)*resolution+1, yl(n+1)*resolution+1, m*resolution))];
end
%dist contains the lines:
dist(sub2ind(size(dist),x,y))=1;
%dist contains the normalized distance of each rastered pixel to the line.
dist=bwdist(dist)/resolution;
pseudo_pdf=normpdf(dist,0,sigma);
%scale up to have acceptance rate of 1 for most likely pixels.
pseudo_pdf=pseudo_pdf/max(pseudo_pdf(:));
sampled_points=zeros(0,2);
while size(sampled_points,1)<points_to_sample
%sample a random point
sx=rand*xmax;
sy=rand*ymax;
%accept it if criteria based on normal distribution matches.
if pseudo_pdf(round(sx*resolution)+1,round(sy*resolution)+1)>rand
sampled_points(end+1,:)=[sx,sy];
end
end
plot(xl,yl,'r','LineWidth',2);
hold on
scatter(sampled_points(:,1),sampled_points(:,2),'filled');

How to differentiate between a double peak and a single peak array in MATLAB?

How to differentiate between a double peak and a single peak array?
Also if the array represents a double peak, how to find the minimum point between two peaks? The minimum points outside of the peaks (left of left peak and right of right peak) should not be considered in finding the minimum point.
I found PEAKDET function to be quite reliable and fast although it's loop based. It does not require pre-smoothing of noisy data, but finds local max and min extrema with difference larger than parameter delta.
Since PEAKDET runs from left to right it sometime misses peaks on the right site. To avoid it I prefer to run it twice:
%# some data
n = 100;
x = linspace(0,3*pi,n);
y = sin(x) + rand(1,n)/5;
%# run peakdet twice left-to-right and right-to-left
delta = 0.5;
[ymaxtab, ymintab] = peakdet(y, delta, x);
[ymaxtab2, ymintab2] = peakdet(y(end:-1:1), delta, x(end:-1:1));
ymaxtab = unique([ymaxtab; ymaxtab2],'rows');
ymintab = unique([ymintab; ymintab2],'rows');
%# plot the curve and show extreme points based on number of peaks
plot(x,y)
hold on
if size(ymaxtab,1) == 2 && size(ymintab,1) == 1 %# if double peak
plot(ymintab(:,1),ymintab(:,2),'r.','markersize',30)
elseif size(ymaxtab,1) == 1 && size(ymintab,1) == 0 %# if single peak
plot(ymaxtab(:,1),ymaxtab(:,2),'r.','markersize',30)
else %# if more (or less)
plot(ymintab(:,1),ymintab(:,2),'r.','markersize',30)
plot(ymaxtab(:,1),ymaxtab(:,2),'r.','markersize',30)
end
hold off
Here is one algorithm that might work depending on how noisy your signal is. Here I define a peak as the set of connected points greater than a given threshold value.
Assuming your original data is in the array A. First, find a threshold:
t = (max(A)+min(A))/2;
Next, find all the points greater than this threshold t:
P = A>t;
Count number of connected entries points that are greater than t using bwlabel
L = bwlabel(P);
numberOfPeaks = max(L);
Now numberOfPeaks should tell you how many peaks (connected points greater than the threshold value) you have in your data
Now to find the minimum point between the two peak we need to identify those points that seperate the two peaks using the label matrix L.
firstPoint = find(L==1,1,'last')+1;
lastPoint = find(L==2,1,'first')-1;
So the valley between the first two peaks is the points with index between firsPoint and lastPoint. The minimum would then be
minValue = min(A(firstPoint:lastPoint));
Solution that does not depend on the Image Processing Toolbox
As #Nzbuu notes the aboth relies on the image processing toolbox function bwlabel. So, here is away to avoid that. First, I Assume that the the array P correctly identifies points belonging to peak (P(i)=1) and those belonging to valleys (P(i)=-1). If this is the case the boundary between peaks and valleys can be identified when dP = P(i+1)-P(i) = 1 or -1.
dP = diff(P);
To calculate the number of peaks simply sum the number of 1's in dP:
numberOfPeaks = sum(dP==1);
And the points identifying the first valley are between
firstPoint = find(dP==-1,1,'first')+1 %# the -1 represents the last point of the peak so add 1
lastPoint = find(dP==1,2,'first'); #% Find the start of the second peak
lastPoint = lastPoint(end); #% Keep the last value
You can find the local min/max as follows:
x = 0:.1:4*pi;
y = sin(x);
plot(x,y)
diffy = diff(y);
localMin = find(diffy(1:end-1)<=0 & diffy(2:end) > 0)+1;
localMax = find(diffy(1:end-1)>=0 & diffy(2:end) < 0)+1;
hold on
plot(x(localMin),y(localMin),'dg')
plot(x(localMax),y(localMax),'*r')
Resulting in:
Basically you are finding where the delta between the y values change signs. If your data is noisy this will cause lots of local min/max values and you may need to filter your data.
To find the minimum value between two peaks you can do something like this:
if numel(localMax) == 1
fprintf('The max value is: %f',y(localMax));
elseif numel(localMax > 1)
betweenPeaksIndex = localMin(localMin > localMax(1) & localMin <localMax(2));
fprintf('The min between the first 2 peaks is: %f',y(betweenPeaksIndex));
else
fprintf('The was no local Max ..???');
end

Find only relevant points in MATLAB

I have a MATLAB function that finds charateristic points in a sample. Unfortunatley it only works about 90% of the time. But when I know at which places in the sample I am supposed to look I can increase this to almost 100%. So I would like to know if there is a function in MATLAB that would allow me to find the range where most of my results are, so I can then recalculate my characteristic points. I have a vector which stores all the results and the right results should lie inside a range of 3% between -24.000 to 24.000. Wheras wrong results are always lower than the correct range. Unfortunatley my background in statistics is very rusty so I am not sure how this would be called.
Can somebody give me a hint what I would be looking for? Is there a function build into MATLAB that would give me the smallest possible range where e.g. 90% of the results lie.
EDIT: I am sorry if I didn't make my question clear. Everything in my vector can only range between -24.000 and 24.000. About 90% of my results will be in a range which spans approximately 1.44 ([24-(-24)]*3% = 1.44). These are very likely to be the correct results. The remaining 10% are outside of that range and always lower (why I am not sure taking then mean value is a good idea). These 10% are false and result from blips in my input data. To find the remaining 10% I want to repeat my calculations, but now I only want to check the small range.
So, my goal is to identify where my correct range lies. Delete the values I have found outside of that range. And then recalculate my values, not on a range between -24.000 and 24.000, but rather on a the small range where I already found 90% of my values.
The relevant points you're looking for are the percentiles:
% generate sample data
data = [randn(900,1) ; randn(50,1)*3 + 5; ; randn(50,1)*3 - 5];
subplot(121), hist(data)
subplot(122), boxplot(data)
% find 5th, 95th percentiles (range that contains 90% of the data)
limits = prctile(data, [5 95])
% find data in that range
reducedData = data(limits(1) < data & data < limits(2));
Other approachs exist to detect outliers, such as the IQR outlier test and the three standard deviation rule, among many others:
%% three standard deviation rule
z = 3;
bounds = z * std(data)
reducedData = data( abs(data-mean(data)) < bounds );
and
%% IQR outlier test
Q = prctile(data, [25 75]);
IQ = Q(2)-Q(1);
%a = 1.5; % mild outlier
a = 3.0; % extreme outlier
bounds = [Q(1)-a*IQ , Q(2)+a*IQ]
reducedData = data(bounds(1) < data & data < bounds(2));
BTW if you want to get the z value (|X|<z) that corresponds to 90% area under the curve, use:
area = 0.9; % two-tailed probability
z = norminv(1-(1-area)/2)
Maybe you should try mean value (in matlab: mean) and standard deviation (in matlab: std)?
What is the statistic distribution of your data?
See also this wiki page, section "Interpretation and application".
In general for almost every distribution, very useful Chebyshev's inequalities take place.
In most of the cases this should work:
meanval = mean(data)
stDev = std(data)
and probably the most (75%) of your values will be placed in range:
<meanVal - 2*stDev, meanVal + 2*stDev>
it seems like maybe you want to find the number x in [-24,24] that maximizes the number of sample points in [x,x+1.44]; probably the fastest way to do this involves a sort of the sample points, which is ultimately nlog(n) time; a cheesy approximation would be as follows:
brkpoints = linspace(-24,24-1.44,n_brkpoints); %choose n_brkpoints big, but < # of sample points?
n_count = histc(data,[brkpoints,inf]); %count # data points between breakpoints;
accbins = 1.44 / (brkpoints(2) - brkpoints(1); %# of bins to accumulate;
cscount = cumsum(n_count); %half of the boxcar sum computation;
boxsum = cscount - [zeros(accbins,1);cscount(1:end-accbins)]; %2nd half;
[dum,maxi] = max(boxsum); %which interval has the maximal # counts?
lorange = brkpoints(maxi); %the lower range;
hirange = lorange + 1.44
this solution does fudge some of the corner case stuff about the bottom and top bin, etc.
note that if you're going to go by the Chebyshev inequality route, Petunin's Inequality is probably applicable, and will give a slight boost.