From 6ab1cefec22e21d7b73a12b50e9477e6a1696a2e Mon Sep 17 00:00:00 2001 From: matthias muntwiler Date: Wed, 14 Aug 2019 17:56:14 +0200 Subject: [PATCH] igor 8 compatibility --- README.md | 12 +- pearl/pearl-area-display.ipf | 11 +- pearl/pearl-area-import.ipf | 242 ++++++++++++++++++- pearl/pearl-arpes.ipf | 13 +- pearl/pearl-compat.ipf | 57 +++++ pearl/pearl-data-explorer.ipf | 5 +- pearl/pearl-pshell-import.ipf | 24 +- pearl/preferences/pearl_elog/preferences.pxp | Bin 12204 -> 6699 bytes 8 files changed, 339 insertions(+), 25 deletions(-) create mode 100644 pearl/pearl-compat.ipf diff --git a/README.md b/README.md index 2dea65b..735f244 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,10 @@ PEARL Procedures should be installed according to the regular Igor Pro guideline - Find the `HDF5.XOP` (`HDF5-64.xop` for Igor 7 64-bit) extension in the `Igor Pro Folder` under `More Extensions/File Loaders` (`More Extensions (64-bit)/File Loaders`), create a shortcut, and move the shortcut to the `Igor Extensions` folder next to your `User Procedures` folder. - Find the `HDF5 Help.ihf` next to `HDF5.XOP`, create a shortcut, and move the shortcut to the `Igor Help Files` folder next to your `User Procedures` folder. -PEARL Procedures has been tested under Igor Pro version 6.37 (32-bit). Older versions prior to 6.36 are not be compatible. Please update to the latest Igor Pro 6 version before reporting any problems. - -PEARL Procedures compiles under Igor 7.00. Some features, in particular 3D graphics, may not work properly. +PEARL Procedures are compatible with Igor Pro versions 6-8, 32-bit and 64-bit. +They are not compatible with Igor 6.36 and older. +As long as no Igor 8 specific features are used, the produced experiment files remain compatible with Igor 6. +Some features, in particular 3D graphics, may not work properly under Igor 7 and newer. License @@ -40,6 +41,11 @@ Copyright 2009-2019 by [Paul Scherrer Institut](http://www.psi.ch) Release Notes ============= +## rev-distro-2.1.0 + +- Check compatibility of major features with Igor 8. +- pshell-import does not apply a detector sensitivity scaling any more. The returned intensities have arbitrary units. + ## rev-distro-2.0.3 - The interpolate_hemi_scan function now requires a projection argument unless stereographic projection is desired. diff --git a/pearl/pearl-area-display.ipf b/pearl/pearl-area-display.ipf index b84587e..b6ef24b 100644 --- a/pearl/pearl-area-display.ipf +++ b/pearl/pearl-area-display.ipf @@ -2,6 +2,7 @@ #pragma IgorVersion = 6.2 #pragma ModuleName = PearlAreaDisplay #pragma version = 1.04 +#include "pearl-compat" /// @file /// @brief visualization tools for 2D and 3D data. @@ -63,7 +64,7 @@ static function /s graphname_from_dfref(df, prefix) name = ReplaceString("root:", name, "") name = name[0, strlen(name) - 2] name = ReplaceString(" ", name, "") - name = CleanupName(prefix + name, 0) + name = PearlCleanupName(prefix + name) if (CheckName(name, 6)) name = UniqueName(name, 6, 0) endif @@ -179,7 +180,7 @@ function /s ad_display_profiles(image, [filter]) dfref imagedf = GetWavesDataFolderDFR(image) string s_imagedf = GetDataFolder(1, imagedf) setdatafolder imagedf - string s_viewdf = CleanupName("view_" + NameOfWave(image), 0) + string s_viewdf = PearlCleanupName("view_" + NameOfWave(image)) newdatafolder /o/s $s_viewdf dfref viewdf = GetDataFolderDFR() s_viewdf = GetDataFolder(1, viewdf) @@ -566,7 +567,7 @@ static function /df make_view_folder(source) dfref imagedf = GetWavesDataFolderDFR(source) string s_imagedf = GetDataFolder(1, imagedf) setdatafolder imagedf - string s_viewdf = CleanupName("view_" + NameOfWave(source), 0) + string s_viewdf = PearlCleanupName("view_" + NameOfWave(source)) newdatafolder /o/s $s_viewdf dfref viewdf = GetDataFolderDFR() @@ -590,7 +591,7 @@ static function /df get_view_folder(source) dfref imagedf = GetWavesDataFolderDFR(source) dfref viewdf setdatafolder imagedf - string s_viewdf = CleanupName("view_" + NameOfWave(source), 0) + string s_viewdf = PearlCleanupName("view_" + NameOfWave(source)) if (DataFolderExists(s_viewdf)) setdatafolder $s_viewdf viewdf = GetDataFolderDFR() @@ -1362,7 +1363,7 @@ function /s ad_display_slice(data) if (exists("slice_graphname") != 2) string /g slice_graphname = "" endif - string /g slice_wavename = CleanupName("slice_" + NameOfWave(data), 0) + string /g slice_wavename = PearlCleanupName("slice_" + NameOfWave(data)) svar graphname = slice_graphname svar slicename = slice_wavename diff --git a/pearl/pearl-area-import.ipf b/pearl/pearl-area-import.ipf index 061a3c9..c2c8af9 100644 --- a/pearl/pearl-area-import.ipf +++ b/pearl/pearl-area-import.ipf @@ -2,6 +2,7 @@ #pragma IgorVersion = 6.2 #pragma ModuleName = PearlAreaImport #include +#include "pearl-compat" #include "pearl-gui-tools" // copyright (c) 2013-18 Paul Scherrer Institut @@ -46,7 +47,7 @@ static function BeforeFileOpenHook(refNum,fileName,path,type,creator,kind) //PathInfo $path //string FilePath = s_path + filename - string NickName = CleanupName(ParseFilePath(3, FileName, ":", 0, 0), 0) + string NickName = PearlCleanupName(ParseFilePath(3, FileName, ":", 0, 0)) string FileExt = LowerStr(ParseFilePath(4, FileName, ":", 0, 0)) string result = "" @@ -149,7 +150,7 @@ function /s ad_suggest_foldername(filename, [ignoredate,sourcename,unique]) sprintf nickname, "%s_%s_%s", sourcename, datepart, indexpart endif else - nickname = CleanupName(basename, 0) + nickname = PearlCleanupName(basename) endif if (unique && CheckName(nickname, 11)) @@ -1175,6 +1176,243 @@ function /s adh5_test_reduction_func(source, reduction_func, reduction_param, re return reduction_param end +/// reduce a three-dimensional intensity distribution +/// +/// this function reduces a three-dimensional intensity distribution +/// to a two-dimensional intensity map. +/// the given reduction function is applied once on each Z section. +/// +/// @param source source wave. +/// three-dimensional intensity distribution (image). +/// the scales are carried over to the result waves. +/// +/// @param reduction_func name of the reduction function to apply to the source data. +/// +/// @param reduction_param string with reduction parameters as required by the specific reduction function. +/// +/// @param result_prefix name prefix of result waves. +/// a numeric index is appended to distinguish the results. +/// the index starts at 1. existing waves are overwritten. +/// +/// +function adh5_reduce_brick(source, reduction_func, reduction_param, result_prefix, [progress, nthreads]) + wave source + funcref adh5_default_reduction reduction_func + string reduction_param + string result_prefix + + variable progress + variable nthreads + + if (ParamIsDefault(progress)) + progress = 1 + endif + if (ParamIsDefault(nthreads)) + nthreads = -1 + endif + variable result = 0 + + // nx and nz are the image dimensions + variable nx, ny, nz, nt + nx = dimsize(source, 0) + ny = dimsize(source, 1) + nz = dimsize(source, 2) + // force 4th dimension to singleton (ad_extract_slab handles 3 dimensions only) + nt = 0 + + variable nzt = max(nz, 1) * max(nt, 1) + variable izt + + // set up multi threading + if (nthreads < 0) + nthreads = ThreadProcessorCount + endif + if (nthreads > 0) + variable threadGroupID = ThreadGroupCreate(nthreads) + variable ithread + for (ithread = 0; ithread < nthreads; ithread += 1) + ThreadStart threadGroupID, ithread, reduce_slab_worker(reduction_func) + endfor + else + make /n=(nzt) /df /free processing_folders + endif + + if (progress) + display_progress_panel("data reduction", "extracting data (step 1 of 2)...", nzt) + endif + + variable iz, it + string dfname + variable iw, nw + string sw + make /n=0 /free /wave result_waves + + izt = 0 + for (iz = 0; iz < max(nz, 1); iz += 1) + for (it = 0; it < max(nt, 1); it += 1) + dfname = "processing_" + num2str(izt) + newdatafolder /s $dfname + ad_extract_slab(source, nan, nan, nan, nan, iz, iz, "image", pscale=1) + wave image + + // send to processing queue + variable /g r_index = iz + variable /g s_index = it + string /g func_param = reduction_param + + if (nthreads > 0) + WaveClear image + ThreadGroupPutDF threadGroupID, : + else + processing_folders[izt] = GetDataFolderDFR() + string param = reduction_param + wave /wave reduced_waves = reduction_func(image, param) + variable /g func_result = numpnts(reduced_waves) + adh5_get_result_waves(reduced_waves, "redw_", 0) + WaveClear image, reduced_waves + setdatafolder :: + endif + + izt += 1 + // progress window + if (progress) + if (update_progress_panel(izt)) + result = -4 // user abort + break + endif + endif + endfor + endfor + + if (progress) + update_progress_panel(0, message="processing data (step 2 of 2)...") + endif + + dfref dfr + for (izt = 0; (izt < nzt) && (result == 0); izt += 1) + if (nthreads > 0) + do + dfr = ThreadGroupGetDFR(threadGroupID, 1000) + if (DatafolderRefStatus(dfr) != 0) + break + endif + if (progress) + if (update_progress_panel(izt)) + result = -4 // user abort + break + endif + endif + while (1) + else + dfr = processing_folders[izt] + if (progress) + if (update_progress_panel(izt)) + result = -4 // user abort + break + endif + endif + endif + + if (result != 0) + break + endif + + nvar rr = dfr:r_index + nvar ss = dfr:s_index + nvar func_result = dfr:func_result + + if (func_result < 1) + result = -3 // dimension reduction error + break + endif + + if (numpnts(result_waves) == 0) + redimension /n=(func_result) result_waves + for (iw = 0; iw < func_result; iw += 1) + sw = "redw_" + num2str(iw) + wave profile = dfr:$sw + sw = "ReducedData" + num2str(iw+1) + make /n=(dimsize(profile, 0), nz, nt) /d /o $sw + wave data = $sw + setdimlabel 0, -1, $getdimlabel(profile, 0, -1), data + setscale /p x dimoffset(profile, 0), dimdelta(profile, 0), waveunits(profile, 0), data + setscale /p y dimoffset(source, 2), dimdelta(source, 2), waveunits(source, 2), data + setscale /p z dimoffset(source, 3), dimdelta(source, 3), waveunits(source, 3), data + setscale d 0, 0, waveunits(profile, -1), data + result_waves[iw] = data + endfor + endif + for (iw = 0; iw < func_result; iw += 1) + sw = "redw_" + num2str(iw) + wave profile = dfr:$sw + wave data = result_waves[iw] + data[][rr][ss] = profile[p] + endfor + endfor + + if (nthreads > 0) + variable tstatus = ThreadGroupRelease(threadGroupID) + if (tstatus == -2) + result = -5 // thread did not terminate properly + endif + else + for (izt = 0; izt < nzt; izt += 1) + KillDataFolder /Z processing_folders[izt] + endfor + endif + + if (progress) + kill_progress_panel() + endif + + return result +end + +/// thread worker for adh5_reduce_brick +/// +/// this function polls job data folders from thread group 0 +/// and calls the reduction function on their contents. +/// the result waves have prefix "redw_" and are saved in the job folder. +/// +threadsafe static function reduce_brick_worker(reduction_func) + funcref adh5_default_reduction reduction_func + do + // wait for job from main thread + do + dfref dfr = ThreadGroupGetDFR(0, 1000) + if (DataFolderRefStatus(dfr) == 0) + if (GetRTError(2)) + return 0 // no more jobs + endif + else + break + endif + while (1) + + // get input data + wave image = dfr:image + svar func_param = dfr:func_param + nvar rr = dfr:r_index + nvar ss = dfr:s_index + + // do the work + newdatafolder /s outDF + variable /g r_index = rr + variable /g s_index = ss + string param = func_param + wave /wave reduced_waves = reduction_func(image, param) + variable /g func_result = numpnts(reduced_waves) + + // send output to queue and clean up + adh5_get_result_waves(reduced_waves, "redw_", 0) + WaveClear image, reduced_waves + ThreadGroupPutDF 0, : + KillDataFolder dfr + while (1) + + return 0 +end + /// copy waves from wave reference wave into current data folder /// /// this function copies waves that are referenced in a wave reference wave into the current data folder. diff --git a/pearl/pearl-arpes.ipf b/pearl/pearl-arpes.ipf index ee8cc16..473557c 100644 --- a/pearl/pearl-arpes.ipf +++ b/pearl/pearl-arpes.ipf @@ -6,6 +6,7 @@ #include "pearl-area-profiles" // data processing for multi-dimensional datasets #include "pearl-area-import" // import data files generated by area detector software #include "pearl-pshell-import" +#include "pearl-compat" // compatibility with igor 6 #include "pearl-data-explorer" // preview and import panel for PEARL data #include "pearl-anglescan-process" // angle scan (XPD) processing functions #include "pearl-anglescan-panel" // panel interface to angle scan processing @@ -16,18 +17,9 @@ #include "pearl-area-live" // live view of area detector #include "pearl-epics" // EPICS access under Igor #include "pearl-arpes-scans" // run ARPES scans under Igor +#include "pearl-sample-tracker" // live tracking and adjustment of sample position #endif -// $Id$ -// -// author: matthias.muntwiler@psi.ch -// Copyright (c) 2012-15 Paul Scherrer Institut -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 - /// @file /// @brief data acquisition and analysis package for ARPES at PEARL. /// @@ -77,6 +69,7 @@ /// * pearl-area-live.ipf /// * pearl-epics.ipf /// * pearl-arpes-scans.ipf +/// * pearl-sample-tracker.ipf /// /// initializes package data once when the procedure is first loaded diff --git a/pearl/pearl-compat.ipf b/pearl/pearl-compat.ipf new file mode 100644 index 0000000..6264760 --- /dev/null +++ b/pearl/pearl-compat.ipf @@ -0,0 +1,57 @@ +#pragma rtGlobals=3 // Use modern global access method and strict wave access. +#pragma IgorVersion = 6.1 +#pragma ModuleName = PearlCompat +#pragma version = 1.01 + +// copyright (c) 2019 Paul Scherrer Institut +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http:///www.apache.org/licenses/LICENSE-2.0 + +/// @file +/// @brief compatibility procedures for igor 8 +/// @ingroup ArpesPackage +/// +/// +/// the compatibility procedures ensure that igor experiments +/// created with the PEARL procedures under igor 8 +/// can be opened with earlier igor versions (>= 6.34). +/// +/// the following possible issues are addressed: +/// +/// @arg length of object names + +/// @namespace PearlCompat +/// @brief compatibility procedures for igor 8 +/// +/// PearlCompat is declared in @ref pearl-compat.ipf. + + +// Compatible CleanupName function +// +// Igor 8's CleanupName may return long object names (> 31 characters). +// This breaks compatibility with earlier Igor versions. +// Experiments that include waves, folders, windows etc. with long names +// cannot be loaded with an earlier version. +// +// This is a drop-in replacement function for CleanupName. +// In addition to the behaviour of CleanupName, +// this replacement ensures that names are limited to 31 characters. +// +// @param name object name to clean up +// +// @return (str) clean object name +// +function /s PearlCleanupName(name) + string name + +#if IgorVersion() >= 8.00 + // note: this function is not threadsafe + return CleanupName(name, 0, 31) +#else + return CleanupName(name, 0) +#endif + +end diff --git a/pearl/pearl-data-explorer.ipf b/pearl/pearl-data-explorer.ipf index 338bf41..40f5c77 100644 --- a/pearl/pearl-data-explorer.ipf +++ b/pearl/pearl-data-explorer.ipf @@ -5,6 +5,7 @@ #include "pearl-area-import" #include "pearl-area-profiles" #include "pearl-area-display" +#include "pearl-compat" #include "pearl-pshell-import" #if exists("MFR_OpenResultFile") #include "pearl-matrix-import" @@ -826,7 +827,7 @@ static function attributes_notebook(attr_names, attr_values, title) setdatafolder $package_path wave /t/z attr_filter, attr_filter_summary - string name = CleanupName("nb_" + title[0,28], 0) + string name = PearlCleanupName("nb_" + title[0,27]) if (WinType(name) == 5) Notebook $name selection={startOfFile, endOfFile} Notebook $name text="" @@ -1302,7 +1303,7 @@ function /s itx_suggest_foldername(filename, [ignoredate,sourcename,unique]) sprintf nickname, "%s_%s_%s", sourcename, datepart, indexpart endif else - nickname = CleanupName(basename, 0) + nickname = PearlCleanupName(basename) endif if (unique && CheckName(nickname, 11)) diff --git a/pearl/pearl-pshell-import.ipf b/pearl/pearl-pshell-import.ipf index 934cdaf..280d3c4 100644 --- a/pearl/pearl-pshell-import.ipf +++ b/pearl/pearl-pshell-import.ipf @@ -2,6 +2,7 @@ #pragma IgorVersion = 6.36 #pragma ModuleName = PearlPShellImport #include +#include "pearl-compat" #include "pearl-gui-tools" #include "pearl-area-import" @@ -83,7 +84,7 @@ strconstant kScientaScalingDatasets = "LensMode;ScientaChannelBegin;ScientaChann strconstant kTransposedDatasets = "ScientaImage;" /// multiply scienta detector intensity by this value to get actual counts. -constant kDetectorSensitivity = 4 +constant kDetectorSensitivity = 1 /// open a HDF5 file created by the PShell data acquisition program and prepare the data folder. /// @@ -204,7 +205,7 @@ function /s psh5_load_complete(ANickName, APathName, AFileName, [load_data, load sg = StringFromList(ig, s_scanpaths, ";") folder = ReplaceString("/", sg, "") folder = ReplaceString(" ", folder, "") - folder = CleanupName(folder, 0) + folder = PearlCleanupName(folder) setdatafolder fileDF newdatafolder /s /o $folder psh5_load_scan_complete(fileID, sg, load_data=load_data, load_attr=load_attr) @@ -606,6 +607,9 @@ end /// - Writables (ScanWritables) /// - Readables (ScanReadables) /// - Steps (ScanSteps) +/// - Iterations (ScanIterations) - if present (XPSSpectrum script) +/// - Step Size (ScanStepSize) - if present (XPSSpectrum script) +/// - Step Time (ScanStepTime) - if present (XPSSpectrum script) /// /// if they are missing in the file, `ScanDimensions` and `ScanReadables` are set to default values /// assuming the file contains a single spectrum. @@ -649,6 +653,20 @@ function /s psh5_load_scan_meta(fileID, scanpath) endif wavenames = ReplaceString(";;", wavenames, ";") + // additional attributes from XPSSpectrum.py + HDF5LoadData /O /Q /Z /A="Iterations" /N=ScanIterations /TYPE=1 fileID, scanpath + if (!v_flag) + wavenames = AddListItem(s_wavenames, wavenames, ";", inf) + endif + HDF5LoadData /O /Q /Z /A="Step Size" /N=ScanStepSize /TYPE=1 fileID, scanpath + if (!v_flag) + wavenames = AddListItem(s_wavenames, wavenames, ";", inf) + endif + HDF5LoadData /O /Q /Z /A="Step Time" /N=ScanStepTime /TYPE=1 fileID, scanpath + if (!v_flag) + wavenames = AddListItem(s_wavenames, wavenames, ";", inf) + endif + return wavenames end @@ -1933,7 +1951,7 @@ function /s psh5_load_reduced(ANickName, APathName, AFileName, reduction_func, r scanpath = StringFromList(ig, s_scanpaths) folder = ReplaceString("/", scanpath, "") folder = ReplaceString(" ", folder, "") - folder = CleanupName(folder, 0) + folder = PearlCleanupName(folder) setdatafolder fileDF newdatafolder /s /o $folder dfref dataDF = GetDataFolderDFR() diff --git a/pearl/preferences/pearl_elog/preferences.pxp b/pearl/preferences/pearl_elog/preferences.pxp index 9d82a6db90f0675dd5e9d21e6b10ed44eaea143f..995298c9cd3f8228db85bde30db7102504630a74 100644 GIT binary patch literal 6699 zcmeHMPj4hg6|clV5~4E<OhR& zTB9x7*=*Pw4W~t&&3pF7y_RDcVgCs~FOb*C3jY56n?HQ|$v=PlZu>vK`{}R#@#lB1 z;^SH8lK3huGPJ)|p!&ufeNgAJ|Jfz1m5Q^SD+xW@qqu$>B{YPeZQ=ExOtaQXfQG*0>HY*z{P>n?J zG-xJDHvNgpMq`6qu76!zrQ{LxWCFiy0LBT%a$*dzryw|J$3xAdQs_q+ibL4|j_9TY`syj08n=(3xU;z20^)^NYS$0DX>IKP%wflQ^Zo#rE|? zq`5pG!a5cs(idE1sW8rqdATxRw@7%(jr^-uo{m#~BG^7|C6rH!xlY7`nUaca8|#mg z97mtaNQ;O^P^Y0#nswtapD6gLZbp%%in>J}4m@^<@58}1d)UFV-`!?|$u{d8nAhg? ztkd(DH|*oIHR5(J$Y`#Bt~r=daW_#$4;m&q*c(o(;jFjU z?ac;sr6Rc#%#2<%C+))eT!$5hItqnSxCio-h5wB#vOJ^5!Hg>4uy%U%&e{4J_4a7n zUZcHaLS4FH1HA9FY}FaMy8~cd%r93ii!J zvu*Hkr7&pQmSs#f%tAWD_-(ssw`myC+Xa@!roDP$K?4i!E-b+GH%kv&IaF_^w5ZQ& z;GPNj{YvFfU%qLFx>WApuH1&6C2*rT$BWK#>89^4I7{ON%~s(T3a5%oOE`dckSSYg=;wB_#UG0}(`+~L<=8lv z+%`*o>mAa^2ymYBemYLik{0nM}8;9*PV-?xD*$xUuV64C*n+M zssi(4BEqTE6B>zAY8-JWv`Cfp#LkUwqwTa|hRTeL5joCgyKPx8X(}ERM!iO1_0E}N z6%M_dr73Oz>&G%43wj%NPovK28a)ul=aUEh!(uAwtXe-8;2;n)aPi3O5xg}q=Xqa;XEvm~-<|)^+q*5QuE&rwGV)>Oa z{5Pr8Qv1HiRB9=~|ASQO$;DLas~pPY=Qa7w|00LFY`9$>_$D1HQt~oRFFZ^r?w~d{ zWi27!xM_o2n%51gP&_X_irNN4Q$fNBPo>G?Sjhbe5^DBgJSy`yxLG~H0+k{C$>BV~ zUnqK(QVbU2cwNy`QOEq80mS->2fWVNK%JDAU^(%8t>GmfRVC3vUa*v#?~{_2TRv5F zQlzu_iLWh!USBT43LmMdo!%hZeeV!)bQ?jm@8Ns6GuR)Zy6IMw>$ZN21c86X`XWV* zgK&IV@rv-jplO#tqjR01< z-zGrEI=(LwG^0_OnVuei-N>|nP7u%(UZU`s;$@}T)zdVAA3=TJd52d2L2ywMjpI}G z{&0=%-rqlXfVu+_)7@y=Ep!ZciZFtXhGE{_MVlu;9g-KVLvf~ikspsykGZ!>ENFbU zTesHeL@M-AW>>C(ApBDqUAYETl3Yi7`t~`>vr4n&}l)*hQw=L|J1Yg-(1gzO#Et@KtWl{~m*Sud4-18v{@-v&QO&_)Y6P4)dC9`QiA&MP2% a2$n*WW@$uE^7oYEPSIFF={JeTP3tpqhl@r4 literal 12204 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