Source code for ccpn.AnalysisAssign.modules.BackboneAssignmentModule

"""Module Documentation here

"""
#=========================================================================================
# 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: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2022-02-24 11:54:22 +0000 (Thu, February 24, 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 typing
from collections import OrderedDict
from PyQt5 import QtGui, QtWidgets
from ccpn.AnalysisAssign.lib.scoring import getNmrResidueMatches
from ccpn.core._OldChemicalShift import _OldChemicalShift
from ccpn.core.NmrResidue import NmrResidue
from ccpn.core.NmrChain import NmrChain
from ccpn.ui.gui.lib.SpectrumDisplay import makeStripPlot
from ccpn.ui.gui.lib.StripLib import matchAxesAndNmrAtoms
from ccpn.ui.gui.lib.StripLib import navigateToNmrResidueInDisplay
from ccpn.ui.gui.modules.NmrResidueTable import NmrResidueTableModule
from ccpn.ui.gui.widgets.CheckBox import CheckBox
from ccpn.ui.gui.widgets.CompoundWidgets import ListCompoundWidget, PulldownListCompoundWidget, CheckBoxCompoundWidget
from ccpn.ui.gui.widgets.MessageDialog import showWarning, progressManager, showYesNo
from ccpn.ui.gui.widgets.PulldownListsForObjects import ChemicalShiftListPulldown
from ccpn.ui.gui.widgets.Spacer import Spacer
from ccpn.ui.gui.lib.GuiNotifier import GuiNotifier
from ccpn.ui.gui.widgets.DropBase import DropBase
from ccpn.ui.gui.widgets.Font import getTextDimensionsFromFont
from ccpn.util.decorators import logCommand
from ccpn.util.Logging import getLogger
from ccpn.core.NmrAtom import NmrAtom
from ccpn.ui.gui.widgets.PlaneToolbar import STRIPLABEL_CONNECTDIR, STRIPLABEL_CONNECTNONE, \
    STRIPCONNECT_LEFT, STRIPCONNECT_RIGHT
from ccpn.core.lib.ContextManagers import undoBlockWithoutSideBar, notificationEchoBlocking


ALL = '<all>'
MINMATCHES = 1
MAXMATCHES = 7
DEFAULTMATCHES = 2
STRIPBACKBONE = 'backboneAssignment'
MARKCONNECTED = False
EXTRAWIDTH = 150
EXTRAOFFSET = 100


