Source code for ccpn.plugins.development.Tsar

#=========================================================================================
# 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 17:00:34 +0000 (Thu, February 24, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: Chris Spronk $"
__date__ = "$Date: 2017-11-28 10:28:42 +0000 (Tue, Nov 28, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================


#=========================================================================================
# TODO
# Catch exceptions in case no valid spectra are in the project, now the plugin doesn't open
# when no spectra are in the project
# Output path selection ? If not default
# Tsar installation path ? if not default
# support for 4D and 5D data sets, including tolerance and nucleus selection
# import of Tsar results (assigned peak lists in nef format)
#=========================================================================================




import os,copy,json,pprint,math,shutil
from collections import OrderedDict as OD
from PyQt5 import QtCore, QtGui, QtWidgets
from ccpn.framework.lib.Plugin import Plugin
from ccpn.ui.gui.modules.PluginModule import PluginModule
from ccpn.ui.gui.widgets.FileDialog import LineEditButtonDialog
from ccpn.ui.gui.widgets.Label import Label
from ccpn.ui.gui.widgets.LineEdit import LineEdit
from ccpn.ui.gui.widgets.TextEditor import TextEditor
from ccpn.ui.gui.widgets.DoubleSpinbox import DoubleSpinbox
from ccpn.ui.gui.widgets.Spinbox import Spinbox
from ccpn.ui.gui.widgets.RadioButtons import RadioButtons
from ccpn.ui.gui.widgets.Button import Button
from ccpn.ui.gui.widgets.ButtonList import ButtonList
from ccpn.ui.gui.widgets.MessageDialog import showYesNoWarning, showWarning, showMessage,showYesNo
from ccpn.ui.gui.widgets.ProjectTreeCheckBoxes import ProjectTreeCheckBoxes
from ccpn.ui.gui.widgets.CheckBox import CheckBox
from ccpn.ui.gui.widgets.PulldownList import PulldownList
from ccpn.ui.gui.widgets.Spacer import Spacer
from ccpn.ui.gui.widgets.ScrollArea import ScrollArea
from ccpn.ui.gui.widgets.Frame import Frame
from ccpn.util.nef import GenericStarParser, StarIo
from ccpn.util.nef.GenericStarParser import SaveFrame, DataBlock, DataExtent, Loop, LoopRow
from functools import partial

############
# Settings #
############

# Widths in pixels for nice widget alignments, length depends on the number of fixed columns in the plugin.
# Variable number of columns will take the last value in the list
columnWidths = [150,100,75,100,75]

# Set some tooltip texts
help = {'Run name': 'Name of the run directory. Spaces will be converted to "_"',
        'Run path': 'Path in which the run directory will be created. Relative to the project path.',
        'Chains': 'Check chains from which to calculate structures',
        'Is IDP': 'Check if protein is intrinsically disordered.',
        'Spectrum': 'Select basis spectrum and check backbone spectra to use.',
        'Basis spectrum': 'Select basis spectrum.',
        'Backbone spectrum': 'Check backbone spectrum to include.',
        'Peak list': 'Select peak list.',
        '13C tolerance': 'Recommended value = spectral width (Hz) / (number of increments * spectrometer frequency (MHz). Default setting taken from spectrum tolerances.',
        'Peak sign': 'Peak sign, e.g.: p = positive, n = negative, u = unknown.',
        'Opposite sign': 'Tsar notation for residues with opposite sign and their relative position, e.g. "G -1", "VIDNASCYF -1"',
        'Setup': 'Sets up the project directory structure and scripts.',
        'Write': 'Writes the current settings in JSON and human readable format to the project notes, with the run name as title',
        'Run': 'Runs the calculations.',
        'Import': 'Imports the results. Wait for calculations to finish.'
        }


# Set the basis and backbone assignment experiment type synonyms as valid inputs
# The Tsar basis experiment is typically:
#  - 15N-HSQC for 3D/4D based automated assignment (HNCO preferred for 4D)
#  - 3D HNCO for 4D and 5D based automated assignment
# Backbone experiments currently implemented for 3D based assignment are organised in this dictionary.
# The dictionary contains the nuclei for chemical shift matches with their peak signs, and the experiment axis matches
# Peak sign values in Tsar can be 'p', 'n', 'u' for positive, negative and unknown
# The list of experiment can be expanded as Tsar can handle any type, see Tsar manual.
# Peak signs are not needed for basis experiments, only connectivities.
# Atom nomenclature follows IUPAC, so HN --> H, in contrast to Tsar HN

