#pragma rtGlobals=3		// Use modern global access method and strict wave access.
#pragma IgorVersion = 6.2
#pragma ModuleName = PearlAreaDisplay
#pragma version = 1.04
#include "pearl-compat"

/// @file
/// @brief visualization tools for 2D and 3D data.
/// @ingroup ArpesPackage
///
/// these tools were initially developed for monitoring output from EPICS area detector software.
/// they are, however, useful for any kind of intensity versus x,y(,z) data.
///
/// @section sec_2d 2D data
///
/// TO DO...
///
/// @section sec_3d 3D data
///
// 3D data is handled by 3 windows. they don't have to be visible all at the same time.
/// * 3D graphics in a Gizmo window
/// * 2D graphics with profiles
/// * Slicer panel
///
/// data dictionary of global variables
/// in view_xxxx folder:
/// * gizmo_graphname = name of the gizmo window
/// * slice_graphname = name of the slice/profiles graph window
/// * slice_panelname = name of the slicer panel
///
/// @section sec_misc Miscellaneous
///
/// scienta HDF5 import, display, and slicer how-to
/// @verbatim
/// ad_display_brick(root:x03da_scienta_20130821_01560:data)
/// ad_scale_scienta(root:x03da_scienta_20130821_01560:data)
/// ad_scale_extra(root:x03da_scienta_20130821_01560:data,root:x03da_scienta_20130821_01560:MonoEnergy,root:x03da_scienta_20130821_01560:data)
/// ad_display_brick(root:x03da_scienta_20130821_01560:data)
/// ad_brick_slicer(root:x03da_scienta_20130821_01560:data, "gizmo_x03da_scienta_20130821_01")
/// @endverbatim
///
/// @author matthias muntwiler, matthias.muntwiler@psi.ch
///
/// @copyright 2013-15 Paul Scherrer Institut @n
/// Licensed under the Apache License, Version 2.0 (the "License"); @n
///   you may not use this file except in compliance with the License. @n
///   You may obtain a copy of the License at
///   http://www.apache.org/licenses/LICENSE-2.0

/// @namespace PearlAreaDisplay
/// @brief instant visualization of angle scan and manipulator position.
///
/// PearlAreaDisplay is declared in @ref pearl-area-display.ipf.
///

/// compose a valid and unique graph name from a data folder reference
static function /s graphname_from_dfref(df, prefix)
	dfref df
	string prefix

	string name

	name = GetDataFolder(1, df)
	name = ReplaceString("root:", name, "")
	name = name[0, strlen(name) - 2]
	name = ReplaceString(" ", name, "")
	name = PearlCleanupName(prefix + name)
	if (CheckName(name, 6))
		name = UniqueName(name, 6, 0)
	endif
	
	return name
end

/// open a new graph window with a 2D image.
///
/// this is essentially <code>display; appendimage</code>.
/// the graph is directly linked to the image wave.
/// it is, thus, updated automatically.
///
/// @param	image		wave which contains the image data.
///
/// @return (string) name of the graph window
///
function /s ad_display(image)
	wave image // wave which contains the image data from the detector
	// returns the name of the graph window
	
	dfref savedf = GetDataFolderDFR()
	dfref imagedf = GetWavesDataFolderDFR(image)
	setdatafolder imagedf

	string dfname = ReplaceString("root:", GetDataFolder(1, imagedf), "")
	string graphtitle = dfname + " View"
	string /g view_graphname = graphname_from_dfref(imagedf, "view_")
	svar graphname = view_graphname
	display /k=1/n=$graphname as graphtitle
	graphname = s_name
	appendimage /w=$graphname image
	
	setdatafolder savedf
	return graphname
end

/// display the histogram of a 2D image.
///
/// the function will create additional objects in the same data folder as the image.
/// this objects are displayed in the graph and are updated by calling ad_calc_profiles().
/// see the code.
///
/// @param	image	wave which contains the image data from the detector.
///
/// @return (string) name of the graph window
///
function /s ad_display_histogram(image)
	wave image

	dfref savedf = GetDataFolderDFR()
	dfref imagedf = GetWavesDataFolderDFR(image)
	string s_imagedf = GetDataFolder(1, imagedf)
	setdatafolder imagedf
	
	make /n=(1)/o hist // histogram

	string dfname = ReplaceString("root:", GetDataFolder(1, imagedf), "")
	string graphtitle = dfname + " Histogram"
	string /g hist_graphname = graphname_from_dfref(imagedf, "hist_")
	svar graphname = hist_graphname
	display /k=1/n=$graphname as graphtitle
	graphname = s_name
	appendtograph /w=$graphname hist
	
	ModifyGraph /w=$graphname rgb(hist)=(39168,0,0)
	ModifyGraph /w=$graphname mode=6
	ModifyGraph /w=$graphname mirror=1
	ModifyGraph /w=$graphname minor=1
	ModifyGraph /w=$graphname axThick=0.5
	ModifyGraph /w=$graphname lblPosMode=1,lblPos=30,lblMargin=0
	ModifyGraph /w=$graphname btLen=4
	ModifyGraph /w=$graphname margin(left)=45,margin(bottom)=35,margin(top)=10,margin(right)=10
	ModifyGraph /w=$graphname gfSize=10
	Label /w=$graphname bottom "value"
	Label /w=$graphname left "# pixels"
	
	ad_calc_histogram(image)

	setdatafolder savedf
	return graphname
end

