41 Commits
0.3.0 ... 0.3.2

Author SHA1 Message Date
a2fceffc1b Updating for version 0.3.2 2021-05-10 17:50:15 +02:00
415d68b4dc Return empty string for non-present anatric param 2021-05-10 17:29:36 +02:00
00ff4117ea Isolate anatric subprocesses 2021-05-10 17:06:20 +02:00
67853b8db4 Avoid using temp_dir for anatric xml config preview 2021-05-10 16:34:58 +02:00
60787bccb7 Clarify path to spind 2021-05-10 15:13:38 +02:00
880d86d750 Fix spind output update issues
Fix #27
2021-05-06 18:22:56 +02:00
7a88e5e254 Adapt spind results display according to #27 2021-05-06 17:43:20 +02:00
20f2a8ada4 Update preview on datatable content change 2021-05-04 17:14:59 +02:00
42c092fc14 Fix Safari browser double file download
Introduce a small time delay between .comm/.incomm file downloads

For #24
2021-05-04 16:50:51 +02:00
8153db9f67 Add an extra index spinner 2021-05-04 12:08:39 +02:00
62c969d6ad Allow .ccl files in param study
Fix #28
2021-05-04 11:09:48 +02:00
085620abae Set conda channel_priority to 'strict' 2021-04-23 11:16:57 +02:00
9ebe290966 Export nan for area value/error in case of a bad fit 2021-04-21 12:41:19 +02:00
c9cd96c521 Set lower and upper bounds for center and sigma 2021-04-20 18:25:14 +02:00
d745cda4a5 Reduce hkl columns width to 4 in comm files
Fix #26
2021-04-20 15:07:36 +02:00
1b5f70afa0 Set f0_intercept and f1_amplitude >= 0
For #26
2021-04-20 15:03:27 +02:00
a034065a09 Use Slider for image index 2021-04-12 18:35:58 +02:00
2a60c86b48 Display experiment conditions in DataTable 2021-04-12 18:18:59 +02:00
ccc075975f Unify data files discovery via proposal number 2021-04-12 17:28:25 +02:00
4982b05de0 Updating for version 0.3.1 2021-04-12 09:10:35 +02:00
2b0c392a3e Vary intercept by default 2021-04-12 09:09:47 +02:00
099842b2bd Use TextInput for verbosity value 2021-04-09 17:01:55 +02:00
bd3efd698a Add results output widget for anatric 2021-04-09 14:52:40 +02:00
24f083e585 Treat the first 4 letters of proposal as a year 2021-04-09 10:14:09 +02:00
f43488af34 Layout improvements 2021-04-09 09:03:50 +02:00
1b90d53466 Use Tabs for algorithm params on anatric panel 2021-04-08 17:43:53 +02:00
c1b3a28351 Replace toggles with checkboxes
The CheckboxGroup widget state functionality is more readable
2021-04-08 16:56:50 +02:00
5b45685257 Remove bin size spinner
Binning will be replaced by running average in the future
2021-04-08 15:31:44 +02:00
e7b28a4e75 Auto update export file preview 2021-04-08 15:23:53 +02:00
83a7d607a5 Forward stdout of anatric subprocs to pyzebra app 2021-04-07 17:01:01 +02:00
5eedd14b3f Handle DataFactory for 3 possible detectors 2021-04-07 16:47:48 +02:00
3db7dca7ba Add linear model with fixed default values of 0 2021-04-07 14:59:04 +02:00
b2d1a0be02 Add an extra y-axis for scanning_motor to overview
Also, fix #25
2021-04-07 14:07:51 +02:00
69d22dd067 Layout fixes on the spind tab 2021-04-07 09:59:53 +02:00
242da76c59 Adaptations to the displayed UB matrix 2021-04-07 09:54:31 +02:00
0c812a5dd5 Add TextAreaInput for UB matrix on spind panel 2021-04-06 17:13:18 +02:00
4cfcb3d396 Fix incorrect spind results handling 2021-04-06 17:03:33 +02:00
8018783eb5 Switch from DataRange1d to Range1d in overviews 2021-04-06 15:19:01 +02:00
fdb1609a41 Print the content of spind result file 2021-04-06 11:27:13 +02:00
e7dda3cda8 Print the content of spind event file 2021-03-26 16:32:01 +01:00
f788d74f15 Fix rendering on chrome 2021-03-26 16:03:34 +01:00
10 changed files with 476 additions and 370 deletions

View File

@ -16,6 +16,7 @@ jobs:
run: | run: |
$CONDA/bin/conda install --quiet --yes conda-build anaconda-client $CONDA/bin/conda install --quiet --yes conda-build anaconda-client
$CONDA/bin/conda config --append channels conda-forge $CONDA/bin/conda config --append channels conda-forge
$CONDA/bin/conda config --set channel_priority strict
$CONDA/bin/conda config --set anaconda_upload yes $CONDA/bin/conda config --set anaconda_upload yes
- name: Build and upload - name: Build and upload

View File

@ -4,4 +4,4 @@ from pyzebra.h5 import *
from pyzebra.xtal import * from pyzebra.xtal import *
from pyzebra.ccl_process import * from pyzebra.ccl_process import *
__version__ = "0.3.0" __version__ = "0.3.2"

View File

