#pragma rtGlobals=3 // Use modern global access method. #pragma version=4.00 // 4.00 4/12/16 // greyscale (2d) images worked in previous versions because Igor didn't complain // about out of range errors, so layer 1 and 2 values were the same as layer 0. // Fixed my bad code and introduced some new features. // Created a "pixel wiper" to assist with touching up image to help with trace extraction // image is assumed to be 3D RGB or 2D greyscale wave // (hopefully) now works with 24 or 48 bit images (8 or 16 bits per pixel) // A future update for Igor 7 will use ScaleToIndex menu "Macros" "Start Tracer", Tracer_Initialise() end // sets up package folder and creates globals // then creates plot and panel function Tracer_Initialise() NewDataFolder/O root:Packages NewDataFolder/O root:Packages:Tracer DFREF dfr=root:Packages:Tracer string /G dfr:ImageStr="", dfr:NewTraceStr="" // is ImageStr used? SVAR NewTraceStr = dfr:NewTraceStr NewTraceStr=UniqueName("trace", 1, 0) make /O/N=11 dfr:sliderTicks=p/10 make /O/T/N=11 dfr:sliderLabels /wave=sliderLabels sliderLabels[0]="Exact" sliderLabels[10]="Fuzzy" make /O/n=30 dfr:TracerGlobals /wave=g SetDimLabel 0, 0, null, g SetDimLabel 0, 1, XA, g SetDimLabel 0, 2, XB, g SetDimLabel 0, 3, YA, g SetDimLabel 0, 4, YB, g SetDimLabel 0, 5, gaps, g SetDimLabel 0, 6, JumpThreshold, g SetDimLabel 0, 7, R, g SetDimLabel 0, 8, G, g SetDimLabel 0, 9, B, g SetDimLabel 0, 10, deltaE, g SetDimLabel 0, 11, bgR, g SetDimLabel 0, 12, bgG, g SetDimLabel 0, 13, bgB, g SetDimLabel 0, 14, deltaEmax, g SetDimLabel 0, 15, mcsr, g SetDimLabel 0, 16, LogX, g SetDimLabel 0, 17, LogY, g SetDimLabel 0, 18, update, g SetDimLabel 0, 19, XYdata, g SetDimLabel 0, 20, editImg, g SetDimLabel 0, 21, wipe, g SetDimLabel 0, 22, UsageHints, g SetDimLabel 0, 23, depth, g SetDimLabel 0, 24, fuzzy, g SetDimLabel 0, 25, RGBthreshold, g g[%null]=nan g[%XA]=nan g[%XB]=nan g[%YA]=nan g[%YB]=nan g[%gaps]=0 g[%JumpThreshold]=100 g[%R]=0 g[%G]=0 g[%B]=0 g[%deltaE]=10 // not used g[%bgR]=65535 // 16 bit RGB values g[%bgG]=65535 g[%bgB]=65535 g[%deltaEmax]=100 // not used g[%mcsr]=0 g[%LogX]=0 g[%LogY]=0 g[%update]=50 g[%XYdata]=0 g[%editImg]=0 g[%wipe]=0 g[%UsageHints]=1 g[%depth]=8 // not used g[%fuzzy]=0.1 g[%RGBthreshold]=32*257 // ignore colour differences smaller than 12.5% in RGB space DoWindow /K TracerGraph // start with a fresh window Display /K=1/N=TracerGraph /W=(300,60,900,660) as "Tracer Image" Tracer_MakePanel() end Function Tracer_MakePanel() DFREF dfr=root:Packages:Tracer wave g=dfr:TracerGlobals doWindow /K TracerPanel NewPanel /K=1/N=TracerPanel/W=(188,611,0,611)/HOST=TracerGraph/EXT=1 as "Tracer Controls" ModifyPanel /W=TracerGraph#TracerPanel, noEdit=1 GroupBox groupImages,pos={9,10},size={172,80},title="Image selection" Button Load,pos={25,30},size={60,20},proc=Tracer_Buttons,title="Load..." Button Load,help={"Load an image to Packages:Tracer:"} Button clean,pos={105,30},size={60,20},title="Clear...", proc=Tracer_Buttons Button clean,help={"Kill all images in Packages:Tracer:"} PopupMenu popImage, mode=1, Value=TracerImgList(), title="",pos={85,60},size={80,20} PopupMenu popImage, proc=Tracer_PopMenuProc, bodyWidth=108, title="Display: " PopupMenu popImage, help={"Display an image from Packages:Tracer:" } GroupBox groupScale,pos={9,97},size={172,144},title="Set Scale" TitleBox titleX,pos={69,113},size={12,20},title="\\Z16X",frame=0,fStyle=1 TitleBox titleY,pos={126,113},size={12,20},title="\\Z16Y",frame=0,fStyle=1 TitleBox titleCsrA,pos={18,136},size={42,14},title="\\Z18\\W642",frame=0 TitleBox titleCsrB,pos={18,163},size={42,14},title="\\Z18\\W612",frame=0 SetVariable setvarXA,pos={54,135},size={43,16},title=" " SetVariable setvarXA,limits={-inf,inf,0},value= g[%XA] SetVariable setvarXA,help={"Type in X value of cursor A"} SetVariable setvarXB,pos={54,162},size={43,16},title=" " SetVariable setvarXB,help={"Type in X value of cursor B"} SetVariable setvarXB,limits={-inf,inf,0},value= g[%XB] SetVariable setvarYA,pos={111,135},size={43,16},title=" " SetVariable setvarYA,limits={-inf,inf,0},value= g[%YA] SetVariable setvarYA,help={"Type in Y value of cursor A"} SetVariable setvarYB,pos={111,162},size={43,16},title=" " SetVariable setvarYB,limits={-inf,inf,0},value= g[%YB] SetVariable setvarYB,help={"Type in Y value of cursor B"} CheckBox checkLogX,pos={57,190},size={36,14},proc=Tracer_CheckProc,title="Log" CheckBox checkLogX,value=g[%LogX] CheckBox checkLogY,pos={114,190},size={36,14},proc=Tracer_CheckProc,title="Log" CheckBox checkLogY,value= g[%LogY] Button setX,pos={54,214},size={43,20},proc=Tracer_Buttons,title="Set X" Button setX,help={"Set image X scaling"} Button setY,pos={111,214},size={43,20},proc=Tracer_Buttons,title="Set Y" Button setY,help={"Set image Y scaling"} GroupBox groupColour,pos={9,253},size={171,123},title="Colour Selector" SetDrawLayer UserBack DrawText 18,292,"trace" DrawText 27,319,"bg" PopupMenu popupColour,pos={54,273},size={50,21},proc=Tracer_PopMenuProc PopupMenu popupColour,mode=1,popColor= (g[%R],g[%G],g[%B]),value= #"\"*COLORPOP*\"" Button CsrTr,pos={115,273},size={50,20},proc=Tracer_Buttons,title="mouse" Button CsrTr,help={"Use mouse to select trace colour from image"} PopupMenu popupbg,pos={54,301},size={50,21},proc=Tracer_PopMenuProc PopupMenu popupbg,mode=1,popColor= (g[%bgR],g[%bgG],g[%bgB]),value= #"\"*COLORPOP*\"" Button CsrBg,pos={116,301},size={50,20},proc=Tracer_Buttons,title="mouse" Button CsrBg,help={"Use mouse to select background colour from image"} Slider slider0,pos={18,328},size={153,42},proc=Tracer_SliderProc,fSize=8 Slider slider0,limits={0,1,0.05},vert= 0 Slider slider0,userTicks={dfr:sliderTicks,dfr:sliderLabels} Slider slider0,value= g[%fuzzy] //Slider slider0,value= g[%deltaE]/g[%deltaEmax] GroupBox groupTrace,pos={10,385},size={170,130},title="Trace extractor" SetVariable setvarTraceName,pos={18,406},size={155,16},proc=Tracer_SetVarProc,title="Create:" SetVariable setvarTraceName,value= dfr:NewTraceStr CheckBox checkXY,pos={60,429},size={58,14},proc=Tracer_CheckProc,title="XY Data" CheckBox checkXY,value= g[%XYdata] CheckBox checkGaps,pos={60,447},size={71,14},proc=Tracer_CheckProc,title="Allow Gaps" CheckBox checkGaps,value= g[%gaps] SetVariable setvarJump,pos={25,467},size={140,16},title="Jump threshold (pixels)" SetVariable setvarJump,limits={0,inf,0},value= g[%JumpThreshold] Button Go,pos={64,491},size={62,20},proc=Tracer_Buttons,title="Trace!" GroupBox groupEdit,pos={10,525},size={170,80},title="Edit" Button editImg,pos={50,550},size={90,20},proc=Tracer_Buttons,title="Edit image" Button editTrace,pos={50,580},size={90,20},proc=Tracer_Buttons,title="Edit trace" SetWindow TracerGraph#TracerPanel, hook(panelHook)=Tracer_PanelHook // if we have already loaded an image, plot it controlinfo /W=TracerGraph#TracerPanel popImage wave w_img=dfr:$S_Value if (waveexists(w_img)) Tracer_PlotImage(w_img) endif End // get list of images in package datafolder function /S TracerImgList() DFREF savDF=GetDataFolderDFR(), dfr=root:Packages:Tracer setDataFolder dfr string ListOfImages=wavelist("*", ";", "DIMS:2")+wavelist("*", ";", "DIMS:3") setDataFolder savDF return ListOfImages end function Tracer_PanelHook(s) STRUCT WMWinHookStruct &s if (s.eventCode==2) dowindow /K TracerGraph endif return 0 end Function Tracer_Buttons(s) : ButtonControl STRUCT WMButtonAction &s if (s.eventCode!=2) return 0 endif DFREF dfr=root:Packages:Tracer wave g=dfr:TracerGlobals SVAR NewTraceStr = dfr:NewTraceStr string cmd, str strswitch(s.ctrlName) case "load": Tracer_LoadImage() return 1 case "clean": Tracer_Cleanup() return 1 endswitch if (strlen(ImageNameList("TracerGraph", ";" ))==0) doAlert 0, "No image display!" return 0 endif strswitch(s.ctrlName) case "setX": if (abs(g[%XA]-g[%XB])<1e-15) doalert 0, "Position cursors on X axis" return 0 endif if (abs(g[%XA]==xcsr(A, "TracerGraph")) && (g[%XB]==xcsr(B, "TracerGraph")) ) doalert 0, "First enter correct X values for cursor positions" return 0 endif Tracer_SetImageScale(0) return 1 case "setY": if (abs(g[%YA]-g[%YB])<1e-15) doalert 0, "Position cursors on Y axis" return 0 endif if ( (g[%YA]==vcsr(A, "TracerGraph")) && (g[%YB]==vcsr(B, "TracerGraph")) ) doalert 0, "First enter correct Y values for cursor positions" return 0 endif Tracer_SetImageScale(1) return 1 case "CsrTr": // use mouse to select colour if (g[%mcsr]==1) g[%mcsr]=0 setwindow TracerGraph hook(colourHook)=$"" Button CsrTr win=TracerGraph#TracerPanel, fcolor=(0,0,0) else if (g[%UsageHints]) doalert 0, "Hover over plot and click to set trace colour" endif g[%mcsr]=1 setwindow TracerGraph hook(colourHook)=Tracer_colourHook Button CsrTr win=TracerGraph#TracerPanel, fcolor=(65535,0,0) Button CsrBg win=TracerGraph#TracerPanel, fcolor=(0,0,0) dowindow /F TracerGraph endif return 1 case "csrBg": if (g[%mcsr]==2) // we were already selecting bg g[%mcsr]=0 // so stop setwindow TracerGraph hook(colourHook)=$"" Button CsrBg win=TracerGraph#TracerPanel, fcolor=(0,0,0) else if (g[%UsageHints]) doalert 0, "Hover over plot and click to set background colour" endif g[%mcsr]=2 setwindow TracerGraph hook(colourHook)=Tracer_colourHook Button CsrBg win=TracerGraph#TracerPanel, fcolor=(65535,0,0) Button CsrTr win=TracerGraph#TracerPanel, fcolor=(0,0,0) dowindow /F TracerGraph endif return 1 case "Go": Tracer_ExtractTrace() return 1 case "editTrace": if (Tracer_EditTrace()) Button editTrace, win=TracerGraph#TracerPanel, fcolor=(65535,0,0), title="Stop edit", rename=StopEdit return 1 endif return 0 case "StopEdit": GraphNormal Button StopEdit, win=TracerGraph#TracerPanel, fColor=(0,0,0), title="Edit trace", rename=editTrace return 1 case "editImg": if (g[%UsageHints]) sprintf cmd, "Warning: you are about to edit %s\r\r", stringfromlist(0, ImageNameList("TracerGraph", ";" )) cmd+="1. Use colour selector to choose colours\r" cmd+="2. Hold down control to add trace colour\r or hold down option to add background colour\r" cmd+="3. Click [and drag] to edit, \r" cmd+=" expand view for detailed editing\r\rContinue?" doalert 1, cmd if (v_flag==2) return 0 endif endif Button editImg, win=TracerGraph#TracerPanel, fcolor=(65535,0,0), title="Stop edit", rename=StopEditImg g[%editImg]=1 setwindow TracerGraph hook(EditImgHook)=Tracer_EditImgHook dowindow /F TracerGraph return 0 case "StopEditImg": Button StopEditImg, win=TracerGraph#TracerPanel, fColor=(0,0,0), title="Edit image", rename=editImg g[%wipe]=0 g[%editImg]=0 setwindow TracerGraph hook(EditImgHook)=$"" return 1 endswitch End Function Tracer_SliderProc(s) : SliderControl STRUCT WMSliderAction &s wave g=root:Packages:Tracer:TracerGlobals if(s.eventCode&1) // bit 0, value set //g[%deltaE]=s.curval*g[%deltaEmax] g[%fuzzy]=s.curval endif return 0 End Function Tracer_LoadImage() DFREF savDF=GetDataFolderDFR() , dfr=root:Packages:Tracer setDataFolder dfr ImageLoad if (V_Flag==0) return 0 endif string s_name=stringfromlist(0, S_waveNames) string s_newname=ParseFilePath(3, s_name, ":", 0, 0) if (CheckName(s_newname, 1)) s_newname=uniquename(s_newname, 1, 0) endif rename $s_name $s_newname setDataFolder savDF wave /SDFR=dfr w_img=$s_newname setscale /P y, 0, -1, w_img Tracer_PlotImage(w_img) return 1 end Function Tracer_PlotImage(w_img) wave w_img DFREF dfr=root:Packages:Tracer wave g=dfr:TracerGlobals // remove images and traces from plot Tracer_ClearPlot() AppendImage /W=TracerGraph w_img string str=nameofwave(w_img) modifygraph width={Plan, abs(DimDelta(w_img, 1 )/ DimDelta(w_img, 0)) ,bottom,left} if(g[%LogX] || g[%LogY]) Tracer_Replot() endif cursor /N=1/I/W=TracerGraph/C=(0,0,65535)/p A $str 0.1*DimSize(w_img, 0), 0.9*DimSize(w_img,1) cursor /N=1/I/W=TracerGraph/C=(0,65535,0)/p B $str 0.9*DimSize(w_img, 0), 0.9*DimSize(w_img,1) g[%XA]=xcsr(A, "TracerGraph") g[%XB]=xcsr(B, "TracerGraph") g[%YA]=vcsr(A, "TracerGraph") g[%YB]=vcsr(B, "TracerGraph") setwindow TracerGraph hook(csrHook)=Tracer_csrHook popupmenu popImage, win=TracerGraph#TracerPanel, popmatch=nameofwave(w_img) return 1 End function Tracer_ClearPlot() string strImage="", strTrace="" do strImage=stringfromlist (0, ImageNameList("TracerGraph", ";" )) removeimage /Z/W=TracerGraph $strImage while(strlen(strImage)) do strTrace=stringfromlist (0, TraceNameList("TracerGraph", ";" ,1)) RemoveFromGraph /Z/W=TracerGraph $strTrace while(strlen(strTrace)) return 1 end Function Tracer_Cleanup() DoAlert 1, "Discard all tracer images from memory?" if (V_Flag==2) return 0 endif Tracer_ClearPlot() DFREF savDF=GetDataFolderDFR(), dfr=root:Packages:Tracer setDataFolder dfr string str, listofimages=wavelist("*", ";", "DIMS:2")+wavelist("*", ";", "DIMS:3") variable i for (i=0;ipcsr(B, "TracerGraph")) variable tempP=pcsr(A, "TracerGraph"), tempQ=qcsr(A, "TracerGraph") cursor /I /P /W=TracerGraph A $CsrWave(A, "TracerGraph") pcsr(B, "TracerGraph"), qcsr(B, "TracerGraph") cursor /I /P /W=TracerGraph B $CsrWave(A, "TracerGraph") tempP, tempQ endif variable resolution=abs(pcsr(B, "TracerGraph")-pcsr(A, "TracerGraph"))+1 TraceNameString=CleanupName(TraceNameString, 0 ) if (CheckName(TraceNameString, 1) ) doalert 1, TraceNameString+" already exists. Overwrite?" if (V_Flag==2) return 0 endif endif make /O/N=(resolution) $TraceNameString=nan wave dataWave=$TraceNameString print "created wave "+nameofwave(dataWave) removefromgraph /Z /W=TracerGraph $nameofwave(dataWave) // just in case if (!g[%logX]) setscale /I x, xcsr(A, "TracerGraph"), xcsr(B, "TracerGraph"), dataWave endif if (g[%XYdata]) make /O/N=(resolution) $TraceNameString+"_X"=nan wave dataXwave=$TraceNameString+"_X" setscale /I x, xcsr(A, "TracerGraph"), xcsr(B, "TracerGraph"), dataXwave if (g[%logX]) // deal with X data for log X plots wave TracerLogX=dfr:TracerLogX dataXwave=TracerLogX[pcsr(A)+p+0.5] else dataXwave=x endif endif // //fix for 31 character names! if (strlen(ImageNameString)>32) ImageNameString=ImageNameString[1,31] endif string info=imageinfo("TracerGraph", ImageNameString, 0) if(g[%XYdata]) AppendToGraph /W=TracerGraph dataWave vs dataXwave else AppendToGraph /W=TracerGraph dataWave endif ModifyGraph /W=TracerGraph mode($nameofwave(dataWave))=0,lsize($nameofwave(dataWave))=2, rgb($nameofwave(dataWave))=(65280,0,0) // default tracing is red if (g[%R]>128*257 && (g[%G]+g[%B])<150*257) // data is red on image ModifyGraph /W=TracerGraph rgb($nameofwave(dataWave))=(0,65280,0) // tracing will be green endif variable i=pcsr(A, "TracerGraph") variable j=0 // DataWave point number variable highP=qcsr(A, "TracerGraph"), lowP=qcsr(A, "TracerGraph") variable gap, jump=0, k, p_Y do gap=0 if (RGBgood(w_img, i, highP)==0) // non-trace pixel do // expand to find a trace pixel lowP-=1 highP+=1 if (RGBgood(w_img, i, highP)) lowP=highP break elseif(RGBgood(w_img, i, lowP)) highP=lowP break endif while ( highP<(DimSize(w_img,1)-1) && lowP>0 ) endif // check that we're on a trace pixel now if (highP>lowP) // failed to find one if (g[%gaps]) gap=1 else if (g[%XYdata]) doalert 0, "Failed to follow trace at x=" + num2str(dataXwave[j])+ "\rTry allowing gaps" else doalert 0, "Failed to follow trace at x=" + num2str(pnt2x(dataWave, j))+ "\rTry allowing gaps" endif return 0 endif else // found a trace pixel k=max(j,1) do k-=1 // find the last non-nan value in dataWave while((numtype(dataWave[k])==2)&&k) if (g[%LogY]) wave TracerLogY=dfr:TracerLogY if (numtype(dataWave[k])==0) p_Y=binarysearchinterp(TracerLogY, dataWave[k]) // p_Y should be image column number if(abs(highP-p_Y)>g[%JumpThreshold]) // more than JumpThreshold pixels away from previous value if (g[%gaps]) gap=1 // treat it as a gap else if (g[%logX]) wave TracerLogX=dfr:TracerLogX jump+=(jump==0)*TracerLogX[pcsr(A)+j+0.5] else jump+=(jump==0)*pnt2x(dataWave, j) // save first jump position endif endif endif endif elseif (abs(highP-(dataWave[k]-DimOffset(w_img, 1))/DimDelta(w_img, 1))>g[%JumpThreshold] ) // more than JumpThreshold pixels away from previous value if (g[%gaps]) gap=1 // treat it as a gap else if (g[%logX]) wave TracerLogX=dfr:TracerLogX jump+=(jump==0)*TracerLogX[pcsr(A)+j+0.5] else jump+=(jump==0)*pnt2x(dataWave, j) // save first jump position endif endif endif endif if (gap) k=max(j,1) do k-=1 // find the last non-nan value in dataWave while(numtype(dataWave[k])==2&&k) dataWave[j]=nan if (g[%LogY]) wave TracerLogY=dfr:TracerLogY highP=binarysearch(TracerLogY, dataWave[k]) else highP=(dataWave[k]-DimOffset(w_img, 1))/DimDelta(w_img, 1) endif lowP=highP else // not a gap do highP+=RGBgood(w_img,i,highP+1) lowP-=RGBgood(w_img,i,lowP-1) lowP=max(lowP,0) highP=min(highP,DimSize(w_img,1)-1) if ( RGBgood(w_img,i,highP+1)==0 && RGBgood(w_img,i,lowP-1)==0 ) // found the upper and lower bounds of trace break endif while (highP<(DimSize(w_img,1)-1) && lowP>0) // find the mid-point if (g[%LogY]) wave TracerLogY=dfr:TracerLogY dataWave[j]=TracerLogY[(highP+LowP)/2+0.5] else dataWave[j]=DimOffset(w_img, 1)+DimDelta(w_img, 1)*(highP+LowP)/2 endif highP=round((highP+LowP)/2) lowP=highP endif // allowing the doupdate can slow things down if((g[%update]) && mod(i, g[%update])==0) // sparse updating doupdate endif i+=1 j+=1 while(i<=pcsr(B, "TracerGraph")) // make sure we found something! variable badData=1 for (j=0;j10) ) // no data, or more than 10 pixels away from target print "Looks like trace failed" doalert 1, "Looks like trace failed.\rDelete "+ nameofwave(dataWave)+"?" if (V_flag==1) removefromgraph /W=TracerGraph $nameofwave(dataWave) killwaves dataWave print "deleted wave "+TraceNameString endif else print "Trace extracted successfully" endif end static function RGBgood(w_img, pixelX, pixelY) wave w_img variable pixelX, pixelY // image pixel // pixelX=min(pixelX, dimsize(w_img,0)) // pixelY=min(pixelY, dimsize(w_img,1)) DFREF dfr=root:Packages:Tracer wave g=dfr:TracerGlobals // this sucked. I thought that a deltaE calculation would work better, // but my made up method that's kinda linear in RGB space actually // seems to work much better // w_RGBref={g[%R],g[%G],g[%B]} // calculate Euclidian distance in L*ab colour space // return (Tracer_ColourDiff(w_RGB, w_RGBref)<=g[%deltaE]) // so try it this way // calculate colours in CIE 1976 colour space // make /free/n=3 w_Lab, w_trLab, w_bgLab // Tracer_getRGB(w_Lab, w_img, pixelX, pixelY) // Tracer_RGB2LAB(w_Lab) // w_trLab={g[%R],g[%G],g[%B]} // Tracer_RGB2LAB(w_trLab) // w_bgLab={g[%bgR],g[%bgG],g[%bgB]} // Tracer_RGB2LAB(w_bgLab) // if (abs(w_Lab[0]-w_trLab[0])>max(g[%fuzzy]*10*(abs(w_trLab[0]-w_bgLab[0])), 5*g[%RGBthreshold])) // return 0 // elseif (abs(w_Lab[1]-w_trLab[1])>max(g[%fuzzy]*(abs(w_trLab[1]-w_bgLab[1])), g[%RGBthreshold])) // return 0 // elseif (abs(w_Lab[2]-w_trLab[2])>max(g[%fuzzy]*(abs(w_trLab[2]-w_bgLab[2])), g[%RGBthreshold])) // return 0 // endif // return 1 // do it the old way make /free/n=3 w_RGB Tracer_getRGB(w_RGB, w_img, pixelX, pixelY) if (abs(w_RGB[0]-g[%R])>max(g[%fuzzy]*(abs(g[%R]-g[%bgR])), g[%RGBthreshold])) return 0 elseif (abs(w_RGB[1]-g[%G])>max(g[%fuzzy]*(abs(g[%G]-g[%bgG])), g[%RGBthreshold])) return 0 elseif (abs(w_RGB[2]-g[%B])>max(g[%fuzzy]*(abs(g[%B]-g[%bgB])), g[%RGBthreshold])) return 0 endif return 1 end static function Tracer_getRGB(w_RGB, w_img, pixelX, pixelY) wave w_RGB, w_img variable pixelX, pixelY if (dimsize(w_img,2)) w_RGB=w_img[pixelX][pixelY][p] else w_RGB=w_img[pixelX][pixelY] endif // figure out colour depth // work with 24 or 48 bit images (8 or 16 bits per pixel) if (WaveType(w_img)&8) w_RGB*=257 endif return 1 end // write RGB values to a pixel, or a bunch of pixels, // depending on how zoomed-in we are on the image static function Tracer_writeRGB(w_RGB, w_img, pixelX, pixelY) wave w_RGB, w_img variable pixelX, pixelY DFREF dfr=root:Packages:Tracer wave g=dfr:TracerGlobals duplicate /free w_RGB w // figure out colour depth // work with 24 or 48 bit images (8 or 16 bits per pixel) if (WaveType(w_img)&8) w/=257 endif // figure out zoom level variable hpoints, vpoints getaxis /W=TracerGraph /Q bottom if (g[%logX]) wave TracerLogX=dfr:TracerLogX hpoints=abs(binarysearch(TracerLogX, v_max)-binarysearch(TracerLogX, v_min)) else hpoints=round((v_max - DimOffset(w_img, 0)) / DimDelta(w_img,0)) hpoints-=round((v_min - DimOffset(w_img, 0)) / DimDelta(w_img,0)) hpoints=abs(hpoints) // replace previous lines for Igor 7: //hpoints =abs(ScaleToIndex(w_img, v_max, 0)-ScaleToIndex(w_img, v_min, 0)) endif getaxis /W=TracerGraph /Q left if (g[%logY]) wave TracerLogY=dfr:TracerLogY vpoints=abs(binarysearch(TracerLogY, v_max)-binarysearch(TracerLogY, v_min)) else vpoints=round((v_max - DimOffset(w_img, 1)) / DimDelta(w_img,1)) vpoints-=round((v_min - DimOffset(w_img, 1)) / DimDelta(w_img,1)) vpoints=abs(vpoints) // replace previous lines for Igor 7: //vpoints =abs(ScaleToIndex(w_img, v_max, 1)-ScaleToIndex(w_img, v_min, 1)) endif getwindow tracergraph gsize variable zoom=hpoints/(v_right-v_left) // pixels per point zoom=min(zoom,vpoints/(v_bottom-v_top)) zoom*=2 variable pmin, pmax, qmin, qmax pmin=ceil(pixelX-zoom) pmax=floor(pixelX+zoom) qmin=ceil(pixelY-zoom) qmax=floor(pixelY+zoom) pmin=max(pmin, 0); qmin=max(qmin,0) pmax=min(pmax, (dimsize(w_img,0)-1)) qmax=min(qmax, (dimsize(w_img,1)-1)) variable i,j for (i=pmin;i<=pmax;i+=1) for (j=qmin;j<=qmax;j+=1) if (dimsize(w_img,2)) w_img[i][j][]=w[r] else w_img[i][j]=w[0] endif endfor endfor return 1 end // hook function to deal with image editing Function Tracer_EditImgHook(s) Struct WMWinHookStruct &s DFREF dfr=root:Packages:Tracer wave g=dfr:TracerGlobals if (g[%editImg]==0) g[%wipe]=0 setwindow TracerGraph hook(EditImgHook)=$"" return 0 endif wave w_img=Tracer_ImgRef() variable v_X, v_Y, p_X, p_Y variable v_key=GetKeyState(0) if ((g[%wipe]!=0) && s.eventCode==5) g[%wipe]=0 // mouseup cancels wipe endif // after mousedown w_wipe remembers status so that we can respond to mousemoved if (g[%editImg] && s.eventCode==3) // mousedown g[%wipe]=1 endif // figure out if we want to add or erase trace colour from image variable v_insert=0 // -1 for erase, 1 for draw if (g[%editImg]&&v_key) // bit 4 is ctrl key on mac if ( (v_key&2^4) && (stringmatch(igorinfo(2),"Macintosh")) ) v_insert=1 // set cursor to type 20 s.doSetCursor=1 s.cursorCode=20 // bit 0 is ctrl on win elseif ( (v_key&2^0) && (stringmatch(igorinfo(2),"Windows")) ) v_insert=1 s.doSetCursor=1 s.cursorCode=20 // bit 1 is option or alt elseif (v_key&2^1) v_insert=-1 s.doSetCursor=1 s.cursorCode=19 endif else g[%wipe]=0 // stop wipe if no modifer keypress s.doSetCursor=0 endif if ( g[%wipe] && (v_insert!=0) && (s.eventCode==3 || s.eventCode==4) ) // mousemoved or mousedown v_X=AxisValFromPixel("TracerGraph", "bottom", s.mouseLoc.h ) v_Y=AxisValFromPixel("TracerGraph", "left", s.mouseLoc.v ) // figure out which image pixel we're working on if(g[%logY]) wave TracerLogY=dfr:TracerLogY p_Y=binarysearch(TracerLogY, v_Y) else p_Y=round((v_Y - DimOffset(w_img, 1)) / DimDelta(w_img,1)) // replace previous line for Igor 7: //p_Y=ScaleToIndex(w_img, v_Y, 1) endif if(g[%logX]) wave TracerLogX=dfr:TracerLogX p_X=binarysearch(TracerLogX, v_X) else p_X=round((v_X - DimOffset(w_img, 0)) / DimDelta(w_img,0)) // replace previous line for Igor 7: //p_X=ScaleToIndex(w_img, v_X, 0) endif if ( (0>p_X) || (p_X>(dimsize(w_img, 0)-1)) ) return 0 endif if ( (0>p_Y) || (p_Y>(dimsize(w_img, 1)-1)) ) return 0 endif make /free/n=3 w_rgb if (v_insert==1) // set image pixel to trace colour w_rgb={g[%R],g[%G],g[%B]} else // set image pixel to bg colour w_rgb={g[%bgR],g[%bgG],g[%bgB]} endif Tracer_writeRGB(w_RGB, w_img, p_X, p_Y) endif return 0 // hook event labelled as not handled // allows nomal graph window interaction end // hook function for colour selectors function Tracer_colourHook(s) Struct WMWinHookStruct &s DFREF dfr=root:Packages:Tracer wave g=dfr:TracerGlobals if (g[%mcsr] && s.eventCode==3) // clear colourpicker g[%mcsr]=0 SetWindow TracerGraph hook(colourHook)=$"" Button CsrTr win=TracerGraph#TracerPanel, fcolor=(0,0,0) Button CsrBg win=TracerGraph#TracerPanel, fcolor=(0,0,0) return 1 endif wave w_img=Tracer_ImgRef() variable v_X, v_Y, p_X, p_Y v_X=AxisValFromPixel("TracerGraph", "bottom", s.mouseLoc.h ) v_Y=AxisValFromPixel("TracerGraph", "left", s.mouseLoc.v ) // figure out which image pixel we're on if(g[%logY]) wave TracerLogY=dfr:TracerLogY p_Y=binarysearch(TracerLogY, v_Y) else p_y=round((v_Y - DimOffset(w_img, 1)) / DimDelta(w_img,1)) // replace previous line for Igor 7: //p_Y=ScaleToIndex(w_img, v_Y, 1) endif if(g[%logX]) wave TracerLogX=dfr:TracerLogX p_X=binarysearch(TracerLogX, v_X) else p_X=round((v_X - DimOffset(w_img, 0)) / DimDelta(w_img,0)) // replace previous line for Igor 7: //p_X=ScaleToIndex(w_img, v_X, 0) endif if ( (0>p_X) || (p_X>(dimsize(w_img, 0)-1)) ) return 0 endif if ( (0>p_Y) || (p_Y>(dimsize(w_img, 1)-1)) ) return 0 endif make /free /n=3 w_RGB Tracer_getRGB(w_RGB, w_img, p_X, p_Y) if(g[%mcsr]==1) // trace picker g[%R]=w_RGB[0] g[%G]=w_RGB[1] g[%B]=w_RGB[2] PopupMenu popupColour,win=TracerGraph#TracerPanel, popColor= (g[%R],g[%G],g[%B]) ControlUpdate /W=TracerGraph popupColour elseif(g[%mcsr]==2) // bg picker g[%bgR]=w_RGB[0] g[%bgG]=w_RGB[1] g[%bgB]=w_RGB[2] PopupMenu popupbg,win=TracerGraph#TracerPanel, popColor= (g[%bgR],g[%bgG],g[%bgB]) ControlUpdate /W=TracerGraph popupbg endif return 0 end // hook function to deal with cursors function Tracer_csrHook(s) Struct WMWinHookStruct &s if (s.eventCode!=7) // cursor moved return 0 endif wave g=root:Packages:Tracer:TracerGlobals wave w_img=Tracer_ImgRef() make /free /n=3 w_RGB strswitch(s.cursorName) case "A": if ( WaveRefsEqual(w_img,CsrWaveRef(A,"TracerGraph")) ) g[%XA]=xcsr(a, "TracerGraph") g[%YA]=vcsr(a, "TracerGraph") // update cursor colour Tracer_GetRGB(w_RGB, w_img, pcsr(A,"TracerGraph"), qcsr(A,"TracerGraph") ) if (w_RGB[0]>150*257 && (w_RGB[1]+w_RGB[2])<150*257) // reddish on image cursor /M/C=(0,65535,0) /W=TracerGraph A // green cursor else cursor /M/C=(65535,0,0) /W=TracerGraph A endif endif return 1 case "B": if ( WaveRefsEqual(w_img,CsrWaveRef(B,"TracerGraph")) ) g[%XB]=xcsr(B, "TracerGraph") g[%YB]=vcsr(B, "TracerGraph") // update cursor colour Tracer_GetRGB(w_RGB, w_img, pcsr(B,"TracerGraph"), qcsr(B,"TracerGraph") ) if (w_RGB[1]>150*257 && w_RGB[0]+w_RGB[2]<150*257) // greenish on image cursor /M/C=(0,0,65535) /W=TracerGraph B else cursor /M/C=(0,65535,0) /W=TracerGraph B endif endif return 1 endswitch return 0 End // convert RGB to L*a*b* colour space function Tracer_RGB2LAB(w) wave w // 16 bit RGB values make /n=3 /free w_XYZ, w_XYZr, w_f variable Ls, as, bs variable eps=216/24389 variable k=24389/27 // RGB to XYZ w=w/65535 // assuming sRGB (D65) w = (w<=0.04045) ? (w/12.92) : ((w+0.055)/1.055)^2.4 // Bradford-adapted, D50 matrix // w_XYZ[0] = 0.436052025*w[0] + 0.385081593*w[1] + 0.143087414*w[2] // w_XYZ[1] = 0.222491598*w[0] + 0.71688606*w[1] + 0.060621486*w[2] // w_XYZ[2] = 0.013929122*w[0] + 0.097097002*w[1] + 0.71418547*w[2] w_XYZ[0] = 0.4124564*w[0] + 0.3575761*w[1] + 0.1804375*w[2] w_XYZ[1] = 0.2126729*w[0] + 0.7151522*w[1] + 0.0721750*w[2] w_XYZ[2] = 0.0193339*w[0] + 0.1191920*w[1] + 0.9503041*w[2] // XYZ to Lab w_XYZr={0.964221,1,0.825211} // reference white D50 w_XYZ=w_XYZ/w_XYZr w_f = (w_XYZ > eps) ? (w_XYZ^(1/3)) : (k*w_XYZ+16)/116 Ls = 116*w_f[1] - 16 as = 500*(w_f[0]-w_f[1]) bs = 200*(w_f[1]-w_f[2]) // w[0]=round(2.55*Ls + 0.5) // w[1]=round(as + 0.5) // w[2]=round(bs + 0.5) w={Ls,as,bs} return 1 end // Computes the difference between two RGB colors // by converting them to the L*a*b scale and // comparing them using the CIE76 algorithm // http://en.wikipedia.org/wiki/Color_difference#CIE76 function Tracer_ColourDiff(w1, w2) wave w1, w2 duplicate /free w1 lab1 duplicate /free w2 lab2 Tracer_RGB2LAB(lab1) Tracer_RGB2LAB(lab2) print w1, w2, lab1, lab2 return sqrt((lab2[0]-lab1[0])^2+(lab2[1]-lab1[1])^2+(lab2[2]-lab1[2])^2) end function /WAVE Tracer_ImgRef() string ImageNameString=stringfromlist (0, ImageNameList("TracerGraph", ";" )) wave w_img=ImageNameToWaveRef("TracerGraph",ImageNameString) return w_img end