#pragma rtGlobals=1 // Use modern global access method. // Routines for reading in and looking at Princeton Instruments CCD, by Jon Tischler, Oak Ridge National Lab // TischlerJZ@ornl.gov // // to use these macros in your Igor experiment, put this file in the "User Procedures" folder // in the "Igor Pro Folder", and uncomment the following line and put it in the top of the procedure //folder // //#include "WinViewProcedures" Menu "WinView" "Display image plot with buttons...", Graph_imageMake("") help={"Display an image and put buttons on the plot for listing spots"} "Center a Gaussian and Add to pkList", AddGaussianToList() "Report Center of a Gaussian ", GaussianCenter(1) help={"fit a gaussian to an ROI an image, and print results. The region is selected with a marquee in an image plot"} "Report Center of Mass of a ROI", GetCenterOfMass(NaN,NaN,NaN,NaN) help={"Compute the center of mass of a region on an image. The region is selected with a marquee in an image plot"} "-" "Fit List of Gaussians...", FitListOfGaussians("","pkList",-1) "Reset pkList from Gaussians", pkList = Reset_pkList_from_Gaussians(Gauss_coef) "-" "Load WinView File...", LoadWinViewFile("") help={"Load a WinView Image from the file, and then display the image"} "Get WinView Info", WinViewInfo("","") help={"Get the other information stored with the winview image"} // "Show WinView Procedures", DisplayProcedure/B=WinViewProcedures "CenterOfMass" "Choose Z range", getZrange($"",NaN) End Menu "Load Waves" "Load WinView File...", LoadWinViewFile("") help={"Load a WinView Image from the file, and then display the image"} End Menu "Analysis" "Report Center of a Gaussian ", GaussianCenter(1) help={"fit a gaussian to an ROI an image, and print results. The region is selected with a marquee in an image plot"} "Report Center of Mass of a ROI", GetCenterOfMass(NaN,NaN,NaN,NaN) help={"Compute the center of mass of a region on an image. The region is selected with a marquee in an image plot"} End Proc GraphImageStyle() : GraphStyle PauseUpdate; Silent 1 // modifying window... String wName = StringFromList(0,WaveList("*",";","DIMS:2,WIN:")) if (strlen(wName)<1) Abort "Unable to find image on top graph" endif ModifyGraph/Z width={Aspect,DimSize($wName,0)/DimSize($wName,1)} ModifyGraph/Z gfSize=18, grid=1, tick=2, mirror=1, minor=1, gridStyle=1 ModifyGraph/Z lowTrip=0.001, standoff=0, axOffset(bottom)=-1 if (stringmatch(IgorInfo(2),"Macintosh" )) ModifyGraph axOffset(left)=0.9 // mac else ModifyGraph axOffset(left)=-1.7 // pc endif Variable/C lohi = getZrange($wName,5) ModifyImage $wName ctab= {real(lohi),imag(lohi),PlanetEarth,1} // ImageStats /M=1 $wName // ModifyImage $wName ctab= {*,V_max/10,PlanetEarth,1} Label/Z left "y (pixels)" Label/Z bottom "x (pixels)" ShowInfo EndMacro Function AddGaussianToList() : GraphMarquee GetMarquee if (V_flag==0) DoAlert 0, "no Marquee selected, do nothing" return 0 endif GaussianCenter(0) AddPoint2pkList(K2,K4) String fldr = GetWavesDataFolder(ImageNameToWaveRef("",StringFromList(0,ImageNameList("",";"))),1 ) NVAR boxesOn = $(fldr+"boxesOn") if (!NVAR_Exists(boxesOn)) Variable/G $(fldr+"boxesOn") endif if (boxesOn) DrawBoxWithTicksAtXY(K2,K4,10) endif End Function/T RemovePeakFromList() : GraphMarquee Variable tol=5 // ± tolerance in pixels Variable V_left, V_right, V_bottom, V_top GetMarquee left, bottom if (V_flag==0) DoAlert 0, "no Marquee selected, do nothing" return "" endif String fldr = GetWavesDataFolder(ImageNameToWaveRef("",StringFromList(0,ImageNameList("",";"))),1 ) SVAR pkList = $(fldr+"pkList") if (!SVAR_Exists(pkList)) // list empty, nothing to remove return "" endif Variable i,ix,iy,x0,y0 x0 = (V_left+V_right)/2 y0 = (V_bottom+V_top)/2 i = ItemsInList(pkList) pkList = RemovePairFromList(pkList,x0,y0,tol) if (i == ItemsInList(pkList)) DoAlert 0, "no peaks removed" endif return pkList End Function AddPeakAtCursor() : GraphMarquee if (strlen(CsrWave(A))<=0) DoAlert 0, "Cursor A not on the Image, do nothing" return 0 endif AddPoint2pkList(pcsr(A),qcsr(A)) // ButtonProc_Include("") String fldr = GetWavesDataFolder(ImageNameToWaveRef("",StringFromList(0,ImageNameList("",";"))),1 ) NVAR boxesOn = $(fldr+"boxesOn") if (!NVAR_Exists(boxesOn)) Variable/G $(fldr+"boxesOn") endif if (boxesOn) DrawBoxWithTicksAtXY(pcsr(A),qcsr(A),10) endif End Function ReportCenterOfGaussian() : GraphMarquee GaussianCenter(1) End Function ReportCenterOfMass() : GraphMarquee GetCenterOfMass(NaN,NaN,NaN,NaN) End Function GaussianCenter(printON) // returns 1 if error, 0 is OK // the result is passed to calling function by W_coef or {K0, K1, ... K6} // this fits a 2-d gaussina to a region on an image. The region is selected with a marquee in an image plot, or specified Variable printON // enables print out of result Variable V_left, V_right, V_bottom, V_top if (WinType("")!=1) Abort "Top window must be a graph, with region selected" endif GetMarquee left, bottom if (V_flag==0) DoAlert 0, "No region selected on the graph" return 1 endif // Find the wave that is the image (assume that it is the only 2-d wave on the graph String ImageName = StringFromList(0,WaveList("*",";","DIMS:2,WIN:")) // name of wave with image if (strlen(ImageName)<1) DoAlert 0, "Unable to find image on top graph" return 1 endif // set up for the gaussian fit Variable i = FitOneGaussianPeak(ImageName,V_left,V_right,V_bottom,V_top) // returns 1 if error, 0 is OK if (i) return i endif Wave W_coef=W_coef Wave W_sigma=W_sigma Variable x0,y0,dx,dy,cor Variable fw= ( 2*sqrt(2*ln(2)) ) // this factor corrects for 2-d sigma to FWHM, apply to K3, K5, and the corresponding errors x0 = W_coef[2] dx = W_coef[3] * fw y0 = W_coef[4] dy = W_coef[5] * fw cor = W_coef[6] if (printON) printf "Gaussian peak in '%s' at (%.2f±%.2f, %.2f±%.2f) with FWHM of (%.2f±%.2f, %.2f±%.2f) and correllation of %.2f±%.2f\r",ImageName,x0,W_sigma[2],y0,W_sigma[4],dx,W_sigma[3]*fw,dy,W_sigma[5]*fw,cor,W_sigma[6] DoAlert 1, "Draw a cross at result" if (V_Flag==1) DrawCross(x0,y0,V_right-V_left,V_top-V_bottom) endif endif return 0 End Function FitListOfGaussians(ImageName,pkList,halfWidth) // build Gauss_coef and Gauss_sigma from pkList by fitting gaussians String ImageName // name of image to use String pkList // integer list of rough centers Variable halfWidth // half width of box for fit range if (halfWidth<1 || strlen(pkList)<7 || DimSize($ImageName,1)<1) // need to prompt halfWidth = (halfWidth<1) ? 10 : halfWidth Prompt halfWidth, "half width of box for fit region" Prompt pkList,"list of strings with lists of point", popup,StringList("*",";") Prompt ImageName, "name of image to use", popup, WaveList("*",";","DIMS:2") DoPrompt "Fit a list of Gaussian Peaks (results go into)", ImageName, pkList, halfWidth if (V_flag) return 1 endif if (DimSize($ImageName,1)<1 || exists(pkList)!=2) return 1 endif SVAR pk = $pkList pkList = pk endif if (halfWidth<1 || DimSize($ImageName,1)<1 || strlen(pkList)<5) return 1 endif Variable n=ItemsInList(pkList) Make/N=(7,n)/O Gauss_coef,Gauss_sigma Gauss_coef = NaN Gauss_sigma = NaN String item Variable i,x0,y0,xmin,xmax,ymin,ymax Variable nx=DimSize($ImageName,0) Variable ny=DimSize($ImageName,1) for (i=0;i 99) Abort "delta ="+num2str(delta)+", which is too small" endif delta /= 100 Variable N = numpnts(image)/2 Make/N=(N)/O hh_temp_ SetScale/P x 0,1,"", hh_temp_ Histogram/B=1 image,hh_temp_ Wave hist=hh_temp_ Integrate hist Variable hmin = hist[0] Variable dh = hist[N-1]-hmin Variable lo = BinarySearchInterp(hist, delta*dh+hmin) Variable hi = BinarySearchInterp(hist, (1-delta)*dh+hmin) lo = DimOffset(hist,0) + lo *DimDelta(hist,0) hi = DimOffset(hist,0) + hi *DimDelta(hist,0) if (printIt) printf "the [%g%%, %g%%] range = [%g, %g]\r",delta*100,100*(1-delta),lo,hi endif KillWaves/Z hist return cmplx(lo,hi) End Function LoadWinViewFile(S_filename) String S_filename // fully qualified name of file to open Variable refNum if (strlen((OnlyWinFileName(S_filename)))<1) // call dialog if no file name passed Open /D/M=".spe file"/R/T="????" refNum // use /D to get full path name endif if (strlen(S_filename)<1) // no file name, quit return 1 endif String wName = WinViewReadROI(S_filename,0,-1,0,-1) // load file into wName String wnote = note($wName) printf "for file '"+S_filename+"'" String bkgFile = StringByKey("bkgFile", wnote,"=") if (strlen(bkgFile)>0) printf ", background file = '%s'\r", bkgFile else printf "\r" endif Variable xdim=NumberByKey("xdim", wnote,"=") Variable ydim=NumberByKey("ydim", wnote,"=") printf "total length = %d x %d = %d points\r", xdim,ydim,xdim*ydim print "number type is '"+WinViewFileTypeString(NumberByKey("numType", wnote,"="))+"'" print "Created a 2-d wave '"+wName+"'" DoAlert 1, "Display this image" if (V_Flag!=1) return 1 endif Graph_imageMake(wName) // Display;AppendImage $wName // PauseUpdate // ModifyGraph/Z gfSize=18, grid=1, tick=2, minor=1, lowTrip(left)=0.001, standoff=0 // ModifyGraph/Z mirror=1 // Label left "y (pixels)" // Label bottom "x (pixels)" // ModifyGraph width={Aspect,xdim/ydim} // ModifyImage $wName ctab= {*,*,PlanetEarth,1} End //Function test() // Variable width // Variable n0, n1 // String baseName="" // fully qualified name of file to open // // String S_filename="" // fully qualified name of file to open // Variable refNum // if (strlen((OnlyWinFileName(S_filename)))<1) // call dialog if no file name passed // Open /D/M=".spe file"/R/T="????" refNum // use /D to get full path name // endif // if (strlen(S_filename)<1) // no file name, quit // return 1 // endif // // String wname //// wname = WinViewReadROI(S_filename,547,640,497,600) // wname = WinViewReadROI(S_filename,0,-1,0,-1) // print "wname=",wname //End // // or just use: // print WinViewReadROI("Macintosh HD:Users:tischler:GeNoise:Ge_inten_1.SPE",0,-1,0,-1) // Function/T WinViewReadROI(S_filename,i0,i1,j0,j1) String S_filename // fully qualified name of file to open (will not prompt) Variable i0,i1,j0,j1 // pixel range of ROI (if i1 or j1<0 then use whole image) Variable fid // file id (file is assumed already opened) String wName // name of wave to create Open /Z/M=".spe file"/R/T="????" fid as S_filename // this acutally opens file if (V_flag) return "" // could not open file endif Make /O/W/U/N=1000 buffer_temp__ buffer_temp__ = -1 FBinRead /F=2/U/B=3 fid,buffer_temp__ Close fid Variable xdim,ydim,itype Variable i,j,offset,exposure String wnote="" // wave note to add to file read in Variable bkgApplied = buffer_temp__(150/2) String bkgFile="" // name of bkg file if it exists if (bkgApplied) bkgFile = wave2string(buffer_temp__,1752,120) // extract backgroud file name it starts 1752 bytes into the file bkgFile = OnlyWinFileName(bkgFile) endif if (bkgApplied %& (strlen(bkgFile)>0)) wnote = AddListItem("bkgFile="+bkgFile,wnote) endif itype = buffer_temp__(108/2) wnote = AddListItem("num_Type="+WinViewFileTypeString(itype),wnote) wnote = AddListItem("numType="+num2istr(itype),wnote) xdim = buffer_temp__(21) // x size of image in file wnote = AddListItem("xdim="+num2istr(xdim),wnote) ydim = buffer_temp__(328) // y size of image in file wnote = AddListItem("ydim="+num2istr(ydim),wnote) i = buffer_temp__(6/2) // detector x dimension of chip wnote = AddListItem("xDimDet="+num2istr(i),wnote) i = buffer_temp__(18/2) // detector y dimension of chip wnote = AddListItem("yDimDet="+num2istr(i),wnote) String fileDate = wave2string(buffer_temp__,20,10) // date that exposure was taken if (strlen(fileDate)>0) wnote = AddListItem("dateExposed="+fileDate,wnote) endif i = buffer_temp__(30/2) // hour of the exposure j = buffer_temp__(32/2) // minute of the exposure if (i!=0 || j!=0) wnote = AddListItem("timeExposed="+num2istr(i)+":"+num2istr(j),wnote) endif if (buffer_temp__(706/2)) // a flat field was applied wnote = AddListItem("flatFieldApplied=1",wnote) String flatFile = wave2string(buffer_temp__,1652,120) // extract flat file name it starts 1652 bytes into the file flatFile = OnlyWinFileName(flatFile) if (strlen(flatFile)>0) wnote = AddListItem("flatFile="+flatFile,wnote) endif endif i = buffer_temp__(190/2) // ADC rate wnote = AddListItem("ADCrate="+num2istr(i),wnote) i = buffer_temp__(192/2) // ADC type wnote = AddListItem("ADCtype="+num2istr(i),wnote) i = buffer_temp__(194/2) // ADC resolution wnote = AddListItem("ADCresolution="+num2istr(i),wnote) i = buffer_temp__(600/2) // geometric 1==rotate, 2==reverse, 4==flip if (i&1) wnote = AddListItem("geo_rotate=1",wnote) endif if (i&2) wnote = AddListItem("geo_reverse=1",wnote) endif if (i&4) wnote = AddListItem("geo_flip=1",wnote) endif // for exposure use float (4bytes starting at byte10) Make /O/R/N=2 bufferFloat_temp__ Open /Z/M=".spe file"/R/T="????" fid as S_filename // this acutally opens file if (V_flag) return "" // could not open file endif FSetPos fid, 10 FBinRead /F=4/B=3 fid,bufferFloat_temp__ Close fid exposure = bufferFloat_temp__[0] KillWaves/Z bufferFloat_temp__ wnote = AddListItem("exposure="+num2str(exposure),wnote) String controllerTypes="new120 (TYPE II);old120 (TYPE I);ST130;ST121;ST138;DC131 (Pentamax);ST133 (MicroMAX/SpectroMAX);ST135 (GPIB);VICCD;ST116 (GPIB);OMA3 (GPIB);OMA4" i = buffer_temp__(704/2) -1 // controller type wnote = AddListItem("controllerType="+StringFromList(i,controllerTypes),wnote) i = buffer_temp__(1510/2) // number of ROI's Variable nROI = max(i,1) // zero means one if (nROI>1) Abort "this file contains multiple ROI's" wnote = AddListItem("NumROI="+num2istr(nROI),wnote) endif String str, ROInames = "startx;endx;groupx;starty;endy;groupy" for (j=1; j<=nROI; j+=1) offset = (1512/2) + (j-1)*12/2 for (i=0;i<6;i+=1) if (nROI>1) sprintf str,"%s_%d=%d",StringFromList(i,ROInames),j,buffer_temp__(offset+i) else sprintf str,"%s=%d",StringFromList(i,ROInames),buffer_temp__(offset+i) endif wnote = AddListItem(str,wnote) endfor endfor // read in each of the 5 lists they start at 200, every 80 (200,280, 360, 440, 520) String item,list Variable n for (j=200;j<521;j+=80) // byte index into the file list = wave2string(buffer_temp__,j,80) // extract a null terminated string from buf starting at index, and going no more than maxLen characters n = strsearch(list," ",0) list = list[n+1,inf] n=Itemsinlist(list) for (i=0;i1) wnote = AddListItem(item,wnote) endif endfor endfor KillWaves/Z buffer_temp__ i1 = (i1<1) ? xdim-1 : i1 // -1 flags use whole range j1 = (j1<1) ? ydim-1 : j1 // printf "for file '"+S_filename+"'" // if (bkgApplied %& (strlen(bkgFile)>0)) // printf ", background file = '%s'\r", bkgFile // else // printf "\r" // endif // printf "xdim = %d ydim = %d ", xdim,ydim // printf "total length = %d x %d = %d points\r", xdim,ydim,xdim*ydim // print "number type is '"+WinViewFileTypeString(itype)+"'" String inWaveName // name of ROI read in inWaveName = WinViewLoadROI(S_filename,itype,xdim,i0,i1,j0,j1) if (strlen(inWaveName)<1) return "" // nothing read in endif wName = OnlyFileName(S_filename) if (exists(wName)) wName = wName+"_" wName = UniqueName(wName, 1, 1) endif Rename $inWaveName $wName // print "Created a 2-d wave '"+wName+"'" if (strlen(wnote)>1) Note /K $wName Note $wName, wnote endif return wname End Function/T WinViewLoadROI(S_filename,itype,xdim,i0,i1,j0,j1) String S_filename // fully qualified name of file to open (will not prompt) Variable itype // WinView file type // 0 "float (4 byte)" // 1 "long integer (4 byte)" // 2 "integer (2 byte)" // 3 "unsigned integer (2 byte)" // 4 "string/char (1 byte)" // 5 "double (8 byte)" // 6 "signed int8 (1 byte)" // 7 "unsigned int8 (1 byte)" Variable xdim // x size of whole array Variable i0,i1,j0,j1 // pixel range of ROI // for whole image use 0,xdim,0,ydim Variable bytes=1 // length of image nuber type in bytes switch(itype) case 5: bytes *=2 case 0: case 1: bytes *=2 case 2: case 3: bytes *=2 endswitch i0 = max(round(i0),0) i1 = max(round(i1),0) j0 = max(round(j0),0) j1 = max(round(j1),0) Variable nx = i1-i0+1 Variable ny = j1-j0+1 if (nx<1 || ny<1) // nothing to read return "" endif Variable skip = 4100+bytes*j0*xdim // offset (bytes) to start of roi, 4100 is to start of image Variable fType = iType2fType(itype) // Igor number type String command if (ny>1) sprintf command, "GBLoadWave/Q/B/T={%d,%d}/S=%d/W=1/U=%ld \"%s\"",fType,fType,skip,xdim*ny,S_filename else sprintf command, "GBLoadWave/Q/B/T={%d,%d}/S=%d/W=1/U=%ld \"%s\"",fType,fType,skip,nx*ny,S_filename endif Execute command NVAR V_flag=V_flag if (V_flag<1) return "" // nothing loaded endif SVAR S_waveNames=S_waveNames String wName = S_waveNames[0,strsearch(S_waveNames, ";", 0)-1] Wave wav = $wName if (nx1) // compress up in the x direction Variable i Variable next=0 // points to starting point in reduced array to store Variable ystart=0 // points to start of this y-value for (i=0;i0) Prompt image,"image to use", popup, list DoPrompt "pick an image", image if(V_flag) return NaN endif endif list = note($image) if (strlen(key)>1) item = StringByKey(key,list,"=") if (strlen(item)>1 && numtype(str2num(item))) printf "for '%s', %s = '%s'\r",image,key,item endif else Prompt item,"item",popup,list DoPrompt "info about "+image, item if(V_flag) return NaN endif key = StringFromList(0,item,"=") item = StringFromList(1,item,"=") printf "for '%s', %s = %s\r",image,key,item endif return str2num(item) End Function/T OnlyFileName(full) String full String name=full Variable ii=-1 do ii = strsearch(name, ":", 0) // remove the path part if (ii>0) name= name[ii+1,inf] endif while(ii>0) ii = strsearch(name, ".SPE", 0) // trim off trailing '.SPE' if (ii<0) ii = strsearch(name, ".spe", 0) // trim off trailing '.spe' endif if (ii>1) name = name[0,ii-1] endif do ii = strsearch(name, ".", 0) // change all '.' to '_' if (ii>=0) name[ii,ii]="_" endif while(ii>=0) if (char2num(name[0,0])=0) name= name[ii+1,inf] endif while(ii>0) ii = -1 do ii = strsearch(name, "\\", 0) // remove the path part if (ii>=0) name= name[ii+1,inf] endif while(ii>=0) return name End Function/T wave2string(buf,offset,maxLen) // extract a null terminated string from buf starting at index, and going no more than maxLen characters Wave buf Variable offset // offset in bytes to the start of string (bytes) Variable maxLen // maximum length of string (bytes) Variable itype = NumberByKey("NUMTYPE", WaveInfo(buf,0)) Variable bytes=1 // number of bytes per index into buf itype = itype & 62 switch(itype) case 4: bytes *=2 case 2: case 32: bytes *=2 case 16: bytes *=2 endswitch offset /= bytes String str="" // result Variable shift,int, i=0 do int = buf(offset + floor(i/bytes)) if (bytes==1) int = int endif if (bytes==2) if (mod(i,2)==0) int = int & 255 // use low byte else int = (int & (255*256))/256 // use next byte endif endif if (bytes==4) if (mod(i,4)==0) int = int & 255 // use low byte elseif (mod(i,4)==1) shift = 256 int = (int & (255*shift))/shift // use next byte elseif (mod(i,4)==2) shift = 256^2 int = (int & (255*shift))/shift // use next byte else shift = 256^3 int = (int & (255*shift))/shift // use next byte endif endif if (int<1) break endif str[i,i] = num2char(int) i += 1 while(i1) i = pcsr(A) // use cursor j= qcsr(A) AddPoint2pkList(i,j) else DoAlert 0, "no Marquee selected and cursor A not on graph, do nothing" endif End Function PopMenuProc(ctrlName,popNum,popStr) : PopupMenuControl String ctrlName Variable popNum String popStr SVAR pkList=pkList NVAR pkIndex=pkIndex Variable i,n=ItemsInList(pkList) for (i=0;i0; i+=1,item=StringFromList(i,pkList)) x0=str2num(StringFromList(0,item,",")) y0=str2num(StringFromList(1,item,",")) if (numtype(x0) || numtype(y0)) continue endif SetDrawEnv xcoord= bottom,ycoord= left,fillpat= 0 DrawRect x0-wid,y0-wid,x0+wid,y0+wid SetDrawEnv xcoord= bottom,ycoord= left,linefgc=(30583,30583,30583) DrawLine x0,y0-wid,x0,y0-wid/2 SetDrawEnv xcoord= bottom,ycoord= left,linefgc=(30583,30583,30583) DrawLine x0,y0+wid/2,x0,y0+wid SetDrawEnv xcoord= bottom,ycoord= left,linefgc=(30583,30583,30583) DrawLine x0-wid,y0,x0-wid/2,y0 SetDrawEnv xcoord= bottom,ycoord= left,linefgc=(30583,30583,30583) DrawLine x0+wid/2,y0,x0+wid,y0 endfor return 1 End Function ButtonProc_Boxes(ctrlName) : ButtonControl String ctrlName String fldr = GetWavesDataFolder(ImageNameToWaveRef("",StringFromList(0,ImageNameList("",";"))),1 ) NVAR boxesOn = $(fldr+"boxesOn") SVAR pkList = $(fldr+"pkList") if (!NVAR_Exists(boxesOn)) Variable/G $(fldr+"boxesOn") endif if (!SVAR_Exists(pkList)) String/G $(fldr+"pkList") endif boxesOn = !boxesOn SetDrawLayer /K UserFront // always clear first if (!boxesOn) return 0 endif Variable x0,y0,xlo,xhi,ylo,yhi Variable i String item Variable wid=5*2 item=StringFromList(0,pkList) SetDrawLayer UserFront for (i=0; strlen(item)>0; i+=1,item=StringFromList(i,pkList)) x0=str2num(StringFromList(0,item,",")) y0=str2num(StringFromList(1,item,",")) DrawBoxWithTicksAtXY(x0,y0,wid) endfor return 1 End Function DrawBoxWithTicksAtXY(x0,y0,hw) Variable x0,y0 Variable hw // half width of box in pixels, usually ~10 if (numtype(x0) || numtype(y0) || numtype(hw) || hw<=0) return 1 endif SetDrawLayer UserFront SetDrawEnv xcoord= bottom,ycoord= left,fillpat= 0 DrawRect x0-hw,y0-hw,x0+hw,y0+hw SetDrawEnv xcoord= bottom,ycoord= left,linefgc=(30583,30583,30583) DrawLine x0,y0-hw,x0,y0-hw/2 SetDrawEnv xcoord= bottom,ycoord= left,linefgc=(30583,30583,30583) DrawLine x0,y0+hw/2,x0,y0+hw SetDrawEnv xcoord= bottom,ycoord= left,linefgc=(30583,30583,30583) DrawLine x0-hw,y0,x0-hw/2,y0 SetDrawEnv xcoord= bottom,ycoord= left,linefgc=(30583,30583,30583) DrawLine x0+hw/2,y0,x0+hw,y0 return 0 End Function/T RemoveDuplicatePairsFromList(list) String list Variable tolerance=5 Variable i,j Variable n=ItemsInList(list) Variable x0,y0,xx,yy String item for(j=0;jj;i-=1) // check all following pairs, starting from the end item = StringFromList(i,list) xx = str2num(StringFromList(0,item,",")) yy = str2num(StringFromList(1,item,",")) if ((abs(xx-x0)0) return list End