Marquee Peaks

A user interface that uses the graph marquee as an efficient way to select an x-range in order to make quick estimates of peak parameters.

This is based on code that I've used for many years before cleaning up and posting here. Most of these parameters could be equally well determined by positioning cursors and using command line or quick fit menus, but I find the graph marquee to provide an especially efficient interface for selecting regions of interest. For more advanced decomposition of overlapping peaks Igor's multipeak fitting package works well, and global fitting is useful for fitting with linked coefficients. But, when you have many spectra plotted in a graph and want to produce just a quick list of peak heights for all of the traces, you may find Marquee Peaks to be helpful.

Most of the Marquee Peak functions use the graph marquee to select a subrange of a plotted trace. They're used to determine peak heights, areas, centroids (centres of mass), and full-width at half-maximum (FWHM). Right click within the graph marquee to select which of these you want to calculate. When a trace is selected from the list of traces that pass though the marquee, the results of the calculation are written to history and shown in a tag on the graph. Select the 'clear' submenu to remove tags from the plot.

Spectra must be baseline-free for these functions to be useful! Fit-derived uncertainties are not really meaningful.

Peak height: fits a third order polynomial through the selected range, and then tries to find the peak by looking for the first zero-gradient within that selected range. For this to work nicely you should select a narrow region around the peak. I used a polynomial so that it should work with asymmetric peaks. Also gives peak position.

Area: simply calculates the area within the selected x-range.

Centroid: integrates the data over the selected range and looks for the position of area/2 in the integral wave. Also gives area.

Peak fit: Selects the best fit from Gaussian, Lorentzian and Voigt over the selected x-range. Gives peak height, FWHM, and position for the best fit.

Doublet Peak: Attempts to fit a pair of peaks to a doublet and derives peak position, height and FWHM parameters. Works best if the peaks are positioned at 1/4 and 3/4 of the selected x-range. Selects the best fit from attempts to fit a pair of Lorentzian, Gaussian and Voigt peaks.

Includes options to do all-in-one determinations for all of the traces on the plot. The results of all-in-one calculation are presented in a table. The waves displayed in the table are located in the package folder. It's up to the user to copy data into a more useful location.

This package also has some functions for normalizing spectra by peak height, peak area or total area. To normalize by total area or wavemax, use the trace and all traces menus (right click or shift-right click on a trace).

'Print Waves in Marquee' prints the names of marquee traces in the history.

Settings can be configured thorough the Analysis - Packages - Marquee Peaks Settings... menu. The traces and tags that Marquee Peaks adds to a graph window are removed either on the next mouseup, or when another Marquee Peak operation is selected, depending on the option selected.

Note: for Voigt fits, the approximate peak FWHM is used as a fit coefficient. The fit function uses the accurate VoigtPeak function. This method allows some kind of uncertainty for peak FWHM to be derived from curve fitting.

Project Details

Current Project Release

Release File: Marquee Peaks152.zip
Version: IGOR.8.00.x-1.52
Version Date: Mon, 02/22/2021 - 12:28 pm
Version Major: 1
Version Patch Level: 52
OS Compatibility: Windows Mac-Intel
Release Notes:

implements the bug fix described in the comments section of the project page

View All Releases

I found one bug: The "Normalize By Total Area" and "Normalize By Wave Maximum" calls in the TracePopup menu are broken, since the called functions want at least a name as input. I guess the original intention was to use a wrapper function here. Also, it seems the TracePopup menu entries appear even for image plots, which may lead to problems if calls are not handled separately for images and traces (easy to do in a wrapper function).

Thanks for letting me know! Hopefully fixed for version 1.5.

Tony, unfortunately this version already summons the debugger upon start because TraceInfo is called for building the menus without a graph open yet. This works better:

static function /S filterByTraceType(string str, int type)
    GetLastUserMenuInfo
    string out = ""
    if (ItemsInList(WinList("*", ";","WIN:1")) > 0  && strlen(S_graphName) && strlen(S_traceName))
        out = SelectString (NumberByKey("TYPE", TraceInfo(S_graphName, S_traceName,0))==type, "", str)
    endif
    return out
end

But I would think the two items could also be useful for images. Instead of blocking the menu here it might be an useful option to branch into ImageToWaveRef and TraceToWaveRef inside the normalization functions to handle both (and just quit for box/violin plots).

EDIT: The code also needed checks for strlen.

Tony, I am sorry, but it still didn't work out. This function is called (twice) on every menu command after all, and the info from GetLastUserMenuInfo gets stale real quick (e.g., by closing the graph and then opening some other menu). Here is the (hopefully ultimate) solution to just build the menu when a trace on the top graph is selected:

static function /S filterByTraceType(string str, int type)
    string out = ""
    string topgraph = StringFromList(0,WinList("*", ";","WIN:1"))
    string traces = TraceNameList("",";",1)
    GetLastUserMenuInfo
    string selgraph = S_graphName, seltrace = S_traceName
    Variable validTrace = FindListItem(seltrace, traces) > -1
    Variable validGraph = StringMatch(selgraph, topgraph) && strlen(topgraph) > 0
    if (validGraph && validTrace)
        out = SelectString (NumberByKey("TYPE", TraceInfo(S_graphName, S_traceName,0))==type, "", str)
    endif
    return out
end

At this point I'd probably have given up on dynamic menus and instead handled the different types of plots inside the normalization functions. ;)

By the way, some other details to note:

  • How about saving the output wave inside the same folder as the initial wave? This would keep things closer together.
  • Repeatedly calling the normalize function creates another wave and appends yet another "_n". How about saving the original waves location (e.g., in the wave notes) and then refer back to the original for the next normalization call.
  • I would anyway add some note somewhere that the data has been modified, e.g., adding something like "quick normalized by area" to the waves note.

Thanks for these good suggestions.

For the dynamic menus, I have used code like this before. Is there a way that this fails?

static function /S filterByTraceType(string str, int type)
    if (WinType("")!=1)
        return "" // don't do anything if Igor is just rebuilding the menu
    endif    
    GetLastUserMenuInfo
    return SelectString (NumberByKey("TYPE", TraceInfo(S_graphName, S_traceName,0))==type, "", str)
end

 

Yes, you can crash this easily, unfortunately:

  1. Have two graphs open and invoke the trace menu on the first graph
  2. Now close this graph and invoke some other menu while the other graph is on top

The result is that GetLastUserMenuInfo still refers to the now-closed old graph and the function drops the ball.

In reply to by chozo

Hmm, that doesn't throw a run-time error for me. If I set a breakpoint I see the graph-does-not-exist error, but would never have noticed without Igor complaining about a run-time error. I've used code like this for a long time without noticing any problem. Do you definitely see a run-time error?

In reply to by tony

OK, I see the problem, TraceInfo is unhappy about nonexistent graph.

This should do it.

static function /S filterByTraceType(string str, int type)
    if (WinType("")!=1)
        return "" // don't do anything if Igor is just rebuilding the menu
    endif    
    GetLastUserMenuInfo
    if (winType(S_graphName) == 1)
        return SelectString (NumberByKey("TYPE", TraceInfo(S_graphName, S_traceName,0))==type, "", str)
    endif
    return ""
end

 

Hi Tony, nice! This will work. Apparently, TraceInfo() does not complain when getting fed a non-existing trace in an existing graph (this can happen, for example when a trace is removed from a graph). But be aware that this builds on some sketchy behavior of several functions: If the trace does not exist then NumberByKey will give NaN which makes SelectString spit out an empty string regardless of the options. In this case, that is what you want. ;) 

In reply to by chozo

Yeah, I had checked what NumberByString does. It doesn't matter matter what string the function returns unless it is the menu of interest that is being built, and in that case both s_graphname and s_tracename will be valid.

Forum

Support

Gallery

Igor Pro 8

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More