"""
This file contains the JCAMP data access class
it serves as an interface between the V3 Spectrum class and the actual spectral data
Some routines rely on code from the Nmrglue package, included in the miniconda distribution
See SpectrumDataSourceABC for a description of the methods
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2021"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
"Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See http://www.ccpn.ac.uk/v3-software/downloads/license")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
"CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
"J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Geerten Vuister $"
__dateModified__ = "$dateModified: 2021-12-23 11:27:17 +0000 (Thu, December 23, 2021) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: gvuister $"
__date__ = "$Date: 2021-07-23 10:28:48 +0000 (Fri, July 23, 2021) $"
#=========================================================================================
# Start of code
#=========================================================================================
import os
from typing import Sequence
import numpy as np
from datetime import datetime
from ccpn.util.Path import Path
from ccpn.util.Logging import getLogger
from ccpn.util.Common import flatten, isIterable
import ccpn.core.lib.SpectrumLib as specLib
from ccpn.core.lib.SpectrumDataSources.SpectrumDataSourceABC import SpectrumDataSourceABC
from nmrglue.fileio.bruker import read_acqus_file, read_jcamp
from nmrglue.fileio.jcampdx import read as readJcamp
[docs]class JcampSpectrumDataSource(SpectrumDataSourceABC):
"""
JCAMP spectral data reading; 1D only
"""
dataFormat = 'Jcamp'
isBlocked = False
wordSize = 4
headerSize = 0
blockHeaderSize = 0
isFloatData = False
suffixes = ['.dx', '.DX']
allowDirectory = False
openMethod = open
defaultOpenReadMode = 'r'
def __init__(self, path=None, spectrum=None, dimensionCount=None):
super().__init__(path=path, spectrum=spectrum, dimensionCount=dimensionCount)
self._realData = None # storage for the real data array
self._imaginaryData = None # storage for the imaginary data
self._params = None # the nmrGlue parsed parameters dictionary
[docs] def readParameters(self):
"""Read the parameters from the file
this will also read the data into the two arrays, as it is parsed in one go
by the nmrglue routine
"""
logger = getLogger()
self.setDefaultParameters()
# Just to use the SpectrumdataSource machinery; open and close the file
self.openFile(mode=self.defaultOpenReadMode)
params, data = readJcamp(self.path)
# Some elementary checks
if (jcampVersion := float(params['JCAMPDX'][0])) < 5.0:
raise RuntimeError('JcampDataSource.readParameters: invalid Jcamp version (%s)' % jcampVersion)
# Get the data arrays and related parameters
if isIterable(data):
if len(data) == 2:
self._realData = np.array(data[0])
self._imaginaryData = np.array(data[1])
self.isComplex[specLib.X_DIM_INDEX] = True
elif len(data) == 1:
self._realData = np.array(data[0])
self._imaginaryData = None
else:
raise RuntimeError(
'JcampDataSource.readParameters: parsing "%s" did not yield viable data' % self.path)
if (dimCount:= len(self._realData.shape)) != 1:
raise RuntimeError('JcampDataSource.readParameters: data reading only implemented for 1D; got dimensionCount "%s"' % dimCount)
self.setDimensionCount(1)
self.pointCounts[specLib.X_DIM_INDEX] = self._realData.shape[0]
# Extract the non-dimensional parameters
_comment = params.get('_comments')
if _comment is not None and isinstance(_comment, list):
_comment.extend('---- end comment ----')
self.comment = '\n'.join(_comment)
if (_date := params.get('$DATE',None)) is not None:
_date = int(_date[0])
_date = datetime.fromtimestamp(_date)
self.date = str(_date)
# self.isBigEndian = int(params['$BYTORDP'][0]) == 1
if 'NC_proc' in params:
nc_proc = float(params['$NC_proc'][0])
self.dataScale = pow(2, nc_proc)
else:
self.dataScale = 1.0
self.temperature = float(params['$TE'][0])
# Extract the dimensional parameters;
# Written to optionally (later) allow for 2D as well (and to remain in-sinc
# with the BrukerSpectrumDataSource
for i in range(self.dimensionCount):
# create a dict with the parameters for this dimension only, also removing the
# '$' from the key
dimDict = dict((key[1:], val[i]) for key, val in params.items() )
# self.pointCounts[i] = int(dimDict['SI'])
# self.blockSizes[i] = int(dimDict['XDIM'])
# if self.blockSizes[i] == 0:
# self.blockSizes[i] = self.pointCounts[i]
# else:
# # for 1D data blockSizes can be > numPoints, which is wrongaaaaaaaaa
# # (comment from orginal V2-based code)
# self.blockSizes[i] = min(self.blockSizes[i], self.pointCounts[i])
self.isotopeCodes[i] = dimDict.get('AXNUC').strip('<>')
self.axisLabels[i] = dimDict.get('AXNAME').strip('<>')
if int(float(dimDict.get('FT_mod', 1))) == 0:
# point/time axis
self.dimensionTypes[i] = specLib.DIMENSION_TIME
self.measurementTypes[i] = specLib.MEASUREMENT_TYPE_TIME
else:
# frequency axis
self.dimensionTypes[i] = specLib.DIMENSION_FREQUENCY
self.measurementTypes[i] = specLib.MEASUREMENT_TYPE_SHIFT
self.spectralWidthsHz[i] = float(dimDict.get('SWH', 1.0)) # SW in ppm
self.spectrometerFrequencies[i] = float(dimDict.get('SFO1', dimDict.get('SF', 1.0)))
self.referenceValues[i] = float(dimDict.get('OFFSET', 0.0))
# self.referencePoints[i] = float(dimDict.get('refPoint', 0.0))
# GWV: No idea!
self.referencePoints[i] = float(dimDict.get('refPoint', 1.0))
# origNumPoints[i] = int(dimDict.get('$FTSIZE', 0))
# pointOffsets[i] = int(dimDict.get('$STSR', 0))
self.phases0[i] = float(dimDict.get('PHC0', 0.0))
self.phases1[i] = float(dimDict.get('PHC1', 0.0))
# end for
# retain the params dictionary
self._params = params
return super().readParameters()
[docs] def getSliceData(self, position: Sequence = None, sliceDim: int = 1):
"""Return a 1D slice"""
if self.hdf5buffer is not None:
return self.hdf5buffer.getSliceData(position=position, sliceDim=sliceDim)
position = self.checkForValidSlice(position=position, sliceDim=sliceDim)
if self._realData is None:
raise RuntimeError('JcampSpectrumDatSource.getSliceData: no data defined; '\
'has readParameters been called?')
return self._realData
JcampSpectrumDataSource._registerFormat()