Source code for spec2nexus.plugins.unicat
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# :author: Pete R. Jemian
# :email: prjemian@gmail.com
# :copyright: (c) 2014-2020, Pete R. Jemian
#
# Distributed under the terms of the Creative Commons Attribution 4.0 International Public License.
#
# The full license is in the file LICENSE.txt, distributed with this software.
# -----------------------------------------------------------------------------
"""
**#H** & **#V** - Metadata in SPEC data files as defined by APS UNICAT
Handles the UNICAT control lines which write additional metadata
in the scans using #H/#V pairs of labels/values.
"""
import re
import six
from .. import eznx
from ..plugin import AutoRegister, ControlLineHandler
from ..utils import strip_first_word
[docs]@six.add_metaclass(AutoRegister)
class UNICAT_MetadataMnemonics(ControlLineHandler):
"""
**#H** -- UNICAT metadata names (numbered rows: #H0, #H1, ...)
Individual metadata names are expected to be single-word strings
but may be multi-word strings as long as the words in the string
are separated by only one space. The delimiter between metadata
names is two consecutive spaces. A tab (``\\t``) character is also
acceptable but should be avoided.
IN-MEMORY REPRESENTATION
* (SpecDataFileHeader) : **H** : labels
* (SpecDataFileScan): **metadata** : {labels: values}
HDF5/NeXus REPRESENTATION
* *NXnote* group named **metadata** below the
*NXentry* group, such as **/entry/metadata**
* datasets created from dictionary <scan>.metadata
"""
key = r"#H\d+"
scan_attributes_defined = ["H"]
[docs] def process(self, text, spec_obj, *args, **kws):
row_text = strip_first_word(text)
labels = re.split(r" +", row_text)
spec_obj.H.append(labels)
[docs]@six.add_metaclass(AutoRegister)
class UNICAT_MetadataValues(ControlLineHandler):
"""
**#V** -- UNICAT metadata values (numbered rows: #V0, #V1, ...)
Individual metadata values are expected to be numbers but may
be multi-word strings as long as the words in the string are
separated by only one space. The delimiter between metadata
values is two consecutive spaces. A tab (``'\\t'``) character is
also acceptable but should be avoided.
All numerical values will be converted into floating point
numbers. Only if that conversion fails, the text of the value
will be reported verbatim.
IN-MEMORY REPRESENTATION
* (SpecDataFileScan): **V** : values
* (SpecDataFileScan): **metadata** : {labels: values}
HDF5/NeXus REPRESENTATION
* *NXnote* group named **metadata** below the
*NXentry* group, such as **/entry/metadata**
* datasets created from dictionary <scan>.metadata
"""
key = r"#V\d+"
scan_attributes_defined = ["V", "metadata"]
[docs] def process(self, text, scan, *args, **kws):
index = len(scan.V)
row_text = strip_first_word(text)
for delimiter in (r" " * 2, r" "):
values = re.split(delimiter, row_text)
if len(scan.header.H[index]) == len(values):
break
scan.V.append(values)
scan.addPostProcessor("unicat_metadata", self.postprocess)
[docs] def postprocess(self, scan, *args, **kws):
"""
interpret the UNICAT metadata (mostly floating point) from the scan header
:param SpecDataFileScan scan: data from a single SPEC scan (instance of SpecDataFileScan)
"""
scan.metadata = {}
if not hasattr(scan.header, "H"):
msg = "No matching #H line(s) for scan %d" % scan.scanNum
raise KeyError(msg)
for row, values in enumerate(scan.V):
if (row + 1) > len(scan.header.H):
msg = "No matching #H%d line for #V%d in scan %d" % (
row,
row,
scan.scanNum,
)
raise KeyError(msg)
for col, val in enumerate(values):
if (col + 1) > len(scan.header.H[row]):
msg = (
"No matching label in #H%d line for #V%d, column %d in scan %d"
% (row, row, col, scan.scanNum)
)
raise KeyError(msg)
label = scan.header.H[row][col]
try:
scan.metadata[label] = float(val)
except ValueError:
scan.metadata[label] = val
scan.addH5writer(self.key, self.writer)
[docs] def writer(self, h5parent, writer, scan, nxclass=None, *args, **kws):
"""Describe how to store this data in an HDF5 NeXus file"""
if hasattr(scan, "metadata") and len(scan.metadata) > 0:
desc = "SPEC metadata (UNICAT-style #H & #V lines)"
group = eznx.makeGroup(
h5parent, "metadata", nxclass, description=desc
)
writer.save_dict(group, scan.metadata)
# link it to the NXinstrument group
nxinstrument = eznx.openGroup(
h5parent, "instrument", "NXinstrument"
)
eznx.makeLink(h5parent, group, nxinstrument.name + "/metadata")