#pragma rtGlobals=3
#pragma version = 1.4
#pragma IgorVersion = 6.2
#pragma ModuleName = PearlAnglescanTracker
#include "pearl-area-profiles", version > 1.04
#include "pearl-area-import", version > 1.05
#include "pearl-scienta-preprocess", version > 1.00
#include "pearl-anglescan-process", version >= 1.6
#include <New Polar Graphs>

// $Id$
//
// author: matthias.muntwiler@psi.ch
// Copyright (c) 2014-15 Paul Scherrer Institut
//
// Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//   http://www.apache.org/licenses/LICENSE-2.0

/// @file
/// @brief instant visualization of angle scan and manipulator position.
/// @ingroup ArpesPackage
///
/// the angle scan tracker is an instant, progressive display of an angle scan during acquisition.
/// the detector window indicator provides an intuitive way to understand the manipulator motion and angle mapping of the instrument.
/// this can be useful to align the detector with a sample direction in a reference scan.
///
/// the tracker will run in offline mode, if it is started on a computer not connected to the beamline network.
/// in offline mode, all features except data exchange with the instrument are still available.
/// this is useful to simulate the measurement settings of an experiment while planning or during data analysis.
///
/// the following public functions can be called by the user.
///
/// * ast_setup() - set up data structures and display graph
/// * ast_prepare() - prepare for new measurement, clear graph
/// * ast_add_image() - add a detector image to the graph
/// * ast_update_detector() - set the current position indicator
/// * ast_import() - load an angle scan as used by pearl-anglescan-process.ipf
/// * ast_export() - export accumulated data to a separate data folder as used by pearl-anglescan-process.ipf
/// * ast_close() - stop tracker, close graph, release data structures
///
/// @pre
/// run-time requirements for online mode at the beamline
/// * EPICS.XOP of Paul Scherrer Institut, version 0.3.0 (March 2015) or later, must be loaded.
/// * caRepeater.exe program (from EPICS distribution) must be running.
/// * EPICS_CA_MAX_ARRAY_BYTES environment variable of Igor and caRepeater must be set to >= 50000000.
///
/// @see pearl-anglescan-process.ipf
///
/// @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 PearlAnglescanTracker
/// @brief instant visualization of angle scan and manipulator position.
///
/// PearlAnglescanTracker is declared in @ref pearl-anglescan-tracker.ipf.
///

/// package name is used as data folder name
static strconstant package_name = "pearl_anglescan_tracker"
/// data folder path
static strconstant package_path = "root:packages:pearl_anglescan_tracker:"
/// semicolon-separated list of persistent variable, string, and wave names
static strconstant prefs_objects = "projection;theta_offset;tilt_offset;phi_offset;reduction_func;reduction_params"

/// initialize package data once when the procedure is first loaded
static function AfterCompiledHook()

	dfref savedf = GetDataFolderDFR()
	variable do_init = 1
	if (DataFolderExists(package_path))
		setdatafolder $(package_path)
		nvar /z init_done
		if (nvar_exists(init_done))
			if (init_done)
				do_init = 0
			endif
		endif
	endif

	if (do_init)
		init_package()
		load_prefs()
		setdatafolder $(package_path)
		variable /g init_done = 1
	endif

	setdatafolder savedf
	return 0
end

static function init_package()
	dfref savedf = getdatafolderdfr()
	setdatafolder root:
	newdatafolder /o/s packages
	newdatafolder /o/s $package_name

	// configuration (persistent)
	string /g graphname = "graph_anglescan_tracker"
	string /g dataname = "tracker"
	string /g projection = "stereographic"
	
	// recently used (persistent)
	variable /g theta_offset = 0
	variable /g tilt_offset = 0
	variable /g phi_offset = 0
	string /g reduction_func = "int_linbg_reduction"
	string /g reduction_params = "Lcrop=0.1;Hcrop=0.1;Lsize=0.2;Hsize=0.2;Cpos=0.5;Csize=0.4"
	
	// recently used (volatile)
	string /g export_folderpath = "root:"
	variable /g export_format = 1
	
	// run-time variables (volatile)
	string /g detector_tracename
	variable /g capturing = 0
	variable /g buf_size = 0	// number of measurements that fit into the data buffer
	variable /g buf_count = 0	// number of measurements contained in the data buffer
	variable /g buf_width = 0	// number of slices that fit into the data buffer

	// load icon of sample holder
	string path = FunctionPath("")
	path = ParseFilePath(1, path, ":", 1, 0) + "tracker-sample-picture.png"
	LoadPict /O/Q path, pict_tracker_sample
	
	setdatafolder savedf
end

