Source code for xrayutilities.io.seifert

# 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) 2009 Eugen Wintersberger <eugen.wintersberger@desy.de>
# Copyright (c) 2009-2023 Dominik Kriegner <dominik.kriegner@gmail.com>

"""
a set of  routines to convert Seifert ASCII files to HDF5
in fact there exist two posibilities how the data is stored (depending on the
used detector):

 1. as a simple line scan (using the point detector)
 2. as a map using the PSD

In the first case the data ist stored
"""

import itertools
import os.path
import re

import numpy

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

# define some regular expressions
nscans_re = re.compile(r"^&NumScans=\d+")
scan_data_re = re.compile(r"^\#Values\d+")
scan_partab_re = re.compile(r"^\#ScanTableParameter")
novalues_re = re.compile(r"^&NoValues=\d+")
scanaxis_re = re.compile(r"^&ScanAxis=.*")

# some constant regular expressions
re_measparams = re.compile(r"#MeasParameter")
re_rsmparams = re.compile(r"#RsmParameter")
re_data = re.compile(r"#Values")
re_keyvalue = re.compile(r"&\S+=\S")
re_invalidkeyword = re.compile(r"^\d+\S")
re_multiblank = re.compile(r"\s+")
re_position = re.compile(r"&Pos=[+-]*\d*\.\d*")
re_start = re.compile(r"&Start=[+-]*\d*\.\d*")
re_end = re.compile(r"&End=[+-]*\d*\.\d*")
re_step = re.compile(r"&Step=[+-]*\d*\.\d*")
re_time = re.compile(r"&Time=\d*.\d*")
re_stepscan = re.compile(r"^&Start")
re_dataline = re.compile(r"^[+-]*\d*\.\d*")
re_absorber = re.compile(r"^&Axis=A6")


