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 (3.02 KB)

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 10

Learn More

Igor XOP Toolkit

Learn More

Igor NIDAQ Tools MX

Learn More