// Multi-peak fitting // ****Version 1.43***** // Changes since 1.42 // Fixed problem setting range when axis exceeds data range and point range is reversed. // ****Version 1.42***** // Changes since 1.41 // Fixed problem when clicking set when data is in a table but not in a graph // Fixed problem when manually editing an existing peak wherein the peak might be offscale (was not being offset the same as the individual peaks) // Added new FitTweaksPanel to allow easy setting of things like gDoBaselineOffset // Changed Graph button to detect existing graph and offer to add fit and residuals traces. // ****Version 1.41***** // Changes since 1.40 // Avoid triggering debugger when xwave is _calculated_ // Restore fitting function info upon RevisitGroup // Regraph data if necessary upon RevisitGroup // Also, RevisitGroup now brings results table forward // Can now suppress the offset of individual peaks to the baseline by executing: // variable/G root:Packages:WMmpFitting:gDoBaselineOffset=0 // Can now cause baseline curve to be displayed by executing // variable/G root:Packages:WMmpFitting:gDoBaselineTrace=1 // ****Version 1.40***** // Changes since 1.3 // Added ExpGaussian and ExpConvExp peak functions. // Made cosmetic changes to the panel so it is better customized to each function. // Removed "Use Cursors" method. // Individual peaks are now offset to the baseline minus a percentage of the y data range Previously individual peaks were based at zero. // To change the percentage, edit the global variable root:Packages:WMmpFitting:gIndPeakOffsetPercent // // // Note: Instructions are in the "Multi-peak Fit" experiment in the Examples:Curve Fitting // folder. That experiment also contains sample data and a guided tour. // // You can make some sample data like so: // Make/N=500 data;SetScale x,300e-9,500e-9,"m" data // data= exp(-((x-380e-9)/20e-9)^2) + 0.5*exp(-((x-430e-9)/30e-9)^2) + gnoise(0.02) // // The package creates its results, waves and variables, in a new data folder it creates in // the DF containing your data (which needs to be the current DF (CDF)). The folder name // is WMPeakFits. Within that DF are individual data folders for each sub-range of data you // create. When it is the currently active set, the data folder is named Current // When you want to freeze the results of a run, you can rename this DF to anything ohter than Current. // That is done for you by the RenameGroup macro. // // The master wave (called mw here but is really named masterCoef) has the following structure: // mw[0] & mw[1] contain the center and width of the x range for use in basline calcs // mw[2-5] contain the coefficeints of a cubic poly using x values scaled with mw[0-1] // mw[6]= amp of first peak // mw[7]= position of first peak // mw[8]= width of first peak // mw[9]= shape factor for voigt for first peak // mw[10-13]= coefs for 2nd peak // etc. #pragma rtGlobals= 2 // require new syntax #pragma moduleName= WMMultiPeakFit #include #include #include Structure SaveFitFuncInfoStruct Variable gFitType Variable g1Width Variable gUseXFUNCs Variable gDoBaseline Variable g1Shape Variable gPeakWidthFactor EndStructure Function WMSaveFitFuncInfo() if( !DataFolderExists(":WMPeakFits:Current") ) return 0 endif String dfSav= GetDataFolder(1) SetDataFolder :WMPeakFits:Current String/G finfoSave SetDataFolder root:Packages:WMmpFitting NVAR gFitType,g1Width, gUseXFUNCs,gDoBaseline,g1Shape, gPeakWidthFactor SetDataFolder dfSav STRUCT SaveFitFuncInfoStruct s s.gFitType= gFitType s.g1Width= g1Width s.gUseXFUNCs= gUseXFUNCs s.gDoBaseline= gDoBaseline s.g1Shape= g1Shape s.gPeakWidthFactor= gPeakWidthFactor StructPut/S/B=2 s,finfoSave return 1 End Function WMRestoreFitFuncInfo() SVAR/Z finfoSave= :WMPeakFits:Current:finfoSave if( !SVAR_Exists(finfoSave) ) return 0 // created with version prior to 1.41 endif String dfSav= GetDataFolder(1) SetDataFolder root:Packages:WMmpFitting NVAR gFitType,g1Width, gUseXFUNCs,gDoBaseline,g1Shape, gPeakWidthFactor SetDataFolder dfSav STRUCT SaveFitFuncInfoStruct s StructGet/S/B=2 s,finfoSave g1Width= s.g1Width gUseXFUNCs= s.gUseXFUNCs gDoBaseline= s.gDoBaseline g1Shape= s.g1Shape gPeakWidthFactor= s.gPeakWidthFactor if( gFitType != s.gFitType ) gFitType= s.gFitType DoWindow/F FitSetupPanel PopupMenu popupFunc,mode=gFitType+1 UpdatePanelForFunc() endif End Function WMCreateFitGlobals() String dfSav= GetDataFolder(1) NewDataFolder/O/S root:Packages NewDataFolder/O/S WMmpFitting Variable/G gChiSquare Variable/G gSaveSet Variable/G gDoBaseline,g1Width,g1Shape,gCurPeak,gNumPeaks,gFitType Variable/G gPeakAmp,gPeakPos,gPeakWidth,gVoigtShape,gUseXFUNCs Variable/G gZeroBaseline // JP070813 Variable/G gPeakWidthFactor String/G gYDataName,gFitDataName,gXDataName,gResidsName String/G gCurDataFolder // the DF containing the current data set Variable/G gFirstCoef=6,gNumCoef=4 // to allow some routines to be generic // Results Options Variable/G gWantResultsTable= 1 // to create results table when print results is performed Variable/G gIndPeakOffsetPercent= 10 // to offset individual peaks this percentage of y data range Variable/G gDoBaselineOffset= 1 // New in 1.41, to allow supression of default offset of individual peaks to baseline Variable/G gDoBaselineTrace= 0 // new in 1.41, set to also plot the baseline curve // AUTOFIND Variable/G gDoAutoPeakFind= 1 Variable/G gMinPeakFraction= 0.01 // peaks smaller than this times the first (biggest) peak are rejected Variable/G gMinPeakNoiseFactor= 0 // 2^gMinPeakNoiseFactor times a noise factor sets a threshold for peak rejection Variable/G gNoiseEstFromAutoFind,gSmoothEstFromAutoFind Variable/G gCurPeakUpdateDummy SetFormula gCurPeakUpdateDummy,"UpdateCurPeakTag(gCurPeak)" SetDataFolder dfSav end Macro CreateFitSetupPanel() DoWindow/F FitSetupPanel if(!V_Flag ) WMCreateFitGlobals() MakeFitSetupPanel() UpdatePanelForFunc() endif end Macro ZapFitAndResiduals() Silent 1; PauseUpdate if( (exists("root:Packages:WMmpFitting:gFitDataName")==0) * (exists("root:Packages:WMmpFitting:gResidsName")==0) ) return endif $root:Packages:WMmpFitting:gFitDataName= NaN $root:Packages:WMmpFitting:gResidsName= NaN end Macro RenameGroup(gname) String gname= StrVarOrDefault(":WMPeakFits:Current:prefix","GroupA") Prompt gname,"name for this group of result waves:" Silent 1; PauseUpdate if( !DataFolderExists(":WMPeakFits:Current") ) DoAlert 0,"First, generate some results." return endif if( DataFolderExists(":WMPeakFits:"+gname) ) DoAlert 0,"Sorry, that name is already taken." return endif String/G :WMPeakFits:Current:prefix= gname WMSaveFitFuncInfo() RenameDataFolder :WMPeakFits:Current,$gname end Function/S WMPF_DataFolderList(theDF) String theDF if( !DataFolderExists(theDF) ) return "" endif String dfSav= GetDataFolder(1) if( strlen(theDF)!=0 ) SetDataFolder theDF endif Variable i=0 String s="",dfname do dfName= GetIndexedObjName("", 4, i) if( strlen(dfName)==0 ) break endif s+=dfname+";" i+=1 while(1) SetDataFolder dfSav return s end // Find topmost window containing given wave // returns zero length string if not found // Function/S FindWindowWithWave(windtype,w) Variable windtype // 1 for graphs, 2 for tables wave w string win="" variable i=0 do win=WinName(i, windtype) // name of ith window if( strlen(win) == 0 ) break; // no more wndows endif CheckDisplayed/W=$win w if(V_Flag) break endif i += 1 while(1) return win end Function ReturnToOldDataSet(gname) String gname // contains desired group name. May be Current or a renamed set (or bogus) if( !exists(":WMPeakFits:yDataName") ) DoAlert 0,"Fit info not found. Did you choose the wrong data folder?" return 1 endif SVAR gCurDataFolder= root:Packages:WMmpFitting:gCurDataFolder String dfSav= GetDataFolder(1) SetDataFolder gCurDataFolder WMSaveFitFuncInfo() SetDataFolder dfSav gCurDataFolder= dfSav SVAR yDataName= :WMPeakFits:yDataName // when we first did a set, we squirreled away the name of the data here (I hope the term squirreled does not offend any rodent-americans) SVAR xDataName= :WMPeakFits:xDataName SVAR gYDataName= root:Packages:WMmpFitting:gYDataName SVAR gXDataName= root:Packages:WMmpFitting:gXDataName SVAR gFitDataName= root:Packages:WMmpFitting:gFitDataName SVAR gResidsName= root:Packages:WMmpFitting:gResidsName gYDataName= yDataName gXDataName= xDataName gFitDataName= "fit_"+gYDataName gResidsName= "res_"+gYDataName String tblname= FindWindowWithWave(2,wr) if( strlen(tblname) != 0 ) DoWindow/F $tblname // could not devise a naming scheme for resutls tables but at least this will bring it forward on revisit group current endif String grfname= FindWindowWithWave(1,$gFitDataName) if( strlen(grfname) != 0 ) DoWindow/F $grfname else MP_MakeGraph() // Apparently the graph has been killed; make a new one. endif DoWindow/F FitSetupPanel PopupMenu popupYData,win=FitSetupPanel,mode=1,popvalue=yDataName // mode value has to be non-zero but we don't know the actual number PopupMenu popupXData,win=FitSetupPanel,mode=2,popvalue=xDataName if( CmpStr(gname,"Current") == 0 ) WMRestoreFitFuncInfo() UpdateCurSettings();CalculateData() endif return 0 end Macro RevisitGroup(gname) String gname= "GroupA" Prompt gname,"Data Folder containing group of result waves:",popup WMPF_DataFolderList("WMPeakFits") Silent 1; PauseUpdate Variable setSwitch= 0 if( CmpStr(GetDataFolder(1),root:Packages:WMmpFitting:gCurDataFolder) != 0 ) // returning from a different data set? setSwitch= 1 if( ReturnToOldDataSet(gname) ) return // error endif endif if( CmpStr(gname,"Current") == 0 ) ResetAxisRange() return endif if( DataFolderExists(":WMPeakFits:Current") ) //Switching to a different setup but haven't saved the old one yet. Try to do it automatically. String s if( exists(":WMPeakFits:Current:prefix") ) // look for possible old name s= :WMPeakFits:Current:prefix else s= "oldCurrent" endif if( DataFolderExists("WMPeakFits:"+s) ) DoAlert 0,"Can't rename Current due to conflict. You'll need to do it yourself." return endif if( !setSwitch ) // don't save previous data set onto new one WMSaveFitFuncInfo() endif RenameDataFolder :WMPeakFits:Current,$s endif RenameDataFolder $":WMPeakFits:"+gname,Current WMRestoreFitFuncInfo() UpdateCurSettings();CalculateData();ResetAxisRange() UpdateCurPeakTag(NumVarOrDefault("root:Packages:WMmpFitting:gCurPeak",0)) end Macro RunMeAfterManualFit() if( exists(":WMPeakFits:Current:masterCoef")==0 ) DoAlert 0,"First, use the Set button in the FitSetupPanel" return endif if( exists(":WMPeakFits:Current:coef")==0 ) DoAlert 0,"First, copy the coef wave to the Current data folder" return endif UpdateMasterCoef();UpdateCurSettings();CalculateData() end Macro PrintPeakParams() fPrintPeakParams() end Function PrepareResultsWaves(func) String func Wave fc= :WMPeakFits:Current:coef NVAR gFitType= root:Packages:WMmpFitting:gFitType NVAR g1Width= root:Packages:WMmpFitting:g1Width NVAR g1Shape= root:Packages:WMmpFitting:g1Shape NVAR gNumPeaks= root:Packages:WMmpFitting:gNumPeaks NVAR gDoBaseline= root:Packages:WMmpFitting:gDoBaseline NVAR gRangeBegin= :WMPeakFits:Current:gRangeBegin NVAR gRangeEnd= :WMPeakFits:Current:gRangeEnd SVAR gXDataName= root:Packages:WMmpFitting:gXDataName SVAR gYDataName= root:Packages:WMmpFitting:gYDataName String infostr sprintf infostr,"NPKS:%d;YDATA:%s;XDATA:%s;X0:%g;X1:%g;BL:%d;1WIDTH:%d;1SHAPE:%d;",gNumPeaks,gYDataName,gXDataName,gRangeBegin,gRangeEnd,gDoBaseline,g1Width,g1Shape Variable ncols=0 if(gFitType==0 || gFitType==1) ncols= 6 //pos,posSigma,area,areaSigma,width,widthSigma elseif( gFitType==2 ) ncols= 12 //pos,posSigma,area,areaSigma,width,widthSigma,wg,wgSigma,wl,wlSigma,shape,shapeSigma elseif( gFitType==3 ) // ExpGaussian ncols= 8 //pos,posSigma,area,areaSigma,Gauss width,Gauss width sigma,exp const,exp const sigma elseif( gFitType==4 ) // ExpConvExp ncols= 8 //pos,posSigma,area,areaSigma,k1,k1 sigma,k2,k2 sigma endif Variable curRow= 0 WAVE/Z wr= $(func+"_Results") // results wave if( WaveExists(wr) ) curRow= DimSize(wr,0) Redimension/N=(curRow+gNumPeaks,-1) wr else Make/D/N=(gNumPeaks,ncols) $(func+"_Results") // main results wave with a colum for each result Make/T/N=(gNumPeaks) $(func+"_Info") // parallel text wave with info at each group start WAVE wr= $(func+"_Results") // results wave just created // some column labels are common... SetDimLabel 1,0,position,wr SetDimLabel 1,1,'position sigma',wr SetDimLabel 1,2,area,wr SetDimLabel 1,3,'area sigma',wr // ...but others are not: if(gFitType==0 || gFitType==1) // Gaussan and Lorentzian SetDimLabel 1,4,width,wr SetDimLabel 1,5,'width sigma',wr elseif( gFitType==2 ) // Voigt SetDimLabel 1,4,width,wr SetDimLabel 1,5,'width sigma',wr SetDimLabel 1,6,'Gauss width',wr SetDimLabel 1,7,'Gauss width sigma',wr SetDimLabel 1,8,'Lor width',wr SetDimLabel 1,9,'Lor width sigma',wr SetDimLabel 1,10,'shape',wr SetDimLabel 1,11,'shape sigma',wr elseif( gFitType==3 ) // ExpGaussian SetDimLabel 1,4,'Gauss width',wr SetDimLabel 1,5,'Gauss width sigma',wr SetDimLabel 1,6,'exp const',wr SetDimLabel 1,7,'exp const sigma',wr elseif( gFitType==4 ) // ExpConvExp SetDimLabel 1,4,'k1',wr SetDimLabel 1,5,'k1 sigma',wr SetDimLabel 1,6,'k2',wr SetDimLabel 1,7,'k2 sigma',wr endif endif WAVE/T wi= $(func+"_Info") // parallel text wave -- this should exist... if( !WaveExists(wi) ) Make/T/N=(curRow+gNumPeaks) $(func+"_Info") // ...but just in case, make sure it does WAVE/T wi= $(func+"_Info") else Redimension/N=(curRow+gNumPeaks,-1) wi endif wi[curRow]= infostr return curRow end // This function operates on the actual fit coefficients and not on the munged // versions stored in masterCoef. This is to make it easier to use the covariance // matrix to calculate sigmas. To see how the coef wave is layed out, see the // GaussFit Help, LorentzianFit Help and VoigtFit Help files found in // the :More Extensions:Curve Fitting: folder. // Function fPrintPeakParams() String func= "" NVAR gFitType= root:Packages:WMmpFitting:gFitType NVAR g1Width= root:Packages:WMmpFitting:g1Width NVAR g1Shape= root:Packages:WMmpFitting:g1Shape NVAR gNumPeaks= root:Packages:WMmpFitting:gNumPeaks NVAR gDoBaseline= root:Packages:WMmpFitting:gDoBaseline Wave fc= :WMPeakFits:Current:coef Wave fcov= :WMPeakFits:Current:M_Covar Variable first,nc // first=first non-baseline coef, nc= number of coefs per peak Variable posIndex,ampIndex,widthIndex,shapeIndex // width and shape indicies may be constant if g1Width or g1Shape are true if( gDoBaseline ) first= 6 else first=1 endif if( gFitType==0 ) // Gaussian = k0*exp(-((x-k1)/k2)^2) func= "Gaussian" nc= 3-g1Width widthIndex= first // will be used only if g1Width first += g1Width endif if( gFitType==1 ) // Lorentzian = k0*/( (x-k1)^2 + k2 ) func= "Lorentzian" nc= 3-g1Width widthIndex= first // will be used only if g1Width first += g1Width endif if( gFitType==2 ) // VoigtFit = k0*Voigt(k1*(x-k2),k3) func= "Voigt" nc= 4-g1Width-g1Shape shapeIndex= first first +=g1Shape widthIndex= first // will be used only if g1Width first += g1Width endif if( gFitType==3 ) // ExpGaussian = (k0/k3)*ExGauss(x-k1,k3,k2) // k2= half width*sqrt(1/ln(2), k3= exp decay func= "ExpGaussian" nc= 4-g1Width-g1Shape // g1Width not used at present shapeIndex= first first +=g1Shape widthIndex= first // will be used only if g1Width first += g1Width endif if( gFitType==4 ) // ExpConvExp = (k0/k3)*ExpConvExp(x-k1,k2,k3) // k2= exp decay 1 (rise), k3= exp decay 2 (fall) func= "ExpConvExp" nc= 4-g1Width-g1Shape // g1Width not used at present shapeIndex= first first +=g1Shape widthIndex= first // will be used only if g1Width first += g1Width endif printf "For %s peaks:\r",func Variable curRow= PrepareResultsWaves(func) WAVE wr= $(func+"_Results") // results wave Variable i=0,pos,posSigma,width,widthSigma,amp,ampSigma,shape,shapeSigma Variable area,areaSigma,wg,wgSigma,wl,wlSigma do ampIndex= first+nc*i // amp index is same for all fit types if( gFitType==0 ) // Gaussian = k1*exp(-((x-k2)/k3)^2) posIndex= ampIndex+1 // K2 if( g1Width==0 ) widthIndex= posIndex+1 // K3 endif amp= fc[ampIndex] ampSigma= sqrt(fcov[ampIndex][ampIndex]) // amp is easy for gaussian width= fc[widthIndex] widthSigma= sqrt(fcov[widthIndex][widthIndex]) // even width is easy for gaussian area= amp*width*sqrt(Pi) areaSigma= area*sqrt( (ampSigma/amp)^2 + (widthSigma/width)^2 +2*fcov[ampIndex][widthIndex]/(amp*width) ) width *= 2*sqrt(ln(2)) widthSigma *= 2*sqrt(ln(2)) endif if( gFitType==1 ) // Lorentzian = k1*/( (x-k2)^2 + k3 ) posIndex= ampIndex+1 // K2 if( g1Width==0 ) widthIndex= posIndex+1 // K3 endif amp= fc[ampIndex] ampSigma= sqrt(fcov[ampIndex][ampIndex]) width= fc[widthIndex] widthSigma= sqrt(fcov[widthIndex][widthIndex]) area= Pi*amp/sqrt(width) areaSigma= (Pi/width)*sqrt(width*ampSigma^2 + (widthSigma*amp)^2/(4*width) -amp*fcov[ampIndex][widthIndex]) widthSigma= widthSigma/sqrt(width) width= 2*sqrt(width) endif if( gFitType==2 ) // VoigtFit = k0*Voigt(k1*(x-k2),k3) if( g1Width==0 ) widthIndex= ampIndex+1 // K1 posIndex= ampIndex+2 // K2 else posIndex= ampIndex+1 // K2 endif if( g1Shape==0 ) shapeIndex= posIndex+1 // K3 endif amp= fc[ampIndex] ampSigma= sqrt(fcov[ampIndex][ampIndex]) width= fc[widthIndex] // This is the width affecting paramer but not a real width widthSigma= sqrt(fcov[widthIndex][widthIndex]) shape= fc[shapeIndex] shapeSigma= sqrt(fcov[shapeIndex][shapeIndex]) area= amp*sqrt(pi)/width areaSigma= area*sqrt( (ampSigma/amp)^2 + (widthSigma/width)^2 - 2*fcov[ampIndex][widthIndex]/(amp*width) ) wg= sqrt(ln(2))/width // gaussian width wgSigma= (wg/width)*widthSigma // gaussian width sigma wl= shape/width // lorentzian width wlSigma= wl*sqrt( (shapeSigma/shape)^2 + (widthSigma/width)^2 - 2*fcov[shapeIndex][widthIndex]/(shape*width) ) // lorentzian width sigma // This is an approximation so we do not calculate an error. width= wl/2 + sqrt( wl^2/4 + wg^2) // voigt width widthSigma= NaN width *=2 // the above were half width at half max and we report fwhm wg *= 2 wgSigma *= 2 wl *= 2 wlSigma *= 2 endif if( gFitType==3 ) // ExpGaussian = (k0/k3)*ExGauss(x-k1,k3,k2) // k2= half width*sqrt(1/ln(2), k3= exp decay if( g1Width==0 ) widthIndex= ampIndex+2 // K2 posIndex= ampIndex+1 // K1 else posIndex= ampIndex+1 // K1 endif if( g1Shape==0 ) shapeIndex= ampIndex+3 // K3 endif amp= fc[ampIndex] // amp parameter and not real amp ampSigma= sqrt(fcov[ampIndex][ampIndex]) width= fc[widthIndex]*2*sqrt(1/ln(2)) // This is not quite right. It should be 2/sqrt rather than 2*sqrt but this gives fairly close agreement with pure gaussian fit. Need to resolve this. widthSigma= sqrt(fcov[widthIndex][widthIndex])*2*sqrt(1/ln(2)) shape= fc[shapeIndex] // this is the exp decay const (exp(-decay*x) shapeSigma= sqrt(fcov[shapeIndex][shapeIndex]) area= amp/shape // k0/k3 -- ExGauss is unity area areaSigma= area*sqrt( (ampSigma/amp)^2 + (shapeSigma/shape)^2 - 2*fcov[ampIndex][shapeIndex]/(amp*shape) ) endif if( gFitType==4 ) // ExpConvExp = (k0/k3)*ExpConvExp(x-k1,k2,k3) // k2= exp decay 1 (rise), k3= exp decay 2 (fall) if( g1Width==0 ) widthIndex= ampIndex+2 // K2 posIndex= ampIndex+1 // K1 else posIndex= ampIndex+1 // K1 endif if( g1Shape==0 ) shapeIndex= ampIndex+3 // K3 endif amp= fc[ampIndex] // amp parameter and not real amp ampSigma= sqrt(fcov[ampIndex][ampIndex]) width= fc[widthIndex] // This is actually just the first exp decay const and is not width widthSigma= sqrt(fcov[widthIndex][widthIndex]) shape= fc[shapeIndex] // this is the 2nd exp decay const shapeSigma= sqrt(fcov[shapeIndex][shapeIndex]) area= amp/shape // k0/k3 -- ExpConvExp is unity area areaSigma= area*sqrt( (ampSigma/amp)^2 + (shapeSigma/shape)^2 - 2*fcov[ampIndex][shapeIndex]/(amp*shape) ) endif pos= fc[posIndex] // position of peak posSigma= sqrt(fcov[posIndex][posIndex]) // position err is easy wr[curRow+i][0]= {{pos},{posSigma},{area},{areaSigma},{width},{widthSigma}} // this much is common (even if use different column labels) if( gFitType<=2 ) // gauss, lor voigt printf "Peak#%d: position= %g+/-%g, area= %g+/-%g, width (fwhm)= %g+/-%g\r",i+1,pos,posSigma,area,areaSigma,width,widthSigma endif if( gFitType==2 ) // VoigtFit = k0*Voigt(k1*(x-k2),k3) printf "\t Gaussian width= %g+/-%g, Lorentzian width= %g+/-%g, shape= %g+/-%g\r",wg,wgSigma,wl,wlSigma,shape,shapeSigma wr[curRow+i][6]= {{wg},{wgSigma},{wl},{wlSigma},{shape},{shapeSigma}} endif if( gFitType==3 ) // ExpGaussian printf "Peak#%d: position= %g+/-%g, area= %g+/-%g, Gauss width= %g+/-%g\r",i+1,pos,posSigma,area,areaSigma,width,widthSigma printf "\t Exp constant= %g+/-%g\r",shape,shapeSigma wr[curRow+i][6]= {{shape},{shapeSigma}} endif if( gFitType==4 ) // ExpConvExp printf "Peak#%d: position= %g+/-%g, area= %g+/-%g, k1= %g+/-%g,k2= %g+/-%g\r",i+1,pos,posSigma,area,areaSigma,width,widthSigma,shape,shapeSigma wr[curRow+i][6]= {{shape},{shapeSigma}} endif SetDimLabel 0,curRow+i,$("Peak "+num2istr(i)),wr i += 1 while(i gNumPeaks ) gCurPeak= 1 endif SetVariable SetPkNum limits={1,gNumPeaks,1},win=FitSetupPanel Variable i= 6+4*(gCurPeak-1) gPeakAmp=w[i];gPeakPos= w[i+1]; gPeakWidth= w[i+2] ; gVoigtShape= w[i+3] CheckBox checkAmp value=hw[i],win=FitSetupPanel CheckBox checkPos value=hw[i+1],win=FitSetupPanel CheckBox checkWid value=hw[i+2],win=FitSetupPanel CheckBox checkVS value=hw[i+3],win=FitSetupPanel End Function SetVarProcNPeaks(ctrlName,varNum,varStr,varName) : SetVariableControl String ctrlName Variable varNum String varStr String varName NVAR gNumPeaks= root:Packages:WMmpFitting:gNumPeaks NVAR gCurPeak= root:Packages:WMmpFitting:gCurPeak SetVariable SetPkNum limits={1,gNumPeaks,1} if( exists(":WMPeakFits:Current:masterCoef") == 0 ) return 0 endif DoSetDataRange() wave w= $":WMPeakFits:Current:masterCoef" Variable oldNpeaks= (numpnts(w)-6)/4,i Redimension/N=(6+4*gNumPeaks) w,$":WMPeakFits:Current:masterCoefHold" if( oldNpeaks < gNumPeaks ) do i= 6+4*(oldNpeaks) w[i]= w[i-4]*0.9 // new peak amp smaller than last w[i+1]= w[i-3]+2*w[i-2] // new pos one peak width from last w[i+2]= w[i-2] // make sure width parameter is not zero (else get nan from gauss) w[i+3]= 1 // always a reasonable guess for voigt oldNpeaks+=1 while(oldNpeaks gNumPeaks ) gCurPeak= gNumPeaks UpdateCurSettings() endif endif CalculateData() End // Speical purpose to gen hold string from the hold wave Function/S GenHoldStrForFitProc(w) Wave w variable ntot= numpnts(w) String s="" Variable i=0 do if( w[i] == 0 ) s += "0" else s += "1" endif i+=1 while(igRangeEnd if( gRangeReversed ) variable tmp= gRangeBegin gRangeBegin= gRangeEnd gRangeEnd= tmp endif return isGraphed End Function ButtonProcSet(ctrlName) : ButtonControl String ctrlName NewDataFolder/O WMPeakFits // contains material about one data set (names of data, sub-range DFs) NewDataFolder/O :WMPeakFits:Current // contains material specific to a sub-range of one data set (peaks, coefs etc) SVAR gCurDataFolder= root:Packages:WMmpFitting:gCurDataFolder SVAR gFitDataName= root:Packages:WMmpFitting:gFitDataName SVAR gXDataName= root:Packages:WMmpFitting:gXDataName SVAR gYDataName= root:Packages:WMmpFitting:gYDataName SVAR gResidsName= root:Packages:WMmpFitting:gResidsName NVAR gNumPeaks= root:Packages:WMmpFitting:gNumPeaks NVAR gCurPeak= root:Packages:WMmpFitting:gCurPeak Variable/G :WMPeakFits:Current:gRangeBegin,:WMPeakFits:Current:gRangeEnd,:WMPeakFits:Current:gRangeReversed NVAR gRangeBegin= :WMPeakFits:Current:gRangeBegin NVAR gRangeEnd= :WMPeakFits:Current:gRangeEnd Variable/G :WMPeakFits:Current:gXMin,:WMPeakFits:Current:gXMax NVAR gXMin= :WMPeakFits:Current:gXMin NVAR gXMax= :WMPeakFits:Current:gXMax String/G :WMPeakFits:yDataName,:WMPeakFits:xDataName SVAR yDataName= :WMPeakFits:yDataName SVAR xDataName= :WMPeakFits:xDataName ControlInfo popupYData gYDataName= S_value ControlInfo popupXData gXDataName= S_value if( strlen(gYDataName)==0 || strlen(gXDataName) == 0 ) DoAlert 0,"Choose X and Y data first" return 0 endif if( WaveExists($gYDataName) == 0 ) DoAlert 0,"Y data not set yet" return 0 endif if( CmpStr(gYDataName,"_Calculated_")!=0 && WaveExists($gYDataName) == 0 ) DoAlert 0,"X data not set yet" return 0 endif gCurDataFolder= GetDataFolder(1) yDataName= gYDataName // for revisit feature when we have been working on a different data set xDataName= gXDataName Variable reset // used to check if we are resetting using the same data or if we are starting fresh reset= CmpStr(gFitDataName, "fit_"+gYDataName)==0 reset *= CmpStr(gResidsName, "res_"+gYDataName)==0 if( reset ) reset *= numpnts($gYDataName)==numpnts($gResidsName) endif if( reset == 0 ) gFitDataName= "fit_"+gYDataName gResidsName= "res_"+gYDataName Duplicate/O $gYDataName,$gFitDataName,$gResidsName Wave w= $gResidsName w= NaN Wave w= $gFitDataName w= NaN printf "Set Y data= %s, X data= %s, fit wave= %s, residuals= %s\r",gYDataName,gXDataName,gFitDataName,gResidsName else print "Reset using same data" endif Variable isGraphed= DoSetDataRange() // AUTOFIND Variable isAuto= isGraphed %& NumVarOrDefault("root:Packages:WMmpFitting:gDoAutoPeakFind",0); if( isAuto ) DoMP_EstPeakNoiseAndSmfact() gNumPeaks= DoMP_AutoFindPeaks() if( gNumPeaks==0 ) isAuto= 0 else gCurPeak= 1 SetVariable SetPkNum limits={1,gNumPeaks,1} endif endif if( gNumPeaks == 0 ) // user hasn't set this yet gCurPeak= 1 gNumPeaks= 2 // arbitrary first SetVariable SetPkNum limits={1,gNumPeaks,1} endif Make/D/O $":WMPeakFits:Current:masterCoef" // LH030106: force double precision Make/O/B $":WMPeakFits:Current:masterCoefHold"={1,1} Redimension/N=(6+4*gNumPeaks) $":WMPeakFits:Current:masterCoef",$":WMPeakFits:Current:masterCoefHold" wave cw= $":WMPeakFits:Current:masterCoef" Variable xmin,xmax if( cmpstr(gXDataName,"_calculated_") == 0 ) xmin= pnt2x($gYDataName,gRangeBegin) xmax= pnt2x($gYDataName,gRangeEnd) else WaveStats/Q/R=[gRangeBegin,gRangeEnd] $gXDataName xmin=V_min xmax=V_max endif gXMin= xmin gXMax= xmax WaveStats/Q/R=[gRangeBegin,gRangeEnd] $gYDataName cw[0]= (xmin+xmax)/2 cw[1]= xmax-xmin if( abs(V_min) < 0.01*abs(V_max) ) // protect against near zero baseline (singlar matrix) cw[2]= 0.01*V_max // estimate of offset else cw[2]= V_min // estimate of offset endif cw[3]= (V_max-V_min)*0.01 // fairly arbitrary estimate for ... cw[4]= cw[3] // coeff of x cw[5]= cw[3] // coeff of x^2 cw[6]= cw[3] // coeff of x^3 // AUTOFIND if( isAuto ) DoMPSetCoefsFromAutoFind(6,4,cw) else Variable i,cp=1,delta= (xmax-xmin)/(gNumPeaks+1) do i=6+4*(cp-1) cw[i]= {V_max-V_min,xmin+cp*delta,delta/10,1} cp += 1 while( cp<=gNumPeaks) endif UpdateCurSettings() CalculateData() End // NOTE: You can create an Override function of the same name in your procedure window if // you need to customize graph creation. Function MP_MakeGraph() SVAR gXDataName= root:Packages:WMmpFitting:gXDataName SVAR gYDataName= root:Packages:WMmpFitting:gYDataName SVAR gFitDataName= root:Packages:WMmpFitting:gFitDataName SVAR gResidsName= root:Packages:WMmpFitting:gResidsName Variable isYN= cmpstr(gXDataName,"_calculated_") == 0 if( isYN ) Display /W=(5,42,410,401) $gYDataName,$gFitDataName AppendToGraph/L=lr $gResidsName else Display /W=(5,42,410,401) $gYDataName,$gFitDataName vs $gXDataName AppendToGraph/L=lr $gResidsName vs $gXDataName endif ModifyGraph margin(left)=73,wbRGB=(49151,65535,49151),gbRGB=(49151,60031,65535) ModifyGraph mode($gYDataName)=3,mode($gResidsName)=2 ModifyGraph marker($gYDataName)=8,marker($gResidsName)=19 ModifyGraph lSize($gFitDataName)=2,lSize($gResidsName)=2 ModifyGraph rgb($gFitDataName)=(0,0,65535) ModifyGraph msize($gYDataName)=2,msize($gResidsName)=2 ModifyGraph mirror(left)=1,mirror(lr)=1 ModifyGraph nticks(left)=4,nticks(lr)=2 ModifyGraph minor=1 ModifyGraph lowTrip(lr)=0.001 ModifyGraph standoff(left)=0,standoff(bottom)=0 ModifyGraph lblPosMode(left)=1,lblPosMode(lr)=1 ModifyGraph freePos(lr)=0 ModifyGraph axisEnab(left)={0,0.75} ModifyGraph axisEnab(lr)={0.8,1} Label left "Amplitude" Label bottom "Wavelength, \\U" Label lr "Residuals, \\U" SetAxis/A/N=1 left SetAxis/A bottom SetAxis/A/N=1/E=2 lr EndMacro Function SetVarProcCoef(ctrlName,varNum,varStr,varName) : SetVariableControl String ctrlName Variable varNum String varStr String varName SetAPeak() End Proc BProcSaveSet(ctrlName) : ButtonControl String ctrlName if( CheckPeakPackage() ) return endif Duplicate/O $":WMPeakFits:Current:masterCoef" $(":WMPeakFits:Current:CoefSet"+num2istr(root:Packages:WMmpFitting:gSaveSet)) Duplicate/O $":WMPeakFits:Current:masterCoefHold" $(":WMPeakFits:Current:CoefHoldSet"+num2istr(root:Packages:WMmpFitting:gSaveSet)) End Proc PProcRecall(ctrlName,popNum,popStr) : PopupMenuControl String ctrlName Variable popNum String popStr if( CheckPeakPackage() ) return endif Duplicate/O $":WMPeakFits:Current:"+popStr $":WMPeakFits:Current:masterCoef" root:Packages:WMmpFitting:gSaveSet= str2num(popStr[strlen("CoefSet"),100]) Duplicate/O $(":WMPeakFits:Current:CoefHoldSet"+num2istr(root:Packages:WMmpFitting:gSaveSet)), $":WMPeakFits:Current:masterCoefHold" Variable npks= (numpnts($":WMPeakFits:Current:masterCoef")-6)/4 root:Packages:WMmpFitting:gNumPeaks= npks // NOTE: done in two steps because previous rhs would have been evaluted in context of DF= root:Packages: etc UpdateCurSettings() CalculateData() SetVariable SetPkNum limits={1,root:Packages:WMmpFitting:gNumPeaks,1} End Function/S ListCoefSetWaves() if( !DataFolderExists(":WMPeakFits:Current") ) return "" endif SetDataFolder :WMPeakFits:Current String s= WaveList("CoefSet*",";","") SetDataFolder ::: return s end // NOTE: when updating this, remember to insert "DoWindow/C FitSetupPanel" and set initial value for popupYData and popupXData // to "Select a Y wave" and "Select an X" Proc MakeFitSetupPanel() PauseUpdate; Silent 1 // building window... NewPanel /K=1 /W=(445,43,672,495) DoWindow/C FitSetupPanel SetDrawLayer UserBack SetDrawEnv fsize= 9 DrawText 189,163,"Hold" SetVariable setvar0,pos={117,148},size={38,15},proc=SetVarProcNPeaks,title="of" SetVariable setvar0,limits={1,1000,0},value= root:Packages:WMmpFitting:gNumPeaks,bodyWidth= 25 SetVariable SetPkNum,pos={16,148},size={94,15},proc=SetPeakNumProc,title="For Peak #" SetVariable SetPkNum,limits={1,4,1},value= root:Packages:WMmpFitting:gCurPeak,bodyWidth= 35 SetVariable setvar2,pos={46,174},size={140,15},proc=SetVarProcCoef,title="Amplitude" SetVariable setvar2,limits={-Inf,Inf,0},value= root:Packages:WMmpFitting:gPeakAmp,bodyWidth= 87 SetVariable setvar3,pos={52,193},size={134,15},proc=SetVarProcCoef,title=" Position" SetVariable setvar3,format="%g" SetVariable setvar3,limits={-Inf,Inf,0},value= root:Packages:WMmpFitting:gPeakPos,bodyWidth= 87 SetVariable setvar4,pos={68,214},size={118,15},proc=SetVarProcCoef,title="width" SetVariable setvar4,format="%g" SetVariable setvar4,limits={-Inf,Inf,0},value= root:Packages:WMmpFitting:gPeakWidth,bodyWidth= 87 Button DoFitButton,pos={51,422},size={114,25},proc=fitProc,title="Do Fit" SetVariable setvar5,pos={67,234},size={119,15},disable=1,proc=SetVarProcCoef,title="Shape" SetVariable setvar5,limits={0,Inf,0},value= root:Packages:WMmpFitting:gVoigtShape,bodyWidth= 87 PopupMenu popupFunc,pos={21,326},size={136,20},proc=PopProcFunc,title="Function:" PopupMenu popupFunc,mode=1,popvalue="Gaussian",value= #"\"Gaussian;Lorentzian;Voigt;ExpGaussian;ExpConvExp\"" CheckBox checkBL,pos={22,349},size={59,14},proc=WMMultiPeakFit#BaselineCheckProc,title="Baseline" CheckBox checkBL,variable= root:Packages:WMmpFitting:gDoBaseline CheckBox checkZeroBL,pos={124,349},size={76,14},title="Zero Offset",variable= root:Packages:WMmpFitting:gZeroBaseline // JP070813 Variable disable= root:Packages:WMmpFitting:gDoBaseline ? 1 : 0 CheckBox checkZeroBL,disable=disable // JP070813 CheckBox checkCW,pos={124,370},size={55,14},proc=CheckProc1Width,title="1 width" CheckBox checkCW,variable= root:Packages:WMmpFitting:g1Width CheckBox checkCS,pos={124,371},size={55,14},disable=1,proc=CheckProc1Shape,title="1 shape" CheckBox checkCS,variable= root:Packages:WMmpFitting:g1Shape CheckBox checkUseX,pos={22,371},size={74,14},proc=CheckProcUseXFUNCs,title="use XFUNCs" CheckBox checkUseX,variable= root:Packages:WMmpFitting:gUseXFUNCs PopupMenu popupYData,pos={17,18},size={106,20},title="Y: " PopupMenu popupYData,mode=1,popvalue="Select a Y wave",value= #"WaveList(\"*\",\";\",\"\")" PopupMenu popupXData,pos={17,40},size={106,20},title="X: " PopupMenu popupXData,mode=3,popvalue="Select an X",value= #"\"_calculated_;\"+WaveList(\"*\",\";\",\"\")" Button button0,pos={83,64},size={50,17},proc=ButtonProcNewGraph,title="Graph" Button button1,pos={22,64},size={50,17},proc=ButtonProcSet,title="Set" Button button2,pos={146,64},size={63,17},proc=MakeFitTweaksPanel,title="Tweaks" Button bSave,pos={16,266},size={50,20},proc=BProcSaveSet,title="Save" SetVariable setvar1,pos={70,267},size={65,15},title="Set",format="%d" SetVariable setvar1,limits={0,10,1},value= root:Packages:WMmpFitting:gSaveSet PopupMenu popRecall,pos={144,266},size={65,20},proc=PProcRecall,title="Recall" PopupMenu popRecall,mode=0,value= #"ListCoefSetWaves()" ValDisplay valdisp0,pos={48,395},size={152,14},title="Chi Square" ValDisplay valdisp0,limits={0,0,0},barmisc={0,1000} ValDisplay valdisp0,value= #"root:Packages:WMmpFitting:gChiSquare" CheckBox checkAmp,pos={189,172},size={16,14},proc=CheckProcHold,title="" CheckBox checkAmp,value= 0 CheckBox checkPos,pos={189,191},size={16,14},proc=CheckProcHold,title="" CheckBox checkPos,value= 0 CheckBox checkWid,pos={189,212},size={16,14},proc=CheckProcHold,title="" CheckBox checkWid,value= 0 CheckBox checkVS,pos={189,232},size={16,14},disable=1,proc=CheckProcHold,title="" CheckBox checkVS,value= 0 Button buttonAuto,pos={22,117},size={50,17},proc=ShowAutoBP,title="Auto..." Button buttonMan,pos={81,117},size={50,17},proc=ShowManBP,title="Man..." GroupBox gb0,pos={5,1},size={215,87},title="Data",fStyle=1,fColor=(0,0,65535) GroupBox gb1,pos={4,96},size={217,200},title="Initial Values",fStyle=1 GroupBox gb1,fColor=(0,0,65535) GroupBox gb1sep0,pos={5,139},size={214,3} GroupBox gb1sep2,pos={5,255},size={214,3} GroupBox gb2,pos={4,311},size={218,106},title="Fitting Function",fStyle=1 GroupBox gb2,fColor=(0,0,65535) GroupBox gb1sep3,pos={5,166},size={214,3} EndMacro // ********************************************************************* // AUTOFIND Function ShowAutoFindPanel() DoWindow/F AutoFindPanel if( V_Flag ) return 0 endif NewPanel/K=1 /W=(635,345,836,564) DoWindow/C AutoFindPanel AutoPositionWindow SetDrawLayer UserBack SetDrawEnv xcoord= rel,ycoord= abs DrawLine 0,56,1,56 SetDrawEnv xcoord= rel,ycoord= abs DrawLine 0,127,1,127 SetVariable svNoise,pos={56,86},size={112,15},title="Noise est:",format="%.2g" SetVariable svNoise,limits={-Inf,Inf,0},value= root:Packages:WMmpFitting:gNoiseEstFromAutoFind SetVariable smFact,pos={58,106},size={109,15},title="Smoothing" SetVariable smFact,limits={-Inf,Inf,0},value= root:Packages:WMmpFitting:gSmoothEstFromAutoFind Button AutoEst,pos={23,61},size={126,20},proc=AutoFindPeakBP,title="Estimate Params" Button AutoFind,pos={25,180},size={126,20},proc=AutoFindPeakBP,title="Find Peaks" CheckBox CheckAuto,pos={28,22},size={160,20},title="Do Autofind on Set",variable=root:Packages:WMmpFitting:gDoAutoPeakFind SetVariable NoiseAmpFactor,pos={18,135},size={170,15},title="Noise Amp Factor" SetVariable NoiseAmpFactor,format="%d" SetVariable NoiseAmpFactor,limits={-2,5,1},value= root:Packages:WMmpFitting:gMinPeakNoiseFactor SetVariable MinFract,pos={18,156},size={170,15},title="Minimum Fraction" SetVariable MinFract,limits={0,0.5,0.01},value= root:Packages:WMmpFitting:gMinPeakFraction EndMacro Function AutoFindPeakBP(ctrlName) : ButtonControl String ctrlName if( CmpStr(ctrlName,"AutoEst") == 0 ) DoMP_EstPeakNoiseAndSmfact() endif if( CmpStr(ctrlName,"AutoFind") == 0 ) DoMP_AutoFIndNoSet() endif End Function DoAutoCheck(ctrlName,checked) : CheckBoxControl String ctrlName Variable checked Variable/G root:Packages:WMmpFitting:gDoAutoPeakFind = checked End Function ShowAutoBP(ctrlName) : ButtonControl String ctrlName ShowAutoFindPanel() End Function DoMP_EstPeakNoiseAndSmfact() SVAR gYDataName= root:Packages:WMmpFitting:gYDataName NVAR gRangeBegin= :WMPeakFits:Current:gRangeBegin NVAR gRangeEnd= :WMPeakFits:Current:gRangeEnd if( NumType(gRangeEnd-gRangeBegin)!=0 ) DoAlert 0,"Click the Setup button first" return 0 endif Variable/C ctmp= EstPeakNoiseAndSmfact($gYDataName,gRangeBegin,gRangeEnd) Variable/G root:Packages:WMmpFitting:gNoiseEstFromAutoFind= real(ctmp) Variable/G root:Packages:WMmpFitting:gSmoothEstFromAutoFind= imag(ctmp) end Function DoMP_AutoFindPeaks() SVAR gYDataName= root:Packages:WMmpFitting:gYDataName SVAR gXDataName= root:Packages:WMmpFitting:gXDataName NVAR gRangeBegin= :WMPeakFits:Current:gRangeBegin NVAR gRangeEnd= :WMPeakFits:Current:gRangeEnd NVAR gNoiseEstFromAutoFind= root:Packages:WMmpFitting:gNoiseEstFromAutoFind NVAR gSmoothEstFromAutoFind= root:Packages:WMmpFitting:gSmoothEstFromAutoFind NVAR gMinPeakFraction= root:Packages:WMmpFitting:gMinPeakFraction NVAR gMinPeakNoiseFactor= root:Packages:WMmpFitting:gMinPeakNoiseFactor if( NumType(gRangeEnd-gRangeBegin)!=0 ) DoAlert 0,"Click the Setup button first" return 0 endif Variable npks,noiseFactor= gNoiseEstFromAutoFind*3^gMinPeakNoiseFactor npks= AutoFindPeaks($gYDataName,gRangeBegin,gRangeEnd, noiseFactor, gSmoothEstFromAutoFind,Inf) Wave wpi= W_AutoPeakInfo // may or may not exist if( npks>0 ) AdjustAutoPeakInfoForX(wpi,$gYDataName,$gXDataName) npks= TrimAmpAutoPeakInfo(wpi,gMinPeakFraction) endif return npks end // AUTOFIND Function DoMPSetCoefsFromAutoFind(firstCoef,numCoef,cw) Variable firstCoef,numCoef Wave cw if( numCoef!=4 ) Abort "Rewrite DoMPSetCoefsFromAutoFind. Mismatch in number of coefs for each peak" endif Wave wpi= W_AutoPeakInfo Variable npks= DimSize(wpi,0) if( npks==0 ) return 0 endif Make/O/N=(npks) wpiTmpIndex,wpiTmpPos=wpi[P][0] //extract the column of positions MakeIndex wpiTmpPos,wpiTmpIndex NVAR gFitType= root:Packages:WMmpFitting:gFitType Variable i,cp=0,cps do i= firstCoef+numCoef*(cp) cps= wpiTmpIndex[cp] // access autofind data in ascending position order // MYCUSTOM - edit to translate results of autofind into initial guesses for peaks cw[i]= wpi[cps][2] cw[i+1]=wpi[cps][0] cw[i+2]= wpi[cps][1] // NOTE: if the following algorithm for converting gaussian guesses to type 3 and 4 changes, also update AdjustMCForFunctionChange if( gFitType==3 ) // ExpGaussian = (k0/k3)*ExGauss(x-k1,k3,k2) // k2= half width*sqrt(1/ln(2), k3= exp decay cw[i+3]= 1/cw[i+2] // NOTE: ExpGaussian goes wacko if k3 is greater than about 5/k2 cw[i]= cw[i]*cw[i+2]*sqrt(Pi)*cw[i+3] // cw[i]/cw[i+3] is the area so the guess for cw[i] is the gaussian area times cw[i+3] cw[i] *= 2 // but the amp parameter so carefully calculated is often too low so we arbitrarilly bump it up here elseif( gFitType==4 ) // ExpConvExp = (k0/k3)*ExpConvExp(x-k1,k2,k3) // k2= exp decay 1 (rise), k3= exp decay 2 (fall) cw[i+3]= 0.3/cw[i+2] cw[i+2] = 10*cw[i+3] // cw[i+1] -= 1/cw[i+3] cw[i] *=2 else cw[i+3]= 1 endif cp += 1 while( cp 0 ) EndManualPeakMode() endif return 0 end Function ShowManBP(ctrlName) : ButtonControl String ctrlName ShowManualPeaksPanel() End Function ManPeakBP(ctrlName) : ButtonControl String ctrlName if( CmpStr(ctrlName,"KillCurrent") == 0 ) DoMP_KillCurrentPeak() endif if( CmpStr(ctrlName,"InsertNew") == 0 ) KillControl KillCurrent KillControl InsertNew KillControl EditOld Button Finish,pos={9,31},size={160,20},proc=ManPeakBP,title="Finish Insert" SVAR gYDataName= root:Packages:WMmpFitting:gYDataName StartManualPeakMode($gYDataName,"MB_PeakEditCallback",$"",$"") endif if( CmpStr(ctrlName,"EditOld") == 0 ) KillControl KillCurrent KillControl InsertNew KillControl EditOld Button Finish,pos={8,55},size={160,20},proc=ManPeakBP,title="Finish Edit Old" DoMP_EditOldPeak() endif if( CmpStr(ctrlName,"Finish") == 0 ) EndManualPeakMode() KillControl Finish Button KillCurrent,pos={8,7},size={160,20},proc=ManPeakBP,title="Delete Current Peak" Button InsertNew,pos={9,31},size={160,20},proc=ManPeakBP,title="Insert New Peak" Button EditOld,pos={8,55},size={160,20},proc=ManPeakBP,title="Edit Old Peak" endif if( CmpStr(ctrlName,"Undo") == 0 ) EndManualPeakMode() KillControl Finish Button KillCurrent,pos={8,7},size={160,20},proc=ManPeakBP,title="Delete Current Peak" Button InsertNew,pos={9,31},size={160,20},proc=ManPeakBP,title="Insert New Peak" Button EditOld,pos={8,55},size={160,20},proc=ManPeakBP,title="Edit Old Peak" DoMP_UndoManMods() endif End Function DoMP_KillCurrentPeak() NVAR gNumPeaks= root:Packages:WMmpFitting:gNumPeaks NVAR gCurPeak= root:Packages:WMmpFitting:gCurPeak NVAR gFirstCoef= root:Packages:WMmpFitting:gFirstCoef NVAR gNumCoef= root:Packages:WMmpFitting:gNumCoef Wave mc= $":WMPeakFits:Current:masterCoef" Wave mch= $":WMPeakFits:Current:masterCoefHold" // for undo Duplicate/O mc,$":WMPeakFits:Current:masterCoefManBak" Duplicate/O mch $":WMPeakFits:Current:CoefHoldSetManBak" DeletePoints gFirstCoef+(gCurPeak-1)*gNumCoef,gNumCoef,mc,mch gNumPeaks -= 1 SetVariable SetPkNum win=FitSetupPanel, limits={1,gNumPeaks,1} UpdateCurSettings() CalculateData() // for undo Duplicate/O mc,$":WMPeakFits:Current:masterCoefManRecent" Duplicate/O mch $":WMPeakFits:Current:CoefHoldSetManRecent" end Function DoMP_EditOldPeak() SVAR gYDataName= root:Packages:WMmpFitting:gYDataName NVAR gNumPeaks= root:Packages:WMmpFitting:gNumPeaks NVAR gFirstCoef= root:Packages:WMmpFitting:gFirstCoef NVAR gNumCoef= root:Packages:WMmpFitting:gNumCoef wave mc= $":WMPeakFits:Current:masterCoef" Make/O/T/N=(gNumPeaks) peaknames Make/O/N=(gNumPeaks,gNumCoef+1) peakcoefs=0 Variable pkOffset= GetPeaksOffset(),i,vbaseline Variable doBaselineOffset= 1 NVAR/Z gDoBaselineOffset= root:Packages:WMmpFitting:gDoBaselineOffset if( NVAR_Exists(gDoBaselineOffset) ) doBaselineOffset= gDoBaselineOffset endif for(i=0;i