/// save persistent package data to the preferences file.
///
/// this function is called when the user clicks the corresponding button.
///
static function save_prefs()
	dfref saveDF = GetDataFolderDFR()

	string fullPath = SpecialDirPath("Packages", 0, 0, 0)
	fullPath += package_name
	NewPath/O/C/Q tempPackagePrefsPath, fullPath
	fullPath += ":preferences.pxp"

	SetDataFolder root:packages
	SetDataFolder $package_name
	SaveData /O /Q /J=prefs_objects fullPath

	KillPath/Z tempPackagePrefsPath
	
 	SetDataFolder saveDF
end

/// load persistent package data from the preferences file.
///
/// the preferences file is an Igor packed experiment file in a special preferences folder.
///
/// this function is called automatically when the procedure is first compiled,
/// or whenever the user clicks the corresponding button.
///
static function load_prefs()
	dfref saveDF = GetDataFolderDFR()

	variable result = -1
	setdatafolder root:
	NewDataFolder /O/S packages
	NewDataFolder /O/S $package_name
	string fullPath = SpecialDirPath("Packages", 0, 0, 0)
	fullPath += package_name

	GetFileFolderInfo /Q /Z fullPath
	if (V_Flag == 0) // Disk directory exists?
		fullPath += ":preferences.pxp"
		GetFileFolderInfo /Q /Z fullPath
		if (V_Flag == 0) // Preference file exist?
			LoadData /O /R /Q fullPath
			result = 0
		endif
	endif
	
 	SetDataFolder saveDF
	return result
end

/// disconnect EPICS channels before Igor quits.
static function IgorQuitHook(app)
	string app
	epics_disconnect()
end

/// set up data structures, display graph, and try to connect to analyser.
function ast_setup()
	setup_data()
	setup_detector()
	setup_graph()
	epics_connect()
end

/// prepare for new measurement and clear the data buffer.
///
/// optionally, set new manipulator offsets.
/// the offsets are the manipulator readback coordinates
/// where the sample surface is oriented in normal emission,
/// and the handle of the sample plate points horizontally to the left (9 o'clock).
///
///	@param	theta_offset	set new theta offset. default: no change.
///	@param	tilt_offset	set new tilt offset. default: no change.
///	@param	phi_offset	set new phi offset. default: no change.
function ast_prepare([theta_offset, tilt_offset, phi_offset])
	variable theta_offset
	variable tilt_offset
	variable phi_offset
	
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	if (!ParamIsDefault(theta_offset))
		nvar v_theta_offset = theta_offset
		v_theta_offset = theta_offset
	endif
	if (!ParamIsDefault(tilt_offset))
		nvar v_tilt_offset = tilt_offset
		v_tilt_offset = tilt_offset
	endif
	if (!ParamIsDefault(phi_offset))
		nvar v_phi_offset = phi_offset
		v_phi_offset = phi_offset
	endif
	
	nvar buf_count
	nvar buf_size
	nvar buf_width
	buf_count = 0
	buf_size = 0
	buf_width = 0
	
	svar dataname
	clear_hemi_grid(dataname)
	// work-around: set one point to a real number to make the rest of the trace in the graph transparent
	wave values = $(dataname + "_i")
	values[numpnts(values) - 1] = 0

	update_data_graph()
	
	setdatafolder saveDF
end

/// set the data processing parameters
///
/// the parameters will be effective for subsequent measurements only.
/// previously acquired data is not affected.
/// the processing parameters are saved with the preferences.
///
///	@param reduction_func		name of custom reduction function, e.g. "int_linbg_reduction".
///			any user-defined function with the same signature as adh5_default_reduction() is allowed.
///	@param reduction_params	parameter string for the reduction function.
///			the format depends on the actual function.
///			for int_linbg_reduction, e.g., "Lcrop=0.1;Hcrop=0.1;Lsize=0.2;Hsize=0.2;Cpos=0.5;Csize=0.4".
function ast_set_processing(reduction_func, reduction_params)
	string reduction_func
	string reduction_params

	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	svar red_func = reduction_func
	svar red_params = reduction_params

	red_func = reduction_func
	red_params = reduction_params

	setdatafolder saveDF
end

/// process and add a detector image to the tracker scan.
///
///	@param	image	detector image with correct X (energy) and Y (angle) scaling
///	@param	theta	polar angle of manipulator
///	@param	tilt		tilt angle of manipulator
///	@param	phi		azimuthal angle of manipulator
///
/// the manipulator angles are corrected by the preset offsets internally.
function ast_add_image(image, theta, tilt, phi)
	wave image
	variable theta
	variable tilt
	variable phi

	add_image_data(image, theta, tilt, phi)
	process_image_data()
	update_data_graph()
