Managing graph-updates in a user-defined function

Hello everybody,

This time I am stuck with the way to program in order to have an instantaneous update of a graph after a user-modification.
Let me explain the situation: My scanning tunnelling spectroscopy (STS) data has some trend (which is not define, can be quadratic, linear, exponential) that I want to remove. For that I developed a function that removes this trend by fitting regions of the data defined by the cursors. The idea is that as far as the cursors are moved, the fit should update and then by clicking on a "save" button I remove the fit to the raw data.
Presently I update the graph by using an infinite loop "do() while(true)". This is not satisfying for two reasons; First, this is silly, and second, this avoids me to update the script by using sliders to select the ROI instead of the graph cursors.

Here is the code I use now. In comment are what I would use with sliders.
Function RemoveLinContribution(srcWave, traceName, graphName, PolyNumber)
    //This function is intended to remove the linear contribution in the measured spectras.
    //If the contribution is flat, there is no use to remove the constant contribution
    //We do a new window, in order to accept the graph
    Wave srcWave
    String  traceName, graphName
    Variable PolyNumber
    //Wave maskWave
    Variable/G saving = 0
    String crs1="", crs2=""
    Button SaveButton, title="Save", proc = SaveRmLinContribution
    Cursor/A=1/W=$graphName A $traceName leftx(srcWave)
    Cursor/A=1/P/W=$graphName B $traceName numpnts(srcWave)-1
    Duplicate/O srcWave rmLinContrib
    Duplicate/O srcWave LineFit//The wave wich will have the original one withouth the slope
    Duplicate/O srcWave maskWave//The mask we will use to fit the line
    Make/O/N=10 crsValues
    AppendToGraph/W=$graphName/C=(0,0,0) LineFit
    //We will construct the panel with the sliders
    //NewPanel /W=(950,161,1749,519) as "Sliders for the trend removal"
