Source code for spectrochempy.core.writers.write_jcamp

# ======================================================================================
# 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 a JCAMP-DX export method."""

from datetime import datetime

import numpy as np

from spectrochempy.core.writers.exporter import Exporter
from spectrochempy.core.writers.exporter import exportermethod
from spectrochempy.utils.datetimeutils import UTC

__all__ = ["write_jcamp"]
__dataset_methods__ = __all__


[docs] def write_jcamp(*args, **kwargs): """ Write a dataset in JCAMP-DX format. (see Published JCAMP-DX Protocols http://www.jcamp-dx.org/protocols.html#ir4.24) Up to now, only IR output is available. Parameters ---------- filename: str or pathlib object, optional If not provided, a dialog is opened to select a file for writing. protocol : {'scp', 'matlab', 'jcamp', 'csv', 'excel'}, optional Protocol used for writing. If not provided, the correct protocol is inferred (whnever it is possible) from the file name extension. directory : str, optional Where to write the specified `filename` . If not specified, write in the current directory. description: str, optional A Custom description. Returns ------- out : `pathlib` object path of the saved file. Examples -------- The extension will be added automatically >>> X.write_jcamp('myfile') """ exporter = Exporter() kwargs["filetypes"] = ["JCAMP-DX files (*.jdx)"] kwargs["suffix"] = ".jdx" return exporter(*args, **kwargs)
@exportermethod def _write_jcamp(*args, **kwargs): # Writes a dataset in JCAMP-DX format dataset, filename = args dataset.filename = filename # Make JCAMP_DX file with filename.open("w") as fid: # Writes first lines fid.write(f"##TITLE={dataset.name}\n") fid.write("##JCAMP-DX=5.01\n") if dataset.shape[0] > 1: # Several spectra => Data Type = LINK fid.write("##DATA TYPE=LINK\n") # Number of spectra (size of 1st dimension) fid.write(f"##BLOCKS={dataset.shape[0]}\n") else: fid.write("##DATA TYPE=INFRARED SPECTRUM\n") # Determine whether the spectra have a title and a datetime field in the labels, # by default, the title if any will be is the first string; the timestamp will # be the fist datetime.datetime title_index = None timestamp_index = None if dataset.y.labels is not None: for i, label in enumerate(dataset.y.labels[0]): if not title_index and isinstance(label, str): title_index = i if not timestamp_index and type(label) is datetime: timestamp_index = i if timestamp_index is None: timestamp = datetime.now(UTC) for i in range(dataset.shape[0]): if dataset.shape[0] > 1: title = ( dataset.y.labels[i][title_index] if title_index else f"spectrum #{i}" ) fid.write(f"##TITLE={title}\n") fid.write("##JCAMP-DX=5.01\n") fid.write(f"##ORIGIN={dataset.origin}\n") fid.write(f"##OWNER={dataset.author}\n") if timestamp_index is not None: timestamp = dataset.y.labels[i][timestamp_index] fid.write(f"##LONGDATE={timestamp.strftime('%Y/%m/%d')}\n") fid.write(f"##TIME={timestamp.strftime('%H:%M:%S')}\n") fid.write("##XUNITS=1/CM\n") fid.write("##YUNITS=ABSORBANCE\n") firstx, lastx = dataset.x.data[0], dataset.x.data[-1] maxx, minx = max(firstx, lastx), min(firstx, lastx) xfactor = 1.0 fid.write(f"##FIRSTX={firstx:.6f}\n") fid.write(f"##LASTX={lastx:.6f}\n") fid.write(f"##MAXX={maxx:.6f}\n") fid.write(f"##MINX={minx:.6f}\n") fid.write(f"##XFACTOR={xfactor}\n") firsty, lasty = dataset.data[0, 0], dataset.data[0, -1] # TODO : mask maxy, miny = np.nanmax(dataset.data), np.nanmin(dataset.data) yfactor = 1.0e-8 fid.write(f"##FIRSTY={firsty:.6f}\n") fid.write(f"##LASTY={lasty:.6f}\n") fid.write(f"##MAXY={maxy:.6f}\n") fid.write(f"##MINY={miny:.6f}\n") fid.write(f"##YFACTOR={yfactor}\n") nx = dataset.shape[1] fid.write(f"##NPOINTS={nx}\n") fid.write("##XYDATA=(X++(Y..Y))\n") line = f"{firstx:.6f} " for j in np.arange(nx): Y = ( "? " if np.isnan(dataset.data[i, j]) else f"{int(dataset.data[i, j] / yfactor):.6f} " ) line += Y if len(line) >= 75 or j == nx - 1: fid.write(f"{line}\n") if j + 1 < nx: line = f"{dataset.x.data[j + 1]:.6f} " fid.write("##END\n") fid.write("##END=" + "\n") return filename