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 10

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More