Source code for xrayutilities.io.ill_numor

# 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) 2016-2019, 2023 Dominik Kriegner <dominik.kriegner@gmail.com>

"""
module for reading ILL data files (station D23): numor files
"""

import collections.abc
import os.path
import re

import numpy

from ..exception import InputError
# relative imports from xrayutilities
from .helper import xu_open

re_comment = re.compile(r"^A+$")
re_basicinfo = re.compile(r"^R+$")
re_values = re.compile(r"^F+$")
re_spectrum = re.compile(r"^S+$")
re_header = re.compile(r"^I+$")


[docs] class numorFile: """ Represents a ILL data file (numor). The file is read during the Constructor call. This class should work for created at station D23 using the mad acquisition system. Parameters ---------- filename : str a string with the name of the data file """ columns = {0: ('detector', 'monitor', 'time', 'gamma', 'omega', 'psi'), 1: ('detector', 'monitor', 'time', 'gamma'), 2: ('detector', 'monitor', 'time', 'omega'), 5: ('detector', 'monitor', 'time', 'psi')}
[docs] def __init__(self, filename, path=None): """ constructor for the data file parser Parameters ---------- filename : str a string with the name of the data file path : str, optional directory of the data file """ self.filename = filename if path is None: self.full_filename = self.filename else: self.full_filename = os.path.join(path, self.filename) self.Read()
[docs] def getline(self, fid): return fid.readline().decode('ascii')
[docs] def ssplit(self, string): """ multispace split. splits string at two or more spaces after stripping it. """ return re.split(r'\s\s+', string.strip())
[docs] def Read(self): """ Read the data from the file """ with xu_open(self.full_filename) as fid: self.filesize = os.stat(self.full_filename).st_size # read header self.init_mopo = {} self.comments = [] self.header = {} self._data = [] while fid.tell() < self.filesize: line = self.getline(fid) if re_comment.match(line): # read AAAA sections line = self.getline(fid) desc = [] for _ in range(int(line.split()[1])): desc += self.ssplit(self.getline(fid)) comval = self.ssplit(self.getline(fid)) self.comments.append((desc, comval)) if re_basicinfo.match(line): # read RRRR section info = self.ssplit(self.getline(fid)) self.dataversion = int(info[2]) self.runnumber = int(info[0]) if int(info[1]) > 0: headerdesc = '' for _ in range(int(info[1])): headerdesc += self.getline(fid) + '\n' self.comments.append((['Fileheader'], [headerdesc])) if re_header.match(line): # read IIII section: integer header values info = self.ssplit(self.getline(fid)) names = [] values = [] for j in range(int(info[1])): names += self.getline(fid).split() values = numpy.fromfile(fid, dtype=int, count=int(info[0]), sep=' ') self.header = dict(zip(names, values)) if re_values.match(line): # read FFFF section: initial motor positions info = self.ssplit(self.getline(fid)) names = [] values = [] for j in range(int(info[1])): names += self.ssplit(self.getline(fid)) values = numpy.fromfile(fid, dtype=float, count=int(info[0]), sep=' ') self.init_mopo = dict(zip(names, values)) if re_spectrum.match(line): # read SSSS section: initial motor positions info = self.ssplit(self.getline(fid)) self.nspectra = int(info[2]) if re_values.match(self.getline(fid)): # read FFFF section: subspectrum data nval = int(self.getline(fid)) # check if nval is multiple of npdone if nval % self.header['npdone'] != 0: raise InputError("File corrupted, wrong number of " "data values (%d) found." % nval) self._data.append(numpy.fromfile(fid, dtype=float, count=nval, sep=' ')) if int(info[1]) == 0: break # make data columns accessible by names data = numpy.reshape(self._data[0], (self.header['npdone'], nval // self.header['npdone'])) self.data = numpy.rec.fromrecords( data, names=self.columns[self.header['manip']])
def __str__(self): ostr = f"Numor: {self.runnumber:d} ({self.filename:s})\n" com = " ".join(s for c in self.comments for s in c[1]) ostr += f"Comments: {com}\n" ostr += f"Npoints/Ndone: {self.header['nkmes']:d}/" ostr += f"{self.header['npdone']:d}\n" ostr += f"Nspectra: {self.nspectra:d}\n" ostr += f"Ncolumns: {self.data.shape[1]}" return ostr
[docs] def numor_scan(scannumbers, *args, **kwargs): """ function to obtain the angular cooridinates as well as intensity values saved in numor datafiles. Especially useful for combining several scans into one data object. Parameters ---------- scannumbers : int or str or iterable number of the numors, or list of numbers. This will be transformed to a string and used as a filename args : str, optional names of the motors e.g.: 'omega', 'gamma' kwargs : dict keyword arguments are passed on to numorFile. e.g. 'path' for the files directory Returns ------- [ang1, ang2, ...] : list angular positions list, omitted if no args are given data : ndarray all the data values. Examples -------- >>> [om, gam], data = xu.io.numor_scan(414363, 'omega', 'gamma')\ # doctest: +SKIP """ if isinstance(scannumbers, (str, int)): scanlist = list([scannumbers]) elif isinstance(scannumbers, collections.abc.Iterable): scanlist = scannumbers else: raise TypeError(f"scannumbers has invalid type ({type(scannumbers)})") angles = dict.fromkeys(args) for key in angles: if not isinstance(key, str): raise InputError("*arg values need to be strings with motornames") angles[key] = numpy.zeros(0) buf = numpy.zeros(0) MAP = numpy.zeros(0) for nr in scanlist: scan = numorFile(str(nr), **kwargs) sdata = scan.data if MAP.dtype == numpy.float64: MAP.dtype = sdata.dtype # append scan data to MAP, where all data are stored MAP = numpy.append(MAP, sdata) # check type of scan for i in range(len(args)): motname = args[i] scanlength = len(sdata) try: buf = sdata[motname] except ValueError: mv = [v for k, v in scan.init_mopo.items() if motname in k][0] buf = mv * numpy.ones(scanlength) angles[motname] = numpy.concatenate((angles[motname], buf)) retval = [] for motname in args: # create return values in correct order retval.append(angles[motname]) if not args: return MAP if len(args) == 1: return retval[0], MAP return retval, MAP