end

/// export tracker data to a separate, independent data set.
///
/// the exported data can then be used for further processing.
/// the data is exported to the current data folder, or root if XPDplot compatibility is requested.
///
///	@param	folder		destination folder path
///	@param	nickname	name prefix for waves
///	@param	xpdplot		xpdplot compatibility, see make_hemi_grid() for details
///		@arg 0 (default)
///		@arg 1 create additional waves and notebook required by XPDplot
///
function ast_export(folder, nickname, [xpdplot])
	dfref folder
	string nickname
	variable xpdplot
	
	if (ParamIsDefault(xpdplot))
		xpdplot = 0
	endif

	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	svar dataname
	duplicate_hemi_scan(dataname, folder, nickname, xpdplot=xpdplot)
	
	setdatafolder saveDF
end

/// import tracker data from an existing angle scan dataset.
///
///	@param	nickname	name prefix for waves. data must be in current data folder.
///
function ast_import(nickname)
	string nickname
	
	dfref savedf = getdatafolderdfr()

	svar /sdfr=$(package_path) dataname
	duplicate_hemi_scan(nickname, $(package_path), dataname)
	
	setdatafolder saveDF
end

/// update the current position indicator.
///
///	@param	theta	polar angle of manipulator
///	@param	tilt		tilt angle of manipulator
///	@param	phi		azimuthal angle of manipulator
///	@param range	angle range (60 or 45)
///
/// the manipulator angles are corrected by the preset offsets internally.
///
function ast_update_detector(theta, tilt, phi, range)
	variable theta
	variable tilt
	variable phi
	variable range
	
	update_detector(theta, tilt, phi, range)
	update_detector_graph()
end

/// stop tracker, close graph, release data structures.
function ast_close()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	epics_disconnect()
	
	svar graphname
	KillWindow $graphname
	
	setdatafolder saveDF
end

static function setup_data()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	nvar buf_size
	nvar buf_count
	nvar buf_width
	buf_size = 0
	buf_count = 0
	buf_width = 0
	make /n=(1,1) /o buf_i
	make /n=(1) /o buf_th, buf_ph, buf_ti
	
	svar dataname
	variable npolar = 91
	make_hemi_grid(npolar, dataname)
	
	// work-around: set one point to a real number to make the rest of the trace in the graph transparent
	wave values = $(dataname + "_i")
	values[numpnts(values) - 1] = 0
	
	setdatafolder saveDF
end

/// extend the data buffer for the next polar scan
///
/// call this function if the buffer is full.
///
///	@param num_slices	number of slices that the measurement contains
///
static function extend_data(num_slices)
	variable num_slices
	
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	nvar buf_size
	nvar buf_count
	nvar buf_width
	
	variable new_size = buf_size + 91
	buf_width = num_slices
	
	wave buf_i
	wave buf_th
	wave buf_ti
	wave buf_ph
	
	redimension /n=(buf_width,new_size) buf_i
	redimension /n=(new_size) buf_th, buf_ph, buf_ti

	buf_i[][buf_size, new_size-1] = nan
	buf_th[buf_size, new_size-1] = nan
	buf_ph[buf_size, new_size-1] = nan
	buf_ti[buf_size, new_size-1] = nan
	
	buf_size = new_size
	
	setdatafolder saveDF
	return buf_count
end

static function setup_detector()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	make /n=31 /o detector_angle, detector_pol, detector_az, detector_rad
	setscale /i x -30, 30, "°", detector_angle, detector_pol, detector_az, detector_rad
	detector_angle = x
	
	setdatafolder saveDF
end

/// reduce a detector image and add the result to the data buffer.
///
///	@param	image	detector image with correct X (energy) and Y (angle) scaling
///	@param	theta	polar angle of manipulator
///	@param	tilt		tilt angle of manipulator
///	@param	phi		azimuthal angle of manipulator
///
/// the manipulator angles are corrected by the preset offsets internally.
static function add_image_data(image, theta, tilt, phi)
	wave image
	variable theta
	variable tilt
	variable phi

	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)
	svar dataname
	nvar theta_offset
	nvar tilt_offset
	nvar phi_offset

	// extract angle distribution from image using reduction function mechanism from area-import
	svar red_func_name = reduction_func
	svar red_params = reduction_params
	funcref adh5_default_reduction red_func = $red_func_name
	variable nx = dimsize(image, 0)
	string loc_params = red_params
	wave /wave red_results = red_func(image, loc_params)
	if (numpnts(red_results) < 1)
		setdatafolder saveDF
		return 0
	endif
	
	wave profile1 = red_results[0]
	nx = numpnts(profile1)
	
	// write the result to the buffer
	nvar buf_count
	nvar buf_size
	nvar buf_width
	wave buf_i
	wave buf_th
	wave buf_ph
	wave buf_ti
	
	if ((buf_count >= buf_size) || (nx > buf_width))
		extend_data(nx)
		setscale /p x dimoffset(profile1,0), dimdelta(profile1,0), waveunits(profile1,0), buf_i
	endif
	
	buf_i[][buf_count] = profile1[p]
	buf_th[buf_count] = theta - theta_offset
	buf_ti[buf_count] = -(tilt - tilt_offset)
	buf_ph[buf_count] = phi - phi_offset
	
	buf_count += 1
	
	setdatafolder saveDF
