Commit 619147aa authored by Wout De Nolf's avatar Wout De Nolf Committed by Linus Pithan

[writer] add support for notes

parent 901e95db
Pipeline #20437 failed with stages
in 34 minutes and 11 seconds
......@@ -718,46 +718,14 @@ def nxEntryInit(
return parent[name]
def nxNoteInit(parent, name, data=None, type=None, raise_on_exists=False):
"""
Initialize NXnote instance
:param h5py.Group parent:
:param str name:
:param str data:
:param str type:
:param bool raise_on_exists:
:return h5py.Group:
:raises RuntimeError: wrong Nexus class or parent
not an Nexus class instance
:raises NexusInstanceExists:
"""
raiseIsNxClass(parent, None)
if nxClassInstantiate(parent, name, u"NXnote", raise_on_exists=raise_on_exists):
h5group = parent[name]
h5group.attrs["NX_class"] = u"NXnote"
update = True
else:
h5group = parent[name]
update = False
if data is not None:
updateDataset(h5group, "data", data)
update = True
if type is not None:
updateDataset(h5group, "type", type)
update = True
if update:
updated(h5group)
return h5group
def nxProcessConfigurationInit(
parent, configdict=None, type="json", indent=2, raise_on_exists=False
parent, date=None, configdict=None, type="json", indent=2, raise_on_exists=False
):
"""
Initialize NXnote instance
:param h5py.Group parent:
:param datetime date:
:param dict configdict:
:param str type: 'json' or 'ini' or None (pprint)
:param num indent: pretty-string with indent level
......@@ -781,12 +749,14 @@ def nxProcessConfigurationInit(
else:
data = None
type = None
name = "configuration"
group = nxNoteInit(
parent, name, data=data, type=type, raise_on_exists=raise_on_exists
return nxNote(
parent,
"configuration",
data=data,
type=type,
date=date,
raise_on_exists=raise_on_exists,
)
updated(group)
return group
def nxProcess(parent, name, configdict=None, raise_on_exists=False, **kwargs):
......@@ -886,6 +856,42 @@ class FilePool(SharedLockPool):
FILEPOOL = FilePool()
def nxNote(parent, name, data=None, type=None, date=None, raise_on_exists=False):
"""
Get NXnote instance (initialize when missing)
:param h5py.Group parent:
:param str name:
:param str data:
:param str type:
:param datetime date:
:param bool raise_on_exists:
:return h5py.Group:
:raises RuntimeError: wrong Nexus class or parent
not an Nexus class instance
:raises NexusInstanceExists:
"""
raiseIsNxClass(parent, None)
if nxClassInstantiate(parent, name, u"NXnote", raise_on_exists=raise_on_exists):
h5group = parent[name]
h5group.attrs["NX_class"] = u"NXnote"
update = True
else:
h5group = parent[name]
update = False
if data is not None:
updateDataset(h5group, "data", data)
update = True
if type is not None:
updateDataset(h5group, "type", type)
update = True
if date:
updateDataset(h5group, "date", datetime_to_nexus(date))
elif update:
updated(h5group)
return h5group
class File(h5py.File):
def __init__(self, name, mode="r", enable_file_locking=None, swmr=None, **kwargs):
"""
......
......@@ -605,7 +605,7 @@ class NexusScanWriterBase(base_subscriber.BaseSubscriber):
@contextmanager
def nxmeasurement(self, subscan):
"""
Yields the generic NXdata instance (h5py.Group) or None
Yields the measurement instance (h5py.Group) or None
when NXentry is missing
"""
with self.nxentry(subscan) as nxentry:
......@@ -614,6 +614,18 @@ class NexusScanWriterBase(base_subscriber.BaseSubscriber):
else:
yield nexus.nxCollection(nxentry, "measurement")
@contextmanager
def nxnotes(self, subscan):
"""
Yields the notes instance (h5py.Group) or None
when NXentry is missing
"""
with self.nxentry(subscan) as nxentry:
if nxentry is None:
yield None
else:
yield nexus.nxCollection(nxentry, "notes")
def _h5missing(self, variable):
"""
:param str variable:
......@@ -648,7 +660,7 @@ class NexusScanWriterBase(base_subscriber.BaseSubscriber):
plotselect = self.plotselect
firstplot = None
plots = self.plots
subscan.logger.info("Create plots: {}".format(list(plots.keys())))
subscan.logger.info("Create {} plots".format(len(plots)))
for plotname, plotparams in plots.items():
if plotname in nxentry:
subscan.logger.warning(
......@@ -977,6 +989,8 @@ class NexusScanWriterBase(base_subscriber.BaseSubscriber):
"""
self._save_positioners(subscan)
self._create_plots(subscan)
self._fetch_subscan_metadata(subscan)
self._fetch_subscan_notes(subscan)
@property
def current_bytes(self):
......@@ -1815,6 +1829,7 @@ class NexusScanWriterBase(base_subscriber.BaseSubscriber):
with self.nxentry(subscan) as parent:
if parent is None:
return
subscan.logger.info("Save scan metadata")
info = self.info
categories = set(info["scan_meta_categories"])
categories -= {"positioners", "nexuswriter"}
......@@ -1837,3 +1852,26 @@ class NexusScanWriterBase(base_subscriber.BaseSubscriber):
subscan.logger.info(
"Saved metadata categories: {}".format(list(scan_meta.keys()))
)
def _fetch_subscan_notes(self, subscan):
"""
Save notes for this subscan
:param Subscan subscan:
"""
notes = self.get_info("comments", [], cache=True)
if not notes:
return
with self.nxnotes(subscan) as parent:
if parent is None:
return
subscan.logger.info("Save scan notes")
for i, note in enumerate(notes, 1):
date = datetime.datetime.fromtimestamp(note["timestamp"])
nexus.nxNote(
parent,
"note_{:02d}".format(i),
data=note["message"],
type="text/plain",
date=date,
)
......@@ -42,6 +42,7 @@ def validate_scan_data(
subscan=1,
masters=None,
detectors=None,
notes=None,
master_name="timer",
scan_shape=None,
config=True,
......@@ -54,6 +55,7 @@ def validate_scan_data(
:param tuple masters: fast axis first by default `master_name` when 1D scan
or None otherwise
:param list(str) detectors: expected detectors (derived from technique when missing)
:param list(str) notes:
:param str master_name: chain master name
:param tuple scan_shape: fast axis first 0D scan by default
:param bool config: configurable writer
......@@ -106,6 +108,7 @@ def validate_scan_data(
withpolicy=withpolicy,
technique=scan_technique,
detectors=detectors,
notes=notes,
variable_length=variable_length,
)
validate_measurement(
......@@ -148,6 +151,7 @@ def validate_scan_data(
save_options=save_options,
detectors=detectors,
)
validate_notes(nxentry, notes)
def validate_scangroup_data(sequence, config=True, **kwargs):
......@@ -207,6 +211,7 @@ def validate_nxentry(
withpolicy=True,
technique=None,
detectors=None,
notes=None,
variable_length=None,
):
"""
......@@ -233,6 +238,8 @@ def validate_nxentry(
if info["signals"]:
expected |= {name, "plotselect"}
expected |= expected_applications(technique, config=config, withpolicy=withpolicy)
if notes:
expected.add("notes")
assert_set_equal(actual, expected)
......@@ -547,6 +554,24 @@ def validate_nxdata(
assert axes == masters, (axes, masters, scan_shape, ptype, nxdata.name)
def validate_notes(nxentry, notes):
"""
:param h5py.Group nxentry:
:param list(str) notes:
"""
if not notes:
assert "notes" not in nxentry, nxentry.name
return
group = nxentry["notes"]
assert group.attrs["NX_class"] == "NXcollection", group.name
for i, data in enumerate(notes, 1):
subgroup = group["note_{:02d}".format(i)]
assert subgroup.attrs["NX_class"] == "NXnote", subgroup.name
assert set(subgroup.keys()) == {"date", "type", "data"}
assert subgroup["data"][()] == data
assert subgroup["type"][()] == "text/plain"
def expected_plots(technique, config=True, withpolicy=True, detectors=None):
"""
All expected plots for this technique (see nexus_definitions.yml)
......
# -*- coding: utf-8 -*-
#
# This file is part of the bliss project
#
# Copyright (c) 2015-2019 Beamline Control Unit, ESRF
# Distributed under the GNU LGPLv3. See LICENSE for more info.
from bliss.common import scans
import nxw_test_utils
import nxw_test_data
def test_nxw_notes(nexus_writer_config):
_test_nxw_notes(**nexus_writer_config)
@nxw_test_utils.writer_stdout_on_exception
def _test_nxw_notes(session=None, tmpdir=None, writer=None, **kwargs):
scan = scans.ct(.1, run=False, save=True)
notes = ["test1", "text2", "text3"]
for note in notes:
scan.add_comment(note)
nxw_test_utils.run_scan(scan)
nxw_test_utils.wait_scan_data_finished([scan], writer=writer, **kwargs)
nxw_test_data.assert_scan_data(scan, notes=notes, **kwargs)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment