Get to Know a Feature: Color Table Wave Creation

In "Get to Know a Feature: Color Table Wave Basics", we showed how Igor Pro 7's color table waves are more flexible than Igor Pro 6's color index waves because multiple uses of the same color table wave can cover differing Z value ranges.

We previously showed that you can use Data→Packages→Color Wave Editor to create color table waves by modifying a copy of a built-in color table, or by directly choosing the color for each row of a color wave.

Color Table Wave Values

Color table (and color index) waves have one row for each color, and 3 or 4 columns for red, green, blue and optionally alpha components containing values that range from 0-65535.

It is useful to realize that any color index wave can also be used as a color table wave; the difference is simply that Igor 7 ignores the X scaling when using a wave as a color table wave.

Here are some other ways to create color table waves:

Importing RGB values from a CSV file

Sometimes a researcher is kind enough to provide a data file containing the red, green, blue values in a data file, which we can import and rescale to Igor's color range.

For example, from Color Schemes Appropriate for Scientific Data Graphics, you can download a "color number text file", such as this Blue-to-Green 14-step table:

You could just copy the values from the text file and paste them into an Igor table, but for larger color tables that would be too painful, and what are computers for, anyway?

Let's load the file (from the Clipboard in my case) as delimited text into a "color0" matrix wave:

•LoadWave/J/M/D/A=color/K=1/V={"\t, "," $",0,1}/L={0,2,0,0,0} "Clipboard"
  Delimited text load from "Clipboard"
  Matrix size: (14,14), wave: color0
•Edit color0

The last three columns are the Red, Green, and Blue components.

An easy way to make a color wave is to Duplicate columns 11, 12, and 13 into a new wave, which we will name "BlueGreen", using Igor 7's handy new /RMD flag for the Duplicate operation:

•Duplicate/O/RMD=[][11,13] color0, BlueGreen // copy all rows, cols 11-13

Igor uses a range of 0-65535 for colors, instead of the 0-255 used here.

It is not obvious, but multiplying by 257 (not the 256 you might expect) scales to Igor's 0-65535 rather nicely:

•Print 65535 / 256
•Print 65535 / 257

Let's convert the wave's numeric type to unsigned 16-bit integer and then multiply by 257.

•Redimension/W/U BlueGreen
•BlueGreen *= 257

It wouldn't hurt to reset the Y scaling to match the column number, and even set dimension labels just for clarity:

•SetScale/P y, 0, 1, "", BlueGreen
•SetDimLabel 1, 0, red, BlueGreen
•SetDimLabel 1, 1, green, BlueGreen
•SetDimLabel 1, 2, blue, BlueGreen

This is what this color table wave looks like in an image plot and ColorScale:

Which compares nicely to the original:

Recreating a Color Table from a Screen Capture

A captured screen shot can be used to reproduce a color table when the numeric values aren't available.

Here's an example of reproducing the many-valued "Ametrine" color table from École Polytechnique Fédérale de Lausanne:

 Screen capture of the Ametrine color table

Screen capture of the Ametrine color table

Loading this screen capture as an Igor RGB Image plot is straightfoward; use a screen capture utility to save a .TIFF or .PNG file, and load it as an image.

Step 1: Save a Screen Capture to a File

On Macintosh, the built-in screen capture to a file is done by pressing Cmd+Shift+4, using the mouse to drag out a marquee around the to-be-captured element.

Upon release of the mouse button, a .tiff file is saved to the desktop (some versions of Mac OS X save a .png file).

Step 2: Load the File as an Image Plot

Use Igor 7's Data->Load Image dialog and load the TIFF or PNG screen shot file as an RGB image:

Step 3: Extract the Color Table Wave from the Image Plot

We want only a portion of the image plot's values for our color table. We can use the marquee to indicate where the colors are, and then create a GraphMarquee menu definition and associated code to extract the colors from that region:

(GraphMarquee code for extracting the color table values, ready to be copied and pasted into an Igor procedure file, follows at the end of this article.)

The resulting color table wave is displayed in an image plot so that you can check that no unintended pixels crept into the color table wave. Adjusting the marquee and selecting Color Table from Selection again updates the same color table wave and image plot.

