Bar Code reader (code 39)

// BarCode reader
// Written by:
//  KJ Baldwin, UK. 2013
// Inputs:
//  wImage  a 2D image Wave
// Function:
//  If it can find and read bar code, this is returned as a string and added to the note of the image wave.
// Notes:
//  Bar Code must be of type Code 39
//  Bar code must appear within image horizontally (roughly).
//  Image should not have anything else that looks too much like a bar code in it - the algorithm here homes in on
//  the portion of the image that most looks like a bar code.

Function/S BarCode39GetBarCodeFromImage(wImage)
    Wave wImage
    // locates the bar code in image, extracts line profile
    // calls read line function and put code (if found) in Image Wave note
    string sBarCode=""
    string sOldDF=GetDataFolder(1)
    NewDataFolder/O root:Packages
    NewDataFolder/O/S root:Packages:BarCode39
    do
        if(WaveExists(wImage)==0)
            break
        endif
        string sNote=note(wImage)
        sNote=ReplaceStringByKey("BarCode",sNote,sBarCode)
        Note/K wImage,sNote
       
        Variable V_fitOptions=4
        // find rows with signal within high-ish freq range
        variable vNumX=DimSize(wImage,0)
        variable vNumY=DimSize(wImage,1)
        variable vX,vY,vLocal,vLevel,vSpacing
        Make/O/N=(vNumY) wIntFreqY
        wIntFreqY[]=0
        Make/O/N=(vNumX) wLine
        for(vY=vNumY-1;vY>=0;vY-=1)
            wLine[]=wImage[p][vY]
            FFT/OUT=4/DEST=wLine_FFT wLine
            vLocal=mean(wLine_FFT,0.1,0.3)-mean(wLine_FFT,0.3,0.5)
            wIntFreqY[vY]=vLocal
        endfor // vY
        Duplicate/O wIntFreqY,wIntFreqY_smth
        Smooth/M=0 9, wIntFreqY_smth
        DebuggerOptions
        variable doDebuggerOnError = V_debugOnError
        DebuggerOptions debugOnError=0
        variable vErr
        try
            CurveFit/Q/NTHR=0 gauss wIntFreqY_smth;AbortOnRTE
        catch
            vErr=GetRTError(1) // get and clear the error
            break
        endtry
        DebuggerOptions debugOnError=doDebuggerOnError
        wave W_coef
        variable vCentre=round(W_coef[2])
        variable vWidth=round(W_coef[3])
        if((vCentre<0)||(vCentre>vNumY)) // out of range
            break
        endif
        // find x-loc for barcode - extract a thin line
        wLine[]=0
        variable vWY=5
        for(vY=vCentre-vWY;vY<=vCentre+vWY;vY+=1)
            wLine[]+=wImage[p][vY]
        endfor
            // look for x pos
        FFT/OUT=1/DEST=wLine_FFT wLine
        wLine_FFT[x2pnt(wLine_FFT,0.3),DimSize(wLine_FFT,0)-1][]=0
        wLine_FFT[0,x2pnt(wLine_FFT,0.1)][]=0
        IFFT/DEST=wLine_FFT_IFFT wLine_FFT
        Make/O/N=(vNumX) wLine2
        wLine2[]=wLine_FFT_IFFT[p]
        variable vSegX=32
        Make/O/N=(vNumX) wXSegInt
        wXSegInt[]=0
        Make/O/N=(vSegX) wXSeg
        for(vX=vSegX/2;vX<vNumX-vSegX/2-1;vX+=1)
            wXSeg[]=wLine_FFT_IFFT[vX-vSegX/2+p]
            FFT/OUT=4/DEST=wXSeg_FFT wXSeg
            vLocal=mean(wXSeg_FFT,0.1,0.3)-mean(wXSeg_FFT,0.3,0.5)
            wXSegInt[vX]=vLocal
        endfor
        DebuggerOptions debugOnError=0
        try
            CurveFit/Q/NTHR=0 gauss wXSegInt;AbortOnRTE
        catch
            vErr=GetRTError(1) // get and clear the error
            break
        endtry
        DebuggerOptions debugOnError=doDebuggerOnError
        variable vCentreX=round(W_coef[2])
        variable vWidthX=round(W_coef[3])
        if((vCentreX<0)||(vCentreX>vNumX)) // out of range
            break
        endif
        // reset extracted line
        for(vY=vCentre-vWY;vY<=vCentre+vWY;vY+=1)
            wLine[]+=wImage[p][vY]
        endfor
        // find level for edges of bars
        vSegX=floor(vWidthX/2)
        Make/O/N=(vSegX) wXSeg
        wXSeg[]=wLine[vCentreX-floor(vSegX/2)+p]
        vLevel=mean(wXSeg)
        // look for X-limits
            // first determine mean spacing between roots in wXSeg
        wXSeg[]=wXSeg[p]-vLevel
        vLocal=0 // count
        for(vX=1;vX<vSegX;vX+=1)
            if(sign(wXSeg[vX-1])!=sign(wXSeg[vX]))
                vLocal+=1
            endif
        endfor
        vSpacing=vSegX/vLocal // mean spacing
            // now for whole line
        wLine[]=wLine[p]-vLevel
        variable vOldXPos,vLowX=-1,vHighX=-1
        variable vXLim=4 // multiplier for indication of end of bar
            // walk down from centre - set low limit
        vX=vCentreX
        vOldXPos=vCentreX
        do
            if(vX<1)
                break
            endif
            if(abs(vX-vOldXPos)>(vXLim*vSpacing))
                vLowX=vX
                break
            endif
            if(sign(wLine[vX-1])!=sign(wLine[vX]))
                vOldXPos=vX
            endif
            vX-=1
        while(1)
            // walk up from centre - set high limit
        vX=vCentreX
        vOldXPos=vCentreX
        do
            if(vX>vNumX-2)
                break
            endif
            if(abs(vX-vOldXPos)>(vXLim*vSpacing))
                vHighX=vX
                break
            endif
            if(sign(wLine[vX+1])!=sign(wLine[vX]))
                vOldXPos=vX
            endif
            vX+=1
        while(1)
        if((vLowX==-1)||(vHighX==-1))
            break
        endif
        ReDimension/N=(vHighX-vLowX+1) wXSeg
        wXSeg[]=wLine[vLowX+p]
       
        sBarCode=BarCode39GetBarCodeFromLine(wXSeg)
       
        sNote=note(wImage)
        sNote=ReplaceStringByKey("BarCode",sNote,sBarCode)
        Note/K wImage,sNote
       
        break
    while(1)
    SetDataFolder $sOldDF
    KillDataFolder/Z root:Packages:BarCode39
    return sBarCode