end

/// process the data buffer to generate the tracker dataset.
///
static function process_image_data()
	wave image
	variable theta
	variable tilt
	variable phi

	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)
	svar dataname

	nvar buf_count
	nvar buf_size
	nvar buf_width
	
	wave buf_i
	wave buf_th
	wave buf_ph
	wave buf_ti
	
	duplicate /free /R=[0,buf_width-1][0,buf_count-1] buf_i, buf_n
	duplicate /free /R=[0,buf_count-1] buf_th, w_th
	duplicate /free /R=[0,buf_count-1] buf_ti, w_ti
	duplicate /free /R=[0,buf_count-1] buf_ph, w_ph
	
	normalize_strip_x(buf_n, smooth_method=4)
	if (buf_count >= 10)
		normalize_strip_theta(buf_n, w_th, smooth_method=4)
	endif
	if (dimoffset(buf_i,0) < -20)
		crop_strip(buf_n, -25, 25)
	else
		crop_strip(buf_n, -15, 15)
	endif
	
	make /n=1 /free d_polar, d_azi
	convert_angles_ttpd2polar(w_th, w_ti, w_ph, buf_n, d_polar, d_azi)
	d_azi += 180	// changed 151030 (v1.4)
	d_azi = d_azi >= 360 ? d_azi - 360 : d_azi
	hemi_add_anglescan(dataname, buf_n, d_polar, d_azi)
	
	setdatafolder saveDF
end

/// update the current position indicator.
///
///	@param	theta	polar angle of manipulator
///	@param	tilt		tilt angle of manipulator
///	@param	phi		azimuthal angle of manipulator
///	@param range	angle range (60 or 45)
///
/// the manipulator angles are corrected by the preset offsets internally.
///
static function update_detector(theta, tilt, phi, range)
	variable theta
	variable tilt
	variable phi
	variable range

	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)
	nvar theta_offset
	nvar tilt_offset
	nvar phi_offset
	
	make /n=1 /free m_theta
	make /n=1 /free m_tilt
	make /n=1 /free m_phi
	m_theta = theta - theta_offset
	m_tilt = tilt - tilt_offset
	m_tilt *= -1 // checked 140702
	m_phi = phi - phi_offset
	//m_phi *= -1 // checked 140702
	
	wave detector_angle, detector_pol, detector_az, detector_rad
	setscale /i x -range/2, +range/2, "°", detector_angle
	detector_angle = x
	
	convert_angles_ttpa2polar(m_theta, m_tilt, m_phi, detector_angle, detector_pol, detector_az)
	redimension /n=(numpnts(detector_pol)) detector_rad
	detector_rad =  2 * tan(detector_pol / 2 * pi / 180)
	detector_az += 180	// changed 151030 (v1.4)
	detector_az = detector_az >= 360 ? detector_az - 360 : detector_az
	
	setdatafolder saveDF
end