Comparing the Extracted the Color Table Wave with Ideal Values

We can check the results by downloading the corresponding csv file:

•LoadWave/J/M/A=ametrine/P=text/K=0 "ametrine.txt"
  Delimited text load from "ametrine.txt"
  Matrix size: (256,3), wave: ametrine0

Then we can scale the ametrine0 values from 0-255 to 0-65535 and apply the resulting color table wave to a copy of the image plot with the extracted color table wave for comparison:

•ametrine0 *= 257
•Redimension/U/W ametrine0
•ModifyImage/W=CTabDemoColorTable_1 ColorTableImg ctab= {*,*,ametrine0,0}

Pretty tough to tell the difference:

Reusing the Extracted the Color Table Wave

A good way to make the color table wave available to multiple experiments is to save the color table wave as an Igor Binary wave in an easily-accessed folder.

Simply load the binary wave and choose it in the Modify Image Appearance dialog.

Whether you copy the binary wave into your experiment file or leave it "shared" (saved separate from the experiment on disk) is up to you.

Igor 7.01 or later will ship with many color table waves in a Color Table Waves folder for convenience. They have been created using the sources listed below.


Color Table from Selection Code

#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3     // Use modern global access method and strict wave access.
#pragma version=7.01        // will ship with Igor 701
#pragma IgorVersion=7       // color table waves require Igor 7+

Menu "Image", dynamic
    WMColorTableFromMenu("Color Table from Graph..."), /Q, WMColorTableFromGraph()

Menu "GraphMarquee", dynamic
    WMColorTableFromMenu("Color Table from Selection..."), /Q, WMColorTableFromGraph()

Function/S WMColorTableFromMenu(String enabledItem)
    String item=""  // disappears
    String graphName= WinName(0,1)
    String images= ImageNameList(graphName, ";")
    if( ItemsInList(images) == 1 )  // works with graphs having only 1 image plot
        String imageName= StringFromList(0,images)
        WAVE/Z image= ImageNameToWaveRef(graphName, imageName)
        Variable layers= DimSize(image,2)
        if( layers >= 3 )   // must be RGB or RGBA image.
            item= enabledItem   // "Color Table from Selection", etc.
    return item

Proc WMColorTableFromGraph(outputName)
    String outputName=StrVarOrDefault("root:Packages:WMColorTableFromGraph:ctableName", "ColorTable")   // default, missing parameter dialog gives user chance to change it
    outputName= CleanupName(outputName,1)
    NewDataFolder/O root:Packages
    NewDataFolder/O root:Packages:WMColorTableFromGraph
    String/G root:Packages:WMColorTableFromGraph:ctableName = outputName
    Variable created = WMColorTableGraphOrMarquee(outputName)
    if( created )

// Samples the first image in the top graph along the image's or marquee's
// longest dimension, in the middle of the shortest dimension.
// Note that dimensions may not correlate with marquee lengths if h & v scales differ.
// Scakes will not differ if NewImage is used to initially display the image
// and if the graph is not resized afterwards.
// Best is to use ModifyGraph width={perUnit,1,top},height={perUnit,1,left} to avoid confusion.
Function WMColorTableGraphOrMarquee(String outputName)

    outputName= CleanupName(outputName,1)
    if( strlen(outputName) == 0 )
        DoAlert 0, "Expected outputName!"
        return 0
    String graphName= WinName(0,1)
    if( strlen(graphName) == 0 )
        DoAlert 0, "Expected graph!"
        return 0
    String images= ImageNameList(graphName, ";")
    if( strlen(images) == 0 )
        DoAlert 0, "Expected image in "+graphName+"!"
        return 0
    String imageName= StringFromList(0,images)
    WAVE image= ImageNameToWaveRef(graphName, imageName)
    Variable layers= DimSize(image,2)
    if( layers < 3 )
        DoAlert 0, "Expected "+imageName+" to be an RGB image!"
        return 0
    Variable firstRow, lastRow, firstCol, lastCol
    Variable haveMarquee= WMGetMarqueeImageBounds(graphName, imageName, firstRow, lastRow, firstCol, lastCol)
    Variable cRows= lastRow-firstRow+1
    Variable cCols= lastCol-firstCol+1
    if( layers > 4 )
        layers= 4
    Variable center
    if( cRows > cCols )
        center= floor((firstCol+lastCol)/2)
        Make/O/N=(cRows, layers) $outputName/WAVE=ctable
        ctable= image[p+firstRow][center][q]
        center= floor((lastRow+firstRow)/2)
        Make/O/N=(cCols, layers) $outputName/WAVE=ctable
        ctable= image[center][p+firstCol][q]
    ctable *= 257   // scale to 0-65535
    Redimension/U/W ctable  // convert to 16-bit int wave
    // set dimension labels
    SetDimLabel 1, 0, red, ctable
    SetDimLabel 1, 1, green, ctable
    SetDimLabel 1, 2, blue, ctable

    // remove any unnecessary alpha column
    if( layers == 4 )
        ImageStats/M=1/G={0, DimSize(ctable,0)-1, 3, 3} ctable
        if( V_min == V_max )
            Redimension/N=(-1,3) ctable
            SetDimLabel 1, 3, alpha, ctable
    return WaveExists(ctable)   // in case of error

