% This is the only matlab file I've ever published, so comments are welcome
% I mainly used matlab as a toybox before recoding in C++ so this code is not
% so correct or nicely written

% Input on correct matlab style, or even better, more sensible ways of coding
% some of this stuff is welcome

% Main Assumptions
% * barcode covers most of the view.
% * no serious distortion across the view.
% * Code 39 only. The main property of code 39 used here is that the widths are in the ratio 1:3

% There's a few things I've done to the C that arn't covered here.
% * Variable length average. This was added because daylight and nightime LED light
%                            work best with different average lengths. 

% (c) 2004 Peter Bradshaw. All rights reserved, get in touch if you
% want to talk. I'm sure there's nothing groundbreaking in here,
% and it may, at best, be used as a rough reference.

% It would be great to hear if it helps anyone out or if it's bloody
% useless and I should be doing something else.

% The script loads an image from a file.
cd('C:\work\Barcode Player\captures\2nd camera\')
[filename,pathname] = uigetfile('*.bmp','Find image file');
barcodeim = imread(strcat(pathname,filename));

%plot the image
subplot(2,2,1);
image(barcodeim);
xlabel(filename);

all_results = [];%[0,'.'];
all_counts  = [];

% I originally had a single line, but I moved to a loop
% you'll probably have to alter these numbers after looking at the output
% there's currenltly no auto sensing of the appropriate area.
for linetotake = (150:2:250);%360;(300:2:380);%
line([0 640],[linetotake linetotake],'Color','k')
%figure;
trace = double(squeeze(barcodeim(linetotake,:,2)));
%plot(trace);

% Make a simple moving average, I tried a few other things, but
% this seems to work best with the data I've found. Lots of webcams
% preprocess data in the driver before handing it off.
filtleno2m1 = 10;
filtlen = filtleno2m1*2 + 1;
filtered = conv(ones(1,filtlen)/filtlen,trace);
paddedtrace = [zeros(1,filtleno2m1) trace zeros(1,filtleno2m1) ]; 
maxlev = [];
minlev = [];
levellen = 1;%8;%30;
% At one point I was playing with using (local min+local max)/2 as the the compare value.
for i = (filtleno2m1:length(filtered)-filtleno2m1)
    maxlev(i) = max(paddedtrace(i-levellen:i+levellen));
    minlev(i) = min(paddedtrace(i-levellen:i+levellen));
end

cmp = [];
maxlev(length(filtered)) = 1;
minlev(length(filtered)) = 1;
cmp(length(filtered)) = 1;

%filtered = 0.5*(maxlev+minlev);

comparewht = 5;
compareblk = -comparewht;


%run thru and make an array of compare results.
for i = (filtleno2m1:length(filtered)-filtleno2m1)
    if(paddedtrace(i) > filtered(i))
        cmp(i) = comparewht;
    else
        cmp(i) = compareblk;
    end
end


% Plot a line for the traces above to see what we're comparing.
subplot(2,2,2);
plot([paddedtrace' filtered' minlev' maxlev']);
hold on;
plot( cmp','x-');
hold off;

oldcmp = 80;
len = 1;

histo=zeros(length(filtered),1);
uphisto=zeros(length(filtered),1);
downhisto=zeros(length(filtered),1);

linemarks = [];

% collect the lengths of the long and short bars
% of both colours. Webcam's somtimes 'spread' whites, and also
% our compare values may be biased.

for i = (1:length(filtered))
    if(cmp(i) ~= oldcmp)
        if (oldcmp == compareblk) || (oldcmp == comparewht)
            linemarks = [linemarks ; [oldcmp len 0]];
            histo(len)=histo(len)+1;
            if(oldcmp>0)
                uphisto(len)=uphisto(len)+1;
            else
                downhisto(len)=downhisto(len)+1;
            end
        end
        oldcmp = cmp(i);
        len = 1;
    else
        len = len+1;
    end
end

% find the two peaks (from the thin and 3 times as wide wide bars)

[upsmallcnt,firstupmax] = max(uphisto);
for secondupmax = (firstupmax+1:length(uphisto));
    [junk,seconduptry] = max(uphisto(secondupmax : length(uphisto)) ) ;
    seconduptry = seconduptry + secondupmax - 1;
    if seconduptry ~= secondupmax
        secondupmax = seconduptry;
        break;
    end
end
    
[downsmallcnt,firstdownmax] = max(downhisto(1:5));
for seconddownmax = (firstdownmax+1:length(downhisto));
    [junk,seconddowntry] = max(downhisto(seconddownmax : length(downhisto)) ) ;
    seconddowntry = seconddowntry + seconddownmax - 1;
    if seconddowntry ~= seconddownmax
        seconddownmax = seconddowntry;
        break;
    end
end

uplimit = secondupmax * 2.4;
downlimit = seconddownmax * 2.4;

% skip past any wide bars at the beginning.
barsstart = 1;
for i = (1:length(linemarks)/2)
    if(linemarks(i,1)>0)
        if(linemarks(i,2)>uplimit)
            barsstart = i+1;
        end
    else
        if(linemarks(i,2)>downlimit)
            barsstart = i+1;
        end        
    end
end

% and the end
barsend = length(linemarks);
for i = (length(linemarks):-1:length(linemarks)/2)
    if(linemarks(i,1)>0)
        if(linemarks(i,2)>uplimit)
            barsend = i-1;
        end
    else
        if(linemarks(i,2)>downlimit)
            barsend = i-1;
        end    
    end
end

% grab the interesting part
bartrace = linemarks(barsstart:barsend,:);

% use the average of the two peaks for comparisons
% the 2/4 numbers are here because I was fiddling proportions
upcmp = (firstupmax*2 + secondupmax*2)/4;
downcmp = (firstdownmax*2 + seconddownmax*2)/4;

% draw two small plots of the stuff we've just been working with
subplot(4,2,5);
bar(uphisto(1:20));
hold on;
stem([upcmp firstupmax secondupmax],[6 7 7],'r-');
hold off;

subplot(4,2,7);
bar(downhisto(1:20));
hold on;
stem([downcmp firstdownmax seconddownmax],[6 7 7],'r-');
hold off;

% now we have our thresholds, we convert to a string representing
% wide and thin (upper and lower case) black and white bars (b and w)
barstring = '';
for i = (1:size(bartrace,1))
    if(bartrace(i,1)>0)
        % its white
        bartrace(i,3)=upcmp;
        if(bartrace(i,2)>upcmp)
            barstring = strcat(barstring,'W');
        else
            barstring = strcat(barstring,'w');
        end
    else
        % its black
        bartrace(i,3)=downcmp;
        if(bartrace(i,2)>downcmp)
            barstring = strcat(barstring,'B');
        else
            barstring = strcat(barstring,'b');
        end
    end
end


%subplot(2,2,3);

%chart the results from the above

subplot(2,2,4);
if(size(bartrace,1))
    plot(bartrace);
end
xlabel(barstring);

% There are a few properites of the barcode I didn't take advantage of
% such as the regular spacing of the white bars

% always a w between each number
%
% These are encodings of the numbers
%                           w w W w w w
barpats = [ 'BwbWbwbwB', %1  X..X....X   10001
            'bwBWbwbwB', %2  ..XX....X   01001
            'BwBWbwbwb', %3  X.XX.....   11000
            'bwbWBwbwB', %4  ...XX...X   00100
            'BwbWBwbwb', %5  X..XX....   10100
            'bwBWBwbwb', %6  ..XXX....   01100
            'bwbWbwBwB', %7  ...X..X.X   00011
            'BwbWbwBwb', %8  X..X..X..   10010
            'bwBWbwBwb', %9  ..XX..X..   01010
            'bwbWBwBwb', %0  ...XX.X..   00110
        ];

barchars = '1234567890';        
        
% as there's 10 elements in each bar, as a first step
% we look for any matches and see at what offset they 
% occur. Hopefully one offset will be much more 
% popular than others. As so often happens, I thought
% I was being really clever, but it turns our that 
% Code 39 designed so this doesn't really happen.

modulo = zeros(10,1);

for i = (1:length(barpats))
%    searchstring = strcat('w',barpats(i),'w')
    searchstring = barpats(i,:);
    res = mod(strfind(barstring,searchstring),10);
    for i2=(1:length(res))
        modulo(res(i2)+1) = modulo(res(i2)+1)+1;
    end
end

[cnt,maxindex]=max(modulo);

correctmodulo = maxindex-1;

% so now we have the correct modulo lets build a string
% its much the same loop as above except we check for the 
% correct modulo, and slot the numbers into a string
% if it's all correct.

%thenumber = ones(1,floor(length(barstring)/10)) * '.';
thenumber = ones(1,30) * '.';


% This disabled hack corrected the width of some white bars
% alwaysbig = correctmodulo+3;
% if(alwaysbig>10)
%     alwaysbig = alwaysbig - 10;
% end
% for i = ( mod(alwaysbig,2) :2:length(barstring))
%     barstring(i)='w'; % fixing message??
% end
% 
% for i = (alwaysbig:10:length(barstring))
%     barstring(i)='W'; % fixing message??
% end

goodnumbers = 0;

% make the right string

for i = (1:length(barpats))
    searchstring = barpats(i,:);
    res = strfind(barstring,searchstring);
    for i2=(1:length(res))
        if mod(res(i2),10) == correctmodulo
            thenumber(1,(res(i2)-correctmodulo)/10 +1) = barchars(i);
            goodnumbers=goodnumbers+1;
        end
    end
end

%tidy up the number a little before printing

for i = (1:length(thenumber))
    if(0 ~= thenumber(1))
        break;
    end
    thenumber = thenumber(2:length(thenumber));
end

for i = (1:length(thenumber))
    if(0 ~= thenumber(length(thenumber)))
        break;
    end
    thenumber = thenumber (1:length(thenumber)-1);
end

for i = (1:length(thenumber))
    if(0 == thenumber(i))
        thenumber(1,i)=',';
    end
end


thenumber = char(thenumber);
subplot(2,2,2);
xlabel(thenumber);

all_counts = [all_counts;goodnumbers];
all_results = [all_results;thenumber];
     
end

% plot( paddedtrace - filtered);

% could find bar frequency with this??
% plot(xcorr(trace),'-+');