Source code for spectrochempy.core.readers.read_labspec

# ======================================================================================
# Copyright (©) 2015-2025 LCS - Laboratoire Catalyse et Spectrochimie, Caen, France.
# CeCILL-B FREE SOFTWARE LICENSE AGREEMENT
# See full LICENSE agreement in the root directory.
# ======================================================================================
"""Extend NDDataset with the import method for Labspec *.txt generated data files."""

__all__ = ["read_labspec"]
__dataset_methods__ = __all__

import datetime

import numpy as np

from spectrochempy.application import error_
from spectrochempy.core.dataset.baseobjects.meta import Meta
from spectrochempy.core.dataset.coord import Coord
from spectrochempy.core.readers.importer import Importer
from spectrochempy.core.readers.importer import _importer_method
from spectrochempy.utils.docreps import _docstring

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


[docs] @_docstring.dedent def read_labspec(*paths, **kwargs): """ Read a single Raman spectrum or a series of Raman spectra. Files to open are :file:`.txt` file created by ``Labspec`` software. Non-labspec :file:`.txt` files are ignored (return None) Parameters ---------- %(Importer.parameters)s Returns ------- %(Importer.returns)s Other Parameters ---------------- %(Importer.other_parameters)s See Also -------- %(Importer.see_also.no_read_labspec)s Examples -------- >>> A = scp.read_labspec('ramandata/labspec/Activation.txt') """ kwargs["filetypes"] = ["LABSPEC exported files (*.txt)"] kwargs["protocol"] = ["labspec", "txt"] importer = Importer() return importer(*paths, **kwargs)
read_txt = read_labspec # ====================================================================================== # Private functions # ====================================================================================== @_importer_method def _read_txt(*args, **kwargs): # read Labspec *txt files or series dataset, filename = args content = kwargs.get("content", False) if content: lines = content.decode("utf-8").splitlines() else: try: with open(filename, encoding="utf-8") as fid: lines = fid.readlines() except UnicodeDecodeError: with open(filename, encoding="latin-1") as fid: lines = fid.readlines() if len(lines) == 0: return None # Metadata meta = Meta() i = 0 while lines[i].startswith("#"): key, val = lines[i].split("=") key = key[1:] if key in meta: key = f"{key} {i}" meta[key] = val.strip() i += 1 # .txt extension is fairly common. We determine non labspc files based # on the absence of few keys. Two types of files (1D or 2D) are considered: labspec_keys_1D = ["Acq. time (s)", "Dark correction"] labspec_keys_2D = ["Exposition", "Grating"] if all(keywd in meta for keywd in labspec_keys_1D) or all( keywd in meta for keywd in labspec_keys_2D ): pass else: # this is not a labspec txt file" return None # read spec rawdata = np.genfromtxt(lines[i:], delimiter="\t") # populate the dataset if rawdata.shape[1] == 2: data = rawdata[:, 1][np.newaxis] _x = Coord(rawdata[:, 0], title="Raman shift", units="1/cm") _y = Coord(None, title="Time", units="s") date_acq, _y = _transf_meta(_y, meta) else: data = rawdata[1:, 1:] _x = Coord(rawdata[0, 1:], title="Raman shift", units="1/cm") _y = Coord(rawdata[1:, 0], title="Time", units="s") date_acq, _y = _transf_meta(_y, meta) # set dataset metadata dataset.data = data dataset.set_coordset(y=_y, x=_x) dataset.title = "Counts" dataset.units = None dataset.name = filename.stem dataset.filename = filename dataset.meta = meta # date_acq is Acquisition date at start (first moment of acquisition) dataset.description = "Spectrum acquisition : " + str(date_acq) # Set origin, description and history dataset.history = f"Imported from LabSpec6 text file {filename}" # reset modification date to cretion date dataset._modified = dataset._created return dataset def _transf_meta(y, meta): # Reformats some of the metadata from Labspec6 information # such as the acquisition date of the spectra and returns a list with the acquisition in datetime format, # the list of effective dates for each spectrum # def val_from_key_wo_time_units(k): # for key in meta: # h, m, s = 0, 0, 0 # if k in key: # _, units = key.split(k) # units = units.strip()[1:-1] # if units == 's': # s = meta[key] # elif units == 'mm:ss': # m, s = meta[key].split(':') # elif units == 'hh:mm': # h, m = meta[key].split(':') # break # return datetime.timedelta(seconds=int(s), minutes=int(m), hours=int(h)) if meta: try: dateacq = datetime.datetime.strptime(meta["Acquired"], "%d.%m.%Y %H:%M:%S") except TypeError: dateacq = datetime.datetime.strptime(meta["Date"], "%d/%m/%y %H:%M:%S") acq = int(meta.get("Acq. time (s)", meta["Exposition"])) accu = int(meta.get("Accumulations", meta.get("Accumulation"))) delay = int(meta.get("Delay time (s)", 0)) # total = val_from_key_wo_time_units('Full time') else: dateacq = datetime.datetime(2000, 1, 1, 0, 0, 0) # datestr = '01/01/2000 00:00:00' acq = 0 accu = 0 delay = 0 # total = datetime.timedelta(0) # delay between spectra delayspectra = datetime.timedelta(seconds=acq * accu + delay) # Date d'acquisition : le temps indiqué est celui où l'on démarre l'acquisition dateacq = dateacq - delayspectra # Dates effectives de chacun des spectres de la série : le temps indiqué est celui où l'on démarre l'acquisition # Labels for time : dates with the initial time for each spectrum try: y.labels = [dateacq + delayspectra * i for i in range(len(y))] except Exception as e: error_(e) return dateacq, y # def rgp_series(lsdatasets, sortbydate=True): # """ # Concatenation of individual spectra to an integrated series # # :type lsdatasets: list # list of datasets (usually created after opening several spectra at once) # # :type sortbydate: bool # to sort data by date order (default=True) # # :type out: NDDataset # single dataset after grouping # """ # # # Test and initialize # if (type(lsdatasets) != list): # print('Error : A list of valid NDDatasets is expected') # return # # lsfile = list(lsdatasets[i].name for i in range(len(lsdatasets))) # # out = stack(lsdatasets) # # lstime = out.y.labels # out.y.data = lstime # # # Orders by date and calculates relative times # if sortbydate: # out = out.sort(dim='y') # # lstime = [] # ref = out[0].y.labels[0] # for i in range(out.shape[0]): # time = (out[i].y.labels[0] - ref).seconds # lstime.append((time)) # # # Formats the concatenated dataset # labels = out.y.labels # out.y = lstime # out.y.labels = labels, lsfile # out.y.title = 'time' # out.y.units = 's' # out.name = 'Series concatenated' # # return out # # # ## Saving data # # def reconstruct_data(dataset): # """ # Recreates raw data matrix from the values of X,Y and data of a NDDataset # """ # dim0, dim1 = dataset.shape # rawdata = np.zeros((dim0 + 1, dim1 + 1)) # rawdata[0, 0] = None # rawdata[1::, 0] = dataset.y # rawdata[0, 1::] = dataset.x # rawdata[1::, 1::] = np.copy(dataset.data) # # metalist = [] # metakeysmod = {} # for ky in dataset.meta.keys(): # writes metadata in the same order as Labspec6 # if ky != 'ordre Labspec6': # ind = dataset.meta['ordre Labspec6'][ky] # kymod = str(ind) + ky # metakeysmod[kymod] = dataset.meta[ky] # for ky2 in sorted(metakeysmod.keys()): # metalist.append(ky2[ky2.find('#'):] + '=' + metakeysmod[ky2]) # # return rawdata, metalist # # # def save_txt(dataset, filename=''): # """ Exports dataset to txt format, aiming to be readable by Labspec software # Only partially efficient. Loss of metadata # """ # # if no filename is provided, opens a dialog box to create txt file # if filename == '': # root = tk.Tk() # root.withdraw() # root.overrideredirect(True) # root.geometry('0x0+0+0') # root.deiconify() # root.lift() # root.focus_force() # f = filedialog.asksaveasfile(initialfile='dataset', # defaultextension=".txt", # filetypes=[("Text", "*.txt"), ("All Files", "*.*")], # confirmoverwrite=True) # if f is None: # asksaveasfile return `None` if dialog closed with "cancel". # return # root.destroy() # else: # f = filename # # rawdata, metalist = reconstruct_data(dataset) # # with open('savetxt.txt', 'w') as f: # # After leaving the above block of code, the file is closed # f.writelines(metalist) # np.savetxt(f, rawdata, delimiter='\t') # # return # # # # Data treatment # # def elimspikes(self, seuil=0.03): # """Spikes removal tool # # :seuil: float : minimal threshold for the detection(fraction)""" # # out = self.copy() # self.plot(reverse=False) # for k in range(3): # outmd = out.data[:, 1:-1:] # median point # out1 = out.data[:, 0:-2:] # previous point # out2 = out.data[:, 2::] # next point # # test1 = (outmd > (1 + seuil) * out1) # test2 = (outmd > (1 + seuil) * out2) # test = (test1 & test2) # outmd[test] = (out1[test] + out2[test]) / 2 # out.data[:, 1:-1:] = outmd # out.name = '*' + self.name # out.history = 'Spikes removed by elimspikes(), with a fraction threshold value=' + str(seuil) # out.plot(reverse=False) # return out