[docs] def repair_key(key): """ Repair a key string in the sense that the string is changed in a way that it can be used as a valid Python identifier. For that purpose all blanks within the string will be replaced by _ and leading numbers get an preceeding _. """ if re_invalidkeyword.match(key): key = "_" + key # now replace all blanks key = key.replace(" ", "_") return key
[docs] class SeifertHeader: """ helper class to represent a Seifert (NJA) scan file header """
[docs] def __init__(self): self.NumScans = 1
def __str__(self): ostr = "" for k in self.__dict__.keys(): value = self.__getattribute__(k) if isinstance(value, float): ostr += k + f" = {value:f}\n" else: ostr += k + f" = {value}\n" return ostr
[docs] class SeifertMultiScan: """ Class to parse a Seifert (NJA) multiscan file """
[docs] def __init__(self, filename, m_scan, m2, path=""): """ Parse data from a multiscan Seifert file. Parameters ---------- filename : str name of the NJA file m_scan : str name of the scan axis m2 : str name of the second moving motor path : str, optional path to the datafile """ self.Filename = os.path.join(path, filename) self.nscans = 0 # total number of scans self.npscan = 0 # number of points per scan self.ctime = 0 # counting time self.re_m2 = re.compile(r"^&Axis=%s\s+&Task=Drive" % m2) self.re_sm = re.compile(r"^&ScanAxis=%s" % m_scan) self.scan_motor_name = m_scan self.sec_motor_name = m2 self.m2_pos = [] self.sm_pos = [] self.data = [] self.n_sm_pos = 0 with xu_open(self.Filename) as self.fid: if config.VERBOSITY >= config.INFO_LOW: print(f"XU.io.SeifertScan: parsing file: {self.Filename}") self.parse()
[docs] def parse(self): self.data = [] m2_tmppos = None self.sm_pos = [] self.m2_pos = [] # flag to check if all header information was parsed header_complete = False for line in self.fid: lb = line.decode('iso-8859-1').strip() # the first thing needed is the number of scans in the file (in # file header) if nscans_re.match(lb): t = lb.split("=")[1] self.nscans = int(t) if self.re_m2.match(lb): t = re_position.findall(lb)[0] t = t.split("=")[1] m2_tmppos = float(t) if novalues_re.match(lb): t = lb.split("=")[1] self.n_sm_pos = int(t) header_complete = True if header_complete: # append motor positions of second motor self.m2_pos.append([[m2_tmppos] * self.n_sm_pos]) # reset header flag header_complete = False # read data lines (number of lines determined by number of # values) datalines = itertools.islice(self.fid, self.n_sm_pos) t = numpy.loadtxt(datalines) self.data.append(t[:, 1]) self.sm_pos.append(t[:, 0]) # after reading all the data self.m2_pos = numpy.array(self.m2_pos, dtype=numpy.double) self.sm_pos = numpy.array(self.sm_pos, dtype=numpy.double) self.data = numpy.array(self.data, dtype=numpy.double) self.data.shape = (self.nscans, self.n_sm_pos) self.m2_pos.shape = (self.nscans, self.n_sm_pos) self.sm_pos.shape = (self.nscans, self.n_sm_pos)
[docs] class SeifertScan: """ Class to parse a single Seifert (NJA) scan file """
[docs] def __init__(self, filename, path=""): """ Constructor for a SeifertScan object. Parameters ---------- filename : str a string with the name of the file to read path : str, optional path to the datafile """ self.Filename = os.path.join(path, filename) self.hdr = SeifertHeader() self.data = [] self.axispos = {} with xu_open(self.Filename) as self.fid: if config.VERBOSITY >= config.INFO_LOW: print(f"XU.io.SeifertScan: parsing file: {self.Filename}") self.parse() if self.hdr.NumScans != 1: self.data.shape = (int(self.data.shape[0] / self.hdr.NoValues), int(self.hdr.NoValues), 2)
[docs] def parse(self): if config.VERBOSITY >= config.INFO_ALL: print("XU.io.SeifertScan.parse: starting the parser") self.data = [] for line in self.fid: lb = line.decode('iso-8859-1') # remove leading and trailing whitespace and newline characeters lb = lb.strip() # every line is broken into its content llist = re_multiblank.split(lb) tmplist = [] axes = "" for e in llist: # if the entry is a key value pair if re_keyvalue.match(e): (key, value) = e.split("=") # remove leading & from the key key = key[1:] # have to manage malformed key names that cannot be used as # Python identifiers (leading numbers or blanks inside the # name) key = repair_key(key) # try to convert the values to float numbers # leave them as strings if this is not possible try: value = float(value) except ValueError: pass if key == "Axis": axes = value if value not in self.axispos: self.axispos[value] = [] elif key == "Pos": self.axispos[axes] += [value, ] setattr(self.hdr, key, value) else: try: tmplist.append(float(e)) except ValueError: pass if tmplist: self.data.append(tmplist) # in the end we convert the data list to a numeric array self.data = numpy.array(self.data, dtype=float) for key in self.axispos: self.axispos[key] = numpy.array(self.axispos[key])
[docs] def getSeifert_map(filetemplate, scannrs=None, path=".", scantype="map", Nchannels=1280): """ parses multiple Seifert ``*.nja`` files and concatenates the results. for parsing the xrayutilities.io.SeifertMultiScan class is used. The function can be used for parsing maps measured with the Meteor1D and point detector. Parameters ---------- filetemplate : str or list template string for the file names, or list of filenames. See :func:`~xrayutilities.io.helper.generate_filenames` for details. scannrs : int or list, optional scan number(s), or other values needed to generate filenames from the filetemplate. path : str, optional common path to the filenames scantype : {'map', 'O2T', 'tsk'}, optional type of datafile: can be either 'map' (reciprocal space map measured with a regular Seifert job (default)) or 'tsk' (MCA spectra measured using the TaskInterpreter) Nchannels : int, optional number of channels of the MCA (needed for 'tsk' measurements) Returns ------- om, tt, psd : ndarray positions and data as flattened numpy arrays Examples -------- >>> om, tt, psd = getSeifert_map("samplename_%d.xrdml", [1, 2], ... path="data") # doctest: +SKIP """ # read raw data and convert to reciprocal space om = numpy.zeros(0) tt = numpy.zeros(0) if scantype in ["map", "O2T"]: psd = numpy.zeros(0) elif scantype == "tsk": psd = numpy.zeros((0, Nchannels)) else: raise ValueError("Unsupported scan type") # create scan names files = generate_filenames(filetemplate, scannrs) # parse files for f in files: if scantype == "map": d = SeifertMultiScan(os.path.join(path, f), 'T', 'O') om = numpy.concatenate((om, d.m2_pos.flatten())) tt = numpy.concatenate((tt, d.sm_pos.flatten())) psd = numpy.concatenate((psd, d.data.flatten())) elif scantype == "tsk": d = SeifertScan(os.path.join(path, f)) om = numpy.concatenate((om, d.axispos['O'].flatten())) tt = numpy.concatenate((tt, d.axispos['T'].flatten())) psd = numpy.concatenate((psd, d.data[:, :, 1])) elif scantype == 'O2T': d = SeifertScan(os.path.join(path, f)) if getattr(d.hdr, "RSMmode") != scantype: raise ValueError(f"Scan {scantype} incompatible with RSMmode") om = numpy.concatenate((om, d.data[:, 0])) tt = numpy.concatenate((tt, d.data[:, 1])) psd = numpy.concatenate((psd, d.data[:, 2])) return om, tt, psd