#pragma rtGlobals=3 #pragma version=4.01 #pragma IgorVersion=7 #pragma ModuleName=BaselineSpline #include // written by tony withers, tony.withers@uwo.ca // please tell me when you find bugs! // How to use Baseline Spline Fit // A 1d wave must be plotted in a graph window before selecting Spline // Baseline from the macros menu. The wave must be plotted on the default // bottom and left axes. A control panel will be added to the left of the // graph window. When 'edit mode' is selected you can drag the nodes. The // spline should update as the nodes are repositioned. When in edit mode // you can add a new node at any position on the graph by positioning the // mouse cursor and clicking while holding down the control key. Nodes // may be deleted by clicking them with the option key held down. // Clicking anywhere in the graph window with the shift key held down // will toggle between node editing mode and 'normal' mode where you can // interact with the graph as usual. execute 'DisplayHelpTopic // Interpolate2' for details of the methods of spline interpolation. // Panel controls // Data wave: select a trace for baseline correction. When switching // between traces the nodes will retain their X-positions but will be // placed on the selected data wave at those positions (unless they lie // outside of the range of the data wave). // Akima: fit an Akima style spline through the nodes. Akima splines can // have sharper bends than cubic splines // Cubic: fit a cubic spline through the nodes. // Ends: At the ends of the spline where the cubic function is not fully // constrained select a method to add a constraint. 1st deriv.: match 1st // derivative; natural: match 2nd derivative. Affects only cubic spline // type. // Linear: connect the nodes with linear segments. // Smoothing: fit a smoothing spline, which does not have to pass exactly // through the nodes. The degree of smoothing is adjustable. // Edit mode: toggle between node editing and normal modes // Reset: set node positions by spacing a set number of nodes at regular // intervals along the section of the data wave displayed in the graph // window. No nodes will be set beyond the range of the graph axes. // Load: reload the node positions from a previous fit. // Subtract: creates a copy of the baseline and a baseline-subtracted // output spectrum in the current data folder and appends them to the // plot; saves the node positions in a 2d wave in the current data // folder. // Version history // 4.01 panel size decreased for compatibility with smaller or lower // resolution screens. Some bugs fixed. // 4.00 Changed to panel interface. Major rewrite. // 3.20 Removed option to prevent nodes from being inserted. Now the /NI // no insert flag is used by default, and we handle node insertion by // using a hook to collect ctrl-clicks. // 3.10 Some changes to the global strings and variables in the package // folder. Tried and failed to add improved guesses for node positions. // 3.01 11/4/16 Bug fix - waves with deltaX<0 produced nodes that were // reverse sorted in X, but Akima calculation cannot deal with reverse // sorted nodes. // 3.00 4/11/16 Added option for Akima style spline. // 2.60 3/30/16 Added option to supress node addition. This is a toggle // that switches on the /NI option in GraphWaveEdit. Updated a lot of // other code. Changed some of the defaults (now saves nodes by default), // so behaviour is not the same as previous version. // 2.50 Log mode to save node positions to file. Can load node positions // from a file. // 2.40 Added textbox reminder of how to toggle graph mode, renamed // procedures for consistency. // 2.30 5/16/11 Bug fix - code was choking on non-existent wave in first // call to SBL_Clear(). Added panel to change fitting options. // 2.10 Call SBL_Clear() from SBL_Subtract(). // 2.0 8/6/09 Incorporated suggestions from JJ Weimer that improve // functionality and clarity of code and compatibility with 6.10 ... and // then wrecked his nice programming with my clumsy code. // 1.43b Allow refit function to switch interpolate flag when number of // nodes is small to allow linear fit. // 1.42 6/8/08 Set y axis to manual range to avoid crazy rescaling if // spline shoots out of range. Show subtracted spectrum whilst fitting. // 1.41 10/9/07 For XY data we now interpolate directly into XY baseline. // Improved guesses for initial node positions for unevenly spaced XY // data. // 1.40 10/9/07 Fixed bug that occurred with baseline subtraction of XY // data. // 1.30 7/24/07 Handles XY data. Baseline is waveform, but data wave need // not be (though it should be reasonably closely spaced). // 1.20 7/3/07 Uses new graphwaveedit flag. Now requires version 6.0.1. Menu "Analysis" Submenu "Packages" "Spline Baseline",/Q, SBL_init() end end Menu "Macros" "Spline Baseline",/Q, SBL_init() end // initialize spline baseline package function SBL_init() if (strlen(WinList("*",";","WIN:1"))==0) // no graphs doalert 0, "Spline baseline requires a trace plotted in a graph window." return 0 endif // make sure the top graph is visible dowindow /F $WinName(0,1) // make sure graph has default axes getaxis /Q left if (v_flag==0) getaxis /Q bottom endif if (v_flag) doalert 0, "Trace must be plotted on bottom/left axes." return 0 endif // clear any package detritus from the last used spline graph SBL_clear() KillWindow /Z SBL_panel // initialize (or reinitialize) the package data folder SBL_resetDFR() DFREF dfr = SBL_getDFREF() wave /SDFR=dfr W_spline_dependency SBL_makePanel() GetWindow /Z SBL_panel activeSW string s_graph=parseFilepath(0, s_value, "#", 0, 0) controlinfo /W=SBL_panel popTrace string s_trace=s_value variable success // place nodes within window success=SBL_resetNodes() if (!success) doalert 0, "Spline baseline requires a trace plotted in a graph window." SBL_clear() KillWindow /Z SBL_panel return 0 endif // set the sub and base waves for this trace success=SBL_resetGraph(s_trace) if (!success) doalert 0, "Spline baseline requires a trace plotted in a graph window." SBL_Clear() KillWindow /Z SBL_panel return 0 endif ModifyGraph zero(left)=1 // set a dependency to trigger interpolation AND update graph when we adjust a node position W_spline_dependency:=SBL_update(root:Packages:SplineFit:W_nodesY) // won't work if we change package folder GraphWaveEdit /W=$s_graph /NI/T=1 /M $"W_nodesY" setWindow $s_graph hook(SBL_hook)=SBL_graphHook return 1 end function SBL_graphHook(s) STRUCT WMWinHookStruct &s if (s.eventcode!=5) // mouseup return 0 endif variable v_key=GetKeyState(0) if (v_key==0) return 0 endif if(v_key&2^2) // shift click // toggle mode SBL_toggleEdit() return 0 endif controlInfo /W=SBL_panel check_edit if(v_value==0) return 0 endif variable insert=0 insert = (stringmatch(igorinfo(2),"Macintosh")) ? (v_key&2^4) : (v_key&2^0) // ctrl if (insert) DFREF dfr = SBL_getDFREF() wave /SDFR=dfr w_nodesY, w_nodesX variable v_X, v_Y v_X=AxisValFromPixel(s.winName, "bottom", s.mouseLoc.h) v_Y=AxisValFromPixel(s.winName, "left", s.mouseLoc.v) // deal with offset controlInfo /W=SBL_panel popTrace string s_info=traceinfo(s.winName,s_value,0) v_Y-=GetNumFromModifyStr(s_info,"offset","{",1) w_nodesX[numpnts(w_nodesX)]={v_X} w_nodesY[numpnts(w_nodesY)]={v_Y} sort w_nodesX, w_nodesX, w_nodesY SBL_update({nan}) endif return 0 end function SBL_toggleEdit() GetWindow /Z SBL_panel activeSW string s_graph=parseFilepath(0, s_value, "#", 0, 0) controlinfo /W=SBL_panel check_edit if (v_value) GraphNormal /W=$s_graph ModifyGraph /W=$s_graph mode(W_nodesY)=2 checkbox check_edit win=SBL_panel, value=0 else // manually scale y axis getaxis /W=$s_graph /Q left setAxis /W=$s_graph left, V_min, V_max ModifyGraph /W=$s_graph mode(W_nodesY)=3 GraphWaveEdit /W=$s_graph /NI/T=1 /M $"W_nodesY" checkbox check_edit win=SBL_panel, value=1 endif end function SBL_resetGraph(s_trace) string s_trace DFREF dfr = SBL_getDFREF() GetWindow /Z SBL_panel activeSW string s_graph=parseFilepath(0, s_value, "#", 0, 0) wave /Z w_data=TraceNameToWaveRef(s_graph, s_trace) if (waveexists(w_data)==0) return 0 endif wave /Z w_x=XWaveRefFromTrace(s_graph, s_trace) if (waveexists(w_x)) // check for monotonic X wave make /free w_diff Differentiate w_x /D=w_diff Wavestats /Q W_diff if (V_max>0 && V_min<0) doalert 0, "X wave is not monatonic! Quitting :(" SBL_clear() killwindow /z SBL_panel return 0 endif endif // string xAxisName= StringByKey("XAXIS", traceinfo(s_graph,s_trace,0)) // string yAxisName= StringByKey("YAXIS", traceinfo(s_graph,s_trace,0)) // manually scale y axis getaxis /Q left setAxis left, V_min, V_max duplicate /O w_data dfr:W_base, dfr:W_sub wave /SDFR=dfr w_nodesX, w_nodesY, w_spline_dependency, w_base, w_sub checkdisplayed /W=$s_graph W_spline_dependency if (v_flag==0) appendtograph /W=$s_graph W_spline_dependency endif // make sure base and sub waves are displayed properly removefromgraph /Z /W=$s_graph w_base, w_sub if (waveexists(w_x)) appendtograph /W=$s_graph W_base vs w_x appendtograph /W=$s_graph W_sub vs w_x else appendtograph /W=$s_graph W_base appendtograph /W=$s_graph W_sub endif ModifyGraph /W=$s_graph hideTrace(W_spline_dependency)=1 // if data are offset, then apply offsets to baseline string s_info=traceinfo(s_graph,s_trace,0) variable trace_offset=GetNumFromModifyStr(s_info,"offset","{",1) make /n=3 /free w_c={0,10000,65535} SBL_chooseColour(w_c, s_graph, s_trace) ModifyGraph /W=$s_graph offset(W_base)={0,trace_offset} ModifyGraph /W=$s_graph rgb(W_base)=(w_c[0],w_c[1],w_c[2]) modifygraph /W=$s_graph live(W_base)=1 ModifyGraph /Z /W=$s_graph offset(W_nodesY)={0,trace_offset} ModifyGraph /W=$s_graph rgb(W_sub)=(0,0,0) ReorderTraces /W=$s_graph $"w_nodesY",{$"w_base"} return 1 end function SBL_resetNodes() GetWindow /Z SBL_panel activeSW string s_graph=parseFilepath(0, s_value, "#", 0, 0) DFREF dfr = SBL_getDFREF() wave /SDFR=dfr W_nodesX, W_nodesY // define some local variables variable i, p_low, p_high, delP, delX, numNodes=5 string s_info variable trace_offset=0 controlinfo /W=SBL_panel popTrace string s_trace=s_value wave /Z w_data=TraceNameToWaveRef(s_graph, s_trace) wave /Z w_x=XWaveRefFromTrace(s_graph, s_trace) if (waveexists(w_data)==0) return 0 endif redimension/N=(numNodes) W_nodesX, W_nodesY // determine where to place nodes on the bottom axis // keep nodes within window GetAxis/Q bottom make /n=2 /free xrange={v_min, v_max} sort xrange, xrange GetAxis/Q left make /n=2 /free yrange={v_min, v_max} sort yrange, yrange // keep nodes within extent of w_data if (waveexists(w_x)) xrange[0]=max(xrange[0],wavemin(w_x)) xrange[1]=min(xrange[1],wavemax(w_x)) else xrange[0]=max(xrange[0], min(pnt2x(w_data, 0), pnt2x(w_data, numpnts(w_data)-1)) ) xrange[1]=min(xrange[1], max(pnt2x(w_data, 0), pnt2x(w_data, numpnts(w_data)-1)) ) endif delX=(xrange[1]-xrange[0])/(numNodes-1) // initialize node positions W_nodesX=xrange[0]+p*delX for (i=0;i x) ? 1 : 0 while(!done && (i2 < numKnots-1)) // edited this to allow extrapolation beyond end knot i1 = i2 - 1 // this edit to allow extrapolation beyond start knot if (i1<0) i1+=1 i2+=1 endif Variable x1, x2, y1, y2, iota1, iota2 x1 = knotX[i1] y1 = knotY[i1] iota1 = knotIota[i1] x2 = knotX[i2] y2 = knotY[i2] iota2 = knotIota[i2] Variable p0, p1, p2, p3, tmp p0 = y1 p1 = iota1 p2 = ( 3 * (y2 - y1)/(x2 - x1) - 2*iota1 - iota2 ) / (x2 - x1) p3 = (iota1 + iota2 - 2 * (y2 - y1)/(x2 - x1)) / (x2 - x1)^2 tmp = x - x1 return p0 + p1 * tmp + p2 * tmp^2 + p3*tmp^3 End