//  ShowTools/A
//  SetDrawLayer UserBack
//  DrawText 28,23,"A"; DrawText 34,47,"B"; DrawText 25,67,"C"; DrawText 30,93,"D";
//  DrawText 29,128,"E"; DrawText 38,148,"F";   DrawText 23,173,"G";    DrawText 34,204,"H"
//  DrawText 23,233,"I"; DrawText 23,240,"I";
//  Slider slA,pos={105.00,14.00},size={621.00,17.00},proc=SliderProc
//  Slider slA,limits={0,200,1},value= crsValues[0],vert= 0,ticks= 0
//  Slider slB,pos={107.00,43.00},size={621.00,17.00},proc=SliderProc
//  Slider slB,limits={0,200,1},value= crsValues[1],vert= 0,ticks= 0
//  Slider slC,pos={109.00,73.00},size={621.00,17.00},proc=SliderProc
//  Slider slC,limits={0,200,1},value= crsValues[2],vert= 0,ticks= 0
//  Slider slD,pos={110.00,105.00},size={621.00,17.00},proc=SliderProc
//  Slider slD,limits={0,200,1},value= crsValues[3],vert= 0,ticks= 0
//  Slider slE,pos={113.00,130.00},size={621.00,17.00},proc=SliderProc
//  Slider slE,limits={0,200,1},value= crsValues[4],vert= 0,ticks= 0
//  Slider slF,pos={109.00,162.00},size={621.00,17.00},proc=SliderProc
//  Slider slF,limits={0,200,1},value= crsValues[5],vert= 0,ticks= 0
//  Slider slG,pos={108.00,197.00},size={621.00,17.00},proc=SliderProc
//  Slider slG,limits={0,200,1},value= crsValues[6],vert= 0,ticks= 0
//  Slider slH,pos={111.00,229.00},size={621.00,17.00},proc=SliderProc
//  Slider slH,limits={0,200,1},value= crsValues[7],vert= 0,ticks= 0
//  Slider slI,pos={112.00,258.00},size={621.00,17.00},proc=SliderProc
//  Slider slI,limits={0,200,1},value= crsValues[8],vert= 0,ticks= 0
//  Slider slJ,pos={111.00,285.00},size={621.00,17.00},proc=SliderProc
//  Slider slJ,limits={0,200,1},value= crsValues[9],vert= 0,ticks= 0
//  if(PolyNumber==3)
//      Variable y0=0,A=0,invT=0
//      Prompt y0, "Enter y0 value: " // Set prompt for x param
//      Prompt A, "Enter A value: " // Set prompt for y param
//      Prompt invT, "Enter invT value:"
//      DoPrompt "Enter Values", y0, A, invT
//      if (V_Flag)
//          return -1 // User canceled
//      endif
//      K0 = y0;K1 = A;K2 = -invT;
//  endif
//      Cursor/A=1/W=$graphName A $traceName crsValues[0]
//      Cursor/A=1/W=$graphName B $traceName crsValues[1]
//      Cursor/A=1/W=$graphName C $traceName crsValues[2]
//      Cursor/A=1/W=$graphName D $traceName crsValues[3]
//      Cursor/A=1/W=$graphName E $traceName crsValues[4]
//      Cursor/A=1/W=$graphName F $traceName crsValues[5]
//      Cursor/A=1/W=$graphName G $traceName crsValues[6]
//      Cursor/A=1/W=$graphName H $traceName crsValues[7]
//      Cursor/A=1/W=$graphName I $traceName crsValues[8]
//      Cursor/A=1/W=$graphName J $traceName crsValues[9]
//      DoUpdate
        maskWave = 0
        //We will put '1' in the maskWave for the data points between the cursors
        crs1 = CsrInfo(A); crs2 = CsrInfo(B)
        //If the two cursors A and B are on the graph
        if(cmpstr(crs1, "", 2) && cmpstr(crs2, "", 2))
            maskWave[pcsr(A),pcsr(B)] = 1
        crs1 = CsrInfo(C); crs2 = CsrInfo(D)
        //If the two cursors C and D are on the graph
        if(cmpstr(crs1, "", 2) && cmpstr(crs2, "", 2))
            maskWave[pcsr(C),pcsr(D)] = 1
        crs1 = CsrInfo(E); crs2 = CsrInfo(F)
        //If the two cursors E and F are on the graph
        if(cmpstr(crs1, "", 2) && cmpstr(crs2, "", 2))
            maskWave[pcsr(E),pcsr(F)] = 1
        crs1 = CsrInfo(G); crs2 = CsrInfo(H)
        //If the two cursors G and H are on the graph
        if(cmpstr(crs1, "", 2) && cmpstr(crs2, "", 2))
            maskWave[pcsr(G),pcsr(H)] = 1
        crs1 = CsrInfo(I); crs2 = CsrInfo(J)
        //If the two cursors I and J are on the graph
        if(cmpstr(crs1, "", 2) && cmpstr(crs2, "", 2))
            maskWave[pcsr(I),pcsr(J)] = 1
        //We fit between the cursor position
        //We fit in the regions of the mask = 1
        if(Polynumber == 1)
            CurveFit/Q line srcWave /M=maskWave /D=LineFit
        elseif(Polynumber == 2)
            CurveFit/Q poly 3, srcWave /M=maskWave /D=LineFit
        Wave W_Coef
        if(PolyNumber == 1)
            LineFit[] = W_Coef[0] + W_Coef[1]*pnt2x(LineFit, p)
        elseif(Polynumber == 2)
            LineFit[] = W_Coef[0] + W_Coef[1]*pnt2x(LineFit, p) + W_Coef[2]*pnt2x(LineFit, p)*pnt2x(LineFit, p)
        //sleep/B/S/C=2 1
        PauseForUser/C $graphName
        DFREF srcDF= GetDataFolderDFR()
        String SaveName = nameofwave(srcWave)+"_rmLin"
        //We remove the linear contribution, centered around 0
        //Then, at 0 the measured point does not change, only outside we remove the slope
        Duplicate/O srcWave srcDF:$SaveName
        Wave srcWave_rmLin = srcDF:$SaveName
        if(Polynumber == 1)
            srcWave_rmLin[] -= W_Coef[1]*pnt2x(srcWave_rmLin, p)
            Note srcWave_rmLin "\rDrif-corrected with a degree 1 polynomial"
        elseif(PolyNumber == 2)
            srcWave_rmLin[] -= W_Coef[1]*pnt2x(srcWave_rmLin, p) + W_Coef[2]*pnt2x(srcWave_rmLin, p)*pnt2x(srcWave_rmLin, p)
            Note srcWave_rmLin "\rDrif-corrected with a degree 2 polynomial"
        //We close the graph now
        RemoveFromGraph/W=$graphName LineFit
        KillControl SaveButton
        Cursor/K/W=$graphName A
        Cursor/K/W=$graphName B
        Cursor/K/W=$graphName C
        Cursor/K/W=$graphName D
        Cursor/K/W=$graphName E
        Cursor/K/W=$graphName F
        Cursor/K/W=$graphName G
        Cursor/K/W=$graphName H
        Cursor/K/W=$graphName I
        Cursor/K/W=$graphName J
    KillWaves LineFit, rmLinContrib
    return 0