[docs]class BackboneAssignmentModule(NmrResidueTableModule): className = 'BackboneAssignmentModule' includeSettingsWidget = True maxSettingsState = 2 # states are defined as: 0: invisible, 1: both visible, 2: only settings visible settingsPosition = 'left' settingsMinimumSizes = (500, 200) includeDisplaySettings = True activePulldownClass = NmrChain def __init__(self, mainWindow=None, name='Backbone Assignment'): super(BackboneAssignmentModule, self).__init__(mainWindow=mainWindow, name=name, selectFirstItem=True) # 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 self.nmrChains = self.application.project.nmrChains self.matchCheckBoxWidget = CheckBox(self.nmrResidueTable._widget, grid=(1, 2), checked=True, text='Find matches') ### Settings ### # change some of the defaults setting inherited from NmrResidueTableModule self.nmrResidueTableSettings.sequentialStripsWidget.checkBox.setChecked(True) if self.nmrResidueTableSettings.displaysWidget: self.nmrResidueTableSettings.displaysWidget.addPulldownItem(0) # colWidth0 = 180 texts = ['i-1 Matches to show:', 'i+1 Matches to show:', 'Match module', 'Search module', 'Match CA NmrAtoms', 'Match CB NmrAtoms', 'Match C NmrAtoms', 'ChemicalShiftList'] _, maxDim = getTextDimensionsFromFont(textList=texts) colWidth0 = maxDim.width() row = self.nmrResidueTableSettings.maxRows ## Number of widgets of NmrResidueTable - add extra widgets below col = 0 # Number of matches to show row += 1 self.numberOfMinusMatchesWidget = PulldownListCompoundWidget(self.nmrResidueTableSettings, grid=(row, col), gridSpan=(1, 2), vAlign='top', hAlign='left', fixedWidths=(colWidth0, colWidth0, None), # orientation='left', labelText=texts[0], texts=[str(tt) for tt in range(MINMATCHES, MAXMATCHES)], default=DEFAULTMATCHES ) row += 1 self.numberOfPlusMatchesWidget = PulldownListCompoundWidget(self.nmrResidueTableSettings, grid=(row, col), gridSpan=(1, 2), vAlign='top', hAlign='left', fixedWidths=(colWidth0, colWidth0, None), # orientation='left', labelText=texts[1], texts=[str(tt) for tt in range(MINMATCHES, MAXMATCHES)], default=DEFAULTMATCHES ) # Match module selection # row += 1 # # cannot set a notifier for displays, as these are not (yet?) implemented # self.matchWidget = ListCompoundWidget(self.nmrResidueTableSettings, # grid=(row, col), vAlign='top', hAlign='left', # fixedWidths=(colWidth0, colWidth0, colWidth0), # orientation='left', # labelText="Match module(s):", # texts=[display.pid for display in self.mainWindow.spectrumDisplays] # ) # self.matchWidget.setPreSelect(self._fillDisplayWidget) # self.matchWidget.setFixedHeights((None, None, 40)) # new match module pulldown list row += 1 # TODO replace with SpectrumDisplayPulldown self.matchWidget = PulldownListCompoundWidget(self.nmrResidueTableSettings, labelText=texts[2], fixedWidths=(colWidth0, colWidth0, None), grid=(row, col), gridSpan=(1, 2), vAlign='top', hAlign='left', ) self.matchWidget.setPreSelect(self._fillMatchWidget) self._fillMatchWidget() self.matchWidget.pulldownList.setIndex(0) # new search module pulldown list row += 1 self.targetWidget = PulldownListCompoundWidget(self.nmrResidueTableSettings, labelText=texts[3], fixedWidths=(colWidth0, colWidth0, None), grid=(row, col), gridSpan=(1, 2), vAlign='top', hAlign='left', ) self.targetWidget.setPreSelect(self._fillTargetWidget) self._fillTargetWidget() self.targetWidget.pulldownList.setIndex(0) row += 1 self.focusYAxis = CheckBoxCompoundWidget(self.nmrResidueTableSettings, grid=(row, col), gridSpan=(1, 2), vAlign='top', hAlign='left', fixedWidths=(colWidth0, None), orientation='left', labelText='Focus Y-Axis', checked=True ) # Select which NmrAtoms to match # VAH: This could probably be improved by putting the check boxes into a group "NmrAtoms to Match" # and then automatically using the label text 'CA', 'CB' etc. in the _setNmrAtomsToMatch function. row += 1 self.matchCA = CheckBoxCompoundWidget(self.nmrResidueTableSettings, grid=(row, col), gridSpan=(1, 2), vAlign='top', hAlign='left', fixedWidths=(colWidth0, None), orientation='left', labelText='Match CA NmrAtoms', callback=self._setNmrAtomsToMatch, checked=True ) row += 1 self.matchCB = CheckBoxCompoundWidget(self.nmrResidueTableSettings, grid=(row, col), gridSpan=(1, 2), vAlign='top', hAlign='left', fixedWidths=(colWidth0, None), orientation='left', labelText='Match CB NmrAtoms', callback=self._setNmrAtomsToMatch, checked=True ) row += 1 self.matchC = CheckBoxCompoundWidget(self.nmrResidueTableSettings, grid=(row, col), gridSpan=(1, 2), vAlign='top', hAlign='left', fixedWidths=(colWidth0, None), orientation='left', labelText='Match C NmrAtoms', callback=self._setNmrAtomsToMatch, checked=False ) self._setNmrAtomsToMatch() # Chemical shift list selection row += 1 self.shiftListWidget = ChemicalShiftListPulldown(self.nmrResidueTableSettings, self.mainWindow, grid=(row, col), gridSpan=(1, 2), vAlign='top', hAlign='left', fixedWidths=(colWidth0, colWidth0, None), callback=self._setupShiftDicts, default=None ) self._setupShiftDicts() self._spacer = Spacer(self.settingsWidget, 5, 5, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding, grid=(row + 20, 10), gridSpan=(1, 1)) # for compatibility with previous implementation #self.moduleList = self.matchWidget.listWidget self._stripNotifiers = [] # list to store GuiNotifiers for strips self.nmrResidueTable.multiSelect = True self.nmrResidueTable.setSelectionMode(self.nmrResidueTable.SingleSelection) #self.nmrResidueTable._setWidgetHeight(48) self.mainWidget.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored) self.layout.setContentsMargins(0, 1, 0, 0) def _fillMatchWidget(self): ll = ['> select-to-add <'] + [display.pid for display in self.mainWindow.spectrumDisplays] thisText = self.matchWidget.getText() self.matchWidget.pulldownList.setData(texts=ll) if thisText: self.matchWidget.select(thisText, True) def _fillTargetWidget(self): ll = ['> select-to-add <'] + [display.pid for display in self.mainWindow.spectrumDisplays] thisText = self.targetWidget.getText() self.targetWidget.pulldownList.setData(texts=ll) if thisText: self.targetWidget.select(thisText, True) def _getDisplays(self): """return list of displays to navigate""" displays = [] if self.nmrResidueTableSettings.displaysWidget: dGids = self.nmrResidueTableSettings.displaysWidget.getTexts() # gid's of displays if len(dGids) == 0: return displays matchGids = self.matchWidget.getText() # gid of the match module targetGids = self.targetWidget.getText() # gid of the targets module - don't discard for the minute if ALL in dGids: displays = [dp for dp in self.application.ui.mainWindow.spectrumDisplays if dp.pid not in (matchGids, targetGids)] else: displays = [self.application.getByGid(gid) for gid in dGids if (gid != ALL and gid not in (matchGids, targetGids))] displays = [display for display in displays if display is not None] return displays def _getMatchDisplays(self): """return list of displays to display matches """ mGids = self.matchWidget.getText() # gid of the match displays displays = [self.application.getByGid(gid) for gid in (mGids,)] displays = [display for display in displays if display is not None] return displays def _getTargetDisplays(self): """return list of displays to display targets """ mGids = self.targetWidget.getText() # gid of the match displays displays = [self.application.getByGid(gid) for gid in (mGids,)] displays = [display for display in displays if display is not None] return displays
[docs] def navigateToNmrResidueCallBack(self, data): """ Navigate in selected displays to nmrResidue; skip if none defined """ from ccpn.core.lib.CallBack import CallBack # nmrResidue = data[CallBack.OBJECT] # if not nmrResidue: # return # if isinstance(nmrResidue, (tuple, list)): # nmrResidue = nmrResidue[0] nmrResidue = data[CallBack.ROWOBJECT] # the item clicked, not everything selected row = data[CallBack.ROW] col = data[CallBack.COL] self.navigateToNmrResidue(nmrResidue, row=row, col=col)
[docs] @logCommand(get='self') def navigateToNmrResidue(self, nmrResidue, row=None, col=None): """ Navigate in selected displays to nmrResidue; skip if no displays defined If matchCheckbox is checked, also call findAndDisplayMatches """ displays = self._getDisplays() # if len(displays) == 0 and self.nmrResidueTableSettings.displaysWidget: # getLogger().warning('Undefined display module(s); select in settings first') # showWarning('startAssignment', 'Undefined display module(s);\nselect in settings first') # return matchIndex = self.matchWidget.getIndex() targetIndex = self.targetWidget.getIndex() if self.matchCheckBoxWidget.isChecked() and matchIndex == 0: getLogger().warning('Undefined Match module; select in settings first or unselect "Find matches"') showWarning('startAssignment', 'Undefined Match module;\nselect in settings first or unselect "Find matches"') return if self.matchCheckBoxWidget.isChecked() and targetIndex == 0: getLogger().warning('Undefined Search module; select in settings first or unselect "Find matches"') showWarning('startAssignment', 'Undefined Search module;\nselect in settings first or unselect "Find matches"') return if (matchIndex == targetIndex) and matchIndex != 0: getLogger().warning('Match module and Search module cannot be the same') showWarning('startAssignment', 'Match module and Search module cannot be the same') return with undoBlockWithoutSideBar(): # optionally clear the marks if self.nmrResidueTableSettings.autoClearMarksWidget.checkBox.isChecked(): self.mainWindow.clearMarks() # clear any notifiers of previous strips for notifier in self._stripNotifiers: notifier.unRegister() del (notifier) self._stripNotifiers = [] nr = nmrResidue.mainNmrResidue targetDisplays = self._getTargetDisplays() matchDisplays = self._getMatchDisplays() # navigate to the other displays - not matchDisplay for display in displays: # display.showAllStripHeaders() # tag all headers with backboneAssignment module as handler if len(display.strips) > 0: # if contains 2D's (e.g. a hsqc) then keep zoom if display.spectrumViews[0].spectrum.dimensionCount <= 2: newWidths = [] #_getCurrentZoomRatio(display.strips[0].viewBox.viewRange()) else: # set the width in case of nD (n>2) _widths = {'H': 2.5, 'C': 1.0, 'N': 1.0} _ac = display.strips[0].axisCodes[0] _w = _widths.setdefault(_ac[0], 1.0) newWidths = [_w, 'full'] strips = navigateToNmrResidueInDisplay(nr, display, stripIndex=0, widths=newWidths, showSequentialResidues=(len(display.axisCodes) > 2) and self.nmrResidueTableSettings.sequentialStripsWidget.checkBox.isChecked(), markPositions=False, #self.nmrResidueTableSettings.markPositionsWidget.checkBox.isChecked() showDropHeaders=display in targetDisplays, ) # for st, strip in enumerate(strips): # if strip is not None: # strip.header.handle = STRIPBACKBONE # strip.header.headerVisible = True strips[0].spectrumDisplay.setColumnStretches(True) # need better way to make sure that the floating axis updates strips[0]._CcpnGLWidget.emitYAxisChanged(allStrips=True) # navigate to the targetDisplays for display in targetDisplays: display.showAllStripHeaders() # tag all headers with backboneAssignment module as handler if len(display.strips) > 0: # add a mask to ignore the position for the Y-axis axisMask = [True] * len(display.axisCodes) axisMask[1] = False newWidths = [] #_getCurrentZoomRatio(display.strips[0].viewBox.viewRange()) strips = navigateToNmrResidueInDisplay(nr, display, stripIndex=0, widths=newWidths, showSequentialResidues=(len(display.axisCodes) > 2) and self.nmrResidueTableSettings.sequentialStripsWidget.checkBox.isChecked(), markPositions=False, #self.nmrResidueTableSettings.markPositionsWidget.checkBox.isChecked() showDropHeaders=True, axisMask=axisMask ) for st, strip in enumerate(strips): if strip is not None: strip.header.handle = STRIPBACKBONE strip.header.headerVisible = True strips[0].spectrumDisplay.setColumnStretches(True) strips[0]._CcpnGLWidget.emitYAxisChanged(allStrips=True) # ejb # if 'i-1' residue, take CA CB, and take H, N from the 'i' residue (.mainNmrResidue) # check if contains '-1' in pid, is this robust? no :) # # VAH: # Changed, so marks are drawn for the C atoms that are being matched and the base # atoms specified here. Relies on use of NEF atom names, but makes it easier to # make more generic at a later stage. baseNmrAtoms = ['H', 'N'] if self.nmrResidueTableSettings.markPositionsWidget.checkBox.isChecked(): if nmrResidue.relativeOffset is not None and nmrResidue.relativeOffset != 0: # offset residue (not necessarily i-1!) so need to split the match nmrAtoms # (e.g. CA/CB) from the base nmrAtoms (e.g. N, H) nmrAtomsOffset = nmrAtomsFromResidue(nmrResidue) nmrAtomsCentre = nmrAtomsFromResidue(nmrResidue.mainNmrResidue) nmrAtoms = [] for naOffset in nmrAtomsOffset: if naOffset.name in self.nmrAtomsToMatch: nmrAtoms.append(naOffset) for naCentre in nmrAtomsCentre: if naCentre.name in baseNmrAtoms: nmrAtoms.append(naCentre) markNmrAtoms(mainWindow=self.mainWindow, nmrAtoms=nmrAtoms) else: if MARKCONNECTED: nmrAtoms = [na for na in nmrAtomsFromResidue(nmrResidue.mainNmrResidue) if na.name in self.nmrAtomsToMatch or na.name in baseNmrAtoms] else: nmrAtoms = [na for na in nmrResidue.mainNmrResidue.nmrAtoms if na.name in self.nmrAtomsToMatch or na.name in baseNmrAtoms] markNmrAtoms(mainWindow=self.mainWindow, nmrAtoms=nmrAtoms) if self.matchCheckBoxWidget.isChecked(): self.findAndDisplayMatches(nmrResidue) # select the order for copying YAxis values if self.focusYAxis.isChecked(): # align the target modules to the display module self._setDisplayPosWidth(matchDisplays, targetDisplays) else: # align the display modules to the target module self._setDisplayPosWidth(targetDisplays, matchDisplays)
# # update current (should trigger SequenceGraph) # self.application.current.nmrChain = nmrResidue.nmrChain # self.application.current.nmrResidue = nmrResidue # finally: # self.application._endCommandBlock() def _setDisplayPosWidth(self, matchDisplays, targetDisplays): if matchDisplays and matchDisplays[0].strips: # get the current position/width of the first match display matchPos = matchDisplays[0].strips[0].getAxisPosition(axisIndex=1) matchWidth = matchDisplays[0].strips[0].getAxisWidth(axisIndex=1) for target in targetDisplays: # align to the match module for tgStrip in target.strips: tgStrip.setAxisPosition(axisIndex=1, position=matchPos, rescale=False, update=False) tgStrip.setAxisWidth(axisIndex=1, width=matchWidth, rescale=True, update=True) #(tgStrip == target.strips[-1])) target.strips[0]._CcpnGLWidget.emitYAxisChanged(allStrips=True)
[docs] def findAndDisplayMatches(self, nmrResidue): """Find and displays the matches to nmrResidue""" # If NmrResidue is a -1 offset NmrResidue, set queryShifts as value from self.interShifts dictionary # Set matchShifts as self.intraShifts if nmrResidue.relativeOffset == -1: if nmrResidue not in self.interShifts: queryShifts = [] else: queryShifts = [shift for shift in self.interShifts[nmrResidue] if (shift and not shift.isDeleted) and shift.nmrAtom and (shift.nmrAtom.name in self.nmrAtomsToMatch)] matchShifts = self.intraShifts # If NmrResidue is not an offset NmrResidue, set queryShifts as value from self.intraShifts dictionary # Set matchShifts as self.interShifts elif nmrResidue.relativeOffset == 0 or nmrResidue.relativeOffset is None: if nmrResidue not in self.intraShifts: queryShifts = [] else: queryShifts = [shift for shift in self.intraShifts[nmrResidue] if (shift and not shift.isDeleted) and shift.nmrAtom.name in self.nmrAtomsToMatch] matchShifts = self.interShifts # If NmrResidue has offset other than -1 or 0/None, tell user that we are not able to match else: getLogger().warning( "Assignment matching not supported for NmrResidue offset %s. Matching display skipped" % nmrResidue.relativeOffset ) return assignMatrix = getNmrResidueMatches(queryShifts, matchShifts, 'averageQScore') # some old code which will match the query shifts against ALL shifts # queryShifts = [shift for shift in self.allShifts[nmrResidue] # if shift.nmrAtom.isotopeCode == '13C'] # assignMatrix = getNmrResidueMatches(queryShifts, self.allShifts, 'averageQScore') if not assignMatrix.values(): getLogger().info('No matches found for NmrResidue: %s - no matching isotopeCodes?' % nmrResidue.pid) return self._createMatchStrips(assignMatrix)
def _processDroppedNmrResidrueLabel(self, data, toLabel=None, plusChain=None): if toLabel and toLabel.obj: self._processDroppedNmrResidue(data, toLabel.obj, plusChain) def _processDroppedNmrResidue(self, data, nmrResidue, plusChain=None): """Process the dropped NmrResidue id""" droppedNmrResidue = None if DropBase.TEXT in data and len(data[DropBase.TEXT]) > 0: droppedNmrResidue = self.application.project.getByPid(data[DropBase.TEXT]) if droppedNmrResidue is None: showWarning(str(self.windowTitle()), 'Backbone assignment: invalid dropped item') getLogger().warning('Backbone assignment: invalid "pid" of dropped item') return if not isinstance(droppedNmrResidue, NmrResidue): showWarning(str(self.windowTitle()), 'Backbone assignment: item is not an nmrResidue') getLogger().warning('Backbone assignment: item is not an nmrResidue') return getLogger().debug('nmrResidue:%s, droppedNmrResidue:%s', nmrResidue, droppedNmrResidue) if droppedNmrResidue == nmrResidue: return allNmrResidues = nmrResidue._getAllConnectedList() isPlus = data[STRIPLABEL_CONNECTDIR] if STRIPLABEL_CONNECTDIR in data else STRIPLABEL_CONNECTNONE if isPlus == STRIPCONNECT_RIGHT: data['shiftLeftMouse'] = False elif isPlus == STRIPCONNECT_LEFT: data['shiftLeftMouse'] = True else: data['shiftLeftMouse'] = None index = allNmrResidues.index(nmrResidue) lenNmr = len(allNmrResidues) - 1 if data['shiftLeftMouse'] and not plusChain and index == 0: # okay to connect to left okay = True elif not data['shiftLeftMouse'] and plusChain and index == lenNmr: # okay to connect to right okay = True elif data['shiftLeftMouse'] and plusChain and index == lenNmr: # check connecting i-1 nmrResidue to the right yesNo = showYesNo(str(self.windowTitle()), "Trying to connect 'i-1' nmrResidue to end of chain.\n\n" "Do you want to continue?") getLogger().warning("Trying to connect 'i-1' nmrResidue to end of chain") if not yesNo: return # force the connection to the start of chain data['shiftLeftMouse'] = False okay = True elif not data['shiftLeftMouse'] and not plusChain and index == 0: # check connecting i+1 nmrResidue to the left yesNo = showYesNo(str(self.windowTitle()), "Trying to connect 'i+1' nmrResidue to start of chain.\n\n" "Do you want to continue?") getLogger().warning("Trying to connect 'i+1' nmrResidue to start of chain") if not yesNo: return # force the connection to the end of chain data['shiftLeftMouse'] = True okay = True else: # connecting to the middle of a stretch - may do disconnect later showWarning(str(self.windowTitle()), "Illegal connection, cannot connect to the middle of a chain") getLogger().warning("Illegal connection, cannot connect to the middle of a chain") return # silence the update of the nmrResidueTable as we will to an explicit update later # put in try/finally block because otherwise if exception thrown in the following code # (which can happen) then you no longer get updates of the NmrResidue table if data['shiftLeftMouse']: progressText = "connecting %s > %s" % (droppedNmrResidue.pid, nmrResidue.pid) else: progressText = "connecting %s < %s" % (nmrResidue.pid, droppedNmrResidue.pid) with undoBlockWithoutSideBar(): with progressManager(self.mainWindow, progressText): try: matchNmrResidue = None try: # display popup warning if data['shiftLeftMouse']: # leftShift drag; connect to previous if not nmrResidue.residue and not droppedNmrResidue.residue: nmrResidue.connectPrevious(droppedNmrResidue) # SPECIAL CASES elif nmrResidue.residue and not droppedNmrResidue.residue: # connected an unassigned nmrChain to the current assigned chain if droppedNmrResidue.nmrChain.id == '@-': # assume that it is the only one droppedNmrResidue.nmrChain.assignSingleResidue(droppedNmrResidue, nmrResidue.residue.previousResidue) else: nRes = nmrResidue.residue for ii in range(len(droppedNmrResidue.nmrChain.mainNmrResidues)): nRes = nRes.previousResidue droppedNmrResidue.nmrChain.assignConnectedResidues(nRes) elif not nmrResidue.residue and droppedNmrResidue.residue: # connected an assigned chain to the current unassigned nmrChain if nmrResidue.nmrChain.id == '@-': # assume that it is the only one nmrResidue.nmrChain.assignSingleResidue(nmrResidue, droppedNmrResidue.residue.nextResidue) else: nmrResidue.nmrChain.assignConnectedResidues(droppedNmrResidue.residue.nextResidue) matchNmrResidue = droppedNmrResidue.getOffsetNmrResidue(offset=-1) if matchNmrResidue is None: # Non -1 residue - stay with current getLogger().info("NmrResidue %s has no i-1 residue to display" % droppedNmrResidue) matchNmrResidue = nmrResidue else: if not nmrResidue.residue and not droppedNmrResidue.residue: nmrResidue.connectNext(droppedNmrResidue) # SPECIAL CASES elif nmrResidue.residue and not droppedNmrResidue.residue: # connected an unassigned nmrChain to the current assigned chain if droppedNmrResidue.nmrChain.id == '@-': # assume that it is the only one droppedNmrResidue.nmrChain.assignSingleResidue(droppedNmrResidue, nmrResidue.residue.nextResidue) else: droppedNmrResidue.nmrChain.assignConnectedResidues(nmrResidue.residue.nextResidue) elif not nmrResidue.residue and droppedNmrResidue.residue: # connected an assigned chain to the current unassigned nmrChain if nmrResidue.nmrChain.id == '@-': # assume that it is the only one nmrResidue.nmrChain.assignSingleResidue(nmrResidue, droppedNmrResidue.residue.previousResidue) else: dropRes = droppedNmrResidue.residue for ii in range(len(nmrResidue.nmrChain.mainNmrResidues)): dropRes = dropRes.previousResidue nmrResidue.nmrChain.assignConnectedResidues(dropRes) matchNmrResidue = droppedNmrResidue except Exception as es: showWarning('Connect NmrResidue', str(es)) finally: if matchNmrResidue: self.navigateToNmrResidue(matchNmrResidue) # # update the NmrResidueTable # getLogger().info('>>>DISPLAYTABLE', droppedNmrResidue.nmrChain, self.project.nmrChains) # self.nmrResidueTable.displayTableForNmrChain(droppedNmrResidue.nmrChain) # from ccpn.ui.gui.lib.OpenGL.CcpnOpenGL import GLNotifier # # GLSignals = GLNotifier(parent=self) # GLSignals.emitEvent(triggers=[GLNotifier.GLMARKS]) except Exception as es: getLogger().warning(str(es)) # raise es finally: pass # update the NmrResidueTable - outside of the undoBlock for notifiers to catch up self.nmrResidueTable.displayTableForNmrChain(droppedNmrResidue.nmrChain) from ccpn.ui.gui.lib.OpenGL.CcpnOpenGL import GLNotifier GLSignals = GLNotifier(parent=self) GLSignals.emitEvent(triggers=[GLNotifier.GLMARKS]) # def _centreStripForNmrResidue(self, nmrResidue, strip): # """ # Centre y-axis of strip based on chemical shifts of from NmrResidue.nmrAtoms # """ # if not nmrResidue: # getLogger().warning('No NmrResidue specified') # return # # if not strip: # getLogger().warning('No Strip specified') # return # # yShifts = matchAxesAndNmrAtoms(strip, nmrResidue.nmrAtoms)[strip.axisOrder[1]] # yShiftValues = [x.value for x in yShifts] # if yShiftValues: # yPosition = (max(yShiftValues) + min(yShiftValues)) / 2 # yWidth = max(yShiftValues) - min(yShiftValues) + 10 # strip.orderedAxes[1].position = yPosition # if strip._CcpnGLWidget.aspectRatioMode == 0: # strip.orderedAxes[1].width = yWidth # # try: # axisCode = strip.axisCodes[1] # if strip._CcpnGLWidget.aspectRatioMode == 0: # strip._CcpnGLWidget.setAxisPosition(axisCode=axisCode, position=yPosition, update=False) # strip._CcpnGLWidget.setAxisWidth(axisCode=axisCode, width=yWidth, update=False) # strip._CcpnGLWidget._rescaleAllAxis() # else: # strip._CcpnGLWidget.setAxisPosition(axisCode=axisCode, position=yPosition, update=True) # # except Exception as es: # getLogger().debugGL('OpenGL widget not instantiated') def _centreCcpnStripsForNmrResidue(self, nmrResidue, strips): """ Centre y-axis of strip based on chemical shifts of from NmrResidue.nmrAtoms """ if not nmrResidue: getLogger().warning('No NmrResidue specified') return if not strips: getLogger().warning('No Strip specified') return yShifts = matchAxesAndNmrAtoms(strips[0], nmrResidue.nmrAtoms)[strips[0].axisOrder[1]] yShiftValues = [x.value for x in yShifts] if yShiftValues: _minPpmWidths = {'H': 0.5, 'C': 8.0, 'N': 2.0} # based on standard ratios yPosition = (max(yShiftValues) + min(yShiftValues)) / 2 yWidth = max(yShiftValues) - min(yShiftValues) axisCode = strips[0].axisCodes[1] yHeight = strips[0]._CcpnGLWidget.mainViewHeight() or 1 minPpm = 1.0 if axisCode[0] not in _minPpmWidths else _minPpmWidths[axisCode[0]] # add increase of EXTRAWIDTH pixels dY = max(yWidth, minPpm) / max(1, (yHeight - EXTRAWIDTH)) # add offset for the top, and extra height yPos = yPosition - (EXTRAOFFSET - (EXTRAWIDTH / 2)) * dY yW = max(yWidth, minPpm) + EXTRAWIDTH * dY # this should rescale all in spectrumDisplay strips[0].setAxisPosition(axisIndex=1, position=yPos, rescale=False, update=False) strips[0].setAxisWidth(axisIndex=1, width=yW, rescale=True, update=True) def _setupShiftDicts(self, *args): """ Creates three ordered dictionaries containing a) intra-residue, b) -1 offset (inter) and c) all shifts for all NmrResidues in the project. """ self.intraShifts = OrderedDict() self.interShifts = OrderedDict() self.allShifts = OrderedDict() chemicalShiftList = self.application.project.getByPid(self.shiftListWidget.pulldownList.currentText()) if chemicalShiftList: for nmrResidue in self.application.project.nmrResidues: nmrAtoms = [nmrAtom for nmrAtom in nmrResidue.nmrAtoms] shifts = [chemicalShiftList.getChemicalShift(atom) for atom in nmrAtoms if chemicalShiftList.getChemicalShift(atom) is not None] if nmrResidue.relativeOffset == -1: self.interShifts[nmrResidue] = shifts elif nmrResidue.relativeOffset == 0 or nmrResidue.relativeOffset is None: self.intraShifts[nmrResidue] = shifts self.allShifts[nmrResidue] = shifts def _setNmrAtomsToMatch(self): self.nmrAtomsToMatch = [] if self.matchCA.isChecked(): self.nmrAtomsToMatch.append('CA') if self.matchCB.isChecked(): self.nmrAtomsToMatch.append('CB') if self.matchC.isChecked(): self.nmrAtomsToMatch.append('C') def _createMatchStrips(self, assignMatrix: typing.Tuple[typing.Dict[NmrResidue, typing.List[_OldChemicalShift]], typing.List[float]]): """ Creates strips in match module corresponding to the best assignment possibilities in the assignMatrix. """ if not assignMatrix: getLogger().warning('No assignment matrix specified') return # Assignment score has format {score: nmrResidue} where score is a float # assignMatrix[0] is a dict {score: nmrResidue} assignMatrix[1] is a concurrent list of scores # numberOfMatches = int(self.numberOfMatchesWidget.getText()) assignmentScores = sorted(list(assignMatrix.keys()))[:MAXMATCHES] nmrAtomPairs = [] scoreAssignment = [] scoreLabelling = [] matchDirection = 0 for assignmentScore in assignmentScores: matchResidue = assignMatrix[assignmentScore] if matchResidue.sequenceCode.endswith('-1'): iNmrResidue = matchResidue.mainNmrResidue # this is where the nmrResidue can be dropped in the existing nmrChain scoreLabelling.append('[ i+1 ]') matchDirection = 1 else: iNmrResidue = matchResidue scoreLabelling.append('[ i-1 ]') matchDirection = -1 scoreAssignment.append('[ %i' % int(100 - min(1000 * assignmentScore, 100)) + '% ]') nmrAtomPairs.append((iNmrResidue.fetchNmrAtom(name='N', isotopeCode='N'), iNmrResidue.fetchNmrAtom(name='H', isotopeCode='H'))) if matchDirection == 1: numberOfMatches = int(self.numberOfPlusMatchesWidget.getText()) else: numberOfMatches = int(self.numberOfMinusMatchesWidget.getText()) nmrAtomPairs = nmrAtomPairs[:numberOfMatches] scoreAssignment = scoreAssignment[:numberOfMatches] scoreLabelling = scoreLabelling[:numberOfMatches] for module in self._getMatchDisplays(): # skip of the module if not defined - possibly in the case that spectrumDisplays have been closed if not module: continue makeStripPlot(module, nmrAtomPairs) for ii, strip in enumerate(module.strips): nmrResiduePid = nmrAtomPairs[ii][0].nmrResidue.pid # strip.setStripLabelText(nmrResiduePid) # strip.showStripLabel() # strip.setStripLabelisPlus(True if scoreLabelling[ii].startswith('i+1') else False) # strip.setStripResidueIdText(scoreLabelling[ii]) # strip.showStripResidueId() # strip.setStripResidueDirText(scoreAssignment[ii]) # strip.showStripResidueDir() strip.header.reset() strip.header.setLabelText(position='l', text=scoreLabelling[ii]) strip.header.setLabelText(position='c', text=nmrResiduePid) # TODO:ED need to improve this # strip.header.setLabelConnectDir(position='c', connectDir=STRIPCONNECT_LEFT if scoreLabelling[ii].startswith('i-1') else STRIPCONNECT_RIGHT) strip.header.setLabelConnectDir(position='c', connectDir=STRIPCONNECT_LEFT if 'i-1' in scoreLabelling[ii] else STRIPCONNECT_RIGHT) strip.header.setLabelText(position='r', text=scoreAssignment[ii]) # disable dropping onto these labels strip.header.setLabelObject(position='l', obj=None) strip.header.setLabelObject(position='c', obj=None) strip.header.setLabelObject(position='r', obj=None) strip.header.handle = STRIPBACKBONE strip.header.headerVisible = True # self._centreStripForNmrResidue(assignMatrix[assignmentScores[0]], module.strips[0]) self._centreCcpnStripsForNmrResidue(assignMatrix[assignmentScores[0]], module.strips) module.setColumnStretches(stretchValue=True) # this forces a refresh/rescale of all strips in the spectrumDisplay module.strips[0]._CcpnGLWidget.emitYAxisChanged(allStrips=True) def _closeModule(self): """ Re-implementation of the closeModule method of the CcpnModule class required """ # TODO: use proper subclassing for display in self._getDisplays() + self._getMatchDisplays(): if display: display.hideAllStripHeaders(handle=STRIPBACKBONE) for notifier in self._stripNotifiers: if notifier: notifier.unRegister() self._stripNotifiers = [] super()._closeModule()
[docs]def nmrAtomsFromResidue(nmrResidue): """ Retrieve a list of nmrAtoms from nmrResidue """ # nmrResidue = nmrResidue.mainNmrResidue nmrResidues = [] previousNmrResidue = nmrResidue.previousNmrResidue if previousNmrResidue: nmrResidues.append(previousNmrResidue) nmrResidues.append(nmrResidue) nextNmrResidue = nmrResidue.nextNmrResidue if nextNmrResidue: nmrResidues.append(nextNmrResidue) nmrAtoms = [] for nr in nmrResidues: nmrAtoms.extend(nr.nmrAtoms) return nmrAtoms
[docs]def nmrAtomsFromOffsets(nmrResidue): """ Retrieve a list of nmrAtoms from nmrResidue """ # nmrResidue = nmrResidue.mainNmrResidue nmrResidues = [nmrResidue] if nmrResidue.offsetNmrResidues: nmrResidues.extend(nmrResidue.offsetNmrResidues) nmrAtoms = [] for nr in nmrResidues: nmrAtoms.extend(nr.nmrAtoms) return nmrAtoms
[docs]def markNmrAtoms(mainWindow, nmrAtoms: typing.List[NmrAtom]): # get the display # displays = self._getDisplays() # application = mainWindow.application # project = mainWindow.application.project # current = mainWindow.application.current displays = [dp for dp in mainWindow.spectrumDisplays] if len(displays) == 0: getLogger().warning('No Spectrum Displays') showWarning('markNmrAtoms', 'No spectrum Displays') return # mainWindow.clearMarks() # clear the marks for the minute for display in displays: strips = display.strips if strips: strip = strips[0] # for strip in strips: # assume that this returns list of nmrAtoms in the display shiftDict = matchAxesAndNmrAtoms(strip, nmrAtoms) # atomPositions = shiftDict[strip.axisOrder[2]] # atomPositions = [[x.value for x in shiftDict[axisCode]] for axisCode in strip.axisOrder] # positions = [] # for atomPos in atomPositions: # if atomPos: # if len(atomPos) < 2: # positions.append(atomPos[0]) # else: # positions.append(max(atomPos) - min(atomPos) / 2) # else: # positions.append('') # navigateToPositionInStrip(strip, positions, widths=widths) # don't need to change display yet mainWindow.markPositions(list(shiftDict.keys()), list(shiftDict.values()))
#===== Just some code to 'save' ===== # def hasNmrResidue(nmrChain, residueCode): # "Simple function to check if sequenCode is found within the nmrResidues of nmrChain" # resCodes = [res.sequenceCode for res in nmrChain.nmrResidues] # return (residueCode in resCodes) # # # def endOfchain(nmrResidue): # # changes to end of connected chain; not a good idea # if nmrResidue.nmrChain.isConnected: # if nmrResidue.sequenceCode.endswith('-1'): # nmrResidue = nmrResidue.nmrChain.mainNmrResidues[0].getOffsetNmrResidue(-1) # else: # nmrResidue = nmrResidue.nmrChain.mainNmrResidues[-1] # return nmrResidue # # # def getPids(fromObject, attributeName): # "Get a list of pids fromObject.attributeName or None on error" # if not hasattr(fromObject, attributeName): return None # return [obj.pid for obj in getattr(fromObject, attributeName)] # # #===== end code save ===== if __name__ == '__main__': from ccpn.ui.gui.widgets.Application import TestApplication app = TestApplication() popup = BackboneAssignmentModule() popup.show() popup.raise_() app.start()