tsarBasisExperiments    = ['15N HSQC/HMQC','HNCO']
tsarBackboneExperiments = ['HNCO','HNcaCO','HNcoCA','HNCA','hbCB/haCAcoNH','HNcoCA/CB','HNCA/CB']

# Tsar experiments.
# Peak signs per nuclei contain default peak sign and residues with opposite sign as in Tsar definition style:
# i.e. sequence of residues in one-letter capitalised, followed by relative position: 'G -1' or 'VIDNASCYF -1'
# Comma separated when multiple relative positions are possible, e.g.: 'VIDNASCYF -1, 'W -2' (check if this actually happens.

# Cross sections are lists: [connectivity,sign,opposite sign]
# In case of multiple connectivities involving the same nucleus, add _0, _-1 etcetera
tsarExperimentDict   = {'15N HSQC/HMQC': {'Axes':           {'Hn': 'H', 'Nh': 'N'},
                                          'Basis':          {'Hn': 0, 'Nh': 0},
                                          'Connectivities': {}
                                         },
                        'HNCO':          {'Axes':           {'Hn': 'H', 'Nh': 'N', 'CO': 'CO'},
                                          'Basis':          {'Hn': 0, 'Nh': 0},
                                          'Connectivities': {'CO': [-1,'p','-']}
                                         },
                        'HNcaCO':        {'Axes':           {'Hn': 'H', 'Nh': 'N', 'CO': 'CO'},
                                          'Basis':          {'Hn': 0, 'Nh': 0},
                                          'Connectivities': {'CO': [0,'p','-']}
                                         },
                        'HNcoCA':        {'Axes':           {'Hn': 'H', 'Nh': 'N', 'CA': 'CA'},
                                          'Basis':          {'Hn': 0, 'Nh': 0},
                                          'Connectivities': {'CA': [-1,'p','-']}
                                         },
                        'HNCA':          {'Axes':           {'Hn': 'H', 'Nh': 'N', 'CA': ['CA_0','CA_-1']},
                                          'Basis':          {'Hn': 0, 'Nh': 0},
                                          'Connectivities': {'CA_0':  [0,'p','-'],
                                                             'CA_-1': [-1,'p','-']}
                                         },
                        'hbCB/haCAcoNH': {'Axes':           {'Hn': 'H', 'Nh': 'N', 'Ch': ['CA','CB']},
                                          'Basis':          {'Hn': 0, 'Nh': 0},
                                          'Connectivities': {'CA': [-1,'p','-'],
                                                             'CB': [-1,'p','-']}
                                         },
                        'HNcoCA/CB':     {'Axes':           {'Hn': 'H', 'Nh': 'N', 'C': ['CA','CB']},
                                          'Basis':          {'Hn': 0, 'Nh': 0},
                                          'Connectivities': {'CA': [-1,'p','-'],
                                                             'CB': [-1,'p','-']}
                                         },
                        'HNCA/CB':       {'Axes':           {'Hn': 'H', 'Nh': 'N', 'C': ['CA_0','CA_-1','CB_0','CB_-1']},
                                          'Basis':          {'Hn': 0, 'Nh': 0},
                                          'Connectivities': {'CA_0':  [0,'p','-'],
                                                             'CA_-1': [-1,'p','-'],
                                                             'CB_0':  [0,'n','-'],
                                                             'CB_-1': [-1,'n','-']
                                                            }
                                         }
                        }



