Source code for ccpn.AnalysisAssign.modules.NmrAtomAssigner

"""
AtomSelector module: 

used for backbone and sidechain assignments to set the nmrAtom of a (number of) selected
peaks to a specific nucleus. Thus far, it set the 'y-axis' dimension of the peak (to be
made flexible in the settings tab). Options other than 'protein' are not yet implemented.

Original by SS
First rework by GWV
Reworked by EJB
"""

#TODO Needs cleanup
"""
- buttons are destroyed (!?) and create with every refresh; 
- atom type prediction in sidechain mode (when type of nmrResidue is not yet known) gives
  the result of the last residue (i.e. Tyr), rather then an average
- Should retain predictions of selected peaks when switching backbone/sidechain
# - assignSelected should be refactored to be proper for all general cases. Done

"""
#=========================================================================================
# 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 https://ccpn.ac.uk/software/licensing/")
__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: Luca Mureddu $"
__dateModified__ = "$dateModified: 2022-02-25 18:55:30 +0000 (Fri, February 25, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: CCPN $"
__date__ = "$Date: 2017-04-07 10:28:40 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================

# import os
import typing
import copy
from functools import partial
from collections import OrderedDict
from PyQt5 import QtCore, QtGui, QtWidgets
from contextlib import contextmanager
from ccpn.core.Peak import Peak
from ccpn.core.NmrResidue import NmrResidue
from ccpn.core.NmrAtom import NmrAtom, UnknownIsotopeCode
from ccpn.core.lib import Pid
from ccpn.core.lib.AssignmentLib import isInterOnlyExpt, getNmrAtomPrediction, CCP_CODES
from ccpn.core.lib.AssignmentLib import peaksAreOnLine
from ccpn.ui.gui.modules.CcpnModule import CcpnModule
from ccpn.ui.gui.widgets.Button import Button
from ccpn.ui.gui.widgets.CheckBox import CheckBox
from ccpn.ui.gui.widgets.Label import Label
from ccpn.ui.gui.widgets.PulldownList import PulldownList
from ccpn.ui.gui.widgets.RadioButton import RadioButton
from ccpn.ui.gui.widgets.RadioButtons import RadioButtons
from ccpn.ui.gui.widgets.Widget import Widget
from ccpn.ui.gui.widgets.Spacer import Spacer
from ccpn.ui.gui.widgets.Frame import Frame, ScrollableFrame
from ccpn.ui.gui.widgets.MessageDialog import showWarning
from ccpn.ui.gui.widgets.PulldownListsForObjects import NmrResiduePulldown, NmrChainPulldown

from ccpn.ui.gui.lib.GuiNotifier import GuiNotifier
from ccpn.ui.gui.widgets.DropBase import DropBase

from ccpn.util.Common import makeIterableList, _truncateText
from ccpnmodel.ccpncore.lib.assignment.ChemicalShift import PROTEIN_ATOM_NAMES, ALL_ATOMS_SORTED
from ccpn.core.lib.AssignmentLib import PROTEIN_NEF_ATOM_NAMES, NEF_ATOM_NAMES_SORTED
from ccpn.util.Logging import getLogger
from ccpn.core.lib.Notifiers import Notifier
from ccpn.core.lib.AssignmentLib import _assignNmrAtomsToPeaks
from ccpn.ui.gui.widgets.ScrollArea import ScrollArea
from ccpn.ui.gui.guiSettings import BORDERNOFOCUS_COLOUR
from ccpn.core.lib import CcpnSorting


logger = getLogger()

# TODO:ED Add DNA, RNA structures to the list
# MOLECULE_TYPES = ['protein', 'DNA', 'RNA', 'carbohydrate', 'other']
MOLECULE_TYPES = ['protein']
BACKBONEATOMS = ['H', 'N', 'CA', 'CB', 'C', 'HA', 'HB']
ADDITIONALBACKBONEATOMS = ['H', 'N', 'C']

MSG = '<Not-defined. Select any to start>'
PROTEIN_MOLECULE = 'protein'
DNA_MOLECULE = 'DNA'
RNA_MOLECULE = 'RNA'

BUTTON_MINX = 70
BUTTON_MINY = 24

DEFAULT_BUTTON = """QRadioButton { background-color: %s }
                   QRadioButton::hover { background-color: %s}""" % ('lightgrey', 'white')
GREEN_BUTTON = """QRadioButton { background-color: %s }
                   QRadioButton::hover { background-color: %s}""" % ('mediumseagreen', 'palegreen')
ORANGE_BUTTON = """QRadioButton { background-color: %s }
                   QRadioButton::hover { background-color: %s}""" % ('orange', 'gold')
RED_BUTTON = """QRadioButton { background-color: %s }
                   QRadioButton::hover { background-color: %s}""" % ('tomato', 'lightpink')


[docs]class NmrAtomAssignerModule(CcpnModule): """ Module to be used with PickAndAssignModule for prediction of nmrAtom names and assignment of nmrAtoms to peak dimensions Responds to current.nmrResidue and current.peaks; accepts nmrResidue pid drops """ className = 'NmrAtomAssignerModule' includeSettingsWidget = True maxSettingsState = 2 # states are defined as: 0: invisible, 1: both visible, 2: only settings visible defaultSettingsState = 0 settingsPosition = 'top' def __init__(self, mainWindow=None, name='NmrAtomAssigner', nmrAtom=None): super().__init__(mainWindow=mainWindow, name=name) # Derive application, project, and current from mainWindow self.mainWindow = mainWindow if mainWindow: self.application = mainWindow.application self.project = mainWindow.application.project self.current = mainWindow.application.current # Settings Widget self._ASwidget = Widget(self.settingsWidget, setLayout=True, grid=(0, 0), vAlign='top', hAlign='left') row = 0 self.selectionLabel = Label(self._ASwidget, 'Select NmrAtom by', grid=(row, 0)) self.selectionRadioButtons = RadioButtons(self._ASwidget, texts=['AtomType', 'Axis'], selectedInd=1, callback=self._selectionCallback, grid=(row, 1)) self.selectAtomType, self.selectAxisCode = self.selectionRadioButtons.radioButtons # pulldown for Molecule type row += 1 self.molTypeLabel = Label(self._ASwidget, 'Molecule Type', grid=(row, 0)) self.molTypePulldown = PulldownList(self._ASwidget, grid=(row, 1), texts=MOLECULE_TYPES, callback=self._changeMoleculeType) row += 1 self.modeTypeLabel = Label(self._ASwidget, 'Mode', grid=(row, 0)) self.modeRadioButtons = RadioButtons(self._ASwidget, texts=['Backbone', 'All'], selectedInd=0, callback=self._createButtonsCallback, grid=(row, 1)) self.selectBackboneButton = self.modeRadioButtons.getRadioButton('Backbone') self.selectAllButton = self.modeRadioButtons.getRadioButton('All') # modifiers for sidechain row += 1 self.offsetLabel = Label(self._ASwidget, 'Offset', grid=(row, 0)) self.offsetSelector = PulldownList(self._ASwidget, grid=(row, 1), texts=['0', '-1', '+1'], callback=self._offsetPullDownCallback) self._sidechainModifiers = [self.offsetLabel, self.offsetSelector] # set size policies to allow the main widget to overlap the settings, cleaner display self._ASwidget.setMinimumSize(self._ASwidget.sizeHint()) self.settingsWidget.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Minimum) self.settingsWidget.setContentsMargins(5, 5, 5, 5) for w in self._sidechainModifiers: w.hide() self._residueFrame = ScrollableFrame(parent=self.mainWidget, showBorder=False, setLayout=True, acceptDrops=True, grid=(0, 0), gridSpan=(1, 1), spacing=(5, 5)) self._scrollAreaWidget = self._residueFrame._scrollArea self._residueFrame.insertCornerWidget() # self._residueFrame = Frame(self.mainWidget, setLayout=True, acceptDrops=True, showBorder=False, spacing=(5, 5)) self._residueFrame.setContentsMargins(5, 5, 5, 5) resRow = 0 _f = Frame(self._residueFrame, setLayout=True, showBorder=False, grid=(resRow, 0), gridSpan=(1, 3)) self._peaksLabel = Label(_f, 'Assigning Peak(s):', bold=True, grid=(0, 0), hPolicy='minimal') self.currentPeaksLabel = Label(_f, grid=(0, 1), gridSpan=(1, 2), hPolicy='minimal', hAlign='l') resRow += 1 _f = Frame(self._residueFrame, setLayout=True, showBorder=False, grid=(resRow, 0), gridSpan=(1, 3)) self._nmrChain = NmrChainPulldown(_f, mainWindow=self.mainWindow, labelText='NmrChain:', showSelectName=True, setCurrent=False, callback=self._nmrChainPullDownCallback, grid=(0, 0), hPolicy='minimal', minimumWidths=None, sizeAdjustPolicy=QtWidgets.QComboBox.AdjustToContents) self._nmrResidue = NmrResiduePulldown(_f, mainWindow=self.mainWindow, labelText='NmrResidue:', useIds=False, showSelectName=False, setCurrent=True, followCurrent=True, filterFunction=self._filterResidues, grid=(0, 1), hPolicy='minimal', minimumWidths=None, sizeAdjustPolicy=QtWidgets.QComboBox.AdjustToContents) self._newNmrResidueButton = Button(_f, text='New', grid=(0, 2), gridSpan=(1, 1), callback=self._newNmrResidueCallback, hPolicy='minimal') self._newNmrResidueButton.setToolTip('Create new nmrResidue in current chain') self._nmrResidueEditButton = Button(_f, text='Edit', grid=(0, 3), gridSpan=(1, 1), callback=self._nmrResidueEditCallback, hPolicy='minimal') self._nmrResidueEditButton.setToolTip('Edit current nmrResidue') Spacer(_f, 2, 2, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed, grid=(1, 4), gridSpan=(1, 1)) resRow += 1 self._labelFrame = Frame(self._residueFrame, setLayout=True, showBorder=False, grid=(resRow, 0), gridSpan=(1, 4)) labRow = 0 # modifier for atomCode self.axisCodeLabel = Label(self._labelFrame, 'Assign by axis', grid=(labRow, 0)) self.axisCodeOptions = RadioButtons(self._labelFrame, selectedInd=0, texts=['C'], callback=self._changeAxisCode, grid=(labRow, 1)) labRow += 1 # modifier for atomType self.atomTypeLabel = Label(self._labelFrame, 'Assign by atomType', grid=(labRow, 0)) self.atomTypeOptions = RadioButtons(self._labelFrame, selectedInd=1, texts=['H', 'C', 'N', 'Other'], callback=self._changeAtomType, grid=(labRow, 1)) labRow += 1 Spacer(self._labelFrame, 2, 2, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed, grid=(labRow, 2), gridSpan=(1, 1)) resRow += 1 self._assignWidget = Frame(self._residueFrame, setLayout=True, showBorder=False, grid=(resRow, 0), spacing=(5, 5)) self._scrollAreaWidget.setWidget(self._residueFrame) resRow += 1 # add spacer to stop columns changing width Spacer(self._residueFrame, 2, 2, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding, grid=(resRow, 4), gridSpan=(1, 1)) resRow += 1 self.buttonGroup = QtWidgets.QButtonGroup() self.buttonGroup.buttonClicked.connect(self._nmrAtomButtonsCallback) self.buttonGroup.setExclusive(False) self.buttons = {} self._registerNotifiers() self._updateWidget() def _registerNotifiers(self): """Register notifiers for the module """ # self.setNotifier(self.project, [Notifier.CHANGE, Notifier.CREATE, Notifier.DELETE], # NmrAtom.className, callback=self._nmrResidueCallBack, onceOnly=True) # self.setNotifier(self.project, [Notifier.CHANGE], # Peak.className, callback=self._nmrResidueCallBack, onceOnly=True) self.setNotifier(self.current, [Notifier.CURRENT], Peak._pluralLinkName, callback=self._currentPeaksCallback, onceOnly=True) self.setNotifier(self.current, [Notifier.CURRENT], NmrResidue._pluralLinkName, callback=self._currentNmrResiduesCallback, onceOnly=True) self.setGuiNotifier(self._residueFrame, [GuiNotifier.DROPEVENT], [DropBase.PIDS], callback=self._handleNmrResidue) self.setNotifier(self.project, [Notifier.RENAME], 'NmrResidue', self._updateNmrResidue, onceOnly=True) def _unRegisterNotifiers(self): """clean up the notifiers """ # _closeModule() will do most of them self._nmrResidue.unRegister() self._nmrChain.unRegister() def _closeModule(self): self._unRegisterNotifiers() super()._closeModule() #================================================================================================================ # callbacks and functionalities #================================================================================================================ def _filterResidues(self, pids): """Filter function for the residue pulldown Add any nmrResidue defined by selected peaks at the top of the list """ # first time hack, as during initialising this routine is called to populate # the pulldown; however _nmrResidue is not yet defined then if not hasattr(self, '_nmrResidue'): return pids nmrChain = self._nmrChain.getSelectedObject() #print('>>> filtering pids on:', nmrChain) # For selected peaks: get the pids of nmrResidues of assigned nmrAtoms newPids = [] for peak in self.current.peaks: if peak: for assignment in peak.assignments: for nmrAtom in assignment: if nmrAtom: newPids.append(nmrAtom.nmrResidue.pid) newPids = list(set(newPids)) newPids.sort() def _isOk(pid): if nmrChain is None: # No filtering return True nmrResidue = self._nmrResidue.value2object(pid) if nmrResidue is not None and nmrResidue.nmrChain == nmrChain: # nmrResidue is part of the filtered nmrChain return True return False newPids = newPids + [pid for pid in pids if _isOk(pid)] return newPids def _newNmrResidueCallback(self): """Callback to create a new nmrResidue and add to the current chain """ nmrChain = self._nmrChain.getSelectedObject() if nmrChain: nmrResidue = self._fetchNmrResidue(nmrChain) self.current.nmrResidue = nmrResidue def _nmrResidueEditCallback(self, data): """Callback to edit the current nmrResidue """ # call popup on current nmrResidue from ccpn.ui.gui.popups.NmrResiduePopup import NmrResidueEditPopup popup = NmrResidueEditPopup(parent=self.mainWindow, mainWindow=self.mainWindow, obj=self.current.nmrResidue) popup.exec_() def _nmrChainPullDownCallback(self, value): "Callback for the NmrChain selection" self._nmrResidue.update() def _handleNmrResidue(self, dataDict): """drop event handler to accept NmrResidue pids """ pids = dataDict.get(DropBase.PIDS) if pids: objs = [self.project.getByPid(pid) for pid in pids] nmrResidues = [obj for obj in objs if (not obj is None) and isinstance(obj, NmrResidue)] if nmrResidues: self.current.nmrResidues = nmrResidues def _togglePressedButton(self, pressedButton=None): '''Ensures only a button at the time is checked, yet allows to uncheck a radio button. If pressedButton is None: unchecks all''' for button in self.buttonGroup.buttons(): if button != pressedButton: button.setChecked(False) def _nmrAtomButtonsCallback(self, pressedButton): self._togglePressedButton(pressedButton) from ccpn.core.lib.ContextManagers import undoBlockWithoutSideBar with undoBlockWithoutSideBar(): try: if pressedButton.isChecked(): self._assignSelected(atomName=pressedButton._atomName, offSet=pressedButton._offSet) else: self._deassignSelected(atomName=pressedButton._atomName, offSet=pressedButton._offSet) except Exception as es: showWarning(str(self.windowTitle()), str(es)) self._togglePressedButton() # uncheck all if any error def _deassignSelected(self, atomName, offSet): nmrResidue = self._getCorrectResidue(self.current.nmrResidue, offSet, atomName) nmrAtom = nmrResidue.getNmrAtom(atomName.translate(Pid.remapSeparators)) if nmrAtom: self.deassignAtomFromSelectedPeaks(self.current.peaks, nmrAtom) def _assignSelected(self, atomName, offSet): nmrResidue = self._getCorrectResidue(self.current.nmrResidue, offSet, atomName) if not nmrResidue: getLogger().warning('Error creating new nmrResidue') raise ValueError('Error creating new nmrResidue') nmrAtom = nmrResidue.fetchNmrAtom(name=atomName) if nmrAtom: if self.selectAxisCode.isChecked(): self.assignNmrAtomsToPeaks(nmrAtom=nmrAtom, peaks=self.current.peaks) else: _assignNmrAtomsToPeaks(nmrAtoms=[nmrAtom], peaks=self.current.peaks) self._setCheckedButtonOfAssignedAtoms(nmrResidue, offSet=offSet) # this so that only assigned atoms are checked. def _createButtonsCallback(self): self._updateWidget() return # def _nmrResidueCallBack(self, data=None): # "Callback if current.nmrResidue changes" # # if self.current.nmrResidue: # self._updateWidget() # else: # self._assignWidgetHide() # # self.currentNmrResidueLabel.setText(MSG) # # self._nmrResidue.select(MSG) def _assignWidgetShow(self): self._assignWidget.show() self._showSelectionButtons() def _assignWidgetHide(self): self._assignWidget.hide() self.atomTypeLabel.hide() self.atomTypeOptions.hide() self.axisCodeLabel.hide() self.axisCodeOptions.hide() def _offsetPullDownCallback(self, tmp=None): "Callback if offset pullDown changes" if self.current.nmrResidue: self._updateWidget() # self._predictAssignments(self.current.peaks) def _setPeaksLabel(self): " update the peaks label from current.peaks" if self.current.peaks and None not in self.current.peaks: splitter = ', ' pText = _truncateText(splitter.join([p.id for p in self.current.peaks]), splitter=splitter) self.currentPeaksLabel.setToolTip(splitter.join([p.id for p in self.current.peaks])) self.currentPeaksLabel.setText(pText) else: self.currentPeaksLabel.setText(MSG) def _setPeakAxisCodes(self, peaks): from ccpn.core.lib.AxisCodeLib import getAxisCodeMatch if peaks: maxLen = 0 refAxisCodes = None for peak in peaks: if len(peak.axisCodes) > maxLen: maxLen = len(peak.axisCodes) refAxisCodes = list(peak.axisCodes) if not maxLen: return axisLabels = [set() for ii in range(maxLen)] mappings = {} for peak in peaks: matchAxisCodes = peak.axisCodes mapping = getAxisCodeMatch(refAxisCodes, matchAxisCodes) for k, v in mapping.items(): if v not in mappings: mappings[v] = set([k]) else: mappings[v].add(k) mapping = getAxisCodeMatch(matchAxisCodes, refAxisCodes) for k, v in mapping.items(): if v not in mappings: mappings[v] = set([k]) else: mappings[v].add(k) # example of mappings dict - includes mapping from both sides # ('Hn', 'C', 'Nh') # {'Hn': {'Hn'}, 'Nh': {'Nh'}, 'C': {'C'}} # {'Hn': {'H', 'Hn'}, 'Nh': {'Nh'}, 'C': {'C'}} # {'CA': {'C'}, 'Hn': {'H', 'Hn'}, 'Nh': {'Nh'}, 'C': {'CA', 'C'}} # {'CA': {'C'}, 'Hn': {'H', 'Hn'}, 'Nh': {'Nh'}, 'C': {'CA', 'C'}} self.peakIndex = {} # go through the peaks for peak in peaks: self.peakIndex[peak] = [0 for ii in range(len(peak.axisCodes))] # get the peak dimension axisCode, nd see if is already there for peakDim, peakAxis in enumerate(peak.axisCodes): if peakAxis in refAxisCodes: self.peakIndex[peak][peakDim] = refAxisCodes.index(peakAxis) axisLabels[self.peakIndex[peak][peakDim]].add(peakAxis) else: # if the axisCode is not in the reference list then find the mapping from the dict for k, v in mappings.items(): if peakAxis in v: # refAxisCodes[dim] = k self.peakIndex[peak][peakDim] = refAxisCodes.index(k) axisLabels[refAxisCodes.index(k)].add(peakAxis) # peakCodes = set() # for peak in peaks: # # for code in peak.peakList.spectrum.isotopeCodes: # for code in peak.axisCodes: # peakCodes.add(code) # peakCodes = sorted(list(peakCodes), key=CcpnSorting.stringSortKey) # # # peakCodes = peaks[0].peakList.spectrum.spectrumDisplay.axisCodes # peakCodes = ['H', 'C', 'N', 'Other'] axisLabels = [', '.join(ax) for ax in axisLabels] self.axisCodeOptions.setButtons(texts=axisLabels, tipTexts=axisLabels, silent=True) def _setPeakAtomCodes(self): atomCodes = ['H', 'C', 'N', 'Other'] self.axisCodeOptions.setButtons(texts=list(atomCodes), tipTexts=list(atomCodes), silent=True) def _blockEvents(self): """Block all updates/signals/notifiers in the module. """ # self.setUpdatesEnabled(False) self.blockSignals(True) def _unblockEvents(self): """Unblock all updates/signals/notifiers in the module. """ self.blockSignals(False) # self.setUpdatesEnabled(True) @contextmanager def _moduleBlocking(self): """Context manager to handle blocking, unblocking of the module. """ self._blockEvents() try: # pass control to the calling function yield except Exception as es: raise es finally: self._unblockEvents() def _updateNmrResidue(self, data): """Update the widget after renaming an nmrResidue """ nmrResidue = data[Notifier.OBJECT] if nmrResidue and nmrResidue == self.current.nmrResidue: self._updateWidget() def _updateWidget(self, dataDict=None): # also used as notifier callback function """Update the widget to reflect the proper state""" # try: ii = jj = 0 with self._moduleBlocking(): self._setPeaksLabel() if self.current.nmrResidue is not None: # self.currentNmrResidueLabel.setText(self.current.nmrResidue.id) self._nmrResidue.select(self.current.nmrResidue.pid) self._assignWidgetHide() # dimensionalities = set([len(peak.position) for peak in self.current.peaks]) # if len(dimensionalities) > 1: # self.currentPeaksLabel.setText(MSG) # getLogger().warning('Not all peaks have the same number of dimensions.') # return False if self.current.peaks and None not in self.current.peaks: self._setPeakAxisCodes(self.current.peaks) if self.selectBackboneButton.isChecked(): for w in self._sidechainModifiers: w.hide() ii, jj = self._createBackBoneButtons() elif self.selectAllButton.isChecked(): for w in self._sidechainModifiers: w.show() ii, jj = self._createSideChainButtons() self._setCheckedButtonOfAssignedAtoms(self.current.nmrResidue) # add a spacer to the radiobutton box Spacer(self._assignWidget, 3, 3, QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding, grid=(30, 30), gridSpan=(1, 1)) if self.current.peaks and None not in self.current.peaks: # temporarily restrict highlighting to backbone only, as the side-chain version needs improving if self.selectBackboneButton.isChecked(): self._predictHighlight(self.current.peaks) self._assignWidgetShow() else: self._assignWidgetHide() def _removeOffsetFromButtonText(self, text: str): p = text.split(' ') if len(p) > 0: return p[0] else: return text def _setCheckedButtonOfAssignedAtoms(self, nmrResidue, offSet='0'): """setChecked the radioButton Of Assigned Nmr Atoms. This makes sure that if a peak is selected and and assigned to an nmrAtom, the relative button is checked """ if not self.current.peak: return if not nmrResidue: return peaks = self.current.peaks currentDisplayedButtons = self.buttonGroup.buttons() buttonsToCheck = [] currentAxis = self._getValidAxisCodeIndex() for peak in peaks: counts = set() if self.selectAxisCode.isChecked(): peakList = makeIterableList(peak.dimensionNmrAtoms[currentAxis]) if currentAxis < len(peak.dimensionNmrAtoms) else [] else: peakList = makeIterableList(peak.assignedNmrAtoms) for assignedNmrAtom in peakList: #makeIterableList(peak.assignedNmrAtoms): if assignedNmrAtom in nmrResidue.nmrAtoms: for button in currentDisplayedButtons: if assignedNmrAtom: if offSet == '0': if assignedNmrAtom.name == button.getText(): counts.add(button) elif button._offSet == offSet: if assignedNmrAtom.name == button._atomName: counts.add(button) else: #Try to search in + and - 1 offset for offset in ['-1', '+1']: r = self._getNmrResidue(nmrResidue.nmrChain, sequenceCode=nmrResidue.mainNmrResidue.sequenceCode + offset) if r: if assignedNmrAtom in r.nmrAtoms: for button in currentDisplayedButtons: if assignedNmrAtom: btext = self.atomLabel(assignedNmrAtom.name, offset) if btext == button.getText(): counts.add(button) buttonsToCheck.append(list(counts)) buttonsToCheck = makeIterableList(buttonsToCheck) if len(buttonsToCheck) >= len(peaks): for b in currentDisplayedButtons: if b in buttonsToCheck: b.setChecked(True) else: b.setChecked(False) else: self._togglePressedButton() def _getValidAxisCodeIndex(self): return self.axisCodeOptions.getIndex() def _getValidAxisCode(self, numChars=1): """Get the valid axis code from the buttons, numChars is included as this may be needed for DNA/RNA """ code = self.axisCodeOptions.getSelectedText() return code[0:numChars] # if code: # for cc in code: # if cc.isalpha(): # return cc.upper()[0] # return '-' def _getValidAtomType(self): """Get the valid atom type from the buttons, numChars is included as this may be needed for DNA/RNA """ return self.atomTypeOptions.getSelectedText() def _createBackBoneButtons(self): self._cleanupPickAndAssignWidget() for w in self._sidechainModifiers: w.hide() # wb104 27 Jun 2017: changed _cleanupPickAndAssignWidget so removes widgets in reverse order # not sure if there was anything else leading to this cludge (and one below) though # cludge: don't know why I have to do this for the button to appear: ###_Label = Label(self, text='') ###self._assignWidget.layout().addWidget(_Label, 0, 0) atoms = BACKBONEATOMS self.buttons = {} # seems to be deleting the same widgets as _clean for b in list(self.buttonGroup.buttons()): self.buttonGroup.removeButton(b) del b # b.setParent(None) # print('>>>deleting', b) # del b # rowCount = self._assignWidget.layout().rowCount() # colCount = self._assignWidget.layout().columnCount() # # for r in range(1, rowCount): # for m in range(colCount): # item = self._assignWidget.layout().itemAtPosition(r, m) # if item: # if item.widget(): # item.widget().hide() # self._assignWidget.layout().removeItem(item) if self.current.nmrResidue: rows = 0 cols = 0 validAxisCode = self._getValidAxisCode() validAtomType = self._getValidAtomType() if not validAxisCode: return 0, 0 for ii, atom in enumerate(atoms): self.buttons[atom] = [] if self.selectAxisCode.isChecked(): # display by axis codes if not atom.startswith(validAxisCode): continue else: # display by atom types if validAtomType != 'Other' and not atom.startswith(validAtomType): continue elif validAtomType == 'Other': if atom[0] in ['H', 'C', 'N']: continue # # skip if startswith these atomTypes # if not self.cCheckBox.isChecked() and atom.startswith('C'): # continue # if not self.hCheckBox.isChecked() and atom.startswith('H'): # continue # if not self.nCheckBox.isChecked() and atom.startswith('N'): # continue # if not self.otherCheckBox.isChecked() and not atom.startswith('C') \ # and not atom.startswith('H') \ # and not atom.startswith('N'): # continue innerCols = 0 for jj, offset in enumerate(['-1', '0', '+1']): btext = self.atomLabel(atom, offset) button = RadioButton(self._assignWidget, text=btext, grid=(rows, jj), callback=None) #partial(self.assignSelected, offset, atom)) button.setMinimumSize(BUTTON_MINX, BUTTON_MINY) self.buttonGroup.addButton(button) button._atomName = atom button._offSet = offset # button.clicked.connect(self._buttonCallback) self.buttons[atom].append(button) innerCols += 1 rows += 1 cols = max(cols, innerCols) # self._predictAssignments(self.current.peaks) return rows, cols def _createSideChainButtons(self): self._cleanupPickAndAssignWidget() for w in self._sidechainModifiers: w.show() # see comment about cludge above # cludge: don't know why I have to do this for the button to appear: TODO: fix this ###_label= Label(self._assignWidget, '', hAlign='l') ###self._assignWidget.layout().addWidget(_label, 0, 0, QtCore.Qt.AlignRight) ii, jj = self._updateChainLayout() # self._predictAssignments(self.current.peaks) return ii, jj def _changeAxisCode(self): self._toggleBox() def _changeAtomType(self): self._toggleBox() def _selectionCallback(self): self._showSelectionButtons() self._toggleBox() def _showSelectionButtons(self): if self.current.peaks: if self.selectAtomType.isChecked(): self.axisCodeOptions.hide() self.axisCodeLabel.hide() self.atomTypeLabel.show() self.atomTypeOptions.show() else: self.axisCodeOptions.show() self.axisCodeLabel.show() self.atomTypeLabel.hide() self.atomTypeOptions.hide() def _toggleBox(self): if self.selectBackboneButton.isChecked(): for w in self._sidechainModifiers: w.hide() elif self.selectAllButton.isChecked(): for w in self._sidechainModifiers: w.show() self._updateWidget() def _getAtomsForButtons(self, atomList, atomName): [atomList.remove(atom) for atom in sorted(atomList) if not atom.startswith(atomName)] def _removeAtomsForButtons(self, atomList, atomName): [atomList.remove(atom) for atom in sorted(atomList) if atom.startswith(atomName)] def _getAtomButtonList(self, residueType=None): additionalAtoms = list(ADDITIONALBACKBONEATOMS) alphaAtoms = [x for x in NEF_ATOM_NAMES_SORTED['alphas']] betaAtoms = [x for x in NEF_ATOM_NAMES_SORTED['betas']] gammaAtoms = [x for x in NEF_ATOM_NAMES_SORTED['gammas']] moreGammaAtoms = [x for x in NEF_ATOM_NAMES_SORTED['moreGammas']] deltaAtoms = [x for x in NEF_ATOM_NAMES_SORTED['deltas']] moreDeltaAtoms = [x for x in NEF_ATOM_NAMES_SORTED['moreDeltas']] epsilonAtoms = [x for x in NEF_ATOM_NAMES_SORTED['epsilons']] moreEpsilonAtoms = [x for x in NEF_ATOM_NAMES_SORTED['moreEpsilons']] zetaAtoms = [x for x in NEF_ATOM_NAMES_SORTED['zetas']] etaAtoms = [x for x in NEF_ATOM_NAMES_SORTED['etas']] moreEtaAtoms = [x for x in NEF_ATOM_NAMES_SORTED['moreEtas']] atomButtonList = [additionalAtoms, alphaAtoms, betaAtoms, gammaAtoms, moreGammaAtoms, deltaAtoms, moreDeltaAtoms, epsilonAtoms, moreEpsilonAtoms, zetaAtoms, etaAtoms, moreEtaAtoms] if residueType and isinstance(residueType, str): residueType = residueType.upper() if residueType in PROTEIN_NEF_ATOM_NAMES: residueAtoms = PROTEIN_NEF_ATOM_NAMES[residueType] residueAdditional = [atom for atom in additionalAtoms if atom in residueAtoms] residueAlphas = [atom for atom in alphaAtoms if atom in residueAtoms] residueBetas = [atom for atom in betaAtoms if atom in residueAtoms] residueGammas = [atom for atom in gammaAtoms if atom in residueAtoms] residueMoreGammas = [atom for atom in moreGammaAtoms if atom in residueAtoms] residueDeltas = [atom for atom in deltaAtoms if atom in residueAtoms] residueMoreDeltas = [atom for atom in moreDeltaAtoms if atom in residueAtoms] residueEpsilons = [atom for atom in epsilonAtoms if atom in residueAtoms] residueMoreEpsilons = [atom for atom in moreEpsilonAtoms if atom in residueAtoms] residueZetas = [atom for atom in zetaAtoms if atom in residueAtoms] residueEtas = [atom for atom in etaAtoms if atom in residueAtoms] residueMoreEtas = [atom for atom in moreEtaAtoms if atom in residueAtoms] residueAtomButtonList = [residueAdditional, residueAlphas, residueBetas, residueGammas, residueMoreGammas, residueDeltas, residueMoreDeltas, residueEpsilons, residueMoreEpsilons, residueZetas, residueEtas, residueMoreEtas] return residueAtomButtonList else: return atomButtonList def _getDnaRnaButtonList(self, atomList=None, residueType=None): residueAtomButtonList = copy.deepcopy(ALL_DNARNA_ATOMS_SORTED) if residueType and atomList: residueAtoms = atomList[residueType] for atomType in ALL_DNARNA_ATOMS_SORTED.keys(): atomTypeList = ALL_DNARNA_ATOMS_SORTED[atomType] for atom in atomTypeList: if atom not in residueAtoms: residueAtomButtonList[atomType].remove(atom) return [residueAtomButtonList[atom] for atom in residueAtomButtonList.keys()] def _removeCodes(self, atomButtonList): if self.selectAxisCode.isChecked(): # add atoms for the axisCode selected [self._getAtomsForButtons(atomList, self._getValidAxisCode()) for atomList in atomButtonList] else: # add atoms for atom type selected validAtomType = self._getValidAtomType() if validAtomType == 'C': [self._getAtomsForButtons(atomList, 'C') for atomList in atomButtonList] elif validAtomType == 'H': [self._getAtomsForButtons(atomList, 'H') for atomList in atomButtonList] elif validAtomType == 'N': [self._getAtomsForButtons(atomList, 'N') for atomList in atomButtonList] elif validAtomType == 'Other': for atomList in atomButtonList: [self._removeAtomsForButtons(atomList, 'C') for atomList in atomButtonList] [self._removeAtomsForButtons(atomList, 'H') for atomList in atomButtonList] [self._removeAtomsForButtons(atomList, 'N') for atomList in atomButtonList] def _updateChainLayout(self): # needs more work to allow DNA/RNA molecules if self.molTypePulldown.currentText() == PROTEIN_MOLECULE: # group atoms in useful categories based on usage atomButtonList = self._getAtomButtonList() elif self.molTypePulldown.currentText() == DNA_MOLECULE: # testing DNA/RNA buttonlist atomButtonList = self._getDnaRnaButtonList(DNA_ATOM_NAMES, 'DT') elif self.molTypePulldown.currentText() == RNA_MOLECULE: # testing DNA/RNA buttonlist atomButtonList = self._getDnaRnaButtonList(RNA_ATOM_NAMES, 'G') self._removeCodes(atomButtonList) # if self.selectAxisCode.isChecked(): # # add atoms for the axisCode selected # [self._getAtomsForButtons(atomList, self._getValidAxisCode()) for atomList in atomButtonList] # # else: # # add atoms for atom type selected # validAtomType = self._getValidAtomType() # if validAtomType == 'C': # [self._getAtomsForButtons(atomList, 'C') for atomList in atomButtonList] # # elif validAtomType == 'H': # [self._getAtomsForButtons(atomList, 'H') for atomList in atomButtonList] # # elif validAtomType == 'N': # [self._getAtomsForButtons(atomList, 'N') for atomList in atomButtonList] # # elif validAtomType == 'Other': # for atomList in atomButtonList: # [self._removeAtomsForButtons(atomList, 'C') for atomList in atomButtonList] # [self._removeAtomsForButtons(atomList, 'H') for atomList in atomButtonList] # [self._removeAtomsForButtons(atomList, 'N') for atomList in atomButtonList] # [atomList.remove(atom) for atom in sorted(atomList) if not atom.startswith('C') \ # and not atom.startswith('H') \ # and not atom.startswith('N')] # # Activate button for Carbons # if not self.cCheckBox.isChecked(): # [self._getAtomsForButtons(atomList, 'C') for atomList in atomButtonList] # # if not self.hCheckBox.isChecked(): # [self._getAtomsForButtons(atomList, 'H') for atomList in atomButtonList] # # if not self.nCheckBox.isChecked(): # [self._getAtomsForButtons(atomList, 'N') for atomList in atomButtonList] # # if not self.otherCheckBox.isChecked(): # for atomList in atomButtonList: # [atomList.remove(atom) for atom in sorted(atomList) if not atom.startswith('C') \ # and not atom.startswith('H') \ # and not atom.startswith('N')] # rowCount = self._assignWidget.layout().rowCount() # colCount = self._assignWidget.layout().columnCount() # # for r in range(1, rowCount): # for m in range(colCount): # item = self._assignWidget.layout().itemAtPosition(r, m) # if item: # if item.widget(): # item.widget().hide() # self._assignWidget.layout().removeItem(item) rows = 0 cols = 0 if self.current.nmrResidue: # self.currentNmrResidueLabel.setText(self.current.nmrResidue.id) self._nmrResidue.select(self.current.nmrResidue.pid) self.buttons = {} # seems to be deleting the same widgets as _clean for b in list(self.buttonGroup.buttons()): self.buttonGroup.removeButton(b) del b if not self.current.nmrResidue.residueType: for ii, atomList in enumerate(atomButtonList): for jj, atom in enumerate(atomList): self.buttons[atom] = [] offset = self.offsetSelector.currentText() bText = self.atomLabel(atom, offset) button = RadioButton(self._assignWidget, text=bText, grid=(rows, jj), hAlign='t', ) # callback=partial(self.assignSelected, offset, atom)) button._atomName = atom button._offSet = offset button.setMinimumSize(BUTTON_MINX, BUTTON_MINY) self.buttonGroup.addButton(button) self.buttons[atom].append(button) cols = max(cols, jj + 1) if atomList: rows += 1 else: if self.offsetSelector.currentText() == '-1': nmrResidue = self.current.nmrResidue.previousNmrResidue elif self.offsetSelector.currentText() == '+1': nmrResidue = self.current.nmrResidue.nextNmrResidue else: nmrResidue = self.current.nmrResidue residueType = nmrResidue.residueType.upper() if nmrResidue else None atomButtonList2 = self._getAtomButtonList(residueType) self._removeCodes(atomButtonList2) for ii, atomList in enumerate(atomButtonList2): for jj, atom in enumerate(atomList): self.buttons[atom] = [] offset = self.offsetSelector.currentText() bText = self.atomLabel(atom, offset) button = RadioButton(self._assignWidget, text=bText, grid=(rows, jj), hAlign='t', ) # callback=partial(self.assignSelected, self.offsetSelector.currentText(), atom)) # button = Button(self._assignWidget, text=atom, grid=(ii, jj), hAlign='t', # callback=partial(self.assignSelected, self.offsetSelector.currentText(), atom)) button._atomName = atom button._offSet = offset self.buttonGroup.addButton(button) button.setMinimumSize(BUTTON_MINX, BUTTON_MINY) # button.setAutoExclusive(True) self.buttons[atom].append(button) cols = max(cols, jj + 1) if atomList: rows += 1 return cols, rows def _showMoreAtomButtons(self, buttons, moreButton): if moreButton.isChecked(): [button.show() for button in buttons] else: [button.hide() for button in buttons] def _removeWidget(self, widget, removeTopWidget=False): """Destroy a widget and all it's contents """ def deleteItems(layout): if layout is not None: while layout.count(): item = layout.takeAt(0) widget = item.widget() if widget is not None: widget.setVisible(False) widget.setParent(None) del widget deleteItems(widget.getLayout()) if removeTopWidget: del widget def _cleanupPickAndAssignWidget(self): self._removeWidget(self._assignWidget)
[docs] def atomLabel(self, atom, offset, showAll=False): if showAll: return str(atom + ' [i]' if offset == '0' else atom + ' [i' + offset + ']') else: return str(atom if offset == '0' else atom + ' [i' + offset + ']')
# NOT NEEDED # def checkAssignedAtoms(self, nmrResidue, atoms, predictAtoms, checkMode='backbone'): # """ # Check if the i-1, i, i+1 nmrAtoms for the current residue exist # :param nmrResidue: # :return foundAtoms - dict containing True for each found nmrAtom: # """ # foundAtoms = {} # if checkMode == 'backbone': # # all residues are displayed so use the central mainNmrResidue # nmrResidue = self._getMainNmrResidue(nmrResidue) # # # atoms = ['H', 'N', 'CA', 'CB', 'CO', 'HA', 'HB'] # for ii, atom in enumerate(atoms): # for jj, offset in enumerate(['-1', '0', '+1']): # bText = self.atomLabel(atom, offset) # # if self._checkAssignedAtom(nmrResidue, offset, atom): # foundAtoms[bText] = True # if atom in self.buttons.keys(): # for button in self.buttons[atom]: # if button.getText() == bText: # # # colour the button if the atom exists # if bText in predictAtoms: # score = predictAtoms[bText] # # if score >= 85: # button.setStyleSheet('background-color: mediumseagreen') # elif 50 < score < 85: # button.setStyleSheet('background-color: lightsalmon') # if score < 50: # button.setStyleSheet('background-color: mediumvioletred') # # # else: # Users don't need to know/ or be notified here that the nmrAtom exist in the selected nmrResidue. # # button.setStyleSheet('background-color: cornflowerblue') # # # return foundAtoms def _getNmrResidue(self, nmrChain, sequenceCode: typing.Union[int, str] = None, residueType: str = None) -> typing.Optional[NmrResidue]: partialId = '%s.%s.' % (nmrChain.id, str(sequenceCode).translate(Pid.remapSeparators)) ll = self.project.getObjectsByPartialId(className='NmrResidue', idStartsWith=partialId) if ll: return ll[0] else: return nmrChain.getNmrResidue(sequenceCode) def _fetchNmrResidue(self, nmrChain, sequenceCode: typing.Union[int, str] = None, residueType: str = None) -> typing.Optional[NmrResidue]: partialId = '%s.%s.' % (nmrChain.id, str(sequenceCode).translate(Pid.remapSeparators)) ll = self.project.getObjectsByPartialId(className='NmrResidue', idStartsWith=partialId) if ll: return ll[0] else: return nmrChain.fetchNmrResidue(sequenceCode) def _getCorrectResidue(self, nmrResidue, offset: str, atomType: str): name = atomType r = None if offset == '-1' and '-1' not in nmrResidue.sequenceCode: r = nmrResidue.previousNmrResidue if not r: r = self._fetchNmrResidue(nmrResidue.nmrChain, sequenceCode=nmrResidue.sequenceCode + '-1') elif offset == '+1' and '+1' not in nmrResidue.sequenceCode: r = nmrResidue.nextNmrResidue if not r: r = self._fetchNmrResidue(nmrResidue.nmrChain, sequenceCode=nmrResidue.sequenceCode + '+1') else: r = nmrResidue return r # NOT NEEDED # def _checkAssignedAtom(self, nmrResidue, offset:int, atomType:str): # ''' # This checks only if an NMRAtom exists not if is assigned to a peak! # :param nmrResidue: # :param offset: # :param atomType: # :return: # ''' # r = self._getCorrectResidue(nmrResidue=nmrResidue, offset=offset, atomType=atomType) # if r: # atom = r.getNmrAtom(atomType.translate(Pid.remapSeparators)) # if atom and not atom.isDeleted: # return r # else: # return None # else: # return None # def _getMainNmrResidue(self, nmrResidue): # if nmrResidue: # if nmrResidue.relativeOffset and nmrResidue.relativeOffset != 0: # return nmrResidue.mainNmrResidue # # return nmrResidue def _assignDimension(self): """ update the peak assignment and create a event to update the module """ pass
[docs] def assignNmrAtomsToPeaks(self, nmrAtom, peaks): """Assign the nmrAtom to the dimension """ if not peaks: return if not nmrAtom: return index = self._getValidAxisCodeIndex() for peak in peaks: for ii, axisCode in enumerate(peak.axisCodes): if self.peakIndex[peak][ii] == index: peak.assignDimension(axisCode, nmrAtom) isotopeCode = peak.peakList.spectrum.getByAxisCodes('isotopeCodes', [axisCode], exactMatch=True)[-1] if nmrAtom.isotopeCode in [UnknownIsotopeCode, None]: nmrAtom._setIsotopeCode(isotopeCode)
[docs] def deassignAtomFromSelectedPeaks(self, peaks, nmrAtom): """Deassign the nmrAtom from the dimension """ if not peaks: return if not nmrAtom: return newAssignedAtoms = () index = self._getValidAxisCodeIndex() for peak in peaks: if self.selectAxisCode.isChecked(): # deassign by axis code dimension for ii, axisCode in enumerate(peak.axisCodes): if self.peakIndex[peak][ii] == index: peak.assignDimension(axisCode, None) else: # deassign by atom types peakDimNmrAtoms = list(peak.dimensionNmrAtoms) for dim, dimNmrAtoms in enumerate(peakDimNmrAtoms): if nmrAtom in dimNmrAtoms: dimNmrAtoms = list(dimNmrAtoms) dimNmrAtoms.remove(nmrAtom) peakDimNmrAtoms[dim] = dimNmrAtoms peak.dimensionNmrAtoms = peakDimNmrAtoms
# for subTuple in peak.assignedNmrAtoms: # a = tuple(None if na is nmrAtom else na for na in subTuple) # newAssignedAtoms += (a,) # try: # peak.assignedNmrAtoms = newAssignedAtoms # except Exception as es: # pass def _returnButtonsToNormal(self): """ Returns all buttons in Atom Selector to original colours and style. """ self._assignWidget.setStyleSheet(DEFAULT_BUTTON) def _currentNmrResiduesCallback(self, data): "Callback for the nmrResidues notifier" self._updateWidget() def _currentPeaksCallback(self, data): "Callback for the peaks notifier" peaks = data[Notifier.VALUE] # self._setPeaksLabel() self._updateWidget() self._nmrResidue.update() # def _predictAssignments(self, peaks: typing.List[Peak]): # """ # Predicts atom type for selected peaks and highlights the relevant buttons with confidence of # that assignment prediction, green is very confident, orange is less confident. # """ # self._assignWidgetHide() # # if self.current.nmrResidue and self.current.peaks: # self._predictHighlight(peaks) # # self._assignWidgetShow() def _predictHighlight(self, peaks: typing.List[Peak]): self._returnButtonsToNormal() # if self.current.nmrResidue is None or len(peaks) == 0: # self._assignWidgetHide() # return # # # make sure that the widget is visible # self._assignWidgetShow() # make sure that you have buttons! if len(self.buttonGroup.buttons()) == 0: return # check if peaks coincide for dim in range(peaks[0].peakList.spectrum.dimensionCount): if not peaksAreOnLine(peaks, dim): logger.debug('dimension %s: peaksAreonLine=False' % dim) return types = set(peak.peakList.spectrum.experimentType for peak in peaks) anyInterOnlyExperiments = any(isInterOnlyExpt(x) for x in types) logger.debug('peaks=%s' % (peaks,)) logger.debug('types=%s, anyInterOnlyExperiments=%s' % (types, anyInterOnlyExperiments)) peak = peaks[0] peakListViews = [peakListView for peakListView in self.project.peakListViews if peakListView.peakList == peak.peakList] if peakListViews: spectrumIndices = peakListViews[0].spectrumView.dimensionIndices # for the 1D case, this is (0, None) if spectrumIndices[1] is None: return isotopeCode = peak.peakList.spectrum.isotopeCodes[spectrumIndices[1]] # backbone if self.selectBackboneButton.isChecked(): predictedAtomTypes = [ getNmrAtomPrediction(ccpCode, peak.position[spectrumIndices[1]], isotopeCode, strict=True) for ccpCode in CCP_CODES] refinedPreds = [(type[0][0][1], type[0][1]) for type in predictedAtomTypes if len(type) > 0] atomPredictions = set() for atomPred, score in refinedPreds: if score > 90: atomPredictions.add(atomPred) # list containing those atoms that exist - used for colouring in 'checkAssignedAtoms' foundPredictList = {} for atomPred in atomPredictions: if atomPred == 'CB' and self.buttons['CB']: if anyInterOnlyExperiments: self.buttons['CB'][0].setStyleSheet(GREEN_BUTTON) foundPredictList[self.atomLabel('CB', '-1')] = 100 else: self.buttons['CB'][0].setStyleSheet(GREEN_BUTTON) self.buttons['CB'][1].setStyleSheet(GREEN_BUTTON) foundPredictList[self.atomLabel('CB', '-1')] = 100 foundPredictList[self.atomLabel('CB', '0')] = 100 if atomPred == 'CA' and self.buttons['CA']: if anyInterOnlyExperiments: self.buttons['CA'][0].setStyleSheet(GREEN_BUTTON) foundPredictList[self.atomLabel('CA', '-1')] = 100 else: self.buttons['CA'][0].setStyleSheet(GREEN_BUTTON) self.buttons['CA'][1].setStyleSheet(GREEN_BUTTON) foundPredictList[self.atomLabel('CA', '-1')] = 100 foundPredictList[self.atomLabel('CA', '0')] = 100 # new routine to colour any existing atoms # foundAtoms = self.checkAssignedAtoms(self.current.nmrResidue, ATOM_TYPES, # foundPredictList, 'backbone') # sidechain is checked elif self.selectAllButton.isChecked(): foundPredictList = {} if self.current.nmrResidue.residueType == '': # In this case, we loop over all CCP_CODES (i.e. residue types) predictedAtomTypes = [] for residueType in CCP_CODES: for type, score in getNmrAtomPrediction(residueType, peak.position[spectrumIndices[1]], isotopeCode): if len(type) > 0 and score > 50: predictedAtomTypes.append((type, score)) else: if self.offsetSelector.currentText() == '-1': nmrResidue = self.current.nmrResidue.previousNmrResidue elif self.offsetSelector.currentText() == '+1': nmrResidue = self.current.nmrResidue.nextNmrResidue else: nmrResidue = self.current.nmrResidue # don't need to predict if there is no nmrResidue if not nmrResidue: return predictedAtomTypes = getNmrAtomPrediction(nmrResidue.residueType.title(), peak.position[spectrumIndices[1]], isotopeCode) # print('>predictAtomTypes>', predictedAtomTypes) # find the maximum of each atomType predictedDict = {} for type, score in predictedAtomTypes: if type[1] not in predictedDict: predictedDict[type[1]] = (type[0], score) else: if score > predictedDict[type[1]][1]: predictedDict[type[1]] = (type[0], score) # print ('>>>predictedDict', predictedDict) currentOffset = self.offsetSelector.currentText() for atomDictType in predictedDict.keys(): bText = self.atomLabel(atomDictType, currentOffset) for atomType, buttons in self.buttons.items(): # get the correct button list if atomDictType == atomType: for button in buttons: if bText == button.getText(): if atomType in ['CA', 'CB']: # match the colouring above if (currentOffset == '-1' and anyInterOnlyExperiments) or \ (currentOffset == '0' and not anyInterOnlyExperiments): # print('>type[1], atomType, button>', atomDictType, bText) foundPredictList[self.atomLabel(atomDictType, currentOffset)] = score if score >= 85: button.setStyleSheet(GREEN_BUTTON) elif 50 < score < 85: button.setStyleSheet(ORANGE_BUTTON) if score < 50: button.setStyleSheet(RED_BUTTON) else: # print('>type[1], atomType, button>', atomDictType, bText) foundPredictList[self.atomLabel(atomDictType, currentOffset)] = score if score >= 85: button.setStyleSheet(GREEN_BUTTON) elif 50 < score < 85: button.setStyleSheet(ORANGE_BUTTON) if score < 50: button.setStyleSheet(RED_BUTTON) # new routine to colour any existing atoms # atomButtonList = self._getAtomButtonList() # atomButtonList = [x for i in atomButtonList for x in i] # foundAtoms = self.checkAssignedAtoms(self.current.nmrResidue, atomButtonList, # foundPredictList, 'sideChain') def _changeMoleculeType(self, data): """ change the available atomList depending on the moleculeType :param data - str from pullDown: """ pass
[docs] def getResidueTypes(self, moleculeType: str = 'protein'): """ return a list of residue types assiciated with the moleculeType :param moleculeType - str ['protein', 'DNA', 'RNA', 'carbohydrate', 'other'] :return list of str: """ if moleculeType in MOLECULE_TYPES: if moleculeType == 'protein': return [atomName for atomName in PROTEIN_NEF_ATOM_NAMES.keys()] else: return None
#TODO: clean this up to a proper place DNA_ATOMS = """ Res Name Atom Count Min. Max. Avg. Std Dev DA H2 H 950 2.60 8.64 7.49 0.80 DA H61 H 145 2.56 13.40 6.83 1.16 DA H62 H 144 5.31 12.90 6.92 1.11 DA H8 H 1097 2.35 9.10 8.00 0.83 DA H1' H 1092 4.19 8.50 6.04 0.33 DA H2' H 1083 0.84 4.82 2.66 0.37 DA H2'' H 1065 0.56 6.89 2.79 0.37 DA H3' H 1031 4.00 6.04 4.94 0.19 DA H4' H 819 2.08 9.13 4.34 0.34 DA H5' H 449 2.79 5.95 4.06 0.30 DA H5'' H 406 2.96 8.41 4.07 0.45 DA C2 C 134 151.00 159.33 154.69 1.25 DA C4 C 2 150.30 150.60 150.45 0.21 DA C5 C 2 119.80 119.90 119.85 0.07 DA C6 C 2 157.00 157.40 157.20 0.28 DA C8 C 145 138.00 142.79 141.33 0.94 DA C1' C 138 81.50 91.90 85.19 1.26 DA C2' C 123 36.23 42.93 40.58 1.05 DA C3' C 99 72.71 83.26 78.46 1.70 DA C4' C 68 84.43 91.33 87.21 1.19 DA C5' C 45 47.77 70.96 67.18 3.42 DA N1 N 3 223.40 227.00 225.20 1.80 DA N3 N 3 214.50 216.40 215.73 1.07 DA N6 N 9 77.40 81.70 80.22 1.29 DA N7 N 2 233.50 234.10 233.80 0.42 DA N9 N 1 170.20 170.20 170.20 0.00 DA P P 239 -45.29 35.48 -2.32 5.04 DC H41 H 593 -0.26 12.08 7.40 1.15 DC H42 H 576 -0.26 10.62 7.48 1.07 DC H5 H 1263 -6.22 8.25 5.40 0.89 DC H6 H 1277 -14.72 8.31 7.36 0.98 DC H1' H 1269 2.17 6.60 5.72 0.69 DC H2' H 1251 -29.45 6.93 2.11 1.42 DC H2'' H 1235 -20.82 8.48 2.39 1.08 DC H3' H 1177 -7.37 8.62 4.72 0.65 DC H4' H 923 -38.59 28.54 4.18 2.40 DC H5' H 458 -18.35 11.19 4.17 1.81 DC H5'' H 381 -41.21 15.96 3.98 3.50 DC C2 C 2 158.80 159.20 159.00 0.28 DC C4 C 2 167.80 168.00 167.90 0.14 DC C5 C 125 84.55 99.90 98.12 2.25 DC C6 C 129 133.73 144.70 142.76 1.63 DC C1' C 134 77.90 88.53 86.94 1.65 DC C2' C 105 30.62 42.27 40.34 1.45 DC C3' C 101 66.41 80.30 76.57 2.75 DC C4' C 52 77.80 88.39 85.90 1.93 DC C5' C 33 59.30 71.72 66.28 2.63 DC N1 N 1 150.70 150.70 150.70 0.00 DC N3 N 1 196.30 196.30 196.30 0.00 DC N4 N 16 95.10 100.41 98.29 1.33 DC P P 332 -14.20 52.72 -1.65 6.05 DG H1 H 1299 -2.91 13.94 11.62 2.06 DG H21 H 322 -26.86 18.44 7.46 3.00 DG H22 H 309 -26.86 18.44 6.36 3.02 DG H8 H 1975 6.07 9.31 7.78 0.25 DG H1' H 1979 2.53 12.45 5.90 0.45 DG H2' H 1932 1.43 8.23 2.67 0.41 DG H2'' H 1902 1.16 11.50 2.72 0.42 DG H3' H 1863 0.00 13.24 4.97 0.68 DG H4' H 1547 0.00 13.26 4.46 0.98 DG H5' H 885 0.00 7.82 4.12 0.36 DG H5'' H 761 0.00 7.76 4.10 0.35 DG C2 C 2 156.50 156.70 156.60 0.14 DG C4 C 2 153.10 154.00 153.55 0.64 DG C5 C 2 117.40 117.60 117.50 0.14 DG C6 C 2 161.00 161.40 161.20 0.28 DG C8 C 182 134.38 142.93 138.20 1.62 DG C1' C 166 74.90 91.82 85.07 2.02 DG C2' C 128 32.64 49.00 40.23 2.00 DG C3' C 124 67.72 81.41 77.69 2.27 DG C4' C 77 75.27 90.64 86.92 2.19 DG C5' C 57 58.50 71.42 67.06 2.21 DG N1 N 33 142.74 147.70 145.74 1.96 DG N2 N 5 75.10 76.10 75.52 0.38 DG N7 N 4 236.90 238.30 237.28 0.68 DG N9 N 1 168.50 168.50 168.50 0.00 DG P P 406 -20.92 47.10 -1.39 6.34 DT H3 H 575 2.12 14.53 12.85 1.97 DT H6 H 1420 1.61 8.10 7.36 0.32 DT H7 H 8 1.46 1.79 1.65 0.13 DT H71 H 1249 0.18 7.53 1.60 0.41 DT H72 H 1248 0.18 7.53 1.60 0.41 DT H73 H 1248 0.18 7.53 1.60 0.41 DT H1' H 1412 1.65 6.72 5.88 0.54 DT H2' H 1402 0.50 6.21 2.16 0.47 DT H2'' H 1383 0.78 4.87 2.39 0.36 DT H3' H 1337 3.79 14.11 4.80 0.47 DT H4' H 997 2.00 14.15 4.16 0.55 DT H5' H 608 2.54 14.20 4.20 1.56 DT H5'' H 547 0.98 7.19 3.97 0.40 DT C2 C 2 152.90 153.40 153.15 0.35 DT C4 C 2 168.50 169.10 168.80 0.42 DT C5 C 9 14.30 113.60 36.43 43.64 DT C6 C 229 135.80 143.60 139.24 0.92 DT C7 C 59 11.96 14.90 14.19 0.62 DT C1' C 215 80.60 92.22 86.88 1.37 DT C2' C 195 34.38 42.96 39.99 0.88 DT C3' C 176 71.58 80.10 77.49 1.64 DT C4' C 64 78.80 89.31 86.04 1.46 DT C5' C 41 59.40 68.40 66.36 1.91 DT N1 N 1 142.90 142.90 142.90 0.00 DT N3 N 41 156.40 160.50 159.27 0.71 DT P P 261 -5.65 47.79 -1.36 7.03 """ RNA_ATOMS = """ Res Name Atom Count Min. Max. Avg. Std Dev A H2 H 1617 0.00 9.39 7.63 0.47 A H61 H 298 0.00 10.85 7.41 1.18 A H62 H 265 0.00 9.98 6.78 1.11 A H8 H 1592 7.00 9.20 8.01 0.27 A H1' H 1616 4.56 7.16 5.88 0.21 A HO2' H 44 4.27 7.75 5.75 1.18 A H2' H 1414 3.61 5.91 4.59 0.22 A H3' H 1213 3.91 5.45 4.63 0.21 A H4' H 1042 0.00 5.29 4.46 0.25 A H5' H 803 0.00 5.03 4.30 0.27 A H5'' H 786 0.00 153.90 4.37 5.35 A C2 C 1015 0.00 167.71 151.71 8.98 A C4 C 35 0.00 151.89 131.04 47.78 A C5 C 35 0.00 153.63 104.96 39.80 A C6 C 37 0.00 159.60 139.05 49.16 A C8 C 993 0.00 145.80 138.18 9.80 A C1' C 944 84.23 130.39 91.64 3.57 A C2' C 703 4.75 99.00 75.25 5.81 A C3' C 640 0.00 101.40 73.68 4.97 A C4' C 629 0.00 92.60 82.53 4.09 A C5' C 540 0.00 109.20 66.22 6.79 A N1 N 253 0.00 229.70 216.11 30.52 A N3 N 203 0.00 231.08 210.51 30.33 A N6 N 157 0.00 97.01 79.33 13.30 A N7 N 151 0.00 237.10 224.40 37.21 A N9 N 189 0.00 176.80 166.48 24.64 A P P 176 -5.96 2.43 -2.97 1.64 C H41 H 973 5.64 10.38 7.91 0.72 C H42 H 951 5.07 9.06 7.25 0.69 C H5 H 1865 4.20 7.09 5.48 0.29 C H6 H 1915 5.62 8.46 7.68 0.21 C H1' H 1871 3.42 6.42 5.55 0.24 C HO2' H 66 3.96 9.70 5.82 1.49 C H2' H 1578 0.00 5.07 4.32 0.24 C H3' H 1389 0.00 5.63 4.41 0.23 C H4' H 1135 0.00 12.58 4.33 0.37 C H5' H 850 0.00 7.17 4.26 0.38 C H5'' H 871 0.00 5.04 4.10 0.46 C C2 C 65 0.00 189.36 136.58 59.06 C C4 C 78 0.00 169.52 147.08 53.62 C C5 C 1101 0.00 141.05 97.28 4.33 C C6 C 1132 93.40 145.02 139.71 7.35 C C1' C 1076 84.00 118.95 92.68 2.10 C C2' C 839 0.00 99.40 75.76 5.32 C C3' C 780 0.00 104.90 73.18 6.41 C C4' C 735 0.00 92.60 81.94 5.86 C C5' C 609 0.00 110.20 64.72 9.47 C N1 N 229 0.00 178.83 145.28 30.98 C N3 N 181 0.00 204.30 184.53 43.16 C N4 N 390 0.00 104.50 96.67 10.21 C P P 186 -5.13 0.62 -3.15 1.65 G H1 H 1527 6.09 14.32 12.35 0.98 G H21 H 444 0.00 9.39 7.37 1.42 G H22 H 402 0.00 9.06 6.31 1.22 G H8 H 2293 0.00 8.63 7.60 0.39 G H1' H 2257 3.41 7.62 5.66 0.33 G HO2' H 53 4.40 7.10 5.77 1.16 G H2' H 1931 3.26 6.30 4.58 0.25 G H3' H 1724 3.80 5.78 4.56 0.26 G H4' H 1405 0.00 5.11 4.43 0.22 G H5' H 1187 2.89 5.42 4.28 0.23 G H5'' H 1157 2.55 5.11 4.18 0.23 G C2 C 59 0.00 162.50 131.48 56.36 G C4 C 44 0.00 158.62 120.73 61.96 G C5 C 78 0.00 167.24 106.95 40.80 G C6 C 86 0.00 162.74 142.40 49.29 G C8 C 1429 0.00 146.73 135.38 8.27 G C1' C 1244 79.80 95.05 91.50 1.97 G C2' C 975 0.00 99.40 75.27 4.88 G C3' C 887 0.00 102.20 73.85 5.27 G C4' C 855 72.08 96.10 82.65 2.63 G C5' C 758 50.36 108.20 66.41 6.34 G N1 N 891 0.00 166.07 145.58 13.10 G N2 N 151 0.00 83.38 71.00 17.00 G N3 N 25 0.00 234.10 97.88 80.79 G N7 N 184 0.00 240.99 221.87 50.58 G N9 N 280 0.00 176.40 164.01 30.01 G P P 208 -6.00 0.56 -2.86 1.59 U H3 H 898 1.20 154.54 13.24 4.89 U H5 H 1524 4.20 7.83 5.47 0.31 U H6 H 1590 5.81 8.52 7.75 0.20 U H1' H 1559 3.69 6.59 5.61 0.26 U HO2' H 45 4.08 9.74 6.06 1.51 U H2' H 1357 0.00 6.63 4.37 0.27 U H3' H 1117 0.00 7.83 4.48 0.25 U H4' H 949 0.00 4.82 4.36 0.23 U H5' H 697 0.00 4.85 4.24 0.30 U H5'' H 713 0.00 4.86 4.15 0.29 U C2 C 80 0.00 183.20 144.31 38.05 U C4 C 86 0.00 169.70 157.82 39.47 U C5 C 944 0.00 154.13 102.90 6.62 U C6 C 975 0.00 169.95 140.14 8.53 U C1' C 990 80.62 96.30 91.90 2.14 U C2' C 737 0.00 99.80 75.04 4.49 U C3' C 656 0.00 102.50 73.74 5.43 U C4' C 643 0.00 94.42 82.70 4.14 U C5' C 525 0.00 106.60 65.07 5.67 U N1 N 209 0.00 192.14 147.96 25.60 U N3 N 553 0.00 167.30 158.99 16.67 U O4 O 5 0.00 0.00 0.00 0.00 U P P 149 -5.30 1.58 -2.99 1.78 """ DNA_ATOM_NAMES = { 'DT': ['H3', 'H6', 'H7', 'H71', 'H72', 'H73', "H1'", "H2'", "H2''", "H3'", "H4'", "H5'", "H5''", 'C2', 'C4', 'C5', 'C6', 'C7', "C1'", "C2'", "C3'", "C4'", "C5'", 'N1', 'N3', 'P'], 'DC': ['H41', 'H42', 'H5', 'H6', "H1'", "H2'", "H2''", "H3'", "H4'", "H5'", "H5''", 'C2', 'C4', 'C5', 'C6', "C1'", "C2'", "C3'", "C4'", "C5'", 'N1', 'N3', 'N4', 'P'], 'DA': ['H2', 'H61', 'H62', 'H8', "H1'", "H2'", "H2''", "H3'", "H4'", "H5'", "H5''", 'C2', 'C4', 'C5', 'C6', 'C8', "C1'", "C2'", "C3'", "C4'", "C5'", 'N1', 'N3', 'N6', 'N7', 'N9', 'P'], 'DG': ['H1', 'H21', 'H22', 'H8', "H1'", "H2'", "H2''", "H3'", "H4'", "H5'", "H5''", 'C2', 'C4', 'C5', 'C6', 'C8', "C1'", "C2'", "C3'", "C4'", "C5'", 'N1', 'N2', 'N7', 'N9', 'P'] } RNA_ATOM_NAMES = { 'G': ['H1', 'H21', 'H22', 'H8', "H1'", "HO2'", "H2'", "H3'", "H4'", "H5'", "H5''", 'C2', 'C4', 'C5', 'C6', 'C8', "C1'", "C2'", "C3'", "C4'", "C5'", 'N1', 'N2', 'N3', 'N7', 'N9', 'P'], 'U': ['H3', 'H5', 'H6', "H1'", "HO2'", "H2'", "H3'", "H4'", "H5'", "H5''", 'C2', 'C4', 'C5', 'C6', "C1'", "C2'", "C3'", "C4'", "C5'", 'N1', 'N3', 'O4', 'P'], 'A': ['H2', 'H61', 'H62', 'H8', "H1'", "HO2'", "H2'", "H3'", "H4'", "H5'", "H5''", 'C2', 'C4', 'C5', 'C6', 'C8', "C1'", "C2'", "C3'", "C4'", "C5'", 'N1', 'N3', 'N6', 'N7', 'N9', 'P'], 'C': ['H41', 'H42', 'H5', 'H6', "H1'", "HO2'", "H2'", "H3'", "H4'", "H5'", "H5''", 'C2', 'C4', 'C5', 'C6', "C1'", "C2'", "C3'", "C4'", "C5'", 'N1', 'N3', 'N4', 'P'] } ALL_DNARNA_ATOMS_SORTED = OrderedDict([ ('O', ["HO2'"]), ('1', ["C1'", 'H1', "H1'", 'N1']), ('2', ['C2', "C2'", 'H2', "H2'", "H2''", 'H21', 'H22', 'N2']), ('3', ["C3'", 'H3', "H3'", 'N3']), ('4', ['C4', "C4'", "H4'", 'H41', 'H42', 'N4', 'O4']), ('5', ['C5', "C5'", 'H5', "H5'", "H5''"]), ('6', ['C6', 'H6', 'H61', 'H62', 'N6']), ('7', ['C7', 'H7', 'H71', 'H72', 'H73', 'N7']), ('8', ['C8', 'H8']), ('9', ['N9']), ('P', ['P']) ]) if __name__ == '__main__': from ccpn.ui.gui.widgets.Application import TestApplication from ccpn.ui.gui.widgets.TextEditor import TextEditor app = TestApplication() popup = NmrAtomAssignerModule() textBox = TextEditor(popup.mainWidget, grid=(3, 0), gridSpan=(1, 1)) all_atoms = dict() for atom in ['O', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'P']: all_atoms[atom] = [] atomList = [DNA_ATOMS, RNA_ATOMS] startAtoms = ['DA', 'DC', 'DG', 'DT', 'A', 'G', 'C', 'U'] for atomText in atomList: atoms = {} atomText = atomText.split('\n') for line in atomText: ll = line.split() if ll: if ll[0] in startAtoms: if ll[0] in atoms.keys(): atoms[ll[0]].append(ll[1]) else: atoms[ll[0]] = [ll[1]] if ll[1] != 'P' and ll[1] not in all_atoms[ll[1][1]]: all_atoms[ll[1][1]].append(ll[1]) all_atoms[ll[1][1]].sort() textBox.append(str(atoms)) all_atoms['P'] = ['P'] textBox.append(str(all_atoms)) popup._nmrResidue.setText('NmrResidue here') print(popup._getDnaRnaButtonList(DNA_ATOM_NAMES, 'DT')) print(popup._getDnaRnaButtonList(DNA_ATOM_NAMES, 'DC')) print(popup._getDnaRnaButtonList(DNA_ATOM_NAMES, 'DA')) print(popup._getDnaRnaButtonList(DNA_ATOM_NAMES, 'DG')) print(popup._getDnaRnaButtonList(RNA_ATOM_NAMES, 'G')) print(popup._getDnaRnaButtonList(RNA_ATOM_NAMES, 'U')) print(popup._getDnaRnaButtonList(RNA_ATOM_NAMES, 'A')) print(popup._getDnaRnaButtonList(RNA_ATOM_NAMES, 'C')) popup.show() popup.raise_() app.start()