GUI for setting image scale using cursors

Often I want to quickly plot my data over an image of a plot from some reference source. Various packages already exist that have some kind interface for setting the dimension scaling of a graph image so that the image plot axes are registered with the graph window axes. This is a stand-alone piece of code to make it easy to set the image scaling interactively, by positioning cursors and typing in the desired values for the cursor positions.

How to use it:

  1. Use the "Load, Display and Scale Image..." menu item to load an image file, or
    1. Load an image of a graph (with linear axes) into Igor
    2. Display the image using Windows > New > Image Plot...
    3. Right click on image in plot and select "Set Image Scale"
  2. Position a pair of cursors at high and low values for each axis and type desired cursor values into the corresponding SetVariable controls
  3. Right click and select "Done with Set Image Scale" to finish

 

#pragma TextEncoding="UTF-8"
#pragma rtGlobals=3
#pragma IndependentModule=ImageScaleGUI
#pragma version=1.70

// GUI for setting image scaling using cursors

// How to use:

// Either select "Load, Display and Scale Image..." from Load Waves menu,
// or create a new image plot, right click, and select 'Set Image Scale'.

// Position cursors and enter desired values for cursor positions.

// Right click and select 'Done with Set Image Scale'.

// Uncomment the definition below to add items to trace popup menu that allow
// scaling to be transferred from one displayed image to another:

//#define CopyPasteScaling

#ifdef CopyPasteScaling
menu "TracePopup", dynamic
    ImageScaleGUI#ScaleMenuString(0), /Q, ImageScaleGUI#CopyImageScale()
    ImageScaleGUI#ScaleMenuString(1), /Q, ImageScaleGUI#PasteImageScale()
end
#endif

menu "TracePopup", dynamic
    ImageScaleGUI#ImageTraceMenu(), /Q, ImageScaleGUI#ScaleImageInGraph()
end

menu "Load Waves"
    "Load, Display and Scale Image...", /Q, ImageScaleGUI#LoadAndScaleImage()
end

function /S ScaleMenuString(int paste)
    if (WinType("") != 1)
        return "" // don't do anything if Igor is just rebuilding the menu
    endif
    // figure out graph and trace names
    GetLastUserMenuInfo
    if (strlen(ImageNameList(s_graphname, ";")) == 0)
        return ""
    endif
    if (paste == 0)
        return "Copy Image Scale"
    elseif (cmpstr(GetScrapText()[0,7], "SetScale") == 0)
        return "Paste Image Scale"
    endif
    return ""
end

function CopyImageScale()
    GetLastUserMenuInfo
    wave /Z w = ImageNameToWaveRef(s_graphname, StringFromList(0, ImageNameList(s_graphname, ";")))
    if (WaveExists(w) == 0)
        return 0
    endif
    string cmd = ""
    sprintf cmd, "SetScale /P x, %g, %g, ###; SetScale /P y, %g, %g, ###;", DimOffset(w, 0), DimDelta(w, 0), DimOffset(w, 1), DimDelta(w, 1)
    PutScrapText cmd
end

function PasteImageScale()
    GetLastUserMenuInfo
    string strImage = StringFromList(0, ImageNameList(s_graphname, ";"))
    wave /Z wImage = ImageNameToWaveRef(s_graphname, strImage)
    if (WaveExists(wImage) == 0)
        return 0
    endif
   
    variable x0, dx, y0, dy
    sscanf GetScrapText(), "SetScale /P x, %g, %g, ###; SetScale /P y, %g, %g, ###;", x0, dx, y0, dy
    if (V_flag != 4)
        return 0
    endif
   
    int autoscale, dim
    string strAxis = "", strInfo = "", strFlags = ""
    strInfo = ImageInfo("", strImage, 0)
    variable indexMin, indexMax, delta, oldDelta
   
    for (dim=0;dim<2;dim+=1)
        autoscale = 1
        strAxis = StringByKey(SelectString(dim, "XAXIS", "YAXIS"), strInfo)
        strFlags = StringByKey("SETAXISFLAGS", AxisInfo("", strAxis))
       
        oldDelta = DimDelta(wImage, dim)
        delta = dim ? dY : dX
       
        if (GrepString(strFlags, "/")==0)
            GetAxis /Q $strAxis
            indexMin = scaleToIndex(wImage, V_min, dim)
            indexMax = scaleToIndex(wImage, V_max, dim)
            autoscale = 0
        endif
        if (dim == 0)
            SetScale /P x, x0, delta, wImage
        else
            SetScale /P y, y0, delta, wImage
        endif
        if (autoscale == 0)
            v_min = IndexToScale(wImage, indexMin, dim)
            v_max = IndexToScale(wImage, indexMax, dim)
            if (v_min > v_max)
                SetAxis /R $strAxis v_min, v_max
            else
                SetAxis $strAxis v_min, v_max
            endif
        endif
        if (sign(oldDelta) != sign(delta))
            // switch the axis limits so that image is not flipped
            if (GrepString(strFlags, "/R"))
                SetAxis /A $strAxis
            elseif (GrepString(strFlags, "/A"))
                SetAxis /A/R $strAxis
            endif
        endif
    endfor
