Source code for spectrochempy.core.readers.read_quadera

# ======================================================================================
# Copyright (©) 2015-2025 LCS - Laboratoire Catalyse et Spectrochimie, Caen, France.
# CeCILL-B FREE SOFTWARE LICENSE AGREEMENT
# See full LICENSE agreement in the root directory.
# ======================================================================================
"""Plugin module to extend NDDataset with the import methods method."""

__all__ = ["read_quadera"]
__dataset_methods__ = __all__

import re
from datetime import datetime
from warnings import warn

import numpy as np

from spectrochempy.core.dataset.nddataset import Coord
from spectrochempy.core.dataset.nddataset import NDDataset
from spectrochempy.core.readers.importer import Importer
from spectrochempy.core.readers.importer import _importer_method
from spectrochempy.core.readers.importer import _openfid
from spectrochempy.utils.docreps import _docstring

# ======================================================================================
# Public functions
# ======================================================================================
_docstring.delete_params("Importer.see_also", "read_quadera")


[docs] @_docstring.dedent def read_quadera(*paths, **kwargs): r""" Read a Pfeiffer Vacuum's QUADERA mass spectrometer software file with extension :file:`.asc`. Parameters ---------- %(Importer.parameters)s Returns ------- %(Importer.returns)s Other Parameters ---------------- timestamp: `bool`, optional, default: `True` Returns the acquisition timestamp as `Coord`. If set to `False`, returns the time relative to the acquisition time of the data %(Importer.other_parameters)s See Also -------- %(Importer.see_also.no_read_quadera)s Notes ----- Currently the acquisition time is that of the first channel as the timeshift of other channels are typically within few seconds, and the data of other channels are NOT interpolated Todo: check with users whether data interpolation should be made Examples -------- >>> scp.read_quadera('msdata/ion_currents.asc') NDDataset: [float64] A (shape: (y:16975, x:10)) """ kwargs["filetypes"] = ["Quadera files (*.asc)"] kwargs["protocol"] = ["asc"] importer = Importer() return importer(*paths, **kwargs)
# -------------------------------------------------------------------------------------- # Private methods # -------------------------------------------------------------------------------------- @_importer_method def _read_asc(*args, **kwargs): _, filename = args fid, kwargs = _openfid(filename, mode="r", **kwargs) lines = fid.readlines() fid.close() timestamp = kwargs.get("timestamp", True) # the list of channels is 2 lines after the line starting with "End Time" i = 0 while not lines[i].startswith("End Time"): i += 1 i += 2 # reads channel names channels = re.split(r"\t+", lines[i].rstrip("\t\n"))[1:] nchannels = len(channels) # the next line contains the columns titles, repeated for each channels # this routine assumes that for each channel are Time / Time relative [s] / Ion Current [A] # check it: i += 1 colnames = re.split(r"\t+", lines[i].rstrip("\t")) if ( colnames[0] == "Time" or colnames[1] != "Time Relative [s]" or colnames[2] != "Ion Current [A]" ): warn( "Columns names are not those expected: the reading of your .asc file could yield " "please notify this to the developers of scpectrochempy", stacklevel=2, ) if nchannels > 1 and colnames[3] != "Time": # pragma: no cover warn( "The number of columms per channel is not that expected: the reading of your .asc file could yield " "please notify this to the developers of spectrochempy", stacklevel=2, ) # the remaining lines contain data and time coords ntimes = len(lines) - i - 1 times = np.empty((ntimes, nchannels), dtype=object) reltimes = np.empty((ntimes, nchannels)) ioncurrent = np.empty((ntimes, nchannels)) i += 1 prev_timestamp = 0 for j, line in enumerate(lines[i:]): data = re.split(r"[\t+]", line.rstrip("\t")) for k in range(nchannels): datetime_ = datetime.strptime( data[3 * k].strip(" "), "%m/%d/%Y %H:%M:%S.%f", ) times[j][k] = datetime_.timestamp() # hours are given in 12h clock format, so we need to add 12h when hour is in the afternoon if times[j][k] < prev_timestamp: times[j][k] += 3600 * 12 reltimes[j][k] = data[1 + 3 * k].replace(",", ".") ioncurrent[j][k] = data[2 + 3 * k].replace(",", ".") prev_timestamp = times[j][k] dataset = NDDataset(ioncurrent) dataset.name = filename.stem dataset.filename = filename dataset.title = "ion current" dataset.units = "amp" if timestamp: _y = Coord(times[:, 0], title="acquisition timestamp (UTC)", units="s") else: _y = Coord(times[:, 0] - times[0, 0], title="Time", units="s") _x = Coord(labels=channels) dataset.set_coordset(y=_y, x=_x) # Set origin, description and history dataset.history = f"Imported from Quadera asc file {filename}" # reset modification date to cretion date dataset._modified = dataset._created return dataset