3583 lines
111 KiB
Igor
3583 lines
111 KiB
Igor
#pragma TextEncoding = "UTF-8"
|
|
#pragma rtGlobals=3 // Use modern global access method and strict wave access.
|
|
#pragma version = 1.9
|
|
#pragma IgorVersion = 6.2
|
|
#pragma ModuleName = PearlAnglescanProcess
|
|
#include "pearl-vector-operations"
|
|
#include "pearl-polar-coordinates"
|
|
#include <New Polar Graphs>
|
|
|
|
// copyright (c) 2013-21 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
|
|
//
|
|
// Please acknowledge the use of this code.
|
|
|
|
/// @file
|
|
/// @brief processing and holographic mapping of angle scanned XPD data.
|
|
/// @ingroup ArpesPackage
|
|
///
|
|
/// the functions in this file map angle scanned data measured at PEARL
|
|
/// onto a hemispherical angle grid which is compatible with XPDplot.
|
|
/// the resulting data are in a canonical polar coordinate system
|
|
/// (normal emission <=> polar angle = 0, azimuthal axis right-handed)
|
|
/// which is anchored in the sample surface.
|
|
/// the orientation of polar graphs (phi = 0 at 3 o'clock) created by this procedure
|
|
/// corresponds to a top view of the sample surface at normal emission,
|
|
/// and the handle of the sample plate pointing to the left (phi = 180).
|
|
/// this is the canonical orientation of a spherical coordinate system
|
|
/// where phi = 0 corresponds to the positive part of the x axis.
|
|
///
|
|
/// @note the orientation of the sample coordinate system has changed in version 1.6.
|
|
/// the change was necessary for compatibility with other data analysis software
|
|
/// and calculation programs.
|
|
/// @note data imported with version 1.5 and earlier, must be offset by 180 deg in phi
|
|
/// to be compatible with the new version.
|
|
/// data imported and displayed by the same code version will give the same picture
|
|
/// but with different azimuthal axis.
|
|
/// the new graph functions shows a warning if they are applied to code imported with earlier versions.
|
|
///
|
|
/// the measurement geometry is hard-coded but may be parametrized in the future.
|
|
/// the theta rotation axis is perpendicular to the scattering plane.
|
|
/// the angle dispersive axis of the analyser is parallel to the theta rotation axis.
|
|
/// the tilt rotation axis is in the scattering plane.
|
|
/// it rotates with theta.
|
|
/// at normal emission it is perpendicular to the axis of the lens stack of the analyser.
|
|
/// the phi rotation axis corresponds to the surface normal of the sample.
|
|
/// it rotates with theta and with tilt.
|
|
/// at normal emission it is parallel to the axis of the lens stack of the analyser.
|
|
///
|
|
/// coordinate transformations: (to be revised - v1.6)
|
|
///@verbatim
|
|
/// theta_sample = theta_manipulator - theta_offset
|
|
/// phi_sample = phi_manipulator - phi_offset
|
|
///@endverbatim
|
|
///
|
|
/// valid for theta_manipulator = normal emission only: (to be revised - v1.6)
|
|
///@verbatim
|
|
/// theta_sample = | -(tilt_manipulator - tilt_offset) |
|
|
/// phi_sample = 270 if tilt_manipulator - tilt_offset > 0
|
|
/// phi_sample = 90 if tilt_manipulator - tilt_offset < 0
|
|
///@endverbatim
|
|
///
|
|
/// @author matthias muntwiler, matthias.muntwiler@psi.ch
|
|
///
|
|
/// @copyright 2013-21 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
|
|
///
|
|
/// @version 1.8
|
|
/// canonical orientation of spherical coordinate system.
|
|
///
|
|
|
|
/// @namespace PearlAnglescanProcess
|
|
/// @brief processing and holographic mapping of angle scanned XPD data.
|
|
///
|
|
/// PearlAnglescanProcess is declared in @ref pearl-anglescan-process.ipf.
|
|
|
|
|
|
/// append an angle scan strip to another one
|
|
///
|
|
/// concatenate two angle scan strips including matching attribute waves
|
|
/// and replace the first strip with the resulting waves.
|
|
/// this is useful if a scan was interrupted and continues in a second data file.
|
|
///
|
|
/// all accompanying 1D waves which have a matching length and exist in both source and destination folders
|
|
/// are concatenated and stored in the destination.
|
|
/// 'accompanying waves' are those in the same folder as the 2D strip wave and those in the :attr sub-folder.
|
|
///
|
|
/// @attention this function modifies all matching waves in the data and attr folders of strip1!
|
|
/// consider a backup before calling the function, or work on a copy of the original data
|
|
/// (cf. Igor's DuplicateDataFolder operation)!
|
|
///
|
|
/// @param[in,out] strip1 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan.
|
|
/// this is the first source wave and destination wave.
|
|
///
|
|
/// @param[in] strip2 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan.
|
|
/// this is the second source wave.
|
|
///
|
|
/// @return (string) semicolon-separated list of modified wave names (without folder path).
|
|
///
|
|
function /s strip_append(strip1, strip2)
|
|
wave strip1
|
|
wave strip2
|
|
|
|
dfref df1 = GetWavesDataFolderDFR(strip1)
|
|
dfref df2 = GetWavesDataFolderDFR(strip2)
|
|
variable ny1 = dimsize(strip1, 1)
|
|
variable ny2 = dimsize(strip2, 1)
|
|
|
|
concatenate /np=1 {strip2}, strip1
|
|
string modified = AddListItem(NameOfWave(strip1), "")
|
|
|
|
variable idf = 0
|
|
do
|
|
variable iw = 0
|
|
do
|
|
wave /z w1 = WaveRefIndexedDFR(df1, iw)
|
|
if (!WaveExists(w1))
|
|
break
|
|
endif
|
|
|
|
wave /z w2 = df2:$(NameOfWave(w1))
|
|
if (WaveExists(w1) && WaveExists(w2))
|
|
if ((DimSize(w1, 0) == ny1) && (DimSize(w1, 1) == 0) && (DimSize(w2, 0) == ny2) && (DimSize(w2, 1) == 0))
|
|
concatenate /np=0 {w2}, w1
|
|
modified = AddListItem(NameOfWave(w1), modified, "", Inf)
|
|
endif
|
|
endif
|
|
iw += 1
|
|
while(1)
|
|
|
|
df1 = df1:attr
|
|
df2 = df2:attr
|
|
if ((DataFolderRefStatus(df1) != 1) || (DataFolderRefStatus(df2) != 1))
|
|
break
|
|
endif
|
|
idf += 1
|
|
while(idf < 2)
|
|
|
|
return modified
|
|
end
|
|
|
|
/// delete a contiguous range of frames from a strip.
|
|
///
|
|
/// this can be used to remove a region of bad frames due to, e.g., measurement problems.
|
|
/// the function operates on 2D intensity data and manipulator coordinates at the same time.
|
|
///
|
|
/// @param[in,out] strip 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan.
|
|
/// the result is written to the original wave.
|
|
///
|
|
/// @param[in,out] theta 1D data, manipulator scan.
|
|
/// the result is written to the original wave.
|
|
///
|
|
/// @param[in,out] tilt 1D data, manipulator scan.
|
|
/// the result is written to the original wave.
|
|
///
|
|
/// @param[in,out] phi 1D data, manipulator scan.
|
|
/// the result is written to the original wave.
|
|
///
|
|
/// @param[in] qlo point index of first frame to delete.
|
|
///
|
|
/// @param[in] qhi point index of last frame to delete.
|
|
/// qhi must be greater or equal than qlo.
|
|
///
|
|
function strip_delete_frames(strip, qlo, qhi, theta, tilt, phi)
|
|
wave strip // 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan
|
|
variable qlo
|
|
variable qhi
|
|
wave theta
|
|
wave tilt
|
|
wave phi
|
|
|
|
if (qlo > qhi)
|
|
return -1
|
|
endif
|
|
|
|
// source indices
|
|
variable snx = dimsize(strip, 0)
|
|
variable sny = dimsize(strip, 1)
|
|
variable sq1lo = 0
|
|
variable sq1hi = max(qlo-1, 0)
|
|
variable sq2lo = min(qhi+1, sny - 1)
|
|
variable sq2hi = dimsize(strip, 1) - 1
|
|
|
|
// dest indices
|
|
variable dnx = snx
|
|
variable dny = sny - (sq2lo - sq1hi + 1)
|
|
variable dq1lo = 0
|
|
variable dq1hi = sq1hi
|
|
variable dq2lo = dq1hi + 1
|
|
variable dq2hi = dny - 1
|
|
variable q1ofs = sq1lo - dq1lo
|
|
variable q2ofs = sq2lo - dq2lo
|
|
|
|
duplicate /free strip, strip_copy
|
|
redimension /n=(dnx,dny) strip
|
|
strip[][dq1lo,dq1hi] = strip_copy[p][q + q1ofs]
|
|
strip[][dq2lo,dq2hi] = strip_copy[p][q + q2ofs]
|
|
|
|
duplicate /free theta, theta_copy
|
|
redimension /n=(dny) theta
|
|
theta[dq1lo,dq1hi] = theta_copy[p + q1ofs]
|
|
theta[dq2lo,dq2hi] = theta_copy[p + q2ofs]
|
|
|
|
duplicate /free tilt, tilt_copy
|
|
redimension /n=(dny) tilt
|
|
tilt[dq1lo,dq1hi] = tilt_copy[p + q1ofs]
|
|
tilt[dq2lo,dq2hi] = tilt_copy[p + q2ofs]
|
|
|
|
duplicate /free phi, phi_copy
|
|
redimension /n=(dny) phi
|
|
phi[dq1lo,dq1hi] = phi_copy[p + q1ofs]
|
|
phi[dq2lo,dq2hi] = phi_copy[p + q2ofs]
|
|
|
|
return 0
|
|
end
|
|
|
|
/// divide the strip by the average X distribution.
|
|
///
|
|
/// this is a simple way to remove the effect of the angle-dependence of the analyser transmission function.
|
|
/// the strip is normalized in place, previous data is overwritten.
|
|
///
|
|
/// the function can handle sparse NaNs.
|
|
///
|
|
/// @param[in,out] strip 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan
|
|
///
|
|
/// @param[in] smooth_method smoothing method
|
|
/// @arg 0 none
|
|
/// @arg 1 binomial, see Igor's Smooth operation
|
|
/// @arg 2 boxcar, see Igor's Smooth operation
|
|
/// @arg 3 scienta_ang_transm() function fit
|
|
/// @arg 4 (default) LOESS smoothing, see Igor's Loess operation
|
|
///
|
|
/// @param[in] smooth_factor num parameter of Igor's Smooth operation.
|
|
/// the default value depends on smooth_method.
|
|
/// it is 0.5 for LOESS smoothing, 2 otherwise.
|
|
///
|
|
/// @param[in] check enable output of intermediate results
|
|
/// @arg 0 (default) don't create additional waves
|
|
/// @arg 1 create check waves in the current folder
|
|
/// @arg 2 calculate check waves only, do not modify strip
|
|
///
|
|
/// @return if check waves are enabled, the following waves are created (overwritten if existing):
|
|
/// @arg check_dist average X distribution
|
|
/// @arg check_smoo smoothed distribution used to normalize the strip
|
|
///
|
|
function normalize_strip_x(strip, [smooth_method, smooth_factor, check])
|
|
wave strip
|
|
variable smooth_method
|
|
variable smooth_factor
|
|
variable check
|
|
|
|
if (ParamIsDefault(smooth_method))
|
|
smooth_method = 4
|
|
endif
|
|
if (ParamIsDefault(smooth_factor))
|
|
switch(smooth_method)
|
|
case 4:
|
|
smooth_factor = 0.5
|
|
break
|
|
default:
|
|
smooth_factor = 2
|
|
endswitch
|
|
endif
|
|
if (ParamIsDefault(check))
|
|
check = 0
|
|
endif
|
|
|
|
// average over all scan positions
|
|
wave raw_dist = ad_profile_x(strip, -inf, inf, "")
|
|
|
|
// remove nans
|
|
extract /free /indx raw_dist, clean_index, numtype(raw_dist) == 0
|
|
duplicate /free raw_dist, dist, dist_x
|
|
redimension /n=(numpnts(clean_index)) dist, dist_x
|
|
dist = raw_dist[clean_index[p]]
|
|
dist_x = pnt2x(raw_dist, clean_index[p])
|
|
variable div = mean(dist)
|
|
dist /= div
|
|
|
|
if (check)
|
|
duplicate /o raw_dist, check_dist
|
|
check_dist = numtype(raw_dist) == 0 ? interp(x, dist_x, dist) : nan
|
|
endif
|
|
|
|
// smooth distribution function
|
|
switch(smooth_method)
|
|
case 1:
|
|
Smooth /B /E=3 smooth_factor, dist
|
|
break
|
|
case 2:
|
|
Smooth /E=3 smooth_factor, dist
|
|
break
|
|
case 3:
|
|
make /n=1 /d /free fit_params
|
|
fit_scienta_ang_transm(raw_dist, fit_params)
|
|
duplicate /free raw_dist, dist, dist_x
|
|
dist_x = x
|
|
dist = scienta_ang_transm(fit_params, x)
|
|
break
|
|
case 4:
|
|
loess /smth=(smooth_factor) srcWave=dist, factors={dist_x}
|
|
break
|
|
endswitch
|
|
|
|
if (check)
|
|
duplicate /o raw_dist, check_smoo
|
|
check_smoo = interp(x, dist_x, dist)
|
|
endif
|
|
|
|
// divide
|
|
if (check != 2)
|
|
strip /= interp(x, dist_x, dist)
|
|
endif
|
|
end
|
|
|
|
/// divide the strip by a sine function in phi (wobble correction).
|
|
///
|
|
/// the sine function is a curve fit to the intensity integrated over detector angle
|
|
/// with a period of 360°.
|
|
///
|
|
/// this normalization may be useful if the intensity varies with a 360° periodicity in the azimuthal angle,
|
|
/// e.g. due to misalignment of the surface normal and the azimuthal rotation axis of the manipulator (wobble).
|
|
/// note, however, that this function does not correct other effects of wobble such as angle shifts.
|
|
///
|
|
/// the strip is normalized in place, previous data is overwritten.
|
|
///
|
|
/// @param[in,out] strip 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan
|
|
/// @param[in] theta polar manipulator angle.
|
|
/// @param[in] phi azimuthal manipulator angle, arbitrary offset.
|
|
/// @param[in] theta_offset theta value corresponding to normal emission (default 0).
|
|
/// @param[in] theta_range maximum (offset corrected) theta to consider in the sine fit (default 10).
|
|
///
|
|
/// @param check enable output of intermediate results
|
|
/// @arg 0 (default) don't create additional waves
|
|
/// @arg 1 create check waves in the current folder
|
|
/// @arg 2 calculate check waves only, do not modify strip
|
|
///
|
|
/// @return if check waves are enabled, the following waves are created (overwritten if existing):
|
|
/// @arg check_dist average theta distribution
|
|
/// @arg check_smoo smoothed distribution used to normalize the strip
|
|
///
|
|
function normalize_strip_phi(strip, theta, phi, [theta_offset, theta_range, check])
|
|
wave strip
|
|
wave theta
|
|
wave phi
|
|
variable theta_offset
|
|
variable theta_range
|
|
variable check
|
|
|
|
if (ParamIsDefault(check))
|
|
check = 0
|
|
endif
|
|
if (ParamIsDefault(theta_offset))
|
|
theta_offset = 0
|
|
endif
|
|
if (ParamIsDefault(theta_range))
|
|
theta_offset = 10
|
|
endif
|
|
|
|
// average over analyser angles
|
|
duplicate /free strip, strip_copy
|
|
MatrixFilter NanZapMedian strip_copy
|
|
wave dist = ad_profile_y(strip_copy, -inf, inf, "")
|
|
|
|
// smooth distribution function
|
|
duplicate /free dist, dist_smoo
|
|
duplicate /free theta, theta_int
|
|
theta_int = theta - theta_offset
|
|
duplicate /free phi, phi_int
|
|
setscale /p x phi_int[0], phi_int[1] - phi_int[0], waveunits(phi, -1), dist, dist_smoo
|
|
|
|
extract /free /indx dist, red_idx, theta_int < theta_range
|
|
duplicate /free red_idx, red_dist, red_phi
|
|
red_dist = dist[red_idx]
|
|
red_phi = phi_int[red_idx]
|
|
|
|
variable wavg = mean(red_dist)
|
|
make /n=4 /d /free coef
|
|
coef[0] = {wavg, wavg/100, pi/180, 0}
|
|
CurveFit /q /h="0010" /g /w=2 sin, kwcWave=coef, red_dist /x=red_phi
|
|
dist_smoo = coef[0] + coef[1] * sin(coef[2] * phi_int[p] + coef[3])
|
|
|
|
// divide
|
|
if (check != 2)
|
|
strip = strip / dist_smoo[q] * coef[0]
|
|
endif
|
|
|
|
// check
|
|
if (check)
|
|
duplicate /o dist, check_dist
|
|
duplicate /o dist_smoo, check_smoo
|
|
setscale /p x dimoffset(dist,0), dimdelta(dist,0), waveunits(dist,0), check_dist, check_smoo
|
|
endif
|
|
end
|
|
|
|
/// divide the strip by the average polar distribution.
|
|
///
|
|
/// this is a simple way to remove the polar angle dependence.
|
|
/// the strip is normalized in place, previous data is overwritten.
|
|
/// @param[in,out] strip 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan
|
|
/// @param[in] theta polar manipulator angle, 0 = normal emission, 90 = grazing emission
|
|
/// @param[in] theta_offset
|
|
/// @param[in] smooth_method smoothing method
|
|
/// @arg 0 none
|
|
/// @arg 1 binomial (requires monotonic theta), see Igor's Smooth operation
|
|
/// @arg 2 boxcar (requires monotonic theta), see Igor's Smooth operation
|
|
/// @arg 3 polynomial fit per slice
|
|
/// @arg 4 (default) Loess, see Igor's Loess operation
|
|
///
|
|
/// caution: binomial and boxcar smoothing are not aware of theta.
|
|
/// this may give unpredictable results if theta is non-monotonic.
|
|
///
|
|
/// @param[in] smooth_factor smoothing parameter, depends on smooth_method
|
|
/// @arg binomial/boxcar: see Igor's Smooth operation
|
|
/// @arg loess: see Igor's Loess operation, 0 <= smooth_factor <= 1, default 0.5
|
|
/// @arg polynomial fit: polynomial degree, 1 = linear (default), 2 = quadratic
|
|
///
|
|
/// @param check enable output of intermediate results
|
|
/// @arg 0 (default) don't create additional waves
|
|
/// @arg 1 create check waves in the current folder
|
|
/// @arg 2 calculate check waves only, do not modify strip
|
|
///
|
|
/// @return if check waves are enabled, the following waves are created (overwritten if existing):
|
|
/// @arg check_dist average theta distribution
|
|
/// @arg check_smoo smoothed distribution used to normalize the strip
|
|
///
|
|
function normalize_strip_theta(strip, theta, [theta_offset, smooth_method, smooth_factor, check])
|
|
wave strip
|
|
wave theta
|
|
variable theta_offset
|
|
variable smooth_method
|
|
variable smooth_factor
|
|
variable check
|
|
|
|
if (ParamIsDefault(check))
|
|
check = 0
|
|
endif
|
|
if (ParamIsDefault(theta_offset))
|
|
theta_offset = 0
|
|
endif
|
|
if (ParamIsDefault(smooth_method))
|
|
smooth_method = 4
|
|
endif
|
|
if (ParamIsDefault(smooth_factor))
|
|
smooth_factor = 0.5
|
|
endif
|
|
|
|
// average over analyser angles
|
|
duplicate /free strip, strip_copy
|
|
MatrixFilter NanZapMedian strip_copy
|
|
wave dist = ad_profile_y(strip_copy, -inf, inf, "")
|
|
|
|
// smooth distribution function
|
|
duplicate /free dist, dist_smoo
|
|
duplicate /free theta, theta_int
|
|
theta_int = theta - theta_offset
|
|
setscale /p x theta_int[0], theta_int[1] - theta_int[0], waveunits(theta,-1), dist, dist_smoo
|
|
variable nx = dimsize(strip, 0)
|
|
variable ix
|
|
|
|
switch(smooth_method)
|
|
case 1:
|
|
Smooth /B /E=3 smooth_factor, dist_smoo
|
|
break
|
|
case 2:
|
|
Smooth /E=3 smooth_factor, dist_smoo
|
|
break
|
|
case 4:
|
|
loess /dest=dist_smoo /smth=(smooth_factor) srcWave=dist, factors={theta_int}
|
|
break
|
|
case 3:
|
|
for (ix = 0; ix < nx; ix += 1)
|
|
dist = strip[ix][p]
|
|
if (smooth_factor > 1)
|
|
CurveFit /nthr=0 /q /w=2 poly smooth_factor+1, dist /x=theta_int /d=dist_smoo
|
|
else
|
|
CurveFit /nthr=0 /q /w=2 line, dist /x=theta_int /d=dist_smoo
|
|
endif
|
|
strip[ix,ix][] /= dist_smoo[q]
|
|
endfor
|
|
dist_smoo = 1
|
|
break
|
|
endswitch
|
|
|
|
// divide
|
|
if (check != 2)
|
|
strip /= dist_smoo[q]
|
|
endif
|
|
|
|
// check
|
|
if (check)
|
|
duplicate /o dist, check_dist
|
|
duplicate /o dist_smoo, check_smoo
|
|
setscale /p x dimoffset(dist,0), dimdelta(dist,0), waveunits(dist,0), check_dist, check_smoo
|
|
endif
|
|
end
|
|
|
|
/// divide the strip by a smooth polar-azimuthal distribution.
|
|
///
|
|
/// this is a simple way to remove the polar angle dependence.
|
|
/// in contrast to @ref normalize_strip_theta this function also removes a smooth variation over azimuthal angles.
|
|
///
|
|
/// the strip is normalized in place, previous data is overwritten.
|
|
///
|
|
/// @warning experimental. this function is under development.
|
|
///
|
|
/// @param[in,out] strip 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan
|
|
/// @param[in] theta polar manipulator angle, 0 = normal emission, 90 = grazing emission
|
|
/// @param[in] phi azimuthal manipulator angle.
|
|
/// @param[in] theta_offset
|
|
/// @param[in] smooth_method smoothing method
|
|
/// @arg 0 none
|
|
/// @arg 4 (default) Loess, see Igor's Loess operation
|
|
///
|
|
/// @param[in] smooth_factor smoothing parameter, depends on smooth_method
|
|
/// @arg loess: see Igor's Loess operation, 0 <= smooth_factor <= 1, default 0.5
|
|
///
|
|
/// @param check enable output of intermediate results
|
|
/// @arg 0 (default) don't create additional waves
|
|
/// @arg 1 create check waves in the current folder
|
|
/// @arg 2 calculate check waves only, do not modify strip
|
|
///
|
|
/// @return if check waves are enabled, the following waves are created (overwritten if existing):
|
|
/// @arg check_dist average theta distribution
|
|
/// @arg check_smoo smoothed distribution used to normalize the strip
|
|
///
|
|
function normalize_strip_thetaphi(strip, theta, phi, [theta_offset, smooth_method, smooth_factor, check])
|
|
wave strip
|
|
wave theta
|
|
wave phi
|
|
variable theta_offset
|
|
variable smooth_method
|
|
variable smooth_factor
|
|
variable check
|
|
|
|
if (ParamIsDefault(check))
|
|
check = 0
|
|
endif
|
|
if (ParamIsDefault(theta_offset))
|
|
theta_offset = 0
|
|
endif
|
|
if (ParamIsDefault(smooth_method))
|
|
smooth_method = 4
|
|
endif
|
|
if (ParamIsDefault(smooth_factor))
|
|
smooth_factor = 0.5
|
|
endif
|
|
|
|
// average over analyser angles
|
|
duplicate /free strip, strip_copy
|
|
MatrixFilter NanZapMedian strip_copy
|
|
wave dist = ad_profile_y(strip_copy, -inf, inf, "")
|
|
|
|
// smooth distribution function
|
|
duplicate /free dist, dist_smoo
|
|
duplicate /free theta, theta_int
|
|
theta_int = theta - theta_offset
|
|
setscale /p x theta_int[0], theta_int[1] - theta_int[0], waveunits(theta,-1), dist, dist_smoo
|
|
variable nx = dimsize(strip, 0)
|
|
variable ix
|
|
|
|
switch(smooth_method)
|
|
case 4:
|
|
loess /dest=dist_smoo /smth=(smooth_factor) srcWave=dist, factors={theta_int, phi}
|
|
break
|
|
default:
|
|
abort "smooth method not supported"
|
|
endswitch
|
|
|
|
// divide
|
|
if (check != 2)
|
|
strip /= dist_smoo[q]
|
|
endif
|
|
|
|
// check
|
|
if (check)
|
|
duplicate /o dist, check_dist
|
|
duplicate /o dist_smoo, check_smoo
|
|
setscale /p x dimoffset(dist,0), dimdelta(dist,0), waveunits(dist,0), check_dist, check_smoo
|
|
endif
|
|
end
|
|
|
|
/// divide the strip piecewise by a smooth polar distribution.
|
|
///
|
|
/// @warning experimental. this function is under development.
|
|
///
|
|
///
|
|
function normalize_strip_theta_scans(strip, theta, [theta_offset, smooth_method, smooth_factor, check])
|
|
wave strip
|
|
wave theta
|
|
variable theta_offset
|
|
variable smooth_method
|
|
variable smooth_factor
|
|
variable check
|
|
|
|
if (ParamIsDefault(check))
|
|
check = 0
|
|
endif
|
|
if (ParamIsDefault(theta_offset))
|
|
theta_offset = 0
|
|
endif
|
|
if (ParamIsDefault(smooth_method))
|
|
smooth_method = 4
|
|
endif
|
|
if (ParamIsDefault(smooth_factor))
|
|
smooth_factor = 0.5
|
|
endif
|
|
|
|
// average over analyser angles
|
|
duplicate /free strip, strip_copy
|
|
MatrixFilter NanZapMedian strip_copy
|
|
wave dist = ad_profile_y(strip_copy, -inf, inf, "")
|
|
|
|
// smooth distribution function
|
|
duplicate /free dist, dist_smoo
|
|
duplicate /free theta, theta_int
|
|
theta_int = theta - theta_offset
|
|
setscale /p x theta_int[0], theta_int[1] - theta_int[0], waveunits(theta,-1), dist, dist_smoo
|
|
|
|
// analyse scanning scheme
|
|
duplicate /free theta_int, d1_theta, d2_theta
|
|
Differentiate /METH=2 theta_int /D=d1_theta
|
|
Differentiate /METH=2 d1_theta /D=d2_theta
|
|
d2_theta = abs(d2_theta)
|
|
make /free w_levels
|
|
FindLevels /edge=1 /p /q /d=w_levels d2_theta, 0.1
|
|
if (v_flag != 1)
|
|
abort "unrecognized scanning scheme"
|
|
endif
|
|
w_levels = ceil(w_levels)
|
|
InsertPoints 0, 1, w_levels
|
|
w_levels[0] = 0
|
|
InsertPoints numpnts(w_levels), 1, w_levels
|
|
w_levels[numpnts(w_levels)-1] = numpnts(theta_int)
|
|
|
|
variable n_scans = numpnts(w_levels) - 1
|
|
variable i_scan
|
|
variable p1, p2
|
|
for (i_scan = 0; i_scan < n_scans; i_scan += 1)
|
|
p1 = w_levels[i_scan]
|
|
p2 = w_levels[i_scan+1] - 1
|
|
duplicate /free /r=[p1, p2] dist, dist_piece, smooth_piece
|
|
duplicate /free /r=[p1, p2] theta_int, theta_piece
|
|
switch(smooth_method)
|
|
case 4:
|
|
loess /dest=smooth_piece /smth=(smooth_factor) srcWave=dist_piece, factors={theta_piece}
|
|
break
|
|
default:
|
|
abort "smooth method not supported"
|
|
endswitch
|
|
dist_smoo[p1, p2] = smooth_piece[p - p1]
|
|
endfor
|
|
|
|
// divide
|
|
if (check != 2)
|
|
strip /= dist_smoo[q]
|
|
endif
|
|
|
|
// check
|
|
if (check)
|
|
duplicate /o dist, check_dist
|
|
duplicate /o dist_smoo, check_smoo
|
|
setscale /p x dimoffset(dist,0), dimdelta(dist,0), waveunits(dist,0), check_dist, check_smoo
|
|
endif
|
|
end
|
|
|
|
/// divide the strip by a two-dimensional normalization function.
|
|
///
|
|
/// @warning experimental. this function is under development.
|
|
///
|
|
/// @param check enable output of intermediate results
|
|
/// @arg 0 (default) don't create additional waves
|
|
/// @arg 1 create check waves in the current folder
|
|
/// @arg 2 calculate check waves only, do not modify strip
|
|
///
|
|
/// @return if check waves are enabled, the following waves are created (overwritten if existing):
|
|
/// @arg check_dist average theta distribution
|
|
/// @arg check_smoo smoothed distribution used to normalize the strip
|
|
///
|
|
function normalize_strip_2d(strip, theta, [theta_offset, smooth_method, smooth_factor, check])
|
|
wave strip
|
|
wave theta
|
|
variable theta_offset
|
|
variable smooth_method
|
|
variable smooth_factor
|
|
variable check
|
|
|
|
if (ParamIsDefault(check))
|
|
check = 0
|
|
endif
|
|
if (ParamIsDefault(theta_offset))
|
|
theta_offset = 0
|
|
endif
|
|
if (ParamIsDefault(smooth_method))
|
|
smooth_method = 4
|
|
endif
|
|
if (ParamIsDefault(smooth_factor))
|
|
smooth_factor = 0.5
|
|
endif
|
|
|
|
variable nx = dimsize(strip, 0)
|
|
variable ny = dimsize(strip, 1)
|
|
|
|
duplicate /free strip, dist, alpha_int, theta_int
|
|
MatrixFilter NanZapMedian dist
|
|
theta_int = theta[q] - theta_offset
|
|
alpha_int = dimoffset(strip, 0) + p * dimdelta(strip, 0)
|
|
redimension /n=(nx * ny) dist, alpha_int, theta_int
|
|
|
|
switch(smooth_method)
|
|
case 4:
|
|
loess /dest=dist_smoo /smth=(smooth_factor) srcWave=dist, factors={alpha_int, theta_int}
|
|
redimension /n=(nx, ny) dist_smoo
|
|
break
|
|
default:
|
|
Abort "undefined smooth method"
|
|
break
|
|
endswitch
|
|
|
|
// divide
|
|
if (check != 2)
|
|
strip /= dist_smoo
|
|
endif
|
|
|
|
// check
|
|
if (check)
|
|
//duplicate /o dist, check_dist
|
|
duplicate /o dist_smoo, check_smoo
|
|
endif
|
|
end
|
|
|
|
/// crop a strip at the sides.
|
|
///
|
|
/// the strip is cropped in place, data outside the region of interest is lost.
|
|
///
|
|
/// @param[in,out] strip 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan
|
|
/// @param[in] xlo lowest analyser angle to keep (will be rounded to nearest existing point)
|
|
/// @param[in] xhi highest analyser angle to keep (will be rounded to nearest existing point)
|
|
///
|
|
/// @remark cropping should be done after smoothing and normalization operations to reduce artefacts.
|
|
function crop_strip(strip, xlo, xhi)
|
|
wave strip
|
|
variable xlo
|
|
variable xhi
|
|
|
|
variable plo = round((xlo - dimoffset(strip, 0)) / dimdelta(strip, 0))
|
|
variable phi = round((xhi - dimoffset(strip, 0)) / dimdelta(strip, 0))
|
|
xlo = plo * dimdelta(strip, 0) + dimoffset(strip, 0)
|
|
xhi = phi * dimdelta(strip, 0) + dimoffset(strip, 0)
|
|
variable nx = phi - plo + 1
|
|
variable ny = dimsize(strip, 1)
|
|
|
|
duplicate /free strip, strip_copy
|
|
redimension /n=(nx,ny) strip
|
|
strip = strip_copy[p + plo][q]
|
|
setscale /i x xlo, xhi, waveunits(strip, 0), strip
|
|
end
|
|
|
|
/// crop a strip in theta.
|
|
///
|
|
/// the strip is cropped in place, data outside the region of interest is lost.
|
|
///
|
|
/// @param[in,out] strip 2D data, X-axis = analyser angle, Y-axis = arbitrary manipulator scan
|
|
/// @param[in] ylo lowest polar angle to keep (will be rounded to nearest existing point)
|
|
/// @param[in] yhi highest polar angle to keep (will be rounded to nearest existing point)
|
|
/// @param[in,out] theta polar angle along the Y dimension of strip.
|
|
/// this wave is modified: cropped rows are deleted.
|
|
/// @param[in,out] tilt tilt angle along the Y dimension of strip.
|
|
/// this wave is modified: cropped rows are deleted.
|
|
/// @param[in,out] phi azimuthal angle along the Y dimension of strip.
|
|
/// this wave is modified: cropped rows are deleted.
|
|
///
|
|
function crop_strip_theta(strip, theta_lo, theta_hi, theta, tilt, phi)
|
|
wave strip
|
|
variable theta_lo
|
|
variable theta_hi
|
|
wave theta
|
|
wave tilt
|
|
wave phi
|
|
|
|
extract /indx /free theta, idx, (theta >= theta_lo) && (theta <= theta_hi)
|
|
variable nx = dimsize(strip, 0)
|
|
variable ny = numpnts(idx)
|
|
|
|
theta[0, ny-1] = theta[idx]
|
|
tilt[0, ny-1] = tilt[idx]
|
|
phi[0, ny-1] = phi[idx]
|
|
redimension /n=(ny) theta, tilt, phi
|
|
|
|
duplicate /free strip, strip_copy
|
|
redimension /n=(nx,ny) strip
|
|
strip = strip_copy[p][idx[q]]
|
|
end
|
|
|
|
/// create a pizza plot from a measured (energy-integrated) data strip
|
|
///
|
|
/// accepts angle-scan data as returned by adh5_load_reduced(),
|
|
/// maps them onto a hemispherical scan grid,
|
|
/// and displays a polar graph.
|
|
///
|
|
/// @param data 2D intensity wave, see requirements above
|
|
/// @arg X-axis analyser angle
|
|
/// @arg Y-axis manipulator scan.
|
|
/// no specific ordering required.
|
|
/// manipulator angle waves (ManipulatorTheta, ManipulatorTilt, ManipulatorPhi)
|
|
/// must be in the subfolder _attr_ below the data wave.
|
|
///
|
|
/// @param nickname nick name for output data
|
|
/// @arg in default mode, this will become the name of a child folder containing the output.
|
|
/// @arg in XPDplot mode, this will become a prefix of the generated data in the root folder.
|
|
///
|
|
/// @param theta_offset manipulator theta angle corresponding to normal emission.
|
|
/// the offset is subtracted from the ManipulatorTheta wave before processing.
|
|
///
|
|
/// @param tilt_offset manipulator tilt angle corresponding to normal emission
|
|
/// the offset is subtracted from the ManipulatorTilt wave before processing.
|
|
///
|
|
/// @param phi_offset manipulator phi angle corresponding to phi_result = 0
|
|
/// the offset is subtracted from the ManipulatorPhi wave before processing.
|
|
///
|
|
/// @param npolar number of polar angles, determines polar and azimuthal step size.
|
|
/// default = 91 (1 degree steps)
|
|
///
|
|
/// @param folding rotational averaging, default = 1
|
|
///
|
|
/// @param nograph display a new graph window?
|
|
/// @arg 0 (default) display a new polar graph
|
|
/// @arg 1 don't display a new graph
|
|
///
|
|
/// @param xpdplot XPDplot compatibility
|
|
/// @arg 0 (default) create waves in child folder $nickname
|
|
/// @arg 1 create waves in root folder (compatible with XPDplot)
|
|
///
|
|
/// @attention if you modify the structure of the data wave, e.g. delete some angles,
|
|
/// this function cannot be used because the manipulator settings do not correspond to the original manipulator waves!
|
|
/// instead, create your own manipulator waves and use pizza_service_2().
|
|
function pizza_service(data, nickname, theta_offset, tilt_offset, phi_offset, [npolar, nograph, folding, xpdplot])
|
|
wave data
|
|
string nickname
|
|
variable theta_offset
|
|
variable tilt_offset
|
|
variable phi_offset
|
|
variable npolar
|
|
variable nograph
|
|
variable folding
|
|
variable xpdplot
|
|
|
|
if (ParamIsDefault(npolar))
|
|
npolar = 91
|
|
endif
|
|
if (ParamIsDefault(nograph))
|
|
nograph = 0
|
|
endif
|
|
if (ParamIsDefault(folding))
|
|
folding = 1
|
|
endif
|
|
if (ParamIsDefault(xpdplot))
|
|
xpdplot = 0
|
|
endif
|
|
|
|
// sort out data folder structure
|
|
dfref saveDF = GetDataFolderDFR()
|
|
dfref dataDF = GetWavesDataFolderDFR(data)
|
|
setdatafolder dataDF
|
|
if (DataFolderExists(":attr"))
|
|
setdatafolder :attr
|
|
endif
|
|
dfref attrDF = GetDataFolderDFR()
|
|
|
|
wave /sdfr=attrDF ManipulatorTheta
|
|
wave /sdfr=attrDF ManipulatorTilt
|
|
wave /sdfr=attrDF ManipulatorPhi
|
|
|
|
if ((dimsize(ManipulatorTheta, 0) != dimsize(data, 1)) || (dimsize(ManipulatorTilt, 0) != dimsize(data, 1)) || (dimsize(ManipulatorPhi, 0) != dimsize(data, 1)))
|
|
Abort "Warning: The dimension size of the manipulator waves does not match the Y dimension of the data wave!\rIf you restructured the data wave, please use pizza_service_2 with properly scaled manipulator waves."
|
|
endif
|
|
|
|
duplicate /free ManipulatorTheta, m_theta
|
|
duplicate /free ManipulatorTilt, m_tilt
|
|
duplicate /free ManipulatorPhi, m_phi
|
|
|
|
m_theta -= theta_offset
|
|
m_tilt -= tilt_offset
|
|
m_phi -= phi_offset
|
|
|
|
pizza_service_2(data, nickname, m_theta, m_tilt, m_phi, npolar=npolar, nograph=nograph, folding=folding, xpdplot=xpdplot)
|
|
|
|
setdatafolder saveDF
|
|
end
|
|
|
|
/// create a pizza plot from a measured (energy-integrated) data strip
|
|
///
|
|
/// accepts angle-scan data as returned by adh5_load_reduced(),
|
|
/// maps them onto a hemispherical scan grid,
|
|
/// and displays a polar graph.
|
|
///
|
|
/// the behaviour of this function is the same as pizza_service()
|
|
/// except that the manipulator waves are specified explicitly.
|
|
///
|
|
/// @param data 2D intensity wave, see requirements above
|
|
/// @arg X-axis analyser angle
|
|
/// @arg Y-axis manipulator scan.
|
|
/// no specific ordering required.
|
|
/// manipulator angle waves (ManipulatorTheta, ManipulatorTilt, ManipulatorPhi)
|
|
/// must be in the subfolder _attr_ below the data wave.
|
|
///
|
|
/// @param nickname nick name for output data
|
|
/// @arg in default mode, this will become the name of a child folder containing the output.
|
|
/// @arg in XPDplot mode, this will become a prefix of the generated data in the root folder.
|
|
///
|
|
/// @param m_theta manipulator theta angles, 0 = normal emission. size = dimsize(data, 1)
|
|
///
|
|
/// @param m_tilt manipulator tilt angles, 0 = normal emission. size = dimsize(data, 1)
|
|
///
|
|
/// @param m_phi manipulator phi angles, 0 = azimuthal origin. size = dimsize(data, 1)
|
|
///
|
|
/// @param npolar number of polar angles, determines polar and azimuthal step size.
|
|
/// default = 91 (1 degree steps)
|
|
///
|
|
/// @param folding rotational averaging, default = 1
|
|
///
|
|
/// @param nograph display a new graph window?
|
|
/// @arg 0 (default) display a new polar graph
|
|
/// @arg 1 don't display a new graph
|
|
///
|
|
/// @param xpdplot XPDplot compatibility
|
|
/// @arg 0 (default) create waves in child folder $nickname
|
|
/// @arg 1 create waves in root folder (compatible with XPDplot)
|
|
///
|
|
function pizza_service_2(data, nickname, m_theta, m_tilt, m_phi, [npolar, nograph, folding, xpdplot])
|
|
wave data
|
|
string nickname
|
|
wave m_theta
|
|
wave m_tilt
|
|
wave m_phi
|
|
variable npolar
|
|
variable nograph
|
|
variable folding
|
|
variable xpdplot
|
|
|
|
if (ParamIsDefault(npolar))
|
|
npolar = 91
|
|
endif
|
|
if (ParamIsDefault(nograph))
|
|
nograph = 0
|
|
endif
|
|
if (ParamIsDefault(folding))
|
|
folding = 1
|
|
endif
|
|
if (ParamIsDefault(xpdplot))
|
|
xpdplot = 0
|
|
endif
|
|
|
|
if ((dimsize(m_theta, 0) != dimsize(data, 1)) || (dimsize(m_tilt, 0) != dimsize(data, 1)) || (dimsize(m_phi, 0) != dimsize(data, 1)))
|
|
Abort "Warning: The dimension size of the manipulator waves does not match the Y dimension of the data wave!"
|
|
endif
|
|
|
|
string graphname = "graph_" + nickname
|
|
string outprefix = nickname
|
|
|
|
// sort out data folder structure
|
|
dfref saveDF = GetDataFolderDFR()
|
|
dfref dataDF = GetWavesDataFolderDFR(data)
|
|
setdatafolder dataDF
|
|
|
|
if (xpdplot)
|
|
setdatafolder root:
|
|
outprefix = nickname
|
|
else
|
|
setdatafolder dataDF
|
|
newdatafolder /s/o $nickname
|
|
outprefix = ""
|
|
endif
|
|
dfref destDF = GetDataFolderDFR()
|
|
|
|
// performance monitoring
|
|
variable timerRefNum
|
|
variable /g pol_perf_secs
|
|
timerRefNum = startMSTimer
|
|
|
|
duplicate /free m_tilt, corr_tilt
|
|
duplicate /free m_phi, corr_phi
|
|
corr_tilt = -m_tilt // checked 140702
|
|
corr_phi = m_phi // checked 140702
|
|
|
|
make /n=1/d/free d_polar, d_azi
|
|
|
|
convert_angles_ttpd2polar(m_theta, corr_tilt, corr_phi, data, d_polar, d_azi)
|
|
d_azi += 180 // changed 151030 (v1.6)
|
|
make_hemi_grid(npolar, outprefix, xpdplot=xpdplot)
|
|
variable ifold
|
|
for (ifold = 0; ifold < folding; ifold += 1)
|
|
d_azi = d_azi >= 360 ? d_azi - 360 : d_azi
|
|
hemi_add_anglescan(outprefix, data, d_polar, d_azi)
|
|
d_azi += 360 / folding
|
|
endfor
|
|
|
|
// normalize folding
|
|
if (strlen(outprefix))
|
|
string s_prefix = outprefix + "_"
|
|
string s_int = s_prefix + "i"
|
|
else
|
|
s_prefix = ""
|
|
s_int = "values"
|
|
endif
|
|
if (folding > 1)
|
|
wave values = $s_int
|
|
values /= folding
|
|
endif
|
|
|
|
if (!nograph)
|
|
display_hemi_scan(outprefix, graphname = graphname)
|
|
endif
|
|
|
|
if (timerRefNum >= 0)
|
|
pol_perf_secs = stopMSTimer(timerRefNum) / 1e6
|
|
endif
|
|
|
|
setdatafolder saveDF
|
|
end
|
|
|
|
/// calculate and display the line seen by the analyser for a specific emission angle
|
|
///
|
|
/// this can be used to compare to an hemispherical plot and check the manipulator angle.
|
|
///
|
|
/// @param theta manipulator theta angle
|
|
/// @param tilt manipulator tilt angle
|
|
/// @param phi manipulator phi angle
|
|
///
|
|
/// @param theta_offset manipulator theta angle corresponding to normal emission
|
|
/// @param tilt_offset manipulator tilt angle corresponding to normal emission
|
|
/// @param phi_offset manipulator phi angle corresponding to phi_result = 0
|
|
///
|
|
/// @param npolar number of polar angles, determines polar and azimuthal step size.
|
|
/// default = 91 (1 degree steps)
|
|
///
|
|
/// @param nograph display a new graph window?
|
|
/// @arg 0 (default) display a new polar graph
|
|
/// @arg 1 don't display a new graph
|
|
///
|
|
/// @param xpdplot XPDplot compatibility
|
|
/// @arg 0 (default) create waves in child folder $nickname
|
|
/// @arg 1 create waves in root folder (compatible with XPDplot)
|
|
///
|
|
/// @remark the function creates angle scan data under the nickname _analyser_.
|
|
///
|
|
function show_analyser_line(theta, tilt, phi, theta_offset, tilt_offset, phi_offset, [npolar, nograph, xpdplot])
|
|
variable theta
|
|
variable tilt
|
|
variable phi
|
|
variable theta_offset
|
|
variable tilt_offset
|
|
variable phi_offset
|
|
variable npolar
|
|
variable nograph
|
|
variable xpdplot
|
|
|
|
string nickname = "analyser"
|
|
|
|
if (ParamIsDefault(npolar))
|
|
npolar = 91
|
|
endif
|
|
if (ParamIsDefault(nograph))
|
|
nograph = 0
|
|
endif
|
|
if (ParamIsDefault(xpdplot))
|
|
xpdplot = 0
|
|
endif
|
|
string graphname = "graph_" + nickname
|
|
string outprefix = nickname
|
|
|
|
// sort out data folder structure
|
|
dfref saveDF = GetDataFolderDFR()
|
|
dfref dataDF = saveDF
|
|
if (xpdplot)
|
|
setdatafolder root:
|
|
outprefix = nickname
|
|
else
|
|
setdatafolder dataDF
|
|
newdatafolder /s/o $nickname
|
|
outprefix = ""
|
|
endif
|
|
dfref destDF = GetDataFolderDFR()
|
|
|
|
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
|
|
|
|
make /n=60 /free data
|
|
setscale /i x -30, 30, data
|
|
data = x
|
|
make /n=1/d/free d_polar, d_azi
|
|
|
|
convert_angles_ttpa2polar(m_theta, m_tilt, m_phi, data, d_polar, d_azi)
|
|
d_azi += 180 // changed 151030 (v1.6)
|
|
d_azi = d_azi >= 360 ? d_azi - 360 : d_azi
|
|
make_hemi_grid(npolar, outprefix, xpdplot=xpdplot)
|
|
hemi_add_anglescan(outprefix, data, d_polar, d_azi)
|
|
|
|
if (!nograph)
|
|
display_hemi_scan(outprefix, graphname = graphname)
|
|
endif
|
|
|
|
setdatafolder saveDF
|
|
end
|
|
|
|
/// convert angles from TTPA (theta-tilt-phi-analyser) scheme to polar coordinates.
|
|
///
|
|
/// similar to convert_angles_ttpa2polar()
|
|
/// but reads the analyser angles from the X scale of data
|
|
|
|
function convert_angles_ttpd2polar(theta, tilt, phi, data, polar, azi)
|
|
wave theta, tilt, phi // see convert_angles_ttpa2polar
|
|
wave data // in, 1D or 2D
|
|
// X-scale must be set to analyser angle scale
|
|
wave polar, azi // see convert_angles_ttpa2polar
|
|
|
|
make /n=(dimsize(data, 0)) /d /free ana
|
|
setscale /p x dimoffset(data, 0), dimdelta(data, 0), waveunits(data, 0), ana
|
|
ana = x
|
|
convert_angles_ttpa2polar(theta, tilt, phi, ana, polar, azi)
|
|
end
|
|
|
|
/// convert angles from TTPA (theta-tilt-phi-analyser) scheme to polar coordinates.
|
|
///
|
|
/// the angles are in the manipulator coordinate system.
|
|
///
|
|
/// @param[in] theta offset-corrected theta angle, normal emission = 0, grazing emission = 90.
|
|
/// one dimensional wave.
|
|
///
|
|
/// @param[in] tilt offset-corrected tilt angle, normal emission = 0
|
|
/// same dimension size and scale as theta
|
|
///
|
|
/// @param[in] phi phi angle, range -360 < phi < +360
|
|
/// offset correction is optional as long as the angles lie in the accepted range.
|
|
/// same dimension size and scale as theta
|
|
///
|
|
/// @param[in] analyser analyser angle scale corresponding to the slices scale of Scienta.
|
|
/// one dimensional wave.
|
|
/// this values are constant regardless of manipulator angle.
|
|
///
|
|
/// @param[out] polar wave to receive the polar coordinates.
|
|
///
|
|
/// @param[out] azi wave to receive the azimuthal coordinates.
|
|
///
|
|
/// for the output parameters polar and azi, you need to pass in existing numeric waves.
|
|
/// dimension size does not matter, the waves are redimensioned by the function
|
|
/// so that they have the same dimensions as the intensity data set.
|
|
/// X dimension = analyser scale, Y dimension = manipulator scan.
|
|
///
|
|
function convert_angles_ttpa2polar(theta, tilt, phi, analyser, polar, azi)
|
|
wave theta
|
|
wave tilt
|
|
wave phi
|
|
wave analyser
|
|
wave polar, azi
|
|
|
|
variable nn = numpnts(theta)
|
|
variable na = numpnts(analyser)
|
|
redimension /n=(na, nn) polar, azi
|
|
|
|
variable radius = 1 // don't need to specify - everything is scalable
|
|
|
|
// step 1: calculate cartesian detection vectors at normal emission
|
|
// this is simply a polar-cartesian mapping, independent of the manipulator
|
|
// phi=0 is in the polar rotation plane
|
|
make /n=(3,na) /d /free w_orig_polar, w_orig_cart, w_rot_cart, w_rot_polar
|
|
w_orig_polar[0][] = radius
|
|
w_orig_polar[1][] = analyser[q]
|
|
w_orig_polar[2][] = 0
|
|
polar2cart_wave(w_orig_polar, w_orig_cart)
|
|
// if the angle-dispersive axis was horizontal, we'd need to rotate the detector
|
|
//rotate_z_wave(w_orig_cart, 90)
|
|
|
|
variable ii
|
|
for (ii = 0; ii < nn; ii += 1)
|
|
// step 2: rotate the detection vectors according to the manipulator angles
|
|
// the order of rotations is important because we rotate about fixed axes
|
|
// y-axis = tilt rotation axis
|
|
// x-axis = polar rotation axis
|
|
// z-axis = normal emission = azimuthal rotation axis
|
|
w_rot_cart = w_orig_cart
|
|
rotate_y_wave(w_rot_cart, -tilt[ii])
|
|
rotate_x_wave(w_rot_cart, -theta[ii])
|
|
rotate_z_wave(w_rot_cart, -phi[ii] - 90)
|
|
// map the vectors back to the sample coordinate system
|
|
cart2polar_wave(w_rot_cart, w_rot_polar)
|
|
// copy to output
|
|
polar[][ii] = w_rot_polar[1][p]
|
|
azi[][ii] = w_rot_polar[2][p]
|
|
endfor
|
|
end
|
|
|
|
static function line_average(source, dest)
|
|
// is this function used?
|
|
wave source
|
|
wave dest
|
|
|
|
variable ii
|
|
variable nn = dimsize(source, 1)
|
|
make /n=(dimsize(source, 0))/d/free line
|
|
for (ii = 0; ii < nn; ii += 1)
|
|
line = source[p][ii]
|
|
wavestats /q line
|
|
dest[][ii] = line[p] / v_max
|
|
endfor
|
|
end
|
|
|
|
/// calculate the number of phis for a given theta
|
|
///
|
|
/// adapted from XPDplot 8.03
|
|
static function calc_nth(Theta_st, Theta_in, th, Phi_ran, Phi_ref, Holomode)
|
|
Variable Theta_st, Theta_in, th, Phi_ran, Phi_ref
|
|
String Holomode
|
|
Variable The_step
|
|
Variable deg2rad=0.01745329
|
|
|
|
if ( cmpstr(Holomode, "Stereographic") == 0)
|
|
The_step =trunc( Phi_ran*sin(th*deg2rad)*Phi_ref/Theta_st )
|
|
if(th==90)
|
|
The_step =trunc( Phi_ran*sin(th*pi/180)*Phi_ref/Theta_st )
|
|
endif
|
|
else
|
|
if (cmpstr(Holomode, "Parallel") == 0)
|
|
The_step=trunc( Phi_ran*tan(th*deg2rad)*Phi_ref/Theta_st )
|
|
else
|
|
if ( cmpstr(Holomode, "h") == 0)
|
|
The_step=trunc( th/Theta_in*Phi_ran/Theta_st )
|
|
else
|
|
//altro
|
|
endif
|
|
endif
|
|
endif
|
|
|
|
return(The_step)
|
|
end
|
|
|
|
/// calculate delta-phi for a given theta
|
|
///
|
|
/// adapted from XPDplot 8.03
|
|
static function calc_phi_step(Theta_in, th, Theta_st, Phi_ran, Phi_ref, Holomode)
|
|
Variable Theta_in, th, Theta_st, Phi_ran, Phi_ref
|
|
String Holomode
|
|
|
|
Variable Phi_st
|
|
Variable deg2rad=0.01745329
|
|
|
|
if ( cmpstr(Holomode, "Stereographic") == 0 )
|
|
if ((th < 0.5) || (trunc(Phi_ran*sin(th*deg2rad)*Phi_ref/Theta_st) == 0))
|
|
Phi_st=0.0
|
|
else
|
|
Phi_st=Phi_ran/(trunc(Phi_ran*sin(th*deg2rad)*Phi_ref/Theta_st))
|
|
endif
|
|
if(th==90)
|
|
Phi_st=2.0
|
|
endif
|
|
endif
|
|
|
|
if ( cmpstr(Holomode, "Parallel") == 0 )
|
|
if((th < 0.5) || (trunc(Phi_ran*tan(th*deg2rad)*Phi_ref/Theta_st) == 0))
|
|
Phi_st=0.0
|
|
else
|
|
Phi_st=Phi_ran/(trunc(Phi_ran*tan(th*deg2rad)*Phi_ref/Theta_st))
|
|
endif
|
|
endif
|
|
|
|
if ( cmpstr(Holomode, "h") == 0 )
|
|
if((th < 0.5) || (trunc(Phi_ran*sin(th*deg2rad)*Phi_ref/Theta_st) == 0))
|
|
Phi_st=0.0
|
|
else
|
|
Phi_st=Phi_ran/trunc(th/Theta_in*Phi_ran/Theta_st)
|
|
endif
|
|
endif
|
|
|
|
if (Phi_st==0)
|
|
Phi_st=360
|
|
endif
|
|
|
|
return(Phi_st)
|
|
end
|
|
|
|
/// calculate delta-theta for a given theta
|
|
///
|
|
/// adapted from XPDplot 8.03
|
|
static function Calc_The_step(th, Theta_st, Holomode)
|
|
String Holomode
|
|
Variable th, Theta_st
|
|
|
|
Variable deg2rad=0.01745329, dt_loc,The_step
|
|
|
|
if ( (cmpstr(Holomode, "Stereographic")) ==0 )
|
|
The_step=Theta_st
|
|
endif
|
|
|
|
if ( (cmpstr(Holomode, "h")) ==0 )
|
|
The_step=Theta_st
|
|
endif
|
|
|
|
if ( cmpstr(Holomode, "Parallel") == 0 )
|
|
if(th < 89.5)
|
|
dt_loc = Theta_st/cos(th*deg2rad)
|
|
if(dt_loc > 10)
|
|
dt_loc=10
|
|
endif
|
|
The_step=dt_loc
|
|
else
|
|
The_step=10
|
|
endif
|
|
endif
|
|
return(The_step)
|
|
end
|
|
|
|
/// calculate the number of thetas for a pattern
|
|
///
|
|
/// adapted from XPDplot 8.03
|
|
static function CalcN_Theta(HoloMode,Theta_in,Theta_ran,Theta_st)
|
|
String HoloMode
|
|
Variable Theta_in,Theta_ran,Theta_st
|
|
Variable n_theta, aux, aux1,ii
|
|
|
|
aux = Theta_in
|
|
aux1= Theta_in - Theta_ran
|
|
ii = 0
|
|
do
|
|
aux = aux - Calc_The_step(aux, Theta_st, HoloMode)
|
|
if(aux<=Theta_in-Theta_ran)
|
|
aux=Theta_in-Theta_ran
|
|
endif
|
|
ii = ii+1
|
|
while((aux>aux1)%&(Theta_in-aux<=Theta_ran)) //
|
|
n_theta=ii+1
|
|
Return(n_theta)
|
|
end
|
|
|
|
/// create a hemispherical, constant solid angle grid
|
|
///
|
|
/// all necessary waves are created in the current data folder
|
|
/// with step size 90 / (npol - 1)
|
|
///
|
|
/// adapted from XPDplot 8.03
|
|
///
|
|
/// @param npol number of polar angles, determines polar and azimuthal step size.
|
|
/// recommended 91 for 1-degree steps.
|
|
///
|
|
/// @param nickname name prefix for waves.
|
|
/// nick name must be unique in the current data folder.
|
|
/// otherwise existing waves get overwritten.
|
|
/// may be empty.
|
|
///
|
|
/// @param xpdplot XPDplot compatibility
|
|
/// @arg 0 (default) create the data structures required by this module
|
|
/// @arg 1 create additional waves and notebook required by XPDplot
|
|
///
|
|
function make_hemi_grid(npol, nickname, [xpdplot])
|
|
variable npol
|
|
string nickname
|
|
variable xpdplot
|
|
|
|
if (ParamIsDefault(xpdplot))
|
|
xpdplot = 0
|
|
endif
|
|
|
|
string HoloMode = "h"
|
|
variable Theta_in = 90
|
|
variable Theta_ran = 90
|
|
variable Theta_st = 90 / (npol - 1)
|
|
variable Phi_ran = 360
|
|
variable Phi_ref = 1
|
|
variable Phi_in = 0
|
|
|
|
variable n_theta = CalcN_Theta(HoloMode, Theta_in, Theta_ran, Theta_st)
|
|
|
|
// wave names
|
|
if (strlen(nickname))
|
|
string s_prefix = nickname + "_"
|
|
string s_int = s_prefix + "i" // Intensity wave (counts/sec)
|
|
else
|
|
s_prefix = ""
|
|
s_int = "values" // "i" is not a valid wave name
|
|
endif
|
|
string s_polar = s_prefix + "pol" // thetas for each int-point of the holo
|
|
string s_azim = s_prefix + "az" // phis for each int-point of the holo
|
|
|
|
string s_index = s_prefix + "index" // starting index for each theta
|
|
string s_theta = s_prefix + "th" // theta values
|
|
string s_dphi = s_prefix + "dphi" // delta phis at each theta
|
|
string s_nphis = s_prefix + "nphis" // number of phis at each theta
|
|
|
|
string s_HoloData = s_prefix + "data" // All holo exp.- parameter information
|
|
string s_HoloInfo = s_prefix + "info"
|
|
|
|
// the following two waves are used by the pearl-anglescan procedures but not by XPDplot
|
|
string s_tot = s_prefix + "tot" // accumulated counts at each point
|
|
string s_weight = s_prefix + "wt" // total accumulation time at each point (arb. units)
|
|
|
|
make /O/D/n=(n_theta) $s_index /wave=index
|
|
make /O/D/n=(n_theta) $s_theta /wave=theta
|
|
make /O/D/n=(n_theta) $s_dphi /wave=dphi
|
|
make /O/D/n=(n_theta) $s_nphis /wave=nphis
|
|
|
|
//---------- calculate phi-step-size for this theta:
|
|
variable aux = Calc_The_step(Theta_in, Theta_st, HoloMode)
|
|
dphi[0] = calc_phi_step(Theta_in, Theta_in, aux, Phi_ran, Phi_ref, HoloMode)
|
|
Theta[0] = Theta_in
|
|
nphis[0] = calc_nth(aux, Theta_in, Theta_in, Phi_ran, Phi_ref, HoloMode)
|
|
Index[0] = nphis[0]
|
|
|
|
//---------- calculate number of phis, phi-step, and starting-index for each theta:
|
|
variable ii = 1
|
|
do
|
|
Theta[ii] = Theta[ii-1] - aux
|
|
if(Theta[ii] <= Theta_in-Theta_ran)
|
|
Theta[ii] = Theta_in-Theta_ran
|
|
endif
|
|
aux = Calc_The_step(Theta[ii], Theta_st, HoloMode)
|
|
dphi[ii] = calc_phi_step(Theta_in, Theta[ii], aux, Phi_ran, Phi_ref, HoloMode)
|
|
nphis[ii] = calc_nth(aux, Theta_in, Theta[ii], Phi_ran, Phi_ref, HoloMode)
|
|
Index[ii] = Index[ii-1] + nphis[ii]
|
|
ii=ii+1
|
|
while(ii < n_theta)
|
|
|
|
if (Index[n_theta-1]==Index[n_theta-2])
|
|
Index[n_theta-1]=Index[n_theta-2]+1
|
|
nphis[n_theta-1]=1
|
|
endif
|
|
|
|
variable NumPoints = sum(nphis, 0, numpnts(nphis))
|
|
|
|
//---------- calculate theta and phi for each data point:
|
|
make /O/D/N=(NumPoints) $s_polar /wave=polar, $s_azim /wave=azim
|
|
note azim, "version=1.6"
|
|
|
|
ii = 0
|
|
variable StartIndex = 0
|
|
variable EndIndex
|
|
do
|
|
EndIndex=Index[ii]
|
|
Polar[StartIndex, EndIndex-1]=Theta[ii]
|
|
Azim[StartIndex, EndIndex-1]= mod(Phi_ran+(x-StartIndex)*dphi[ii]+Phi_in,Phi_ran)
|
|
ii = ii + 1
|
|
StartIndex = EndIndex
|
|
while(ii < n_theta)
|
|
|
|
duplicate /o azim, $s_int /wave=values
|
|
duplicate /o azim, $s_tot /wave=totals
|
|
duplicate /o azim, $s_weight /wave=weights
|
|
values = nan
|
|
totals = 0
|
|
weights = 0
|
|
|
|
// XPDplot metadata
|
|
if (xpdplot)
|
|
string s_FileName = ""
|
|
string s_Comment = "created by pearl-anglescan-process.ipf"
|
|
string s_HoloMode = "Stereographic"
|
|
variable /g gb_SpectraFile = 0
|
|
|
|
Make/O/D/n=22 $s_HoloData /wave=HoloData
|
|
HoloData[0] = NaN // v_StartKE
|
|
HoloData[1] = NaN // v_StoppKE
|
|
HoloData[6] = NumPoints
|
|
HoloData[7] = Theta_in
|
|
HoloData[8] = Theta_ran
|
|
HoloData[9] = Theta_st
|
|
HoloData[11] = Phi_in
|
|
HoloData[12] = Phi_ran
|
|
HoloData[13] = Theta_st
|
|
HoloData[15] = Phi_ref
|
|
HoloData[16] = Phi_ran
|
|
HoloData[17] = 0 // v_HoloBit (stereographic)
|
|
|
|
Make/O/T/n=22 $s_HoloInfo /wave=HoloInfo
|
|
HoloInfo[0] = s_FileName
|
|
HoloInfo[1] = s_Comment
|
|
HoloInfo[10] = s_HoloMode
|
|
HoloInfo[11] = "" // s_MeasuringMode
|
|
|
|
// notebook for XPDplot
|
|
if (WinType(NickName) == 5)
|
|
Notebook $NickName selection={startOfFile, endOfFile}
|
|
Notebook $NickName text=""
|
|
else
|
|
NewNotebook /F=0 /K=1 /N=$NickName /W=(5,40,341,260)
|
|
Notebook $NickName defaultTab=140
|
|
Notebook $NickName statusWidth=300
|
|
Notebook $NickName backRGB=(56797,56797,56797)
|
|
Notebook $NickName pageMargins={80,80,80,80}
|
|
Notebook $NickName fSize=10
|
|
Notebook $NickName fStyle=0,textRGB=(65535,0,26214)
|
|
Notebook $NickName textRGB=(65535,0,26214)
|
|
endif
|
|
Notebook $NickName text = "File:\t" + s_FileName + "\r"
|
|
Notebook $NickName text = "*** " + s_Comment + " ***\r\r"
|
|
Notebook $NickName text = "Angle-Mode:\t" + s_HoloMode + "\r"
|
|
Notebook $NickName text = "XPDplot Nickname:\t" + NickName + "\r"
|
|
endif
|
|
end
|
|
|
|
/// finds the nick name given any hemi wave
|
|
///
|
|
/// the nick name is either the name of a child folder in the current data folder (PEARL specification),
|
|
/// or a prefix of the hemi wave names (XPDplot specification).
|
|
///
|
|
/// @returns the nick name
|
|
function /s get_hemi_nickname(w)
|
|
wave w
|
|
|
|
string prefix = get_hemi_prefix(w)
|
|
string wname = nameofwave(w)
|
|
string nickname
|
|
|
|
if (strlen(prefix))
|
|
nickname = prefix
|
|
else
|
|
string s_wave_df = GetWavesDataFolder(w, 1)
|
|
dfref parent_df = $(s_wave_df + "::")
|
|
nickname = GetDataFolder(0, parent_df)
|
|
endif
|
|
|
|
return nickname
|
|
end
|
|
|
|
/// finds the prefix given any hemi wave
|
|
///
|
|
/// the prefix is the part of the wave name before the first underscore.
|
|
/// the prefix is used by XPDplot where it is identical to the nick name.
|
|
/// the prefix is empty in the PEARL specification.
|
|
///
|
|
/// @returns the prefix
|
|
function /s get_hemi_prefix(w)
|
|
wave w
|
|
|
|
string wname = nameofwave(w)
|
|
string prefix
|
|
if (ItemsInList(wname, "_") >= 2)
|
|
prefix = StringFromList(0, wname, "_")
|
|
else
|
|
prefix = ""
|
|
endif
|
|
|
|
return prefix
|
|
end
|
|
|
|
/// finds the folder, prefix and name of holo waves given their nick name
|
|
///
|
|
/// the function looks for holo waves in the following order:
|
|
/// 1. if nickname is empty, check for prefix-less waves in current folder.
|
|
/// 2. if nickname is the name of a child folder in the current data folder,
|
|
/// clear the (prefix-less) waves in the child folder.
|
|
/// 3. nickname is prefix of waves in current folder.
|
|
/// 4. nickname is prefix of waves in root folder.
|
|
///
|
|
/// @param[in] nickname folder name or name prefix of holo waves. may be empty.
|
|
///
|
|
/// @param[out] prefix name prefix of waves. may be empty.
|
|
///
|
|
/// @param[out] intwave name of intensity/values wave
|
|
///
|
|
/// @returns reference of the data folder which contains the waves
|
|
///
|
|
function /df find_hemi_data(nickname, prefix, intwave)
|
|
string nickname
|
|
string &prefix
|
|
string &intwave
|
|
|
|
dfref datadf
|
|
prefix = ""
|
|
intwave = "values"
|
|
if (strlen(nickname))
|
|
if (DataFolderExists(nickname))
|
|
datadf = $nickname
|
|
else
|
|
datadf = getdatafolderdfr()
|
|
prefix = nickname + "_"
|
|
intwave = prefix + "i"
|
|
if (exists(intwave) != 1)
|
|
datadf = root:
|
|
endif
|
|
endif
|
|
else
|
|
datadf = getdatafolderdfr()
|
|
prefix = ""
|
|
intwave = "values"
|
|
endif
|
|
return datadf
|
|
end
|
|
|
|
/// clear a hemispherical scan grid
|
|
///
|
|
/// values and weights waves are set to zero.
|
|
/// the intensity wave is set to NaN.
|
|
///
|
|
/// @param nickname folder name or name prefix of holo waves. may be empty.
|
|
///
|
|
function clear_hemi_grid(nickname)
|
|
string nickname
|
|
|
|
dfref datadf
|
|
string s_prefix
|
|
string s_int
|
|
datadf = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_totals = s_prefix + "tot"
|
|
string s_weights = s_prefix + "wt"
|
|
|
|
wave /sdfr=datadf /z w_values = $s_int
|
|
wave /sdfr=datadf /z w_totals = $s_totals
|
|
wave /sdfr=datadf /z w_weights = $s_weights
|
|
|
|
if (waveexists(w_totals))
|
|
w_totals = 0
|
|
endif
|
|
if (waveexists(w_weights))
|
|
w_weights = 0
|
|
endif
|
|
if (waveexists(w_values))
|
|
w_values = nan
|
|
endif
|
|
end
|
|
|
|
/// duplicate a hemispherical scan dataset.
|
|
///
|
|
/// this function works only for hemi scans created by make_hemi_grid() (or compatible functions).
|
|
/// the angle grid is recreated rather than copied point-by-point.
|
|
/// the new dataset is independent from the original one.
|
|
///
|
|
/// if the version of the source dataset is pre 1.6, it is converted to version 1.6.
|
|
///
|
|
/// @param source_nickname name prefix for waves. source data must be in current data folder.
|
|
///
|
|
/// @param dest_folder destination folder. folder must exist.
|
|
///
|
|
/// @param dest_nickname name prefix for destination waves.
|
|
/// must be unique in the current data folder.
|
|
/// otherwise existing waves get overwritten.
|
|
/// may be empty.
|
|
///
|
|
/// @param xpdplot XPDplot compatibility
|
|
/// @arg 0 (default) create the data structures required by this module
|
|
/// @arg 1 create additional waves and notebook required by XPDplot
|
|
///
|
|
function duplicate_hemi_scan(source_nickname, dest_folder, dest_nickname, [xpdplot])
|
|
string source_nickname
|
|
dfref dest_folder
|
|
string dest_nickname
|
|
variable xpdplot
|
|
|
|
if (ParamIsDefault(xpdplot))
|
|
xpdplot = 0
|
|
endif
|
|
|
|
dfref savedf = getdatafolderdfr()
|
|
|
|
// source data
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref source_df = find_hemi_data(source_nickname, s_prefix, s_int)
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
string s_theta = s_prefix + "th"
|
|
string s_tot = s_prefix + "tot"
|
|
string s_weight = s_prefix + "wt"
|
|
string s_matrix = s_prefix + "matrix"
|
|
|
|
wave /sdfr=source_df theta1 = $s_theta
|
|
wave /sdfr=source_df polar1 = $s_polar
|
|
wave /sdfr=source_df azim1 = $s_azim
|
|
wave /sdfr=source_df tot1 = $s_tot
|
|
wave /sdfr=source_df weight1 = $s_weight
|
|
wave /sdfr=source_df values1 = $s_int
|
|
wave /sdfr=source_df /z matrix1 = $s_matrix
|
|
|
|
variable npol = numpnts(theta1)
|
|
|
|
setdatafolder dest_folder
|
|
make_hemi_grid(npol, dest_nickname, xpdplot=xpdplot)
|
|
|
|
// dest data
|
|
dfref dest_df = find_hemi_data(dest_nickname, s_prefix, s_int)
|
|
s_polar = s_prefix + "pol"
|
|
s_azim = s_prefix + "az"
|
|
s_theta = s_prefix + "th"
|
|
s_tot = s_prefix + "tot"
|
|
s_weight = s_prefix + "wt"
|
|
s_matrix = s_prefix + "matrix"
|
|
|
|
wave /sdfr=dest_df theta2 = $s_theta
|
|
wave /sdfr=dest_df polar2 = $s_polar
|
|
wave /sdfr=dest_df azim2 = $s_azim
|
|
wave /sdfr=dest_df tot2 = $s_tot
|
|
wave /sdfr=dest_df weight2 = $s_weight
|
|
wave /sdfr=dest_df values2 = $s_int
|
|
|
|
tot2 = tot1
|
|
weight2 = weight1
|
|
values2 = values1
|
|
if (waveexists(matrix1))
|
|
setdatafolder dest_df
|
|
duplicate /o matrix1, $s_matrix
|
|
endif
|
|
|
|
if (!(NumberByKey("version", note(azim1), "=", "\r") >= 1.6))
|
|
azim2 += 180 // changed 151030 (v1.6)
|
|
azim2 = azim2 >= 360 ? azim2 - 360 : azim2
|
|
endif
|
|
|
|
setdatafolder saveDF
|
|
end
|
|
|
|
/// azimuthally rotate a hemispherical scan dataset.
|
|
///
|
|
/// this function works only for hemi scans created by make_hemi_grid() (or compatible functions).
|
|
///
|
|
/// @param nickname name prefix for waves. source data must be in current data folder.
|
|
/// @param angle azimuthal rotation angle in degrees.
|
|
///
|
|
function rotate_hemi_scan(nickname, angle)
|
|
string nickname
|
|
variable angle
|
|
|
|
dfref savedf = getdatafolderdfr()
|
|
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
string s_tot = s_prefix + "tot"
|
|
string s_weight = s_prefix + "wt"
|
|
|
|
wave /sdfr=df polar = $s_polar
|
|
wave /sdfr=df azim = $s_azim
|
|
wave /sdfr=df tot = $s_tot
|
|
wave /sdfr=df weight = $s_weight
|
|
wave /sdfr=df values = $s_int
|
|
|
|
azim += angle
|
|
azim = azim < 0 ? azim + 360 : azim
|
|
azim = azim >= 360 ? azim - 360 : azim
|
|
|
|
duplicate /free polar, neg_polar
|
|
neg_polar = -polar
|
|
sort {neg_polar, azim}, polar, azim, tot, weight, values
|
|
|
|
setdatafolder saveDF
|
|
end
|
|
|
|
/// create waves for plotting a hemispherical angle scan.
|
|
///
|
|
/// the scan data must exist in the current data folder.
|
|
///
|
|
/// @param nickname name prefix of holo waves.
|
|
/// may be empty.
|
|
///
|
|
/// @param projection mapping function from polar to cartesian coordinates.
|
|
/// see @ref PageProjections for details.
|
|
/// @arg kProjDist = 0 azimuthal equidistant
|
|
/// @arg kProjStereo = 1 stereographic (default)
|
|
/// @arg kProjArea = 2 azimuthal equal-area
|
|
/// @arg kProjGnom = 3 gnomonic (0 <= polar < 90)
|
|
/// @arg kProjOrtho = 4 orthographic
|
|
///
|
|
///
|
|
function /s prepare_hemi_scan_display(nickname, [projection])
|
|
string nickname
|
|
variable projection
|
|
|
|
dfref savedf = getdatafolderdfr()
|
|
|
|
if (ParamIsDefault(projection))
|
|
projection = 1
|
|
endif
|
|
|
|
// hemi grid waves
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
|
|
wave /sdfr=df /z values = $s_int
|
|
wave /sdfr=df /z azim = $s_azim
|
|
wave /sdfr=df /z polar = $s_polar
|
|
|
|
setdatafolder df
|
|
string s_ster_rad = s_prefix + "ster_rad"
|
|
duplicate /o polar, $s_ster_rad /wave=ster_rad
|
|
ster_rad = calc_graph_radius(polar, projection=projection)
|
|
|
|
string s_ster_x = s_prefix + "ster_x"
|
|
string s_ster_y = s_prefix + "ster_y"
|
|
duplicate /o azim, $s_ster_x /wave=ster_x, $s_ster_y /wave=ster_y
|
|
ster_x = ster_rad * cos(azim * pi / 180)
|
|
ster_y = ster_rad * sin(azim * pi / 180)
|
|
|
|
setdatafolder savedf
|
|
end
|
|
|
|
/// display a plot of a hemispherical angle scan.
|
|
///
|
|
/// the scan data must exist in the current data folder.
|
|
/// azimuth = 0 should be at 9 o'clock.
|
|
/// then the orientation is the same as the sample at normal emission and phi = 0,
|
|
/// the handle of the sample plate pointing to the left.
|
|
///
|
|
/// @param nickname name prefix of holo waves.
|
|
/// may be empty.
|
|
///
|
|
/// @param projection mapping function from polar to cartesian coordinates.
|
|
/// see @ref PageProjections for details.
|
|
/// @arg kProjDist = 0 azimuthal equidistant
|
|
/// @arg kProjStereo = 1 stereographic (default)
|
|
/// @arg kProjArea = 2 azimuthal equal-area
|
|
/// @arg kProjGnom = 3 gnomonic (0 <= polar < 90)
|
|
/// @arg kProjOrtho = 4 orthographic
|
|
///
|
|
/// @param graphtype type of graph
|
|
/// @arg 1 (pol, az) trace in Igor "New Polar" (default).
|
|
/// @arg 2 XPDplot (reserved, not implemented).
|
|
/// @arg 3 matrix in Igor "New Polar".
|
|
/// the matrix wave is a 2D wave with X and Y scaling corresponding to the selected projection.
|
|
/// matrix waves can be created by interpolate_hemi_scan().
|
|
/// note: the pol and az waves are required as well.
|
|
///
|
|
/// @param do_ticks select which ticks to draw.
|
|
/// value must be the arithmetic OR of all selected items.
|
|
/// default: 3
|
|
/// @arg 0 none
|
|
/// @arg 1 major azimuthal
|
|
/// @arg 2 minor azimuthal
|
|
///S
|
|
/// @param do_grids select which grids to draw.
|
|
/// value must be the arithmetic OR of all selected items.
|
|
/// default: 3
|
|
/// @arg 0 none
|
|
/// @arg 1 radius at 0 and 90 degree azimuth
|
|
/// @arg 2 circle at 30 and 60 degree polar
|
|
///
|
|
/// @param graphname name of graph window. default: nickname
|
|
/// if empty, a default name is assigned.
|
|
/// if a window with this name is existing, the function brings it to the front, and does nothing else.
|
|
///
|
|
/// @returns the name of the graph window
|
|
///
|
|
function /s display_hemi_scan(nickname, [projection, graphtype, do_ticks, do_grids, graphname])
|
|
string nickname
|
|
variable projection
|
|
variable graphtype
|
|
variable do_ticks
|
|
variable do_grids
|
|
string graphname
|
|
|
|
dfref savedf = getdatafolderdfr()
|
|
|
|
if (ParamIsDefault(projection))
|
|
projection = 1
|
|
endif
|
|
if (ParamIsDefault(graphtype))
|
|
graphtype = 1
|
|
endif
|
|
if (ParamIsDefault(do_ticks))
|
|
do_ticks = 3
|
|
endif
|
|
if (ParamIsDefault(do_grids))
|
|
do_grids = 3
|
|
endif
|
|
if (ParamIsDefault(graphname))
|
|
if (strlen(nickname) > 0)
|
|
graphname = nickname
|
|
else
|
|
graphname = GetDataFolder(0)
|
|
endif
|
|
endif
|
|
|
|
prepare_hemi_scan_display(nickname, projection=projection)
|
|
|
|
// hemi grid waves
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
string s_matrix = s_prefix + "matrix"
|
|
string s_ster_rad = s_prefix + "ster_rad"
|
|
|
|
wave /sdfr=df /z values = $s_int
|
|
wave /sdfr=df /z azim = $s_azim
|
|
wave /sdfr=df /z polar = $s_polar
|
|
wave /sdfr=df /z ster_rad = $s_ster_rad
|
|
wave /sdfr=df /z matrix = $s_matrix
|
|
|
|
setdatafolder df
|
|
variable azim_offset = 0
|
|
if (!(NumberByKey("version", note(azim), "=", "\r") >= 1.6))
|
|
DoAlert /T="display hemi scan" 0, "your dataset doesn't include the version 1.6 flag. if it was created with an earlier version that might be okay. please check that the orientation is correct!"
|
|
azim_offset = 180 // changed 151030 (v1.6)
|
|
endif
|
|
|
|
string s_trace
|
|
DoWindow $graphname
|
|
if (v_flag)
|
|
if (str2num(GetUserData(graphname, "", "graphtype")) == graphtype)
|
|
// graph exists and will update automatically - do not recreate
|
|
graphtype = 0
|
|
else
|
|
// graph exists - but needs recreating
|
|
killwindow $graphname
|
|
endif
|
|
endif
|
|
|
|
switch(graphtype)
|
|
case 1:
|
|
graphname = display_polar_graph(graphname, angle_offset=azim_offset, do_ticks=do_ticks)
|
|
|
|
s_trace = WMPolarAppendTrace(graphname, ster_rad, azim, 360)
|
|
ModifyGraph /W=$graphname mode($s_trace)=2, lsize($s_trace)=2
|
|
ModifyGraph /W=$graphname zColor($s_trace)={values,*,*,BlueGreenOrange,0}
|
|
|
|
ColorScale /W=$graphname /C /N=text0 /E=2 /F=0 /B=1 /A=RB /X=0.00 /Y=0.00 trace=polarY0
|
|
ColorScale /W=$graphname /C /N=text0 side=2, width=5, heightPct=40, frame=0.50, lblMargin=0
|
|
ColorScale /W=$graphname /C /N=text0 nticks=2, minor=1, tickLen=4.00, tickThick=0.50
|
|
|
|
SetWindow $graphname, userdata(projection)=num2str(projection)
|
|
SetWindow $graphname, userdata(graphtype)=num2str(graphtype)
|
|
draw_hemi_axes(graphname, do_grids=do_grids)
|
|
break
|
|
case 3:
|
|
graphname = display_polar_graph(graphname, angle_offset=azim_offset, do_ticks=do_ticks)
|
|
|
|
s_trace = WMPolarAppendTrace(graphname, ster_rad, azim, 360)
|
|
ModifyGraph /W=$graphname mode($s_trace)=0, lsize($s_trace)=0
|
|
AppendImage /L=VertCrossing /B=HorizCrossing matrix
|
|
|
|
ColorScale /W=$graphname /C /N=text0 /E=2 /F=0 /B=1 /A=RB /X=0.00 /Y=0.00 image=$s_matrix
|
|
ColorScale /W=$graphname /C /N=text0 side=2, width=5, heightPct=40, frame=0.50, lblMargin=0
|
|
ColorScale /W=$graphname /C /N=text0 nticks=2, minor=1, tickLen=4.00, tickThick=0.50
|
|
|
|
SetWindow $graphname, userdata(projection)=num2str(projection)
|
|
SetWindow $graphname, userdata(graphtype)=num2str(graphtype)
|
|
draw_hemi_axes(graphname, do_grids=do_grids)
|
|
break
|
|
endswitch
|
|
|
|
setdatafolder savedf
|
|
return graphname
|
|
end
|
|
|
|
/// displays an empty polar graph
|
|
///
|
|
/// the graph is drawn using Wavemetrics "New Polar Graphs.ipf".
|
|
///
|
|
/// initially the graph is empty.
|
|
/// hemispherical scans are displayed by adding a trace that coveres the whole plot area,
|
|
/// and setting the trace color to a function of the intensity.
|
|
/// traces are added by calling WMPolarAppendTrace.
|
|
///
|
|
/// the following items of the graph > packages menu might be useful:
|
|
/// * modify polar graph
|
|
/// * color table control
|
|
/// * show polar cursors
|
|
/// * polar graph legend
|
|
///
|
|
/// parameters can be changed programmatically as shown in the code of this function.
|
|
/// after programmatic parameter changes, call WMPolarAxesRedrawGraphNow(graphname).
|
|
///
|
|
/// @param graphname requested name of new graph window.
|
|
/// if empty, a default name is assigned.
|
|
/// if a window with this name is existing, the function brings it to the front, and does nothing else.
|
|
///
|
|
/// @param angle_offset azimuth (on screen) where angle 0 is plotted
|
|
/// (zeroAngleWhere parameter of polar graphs).
|
|
/// starting with version 1.6, the default is 0.
|
|
/// for hemi grids created with earlier versions,
|
|
/// it should be set to 180 for correct orientation.
|
|
///
|
|
/// @param do_ticks select which ticks to draw.
|
|
/// value must be the arithmetic OR of all selected items.
|
|
/// default: 3
|
|
/// @arg 0 none
|
|
/// @arg 1 major azimuthal
|
|
/// @arg 2 minor azimuthal
|
|
///
|
|
/// @returns the name of the graph window.
|
|
///
|
|
/// @version 1.7
|
|
/// interface change: the trace drawing code is moved to display_hemi_scan,
|
|
/// so that this function can be reused by other graph types, e.g. display_scanlines.
|
|
///
|
|
static function /s display_polar_graph(graphname, [angle_offset, do_ticks])
|
|
|
|
string graphname
|
|
variable angle_offset
|
|
variable do_ticks
|
|
|
|
dfref savedf = GetDataFolderDFR()
|
|
|
|
if (ParamIsDefault(angle_offset))
|
|
angle_offset = 0
|
|
endif
|
|
if (ParamIsDefault(do_ticks))
|
|
do_ticks = 3
|
|
endif
|
|
|
|
if ((strlen(graphname) == 0) || (wintype(graphname) == 0))
|
|
Display /k=1 /W=(10,45,360,345)
|
|
DoWindow /C $graphname
|
|
graphname = WMNewPolarGraph("", graphname)
|
|
WMPolarGraphSetVar(graphname, "zeroAngleWhere", angle_offset)
|
|
|
|
WMPolarGraphSetVar(graphname, "angleAxisThick", 0.5)
|
|
WMPolarGraphSetStr(graphname, "doMajorAngleTicks", "manual")
|
|
WMPolarGraphSetVar(graphname, "majorAngleInc", 30) // major ticks in 30 deg steps
|
|
WMPolarGraphSetVar(graphname, "minorAngleTicks", 2) // minor ticks in 10 deg steps
|
|
WMPolarGraphSetStr(graphname, "angleTicksLocation", "Outside")
|
|
WMPolarGraphSetVar(graphname, "doAngleTickLabelSubRange", 1)
|
|
WMPolarGraphSetVar(graphname, "angleTickLabelRangeStart", 0)
|
|
WMPolarGraphSetVar(graphname, "angleTickLabelRangeExtent", 90)
|
|
WMPolarGraphSetStr(graphname, "angleTickLabelNotation", "%g°")
|
|
|
|
WMPolarGraphSetVar(graphname, "doPolarGrids", 0)
|
|
WMPolarGraphSetVar(graphname, "doRadiusTickLabels", 0)
|
|
WMPolarGraphSetStr(graphname, "radiusAxesWhere", " Off") // note the leading spaces, cf. WMPolarAnglesForRadiusAxes
|
|
WMPolarGraphSetStr(graphname, "radiusTicksLocation", "Off")
|
|
|
|
WMPolarGraphSetVar(graphname, "majorTickLength", 2)
|
|
WMPolarGraphSetVar(graphname, "majorTickThick", 0.5)
|
|
WMPolarGraphSetVar(graphname, "minorTickLength", 1)
|
|
WMPolarGraphSetVar(graphname, "minorTickThick", 0.5)
|
|
WMPolarGraphSetVar(graphname, "tickLabelOpaque", 0)
|
|
WMPolarGraphSetVar(graphname, "tickLabelFontSize", 7)
|
|
|
|
// changes
|
|
if (do_ticks & 1)
|
|
WMPolarGraphSetStr(graphname, "angleTicksLocation", "Outside")
|
|
else
|
|
WMPolarGraphSetStr(graphname, "angleTicksLocation", "Off")
|
|
endif
|
|
if (do_ticks & 2)
|
|
WMPolarGraphSetVar(graphname, "doMinorAngleTicks", 1)
|
|
else
|
|
WMPolarGraphSetVar(graphname, "doMinorAngleTicks", 0)
|
|
endif
|
|
|
|
DoWindow /T $graphname, graphname
|
|
|
|
// cursor info in angles
|
|
string graphdf = "root:packages:WMPolarGraphs:" + graphname
|
|
setdatafolder graphdf
|
|
// current theta, phi coordinates are stored in global variables in the package folder of the graph
|
|
variable /g csrA_theta
|
|
variable /g csrA_phi
|
|
variable /g csrB_theta
|
|
variable /g csrB_phi
|
|
// the text box is hidden initially. it shows up and hides with the cursor info box.
|
|
string tb
|
|
tb = "\\{"
|
|
tb = tb + "\"A = (%.1f, %.1f)\","
|
|
tb = tb + graphdf + ":csrA_theta,"
|
|
tb = tb + graphdf + ":csrA_phi"
|
|
tb = tb + "}"
|
|
TextBox /W=$graphname /A=LT /B=1 /E=2 /F=0 /N=tb_angles /X=1 /Y=1 /V=0 tb
|
|
tb = "\\{"
|
|
tb = tb + "\"B = (%.1f, %.1f)\","
|
|
tb = tb + graphdf + ":csrB_theta,"
|
|
tb = tb + graphdf + ":csrB_phi"
|
|
tb = tb + "}"
|
|
AppendText /W=$graphname /N=tb_angles tb
|
|
// updates are triggered by a window hook
|
|
SetWindow $graphname, hook(polar_graph_hook)=PearlAnglescanProcess#polar_graph_hook
|
|
else
|
|
// graph window exists
|
|
DoWindow /F $graphname
|
|
endif
|
|
|
|
setdatafolder savedf
|
|
return graphname
|
|
end
|
|
|
|
/// draw polar and azimuthal grids in an existing polar graph.
|
|
///
|
|
/// the function adds the following draw objects to a polar graph:
|
|
/// * concentric circles at polar angles 0, 30, and 60 degrees with labels.
|
|
/// * radial axes at 0 and 90 degree azimuth.
|
|
///
|
|
/// the objects are added to the ProgFront drawing layer and will appear in front of the data trace.
|
|
/// in interactive drawing mode, you can select the active drawing layer by clicking the tree icon
|
|
/// while holding the Alt key.
|
|
///
|
|
/// the graph must have been created by display_polar_graph().
|
|
/// the function reads the projection mode from the window user data "projection".
|
|
///
|
|
/// @param graphname name of graph window.
|
|
///
|
|
/// @param do_grids select which optional grids to draw.
|
|
/// value must be the arithmetic OR of all selected items.
|
|
/// default: 3
|
|
/// @arg 0 none
|
|
/// @arg 1 radius at 0 and 90 degree azimuth
|
|
/// @arg 2 circle at 30 and 60 degree polar
|
|
///
|
|
/// @warning EXPERIMENTAL!
|
|
/// this function is under development.
|
|
/// the interface and behaviour of this function may change significantly in future versions.
|
|
static function /s draw_hemi_axes(graphname, [do_grids])
|
|
string graphname
|
|
variable do_grids
|
|
|
|
if (ParamIsDefault(do_grids))
|
|
do_grids = 3
|
|
endif
|
|
|
|
dfref savedf = GetDataFolderDFR()
|
|
|
|
string sproj = GetUserData(graphname, "", "projection")
|
|
variable projection = str2num("0" + sproj)
|
|
|
|
SetDrawLayer /W=$graphname ProgFront
|
|
|
|
// polar axis
|
|
SetDrawEnv /W=$graphname xcoord=HorizCrossing, ycoord=VertCrossing
|
|
SetDrawEnv /W=$graphname linethick= 0.5
|
|
SetDrawEnv /W=$graphname dash=2
|
|
SetDrawEnv /W=$graphname fillpat=0
|
|
SetDrawEnv /W=$graphname fname="default", fsize=7
|
|
SetDrawEnv /W=$graphname textxjust=1, textyjust=1
|
|
//SetDrawEnv /W=$graphname linefgc=(65535,65535,65535)
|
|
SetDrawEnv /W=$graphname save
|
|
|
|
if (do_grids & 1)
|
|
DrawLine /W=$graphname 0, -2, 0, 2
|
|
DrawLine /W=$graphname -2, 0, 2, 0
|
|
endif
|
|
|
|
variable radi
|
|
if (do_grids & 2)
|
|
radi = calc_graph_radius(0.5, projection=projection)
|
|
DrawOval /W=$graphname -radi, radi, radi, -radi
|
|
radi = calc_graph_radius(30, projection=projection)
|
|
DrawOval /W=$graphname -radi, radi, radi, -radi
|
|
radi = calc_graph_radius(60, projection=projection)
|
|
DrawOval /W=$graphname -radi, radi, radi, -radi
|
|
|
|
SetDrawEnv /W=$graphname textxjust= 1,textyjust= 2
|
|
SetDrawEnv /W=$graphname save
|
|
radi = calc_graph_radius(30, projection=projection)
|
|
DrawText /W=$graphname radi, -0.1, "30°"
|
|
radi = calc_graph_radius(60, projection=projection)
|
|
DrawText /W=$graphname radi, -0.1, "60°"
|
|
endif
|
|
|
|
setdatafolder savedf
|
|
end
|
|
|
|
/// draw the circle of a diffraction cone in a stereographic polar graph.
|
|
///
|
|
/// the diffraction cone consists of a circle marking the diffraction ring, and a dot marking the axis.
|
|
/// the cone is drawn as a group of draw objects on the UserFront layer.
|
|
/// the objects can be edited interactively.
|
|
///
|
|
/// @param graphname name of graph window (not implemented yet).
|
|
///
|
|
/// @param groupname name of a drawing group.
|
|
/// if the group exists (from a previous cone) it is replaced.
|
|
/// if the group doesn't exist, a new one is created.
|
|
///
|
|
/// @param theta_axis polar angle of the cone axis in degrees.
|
|
///
|
|
/// @param theta_inner polar angle of the innermost point of the circle in degrees.
|
|
///
|
|
/// @param phi azimuthal angle of the cone axis in degrees.
|
|
///
|
|
/// @warning EXPERIMENTAL!
|
|
/// this function is under development.
|
|
/// the interface and behaviour of this function may change significantly in future versions.
|
|
///
|
|
function draw_diffraction_cone(graphname, groupname, theta_axis, theta_inner, phi)
|
|
string graphname
|
|
string groupname
|
|
|
|
variable theta_axis
|
|
variable theta_inner
|
|
variable phi
|
|
|
|
variable r_axis = calc_graph_radius(theta_axis)
|
|
variable r_inner = calc_graph_radius(theta_inner)
|
|
variable r_outer = calc_graph_radius(2 * theta_axis - theta_inner)
|
|
|
|
SetDrawEnv push
|
|
SetDrawLayer UserFront
|
|
DrawAction getgroup=$groupname, delete
|
|
SetDrawEnv gstart, gname=$groupname
|
|
variable xc, yc, xr, yr
|
|
|
|
// cone periphery
|
|
variable r_center = (r_outer + r_inner) / 2
|
|
variable r_radius = (r_outer - r_inner) / 2
|
|
xc = r_center * cos(phi * pi / 180)
|
|
yc = r_center * sin(phi * pi / 180)
|
|
xr = r_radius
|
|
yr = r_radius
|
|
SetDrawEnv xcoord=HorizCrossing, ycoord=VertCrossing
|
|
SetDrawEnv dash=11, fillpat=0
|
|
DrawOval xc - xr, yc - yr, xc + xr, yc + yr
|
|
|
|
// cone axis
|
|
xc = r_axis * cos(phi * pi / 180)
|
|
yc = r_axis * sin(phi * pi / 180)
|
|
r_radius = calc_graph_radius(2)
|
|
xr = r_radius
|
|
yr = r_radius
|
|
SetDrawEnv xcoord=HorizCrossing, ycoord=VertCrossing
|
|
SetDrawEnv fillfgc=(0,0,0)
|
|
DrawOval xc - xr, yc - yr, xc + xr, yc + yr
|
|
|
|
SetDrawEnv gstop
|
|
SetDrawEnv pop
|
|
end
|
|
|
|
/// display a polar graph with lines indicating the angles covered by an angle scan.
|
|
///
|
|
/// @param nickname nick name for output data.
|
|
/// this will become the name of a child folder containing the output.
|
|
///
|
|
/// @param alpha_lo low limit of the analyser angle.
|
|
///
|
|
/// @param alpha_hi high limit of the analyser angle.
|
|
///
|
|
/// @param m_theta manipulator theta angles, 0 = normal emission. size = dimsize(data, 1)
|
|
///
|
|
/// @param m_tilt manipulator tilt angles, 0 = normal emission. size = dimsize(data, 1)
|
|
///
|
|
/// @param m_phi manipulator phi angles, 0 = azimuthal origin. size = dimsize(data, 1)
|
|
///
|
|
/// @param folding rotational averaging, default = 1
|
|
///
|
|
/// @param projection mapping function from polar to cartesian coordinates. see calc_graph_radius().
|
|
///
|
|
/// @remark this function is extremely slow.
|
|
///
|
|
function /s display_scanlines(nickname, alpha_lo, alpha_hi, m_theta, m_tilt, m_phi, [folding, projection])
|
|
string nickname
|
|
variable alpha_lo
|
|
variable alpha_hi
|
|
wave m_theta
|
|
wave m_tilt
|
|
wave m_phi
|
|
variable folding
|
|
variable projection
|
|
|
|
if (ParamIsDefault(folding))
|
|
folding = 1
|
|
endif
|
|
if (ParamIsDefault(projection))
|
|
projection = 1
|
|
endif
|
|
|
|
// sort out data folder structure
|
|
dfref saveDF = GetDataFolderDFR()
|
|
newdatafolder /s/o $nickname
|
|
string graphname = "graph_" + nickname
|
|
|
|
duplicate /free m_tilt, loc_m_tilt
|
|
loc_m_tilt = -m_tilt
|
|
|
|
make /n=1 /d /free d_polar, d_azi
|
|
variable n_alpha = round(alpha_hi - alpha_lo) + 1
|
|
make /n=(n_alpha) /d /free analyser
|
|
setscale /i x alpha_lo, alpha_hi, "°", analyser
|
|
analyser = x
|
|
|
|
convert_angles_ttpa2polar(m_theta, loc_m_tilt, m_phi, analyser, d_polar, d_azi)
|
|
duplicate /free d_polar, d_radius
|
|
d_radius = calc_graph_radius(d_polar, projection=projection)
|
|
d_azi += 180 // changed 151030 (v1.6)
|
|
|
|
graphname = display_polar_graph(graphname)
|
|
SetWindow $graphname, userdata(projection)=num2str(projection)
|
|
|
|
variable ifold
|
|
variable iang
|
|
variable nang = numpnts(m_theta)
|
|
string s_rad
|
|
string s_azi
|
|
string s_trace
|
|
for (ifold = 0; ifold < folding; ifold += 1)
|
|
d_azi = d_azi >= 360 ? d_azi - 360 : d_azi
|
|
for (iang = 0; iang < nang; iang += 1)
|
|
sprintf s_rad, "rad_%d_%d", ifold, iang
|
|
duplicate /o analyser, $s_rad
|
|
wave w_rad = $s_rad
|
|
w_rad = d_radius[p][iang]
|
|
|
|
sprintf s_azi, "azi_%d_%d", ifold, iang
|
|
duplicate /o analyser, $s_azi
|
|
wave w_azi = $s_azi
|
|
w_azi = d_azi[p][iang]
|
|
|
|
if (numtype(sum(w_rad)) == 0)
|
|
s_trace = WMPolarAppendTrace(graphname, w_rad, w_azi, 360)
|
|
ModifyGraph /w=$graphname mode($s_trace)=0, lsize($s_trace)=0.5
|
|
endif
|
|
endfor
|
|
d_azi += 360 / folding
|
|
endfor
|
|
|
|
draw_hemi_axes(graphname)
|
|
|
|
setdatafolder saveDF
|
|
return graphname
|
|
end
|
|
|
|
/// @page PageProjections Projections
|
|
///
|
|
/// the functions of the anglescan package support the following map projections.
|
|
/// for a description of the different projections, see, for example,
|
|
/// https://en.wikipedia.org/wiki/Map_projection
|
|
///
|
|
/// | Selector | Projection | Function | Properties |
|
|
/// | :----: | :----: | :----: | :---- |
|
|
/// | kProjDist = 0 | azimuthal equidistant | r = c * theta | radius is proportional to polar angle. |
|
|
/// | kProjStereo = 1 | stereographic | r = c * tan theta/2 | circles on sphere map to circles. |
|
|
/// | kProjArea = 2 | azimuthal equal-area | r = c * sin theta/2 | preserves area measure. |
|
|
/// | kProjGnom = 3 | gnomonic | r = c * tan theta | great circles map to straight lines. |
|
|
/// | kProjOrtho = 4 | orthographic | r = c * sin theta | k-space mapping in ARPES and LEED. |
|
|
///
|
|
/// the projections in this package are defined for 0 <= theta < 90.
|
|
///
|
|
constant kProjDist = 0
|
|
constant kProjStereo = 1
|
|
constant kProjArea = 2
|
|
constant kProjGnom = 3
|
|
constant kProjOrtho = 4
|
|
|
|
static constant kProjScaleDist = 2
|
|
static constant kProjScaleStereo = 2
|
|
static constant kProjScaleArea = 2
|
|
// scaled so that radius(gnom) = radius(stereo) for polar = 88
|
|
static constant kProjScaleGnom = 0.06744519021
|
|
static constant kProjScaleOrtho = 2
|
|
|
|
/// calculate the projected polar angle
|
|
///
|
|
/// @param polar polar angle in degrees
|
|
///
|
|
/// @param projection mapping function from polar to cartesian coordinates.
|
|
/// see @ref PageProjections for details.
|
|
/// @arg kProjDist = 0 azimuthal equidistant
|
|
/// @arg kProjStereo = 1 stereographic (default)
|
|
/// @arg kProjArea = 2 azimuthal equal-area
|
|
/// @arg kProjGnom = 3 gnomonic (0 <= polar < 90)
|
|
/// @arg kProjOrtho = 4 orthographic
|
|
///
|
|
/// @return projected radius.
|
|
/// the radius is scaled such that grazing emission maps to 2.
|
|
threadsafe function calc_graph_radius(polar, [projection])
|
|
variable polar
|
|
variable projection
|
|
|
|
if (ParamIsDefault(projection))
|
|
projection = 1
|
|
endif
|
|
|
|
variable radius
|
|
switch(projection)
|
|
case kProjStereo: // stereographic
|
|
radius = kProjScaleStereo * tan(polar / 2 * pi / 180)
|
|
break
|
|
case kProjArea: // equal area
|
|
radius = kProjScaleArea * sin(polar / 2 * pi / 180)
|
|
break
|
|
case kProjGnom: // gnomonic
|
|
radius = polar < 90 ? kProjScaleGnom * tan(polar * pi / 180) : inf
|
|
break
|
|
case kProjOrtho: // orthographic
|
|
radius = kProjScaleOrtho * sin(polar * pi / 180)
|
|
break
|
|
default: // equidistant
|
|
radius = kProjScaleDist * polar / 90
|
|
endswitch
|
|
|
|
return radius
|
|
end
|
|
|
|
/// calculate polar angle from Cartesian coordinate
|
|
///
|
|
/// this is the reverse mapping to calc_graph_radius()
|
|
///
|
|
/// @param x, y projected Cartesian coordinate
|
|
///
|
|
/// @param projection mapping function from polar to cartesian coordinates.
|
|
/// see @ref PageProjections for details.
|
|
/// @arg kProjDist = 0 azimuthal equidistant
|
|
/// @arg kProjStereo = 1 stereographic (default)
|
|
/// @arg kProjArea = 2 azimuthal equal-area
|
|
/// @arg kProjGnom = 3 gnomonic (0 <= polar < 90)
|
|
/// @arg kProjOrtho = 4 orthographic
|
|
///
|
|
/// @returns polar angle in degrees
|
|
///
|
|
threadsafe function calc_graph_polar(x, y, [projection])
|
|
variable x
|
|
variable y
|
|
variable projection
|
|
|
|
if (ParamIsDefault(projection))
|
|
projection = 1
|
|
endif
|
|
|
|
variable radius
|
|
variable polar
|
|
|
|
radius = sqrt(x^2 + y^2)
|
|
switch(projection)
|
|
case kProjStereo: // stereographic
|
|
polar = 2 * atan(radius / kProjScaleStereo) * 180 / pi
|
|
break
|
|
case kProjArea: // equal area
|
|
polar = 2 * asin(radius / kProjScaleArea) * 180 / pi
|
|
break
|
|
case kProjGnom: // gnomonic
|
|
polar = atan(radius / kProjScaleGnom) * 180 / pi
|
|
break
|
|
case kProjOrtho: // orthographic
|
|
polar = asin(radius / kProjScaleOrtho) * 180 / pi
|
|
break
|
|
default: // equidistant
|
|
polar = 90 * radius / kProjScaleDist
|
|
endswitch
|
|
|
|
return polar
|
|
end
|
|
|
|
/// calculate azimuthal angle from Cartesian coordinate
|
|
///
|
|
/// @param x, y projected Cartesian coordinate
|
|
///
|
|
/// @param projection mapping function from polar to cartesian coordinates.
|
|
/// all supported projections are azimuthal, they have no effect on the azimuthal coordinate.
|
|
/// see @ref PageProjections for details.
|
|
/// @arg kProjDist = 0 azimuthal equidistant
|
|
/// @arg kProjStereo = 1 stereographic (default)
|
|
/// @arg kProjArea = 2 azimuthal equal-area
|
|
/// @arg kProjGnom = 3 gnomonic (0 <= polar < 90)
|
|
/// @arg kProjOrtho = 4 orthographic
|
|
///
|
|
/// @param zeroAngle zeroAngleWhere parameter of polar graphs
|
|
/// @arg 0 (default) zero is at the 3 o'clock position
|
|
/// @arg 180 zero is at the 9 o'clock position
|
|
/// @arg other values not tested
|
|
///
|
|
/// @returns polar angle in degrees
|
|
///
|
|
threadsafe function calc_graph_azi(x, y, [projection,zeroAngle])
|
|
variable x
|
|
variable y
|
|
variable projection
|
|
variable zeroAngle
|
|
|
|
if (ParamIsDefault(projection))
|
|
projection = 1
|
|
endif
|
|
if (ParamIsDefault(zeroAngle))
|
|
zeroAngle = 0
|
|
endif
|
|
|
|
variable azi
|
|
if (x > 0)
|
|
azi = atan(y / x) * 180 / pi
|
|
else
|
|
azi = atan(y / x) * 180 / pi + 180
|
|
endif
|
|
|
|
azi += zeroAngle
|
|
if (azi < 0)
|
|
azi += 360
|
|
endif
|
|
if (azi >= 360)
|
|
azi -= 360
|
|
endif
|
|
if (numtype(azi) != 0)
|
|
azi = 0
|
|
endif
|
|
|
|
return azi
|
|
end
|
|
|
|
/// update the angles info based on cursors A and B of a given polar graph window
|
|
///
|
|
/// the function reads the projection mode from the user data of the graph window
|
|
/// and the zeroAngleWhere variable from the associated WMPolarGraph data folder.
|
|
///
|
|
/// the calculated angles are written to the csrA_theta, csrA_phi, csrB_theta, and csrB_phi
|
|
/// global variables in the polar graph data folder.
|
|
/// the angles text box of the graph updates from to these variables dynamically.
|
|
///
|
|
/// @param graphname name of polar graph window
|
|
///
|
|
static function update_polar_info(graphname)
|
|
string graphname
|
|
|
|
dfref savedf = GetDataFolderDFR()
|
|
|
|
string graphdf = "root:packages:WMPolarGraphs:" + graphname
|
|
setdatafolder graphdf
|
|
|
|
nvar csrA_theta
|
|
nvar csrA_phi
|
|
nvar csrB_theta
|
|
nvar csrB_phi
|
|
|
|
string sproj = GetUserData(graphname, "", "projection")
|
|
variable projection = str2num("0" + sproj)
|
|
nvar zeroAngleWhere
|
|
|
|
variable x = hcsr(A, graphname)
|
|
variable y = vcsr(A, graphname)
|
|
csrA_theta = calc_graph_polar(x, y, projection=projection)
|
|
csrA_phi = calc_graph_azi(x, y, projection=projection, zeroAngle=zeroAngleWhere)
|
|
|
|
x = hcsr(B, graphname)
|
|
y = vcsr(B, graphname)
|
|
csrB_theta = calc_graph_polar(x, y, projection=projection)
|
|
csrB_phi = calc_graph_azi(x, y, projection=projection, zeroAngle=zeroAngleWhere)
|
|
|
|
setdatafolder savedf
|
|
end
|
|
|
|
/// polar graph window hook
|
|
///
|
|
/// this hook converts the cursor positions to polar coordinates
|
|
/// and displays them in a text box on the graph.
|
|
/// the text box is visible while the cursor info box is visible.
|
|
static function polar_graph_hook(s)
|
|
STRUCT WMWinHookStruct &s
|
|
|
|
Variable hookResult = 0
|
|
|
|
switch(s.eventCode)
|
|
case 7: // cursor moved
|
|
update_polar_info(s.winname)
|
|
break
|
|
case 20: // show info
|
|
TextBox /W=$s.winname /N=tb_angles /C /V=1
|
|
break
|
|
case 21: // hide info
|
|
TextBox /W=$s.winname /N=tb_angles /C /V=0
|
|
break
|
|
endswitch
|
|
|
|
return hookResult // 0 if nothing done, else 1
|
|
end
|
|
|
|
function set_polar_graph_cursor(nickname, cursorname, polar_angle, azim_angle, [graphname])
|
|
string nickname
|
|
string cursorname
|
|
variable polar_angle
|
|
variable azim_angle
|
|
string graphname
|
|
|
|
if (ParamIsDefault(graphname))
|
|
if (strlen(nickname) > 0)
|
|
graphname = nickname
|
|
else
|
|
graphname = GetDataFolder(0)
|
|
endif
|
|
endif
|
|
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
wave /sdfr=df /z azim = $s_azim
|
|
wave /sdfr=df /z polar = $s_polar
|
|
|
|
FindLevel /P /Q polar, polar_angle
|
|
if (v_flag == 0)
|
|
variable polar_level = floor(v_levelx)
|
|
FindLevel /P /Q /R=[polar_level] azim, azim_angle
|
|
if (v_flag == 0)
|
|
variable azim_level = round(v_levelx)
|
|
string tracename = "polarY0"
|
|
Cursor /W=$graphname /P $cursorname $traceName azim_level
|
|
endif
|
|
endif
|
|
end
|
|
|
|
|
|
/// add arbitrary angle scan data to a hemispherical scan grid.
|
|
///
|
|
/// the function fills the input data into bins defined by the hemi scan grid.
|
|
/// it sums up the values and weights of the data points which fall into each bin,
|
|
/// and adds the results to the totals and weights waves of the existing hemi grid.
|
|
/// finally, it updates the values wave (values divided by weights).
|
|
///
|
|
/// the hemi grid must have been created in the current data folder by the make_hemi_grid() function.
|
|
/// the function does not clear previous values before adding new data.
|
|
/// values are added to the _tot wave, weights to the _wt wave.
|
|
/// the intensity (_i/values) wave is calculated as _tot divided by _wt.
|
|
///
|
|
/// @param nickname name prefix of holo waves.
|
|
/// empty if waves are in current data folder.
|
|
/// @param values counts/intensity values at the positions given in the polara nd azi waves.
|
|
/// one- or two-dimensional.
|
|
/// NaN values are ignored.
|
|
/// @param polar polar angles (in degrees) of each data point.
|
|
/// allowed range 0 <= theta <= 90.
|
|
/// no specific ordering required.
|
|
/// @param azi azimuthal angles (in degrees) of each data point.
|
|
/// allowed range -360 <= phi < +360.
|
|
/// no specific order required.
|
|
/// @param weights weight or accumulation time of each point of values.
|
|
/// weights must be positive. values with weight 0 are ignored.
|
|
/// defaults to 1 if not specified.
|
|
///
|
|
/// the values, weights, polar and azi waves must have the same dimensions (one- or two-dimensional).
|
|
/// no specific order is required, the function sorts (copies of) the arrays internally.
|
|
///
|
|
/// the actual binning is delegated to the thread-safe add_anglescan_worker() function
|
|
/// under Igor's automatic multi-threading facility.
|
|
///
|
|
function hemi_add_anglescan(nickname, values, polar, azi, [weights])
|
|
string nickname
|
|
wave values
|
|
wave polar
|
|
wave azi
|
|
wave weights
|
|
|
|
dfref savedf = GetDataFolderDFR()
|
|
|
|
if (ParamIsDefault(weights))
|
|
duplicate /free values, weights
|
|
weights = 1
|
|
endif
|
|
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_totals = s_prefix + "tot"
|
|
string s_weights = s_prefix + "wt"
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
string s_index = s_prefix + "index"
|
|
string s_theta = s_prefix + "th"
|
|
string s_dphi = s_prefix + "dphi"
|
|
string s_nphis = s_prefix + "nphis"
|
|
|
|
wave /sdfr=df w_polar = $s_polar
|
|
wave /sdfr=df w_azim = $s_azim
|
|
wave /sdfr=df w_values = $s_int
|
|
wave /sdfr=df w_totals = $s_totals
|
|
wave /sdfr=df w_weights = $s_weights
|
|
wave /sdfr=df w_index = $s_index
|
|
wave /sdfr=df w_theta = $s_theta
|
|
wave /sdfr=df w_dphi = $s_dphi
|
|
wave /sdfr=df w_nphis = $s_nphis
|
|
|
|
// make internal copies of input, one-dimensional, ordered in theta
|
|
duplicate /free values, values_copy
|
|
duplicate /free polar, polar_copy
|
|
duplicate /free azi, azi_copy
|
|
duplicate /free weights, weights_copy
|
|
variable nn = dimsize(values, 0) * max(dimsize(values, 1), 1)
|
|
redimension /n=(nn) values_copy, polar_copy, azi_copy, weights_copy
|
|
sort /r polar_copy, polar_copy, azi_copy, values_copy, weights_copy
|
|
|
|
make /n=(numpnts(w_theta)) /free /df dfw
|
|
// for debugging: remove the MultiThread keyword and the ThreadSafe keywords of sub-functions
|
|
MultiThread dfw = add_anglescan_worker(p, values_copy, weights_copy, polar_copy, azi_copy, w_polar, w_azim, w_theta, w_index, w_dphi, w_nphis)
|
|
|
|
variable pp
|
|
for (pp = 0; pp < numpnts(dfw); pp += 1)
|
|
dfref tdf= dfw[pp]
|
|
wave df_totals = tdf:w_totals
|
|
wave df_weights = tdf:w_weights
|
|
w_totals += df_totals
|
|
w_weights += df_weights
|
|
endfor
|
|
w_values = w_weights > 0 ? w_totals / w_weights : nan
|
|
|
|
SetDataFolder savedf
|
|
end
|
|
|
|
/// thread worker for hemi_add_anglescan
|
|
///
|
|
/// this function extracts one azimuthal scan from the input data and adds it to an existing holo scan.
|
|
/// it should be considered as a part of hemi_add_anglescan and not used elsewhere,
|
|
/// as its interface may change in the future.
|
|
///
|
|
/// the function takes as input the entire input data, an existing hemi grid and the index of a polar angle to work on.
|
|
/// the results are a w_totals and w_weights wave that can be added to the hemi scan.
|
|
/// the two waves are returned in a free data folder referenced by the return value of the function.
|
|
/// the function does not change global data.
|
|
///
|
|
threadsafe static function /df add_anglescan_worker(ith, values, weights, polar, azi, w_polar, w_azim, w_theta, w_index, w_dphi, w_nphis)
|
|
variable ith // index into w_theta
|
|
wave values // input data: intensity/counts
|
|
wave weights // input data: weights/dwell time
|
|
wave polar // input data: polar angles
|
|
wave azi // input data: azimuthal angles
|
|
wave w_polar // hemi grid
|
|
wave w_azim // hemi grid
|
|
wave w_theta // hemi grid
|
|
wave w_index // hemi grid
|
|
wave w_dphi // hemi grid
|
|
wave w_nphis // hemi grid
|
|
|
|
dfref savedf= GetDataFolderDFR()
|
|
dfref freedf= NewFreeDataFolder()
|
|
SetDataFolder freedf
|
|
make /n=(numpnts(w_polar)) /d w_totals, w_weights
|
|
|
|
variable pol = w_theta[ith]
|
|
variable pol_st = abs(w_theta[1] - w_theta[0])
|
|
variable pol1 = pol - pol_st / 2
|
|
variable pol2 = pol + pol_st / 2
|
|
|
|
extract /free /indx polar, sel, (pol1 < polar) && (polar <= pol2) && (numtype(values) == 0) && (weights > 0)
|
|
if (numpnts(sel) > 0)
|
|
duplicate /free /r=[0, numpnts(sel)-1] azi, azi_slice
|
|
duplicate /free /r=[0, numpnts(sel)-1] values, values_slice
|
|
duplicate /free /r=[0, numpnts(sel)-1] weights, weights_slice
|
|
azi_slice = azi[sel]
|
|
values_slice = values[sel]
|
|
weights_slice = weights[sel]
|
|
add_aziscan_core(values_slice, weights_slice, pol, azi_slice, w_theta, w_azim, w_index, w_dphi, w_totals, w_weights)
|
|
endif
|
|
|
|
SetDataFolder savedf
|
|
return freedf
|
|
end
|
|
|
|
/// thread worker for hemi_add_anglescan and hemi_add_aziscan
|
|
///
|
|
/// this function adds one azimuthal scan to an existing holo scan.
|
|
/// it should be considered as a part of hemi_add_anglescan and hemi_add_aziscan and not used elsewhere,
|
|
/// as its interface may change in the future.
|
|
///
|
|
/// the function takes as input an azimuthal scan and an existing hemi grid.
|
|
/// the results are added to w_totals and w_weights waves.
|
|
///
|
|
/// @attention the function sorts the input arrays by azimuthal angle!
|
|
/// these waves must not refer to global objects if multi-threading is used!
|
|
///
|
|
threadsafe static function add_aziscan_core(values, weights, polar, azi, w_theta, w_azim, w_index, w_dphi, w_totals, w_weights)
|
|
wave values // input data: intensity/counts
|
|
wave weights // input data: weights/dwell time
|
|
variable polar // input data: polar angle
|
|
wave azi // input data: angle positions of the azimuthal scan
|
|
// acceptable range: >= -360 and < +360
|
|
// no specific order required, the function sorts the array in place (!)
|
|
wave w_theta // hemi grid
|
|
wave w_azim // hemi grid
|
|
wave w_index // hemi grid
|
|
wave w_dphi // hemi grid
|
|
wave w_totals // output data: total counts in hemi grid order
|
|
wave w_weights // output data: total weights in hemi grid order
|
|
|
|
// destination slice coordinates
|
|
variable ipol = BinarySearch(w_theta, polar)
|
|
if (ipol < 0)
|
|
return -1
|
|
endif
|
|
|
|
variable d1, d2
|
|
if (ipol >= 1)
|
|
d1 = w_index[ipol - 1]
|
|
else
|
|
d1 = 0
|
|
endif
|
|
d2 = w_index[ipol] - 1
|
|
variable nd = d2 - d1 + 1
|
|
variable dphi = w_dphi[ipol]
|
|
make /n=(nd+1) /free bin_index
|
|
setscale /i x w_azim[d1] - dphi/2, w_azim[d2] + dphi/2, "deg", bin_index
|
|
|
|
// source slice coordinates
|
|
// order the slice from -dphi/2 to 360-dphi/2
|
|
azi = azi < 0 ? azi + 360 : azi
|
|
azi = azi >= 360 - dphi/2 ? azi - 360 : azi
|
|
sort azi, values, weights, azi
|
|
setscale /p x 0, 1, "", values, weights, azi
|
|
|
|
bin_index = BinarySearch(azi, x) + 1
|
|
bin_index = bin_index == -2 ? 0 : bin_index[p]
|
|
bin_index = bin_index == -1 ? numpnts(azi) : bin_index[p]
|
|
bin_index[nd] = numpnts(azi)
|
|
|
|
// loop over destination
|
|
variable id
|
|
variable v1, v2, w1, w2
|
|
for (id = 0; id < nd; id += 1)
|
|
if (bin_index[id+1] > bin_index[id])
|
|
v1 = w_totals[d1 + id]
|
|
w1 = w_weights[d1 + id]
|
|
if ((numtype(v1) == 2) || (w1 <= 0))
|
|
v1 = 0
|
|
w1 = 0
|
|
endif
|
|
v2 = sum(values, bin_index[id], bin_index[id+1] - 1)
|
|
w2 = sum(weights, bin_index[id], bin_index[id+1] - 1)
|
|
w_totals[d1 + id] = v1 + v2
|
|
w_weights[d1 + id] = w1 + w2
|
|
endif
|
|
endfor
|
|
end
|
|
|
|
/// add azimuthal data to a hemispherical scan grid.
|
|
///
|
|
/// the hemi grid must have been created in the current data folder by the make_hemi_grid() function.
|
|
/// the function determines the bin size at the given polar angle,
|
|
/// sums up the values and weights of the data points which fall into each bin,
|
|
/// and adds the results to the totals and weights waves of the existing hemi grid.
|
|
/// finally, it updates the values wave (values divided by weights).
|
|
///
|
|
/// @param nickname name prefix of holo waves.
|
|
/// empty if waves are in current data folder.
|
|
/// @param values counts/intensity values of the azimuthal scan at the positions given in the azi parameter.
|
|
/// @param polar polar angle (in degrees) where to add the azi scan.
|
|
/// @param azi angle positions of the azimuthal scan.
|
|
/// acceptable range: >= -360 and < +360.
|
|
/// no specific order required, the function sorts the array internally.
|
|
/// @param weights weight or accumulation time of each point of values.
|
|
/// defaults to 1 if not specified.
|
|
///
|
|
/// the actual binning is delegated to the thread-safe add_aziscan_core() function shared with hemi_add_anglescan().
|
|
///
|
|
function hemi_add_aziscan(nickname, values, polar, azi, [weights])
|
|
string nickname
|
|
wave values
|
|
variable polar
|
|
wave azi
|
|
wave weights
|
|
|
|
dfref savedf = GetDataFolderDFR()
|
|
|
|
duplicate /free values, values_copy
|
|
duplicate /free azi, azi_copy
|
|
if (ParamIsDefault(weights))
|
|
duplicate /free values, weights_copy
|
|
weights_copy = 1
|
|
else
|
|
duplicate /free weights, weights_copy
|
|
endif
|
|
|
|
// hemi grid waves
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_totals = s_prefix + "tot"
|
|
string s_weights = s_prefix + "wt"
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
string s_index = s_prefix + "index"
|
|
string s_theta = s_prefix + "th"
|
|
string s_dphi = s_prefix + "dphi"
|
|
string s_nphis = s_prefix + "nphis"
|
|
|
|
wave /sdfr=df w_polar = $s_polar
|
|
wave /sdfr=df w_azim = $s_azim
|
|
wave /sdfr=df w_values = $s_int
|
|
wave /sdfr=df w_totals = $s_totals
|
|
wave /sdfr=df w_weights = $s_weights
|
|
wave /sdfr=df w_index = $s_index
|
|
wave /sdfr=df w_theta = $s_theta
|
|
wave /sdfr=df w_dphi = $s_dphi
|
|
wave /sdfr=df w_nphis = $s_nphis
|
|
|
|
add_aziscan_core(values_copy, weights_copy, polar, azi_copy, w_theta, w_azim, w_index, w_dphi, w_totals, w_weights)
|
|
|
|
w_values = w_weights > 0 ? w_totals / w_weights : nan
|
|
|
|
SetDataFolder savedf
|
|
end
|
|
|
|
/// interpolate a hemispherical scan onto a rectangular grid
|
|
///
|
|
/// the scan data must exist in the current data folder
|
|
/// or in the sub-folder given by the nickname parameter.
|
|
///
|
|
/// the interpolated data is written to a new two-dimensional wave "matrix".
|
|
/// the wave has a fixed size of 181 x 181 points optimized for 1-degree polar steps.
|
|
///
|
|
/// missing values (nan) are interpolated.
|
|
/// this works well only if the missing values are reasonable sparse.
|
|
/// the function also applies a gaussian filter to smooth the image.
|
|
/// empty rings at high polar angles map are preserved.
|
|
///
|
|
/// to display the result call display_hemi_scan() with graphtype=3.
|
|
///
|
|
/// @param nickname name prefix of holo waves.
|
|
/// may be empty.
|
|
///
|
|
/// @param projection mapping function from polar to cartesian coordinates.
|
|
/// see @ref PageProjections for details.
|
|
/// @arg kProjDist = 0 azimuthal equidistant
|
|
/// @arg kProjStereo = 1 stereographic (default)
|
|
/// @arg kProjArea = 2 azimuthal equal-area
|
|
/// @arg kProjGnom = 3 gnomonic (0 <= polar < 90)
|
|
/// @arg kProjOrtho = 4 orthographic
|
|
///
|
|
///
|
|
function interpolate_hemi_scan(nickname, [projection])
|
|
string nickname
|
|
variable projection
|
|
|
|
dfref savedf = GetDataFolderDFR()
|
|
|
|
if (ParamIsDefault(projection))
|
|
projection = 1
|
|
endif
|
|
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
prepare_hemi_scan_display(nickname, projection=projection)
|
|
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
string s_matrix = s_prefix + "matrix"
|
|
string s_ster_rad = s_prefix + "ster_rad"
|
|
string s_ster_x = s_prefix + "ster_x"
|
|
string s_ster_y = s_prefix + "ster_y"
|
|
|
|
wave /sdfr=df /z values = $s_int
|
|
wave /sdfr=df /z azim = $s_azim
|
|
wave /sdfr=df /z polar = $s_polar
|
|
wave /sdfr=df /z ster_rad = $s_ster_rad
|
|
wave /sdfr=df /z ster_x = $s_ster_x
|
|
wave /sdfr=df /z ster_y = $s_ster_y
|
|
|
|
variable min_ster_x = wavemin(ster_x)
|
|
variable max_ster_x = wavemax(ster_x)
|
|
variable x0 = min_ster_x
|
|
variable xn = 181
|
|
variable dx = (max_ster_x - min_ster_x) / (xn - 1)
|
|
make /n=(numpnts(ster_x), 3) /free triplet
|
|
triplet[][0] = ster_x[p]
|
|
triplet[][1] = ster_y[p]
|
|
triplet[][2] = values[p]
|
|
|
|
variable size = 181
|
|
setdatafolder df
|
|
make /n=(size, size) /d /o $(s_prefix + "matrix") /wave=matrix
|
|
make /n=(size, size) /free mnorm
|
|
ImageFromXYZ /as {ster_x, ster_y, values}, matrix, mnorm
|
|
matrix /= mnorm
|
|
matrixfilter NanZapMedian, matrix
|
|
matrixfilter gauss, matrix
|
|
|
|
duplicate /free values, ster_finite
|
|
ster_finite = (numtype(values) == 0) * (ster_x^2 + ster_y^2)
|
|
variable ster_max = wavemax(ster_finite)
|
|
matrix = (x^2 + y^2) <= ster_max ? matrix : nan
|
|
|
|
setdatafolder savedf
|
|
end
|
|
|
|
/// map angle scan data onto a rectangular grid in stereographic projection
|
|
///
|
|
/// accepts angle-scan data as returned by adh5_load_reduced,
|
|
/// maps them onto a rectangular grid in stereographic projection
|
|
///
|
|
/// @param data 2D data wave, X-axis = analyser angle, Y-axis = manipulator scan (no specific ordering required)
|
|
///
|
|
/// @pre manipulator angles as attributes in attr folder next to the data wave
|
|
///
|
|
/// @warning EXPERIMENTAL
|
|
function quick_pizza_image(data, nickname, theta_offset, tilt_offset, phi_offset, [npolar, nograph, folding])
|
|
wave data // 2D intensity wave, see requirements above
|
|
string nickname // nick name for output data
|
|
// in default mode, this will be the name of a child folder containing the output
|
|
// in XPDplot mode, this will be a prefix of the generated data in the root folder
|
|
variable theta_offset // manipulator theta angle corresponding to normal emission
|
|
variable tilt_offset // manipulator tilt angle corresponding to normal emission
|
|
variable phi_offset // manipulator phi angle corresponding to phi_result = 0
|
|
variable npolar // number of polar angles, determines polar and azimuthal step size
|
|
// default = 91 (1 degree steps)
|
|
variable nograph // 0 (default) = display a new polar graph
|
|
// 1 = don't display a new graph (if a graph is existing from a previous call, it will update)
|
|
variable folding // rotational averaging, default = 1
|
|
|
|
if (ParamIsDefault(npolar))
|
|
npolar = 91
|
|
endif
|
|
if (ParamIsDefault(nograph))
|
|
nograph = 0
|
|
endif
|
|
if (ParamIsDefault(folding))
|
|
folding = 1
|
|
endif
|
|
string graphname = "graph_" + nickname
|
|
string s_prefix = ""
|
|
|
|
// sort out data folder structure
|
|
dfref saveDF = GetDataFolderDFR()
|
|
dfref dataDF = GetWavesDataFolderDFR(data)
|
|
setdatafolder dataDF
|
|
if (DataFolderExists(":attr"))
|
|
setdatafolder :attr
|
|
endif
|
|
dfref attrDF = GetDataFolderDFR()
|
|
setdatafolder dataDF
|
|
newdatafolder /s/o $nickname
|
|
dfref destDF = GetDataFolderDFR()
|
|
|
|
// performance monitoring
|
|
variable timerRefNum
|
|
variable /g xyz_perf_secs
|
|
timerRefNum = startMSTimer
|
|
|
|
wave /sdfr=attrDF ManipulatorTheta
|
|
wave /sdfr=attrDF ManipulatorTilt
|
|
wave /sdfr=attrDF ManipulatorPhi
|
|
duplicate /free ManipulatorTheta, m_theta
|
|
duplicate /free ManipulatorTilt, m_tilt
|
|
duplicate /free ManipulatorPhi, m_phi
|
|
m_theta -= theta_offset
|
|
m_tilt -= tilt_offset
|
|
m_tilt *= -1 // checked 140702
|
|
m_phi -= phi_offset
|
|
//m_phi *= -1 // checked 140702
|
|
|
|
make /n=1/d/free d_polar, d_azi
|
|
convert_angles_ttpd2polar(m_theta, m_tilt, m_phi, data, d_polar, d_azi)
|
|
d_azi += 180 // changed 151030 (v1.6)
|
|
d_azi = d_azi >= 360 ? d_azi - 360 : d_azi
|
|
|
|
duplicate /free data, values
|
|
variable nn = dimsize(values, 0) * max(dimsize(values, 1), 1)
|
|
redimension /n=(nn) values, d_polar, d_azi
|
|
duplicate /o d_polar, ster_rad, ster_x, ster_y
|
|
|
|
variable projection = 1
|
|
switch(projection)
|
|
case 1: // stereographic
|
|
ster_rad = 2 * tan(d_polar / 2 * pi / 180)
|
|
break
|
|
case 2: // azimuthal
|
|
ster_rad = 2 * cos((180 - d_polar) / 2 * pi / 180)
|
|
break
|
|
endswitch
|
|
string s_ster_x = s_prefix + "ster_x"
|
|
string s_ster_y = s_prefix + "ster_y"
|
|
|
|
nn = 401
|
|
make /n=(nn, nn) /d /o matrix
|
|
make /n=(nn, nn) /free mnorm
|
|
setscale /i x -2, +2, matrix, mnorm
|
|
setscale /i y -2, +2, matrix, mnorm
|
|
matrix = 0
|
|
mnorm = 0
|
|
|
|
variable ifold
|
|
for (ifold = 0; ifold < folding; ifold += 1)
|
|
ster_x = ster_rad * cos(d_azi * pi / 180)
|
|
ster_y = ster_rad * sin(d_azi * pi / 180)
|
|
ImageFromXYZ {ster_x, ster_y, values}, matrix, mnorm
|
|
d_azi = d_azi >= 180 ? d_azi + 360 / folding - 180 : d_azi + 360 / folding
|
|
endfor
|
|
|
|
matrix /= mnorm
|
|
matrixfilter /n=5 NanZapMedian matrix
|
|
matrixfilter /n=3 gauss matrix
|
|
|
|
if (!nograph)
|
|
display /k=1
|
|
appendimage matrix
|
|
modifygraph width={Plan,1,bottom,left}
|
|
endif
|
|
|
|
if (timerRefNum >= 0)
|
|
xyz_perf_secs = stopMSTimer(timerRefNum) / 1e6
|
|
endif
|
|
|
|
setdatafolder saveDF
|
|
end
|
|
|
|
/// save a hemispherical scan to an Igor text file
|
|
function save_hemi_scan(nickname, pathname, filename)
|
|
string nickname
|
|
string pathname
|
|
string filename
|
|
|
|
dfref savedf = getdatafolderdfr()
|
|
|
|
// source data
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
string s_theta = s_prefix + "th"
|
|
string s_tot = s_prefix + "tot"
|
|
string s_weight = s_prefix + "wt"
|
|
|
|
wave /sdfr=df theta1 = $s_theta
|
|
wave /sdfr=df polar1 = $s_polar
|
|
wave /sdfr=df azim1 = $s_azim
|
|
wave /sdfr=df tot1 = $s_tot
|
|
wave /sdfr=df weight1 = $s_weight
|
|
wave /sdfr=df values1 = $s_int
|
|
|
|
save /m="\r\n" /o /p=$pathname /t theta1, polar1, azim1, tot1, weight1, values1 as filename
|
|
|
|
setdatafolder saveDF
|
|
end
|
|
|
|
/// load a hemispherical scan from an Igor text file
|
|
///
|
|
/// @todo function not implemented
|
|
function load_hemi_scan(nickname, pathname, filename)
|
|
string nickname
|
|
string pathname
|
|
string filename
|
|
|
|
dfref savedf = getdatafolderdfr()
|
|
|
|
//loadwave /p=$pathname /t theta1, polar1, azim1, tot1, weight1, values1 as filename
|
|
//LoadWave /t/p=pearl_explorer_filepath/q filename
|
|
//svar waves = s_wavenames
|
|
//if (v_flag > 0)
|
|
// string /g pearl_explorer_import = "load_itx_file"
|
|
//endif
|
|
|
|
setdatafolder saveDF
|
|
end
|
|
|
|
/// import a hemispherical scan from theta-phi-intensity waves and display it
|
|
///
|
|
/// in the tpi format, the hemi scan data is represented
|
|
/// by a triple of flat one-dimensional waves
|
|
/// corresponding to the polar angle (theta), azimuthal angle (phi) and intensity.
|
|
/// no specific sort order is required.
|
|
///
|
|
/// @param nickname nick name for output data
|
|
/// @arg in default mode, this will become the name of a child folder containing the output.
|
|
/// @arg in XPDplot mode, this will become a prefix of the generated data in the root folder.
|
|
///
|
|
/// @param theta theta angles, 0 = normal emission.
|
|
///
|
|
/// @param phi phi angles, 0 = azimuthal origin. size = dimsize(data, 1)
|
|
///
|
|
/// @param intensity intensity wave, see requirements above.
|
|
///
|
|
/// @param npolar number of polar angles, determines polar and azimuthal step size.
|
|
/// default = 91 (1 degree steps)
|
|
///
|
|
/// @param folding rotational averaging.
|
|
/// example: 3 = average to 3-fold symmetry.
|
|
/// default = 1.
|
|
///
|
|
/// @param nograph display a new graph window?
|
|
/// @arg 0 (default) display a new polar graph
|
|
/// @arg 1 don't display a new graph
|
|
///
|
|
/// @param xpdplot XPDplot compatibility
|
|
/// @arg 0 (default) create waves in child folder $nickname
|
|
/// @arg 1 create waves in root folder (compatible with XPDplot)
|
|
///
|
|
function import_tpi_scan(nickname, theta, phi, intensity, [folding, npolar, nograph, xpdplot])
|
|
string nickname
|
|
wave theta
|
|
wave phi
|
|
wave intensity
|
|
|
|
variable folding
|
|
variable npolar
|
|
variable nograph
|
|
variable xpdplot
|
|
|
|
if (ParamIsDefault(npolar))
|
|
npolar = 91
|
|
endif
|
|
if (ParamIsDefault(nograph))
|
|
nograph = 0
|
|
endif
|
|
if (ParamIsDefault(folding))
|
|
folding = 1
|
|
endif
|
|
if (ParamIsDefault(xpdplot))
|
|
xpdplot = 0
|
|
endif
|
|
|
|
make_hemi_grid(npolar, nickname, xpdplot=xpdplot)
|
|
|
|
variable ifold
|
|
duplicate /free phi, fold_phi
|
|
for (ifold = 0; ifold < folding; ifold += 1)
|
|
fold_phi = fold_phi >= 360 ? fold_phi - 360 : fold_phi
|
|
hemi_add_anglescan(nickname, intensity, theta, fold_phi)
|
|
fold_phi += 360 / folding
|
|
endfor
|
|
|
|
if (nograph==0)
|
|
display_hemi_scan(nickname)
|
|
endif
|
|
end
|
|
|
|
/// trim a hemispherical scan at grazing angle
|
|
///
|
|
/// the function recalaculates the values wave from totals and weights
|
|
/// but sets elements above a given polar angle to nan.
|
|
///
|
|
/// @param nickname name of the scan dataset.
|
|
/// can be empty if no prefix is used.
|
|
/// the dataset must be in the current datafolder.
|
|
///
|
|
/// @param theta_max highest polar angle to keep (0...90 degrees).
|
|
///
|
|
function trim_hemi_scan(nickname, theta_max)
|
|
string nickname
|
|
variable theta_max
|
|
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_totals = s_prefix + "tot"
|
|
string s_weights = s_prefix + "wt"
|
|
string s_polar = s_prefix + "pol"
|
|
|
|
wave /sdfr=df w_polar = $s_polar
|
|
wave /sdfr=df w_values = $s_int
|
|
wave /sdfr=df w_totals = $s_totals
|
|
wave /sdfr=df w_weights = $s_weights
|
|
|
|
w_values = w_polar <= theta_max ? w_totals / w_weights : nan
|
|
end
|
|
|
|
/// extract a polar cut from a hemispherical scan.
|
|
///
|
|
/// for each polar angle, the function first extracts all azimuthal angles.
|
|
/// the intensity is then interpolated between the nearest neighbours of the given azimuth.
|
|
///
|
|
/// the hemi grid must have been created in the current data folder by the make_hemi_grid function.
|
|
/// correct ordering is required.
|
|
///
|
|
/// @param nickname name of the scan dataset.
|
|
/// can be empty if no prefix is used.
|
|
/// the dataset must be in the current datafolder.
|
|
///
|
|
/// @param azim azimuthal angle in degrees
|
|
///
|
|
/// @return reference of the created wave.
|
|
/// the wave has the same name as the intensity wave of the dataset
|
|
/// with the suffix "_azi" and the azimuthal angle rounded to integer.
|
|
/// it is created in the same datafolder as the original data.
|
|
///
|
|
function /wave hemi_polar_cut(nickname, azim)
|
|
string nickname
|
|
variable azim
|
|
|
|
dfref savedf = getdatafolderdfr()
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_totals = s_prefix + "tot"
|
|
string s_weights = s_prefix + "wt"
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
string s_index = s_prefix + "index"
|
|
string s_theta = s_prefix + "th"
|
|
string s_dphi = s_prefix + "dphi"
|
|
string s_nphis = s_prefix + "nphis"
|
|
string s_cut
|
|
sprintf s_cut, "%s_azi%03u", s_int, round(azim)
|
|
|
|
wave /sdfr=df w_polar = $s_polar
|
|
wave /sdfr=df w_azim = $s_azim
|
|
wave /sdfr=df w_values = $s_int
|
|
wave /sdfr=df w_totals = $s_totals
|
|
wave /sdfr=df w_weights = $s_weights
|
|
wave /sdfr=df w_index = $s_index
|
|
wave /sdfr=df w_theta = $s_theta
|
|
wave /sdfr=df w_dphi = $s_dphi
|
|
wave /sdfr=df w_nphis = $s_nphis
|
|
|
|
variable npol = numpnts(w_theta)
|
|
variable ipol
|
|
variable pol_st = abs(w_theta[1] - w_theta[0])
|
|
variable pol
|
|
variable pol1, pol2
|
|
variable nsel
|
|
|
|
setdatafolder df
|
|
make /n=(npol) /o $s_cut
|
|
wave w_cut = $s_cut
|
|
setscale /i x w_theta[0], w_theta[numpnts(w_theta)-1], "deg", w_cut
|
|
make /n=1 /free azi_slice
|
|
make /n=1 /free values_slice
|
|
|
|
for (ipol = 0; ipol < npol; ipol += 1)
|
|
pol = w_theta[ipol]
|
|
pol1 = pol - pol_st / 2
|
|
pol2 = pol + pol_st / 2
|
|
extract /free /indx w_polar, sel, (pol1 < w_polar) && (w_polar <= pol2)
|
|
nsel = numpnts(sel)
|
|
if (nsel > 0)
|
|
redimension /n=(nsel+2) azi_slice, values_slice
|
|
azi_slice[1, nsel] = w_azim[sel[p-1]]
|
|
azi_slice[0] = azi_slice[nsel] - 360
|
|
azi_slice[nsel+1] = azi_slice[1] + 360
|
|
values_slice[1, nsel] = w_values[sel[p-1]]
|
|
values_slice[0] = values_slice[nsel]
|
|
values_slice[nsel+1] = values_slice[1]
|
|
w_cut[ipol] = interp(azim, azi_slice, values_slice)
|
|
else
|
|
w_cut[ipol] = nan
|
|
endif
|
|
endfor
|
|
|
|
setdatafolder savedf
|
|
return w_cut
|
|
end
|
|
|
|
/// extract an azimuthal cut from a hemispherical scan
|
|
///
|
|
/// the function extracts all azimuthal angles that are present for the given polar angle.
|
|
///
|
|
/// the hemi grid must have been created in the current data folder by the make_hemi_grid function.
|
|
/// correct ordering is required.
|
|
///
|
|
/// @param nickname name of the scan dataset.
|
|
/// can be empty if no prefix is used.
|
|
/// the dataset must be in the current datafolder.
|
|
///
|
|
/// @param pol polar angle in degrees
|
|
///
|
|
/// @return reference of the created wave.
|
|
/// the wave has the same name as the intensity wave of the dataset
|
|
/// with the suffix "_azi" and the azimuthal angle rounded to integer.
|
|
/// it is created in the same datafolder as the original data.
|
|
///
|
|
function /wave hemi_azi_cut(nickname, pol)
|
|
string nickname
|
|
variable pol
|
|
|
|
dfref savedf = getdatafolderdfr()
|
|
string s_prefix = ""
|
|
string s_int = "values"
|
|
dfref df = find_hemi_data(nickname, s_prefix, s_int)
|
|
|
|
string s_totals = s_prefix + "tot"
|
|
string s_weights = s_prefix + "wt"
|
|
string s_polar = s_prefix + "pol"
|
|
string s_azim = s_prefix + "az"
|
|
string s_index = s_prefix + "index"
|
|
string s_theta = s_prefix + "th"
|
|
string s_dphi = s_prefix + "dphi"
|
|
string s_nphis = s_prefix + "nphis"
|
|
string s_cut
|
|
sprintf s_cut, "%s_pol%03u", s_int, round(pol)
|
|
|
|
wave /sdfr=df w_polar = $s_polar
|
|
wave /sdfr=df w_azim = $s_azim
|
|
wave /sdfr=df w_values = $s_int
|
|
wave /sdfr=df w_totals = $s_totals
|
|
wave /sdfr=df w_weights = $s_weights
|
|
wave /sdfr=df w_index = $s_index
|
|
wave /sdfr=df w_theta = $s_theta
|
|
wave /sdfr=df w_dphi = $s_dphi
|
|
wave /sdfr=df w_nphis = $s_nphis
|
|
|
|
variable pol_st = abs(w_theta[1] - w_theta[0])
|
|
variable pol1, pol2
|
|
variable nsel
|
|
|
|
pol1 = pol - pol_st / 2
|
|
pol2 = pol + pol_st / 2
|
|
extract /free /indx w_polar, sel, (pol1 < w_polar) && (w_polar <= pol2)
|
|
nsel = numpnts(sel)
|
|
if (nsel > 0)
|
|
setdatafolder df
|
|
make /n=(nsel) /o $s_cut
|
|
wave w_cut = $s_cut
|
|
w_cut = w_values[sel]
|
|
setscale /i x w_azim[sel[0]], w_azim[sel[nsel-1]], "°", w_cut
|
|
setdatafolder savedf
|
|
return w_cut
|
|
else
|
|
setdatafolder savedf
|
|
return $""
|
|
endif
|
|
setdatafolder savedf
|
|
end
|
|
|
|
static function check_contrast(values, pcmin, pcmax, vmin, vmax, sym)
|
|
wave values
|
|
variable pcmin
|
|
variable pcmax
|
|
variable &vmin
|
|
variable &vmax
|
|
variable sym
|
|
|
|
dfref save_df = GetDataFolderDFR()
|
|
dfref dfr = NewFreeDataFolder()
|
|
setdatafolder dfr
|
|
StatsQuantiles /inan /iw /q /z values
|
|
wave /z index = w_quantilesindex
|
|
setdatafolder save_df
|
|
|
|
if (waveexists(index))
|
|
variable imin = round(numpnts(index) * pcmin / 100)
|
|
variable imax = round(numpnts(index) * (100 - pcmax) / 100)
|
|
vmin = values[index[imin]]
|
|
vmax = values[index[imax]]
|
|
if (sym)
|
|
variable d = vmax - vmin
|
|
if ((vmax >= d/4) && (-vmin >= d/4))
|
|
vmax = min(abs(vmin), abs(vmax))
|
|
vmin = -vmax
|
|
endif
|
|
endif
|
|
else
|
|
vmin = wavemin(values)
|
|
vmax = wavemax(values)
|
|
endif
|
|
end
|
|
|
|
/// set the pseudocolor contrast by percentile.
|
|
///
|
|
/// set the minimum and maximum values of the pseudocolor scale
|
|
/// such that a specified percentile of the distribution lies outside the limits.
|
|
///
|
|
/// the new contrast is applied to traces and images of the selected graph
|
|
/// that have pseudocolor tables.
|
|
///
|
|
/// the function is not specific to angle scans.
|
|
/// it can be used for any pseudocolor trace or image plots except contour plots.
|
|
///
|
|
/// @param pcmin percentile below the minimum color (0-100).
|
|
/// @param pcmax percentile above the maximum color (0-100).
|
|
/// @param graphname name of graph. default: top graph.
|
|
/// @param colortable name of new colortable. default: keep current table.
|
|
/// @param reversecolors reverse colors of new colorable.
|
|
/// takes effect only if colortable argument is defined.
|
|
/// @arg 0 (default) normal colors,
|
|
/// @arg 1 reverse color table
|
|
/// @param symmetric make scale symmetric about zero (for modulation functions, e.g.).
|
|
/// @arg 0 (default) do not enforce symmetry.
|
|
/// @arg 1 try symmetric scale if "reasonable".
|
|
///
|
|
function set_contrast(pcmin, pcmax, [graphname, colortable, reversecolors, symmetric])
|
|
variable pcmin
|
|
variable pcmax
|
|
string graphname
|
|
string colortable
|
|
variable reversecolors
|
|
variable symmetric
|
|
|
|
if (ParamIsDefault(graphname))
|
|
graphname = ""
|
|
endif
|
|
if (ParamIsDefault(colortable))
|
|
colortable = ""
|
|
endif
|
|
if (ParamIsDefault(reversecolors))
|
|
reversecolors = 0
|
|
endif
|
|
if (ParamIsDefault(symmetric))
|
|
symmetric = 0
|
|
endif
|
|
|
|
dfref save_df = GetDataFolderDFR()
|
|
|
|
string objname
|
|
string info
|
|
string wname
|
|
string ctab
|
|
variable rev
|
|
variable n
|
|
variable i
|
|
variable vmin
|
|
variable vmax
|
|
|
|
string traces = TraceNameList(graphname, ";", 1+4)
|
|
n = ItemsInList(traces, ";")
|
|
for (i = 0; i < n; i += 1)
|
|
objname = StringFromList(i, traces, ";")
|
|
info = TraceInfo(graphname, objname, 0)
|
|
if (strlen(info) > 0)
|
|
info = StringByKey("RECREATION", info, ":", ";")
|
|
info = StringByKey("zColor(x)", info, "=", ";")
|
|
if (strlen(info) > 2)
|
|
info = info[1,strlen(info)-2]
|
|
wname = StringFromList(0, info, ",")
|
|
wave w = $wname
|
|
ctab = StringFromList(3, info, ",")
|
|
rev = str2num("0" + StringFromList(4, info, ","))
|
|
if (strlen(colortable) > 0)
|
|
ctab = colortable
|
|
rev = reversecolors
|
|
endif
|
|
check_contrast(w, pcmin, pcmax, vmin, vmax, symmetric)
|
|
if (vmax > vmin)
|
|
ModifyGraph /w=$graphname zColor($objname)={w, vmin, vmax, $ctab, rev}
|
|
endif
|
|
endif
|
|
endif
|
|
endfor
|
|
|
|
string images = ImageNameList(graphname, ";")
|
|
n = ItemsInList(images, ";")
|
|
for (i = 0; i < n; i += 1)
|
|
objname = StringFromList(i, images, ";")
|
|
wave w = ImageNameToWaveRef(graphname, objname)
|
|
info = ImageInfo(graphname, objname, 0)
|
|
if (strlen(info) > 0)
|
|
info = StringByKey("RECREATION", info, ":", ";")
|
|
info = StringByKey("ctab", info, "=", ";")
|
|
if (strlen(info) > 2)
|
|
info = info[1,strlen(info)-2]
|
|
ctab = StringFromList(2, info, ",")
|
|
rev = str2num("0" + StringFromList(3, info, ","))
|
|
if (strlen(colortable) > 0)
|
|
ctab = colortable
|
|
rev = reversecolors
|
|
endif
|
|
check_contrast(w, pcmin, pcmax, vmin, vmax, symmetric)
|
|
if (vmax > vmin)
|
|
ModifyImage /w=$graphname $objname ctab={vmin, vmax, $ctab, rev}
|
|
endif
|
|
endif
|
|
endif
|
|
endfor
|
|
|
|
setdatafolder save_df
|
|
end
|
|
|
|
/// k-space mapping of 2D angle-energy distribution (scienta image)
|
|
///
|
|
/// courtesy of F. Matsui
|
|
///
|
|
/// @param inwave 2D wave, x = kinetic energy (eV), y = polar angle (deg)
|
|
/// note: the kinetic energy is with reference to the vacuum level at the sample.
|
|
/// if the work functions of the analyser and the sample differ:
|
|
/// Ekin,sample = Ekin,analyser + WFanalyser - WFsample
|
|
/// where WFanalyser = Ephot - EFermi
|
|
///
|
|
/// @return the output wave has the name of the input wave with the suffix "_k".
|
|
///
|
|
Function AngleToK(inwave)
|
|
Wave inwave
|
|
String newname = NameofWave(inwave)+"_k"
|
|
Duplicate/O inwave, $newname
|
|
Wave outwave = $newname
|
|
Variable rows,columns,xdelta,xoffset,ydelta,yoffset,kmin,kmax,Emax
|
|
// inwave parameters
|
|
rows = DimSize(inwave,0)
|
|
columns = DimSize(inwave,1)
|
|
xdelta = DimDelta(inwave,0)
|
|
xoffset = DimOffset(inwave,0)
|
|
ydelta = DimDelta(inwave,1)
|
|
yoffset = DimOffset(inwave,1)
|
|
Emax= xoffset + xdelta*(rows-1)
|
|
kmin = 0.5123*sqrt(Emax)*sin(pi/180*(yoffset)) // calculate the k boundaries
|
|
kmax = 0.5123*sqrt(Emax)*sin(pi/180*(yoffset+(columns-1)*ydelta))
|
|
SetScale/I y kmin,kmax,"Ang^-1", outwave
|
|
// scale the y axis
|
|
outwave = interp2D(inwave, x, 180/pi*asin(y/ (0.5123*sqrt(x)))) // recalculate to k
|
|
outwave = (NumType(outwave)==2) ? 0 : outwave // replace NaNs (optional)
|
|
End
|