[docs]class TsarGuiPlugin(PluginModule): className = 'TSAR' def __init__(self, mainWindow=None, plugin=None, application=None, **kwds): super(TsarGuiPlugin, self) PluginModule.__init__(self, mainWindow=mainWindow, plugin=plugin, application=application) # Import some functionality for placing widgets on the grid and setting their properties from .pluginAddons import _addRow, _addColumn, _addVerticalSpacer, _setWidth, _setWidgetProperties self.scrollArea = ScrollArea(self.mainWidget,grid=(0,0)) self.scrollArea.setWidgetResizable(True) self.scrollAreaLayout = Frame(None, setLayout=True) self.scrollArea.setWidget(self.scrollAreaLayout) self.scrollAreaLayout.setContentsMargins(20, 20, 20, 20) # self.userPluginPath = self.application.preferences.general.userPluginPath # self.outputPath = self.application.preferences.general.dataPath self.pluginPath = os.path.join(self.project.path,TsarGuiPlugin.className) # Create ORDERED dictionary to store all parameters for the run # deepcopy doesn't work on dictionaries with the qt widgets, so to get a separation of gui widgets and values for storage # it is needed to create the two dictionaries alongside each other self.guiDict = OD([('General',OD()), ('Chains',OD()), ('Basis spectrum', OD()), ('Backbone spectra', OD())]) self.settings = OD([('General',OD()), ('Chains',OD()), ('Basis spectrum', OD()), ('Backbone spectra', OD())]) # Set the basis type synonyms as valid inputs self.basisSpectra = [self.project.spectra[i].id for i in range(len(self.project.spectra)) if self.project.spectra[i].synonym in tsarBasisExperiments] # Spectrum ids are unique. For easier lookup later, duplicate ExperimentTypes info with spectrum id as keys # Easier for later extraction and export to NEF for i in range(len(self.project.spectra)): if self.project.spectra[i].synonym in tsarBackboneExperiments: self.guiDict['Backbone spectra'][self.project.spectra[i].id] = OD() self.settings['Backbone spectra'][self.project.spectra[i].id] = OD() # Input check validInput = self._inputDataCheck() if not validInput: return # Run name grid = 0,0 widget = Label(self.scrollAreaLayout,text='Run name', grid=grid,tipText=help['Run name']) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) grid = _addColumn(grid) widget = LineEdit(self.scrollAreaLayout, text = 'Run_1', grid=grid,tipText=help['Run name']) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) self.guiDict['General']['Run name'] = widget self.settings['General']['Run name'] = self._getValue(widget) # Set molecular chains, use checkboxes with own labels grid = _addVerticalSpacer(self.scrollAreaLayout,grid) grid = _addRow(grid) widget = Label(self.scrollAreaLayout,text='Molecular chains', grid=grid) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) for chain in sorted(self.project.chains): grid = _addColumn(grid) # Check whether to use the name, id or serial, docs are confusing here widget = CheckBox(self.scrollAreaLayout, text=chain.shortName, checked=True, grid=grid, tipText=help['Chains']) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) self.guiDict['Chains'][chain.id] = OD([('Active', widget)]) self.settings['Chains'][chain.id] = OD([('Active', self._getValue(widget))]) # Folded protein or IDP selection grid = _addRow(grid) widget = Label(self.scrollAreaLayout,text='Folding state', grid=grid) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) grid = _addColumn(grid) widget = CheckBox(self.scrollAreaLayout, text='Is IDP', checked=False, grid=grid, tipText=help['Is IDP']) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) self.guiDict['General']['Folding state'] = widget self.settings['General']['Folding state'] = self._getValue(widget) # Create header row for clean plugin content organisation grid = _addVerticalSpacer(self.scrollAreaLayout,grid) for header in ['Spectrum', 'Peak list', '13C tolerance']: grid = _addColumn(grid) widget = Label(self.scrollAreaLayout, text=header, grid=grid, tipText=help[header]) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) grid = _addColumn(grid) widget = Label(self.scrollAreaLayout, text='Peak', grid=grid) _setWidgetProperties(widget,_setWidth(columnWidths,grid), hAlign='r') grid = _addColumn(grid) widget = Label(self.scrollAreaLayout, text='Sign', grid=grid, tipText=help['Peak sign']) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) grid = _addColumn(grid) widget = Label(self.scrollAreaLayout, text='Opposite sign', grid=grid, tipText=help['Opposite sign']) _setWidgetProperties(widget,width=100) # Set basis spectrum grid = _addRow(grid) widget = Label(self.scrollAreaLayout, text='Basis spectrum', grid=grid) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) # Basis spectrum selection updates the peak list pulldown using spectrum selection function. grid = _addColumn(grid) widget = PulldownList(self.scrollAreaLayout, grid=grid, callback=self._selectBasisPeaklist, tipText=help['Basis spectrum']) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) widget.setData(self.basisSpectra) self.guiDict['Basis spectrum']['SpectrumId'] = widget self.settings['Basis spectrum']['SpectrumId'] = self._getValue(widget) grid = _addColumn(grid) widget = PulldownList(self.scrollAreaLayout, grid=grid, tipText=help['Peak list']) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) self.guiDict['Basis spectrum']['Peak list'] = widget self.settings['Basis spectrum']['Peak list'] = self._getValue(widget) # Invoke the callback function to catch the selection in case only one spectrum is available as basisExperiment if self.basisSpectra: self._selectBasisPeaklist(self.basisSpectra[0]) # Add the backbone spectra grid = _addVerticalSpacer(self.scrollAreaLayout,grid) # Create a header, depending on the type of spectra in the project widget = Label(self.scrollAreaLayout, text='Backbone spectra', grid=grid) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) # Add the backbone spectra, and tolerances depending on the selected basis experiment # Basis spectrum callback will need adaptation for 4D / 5D basisSpectrum = self._spectrumId2Spectrum(self.settings['Basis spectrum']['SpectrumId']) for spectrumId in sorted(self.settings['Backbone spectra']): spectrum = self._spectrumId2Spectrum(spectrumId) grid = _addColumn(grid) widget = CheckBox(self.scrollAreaLayout, text=spectrumId, checked=True, grid=grid,callback=partial(self._selectBackbonePeaklist, spectrumId), tipText=help['Backbone spectrum']) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) self.guiDict['Backbone spectra'][spectrumId]['Active'] = widget self.settings['Backbone spectra'][spectrumId]['Active'] = self._getValue(widget) grid = _addColumn(grid) widget = PulldownList(self.scrollAreaLayout, grid=grid, tipText=help['Peak list']) _setWidgetProperties(widget,_setWidth(columnWidths,grid)) self.guiDict['Backbone spectra'][spectrumId]['Peak list'] = widget self.settings['Backbone spectra'][spectrumId]['Peak list'] = self._getValue(widget) self._selectBackbonePeaklist(spectrumId) # Tolerances if spectrum.dimensionCount == 3 and basisSpectrum.synonym == '15N HSQC/HMQC': # In case of 3D spectra, we only need one tolerance, for the Carbon # Check which is the carbon dimension tolerance = spectrum.assignmentTolerances[spectrum.isotopeCodes.index('13C')] grid = _addColumn(grid) widget = DoubleSpinbox(self.scrollAreaLayout, value=tolerance, step=0.05, decimals=3,grid=grid, tipText=help['13C tolerance']) _setWidgetProperties(widget,_setWidth(columnWidths,grid), hAlign='r') widget.setButtonSymbols(2) self.guiDict['Backbone spectra'][spectrumId]['Tolerance1'] = widget self.settings['Backbone spectra'][spectrumId]['Tolerance1'] = self._getValue(widget) else: # This needs expansion when 4D/5D is included, for now we limit to 3D grid = _addColumn(grid) widget = DoubleSpinbox(self.scrollAreaLayout, value=tolerance, step=0.05, decimals=3,grid=grid, tipText=help['13C tolerance']) _setWidgetProperties(widget,_setWidth(columnWidths,grid), hAlign='r') widget.setButtonSymbols(2) self.guiDict['Backbone spectra'][spectrumId]['Tolerance1'] = widget self.settings['Backbone spectra'][spectrumId]['Tolerance1'] = self._getValue(widget) grid = _addColumn(grid) widget = DoubleSpinbox(self.scrollAreaLayout, value=tolerance, step=0.05, decimals=3,grid=grid, tipText=help['13C tolerance']) _setWidgetProperties(widget,_setWidth(columnWidths,grid), hAlign='r') widget.setButtonSymbols(2) self.guiDict['Backbone spectra'][spectrumId]['Tolerance2'] = widget self.settings['Backbone spectra'][spectrumId]['Tolerance2'] = self._getValue(widget) # Peak signs listValues = ['p', 'n', 'u'] experiment = tsarExperimentDict[spectrum.synonym] cnt = 0 self.guiDict['Backbone spectra'][spectrumId]['Peak signs'] = OD([]) self.settings['Backbone spectra'][spectrumId]['Peak signs'] = OD([]) for connectivity in sorted(experiment['Connectivities']): if cnt > 0: grid = (grid[0]+1,3) # Set the default value to start with nucleus = connectivity.split('_')[0] label = '{0} {1:>2}'.format(nucleus,experiment['Connectivities'][connectivity][0]) defaultSign = experiment['Connectivities'][connectivity][1] defaultOppositeSign = experiment['Connectivities'][connectivity][2] grid = _addColumn(grid) widget = Label(self.scrollAreaLayout, text=label, grid=grid) _setWidgetProperties(widget,_setWidth(columnWidths,grid), hAlign='r') grid = _addColumn(grid) widget = PulldownList(self.scrollAreaLayout, texts=listValues, grid=grid, tipText=help['Peak sign']) widget.setCurrentIndex(listValues.index(defaultSign)) _setWidgetProperties(widget,_setWidth(columnWidths,grid), hAlign='c') if connectivity not in self.guiDict['Backbone spectra'][spectrumId]['Peak signs']: self.guiDict['Backbone spectra'][spectrumId]['Peak signs'][connectivity] = OD([('Sign', widget)]) self.settings['Backbone spectra'][spectrumId]['Peak signs'][connectivity] = OD([('Sign', self._getValue(widget))]) else: self.guiDict['Backbone spectra'][spectrumId]['Peak signs'][connectivity]['Sign'] = widget self.settings['Backbone spectra'][spectrumId]['Peak signs'][connectivity]['Sign'] = self._getValue(widget) # Add residues with opposite peak sign grid = _addColumn(grid) widget = LineEdit(self.scrollAreaLayout, text=defaultOppositeSign, grid=grid, tipText=help['Opposite sign']) _setWidgetProperties(widget,width=100) self.guiDict['Backbone spectra'][spectrumId]['Peak signs'][connectivity]['Opposite sign'] = widget self.settings['Backbone spectra'][spectrumId]['Peak signs'][connectivity]['Opposite sign'] = self._getValue(widget) cnt += 1 grid = _addRow(grid) grid = _addVerticalSpacer(self.scrollAreaLayout, grid) # Action buttons: Set up, Run, import grid = _addVerticalSpacer(self.scrollAreaLayout, grid) grid = _addColumn(grid) texts = ['Set up project', 'Write settings', 'Run calculation', 'Import results'] tipTexts = [help['Setup'], help['Write'], help['Run'], help['Import']] # callbacks = [self._setupProject, partial(self._writeSettings,''), self._runCalculation, self._importResults] callbacks = [self._setupProject, self._writeSettings, self._runCalculation, self._importResults] widget = ButtonList(parent=self.scrollAreaLayout, texts=texts, callbacks=callbacks, tipTexts=tipTexts, grid=grid, gridSpan=(1,6)) _setWidgetProperties(widget,_setWidth(columnWidths,grid),heightType='Minimum') Spacer(self.scrollAreaLayout, 5, 5, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding, grid=(grid[0]+1, 10), gridSpan=(1, 1)) def _inputDataCheck(self): # Checks available input data at plugin start return True inputWarning = '' if len(self.project.chains) == 0: inputWarning += 'No molecular chains found in the project\n' if len(self.basisSpectra) == 0: inputWarning += 'No valid basis spectrum found in the project\n' if len(self.settings['Backbone spectra']) == 0: inputWarning += 'No valid backbone spectra found in the project\n' if inputWarning != '': showWarning('Warning',inputWarning) #self._closeModule() self.deleteLater() return False return True def _runDataCheck(self): # Checks data before run time inputWarning = '' chains = 0 spectra = 0 # for i in ['Run name','Run path']: # if len(self.settings['General'][i].strip()) == 0: # inputWarning += 'No {0} specified\n'.format(i) # if len(self.settings['General'][i].split()) > 1: # inputWarning += '{0} may not contain spaces\n'.format(i) if len(self.settings['General']['Run name'].strip()) == 0: inputWarning += 'No run name specified\n' if len(self.settings['General']['Run name'].split()) > 1: inputWarning += 'Run name may not contain spaces\n' for pid in self.exportPidList: if 'MC' in pid: chains += 1 if 'PL' in pid: spectra += 1 if chains == 0: inputWarning += 'No molecular chains selected\n' if spectra <= 1: # Backbone peak list is always in, the plugin doesn't start without backbone spectrum inputWarning += 'No backbone spectra selected\n' if inputWarning != '': showWarning('Warning',inputWarning) setupComplete = False else: setupComplete = True return setupComplete def _spectrumId2Spectrum(self,spectrumId): if spectrumId: return self.project.getByPid('SP:'+spectrumId) def _selectBasisPeaklist(self,spectrumId): spectrum = self._spectrumId2Spectrum(spectrumId) widget = self.guiDict['Basis spectrum']['Peak list'] widget.setData([str(PL.serial) for PL in spectrum.peakLists]) self.settings['Basis spectrum']['Peak list'] = self._getValue(widget) def _selectBackbonePeaklist(self, spectrumId): if self.guiDict['Backbone spectra'][spectrumId]['Active'].isChecked(): spectrum = self._spectrumId2Spectrum(spectrumId) widget = self.guiDict['Backbone spectra'][spectrumId]['Peak list'] widget.setData([str(PL.serial) for PL in spectrum.peakLists]) widget.setEnabled(True) self.settings['Backbone spectra'][spectrumId]['Peak list'] = self._getValue(widget) else: widget = self.guiDict['Backbone spectra'][spectrumId]['Peak list'] widget.setEnabled(True) def _getValue(self,widget): # Get the current value of the widget: if isinstance(widget,str): value = widget if not isinstance(widget, Button) and not isinstance(widget, ButtonList): if hasattr(widget,'get'): value = widget.get() elif hasattr(widget, 'isChecked'): value = widget.isChecked() elif hasattr(widget, 'value'): value = widget.value() return value def _updateSettings(self, inputDict, outputDict): # Gets all the values from a dictionary that contains widgets # and writes values to the dictionary with the same structure for k, v in inputDict.items(): if isinstance(v, dict): self._updateSettings(v, outputDict[k]) else: outputDict[k] = self._getValue(v) return outputDict def _writeSettings(self): # Get values from the GUI widgets and save in the settings self._updateSettings(self.guiDict, self.settings) # Create a data block of the regular CCPN objects, which would normally be exported directly. self._createPidList() # Check if all input requirements are met setupComplete = self._runDataCheck() if setupComplete == True: # Creates a JSON string and a cleaned up human readable string, both added to notes, in a single note jsonString = json.dumps(self.settings,indent=4) s = pprint.pformat(jsonString) n = '' maxChars = 0 for line in s.split('\n'): for char in ['"','{','}',',',"'",'(',')','\\']: line = line.replace(char,'') # Remove preceding 5 spaces and \n without removing newline line = line[5:-1].rstrip(' ') + '\n' if len(line.split(':')[0]) > maxChars: maxChars = len(line) # Don't write unnecessary empty newlines if not len(line.split()) == 0: n += line # Justify key and values nicely formatted = '' for line in n.split('\n'): try: k,v = line.split(':') line = '{0}{1}\n'.format((k + ':').ljust(maxChars + 4), v) # For non mono spaced fonts we need to convert spaces to tabs, but tab locations in the notes are different # from the editor, where this method works: # nTabs = math.ceil((maxChars - len(k))/4) # line = '{0}{1}{2} {3} {4} {5}\n'.format(k+':',nTabs*'\t', str(v).strip(), len(k), maxChars, nTabs) except ValueError: pass formatted += line noteTitle = '{0} - Tsar'.format(self.settings['General']['Run name']) note = self.project.newNote(noteTitle) note.text = formatted + '\n\nJSON format:\n\n' + jsonString showMessage('Message', 'Created project note {0}'.format(noteTitle)) def _createPidList(self): # Get basis experiment self.basisSpectrumId = self.settings['Basis spectrum']['SpectrumId'] self.basisSpectrumPeakListId = self.settings['Basis spectrum']['Peak list'] self.basisSpectrum = self._spectrumId2Spectrum(self.basisSpectrumId) # 1. # Create the list of standard project objects to be written, which is a list of their pids self.exportPidList = [] # Add sequence pids: # We only support a single chain for the moment, hetero multimers would require writing all chains for chainId in self.settings['Chains']: if self.settings['Chains'][chainId]['Active'] == True: self.exportPidList.append('MC:' + chainId) # Add peak lists # Basis spectrum peak list self.exportPidList.append('PL:{0}.{1}'.format(self.basisSpectrumId,self.basisSpectrumPeakListId)) # Backbone spectra peak lists for spectrumId in self.settings['Backbone spectra']: if self.settings['Backbone spectra'][spectrumId]['Active'] == True: self.exportPidList.append('PL:{0}.{1}'.format(spectrumId,self.settings['Backbone spectra'][spectrumId]['Peak list'])) def _convertNefToDataBlock(self): # Convert the standard NEF data to a Data block from ccpn.framework.lib.DataLoaders.NefDataLoader import NefDataLoader self.dataBlock = NefDataLoader._convertToDataBlock(self.project, skipPrefixes=(), expandSelection=False, includeOrphans=False, pidList=self.exportPidList) def _createConnectivities(self,spectrum): connectivities = [] peakSigns = [] peakOppositeSigns = [] # Basis connectivities, no connectivities if tsarExperimentDict[spectrum.synonym]['Connectivities'] == {}: s = '' for axisCode in spectrum.axisCodes: s += '{0} {1} '.format(tsarExperimentDict[spectrum.synonym]['Axes'][axisCode],tsarExperimentDict[spectrum.synonym]['Basis'][axisCode]) connectivities.append(s.strip()) else: for connectivity in sorted(tsarExperimentDict[spectrum.synonym]['Connectivities']): s = '' for axisCode in spectrum.axisCodes: if axisCode in tsarExperimentDict[spectrum.synonym]['Basis']: nucleus = tsarExperimentDict[spectrum.synonym]['Axes'][axisCode] s += '{0} {1} '.format(nucleus,tsarExperimentDict[spectrum.synonym]['Basis'][axisCode]) else: nucleus = connectivity.split('_')[0] s += '{0} {1} '.format(nucleus,tsarExperimentDict[spectrum.synonym]['Connectivities'][connectivity][0]) # Peak signs and opposite signs need to be taken from self.settings peakSigns.append(self.settings['Backbone spectra'][spectrum.id]['Peak signs'][connectivity]['Sign']) peakOppositeSigns.append(self.settings['Backbone spectra'][spectrum.id]['Peak signs'][connectivity]['Opposite sign']) connectivities.append(s.strip()) return connectivities,peakSigns,peakOppositeSigns def _addTsarBlocks(self): # Get Run name runName = self.settings['General']['Run name'].strip() # Get IDP state (y/n) if self.settings['General']['Folding state'] == True: self.isIdp = 'y' else: self.isIdp = 'n' # NEFS removed because they are not in NEF definitions yet sf_category = 'ccpn_tsar_run' sf_framecode = sf_category+'_settings' newSaveFrame = SaveFrame(name=sf_framecode) newSaveFrame.addItem('_{0}.sf_category'.format(sf_category),sf_category) newSaveFrame.addItem('_{0}.sf_framecode'.format(sf_category),sf_framecode) newSaveFrame.addItem('_{0}.name'.format(sf_category),runName) self.dataBlock.addItem(sf_category, newSaveFrame) sf_category = 'ccpn_tsar_folding_state' sf_framecode = sf_category + '_settings' newSaveFrame = SaveFrame(name=sf_framecode) newSaveFrame.addItem('_{0}.sf_category'.format(sf_category),sf_category) newSaveFrame.addItem('_{0}.sf_framecode'.format(sf_category),sf_framecode) newSaveFrame.addItem('_{0}.isIDP'.format(sf_category),self.isIdp) self.dataBlock.addItem(sf_category, newSaveFrame) sf_category = 'ccpn_tsar_basis_spectrum' sf_framecode = sf_category + '_{0}'.format(self.basisSpectrumId) newSaveFrame = SaveFrame(name=sf_framecode) newSaveFrame.addItem('_{0}.sf_category'.format(sf_category),sf_category) newSaveFrame.addItem('_{0}.sf_framecode'.format(sf_category),sf_framecode) connectivities, peakSigns, peakOppositeSigns = self._createConnectivities(self.basisSpectrum) newSaveFrame.addItem('_{0}.connectivities'.format(sf_category),connectivities) self.dataBlock.addItem(sf_category, newSaveFrame) # Add Tsar spectrum to connectivity mappings for spectrumId in sorted(self.settings['Backbone spectra']): if self.settings['Backbone spectra'][spectrumId]['Active'] == True: spectrum = self._spectrumId2Spectrum(spectrumId) sf_category = 'ccpn_tsar_backbone_spectrum' sf_framecode = sf_category + '_{0}'.format(spectrumId) newSaveFrame = SaveFrame(name=sf_framecode) newSaveFrame.addItem('_{0}.sf_category'.format(sf_category), sf_category) newSaveFrame.addItem('_{0}.sf_framecode'.format(sf_category), sf_framecode) connectivities, peakSigns, peakOppositeSigns = self._createConnectivities(spectrum) newSaveFrame.addItem('_{0}.connectivities'.format(sf_category),connectivities) newSaveFrame.addItem('_{0}.peak_signs'.format(sf_category),peakSigns) newSaveFrame.addItem('_{0}.opposite_signs'.format(sf_category),peakOppositeSigns) newSaveFrame.addItem('_{0}.13C_tolerance'.format(sf_category), '{0:.3f}'.format(self.settings['Backbone spectra'][spectrumId]['Tolerance1'])) self.dataBlock.addItem(sf_framecode, newSaveFrame) def _exportNef(self): # self.outputPath = os.path.join(self.settings['General']['Run path'], '{0}.nef'.format(self.settings['General']['Run name'])) # self.project._writeDataBlockToFile(dataBlock=self.dataBlock, path=self.outputPath, overwriteExisting=True) from ccpn.framework.lib.DataLoaders.NefDataLoader import NefDataLoader NefDataLoader._writeDataBlockToFile(dataBlock=self.dataBlock, path=os.path.join(self.runPath, '{0}.nef'.format(self.settings['General']['Run name'])), overwriteExisting=True) def _setupProject(self): # Get values from the GUI widgets and save in the settings self._updateSettings(self.guiDict, self.settings) # Create a data block of the regular CCPN objects, which would normally be exported directly. self._createPidList() # Check if all input requirements are met setupComplete = self._runDataCheck() # Check if tree exists, and if to overwrite # Set the run path self.runPath = os.path.join(self.pluginPath,self.settings['General']['Run name']) if setupComplete == True: if os.path.exists(self.runPath): overwrite = showYesNo('Warning', 'Run {0} exists. Overwrite?'.format(self.settings['General']['Run name'])) if overwrite == False: showMessage('Message', 'Project set up aborted') return if overwrite == True: shutil.rmtree(self.runPath) # Create a (new) directory tree self._createRunTree() # Convert data to NEF self._convertNefToDataBlock() # Add the calculation specific data blocks that describe additional input parameters self._addTsarBlocks() # Export the NEF file as input for Tsar self._exportNef() showMessage('Message', 'Project set up complete') def _createRunTree(self): if not os.path.exists(self.pluginPath): os.makedirs(self.pluginPath) if not os.path.exists(self.runPath): os.makedirs(self.runPath) def _runCalculation(self): # Check on presence of complete set up pass def _importResults(self): # Check on presence of completed calculation pass
[docs]class TsarPlugin(Plugin): PLUGINNAME = 'TSAR2 - Automated backbone assignment' guiModule = TsarGuiPlugin
[docs] def run(self, **kwargs): ''' Insert here the script for running Tsar ''' print('Running TSAR', kwargs)
# TsarPlugin.register() # Registers the pipe in the pluginList # Set tolerances from project.spectrum.tolerances by default