code changes for release 3.1.0: SciLog, DA30-PShell data files

This commit is contained in:
2025-12-22 17:04:08 +01:00
parent 43689ef162
commit ea838b395f
12 changed files with 2437 additions and 169 deletions

View File

@@ -0,0 +1,46 @@
name: build and deploy documentation
on:
push:
branches:
- distro
jobs:
build-and-deploy:
runs-on: ubuntu-latest
container:
image: gitea.psi.ch/pearl/docs
credentials:
username: ${{ gitea.actor }}
password: ${{ secrets.package_token }}
steps:
- name: checkout
working-directory: /app
run: |
git clone --branch master --single-branch https://${{ secrets.REPO_TOKEN }}@gitea.psi.ch/${{ github.repository }}.git
- name: build
working-directory: /app/igor-procs/doc
run: |
REVISION=$(git describe --always --tags --dirty --long || date +"%F %T %z")
export REVISION
doxygen config.dox
mv html/ /app/
- name: configure git
working-directory: /app/igor-procs
run: |
git config --global user.name "Gitea Actions"
git config --global user.email "actions@gitea.local"
- name: push to gitea-pages
working-directory: /app/igor-procs
run: |
git checkout --orphan gitea-pages
git reset --hard
cp -r /app/html/* .
git add .
git commit -m "Deploy documentation to gitea"
git push -f https://${{ secrets.REPO_TOKEN }}@gitea.psi.ch/${{ github.repository }}.git gitea-pages

View File

@@ -36,12 +36,18 @@ Matthias Muntwiler, <mailto:matthias.muntwiler@psi.ch>
Copyright
---------
Copyright 2009-2022 by [Paul Scherrer Institut](http://www.psi.ch)
Copyright 2009-2025 by [Paul Scherrer Institut](http://www.psi.ch)
Release Notes
=============
## rev-distro-3.1.0
- Ingestor to SciLog electronic logbook
- Support for PShell files from DA30/DFS30 analyser
- Fix Gizmo window in Igor 8 and higher
## rev-distro-3.0.0
- New panel and procedure interface for PShell data file import.

View File

@@ -1581,7 +1581,7 @@ EXTRA_SEARCH_MAPPINGS =
# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.
# The default value is: YES.
GENERATE_LATEX = YES
GENERATE_LATEX = NO
# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a
# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of

View File

@@ -1,8 +1,8 @@
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3 // Use modern global access method and strict wave access.
#pragma IgorVersion = 6.2
#pragma IgorVersion = 7.0
#pragma ModuleName = PearlAreaDisplay
#pragma version = 1.04
#pragma version = 1.05
#include "pearl-compat"
#include "pearl-area-profiles"
@@ -1161,7 +1161,7 @@ function /s ad_display_brick(data)
string /g gizmo_graphname = graphname_from_dfref(datadf, "giz_")
svar graphname = gizmo_graphname
if ((strlen(graphname) > 0) && (wintype(graphname) == 13))
if ((strlen(graphname) > 0) && (wintype(graphname) == 17))
setdatafolder savedf
return graphname // gizmo window exists
endif
@@ -1380,7 +1380,7 @@ function ad_brick_slicer(data)
z_slice_pos = dimoffset(data, 2) + dimsize(data, 2) * dimdelta(data, 2) / 2
svar /z /sdfr=viewdf gizmo_graphname
if (svar_exists(gizmo_graphname) && (strlen(gizmo_graphname) > 0) && (wintype(gizmo_graphname) == 13))
if (svar_exists(gizmo_graphname) && (strlen(gizmo_graphname) > 0) && (wintype(gizmo_graphname) == 17))
ad_gizmo_set_plane(data, 0, x_slice_pos)
ad_gizmo_set_plane(data, 1, y_slice_pos)
ad_gizmo_set_plane(data, 2, z_slice_pos)
@@ -1495,7 +1495,7 @@ function ad_gizmo_set_plane(brick, dim, value)
return -1 // requested value out of range
endif
if (svar_exists(graphname) && (strlen(graphname) > 0) && (wintype(graphname) == 13))
if (svar_exists(graphname) && (strlen(graphname) > 0) && (wintype(graphname) == 17))
string axes = "xyz"
string obj = "surface_" + axes[dim] + "mid"
string cmd

View File

@@ -2,7 +2,7 @@
#pragma rtGlobals=3 // Use modern global access method and strict wave access.
#pragma IgorVersion = 6.1
#pragma ModuleName = PearlArpes
#pragma version = 1.05
#pragma version = 1.06
#include "pearl-area-display" // 2D and 3D data visualization
#include "pearl-area-profiles" // data processing for multi-dimensional datasets
#include "pearl-area-import" // import data files generated by area detector software
@@ -14,6 +14,7 @@
#include "pearl-anglescan-tracker" // live preview of hemispherical angle scan
#include "pearl-scienta-preprocess" // pre-processing functions for Scienta detector images
#include "pearl-elog"
#include "pearl-scilog"
#if exists("pvOpen")
#include "pearl-area-live" // live view of area detector
#include "pearl-epics" // EPICS access under Igor

View File

@@ -2,7 +2,7 @@
#pragma rtGlobals=3 // Use modern global access method and strict wave access.
#pragma IgorVersion = 6.36
#pragma ModuleName = PearlDataExplorer
#pragma version = 2.1
#pragma version = 2.2
#include <HierarchicalListWidget>, version >= 1.14
#include "pearl-area-import"
#include "pearl-area-profiles"
@@ -10,7 +10,7 @@
#include "pearl-compat"
#include "pearl-pshell-import"
// copyright (c) 2013-22 Paul Scherrer Institut
// copyright (c) 2013-25 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.
@@ -543,87 +543,6 @@ static function notebook_add_attributes(notebook_name, attr_filter, attr_names,
endfor
end
/// send general metadata to ELOG panel - if available
///
/// the following metatdata are sent.
/// they must be present as strings in the specified data folder:
///
/// | ELOG parameter | global string | function argument |
/// | --- | --- | --- |
/// | file | s_filepath | filename |
/// | graph attachment | | graphname |
/// | author | authors | |
/// | p-group | pgroup | |
/// | project | proposal | |
/// | sample | sample | |
///
/// @param file_df data folder that contains the metadata.
///
/// @param filename override file path read from s_filepath global string variable.
/// if neither is declared, the file name is reset to empty field.
///
/// @param graphname select this graph window for attaching.
/// default: do not change the selection.
///
static function set_elog_attributes(file_df, [filename, graphname])
dfref file_df
string filename
string graphname
if (ParamIsDefault(filename))
svar /sdfr=file_df /z loaded_file=s_filepath
if (svar_Exists(loaded_file))
filename = loaded_file
else
filename = ""
endif
endif
if (ParamIsDefault(graphname))
graphname = ""
endif
string cmd
if (exists("PearlElog#set_panel_attributes") == 6)
sprintf cmd, "PearlElog#set_panel_attributes(\"\", \"File=%s\")", ParseFilePath(0, filename, ":", 1, 0)
execute /Q/Z cmd
if ((strlen(graphname) > 0) && (WinType(graphname) == 1))
sprintf cmd, "PearlElog#set_panel_graphs(\"\", \"%s\")", graphname
execute /Q/Z cmd
endif
svar /sdfr=file_df /z authors
if (svar_Exists(authors))
if (strlen(authors)>=1)
sprintf cmd, "PearlElog#set_panel_attributes(\"\", \"author=%s\")", authors
execute /Q/Z cmd
endif
endif
svar /sdfr=file_df /z pgroup
if (svar_Exists(pgroup))
if (strlen(pgroup)>=1)
sprintf cmd, "PearlElog#set_panel_attributes(\"\", \"p-group=%s\")", pgroup
execute /Q/Z cmd
endif
endif
svar /sdfr=file_df /z proposal
if (svar_Exists(proposal))
if (strlen(proposal)>=1)
sprintf cmd, "PearlElog#set_panel_attributes(\"\", \"project=%s\")", proposal
execute /Q/Z cmd
endif
endif
svar /sdfr=file_df /z proposer
svar /sdfr=file_df /z sample
if (svar_Exists(sample))
if (strlen(sample)>=1)
sprintf cmd, "PearlElog#set_panel_attributes(\"\", \"sample=%s\")", sample
execute /Q/Z cmd
endif
endif
endif
end
// ====== preview ======
static function preview_file(filename)
@@ -1475,6 +1394,12 @@ end
// ====== panel ======
/// macro to create the data explorer panel
///
/// note: after editing the panel using the graphical editor,
/// remove all automatically generated assignments from the lb_contents listbox.
/// only the pos, size and keySelectCol properties should remain.
///
Window PearlDataExplorer() : Panel
PauseUpdate; Silent 1 // building window...
NewPanel /K=1 /W=(510,45,1190,539) as "PEARL Data Explorer"
@@ -1486,6 +1411,7 @@ Window PearlDataExplorer() : Panel
TitleBox tb_filepath,variable= root:packages:pearl_explorer:s_short_filepath,fixedSize=1
Button b_browse_filepath,pos={303.00,24.00},size={20.00,20.00},proc=PearlDataExplorer#bp_browse_filepath,title="..."
Button b_browse_filepath,fColor=(65280,48896,32768)
Button b_browse_filepath,help={"select the file system folder that contains the data files"}
GroupBox gb_prefs,pos={8.00,351.00},size={65.00,131.00},title="prefs"
GroupBox gb_prefs,help={"explorer package preferences"}
Button b_save_prefs,pos={21.00,394.00},size={38.00,17.00},proc=PearlDataExplorer#bp_save_prefs,title="save"
@@ -1500,18 +1426,27 @@ Window PearlDataExplorer() : Panel
ListBox lb_files,selWave=root:packages:pearl_explorer:wSelectedFiles,mode= 4
Button b_update_filelist,pos={246.00,315.00},size={76.00,22.00},proc=PearlDataExplorer#bp_update_filelist,title="update list"
Button b_update_filelist,fColor=(65280,48896,32768)
CheckBox cb_file_preview,pos={78.00,318.00},size={59.00,14.00},title="preview"
Button b_update_filelist,help={"reload the file list"}
CheckBox cb_file_preview,pos={94.00,375.00},size={59.00,14.00},title="preview"
CheckBox cb_file_preview,help={"enable/disable automatic preview window when selecting a data file"}
CheckBox cb_file_preview,value= 0
CheckBox cb_file_elog,pos={94.00,395.00},size={82.00,14.00},title="ELOG"
CheckBox cb_file_elog,help={"enable/disable sending metadata to ELOG panel when selecting a data file (does not submit to ELOG)"}
CheckBox cb_file_elog,value= 0
Button b_attr_notebook,pos={94.00,415.00},size={64.00,22.00},disable=2,proc=PearlDataExplorer#bp_attr_notebook,title="notebook"
Button b_attr_notebook,help={"show a summary of attributes in a notebook window"}
Button b_attr_notebook,fColor=(65280,48896,32768)
Button b_file_prev,pos={20.00,314.00},size={22.00,22.00},proc=PearlDataExplorer#bp_file_prev,title="\\W646"
Button b_file_prev,help={"previous file"},fColor=(65280,48896,32768)
Button b_file_prev,fColor=(65280,48896,32768)
Button b_file_prev,help={"select previous file from list"}
Button b_file_next,pos={44.00,314.00},size={22.00,22.00},proc=PearlDataExplorer#bp_file_next,title="\\W649"
Button b_file_next,help={"next file"},fColor=(65280,48896,32768)
Button b_file_next,fColor=(65280,48896,32768)
Button b_file_next,help={"select next file from list"}
Button b_goto_dataset,pos={355.00,315.00},size={64.00,22.00},disable=2,proc=PearlDataExplorer#bp_goto_dataset,title="goto DF"
Button b_goto_dataset,help={"change the current data folder ot where the selected dataset could be located"}
Button b_goto_dataset,help={"change the current data folder to find the selected dataset (if loaded)"}
Button b_goto_dataset,fColor=(65280,48896,32768)
Button b_display_dataset,pos={423.00,315.00},size={64.00,22.00},disable=2,proc=PearlDataExplorer#bp_display_dataset,title="display"
Button b_display_dataset,help={"display the selected dataset in its own window"}
Button b_display_dataset,help={"display the selected dataset"}
Button b_display_dataset,fColor=(65280,48896,32768)
Button b_load_complete,pos={355.00,451.00},size={92.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="all data"
Button b_load_complete,help={"load all datasets of the selected file."}
@@ -1520,35 +1455,29 @@ Window PearlDataExplorer() : Panel
TitleBox tb_selected_file,pos={360.00,28.00},size={309.00,22.00},frame=0
TitleBox tb_selected_file,variable= root:packages:pearl_explorer:s_selected_file,fixedSize=1
GroupBox gb_contents,pos={346.00,55.00},size={327.00,294.00},title="datasets"
Button b_attr_notebook,pos={97.00,375.00},size={64.00,22.00},disable=2,proc=PearlDataExplorer#bp_attr_notebook,title="notebook"
Button b_attr_notebook,help={"show a summary of attributes in a notebook window"}
Button b_attr_notebook,fColor=(65280,48896,32768)
ListBox lb_contents,pos={355.00,84.00},size={305.00,222.00}
ListBox lb_contents,keySelectCol= 1
GroupBox gb_selected_file,pos={346.00,4.00},size={328.00,48.00},title="selected file"
Button b_load_region,pos={355.00,426.00},size={92.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="region"
Button b_load_region,help={"load the selected region"}
Button b_load_region,help={"load significant datasets and metadata from the selected region"}
Button b_load_region,userdata= "mode:load_region;",fColor=(65280,48896,32768)
PopupMenu popup_reduction,pos={366.00,391.00},size={200.00,17.00},bodyWidth=200,proc=PearlDataExplorer#pmp_reduction_func
PopupMenu popup_reduction,help={"data reduction of 3d ScientaImage. note: the list may contain unsuitable functions. check the code or documentation!"}
PopupMenu popup_reduction,help={"data reduction function for 3d ScientaImage. note: the list may contain unsuitable functions. check the code or documentation!"}
PopupMenu popup_reduction,mode=1,popvalue="None",value= #"PearlDataExplorer#pm_reduction_values()"
GroupBox group_import,pos={346.00,351.00},size={326.00,131.00},title="import"
Button b_load_scan,pos={450.00,426.00},size={94.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="scan"
Button b_load_scan,help={"load the selected scan"},userdata= "mode:load_scan;"
Button b_load_scan,help={"load significant datasets and metadata from the selected scan"},userdata= "mode:load_scan;"
Button b_load_scan,fColor=(65280,48896,32768)
Button b_load_diags,pos={450.00,451.00},size={94.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="diagnostics"
Button b_load_diags,help={"load diagnostics of selected scans"},userdata= "mode:load_diags;"
Button b_load_diags,fColor=(65280,48896,32768)
Button b_load_diags,help={"load diagnostic datasets of the selected scans"}
Button b_load_diags,userdata= "mode:load_diags;",fColor=(65280,48896,32768)
Button b_load_dataset,pos={547.00,426.00},size={101.00,22.00},disable=2,proc=PearlDataExplorer#bp_load_options,title="dataset"
Button b_load_dataset,help={"load the selected datasets"}
Button b_load_dataset,help={"load the selected dataset(s) and significant metadata"}
Button b_load_dataset,userdata= "mode:load_dataset;",fColor=(65280,48896,32768)
Button b_reduction_params,pos={571.00,390.00},size={71.00,19.00},disable=2,proc=PearlDataExplorer#bp_reduction_params,title="set params"
Button b_reduction_params,help={"set data reduction parameters"}
Button b_reduction_params,fColor=(65280,48896,32768)
GroupBox g_fileinfo,pos={85.00,351.00},size={251.00,131.00},title="file info"
Button b_elog,pos={97.00,401.00},size={64.00,22.00},disable=2,proc=PearlDataExplorer#bp_elog,title="ELOG"
Button b_elog,help={"send file metadata to ELOG panel (does not submit to ELOG)"}
Button b_elog,fColor=(65280,48896,32768)
ToolsGrid grid=(0,28.35,5)
EndMacro
@@ -1591,8 +1520,6 @@ static function update_controls()
dis = file_selected && scan_selected ? 0 : 2
Button b_attr_notebook win=PearlDataExplorer,disable=dis
dis = file_selected && (strlen(WinList("*ElogPanel*", ";", "WIN:64")) > 1) ? 0 : 2
Button b_elog win=PearlDataExplorer,disable=dis
dis = scan_selected ? 0 : 2
Button b_load_scan win=PearlDataExplorer,disable=dis
dis = region_selected ? 0 : 2
@@ -1782,24 +1709,34 @@ End
///
/// - load metadata
/// - load preview if requested
/// - send to elog panel if requested
///
/// @param file name of selected file
///
/// @param do_preview enable/disable loading of preview data
/// non-zero: load preview,
/// zero: don't load preview
///
static function selected_file(file, do_preview)
/// @param do_elog enable/disable sending metadata to elog panel
/// non-zero: send,
/// zero: don't send
///
static function selected_file(file, do_preview, do_elog)
string file
variable do_preview
variable do_elog
dfref save_df = GetDataFolderDFR()
setdatafolder $package_path
svar s_selected_file
s_selected_file = file
get_file_info(file)
if (do_preview)
variable fi = get_file_info(file)
if (fi == 0 && do_preview != 0)
preview_file(file)
endif
if (fi == 0 && do_elog != 0)
send_to_elog()
endif
update_controls()
setdatafolder save_df
@@ -1825,7 +1762,10 @@ static function bp_file_next(ba) : ButtonControl
if (v_value >= 0)
variable ifile = v_value
ControlInfo /W=PearlDataExplorer cb_file_preview
selected_file(wtFiles[ifile], v_value)
variable do_preview = v_value
ControlInfo /W=PearlDataExplorer cb_file_elog
variable do_elog = v_value
selected_file(wtFiles[ifile], do_preview, do_elog)
endif
update_controls()
break
@@ -1856,7 +1796,10 @@ static function bp_file_prev(ba) : ButtonControl
if (v_value >= 0)
variable ifile = v_value
ControlInfo /W=PearlDataExplorer cb_file_preview
selected_file(wtFiles[ifile], v_value)
variable do_preview = v_value
ControlInfo /W=PearlDataExplorer cb_file_elog
variable do_elog = v_value
selected_file(wtFiles[ifile], do_preview, do_elog)
endif
update_controls()
break
@@ -1887,9 +1830,12 @@ static function lbp_filelist(lba) : ListBoxControl
if (selWave[row])
if (sum(wSelectedFiles) == 1)
ControlInfo /W=PearlDataExplorer cb_file_preview
selected_file(listWave[row], v_value)
variable do_preview = v_value
ControlInfo /W=PearlDataExplorer cb_file_elog
variable do_elog = v_value
selected_file(listWave[row], do_preview, do_elog)
else
selected_file(listWave[row], 0)
selected_file(listWave[row], 0, 0)
endif
endif
update_controls()
@@ -2285,6 +2231,24 @@ static function bp_display_dataset(ba) : ButtonControl
return 0
End
/// *******
static function /s get_default_elog_module()
string modules = "PearlSciLog;PearlElog"
variable imod
variable nmod = ItemsInList(modules, ";")
string smod
for (imod = 0; imod < nmod; imod += 1)
smod = StringFromList(imod, modules, ";")
if (exists(smod + "#set_panel_attributes") == 6)
return smod
endif
endfor
return ""
end
/// send file metadata to the ELOG panel
///
/// metadate is looked up in the following locations:
@@ -2292,6 +2256,9 @@ End
/// 2. file info folder inside package folder
/// 3. package folder if it contains preview data from the selected file (???)
///
/// the data is sent to the first ElogPanel in the window list.
/// call open_pearl_elog() to ensure a new default panel.
///
static function send_to_elog()
dfref save_df = GetDataFolderDFR()
@@ -2338,27 +2305,123 @@ static function send_to_elog()
graphname = ""
endif
string module = get_default_elog_module()
funcref PearlDataExplorer_proto_get_panel_name f_get_panel_name = $(module + "#get_default_panel_name")
string panel = f_get_panel_name()
if (result == 0)
set_elog_attributes(data_df, filename=s_selected_file, graphname=graphname)
string windowname
windowname = StringFromList(0, WinList("*ElogPanel*", ";", "WIN:64"), ";")
DoWindow /F $windowname
set_elog_attributes(module, panel, data_df, filename=s_selected_file, graphname=graphname)
endif
setdatafolder save_df
end
static function bp_elog(ba) : ButtonControl
STRUCT WMButtonAction &ba
function /s PearlDataExplorer_proto_get_panel_name()
return ""
end
switch( ba.eventCode )
case 2: // mouse up
send_to_elog()
break
case -1: // control being killed
break
endswitch
function /s PearlDataExplorer_proto_set_panel_attributes(windowname, attributes, [clear])
string windowname
string attributes
variable clear
return ""
end
return 0
End
function /s PearlDataExplorer_proto_set_panel_graphs(windowname, graphs)
string windowname
string graphs
return ""
end
/// send general metadata to ELOG panel - if available
///
/// the function works with any electronic logbook that has the same interfaces as pearl-elog.ipf.
/// the set_panel_attributes and set_panel_graphs functions are required.
///
/// the following metatdata are sent.
/// they must be present as strings in the specified data folder:
///
/// | ELOG parameter | global string | function argument |
/// | --- | --- | --- |
/// | file | s_filepath | filename |
/// | graph attachment | | graphname |
/// | author | authors | |
/// | p-group | pgroup | |
/// | project | proposal | |
/// | sample | sample | |
///
/// @param elog_module Igor module name of the electronic logbook, PearlElog or PearlSciLog.
///
/// @param panel_name Window name of the logbook panel
///
/// @param file_df data folder that contains the metadata.
///
/// @param filename override file path read from s_filepath global string variable.
/// if neither is declared, the file name is reset to empty field.
///
/// @param graphname select this graph window for attaching.
/// default: do not change the selection.
///
static function set_elog_attributes(elog_module, panel_name, file_df, [filename, graphname])
string elog_module
string panel_name
dfref file_df
string filename
string graphname
if (ParamIsDefault(filename))
svar /sdfr=file_df /z loaded_file=s_filepath
if (svar_Exists(loaded_file))
filename = loaded_file
else
filename = ""
endif
endif
if (ParamIsDefault(graphname))
graphname = ""
endif
string cmd
string attrib = ""
if (exists(elog_module + "#set_panel_attributes") == 6)
funcref PearlDataExplorer_proto_set_panel_attributes f_set_attributes = $(elog_module + "#set_panel_attributes")
funcref PearlDataExplorer_proto_set_panel_graphs f_set_graphs = $(elog_module + "#set_panel_graphs")
attrib = ReplaceStringByKey("file", attrib, ParseFilePath(0, filename, ":", 1, 0), ":", ";")
svar /sdfr=file_df /z authors
if (svar_Exists(authors))
if (strlen(authors)>=1)
attrib = ReplaceStringByKey("author", attrib, authors, ":", ";")
endif
endif
svar /sdfr=file_df /z pgroup
if (svar_Exists(pgroup))
if (strlen(pgroup)>=1)
attrib = ReplaceStringByKey("p-group", attrib, pgroup, ":", ";")
endif
endif
svar /sdfr=file_df /z proposal
if (svar_Exists(proposal))
if (strlen(proposal)>=1)
attrib = ReplaceStringByKey("project", attrib, proposal, ":", ";")
endif
endif
svar /sdfr=file_df /z proposer
svar /sdfr=file_df /z sample
if (svar_Exists(sample))
if (strlen(sample)>=1)
attrib = ReplaceStringByKey("sample", attrib, sample, ":", ";")
endif
endif
if (strlen(attrib)>=3)
f_set_attributes(panel_name, attrib)
endif
if ((strlen(graphname) > 0) && (WinType(graphname) == 1))
f_set_graphs(panel_name, graphname)
endif
endif
end

View File

@@ -1,11 +1,11 @@
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3 // Use modern global access method and strict wave access.
#pragma version = 1.50
#pragma version = 2.2
#pragma IgorVersion = 6.36
#pragma ModuleName = PearlElog
// author: matthias.muntwiler@psi.ch
// Copyright (c) 2013-20 Paul Scherrer Institut
// Copyright (c) 2013-25 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.
@@ -145,8 +145,7 @@ static function AfterFileOpenHook(refNum,file,pathName,type,creator,kind)
Variable refNum,kind
String file,pathName,type,creator
if( (kind >= 1) && (kind <= 2))
init_package(clean=1)
load_prefs()
clear_package_data()
endif
return 0
end
@@ -206,6 +205,30 @@ static function /df get_elog_df(name, category)
endif
end
/// delete all package data
///
/// also kills any ELOG panels
///
static function clear_package_data()
dfref savedf = getdatafolderdfr()
dfref df_root = get_elog_df("", kdfRoot)
if (DataFolderRefStatus(df_root) == 1)
string wins = WinList("*ElogPanel*", ";", "WIN:64")
variable iwin
variable nwin = ItemsInList(wins, ";")
string swin
for (iwin = 0; iwin < nwin; iwin += 1)
swin = StringFromList(iwin, wins, ";")
KillWindow /Z $swin
endfor
KillDataFolder /Z df_root
endif
setdatafolder savedf
return 0
end
/// initialize the package data folder.
///
/// the data folder is initialized with a default, local configuration without any logbooks.
@@ -1406,6 +1429,7 @@ static function elog_panel_hook(s)
svar /sdfr=df_volatile url
url = format_url(logbook)
update_attach_items(logbook)
update_buttons(s.winName, logbook)
endif
break
case 6: // resize
@@ -1427,6 +1451,32 @@ static function elog_panel_hook(s)
return hookResult // 0 if nothing done, else 1
end
static function update_buttons(win_name, logbook)
string win_name
string logbook
dfref df = get_elog_df(logbook, kdfVolatile)
//string win_name = StringFromList(0, WinList(logbook + "ElogPanel*", ";", "WIN:64"), ";")
variable logged = 0
if (strlen(logbook) > 0)
svar /z /sdfr=df g_username=username
svar /z /sdfr=df g_password=password
if (svar_exists(g_username) && svar_exists(g_password))
logged = strlen(g_username) > 0 && strlen(g_password) > 0
endif
endif
if (strlen(win_name) > 0)
if (logged)
Button b_login, win=$win_name, disable=3
Button b_logout, win=$win_name, disable=0
else
Button b_login, win=$win_name, disable=0
Button b_logout, win=$win_name, disable=3
endif
endif
end
static constant kAttachColSel = 0
static constant kAttachColTitle = 1
static constant kAttachColName = 2
@@ -1756,7 +1806,7 @@ end
/// @param windowname window name of the ELOG panel
/// if empty, use default name "PearlElogPanel"
///
/// @return list of attributes to in the format <code>"key1=value1;key2=value2"</code>.
/// @return list of attributes to in the format `"key1:value1;key2:value2"`.
///
static function /s get_panel_attributes(windowname)
string windowname
@@ -1781,11 +1831,11 @@ static function /s get_panel_attributes(windowname)
ControlInfo /w=$windowname $control
switch(v_flag)
case 2: // checkbox
attributes = ReplaceNumberByKey(attribute, attributes, v_value, "=", ";")
attributes = ReplaceNumberByKey(attribute, attributes, v_value, ":", ";")
break
case 3: // popupmenu
case 5: // setvariable
attributes = ReplaceStringByKey(attribute, attributes, s_value, "=", ";")
attributes = ReplaceStringByKey(attribute, attributes, s_value, ":", ";")
break
endswitch
endif
@@ -1799,7 +1849,7 @@ end
/// @param windowname window name of the ELOG panel
/// if empty, use default name "PearlElogPanel"
///
/// @param attributes list of attributes to set (format "key1=value1;key2=value2")
/// @param attributes list of attributes to set (format "key1:value1;key2:value2")
///
/// @param clear what to do if a key is missing in attributes?
/// @arg 0 (default) leave the field unchanged
@@ -1839,12 +1889,12 @@ static function /s set_panel_attributes(windowname, attributes, [clear])
control = StringFromList(ico, controls, ";")
attribute = GetUserData(windowname, control, "attribute")
if (strlen(attribute))
value = StringByKey(attribute, attributes, "=", ";")
value = StringByKey(attribute, attributes, ":", ";")
if (strlen(value) || clear)
ControlInfo /w=$windowname $control
switch(v_flag)
case 2: // checkbox
numval = NumberByKey(attribute, attributes, "=", ";")
numval = NumberByKey(attribute, attributes, ":", ";")
if ((numtype(numval) != 0) && clear)
numval = 0
endif
@@ -1853,7 +1903,7 @@ static function /s set_panel_attributes(windowname, attributes, [clear])
endif
break
case 3: // popupmenu
options_path = persistent_path + StringByKey(attribute, options, "=", ";")
options_path = persistent_path + StringByKey(attribute, options, ":", ";")
svar values = $options_path
numval = WhichListItem(value, values, ";") + 1
if (numval >= 1)

View File

@@ -1,13 +1,13 @@
#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=1 // Use modern global access method.
#pragma ModuleName = PearlMenu
#pragma version = 1.02
#pragma version = 1.03
// main menu for PEARL data acquisition and analysis packages
// $Id$
// author: matthias.muntwiler@psi.ch
// Copyright (c) 2013-14 Paul Scherrer Institut
// Copyright (c) 2013-25 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.
@@ -65,6 +65,8 @@ menu "PEARL"
submenu "Services"
PearlMenuEnableFunc("pearl_elog") + "Open ELOG Panel", /Q, pearl_elog("")
help = {"Open an ELOG panel to send entries to an ELOG logbook"}
PearlMenuEnableFunc("pearl_scilog") + "Open SciLog Panel", /Q, pearl_scilog("")
help = {"Open a panel to send entries to a SciLog logbook"}
end
submenu "Sample Preparation"

View File

@@ -3,7 +3,7 @@
#pragma IgorVersion = 6.2
#pragma ModuleName = PearlPmscoImport
// copyright (c) 2018 Paul Scherrer Institut
// copyright (c) 2018-25 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.
@@ -326,3 +326,30 @@ function /s pmsco_load_xyz(pathname, filename)
wave /t at
at = unpadstring(at, 32)
end
function pmsco_load_alm(pathname, filename, wname)
string pathname
string filename
string wname
variable fid
if (strlen(pathname) > 0)
HDF5OpenFile /P=pathname /R /Z fid as filename
else
HDF5OpenFile /R /Z fid as filename
endif
if (v_flag == 0)
HDF5LoadData /N=$wname /O /Q /Z fid, "alm"
HDF5CloseFile fid
wave alm_r = $(wname + "_r")
wave alm_i = $(wname + "_i")
make /c /n=(dimsize(alm_r, 0), dimsize(alm_r, 1)) /o $wname
wave /c alm = $wname
alm = cmplx(alm_r, alm_i)
variable lmax = (dimsize(alm, 0) - 1) * 2
setscale /i x 0, lmax, "l", alm, alm_r, alm_i
setscale /i y -lmax, lmax, "m", alm, alm_r, alm_i
endif
end

View File

@@ -2,7 +2,7 @@
#pragma rtGlobals=3 // Use modern global access method and strict wave access.
#pragma IgorVersion = 8.00
#pragma ModuleName = PearlPShellImport
#pragma version = 2.1
#pragma version = 2.2
#if IgorVersion() < 9.00
#include <HDF5 Browser>
#endif
@@ -10,7 +10,7 @@
#include "pearl-gui-tools"
#include "pearl-area-import"
// copyright (c) 2013-22 Paul Scherrer Institut
// copyright (c) 2013-25 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.
@@ -63,7 +63,7 @@ strconstant kDataDimLabel = "data"
strconstant kPreviewDatasets = "ImageEnergyDistribution;ScientaSpectrum;ScientaImage;Counts;SampleCurrent;"
/// List of datasets that must be loaded to determine the axis scaling of a Scienta image
strconstant kScientaScalingDatasets = "LensMode;ScientaChannelBegin;ScientaChannelEnd;ScientaSliceBegin;ScientaSliceEnd;Eph;"
strconstant kScientaScalingDatasets = "LensMode;ScientaChannelBegin;ScientaChannelEnd;ScientaSliceBegin;ScientaSliceEnd;Eph;ScientaHighEnergy;ScientaHighThetaX;ScientaLowEnergy;ScientaLowThetaX;"
/// List of diagnostic datasets that are normally loaded with a scan
strconstant kEssentialDiagnostics = "ManipulatorX;ManipulatorY;ManipulatorZ;ManipulatorTheta;ManipulatorTilt;ManipulatorPhi;MonoEnergy;"
@@ -2202,6 +2202,10 @@ end
/// where the first folder in the list takes precedence.
/// it may fall back to more or less reasonable default values if no data is not found.
/// @arg `LensMode`
/// @arg `ScientaLowEnergy`
/// @arg `ScientaHighEnergy`
/// @arg `ScientaLowThetaX`
/// @arg `ScientaHighThetaX`
/// @arg `ScientaChannelBegin`
/// @arg `ScientaChannelEnd`
/// @arg `ScientaSliceBegin`
@@ -2260,6 +2264,10 @@ function ps_detect_scale(data_df, ax, lo, hi, un)
ax[%$kDataDimLabel] = "value"
wave /T /Z LensMode = ps_find_scale_wave("LensMode", data_df, scan_df, attr_df)
wave /Z LowEnergy = ps_find_scale_wave("ScientaLowEnergy", data_df, scan_df, attr_df)
wave /Z HighEnergy = ps_find_scale_wave("ScientaHighEnergy", data_df, scan_df, attr_df)
wave /Z LowThetaX = ps_find_scale_wave("ScientaLowThetaX", data_df, scan_df, attr_df)
wave /Z HighThetaX = ps_find_scale_wave("ScientaHighThetaX", data_df, scan_df, attr_df)
wave /Z ChannelBegin = ps_find_scale_wave("ScientaChannelBegin", data_df, scan_df, attr_df)
wave /Z ChannelEnd = ps_find_scale_wave("ScientaChannelEnd", data_df, scan_df, attr_df)
wave /Z SliceBegin = ps_find_scale_wave("ScientaSliceBegin", data_df, scan_df, attr_df)
@@ -2268,35 +2276,30 @@ function ps_detect_scale(data_df, ax, lo, hi, un)
// lens mode can give more detail
if (waveexists(LensMode) && (numpnts(LensMode) >= 1))
strswitch(LensMode[0])
case "Angular45":
lo[%$kAngleDimLabel] = -45/2
hi[%$kAngleDimLabel] = +45/2
un[%$kAngleDimLabel] = "°"
ax[%$kAngleDimLabel] = "angle"
break
case "Angular60":
lo[%$kAngleDimLabel] = -60/2
hi[%$kAngleDimLabel] = +60/2
un[%$kAngleDimLabel] = "°"
ax[%$kAngleDimLabel] = "angle"
break
case "Transmission":
un[%$kAngleDimLabel] = "arb."
ax[%$kAngleDimLabel] = "offset"
break
endswitch
if (stringmatch(LensMode[0], "*Transmission*"))
un[%$kAngleDimLabel] = "mm"
ax[%$kAngleDimLabel] = "position"
else
un[%$kAngleDimLabel] = "°"
ax[%$kAngleDimLabel] = "angle"
endif
endif
// best option if scales are explicit in separate waves
if (waveexists(ChannelBegin) && waveexists(ChannelEnd) && (numpnts(ChannelBegin) >= 1) && (numpnts(ChannelEnd) >= 1))
if (waveexists(LowEnergy) && waveexists(HighEnergy) && (numpnts(LowEnergy) >= 1) && (numpnts(HighEnergy) >= 1))
lo[%$kEnergyDimLabel] = LowEnergy[0]
hi[%$kEnergyDimLabel] = HighEnergy[0]
elseif (waveexists(ChannelBegin) && waveexists(ChannelEnd) && (numpnts(ChannelBegin) >= 1) && (numpnts(ChannelEnd) >= 1))
lo[%$kEnergyDimLabel] = ChannelBegin[0]
hi[%$kEnergyDimLabel] = ChannelEnd[0]
elseif (waveexists(ScientaChannels) && (numpnts(ScientaChannels) >= 1))
lo[%$kEnergyDimLabel] = ScientaChannels[0]
hi[%$kEnergyDimLabel] = ScientaChannels[numpnts(ScientaChannels)-1]
endif
if (waveexists(SliceBegin) && waveexists(SliceEnd) && (numpnts(SliceBegin) >= 1) && (numpnts(SliceEnd) >= 1))
if (waveexists(LowThetaX) && waveexists(HighThetaX) && (numpnts(LowThetaX) >= 1) && (numpnts(HighThetaX) >= 1))
lo[%$kAngleDimLabel] = LowThetaX[0]
hi[%$kAngleDimLabel] = HighThetaX[0]
elseif (waveexists(SliceBegin) && waveexists(SliceEnd) && (numpnts(SliceBegin) >= 1) && (numpnts(SliceEnd) >= 1))
lo[%$kAngleDimLabel] = SliceBegin[0]
hi[%$kAngleDimLabel] = SliceEnd[0]
endif

1739
pearl/pearl-scilog.ipf Normal file

File diff suppressed because it is too large Load Diff

331
pearl/scilog-ingest.py Normal file
View File

@@ -0,0 +1,331 @@
#!/usr/bin/env -S uv run --script
#
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "pillow",
# "scilog",
# ]
# ///
import argparse
import re
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple
from PIL import Image, UnidentifiedImageError
from scilog import SciLog, LogbookMessage
def get_image_size(file_path):
"""
Args:
file_path:
Returns:
Raises:
OSError: If the file does not exist.
PIL.Image.UnidentifiedImageError: If the image is not recognized.
"""
with Image.open(file_path) as img:
return img.size
re_single_nbsp = re.compile(r"(?<!;)&nbsp;(?!&)")
re_rep_pre_spc = re.compile(r" {2,}")
def replace_pre_spaces(s: str) -> str:
"""
Replace multiple spaces with the corresponding number of &nbsp; entities.
Args:
s: string
Returns: string
"""
def nbsp(mo):
return "&nbsp;" * len(mo.group())
return re_rep_pre_spc.sub(nbsp, s)
class ScilogIngestor:
def __init__(self):
super().__init__()
self._author: str = ""
self._pgroup: str = ""
self._logbook: str = ""
self._message: str = ""
self._tags: List[str] = []
self._attachments: List[str] = []
self._url: str = ""
self._user: str = ""
self._password: str = ""
self._location: str = ""
self._scilog: Optional[SciLog] = None
def prepare(self):
pass
@staticmethod
def _adjust_image_size(file_info):
try:
filepath = file_info['filepath']
except KeyError:
return
try:
w, h = get_image_size(filepath)
except (OSError, UnidentifiedImageError):
return
else:
# limit image size
while w > 400:
w //= 2
file_info['style'] = {'width': f'{w}px', 'height': ''}
def convert_plain(self, mesg: str) -> str:
"""
convert plain-text entry to HTML
- Convert plain text message to HTML paragraph with <tt> formatting.
- Replace line feeds by <br> tags.
- Replace tabs and spaces.
Args:
mesg:
Returns: string
"""
mesg = mesg.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
mesg = mesg.replace("\n", "<br>")
mesg = mesg.expandtabs(8)
mesg = replace_pre_spaces(mesg)
mesg = "<p><tt>" + mesg + "</tt></p>"
return mesg
@staticmethod
def is_pgroup(value: str) -> str:
value = value.lower()
if len(value) == 6 and value[0] in {"e", "p"} and int(value[1:]):
return "p" + value[1:]
else:
return ""
@property
def scilog(self) -> SciLog:
"""
Return a SciLog instance.
Returns: SciLog
"""
if self._scilog is None:
options = {"username": self._user,
"password": self._password}
url = self._url
self._scilog = SciLog(url, options=options)
return self._scilog
def ingest_message(self):
"""
ingest message into scilog
this method ingests the current message into scilog.
"""
log = self.scilog
try:
logbook_name = self._logbook
except KeyError:
lb_filter = {"ownerGroup": self._pgroup, "deleted": False}
else:
lb_filter = {"ownerGroup": self._pgroup, "name": logbook_name, "deleted": False}
logbooks = log.get_logbooks(where=lb_filter, limit=10)
try:
logbook = logbooks[0]
except IndexError:
raise ValueError(f"no logbook found for {lb_filter}")
else:
if len(logbooks) > 1:
raise ValueError(f"multiple logbooks found for {lb_filter}")
else:
log.select_logbook(logbook)
msg = LogbookMessage()
if self._message[0] == '<':
msg.add_text(self._message)
else:
for line in self._message.split('\n'):
msg.add_text(line)
for att in self._attachments:
msg.add_file(att)
file_info = msg._content.files[-1]
self._adjust_image_size(file_info)
if self._tags:
msg.add_tag(self._tags)
log.send_logbook_message(msg)
def load_attributes_from_file(self, attributes_file):
"""
Load attributes, tags and attachment paths from a file.
Each line of the file declares a key:value pair.
The keys are:
- author: (required) Value is the e-mail address of the author.
- pgroup: (required) Value is the p-group of the logbook.
- logbook: (required) Value is the name of the logbook.
- location: (not used) Value is the name of the location. Currently not used.
- tag: (optional) Value is a tag to be added to the snippet. Can occur multiple times.
Duplicates are ignored. Spaces and commas are removed.
- attachment: (optional) Value is the path of an attachment. Can occur multiple times.
Duplicates are ignored.
:param attributes_file:
:return:
"""
unique_tags = set()
unique_attachments = set()
with open(attributes_file, "rt", encoding="utf8") as f:
for line in f.readlines():
try:
key, val = line.split(":", 1)
key = key.strip()
val = val.strip()
except ValueError:
continue
if key == "tag":
val = val.replace(" ", "")
val = val.replace(",", "")
if val not in unique_tags:
self._tags.append(val)
unique_tags.add(val)
elif key == "attachment":
if val not in unique_attachments:
self._attachments.append(val)
unique_attachments.add(val)
elif key == "author":
self._author = val
elif key == "pgroup":
self._pgroup = val
elif key == "location":
self._location = val
elif key == "logbook":
self._logbook = val
def load_message_from_file(self, message_file):
"""
Load message from file
If the file starts with a `<`, the content is assumed to be HTML.
Otherwise, it is assumed to be plain text and will be tagged and escaped.
:param message_file:
:return:
"""
with open(message_file, "rt", encoding="utf8") as f:
self._message = "\n".join(f.readlines())
def load_credentials_from_file(self, credentials_file):
"""
Load the credentials from a file.
The file must contain three lines, each in the form key:value.
The keys are `url`, `user`, and `password`.
The URL must start with `https://` and end with `/api/v1`.
:param credentials_file: path of the credentials file.
If None, it is read from `.scilog.cred` the home directory.
Make sure to protect the credentials from unauthorized access!
:return: None
"""
if credentials_file is None:
credentials_file = Path.home() / ".scilog.cred"
if not Path(credentials_file).exists():
raise ValueError("Missing credentials")
with open(credentials_file, "rt", encoding="utf8") as f:
for line in f.readlines():
try:
key, val = line.split(":", 1)
key = key.strip()
val = val.strip()
except ValueError:
continue
if key == "user":
self._user = val
elif key == "password":
self._password = val
elif key == "url":
self._url = val
def validate(self):
"""
Perform a number of basic validity checks on the attributes.
:return:
:raise ValueError if a problem is found.
"""
if not re.match(r"[^@]+@[^@]+\.[^@]+", self._author):
raise ValueError("Invalid email address.")
if not self._user or not self._password:
raise ValueError("Invalid credentials.")
if not self.is_pgroup(self._pgroup):
raise ValueError("Invalid pgroup.")
if not self._logbook:
raise ValueError("Empty logbook name.")
if not self._message:
raise ValueError("Empty message.")
if not re.match(r"https://[a-zA-Z0-9]+\.[a-zA-Z0-9.]+(:[0-9]+)?/api/v1", self._url):
raise ValueError("Invalid URL.")
def run(self):
self.validate()
self.ingest_message()
def main(attributes_file, message_file, credentials_file):
ingestor = ScilogIngestor()
ingestor.load_attributes_from_file(attributes_file)
ingestor.load_message_from_file(message_file)
ingestor.load_credentials_from_file(credentials_file)
ingestor.run()
print("Message successfully transmitted")
def parse_args():
parser = argparse.ArgumentParser(
description="Simple file interface to ingest an entry into a SciLog logbook",
formatter_class=argparse.ArgumentDefaultsHelpFormatter
)
parser.add_argument("-c", "--credentials", default=None, help="credentials file, UTF-8 encoded")
parser.add_argument("attributes", help="attributes file, UTF-8 encoded")
parser.add_argument("message", help="message file, UTF-8 encoded")
clargs = parser.parse_args()
return clargs
if __name__ == "__main__":
clargs = parse_args()
main(clargs.attributes, clargs.message, clargs.credentials)