"""
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-03-08 17:58:39 +0000 (Tue, March 08, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: Ed Brooksbank $"
__date__ = "$Date: 2022-02-25 16:03:34 +0100 (Fri, February 25, 2022) $"
#=========================================================================================
# Start of code
#=========================================================================================
import pandas as pd
from PyQt5 import QtWidgets
from ccpn.core.ViolationTable import ViolationTable
from ccpn.core.lib import Pid
from ccpn.core.lib.CcpnSorting import universalSortKey
from ccpn.ui.gui.widgets.Label import Label
from ccpn.ui.gui.widgets.ButtonList import ButtonList
from ccpn.ui.gui.widgets.Frame import ScrollableFrame
from ccpn.ui.gui.modules.PluginModule import PluginModule
from ccpn.ui.gui.widgets.CompoundWidgets import PulldownListCompoundWidget, EntryCompoundWidget
from ccpn.ui.gui.widgets.PulldownListsForObjects import RestraintTablePulldown, ViolationTablePulldown
from ccpn.ui.gui.widgets.MessageDialog import showWarning
from ccpn.ui.gui.widgets.HLine import LabeledHLine
from ccpn.ui.gui.widgets.TextEditor import TextEditor
from ccpn.ui.gui.guiSettings import getColours, DIVIDER
from ccpn.ui.gui.guiSettings import BORDERNOFOCUS_COLOUR
from ccpn.ui.gui.lib.Validators import LineEditValidatorCoreObject
from ccpn.framework.lib.Plugin import Plugin
from ccpn.util.AttrDict import AttrDict
LineEditsMinimumWidth = 195
DEFAULTSPACING = 3
DEFAULTMARGINS = (14, 14, 14, 14)
DEFAULT_RUNNAME = 'output'
# Set some tooltip texts
RUNBUTTON = 'Run'
_help = {RUNBUTTON: 'Run the plugin', }
_RESTRAINTTABLE = 'restraintTable'
_VIOLATIONTABLE = 'violationTable'
_VIOLATIONRESULT = 'violationResult'
_RUNNAME = 'runName'
[docs]class ViolationResultsGuiPlugin(PluginModule):
className = 'ViolationResults'
def __init__(self, mainWindow=None, plugin=None, application=None, **kwds):
super().__init__(mainWindow=mainWindow, plugin=plugin, application=application)
if mainWindow:
self.application = mainWindow.application
self.project = mainWindow.application.project
self.preferences = mainWindow.application.preferences
else:
self.application = None
self.project = None
self.preferences = None
# set up object to pass information to the main plugin
self.obj = AttrDict()
# set up the widgets
self._setWidgets()
self._populate()
self.plugin._loggerCallback = self._guiLogger
def _guiLogger(self, *args):
self._textEditor.append(*args)
def _setWidgets(self):
"""Set up the mainwidgets
"""
# set up a scroll area in the mainWidget
self._scrollFrame = ScrollableFrame(parent=self.mainWidget,
showBorder=False, setLayout=True,
acceptDrops=True, grid=(0, 0), gridSpan=(1, 1), spacing=(5, 5))
self._scrollAreaWidget = self._scrollFrame._scrollArea
self._scrollAreaWidget.setStyleSheet('ScrollArea { border-right: 1px solid %s;'
'border-bottom: 1px solid %s;'
'background: transparent; }' % (BORDERNOFOCUS_COLOUR, BORDERNOFOCUS_COLOUR))
self._scrollFrame.insertCornerWidget()
self._scrollFrame.setContentsMargins(*DEFAULTMARGINS)
self._scrollFrame.getLayout().setSpacing(DEFAULTSPACING)
# add contents to the scroll frame
parent = self._scrollFrame
row = 0
Label(parent, text='Create Restraint Analysis Data', bold=True, grid=(row, 0))
row += 1
self._rTable = RestraintTablePulldown(parent=parent,
mainWindow=self.mainWindow,
labelText='Restraint Table',
grid=(row, 0), gridSpan=(1, 2),
showSelectName=True,
minimumWidths=(250, 100),
sizeAdjustPolicy=QtWidgets.QComboBox.AdjustToContents,
callback=self._selectRTableCallback)
row += 1
self._vTable = PulldownListCompoundWidget(parent=parent,
mainWindow=self.mainWindow,
labelText="Source Violation Table",
grid=(row, 0), gridSpan=(1, 2),
minimumWidths=(250, 100),
sizeAdjustPolicy=QtWidgets.QComboBox.AdjustToContents,
callback=None)
row += 1
self._outputName = EntryCompoundWidget(parent=parent,
mainWindow=self.mainWindow,
labelText="Output Violation Table Name",
grid=(row, 0), gridSpan=(1, 2),
minimumWidths=(250, 100),
sizeAdjustPolicy=QtWidgets.QComboBox.AdjustToContents,
callback=None,
compoundKwds={'backgroundText': '> Enter name <'},
)
self._outputName.entry.returnPressed.connect(self.runGui)
row += 1
texts = [RUNBUTTON]
tipTexts = [_help[RUNBUTTON]]
callbacks = [self.runGui]
ButtonList(parent=parent, texts=texts, callbacks=callbacks, tipTexts=tipTexts,
grid=(row, 1), gridSpan=(1, 1), hAlign='r')
row += 1
LabeledHLine(parent, text='Output', grid=(row, 0), gridSpan=(1, 3), height=16, colour=getColours()[DIVIDER])
row += 1
self._textEditor = TextEditor(parent, grid=(row, 0), gridSpan=(1, 3), enabled=True, addWordWrap=True)
self._textEditor.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
row += 1
parent.getLayout().setColumnStretch(2, 1)
[docs] def runGui(self):
"""Run the plugin
"""
_rTable = self.project.getByPid(self._rTable.getText())
_vTable = self.project.getByPid(self._vTable.getText())
_runName = self._outputName.getText()
_title = 'Create Restraint Analysis Data'
if not (_rTable and _vTable):
showWarning(_title, 'Please select from the pulldowns')
elif not _runName:
showWarning(_title, 'Please select output violationTable name')
elif not self._outputName.entry.validator().isValid:
showWarning(_title, f'Output violationTable name contains bad characters, or name already exists.\n\n'
f'Check the existing violationTables in structureData {_rTable.structureData}')
else:
# create the data
self.obj[_RESTRAINTTABLE] = _rTable
self.obj[_VIOLATIONTABLE] = _vTable
self.obj[_RUNNAME] = _runName
if (result := self.plugin.run(**self.obj)):
self._populate(name=result.name)
def _populate(self, name=None):
"""Populate the pulldowns from the project restraintTables
"""
firstItemName = self._rTable.getFirstItemText()
if firstItemName:
# set the item in the pulldown
self._rTable.select(firstItemName)
self._outputName.setText(name or DEFAULT_RUNNAME)
self._selectRTableCallback(firstItemName)
def _selectRTableCallback(self, pid):
"""Handle the callback from the restraintTable selection
"""
if (resTable := self.project.getByPid(pid)):
_texts = [''] + [vt.pid for vt in self.project.violationTables
if vt.getMetadata(_RESTRAINTTABLE) == pid and vt.getMetadata(_VIOLATIONRESULT) is not True]
self._vTable.modifyTexts(texts=_texts)
# set validator to check names in the parent structureData
_validator = LineEditValidatorCoreObject(parent=self._outputName.entry, target=resTable.structureData, klass=ViolationTable,
allowSpace=False, allowEmpty=False)
self._outputName.entry.setValidator(_validator)
self._outputName.entry.validator().resetCheck()
[docs]class ViolationResultsPlugin(Plugin):
"""Plugin to create violation results from restraintTables and processed violationTables
"""
PLUGINNAME = 'Create Restraint Analysis Data'
guiModule = ViolationResultsGuiPlugin
def __init__(self, *args, **kwds):
super().__init__(*args, **kwds)
self._kwds = AttrDict()
self._loggerCallback = None
def _logger(self, *args):
if self._loggerCallback:
self._loggerCallback(*args)
[docs] def run(self, **kwargs):
"""Generate the output dataTable
"""
# pd.set_option('display.max_columns', None)
# pd.set_option('display.max_rows', 7)
_requiredColumns = ['model_id', 'restraint_id', 'atoms', 'violation']
self._logger('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
# check the parameters
if not (restraintTable := kwargs.get(_RESTRAINTTABLE)):
self._logger('ERROR: RestraintTable not specified')
return
if not (violationTable := kwargs.get(_VIOLATIONTABLE)):
self._logger('ERROR: ViolationTable not specified')
return
_df = violationTable.data
if _df is None:
self._logger('ERROR: ViolationTable contains no data')
return
self._logger(f'Running - {self.PLUGINNAME}')
self._logger(f' {_df.columns}')
_invalidColumns = [col for col in _requiredColumns if col not in _df.columns]
if _invalidColumns:
self._logger(f'ERROR: missing required columns {_invalidColumns}')
return
# get the models defined for the violations
models = [v for k, v in _df.groupby(['model_id'], as_index=False)]
if models:
self._logger(f'MODELS {len(models)}')
self._logger(f'{models[0].columns}')
restraintsFromModels = []
targetsFromModels = []
# use the serial to get the restraint from the peak - make list for each model just to check is actually working
for mm, _mod in enumerate(models):
restraintsFromModel = []
restraintsFromModels.append(restraintsFromModel)
targetsFromModel = []
targetsFromModels.append(targetsFromModel)
for serial in _mod['restraint_id']:
restraintId = Pid.IDSEP.join(('' if x is None else str(x)) for x in (restraintTable.structureData.name, restraintTable.name, serial))
modelRestraint = self.project.getObjectsByPartialId(className='Restraint', idStartsWith=restraintId)
restraintsFromModel.append(modelRestraint[0].pid if modelRestraint else None)
targetsFromModel.append((modelRestraint[0].targetValue,
modelRestraint[0].lowerLimit,
modelRestraint[0].upperLimit) if modelRestraint else None)
# check all the same
self._logger(str(all(restraintsFromModels[0] == resMod for resMod in restraintsFromModels)))
# calculate statistics for the violations and concatenate into a single dataFrame
average = pd.concat([v['violation'].reset_index(drop=True) for v in models],
ignore_index=True,
axis=1).agg(['min',
'max',
'mean',
'std',
lambda x: int(sum(x > 0.3)),
lambda x: int(sum(x > 0.5)), ], axis=1)
self._logger('**** average *****')
self._logger(str(average))
# # merge the atom columns - done in nef loader?
# atoms = models[0]['atom1'].map(str) + ' - ' + models[0]['atom2'].map(str)
# remove the indexing for the next concat
_atoms = models[0]['atoms'].reset_index(drop=True)
average.reset_index(drop=True)
# ensure that the atoms in each cell
atoms = pd.Series([' - '.join(sorted(st.split(' - '), key=universalSortKey)) if st else None for st in list(_atoms)])
self._logger('**** atoms *****')
self._logger(str(atoms))
pids = pd.DataFrame(restraintsFromModels[0], columns=['pid'])
targets = pd.DataFrame(targetsFromModels[0], columns=['targetValue', 'lowerLimit', 'upperLimit'])
# ids = models[0]['restraint_id']
# subIds = models[0]['restraint_sub_id']
# build the result dataFrame
result = pd.concat([pids, atoms, targets, average], ignore_index=True, axis=1)
# rename the columns (lambda just gives the name 'lambda') - try multiLevel?
# result.columns = ('RestraintPid', 'Atoms', 'Min', 'Max', 'Mean', 'STD', 'Count>0.3', 'Count>0.5')
result.columns = ('Restraint Pid', 'Atoms', 'Target Value', 'Lower Limit', 'Upper Limit', 'Min', 'Max', 'Mean', 'STD', 'Count > 0.3', 'Count > 0.5')
# put into a new dataTable
if (output := restraintTable.structureData.newViolationTable(name=kwargs.get(_RUNNAME))):
output.setMetadata(_RESTRAINTTABLE, restraintTable.pid)
output.setMetadata(_VIOLATIONRESULT, True)
output.data = result
self._logger(f'\n Results in structureData: {restraintTable.structureData.pid}')
self._logger(f' input restraintTable: {restraintTable.pid}')
self._logger(f' input violationTable: {violationTable.pid}\n')
self._logger(f' output violationTable: {output.pid}\n')
return output
else:
self._logger('ERROR: violationTable contains no models')
ViolationResultsPlugin.register() # Registers the plugin
[docs]def main():
"""Show the violationResults plugin in a test app
"""
from ccpn.ui.gui.widgets.Application import newTestApplication
from ccpn.framework.Application import getApplication
# create a new test application
app = newTestApplication(interface='Gui')
application = getApplication()
mainWindow = application.ui.mainWindow
# add the module to mainWindow
mainWindow.startPlugin(ViolationResultsPlugin)
# show the mainWindow
app.start()
if __name__ == '__main__':
"""Call the test function
"""
main()