GUI for setting image scale using cursors
tony
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:
- Use the "Load, Display and Scale Image..." menu item to load an image file, or
- Load an image of a graph (with linear axes) into Igor
- Display the image using Windows > New > Image Plot...
- Right click on image in plot and select "Set Image Scale"
- Position a pair of cursors at high and low values for each axis and type desired cursor values into the corresponding SetVariable controls
- 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
#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
Forum
Support
Gallery
Igor Pro 9
Learn More
Igor XOP Toolkit
Learn More
Igor NIDAQ Tools MX
Learn More
Edited snippet to add a menu item that I find useful, and fixed some annoyances related to changing axis values after rescale.
December 1, 2020 at 09:21 am - Permalink
The updated file ImageScaleGUI_130.zip contains additional code that allows scaling to be transferred from one displayed image to another.
February 5, 2021 at 09:25 am - Permalink
Edited snippet to tweak the logic for autoscaling axes after image setscale.
April 13, 2021 at 03:11 am - Permalink