end

function LoadAndScaleImage()
    try
        ImageLoad /T=any; AbortOnRTE
    catch
        // Clear the error silently.
        variable Verror = GetRTError(1) // 1 to clear the error
        doalert 0, "Could not load image"  
        return 0
    endtry
    if(v_flag == 0)
        return 0
    endif
    Display
    AppendImage $S_fileName
    SetAxis/A/R left
    ScaleImageInGraph()
end

function /T ImageTraceMenu()
    if (WinType("") != 1)
        return "" // don't do anything if Igor is just rebuilding the menu
    endif
    ControlInfo SetVarA
    if (v_flag != 0)
        return "Done with Set Image Scale"
    endif
    // figure out graph and trace names
    GetLastUserMenuInfo
    return SelectString(strlen(ImageNameList(s_graphname, ";")) > 0, "", "Set Image Scale")
end

function ScaleImageInGraph()
    ControlInfo SetVarA
    if (v_flag!=0) // SetVar exists, clean up
        SetWindow kwTopWin hook(setscaleGUI)=$""
        KillControl SetVarA; KillControl SetVarB; KillControl SetVarC; KillControl SetVarD
        Cursor /K A; Cursor /K B; Cursor /K C; Cursor /K D
        return 1
    endif
    string strImage = StringFromList(0, ImageNameList("", ";"))
    wave wImage = ImageNameToWaveRef("", strImage)
    variable hSize = DimSize(wImage, 0), vSize = DimSize(wImage,1)
    // vertical hairs for X cursors
    Cursor /N=1/S=2/I/H=2/C=(65535,0,0)/P A $strImage 0.1*hSize, 0.2*vSize
    Cursor /N=1/S=2/I/H=2/C=(65535,0,0)/P B $strImage 0.9*hSize, 0.2*vSize
    // horizontal hairs for Y cursors
    Cursor /N=1/S=2/I/H=3/C=(0,65535,0)/P C $strImage 0.2*hSize, 0.9*vSize
    Cursor /N=1/S=2/I/H=3/C=(0,65535,0)/P D $strImage 0.2*hSize, 0.1*vSize
   
    string strInfo = ImageInfo("", strImage, 0)
    string strXaxis = StringByKey("XAXIS",strInfo)
    string strYaxis = StringByKey("YAXIS",strInfo)
    STRUCT Point pt
   
    Make /free/T csr = {"A","B","C","D"}
    int i
    for (i=0;i<4;i+=1)
        SetVariable $"SetVar"+csr[i] title="", value=_NUM: i<2 ? xcsr($csr[i]) : vcsr($csr[i])
        SetVariable $"SetVar"+csr[i] limits={-Inf,Inf,0}, size={40,10}, fsize=14, Proc=ImageScaleGUI#Rescale
        SetVariable $"SetVar"+csr[i] valueColor=(65535*(i<2),65535*(i>1),0)
    endfor

    SetWindow kwTopWin hook(setscaleGUI)=ImageScaleGUI#ImgHook, hookevents=4
    // enter ImgHook function with resize event to reposition setvars
    STRUCT WMWinHookStruct s
    s.eventcode = 6
    ImgHook(s)
end

