Source code for ccpn.ui.gui.modules.MultipletListTable
"""Module Documentation here
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2021"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
"Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See http://www.ccpn.ac.uk/v3-software/downloads/license")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
"CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
"J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Geerten Vuister $"
__dateModified__ = "$dateModified: 2021-12-23 15:18:25 +0000 (Thu, December 23, 2021) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: CCPN $"
__date__ = "$Date: 2017-04-07 10:28:41 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================
from PyQt5 import QtGui, QtWidgets, QtCore
from ccpn.core.lib.Notifiers import Notifier
from ccpn.ui.gui.modules.CcpnModule import CcpnModule
from ccpn.ui.gui.widgets.Label import Label
from ccpn.ui.gui.widgets.PulldownList import PulldownList
from ccpn.ui.gui.widgets.PulldownListsForObjects import MultipletListPulldown
# from ccpn.ui.gui.widgets.Table import ObjectTable, Column, ColumnViewSettings, ObjectTableFilter
from ccpn.ui.gui.widgets.GuiTable import GuiTable
from ccpn.ui.gui.widgets.Column import ColumnClass, Column
from ccpn.ui.gui.widgets.Widget import Widget
from ccpn.core.MultipletList import MultipletList
from ccpn.core.Multiplet import Multiplet
from ccpn.core.Multiplet import Multiplet
from ccpn.core.NmrAtom import NmrAtom
from ccpn.util.Logging import getLogger
from ccpn.ui.gui.modules.PeakTable import PeakListTableWidget
from ccpn.ui.gui.modules.MultipletPeakTable import MultipletPeakListTableWidget
from ccpn.ui.gui.widgets.DropBase import DropBase
from ccpn.ui.gui.widgets.Frame import Frame
from ccpn.ui.gui.widgets.Font import getFontHeight
from ccpn.ui.gui.lib.GuiNotifier import GuiNotifier
from ccpn.core.lib.peakUtils import getPeakPosition, getPeakAnnotation, getPeakLinewidth, getMultipletPosition
from ccpn.ui.gui.widgets.Splitter import Splitter
from ccpn.ui.gui.widgets.ScrollArea import ScrollArea
from ccpn.ui.gui.widgets.Spacer import Spacer
from ccpn.util.OrderedSet import OrderedSet
logger = getLogger()
MultipletPosUnits = ['ppm', 'Hz']
[docs]class MultipletTableModule(CcpnModule):
"""This class implements the module by wrapping a MultipletListTable instance
"""
includeSettingsWidget = False
maxSettingsState = 2
settingsPosition = 'top'
className = 'MultipletTableModule'
def __init__(self, mainWindow=None, name='Multiplet Table',
multipletList=None, selectFirstItem=False):
super().__init__(mainWindow=mainWindow, name=name)
# Derive application, project, and current from mainWindow
self.mainWindow = mainWindow
self.application = mainWindow.application
self.project = mainWindow.project
self.current = mainWindow.application.current
self.splitter = Splitter(horizontal=True, collapsible=False)
# mainWidget
self.peaksFrame = Frame(self.mainWidget, setLayout=True, grid=(0, 1))
self.peakListTableLabel = Label(self.peaksFrame, 'Peaks:', grid=(0, 0), )
self.peakListTableLabel.setFixedHeight(getFontHeight())
self.peakListTable = MultipletPeakListTableWidget(parent=self.peaksFrame,
mainWindow=self.mainWindow,
moduleParent=self.peaksFrame, # just to give a unique id
setLayout=False,
multiSelect=True,
grid=(1, 0))
self.peakListTable._widgetScrollArea.hide()
self.multipletListTable = MultipletListTableWidget(parent=self.mainWidget, mainWindow=self.mainWindow,
moduleParent=self, setLayout=True, multiSelect=False,
grid=(0, 0))
if multipletList is not None:
self.selectMultipletList(multipletList)
elif selectFirstItem:
self.multipletListTable.mLwidget.selectFirstItem()
self.installMaximiseEventHandler(self._maximise, self._closeModule)
self.splitter.addWidget(self.multipletListTable)
self.splitter.addWidget(self.peaksFrame)
# it is beyond explanation how stretchFactor works :)
self.splitter.setStretchFactor(1, 1)
self.splitter.setStretchFactor(0, 1)
self.mainWidget.getLayout().addWidget(self.splitter)
def _maximise(self):
"""
Maximise the attached table
"""
self.multipletListTable._maximise()
[docs] def selectMultipletList(self, multipletList=None):
"""
Manually select a multipletList from the pullDown
"""
self.multipletListTable._selectMultipletList(multipletList)
def _closeModule(self):
"""Re-implementation of closeModule function from CcpnModule to unregister notification """
self.multipletListTable._close()
self.peakListTable._close()
super()._closeModule()
[docs]class MultipletListTableWidget(GuiTable):
"""
Class to present a multipletList Table
"""
className = 'MultipletListTable'
attributeName = 'multipletLists'
positionsUnit = MultipletPosUnits[0] #default
@staticmethod
def _setFigureOfMerit(obj, value):
"""
CCPN-INTERNAL: Set figureOfMerit from table
Must be a floatRatio in range [0.0, 1.0]
"""
# ejb - why is it blanking a notification here?
# NmrResidueTable._project.blankNotification()
# clip and set the figure of merit
obj.figureOfMerit = min(max(float(value), 0.0), 1.0) if value else None
def __init__(self, parent=None, mainWindow=None, moduleParent=None, multipletList=None, multiSelect=True,
actionCallback=None, selectionCallback=None, **kwds):
"""
Initialise the widgets for the module. kwds passed to the scrollArea widget
"""
# 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
else:
self.application = None
self.project = None
self.current = None
if moduleParent:
self.peakListTable = moduleParent.peakListTable
MultipletListTableWidget.project = self.project
self.settingWidgets = None
self._selectedMultipletList = None
kwds['setLayout'] = True ## Assure we have a layout with the widget
# Initialise the scroll widget and common settings
self._initTableCommonWidgets(parent, **kwds)
row = 0
self.spacer = Spacer(self._widget, 5, 5,
QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed,
grid=(row, 0), gridSpan=(1, 1))
row += 1
gridHPos = 0
self.mLwidget = MultipletListPulldown(parent=self._widget,
mainWindow=self.mainWindow,
grid=(row, gridHPos), gridSpan=(1, 1),
showSelectName=True,
minimumWidths=(0, 100),
sizeAdjustPolicy=QtWidgets.QComboBox.AdjustToContents,
callback=self._pulldownPLcallback)
## create widgets for selection of position units
gridHPos += 1
self.posUnitPulldownLabel = Label(parent=self._widget, text=' Position Unit', grid=(row, gridHPos))
gridHPos += 1
self.posUnitPulldown = PulldownList(parent=self._widget, texts=MultipletPosUnits, callback=self._pulldownUnitsCallback, grid=(row, gridHPos))
row += 1
self.spacer = Spacer(self._widget, 5, 5,
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Fixed,
grid=(row, gridHPos + 1), gridSpan=(1, 1))
self._widget.getLayout().setColumnStretch(gridHPos + 1, 2)
self._hiddenColumns = ['Pid', 'Spectrum', 'MultipletList', 'Id']
self.dataFrameObject = None
selectionCallback = self._selectionCallback if selectionCallback is None else selectionCallback
actionCallback = self._actionCallback if actionCallback is None else actionCallback
super().__init__(parent=parent,
mainWindow=self.mainWindow,
dataFrameObject=None,
setLayout=True,
autoResize=True, multiSelect=True,
actionCallback=actionCallback,
selectionCallback=selectionCallback,
grid=(3, 0), gridSpan=(1, 6))
self.moduleParent = moduleParent
# self.tableMenu.addAction('Copy Multiplets...', self._copyMultiplets)
self.tableMenu.insertSeparator(self.tableMenu.actions()[0])
a = self.tableMenu.addAction('Edit Multiplet...', self._editMultiplets)
self.tableMenu.insertAction(self.tableMenu.actions()[0], a)
## populate the table if there are multipletlists in the project
if multipletList is not None:
self._selectMultipletList(multipletList)
self.setTableNotifiers(tableClass=MultipletList,
rowClass=Multiplet,
cellClassNames=None,
tableName='multipletList', rowName='multiplet',
changeFunc=self._updateAllModule,
className=self.attributeName,
updateFunc=self._updateAllModule,
tableSelection='_selectedMultipletList',
pullDownWidget=self.mLwidget,
callBackClass=Multiplet,
selectCurrentCallBack=self._selectOnTableCurrentMultipletsNotifierCallback,
moduleParent=moduleParent)
# Initialise the notifier for processing dropped items
self._postInitTableCommonWidgets()
def _processDroppedItems(self, data):
"""
CallBack for Drop events
"""
pids = data.get('pids', [])
self._handleDroppedItems(pids, MultipletList, self.mLwidget)
def _getTableColumns(self, multipletList):
"""Add default columns plus the ones according with multipletList.spectrum dimension
format of column = ( Header Name, value, tipText, editOption)
editOption allows the user to modify the value content by doubleclick
"""
columnDefs = []
# Serial column
columnDefs.append(('#', 'serial', 'Multiplet serial number', None, None))
columnDefs.append(('Pid', lambda ml: ml.pid, 'Pid of the Multiplet', None, None))
columnDefs.append(('_object', lambda ml: ml, 'Object', None, None))
columnDefs.append(('Spectrum', lambda multiplet: multiplet.multipletList.spectrum.id, 'Spectrum containing the Multiplet', None, None))
columnDefs.append(('MultipletList', lambda multiplet: multiplet.multipletList.serial, 'MultipletList containing the Multiplet', None, None))
columnDefs.append(('Id', lambda multiplet: multiplet.serial, 'Multiplet serial', None, None))
# # Assignment column
# for i in range(multipletList.spectrum.dimensionCount):
# assignTipText = 'NmrAtom assignments of multiplet in dimension %s' % str(i + 1)
# columnDefs.append(
# ('Assign F%s' % str(i + 1), lambda ml, dim=i: getPeakAnnotation(ml, dim), assignTipText, None, None))
# Multiplet positions column
for i in range(multipletList.spectrum.dimensionCount):
positionTipText = 'Multiplet position in dimension %s' % str(i + 1)
columnDefs.append(('Pos F%s' % str(i + 1),
lambda ml, dim=i, unit=MultipletListTableWidget.positionsUnit: getMultipletPosition(ml, dim, unit),
positionTipText, None, '%0.3f'))
# linewidth column
for i in range(multipletList.spectrum.dimensionCount):
linewidthTipTexts = 'Multiplet line width %s' % str(i + 1)
columnDefs.append(
('LW F%s' % str(i + 1), lambda ml, dim=i: getPeakLinewidth(ml, dim), linewidthTipTexts, None, '%0.3f'))
# height column
heightTipText = 'Magnitude of spectrum intensity at multiplet center (interpolated), unless user edited'
columnDefs.append(('Height', lambda ml: ml.height, heightTipText, None, None))
# volume column
volumeTipText = 'Integral of spectrum intensity around multiplet location, according to chosen volume method'
columnDefs.append(('Volume', lambda ml: ml.volume, volumeTipText, None, None))
# numPeaks column
numPeaksTipText = 'Peaks count'
columnDefs.append(('Peaks count', lambda ml: ml.numPeaks, numPeaksTipText, None, None))
# figureOfMerit column
figureOfMeritTipText = 'Figure of merit'
columnDefs.append(('Merit', lambda ml: ml.figureOfMerit, figureOfMeritTipText,
lambda ml, value: self._setFigureOfMerit(ml, value), None))
# comment column
commentsTipText = 'Textual notes about the multiplet'
columnDefs.append(('Comment', lambda ml: self._getCommentText(ml), commentsTipText,
lambda ml, value: self._setComment(ml, value), None))
return ColumnClass(columnDefs)
################## Updates ##################
def _maximise(self):
"""
refresh the table on a maximise event
"""
self._updateTable()
def _updateAllModule(self, data=None):
"""Updates the table and the settings widgets"""
# self.peakListTable.clear()
self._updateTable()
def _updateTable(self, ):
"""Display the multiplets on the table for the selected MultipletList.
Obviously, If the multiplet has not been previously deleted and flagged isDeleted"""
# self.setObjectsAndColumns(objects=[], columns=[]) #clear current table first
self._selectedMultipletList = self.project.getByPid(self.mLwidget.getText())
if self._selectedMultipletList:
self.populateTable(rowObjects=self._selectedMultipletList.multiplets,
columnDefs=self._getTableColumns(self._selectedMultipletList),
selectedObjects=self.current.multiplets
)
self._updateMultipletPeaksOnTable()
# self.project.blankNotification()
# self._dataFrameObject = self.getDataFrameFromList(table=self,
# buildList=self._selectedMultipletList.multiplets,
# colDefs=self._getTableColumns(self._selectedMultipletList),
# hiddenColumns=self._hiddenColumns)
#
# # populate from the Pandas dataFrame inside the dataFrameObject
# self.setTableFromDataFrameObject(dataFrameObject=self._dataFrameObject)
# self._highLightObjs(self.current.multiplets)
# multiplet = self.current.multiplet
# #
# self._updateMultipletPeaksOnTable()
# self.project.unblankNotification()
else:
self.clear()
self.peakListTable.clear()
self.peakListTable._selectedMultipletPeakList = None
def _selectMultipletList(self, multipletList=None):
"""
Manually select a MultipletList from the pullDown
"""
if multipletList is None:
# logger.warning('select: No MultipletList selected')
# raise ValueError('select: No MultipletList selected')
self.mLwidget.selectFirstItem()
else:
if not isinstance(multipletList, MultipletList):
logger.warning('select: Object is not of type MultipletList')
raise TypeError('select: Object is not of type MultipletList')
else:
for widgetObj in self.mLwidget.textList:
if multipletList.pid == widgetObj:
self._selectedMultipletList = multipletList
self.mLwidget.select(self._selectedMultipletList.pid)
################## Widgets callbacks ##################
def _getPullDownSelection(self):
return self.mLwidget.getText()
def _selectPullDown(self, value):
self.mLwidget.select(value)
self._updateTable()
# def displayTableForMultipletList(self, multipletList):
# """
# Display the table for all NmrResidue's of nmrChain
# """
# self.mLwidget.select(multipletList.pid)
# self._updateTable(multiplets=multipletList.multiplets)
def _actionCallback(self, data, *args):
""" If current strip contains the double clicked multiplet will navigateToPositionInStrip """
from ccpn.core.PeakList import PeakList
from ccpn.ui.gui.lib.StripLib import navigateToPositionInStrip, _getCurrentZoomRatio
# TODO hack until we have multiplet views
multiplet = self.current.multiplet
if multiplet:
if len(multiplet.peaks) > 0:
peak = multiplet.peaks[-1]
if self.current.strip is not None:
validPeakListViews = [pp.peakList for pp in self.current.strip.peakListViews if
isinstance(pp.peakList, PeakList)]
if peak.peakList in validPeakListViews:
widths = None
if peak.peakList.spectrum.dimensionCount <= 2:
widths = _getCurrentZoomRatio(self.current.strip.viewRange())
navigateToPositionInStrip(strip=self.current.strip, positions=multiplet.position, widths=widths)
else:
logger.warning('Impossible to navigate to peak position. No peaks in multiplet')
else:
logger.warning('Impossible to navigate to peak position. Set a current strip first')
def _selectionCallback(self, data, *args):
"""
set as current the selected multiplets on the table
"""
multiplets = data[Notifier.OBJECT]
if multiplets is None:
self.current.clearMultiplets()
self.peakListTable.clear()
self.peakListTable._selectedMultipletPeakList = None
self.highlightObjects(None)
else:
self.current.multiplets = multiplets
# show only the current multiplet peaks
self._populateMultipletPeaksOnTable()
self._updateMultipletPeaksOnTable()
def _updateMultipletPeaksOnTable(self):
if self.current.multiplets:
peaks = OrderedSet()
[peaks.add(peak) for mt in self.current.multiplets for peak in mt.peaks]
peaks = tuple(peaks)
if len(peaks) > 0:
peakList = peaks[0].peakList # needed to create the columns in the peak table
if peakList:
self.peakListTable._selectedMultipletPeakList = self.current.multiplets
self.peakListTable._updateTable(useSelectedPeakList=False, peaks=peaks, peakList=peakList)
def _populateMultipletPeaksOnTable(self):
"""populates a dedicate peak table containing peaks of the current multiplet """
peaks = OrderedSet()
[peaks.add(peak) for mt in self.current.multiplets for peak in mt.peaks]
peaks = tuple(peaks)
if peaks:
# peakList may not exist for deleted objects
peakList = peaks[0].peakList
if peakList:
self.peakListTable.populateTable(rowObjects=peaks,
columnDefs=self.peakListTable._getTableColumns(peakList))
else:
self.peakListTable.clear()
self.peakListTable._selectedMultipletPeakList = None
def _pulldownUnitsCallback(self, unit):
# update the table with new units
self._setPositionUnit(unit)
self._updateAllModule()
self.peakListTable._setPositionUnit(unit)
self._updateMultipletPeaksOnTable()
def _pulldownPLcallback(self, data):
self._updateAllModule()
# def _copyMultiplets(self):
# pass
def _editMultiplets(self):
from ccpn.ui.gui.popups.EditMultipletPopup import EditMultipletPopup
multiplets = self.current.multiplets
if len(multiplets) > 0:
multiplet = multiplets[-1]
popup = EditMultipletPopup(parent=self.mainWindow, mainWindow=self.mainWindow, multiplet=multiplet)
else:
popup = EditMultipletPopup(parent=self.mainWindow, mainWindow=self.mainWindow)
popup.exec()
popup.raise_()
################## Notifiers callbacks ##################
def _selectOnTableCurrentMultipletsNotifierCallback(self, data):
"""
Callback from a notifier to highlight the multiplets on the multiplet table
:param data:
"""
currentMultiplets = data['value']
self._selectOnTableCurrentMultiplets(currentMultiplets)
def _selectOnTableCurrentMultiplets(self, currentMultiplets):
"""
Highlight the list of multiplets on the table
:param currentMultiplets:
"""
self.highlightObjects(currentMultiplets)
if len(currentMultiplets) > 0:
# self._highLightObjs(currentMultiplets)
self._populateMultipletPeaksOnTable()
else:
# self.clearSelection()
self.peakListTable.clear()
self.peakListTable._selectedMultipletPeakList = None
def _setPositionUnit(self, value):
if value in MultipletPosUnits:
MultipletListTableWidget.positionsUnit = value