/// create the graph window.
static function setup_graph()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)
	
	svar dataname
	svar graphname
	wave detector_az
	wave detector_rad
	wave detector_angle
	svar tracename = detector_tracename
	
	graphname = display_hemi_scan(dataname, graphname=graphname)
	tracename = WMPolarAppendTrace(graphname, detector_rad, detector_az, 360)
	ModifyGraph /w=$graphname lstyle($tracename)=0
	ModifyGraph /w=$graphname lsize($tracename)=1.5
	ModifyGraph /w=$graphname zColor($tracename)={detector_angle,*,*,RedWhiteBlue,0}
	ColorScale /w=$graphname /C /N=text1 trace=$tracename
	ColorScale /w=$graphname /C /N=text1 /F=0 /B=1 /A=LB /X=0.00 /Y=0.00
	ColorScale /w=$graphname /C /N=text1 width=1.5, heightPct=20, frame=0.00
	ColorScale /w=$graphname /C /N=text1 lblMargin=0
	ColorScale /w=$graphname /C /N=text1 nticks=2, tickLen=2.00, tickThick=0.50

	TextBox /w=$graphname /C /N=tb_manip /F=0 /B=1 /X=0.00 /Y=0.00 /E=2 "\\{\"manip = (%.1f, %.1f, %.1f)\", "
	AppendText /w=$graphname /N=tb_manip /NOCR "root:packages:pearl_anglescan_tracker:curTheta, "
	AppendText /w=$graphname /N=tb_manip /NOCR "root:packages:pearl_anglescan_tracker:curTilt, "
	AppendText /w=$graphname /N=tb_manip /NOCR "root:packages:pearl_anglescan_tracker:curPhi}"

	// the window hook releases the EPICS variables when the window is killed
	DoWindow /T $graphname, "Angle Scan Tracker"
	SetWindow $graphname, hook(ast_hook) = ast_window_hook
	
	ControlBar /w=$graphname 21
	Button b_capture win=$graphname, title="start", pos={0,0}, size={40,21}, proc=PearlAnglescanTracker#bp_capture
	Button b_capture win=$graphname, fColor=(65535,65535,65535), fSize=10
	Button b_capture win=$graphname, help={"Start/stop capturing."}
	PopupMenu pm_params win=$graphname, mode=0, value="load preferences;save preferences;reduction parameters;manipulator offsets", title="parameters"
	PopupMenu pm_params win=$graphname, pos={70,0}, bodyWidth=80, proc=PearlAnglescanTracker#pmp_parameters
	PopupMenu pm_params win=$graphname, help={"Load/save/edit data processing parameters"}
	PopupMenu pm_data win=$graphname, mode=0, value="import;export;load file;save file", title="data"
	PopupMenu pm_data win=$graphname, pos={120,0}, proc=PearlAnglescanTracker#pmp_data
	PopupMenu pm_data win=$graphname, help={"Load/save data from/to independent dataset or file"}
	
	SetDrawLayer /w=$graphname ProgFront
	SetDrawEnv /w=$graphname xcoord=rel, ycoord=rel
	DrawPict /w=$graphname 0, 0, 1, 1, pict_tracker_sample
	
	update_capture()
	
	setdatafolder saveDF
end

static function update_data_graph()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)
	
	svar dataname
	svar graphname
	
	// nothing to do - trace is updated automatically
	
	setdatafolder saveDF
end

static function update_detector_graph()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)
	
	svar dataname
	svar graphname
	
	// nothing to do - trace is updated automatically
	
	setdatafolder saveDF
end

