#!/usr/bin/env python
##############################################################################
#
# diffpy.srfit by DANSE Diffraction group
# Simon J. L. Billinge
# (c) 2008 The Trustees of Columbia University
# in the City of New York. All rights reserved.
#
# File coded by: Chris Farrow
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE_DANSE.txt for license information.
#
##############################################################################
"""This module contains parsers for PDF data.
PDFParser is suitable for parsing data generated from PDFGetN and
PDFGetX.
See the class documentation for more information.
"""
__all__ = ["PDFParser"]
import re
from pathlib import Path
from diffpy.srfit.fitbase.profileparser import ProfileParser
_FLOAT_RX = r"[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?"
[docs]
class PDFParser(ProfileParser):
"""Parser for PDF diffraction pattern data.
PDFgetX and PDFgetN write their header as plain ``name = value``
pairs, including ``stype = X`` or ``stype = N`` for the scattering
type, so this class parses files identically to ``ProfileParser``
for those. Some facilities instead prepend a
free-text instrument comment, so this class also falls back to
scanning that comment for the scattering type, ``qmin``, ``qmax``,
``qdamp``, and ``qbroad`` when they are not already present as
``name = value`` pairs.
Attributes
----------
_format
The name of the data format that this parses (string, default
``""``). The format string is a unique identifier for the data
format handled by the parser.
_banks
The data from each bank. Each bank contains a
(x, y, dx, dy) tuple:
x
A numpy array containing the independent
variable read from the file.
y
A numpy array containing the profile
from the file.
dx
A numpy array containing the uncertainty in x
read from the file. This is None if the
uncertainty cannot be read.
dy
A numpy array containing the uncertainty read
from the file. This is None if the uncertainty
cannot be read.
_x
The independent variable from the chosen bank.
_y
The profile from the chosen bank.
_dx
The uncertainty in independent variable from the chosen bank.
_dy
The uncertainty in profile from the chosen bank.
_meta
A dictionary containing metadata read from the file.
General Metadata
-----------------
filename
The name of the file from which data was parsed. This key
will not exist if data was not read from file.
nbanks
The number of banks parsed.
bank
The chosen bank number.
Metadata
--------
stype
The scattering type ("X", "N").
qmin
The minimum scattering vector (float).
qmax
The maximum scattering vector (float).
qdamp
The Q-resolution damping factor (float).
qbroad
The Q-resolution broadening factor (float).
These, along with any other ``name = value`` pairs in the header,
may appear in the metadata dictionary.
"""
_format = "PDF"
def _parse_metadata(self, filename):
"""Return the metadata read from a PDFgetX or PDFgetN header.
This calls ``ProfileParser``'s ``name = value`` based parsing
first, then falls back to scanning the free-text instrument
comments some facilities prepend to their files for the
scattering type and Q-resolution parameters that
such comments are not already covered by a ``name = value``
pair.
Parameters
----------
filename : str or Path
The name of the file to parse.
Returns
-------
dict
The metadata read from the file header.
"""
metadata = super()._parse_metadata(filename)
self._parse_comment_metadata(Path(filename).read_text(), metadata)
return metadata
@staticmethod
def _parse_comment_metadata(header_text, metadata):
"""Fill in stype, qmin, qmax, qdamp, and qbroad from free-text
instrument comments, without overwriting values already found
by the ``name = value`` based parsing.
Parameters
----------
header_text : str
The full text of the file being parsed.
meta : dict
The metadata dictionary to update in place.
Returns
-------
dict
The updated metadata dictionary.
"""
if "stype" not in metadata:
if re.search(r"(x-?ray|PDFgetX)", header_text, re.I):
metadata["stype"] = "X"
elif re.search(r"(neutron|PDFgetN)", header_text, re.I):
metadata["stype"] = "N"
regexes = {
"qmin": r"\bqmin *= *(%s)\b" % _FLOAT_RX,
"qmax": r"\bqmax *= *(%s)\b" % _FLOAT_RX,
"qdamp": r"\b(?:qdamp|qsig) *= *(%s)\b" % _FLOAT_RX,
"qbroad": r"\b(?:qbroad|qalp) *= *(%s)\b" % _FLOAT_RX,
}
for key, pattern in regexes.items():
if key in metadata:
continue
res = re.search(pattern, header_text, re.I)
if res:
metadata[key] = float(res.group(1))
return metadata
# End of PDFParser