@ -23,8 +23,17 @@ REFLECTION_PRINTER_FORMATS = [
ALGORITHMS = ["adaptivemaxcog", "adaptivedynamic"] ALGORITHMS = ["adaptivemaxcog", "adaptivedynamic"]
def anatric(config_file, anatric_path="/afs/psi.ch/project/sinq/rhel7/bin/anatric"): def anatric(config_file, anatric_path="/afs/psi.ch/project/sinq/rhel7/bin/anatric", cwd=None):
subprocess.run([anatric_path, config_file], check=True) comp_proc = subprocess.run(
[anatric_path, config_file],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
cwd=cwd,
check=True,
text=True,
)
print(" ".join(comp_proc.args))
print(comp_proc.stdout)
class AnatricConfig: class AnatricConfig:
@ -51,10 +60,13 @@ class AnatricConfig:
def save_as(self, filename): def save_as(self, filename):
self._tree.write(filename) self._tree.write(filename)
def tostring(self):
return ET.tostring(self._tree.getroot(), encoding="unicode")
def _get_attr(self, name, tag, attr): def _get_attr(self, name, tag, attr):
elem = self._tree.find(name).find(tag) elem = self._tree.find(name).find(tag)
if elem is None: if elem is None:
return None return ""
return elem.attrib[attr] return elem.attrib[attr]
def _set_attr(self, name, tag, attr, value): def _set_attr(self, name, tag, attr, value):
@ -217,7 +229,7 @@ class AnatricConfig:
elem = self._tree.find("crystal").find("UB") elem = self._tree.find("crystal").find("UB")
if elem is not None: if elem is not None:
return elem.text return elem.text
return None return ""
@crystal_UB.setter @crystal_UB.setter
def crystal_UB(self, value): def crystal_UB(self, value):
@ -236,12 +248,37 @@ class AnatricConfig:
@property @property
def dataFactory_dist1(self): def dataFactory_dist1(self):
return self._tree.find("DataFactory").find("dist1").attrib["value"] elem = self._tree.find("DataFactory").find("dist1")
if elem is not None:
return elem.attrib["value"]
return ""
@dataFactory_dist1.setter @dataFactory_dist1.setter
def dataFactory_dist1(self, value): def dataFactory_dist1(self, value):
self._tree.find("DataFactory").find("dist1").attrib["value"] = value self._tree.find("DataFactory").find("dist1").attrib["value"] = value
@property
def dataFactory_dist2(self):
elem = self._tree.find("DataFactory").find("dist2")
if elem is not None:
return elem.attrib["value"]
return ""
@dataFactory_dist2.setter
def dataFactory_dist2(self, value):
self._tree.find("DataFactory").find("dist2").attrib["value"] = value
@property
def dataFactory_dist3(self):
elem = self._tree.find("DataFactory").find("dist3")
if elem is not None:
return elem.attrib["value"]
return ""
@dataFactory_dist3.setter
def dataFactory_dist3(self, value):
self._tree.find("DataFactory").find("dist3").attrib["value"] = value
@property @property
def reflectionPrinter_format(self): def reflectionPrinter_format(self):
return self._tree.find("ReflectionPrinter").attrib["format"] return self._tree.find("ReflectionPrinter").attrib["format"]
@ -253,6 +290,14 @@ class AnatricConfig:
self._tree.find("ReflectionPrinter").attrib["format"] = value self._tree.find("ReflectionPrinter").attrib["format"] = value
@property
def reflectionPrinter_file(self):
return self._tree.find("ReflectionPrinter").attrib["file"]
@reflectionPrinter_file.setter
def reflectionPrinter_file(self, value):
self._tree.find("ReflectionPrinter").attrib["file"] = value
@property @property
def algorithm(self): def algorithm(self):
return self._tree.find("Algorithm").attrib["implementation"] return self._tree.find("Algorithm").attrib["implementation"]
@ -269,7 +314,7 @@ class AnatricConfig:
def _get_alg_attr(self, alg, tag, attr): def _get_alg_attr(self, alg, tag, attr):
param_elem = self._alg_elems[alg].find(tag) param_elem = self._alg_elems[alg].find(tag)
if param_elem is None: if param_elem is None:
return None return ""
return param_elem.attrib[attr] return param_elem.attrib[attr]
def _set_alg_attr(self, alg, tag, attr, value): def _set_alg_attr(self, alg, tag, attr, value):

View File

@ -10,6 +10,7 @@ from bokeh.models import (
BasicTicker, BasicTicker,
Button, Button,
CheckboxEditor, CheckboxEditor,
CheckboxGroup,
ColumnDataSource, ColumnDataSource,
CustomJS, CustomJS,
DataRange1d, DataRange1d,
@ -37,7 +38,6 @@ from bokeh.models import (
TableColumn, TableColumn,
TextAreaInput, TextAreaInput,
TextInput, TextInput,
Toggle,
WheelZoomTool, WheelZoomTool,
Whisker, Whisker,
) )
@ -47,23 +47,26 @@ from pyzebra.ccl_io import AREA_METHODS
javaScript = """ javaScript = """
let j = 0;
for (let i = 0; i < js_data.data['fname'].length; i++) { for (let i = 0; i < js_data.data['fname'].length; i++) {
if (js_data.data['content'][i] === "") continue; if (js_data.data['content'][i] === "") continue;
const blob = new Blob([js_data.data['content'][i]], {type: 'text/plain'}) setTimeout(function() {
const link = document.createElement('a'); const blob = new Blob([js_data.data['content'][i]], {type: 'text/plain'})
document.body.appendChild(link); const link = document.createElement('a');
const url = window.URL.createObjectURL(blob); document.body.appendChild(link);
link.href = url; const url = window.URL.createObjectURL(blob);
link.download = js_data.data['fname'][i]; link.href = url;
link.click(); link.download = js_data.data['fname'][i];
window.URL.revokeObjectURL(url); link.click();
document.body.removeChild(link); window.URL.revokeObjectURL(url);
document.body.removeChild(link);
}, 100 * j)
j++;
} }
""" """
PROPOSAL_PATH = "/afs/psi.ch/project/sinqdata/2020/zebra/"
def create(): def create():
det_data = {} det_data = {}
@ -71,14 +74,16 @@ def create():
js_data = ColumnDataSource(data=dict(content=["", ""], fname=["", ""])) js_data = ColumnDataSource(data=dict(content=["", ""], fname=["", ""]))
def proposal_textinput_callback(_attr, _old, new): def proposal_textinput_callback(_attr, _old, new):
ccl_path = os.path.join(PROPOSAL_PATH, new.strip()) proposal = new.strip()
ccl_file_list = [] year = new[:4]
for file in os.listdir(ccl_path): proposal_path = f"/afs/psi.ch/project/sinqdata/{year}/zebra/{proposal}"
file_list = []
for file in os.listdir(proposal_path):
if file.endswith((".ccl", ".dat")): if file.endswith((".ccl", ".dat")):
ccl_file_list.append((os.path.join(ccl_path, file), file)) file_list.append((os.path.join(proposal_path, file), file))
file_select.options = ccl_file_list file_select.options = file_list
proposal_textinput = TextInput(title="Proposal number:", default_size=145) proposal_textinput = TextInput(title="Proposal number:", width=210)
proposal_textinput.on_change("value", proposal_textinput_callback) proposal_textinput.on_change("value", proposal_textinput_callback)
def _init_datatable(): def _init_datatable():
@ -97,11 +102,7 @@ def create():
merge_dest_select.options = merge_options merge_dest_select.options = merge_options
merge_dest_select.value = merge_options[0][0] merge_dest_select.value = merge_options[0][0]
def ccl_file_select_callback(_attr, _old, _new): file_select = MultiSelect(title="Available .ccl/.dat files:", width=210, height=250)
pass
file_select = MultiSelect(title="Available .ccl/.dat files:", default_size=200, height=250)
file_select.on_change("value", ccl_file_select_callback)
def file_open_button_callback(): def file_open_button_callback():
nonlocal det_data nonlocal det_data
@ -121,7 +122,7 @@ def create():
_init_datatable() _init_datatable()
file_open_button = Button(label="Open New", default_size=100) file_open_button = Button(label="Open New", width=100)
file_open_button.on_click(file_open_button_callback) file_open_button.on_click(file_open_button_callback)
def file_append_button_callback(): def file_append_button_callback():
@ -135,7 +136,7 @@ def create():
_init_datatable() _init_datatable()
file_append_button = Button(label="Append", default_size=100) file_append_button = Button(label="Append", width=100)
file_append_button.on_click(file_append_button_callback) file_append_button.on_click(file_append_button_callback)
def upload_button_callback(_attr, _old, new): def upload_button_callback(_attr, _old, new):
@ -157,7 +158,7 @@ def create():
_init_datatable() _init_datatable()
upload_div = Div(text="or upload new .ccl/.dat files:", margin=(5, 5, 0, 5)) upload_div = Div(text="or upload new .ccl/.dat files:", margin=(5, 5, 0, 5))
upload_button = FileInput(accept=".ccl,.dat", multiple=True, default_size=200) upload_button = FileInput(accept=".ccl,.dat", multiple=True, width=200)
upload_button.on_change("value", upload_button_callback) upload_button.on_change("value", upload_button_callback)
def append_upload_button_callback(_attr, _old, new): def append_upload_button_callback(_attr, _old, new):
@ -172,7 +173,7 @@ def create():
_init_datatable() _init_datatable()
append_upload_div = Div(text="append extra files:", margin=(5, 5, 0, 5)) append_upload_div = Div(text="append extra files:", margin=(5, 5, 0, 5))
append_upload_button = FileInput(accept=".ccl,.dat", multiple=True, default_size=200) append_upload_button = FileInput(accept=".ccl,.dat", multiple=True, width=200)
append_upload_button.on_change("value", append_upload_button_callback) append_upload_button.on_change("value", append_upload_button_callback)
def monitor_spinner_callback(_attr, old, new): def monitor_spinner_callback(_attr, old, new):
@ -254,7 +255,7 @@ def create():
plot_bkg_source, Line(x="x", y="y", line_color="green", line_dash="dashed") plot_bkg_source, Line(x="x", y="y", line_color="green", line_dash="dashed")
) )
plot_peak_source = ColumnDataSource(dict(xs=[0], ys=[0])) plot_peak_source = ColumnDataSource(dict(xs=[[0]], ys=[[0]]))
plot_peak = plot.add_glyph( plot_peak = plot.add_glyph(
plot_peak_source, MultiLine(xs="xs", ys="ys", line_color="red", line_dash="dashed") plot_peak_source, MultiLine(xs="xs", ys="ys", line_color="red", line_dash="dashed")
) )
@ -299,7 +300,12 @@ def create():
_update_plot(det_data[new[0]]) _update_plot(det_data[new[0]])
def scan_table_source_callback(_attr, _old, _new):
_update_preview()
scan_table_source = ColumnDataSource(dict(scan=[], hkl=[], fit=[], export=[])) scan_table_source = ColumnDataSource(dict(scan=[], hkl=[], fit=[], export=[]))
scan_table_source.on_change("data", scan_table_source_callback)
scan_table = DataTable( scan_table = DataTable(
source=scan_table_source, source=scan_table_source,
columns=[ columns=[
@ -339,13 +345,13 @@ def create():
def fit_from_spinner_callback(_attr, _old, new): def fit_from_spinner_callback(_attr, _old, new):
fit_from_span.location = new fit_from_span.location = new
fit_from_spinner = Spinner(title="Fit from:", default_size=145) fit_from_spinner = Spinner(title="Fit from:", width=145)
fit_from_spinner.on_change("value", fit_from_spinner_callback) fit_from_spinner.on_change("value", fit_from_spinner_callback)
def fit_to_spinner_callback(_attr, _old, new): def fit_to_spinner_callback(_attr, _old, new):
fit_to_span.location = new fit_to_span.location = new
fit_to_spinner = Spinner(title="to:", default_size=145) fit_to_spinner = Spinner(title="to:", width=145)
fit_to_spinner.on_change("value", fit_to_spinner_callback) fit_to_spinner.on_change("value", fit_to_spinner_callback)
def fitparams_add_dropdown_callback(click): def fitparams_add_dropdown_callback(click):
@ -364,7 +370,7 @@ def create():
("Pseudo Voigt", "pvoigt"), ("Pseudo Voigt", "pvoigt"),
# ("Pseudo Voigt1", "pseudovoigt1"), # ("Pseudo Voigt1", "pseudovoigt1"),
], ],
default_size=145, width=145,
disabled=True, disabled=True,
) )
fitparams_add_dropdown.on_click(fitparams_add_dropdown_callback) fitparams_add_dropdown.on_click(fitparams_add_dropdown_callback)
@ -385,7 +391,7 @@ def create():
else: else:
fitparams_table_source.data.update(dict(param=[], value=[], vary=[], min=[], max=[])) fitparams_table_source.data.update(dict(param=[], value=[], vary=[], min=[], max=[]))
fitparams_select = MultiSelect(options=[], height=120, default_size=145) fitparams_select = MultiSelect(options=[], height=120, width=145)
fitparams_select.tags = [0] fitparams_select.tags = [0]
fitparams_select.on_change("value", fitparams_select_callback) fitparams_select.on_change("value", fitparams_select_callback)
@ -400,7 +406,7 @@ def create():
fitparams_select.value = [] fitparams_select.value = []
fitparams_remove_button = Button(label="Remove fit function", default_size=145, disabled=True) fitparams_remove_button = Button(label="Remove fit function", width=145, disabled=True)
fitparams_remove_button.on_click(fitparams_remove_button_callback) fitparams_remove_button.on_click(fitparams_remove_button_callback)
def fitparams_factory(function): def fitparams_factory(function):
@ -422,6 +428,14 @@ def create():
param=params, value=[None] * n, vary=[True] * n, min=[None] * n, max=[None] * n, param=params, value=[None] * n, vary=[True] * n, min=[None] * n, max=[None] * n,
) )
if function == "linear":
fitparams["value"] = [0, 1]
fitparams["vary"] = [False, True]
fitparams["min"] = [None, 0]
elif function == "gaussian":
fitparams["min"] = [0, None, None]
return fitparams return fitparams
fitparams_table_source = ColumnDataSource(dict(param=[], value=[], vary=[], min=[], max=[])) fitparams_table_source = ColumnDataSource(dict(param=[], value=[], vary=[], min=[], max=[]))
@ -458,7 +472,7 @@ def create():
_update_plot(_get_selected_scan()) _update_plot(_get_selected_scan())
_update_table() _update_table()
fit_all_button = Button(label="Fit All", button_type="primary", default_size=145) fit_all_button = Button(label="Fit All", button_type="primary", width=145)
fit_all_button.on_click(fit_all_button_callback) fit_all_button.on_click(fit_all_button_callback)
def fit_button_callback(): def fit_button_callback():
@ -470,22 +484,26 @@ def create():
_update_plot(scan) _update_plot(scan)
_update_table() _update_table()
fit_button = Button(label="Fit Current", default_size=145) fit_button = Button(label="Fit Current", width=145)
fit_button.on_click(fit_button_callback) fit_button.on_click(fit_button_callback)
def area_method_radiobutton_callback(_handler):
_update_preview()
area_method_radiobutton = RadioButtonGroup( area_method_radiobutton = RadioButtonGroup(
labels=["Fit area", "Int area"], active=0, default_size=145, disabled=True labels=["Fit area", "Int area"], active=0, width=145, disabled=True
) )
area_method_radiobutton.on_click(area_method_radiobutton_callback)
bin_size_spinner = Spinner( def lorentz_checkbox_callback(_handler):
title="Bin size:", value=1, low=1, step=1, default_size=145, disabled=True _update_preview()
)
lorentz_toggle = Toggle(label="Lorentz Correction", default_size=145) lorentz_checkbox = CheckboxGroup(labels=["Lorentz Correction"], width=145, margin=[13, 5, 5, 5])
lorentz_checkbox.on_click(lorentz_checkbox_callback)
export_preview_textinput = TextAreaInput(title="Export preview:", width=500, height=400) export_preview_textinput = TextAreaInput(title="Export file preview:", width=500, height=400)
def preview_button_callback(): def _update_preview():
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
temp_file = temp_dir + "/temp" temp_file = temp_dir + "/temp"
export_data = [] export_data = []
@ -497,7 +515,7 @@ def create():
export_data, export_data,
temp_file, temp_file,
area_method=AREA_METHODS[int(area_method_radiobutton.active)], area_method=AREA_METHODS[int(area_method_radiobutton.active)],
lorentz=lorentz_toggle.active, lorentz=bool(lorentz_checkbox.active),
hkl_precision=int(hkl_precision_select.value), hkl_precision=int(hkl_precision_select.value),
) )
@ -516,14 +534,15 @@ def create():
js_data.data.update(content=file_content) js_data.data.update(content=file_content)
export_preview_textinput.value = exported_content export_preview_textinput.value = exported_content
preview_button = Button(label="Preview", default_size=200) def hkl_precision_select_callback(_attr, _old, _new):
preview_button.on_click(preview_button_callback) _update_preview()
hkl_precision_select = Select( hkl_precision_select = Select(
title="hkl precision:", options=["2", "3", "4"], value="2", default_size=80 title="hkl precision:", options=["2", "3", "4"], value="2", width=80
) )
hkl_precision_select.on_change("value", hkl_precision_select_callback)
save_button = Button(label="Download preview", button_type="success", default_size=200) save_button = Button(label="Download File", button_type="success", width=200)
save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript)) save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript))
fitpeak_controls = row( fitpeak_controls = row(
@ -532,8 +551,7 @@ def create():
Spacer(width=20), Spacer(width=20),
column( column(
row(fit_from_spinner, fit_to_spinner), row(fit_from_spinner, fit_to_spinner),
row(bin_size_spinner, column(Spacer(height=19), lorentz_toggle)), row(area_method_radiobutton, lorentz_checkbox),
row(area_method_radiobutton),
row(fit_button, fit_all_button), row(fit_button, fit_all_button),
), ),
) )
@ -556,7 +574,7 @@ def create():
export_layout = column( export_layout = column(
export_preview_textinput, export_preview_textinput,
row(hkl_precision_select, column(Spacer(height=19), row(preview_button, save_button))), row(hkl_precision_select, column(Spacer(height=19), row(save_button))),
) )
tab_layout = column( tab_layout = column(

View File

@ -1,5 +1,6 @@
import base64 import base64
import io import io
import os
import re import re
import tempfile import tempfile
@ -10,9 +11,9 @@ from bokeh.models import (
Div, Div,
FileInput, FileInput,
Panel, Panel,
RadioButtonGroup,
Select, Select,
Spacer, Spacer,
Tabs,
TextAreaInput, TextAreaInput,
TextInput, TextInput,
) )
@ -29,7 +30,7 @@ def create():
config.load_from_file(file) config.load_from_file(file)
logfile_textinput.value = config.logfile logfile_textinput.value = config.logfile
logfile_verbosity_select.value = config.logfile_verbosity logfile_verbosity.value = config.logfile_verbosity
filelist_type.value = config.filelist_type filelist_type.value = config.filelist_type
filelist_format_textinput.value = config.filelist_format filelist_format_textinput.value = config.filelist_format
@ -44,11 +45,16 @@ def create():
ub_textareainput.value = config.crystal_UB ub_textareainput.value = config.crystal_UB
dataFactory_implementation_select.value = config.dataFactory_implementation dataFactory_implementation_select.value = config.dataFactory_implementation
dataFactory_dist1_textinput.value = config.dataFactory_dist1 if config.dataFactory_dist1 is not None:
dataFactory_dist1_textinput.value = config.dataFactory_dist1
if config.dataFactory_dist2 is not None:
dataFactory_dist2_textinput.value = config.dataFactory_dist2
if config.dataFactory_dist3 is not None:
dataFactory_dist3_textinput.value = config.dataFactory_dist3
reflectionPrinter_format_select.value = config.reflectionPrinter_format reflectionPrinter_format_select.value = config.reflectionPrinter_format
set_active_widgets(config.algorithm)
if config.algorithm == "adaptivemaxcog": if config.algorithm == "adaptivemaxcog":
algorithm_params.active = 0
threshold_textinput.value = config.threshold threshold_textinput.value = config.threshold
shell_textinput.value = config.shell shell_textinput.value = config.shell
steepness_textinput.value = config.steepness steepness_textinput.value = config.steepness
@ -57,6 +63,7 @@ def create():
aps_window_textinput.value = str(tuple(map(int, config.aps_window.values()))) aps_window_textinput.value = str(tuple(map(int, config.aps_window.values())))
elif config.algorithm == "adaptivedynamic": elif config.algorithm == "adaptivedynamic":
algorithm_params.active = 1
adm_window_textinput.value = str(tuple(map(int, config.adm_window.values()))) adm_window_textinput.value = str(tuple(map(int, config.adm_window.values())))
border_textinput.value = str(tuple(map(int, config.border.values()))) border_textinput.value = str(tuple(map(int, config.border.values())))
minWindow_textinput.value = str(tuple(map(int, config.minWindow.values()))) minWindow_textinput.value = str(tuple(map(int, config.minWindow.values())))
@ -66,45 +73,16 @@ def create():
loop_textinput.value = config.loop loop_textinput.value = config.loop
minPeakCount_textinput.value = config.minPeakCount minPeakCount_textinput.value = config.minPeakCount
displacementCurve_textinput.value = "\n".join(map(str, config.displacementCurve)) displacementCurve_textinput.value = "\n".join(map(str, config.displacementCurve))
else: else:
raise ValueError("Unknown processing mode.") raise ValueError("Unknown processing mode.")
def set_active_widgets(implementation):
if implementation == "adaptivemaxcog":
mode_radio_button_group.active = 0
disable_adaptivemaxcog = False
disable_adaptivedynamic = True
elif implementation == "adaptivedynamic":
mode_radio_button_group.active = 1
disable_adaptivemaxcog = True
disable_adaptivedynamic = False
else:
raise ValueError("Implementation can be either 'adaptivemaxcog' or 'adaptivedynamic'")
threshold_textinput.disabled = disable_adaptivemaxcog
shell_textinput.disabled = disable_adaptivemaxcog
steepness_textinput.disabled = disable_adaptivemaxcog
duplicateDistance_textinput.disabled = disable_adaptivemaxcog
maxequal_textinput.disabled = disable_adaptivemaxcog
aps_window_textinput.disabled = disable_adaptivemaxcog
adm_window_textinput.disabled = disable_adaptivedynamic
border_textinput.disabled = disable_adaptivedynamic
minWindow_textinput.disabled = disable_adaptivedynamic
reflectionFile_textinput.disabled = disable_adaptivedynamic
targetMonitor_textinput.disabled = disable_adaptivedynamic
smoothSize_textinput.disabled = disable_adaptivedynamic
loop_textinput.disabled = disable_adaptivedynamic
minPeakCount_textinput.disabled = disable_adaptivedynamic
displacementCurve_textinput.disabled = disable_adaptivedynamic
def upload_button_callback(_attr, _old, new): def upload_button_callback(_attr, _old, new):
with io.BytesIO(base64.b64decode(new)) as file: with io.BytesIO(base64.b64decode(new)) as file:
_load_config_file(file) _load_config_file(file)
upload_div = Div(text="Open XML configuration file:") upload_div = Div(text="Open .xml config:")
upload_button = FileInput(accept=".xml") upload_button = FileInput(accept=".xml", width=200)
upload_button.on_change("value", upload_button_callback) upload_button.on_change("value", upload_button_callback)
# General parameters # General parameters
@ -112,16 +90,14 @@ def create():
def logfile_textinput_callback(_attr, _old, new): def logfile_textinput_callback(_attr, _old, new):
config.logfile = new config.logfile = new
logfile_textinput = TextInput(title="Logfile:", value="logfile.log", width=320) logfile_textinput = TextInput(title="Logfile:", value="logfile.log")
logfile_textinput.on_change("value", logfile_textinput_callback) logfile_textinput.on_change("value", logfile_textinput_callback)
def logfile_verbosity_select_callback(_attr, _old, new): def logfile_verbosity_callback(_attr, _old, new):
config.logfile_verbosity = new config.logfile_verbosity = new
logfile_verbosity_select = Select( logfile_verbosity = TextInput(title="verbosity:", width=70)
title="verbosity:", options=["0", "5", "10", "15", "30"], width=70 logfile_verbosity.on_change("value", logfile_verbosity_callback)
)
logfile_verbosity_select.on_change("value", logfile_verbosity_select_callback)
# ---- FileList # ---- FileList
def filelist_type_callback(_attr, _old, new): def filelist_type_callback(_attr, _old, new):
@ -148,20 +124,20 @@ def create():
ranges.append(re.findall(r"\b\d+\b", line)) ranges.append(re.findall(r"\b\d+\b", line))
config.filelist_ranges = ranges config.filelist_ranges = ranges
filelist_ranges_textareainput = TextAreaInput(title="ranges:", height=100) filelist_ranges_textareainput = TextAreaInput(title="ranges:", rows=1)
filelist_ranges_textareainput.on_change("value", filelist_ranges_textareainput_callback) filelist_ranges_textareainput.on_change("value", filelist_ranges_textareainput_callback)
# ---- crystal # ---- crystal
def crystal_sample_textinput_callback(_attr, _old, new): def crystal_sample_textinput_callback(_attr, _old, new):
config.crystal_sample = new config.crystal_sample = new
crystal_sample_textinput = TextInput(title="Sample Name:") crystal_sample_textinput = TextInput(title="Sample Name:", width=290)
crystal_sample_textinput.on_change("value", crystal_sample_textinput_callback) crystal_sample_textinput.on_change("value", crystal_sample_textinput_callback)
def lambda_textinput_callback(_attr, _old, new): def lambda_textinput_callback(_attr, _old, new):
config.crystal_lambda = new config.crystal_lambda = new
lambda_textinput = TextInput(title="lambda:", width=145) lambda_textinput = TextInput(title="lambda:", width=100)
lambda_textinput.on_change("value", lambda_textinput_callback) lambda_textinput.on_change("value", lambda_textinput_callback)
def ub_textareainput_callback(_attr, _old, new): def ub_textareainput_callback(_attr, _old, new):
@ -173,19 +149,19 @@ def create():
def zeroOM_textinput_callback(_attr, _old, new): def zeroOM_textinput_callback(_attr, _old, new):
config.crystal_zeroOM = new config.crystal_zeroOM = new
zeroOM_textinput = TextInput(title="zeroOM:", width=145) zeroOM_textinput = TextInput(title="zeroOM:", width=100)
zeroOM_textinput.on_change("value", zeroOM_textinput_callback) zeroOM_textinput.on_change("value", zeroOM_textinput_callback)
def zeroSTT_textinput_callback(_attr, _old, new): def zeroSTT_textinput_callback(_attr, _old, new):
config.crystal_zeroSTT = new config.crystal_zeroSTT = new
zeroSTT_textinput = TextInput(title="zeroSTT:", width=145) zeroSTT_textinput = TextInput(title="zeroSTT:", width=100)
zeroSTT_textinput.on_change("value", zeroSTT_textinput_callback) zeroSTT_textinput.on_change("value", zeroSTT_textinput_callback)
def zeroCHI_textinput_callback(_attr, _old, new): def zeroCHI_textinput_callback(_attr, _old, new):
config.crystal_zeroCHI = new config.crystal_zeroCHI = new
zeroCHI_textinput = TextInput(title="zeroCHI:", width=145) zeroCHI_textinput = TextInput(title="zeroCHI:", width=100)
zeroCHI_textinput.on_change("value", zeroCHI_textinput_callback) zeroCHI_textinput.on_change("value", zeroCHI_textinput_callback)
# ---- DataFactory # ---- DataFactory
@ -200,9 +176,21 @@ def create():
def dataFactory_dist1_textinput_callback(_attr, _old, new): def dataFactory_dist1_textinput_callback(_attr, _old, new):
config.dataFactory_dist1 = new config.dataFactory_dist1 = new
dataFactory_dist1_textinput = TextInput(title="dist1:", width=145) dataFactory_dist1_textinput = TextInput(title="dist1:", width=75)
dataFactory_dist1_textinput.on_change("value", dataFactory_dist1_textinput_callback) dataFactory_dist1_textinput.on_change("value", dataFactory_dist1_textinput_callback)
def dataFactory_dist2_textinput_callback(_attr, _old, new):
config.dataFactory_dist2 = new
dataFactory_dist2_textinput = TextInput(title="dist2:", width=75)
dataFactory_dist2_textinput.on_change("value", dataFactory_dist2_textinput_callback)
def dataFactory_dist3_textinput_callback(_attr, _old, new):
config.dataFactory_dist3 = new
dataFactory_dist3_textinput = TextInput(title="dist3:", width=75)
dataFactory_dist3_textinput.on_change("value", dataFactory_dist3_textinput_callback)
# ---- BackgroundProcessor # ---- BackgroundProcessor
# ---- DetectorEfficency # ---- DetectorEfficency
@ -221,42 +209,42 @@ def create():
def threshold_textinput_callback(_attr, _old, new): def threshold_textinput_callback(_attr, _old, new):
config.threshold = new config.threshold = new
threshold_textinput = TextInput(title="Threshold:") threshold_textinput = TextInput(title="Threshold:", width=145)
threshold_textinput.on_change("value", threshold_textinput_callback) threshold_textinput.on_change("value", threshold_textinput_callback)
# ---- shell # ---- shell
def shell_textinput_callback(_attr, _old, new): def shell_textinput_callback(_attr, _old, new):
config.shell = new config.shell = new
shell_textinput = TextInput(title="Shell:") shell_textinput = TextInput(title="Shell:", width=145)
shell_textinput.on_change("value", shell_textinput_callback) shell_textinput.on_change("value", shell_textinput_callback)
# ---- steepness # ---- steepness
def steepness_textinput_callback(_attr, _old, new): def steepness_textinput_callback(_attr, _old, new):
config.steepness = new config.steepness = new
steepness_textinput = TextInput(title="Steepness:") steepness_textinput = TextInput(title="Steepness:", width=145)
steepness_textinput.on_change("value", steepness_textinput_callback) steepness_textinput.on_change("value", steepness_textinput_callback)
# ---- duplicateDistance # ---- duplicateDistance
def duplicateDistance_textinput_callback(_attr, _old, new): def duplicateDistance_textinput_callback(_attr, _old, new):
config.duplicateDistance = new config.duplicateDistance = new
duplicateDistance_textinput = TextInput(title="Duplicate Distance:") duplicateDistance_textinput = TextInput(title="Duplicate Distance:", width=145)
duplicateDistance_textinput.on_change("value", duplicateDistance_textinput_callback) duplicateDistance_textinput.on_change("value", duplicateDistance_textinput_callback)
# ---- maxequal # ---- maxequal
def maxequal_textinput_callback(_attr, _old, new): def maxequal_textinput_callback(_attr, _old, new):
config.maxequal = new config.maxequal = new
maxequal_textinput = TextInput(title="Max Equal:") maxequal_textinput = TextInput(title="Max Equal:", width=145)
maxequal_textinput.on_change("value", maxequal_textinput_callback) maxequal_textinput.on_change("value", maxequal_textinput_callback)
# ---- window # ---- window
def aps_window_textinput_callback(_attr, _old, new): def aps_window_textinput_callback(_attr, _old, new):
config.aps_window = dict(zip(("x", "y", "z"), re.findall(r"\b\d+\b", new))) config.aps_window = dict(zip(("x", "y", "z"), re.findall(r"\b\d+\b", new)))
aps_window_textinput = TextInput(title="Window (x, y, z):") aps_window_textinput = TextInput(title="Window (x, y, z):", width=145)
aps_window_textinput.on_change("value", aps_window_textinput_callback) aps_window_textinput.on_change("value", aps_window_textinput_callback)
# Adaptive Dynamic Mask Integration (adaptivedynamic) # Adaptive Dynamic Mask Integration (adaptivedynamic)
@ -264,56 +252,56 @@ def create():
def adm_window_textinput_callback(_attr, _old, new): def adm_window_textinput_callback(_attr, _old, new):
config.adm_window = dict(zip(("x", "y", "z"), re.findall(r"\b\d+\b", new))) config.adm_window = dict(zip(("x", "y", "z"), re.findall(r"\b\d+\b", new)))
adm_window_textinput = TextInput(title="Window (x, y, z):") adm_window_textinput = TextInput(title="Window (x, y, z):", width=145)
adm_window_textinput.on_change("value", adm_window_textinput_callback) adm_window_textinput.on_change("value", adm_window_textinput_callback)
# ---- border # ---- border
def border_textinput_callback(_attr, _old, new): def border_textinput_callback(_attr, _old, new):
config.border = dict(zip(("x", "y", "z"), re.findall(r"\b\d+\b", new))) config.border = dict(zip(("x", "y", "z"), re.findall(r"\b\d+\b", new)))
border_textinput = TextInput(title="Border (x, y, z):") border_textinput = TextInput(title="Border (x, y, z):", width=145)
border_textinput.on_change("value", border_textinput_callback) border_textinput.on_change("value", border_textinput_callback)
# ---- minWindow # ---- minWindow
def minWindow_textinput_callback(_attr, _old, new): def minWindow_textinput_callback(_attr, _old, new):
config.minWindow = dict(zip(("x", "y", "z"), re.findall(r"\b\d+\b", new))) config.minWindow = dict(zip(("x", "y", "z"), re.findall(r"\b\d+\b", new)))
minWindow_textinput = TextInput(title="Min Window (x, y, z):") minWindow_textinput = TextInput(title="Min Window (x, y, z):", width=145)
minWindow_textinput.on_change("value", minWindow_textinput_callback) minWindow_textinput.on_change("value", minWindow_textinput_callback)
# ---- reflectionFile # ---- reflectionFile
def reflectionFile_textinput_callback(_attr, _old, new): def reflectionFile_textinput_callback(_attr, _old, new):
config.reflectionFile = new config.reflectionFile = new
reflectionFile_textinput = TextInput(title="Reflection File:") reflectionFile_textinput = TextInput(title="Reflection File:", width=145)
reflectionFile_textinput.on_change("value", reflectionFile_textinput_callback) reflectionFile_textinput.on_change("value", reflectionFile_textinput_callback)
# ---- targetMonitor # ---- targetMonitor
def targetMonitor_textinput_callback(_attr, _old, new): def targetMonitor_textinput_callback(_attr, _old, new):
config.targetMonitor = new config.targetMonitor = new
targetMonitor_textinput = TextInput(title="Target Monitor:") targetMonitor_textinput = TextInput(title="Target Monitor:", width=145)
targetMonitor_textinput.on_change("value", targetMonitor_textinput_callback) targetMonitor_textinput.on_change("value", targetMonitor_textinput_callback)
# ---- smoothSize # ---- smoothSize
def smoothSize_textinput_callback(_attr, _old, new): def smoothSize_textinput_callback(_attr, _old, new):
config.smoothSize = new config.smoothSize = new
smoothSize_textinput = TextInput(title="Smooth Size:") smoothSize_textinput = TextInput(title="Smooth Size:", width=145)
smoothSize_textinput.on_change("value", smoothSize_textinput_callback) smoothSize_textinput.on_change("value", smoothSize_textinput_callback)
# ---- loop # ---- loop
def loop_textinput_callback(_attr, _old, new): def loop_textinput_callback(_attr, _old, new):
config.loop = new config.loop = new
loop_textinput = TextInput(title="Loop:") loop_textinput = TextInput(title="Loop:", width=145)
loop_textinput.on_change("value", loop_textinput_callback) loop_textinput.on_change("value", loop_textinput_callback)
# ---- minPeakCount # ---- minPeakCount
def minPeakCount_textinput_callback(_attr, _old, new): def minPeakCount_textinput_callback(_attr, _old, new):
config.minPeakCount = new config.minPeakCount = new
minPeakCount_textinput = TextInput(title="Min Peak Count:") minPeakCount_textinput = TextInput(title="Min Peak Count:", width=145)
minPeakCount_textinput.on_change("value", minPeakCount_textinput_callback) minPeakCount_textinput.on_change("value", minPeakCount_textinput_callback)
# ---- displacementCurve # ---- displacementCurve
@ -324,95 +312,85 @@ def create():
config.displacementCurve = maps config.displacementCurve = maps
displacementCurve_textinput = TextAreaInput( displacementCurve_textinput = TextAreaInput(
title="Displacement Curve (twotheta, x, y):", height=100 title="Displ. Curve (, x, y):", width=145, height=100
) )
displacementCurve_textinput.on_change("value", displacementCurve_textinput_callback) displacementCurve_textinput.on_change("value", displacementCurve_textinput_callback)
def mode_radio_button_group_callback(active): def algorithm_tabs_callback(_attr, _old, new):
if active == 0: if new == 0:
config.algorithm = "adaptivemaxcog" config.algorithm = "adaptivemaxcog"
set_active_widgets("adaptivemaxcog")
else: else:
config.algorithm = "adaptivedynamic" config.algorithm = "adaptivedynamic"
set_active_widgets("adaptivedynamic")
mode_radio_button_group = RadioButtonGroup( algorithm_params = Tabs(
labels=["Adaptive Peak Detection", "Adaptive Dynamic Integration"], active=0 tabs=[
Panel(
child=column(
row(threshold_textinput, shell_textinput, steepness_textinput),
row(duplicateDistance_textinput, maxequal_textinput, aps_window_textinput),
),
title="Peak Search",
),
Panel(
child=column(
row(adm_window_textinput, border_textinput, minWindow_textinput),
row(reflectionFile_textinput, targetMonitor_textinput, smoothSize_textinput),
row(loop_textinput, minPeakCount_textinput, displacementCurve_textinput),
),
title="Dynamic Integration",
),
]
) )
mode_radio_button_group.on_click(mode_radio_button_group_callback) algorithm_params.on_change("active", algorithm_tabs_callback)
set_active_widgets("adaptivemaxcog")
def process_button_callback(): def process_button_callback():
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
temp_file = temp_dir + "/temp.xml" temp_file = temp_dir + "/config.xml"
config.save_as(temp_file) config.save_as(temp_file)
if doc.anatric_path: if doc.anatric_path:
pyzebra.anatric(temp_file, anatric_path=doc.anatric_path) pyzebra.anatric(temp_file, anatric_path=doc.anatric_path, cwd=temp_dir)
else: else:
pyzebra.anatric(temp_file) pyzebra.anatric(temp_file, cwd=temp_dir)
with open(config.logfile) as f_log: with open(os.path.join(temp_dir, config.logfile)) as f_log:
output_log.value = f_log.read() output_log.value = f_log.read()
with open(os.path.join(temp_dir, config.reflectionPrinter_file)) as f_res:
output_res.value = f_res.read()
process_button = Button(label="Process", button_type="primary") process_button = Button(label="Process", button_type="primary")
process_button.on_click(process_button_callback) process_button.on_click(process_button_callback)
output_log = TextAreaInput(title="Logfile output:", height=600, disabled=True) output_log = TextAreaInput(title="Logfile output:", height=320, width=465, disabled=True)
output_config = TextAreaInput(title="Current config:", height=600, width=400, disabled=True) output_res = TextAreaInput(title="Result output:", height=320, width=465, disabled=True)
output_config = TextAreaInput(title="Current config:", height=320, width=465, disabled=True)
general_params_layout = column( general_params_layout = column(
row(logfile_textinput, logfile_verbosity_select), row(column(Spacer(height=2), upload_div), upload_button),
row(logfile_textinput, logfile_verbosity),
row(filelist_type, filelist_format_textinput), row(filelist_type, filelist_format_textinput),
filelist_datapath_textinput, filelist_datapath_textinput,
filelist_ranges_textareainput, filelist_ranges_textareainput,
crystal_sample_textinput, row(crystal_sample_textinput, lambda_textinput),
row(lambda_textinput, zeroOM_textinput),
row(zeroSTT_textinput, zeroCHI_textinput),
ub_textareainput, ub_textareainput,
row(dataFactory_implementation_select, dataFactory_dist1_textinput), row(zeroOM_textinput, zeroSTT_textinput, zeroCHI_textinput),
reflectionPrinter_format_select, row(
dataFactory_implementation_select,
dataFactory_dist1_textinput,
dataFactory_dist2_textinput,
dataFactory_dist3_textinput,
),
row(reflectionPrinter_format_select),
) )
algorithm_params_layout = column( tab_layout = row(
mode_radio_button_group, general_params_layout,
row( column(output_config, algorithm_params, row(process_button)),
column( column(output_log, output_res),
threshold_textinput,
shell_textinput,
steepness_textinput,
duplicateDistance_textinput,
maxequal_textinput,
aps_window_textinput,
),
column(
adm_window_textinput,
border_textinput,
minWindow_textinput,
reflectionFile_textinput,
targetMonitor_textinput,
smoothSize_textinput,
loop_textinput,
minPeakCount_textinput,
displacementCurve_textinput,
),
),
)
tab_layout = column(
row(column(Spacer(height=2), upload_div), upload_button),
row(
general_params_layout,
algorithm_params_layout,
column(row(output_config, output_log), row(process_button)),
),
) )
async def update_config(): async def update_config():
with tempfile.TemporaryDirectory() as temp_dir: output_config.value = config.tostring()
temp_file = temp_dir + "/debug.xml"
config.save_as(temp_file)
with open(temp_file) as f_config:
output_config.value = f_config.read()
doc.add_periodic_callback(update_config, 1000) doc.add_periodic_callback(update_config, 1000)

View File

@ -9,11 +9,15 @@ from bokeh.models import (
BoxEditTool, BoxEditTool,
BoxZoomTool, BoxZoomTool,
Button, Button,
CheckboxGroup,
ColumnDataSource, ColumnDataSource,
DataRange1d, DataRange1d,
DataTable,
Div, Div,
FileInput, FileInput,
Grid, Grid,
MultiSelect,
NumberFormatter,
HoverTool, HoverTool,
Image, Image,
Line, Line,
@ -22,17 +26,17 @@ from bokeh.models import (
Panel, Panel,
PanTool, PanTool,
Plot, Plot,
RadioButtonGroup,
Range1d, Range1d,
Rect, Rect,
ResetTool, ResetTool,
Select, Select,
Slider,
Spacer, Spacer,
Spinner, Spinner,
TableColumn,
TextAreaInput, TextAreaInput,
TextInput, TextInput,
Title, Title,
Toggle,
WheelZoomTool, WheelZoomTool,
) )
from bokeh.palettes import Cividis256, Greys256, Plasma256 # pylint: disable=E0611 from bokeh.palettes import Cividis256, Greys256, Plasma256 # pylint: disable=E0611
@ -41,10 +45,8 @@ import pyzebra
IMAGE_W = 256 IMAGE_W = 256
IMAGE_H = 128 IMAGE_H = 128
IMAGE_PLOT_W = int(IMAGE_W * 2.5) IMAGE_PLOT_W = int(IMAGE_W * 2) + 52
IMAGE_PLOT_H = int(IMAGE_H * 2.5) IMAGE_PLOT_H = int(IMAGE_H * 2) + 27
PROPOSAL_PATH = "/afs/psi.ch/project/sinqdata/2020/zebra/"
def create(): def create():
@ -52,26 +54,26 @@ def create():
roi_selection = {} roi_selection = {}
def proposal_textinput_callback(_attr, _old, new): def proposal_textinput_callback(_attr, _old, new):
full_proposal_path = os.path.join(PROPOSAL_PATH, new.strip()) proposal = new.strip()
year = new[:4]
proposal_path = f"/afs/psi.ch/project/sinqdata/{year}/zebra/{proposal}"
file_list = [] file_list = []
for file in os.listdir(full_proposal_path): for file in os.listdir(proposal_path):
if file.endswith(".hdf"): if file.endswith(".hdf"):
file_list.append((os.path.join(full_proposal_path, file), file)) file_list.append((os.path.join(proposal_path, file), file))
filelist.options = file_list file_select.options = file_list
filelist.value = file_list[0][0]
proposal_textinput = TextInput(title="Enter proposal number:", default_size=145) proposal_textinput = TextInput(title="Proposal number:", width=210)
proposal_textinput.on_change("value", proposal_textinput_callback) proposal_textinput.on_change("value", proposal_textinput_callback)
def upload_button_callback(_attr, _old, new): def upload_button_callback(_attr, _old, new):
with io.StringIO(base64.b64decode(new).decode()) as file: with io.StringIO(base64.b64decode(new).decode()) as file:
h5meta_list = pyzebra.parse_h5meta(file) h5meta_list = pyzebra.parse_h5meta(file)
file_list = h5meta_list["filelist"] file_list = h5meta_list["filelist"]
filelist.options = [(entry, os.path.basename(entry)) for entry in file_list] file_select.options = [(entry, os.path.basename(entry)) for entry in file_list]
filelist.value = file_list[0]
upload_div = Div(text="or upload .cami file:", margin=(5, 5, 0, 5)) upload_div = Div(text="or upload .cami file:", margin=(5, 5, 0, 5))
upload_button = FileInput(accept=".cami") upload_button = FileInput(accept=".cami", width=200)
upload_button.on_change("value", upload_button_callback) upload_button.on_change("value", upload_button_callback)
def update_image(index=None): def update_image(index=None):
@ -91,7 +93,7 @@ def create():
) )
image_source.data.update(image=[current_image]) image_source.data.update(image=[current_image])
if auto_toggle.active: if main_auto_checkbox.active:
im_min = np.min(current_image) im_min = np.min(current_image)
im_max = np.max(current_image) im_max = np.max(current_image)
@ -102,14 +104,14 @@ def create():
image_glyph.color_mapper.high = im_max image_glyph.color_mapper.high = im_max
if "mf" in det_data: if "mf" in det_data:
mf_spinner.value = det_data["mf"][index] metadata_table_source.data.update(mf=[det_data["mf"][index]])
else: else:
mf_spinner.value = None metadata_table_source.data.update(mf=[None])
if "temp" in det_data: if "temp" in det_data:
temp_spinner.value = det_data["temp"][index] metadata_table_source.data.update(temp=[det_data["temp"][index]])
else: else:
temp_spinner.value = None metadata_table_source.data.update(temp=[None])
gamma, nu = calculate_pol(det_data, index) gamma, nu = calculate_pol(det_data, index)
omega = np.ones((IMAGE_H, IMAGE_W)) * det_data["omega"][index] omega = np.ones((IMAGE_H, IMAGE_W)) * det_data["omega"][index]
@ -121,10 +123,10 @@ def create():
overview_x = np.mean(h5_data, axis=1) overview_x = np.mean(h5_data, axis=1)
overview_y = np.mean(h5_data, axis=2) overview_y = np.mean(h5_data, axis=2)
overview_plot_x_image_source.data.update(image=[overview_x], dw=[n_x]) overview_plot_x_image_source.data.update(image=[overview_x], dw=[n_x], dh=[n_im])
overview_plot_y_image_source.data.update(image=[overview_y], dw=[n_y]) overview_plot_y_image_source.data.update(image=[overview_y], dw=[n_y], dh=[n_im])
if proj_auto_toggle.active: if proj_auto_checkbox.active:
im_min = min(np.min(overview_x), np.min(overview_y)) im_min = min(np.min(overview_x), np.min(overview_y))
im_max = max(np.max(overview_x), np.max(overview_y)) im_max = max(np.max(overview_x), np.max(overview_y))
@ -136,48 +138,69 @@ def create():
overview_plot_x_image_glyph.color_mapper.high = im_max overview_plot_x_image_glyph.color_mapper.high = im_max
overview_plot_y_image_glyph.color_mapper.high = im_max overview_plot_y_image_glyph.color_mapper.high = im_max
if frame_button_group.active == 0: # Frame frame_range.start = 0
overview_plot_x.axis[1].axis_label = "Frame" frame_range.end = n_im
overview_plot_y.axis[1].axis_label = "Frame" frame_range.reset_start = 0
frame_range.reset_end = n_im
frame_range.bounds = (0, n_im)
overview_plot_x_image_source.data.update(y=[0], dh=[n_im]) scan_motor = det_data["scan_motor"]
overview_plot_y_image_source.data.update(y=[0], dh=[n_im]) overview_plot_y.axis[1].axis_label = f"Scanning motor, {scan_motor}"
elif frame_button_group.active == 1: # Variable angle var = det_data[scan_motor]
scan_motor = det_data["scan_motor"] var_start = var[0]
overview_plot_x.axis[1].axis_label = scan_motor var_end = var[-1] + (var[-1] - var[0]) / (n_im - 1)
overview_plot_y.axis[1].axis_label = scan_motor
var = det_data[scan_motor] scanning_motor_range.start = var_start
var_start = var[0] scanning_motor_range.end = var_end
var_end = (var[-1] - var[0]) * n_im / (n_im - 1) scanning_motor_range.reset_start = var_start
overview_plot_x_image_source.data.update(y=[var_start], dh=[var_end]) scanning_motor_range.reset_end = var_end
overview_plot_y_image_source.data.update(y=[var_start], dh=[var_end]) scanning_motor_range.bounds = (var_start, var_end)
def filelist_callback(_attr, _old, new): def file_select_callback(_attr, old, new):
nonlocal det_data nonlocal det_data
det_data = pyzebra.read_detector_data(new) if not new:
# skip empty selections
return
# Avoid selection of multiple indicies (via Shift+Click or Ctrl+Click)
if len(new) > 1:
# drop selection to the previous one
file_select.value = old
return
if len(old) > 1:
# skip unnecessary update caused by selection drop
return
det_data = pyzebra.read_detector_data(new[0])
index_spinner.value = 0 index_spinner.value = 0
index_spinner.high = det_data["data"].shape[0] - 1 index_spinner.high = det_data["data"].shape[0] - 1
index_slider.end = det_data["data"].shape[0] - 1
zebra_mode = det_data["zebra_mode"] zebra_mode = det_data["zebra_mode"]
if zebra_mode == "nb": if zebra_mode == "nb":
geometry_textinput.value = "normal beam" metadata_table_source.data.update(geom=["normal beam"])
else: # zebra_mode == "bi" else: # zebra_mode == "bi"
geometry_textinput.value = "bisecting" metadata_table_source.data.update(geom=["bisecting"])
update_image(0) update_image(0)
update_overview_plot() update_overview_plot()
filelist = Select(title="Available .hdf files:") file_select = MultiSelect(title="Available .hdf files:", width=210, height=250)
filelist.on_change("value", filelist_callback) file_select.on_change("value", file_select_callback)
def index_spinner_callback(_attr, _old, new): def index_callback(_attr, _old, new):
update_image(new) update_image(new)
index_spinner = Spinner(title="Image index:", value=0, low=0) index_slider = Slider(value=0, start=0, end=1, show_value=False, width=400)
index_spinner.on_change("value", index_spinner_callback)
index_spinner = Spinner(title="Image index:", value=0, low=0, width=100)
index_spinner.on_change("value", index_callback)
index_slider.js_link("value_throttled", index_spinner, "value")
index_spinner.js_link("value", index_slider, "value")
plot = Plot( plot = Plot(
x_range=Range1d(0, IMAGE_W, bounds=(0, IMAGE_W)), x_range=Range1d(0, IMAGE_W, bounds=(0, IMAGE_W)),
@ -310,15 +333,18 @@ def create():
) )
plot.toolbar.active_scroll = wheelzoomtool plot.toolbar.active_scroll = wheelzoomtool
# shared frame range # shared frame ranges
frame_range = DataRange1d() frame_range = Range1d(0, 1, bounds=(0, 1))
scanning_motor_range = Range1d(0, 1, bounds=(0, 1))
det_x_range = Range1d(0, IMAGE_W, bounds=(0, IMAGE_W)) det_x_range = Range1d(0, IMAGE_W, bounds=(0, IMAGE_W))
overview_plot_x = Plot( overview_plot_x = Plot(
title=Title(text="Projections on X-axis"), title=Title(text="Projections on X-axis"),
x_range=det_x_range, x_range=det_x_range,
y_range=frame_range, y_range=frame_range,
extra_y_ranges={"scanning_motor": scanning_motor_range},
plot_height=400, plot_height=400,
plot_width=IMAGE_PLOT_W, plot_width=IMAGE_PLOT_W - 3,
) )
# ---- tools # ---- tools
@ -354,8 +380,9 @@ def create():
title=Title(text="Projections on Y-axis"), title=Title(text="Projections on Y-axis"),
x_range=det_y_range, x_range=det_y_range,
y_range=frame_range, y_range=frame_range,
extra_y_ranges={"scanning_motor": scanning_motor_range},
plot_height=400, plot_height=400,
plot_width=IMAGE_PLOT_H, plot_width=IMAGE_PLOT_H + 22,
) )
# ---- tools # ---- tools
@ -369,7 +396,12 @@ def create():
# ---- axes # ---- axes
overview_plot_y.add_layout(LinearAxis(axis_label="Coordinate Y, pix"), place="below") overview_plot_y.add_layout(LinearAxis(axis_label="Coordinate Y, pix"), place="below")
overview_plot_y.add_layout( overview_plot_y.add_layout(
LinearAxis(axis_label="Frame", major_label_orientation="vertical"), place="left" LinearAxis(
y_range_name="scanning_motor",
axis_label="Scanning motor",
major_label_orientation="vertical",
),
place="right",
) )
# ---- grid lines # ---- grid lines
@ -386,16 +418,10 @@ def create():
overview_plot_y_image_source, overview_plot_y_image_glyph, name="image_glyph" overview_plot_y_image_source, overview_plot_y_image_glyph, name="image_glyph"
) )
def frame_button_group_callback(_active):
update_overview_plot()
frame_button_group = RadioButtonGroup(labels=["Frames", "Variable Angle"], active=0)
frame_button_group.on_click(frame_button_group_callback)
roi_avg_plot = Plot( roi_avg_plot = Plot(
x_range=DataRange1d(), x_range=DataRange1d(),
y_range=DataRange1d(), y_range=DataRange1d(),
plot_height=200, plot_height=150,
plot_width=IMAGE_PLOT_W, plot_width=IMAGE_PLOT_W,
toolbar_location="left", toolbar_location="left",
) )
@ -426,13 +452,13 @@ def create():
overview_plot_x_image_glyph.color_mapper = LinearColorMapper(palette=cmap_dict[new]) overview_plot_x_image_glyph.color_mapper = LinearColorMapper(palette=cmap_dict[new])
overview_plot_y_image_glyph.color_mapper = LinearColorMapper(palette=cmap_dict[new]) overview_plot_y_image_glyph.color_mapper = LinearColorMapper(palette=cmap_dict[new])
colormap = Select(title="Colormap:", options=list(cmap_dict.keys()), default_size=145) colormap = Select(title="Colormap:", options=list(cmap_dict.keys()), width=210)
colormap.on_change("value", colormap_callback) colormap.on_change("value", colormap_callback)
colormap.value = "plasma" colormap.value = "plasma"
STEP = 1 STEP = 1
# ---- colormap auto toggle button
def auto_toggle_callback(state): def main_auto_checkbox_callback(state):
if state: if state:
display_min_spinner.disabled = True display_min_spinner.disabled = True
display_max_spinner.disabled = True display_max_spinner.disabled = True
@ -442,45 +468,43 @@ def create():
update_image() update_image()
auto_toggle = Toggle( main_auto_checkbox = CheckboxGroup(
label="Main Auto Range", active=True, button_type="default", default_size=125 labels=["Main Auto Range"], active=[0], width=145, margin=[10, 5, 0, 5]
) )
auto_toggle.on_click(auto_toggle_callback) main_auto_checkbox.on_click(main_auto_checkbox_callback)
# ---- colormap display max value
def display_max_spinner_callback(_attr, _old_value, new_value): def display_max_spinner_callback(_attr, _old_value, new_value):
display_min_spinner.high = new_value - STEP display_min_spinner.high = new_value - STEP
image_glyph.color_mapper.high = new_value image_glyph.color_mapper.high = new_value
display_max_spinner = Spinner( display_max_spinner = Spinner(
title="Max Value:",
low=0 + STEP, low=0 + STEP,
value=1, value=1,
step=STEP, step=STEP,
disabled=auto_toggle.active, disabled=bool(main_auto_checkbox.active),
default_size=80, width=100,
height=31,
) )
display_max_spinner.on_change("value", display_max_spinner_callback) display_max_spinner.on_change("value", display_max_spinner_callback)
# ---- colormap display min value
def display_min_spinner_callback(_attr, _old_value, new_value): def display_min_spinner_callback(_attr, _old_value, new_value):
display_max_spinner.low = new_value + STEP display_max_spinner.low = new_value + STEP
image_glyph.color_mapper.low = new_value image_glyph.color_mapper.low = new_value
display_min_spinner = Spinner( display_min_spinner = Spinner(
title="Min Value:",
low=0, low=0,
high=1 - STEP, high=1 - STEP,
value=0, value=0,
step=STEP, step=STEP,
disabled=auto_toggle.active, disabled=bool(main_auto_checkbox.active),
default_size=80, width=100,
height=31,
) )
display_min_spinner.on_change("value", display_min_spinner_callback) display_min_spinner.on_change("value", display_min_spinner_callback)
PROJ_STEP = 0.1 PROJ_STEP = 0.1
# ---- proj colormap auto toggle button
def proj_auto_toggle_callback(state): def proj_auto_checkbox_callback(state):
if state: if state:
proj_display_min_spinner.disabled = True proj_display_min_spinner.disabled = True
proj_display_max_spinner.disabled = True proj_display_max_spinner.disabled = True
@ -490,41 +514,39 @@ def create():
update_overview_plot() update_overview_plot()
proj_auto_toggle = Toggle( proj_auto_checkbox = CheckboxGroup(
label="Proj Auto Range", active=True, button_type="default", default_size=125 labels=["Projections Auto Range"], active=[0], width=145, margin=[10, 5, 0, 5]
) )
proj_auto_toggle.on_click(proj_auto_toggle_callback) proj_auto_checkbox.on_click(proj_auto_checkbox_callback)
# ---- proj colormap display max value
def proj_display_max_spinner_callback(_attr, _old_value, new_value): def proj_display_max_spinner_callback(_attr, _old_value, new_value):
proj_display_min_spinner.high = new_value - PROJ_STEP proj_display_min_spinner.high = new_value - PROJ_STEP
overview_plot_x_image_glyph.color_mapper.high = new_value overview_plot_x_image_glyph.color_mapper.high = new_value
overview_plot_y_image_glyph.color_mapper.high = new_value overview_plot_y_image_glyph.color_mapper.high = new_value
proj_display_max_spinner = Spinner( proj_display_max_spinner = Spinner(
title="Max Value:",
low=0 + PROJ_STEP, low=0 + PROJ_STEP,
value=1, value=1,
step=PROJ_STEP, step=PROJ_STEP,
disabled=proj_auto_toggle.active, disabled=bool(proj_auto_checkbox.active),
default_size=80, width=100,
height=31,
) )
proj_display_max_spinner.on_change("value", proj_display_max_spinner_callback) proj_display_max_spinner.on_change("value", proj_display_max_spinner_callback)
# ---- proj colormap display min value
def proj_display_min_spinner_callback(_attr, _old_value, new_value): def proj_display_min_spinner_callback(_attr, _old_value, new_value):
proj_display_max_spinner.low = new_value + PROJ_STEP proj_display_max_spinner.low = new_value + PROJ_STEP
overview_plot_x_image_glyph.color_mapper.low = new_value overview_plot_x_image_glyph.color_mapper.low = new_value
overview_plot_y_image_glyph.color_mapper.low = new_value overview_plot_y_image_glyph.color_mapper.low = new_value
proj_display_min_spinner = Spinner( proj_display_min_spinner = Spinner(
title="Min Value:",
low=0, low=0,
high=1 - PROJ_STEP, high=1 - PROJ_STEP,
value=0, value=0,
step=PROJ_STEP, step=PROJ_STEP,
disabled=proj_auto_toggle.active, disabled=bool(proj_auto_checkbox.active),
default_size=80, width=100,
height=31,
) )
proj_display_min_spinner.on_change("value", proj_display_min_spinner_callback) proj_display_min_spinner.on_change("value", proj_display_min_spinner_callback)
@ -533,7 +555,7 @@ def create():
h, k, l = calculate_hkl(det_data, index) h, k, l = calculate_hkl(det_data, index)
image_source.data.update(h=[h], k=[k], l=[l]) image_source.data.update(h=[h], k=[k], l=[l])
hkl_button = Button(label="Calculate hkl (slow)") hkl_button = Button(label="Calculate hkl (slow)", width=210)
hkl_button.on_click(hkl_button_callback) hkl_button.on_click(hkl_button_callback)
selection_list = TextAreaInput(rows=7) selection_list = TextAreaInput(rows=7)
@ -549,7 +571,7 @@ def create():
int(np.ceil(frame_range.end)), int(np.ceil(frame_range.end)),
] ]
filename_id = filelist.value[-8:-4] filename_id = file_select.value[0][-8:-4]
if filename_id in roi_selection: if filename_id in roi_selection:
roi_selection[f"{filename_id}"].append(selection) roi_selection[f"{filename_id}"].append(selection)
else: else:
@ -560,32 +582,38 @@ def create():
selection_button = Button(label="Add selection") selection_button = Button(label="Add selection")
selection_button.on_click(selection_button_callback) selection_button.on_click(selection_button_callback)
mf_spinner = Spinner( metadata_table_source = ColumnDataSource(dict(geom=[""], temp=[None], mf=[None]))
title="Magnetic field:", format="0.00", width=145, disabled=True num_formatter = NumberFormatter(format="0.00", nan_format="")
metadata_table = DataTable(
source=metadata_table_source,
columns=[
TableColumn(field="geom", title="Geometry", width=100),
TableColumn(field="temp", title="Temperature", formatter=num_formatter, width=100),
TableColumn(field="mf", title="Magnetic Field", formatter=num_formatter, width=100),
],
width=300,
height=50,
autosize_mode="none",
index_position=None,
) )
temp_spinner = Spinner(title="Temperature:", format="0.00", width=145, disabled=True)
geometry_textinput = TextInput(title="Geometry:", disabled=True)
# Final layout # Final layout
import_layout = column(proposal_textinput, upload_div, upload_button, file_select)
layout_image = column(gridplot([[proj_v, None], [plot, proj_h]], merge_tools=False)) layout_image = column(gridplot([[proj_v, None], [plot, proj_h]], merge_tools=False))
colormap_layout = column( colormap_layout = column(
row(colormap), colormap,
row(column(Spacer(height=19), auto_toggle), display_max_spinner, display_min_spinner), main_auto_checkbox,
row( row(display_min_spinner, display_max_spinner),
column(Spacer(height=19), proj_auto_toggle), proj_auto_checkbox,
proj_display_max_spinner, row(proj_display_min_spinner, proj_display_max_spinner),
proj_display_min_spinner,
),
) )
hkl_layout = column(geometry_textinput, hkl_button)
params_layout = row(mf_spinner, temp_spinner)
layout_controls = row( layout_controls = row(
column(selection_button, selection_list), column(selection_button, selection_list),
Spacer(width=20), Spacer(width=20),
column(frame_button_group, colormap_layout), column(
Spacer(width=20), row(index_spinner, column(Spacer(height=25), index_slider)), metadata_table, hkl_button
column(index_spinner, params_layout, hkl_layout), ),
) )
layout_overview = column( layout_overview = column(
@ -598,13 +626,8 @@ def create():
) )
tab_layout = row( tab_layout = row(
column( column(import_layout, colormap_layout),
row( column(layout_overview, layout_controls),
proposal_textinput, filelist, Spacer(width=100), column(upload_div, upload_button),
),
layout_overview,
layout_controls,
),
column(roi_avg_plot, layout_image), column(roi_avg_plot, layout_image),
) )