Function WMGetMarqueeImageBounds(graphName, imageName, firstRow, lastRow, firstCol, lastCol)
    String graphName, imageName
    Variable &firstRow, &lastRow, &firstCol, &lastCol

    WAVE image= ImageNameToWaveRef(graphName, imageName)
    Variable rows= DimSize(image,0)
    Variable cols= DimSize(image,1)
    firstRow= 0
    lastRow= rows-1
    lastCol= cols-1
    // Use entire image
    String info= ImageInfo(graphName, imageName, 0)
    String hAxis= StringByKey("XAXIS", info)
    String vAxis= StringByKey("YAXIS", info)
    GetMarquee /W=$graphName/Z $hAxis, $vAxis
    Variable haveMarquee= V_Flag
    if( haveMarquee ) // if marquee, use subset of image
        // convert horizontal (X) coordinates to rows
        // convert vertical (Y) coordinates to cols
        Variable swap
        // if swapXY, exchange V_left with V_top, and V_right with V_bottom
        String hAxisInfo= AxisInfo(graphName, hAxis)
        String hAxisType= StringByKey("AXTYPE", hAxisInfo)
        Variable axesSwapped= CmpStr(hAxisType, "left") == 0 || CmpStr(hAxisType, "right") == 0
        if( axesSwapped )
            swap= V_left
            V_left= V_top
            V_top= swap
            swap= V_right
            V_right= V_bottom
            V_bottom= swap
        firstRow= (V_left-DimOffset(image,0))/DimDelta(image,0)
        lastRow= (V_right-DimOffset(image,0))/DimDelta(image,0)
        if( firstRow > lastRow )
            swap= firstRow
            firstRow= lastRow
            lastRow= swap
        if( firstRow < 0 )
            firstRow= 0
        if( lastRow > rows-1 )
            lastRow= rows-1
        firstRow= floor(firstRow)
        lastRow= ceil(lastRow)

        firstCol= (V_top-DimOffset(image,1))/DimDelta(image,1)
        lastCol= (V_bottom-DimOffset(image,1))/DimDelta(image,1)
        if( firstCol > lastCol )
            swap= firstCol
            firstCol= lastCol
            lastCol= swap
        if( firstCol < 0 )
            firstCol= 0
        if( lastCol > cols-1 )
            lastCol= cols-1
        firstCol= floor(firstCol)
        lastCol= ceil(lastCol)
    return haveMarquee

Function WMDemoColorTableWave(WAVE ctable)

    Variable rows= DimSize(ctable,0)
    String name= NameOfWave(ctable)
    String graphName= CleanupName("CTabDemo"+name,0)[0,30]
    String demoWaveName= CleanupName(name+"Img",0)[0,30]
    Make/O/N=(rows, 30) $demoWaveName= p // simple ramp
    WAVE img= $demoWaveName
    DoWindow/F $graphName
    if( V_Flag == 0 )
        Newimage/N=$graphName img
        ModifyImage/W=$graphName ''#0, ctab={*,*,ctable} // apply color table wave
    ModifyGraph/W=$graphName nticks=0, width=300, height=40, axThick=1