/// connect the angle scan tracker to EPICS
///
/// the tracker uses channels of the analyser and the manipulator.
///
/// if the EPICS XOP is not loaded, the function does nothing.
/// if channels are not available, the function exits with an error code after a timeout of 5 seconds.
/// the Igor run-time error status is reset to suppress the error dialog.
///
/// @returns zero if successful, non-zero if an error occurred
///
/// @todo the X03DA channel names are hard-coded.
static function epics_connect()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)
	
	// close PVs which may be open from a previous call
	epics_disconnect()
	
	// create variables and waves
	make /n=(1)/o arraydata, xscale, yscale
	make /n=(1,1)/o image
	variable /g ndimensions
	variable /g arraysize0, arraysize1
	variable /g datatype
	variable /g colormode
	string /g controls, monitors
	string /g xunits, yunits

	// channel ID variables
	variable /g chidDetectorState = 0
	variable /g chidArrayData = 0
	variable /g chidXScale = 0
	variable /g chidYScale = 0
	variable /g chidNDimensions = 0
	variable /g chidArraySize0 = 0
	variable /g chidArraySize1 = 0
	variable /g chidDataType = 0
	variable /g chidColorMode = 0
	variable /g chidLensMode = 0
	variable /g chidTheta = 0
	variable /g chidTilt = 0
	variable /g chidPhi = 0
	variable /g curDetectorState = 0
	variable /g curLensMode = 0
	variable /g curTheta = 0
	variable /g curTilt = 0
	variable /g curPhi = 0
	variable /g acqTheta = 0
	variable /g acqTilt = 0
	variable /g acqPhi = 0
	variable /g connected = 0
	
	string epicsname = "X03DA-SCIENTA:"
	string imagename = epicsname + "image1:"
	string camname = epicsname + "cam1:"
	string manipname = "X03DA-ES2-MA:"
	variable timeout = 5	// seconds
	
	#if exists("pvWait")
	// EPICS.XOP version 0.3.0 or later
	pvOpen /Q chidDetectorState, camname + "DetectorState_RBV" // 0 = idle
	pvOpen /Q chidLensMode, camname + "LENS_MODE_RBV"
	pvOpen /Q chidXScale, camname + "CHANNEL_SCALE_RBV"
	pvOpen /Q chidYScale, camname + "SLICE_SCALE_RBV"
	pvOpen /Q chidArrayData, imagename + "ArrayData"
	pvOpen /Q chidNDimensions, imagename + "NDimensions_RBV"
	pvOpen /Q chidArraySize0, imagename + "ArraySize0_RBV"
	pvOpen /Q chidArraySize1, imagename + "ArraySize1_RBV"
	pvOpen /Q chidDataType, imagename + "DataType_RBV"
	pvOpen /Q chidColorMode, imagename + "ColorMode_RBV"
	
	pvOpen /Q chidTheta, manipname + "THT.RBV"
	pvOpen /Q chidTilt, manipname + "TLT.RBV"
	pvOpen /Q chidPhi, manipname + "PHI.RBV"

	pvWait timeout
	
	if (!GetRTError(1))
		connected = 1
	endif
	#elif exists("pvOpen")
	// EPICS.XOP version < 0.3.0
	pvOpen /T=(timeout) chidDetectorState, camname + "DetectorState_RBV" // 0 = idle
	pvOpen /T=(timeout) chidLensMode, camname + "LENS_MODE_RBV"
	pvOpen /T=(timeout) chidXScale, camname + "CHANNEL_SCALE_RBV"
	pvOpen /T=(timeout) chidYScale, camname + "SLICE_SCALE_RBV"
	pvOpen /T=(timeout) chidArrayData, imagename + "ArrayData"
	pvOpen /T=(timeout) chidNDimensions, imagename + "NDimensions_RBV"
	pvOpen /T=(timeout) chidArraySize0, imagename + "ArraySize0_RBV"
	pvOpen /T=(timeout) chidArraySize1, imagename + "ArraySize1_RBV"
	pvOpen /T=(timeout) chidDataType, imagename + "DataType_RBV"
	pvOpen /T=(timeout) chidColorMode, imagename + "ColorMode_RBV"
	
	pvOpen /T=(timeout) chidTheta, manipname + "THT.RBV"
	pvOpen /T=(timeout) chidTilt, manipname + "TLT.RBV"
	pvOpen /T=(timeout) chidPhi, manipname + "PHI.RBV"

	if (!GetRTError(1))
		connected = 1
	endif
	#endif
	
	#if exists("pvMonitor")
	if (connected)
		pvMonitor /F=ast_callback_detector chidDetectorState, curDetectorState	
		pvMonitor /F=ast_callback_manip chidTheta, curTheta
		pvMonitor /F=ast_callback_manip chidTilt, curTilt
		pvMonitor /F=ast_callback_manip chidPhi, curPhi
		pvMonitor /F=ast_callback_manip chidLensMode, curLensMode
		pvMonitor /F=ast_callback_data chidArrayData
	endif
	#endif
	
	if (connected)
		print "angle scan tracker: online"
	else
		print "angle scan tracker: offline"
	endif

	setdatafolder saveDF
	return !connected
end

static function epics_disconnect_chid(chid_var_name)
	string chid_var_name
	
	#if exists("pvClose")
	nvar /z chid = $chid_var_name
	if (nvar_exists(chid))
		if (chid != 0)
			pvClose chid
		endif
		chid = 0
	endif
	#endif
end

static function epics_disconnect()
	dfref savedf = GetDataFolderDFR()
	
	if (DataFolderExists(package_path))
		SetDataFolder $(package_path)
	else
		return nan
	endif
	
	nvar connected
	if (connected)
		connected = 0
		epics_disconnect_chid("chidDetectorState")
		epics_disconnect_chid("chidArrayData")
		epics_disconnect_chid("chidXScale")
		epics_disconnect_chid("chidYScale")
		epics_disconnect_chid("chidNDimensions")
		epics_disconnect_chid("chidArraySize0")
		epics_disconnect_chid("chidArraySize1")
		epics_disconnect_chid("chidDataType")
		epics_disconnect_chid("chidColorMode")
		epics_disconnect_chid("chidLensMode")
		epics_disconnect_chid("chidTheta")
		epics_disconnect_chid("chidTilt")
		epics_disconnect_chid("chidPhi")
		print "angle scan tracker: offline"
	endif
	
	setdatafolder savedf
end

/// window hook
///
/// disconnects from EPICS when the window is closed.
///
static function ast_window_hook(s)
	STRUCT WMWinHookStruct &s

	Variable hookResult = 0

	switch(s.eventCode)
		case 2: // kill
			epics_disconnect()
			break
	endswitch

	return hookResult	
End

