- new data reduction interface for more efficient multi-peak fitting. the new interface breaks compatibility with pre-2.0 data reduction functions. user-defined functions must be adapted to the new interface. - new angle scan processing panel for interactive data analysis.
1264 lines
33 KiB
Igor
1264 lines
33 KiB
Igor
#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, "<22>", 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, "<22>", 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()
|
||
setdatafolder $(package_path)
|
||
|
||
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
|