function ImgHook(STRUCT WMWinHookStruct &s)
    string strInfo, strXaxis, strYaxis
   
    switch (s.eventcode)
        case 6:
        case 7:
        case 8:
            strInfo = ImageInfo(s.winName, s.traceName, 0) // if s.tracename is empty this will be top image.
            strXaxis = StringByKey("XAXIS",strInfo)
            strYaxis = StringByKey("YAXIS",strInfo)
           
            if (s.eventcode == 7) // cursormoved
                if (GrepString(s.cursorName,"[A-D]") == 0)
                    return 0
                endif
                   
                // keep axis coordinates within bounds of axes
                variable ptX, ptY
                GetAxis /Q $strXaxis
                ptX = limit(xcsr($s.cursorName, s.winName), min(V_Min,V_Max), max(V_Min,V_Max))
                GetAxis /Q $strYaxis
                ptY = limit(vcsr($s.cursorName, s.winName), min(V_Min,V_Max), max(V_Min,V_Max))
           
                STRUCT Point pt
                pt.h = PosFromAxisVal(s.winName, strXaxis, ptX)
                pt.v = PosFromAxisVal(s.winName, strYaxis, ptY)
               
                variable val = GrepString(s.cursorName,"[AB]") ? xcsr($s.cursorName, s.winName) : vcsr($s.cursorName, s.winName)
                       
                SetVariable $"SetVar"+s.cursorName win=$s.winName, value=_NUM:val, pos={pt.h-20,pt.v-10}, disable=0
                break
            endif
   
            // reposition or disable setvars when window is resized
            s.eventCode = 7 // prepare to reenter this function with cursormoved eventcode
            Make /free/T csr = {"A","B","C","D"}
            int i
            for (i=0;i<4;i+=1)
                s.cursorName = csr[i]
                variable csrpos = i > 1 ? vcsr($s.cursorName, s.winName) : xcsr($s.cursorName, s.winName)
                GetAxis /W=$s.winName/Q $SelectString(i>1, strXaxis, strYaxis)
                if (csrpos>min(v_max,v_min) && csrpos<max(v_max,v_min))
                    ImgHook(s)
                else
                    SetVariable $"setvar"+(s.cursorName), win=$s.winName, disable=1
                endif
            endfor
    endswitch
    return 0
end

function Rescale(STRUCT WMSetVariableAction &s)
    if (s.eventCode != 8)
        return 0
    endif
    string strImage = StringFromList(0, ImageNameList(s.win, ";"))
    wave wImage = ImageNameToWaveRef(s.win, strImage)
    int isX = GrepString((s.ctrlName), "[AB]"), autoscale = 1
   
    string strAxis, strInfo, strFlags
    strInfo = ImageInfo("", strImage, 0)
    strAxis = StringByKey(SelectString(isX, "YAXIS", "XAXIS"), strInfo)
    strFlags = StringByKey("SETAXISFLAGS", AxisInfo(s.win, strAxis))
    variable indexMin, indexMax
    if(GrepString(strFlags, "/")==0)
        GetAxis /Q $strAxis
        indexMin = scaleToIndex(wImage, V_min, 1-isX)
        indexMax = scaleToIndex(wImage, V_max, 1-isX)
        autoscale = 0
    endif
   
    variable ValAC, ValBD, delta, offset, oldDelta
    ControlInfo $SelectString(isX, "SetVarC", "SetVarA")
    ValAC = V_Value
    ControlInfo $SelectString(isX, "SetVarD", "SetVarB")
    ValBD = V_Value
           
    delta = isX ? (ValBD-ValAC)/(pcsr(B)-pcsr(A)) : (ValBD-ValAC)/(qcsr(D)-qcsr(C))
    offset = isX ? ValAC - delta*pcsr(A) : ValAC - delta*qcsr(C)
    oldDelta = DimDelta(wImage, 1-isX)
    if (isX)
        SetScale /P x, offset, delta, wImage
    else
        SetScale /P y, offset, delta, wImage
    endif
    if (autoscale == 0)
        // don't use IndexToScale, because ends of axis may fall outside of image
        v_min = DimOffset(wImage, 1-isX) + indexMin*DimDelta(wImage, 1-isX)
        v_max = DimOffset(wImage, 1-isX) + indexMax*DimDelta(wImage, 1-isX)
        if (v_min > v_max)
            SetAxis /R/W=$s.win $strAxis v_min, v_max
        else
            SetAxis /W=$s.win $strAxis v_min, v_max
        endif
    elseif ((sign(oldDelta)==sign(delta)) %^ GrepString(strFlags, "/R"))
        // switch the axis limits so that image is not flipped
        SetAxis /A/W=$s.win $strAxis
    else
        SetAxis /A/R/W=$s.win $strAxis
    endif
   
    return 0
end

function PosFromAxisVal(string graphNameStr, string axNameStr, variable val)
    variable pixel = PixelFromAxisVal(graphNameStr, axNameStr, val)
    variable resolution = ScreenResolution
    return resolution > 96 ? pixel * 72/resolution : pixel
end

 

ImageScaleGUI170.zip

Edited snippet to add a menu item that I find useful, and fixed some annoyances related to changing axis values after rescale.

The updated file ImageScaleGUI_130.zip contains additional code that allows scaling to be transferred from one displayed image to another.

Edited snippet to tweak the logic for autoscaling axes after image setscale.

Forum

Support

Gallery

Igor Pro 9

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More