/// callback function for new analyser data from EPICS.
function ast_callback_data(chan)
	variable chan

	nvar capturing = $(package_path + "capturing")
	if (!capturing)
		return 0
	endif

	dfref savedf = GetDataFolderDFR()
	setdatafolder $(package_path)
	#if exists("pvGetWave")
	
	// retrieve data
	nvar chidArrayData
	nvar chidXScale
	nvar chidYScale
	nvar chidNDimensions
	nvar chidArraySize0
	nvar chidArraySize1
	nvar chidDataType
	nvar chidColorMode
	nvar chidTheta
	nvar chidTilt
	nvar chidPhi
	nvar acqTheta
	nvar acqTilt
	nvar acqPhi
	
	wave arraydata
	wave image
	wave xscale
	wave yscale
	variable ndimensions
	variable arraysize0
	variable arraysize1
	variable datatype
	variable colormode
	
	//printf "array callback: acqtheta = %.1f, acqtilt = %.1f, acqphi = %.1f\r", acqTheta, acqTilt, acqPhi

	pvGet chidNDimensions, ndimensions
	pvGet chidArraySize0, arraysize0
	pvGet chidArraySize1, arraysize1
	pvGet chidDataType, datatype
	pvGet chidColorMode, colormode
	
	// sanity checks
	if (ndimensions != 2)
		return -2
	endif
	if (colormode != 0)
		return -3
	endif
	
	redimension /n=(arraysize0 * arraysize1) arraydata
	redimension /n=(arraysize0, arraysize1) image
	redimension /n=(arraysize0) xscale
	redimension /n=(arraysize1) yscale
	
	switch(datatype)
		case 0: // int8
			redimension /b arraydata, image
			break
		case 1: // uint8
			redimension /b/u arraydata, image
			break
		case 2: // int16
			redimension /w arraydata, image
			break
		case 3: // uint16
			redimension /w/u arraydata, image
			break
		case 4: // int32
			redimension /i arraydata, image
			break
		case 5: // uint32
			redimension /i/u arraydata, image
			break
		case 6: // float32
			redimension /s arraydata, image
			break
		case 7: // float64
			redimension /d arraydata, image
			break
	endswitch
	
	pvGetWave chidArrayData, arraydata
	pvGetWave chidXScale, xscale
	pvGetWave chidYScale, yscale
	
	image = arraydata[p + q * arraysize0]
	setscale /i x xscale[0], xscale[numpnts(xscale)-1], image
	setscale /i y yscale[0], yscale[numpnts(yscale)-1], image
	
	ast_add_image(image, acqTheta, acqTilt, acqPhi)
	
	#endif
	setdatafolder savedf
	return 0
end

/// callback function for new detector state from EPICS.
///
/// save the manipulator position at the beginning of image acquisition.
/// it is used by ast_callback_data().
function ast_callback_detector(chan)
	variable chan

	dfref savedf = GetDataFolderDFR()
	setdatafolder $(package_path)
	
	// retrieve data
	nvar curDetectorState
	nvar curTheta
	nvar curTilt
	nvar curPhi
	
	nvar acqTheta
	nvar acqTilt
	nvar acqPhi

	if (curDetectorState == 1)
		acqTheta = curTheta
		acqTilt = curTilt
		acqPhi = curPhi
	endif
	
	//printf "detector callback: acqtheta = %.1f, acqtilt = %.1f, acqphi = %.1f, detstate = %d\r", acqTheta, acqTilt, acqPhi, curDetectorState

	setdatafolder savedf
	return 0
end

/// callback function for new manipulator position from EPICS.
function ast_callback_manip(chan)
	variable chan

	dfref savedf = GetDataFolderDFR()
	setdatafolder $(package_path)
	
	// retrieve data
	nvar lensmode = curLensMode
	nvar theta = curTheta
	nvar tilt = curTilt
	nvar phi = curPhi

	//printf "manipulator callback: curtheta = %.1f, curtilt = %.1f, curphi = %.1f, lensmode = %d\r", theta, tilt, phi, lensmode

	variable range
	switch(lensmode)
		case 1:
			range = 45 // angular 45
			break
		case 2:
			range = 60 // angular 60
			break
		default:
			range = 2 // transmission or error
	endswitch
	ast_update_detector(theta, tilt, phi, range)
	
	setdatafolder savedf
	return 0
end

// GUI functions

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

	switch( ba.eventCode )
		case 2: // mouse up
			toggle_capture()
			break
		case -1: // control being killed
			break
	endswitch

	return 0
end

static function toggle_capture()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	nvar capturing
	svar graphname

	capturing = !capturing
	if (capturing)
		ast_prepare()
		Button b_capture win=$graphname, title="stop"
	else
		Button b_capture win=$graphname, title="start"
	endif

	setdatafolder saveDF
