- pshell import: fix units and data scaling. - pshell import: support new multi-region scans. - angle scans: add trim function. - angle scans: update import_tpi_scan function. - angle scans: fix scales of check waves in normalization. - area display: new cursor mode for background selection. - elog: bugfixes (attachment list, check existing logbook).
1259 lines
33 KiB
Igor
1259 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, "°", 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)
|
|
make /n=(nx) /free profile1, profile2
|
|
string loc_params = red_params
|
|
red_func(image, profile1, profile2, loc_params)
|
|
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()
|
|
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
|