End
//
Function/S BarCode39GetBarCodeFromLine(wLine)
    wave wLine
    // returns bar code (if it can) from line segment
    // line segment must be zero-crossing at black-white transitions
    // with black negative and white positive.
    string sOut=""
    do
        variable vNum=DimSize(wLine,0)
        if(vNum<10)
            break
        endif
        Make/O/N=(vNum) wSpacing
        wSpacing[]=NaN
        variable vSpCount=0 // postion in wSpacing
        variable i,vLocal
        variable vOldX=-1
        for(i=1;i<vNum;i+=1)
            if(sign(wLine[i])!=sign(wLine[i-1])) // have transition
                vLocal=(i-1)+abs(wLine[i-1])/abs(wLine[i]-wLine[i-1])
                if(vOldX==-1) // not been set yet
                    vOldX=vLocal
                else // has been set, so assign to wSpacing
                    wSpacing[vSpCount]=vLocal-vOldX
                    vOldX=vLocal
                    vSpCount+=1
                endif
            endif
        endfor
        if(mod(vSpCount,10)!=9) // must have eg 79, 89, etc transitions for valid code
            break
        endif
        if(vSpCount<19) // must have at least two items
            break
        endif
        ReDimension/N=(vSpCount) wSpacing
        // threshold spacing
        Make/N=10/FREE/O wSpacing_Hist // histogram, 10 wells
        Histogram/C/R=[1,88]/B=1 wSpacing,wSpacing_Hist // not use first point as this is 0, and would skew the data
        WaveStats/Q wSpacing_Hist
        if(V_min!=0) // must have a zero well to be valid
            break
        endif
        variable vThreshold=-1
        for(i=0;i<4;i+=1)
            if(wSpacing_Hist[5+i]==0)
                vThreshold=pnt2x(wSpacing_Hist,5+i)
                break
            endif
            if(wSpacing_Hist[4-i]==0)
                vThreshold=pnt2x(wSpacing_Hist,4-i)
                break
            endif
        endfor
        if(vThreshold==-1)
            break
        endif
        wSpacing[]=(wSpacing[p]>vThreshold) ? 1 : 0
        // try to extract code (forwards)
        string sBinary,sItem,sCode=""
        variable vIsValid=1
        vNum=(vSpCount+1)/10 // number of elements
        for(i=0;i<vNum;i+=1)
            sBinary=""
            for(vLocal=0;vLocal<9;vLocal+=1)
                sBinary+=num2str(wSpacing[i*10+vLocal])
            endfor
            sItem=BarCode39GetCharFromBinaryBar39(sBinary)
            if(strLen(sItem)==0) // invalid
                vIsValid=0
                break
            endif
            sCode+=sItem
        endfor
        if(strLen(sCode)<2) // must have at least 2 chars
            vIsValid=0
        else
            if(cmpStr(sCode[0],"*")!=0) // not start with *
                vIsValid=0
            endif
            if(cmpStr(sCode[strLen(sCode)-1],"*")!=0) // not end with *
                vIsValid=0
            endif
        endif
        if(vIsValid==0) // try backwards
            vIsValid=1
            sCode=""
            for(i=vNum;i>0;i-=1)
                sBinary=""
                for(vLocal=0;vLocal<9;vLocal+=1)
                    sBinary+=num2str(wSpacing[i*10-vLocal-2])
                endfor
                sItem=BarCode39GetCharFromBinaryBar39(sBinary)
                if(strLen(sItem)==0) // invalid
                    vIsValid=0
                    break
                endif
                sCode+=sItem
            endfor
            if(strLen(sCode)<2) // must have at least 2 chars
                vIsValid=0
            else
                if(cmpStr(sCode[0],"*")!=0) // not start with *
                    vIsValid=0
                endif
                if(cmpStr(sCode[strLen(sCode)-1],"*")!=0) // not end with *
                    vIsValid=0
                endif
            endif
        endif
        if(vIsValid==0)
            break
        endif
        sOut=sCode[1,strLen(sCode)-2]
        break
    while(1)
    return sOut
