Source code for xrayutilities.io.panalytical_xml

# This file is part of xrayutilities.
#
# xrayutilities is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.
#
# Copyright (c) 2010-2019, 2023 Dominik Kriegner <dominik.kriegner@gmail.com>

"""
Panalytical XML (www.XRDML.com) data file parser

based on the native python xml.dom.minidom module.
want to keep the number of dependancies as small as possible
"""

import os.path
import warnings
from xml.etree import cElementTree as ElementTree

import numpy

from .. import config
from .helper import generate_filenames, xu_open


[docs] class XRDMLMeasurement: """ class to handle scans in a XRDML datafile """
[docs] def __init__(self, measurement, namespace=''): """ initialization routine for a XRDML measurement which parses are all scans within this measurement. """ self.namespace = namespace # get scans in <xrdMeasurement> slist = measurement.findall(self.namespace + "scan") self.hkl = (numpy.nan, numpy.nan, numpy.nan) self.material = "" self.ddict = {} for field in ["countTime", "detector", "counts", "beamAttenuationFactors", "hkl"]: self.ddict[field] = [] is_scalar = 0 # loop over all scan entries - scan points for s in slist: # check if scan is complete scanstatus = s.get("status") if scanstatus in ("Aborted", "Not finished") and len(slist) > 1: if config.VERBOSITY >= config.INFO_LOW: print("XU.io.XRDMLFile: subscan has been aborted " "(part of the data unavailable)!") else: self.scanmotname = s.get("scanAxis") reflection = s.find(self.namespace + "reflection") if reflection is not None: m = reflection.find(self.namespace + "material") if m is not None: self.material = m.text hkl = reflection.find(self.namespace + "hkl") if hkl is not None: hkl_h = int(hkl.find(self.namespace + "h").text) hkl_k = int(hkl.find(self.namespace + "k").text) hkl_l = int(hkl.find(self.namespace + "l").text) self.hkl = (hkl_h, hkl_k, hkl_l) points = s.find(self.namespace + "dataPoints") # add count time to output data countTime = points.find(self.namespace + "commonCountingTime").text self.ddict["countTime"].append(float(countTime)) # check for intensities first to get number of points in scan int_elem = points.find(self.namespace + "intensities") if int_elem is not None: data = int_elem.text hascounts = False else: ct_elem = points.find(self.namespace + "counts") ct_npy = numpy.fromstring(ct_elem.text, sep=" ") self.ddict["counts"].append(ct_npy.tolist()) data = ct_elem.text hascounts = True # count time normalization; output is counts/sec ct_rate = numpy.fromstring(data, sep=" ") / float(countTime) nofpoints = ct_rate.size # if present read beamAttenuationFactors # they are already corrected in the data file, but may be # interesting attfact = points.find(self.namespace + "beamAttenuationFactors") if attfact is not None: atten = numpy.fromstring(attfact.text, sep=" ") atten_list = atten.tolist() self.ddict["beamAttenuationFactors"].append(atten_list) hasatten = True else: hasatten = False if hascounts and hasatten: self.ddict["detector"].append((ct_rate*atten).tolist()) else: self.ddict["detector"].append(ct_rate.tolist()) # read the axes position pos = points.findall(self.namespace + "positions") for p in pos: # read axis name and unit aname = p.get("axis") # aunit = p.get("unit") # read axis data listp = p.findall(self.namespace + "listPositions") s = p.findall(self.namespace + "startPosition") e = p.findall(self.namespace + "endPosition") c = p.find(self.namespace + "commonPosition") if listp: # listPositions listp = listp[0] data_list = numpy.fromstring(listp.text, sep=" ") data_list = data_list.tolist() elif s and e: # start endPosition data_list = numpy.linspace( float(s[0].text), float(e[0].text), nofpoints).tolist() elif c is not None: # commonPosition data_list = numpy.fromstring(c.text, sep=" ") data_list = data_list.tolist() is_scalar = 1 else: raise ValueError( f"no positions for axis {aname} found") # have to append the data to the data dictionary in case # the scan is complete! if aname not in self.ddict: self.ddict[aname] = [] if not is_scalar: self.ddict[aname].append(data_list) else: self.ddict[aname].append(data_list[0]) is_scalar = 0 # finally all scan data needs to be converted to numpy arrays for k in self.ddict: self.ddict[k] = numpy.array(self.ddict[k]) # flatten output if only one scan was present if len(slist) == 1: for k in self.ddict: self.ddict[k] = numpy.ravel(self.ddict[k]) # save scanmot-values and detector counts in special arrays if self.scanmotname in ['2Theta-Omega', 'Gonio']: self.scanmot = self.ddict['2Theta'] elif self.scanmotname == 'Omega-2Theta': self.scanmot = self.ddict['Omega'] elif self.scanmotname in self.ddict: self.scanmot = self.ddict[self.scanmotname] else: warnings.warn('XU.io: unknown scan motor name in XRDML-File') self.int = self.ddict['detector']
def __getitem__(self, key): return self.ddict[key] def __str__(self): ostr = "XRDML Measurement\n" if self.material: ostr += f"Material: '{self.material}'; hkl: {str(self.hkl)}\n" for k, v in self.ddict.items(): ostr += f"{k} with {str(v.shape)} points\n" return ostr
[docs] class XRDMLFile: """ class to handle XRDML data files. The class is supplied with a file name and uses the XRDMLScan class to parse the xrdMeasurement in the file """
[docs] def __init__(self, fname, path=""): """ initialization routine supplied with a filename the file is automatically parsed and the data are available in the "scan" object. If more <xrdMeasurement> tags are present, which should not be the case, their data is present in the "scans" object. Parameters ---------- fname : str filename of the XRDML file path : str, optional path to the XRDML file """ if isinstance(fname, str): self.full_filename = os.path.join(path, fname) self.filename = os.path.basename(self.full_filename) else: self.full_filename = fname self.filename = None with xu_open(self.full_filename) as fid: d = ElementTree.parse(fid) root = d.getroot() try: namespace = root.tag[:root.tag.index('}')+1] except ValueError: namespace = '' slist = root.findall(namespace+"xrdMeasurement") # determine the number of scans in the file self.nscans = len(slist) self.scans = [] for s in slist: self.scans.append(XRDMLMeasurement(s, namespace)) if self.nscans == 1: self.scan = self.scans[0]
def __str__(self): ostr = f"XRDML File: {self.filename}\n" for s in self.scans: ostr += s.__str__() return ostr
[docs] def getxrdml_map(filetemplate, scannrs=None, path=".", roi=None): """ parses multiple XRDML file and concatenates the results for parsing the xrayutilities.io.XRDMLFile class is used. The function can be used for parsing maps measured with the PIXCel 1D detector (and in limited way also for data acquired with a point detector -> see getxrdml_scan instead). Parameters ---------- filetemplate : str template string for the file names, can contain a %d or other replacement variables which are understood be :func:`~xrayutilities.io.helper.generate_filenames`. also see the scannrs argument which is used to specify the replacement variables. scannrs : int or list, optional scan number(s) path : str, optional common path to the filenames roi : tuple, optional region of interest for the PIXCel detector, for other measurements this is not useful! Returns ------- om, tt, psd : ndarray motor positions and data as flattened numpy arrays Examples -------- >>> om, tt, psd = xrayutilities.io.getxrdml_map("samplename_%d.xrdml", ... [1, 2], path="data") # doctest: +SKIP """ def getOmPixcel(omraw, ttraw): """ function to reshape the Omega values into a form needed for further treatment with xrayutilities """ return (omraw[:, numpy.newaxis] * numpy.ones(ttraw.shape)).flatten() # read raw data and convert to reciprocal space om = numpy.zeros(0) tt = numpy.zeros(0) psd = numpy.zeros(0) # create scan names files = generate_filenames(filetemplate, scannrs) # parse files for f in files: d = XRDMLFile(os.path.join(path, f)) s = d.scan if len(s['detector'].shape) == 1: raise TypeError("XU.getxrdml_map: This function can only be used " "to parse reciprocal space map files") if roi is None: roi = [0, s['detector'].shape[1]] if s['Omega'].size < s['2Theta'].size: om = numpy.concatenate( (om, getOmPixcel(s['Omega'], s['2Theta'][:, roi[0]:roi[1]]))) tt = numpy.concatenate( (tt, s['2Theta'][:, roi[0]:roi[1]].flatten())) elif s['Omega'].size > s['2Theta'].size: om = numpy.concatenate((om, s['Omega'].flatten())) tt = numpy.concatenate(( tt, numpy.ravel(s['2Theta'][:, numpy.newaxis] * numpy.ones(s['Omega'].shape)))) else: om = numpy.concatenate((om, s['Omega'].flatten())) tt = numpy.concatenate( (tt, s['2Theta'][:, roi[0]:roi[1]].flatten())) psd = numpy.concatenate( (psd, s['detector'][:, roi[0]:roi[1]].flatten())) return om, tt, psd
[docs] def getxrdml_scan(filetemplate, *motors, **kwargs): """ parses multiple XRDML file and concatenates the results for parsing the xrayutilities.io.XRDMLFile class is used. The function can be used for parsing arbitrary scans and will return the the motor values of the scan motor and additionally the positions of the motors given by in the ``*motors`` argument Parameters ---------- filetemplate : str template string for the file names, can contain a %d or other replacement variables which are understood be :func:`~xrayutilities.io.helper.generate_filenames`. also see the scannrs keyword argument which is used to specify the replacement variables. motors : str motor names to return: e.g.: 'Omega', '2Theta', ... one can also use abbreviations: - 'Omega' = 'om' = 'o' - '2Theta' = 'tt' = 't' - 'Chi' = 'c' - 'Phi' = 'p' scannrs : int or list, optional scan number(s) path : str, optional common path to the filenames Returns ------- scanmot, mot1, mot2,..., detectorint : ndarray motor positions and data as flattened numpy arrays Examples -------- >>> scanmot, om, tt, inte = getxrdml_scan( ... "samplename_1.xrdml", 'om', 'tt', path="data") # doctest: +SKIP """ flatten = True # parse keyword arguments path = kwargs.get('path', '.') scannrs = kwargs.get('scannrs', None) validmotors = ['Omega', '2Theta', 'Psi', 'Chi', 'Phi', 'Z', 'X', 'Y'] validmotorslow = [mot.lower() for mot in validmotors] # create correct motor names from input values motnames = [] for mot in motors: if mot.lower() in validmotorslow: motnames.append(validmotors[validmotorslow.index(mot.lower())]) elif mot.lower() in ['phi', 'p']: motnames.append('Phi') elif mot.lower() in ['chi', 'c']: motnames.append('Chi') elif mot.lower() in ['psi']: motnames.append('Psi') elif mot.lower() in ['tt', 't']: motnames.append('2Theta') elif mot.lower() in ['om', 'o']: motnames.append('Omega') else: raise ValueError("XU: invalid motor name given") motvals = numpy.empty((len(motnames) + 1, 0)) detvals = numpy.empty(0) # create scan names files = generate_filenames(filetemplate, scannrs) # parse files if len(files) == 1: flatten = False for f in files: d = XRDMLFile(os.path.join(path, f)) s = d.scan detshape = s['detector'].shape if len(detshape) == 2: angles = numpy.ravel(s.scanmot) angles.shape = (1, angles.size) for mot in motnames: if s[mot].shape != detshape: angles = numpy.vstack(( angles, numpy.ravel(s[mot][:, numpy.newaxis] * numpy.ones(detshape)))) else: angles = numpy.vstack((angles, numpy.ravel(s[mot]))) motvals = numpy.concatenate((motvals, angles), axis=1) dval = numpy.ravel(s['detector']) detvals = numpy.concatenate((detvals, dval)) if not flatten: detvals.shape = detshape motvals.shape = (len(motnames) + 1, detshape[0], detshape[1]) else: detvals = numpy.concatenate((detvals, s['detector'])) angles = s.scanmot angles.shape = (1, angles.size) for mot in motnames: try: angles = numpy.vstack((angles, s[mot])) except ValueError: # motor is not array angles = numpy.vstack( (angles, s[mot] * numpy.ones(detshape))) motvals = numpy.concatenate((motvals, angles), axis=1) # make return value ret = [] for i in range(motvals.shape[0]): ret.append(motvals[i, ...]) ret.append(detvals) return ret