/// open a new profiles graph window.
///
/// opens an extended graph window with profiles for the specified image.
/// the function copies/creates all necessary data structures in a subfolder
/// of the one which contains the image wave.
/// the data folder name is derived from the image wave name by prefixing with "view_".
/// there can be at most one profiles window of each image wave.
/// the original wave must not be renamed while the graph window is used.
/// to update the graph after modifying the original wave, call ad_update_profiles().
///
/// @param	image		wave which contains the image data.
/// @param	filter			name of a filter function which maps the original data to the displayed data.
///						the function must have the same parameters as ad_default_image_filter().
///						default: boxcar average (ad_box_filter()) using parameters view_filter_smoothing_x and _y.
/// @return				name of the graph window
function /s ad_display_profiles(image, [filter])
	wave image
	string filter
	variable show_legend = 0 // currently not supported

	if (WaveDims(image) != 2)
		abort "ad_display_profiles: image wave must be two-dimensional."
	endif
	if (ParamIsDefault(filter))
		filter = "ad_box_filter"
	endif
	
	// data folders and references
	dfref savedf = GetDataFolderDFR()
	dfref imagedf = GetWavesDataFolderDFR(image)
	string s_imagedf = GetDataFolder(1, imagedf)
	setdatafolder imagedf
	string s_viewdf = PearlCleanupName("view_" + NameOfWave(image))
	newdatafolder /o/s $s_viewdf
	dfref viewdf = GetDataFolderDFR()
	s_viewdf = GetDataFolder(1, viewdf)
	
	// data structures
	string /g sourcepath = GetWavesDataFolder(image, 2)
	string viewname = "view_image"
	duplicate /o image, $viewname /wave=view
	make /n=(3,3)/o xprofiles // NX x 3 wave with 3 one-dimensional profiles along Y dimension
	make /n=(3,3)/o yprofiles // NY x 3 wave with 3 one-dimensional profiles along X dimension
	string /g view_filter
	string /g view_filter_options
	view_filter = filter
	view_filter_options = ""
	variable /g view_filter_smoothing_x = 1
	variable /g view_filter_smoothing_y = 1
	variable /g view_cursor_mode = 0
	string dfname = ReplaceString("root:", GetDataFolder(1, imagedf), "")
	string graphtitle = dfname + NameOfWave(image) + " Profiles"
	string /g prof_graphname = graphname_from_dfref(imagedf, "prof_")
	svar graphname = prof_graphname
	variable /g graph_avg // average value in ROI (ROI is defined by the crosshairs A and B)
	variable /g graph_min // minimum value in ROI
	variable /g graph_max // maximum value in ROI
	variable /g graph_sum // sum of all values in ROI
	variable /g graph_sdev // standard deviation of all values in ROI
	
	// graph setup
	display /k=1 /n=$graphname /w=(100,100,500,400) as graphtitle
	graphname = s_name
	AppendToGraph /w=$graphname /L=xprofiles xprofiles[*][0],xprofiles[*][1],xprofiles[*][2]
	AppendToGraph /w=$graphname /VERT/B=yprofiles yprofiles[*][0],yprofiles[*][1],yprofiles[*][2]
	AppendImage /w=$graphname view
	string imgname = StringFromList(0, ImageNameList(graphname, ";"))
	ModifyImage /w=$graphname $imgname ctab= {*,*,BlueGreenOrange,0}
	ModifyGraph /w=$graphname rgb(xprofiles)=(39168,0,0),rgb(yprofiles)=(39168,0,0)
	ModifyGraph /w=$graphname rgb(xprofiles#1)=(0,26112,0),rgb(yprofiles#1)=(0,26112,0)
	ModifyGraph /w=$graphname rgb(xprofiles#2)=(0,9472,39168),rgb(yprofiles#2)=(0,9472,39168)
	ModifyGraph /w=$graphname mirror(xprofiles)=2,mirror(bottom)=3,mirror(yprofiles)=2,mirror(left)=3
	ModifyGraph /w=$graphname nticks=3
	ModifyGraph /w=$graphname minor=1
	ModifyGraph /w=$graphname axThick=0.5
	ModifyGraph /w=$graphname lblPosMode=1,lblPos=30,lblMargin=0
	ModifyGraph /w=$graphname btLen=4
	ModifyGraph /w=$graphname freePos(xprofiles)=0
	ModifyGraph /w=$graphname freePos(yprofiles)=0
	ModifyGraph /w=$graphname axisEnab(xprofiles)={0.64,1}
	ModifyGraph /w=$graphname axisEnab(bottom)={0,0.6}
	ModifyGraph /w=$graphname axisEnab(yprofiles)={0.64,1}
	ModifyGraph /w=$graphname axisEnab(left)={0,0.6}
	ModifyGraph /w=$graphname zero(left)=8
	ModifyGraph /w=$graphname margin(left)=40,margin(bottom)=30,margin(top)=20,margin(right)=40
	ModifyGraph /w=$graphname gfSize=10

	// axis labels	
	string labels = note(image)
	string lab
	lab = StringByKey("AxisLabelX", labels, "=", "\r")
	if (!strlen(lab))
		lab = "X"
	endif
	Label /w=$graphname bottom lab + " (\\U)"
	lab = StringByKey("AxisLabelY", labels, "=", "\r")
	if (!strlen(lab))
		lab = "Y"
	endif
	Label /w=$graphname left lab + " (\\U)"
	lab = StringByKey("AxisLabelD", labels, "=", "\r")
	if (!strlen(lab))
		lab = "value"
	endif
	Label /w=$graphname xprofiles lab + " (\\U)"
	Label /w=$graphname yprofiles lab + " (\\U)"
	
	// legend
	if (show_legend)
		Legend /w=$graphname/C/N=text0/J/F=2/D=0.5/T={28}/A=RT/X=0.00/Y=0.00 "\\s(xprofiles)\tprofile A"
		AppendText /w=$graphname "\\s(xprofiles#1)\tprofile B"
		AppendText /w=$graphname "\\s(xprofiles#2)\tROI average"
		AppendText /w=$graphname "min\t\\{" + s_viewdf + "graph_min}"
		AppendText /w=$graphname "max\t\\{" + s_viewdf + "graph_max}"
		AppendText /w=$graphname "sum\t\\{" + s_viewdf + "graph_sum}"
		AppendText /w=$graphname "avg\t\\{" + s_viewdf + "graph_avg}"
		AppendText /w=$graphname "sdev\t\\{" + s_viewdf + "graph_sdev}"
	else
		TextBox /w=$graphname /C/N=text0 /F=0 /B=1 /X=1.00 /Y=1.00
		lab = StringByKey("Dataset", labels, "=", "\r")
		if (strlen(lab))
			AppendText /w=$graphname lab
		endif
		AppendText /w=$graphname "sum\t\\{" + s_viewdf + "graph_sum}"
		AppendText /w=$graphname "avg\t\\{" + s_viewdf + "graph_avg}"
		AppendText /w=$graphname "sdev\t\\{" + s_viewdf + "graph_sdev}"
	endif
	
	// interactive elements
	Cursor /w=$graphname /A=1 /P /I /S=2 /H=1 /L=1 A $imgname 0,0
	Cursor /w=$graphname /A=1 /P /I /S=2 /H=1 /L=1 B $imgname DimSize(view, 0)-1, DimSize(view, 1)-1
	variable pcurs
	pcurs = floor(DimSize(xprofiles, 0) / 3)
	Cursor /w=$graphname /A=0 /P /S=1 /H=0 C xprofiles#2 pcurs
	pcurs = floor(DimSize(xprofiles, 0) * 2 / 3)
	Cursor /w=$graphname /A=0 /P /S=1 /H=0 D xprofiles#2 pcurs
	pcurs = floor(DimSize(yprofiles, 0) / 3)
	Cursor /w=$graphname /A=0 /P /S=1 /H=0 E yprofiles#2 pcurs
	pcurs = floor(DimSize(yprofiles, 0) * 2 / 3)
	Cursor /w=$graphname /A=0 /P /S=1 /H=0 F yprofiles#2 pcurs
	ShowInfo /w=$graphname /CP=0
	
	SetWindow  $graphname, hook(ad_profiles_hook)=ad_profiles_hook
	ControlBar /w=$graphname 21
	Button b_reset_cursors win=$graphname, title="reset cursors",pos={0,0},size={70,20},proc=PearlAreaDisplay#bp_reset_cursors
	Button b_reset_cursors win=$graphname, fColor=(65535,65535,65535),fSize=10
	
	SetVariable sv_smoothing_x win=$graphname, title="X smoothing",pos={130,2},bodyWidth=40
	SetVariable sv_smoothing_x win=$graphname, value=view_filter_smoothing_x,limits={1,100,1}
	SetVariable sv_smoothing_x win=$graphname, proc=PearlAreaDisplay#svp_smoothing
	SetVariable sv_smooting_y win=$graphname, title="Y smoothing",pos={240,2},bodyWidth=40
	SetVariable sv_smooting_y win=$graphname, value=view_filter_smoothing_y,limits={1,100,1}
	SetVariable sv_smooting_y win=$graphname, proc=PearlAreaDisplay#svp_smoothing

	PopupMenu pm_export win=$graphname, mode=0,title="Export"
	PopupMenu pm_export win=$graphname, value="X profile;Y profile;X profile (collate);Y profile (collate)"
	PopupMenu pm_export win=$graphname, pos={308,0},bodyWidth=60,proc=PearlAreaDisplay#pmp_export
	PopupMenu pm_export win=$graphname, help={"Export profile of selected area and display in graph. Collate mode = display all profiles in same graph."}
	
	// data processing
	ad_update_profiles(image)
	
	setdatafolder savedf
	return graphname
end

/// add an overlay on top of the displayed image
///
/// the function creates the overlay wave and returns it as function result.
/// the name of the wave is "view_overlay" and is created in the same folder as the "view_image" wave.
///
/// @param image 		image wave that identifies the profiles window.
///
/// @return overlay 	overlay wave. 
///							same dimensions and scales as image, but unsigned binary.
///							pixels that are 0 are overlaid with semi-transparent color.
///							other pixels should be 64 (igor's mask convention).
///
function /wave ad_add_overlay(image, [rgba])
	wave image
	string rgba
	
	if (ParamIsDefault(rgba))
		rgba = "65535,65532,16385,32767"
	elseif (ItemsInList(rgba, ",") == 3)
		rgba += ",32767"
	endif
	
	dfref savedf = GetDataFolderDFR()
	wave view_image = get_view_image(image)
	dfref viewdf = GetWavesDataFolderDFR(view_image)
	svar /sdfr=viewdf graphname = prof_graphname
	setdatafolder viewdf

	string overlayname = "view_overlay"
	duplicate /o image, $overlayname /wave=overlay
	redimension /b/u overlay
	overlay = 64

	string imagenames = ImageNameList(graphname, ";")
	if (whichlistItem(overlayname, imagenames, ";") < 0)
		AppendImage /w=$graphname $overlayname
		rgba = replacestring("(", rgba, "")
		rgba = replacestring(")", rgba, "")
		variable rr = str2num(StringFromList(0, rgba, ","))
		variable gg = str2num(StringFromList(1, rgba, ","))
		variable bb = str2num(StringFromList(2, rgba, ","))
		variable aa = str2num(StringFromList(3, rgba, ","))
#if IgorVersion() >= 8
		ModifyImage /w=$graphname $overlayname explicit=1,eval={0,rr,gg,bb,aa},eval={255,-1,-1,-1}
#else
		ModifyImage /w=$graphname $overlayname explicit=1,eval={0,rr,gg,bb},eval={255,-1,-1,-1}
#endif
	endif
	
	setdatafolder savedf
	return overlay
end

/// update a profiles graph with new data.
///
/// @param	image		wave which contains the image data.
///						must be the same (by data folder and name) wave used with ad_display_profiles().
///
function ad_update_profiles(image)
	wave image

	// data folders and references
	dfref viewdf = get_view_folder(image)
	if (DataFolderRefStatus(viewdf) == 0)
		return -1 // data folder not found
	endif
	dfref savedf = GetDataFolderDFR()
	setdatafolder viewdf
	
	// data structures
	string viewname = "view_image"
	duplicate /o image, $viewname /wave=view
	string overlayname = "view_overlay"
	wave /z overlay = $overlayname
	if (waveexists(overlay))
		redimension /n=(dimsize(view,0), dimsize(view,1)) overlay
	endif

	// data processing
	svar view_filter
	svar view_filter_options
	nvar smoothing_x = view_filter_smoothing_x
	nvar smoothing_y = view_filter_smoothing_y
	funcref ad_default_image_filter filterfunc = $view_filter
	view_filter_options = ReplaceNumberByKey("SmoothingX", view_filter_options, smoothing_x, "=", ";")
	view_filter_options = ReplaceNumberByKey("SmoothingY", view_filter_options, smoothing_y, "=", ";")
	filterfunc(view, view_filter_options)
	
	ad_calc_cursor_profiles(view)
	
	setdatafolder savedf
	return 0
end

/// switch cursors on a profiles graph
///
/// the standard cursors allow to select the profiles to display in the profiles panes.
/// additional cursors are shown in the profiles panes.
///
/// in the background selection mode, additional cursors allow the user to select
/// the limits of the background and peak integration regions.
/// the meaning of the cursors depends on the particular processing function.
///
/// @param	mode			cursor mode.
///	@arg	0 (default)		standard profile selection. cursors C-F on profile panes.
///	@arg	1				background selection. cursors A-F on image.
///
/// @param	image			image displayed in the graph. 
///							this is the original image, not the one in the view data folder.
///
function ad_profiles_cursor_mode(image, mode)
	wave image
	variable mode
	
	dfref savedf = GetDataFolderDFR()
	wave view_image = get_view_image(image)
	dfref viewdf = GetWavesDataFolderDFR(view_image)
	svar /sdfr=viewdf graphname = prof_graphname
	nvar /sdfr=viewdf cursor_mode = view_cursor_mode
	wave /sdfr=viewdf xprofiles, yprofiles
	
	variable dx = DimSize(view_image, 0)
	variable dy = DimSize(view_image, 1)
	switch(mode)
		case 1: // background selection
			Cursor /w=$graphname /A=0 /P /I /S=2 /H=1 /L=1 A view_image 0, 0
			Cursor /w=$graphname /A=0 /P /I /S=2 /H=1 /L=1 B view_image dx-1, dy-1
			Cursor /w=$graphname /A=0 /P /I /S=2 /H=2 /L=1 C view_image round(0.2 * dx) -1, 0
			Cursor /w=$graphname /A=0 /P /I /S=2 /H=2 /L=1 D view_image round(0.8 * dx) -1, 0
			Cursor /w=$graphname /A=0 /P /I /S=2 /H=2 /L=1 E view_image round(0.4 * dx) -1, 0
			Cursor /w=$graphname /A=0 /P /I /S=2 /H=2 /L=1 F view_image round(0.6 * dx) -1, 0

			ShowInfo /w=$graphname /CP=0
			cursor_mode = mode
			break
		default:
			Cursor /w=$graphname /A=1 /P /I /S=2 /H=1 /L=1 A view_image 0,0
			Cursor /w=$graphname /A=1 /P /I /S=2 /H=1 /L=1 B view_image dx-1, dy-1
			variable pcurs
			pcurs = floor(DimSize(xprofiles, 0) / 3)
			Cursor /w=$graphname /A=0 /P /S=1 /H=0 C xprofiles#2 pcurs
			pcurs = floor(DimSize(xprofiles, 0) * 2 / 3)
			Cursor /w=$graphname /A=0 /P /S=1 /H=0 D xprofiles#2 pcurs
			pcurs = floor(DimSize(yprofiles, 0) / 3)
			Cursor /w=$graphname /A=0 /P /S=1 /H=0 E yprofiles#2 pcurs
			pcurs = floor(DimSize(yprofiles, 0) * 2 / 3)
			Cursor /w=$graphname /A=0 /P /S=1 /H=0 F yprofiles#2 pcurs
			ShowInfo /w=$graphname /CP=0
			cursor_mode = 0
	endswitch
	
	setdatafolder savedf
	return 0
end

/// move a cursor to the specified position in a profiles graph.
///
/// this function can only set cursors in the image part of the profiles graph.
///
/// @param image		image displayed in the graph. 
///						this is the original image, not the one in the view data folder.
/// @param cursorname	name of the cursor, e.g. "A" or "B".
///						other cursors are allowed but need to be activated separately.
/// @param xa			x-coordinate to move the cursor to.
///						the position is coerced to the image scale. +/-inf is allowed.
/// @param ya			y-coordinate to move the cursor to.
///						the position is coerced to the image scale. +/-inf is allowed.
/// @param pscale		scaling of the position argument
///						@arg 0 (default) wave scaling
///						@arg 1 point scaling
///
function ad_profiles_set_cursor(image, cursorname, xa, ya, [pscale])
	wave image
	string cursorname
	variable xa, ya
	variable pscale
	
	if (ParamIsDefault(pscale))
		pscale = 0
	endif
	
	// data folders and references
	dfref savedf = GetDataFolderDFR()
	wave view_image = get_view_image(image)
	dfref viewdf = GetWavesDataFolderDFR(view_image)
	svar /sdfr=viewdf graphname = prof_graphname

	variable pa, qa
	if (pscale)
		pa = xa
		qa = ya
	else
		pa = round((xa - DimOffset(view_image, 0)) / DimDelta(view_image, 0))
		qa = round((ya - DimOffset(view_image, 1)) / DimDelta(view_image, 1))
	endif
	
	pa = min(pa, DimSize(view_image, 0) - 1)
	pa = max(pa, 0)
	qa = min(qa, DimSize(view_image, 1) - 1)
	qa = max(qa, 0)
	Cursor /i /p /w=$graphname $cursorname view_image pa, qa

	setdatafolder savedf
	return 0
End

/// draw permanent crosshairs in a profiles graph.
///
/// adds dash-dotted horizontal and vertical crosshairs to a profiles graph.
/// for each active cursor A and/or B, a pair of lines crossing at the cursor position is added.
/// existing crosshairs are moved to the current cursor positions.
/// optionally, existing crosshairs are removed from the graph.
///
/// in contrast to the cursors, these crosshairs will be exported and printed with the graph.
/// they are drawn using Igor's DrawLine operation.
/// all lines drawn by this function are part of the "crosshairs" draw group.
// the lines can be removed manually using the draw toolbox, or by calling this function with @c clean=1.
///
/// @param	image		image displayed in the graph.
///						this is the original image, not the one in the view data folder.
/// @param	clear		0 (default) = add/update lines.
///						1 = remove lines.
///
function ad_profiles_crosshairs(image, [clear])
	wave image
	variable clear
	
	if (ParamIsDefault(clear))
		clear = 0
	endif
	
	// data folders and references
	wave view_image = get_view_image(image)
	dfref viewdf = GetWavesDataFolderDFR(view_image)
	svar /sdfr=viewdf graphname = prof_graphname

	string cursors = "A;B"
	string colors = "39168,0,0;0,26112,0"
	string color
	variable ncursors
	variable icursor
	string cursorname
	string groupname = "crosshairs"
	variable xx, yy
	struct RGBColor rgb
	
	if (clear == 0)
		SetDrawEnv /W=$graphname push
		DrawAction /w=$graphname getgroup=$groupname, delete, begininsert
		SetDrawEnv /w=$graphname gstart, gname=$groupname
	
		SetDrawEnv /W=$graphname dash=4
		SetDrawEnv /W=$graphname linethick=0.5

		ncursors = ItemsInList(cursors, ";")
		for (icursor=0; icursor < ncursors; icursor += 1)
			cursorname = StringFromList(icursor, cursors, ";")
			color = StringFromList(icursor, colors, ";")
			rgb.red = str2num(StringFromList(0, color, ","))
			rgb.green = str2num(StringFromList(1, color, ","))
			rgb.blue = str2num(StringFromList(2, color, ","))
			if (strlen(CsrInfo($cursorname, graphname)) > 0)
				xx = hcsr($cursorname, graphname)
				yy = vcsr($cursorname, graphname)
				SetDrawEnv /W=$graphname linefgc=(rgb.red, rgb.green, rgb.blue)
				SetDrawEnv /W=$graphname save
				SetDrawEnv /W=$graphname xcoord=bottom, ycoord=prel
				DrawLine /W=$graphname xx, 0, xx, 1
				SetDrawEnv /W=$graphname xcoord=prel, ycoord=left
				DrawLine /W=$graphname 0, yy, 1, yy
			endif
		endfor

		SetDrawEnv /w=$graphname gstop
		DrawAction /w=$graphname endinsert
		SetDrawEnv /W=$graphname pop
		SetDrawEnv /W=$graphname save
	else
		DrawAction /w=$graphname getgroup=$groupname, delete
	endif

	return 0
end

/// find the source image wave corresponding to the given view.
///
/// @return wave reference of the original data wave.
///	the reference may be invalid if the source wave cannot be found.
///
static function /wave get_source_image(view)
	wave view // image wave displayed in a profiles window

	dfref viewdf = GetWavesDataFolderDFR(view)
	svar /z /sdfr=viewdf sourcepath
	if (svar_exists(sourcepath))
		wave /z img = $sourcepath
	else
		wave /z img = $""
	endif
	return img
end

/// create a view data folder.
static function /df make_view_folder(source)
	wave source // wave which contains the raw data from the detector.

	// data folders and references
	dfref savedf = GetDataFolderDFR()
	dfref imagedf = GetWavesDataFolderDFR(source)
	string s_imagedf = GetDataFolder(1, imagedf)
	setdatafolder imagedf
	string s_viewdf = PearlCleanupName("view_" + NameOfWave(source))
	newdatafolder /o/s $s_viewdf
	dfref viewdf = GetDataFolderDFR()

	setdatafolder savedf
	return viewdf
end

/// find the view data folder corresponding to the given source.
///
/// the result data folder reference may be invalid if no view is currently open.
/// use the built-in DataFolderRefStatus function to check for validity.
///
/// @param	source		wave which contains the image data.
///						must be the same (by data folder and name) wave used with ad_display_profiles().
///
static function /df get_view_folder(source)
	wave source

	// data folders and references
	dfref savedf = GetDataFolderDFR()
	dfref imagedf = GetWavesDataFolderDFR(source)
	dfref viewdf
	setdatafolder imagedf
	string s_viewdf = PearlCleanupName("view_" + NameOfWave(source))
	if (DataFolderExists(s_viewdf))
		setdatafolder $s_viewdf
		viewdf = GetDataFolderDFR()
	endif
	
	setdatafolder savedf
	return viewdf
end

/// find the view image wave corresponding to the given source.
///
/// @param	source		wave which contains the image data.
///						must be the same (by data folder and name) wave used with ad_display_profiles().
///
static function /wave get_view_image(source)
	wave source

	dfref viewdf = get_view_folder(source)
	string viewname = "view_image"
	wave /sdfr=viewdf view = $viewname

	return view
end

static function bp_reset_cursors(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	switch( ba.eventCode )
		case 2: // mouse up
			string imgname = StringFromList(0, ImageNameList(ba.win, ";"))
			wave /z image = ImageNameToWaveRef(ba.win, imgname)
			if (waveexists(image))
				Cursor /i/p A $imgname 0,0
				Cursor /i/p B $imgname DimSize(image, 0)-1, DimSize(image, 1)-1
			endif
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End

static function svp_smoothing(sva) : SetVariableControl
	STRUCT WMSetVariableAction &sva

	string imglist

	switch( sva.eventCode )
		case 1: // mouse up
		case 2: // Enter key
		case 3: // Live update
			imglist = ImageNameList(sva.win, ";")
			wave /z img = ImageNameToWaveRef(sva.win, StringFromList(0, imglist))
			if (WaveExists(img))
				wave source = get_source_image(img)
				if (WaveExists(source))
					ad_update_profiles(source)
				endif
			endif
			break
		case -1: // control being killed
			break
	endswitch

	return 0
end

static function pmp_export(pa) : PopupMenuControl
	STRUCT WMPopupAction &pa

	switch( pa.eventCode )
		case 2: // mouse up
			variable popNum = pa.popNum
			
			string imgname = StringFromList(0, ImageNameList(pa.win, ";"))
			wave /z image = ImageNameToWaveRef(pa.win, imgname)
			if (waveexists(image) && (popNum >= 1) && (popNum <= 2))
				ad_export_profile(image, popNum - 1, show=1)
			elseif (waveexists(image) && (popNum >= 3) && (popNum <= 4))
				ad_export_profile(image, popNum - 3, show=2)
			endif
			
			break
		case -1: // control being killed
			break
	endswitch

	return 0
End

/// hook function for user events in the profiles window.
function ad_profiles_hook(s)
	struct WMWinHookStruct &s
	variable hookresult = 0
	string imglist
	string cmd
	dfref viewdf
	
	switch(s.eventCode)
		case 2: // delete data folder after window is killed
			imglist = ImageNameList(s.winName, ";")
			wave /z img = ImageNameToWaveRef(s.winName, StringFromList(0, imglist))
			if (WaveExists(img))
				viewdf = GetWavesDataFolderDFR(img)
				cmd = "killdatafolder /z " + GetDataFolder(1, viewdf)
				Execute /P/Q/Z cmd
			endif
			break
		case 7: // update profiles when cursor is moved
			imglist = ImageNameList(s.winName, ";")
			wave /z img = ImageNameToWaveRef(s.winName, StringFromList(0, imglist))
			if (WaveExists(img))
				ad_calc_cursor_profiles(img)
				hookresult = 1
			else
				hookresult = 0
			endif
			break
	endswitch
	
	return hookresult
end

/// calculate profiles, statistics, and histogram of a cross-hair delimited region of interest.
///
/// @param	image		wave which contains the image data from the detector.
///
/// the function expects further objects as created by ad_display_profiles()
/// in the same data folder as the image wave.
/// the most recent profiles graph of the image must exist,
/// and the cursors A and B must be set on the image.
function ad_calc_cursor_profiles(image)
	wave image
	
	dfref savedf = GetDataFolderDFR()
	dfref imagedf = GetWavesDataFolderDFR(image)
	setdatafolder imagedf

	svar graphname = prof_graphname

	variable pa, qa // point coordinates cursor A
	if (strlen(CsrInfo(A, graphname)) > 0)
		pa = pcsr(A, graphname)
		qa = qcsr(A, graphname)
	else
		pa = 0
		qa = 0
	endif
	
	variable pb, qb // point coordinates cursor B
	if (strlen(CsrInfo(B, graphname)) > 0)
		pb = pcsr(B, graphname)
		qb = qcsr(B, graphname)
	else
		pb = DimSize(image, 0) - 1
		qb = DimSize(image, 1) - 1
	endif
	
	ad_calc_profiles(image, pa, qa, pb, qb)	
	setdatafolder savedf
end

/// calculate profiles, statistics, and histogram of a rectangular region of interest.
///
/// the region of interest a rectangle spanned by the two points A and B.
/// pixels at these coordinates are included.
///
/// @param	image		wave which contains the image data.
/// @param	pa			first point coordinate of A.
/// @param	qa			second point coordinate of A.
/// @param	pb			first point coordinate of B.
/// @param	qb			second point coordinate of B.
///
/// the function expects further objects as created by ad_display_profiles()
/// in the same data folder as the image wave.
///
/// this function does not require that the graph exists as long as the data folder is complete.
///
function ad_calc_profiles(image, pa, qa, pb, qb)
	wave image
	variable pa, qa
	variable pb, qb
	
	dfref savedf = GetDataFolderDFR()
	dfref imagedf = GetWavesDataFolderDFR(image)
	setdatafolder imagedf

	wave xprofiles
	wave yprofiles
	nvar graph_avg
	nvar graph_min
	nvar graph_max
	nvar graph_sum
	nvar graph_sdev
	
	// horizontal profiles at crosshairs
	redimension /n=(dimsize(image,0), 3) xprofiles
	setscale /p x dimoffset(image,0), dimdelta(image,0), WaveUnits(image,0), xprofiles
	setscale d 0, 0, waveunits(image,-1), xprofiles
	xprofiles[][0] = image[p][qa]
	xprofiles[][1] = image[p][qb]
	
	note /k xprofiles
	note xprofiles, "SourceWave=" + nameofwave(image)
	note xprofiles, "SourceDimension=0"
	note xprofiles, "SourceIndex0=" + num2str(qa)
	note xprofiles, "SourceIndex1=" + num2str(qb)

	// average horizontal profile between crosshairs
	variable qq, q0, q1
	q0 = min(qa, qb)
	q1 = max(qa, qb)
	xprofiles[][2] = 0
	for (qq = q0; qq <= q1; qq += 1)
		xprofiles[][2] += image[p][qq]
	endfor
	xprofiles[][2] /= q1 - q0 + 1
		
	// vertical profiles at crosshairs
	redimension /n=(dimsize(image,1), 3) yprofiles
	setscale /p x dimoffset(image,1), dimdelta(image,1), WaveUnits(image,1), yprofiles
	setscale d 0, 0, waveunits(image,-1), yprofiles
	yprofiles[][0] = image[pa][p]
	yprofiles[][1] = image[pb][p]

	note /k yprofiles
	note yprofiles, "SourceWave=" + nameofwave(image)
	note yprofiles, "SourceDimension=1"
	note yprofiles, "SourceIndex0=" + num2str(pa)
	note yprofiles, "SourceIndex1=" + num2str(pb)
	
	// average vertical profile between crosshairs
	variable pp, p0, p1
	p0 = min(pa, pb)
	p1 = max(pa, pb)
	yprofiles[][2] = 0
	for (pp = p0; pp <= p1; pp += 1)
		yprofiles[][2] += image[pp][p]
	endfor
	yprofiles[][2] /= p1 - p0 + 1

	// statistics between crosshairs
	Duplicate /r=[p0,p1][q0,q1]/o image, roi_image
	WaveStats /Q roi_image
	graph_avg = v_avg
	graph_min = v_min
	graph_max = v_max
	graph_sum = v_avg * v_npnts
	graph_sdev = v_sdev

	// histogram
	wave /z hist
	if (waveexists(hist))
		Histogram /B=3 roi_image, hist
	endif
	
	setdatafolder savedf
end

/// export a profile from a profiles graph to the source data folder.
///
/// this function does not require that the show exists as long as the view data folder is complete.
///
/// @param	view_image		wave which contains the view image (image wave on display in profiles window).
///							the function expects further objects as created by ad_display_profiles()
///							in the same data folder as the view_image wave.
/// @param	dim				dimension index (0 = x, 1 = y).
/// @param	trace			select profile trace:
/// *							0 = cursor A
/// *							1 = cursor B
/// *							2 = average between cursors (default)
/// @param	show			display mode:
/// *							0 = do not show (default)
/// *							1 = display in new graph, or append to existing graph
/// *							2 = collate: common graph for all profiles of a dimension.
/// 							rename graph manually to detach it from future additions.
/// @param	overwrite			overwrite mode:
/// *							0 = create new wave (default). wave name may get a suffix to be unique.
/// *							1 = overwrite existing wave
///
function ad_export_profile(view_image, dim, [trace, show, overwrite])
	wave view_image
	variable dim
	variable trace
	variable show
	variable overwrite
	
	dfref savedf = GetDataFolderDFR()

	if (ParamIsDefault(trace))
		trace = 2
	endif
	if (ParamIsDefault(show))
		show = 0
	endif
	if (ParamIsDefault(overwrite))
		overwrite = 0
	endif
	
	// view folder
	dfref imagedf = GetWavesDataFolderDFR(view_image)
	string dim_label
	switch(dim)
		case 0:
			wave /sdfr=imagedf profiles=xprofiles
			dim_label = "x"
			break
		case 1:
			wave /sdfr=imagedf profiles=yprofiles
			dim_label = "y"
			break
		default:
			return -1	// invalid argument
	endswitch
	string graphname_string = "export_graph_" + dim_label
	svar /z /sdfr=imagedf linked_graphname = $graphname_string
	
	// source folder
	wave /z source_image = get_source_image(view_image)
	if (WaveExists(source_image))
		dfref sourcedf = GetWavesDataFolderDFR(source_image)
		setdatafolder sourcedf
	else
		return -2	// invalid source data folder
	endif

	// format dest wave name
	string profile_note = note(profiles)
	string name_base
	string name_dim
	string name_index
	string profile_name
	variable index_width = ceil(log(DimSize(view_image, 1 - dim)))
	variable index0 = NumberByKey("SourceIndex0", profile_note, "=", "\r")
	variable index1 = NumberByKey("SourceIndex1", profile_note, "=", "\r")
	name_dim = "_" + dim_label
	sprintf name_index, "%0*u_%0*u", index_width, index0, index_width, index1
	name_base = NameOfWave(source_image)
	name_base = name_base[0, min(strlen(name_base), 31 - strlen(name_index) - strlen(name_dim) - 1)]
	profile_name = name_base + name_dim + name_index
	if ((overwrite == 0) && (CheckName(profile_name, 1)))
		profile_name = UniqueName(profile_name + "_", 1, 0)
	endif
	
	// create dest wave
	duplicate /o /r=[][trace] profiles, $profile_name /wave=dest_profile
	redimension /n=(dimsize(profiles, 0)) dest_profile
	profile_note = ReplaceStringByKey("SourceWave", profile_note, NameOfWave(source_image), "=", "\r")
	note /k dest_profile
	note dest_profile, profile_note
	print "created", GetWavesDataFolder(dest_profile, 2)
	
	if (show)
		string graphname
		string graphtitle
		if (show == 2)
			// common graph for all profiles of a dimension
			graphname = "export_profiles_" + dim_label
			graphtitle = UpperStr(dim_label) + " Profiles"
		else
			// one graph per source image
			if (svar_exists(linked_graphname) && (ItemsInList(WinList(linked_graphname, ";", "WIN:1"), ";") >= 1))
				graphname = linked_graphname
			else
				graphname = GetWavesDataFolder(source_image, 0) + name_dim
			endif
			graphtitle = UpperStr(dim_label) + " Profiles: " + GetWavesDataFolder(source_image, 2)
		endif
	
		if ((ItemsInList(WinList(graphname, ";", "WIN:1"), ";") >= 1))
			appendtograph /w=$graphname dest_profile
		else
			setdatafolder imagedf
			display /k=1 /n=$graphname dest_profile as graphtitle
			graphname = s_name
			ModifyGraph /w=$graphname mirror=1,nticks=3,minor=1
			ModifyGraph /w=$graphname axThick=0.5,btLen=4
			ModifyGraph /w=$graphname gfSize=10
			ModifyGraph /w=$graphname grid=2,gridHair=0,gridRGB=(52224,52224,52224)
			Legend /w=$graphname /C/N=legend0/F=0/B=1/A=LT/X=0.00/Y=0.00
			
			if (show != 2)
				string /g $graphname_string = graphname
			endif
		endif
	endif
	
	setdatafolder savedf
	return 0
end

static function set_trace_colors(graphname)
	string graphname
	
	ModifyGraph /w=$graphname /z rgb[0]=(0, 0, 0)
	ModifyGraph /w=$graphname /z rgb[1]=(65535, 16385, 16385)
	ModifyGraph /w=$graphname /z rgb[2]=(2, 39321, 1)
	ModifyGraph /w=$graphname /z rgb[3]=(0, 0, 65535)
	ModifyGraph /w=$graphname /z rgb[4]=(39321, 1, 31457)
	ModifyGraph /w=$graphname /z rgb[5]=(48059, 48059, 48059)
	ModifyGraph /w=$graphname /z rgb[6]=(65535, 32768, 32768)
	ModifyGraph /w=$graphname /z rgb[7]=(0, 65535, 0)
	ModifyGraph /w=$graphname /z rgb[8]=(16385,65535,65535)
	ModifyGraph /w=$graphname /z rgb[9]=(65535, 32768, 58981)
end

/// calculate the histogram.
///
/// @param	image		wave which contains the image data from the detector.
/// 						the function expects further objects as created by ad_display_histogram()
///						in the same data folder as the image wave.
///
function ad_calc_histogram(image)
	wave image
	
	dfref savedf = GetDataFolderDFR()
	dfref imagedf = GetWavesDataFolderDFR(image)
	setdatafolder imagedf

	wave hist
	Histogram /B=3 image, hist
	
	setdatafolder savedf
end

/// abstract filter function for image display.
///
/// this is a function prototype for filtering two-dimensional data for preview.
/// to write your own filter, define a new function which has the same signature.
///
/// @param	image		image to be filtered: original data and filter result.
/// @param	options		filter options in <code>key1=value1;key2=value2;...</code> format.
///
/// @return the result must be written to the incoming image wave.
///
function ad_default_image_filter(image, options)
	wave image
	string options
end

/// boxcar smoothing filter.
///
/// filters the image in X and Y directions using Igor's Smooth operation.
///
/// @param	image		image to be filtered: original data and filter result.
/// @param	options		smoothing factors in <code>key1=value1;key2=value2;...</code> format.
/// * SmoothingX
/// * SmoothingY
///
function ad_box_filter(image, options)
	wave image
	string options
	
	variable xsmoothing = NumberByKey("SmoothingX", options, "=", ";")
	variable ysmoothing = NumberByKey("SmoothingY", options, "=", ";")

	if ((NumType(xsmoothing) == 0) && (xsmoothing >= 2))
		Smooth /B /DIM=0 /E=3 xsmoothing, image
	endif
	if ((NumType(ysmoothing) == 0) && (ysmoothing >= 2))
		Smooth /B /DIM=1 /E=3 ysmoothing, image
	endif
end

/// transpose image filter.
///
/// transposes the image.
///
/// @param	image		image to be transposed: original data and result.
/// @param	options		not used. should be empty.
///
function ad_transpose_filter(image, options)
	wave image
	string options

	MatrixTranspose image	
end


// ################### 3D DATA ##################

/// open a new "gizmo" window with three-dimensional data.
///
/// @param	data			three-dimensional wave.
///
/// @return				name of the gizmo window.
///
function /s ad_display_brick(data)
	wave data
	
	if(exists("NewGizmo") != 4)
		abort "Gizmo XOP must be installed."
	endif
	if (WaveDims(data) != 3)
		abort "ad_display_brick: data must be three-dimensional."
	endif
	
	dfref savedf = GetDataFolderDFR()
	dfref datadf = GetWavesDataFolderDFR(data)
	string s_datadf = GetDataFolder(1, datadf)
	dfref viewdf = make_view_folder(data)
	
	setdatafolder viewdf
	string dfname = ReplaceString("root:", s_datadf, "")
	string graphtitle = dfname + " Gizmo"
	string /g gizmo_graphname = graphname_from_dfref(datadf, "giz_")
	svar graphname = gizmo_graphname

	if ((strlen(graphname) > 0) && (wintype(graphname) == 13))
		setdatafolder savedf
		return graphname // gizmo window exists
	endif

	variable nx = dimsize(data, 0)
	variable ny = dimsize(data, 1)
	variable nz = dimsize(data, 2)
	
	variable pp
	string obj
	string cmd
	
	// igor does not allow calling gizmo functions directly
	setdatafolder datadf
	sprintf cmd, "NewGizmo /k=1 /n=%s /w=(100,100,500,400) /t=\"%s\"", graphname, graphtitle
	execute /q cmd
	cmd = "AppendToGizmo /D Axes=BoxAxes, name=axes0"
	execute /q cmd

	obj = "surface_xmid"
	pp = round(nx / 2 - 1)
	sprintf cmd, "AppendToGizmo /D surface=%s, name=%s", nameofwave(data), obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s, property={srcMode, 128}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s, property={plane, %d}", obj, pp
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s, property={surfaceCtab, BlueGreenOrange}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s, property={SurfaceCTABScaling,128}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s, property={surfaceCTABAlpha, 1.0}", obj
	execute /q cmd

	obj = "surface_ymid"
	pp = round(ny / 2 - 1)
	sprintf cmd, "AppendToGizmo /D surface=%s, name=%s", nameofwave(data), obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s, property={srcMode, 64}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s, property={plane, %d}", obj, pp
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s, property={surfaceCtab, BlueGreenOrange}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s, property={SurfaceCTABScaling,128}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s, property={surfaceCTABAlpha, 1.0}", obj
	execute /q cmd

	obj = "surface_zmid"
	pp = round(nz / 2 - 1)
	sprintf cmd, "AppendToGizmo /D surface=%s, name=%s", nameofwave(data), obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s, property={srcMode, 32}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s, property={plane, %d}", obj, pp
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s, property={surfaceCtab, BlueGreenOrange}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s, property={SurfaceCTABScaling,128}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s, property={surfaceCTABAlpha, 1.0}", obj
	execute /q cmd

	obj = "axes0"
	sprintf cmd, "ModifyGizmo ModifyObject=%s,property={-1,axisScalingMode,1}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s,property={-1,axisColor,0,0,0,1}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s,property={0,ticks,3}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s,property={1,ticks,3}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo ModifyObject=%s,property={2,ticks,3}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s property={Clipped,0}", obj
	execute /q cmd
	sprintf cmd, "ModifyGizmo modifyObject=%s property={-1,fontScaleFactor,2}", obj
	execute /q cmd

	sprintf cmd, "ModifyGizmo showAxisCue=1"
	execute /q cmd

	setdatafolder savedf
	return graphname
end

/// open a slicer panel for 3D data.
///
/// if a panel exists, bring it to the front.
///
/// @param	data			three-dimensional wave.
///
function ad_brick_slicer(data)
	wave data

	// data folders and references
	dfref savedf = GetDataFolderDFR()
	dfref datadf = GetWavesDataFolderDFR(data)
	string s_datadf = GetDataFolder(1, datadf)
	dfref viewdf = make_view_folder(data)

	setdatafolder viewdf
	svar /z ex_panel = slicer_panelname
	if (svar_exists(ex_panel))
		string panels = WinList("SlicerPanel*", ";", "WIN:64")
		if (WhichListItem(ex_panel, panels, ";") >= 0)
			dowindow /f $(StringFromList(0, panels, ";"))
			return 0
		endif
	endif

	variable /g x_slice_pos
	variable /g y_slice_pos
	variable /g z_slice_pos
	variable /g slab_thickness
	string /g brick_path = getwavesdatafolder(data, 2)
	variable /g x_autoinc = 0
	variable /g y_autoinc = 0
	variable /g z_autoinc = 0

	// axis labels	
	string labels = note(data)
	string xlabel = StringByKey("AxisLabelX", labels, "=", "\r")
	if (!strlen(xlabel))
		xlabel = "X"
	endif
	string ylabel = StringByKey("AxisLabelY", labels, "=", "\r")
	if (!strlen(ylabel))
		ylabel = "Y"
	endif
	string zlabel = StringByKey("AxisLabelZ", labels, "=", "\r")
	if (!strlen(zlabel))
		zlabel = "Z"
	endif
	string dlabel = StringByKey("Dataset", labels, "=", "\r")
	if (!strlen(dlabel))
		dlabel = NameOfWave(data)
	endif
	
	// this section copied from slicer panel	
	NewPanel /k=1 /W=(500,600,890,940) /N=SlicerPanel as "Brick Slicer"
	string /g slicer_panelname = S_name
	string panel = s_name

	GroupBox g_xslice win=$panel,pos={8,8},size={376,96},title=xlabel
	Slider sl_xslice_position win=$panel,pos={16,32},size={240,56},proc=PearlAreaDisplay#slp_slice_position
	Slider sl_xslice_position win=$panel,limits={0,100,1},variable=x_slice_pos,vert= 0
	SetVariable sv_xslice_position win=$panel,pos={20,80},size={92,16},proc=PearlAreaDisplay#svp_slice_position,title="X"
	SetVariable sv_xslice_position win=$panel,limits={0,100,1},value=x_slice_pos
	Button b_xslice_center win=$panel,pos={122,80},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W618"
	Button b_xslice_center win=$panel,help={"reset to center position"}
	Button b_xslice_extract win=$panel,pos={288,80},size={68,20},proc=PearlAreaDisplay#bp_extract_slice,title="extract slice"
	Button b_xslice_extract win=$panel,help={"extract this slice to a separate wave"}
	//CheckBox cb_xslab_active win=$panel,pos={288,80},size={80,16},title="Display X Slab"
	//CheckBox cb_xslab_active win=$panel,value= 0
	TitleBox tb_xslice_animation win=$panel,pos={288,32},size={356,16},title="animation",frame=0
	TitleBox tb_xslice_animation win=$panel,anchor= MC
	Button b_xslice_back win=$panel,pos={288,48},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W646"
	Button b_xslice_back win=$panel,help={"animate backwards"}
	Button b_xslice_forward win=$panel,pos={312,48},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W649"
	Button b_xslice_forward win=$panel,help={"animate forward"}
	Button b_xslice_stop win=$panel,pos={336,48},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W616"
	Button b_xslice_stop win=$panel,help={"stop animation"}
	
	GroupBox g_yslice win=$panel,pos={8,108},size={376,96},title=ylabel
	Slider sl_yslice_position win=$panel,pos={16,132},size={240,56},proc=PearlAreaDisplay#slp_slice_position
	Slider sl_yslice_position win=$panel,limits={0,100,1},variable=y_slice_pos,vert= 0
	SetVariable sv_yslice_position win=$panel,pos={20,180},size={92,16},proc=PearlAreaDisplay#svp_slice_position,title="Y"
	SetVariable sv_yslice_position win=$panel,limits={0,100,1},value=y_slice_pos
	Button b_yslice_center win=$panel,pos={122,180},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W618"
	Button b_yslice_center win=$panel,help={"reset to center position"}
	Button b_yslice_extract win=$panel,pos={288,180},size={68,20},proc=PearlAreaDisplay#bp_extract_slice,title="extract slice"
	Button b_yslice_extract win=$panel,help={"extract this slice to a separate wave"}
	//CheckBox cb_yslab_active win=$panel,pos={288,180},size={80,16},title="Display Y Slab"
	//CheckBox cb_yslab_active win=$panel,value= 0
	TitleBox tb_yslice_animation win=$panel,pos={288,132},size={356,16},title="animation",frame=0
	TitleBox tb_yslice_animation win=$panel,anchor= MC
	Button b_yslice_back win=$panel,pos={288,148},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W646"
	Button b_yslice_back win=$panel,help={"animate backwards"}
	Button b_yslice_forward win=$panel,pos={312,148},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W649"
	Button b_yslice_forward win=$panel,help={"animate forward"}
	Button b_yslice_stop win=$panel,pos={336,148},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W616"
	Button b_yslice_stop win=$panel,help={"stop animation"}
	
	GroupBox g_zslice win=$panel,pos={8,208},size={376,96},title=zlabel
	Slider sl_zslice_position win=$panel,pos={16,232},size={240,56},proc=PearlAreaDisplay#slp_slice_position
	Slider sl_zslice_position win=$panel,limits={0,100,1},variable=z_slice_pos,vert= 0
	SetVariable sv_zslice_position win=$panel,pos={20,280},size={92,16},proc=PearlAreaDisplay#svp_slice_position,title="Z"
	SetVariable sv_zslice_position win=$panel,limits={0,100,1},value=z_slice_pos
	Button b_zslice_center win=$panel,pos={122,280},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W618"
	Button b_zslice_center win=$panel,help={"reset to center position"}
	Button b_zslice_extract win=$panel,pos={288,280},size={68,20},proc=PearlAreaDisplay#bp_extract_slice,title="extract slice"
	Button b_zslice_extract win=$panel,help={"extract this slice to a separate wave"}
	//CheckBox cb_zslab_active win=$panel,pos={288,280},size={80,16},title="Display Z Slab"
	//CheckBox cb_zslab_active win=$panel,value= 0
	TitleBox tb_zslice_animation win=$panel,pos={288,232},size={356,16},title="animation",frame=0
	TitleBox tb_zslice_animation win=$panel,anchor= MC
	Button b_zslice_back win=$panel,pos={288,248},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W646"
	Button b_zslice_back win=$panel,help={"animate backwards"}
	Button b_zslice_forward win=$panel,pos={312,248},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W649"
	Button b_zslice_forward win=$panel,help={"animate forward"}
	Button b_zslice_stop win=$panel,pos={336,248},size={20,20},proc=PearlAreaDisplay#bp_move_slice,title="\\W616"
	Button b_zslice_stop win=$panel,help={"stop animation"}

	TitleBox t_slicerpath win=$panel,pos={8,316},size={128,20},disable=2,title=dlabel
	//SetVariable setvar0 win=$panel,pos={240,316},size={120,16},title="slab thickness"
	//SetVariable setvar0 win=$panel,limits={1,inf,1},value=slab_thickness
	
	// update control limits and move slicing planes to the center
	setwindow $panel, userdata(control_datafolder) = GetDataFolder(1, viewdf)
	setwindow $panel, userdata(brick_path) = brick_path
	update_slice_info()
	x_slice_pos = dimoffset(data, 0) + dimsize(data, 0) * dimdelta(data, 0) / 2
	y_slice_pos = dimoffset(data, 1) + dimsize(data, 1) * dimdelta(data, 1) / 2
	z_slice_pos = dimoffset(data, 2) + dimsize(data, 2) * dimdelta(data, 2) / 2
	
	svar /z /sdfr=viewdf gizmo_graphname
	if (svar_exists(gizmo_graphname) && (strlen(gizmo_graphname) > 0) && (wintype(gizmo_graphname) == 13))
		ad_gizmo_set_plane(data, 0, x_slice_pos)
		ad_gizmo_set_plane(data, 1, y_slice_pos)
		ad_gizmo_set_plane(data, 2, z_slice_pos)
	endif
	svar /z /sdfr=viewdf slice_graphname
	if (svar_exists(slice_graphname) && (strlen(slice_graphname) > 0) && (wintype(slice_graphname) == 1))
		ad_profiles_set_slice(data, 2, z_slice_pos)
	endif
	
	ad_slicer_init_bg()
	setdatafolder savedf
end

/// display three-dimensional data by 2D slice.
///
/// to select the slice data to display, call ad_profiles_set_slice(),  or open a ad_brick_slicer() panel.
/// do not modify the content of the created view_ data folder.
///
/// @param	data			three-dimensional wave.
///
/// @return				name of the graph window.
///
function /s ad_display_slice(data)
	wave data
	
	if (WaveDims(data) != 3)
		abort "ad_display_slice: data must be three-dimensional."
	endif
	
	dfref savedf = GetDataFolderDFR()
	dfref datadf = GetWavesDataFolderDFR(data)
	string s_datadf = GetDataFolder(1, datadf)
	dfref viewdf = make_view_folder(data)

	setdatafolder viewdf
	string dfname = ReplaceString("root:", s_datadf, "")
	dfname = dfname[0, strlen(dfname) - 2]
	string graphtitle = dfname + " Slice"
	
	if (exists("slice_graphname") != 2)
		string /g slice_graphname = ""
	endif
	string /g slice_wavename = PearlCleanupName("slice_" + NameOfWave(data))
	svar graphname = slice_graphname
	svar slicename = slice_wavename

	make /n=(1,1)/o $slicename
	wave slice = $slicename
	if ((strlen(graphname) == 0) || (wintype(graphname) != 1))
		graphname = ad_display_profiles(slice)
	endif
	variable z_slice_pos = dimoffset(data, 2) + dimsize(data, 2) * dimdelta(data, 2) / 2
	ad_profiles_set_slice(data, 2, z_slice_pos)
	ad_profiles_set_cursor(slice, "A", -inf, -inf, pscale=1)
	ad_profiles_set_cursor(slice, "B", +inf, +inf, pscale=1)
	
	setdatafolder savedf
	return graphname
end

/// update controls with data scale limits.
///
/// current folder must be slicer info
static function update_slice_info()
	dfref savedf = GetDataFolderDFR()
	
	svar brick_path
	//svar slicer_panelname
	wave brick = $brick_path
	
	//dowindow /F $slicer_panelname
	variable lo, hi, inc
	lo = dimoffset(brick, 0)
	inc = dimdelta(brick, 0)
	hi = lo + inc * (dimsize(brick, 0) - 1)
	Slider sl_xslice_position,limits={lo,hi,inc}
	SetVariable sv_xslice_position,limits={lo,hi,inc}
	lo = dimoffset(brick, 1)
	inc = dimdelta(brick, 1)
	hi = lo + inc * (dimsize(brick, 1) - 1)
	Slider sl_yslice_position,limits={lo,hi,inc}
	SetVariable sv_yslice_position,limits={lo,hi,inc}
	lo = dimoffset(brick, 2)
	inc = dimdelta(brick, 2)
	hi = lo + inc * (dimsize(brick, 2) - 1)
	Slider sl_zslice_position,limits={lo,hi,inc}
	SetVariable sv_zslice_position,limits={lo,hi,inc}

	setdatafolder savedf
end

/// set the position of a slicing plane of a 3D brick in a Gizmo window.
///
/// @param	brick		original data wave.
/// @param	dim			dimension index: 0, 1, or 2.
/// @param	value		new coordinate of the slicing plane (axis scaling).
///
/// @return				0 if successful, non-zero otherwise
///
function ad_gizmo_set_plane(brick, dim, value)
	wave brick
	variable dim
	variable value
	
	dfref savedf = GetDataFolderDFR()
	dfref datadf = GetWavesDataFolderDFR(brick)
	dfref viewdf = get_view_folder(brick)
	svar /z /sdfr=viewdf graphname=gizmo_graphname

	variable pp = round((value - dimoffset(brick, dim)) / dimdelta(brick, dim))
	if ((pp < 0) || (pp >= dimsize(brick, dim)))
		return -1	// requested value out of range
	endif
	
	if (svar_exists(graphname) && (strlen(graphname) > 0) && (wintype(graphname) == 13))
		string axes = "xyz"
		string obj = "surface_" + axes[dim] + "mid"
		string cmd
		sprintf cmd, "ModifyGizmo /N=%s ModifyObject=%s, property={plane, %d}", graphname, obj, pp
		execute /q cmd
	else
		return -2 // gizmo window not found
	endif
	
	return 0
end

/// set the position of the slicing plane of a 3D brick in a profiles window.
///
/// @param	brick		original data wave.
/// @param	dim			dimension index: 0, 1, or 2.
/// @param	value		new coordinate of the slicing plane (axis scaling).
///
/// @return				0 if successful, non-zero otherwise
///
function ad_profiles_set_slice(brick, dim, value)
	wave brick
	variable dim
	variable value
	
	dfref savedf = GetDataFolderDFR()
	dfref datadf = GetWavesDataFolderDFR(brick)
	dfref viewdf = get_view_folder(brick)
	svar /z /sdfr=viewdf graphname = slice_graphname
	svar /z /sdfr=viewdf slicename = slice_wavename

	variable pp = round((value - dimoffset(brick, dim)) / dimdelta(brick, dim))
	if ((pp < 0) || (pp >= dimsize(brick, dim)))
		return -1	// requested value out of range
	endif

	if (svar_exists(graphname) && (strlen(graphname) > 0) && (wintype(graphname) == 1))
		setdatafolder viewdf
		switch(dim)
			case 0: // X
				wave wdest = ad_extract_slab_x(brick, pp, pp, slicename)
				ad_update_profiles(wdest)
				break
			case 1: // Y
				wave wdest = ad_extract_slab_y(brick, pp, pp, slicename)
				ad_update_profiles(wdest)
				break
			case 2: // Z
				wave wdest = ad_extract_slab_z(brick, pp, pp, slicename)
				ad_update_profiles(wdest)
				break
		endswitch
	else
		return -2 // graph window not found
	endif
	
	setdatafolder savedf
	return 0
end

/// set slice coordinate (slider procedure).
static function slp_slice_position(sa) : SliderControl
	STRUCT WMSliderAction &sa

	dfref savedf = GetDataFolderDFR()

	switch( sa.eventCode )
		case -1: // control being killed
			break
		default:
			if( sa.eventCode & 1 ) // value set
				string control_datafolder = GetUserData(sa.win, "", "control_datafolder")
				setdatafolder control_datafolder
				string brick_path = GetUserData(sa.win, "", "brick_path")
				string axis = StringFromList(1, sa.ctrlName, "_")
				variable dim = char2num(axis[0]) - char2num("x")
				
				wave /z brick = $brick_path
				if (WaveExists(brick))
					ad_gizmo_set_plane(brick, dim, sa.curval)
					ad_profiles_set_slice(brick, dim, sa.curval)
				else
					Abort "can't find original wave " + brick_path
				endif
			endif
			break
	endswitch

	setdatafolder savedf
	return 0
End

/// set slice coordinate (button procedure).
static function svp_slice_position(sva) : SetVariableControl
	STRUCT WMSetVariableAction &sva

	dfref savedf = GetDataFolderDFR()

	switch( sva.eventCode )
		case 1: // mouse up
		case 2: // Enter key
		case 3: // Live update
			string control_datafolder = GetUserData(sva.win, "", "control_datafolder")
			setdatafolder control_datafolder
			string brick_path = GetUserData(sva.win, "", "brick_path")
			string axis = StringFromList(1, sva.ctrlName, "_")
			variable dim = char2num(axis[0]) - char2num("x")
			
			wave /z brick = $brick_path
			if (WaveExists(brick))
				ad_gizmo_set_plane(brick, dim, sva.dval)
				ad_profiles_set_slice(brick, dim, sva.dval)
			else
				Abort "can't find original wave " + brick_path
			endif
			break
		case -1: // control being killed
			break
	endswitch

	setdatafolder savedf
	return 0
End

/// move slice (button procedure).
static function bp_move_slice(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	dfref savedf = GetDataFolderDFR()

	switch( ba.eventCode )
		case 2: // mouse up
			string control_datafolder = GetUserData(ba.win, "", "control_datafolder")
			setdatafolder control_datafolder
			string brick_path = GetUserData(ba.win, "", "brick_path")
			string axis = StringFromList(1, ba.ctrlName, "_")
			string cmd = StringFromList(2, ba.ctrlName, "_")
			variable dim = char2num(axis[0]) - char2num("x")
			string posvariable = getdatafolder(1) + axis[0] + "_slice_pos"
			nvar pos = $(posvariable)

			wave /z brick = $brick_path
			if (WaveExists(brick))
				strswitch (cmd)
					case "forward":
						ad_slicer_start_bg(brick, dim, posvariable, dimdelta(brick, dim))
						break
					case "back":
						ad_slicer_start_bg(brick, dim, posvariable, -dimdelta(brick, dim))
						break
					case "center":
						ad_slicer_stop_bg(posvariable)
						bp_move_slice_center(brick, dim, posvariable)
						break
					case "stop":
						ad_slicer_stop_bg(posvariable)
						break
				endswitch
			else
				ad_slicer_stop_bg(posvariable)
				Abort "can't find original wave " + brick_path
			endif
			break
		case -1: // control being killed
			break
	endswitch

	setdatafolder savedf
	return 0
End

/// export a slice (button procedure).
///
/// extract a slice and saves it in a separate wave.
static function bp_extract_slice(ba) : ButtonControl
	STRUCT WMButtonAction &ba

	dfref savedf = GetDataFolderDFR()

	switch( ba.eventCode )
		case 2: // mouse up
			string control_datafolder = GetUserData(ba.win, "", "control_datafolder")
			setdatafolder control_datafolder
			string brick_path = GetUserData(ba.win, "", "brick_path")
			wave brick = $brick_path
			dfref brickdf = GetWavesDataFolderDFR(brick)
			
			string axis = StringFromList(1, ba.ctrlName, "_")
			string cmd = StringFromList(2, ba.ctrlName, "_")
			variable dim = char2num(axis[0]) - char2num("x")
			string posvariable = getdatafolder(1) + axis[0] + "_slice_pos"
			
			nvar pos = $(posvariable)
			variable pp = round((pos - dimoffset(brick, dim)) / dimdelta(brick, dim))
			if ((pp < 0) || (pp >= dimsize(brick, dim)))
				return -1	// requested value out of range
			endif
			
			variable dig = ceil(log(dimsize(brick, dim)))
			string slicename
			sprintf slicename, "%s_%s%0*u", NameOfWave(brick), axis[0], dig, pp
			setdatafolder brickdf
			switch(dim)
				case 0: // X
					wave wdest = ad_extract_slab_x(brick, pp, pp, slicename)
					break
				case 1: // Y
					wave wdest = ad_extract_slab_y(brick, pp, pp, slicename)
					break
				case 2: // Z
					wave wdest = ad_extract_slab_z(brick, pp, pp, slicename)
					break
			endswitch
			
			string msg
			sprintf msg, "%s=%g", axis[0], pos
			note wdest, msg
		
			break
		case -1: // control being killed
			break
	endswitch

	setdatafolder savedf
	return 0
End

/// move the slice to the center of the dimension (button procedure).
static function bp_move_slice_center(brick, dim, posvariable)
	wave brick
	variable dim
	string posvariable

	nvar pos = $posvariable
	pos = dimoffset(brick, dim) + dimdelta(brick, dim) * dimsize(brick, dim) / 2
	ad_gizmo_set_plane(brick, dim, pos)
	ad_profiles_set_slice(brick, dim, pos)
end

/// move a slice by one step (background task).
static function ad_slicer_move_bg(s)
	STRUCT WMBackgroundStruct &s
	
	dfref savedf = GetDataFolderDFR()
	setdatafolder root:pearl_area:slicer
	wave /t bg_brickpaths
	wave /t bg_graphnames
	wave /t bg_variablepaths
	wave bg_dimensions
	wave bg_increments
	
	variable ii
	variable nn = numpnts(bg_brickpaths)
	variable dim
	variable pp

	for (ii = 0; ii < nn; ii += 1)
		wave /z brick = $bg_brickpaths[ii]
		nvar /z pos = $bg_variablepaths[ii]
		dim = bg_dimensions[0]
		pos += bg_increments[ii]
		// wrap around at limits
		pp = round((pos - dimoffset(brick, dim)) / dimdelta(brick, dim))
		if (pp <= -0.5)
			pos = dimoffset(brick, dim) + dimdelta(brick, dim) * (dimsize(brick, dim) - 1)
		elseif (pp >= dimsize(brick, dim) - 0.5)
			pos = dimoffset(brick, dim)
		endif
		if (waveexists(brick))
			ad_gizmo_set_plane(brick, dim, pos)
			ad_profiles_set_slice(brick, dim, pos)
		endif
	endfor
	
	setdatafolder savedf
	return 0
End

/// initialize the slice animation background task.
function ad_slicer_init_bg()
	dfref savedf = GetDataFolderDFR()
	setdatafolder root:
	newdatafolder /o/s pearl_area
	newdatafolder /o/s slicer
	
	make /n=0/o/t bg_brickpaths
	make /n=0/o/t bg_variablepaths
	make /n=0/o/i/u bg_dimensions
	make /n=0/o bg_increments
	
	CtrlNamedBackground ad_slicer, period = 30, proc = PearlAreaDisplay#ad_slicer_move_bg
	
	setdatafolder savedf
	return 0
end

/// start the animation.
///
/// @param	brick			3D data wave
/// @param	dimension		dimension to animate, 0, 1, or 2.
/// @param	posvariable		full path to the global position variable.
/// @param	delta			step increment, should be +/- dimdelta.
///
function ad_slicer_start_bg(brick, dimension, posvariable, delta)
	wave brick // 3D data wave
	variable dimension // dimension to animate, 0, 1, or 2
	string posvariable // full path to the global position variable
	variable delta // step increment, should be +/- dimdelta
	
	dfref savedf = GetDataFolderDFR()
	setdatafolder root:pearl_area:slicer
	wave /t bg_brickpaths
	wave /t bg_variablepaths
	wave bg_dimensions
	wave bg_increments

	// create entry in ad_slicer background task table
	variable idx
	FindValue /TEXT=posvariable /TXOP=4 /Z bg_variablepaths
	if (v_value >= 0)
		idx = v_value
	else
		idx = numpnts(bg_variablepaths)
		InsertPoints idx, 1, bg_brickpaths, bg_variablepaths, bg_dimensions, bg_increments
	endif

	// set background task
	bg_brickpaths[idx] = GetWavesDataFolder(brick, 2)
	bg_variablepaths[idx] = posvariable
	bg_dimensions[idx] = dimension
	bg_increments[idx] = delta
	
	// start background task
	if (numpnts(bg_variablepaths) > 0)
		CtrlNamedBackground ad_slicer, start
	endif
	
	setdatafolder savedf
	return 0
end

/// stop the animation.
///
/// @param	posvariable		full path to the global position variable.
///
function ad_slicer_stop_bg(posvariable)
	string posvariable

	dfref savedf = GetDataFolderDFR()
	setdatafolder root:pearl_area:slicer
	wave /t bg_brickpaths
	wave /t bg_variablepaths
	wave bg_dimensions
	wave bg_increments

	// find entry in ad_slicer background task table
	FindValue /TEXT=posvariable /TXOP=4 /Z bg_variablepaths
	if (v_value >= 0)
		DeletePoints v_value, 1, bg_brickpaths, bg_variablepaths, bg_dimensions, bg_increments
	endif

	// stop background task if task table is empty
	if (numpnts(bg_variablepaths) == 0)
		CtrlNamedBackground ad_slicer, stop
	endif
	
	setdatafolder savedf
	return 0
end