End
//
Function/S BarCode39GetCharFromBinaryBar39(sCode)
    string sCode
    // simple lookup for character codes
    string sChar=""
    strSwitch(sCode)
        case "010010100":
            sChar="*"
            break
        case "000110100":
            sChar="0"
            break
        case "100100001":
            sChar="1"
            break
        case "001100001":
            sChar="2"
            break
        case "101100000":
            sChar="3"
            break
        case "000110001":
            sChar="4"
            break
        case "100110000":
            sChar="5"
            break
        case "001110000":
            sChar="6"
            break
        case "000100101":
            sChar="7"
            break
        case "100100100":
            sChar="8"
            break
        case "001100100":
            sChar="9"
            break
        case "100001001":
            sChar="A"
            break
        case "001001001":
            sChar="B"
            break
        case "101001000":
            sChar="C"
            break
        case "000011001":
            sChar="D"
            break
        case "100011000":
            sChar="E"
            break
        case "001011000":
            sChar="F"
            break
        case "000001101":
            sChar="G"
            break
        case "100001100":
            sChar="H"
            break
        case "001001100":
            sChar="I"
            break
        case "000011100":
            sChar="J"
            break
        case "100000011":
            sChar="K"
            break
        case "001000011":
            sChar="L"
            break
        case "101000010":
            sChar="M"
            break
        case "000010011":
            sChar="N"
            break
        case "100010010":
            sChar="O"
            break
        case "001010010":
            sChar="P"
            break
        case "000000111":
            sChar="Q"
            break
        case "100000110":
            sChar="R"
            break
        case "001000110":
            sChar="S"
            break
        case "000010110":
            sChar="T"
            break
        case "110000001":
            sChar="U"
            break
        case "011000001":
            sChar="V"
            break
        case "111000000":
            sChar="W"
            break
        case "010010001":
            sChar="X"
            break
        case "110010000":
            sChar="Y"
            break
        case "011010000":
            sChar="Z"
            break
        case "010000101":
            sChar="-"
            break
        case "110000100":
            sChar="."
            break
        case "011000100":
            sChar=" "
            break
        case "010101000":
            sChar="$"
            break
        case "010100010":
            sChar="/"
            break
        case "010001010":
            sChar="+"
            break
        case "000101010":
            sChar="%"
            break
    endSwitch
    return sChar
End

Forum

Support

Gallery

Igor Pro 8

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More