Function SaveRmLinContribution(ctrlName) : ButtonControl
    String ctrlName
    NVAR saving

    saving = 1
Function SliderProc(ctrlName,sliderValue,event) : SliderControl
    String ctrlName
    Variable sliderValue
    Variable event  // bit field: bit 0: value set, 1: mouse down, 2: mouse up, 3: mouse moved
    Wave crsValues
    if(event & 0x1) // bit 0, value set
        if(!cmpstr(ctrlName, "slA"))
            crsValues[0] = sliderValue
        elseif(!cmpstr(ctrlName, "slB"))
            crsValues[1] = sliderValue
        elseif(!cmpstr(ctrlName, "slC"))
            crsValues[2] = sliderValue
        elseif(!cmpstr(ctrlName, "slD"))
            crsValues[3] = sliderValue
        elseif(!cmpstr(ctrlName, "slE"))
            crsValues[4] = sliderValue
        elseif(!cmpstr(ctrlName, "slF"))
            crsValues[5] = sliderValue
        elseif(!cmpstr(ctrlName, "slG"))
            crsValues[6] = sliderValue
        elseif(!cmpstr(ctrlName, "slH"))
            crsValues[7] = sliderValue
        elseif(!cmpstr(ctrlName, "slI"))
            crsValues[8] = sliderValue
        elseif(!cmpstr(ctrlName, "slJ"))
            crsValues[9] = sliderValue

I tried to look to the benefits of the Sleep function, but actually this wasn't successful...
Perhaps one of you have a better idea to achieve the desired result? I just want
1) To fit a line (or 2-degree polynomial) to a part of the data (selected by cursors, or sliders values)
2) Remove this fit from the raw data
3) Save the result in a new folder and wave name

Thank you very much for the kind advices you could provide.

What you are looking for is a Window Hook, i.e., a link to an user function which is called every time something is changed in a (graph or other) window. You create link to a function which selects the action to take depending on the modification which occurred (for example, call UpdateBackgroundFit() after a Cursor has been moved). From there you can chain all desired events (subtraction, saving) together as needed.

You can read about this topic by calling:
DisplayHelpTopic "Window Hook Functions"

If you have questions or need specific examples, just keep asking here.

By the way, I would suggest you avoid putting everything in one function. Instead write specialized functions which prepare you working data (preferably in a separate folder), create the graph and its hook, clean-up stuff etc.
[quote=tony]An alternative to using cursors is to use the graph marquee to select regions to add or subtract from a maskwave. The fit can be updated from the marquee menu, no need for a hook function. That's what I do in the baselines package:[/quote]

Oh Tony, this is just incredible. This is exactly what I need, I don't know how I did not find it before. Anyway, do you give me the permission to adapt the code to my needs? Mostly I will adapt it to execute your code in a loop where I loop on all the waves I have to remove the trend, and I do not need to remove the offset at point 0!

I will also take a look at the Window Hook, this is something I clearly never heard about.

Thank you both choso and tony!!


Protra wrote:
do you give me the permission to adapt the code to my needs?

of course, and I'm glad it's of use.
The baselines package has capability to loop over all the traces displayed in a graph window, so that may give you a start on the looping part.