View File

@ -11,6 +11,7 @@ from bokeh.models import (
BasicTicker, BasicTicker,
Button, Button,
CheckboxEditor, CheckboxEditor,
CheckboxGroup,
ColumnDataSource, ColumnDataSource,
CustomJS, CustomJS,
DataRange1d, DataRange1d,
@ -40,7 +41,6 @@ from bokeh.models import (
Tabs, Tabs,
TextAreaInput, TextAreaInput,
TextInput, TextInput,
Toggle,
WheelZoomTool, WheelZoomTool,
Whisker, Whisker,
) )
@ -66,8 +66,6 @@ for (let i = 0; i < js_data.data['fname'].length; i++) {
} }
""" """
PROPOSAL_PATH = "/afs/psi.ch/project/sinqdata/2020/zebra/"
def color_palette(n_colors): def color_palette(n_colors):
palette = itertools.cycle(Category10[10]) palette = itertools.cycle(Category10[10])
@ -80,14 +78,16 @@ def create():
js_data = ColumnDataSource(data=dict(content=["", ""], fname=["", ""])) js_data = ColumnDataSource(data=dict(content=["", ""], fname=["", ""]))
def proposal_textinput_callback(_attr, _old, new): def proposal_textinput_callback(_attr, _old, new):
full_proposal_path = os.path.join(PROPOSAL_PATH, new.strip()) proposal = new.strip()
dat_file_list = [] year = new[:4]
for file in os.listdir(full_proposal_path): proposal_path = f"/afs/psi.ch/project/sinqdata/{year}/zebra/{proposal}"
if file.endswith(".dat"): file_list = []
dat_file_list.append((os.path.join(full_proposal_path, file), file)) for file in os.listdir(proposal_path):
file_select.options = dat_file_list if file.endswith((".ccl", ".dat")):
file_list.append((os.path.join(proposal_path, file), file))
file_select.options = file_list
proposal_textinput = TextInput(title="Proposal number:", default_size=200) proposal_textinput = TextInput(title="Proposal number:", width=210)
proposal_textinput.on_change("value", proposal_textinput_callback) proposal_textinput.on_change("value", proposal_textinput_callback)
def _init_datatable(): def _init_datatable():
@ -108,11 +108,7 @@ def create():
param_select.value = "user defined" param_select.value = "user defined"
def file_select_callback(_attr, _old, _new): file_select = MultiSelect(title="Available .ccl/.dat files:", width=210, height=250)
pass
file_select = MultiSelect(title="Available .dat files:", default_size=200, height=250)
file_select.on_change("value", file_select_callback)
def file_open_button_callback(): def file_open_button_callback():
nonlocal det_data nonlocal det_data
@ -131,7 +127,7 @@ def create():
_init_datatable() _init_datatable()
file_open_button = Button(label="Open New", default_size=100) file_open_button = Button(label="Open New", width=100)
file_open_button.on_click(file_open_button_callback) file_open_button.on_click(file_open_button_callback)
def file_append_button_callback(): def file_append_button_callback():
@ -145,7 +141,7 @@ def create():
_init_datatable() _init_datatable()
file_append_button = Button(label="Append", default_size=100) file_append_button = Button(label="Append", width=100)
file_append_button.on_click(file_append_button_callback) file_append_button.on_click(file_append_button_callback)
def upload_button_callback(_attr, _old, new): def upload_button_callback(_attr, _old, new):
@ -165,8 +161,8 @@ def create():
_init_datatable() _init_datatable()
upload_div = Div(text="or upload new .dat files:", margin=(5, 5, 0, 5)) upload_div = Div(text="or upload new .ccl/.dat files:", margin=(5, 5, 0, 5))
upload_button = FileInput(accept=".dat", multiple=True, default_size=200) upload_button = FileInput(accept=".ccl,.dat", multiple=True, width=200)
upload_button.on_change("value", upload_button_callback) upload_button.on_change("value", upload_button_callback)
def append_upload_button_callback(_attr, _old, new): def append_upload_button_callback(_attr, _old, new):
@ -181,7 +177,7 @@ def create():
_init_datatable() _init_datatable()
append_upload_div = Div(text="append extra files:", margin=(5, 5, 0, 5)) append_upload_div = Div(text="append extra files:", margin=(5, 5, 0, 5))
append_upload_button = FileInput(accept=".dat", multiple=True, default_size=200) append_upload_button = FileInput(accept=".ccl,.dat", multiple=True, width=200)
append_upload_button.on_change("value", append_upload_button_callback) append_upload_button.on_change("value", append_upload_button_callback)
def monitor_spinner_callback(_attr, _old, new): def monitor_spinner_callback(_attr, _old, new):
@ -297,7 +293,7 @@ def create():
plot_bkg_source, Line(x="x", y="y", line_color="green", line_dash="dashed") plot_bkg_source, Line(x="x", y="y", line_color="green", line_dash="dashed")
) )
plot_peak_source = ColumnDataSource(dict(xs=[0], ys=[0])) plot_peak_source = ColumnDataSource(dict(xs=[[0]], ys=[[0]]))
plot_peak = plot.add_glyph( plot_peak = plot.add_glyph(
plot_peak_source, MultiLine(xs="xs", ys="ys", line_color="red", line_dash="dashed") plot_peak_source, MultiLine(xs="xs", ys="ys", line_color="red", line_dash="dashed")
) )
@ -390,7 +386,12 @@ def create():
_update_plot() _update_plot()
def scan_table_source_callback(_attr, _old, _new):
_update_preview()
scan_table_source = ColumnDataSource(dict(file=[], scan=[], param=[], fit=[], export=[])) scan_table_source = ColumnDataSource(dict(file=[], scan=[], param=[], fit=[], export=[]))
scan_table_source.on_change("data", scan_table_source_callback)
scan_table = DataTable( scan_table = DataTable(
source=scan_table_source, source=scan_table_source,
columns=[ columns=[
@ -427,20 +428,20 @@ def create():
title="Parameter:", title="Parameter:",
options=["user defined", "temp", "mf", "h", "k", "l"], options=["user defined", "temp", "mf", "h", "k", "l"],
value="user defined", value="user defined",
default_size=145, width=145,
) )
param_select.on_change("value", param_select_callback) param_select.on_change("value", param_select_callback)
def fit_from_spinner_callback(_attr, _old, new): def fit_from_spinner_callback(_attr, _old, new):
fit_from_span.location = new fit_from_span.location = new
fit_from_spinner = Spinner(title="Fit from:", default_size=145) fit_from_spinner = Spinner(title="Fit from:", width=145)
fit_from_spinner.on_change("value", fit_from_spinner_callback) fit_from_spinner.on_change("value", fit_from_spinner_callback)
def fit_to_spinner_callback(_attr, _old, new): def fit_to_spinner_callback(_attr, _old, new):
fit_to_span.location = new fit_to_span.location = new
fit_to_spinner = Spinner(title="to:", default_size=145) fit_to_spinner = Spinner(title="to:", width=145)
fit_to_spinner.on_change("value", fit_to_spinner_callback) fit_to_spinner.on_change("value", fit_to_spinner_callback)
def fitparams_add_dropdown_callback(click): def fitparams_add_dropdown_callback(click):
@ -459,7 +460,7 @@ def create():
("Pseudo Voigt", "pvoigt"), ("Pseudo Voigt", "pvoigt"),
# ("Pseudo Voigt1", "pseudovoigt1"), # ("Pseudo Voigt1", "pseudovoigt1"),
], ],
default_size=145, width=145,
) )
fitparams_add_dropdown.on_click(fitparams_add_dropdown_callback) fitparams_add_dropdown.on_click(fitparams_add_dropdown_callback)
@ -479,7 +480,7 @@ def create():
else: else:
fitparams_table_source.data.update(dict(param=[], value=[], vary=[], min=[], max=[])) fitparams_table_source.data.update(dict(param=[], value=[], vary=[], min=[], max=[]))
fitparams_select = MultiSelect(options=[], height=120, default_size=145) fitparams_select = MultiSelect(options=[], height=120, width=145)
fitparams_select.tags = [0] fitparams_select.tags = [0]
fitparams_select.on_change("value", fitparams_select_callback) fitparams_select.on_change("value", fitparams_select_callback)
@ -494,7 +495,7 @@ def create():
fitparams_select.value = [] fitparams_select.value = []
fitparams_remove_button = Button(label="Remove fit function", default_size=145) fitparams_remove_button = Button(label="Remove fit function", width=145)
fitparams_remove_button.on_click(fitparams_remove_button_callback) fitparams_remove_button.on_click(fitparams_remove_button_callback)
def fitparams_factory(function): def fitparams_factory(function):
@ -516,6 +517,14 @@ def create():
param=params, value=[None] * n, vary=[True] * n, min=[None] * n, max=[None] * n, param=params, value=[None] * n, vary=[True] * n, min=[None] * n, max=[None] * n,
) )
if function == "linear":
fitparams["value"] = [0, 1]
fitparams["vary"] = [False, True]
fitparams["min"] = [None, 0]
elif function == "gaussian":
fitparams["min"] = [0, None, None]
return fitparams return fitparams
fitparams_table_source = ColumnDataSource(dict(param=[], value=[], vary=[], min=[], max=[])) fitparams_table_source = ColumnDataSource(dict(param=[], value=[], vary=[], min=[], max=[]))
@ -552,7 +561,7 @@ def create():
_update_plot() _update_plot()
_update_table() _update_table()
fit_all_button = Button(label="Fit All", button_type="primary", default_size=145) fit_all_button = Button(label="Fit All", button_type="primary", width=145)
fit_all_button.on_click(fit_all_button_callback) fit_all_button.on_click(fit_all_button_callback)
def fit_button_callback(): def fit_button_callback():
@ -564,22 +573,26 @@ def create():
_update_plot() _update_plot()
_update_table() _update_table()
fit_button = Button(label="Fit Current", default_size=145) fit_button = Button(label="Fit Current", width=145)
fit_button.on_click(fit_button_callback) fit_button.on_click(fit_button_callback)
def area_method_radiobutton_callback(_handler):
_update_preview()
area_method_radiobutton = RadioButtonGroup( area_method_radiobutton = RadioButtonGroup(
labels=["Fit area", "Int area"], active=0, default_size=145, disabled=True labels=["Fit area", "Int area"], active=0, width=145, disabled=True
) )
area_method_radiobutton.on_click(area_method_radiobutton_callback)
bin_size_spinner = Spinner( def lorentz_checkbox_callback(_handler):
title="Bin size:", value=1, low=1, step=1, default_size=145, disabled=True _update_preview()
)
lorentz_toggle = Toggle(label="Lorentz Correction", default_size=145) lorentz_checkbox = CheckboxGroup(labels=["Lorentz Correction"], width=145, margin=[13, 5, 5, 5])
lorentz_checkbox.on_click(lorentz_checkbox_callback)
export_preview_textinput = TextAreaInput(title="Export preview:", width=450, height=400) export_preview_textinput = TextAreaInput(title="Export file preview:", width=450, height=400)
def preview_button_callback(): def _update_preview():
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
temp_file = temp_dir + "/temp" temp_file = temp_dir + "/temp"
export_data = [] export_data = []
@ -591,7 +604,7 @@ def create():
export_data, export_data,
temp_file, temp_file,
area_method=AREA_METHODS[int(area_method_radiobutton.active)], area_method=AREA_METHODS[int(area_method_radiobutton.active)],
lorentz=lorentz_toggle.active, lorentz=bool(lorentz_checkbox.active),
) )
exported_content = "" exported_content = ""
@ -609,10 +622,7 @@ def create():
js_data.data.update(content=file_content) js_data.data.update(content=file_content)
export_preview_textinput.value = exported_content export_preview_textinput.value = exported_content
preview_button = Button(label="Preview", default_size=220) save_button = Button(label="Download File", button_type="success", width=220)
preview_button.on_click(preview_button_callback)
save_button = Button(label="Download preview", button_type="success", default_size=220)
save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript)) save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript))
fitpeak_controls = row( fitpeak_controls = row(
@ -621,8 +631,7 @@ def create():
Spacer(width=20), Spacer(width=20),
column( column(
row(fit_from_spinner, fit_to_spinner), row(fit_from_spinner, fit_to_spinner),
row(bin_size_spinner, column(Spacer(height=19), lorentz_toggle)), row(area_method_radiobutton, lorentz_checkbox),
row(area_method_radiobutton),
row(fit_button, fit_all_button), row(fit_button, fit_all_button),
), ),
) )
@ -639,7 +648,7 @@ def create():
append_upload_button, append_upload_button,
) )
export_layout = column(export_preview_textinput, row(preview_button, save_button)) export_layout = column(export_preview_textinput, row(save_button))
tab_layout = column( tab_layout = column(
row(import_layout, scan_layout, plots, Spacer(width=30), export_layout), row(import_layout, scan_layout, plots, Spacer(width=30), export_layout),

View File

@ -3,7 +3,6 @@ import math
import os import os
import subprocess import subprocess
import tempfile import tempfile
from collections import defaultdict
import numpy as np import numpy as np
from bokeh.layouts import column, row from bokeh.layouts import column, row
@ -28,15 +27,19 @@ def create():
lattice_const_textinput = TextInput( lattice_const_textinput = TextInput(
title="Lattice constants:", value="8.3211,8.3211,8.3211,90.00,90.00,90.00" title="Lattice constants:", value="8.3211,8.3211,8.3211,90.00,90.00,90.00"
) )
max_res_spinner = Spinner(title="max-res", value=2, step=0.01) max_res_spinner = Spinner(title="max-res:", value=2, step=0.01, width=145)
seed_pool_size_spinner = Spinner(title="seed-pool-size", value=5, step=0.01) seed_pool_size_spinner = Spinner(title="seed-pool-size:", value=5, step=0.01, width=145)
seed_len_tol_spinner = Spinner(title="seed-len-tol", value=0.02, step=0.01) seed_len_tol_spinner = Spinner(title="seed-len-tol:", value=0.02, step=0.01, width=145)
seed_angle_tol_spinner = Spinner(title="seed-angle-tol", value=1, step=0.01) seed_angle_tol_spinner = Spinner(title="seed-angle-tol:", value=1, step=0.01, width=145)
eval_hkl_tol_spinner = Spinner(title="eval-hkl-tol", value=0.15, step=0.01) eval_hkl_tol_spinner = Spinner(title="eval-hkl-tol:", value=0.15, step=0.01, width=145)
diff_vec = [] diff_vec = []
ub_matrices = []
def process_button_callback(): def process_button_callback():
# drop table selection to clear result fields
results_table_source.selected.indices = []
nonlocal diff_vec nonlocal diff_vec
with tempfile.TemporaryDirectory() as temp_dir: with tempfile.TemporaryDirectory() as temp_dir:
temp_peak_list_dir = os.path.join(temp_dir, "peak_list") temp_peak_list_dir = os.path.join(temp_dir, "peak_list")
@ -51,7 +54,7 @@ def create():
"-n", "-n",
"2", "2",
"python", "python",
"spind/gen_hkl_table.py", os.path.expanduser("~/spind/gen_hkl_table.py"),
lattice_const_textinput.value, lattice_const_textinput.value,
"--max-res", "--max-res",
str(max_res_spinner.value), str(max_res_spinner.value),
@ -68,13 +71,17 @@ def create():
diff_vec = prepare_event_file(temp_event_file, roi_dict, path_prefix_textinput.value) diff_vec = prepare_event_file(temp_event_file, roi_dict, path_prefix_textinput.value)
print(f"Content of {temp_event_file}:")
with open(temp_event_file) as f:
print(f.read())
comp_proc = subprocess.run( comp_proc = subprocess.run(
[ [
"mpiexec", "mpiexec",
"-n", "-n",
"2", "2",
"python", "python",
"spind/SPIND.py", os.path.expanduser("~/spind/SPIND.py"),
temp_peak_list_dir, temp_peak_list_dir,
temp_hkl_file, temp_hkl_file,
"-o", "-o",
@ -96,9 +103,12 @@ def create():
print(" ".join(comp_proc.args)) print(" ".join(comp_proc.args))
print(comp_proc.stdout) print(comp_proc.stdout)
spind_out_file = os.path.join(temp_dir, "spind.txt")
spind_res = dict(
label=[], crystal_id=[], match_rate=[], matched_peaks=[], column_5=[], ub_matrix=[],
)
try: try:
with open(os.path.join(temp_dir, "spind.txt")) as f_out: with open(spind_out_file) as f_out:
spind_res = defaultdict(list)
for line in f_out: for line in f_out:
c1, c2, c3, c4, c5, *c_rest = line.split() c1, c2, c3, c4, c5, *c_rest = line.split()
spind_res["label"].append(c1) spind_res["label"].append(c1)
@ -109,32 +119,42 @@ def create():
# last digits are spind UB matrix # last digits are spind UB matrix
vals = list(map(float, c_rest)) vals = list(map(float, c_rest))
ub_matrix_spind = np.array(vals).reshape(3, 3) ub_matrix_spind = np.transpose(np.array(vals).reshape(3, 3))
ub_matrix = np.linalg.inv(np.transpose(ub_matrix_spind)) * 1e10 ub_matrix = np.linalg.inv(ub_matrix_spind)
spind_res["ub_matrix"].append(ub_matrix) ub_matrices.append(ub_matrix)
spind_res["ub_matrix"].append(str(ub_matrix_spind * 1e-10))
results_table_source.data.update(spind_res) print(f"Content of {spind_out_file}:")
with open(spind_out_file) as f:
print(f.read())
except FileNotFoundError: except FileNotFoundError:
print("No results from spind") print("No results from spind")
results_table_source.data.update(spind_res)
process_button = Button(label="Process", button_type="primary") process_button = Button(label="Process", button_type="primary")
process_button.on_click(process_button_callback) process_button.on_click(process_button_callback)
hkl_textareainput = TextAreaInput(title="hkl values:", rows=7) ub_matrix_textareainput = TextAreaInput(title="UB matrix:", rows=7, width=400)
hkl_textareainput = TextAreaInput(title="hkl values:", rows=7, width=400)
def results_table_select_callback(_attr, old, new): def results_table_select_callback(_attr, old, new):
if new: if new:
ind = new[0] ind = new[0]
ub_matrix = results_table_source.data["ub_matrix"][ind] ub_matrix = ub_matrices[ind]
res = "" res = ""
for vec in diff_vec: for vec in diff_vec:
res += f"{vec @ ub_matrix}\n" res += f"{ub_matrix @ vec}\n"
ub_matrix_textareainput.value = str(ub_matrix * 1e10)
hkl_textareainput.value = res hkl_textareainput.value = res
else: else:
hkl_textareainput.value = None ub_matrix_textareainput.value = ""
hkl_textareainput.value = ""
results_table_source = ColumnDataSource(dict()) results_table_source = ColumnDataSource(
dict(label=[], crystal_id=[], match_rate=[], matched_peaks=[], column_5=[], ub_matrix=[])
)
results_table = DataTable( results_table = DataTable(
source=results_table_source, source=results_table_source,
columns=[ columns=[
@ -143,10 +163,10 @@ def create():
TableColumn(field="match_rate", title="Match Rate", width=100), TableColumn(field="match_rate", title="Match Rate", width=100),
TableColumn(field="matched_peaks", title="Matched Peaks", width=100), TableColumn(field="matched_peaks", title="Matched Peaks", width=100),
TableColumn(field="column_5", title="", width=100), TableColumn(field="column_5", title="", width=100),
TableColumn(field="ub_matrix", title="UB Matrix", width=250), TableColumn(field="ub_matrix", title="UB Matrix", width=700),
], ],
height=300, height=300,
width=700, width=1200,
autosize_mode="none", autosize_mode="none",
index_position=None, index_position=None,
) )
@ -158,14 +178,12 @@ def create():
path_prefix_textinput, path_prefix_textinput,
selection_list, selection_list,
lattice_const_textinput, lattice_const_textinput,
max_res_spinner, row(max_res_spinner, seed_pool_size_spinner),
seed_pool_size_spinner, row(seed_len_tol_spinner, seed_angle_tol_spinner),
seed_len_tol_spinner, row(eval_hkl_tol_spinner),
seed_angle_tol_spinner,
eval_hkl_tol_spinner,
process_button, process_button,
), ),
column(results_table, row(hkl_textareainput)), column(results_table, row(ub_matrix_textareainput, hkl_textareainput)),
) )
return Panel(child=tab_layout, title="spind") return Panel(child=tab_layout, title="spind")
@ -244,9 +262,10 @@ def prepare_event_file(export_filename, roi_dict, path_prefix=""):
ga, nu = pyzebra.det2pol(ddist, gamma, nu, x_pos, y_pos) ga, nu = pyzebra.det2pol(ddist, gamma, nu, x_pos, y_pos)
diff_vector = pyzebra.z1frmd(wave, ga, omega, chi, phi, nu) diff_vector = pyzebra.z1frmd(wave, ga, omega, chi, phi, nu)
d_spacing = float(pyzebra.dandth(wave, diff_vector)[0]) d_spacing = float(pyzebra.dandth(wave, diff_vector)[0])
dv1, dv2, dv3 = diff_vector.flatten() * 1e10 diff_vector = diff_vector.flatten() * 1e10
dv1, dv2, dv3 = diff_vector
diff_vec.append(diff_vector.flatten()) diff_vec.append(diff_vector)
f.write(f"{x_pos} {y_pos} {intensity} {snr_cnts} {dv1} {dv2} {dv3} {d_spacing}\n") f.write(f"{x_pos} {y_pos} {intensity} {snr_cnts} {dv1} {dv2} {dv3} {d_spacing}\n")

View File

@ -261,22 +261,24 @@ def export_1D(data, path, area_method=AREA_METHODS[0], lorentz=False, hkl_precis
h, k, l = scan["h"], scan["k"], scan["l"] h, k, l = scan["h"], scan["k"], scan["l"]
hkl_are_integers = isinstance(h, int) # if True, other indices are of type 'int' too hkl_are_integers = isinstance(h, int) # if True, other indices are of type 'int' too
if hkl_are_integers: if hkl_are_integers:
hkl_str = f"{h:6}{k:6}{l:6}" hkl_str = f"{h:4}{k:4}{l:4}"
else: else:
hkl_str = f"{h:8.{hkl_precision}f}{k:8.{hkl_precision}f}{l:8.{hkl_precision}f}" hkl_str = f"{h:8.{hkl_precision}f}{k:8.{hkl_precision}f}{l:8.{hkl_precision}f}"
for name, param in scan["fit"].params.items(): for name, param in scan["fit"].params.items():
if "amplitude" in name: if "amplitude" in name:
area_n = param.value if param.stderr is None:
area_s = param.stderr area_n = np.nan
area_s = np.nan
else:
area_n = param.value
area_s = param.stderr
# TODO: take into account multiple peaks
break break
else: else:
area_n = 0 # no peak functions in a fit model
area_s = 0 area_n = np.nan
area_s = np.nan
if area_n is None or area_s is None:
print(f"Couldn't export scan: {scan['idx']}")
continue
# apply lorentz correction to area # apply lorentz correction to area
if lorentz: if lorentz:

View File

@ -128,6 +128,17 @@ def fit_scan(scan, model_dict, fit_from=None, fit_to=None):
else: else:
param_hints[hint_name] = tmp param_hints[hint_name] = tmp
if "center" in param_name:
if np.isneginf(param_hints["min"]):
param_hints["min"] = np.min(x_fit)
if np.isposinf(param_hints["max"]):
param_hints["max"] = np.max(x_fit)
if "sigma" in param_name:
if np.isposinf(param_hints["max"]):
param_hints["max"] = np.max(x_fit) - np.min(x_fit)
_model.set_param_hint(param_name, **param_hints) _model.set_param_hint(param_name, **param_hints)
if model is None: if model is None: