"""
Module to manage Star files in ccpn context
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (https://www.ccpn.ac.uk) 2014 - 2022"
__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: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2022-03-18 14:08:27 +0000 (Fri, March 18, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: geertenv $"
__date__ = "$Date: 2020-02-17 10:28:41 +0000 (Thu, February 17, 2022) $"
#=========================================================================================
# Start of code
#=========================================================================================
from ccpn.util.Logging import getLogger
from ccpn.util.nef.GenericStarParser import LoopRow
from ccpn.framework.lib.ccpnNmrStarIo.SaveFrameABC import SaveFrameABC
# from sandbox.Geerten.NTdb.NTdbLib import getNefName
from ccpn.framework.lib.NTdb.NTdbDefs import getNTdbDefs
[docs]class ChemicalShiftSaveFrame(SaveFrameABC):
"""A class to manage chemicalShift saveFrame
"""
_sf_category = 'assigned_chemical_shifts'
# this key contains the NmrLoop with the chemical-shift data
_LOOP_KEY = 'atom_chem_shift'
# These keys map the row onto the V3 ChemicalShift object
_SEQUENCE_CODE_TAG = 'seq_id'
_RESIDUE_TYPE_TAG = 'comp_id'
_ATOM_NAME_TAG = 'atom_id'
_AMBIGUITY_CODE = 'ambiguity_code'
_ISOTOPE_TAG_1 = 'atom_isotope_number'
_ISOTOPE_TAG_2 = 'atom_type'
_VALUE_TAG = 'val'
_VALUE_ERROR_TAG = 'val_err'
_FIGURE_OF_MERIT_TAG = 'assign_fig_of_merit'
_COMMENT_TAG = 'details'
_ntDefs = getNTdbDefs()
@property
def chemicalShifts(self) ->list :
""":return a list of chemical shift LoopRow's
"""
if (_loop := self.get(self._LOOP_KEY)) is None:
return []
return _loop.data
def _getNefName(self, aDef) -> str:
"""Construct the nefName from aDef.name; account for the different possibilities
"""
# all methyls
if aDef.isMethyl and aDef.isProton:
_nefName = aDef.name[:-1] + '%'
# Asn, Gln amine groups
elif aDef.parent.name in ('ASN','GLN') and aDef.name in 'HD21 HD22 HE21 HE22'.split():
_nefName = aDef.name[:-1] + aDef.name[-1:].replace('1','x').replace('2','y')
# Ade, Gua amine groups
elif aDef.parent.name in ('A','DA','G','DG') and aDef.name in 'H61 H62 H21 H22'.split():
_nefName = aDef.name[:-1] + aDef.name[-1:].replace('1','x').replace('2','y') \
# amino acids methylenes
elif aDef.parent.isAminoAcid and aDef.isMethylene and aDef.isProton:
_nefName = aDef.name[:-1] + aDef.name[-1:].replace('2','x').replace('3','y')
# nucleic acids methylenes
elif aDef.parent.isNucleicAcid and aDef.isMethylene and aDef.isProton:
_nefName = aDef.name.replace("''",'x').replace("'",'y')
# Phe, Tyr aromatic sidechains
elif aDef.parent.name in ('PHE','TYR') and aDef.name in 'HD1 CD1 HD2 CD2 HE1 CE1 HE2 CE2'.split():
_nefName = aDef.name.replace('1','x').replace('2','y')
else:
_nefName = aDef.name
return _nefName
def _parseChemicalShiftRow(self, csRow:LoopRow):
"""
Parse the chemical shift row and assign attributes for subsequent processing
:param csRow: a LoopRow instance
"""
csRow.value = float(csRow.get(self._VALUE_TAG))
csRow.valueError = float(csRow.get(self._VALUE_ERROR_TAG))
figureOfMerit = csRow.get(self._FIGURE_OF_MERIT_TAG)
csRow.figureOfMerit = float(figureOfMerit) if figureOfMerit is not None else 1.0
csRow.sequenceCode = str(csRow.get(self._SEQUENCE_CODE_TAG))
csRow.residueType = str(csRow.get(self._RESIDUE_TYPE_TAG))
csRow.isotopeCode = '%s%s' % (csRow.get(self._ISOTOPE_TAG_1), csRow.get(self._ISOTOPE_TAG_2))
csRow.atomName = str(csRow.get(self._ATOM_NAME_TAG))
csRow.ambiguityCode = int(csRow.get(self._AMBIGUITY_CODE))
csRow.comment = csRow.get(self._COMMENT_TAG)
# (try to) get the NTdb definition for this residue, atom
csRow.ntDef = self._ntDefs.getDef((csRow.residueType, csRow.atomName))
# convert atomName to NEF;
csRow.nefAtomName = self._getNefName(csRow.ntDef)
csRow.skip = False
def _newChemicalShift(self, csRow:LoopRow, chemShiftList):
"""Use chemShift to make a new (v3) chemicalShift in chemShiftList
If need be: look back or look ahead into other rows
:param csRow: the row to process
:param chemShiftList: ChemicalShifList instance to generate new ChemicalShift
:return a ChemicalShift instance or None
"""
_row = csRow
if _row.skip:
return None
project = chemShiftList.project
chainCode = chemShiftList.name
atomName = _row.atomName
if _row.ambiguityCode == 1:
# unfortunately:
# - methyl protons have identical chemical shifts with ambiguity code 1
if _row.ntDef.isMethyl and _row.ntDef.isProton:
atomName = _row.nefAtomName
# We can skip all other methyl protons
for _aDef in _row.ntDef.otherAttachedProtons:
if (_aRow := self._lookupDict.get( (_row.residueType, _row.sequenceCode, _aDef.name) )):
_aRow.skip = True
# - methylene protons with identical chemical shifts have ambiguity code 1
elif _row.ntDef.isMethylene and _row.ntDef.isProton:
_aDef = _row.ntDef.otherAttachedProtons[0]
if (_aRow := self._lookupDict.get( (_row.residueType, _row.sequenceCode, _aDef.name) )):
if _row.value == _aRow.value:
_aRow.skip = True
atomName = _row.atomName.replace('2','%').replace('3','%')
# - Phe, Tyr aromatic protons/carbons (e.g. HD1/HD2) with identical chemical shifts have ambiguity code 1
# these occur on non-sequential rows;
elif _row.ntDef.parent.name in ('PHE', 'TYR') and _row.atomName in 'HD1 CD1 HD2 CD2 HE1 CE1 HE2 CE2'.split():
if atomName.endswith('1'):
_aName = _row.atomName.replace('1','2')
elif atomName.endswith('2'):
_aName = _row.atomName.replace('2','1')
if (_aRow := self._lookupDict.get( (_row.residueType, _row.sequenceCode, _aName) )):
if _row.value == _aRow.value:
_aRow.skip = True
atomName = _row.atomName.replace('1','%').replace('2','%')
elif _row.ambiguityCode == 2:
# (Val, Leu NEF xy rules propagation; i.e. HDx% connected to CDx)
if _row.ntDef.parent.name in ('VAL', 'LEU') and _row.ntDef.isMethyl and _row.ntDef.isProton:
atomName = _row.nefAtomName.replace('1','x').replace('2','y')
# We can skip all other methyl protons
for _aDef in _row.ntDef.otherAttachedProtons:
if (_aRow := self._lookupDict.get( (_row.residueType, _row.sequenceCode, _aDef.name) )):
_aRow.skip = True
# propagate xy to carbon; fortunately, these rows appear follow the proton ones so we
# can adjust the attributes
_cName = _row.ntDef.attachedHeavyAtom.name
_cRow = self._lookupDict.get( (_row.residueType, _row.sequenceCode, _cName) )
_cRow.nefAtomName = _cRow.atomName.replace('1','x').replace('2','y')
_cRow.ambiguityCode = 2
else:
atomName = _row.nefAtomName
elif _row.ambiguityCode == 3:
# (Phe, Tyr NEF xy rules propagation; i.e. HDx connected to CDx)
if _row.ntDef.parent.name in ('PHE', 'TYR') and _row.atomName in 'HD1 HD2 HE1 HE2'.split():
atomName = _row.nefAtomName
# propagate to Carbon
_cName = _row.ntDef.attachedHeavyAtom.name
if (_cRow := self._lookupDict.get( (_row.residueType, _row.sequenceCode, _cName) )):
_cRow.nefAtomName = _cRow.atomName.replace('1','x').replace('2','y')
_cRow.ambiguityCode = 3
else:
atomName = _row.nefAtomName
else:
getLogger().warning(f'No provisions for ({_row.residueType},{_row.atomName}) with ambiguity code {_row.ambiguityCode}')
# get the NmrAtom object
nmrChain = project.fetchNmrChain(chainCode)
nmrResidue = nmrChain.fetchNmrResidue(residueType=_row.residueType, sequenceCode=_row.sequenceCode)
nmrAtom = nmrResidue.newNmrAtom(name=atomName, isotopeCode=_row.isotopeCode)
# create the ChemicalShift
chemShift = chemShiftList.newChemicalShift(nmrAtom=nmrAtom,
value=_row.value,
valueError=_row.valueError,
figureOfMerit=_row.figureOfMerit,
comment=_row.comment)
return chemShift
[docs] def importIntoProject(self, project) -> list:
"""Import the data of self into project.
:param project: a Project instance
:return list of imported V3 objects
"""
name = f'entry{self.entry_id}'
chemShiftList = project.newChemicalShiftList(name = name,
autoUpdate = False,
comment = f'from BMRB entry {self.entry_id}; {self.name}'
)
# A two-stage conversion, as sometimes we need to look back or forward
# 'parse'/convert the rows, assigning the attributes; create a lookupDict
self._lookupDict = {}
for _row in self.chemicalShifts:
self._parseChemicalShiftRow(_row)
self._lookupDict[(_row.residueType, _row.sequenceCode, _row.atomName)] = _row
# Loop again to create the V3 chemcialShift objects
for _row in self.chemicalShifts:
self._newChemicalShift(_row, chemShiftList)
return [chemShiftList]
ChemicalShiftSaveFrame._registerSaveFrame()