end

static function update_capture()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	nvar capturing
	svar graphname

	if (capturing)
		Button b_capture win=$graphname, title="stop"
	else
		Button b_capture win=$graphname, title="start"
	endif

	setdatafolder saveDF
end

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

	switch( pa.eventCode )
		case 2: // mouse up
			pmp_data_mouseup(pa)
			break
		case -1: // control being killed
			break
	endswitch

	return 0
end

static function pmp_data_mouseup(pa)
	STRUCT WMPopupAction &pa

	switch(pa.popNum)
		case 1:
			import_tracker_data()
			break
		case 2:
			export_tracker_data()
			break
		case 3:
			load_tracker_data()
			break
		case 4:
			save_tracker_data()
			break
	endswitch
end

/// export tracker data (with prompt)
static function export_tracker_data()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	svar export_folderpath
	nvar export_format
	
	string folderpath = export_folderpath
	string nickname = ""
	variable format = export_format
	
	prompt folderpath, "Folder Path"
	prompt nickname, "Nick Name"
	prompt format, "Format", popup, "PEARL;XPDplot"
	
	doprompt "Export Parameters", folderpath, nickname, format
	
	if (v_flag == 0)
		export_folderpath = folderpath
		export_format = format
		// note: if a full or partial path is used, all data folders except for the last in the path must already exist.
		newdatafolder /o $folderpath
		variable xpdplot = format == 2
		ast_export($folderpath, nickname, xpdplot=xpdplot)
	endif

	setdatafolder saveDF
end

/// import tracker data (with prompt)
static function import_tracker_data()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	svar export_folderpath
	string folderpath = export_folderpath
	string nickname = ""

	dfref dfBefore = GetDataFolderDFR()
	Execute /q/z "CreateBrowser prompt=\"Select wave from dataset\", showWaves=1, showVars=0, showStrs=0"
	dfref dfAfter = GetDataFolderDFR()
	SetDataFolder dfBefore
	
	SVAR list = S_BrowserList
	NVAR flag = V_Flag
	
	if ((flag != 0) && (ItemsInList(list) >= 1))
		string wname = StringFromList(0, list)
		wave w = $wname
		string prefix = get_hemi_prefix(w)
		dfref df = GetWavesDataFolderDFR(w)
		setdatafolder df
		ast_import(prefix)
	endif

	setdatafolder saveDF
end

/// save tracker data to file (with prompt)
static function save_tracker_data()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	svar dataname
	save_hemi_scan(dataname, "", "")
	
	setdatafolder saveDF
end

/// import tracker data from file (with prompt)
static function load_tracker_data()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	NewDataFolder /O/S load_data
	LoadWave /t /q
	if (v_flag > 0)
		string wname = StringFromList(0, s_wavenames, ";")
		wave w = $wname
		string prefix = get_hemi_prefix(w)
		ast_import(prefix)
	endif

	setdatafolder $(package_path)
	KillDataFolder /Z load_data
	
	setdatafolder saveDF
end

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

	switch( pa.eventCode )
		case 2: // mouse up
			pmp_parameters_mouseup(pa)
			break
		case -1: // control being killed
			break
	endswitch

	return 0
end

static function pmp_parameters_mouseup(pa)
	STRUCT WMPopupAction &pa

	switch(pa.popNum)
		case 1:
			load_prefs()
			break
		case 2:
			save_prefs()
			break
		case 3:
			edit_reduction_params()
			break
		case 4:
			edit_offsets()
			break
	endswitch

end

static function edit_reduction_params()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	svar pref_func = reduction_func
	svar pref_params = reduction_params

	string loc_func = pref_func
	string loc_params = pref_params
	if (prompt_func_params(loc_func, loc_params) == 0)
		ast_set_processing(loc_func, loc_params)
	endif

	setdatafolder saveDF
end

static function edit_offsets()
	dfref savedf = getdatafolderdfr()
	setdatafolder $(package_path)

	nvar theta_offset
	nvar tilt_offset
	nvar phi_offset
	
	variable loc_theta = theta_offset
	variable loc_tilt = tilt_offset
	variable loc_phi = phi_offset
	
	prompt loc_theta, "theta offset"
	prompt loc_tilt, "tilt offset"
	prompt loc_phi, "phi offset"
	
	doprompt "manipulator offsets", loc_theta, loc_tilt, loc_phi
	if (v_flag == 0)
		theta_offset = loc_theta
		tilt_offset = loc_tilt
		phi_offset = loc_phi
	endif

	setdatafolder saveDF
end
