class CreateAxisPopup(BasePopup): def __init__(self, parent, **kw): self.axisType = None BasePopup.__init__(self, parent=parent, title='Create window axis', modal=True, transient=False, **kw) def body(self, master): row = 0 label = Label(master, text='Axis type: ', grid=(row, 0)) tipText = 'Selects what type of measurement is displayed along the window axis' self.type_list = PulldownList(master, tipText=tipText, grid=(row, 1)) row += 1 tipTexts = ['Make an axis of the selected type in the window & close this popup'] texts = [ 'Create' ] commands = [ self.ok ] buttons = UtilityButtonList(master, texts=texts, doClone=False, grid=(row, 0), commands=commands, helpUrl=self.help_url, gridSpan=(1,2), tipTexts=tipTexts) self.administerNotifiers(self.registerNotify) self.update() def administerNotifiers(self, notifyFunc): for func in ('__init__', 'delete', 'setName'): notifyFunc(self.update, 'ccpnmr.Analysis.AxisType', func) def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self) def update(self, *extra): axisType = self.axisType axisTypes = self.parent.parent.getAxisTypes() names = [x.name for x in axisTypes] if axisTypes: if axisType not in axisTypes: self.axisType = axisType = axisTypes[0] index = axisTypes.index(axisType) else: index = 0 self.axisType = None self.type_list.setup(names, axisTypes, index) def apply(self): self.axisType = self.type_list.getObject() return True
class EditExperimentSeriesPopup(BasePopup): """ **Setup Experiment Series for Chemical Shift and Intensity Changes** The purpose of this popup is to setup ordered groups of experiments that are related by the variation in some condition or parameter, but otherwise the kind of experiment being run is the same. For example the user could setup experiments for a temperature series, ligand binding titration or relaxation rate measurement. The layout is divided into two tables. The upper table shows all of the series that are known to the current project and the lower table shows all of the experiments or planes that comprise the selected series. These series relate to both groups of separate experiments (and hence spectra) and also single experiment where one dimension is not for NMR frequency but rather a "sampled" dimension and thus effectively combines many experiments (e.g. for different T1 values) as different planes. A stack of effectively 2D experiments combined in this manner is typically referred to as pseudo-3D. - The experiment is 3D but there are only two NMR dimensions. Series which are stacks of planes in a single experiment entity are automatically detected and entered into the table once they are loaded. Series that are constructed from multiple, separate experiments however must be setup by the user. To setup a new series of this kind [Add Series] makes a new, empty NMR series. Next the user should change the "Parameter varied" column to specify what type of thing is varied between the different experiments. The user then adds experiments into this series with the [Add Series Point] function at the bottom. Once the points have been added to the series the name of the experiment for each point may be changed. Initially, arbitrary experiments appear for the series points, so these almost always have to be adjusted. Once a stacked-plane experiment or series of experiments is setup, the user next sets (or checks) the value of the parameter associated with each point in the lower table. When loading stacked-plane experiments these values may come though automatically, if they are present in the spectrum header or parameter file. Given a completed NMR series specification the user may then extract the relevant data from the series using one of the analysis tools like `Follow Intensity Changes`_ or `Follow Shift Changes`_. **Caveats & Tips** Make sure the "Parameter Varied" for a given NMR series is appropriate to the type of analysis being performed. Many tools that extract T1 or Kd measurements for example look for specific types of series. The "Set/Unset Ref Plane" function is only used in certain kinds of series such as those that use trains of CPMG pulses. .. _`Follow Intensity Changes`: CalcRatesPopup.html .. _`Follow Shift Changes`: FollowShiftChangesPopup.html """ def __init__(self, parent, *args, **kw): self.guiParent = parent self.expSeries = None self.conditionPoint = None self.waiting = 0 BasePopup.__init__(self, parent, title="Experiment : NMR Series", **kw) def body(self, guiFrame): self.geometry("500x500") self.nameEntry = Entry(self, text='', returnCallback=self.setName, width=12) self.detailsEntry = Entry(self, text='', returnCallback=self.setDetails, width=16) self.valueEntry = FloatEntry(self, text='', returnCallback=self.setValue, width=10) self.errorEntry = FloatEntry(self, text='', returnCallback=self.setError, width=8) self.conditionNamesPulldown = PulldownList(self, callback=self.setConditionName, texts=self.getConditionNames()) self.unitPulldown = PulldownList(self, callback=self.setUnit, texts=self.getUnits()) self.experimentPulldown = PulldownList(self, callback=self.setExperiment) guiFrame.grid_columnconfigure(0, weight=1) row = 0 frame = Frame(guiFrame, grid=(row, 0)) frame.expandGrid(None,0) div = LabelDivider(frame, text='Current Series', grid=(0, 0)) utilButtons = UtilityButtonList(frame, helpUrl=self.help_url, grid=(0,1)) row += 1 frame0 = Frame(guiFrame, grid=(row, 0)) frame0.expandGrid(0,0) tipTexts = ['The serial number of the experiment series, but left blank if the series as actually a pseudo-nD experiment (with a sampled non-frequency axis)', 'The name of the experiment series, which may be a single pseudo-nD experiment', 'The number of separate experiments (and hence spectra) present in the series', 'The kind of quantity that varies for different experiments/planes within the NMR series, e.g. delay time, temperature, ligand concentration etc.', 'The number of separate points, each with a separate experiment/plane and parameter value, in the series'] headingList = ['#','Name','Experiments','Parameter\nVaried','Num\nPoints'] editWidgets = [None, self.nameEntry, None, self.conditionNamesPulldown, None] editGetCallbacks = [None, self.getName, None, self.getConditionName, None] editSetCallbacks = [None, self.setName, None, self.setConditionName, None] self.seriesMatrix = ScrolledMatrix(frame0, tipTexts=tipTexts, editSetCallbacks=editSetCallbacks, editGetCallbacks=editGetCallbacks, editWidgets=editWidgets, headingList=headingList, callback=self.selectExpSeries, deleteFunc=self.deleteExpSeries, grid=(0,0), gridSpan=(None, 3)) tipTexts = ['Make a new, blank NMR series specification in the CCPN project', 'Delete the selected NMR series from the project, although any component experiments remain. Note you cannot delete pseudo-nD series; delete the actual experiment instead', 'Colour the spectrum contours for each experiment in the selected series (not pseudo-nD) using a specified scheme'] texts = ['Add Series','Delete Series', 'Auto Colour Spectra'] commands = [self.addExpSeries,self.deleteExpSeries, self.autoColorSpectra] self.seriesButtons = ButtonList(frame0, texts=texts, commands=commands, grid=(1,0), tipTexts=tipTexts) label = Label(frame0, text='Scheme:', grid=(1,1)) tipText = 'Selects which colour scheme to apply to the contours of (separate) experiments within an NMR series' self.colorSchemePulldown = PulldownList(frame0, grid=(1,2), tipText=tipText) row += 1 div = LabelDivider(guiFrame, text='Experimental Parameters & Conditions', grid=(row, 0)) row += 1 guiFrame.grid_rowconfigure(row, weight=1) frame1 = Frame(guiFrame, grid=(row, 0)) frame1.expandGrid(0,0) tipTexts = ['The kind of experimental parameter that is being used to define the NMR series', 'The experiment that corresponds to the specified parameter value; can be edited from an arbitrary initial experiment', 'The numeric value of the parameter (condition) that relates to the experiment or point in the NMR series', 'The estimated error in value of the condition', 'The measurement unit in which the value of the condition is represented'] headingList = ['Parameter','Experiment','Value','Error','Unit'] editWidgets = [None,self.experimentPulldown,self.valueEntry,self.errorEntry, self.unitPulldown] editGetCallbacks = [None,self.getExperiment, self.getValue, self.getError, self.getUnit] editSetCallbacks = [None,self.setExperiment, self.setValue, self.setError, self.setUnit] self.conditionPointsMatrix = ScrolledMatrix(frame1, grid=(0,0), tipTexts=tipTexts, editSetCallbacks=editSetCallbacks, editGetCallbacks=editGetCallbacks, editWidgets=editWidgets, headingList=headingList, callback=self.selectConditionPoint, deleteFunc=self.deleteConditionPoint) self.conditionPointsMatrix.doEditMarkExtraRules = self.conditionTableShow tipTexts = ['Add a new point to the NMR series with an associated parameter value and experiment', 'Remove the selected point from the series, including any associated parameter value', 'For appropriate kinds of NMR series, set or unset a point as representing the plane to use as a reference'] texts = ['Add Series Point','Delete Series Point','Set/Unset Ref Plane'] commands = [self.addConditionPoint,self.deleteConditionPoint,self.setSampledReferencePlane] self.conditionPointsButtons = ButtonList(frame1, texts=texts, commands=commands, tipTexts=tipTexts, grid=(1,0)) self.updateAfter() self.updateColorSchemes() self.administerNotifiers(self.registerNotify) def administerNotifiers(self, notifyFunc): #for func in ('__init__', 'delete','setName'): for func in ('__init__', 'delete','setName','setConditionNames', 'addConditionName','removeConditionName'): notifyFunc(self.updateAfter,'ccp.nmr.Nmr.NmrExpSeries', func) for func in ('__init__', 'delete','setName'): notifyFunc(self.updateExperiments,'ccp.nmr.Nmr.Experiment', func) for func in ('__init__', 'delete'): notifyFunc(self.updateDataDim,'ccp.nmr.Nmr.SampledDataDim', func) for func in ('__init__', 'delete','setCondition','setUnit','setValue','setError'): notifyFunc(self.updateConditionsAfter,'ccp.nmr.Nmr.SampleCondition', func) for func in ('__init__', 'delete','setCondition'): notifyFunc(self.updateAfter,'ccp.nmr.Nmr.SampleCondition', func) for func in ('setConditionVaried', 'setPointErrors', 'addPointError', 'removePointError', 'setPointValues','addPointValue', 'removePointValue','setUnit'): notifyFunc(self.updateAfter,'ccp.nmr.Nmr.SampledDataDim', func) for func in ('__init__', 'delete'): notifyFunc(self.updateColorSchemes,'ccpnmr.AnalysisProfile.ColorScheme', func) def open(self): self.updateAfter() BasePopup.open(self) def updateColorSchemes(self, scheme=None): index = 0 prevScheme = self.colorSchemePulldown.getObject() schemes = getHueSortedColorSchemes(self.analysisProfile) schemes = [s for s in schemes if len(s.colors) > 1] colors = [list(s.colors) for s in schemes] if schemes: names = [s.name for s in schemes] if prevScheme in schemes: index = schemes.index(prevScheme) else: names = [] self.colorSchemePulldown.setup(names, schemes, index, colors) def autoColorSpectra(self): if self.expSeries and (self.expSeries.className != 'Experiment'): scheme = self.colorSchemePulldown.getObject() if scheme: colors = scheme.colors else: colors = ['#FF0000','#00FF00','#0000FF'] cdict = getNmrExpSeriesSampleConditions(self.expSeries) conditionName = list(self.expSeries.conditionNames)[0] expList = [] for sampleCondition in cdict.get(conditionName, []): expList.append( (sampleCondition.value, sampleCondition.parent.experiments) ) expList.sort() m = len(expList)-1.0 c = len(colors)-1 for i, (value, experiments) in enumerate(expList): p = c*i/m j = int(p) r1, g1, b1 = Color.hexToRgb(colors[j]) r2, g2, b2 = Color.hexToRgb(colors[min(c,j+1)]) f2 = p-j f1 = 1.0-f2 r = (r1*f1)+(r2*f2) g = (g1*f1)+(g2*f2) b = (b1*f1)+(b2*f2) hexColor = Color.hexRepr(r,g,b) for experiment in experiments: for spectrum in experiment.dataSources: if spectrum.dataType == 'processed': analysisSpec = getAnalysisSpectrum(spectrum) if analysisSpec.posColors: analysisSpec.posColors = [hexColor,] elif analysisSpec.negColors: analysisSpec.negColors = [hexColor,] def getUnusedExperiments(self): sampleExperiments = getSampledDimExperiments(self.nmrProject) experiments = [] for experiment in self.nmrProject.sortedExperiments(): if experiment in sampleExperiments: continue if self.expSeries and (self.expSeries.className != 'Experiment'): if experiment in self.expSeries.experiments: continue experiments.append(experiment) return experiments def conditionTableShow(self, object, row, col): if type(object) is type(()): dataDim, index = object refPlane = dataDim.analysisDataDim.refSamplePlane if refPlane == index: return False if col == 1: return False return True def setSampledReferencePlane(self): if self.expSeries and (self.expSeries.className == 'Experiment'): if self.conditionPoint: dataDim, point = self.conditionPoint analysisDataDim = dataDim.analysisDataDim refPoint = analysisDataDim.refSamplePlane if refPoint == point: analysisDataDim.refSamplePlane = None else: analysisDataDim.refSamplePlane = point self.updateAfter() def checkAddSampleCondition(self, experiment): conditionSet = getExperimentConditionSet(experiment) conditionName = self.expSeries.conditionNames[0] condDict = getNmrExpSeriesSampleConditions(self.expSeries) sampleConditions = condDict.get(conditionName, []) units = [sc.unit for sc in sampleConditions if sc.unit] if units: sortList = list(set(units)) sortList.sort(key = lambda u:units.count(u)) unit = sortList[-1] else: unit = CONDITION_UNITS_DICT[conditionName][0] condition = conditionSet.findFirstSampleCondition(condition=conditionName) if not condition: condition = conditionSet.newSampleCondition(condition=conditionName, unit=unit, value=0.0, error=0.0) def addConditionPoint(self): if self.expSeries and (self.expSeries.className != 'Experiment'): experiments = self.getUnusedExperiments() if not experiments: showWarning('Warning','No experiments available', parent=self) return experiment = experiments[0] if experiment not in self.expSeries.experiments: self.expSeries.addExperiment(experiment) self.checkAddSampleCondition(experiment) self.updateAfter() def deleteConditionPoint(self, *event): if self.conditionPoint and (self.expSeries.className != 'Experiment'): if showOkCancel('Confirm','Really delete series point?', parent=self): conditionSet = self.conditionPoint.sampleConditionSet experiments = [e for e in conditionSet.experiments if e in self.expSeries.experiments] for experiment in experiments: self.expSeries.removeExperiment(experiment) for experiment in experiments: for expSeries in experiment.nmrExpSeries: if self.conditionPoint.condition in self.expSeries.conditionNames: break else: continue break else: self.conditionPoint.delete() self.conditionPoint = None def selectConditionPoint(self, object, row, col): if object: self.conditionPoint = object self.updateButtons() def selectExpSeries(self, object, row, col): if object: self.expSeries = object self.checkExperimentConditionsConsistent() self.updateConditions() def checkExperimentConditionsConsistent(self): if self.expSeries.className != 'Experiment': for experiment in self.expSeries.experiments: self.checkAddSampleCondition(experiment) def getUnits(self): units = [] if self.expSeries: if self.expSeries.className == 'Experiment': conditionName = getExperimentSampledDim(self.expSeries).conditionVaried else: conditionName = self.expSeries.conditionNames[0] units = CONDITION_UNITS_DICT.get(conditionName) if not units: units = ['?',] return units def getUnit(self, sampleCondition): index = -1 units = self.getUnits() if units: if sampleCondition: if type(sampleCondition) is type(()): dataDim, index = sampleCondition unit = dataDim.unit else: unit = sampleCondition.unit if unit not in units: unit = units[0] index = units.index(unit) self.unitPulldown.setup(units,units,index) def setUnit(self, obj): name = self.unitPulldown.getObject() if self.conditionPoint: if type(self.conditionPoint) is type(()): dataDim, index = self.conditionPoint dataDim.setUnit(name) else: self.conditionPoint.setUnit(name) def getConditionNames(self): if self.expSeries and (self.expSeries.className == 'Experiment'): names = ['delay time','mixing time','num delays','pulsing frequency','gradient strength'] else: names = CONDITION_UNITS_DICT.keys() names.sort() return names def setConditionName(self, obj): name = self.conditionNamesPulldown.getObject() if self.expSeries: if self.expSeries.className == 'Experiment': dataDim = getExperimentSampledDim(self.expSeries) dataDim.conditionVaried = name else: self.expSeries.setConditionNames([name,]) def getConditionName(self, expSeries): index = 0 names = self.getConditionNames() if names: if expSeries: if expSeries.className == 'Experiment': name = getExperimentSampledDim(expSeries).conditionVaried else: name = expSeries.conditionNames[0] if name: index = names.index(name) else: index = 0 self.conditionNamesPulldown.setup(names,names,index) def getName(self, expSeries): if expSeries : self.nameEntry.set(expSeries.name) def setName(self, event): text = self.nameEntry.get() if text and text != ' ': self.expSeries.setName( text ) def getValue(self, conditionPoint): if conditionPoint: if type(self.conditionPoint) is type(()): dataDim, index = conditionPoint value = dataDim.pointValues[index] else: value = conditionPoint.value self.valueEntry.set(value) def setValue(self, event): value = self.valueEntry.get() if value is not None: if type(self.conditionPoint) is type(()): dataDim, index = self.conditionPoint values = list(dataDim.pointValues) values[index] = value dataDim.setPointValues(values) else: self.conditionPoint.setValue( value ) def getError(self, conditionPoint): if conditionPoint: if type(self.conditionPoint) is type(()): dataDim, index = conditionPoint if index < len(dataDim.pointErrors): error = dataDim.pointValues[index] else: error = 0.0 else: error = conditionPoint.error self.errorEntry.set(error) def setError(self, event): value = self.errorEntry.get() if value is not None: if type(self.conditionPoint) is type(()): dataDim, index = self.conditionPoint pointErrors = dataDim.pointErrors if pointErrors: values = list(pointErrors) else: values = [0.0] * dataDim.numPoints values[index] = value dataDim.setPointErrors(values) else: self.conditionPoint.setError( value ) def getDetails(self, expSeries): if expSeries : self.detailsEntry.set(expSeries.details) def setDetails(self, event): text = self.detailsEntry.get() if text and text != ' ': self.expSeries.setDetails( text ) def addExpSeries(self): expSeries = self.nmrProject.newNmrExpSeries(conditionNames=['delay time',]) def deleteExpSeries(self, *event): if self.expSeries and (self.expSeries.className != 'Experiment'): if showOkCancel('Confirm','Really delete series?', parent=self): self.expSeries.delete() self.expSeries = None self.conditionPoint = None def getExperiments(self): if self.expSeries and (self.expSeries.className == 'Experiment'): return [self.expSeries,] else: return self.nmrProject.sortedExperiments() def getExperiment(self, sampleCondition): index = 0 names = [] if self.conditionPoint and (type(self.conditionPoint) != type(())): index = 0 experiment = self.conditionPoint.parent.findFirstExperiment() experiments = self.getUnusedExperiments() name = experiment.name names = [name] + [e.name for e in experiments] experiments = [experiment,] + experiments self.experimentPulldown.setup(names,experiments,index) def setExperiment(self, obj): experiment = self.experimentPulldown.getObject() if self.conditionPoint and (type(self.conditionPoint) != type(())): conditionSet = getExperimentConditionSet(experiment) if conditionSet is not self.conditionPoint.parent: if experiment not in self.expSeries.experiments: self.expSeries.addExperiment(experiment) unit = self.conditionPoint.unit if not unit: unit = CONDITION_UNITS_DICT[self.expSeries.conditionNames[0]] condition = self.conditionPoint.condition experiments = set(self.expSeries.experiments) for experiment0 in self.conditionPoint.parent.experiments: if experiment0 in experiments: experiments.remove(experiment0) self.expSeries.experiments = experiments value = self.conditionPoint.value error = self.conditionPoint.error self.conditionPoint.delete() self.conditionPoint = conditionSet.findFirstSampleCondition(condition=condition) if self.conditionPoint: self.conditionPoint.unit = unit self.updateAfter() else: self.conditionPoint = conditionSet.newSampleCondition(condition=condition,unit=unit, value=value,error=error) def updateDataDim(self, sampledDataDim): experiment = sampledDataDim.dataSource.experiment self.updateExperiments(experiment) def updateExperiments(self, experiment): experiments = self.getExperiments() names = [e.name for e in experiments] self.experimentPulldown.setup(names, experiments,0) if getExperimentSampledDim(experiment): self.updateAfter() elif self.expSeries: if self.expSeries.className == 'Experiment': if experiment is self.expSeries: self.updateConditionsAfter() elif experiment in self.expSeries.experiments: self.updateConditionsAfter() def updateConditionsAfter(self, sampleCondition=None): if self.waitingConditions: return if sampleCondition: experiments = sampleCondition.sampleConditionSet.experiments for experiment in experiments: if self.expSeries.className == 'Experiment': if experiment is self.expSeries: self.waitingConditions = True self.after_idle(self.updateConditions) break elif experiment in self.expSeries.experiments: self.waitingConditions = True self.after_idle(self.updateConditions) break else: self.waitingConditions = True self.after_idle(self.updateConditions) def updateConditions(self): self.updateButtons() objectList = [] textMatrix = [] colorMatrix = [] nCols = len(self.conditionPointsMatrix.headingList) defaultColors = [None] * nCols if self.expSeries: if self.expSeries.className == 'Experiment': dataDim = getExperimentSampledDim(self.expSeries) analysisDataDim = getAnalysisDataDim(dataDim) conditionVaried = dataDim.conditionVaried expName = self.expSeries.name unit = dataDim.unit pointValues = dataDim.pointValues pointErrors = dataDim.pointErrors refPlane = analysisDataDim.refSamplePlane for i in range(dataDim.numPoints): if i < len(pointErrors): error = pointErrors[i] else: error = None pointText = ':%3d' % (i+1) if i == refPlane: datum = ['* Ref Plane *', expName+pointText, None, None, None] colorMatrix.append(['#f08080'] * nCols) else: datum = [conditionVaried , expName+pointText, pointValues[i], error, unit] colorMatrix.append(defaultColors) textMatrix.append(datum) objectList.append((dataDim, i)) else: condDict = getNmrExpSeriesSampleConditions(self.expSeries) conditionNames = self.expSeries.conditionNames for conditionName in conditionNames: for sampleCondition in condDict.get(conditionName, []): datum = [sampleCondition.condition, ' '.join([e.name for e in sampleCondition.parent.experiments]), sampleCondition.value, sampleCondition.error, sampleCondition.unit] textMatrix.append(datum) objectList.append(sampleCondition) colorMatrix.append(defaultColors) self.conditionPointsMatrix.update(objectList=objectList, colorMatrix=colorMatrix, textMatrix=textMatrix) self.waitingConditions = 0 def updateAfter(self, object=None): if self.waiting: return else: self.waiting = True self.after_idle(self.update) def updateButtons(self): if self.expSeries is None: self.seriesButtons.buttons[1].disable() self.seriesButtons.buttons[2].disable() self.conditionPointsButtons.buttons[0].disable() self.conditionPointsButtons.buttons[1].disable() self.conditionPointsButtons.buttons[2].disable() elif self.expSeries.className == 'Experiment': self.seriesButtons.buttons[1].disable() self.seriesButtons.buttons[2].disable() self.conditionPointsButtons.buttons[0].disable() self.conditionPointsButtons.buttons[1].disable() self.conditionPointsButtons.buttons[2].enable() else: self.seriesButtons.buttons[1].enable() self.seriesButtons.buttons[2].enable() self.conditionPointsButtons.buttons[0].enable() self.conditionPointsButtons.buttons[2].disable() if self.conditionPoint is None: self.conditionPointsButtons.buttons[1].disable() else: self.conditionPointsButtons.buttons[1].enable() def update(self): self.updateButtons() objectList = [] textMatrix = [] for experiment in getSampledDimExperiments(self.nmrProject): getExperimentConditionSet(experiment) sampledDim = getExperimentSampledDim(experiment) datum = [None, experiment.name, 1, sampledDim.conditionVaried, sampledDim.numPoints] textMatrix.append(datum) objectList.append(experiment) for expSeries in self.nmrProject.sortedNmrExpSeries(): experiments = expSeries.experiments conditionSets = len([e.sampleConditionSet for e in experiments if e.sampleConditionSet]) datum = [expSeries.serial, expSeries.name or ' ', len(experiments), ','.join(expSeries.conditionNames), conditionSets] textMatrix.append(datum) objectList.append(expSeries) self.seriesMatrix.update(objectList=objectList, textMatrix=textMatrix) self.updateConditions() self.waiting = False def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self)
class NewWindowPopup(BasePopup): """ **Create New Windows to Display Spectra** This tool is used to make new windows for the graphical display of spectra, which will usually be as contours. It is notable that some spectrum windows will be made automatically when spectra are loaded if there is no existing appropriate window to display a spectrum. However, making new spectrum windows allows the user to specialise different windows for different tasks and gives complete freedom as to which types of axis go in which direction. For example the user may wish to make a new window so that a spectrum can be viewed from an orthogonal, rotated aspect. A new spectrum window is made by first considering whether it is similar to any existing windows. If so, then the user selects the appropriate template window to base the new one upon. The user then chooses a name for the window via the "New window name" field, although the name may be changed after the window is created. Usually the user does not need to consider the "Strips" section, but if required the new window can be created with starting strips and orthogonal sub-divisions (although these are not permanent). After setting the required axes and spectrum visibility, as described below, the user clicks on [Create Window!] to actually make the new spectrum display window. **Axes** The number and type of axes for the new window are chosen using the pulldown menus in the "Axes" section. The idea is that the user chooses which NMR isotopes should appear on the X, Y, & Z axes. Naturally, X and Y axes must always be set to something, to represent the plane of the screen, but the Z axes are optional. If not required, a Z axis may be set to "None" to indicate that it will not be used. Up to four Z axes may be specified, labelled as "z1", "z2" etc., and these represent extra dimensions orthogonal to the plane of the screen, which are often conceptualised as depth axes. It should be noted that the Y axis type may be set to "value", which refers to a spectrum intensity axis, rather than an NMR isotope axis. Setting the Y axis to "value" and only having the X axis set to an isotope is used to create windows that can show 1D spectra. Such value axes can also be used for 2D and higher dimensionality spectra, to show the data as an intensity graph (a "slice") rather than as contours. **Spectra** The lower "Viewed Spectra" section lists all of the spectra within the project that may be shown by a window with the selected axes. All spectra with isotopes in their data dimensions that match the isotope types of the window axes can potentially be displayed. This system also allows for displayed spectra to have fewer dimensions than the axes has windows, as long as at least the X and Y axes are present. For example a 2D H-N spectrum can be shown in a 3D H-N-H window, but not a 3D H-H-N. For spectra that have more than one data dimension of the same isotope, then which data dimension goes with which window axis is not always known to Analysis. Where there is ambiguity, this system will simply map the spectrum data dimensions in order to the next matching window axis. If this mapping turns out to be wrong, then it may be changed at any time via the main _Windows settings; toggling the "Dim. Mapping" of the "Spectrum & Peak List Mappings" tab. For the spectra listed in the lower table, which may be placed in the new window, the user has control over whether the spectra actually will appear. Firstly the user can change the "Visible?" column, either via a double-click or by using the appropriate lower buttons. By default spectra are set as not being visible in new windows, and the user toggles the ones that should be seen to "Yes". This basic spectrum visibility can readily be changed by the toggle buttons that appear in the "toolbar" at the top of the spectrum display, so the "Visible?" setting here is only about what initially appears. The "In Toolbar?" setting of a spectrum is a way of allowing the user to state that a spectrum should never appear in the window, and not even allow it to be toggled on later via the toolbar at the top of the windows. This is a way of reducing clutter, and allows certain windows to be used for particular subsets of spectra. For example the user may wish to put the spectra for a temperature series in one window, but not in other windows used for resonance assignment where they would get in the way. The "In Toolbar" setting can be changed after a window has been made, but only via the main Windows_ settings popup. .. _Windows: EditWindowPopup.html """ def __init__(self, parent, *args, **kw): self.visibleSpectra = parent.visibleSpectra self.toolbarSpectra = parent.toolbarSpectra self.waiting = False self.window = None BasePopup.__init__(self, parent=parent, title='Window : New Window', **kw) def body(self, guiFrame): guiFrame.grid_columnconfigure(2, weight=1) row = 0 label = Label(guiFrame, text='Template window: ', grid=(row, 0)) tipText = 'Selects which window to use as the basis for making a new spectrum window; sets the axis types accordingly' self.window_list = PulldownList(guiFrame, grid=(row, 1), callback=self.setAxisTypes, tipText=tipText) frame = LabelFrame(guiFrame, text='Strips', grid=(row, 2), gridSpan=(2, 1)) buttons = UtilityButtonList(guiFrame, doClone=False, helpUrl=self.help_url, grid=(row, 3)) row += 1 label = Label(guiFrame, text='New window name: ', grid=(row, 0)) tipText = 'A short name to identify the spectrum window, which will appear in the graphical interface' self.nameEntry = Entry(guiFrame, width=16, grid=(row, 1), tipText=tipText) row += 1 label = Label(frame, text='Columns: ', grid=(0, 0)) tipText = 'The number of vertical strips/dividers to initially make in the spectrum window' self.cols_menu = PulldownList(frame, objects=STRIP_NUMS, grid=(0, 1), texts=[str(x) for x in STRIP_NUMS], tipText=tipText) label = Label(frame, text='Rows: ', grid=(0, 2)) tipText = 'The number of horizontal strips/dividers to initially make in the spectrum window' self.rows_menu = PulldownList(frame, objects=STRIP_NUMS, grid=(0, 3), texts=[str(x) for x in STRIP_NUMS], tipText=tipText) row += 1 div = LabelDivider(guiFrame, text='Axes', grid=(row, 0), gridSpan=(1, 4)) row += 1 self.axis_lists = {} frame = Frame(guiFrame, grid=(row, 0), gridSpan=(1, 4)) col = 0 self.axisTypes = {} self.axisTypesIncludeNone = {} for label in AXIS_LABELS: self.axisTypes[label] = None w = Label(frame, text=' ' + label) w.grid(row=0, column=col, sticky='w') col += 1 if label in ('x', 'y'): includeNone = False tipText = 'Sets the kind of measurement (typically ppm for a given isotope) that will be used along the window %s axis' % label else: includeNone = True tipText = 'Where required, sets the kind of measurement (typically ppm for a given isotope) that will be used along the window %s axis' % label self.axisTypesIncludeNone[label] = includeNone getAxisTypes = lambda label=label: self.getAxisTypes(label) callback = lambda axisType, label=label: self.changedAxisType( label, axisType) self.axis_lists[label] = PulldownList(frame, callback=callback, tipText=tipText) self.axis_lists[label].grid(row=0, column=col, sticky='w') col += 1 frame.grid_columnconfigure(col, weight=1) row += 1 div = LabelDivider(guiFrame, text='Viewed Spectra', grid=(row, 0), gridSpan=(1, 4)) row += 1 guiFrame.grid_rowconfigure(row, weight=1) editWidgets = [None, None, None, None] editGetCallbacks = [None, self.toggleVisible, self.toggleToolbar, None] editSetCallbacks = [None, None, None, None] tipTexts = [ 'The "experiment:spectrum" name for the spectrum that may be viewed in the new window, given the axis selections', 'Sets whether the spectrum contours will be visible in the new window', 'Sets whether the spectrum appears at all in the window; if not in the toolbar it cannot be displayed', 'The number of peak lists the spectrum contains' ] headingList = ['Spectrum', 'Visible?', 'In Toolbar?', 'Peak Lists'] self.scrolledMatrix = ScrolledMatrix(guiFrame, headingList=headingList, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, multiSelect=True, grid=(row, 0), gridSpan=(1, 4), tipTexts=tipTexts) row += 1 tipTexts = [ 'Creates a new spectrum window with the specified parameters', 'Sets the contours of the selected spectra to be visible when the new window is made', 'Sets the contours of the selected spectra to not be displayed when the new window is made', 'Sets the selected spectra as absent from the window toolbar, and thus not displayable at all' ] texts = [ 'Create Window!', 'Selected\nVisible', 'Selected\nNot Visible', 'Selected\nNot In Toolbar' ] commands = [ self.ok, self.setSelectedDisplay, self.setSelectedHide, self.setSelectedAbsent ] buttonList = ButtonList(guiFrame, texts=texts, grid=(row, 0), commands=commands, gridSpan=(1, 4), tipTexts=tipTexts) buttonList.buttons[0].config(bg='#B0FFB0') self.updateAxisTypes() self.updateWindow() self.updateWindowName() self.administerNotifiers(self.registerNotify) self.updateAfter() def administerNotifiers(self, notifyFunc): self.registerNotify(self.updateWindowName, 'ccpnmr.Analysis.SpectrumWindow', '__init__') for clazz in ('ccp.nmr.Nmr.Experiment', 'ccp.nmr.Nmr.DataSource'): for func in ('__init__', 'delete', 'setName'): notifyFunc(self.updateAfter, clazz, func) for func in ('__init__', 'delete'): notifyFunc(self.updateAfter, 'ccp.nmr.Nmr.PeakList', func) for func in ('__init__', 'delete', 'setName', 'addSpectrumWindowGroup', 'removeSpectrumWindowGroup', 'setSpectrumWindowGroups'): notifyFunc(self.updateWindow, 'ccpnmr.Analysis.SpectrumWindow', func) for func in ('addSpectrumWindow', 'removeSpectrumWindow', 'setSpectrumWindows'): notifyFunc(self.updateWindow, 'ccpnmr.Analysis.SpectrumWindowGroup', func) for func in ('__init__', 'delete', 'setName'): notifyFunc(self.updateAxisTypes, 'ccpnmr.Analysis.AxisType', func) # Set visible contours, on commit, according to selection # Get hold of spectrumWindowView ASAP def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self) def getSpectra(self): spectrumIsotopes = {} spectra = [] for experiment in self.nmrProject.sortedExperiments(): name = experiment.name for spectrum in experiment.sortedDataSources(): spectrumIsotopes[spectrum] = [] spectra.append(['%s:%s' % (name, spectrum.name), spectrum]) for dataDim in spectrum.dataDims: dimTypes = [] if dataDim.className != 'SampledDataDim': for dataDimRef in dataDim.dataDimRefs: expDimRef = dataDimRef.expDimRef isotopes = set() for isotopeCode in expDimRef.isotopeCodes: isotopes.add(isotopeCode) dimTypes.append( (expDimRef.measurementType.lower(), isotopes)) else: dimTypes.append('sampled') spectrumIsotopes[spectrum].append(dimTypes) axisIsotopes = {} for label in AXIS_LABELS: if label not in ('x', 'y'): if self.axis_lists[label].getSelectedIndex() == 0: continue axisType = self.axis_lists[label].getObject() axisIsotopes[label] = (axisType.measurementType.lower(), set(axisType.isotopeCodes)) spectraSel = [] axes = axisIsotopes.keys() axes.sort() for name, spectrum in spectra: dimIsotopes = spectrumIsotopes[spectrum] for label in axes: mType, selected = axisIsotopes[label] if label == 'y' and mType == 'none': continue # value axis for i, dimTypes in enumerate(dimIsotopes): for dimType in dimTypes: if dimType == 'sampled': if label != 'z1': continue axisType = self.axis_lists[label].getObject() if axisType.name != 'sampled': continue dimIsotopes.pop(i) break else: measurementType, isotopes = dimType if (mType == measurementType) and (selected <= isotopes): dimIsotopes.pop(i) break else: continue break else: if label in ('x', 'y'): break else: if not dimIsotopes: spectraSel.append([name, spectrum]) return spectraSel def setSelectedAbsent(self): for spectrum in self.scrolledMatrix.currentObjects: self.visibleSpectra[spectrum] = False self.toolbarSpectra[spectrum] = False self.updateAfter() def setSelectedDisplay(self): for spectrum in self.scrolledMatrix.currentObjects: self.visibleSpectra[spectrum] = True self.toolbarSpectra[spectrum] = True self.updateAfter() def setSelectedHide(self): for spectrum in self.scrolledMatrix.currentObjects: self.visibleSpectra[spectrum] = False self.toolbarSpectra[spectrum] = True self.updateAfter() def toggleToolbar(self, spectrum): boolean = not self.toolbarSpectra.get(spectrum, True) self.toolbarSpectra[spectrum] = boolean if boolean is False: self.visibleSpectra[spectrum] = False self.updateAfter() def toggleVisible(self, spectrum): boolean = not self.visibleSpectra.get(spectrum, False) self.visibleSpectra[spectrum] = boolean if boolean: if not self.toolbarSpectra.get(spectrum, True): self.toolbarSpectra[spectrum] = True self.updateAfter() def updateAfter(self, object=None): if self.waiting: return else: self.waiting = True self.after_idle(self.update) def update(self): for spectrum in self.visibleSpectra.keys(): if spectrum.isDeleted: del self.visibleSpectra[spectrum] textMatrix = [] objectList = [] colorMatrix = [] for name, spectrum in self.getSpectra(): colours = [None, None, None, None] if self.visibleSpectra.get(spectrum): colours[0] = '#60F060' isVisible = 'Yes' self.visibleSpectra[ spectrum] = True # do not need this but play safe in case above if changed else: isVisible = 'No' self.visibleSpectra[spectrum] = False if self.toolbarSpectra.get(spectrum, True): inToolbar = 'Yes' self.toolbarSpectra[spectrum] = True else: colours[0] = '#600000' inToolbar = 'No' self.toolbarSpectra[spectrum] = False datum = [ name, isVisible, inToolbar, ','.join(['%d' % pl.serial for pl in spectrum.peakLists]) ] textMatrix.append(datum) objectList.append(spectrum) colorMatrix.append(colours) self.scrolledMatrix.update(objectList=objectList, textMatrix=textMatrix, colorMatrix=colorMatrix) self.waiting = False def updateAxisTypes(self, *extra): for label in AXIS_LABELS: names = [] objects = [] includeNone = self.axisTypesIncludeNone[label] if includeNone: names.append('None') objects.append(None) axisType = self.axisTypes[label] axisTypes = self.getAxisTypes(label) objects.extend(axisTypes) if axisTypes: if axisType not in objects: axisType = objects[0] index = objects.index(axisType) names.extend([x.name for x in axisTypes]) else: index = 0 self.axis_lists[label].setup(names, objects, index) self.changedAxisType(label, axisType) def changedAxisType(self, label, axisType): if axisType is not self.axisTypes[label]: self.axisTypes[label] = axisType self.updateAfter() def updateWindow(self, *extra): window = self.window windows = self.parent.getWindows() if windows: if window not in windows: window = windows[0] index = windows.index(window) names = [x.name for x in windows] else: index = 0 names = [] self.window_list.setup(names, windows, index) self.setAxisTypes(window) def updateWindowName(self, *extra): self.nameEntry.set(WindowBasic.defaultWindowName(self.project)) def getAxisTypes(self, label): axisTypes = self.parent.getAxisTypes() if label == 'z1': axisTypes = [ axisType for axisType in axisTypes if axisType.name == 'sampled' or not axisType.isSampled ] else: axisTypes = [ axisType for axisType in axisTypes if not axisType.isSampled ] if label != 'y': axisTypes = [ at for at in axisTypes if at.name != WindowBasic.VALUE_AXIS_NAME ] return axisTypes def setAxisTypes(self, window): project = self.project if not project: return if window is self.window: return self.window = window if window: windowPanes = window.sortedSpectrumWindowPanes() if windowPanes: # might be empty because notifier called before windowPanes set up windowPane = windowPanes[0] for label in AXIS_LABELS: axisPanel = windowPane.findFirstAxisPanel(label=label) if axisPanel and axisPanel.panelType \ and axisPanel.panelType.axisType: self.axis_lists[label].setSelected( axisPanel.panelType.axisType) elif label not in ('x', 'y'): self.axis_lists[label].setIndex(0) self.updateAfter() def apply(self): project = self.project if not project: return False name = self.nameEntry.get().strip() if not name: showError('No name', 'Need to enter name', parent=self) return False names = [ window.name for window in self.analysisProject.spectrumWindows ] if (name in names): showError('Repeated name', 'Name already used', parent=self) return False axisTypes = [] for label in AXIS_LABELS: if label not in ('x', 'y'): if self.axis_lists[label].getSelectedIndex() == 0: continue axisType = self.axis_lists[label].getObject() axisTypes.append(axisType) ncols = self.cols_menu.getObject() nrows = self.rows_menu.getObject() window = WindowBasic.createSpectrumWindow(project, name, [ axisTypes, ], ncols=ncols, nrows=nrows) return True
class PrintWindowPopup(BasePopup): """ **Print Window to Output File** The purpose of this dialog is to allow the saving of the drawing of one of the spectrum windows to a file, in one of the following formats: PostScript (PS), Encapsulated PostScript (EPS) or Portable Document Format (PDF). The one window that is being printed out is specified at the top. There are four tabs. The first one, Options, is the most important. In particular, it is used to specify the File name. At its simplest to print out a window you just need to specify the File name, and then click "Save Print File". But it is likely you will at the very least want to change some of the settings in the Options tab. You can specify a Title and a label for the X axis and/or Y axis. This tab is also used to specify the Paper size (default A4), the Orientation of the paper (default Portrait), whether the printout is Color or Black and white (the Style, default Color), and what the Format is (PS, EPS or PDF, default PS). The ticks for the rulers can be chosen to be Inside or Outside the main frame and can be in any combination of the Top, Bottom, Left or Right side of the main frame. The Tick Font includes the option of not printing the tick labels at all. The Tick spacing between the major and minor ticks can be set automatically (so the program determines it) or manually. For the latter the user has to specify the Major and Minor spacings in terms of the unit of the display (normally ppm), and also the number of decimal places for the Tick labels. How the main frame fits into the paper is determined by the Scaling option. The Percentage option just means that the main frame is scaled by that amount relative to the biggest size it could be and still fit on the paper. The remaining options are if you want to specify the cms or inches per unit (normally ppm), or the inverse of these. In this case it could be the case that the main frame actually exceeds the size of the paper. You can also include the Time and Date and/or the File Name in the printout, and you can specify the font used for this. The same font is used for the Title and X and Y axis labels, except that the Title font is 6 pts bigger. Finally, you can also set the linewidth, in points (the default is 0.1). The other three tabs provide fine tuning of what is output. In many cases they can be ignored. The Spectra tab lets you choose settings for which of the window's spectra are drawn, in terms of both the positive and negative contours. This is independent of what is actually drawn on the screen. But you need to check the "Use below settings when printing" checkbutton if you want the values specified here to be used rather than the screen settings. The values in the table are initially set to be the screen values but afterwards can only be changed manually. Clicking on the "Reset Selected" button changes the values in the table to the current screen ones. The Peak Lists tab is similar, except it applies to the peak lists in the window rather than the spectra. Again, if you want the values in the table to be used then you need to check the "Use below settings when printing" checkbutton. The Region tab is for specifying the region for the x, y and orthogonal axes, if you do not want to use the regions as seen in the window on the screen. Again, you have to check "Use override region when printing" checkbutton to actually have the values in the table be used in the printout. By default, the override region is set to the current window region if it is not set already, otherwise it is left to the previous value unless you click the "Set Region from Window" or the "Set Width from Window" or the "Set Center from Window" buttons. And the override region can be specified either using the min and max values of the region, or the center and width. """ def __init__(self, parent, *args, **kw): self.waiting = False title='Window : Print Window' BasePopup.__init__(self, parent=parent, title=title, **kw) def body(self, guiFrame): self.geometry('600x350') project = self.project analysisProject = self.analysisProject guiFrame.grid_columnconfigure(1, weight=1) row = 0 frame = Frame(guiFrame, grid=(0,0)) label = Label(frame, text=' Window:', grid=(0,0)) self.windowPulldown = PulldownList(frame, grid=(0,1), tipText='The window that will be printed out', callback=self.selectWindow) tipTexts = ['For the window pulldown, show all of the spectrum windows in the current project, irrespective of the active group', 'For the window pulldown, show only spectrum windows that are in the currently active window group'] self.whichWindows = RadioButtons(frame, grid=(0,2), entries=ACTIVE_OPTIONS, tipTexts=tipTexts, select_callback=self.updateWindows) texts = [ 'Save Print File' ] tipTexts = [ 'Save the printout to the specified file' ] commands = [ self.saveFile ] buttons = UtilityButtonList(guiFrame, helpUrl=self.help_url, grid=(row,2), commands=commands, texts=texts, tipTexts=tipTexts) self.buttons = buttons buttons.buttons[0].config(bg='#B0FFB0') row += 1 guiFrame.grid_rowconfigure(row, weight=1) options = ['Options', 'Spectra', 'Peak Lists', 'Region'] tipTexts = ['Optional settings for spectra', 'Optional settings for peak lists', 'Optional settings for the region'] tabbedFrame = TabbedFrame(guiFrame, options=options, tipTexts=tipTexts) tabbedFrame.grid(row=row, column=0, columnspan=3, sticky='nsew') self.tabbedFrame = tabbedFrame optionFrame, spectrumFrame, peakListFrame, regionFrame = tabbedFrame.frames optionFrame.expandGrid(0, 0) getOption = lambda key, defaultValue: PrintBasic.getPrintOption(analysisProject, key, defaultValue) setOption = lambda key, value: PrintBasic.setPrintOption(analysisProject, key, value) self.printFrame = PrintFrame(optionFrame, getOption=getOption, grid=(0,0), gridSpan=(1,1), setOption=setOption, haveTicks=True, doOutlineBox=False) spectrumFrame.expandGrid(0, 0) frame = Frame(spectrumFrame, grid=(0,0), gridSpan=(1,1)) frame.expandGrid(1,0) self.overrideSpectrum = CheckButton(frame, text='Use below settings when printing', tipText='Use below settings when printing instead of the window values', grid=(0,0), sticky='w') tipText = 'Change the settings of the selected spectra back to their window values' button = Button(frame, text='Reset Selected', tipText=tipText, command=self.resetSelected, grid=(0,1), sticky='e') self.posColorPulldown = PulldownList(self, callback=self.setPosColor) self.negColorPulldown = PulldownList(self, callback=self.setNegColor) headings = ['Spectrum', 'Pos. Contours\nDrawn', 'Neg. Contours\nDrawn', 'Positive\nColours', 'Negative\nColours'] tipTexts = ['Spectrum in window', 'Whether the positive contours should be drawn', 'Whether the negative contours should be drawn', 'Colour scheme for positive contours (can be a single colour)', 'Colour scheme for negative contours (can be a single colour)'] editWidgets = [ None, None, None, self.posColorPulldown, self.negColorPulldown] editGetCallbacks = [ None, self.togglePos, self.toggleNeg, self.getPosColor, self.getNegColor] editSetCallbacks = [ None, None, None, self.setPosColor, self.setNegColor] self.spectrumTable = ScrolledMatrix(frame, headingList=headings, tipTexts=tipTexts, multiSelect=True, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, grid=(1,0), gridSpan=(1,2)) peakListFrame.expandGrid(0, 0) frame = Frame(peakListFrame, grid=(0,0), gridSpan=(1,3)) frame.expandGrid(1,0) self.overridePeakList = CheckButton(frame, text='Use below settings when printing', tipText='Use below settings when printing instead of the window values', grid=(0,0)) tipText = 'Change the settings of the selected peak lists back to their window values' button = Button(frame, text='Reset Selected', tipText=tipText, command=self.resetSelected, grid=(0,1), sticky='e') colors = Color.standardColors self.peakColorPulldown = PulldownList(self, callback=self.setPeakColor, texts=[c.name for c in colors], objects=[c.hex for c in colors], colors=[c.hex for c in colors]) headings = [ 'Peak List', 'Symbols Drawn', 'Peak Font', 'Peak Colour'] self.fontMenu = FontList(self, mode='Print', extraTexts=[no_peak_text]) editWidgets = [ None, None, self.fontMenu, self.peakColorPulldown] editGetCallbacks = [ None, self.togglePeaks, self.getPeakFont, self.getPeakColor ] editSetCallbacks = [ None, None, self.setPeakFont, self.setPeakColor ] self.peakListTable = ScrolledMatrix(frame, headingList=headings, multiSelect=True, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, grid=(1,0), gridSpan=(1,2)) regionFrame.expandGrid(0, 0) frame = Frame(regionFrame, grid=(0,0), gridSpan=(1,3)) frame.expandGrid(3,0) tipText = 'Use the specified override region when printing rather than the window values' self.overrideButton = CheckButton(frame, text='Use override region when printing', tipText=tipText, callback=self.toggledOverride, grid=(0,0)) tipTexts = ('Use min and max to specify override region', 'Use center and width to specify override region') self.use_entry = USE_ENTRIES[0] self.useButtons = RadioButtons(frame, entries=USE_ENTRIES, tipTexts=tipTexts, select_callback=self.changedUseEntry, grid=(1,0)) texts = ('Set Region from Window', 'Set Center from Window', 'Set Width from Window') tipTexts = ('Set the override region to be the current window region', 'Set the center of the override region to be the center of the current window region', 'Set the width of the override region to be the width of the current window region') commands = (self.setRegionFromWindow, self.setCenterFromWindow, self.setWidthFromWindow) self.setRegionButton = ButtonList(frame, texts=texts, tipTexts=tipTexts, commands=commands, grid=(2,0)) self.minRegionWidget = FloatEntry(self, returnCallback=self.setMinRegion, width=10) self.maxRegionWidget = FloatEntry(self, returnCallback=self.setMaxRegion, width=10) headings = MIN_MAX_HEADINGS editWidgets = [ None, None, self.minRegionWidget, self.maxRegionWidget ] editGetCallbacks = [ None, None, self.getMinRegion, self.getMaxRegion ] editSetCallbacks = [ None, None, self.setMinRegion, self.setMaxRegion ] self.regionTable = RegionScrolledMatrix(frame, headingList=headings, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, grid=(3,0)) self.updateWindows() self.updateAfter() self.administerNotifiers(self.registerNotify) def administerNotifiers(self, notifyFunc): for func in ('__init__', 'delete', 'setOverrideRegion'): notifyFunc(self.updateAfter, 'ccpnmr.Analysis.AxisRegion', func) notifyFunc(self.updateAfter, 'ccpnmr.Analysis.SpectrumWindow', 'setUseOverrideRegion') for func in ('__init__', 'delete', 'setName'): notifyFunc(self.updateWindows, 'ccpnmr.Analysis.SpectrumWindow', func) notifyFunc(self.updateWindows, 'ccpnmr.Analysis.SpectrumWindowPane', func) for func in ('addSpectrumWindow', 'removeSpectrumWindow'): notifyFunc(self.updateWindows, 'ccpnmr.Analysis.SpectrumWindowGroup', func) def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self) def changedUseEntry(self, entry): self.use_entry = entry self.updateRegionTable() def resetSpectrum(self): spectrumWindowViews = self.spectrumTable.currentObjects analysisSpectra = set() for spectrumWindowView in spectrumWindowViews: PrintBasic.setPrintOption(spectrumWindowView, 'PositiveOn', spectrumWindowView.isPosVisible) PrintBasic.setPrintOption(spectrumWindowView, 'NegativeOn', spectrumWindowView.isNegVisible) analysisSpectra.add(spectrumWindowView.analysisSpectrum) for analysisSpectrum in analysisSpectra: PrintBasic.setPrintOption(analysisSpectrum, 'PositiveColors', analysisSpectrum.posColors) PrintBasic.setPrintOption(analysisSpectrum, 'NegativeColors', analysisSpectrum.negColors) self.updateAfter() def resetPeakList(self): windowPeakLists = self.peakListTable.currentObjects analysisPeakLists = set() for windowPeakList in windowPeakLists: PrintBasic.setPrintOption(windowPeakList, 'PeaksOn', windowPeakList.isSymbolDrawn) PrintBasic.setPrintOption(windowPeakList, 'PeakFont', windowPeakList.spectrumWindowView.analysisSpectrum.font) analysisPeakLists.add(windowPeakList.analysisPeakList) for analysisPeakList in analysisPeakLists: PrintBasic.setPrintOption(analysisPeakList, 'PeakColor', analysisPeakList.symbolColor) self.updateAfter() def resetSelected(self): n = self.tabbedFrame.selected if n == 0: self.resetSpectrum() elif n == 1: self.resetPeakList() def togglePos(self, spectrumWindowView): PrintBasic.setPrintOption(spectrumWindowView, 'PositiveOn', not PrintBasic.getPrintOption(spectrumWindowView, 'PositiveOn', defaultValue=spectrumWindowView.isPosVisible)) self.updateAfter() def toggleNeg(self, spectrumWindowView): PrintBasic.setPrintOption(spectrumWindowView, 'NegativeOn', not PrintBasic.getPrintOption(spectrumWindowView, 'NegativeOn', defaultValue=spectrumWindowView.isNegVisible)) self.updateAfter() def getPosColor(self, spectrumWindowView): schemes = getHueSortedColorSchemes(self.analysisProfile) names = [s.name for s in schemes] colors = [list(s.colors) for s in schemes] index = 0 analysisSpec = spectrumWindowView.analysisSpectrum posColors = list(PrintBasic.getPrintOption(analysisSpec, 'PositiveColors', defaultValue=analysisSpec.posColors)) if posColors in colors: index = colors.index(posColors) self.posColorPulldown.setup(names, schemes, index, colors) def setPosColor(self, *extra): spectrumWindowView = self.spectrumTable.currentObject if spectrumWindowView: analysisSpec = spectrumWindowView.analysisSpectrum PrintBasic.setPrintOption(analysisSpec, 'PositiveColors', self.posColorPulldown.getObject().colors) self.updateSpectrumTable() def getNegColor(self, spectrumWindowView): schemes = getHueSortedColorSchemes(self.analysisProfile) names = [s.name for s in schemes] colors = [list(s.colors) for s in schemes] index = 0 analysisSpec = spectrumWindowView.analysisSpectrum negColors = list(PrintBasic.getPrintOption(analysisSpec, 'NegativeColors', defaultValue=analysisSpec.negColors)) if negColors in colors: index = colors.index(negColors) self.negColorPulldown.setup(names, schemes, index, colors) def setNegColor(self, *extra): spectrumWindowView = self.spectrumTable.currentObject if spectrumWindowView: analysisSpec = spectrumWindowView.analysisSpectrum PrintBasic.setPrintOption(analysisSpec, 'NegativeColors', self.negColorPulldown.getObject().colors) self.updateSpectrumTable() def getPeakColor(self, windowPeakList): color = windowPeakList.analysisPeakList.symbolColor self.peakColorPulldown.set(color) def setPeakColor(self, *extra): windowPeakList = self.peakListTable.currentObject if windowPeakList: color = self.peakColorPulldown.getObject() scheme = self.analysisProfile.findFirstColorScheme(colors=(color,)) if scheme: color = scheme.name analysisPeakList = windowPeakList.analysisPeakList PrintBasic.setPrintOption(analysisPeakList, 'PeakColor', color) self.updatePeakListTable() def togglePeaks(self, windowPeakList): PrintBasic.setPrintOption(windowPeakList, 'PeaksOn', not PrintBasic.getPrintOption(windowPeakList, 'PeaksOn', defaultValue=windowPeakList.isSymbolDrawn)) self.updateAfter() def getPeakFont(self, windowPeakList): if windowPeakList.isAnnotationDrawn: default = windowPeakList.analysisPeakList.analysisSpectrum.font else: default = no_peak_text font = PrintBasic.getPrintOption(windowPeakList, 'PeakFont', defaultValue=default) self.fontMenu.set(font) def setPeakFont(self, windowPeakList): PrintBasic.setPrintOption(windowPeakList, 'PeakFont', self.fontMenu.getText()) self.updateAfter() def updateWindows(self, obj=None): useAll = (self.whichWindows.get() == ACTIVE_OPTIONS[0]) index = 0 windowPane = self.windowPulldown.getObject() windowPanes = [] names = [] if not windowPane: application = self.project.application name = application.getValue(self.analysisProject, keyword='printWindowWindow') if useAll: window = self.analysisProject.findFirstSpectrumWindow(name=name) if window: windowPane = window.findFirstSpectrumWindowPane() else: for window in self.analysisProject.sortedSpectrumWindows(): if isActiveWindow(window): windowPane = window.findFirstSpectrumWindowPane() break else: window = None for window in self.analysisProject.sortedSpectrumWindows(): if useAll or isActiveWindow(window): for windowPane0 in window.sortedSpectrumWindowPanes(): windowPanes.append(windowPane0) names.append(getWindowPaneName(windowPane0)) if windowPanes: if windowPane not in windowPanes: windowPane = windowPanes[0] index = windowPanes.index(windowPane) else: windowPane = None self.selectWindow(windowPane) self.windowPulldown.setup(names, windowPanes, index) def saveFile(self): windowPane = self.windowPulldown.getObject() if not windowPane: return axisPanels = windowPane.sortedAxisPanels() aspectRatio = self.getPrintAspectRatio() pixelWidth = self.totalSize(axisPanels[0]) pixelHeight = aspectRatio*self.totalSize(axisPanels[1]) unitWidth = self.totalOverrideRegion(axisPanels[0]) unitHeight = self.totalOverrideRegion(axisPanels[1]) printFrame = self.printFrame isOverrideSpectrumSelected = self.overrideSpectrum.isSelected() if isOverrideSpectrumSelected: spectrumWindowViews = self.spectrumTable.objectList # alternatively, spectrumWindowViews = windowPane.spectrumWindowViews analysisSpectra = set() for spectrumWindowView in spectrumWindowViews: spectrumWindowView.printPositive = PrintBasic.getPrintOption(spectrumWindowView, 'PositiveOn', spectrumWindowView.isPosVisible) spectrumWindowView.printNegative = PrintBasic.getPrintOption(spectrumWindowView, 'NegativeOn', spectrumWindowView.isNegVisible) analysisSpectra.add(spectrumWindowView.analysisSpectrum) for analysisSpectrum in analysisSpectra: analysisSpectrum.printPositiveColors = PrintBasic.getPrintOption(analysisSpectrum, 'PositiveColors', analysisSpectrum.posColors) analysisSpectrum.printNegativeColors = PrintBasic.getPrintOption(analysisSpectrum, 'NegativeColors', analysisSpectrum.negColors) isOverridePeakListSelected = self.overridePeakList.isSelected() if isOverridePeakListSelected: windowPeakLists = self.peakListTable.objectList analysisPeakLists = set() for windowPeakList in windowPeakLists: windowPeakList.printPeaks = PrintBasic.getPrintOption(windowPeakList, 'PeaksOn', windowPeakList.isSymbolDrawn) if windowPeakList.isAnnotationDrawn: default = windowPeakList.analysisPeakList.analysisSpectrum.font else: default = no_peak_text windowPeakList.printFont = PrintBasic.getPrintOption(windowPeakList, 'PeakFont', default) analysisPeakLists.add(windowPeakList.analysisPeakList) for analysisPeakList in analysisPeakLists: analysisPeakList.printColor = PrintBasic.getPrintOption(analysisPeakList, 'PeakColor', analysisPeakList.symbolColor) xrr = axisPanels[0].findFirstAxisRegion().region dxx = abs(xrr[0]-xrr[1]) yrr = axisPanels[1].findFirstAxisRegion().region dyy = abs(yrr[0]-yrr[1]) try: outputHandler = printFrame.getOutputHandler(pixelWidth, pixelHeight, unitWidth, unitHeight, fonts=printNames) if not outputHandler: return analysisProject = self.analysisProject major_minor_dict = {} spacing_choice = PrintBasic.getPrintOption(analysisProject, 'SpacingChoice', spacing_choices[0]) if spacing_choice != spacing_choices[0]: for attr in ('XMajor', 'XMinor', 'XDecimal', 'YMajor', 'YMinor', 'YDecimal',): val = PrintBasic.getPrintOption(analysisProject, attr, None) if val is not None: major_minor_dict[attr] = val tick_length_choice = PrintBasic.getPrintOption(analysisProject, 'TickLengthChoice', tick_length_choices[0]) if tick_length_choice != tick_length_choices[0]: for attr in ('TickMajor', 'TickMinor'): val = PrintBasic.getPrintOption(analysisProject, attr, None) if val is not None: major_minor_dict[attr] = val windowDraw = WindowDraw(self.parent, windowPane) PrintBasic.printWindow(windowDraw, outputHandler, printFrame.tick_location, printFrame.tick_placement, aspectRatio, printFrame.tick_font, major_minor_dict) msg = 'Saved to file "%s"' % printFrame.file_name showInfo('Success', msg, parent=self) except IOError, e: showError('IO Error', str(e), parent=self) if isOverrideSpectrumSelected: analysisSpectra = set() for spectrumWindowView in spectrumWindowViews: del spectrumWindowView.printPositive del spectrumWindowView.printNegative analysisSpectra.add(spectrumWindowView.analysisSpectrum) for analysisSpectrum in analysisSpectra: del analysisSpectrum.printPositiveColors del analysisSpectrum.printNegativeColors if isOverridePeakListSelected: analysisPeakLists = set() for windowPeakList in windowPeakLists: del windowPeakList.printPeaks del windowPeakList.printFont analysisPeakLists.add(windowPeakList.analysisPeakList) for analysisPeakList in analysisPeakLists: del analysisPeakList.printColor
class CreatePanelTypePopup(BasePopup): def __init__(self, parent, *args, **kw): self.axisType = None BasePopup.__init__(self, parent=parent, title='Create panel type', modal=True, **kw) def body(self, master): master.grid_columnconfigure(1, weight=1) row = 0 label = Label(master, text='Panel name: ', grid=(row, 0)) tipText = 'Short text name for the new axis panel, e.g. "N2"' self.name_entry = Entry(master, width=15, grid=(row, 1), tipText=tipText) row += 1 label = Label(master, text='Axis type:', grid=(row, 0)) tipText = 'The type of axis (isotope, time, sampled etc.) represented by panel type' self.types_list = PulldownList(master, grid=(row, 1), tipText=tipText) row += 1 tipTexts = [ 'Create a new panel type object with the selected options & close the popup' ] texts = ['Create'] commands = [self.ok] buttons = UtilityButtonList(master, texts=texts, commands=commands, doClone=False, closeText='Cancel', helpUrl=self.help_url, grid=(row, 0), gridSpan=(1, 2), tipTexts=tipTexts) master.grid_rowconfigure(row, weight=1) self.administerNotifiers(self.registerNotify) self.update() def administerNotifiers(self, notifyFunc): for func in ('__init__', 'delete', 'setName'): notifyFunc(self.update, 'ccpnmr.Analysis.AxisType', func) def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self) def update(self, *extra): axisType = self.axisType axisTypes = self.parent.getAxisTypes() names = [x.name for x in axisTypes] if axisTypes: if axisType not in axisTypes: self.axisType = axisType = axisTypes[0] index = axisTypes.index(axisType) else: index = 0 self.axisType = None self.types_list.setup(names, axisTypes, index) def apply(self): name = self.name_entry.get() if not name: showError('No name', 'Need to enter name', parent=self) return False names = [ panelType.name for panelType in self.analysisProject.panelTypes ] if name in names: showError('Repeated name', 'Name already used', parent=self) return False axisType = self.types_list.getObject() if not axisType: showError('No axis type', 'Need to create axis type', parent=self) return False self.analysisProject.newPanelType(name=name, axisType=axisType) return True
class ViewStructurePopup(BasePopup): """ **A Simple Graphical Display for Macromolecule 3D Coordinates** CcpNmr Analysis contains a simple 3D structure viewing module which is used to display NMR derived information on macromolecular coordinates. For example this viewer can be used to display alternative possibilities for NOE assignments as dashed lines that connect different parts of a molecular structure. The structural model may be moved and rotated by various means listed below. Also, specific atoms may be selected and de-selected in the display by left clicking. The structures that may be displayed with this system are loaded in to the CCPN project via the main Structures_ popup window. A structure is chosen for display by selecting from the "MolSystem", "Ensemble" and "Model" pulldown menus, i.e. at present only one conformational model is displayed at a time. The "Peak List" selection is used in combination with the [Show Peaks] button at the bottom, which brings up a table listing all of the peaks, within the selected peak list, that relate to that atoms chosen (left mouse click) in the structural view. Much of the NMR-derived information that is presented in the graphical display will be controlled via separate popups, for example the [Show On Structure] button of the `Assignment Panel`_ or [Show Selected On Structure] in the `Restraints and Violations`_ popup. Nonetheless, some data can be added to the display via the viewer directly; ensemble RMSDs and other validation parameters can be superimposed as coloured spheres of various sizes. **View Controls** To move and rotate the three-dimensional coordinate display the following keyboard controls may be used: * Rotate: Arrow keys * Zoom: Page Up & Page Down keys * Translate: Arrow keys + Control key Or alternatively the following mouse controls: * Rotate: Middle button click & drag * Zoom: Mouse wheel or middle button click + Shift key & drag up/down * Translate: Middle button click & drag + Control key Also an options menu appears when the right mouse button is clicked and the left mouse button is used to select and de-select atoms in the current model view. .. _Structures: EditStructuresPopup.html .. _`Assignment Panel`: EditAssignmentPopup.html .. _`Restraints and Violations`: BrowseConstraintsPopup.html """ def __init__(self, parent, *args, **kw): self.molSystem = None self.structure = None self.model = None self.distMethod = 'noe' self.waiting = False self.connections = [] self.selectedAtoms = set([]) self.selectedResidues = set([]) self.guiParent = parent self.peakList = None BasePopup.__init__(self, parent=parent, title="Structure : Structure Viewer", **kw) def body(self, guiFrame): guiFrame.grid_columnconfigure(0, weight=1) row = 0 frame = Frame(guiFrame, grid=(row, 0), sticky='ew') frame.grid_columnconfigure(6, weight=1) label = Label(frame, text='MolSystem:', grid=(0, 0)) tipText = 'Selects which molecular system to select a structure for' self.molSystemPulldown = PulldownList(frame, callback=self.setMolSystem, grid=(0, 1), tipText=tipText) label = Label(frame, text=' Ensemble:', grid=(0, 2)) tipText = 'Selects which structure ensemble to display, for the specified molecular system' self.structurePulldown = PulldownList(frame, callback=self.setStructure, grid=(0, 3), tipText=tipText) label = Label(frame, text=' Model:', grid=(0, 4)) tipText = 'Selects which conformational model of the selected structure/ensemble to display' self.modelPulldown = PulldownList(frame, callback=self.setModel, grid=(0, 5), tipText=tipText) label = Label(frame, text='Peak List:', grid=(0, 7), sticky='e') tipText = 'When using the "Show Peak" option, sets which peak list is used to display atom connectivities' self.peakListPulldown = PulldownList(frame, callback=self.setPeakList, grid=(0, 8), sticky='e', tipText=tipText) label = Label(frame, text=' Dist Method:', grid=(0, 9), sticky='e') tipText = 'Where the distances between sets of atoms are displayed, sets whether to use the NOE equivalent (sum r^-6 intensities) or minimum distance' self.distMethodPulldown = PulldownList(frame, callback=self.setDistMethod, texts=distanceMethods.keys(), grid=(0, 10), sticky='e', tipText=tipText) row += 1 guiFrame.grid_rowconfigure(row, weight=1) analysisProject = self.analysisProject getOption = lambda key, defaultValue: getPrintOption( analysisProject, key, defaultValue) setOption = lambda key, value: setPrintOption(analysisProject, key, value) self.structFrame = ViewStructureFrame(guiFrame, project=self.project, radiiScale=0.0, bondWidth=1, atomCallback=self.selectAtom, getPrintOption=getOption, setPrintOption=setOption, grid=(row, 0)) row += 1 frame = Frame(guiFrame) frame.grid(row=row, column=0, sticky='ew') frame.grid_columnconfigure(2, weight=1) tipTexts = [ 'Remove all highlights and connections from the structure display', 'For an ensemble, calculate the per-atom coordinate root mean square deviations and adjust atom size and colours accordingly', 'Display the selected structural parameters on the structure, adjusting atom labels, size and colours accordingly' ] texts = [ 'Reset', 'RMSDs', 'Display Params:', ] commands = [ self.clearConnections, self.displayAtomRmsds, self.displayStrucParams, ] self.paramButtons = ButtonList(frame, texts=texts, commands=commands, grid=(0, 0), tipTexts=tipTexts) tipText = 'Selects which structural parameters, from those calculated, to display on the structure' self.strucParamPulldown = PulldownList(frame, grid=(0, 1), tipText=tipText) tipTexts = [ 'In the stated peak list, display peak assignment connectivities between the highlighted atoms (left click to select atoms in the display)', ] texts = ['Show Peaks'] commands = [ self.showPeaks, ] self.bottomButtons = UtilityButtonList(frame, texts=texts, commands=commands, helpUrl=self.help_url, grid=(0, 3), tipTexts=tipTexts) self.updateMolSystems() self.updatePeakLists() self.administerNotifiers(self.registerNotify) def administerNotifiers(self, notifyFunc): for func in ('__init__', 'delete', 'setName'): notifyFunc(self.updateMolSystems, 'ccp.molecule.MolSystem.MolSystem', func) for func in ('__init__', 'delete', 'setName'): notifyFunc(self.updatePeakLists, 'ccp.nmr.Nmr.DataSource', func) notifyFunc(self.updatePeakLists, 'ccp.nmr.Nmr.Experiment', func) notifyFunc(self.updatePeakLists, 'ccp.nmr.Nmr.PeakList', func) for func in ('delete', '__init__'): notifyFunc(self.updateStructures, 'ccp.molecule.MolStructure.StructureEnsemble', func) for func in ('delete', '__init__'): notifyFunc(self.updateModels, 'ccp.molecule.MolStructure.Model', func) def updatePeakLists(self, obj=None): index = 0 names = [] peakLists = getThroughSpacePeakLists(self.project) if peakLists: if self.peakList not in peakLists: self.peakList = peakLists[0] index = peakLists.index(self.peakList) names = ['%s:%s:%d' % (pl.dataSource.experiment.name, \ pl.dataSource.name, pl.serial) for pl in peakLists ] peakLists.append(None) names.append('<All Avail>') else: self.peakList = None self.peakListPulldown.setup(names, peakLists, index) def setPeakList(self, peakList): if self.peakList is not peakList: self.peakList = peakList def showPeaks(self): if not self.selectedAtoms: msg = 'No atoms selected' showWarning('Warning', msg, parent=self) return if self.peakList: peakLists = [ self.peakList, ] else: peakLists = set(getThroughSpacePeakLists(self.project)) resonances = [] for coordAtom in self.selectedAtoms: atom = coordAtom.atom atomSet = atom.atomSet if not atomSet: continue for resonanceSet in atomSet.resonanceSets: for resonance in resonanceSet.resonances: resonances.append(resonance) peaks = set([]) for resonance in resonances: for contrib in resonance.peakDimContribs: peak = contrib.peakDim.peak if peak.peakList in peakLists: peaks.add(peak) self.connections = [] self.structFrame.clearConnections() self.structFrame.clearHighlights() if peaks: for peak in peaks: self.showPeakConnection(peak) self.guiParent.viewPeaks(list(peaks)) self.updateAfter() def displayAtomParamsList(self, atomParams, size=3.0): self.connections = [] self.structFrame.clearConnections() configAtom = self.structFrame.highlightAtom if self.structure and atomParams: atomDict = {} for chain in self.structure.coordChains: for residue in chain.residues: for atom in residue.atoms: atomDict[atom.atom] = atom atomParams.sort() minVal = atomParams[0][0] maxVal = atomParams[-1][0] scale = maxVal - minVal if not scale: return for value, atoms in atomParams: frac = (value - minVal) / scale rgb = [frac, frac, 1 - frac] label = None for atom in atoms: coordAtom = atomDict.get(atom) if not coordAtom: continue if not label: residue = atom.residue label = '%d%s%s %.3f' % ( residue.seqCode, residue.ccpCode, atom.name, value) else: label = atom.name configAtom(coordAtom, rgb, frac * size, label=label) def displayAtomRmsds(self, scale=5.0): self.connections = [] self.structFrame.clearConnections() configAtom = self.structFrame.highlightAtom if self.structure: nModels = len(self.structure.models) if nModels < 2: msg = 'Cannot calculate atom RMSDs:\n' if nModels == 1: msg += 'Only one model present' else: msg += 'No models' showWarning('Failure', msg) return #structures, error, structureRmsds, atomRmsdDict data = alignStructures([ self.structure, ]) atomRmsdDict = data[3] for atom, rmsd in atomRmsdDict.items(): frac = min(scale, rmsd) / scale if atom.name == 'CA': residue = atom.residue label = '%d%s' % (residue.seqCode, residue.residue.ccpCode) else: label = '' rgb = (frac, frac, 1 - frac) configAtom(atom, rgb, rmsd / scale, label=label) self.updateParams() def displayStrucParams(self): paramKey = self.strucParamPulldown.getObject() if self.structure and paramKey: context, keyword, validStore = paramKey self.displayResidueParams(validStore, context, keyword) def displayResidueParams(self, validStore, context, keyword): self.connections = [] self.structFrame.clearConnections() self.structFrame.clearHighlights() configResidue = self.structFrame.customResidueStyle if self.structure and (validStore.structureEnsemble is self.structure): scores = [] residues = [] unscoredResidues = [] if (context == 'CING') and (keyword == 'ROGscore'): for chain in self.structure.coordChains: for residue in chain.residues: validObj = residue.findFirstResidueValidation( context=context, keyword=keyword) if validObj: value = validObj.textValue if value: scores.append(value) residues.append(residue) else: unscoredResidues.append(residue) else: for chain in self.structure.coordChains: for residue in chain.residues: validObj = residue.findFirstResidueValidation( context=context, keyword=keyword) if validObj: value = validObj.floatValue if value is not None: scores.append(value) residues.append(residue) else: unscoredResidues.append(residue) if scores: if (context == 'CING') and (keyword == 'ROGscore'): for i, residue in enumerate(residues): score = scores[i] if not score: continue if score == 'red': rgb = (0.9, 0.0, 0.0) size = 1.0 elif score == 'orange': rgb = (0.9, 0.7, 0.0) size = 0.5 else: rgb = (0.0, 0.5, 0.0) size = 0.1 configResidue(residue, label='', color=rgb, size=size) else: upper = max(scores) lower = min([s for s in scores if s is not None]) delta = upper - lower if delta: for i, residue in enumerate(residues): score = scores[i] frac = 0.9 * (score - lower) / delta if context == 'RPF': frac = 1 - frac rgb = (frac, frac, 1 - frac) size = frac configResidue(residue, label='%.2f' % score, color=rgb, size=size) for residue in unscoredResidues: configResidue(residue, label='', color=(0.5, 0.5, 0.5), size=0.1) def selectAtom(self, atom, hilightSize=0.4): if atom in self.selectedAtoms: self.selectedAtoms.remove(atom) self.structFrame.clearAtomHighlight(atom, atomSize=hilightSize) else: self.selectedAtoms.add(atom) self.structFrame.highlightResidue(atom, color=(0.0, 1.0, 0.0), atomSize=hilightSize) selectedResidues = set([a.residue for a in self.selectedAtoms]) for residue in self.selectedResidues: if residue not in selectedResidues: self.structFrame.clearResidueHighlight(residue) self.selectedResidues = selectedResidues #c = atom.findFirstCoord() #self.structFrame.clearHighlights() self.structFrame.drawStructure() def clearConnections(self): for atom in self.selectedAtoms: self.structFrame.clearAtomHighlight(atom, atomSize=0.4) for residue in self.selectedResidues: self.structFrame.clearResidueHighlight(residue) self.selectedAtoms = set([]) self.selectedResidues = set([]) self.connections = [] self.structFrame.clearConnections() self.structFrame.clearHighlights() self.updateAfter() def showPeakConnection(self, peak): peakContribs = peak.peakContribs dimAtomSets = [] dimIsotopes = [] for peakDim in peak.sortedPeakDims(): isotope = None for contrib in peakDim.peakDimContribs: resonance = contrib.resonance resonanceSet = resonance.resonanceSet if resonanceSet: atomSets = resonanceSet.atomSets isotope = resonance.isotopeCode dimAtomSets.append( (peakDim, contrib.peakContribs, atomSets, isotope)) dimIsotopes.append(isotope) M = len(dimAtomSets) atomSetsPairs = set() if dimIsotopes.count('1H') > 1: hOnly = True else: hOnly = False for i in range(M - 1): peakDimI, peakContribsI, atomSetsI, isotopeI = dimAtomSets[i] if hOnly and (isotopeI != '1H'): continue for j in range(i + 1, M): peakDimJ, peakContribsJ, atomSetsJ, isotopeJ = dimAtomSets[j] if peakDimJ is peakDimI: continue if isotopeI != isotopeJ: continue if hOnly and (isotopeJ != '1H'): continue if atomSetsI == atomSetsJ: continue if peakContribs: for peakContrib in peakContribsJ: if peakContrib in peakContribsI: atomSetsPairs.add(frozenset([atomSetsI, atomSetsJ])) break else: atomSetsPairs.add(frozenset([atomSetsI, atomSetsJ])) if len(atomSetsPairs): for atomSetsI, atomSetsJ in atomSetsPairs: value = getAtomSetsDistance(atomSetsI, atomSetsJ, self.structure, self.model, method=self.distMethod) color = self.getNoeColor(value) self.showAtomSetsConnection(atomSetsI, atomSetsJ, value, color=color) # If the AtomSets could not be paired just highlight the respective atoms. else: for i in range(M): peakDimI, peakContribsI, atomSetsI, isotopeI = dimAtomSets[i] self.highlightAtoms(atomSetsI) def showResonancesConnection(self, resonance1, resonance2): if resonance1.resonanceSet and resonance2.resonanceSet: atomSets1 = resonance1.resonanceSet.atomSets atomSets2 = resonance2.resonanceSet.atomSets value = getAtomSetsDistance(atomSets1, atomSets2, self.structure, self.model, method=self.distMethod) color = self.getNoeColor(value) self.showAtomSetsConnection(atomSets1, atomSets2, value, color=color) def getNoeColor(self, value): color = (0.0, 1.0, 0.0) if value > 8.0: color = (1.0, 0.0, 0.0) elif value > 5.5: color = (1.0, 1.0, 0.0) return color def showResonancesDihedral(self, resonances): atomSets = [] for resonance in resonances: if not resonance.resonanceSet: return atomSets.append(list(resonance.resonanceSet.atomSets)) angle = getAtomSetsDihedral(atomSets, self.structure) color = (0.0, 1.0, 1.0) # Cyan - so it doesn't look like NOEs self.showAtomSetsConnection(atomSets[0], atomSets[1], color=color) self.showAtomSetsConnection(atomSets[1], atomSets[2], color=color) self.showAtomSetsConnection(atomSets[2], atomSets[3], color=color) self.showAtomSetsConnection(atomSets[3], atomSets[0], value=angle, color=color) def showConstraintConnections(self, constraint): if not hasattr(constraint, 'items'): print "Structure display not supported for constraint type %s" % constraint.className if constraint.className == 'DihedralConstraint': self.showResonancesDihedral(constraint.resonances) else: for item in constraint.items: resonances = list(item.resonances) self.showResonancesConnection(resonances[0], resonances[1]) def showAtomSetsConnection(self, atomSets1, atomSets2, value=None, color=(1.0, 1.0, 1.0)): atomSet1 = list(atomSets1)[0] atomSet2 = list(atomSets2)[0] molSystem = atomSet1.findFirstAtom().residue.chain.molSystem if self.molSystem is not molSystem: self.molSystem = molSystem self.updateMolSystems() # will find a structure if there's a valid one if self.structure: residue1 = atomSet1.findFirstAtom().residue residue2 = atomSet2.findFirstAtom().residue chain1 = residue1.chain chain2 = residue2.chain coordChain1 = self.structure.findFirstCoordChain(code=chain1.code) coordChain2 = self.structure.findFirstCoordChain(code=chain2.code) if not (coordChain1 and coordChain2): print "No coord chain found" return coordRes1 = coordChain1.findFirstResidue(seqId=residue1.seqId) coordRes2 = coordChain2.findFirstResidue(seqId=residue2.seqId) if not (coordRes1 and coordRes2): print "No coord res found" return coordAtoms1 = [] for atomSet in atomSets1: for atom in atomSet.atoms: coordAtom = coordRes1.findFirstAtom(name=atom.name) if coordAtom: coordAtoms1.append(coordAtom) coordAtoms2 = [] for atomSet in atomSets2: for atom in atomSet.atoms: coordAtom = coordRes2.findFirstAtom(name=atom.name) if coordAtom: coordAtoms2.append(coordAtom) for atom1 in coordAtoms1: self.structFrame.highlightAtom(atom1) for atom2 in coordAtoms2: self.structFrame.highlightAtom(atom2) if coordAtoms1 and coordAtoms2: if value is None: cBond = self.structFrame.drawConnection(coordAtoms1, coordAtoms2, color=color) else: cBond = self.structFrame.drawConnection(coordAtoms1, coordAtoms2, color=color, label='%.3f' % value) self.connections.append( [atomSets1, atomSets2, cBond, color, value]) else: print "Display attempted for atoms without structure" self.updateAfter() def highlightAtoms(self, atomSetsIn): atomSets = list(atomSetsIn) molSystem = atomSets[0].findFirstAtom().residue.chain.molSystem if self.molSystem is not molSystem: self.molSystem = molSystem self.updateMolSystems() # will find a structure if there's a valid one if self.structure: residue = atomSets[0].findFirstAtom().residue chain = residue.chain coordChain = self.structure.findFirstCoordChain(code=chain.code) if not (coordChain): print "No coord chain found" return coordRes = coordChain.findFirstResidue(seqId=residue.seqId) if not (coordRes): print "No coord res found" return coordAtoms = [] for atomSet in atomSets: for atom in atomSet.atoms: coordAtom = coordRes.findFirstAtom(name=atom.name) if coordAtom: coordAtoms.append(coordAtom) for atom in coordAtoms: self.structFrame.highlightAtom(atom) else: print "Display attempted for atoms without structure" self.updateAfter() def setDistMethod(self, name): distMethod = distanceMethods.get(name, 'noe') if distMethod != self.distMethod: self.distMethod = distMethod for i, data in enumerate(self.connections): atomSets1, atomSets2, cBond, oldColor, oldVal = data value = getAtomSetsDistance(atomSets1, atomSets2, self.structure, self.model, method=self.distMethod) color = self.getNoeColor(value) cBond.setColor(color) cBond.setAnnotation('%.3f' % value) self.connections[i][3] = color self.connections[i][4] = value self.updateAfter() def updateModels(self, model=None): self.updateParams() if model and (model.structureEnsemble is not self.structure): return names = [] models = [] index = 0 model = self.model if self.structure: models = self.structure.sortedModels() if models: if model not in models: model = models[0] names = ['%d' % m.serial for m in models] index = models.index(model) else: model = None self.modelPulldown.setup(names, models, index) if self.model is not model: self.setModel(model) def setModel(self, model): if model is not self.model: self.model = model oldConn = self.connections[:] drawConn = self.showAtomSetsConnection self.connections = [] self.structFrame.clearConnections() self.update() for atomSets1, atomSets2, cBond, color, value in oldConn: value = getAtomSetsDistance(atomSets1, atomSets2, self.structure, self.model, method=self.distMethod) drawConn(atomSets1, atomSets2, value, color) def setStructure(self, structure): if structure is not self.structure: self.structure = structure self.model = None self.updateModels() def getStructures(self): structures = [] if self.molSystem: for structure in self.molSystem.sortedStructureEnsembles(): structures.append(structure) return structures def updateStructures(self, structure=None): if structure: if self.molSystem and (structure.molSystem is not self.molSystem): return self.molSystem = structure.molSystem self.updateMolSystems() names = [] index = 0 structures = self.getStructures() if structures: if self.structure in structures: structure = self.structure else: structure = structures[0] names = [str(x.ensembleId) for x in structures] index = structures.index(structure) else: structure = None self.structurePulldown.setup(names, structures, index) if structure is not self.structure: self.structure = structure self.model = None self.updateModels() def setMolSystem(self, molSystem): if molSystem is not self.molSystem: self.molSystem = molSystem self.structure = None self.model = None self.updateStructures() def getMolSystems(self): molSystems = [] if self.project: for molSystem in self.project.sortedMolSystems(): if molSystem.structureEnsembles: molSystems.append(molSystem) return molSystems def updateMolSystems(self, *object): names = [] index = 0 molSystems = self.getMolSystems() if molSystems: if self.molSystem in molSystems: molSystem = self.molSystem else: molSystem = molSystems[0] names = [x.code for x in molSystems] index = molSystems.index(molSystem) else: molSystem = None self.molSystemPulldown.setup(names, molSystems, index) if molSystem is not self.molSystem: self.molSystem = molSystem self.structure = None self.model = None self.updateStructures() def updateAfter(self, object=None): if self.waiting: return self.waiting = True self.after_idle(self.update) def update(self, structure=None): # Main display if structure: self.structure = structure self.molSystem = structure.molSystem self.updateModels() if self.model: if self.structFrame.model is not self.model: self.structFrame.update(self.model) self.structFrame.highlightBackbone() self.structFrame.drawStructure() self.updateStructures() self.updateMolSystems() self.updateParams() self.waiting = False def updateParams(self): # Parameter pulldown structure = self.structure names = [] index = 0 validKeys = [] if structure: validKeys = set() for validStore in structure.validationStores: validObjs = validStore.findAllValidationResults( className='ResidueValidation') for validObj in validObjs: context = validObj.context key = (validObj.context, validObj.keyword, validStore) validKeys.add(key) validKeys = list(validKeys) validKeys.sort() names = ['%s:%s' % (c, k) for c, k, s in validKeys] if names: index = min(self.strucParamPulldown.index, len(names) - 1) self.strucParamPulldown.setup(names, validKeys, index) self.waiting = False def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self)
class ViewRamachandranPopup(BasePopup): """ **Display Protein Backbone Phi & Psi Angles** This graphical display allow the user to display phi and psi protein backbone dihedral angles on a Ramachandran plot that indicates the likelihood (database abundance) of those angles. This can be used as a form of structure quality control to detect residues in a calculated three dimensional structure that are distorted away from regular protein-like conformations. Although a few atypical angles may truly occur in a given protein structure the presence of many unusual angles and abnormal distributions of angles over a structure ensemble with many models may indicate a poor quality structure. With this popup window the user selects a molecular system and then a structure ensemble that relates to that system. Th e user can choose to display dihedral angle information for all models (conformations) in the structure ensemble or just one model, by changing the "Model" pulldown menu. The user can control which residues are considered by selecting either a particular type of residue via "Ccp Code" or a specific residue in the sequence. Other options control how the phi & psi angle information is presented on screen. The "Labels" options control which angle spots on the Ramachandran chart have residue sequence number and type displayed; by default only the "disallowed" angles in unusual (white) regions of the plot are labelled. The "Spot Size" dictates how large the angle markers are and the "Colours" is a colour scheme to differentiate the different models from within the selected ensemble. The [Previous Residue] and [Next Residue] buttons allow the user to quickly scan though all of the residues in the structure, to check for unusual/disallowed backbone angles or unlikely distributions of within the ensemble. In the Ramachandran matrix plot the wite areas represent phi & psi angle combinations that are very unusual and thus usually indicative of a poor structure at that point. The grey and red areas represent the more common, and thus more expected, angle combinations. The more red the colour of a square the greater the likelihood of the phi & psi angles. It should be noted that when displaying angle points for only a single residue or residue type, the Ramachandran chart in the background is at residue-specific version, i.e. it shows angle likelihoods for only that one kind of residue. This is particularly important for Pro and Gly residues that have notably different distributions (and hence angle expectations) to other residues. """ def __init__(self, parent, *args, **kw): self.molSystem = None self.structure = None self.model = None self.waiting = False self.colorScheme = None self.ccpCode = None # Which Rama background to use self.residue = None self.labelMode = LABEL_MODES[1] BasePopup.__init__(self, parent=parent, title='Chart : Ramachandran Plot', **kw) def open(self): BasePopup.open(self) self.updateAfter() def body(self, guiFrame): self.geometry('700x700') self.update_idletasks() guiFrame.grid_columnconfigure(0, weight=1) row = 0 frame = Frame(guiFrame, relief='raised', bd=1, grid=(row,0), sticky='ew') frame.grid_columnconfigure(8, weight=1) label = Label(frame, text='MolSystem:', grid=(0,0)) tipText = 'Selects which molecular system to display data for; from which a structure is selected' self.molSystemPulldown = PulldownList(frame, callback=self.setMolSystem, grid=(0,1), tipText=tipText) label = Label(frame, text=' Structure:', grid=(0,2)) tipText = 'Selects which structure ensemble to display phi/psi backbone angle data for' self.structurePulldown = PulldownList(frame, callback=self.setStructure, grid=(0,3), tipText=tipText) label = Label(frame, text=' Model:', grid=(0,4)) tipText = 'Selects which conformational model(s), from the structure ensemble, to display data for' self.modelPulldown = PulldownList(frame, callback=self.setModel, grid=(0,5), tipText=tipText) label = Label(frame, text=' Labels:', grid=(0,6)) tipText = 'Sets which phi/psi points carry residue labels, e.g. according to whether values are "disallowed" (very uncommon)' self.labelPulldown = PulldownList(frame, texts=LABEL_MODES, index=1, callback=self.updateLabels, grid=(0,7), sticky='e', tipText=tipText) utilButtons = UtilityButtonList(frame, helpUrl=self.help_url, grid=(0,9)) label = Label(frame, text='Ccp Code:', grid=(1,0)) tipText = 'Allows the phi/psi points to be restricted to only those from a particular residue type' self.ccpCodePulldown = PulldownList(frame, callback=self.setCcpCode, grid=(1,1), tipText=tipText) label = Label(frame, text=' Residue:', grid=(1,2)) tipText = 'Allows the display of phi/psi points from only a single residue' self.residuePulldown = PulldownList(frame, callback=self.setResidue, grid=(1,3), tipText=tipText) label = Label(frame, text=' Spot Size:', grid=(1,4)) sizes = [1,2,3,4,5,6,7,8,9,10] texts = [str(s) for s in sizes] tipText = 'Sets how large to display the circles that indicate the phi/psi points' self.spotSizePulldown = PulldownList(frame, texts=texts, objects=sizes, callback=self.updateAfter, index=1, grid=(1,5), tipText=tipText) label = Label(frame, text=' Colours:', grid=(1,6)) tipText = 'Selects which colour scheme to use to distinguish phi/psi points from different conformational models' self.schemePulldown = PulldownList(frame, callback=self.selectColorScheme, grid=(1,7), tipText=tipText) row +=1 tipText = 'The percentage of residues found in the different regions of the Ramachandran plot, according to PROCHECK cetegories' self.regionLabel = Label(guiFrame, text=REGION_TEXT % (0.0,0.0,0.0), grid=(row, 0), tipText=tipText) row +=1 self.colorRow = row self.colorFrame = Frame(guiFrame, sticky='ew') self.colorFrame.grid_columnconfigure(0, weight=1) self.colorLabels = [] tipText = 'The colors of the ensembles in the plot' label = Label(self.colorFrame, text='Colors: ', grid=(0,0), sticky='w', tipText=tipText) row +=1 guiFrame.grid_rowconfigure(row, weight=1) self.plot = ViewRamachandranFrame(guiFrame, relief='sunken', bgColor=self.cget('bg')) self.plot.grid(row=row, column=0, sticky='nsew') row +=1 tipTexts = ['Show phi/spi spots for the previous residue in the sequence; when there is no current residue, starts fron the first in sequence there is data for', 'Show phi/spi spots for the next residue in the sequence; when there is no current residue, starts fron the first in sequence there is data for'] texts = ['Previous Residue','Next Residue'] commands = [self.prevResidue,self.nextResidue] self.bottomButtons = ButtonList(guiFrame, commands=commands, texts=texts, grid=(row,0), tipTexts=tipTexts) self.updateColorSchemes() self.updateMolSystems() self.updateAfter() self.administerNotifiers(self.registerNotify) def administerNotifiers(self, notifyFunc): for func in ('__init__', 'delete',): notifyFunc(self.updateStructuresAfter, 'ccp.molecule.MolStructure.StructureEnsemble', func) for func in ('__init__', 'delete','setColors'): notifyFunc(self.updateColorSchemes, 'ccpnmr.AnalysisProfile.ColorScheme', func) def stepResidue(self, step=1): structure = self.structure if structure: if structure == 'All': structure = self.getStructures()[0] if not self.residue: chain = structure.sortedCoordChains()[0] residue = chain.sortedResidues()[1] else: residue = self.residue chain = self.residue.chain residues = chain.sortedResidues()[1:] index = residues.index(residue) index = (index+step) % len(residues) residue = residues[index] self.residue = residue self.updateResidues() self.updateAfter() def prevResidue(self): self.stepResidue(-1) def nextResidue(self): self.stepResidue(1) def setResidue(self,residue): if residue is not self.residue: if residue: self.ccpCode = None self.ccpCodePulldown.setIndex(0) self.residue = residue self.updateAfter() def setCcpCode(self, ccpCode): if ccpCode != self.ccpCode: if ccpCode: self.residue = None self.residuePulldown.setIndex(0) self.ccpCode = ccpCode self.updateAfter() def updateLabels(self, selection): if selection is not self.labelMode: self.labelMode = selection self.updateAfter() def updateStructuresAfter(self, structure): if structure.molSystem is self.molSystem: self.updateStructures() def selectColorScheme(self, scheme): if scheme is not self.colorScheme: self.colorScheme = scheme if self.structure: self.updateAfter() def updateColorSchemes(self, scheme=None): schemes = getHueSortedColorSchemes(self.analysisProfile) names = [s.name for s in schemes] colors = [list(s.colors) for s in schemes] index = 0 scheme = self.colorScheme if schemes: if scheme not in schemes: scheme = self.analysisProfile.findFirstColorScheme(name=DEFAULT_SCHEME) if not scheme: scheme = schemes[0] index = schemes.index(scheme) else: scheme = None if scheme is not self.colorScheme: self.colorScheme = scheme if self.structure: self.updateAfter() self.schemePulldown.setup(names, schemes, index, colors=colors) def updateResidues(self): resNames = ['<All>',] ccpNames = ['<All>',] ccpCodes = [None,] residues = [None,] resCats = [None,] indexR = 0 indexC = 0 structure = self.structure if structure: if structure == 'All': structure = self.getStructures()[0] models = structure.sortedModels() models.append(None) ccpCodes0 = set() for chain in structure.sortedCoordChains(): chainCode = chain.code for residue in chain.sortedResidues()[1:]: sysResidue = residue.residue seqCode = residue.seqCode ccpCode = sysResidue.ccpCode ccpCodes0.add(ccpCode) resName = '%d %s' % (seqCode, ccpCode) n = 10*int(seqCode/10) resCat = '%s %d-%d' % (chainCode,n,n+10) resNames.append(resName) residues.append(residue) resCats.append(resCat) ccpCodes0 = list(ccpCodes0) ccpCodes0.sort() ccpCodes += ccpCodes0 ccpNames += ccpCodes0 doUpdate = False if self.residue not in residues: if self.residue and len(residues) > 1: chain = residues[1].chain self.residue = chain.findFirstResidue(residue=self.residue.residue) else: self.residue = None doUpdate = True indexR = residues.index(self.residue) if self.ccpCode not in ccpCodes: self.ccpCode = None doUpdate = True indexC = ccpCodes.index(self.ccpCode) self.residuePulldown.setup(resNames, residues, indexR, categories=resCats) self.ccpCodePulldown.setup(ccpNames, ccpCodes, indexC) if doUpdate: self.updateAfter() def updateModels(self, model=None): structure = self.structure # looks like model is always None # possibly need to worry about below otherwise if structure = 'All' if model and (model.structureEnsemble is not structure): return self.updateResidues() models = [] names = [] index = 0 model = self.model if structure: if structure != 'All': models = self.structure.sortedModels() models.append(None) if models: if model not in models: model = models[0] names = ['%d' % m.serial for m in models[:-1]] names.append('<All>') index = models.index(model) else: model = None self.modelPulldown.setup(names, models, index) if self.model is not model: self.model = model self.updateAfter() def setModel(self, model): if model is not self.model: self.model = model self.updateAfter() def setStructure(self, structure): if structure is not self.structure: self.structure = structure self.model = None self.updateModels() self.updateAfter() def getStructures(self, molSystem=None): molSystem = molSystem or self.molSystem structures = [] if molSystem: for structure in molSystem.sortedStructureEnsembles(): for chain in structure.coordChains: for residue in chain.residues: if residue.residue.molResidue.molType == 'protein': structures.append(structure) break else: continue break return structures def updateStructures(self, structure=None): if structure and (structure.molSystem is not self.molSystem): return names = [] index = 0 structures = self.getStructures() if structures: names = [str(x.ensembleId) for x in structures] names.append('<All>') structures.append('All') if self.structure in structures: structure = self.structure else: structure = structures[0] index = structures.index(structure) else: structure = None self.structurePulldown.setup(names, structures, index) if structure is not self.structure: self.structure = structure self.model = None self.updateModels() def setMolSystem(self, molSystem): if molSystem is not self.molSystem: self.molSystem = molSystem self.structure = None self.model = None self.updateStructures() def getMolSystems(self): molSystems = [] if self.project: for molSystem in self.project.sortedMolSystems(): if self.getStructures(molSystem): molSystems.append(molSystem) return molSystems def updateMolSystems(self, *object): names = [] index = 0 molSystems = self.getMolSystems() if molSystems: if self.molSystem in molSystems: molSystem = self.molSystem else: molSystem = molSystems[0] names = [x.code for x in molSystems] index = molSystems.index(molSystem) else: molSystem = None self.molSystemPulldown.setup(names, molSystems, index) if molSystem is not self.molSystem: self.molSystem = molSystem self.structure = None self.model = None self.updateStructures() def updateAfter(self, object=None): if self.waiting: return self.waiting = True self.after_idle(self.update) def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self) def update(self): self.plot.setAminoAcid(self.ccpCode) self.updatePhiPsi() self.waiting = False def updatePhiPsi(self): # labels='outliers', '' if not self.structure: return model = self.model ccpCode = self.ccpCode residueSel = self.residue labelMode = self.labelMode getValue = self.plot.getIntensityValue ccpCode = self.ccpCode if self.residue: ccpCode = self.residue.residue.ccpCode self.plot.setAminoAcid(ccpCode) phiPsiAccept = [] plotObjects = [] resLabels = [] colors = [] if self.colorScheme: scheme = list(self.colorScheme.colors) else: scheme = ['#800000','#008000','#000080'] nCols = len(scheme) nCore = 0 nAllowed = 0 nDisallowed = 0 colorFrame = self.colorFrame colorLabels = self.colorLabels structure = self.structure if structure == 'All': structures = self.getStructures() nstructures = len(structures) ncolorLabels = len(colorLabels) n = nstructures - ncolorLabels if n > 0: for i in range(n): label = Label(colorFrame, grid=(0,i+ncolorLabels+1)) colorLabels.append(label) for i, structure0 in enumerate(structures): text = 'Structure %d' % structure0.ensembleId label = colorLabels[i] label.set(text) color = scheme[i % nCols] label.config(bg=color) if color == '#000000': label.config(fg='#FFFFFF') else: label.config(fg='#000000') colorFrame.grid(row=self.colorRow, column=0) else: structures = [structure] colorFrame.grid_forget() for structure0 in structures: if model: models = [model,] else: models = list(structure0.models) nModels = len(models) for chain in structure0.coordChains: for residue in chain.residues: sysResidue = residue.residue sysCode = getResidueCode(sysResidue) resLabel = '%d%s' % (sysResidue.seqCode,sysCode) if sysResidue.molResidue.molType != 'protein': continue if residue and residueSel and (residue.residue is not residueSel.residue): continue if ccpCode and (sysCode != ccpCode): continue for model0 in models: phi, psi = getResiduePhiPsi(residue, model=model0) if None in (phi,psi): continue value = getValue(phi,psi) if nModels == 1: resLabels.append(resLabel) else: resLabels.append( '%s:%d' % (resLabel, model0.serial) ) doLabel = False if value < 6.564e-5: if labelMode == LABEL_MODES[1]: doLabel = True nDisallowed += 1 elif value < 0.000821: nAllowed += 1 else: nCore += 1 if labelMode == LABEL_MODES[0]: doLabel = False elif labelMode == LABEL_MODES[2]: doLabel = True if structure == 'All': ind = structures.index(structure0) else: ind = model0.serial - 1 colors.append(scheme[ind % nCols]) plotObjects.append((residue, model0)) phiPsiAccept.append((phi,psi,doLabel)) spotSize = self.spotSizePulldown.getObject() nRes = 0.01*float(nDisallowed+nAllowed+nCore) if nRes: self.regionLabel.set(REGION_TEXT % (nCore/nRes,nAllowed/nRes,nDisallowed/nRes)) else: self.regionLabel.set(REGION_TEXT % (0.0, 0.0, 0.0)) self.plot.cirRadius = spotSize self.plot.updateObjects(phiPsiAccept, plotObjects, resLabels, colors)
class CingFrame(NmrSimRunFrame): def __init__(self, parent, application, *args, **kw): project = application.project simStore = project.findFirstNmrSimStore(application=APP_NAME) or \ project.newNmrSimStore(application=APP_NAME, name=APP_NAME) self.application = application self.residue = None self.structure = None self.serverCredentials = None self.iCingBaseUrl = DEFAULT_URL self.resultsUrl = None self.chain = None self.nmrProject = application.nmrProject self.serverDone = False NmrSimRunFrame.__init__(self, parent, project, simStore, *args, **kw) # # # # # # New Structure Frame # # # # # self.structureFrame.grid_forget() tab = self.tabbedFrame.frames[0] frame = Frame(tab, grid=(1,0)) frame.expandGrid(2,1) div = LabelDivider(frame, text='Structures', grid=(0,0), gridSpan=(1,2)) label = Label(frame, text='Ensemble: ', grid=(1,0)) self.structurePulldown = PulldownList(frame, callback=self.changeStructure, grid=(1,1)) headingList = ['Model','Use'] editWidgets = [None,None] editGetCallbacks = [None,self.toggleModel] editSetCallbacks = [None,None,] self.modelTable = ScrolledMatrix(frame, grid=(2,0), gridSpan=(1,2), callback=self.selectStructModel, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, headingList=headingList) texts = ['Activate Selected','Inactivate Selected'] commands = [self.activateModels, self.disableModels] buttons = ButtonList(frame, texts=texts, commands=commands, grid=(3,0), gridSpan=(1,2)) # # # # # # Submission frame # # # # # # tab = self.tabbedFrame.frames[1] tab.expandGrid(1,0) frame = LabelFrame(tab, text='Server Job Submission', grid=(0,0)) frame.expandGrid(None,2) srow = 0 label = Label(frame, text='iCing URL:', grid=(srow, 0)) urls = [DEFAULT_URL,] self.iCingBaseUrlPulldown = PulldownList(frame, texts=urls, objects=urls, index=0, grid=(srow,1)) srow +=1 label = Label(frame, text='Results File:', grid=(srow, 0)) self.resultFileEntry = Entry(frame, bd=1, text='', grid=(srow,1), width=50) self.setZipFileName() button = Button(frame, text='Choose File', bd=1, sticky='ew', command=self.chooseZipFile, grid=(srow, 2)) srow +=1 label = Label(frame, text='Results URL:', grid=(srow, 0)) self.resultUrlEntry = Entry(frame, bd=1, text='', grid=(srow,1), width=50) button = Button(frame, text='View Results HTML', bd=1, sticky='ew', command=self.viewHtmlResults, grid=(srow, 2)) srow +=1 texts = ['Submit Project!', 'Check Run Status', 'Purge Server Result', 'Download Results'] commands = [self.runCingServer, self.checkStatus, self.purgeCingServer, self.downloadResults] self.buttonBar = ButtonList(frame, texts=texts, commands=commands, grid=(srow, 0), gridSpan=(1,3)) for button in self.buttonBar.buttons[:1]: button.config(bg=CING_BLUE) # # # # # # Residue frame # # # # # # frame = LabelFrame(tab, text='Residue Options', grid=(1,0)) frame.expandGrid(1,1) label = Label(frame, text='Chain: ') label.grid(row=0,column=0,sticky='w') self.chainPulldown = PulldownList(frame, callback=self.changeChain) self.chainPulldown.grid(row=0,column=1,sticky='w') headingList = ['#','Residue','Linking','Decriptor','Use?'] editWidgets = [None,None,None,None,None] editGetCallbacks = [None,None,None,None,self.toggleResidue] editSetCallbacks = [None,None,None,None,None,] self.residueMatrix = ScrolledMatrix(frame, headingList=headingList, multiSelect=True, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, callback=self.selectResidue) self.residueMatrix.grid(row=1, column=0, columnspan=2, sticky = 'nsew') texts = ['Activate Selected','Inactivate Selected'] commands = [self.activateResidues, self.deactivateResidues] self.resButtons = ButtonList(frame, texts=texts, commands=commands,) self.resButtons.grid(row=2, column=0, columnspan=2, sticky='ew') """ # # # # # # Validate frame # # # # # # frame = LabelFrame(tab, text='Validation Options', grid=(2,0)) frame.expandGrid(None,2) srow = 0 self.selectCheckAssign = CheckButton(frame) self.selectCheckAssign.grid(row=srow, column=0,sticky='nw' ) self.selectCheckAssign.set(True) label = Label(frame, text='Assignments and shifts') label.grid(row=srow,column=1,sticky='nw') srow += 1 self.selectCheckResraint = CheckButton(frame) self.selectCheckResraint.grid(row=srow, column=0,sticky='nw' ) self.selectCheckResraint.set(True) label = Label(frame, text='Restraints') label.grid(row=srow,column=1,sticky='nw') srow += 1 self.selectCheckQueen = CheckButton(frame) self.selectCheckQueen.grid(row=srow, column=0,sticky='nw' ) self.selectCheckQueen.set(False) label = Label(frame, text='QUEEN') label.grid(row=srow,column=1,sticky='nw') srow += 1 self.selectCheckScript = CheckButton(frame) self.selectCheckScript.grid(row=srow, column=0,sticky='nw' ) self.selectCheckScript.set(False) label = Label(frame, text='User Python script\n(overriding option)') label.grid(row=srow,column=1,sticky='nw') self.validScriptEntry = Entry(frame, bd=1, text='') self.validScriptEntry.grid(row=srow,column=2,sticky='ew') scriptButton = Button(frame, bd=1, command=self.chooseValidScript, text='Browse') scriptButton.grid(row=srow,column=3,sticky='ew') """ # # # # # # # # # # self.update(simStore) self.administerNotifiers(application.registerNotify) def downloadResults(self): if not self.run: msg = 'No current iCing run' showWarning('Failure', msg, parent=self) return credentials = self.serverCredentials if not credentials: msg = 'No current iCing server job' showWarning('Failure', msg, parent=self) return fileName = self.resultFileEntry.get() if not fileName: msg = 'No save file specified' showWarning('Failure', msg, parent=self) return if os.path.exists(fileName): msg = 'File %s already exists. Overwite?' % fileName if not showOkCancel('Query', msg, parent=self): return url = self.iCingBaseUrl iCingUrl = self.getServerUrl(url) logText = iCingRobot.iCingFetch(credentials, url, iCingUrl, fileName) print logText msg = 'Results saved to file %s\n' % fileName msg += 'Purge results from iCing server?' if showYesNo('Query',msg, parent=self): self.purgeCingServer() def getServerUrl(self, baseUrl): iCingUrl = os.path.join(baseUrl, 'icing/serv/iCingServlet') return iCingUrl def viewHtmlResults(self): resultsUrl = self.resultsUrl if not resultsUrl: msg = 'No current iCing results URL' showWarning('Failure', msg, parent=self) return webBrowser = WebBrowser(self.application, popup=self.application) webBrowser.open(self.resultsUrl) def runCingServer(self): if not self.project: return run = self.run if not run: msg = 'No CING run setup' showWarning('Failure', msg, parent=self) return structure = self.structure if not structure: msg = 'No structure ensemble selected' showWarning('Failure', msg, parent=self) return ensembleText = getModelsString(run) if not ensembleText: msg = 'No structural models selected from ensemble' showWarning('Failure', msg, parent=self) return residueText = getResiduesString(structure) if not residueText: msg = 'No active residues selected in structure' showWarning('Failure', msg, parent=self) return url = self.iCingBaseUrlPulldown.getObject() url.strip() if not url: msg = 'No iCing server URL specified' showWarning('Failure', msg, parent=self) self.iCingBaseUrl = None return msg = 'Submit job now? You will be informed when the job is done.' if not showOkCancel('Confirm', msg, parent=self): return self.iCingBaseUrl = url iCingUrl = self.getServerUrl(url) self.serverCredentials, results, tarFileName = iCingRobot.iCingSetup(self.project, userId='ccpnAp', url=iCingUrl) if not results: # Message already issued on failure self.serverCredentials = None self.resultsUrl = None self.update() return else: credentials = self.serverCredentials os.unlink(tarFileName) entryId = iCingRobot.iCingProjectName(credentials, iCingUrl).get(iCingRobot.RESPONSE_RESULT) baseUrl, htmlUrl, logUrl, zipUrl = iCingRobot.getResultUrls(credentials, entryId, url) self.resultsUrl = htmlUrl # Save server data in this run for persistence setRunParameter(run, iCingRobot.FORM_USER_ID, self.serverCredentials[0][1]) setRunParameter(run, iCingRobot.FORM_ACCESS_KEY, self.serverCredentials[1][1]) setRunParameter(run, ICING_BASE_URL, url) setRunParameter(run, HTML_RESULTS_URL, htmlUrl) self.update() run.inputStructures = structure.sortedModels() # select residues from the structure's chain #iCingRobot.iCingResidueSelection(credentials, iCingUrl, residueText) # Select models from ensemble #iCingRobot.iCingEnsembleSelection(credentials, iCingUrl, ensembleText) # Start the actual run self.serverDone = False iCingRobot.iCingRun(credentials, iCingUrl) # Fetch server progress occasionally, report when done # this function will call itself again and again self.after(CHECK_INTERVAL, self.timedCheckStatus) self.update() def timedCheckStatus(self): if not self.serverCredentials: return if self.serverDone: return status = iCingRobot.iCingStatus(self.serverCredentials, self.getServerUrl(self.iCingBaseUrl)) if not status: #something broke, already warned return result = status.get(iCingRobot.RESPONSE_RESULT) if result == iCingRobot.RESPONSE_DONE: self.serverDone = True msg = 'CING run is complete!' showInfo('Completion', msg, parent=self) return self.after(CHECK_INTERVAL, self.timedCheckStatus) def checkStatus(self): if not self.serverCredentials: return status = iCingRobot.iCingStatus(self.serverCredentials, self.getServerUrl(self.iCingBaseUrl)) if not status: #something broke, already warned return result = status.get(iCingRobot.RESPONSE_RESULT) if result == iCingRobot.RESPONSE_DONE: msg = 'CING run is complete!' showInfo('Completion', msg, parent=self) self.serverDone = True return else: msg = 'CING job is not done.' showInfo('Processing', msg, parent=self) self.serverDone = False return def purgeCingServer(self): if not self.project: return if not self.run: msg = 'No CING run setup' showWarning('Failure', msg, parent=self) return if not self.serverCredentials: msg = 'No current iCing server job' showWarning('Failure', msg, parent=self) return url = self.iCingBaseUrl results = iCingRobot.iCingPurge(self.serverCredentials, self.getServerUrl(url)) if results: showInfo('Info','iCing server results cleared') self.serverCredentials = None self.iCingBaseUrl = None self.serverDone = False deleteRunParameter(self.run, iCingRobot.FORM_USER_ID) deleteRunParameter(self.run, iCingRobot.FORM_ACCESS_KEY) deleteRunParameter(self.run, HTML_RESULTS_URL) else: showInfo('Info','Purge failed') self.update() def chooseZipFile(self): fileTypes = [ FileType('Zip', ['*.zip']), ] popup = FileSelectPopup(self, file_types=fileTypes, file=self.resultFileEntry.get(), title='Results zip file location', dismiss_text='Cancel', selected_file_must_exist=False) fileName = popup.getFile() if fileName: self.resultFileEntry.set(fileName) popup.destroy() def setZipFileName(self): zipFile = '%s_CING_report.zip' % self.project.name self.resultFileEntry.set(zipFile) def selectStructModel(self, model, row, col): self.model = model def selectResidue(self, residue, row, col): self.residue = residue def deactivateResidues(self): for residue in self.residueMatrix.currentObjects: residue.useInCing = False self.updateResidues() def activateResidues(self): for residue in self.residueMatrix.currentObjects: residue.useInCing = True self.updateResidues() def activateModels(self): if self.run: for model in self.modelTable.currentObjects: if model not in self.run.inputStructures: self.run.addInputStructure(model) self.updateModels() def disableModels(self): if self.run: for model in self.modelTable.currentObjects: if model in self.run.inputStructures: self.run.removeInputStructure(model) self.updateModels() def toggleModel(self, *opt): if self.model and self.run: if self.model in self.run.inputStructures: self.run.removeInputStructure(self.model) else: self.run.addInputStructure(self.model) self.updateModels() def toggleResidue(self, *opt): if self.residue: self.residue.useInCing = not self.residue.useInCing self.updateResidues() def updateResidues(self): if self.residue and (self.residue.topObject is not self.structure): self.residue = None textMatrix = [] objectList = [] colorMatrix = [] if self.chain: chainCode = self.chain.code for residue in self.chain.sortedResidues(): msResidue = residue.residue if not hasattr(residue, 'useInCing'): residue.useInCing = True if residue.useInCing: colors = [None, None, None, None, CING_BLUE] use = 'Yes' else: colors = [None, None, None, None, None] use = 'No' datum = [residue.seqCode, msResidue.ccpCode, msResidue.linking, msResidue.descriptor, use,] textMatrix.append(datum) objectList.append(residue) colorMatrix.append(colors) self.residueMatrix.update(objectList=objectList, textMatrix=textMatrix, colorMatrix=colorMatrix) def updateChains(self): index = 0 names = [] chains = [] chain = self.chain if self.structure: chains = self.structure.sortedCoordChains() names = [chain.code for chain in chains] if chains: if chain not in chains: chain = chains[0] index = chains.index(chain) self.changeChain(chain) self.chainPulldown.setup(names, chains, index) def updateStructures(self): index = 0 names = [] structures = [] structure = self.structure if self.run: model = self.run.findFirstInputStructure() if model: structure = model.structureEnsemble structures0 = [(s.ensembleId, s) for s in self.project.structureEnsembles] structures0.sort() for eId, structure in structures0: name = '%s:%s' % (structure.molSystem.code, eId) structures.append(structure) names.append(name) if structures: if structure not in structures: structure = structures[-1] index = structures.index(structure) self.changeStructure(structure) self.structurePulldown.setup(names, structures, index) def updateModels(self): textMatrix = [] objectList = [] colorMatrix = [] if self.structure and self.run: used = self.run.inputStructures for model in self.structure.sortedModels(): if model in used: colors = [None, CING_BLUE] use = 'Yes' else: colors = [None, None] use = 'No' datum = [model.serial,use] textMatrix.append(datum) objectList.append(model) colorMatrix.append(colors) self.modelTable.update(objectList=objectList, textMatrix=textMatrix, colorMatrix=colorMatrix) def changeStructure(self, structure): if self.project and (self.structure is not structure): self.project.currentEstructureEnsemble = structure self.structure = structure if self.run: self.run.inputStructures = structure.sortedModels() self.updateModels() self.updateChains() def changeChain(self, chain): if self.project and (self.chain is not chain): self.chain = chain self.updateResidues() def chooseValidScript(self): # Prepend default Cyana file extension below fileTypes = [ FileType('Python', ['*.py']), ] popup = FileSelectPopup(self, file_types = fileTypes, title='Python file', dismiss_text='Cancel', selected_file_must_exist = True) fileName = popup.getFile() self.validScriptEntry.set(fileName) popup.destroy() def updateAll(self, project=None): if project: self.project = project self.nmrProject = project.currentNmrProject simStore = project.findFirstNmrSimStore(application='CING') or \ project.newNmrSimStore(application='CING', name='CING') else: simStore = None if not self.project: return self.setZipFileName() if not self.project.currentNmrProject: name = self.project.name self.nmrProject = self.project.newNmrProject(name=name) else: self.nmrProject = self.project.currentNmrProject self.update(simStore) def update(self, simStore=None): NmrSimRunFrame.update(self, simStore) run = self.run urls = [DEFAULT_URL,] index = 0 if run: userId = getRunParameter(run, iCingRobot.FORM_USER_ID) accessKey = getRunParameter(run, iCingRobot.FORM_ACCESS_KEY) if userId and accessKey: self.serverCredentials = [(iCingRobot.FORM_USER_ID, userId), (iCingRobot.FORM_ACCESS_KEY, accessKey)] url = getRunParameter(run, ICING_BASE_URL) if url: htmlUrl = getRunParameter(run, HTML_RESULTS_URL) self.iCingBaseUrl = url self.resultsUrl = htmlUrl # May be None self.resultUrlEntry.set(self.resultsUrl) if self.iCingBaseUrl and self.iCingBaseUrl not in urls: index = len(urls) urls.append(self.iCingBaseUrl) self.iCingBaseUrlPulldown.setup(urls, urls, index) self.updateButtons() self.updateStructures() self.updateModels() self.updateChains() def updateButtons(self, event=None): buttons = self.buttonBar.buttons if self.project and self.run: buttons[0].enable() if self.resultsUrl and self.serverCredentials: buttons[1].enable() buttons[2].enable() buttons[3].enable() else: buttons[1].disable() buttons[2].disable() buttons[3].disable() else: buttons[0].disable() buttons[1].disable() buttons[2].disable() buttons[3].disable()
class ViewChemicalShiftsPopup(BasePopup): """ **A Table of Chemical Shifts for Export** This section is designed to make a layout of a table for chemical shifts on a per-residue basis which may them be exported as either PostScript, for printing and graphical manipulation, or as plain text for import into other software or computer scripts. The user chooses the molecular chain (which sequence) and the shift list to use at the top of the popup, together with a few other options that control how things are rendered. Then buttons are toggled to select which kinds of atom will be displayed in aligned columns; other kinds will simply be listed to the right of the columns. Thus for example if the shift list does not contain any carbonyl resonances in a protein chain then the user may toggle the empty "C" column off. Once the desired layout is achieved the user then uses the [Export PostScript] or [Export Text] buttons to write the data into a file of the appropriate type. The user will be presented wit ha file browser to specify the location and the name of the file to be saved. It should be noted that although the graphical display in the popup itself is somewhat limited, e.g. the gaps and spacing doesn't always look perfect, the PostScript version that is exported is significantly neater. **Caveats & Tips** If you need a chemical shift list represented in a particular format, specific for a particular external NMR program then you should use the FormatConverter software. Chemical shifts may also be exported from any table in Analysis that contains such data by clicking the right mouse button over the table and selecting the export option. """ def __init__(self, parent, *args, **kw): self.shiftList = None self.font = 'Helvetica 10' self.boldFont = 'Helvetica 10 bold' self.symbolFont = 'Symbol 8' self.smallFont = 'Helvetica 8' self.chain = None self.textOut = '' self.textMatrix = [] BasePopup.__init__(self, parent=parent, title='Chart : Chemical Shifts Table') def body(self, guiFrame): row = 0 frame = Frame(guiFrame, grid=(row, 0)) frame.expandGrid(None, 6) label = Label(frame, text='Chain:', grid=(0, 0)) tipText = 'Selects which molecular chain to show residues and chemical shift values for' self.chainPulldown = PulldownList(frame, callback=self.changeChain, grid=(0, 1), tipText=tipText) label = Label(frame, text=' Shift List:', grid=(0, 2)) tipText = 'Selects which shift list is used to derive the displayed chemical shift values' self.shiftListPulldown = PulldownList(frame, callback=self.changeShiftList, grid=(0, 3), tipText=tipText) label = Label(frame, text=' List all shifts:', grid=(0, 4)) tipText = 'Sets whether to display all the chemical shifts for residues or just for the nominated atom types in columns' self.otherShiftsSelect = CheckButton(frame, callback=self.draw, grid=(0, 5), tipText=tipText) utilButtons = UtilityButtonList(frame, helpUrl=self.help_url, grid=(0, 7)) row += 1 frame = Frame(guiFrame, grid=(row, 0)) frame.expandGrid(None, 6) label = Label(frame, text=' 1-letter codes:', grid=(0, 0)) tipText = 'Whether to use 1-letter residue codes in the table, or otherwise Ccp/three-letter codes' self.oneLetterSelect = CheckButton(frame, callback=self.draw, grid=(0, 1), selected=False, tipText=tipText) precisions = [0.1, 0.01, 0.001] texts = [str(t) for t in precisions] label = Label(frame, text=' 1H precision:', grid=(0, 2)) tipText = 'Specifies how many decimal places to use when displaying 1H chemical shift values' self.protonPrecisionSelect = PulldownList(frame, texts=texts, objects=precisions, callback=self.draw, index=1, grid=(0, 3), tipText=tipText) label = Label(frame, text=' Other precision:') label.grid(row=0, column=4, sticky='w') tipText = 'Specifies how many decimal places to use when displaying chemical shift values for isotopes other than 1H' self.otherPrecisionSelect = PulldownList(frame, texts=texts, objects=precisions, callback=self.draw, index=1, grid=(0, 5), tipText=tipText) row += 1 frame = Frame(guiFrame, grid=(row, 0)) frame.expandGrid(None, 1) label = Label(frame, text='Column\nAtoms:', grid=(0, 0)) tipText = 'Selects which kinds of atoms are displayed in aligned columns, or otherwise displayed at the end of the residue row (if "List all shifts" is set)' self.optSelector = PartitionedSelector(frame, self.toggleOpt, tipText=tipText, maxRowObjects=10, grid=(0, 1), sticky='ew') options = ['H', 'N', 'C', 'CA', 'CB', 'CG'] self.optSelector.update(objects=options, labels=options, selected=['H', 'N', 'CA']) row += 1 guiFrame.expandGrid(row, 0) self.canvasFrame = ScrolledCanvas(guiFrame, relief='groove', width=650, borderwidth=2, resizeCallback=None, grid=(row, 0), padx=1, pady=1) self.canvas = self.canvasFrame.canvas #self.canvas.bind('<Button-1>', self.toggleResidue) row += 1 tipTexts = [ 'Output information from the table as PostScript file, for printing etc.', 'Output information from the table as a whitespace separated plain text file' ] commands = [self.makePostScript, self.exportText] texts = ['Export PostScript', 'Export Text'] buttonList = ButtonList(guiFrame, commands=commands, texts=texts, grid=(row, 0), tipTexts=tipTexts) chains = self.getChains() if len(chains) > 1: self.chain = chains[1] else: self.chain = None self.updateShiftLists() self.updateChains() self.otherShiftsSelect.set(True) self.update() for func in ('__init__', 'delete'): self.registerNotify(self.updateChains, 'ccp.molecule.MolSystem.Chain', func) for func in ('__init__', 'delete'): self.registerNotify(self.updateShiftLists, 'ccp.nmr.Nmr.ShiftList', func) def changeShiftList(self, shiftList): if shiftList is not self.shiftList: self.shiftList = shiftList self.update() def updateShiftLists(self, *opt): names = [] index = 0 shiftLists = getShiftLists(self.nmrProject) shiftList = self.shiftList if shiftLists: names = [ '%s [%d]' % (sl.name or '<No name>', sl.serial) for sl in shiftLists ] if shiftList not in shiftLists: shiftList = shiftLists[0] index = shiftLists.index(shiftList) if self.shiftList is not shiftList: self.shiftList = shiftList self.shiftListPulldown.setup(names, shiftLists, index) def getChains(self): chains = [ None, ] for molSystem in self.project.sortedMolSystems(): for chain in molSystem.sortedChains(): if len(chain.residues) > 1: chains.append(chain) return chains def changeChain(self, chain): if chain is not self.chain: self.chain = chain self.update() def updateChains(self, *opt): names = [] index = -1 chains = self.getChains() chain = self.chain if len(chains) > 1: names = [ 'None', ] + ['%s:%s' % (ch.molSystem.code, ch.code) for ch in chains[1:]] if chain not in chains: chain = chains[1] index = chains.index(chain) else: chain = None self.chainPulldown.setup(names, chains, index) def destroy(self): for func in ('__init__', 'delete'): self.unregisterNotify(self.updateChains, 'ccp.molecule.MolSystem.Chain', func) for func in ('__init__', 'delete'): self.unregisterNotify(self.updateShiftLists, 'ccp.nmr.Nmr.ShiftList', func) BasePopup.destroy(self) def exportText(self, *event): from memops.gui.FileSelect import FileType from memops.gui.FileSelectPopup import FileSelectPopup if self.textOut: fileTypes = [ FileType('Text', ['*.txt']), FileType('CSV', ['*.csv']), FileType('All', ['*']) ] fileSelectPopup = FileSelectPopup(self, file_types=fileTypes, title='Save table as text', dismiss_text='Cancel', selected_file_must_exist=False) fileName = fileSelectPopup.getFile() if fileName: file = open(fileName, 'w') if fileName.endswith('.csv'): for textRow in self.textMatrix: file.write(','.join(textRow) + '\n') else: file.write(self.textOut) def toggleOpt(self, selected): self.draw() def makePostScript(self): self.canvasFrame.printCanvas() def update(self): colors = [] selected = set() atomNames = set() if self.chain: for residue in self.chain.sortedResidues(): for atom in residue.atoms: chemAtom = atom.chemAtom if colorDict.get(chemAtom.elementSymbol) is None: continue if chemAtom.waterExchangeable: continue atomNames.add(atom.name[:2]) molType = residue.molResidue.molType if molType == 'protein': selected.add('H') selected.add('N') selected.add('CA') selected.add('C') elif molType == 'DNA': selected.add("C1'") elif molType == 'RNA': selected.add("C1'") elif molType == 'carbohydrate': selected.add("C1") else: for spinSystem in self.shiftList.nmrProject.resonanceGroups: if not spinSystem.residue: for resonance in spinSystem.resonances: for name in resonance.assignNames: atomNames.add(name) options = list(atomNames) molType = 'protein' if self.chain: molType = self.chain.molecule.molType options = greekSortAtomNames(options, molType=molType) if 'H' in options: options.remove('H') options = [ 'H', ] + options colors = [colorDict.get(n[0]) for n in options] if options and not selected: selected = [ options[0], ] self.optSelector.update(objects=options, labels=options, colors=colors, selected=list(selected)) self.draw() def draw(self, *opt): if not self.shiftList: return nmrProject = self.shiftList.nmrProject shiftList = self.shiftList font = self.font bfont = self.boldFont symbolFont = self.symbolFont sFont = self.smallFont bbox = self.canvas.bbox doOthers = self.otherShiftsSelect.get() spc = 4 gap = 14 x = gap y = gap ct = self.canvas.create_text cl = self.canvas.create_line cc = self.canvas.coords self.canvas.delete('all') ssDict = {} formatDict = { 0.1: '%.1f', 0.01: '%.2f', 0.001: '%.3f', } protonFormat = formatDict[self.protonPrecisionSelect.getObject()] otherFormat = formatDict[self.otherPrecisionSelect.getObject()] uSpinSystems = [] chains = set() molSystems = set() for spinSystem in nmrProject.resonanceGroups: residue = spinSystem.residue if residue: ssDict[residue] = ssDict.get(residue, []) + [ spinSystem, ] else: uSpinSystems.append((spinSystem.serial, spinSystem)) uSpinSystems.sort() commonAtoms = self.optSelector.getSelected() N = len(commonAtoms) chain = self.chain if chain: spinSystems = [] for residue in chain.sortedResidues(): spinSystems0 = ssDict.get(residue, []) for spinSystem in spinSystems0: if spinSystem and spinSystem.resonances: spinSystems.append([residue, spinSystem]) else: spinSystems = uSpinSystems strings = [] doOneLetter = self.oneLetterSelect.get() if spinSystems: x = gap y += gap numItems = [] codeItems = [] commonItems = [] otherItems = [] numWidth = 0 codeWidth = 0 commonWidths = [0] * N commonCounts = [0] * N for residue, spinSystem in spinSystems: if type(residue) is type(1): seqNum = '{%d}' % residue if doOneLetter: ccpCode = '-' else: ccpCode = spinSystem.ccpCode or '' else: if doOneLetter: ccpCode = residue.chemCompVar.chemComp.code1Letter else: ccpCode = getResidueCode(residue) seqNum = str(residue.seqCode) subStrings = [] subStrings.append(seqNum) subStrings.append(ccpCode) item = ct(x, y, text=seqNum, font=font, anchor='se') box = bbox(item) iWidth = box[2] - box[0] numWidth = max(numWidth, iWidth) numItems.append(item) item = ct(x, y, text=ccpCode, font=font, anchor='sw') box = bbox(item) iWidth = box[2] - box[0] codeWidth = max(codeWidth, iWidth) codeItems.append(item) commonShifts, commonElements, otherShifts = self.getShiftData( spinSystem, shiftList, commonAtoms) items = [] for i in range(N): values = commonShifts[i] element = commonElements[i] if element == 'H': shiftFormat = protonFormat else: shiftFormat = otherFormat subItems = [] for value in values: text = shiftFormat % value if text: item = ct(x, y, text=text, font=font, anchor='se') box = bbox(item) iWidth = box[2] - box[0] commonWidths[i] = max(commonWidths[i], iWidth) commonCounts[i] += 1 subItems.append(item) subStrings.append( ','.join([shiftFormat % v for v in values]) or '-') items.append(subItems) commonItems.append(items) if doOthers: items0 = [] i = 0 I = len(otherShifts) for atomLabel, element, value in otherShifts: label = atomLabel if label[0] == '?': label = label[3:-1] if element == 'H': shiftFormat = protonFormat else: shiftFormat = otherFormat subStrings.append('%6s:%-4s' % (shiftFormat % value, label)) i += 1 atoms = atomLabel.split('|') items = [] j = 0 for atom in atoms: text = element if j > 0: text = '/' + text item = ct(x, y, text=text, font=font, anchor='sw') box = bbox(item) iWidth = box[2] - box[0] - 3 items.append((iWidth, item, 0)) p = len(element) if len(atom) > p: letter = atom[p] if letter not in ('0123456789'): item = ct(x, y, text=letter.lower(), font=symbolFont, anchor='sw') box = bbox(item) iWidth = box[2] - box[0] - 2 items.append((iWidth, item, -4)) p += 1 text = atom[p:] if text: item = ct(x, y, text=text, font=sFont, anchor='sw') box = bbox(item) iWidth = box[2] - box[0] - 2 items.append((iWidth, item, -4)) j += 1 text = ' ' + shiftFormat % value if i != I: text += ',' item = ct(x, y, text=text, font=font, anchor='sw') box = bbox(item) iWidth = box[2] - box[0] - 4 items.append((iWidth, item, 0)) items0.append(items) otherItems.append(items0) strings.append(subStrings) y0 = y x = x0 = gap + numWidth + codeWidth + spc + spc for i in range(N): if not commonCounts[i]: continue x += commonWidths[i] + spc + spc element = commonElements[i] iWidth = 0 text = commonAtoms[i][len(element):].lower() if text: item = ct(x, y, text=text, font=symbolFont, anchor='se') box = bbox(item) iWidth = box[2] - box[0] - 2 ct(x - iWidth, y, text=element, font=font, anchor='se') y += gap for i in range(len(numItems)): x = gap + numWidth + spc cc(numItems[i], x, y) x += spc cc(codeItems[i], x, y) x += codeWidth x1 = x + spc yM = y for j in range(N): if not commonCounts[j]: continue x += commonWidths[j] + spc + spc items = commonItems[i][j] yB = y - gap for item in items: yB += gap cc(item, x, yB) yM = max(yB, yM) x += gap if doOthers: x += spc x3 = x for items in otherItems[i]: if x > 550: x = x3 y += gap for iWidth, item, dy in items: cc(item, x, y + dy) x += iWidth y = max(y, yM) y += gap x = x0 for i in range(N): if not commonCounts[i]: continue x += commonWidths[i] + spc + spc cl(x + 8, y0, x + 8, y - gap, width=0.3, fill='#808080') cl(x1, y0, x1, y - gap, width=0.3, fill='#808080') cl(0, 0, 550, 0, width=0.3, fill='#FFFFFF') y += gap textWidths = {} for subStrings in strings: for i, text in enumerate(subStrings): if text and len(text) > textWidths.get(i, 0): textWidths[i] = len(text) else: textWidths[i] = 0 formats = {} for i in textWidths.keys(): formats[i] = ' %%%ds' % max(6, textWidths[i]) textOut = '!' textRow = ['', ''] textMatrix = [textRow] textOut += ' ' * (max(6, textWidths.get(0, 6)) + max(6, textWidths.get(1, 6)) + 1) i = 2 for atom in commonAtoms: if i in formats: textOut += formats[i] % atom textRow.append((formats[i] % atom).strip()) i += 1 textOut += '\n' for subStrings in strings: textRow = [] textMatrix.append(textRow) i = 0 for text in subStrings: textOut += formats[i] % text textRow.append((formats[i] % text).strip()) i += 1 textOut += '\n' self.textOut = textOut self.textMatrix = textMatrix def getShiftData(self, spinSystem, shiftList, commonAtoms=('H', 'N', 'CA', 'CB')): commonShifts = [] commonResonances = {} commonElements = [] for atomName in commonAtoms: elements = set() resonances = [] for resonance in spinSystem.resonances: resonanceSet = resonance.resonanceSet if resonanceSet: for atomSet in resonanceSet.atomSets: for atom in atomSet.atoms: if atomName == atom.name[:2]: resonances.append(resonance) break else: continue break else: for assignName in resonance.assignNames: if atomName == assignName[:2]: resonances.append(resonance) break shiftValues = [] for resonance in resonances: isotope = resonance.isotope if isotope: elements.add(isotope.chemElement.symbol) commonResonances[resonance] = True shift = resonance.findFirstShift(parentList=shiftList) if shift: shiftValues.append(shift.value) if not elements: element = atomName[0] else: element = elements.pop() commonElements.append(element) commonShifts.append(shiftValues) otherShifts = [] for resonance in spinSystem.resonances: if not commonResonances.get(resonance): shift = resonance.findFirstShift(parentList=shiftList) if shift: isotope = resonance.isotope if isotope: element = isotope.chemElement.symbol else: element = '??' if resonance.assignNames or resonance.resonanceSet: name = getResonanceName(resonance) else: name = '??[%d]' % resonance.serial otherShifts.append((name, element, shift.value)) molType = 'protein' if spinSystem.residue: molType = spinSystem.residue.molResidue.molType otherShifts = greekSortAtomNames(otherShifts, molType) return commonShifts, commonElements, otherShifts
class SpinSystemTypeScoresPopup(BasePopup): """ **Predict Residue Type for a Spin System of Resonances** This tool aims to predict the residue type of a spin system based upon the chemical shifts of the resonances that it contains. The general principle is that different kinds of atoms in different kinds of residues have different observed distributions of chemical shifts. This system uses chemical shift distributions from the RefDB database, or otherwise from the BMRB where data is not available in RefDB. The observed chemical shifts of a spin system are compared to the per-atom distributions for each residue type and the residue types with the best matches are deemed to be more likely. This system can work with various levels of information, although the more information the better. Naturally, the more chemical shifts you have in a spin system then the better the prediction of type, and 13C resonances are more distinctive than 1H on the whole. Also, setting the atom type of a resonance can have a big influence on the type of residue predicted, for example knowing that a 13C resonance at 63 ppm is of type CB points very strongly toward the residue being a serine. Atom type information can come from two sources: from a specific type assignment made by the user (via this popup or elsewhere) or by virtue of assignment in an experimental dimension that detects a restricted class of atom - e.g. 13C resonances in an HNCA experiment, assuming their shift matches, are of CA type as far as this prediction is concerned. Resonances that do not have a known atom type are compared with all of the unallocated types to find the combination that is most likely. The residue type prediction is based on the list of resonances displayed in the upper table. Here the user can see the chemical shifts (from the selected shift list) and any specific atom type setting. The user may set the atom type for any of the resonances, which would normally be done to reduce prediction ambiguity, by double-clicking in the "Atom Type" column. The lower table shows a ranked list of the probable residue types. All probability scores are normalised and represented as a percentage of the total of all scores, considering residue types in the selected chain. The type of a spin system may be set by clicking on a row of the lower table (hopefully a unique and high-scoring option) and then selecting [Assign Spin System Type]. If the user attempts to change the type of a spin system that is currently assigned to a specific residue then there is an opportunity to back out of the assignment, but otherwise any sequence specific information will be removed. **Caveats & Tips** It is assumed that the spectra from which the chemical shifts are derived are fairly well referenced. A type prediction will always be given, no matter how few resonances are present in a spin system. This system says which of the available types are most likely, *not how reliable* the prediction is; the latter depends largely on the amount of information present. The user should not for example make a judgement based only on amide resonances. Reliability scores will be added in the future. Rouge resonances in a spin system often adversely affect the prediction, if something is not genuinely in the spin system it should be removed. The system will never predict the residue type to be something that does not appear in the selected molecular chain. Thus, make sure the chain selection is appropriate for your prediction. **Reference** The residue type prediction method is not published independently but is very similar to the Bayesian method presented in: *Marin A, Malliavin TE, Nicolas P, Delsuc MA. From NMR chemical shifts to amino acid types: investigation of the predictive power carried by nuclei. J Biomol NMR. 2004 Sep;30(1):47-60.* One major difference however is that probabilities for resonances not being observed are not used. The CCPN prediction method is not only for complete spin systems and may be used at any time during the assignment process; here missing resonances are mostly due to the current assignment state and not such a useful indicator of residue type. """ def __init__(self, parent, spinSystem=None, chain=None, *args, **kw): self.spinSystem = spinSystem self.shiftList = None self.resonance = None self.isotopes = ('1H','13C','15N') self.chain = chain self.ccpCode = None self.waiting = False self.atomTypes = {} self.project = parent.project self.guiParent = parent BasePopup.__init__(self, parent, title="Spin System Type Scores", **kw) def body(self, guiFrame): guiFrame.grid_columnconfigure(3, weight=1) row = 0 label = Label(guiFrame, text='Spin System: ', grid=(row,0)) tipText = 'Indicates which spin system the residue type prediction is done for' self.spinSystemLabel = Label(guiFrame, text='Serial: Assignment:', grid=(row,1), gridSpan=(1,3), tipText=tipText) row += 1 label = Label(guiFrame, text='Shift List: ', grid=(row,0)) tipText = 'Selects which shift list is the source of chemical shift information to make the residue type prediction' self.shiftListPulldown = PulldownList(guiFrame, tipText=tipText, callback=self.setShiftList, grid=(row,1)) label = Label(guiFrame, text='Chain: ', grid=(row,2)) tipText = 'Selects which molecular chain the prediction is for; sets prior probabilities for the various residue types' self.chainPulldown = PulldownList(guiFrame, self.changeChain, grid=(row,3), tipText=tipText) row += 1 labelFrame = LabelFrame(guiFrame, text='Resonances', grid=(row,0), gridSpan=(1,4)) labelFrame.expandGrid(0,0) self.atomTypePulldown = PulldownList(self, callback=self.setAtomType) editWidgets = [ None, None, None, None, self.atomTypePulldown ] editGetCallbacks = [ None, None, None, None, self.getAtomType] editSetCallbacks = [ None, None, None, None, self.setAtomType] tipTexts = ['The nuclear isotope type of the resonance within the current spin system', 'The assignment annotation for the spin system resonance within the current spin system', 'The chemical shift of the resonance in the stated shift list', 'The weighted standard deviation of the resonance chemical shift', 'The current atom type of the resonance; when set this helps refine residue type prediction'] headingList = ['Isotope','Name','Shift\nValue','Shift\nError','Atom\nType'] self.resonanceMatrix = ScrolledMatrix(labelFrame, editWidgets=editWidgets, multiSelect=False, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, headingList=headingList, callback=self.selectResonance, grid=(0,0), tipTexts=tipTexts) tipTexts = ['Remove the selected resonance from the current spin system', 'Remove residue type information from the current spin system', 'Show a table of information for the selected resonance, including a list of all peak dimension positions', 'Show a table of the peaks to which the selected resonance is assigned'] texts = ['Remove From\nSpin System', 'Deassign\nResidue Type', 'Resonance\nInfo', 'Show\nPeaks'] commands = [self.removeResonance, self.deassignType, self.showResonanceInfo, self.showPeaks] buttonList = ButtonList(labelFrame, texts=texts, commands=commands, grid=(1,0), tipTexts=tipTexts) self.resButtons = buttonList.buttons row += 1 guiFrame.grid_rowconfigure(row, weight=1) labelFrame = LabelFrame(guiFrame, text='Type Scores', grid=(row,0), gridSpan=(1,4)) labelFrame.expandGrid(0,0) tipTexts = ['The ranking of the residue type possibility for the current spin system', 'The CCPN residue code for the type', 'The estimated percentage probability of the spin system being the residue type'] headingList = ['Rank','Ccp Code','% Probability'] self.scoresMatrix = ScrolledMatrix(labelFrame, headingList=headingList, callback=self.selectCcpCode, grid=(0,0), tipTexts=tipTexts) row += 1 tipTexts = ['Assign the residue type of the current spin system to the kind selected in the lower table',] texts = ['Assign Spin System Type'] commands = [self.assign] bottomButtons = UtilityButtonList(guiFrame, texts=texts, commands=commands, helpUrl=self.help_url, grid=(row,0), gridSpan=(1,4), tipTexts=tipTexts) self.assignButton = bottomButtons.buttons[0] self.updateShiftLists() self.updateChains() self.getChainAtomTypes() self.update() self.curateNotifiers(self.registerNotify) def curateNotifiers(self, notifyFunc): for func in ('addResonance','removeResonance', 'setResonances','delete','setName'): notifyFunc(self.updateAfter, 'ccp.nmr.Nmr.ResonanceGroup', func) for func in ('setResonanceSet','addAssignName', 'removeAssignName','setAssignNames'): notifyFunc(self.updateAfter, 'ccp.nmr.Nmr.Resonance', func) for func in ('__init__','delete'): notifyFunc(self.updateAfter, 'ccp.nmr.Nmr.ResonanceSet', func) notifyFunc(self.updateChains, 'ccp.molecule.MolSystem.Chain', func) notifyFunc(self.updateShiftLists, 'ccp.nmr.Nmr.ShiftList', func) for func in ('__init__','setValue'): notifyFunc(self.updateShiftAfter, 'ccp.nmr.Nmr.Shift', func) def getChainAtomTypes(self): doneResType = {} atomTypes = atomTypes = {} for isotope in DEFAULT_ISOTOPES.values(): atomTypes[isotope] = set() if self.chain: for residue in self.chain.residues: molResidue = residue.molResidue ccpCode = molResidue.ccpCode molType = molResidue.molType key = '%s:%s:%s' % (ccpCode, molResidue.linking, molResidue.descriptor) if doneResType.get(key): continue doneResType[key] = True for atom in residue.atoms: chemAtom = atom.chemAtom element = chemAtom.elementSymbol isotope = DEFAULT_ISOTOPES.get(element) if not isotope: continue atomTypes[isotope].add((ccpCode, atom.name, molType)) self.atomTypes = atomTypes def getAtomType(self, resonance): index = 0 atomNames = set(['<None>',]) assignNames = resonance.assignNames if assignNames: for atomName in assignNames: atomNames.add(atomName) if len(assignNames) > 1: orig = ','.join(assignNames) atomNames.add(orig) shift = resonance.findFirstShift(parentList=self.shiftList) if shift and self.chain: project = self.project atomTypes = self.atomTypes.get(resonance.isotopeCode, []) for ccpCode, atomName, molType in atomTypes: prob = lookupAtomProbability(project, ccpCode, atomName, shift.value, molType=molType) if prob >= 0.001: atomNames.add(atomName) atomNames = list(atomNames) atomNames.sort() if resonance.assignNames: orig = ','.join(assignNames) index = atomNames.index(orig) atomNameObjs = atomNames[:] atomNameObjs[0] = None self.atomTypePulldown.setup(atomNames, atomNameObjs, index) def setAtomType(self, obj): atomNameStr = self.atomTypePulldown.getObject() if self.resonance: if atomNameStr: atomNames = atomNameStr.split(',') assignResonanceType(self.resonance, assignNames=atomNames) else: assignResonanceType(self.resonance, assignNames=None) def removeResonance(self): if self.resonance and self.spinSystem and (self.resonance in self.spinSystem.resonances): if showOkCancel('Confirm','Really remove resonance from spin system?', parent=self): self.spinSystem.codeScoreDict = {} deassignResonance(self.resonance, clearAssignNames=False) removeSpinSystemResonance(self.spinSystem, self.resonance) def showResonanceInfo(self): if self.resonance: self.guiParent.browseResonanceInfo(self.resonance) def showPeaks(self): if self.resonance: peaksDict = {} for contrib in self.resonance.peakDimContribs: peaksDict[contrib.peakDim.peak] = 1 peaks = peaksDict.keys() if len(peaks) > 0: self.guiParent.viewPeaks(peaks) def deassignType(self): if self.spinSystem: residue = self.spinSystem.residue if residue: resText = '%d%s' % (residue.seqCode, residue.ccpCode) msg = 'Spin system assigned to %s. Continue and deassign residue?' if showOkCancel('Warning', msg % resText, parent=self): assignSpinSystemResidue(self.spinSystem, None) assignSpinSystemType(self.spinSystem, None) else: assignSpinSystemType(self.spinSystem,None) def assign(self): if self.spinSystem and self.ccpCode: if self.spinSystem.residue and (self.spinSystem.residue.ccpCode != self.ccpCode): resText = '%d%s' % (self.spinSystem.residue.seqCode, self.spinSystem.residue.ccpCode) msg = 'Spin system is already assigned to %s. Continue?' if showOkCancel('Warning', msg % resText, parent=self): assignSpinSystemResidue(self.spinSystem,residue=None) else: return if self.spinSystem.ccpCode != self.ccpCode: assignSpinSystemType(self.spinSystem,self.ccpCode,'protein') self.update() def getChains(self): chains = [] if self.project: for molSystem in self.project.sortedMolSystems(): for chain in molSystem.sortedChains(): if chain.molecule.molType in ('protein',None): text = '%s:%s' % (molSystem.code, chain.code) chains.append( [text, chain] ) return chains def changeChain(self, chain): if self.chain is not chain: self.chain = chain self.getChainAtomTypes() self.updateAfter() def updateChains(self, *chain): data = self.getChains() names = [x[0] for x in data] chains = [x[1] for x in data] chain = self.chain index = 0 if chains: if chain not in chains: chain = chains[0] index = chains.index(chain) if chain is not self.chain: self.chain = chain self.getChainAtomTypes() self.updateAfter() self.chainPulldown.setup(names, chains, index) def updateShiftLists(self, *opt): shiftLists = getShiftLists(self.nmrProject) shiftList = self.shiftList names = ['%s [%d]' % (x.name or '<No name>', x.serial) for x in shiftLists] index = 0 if names: if shiftList not in shiftLists: shiftList = shiftLists[0] index = shiftLists.index(shiftList) if shiftList is not self.shiftList: self.shiftList = shiftList self.updateAfter() self.shiftListPulldown.setup(names, shiftLists, index) def setShiftList(self, shiftList): if self.shiftList is not shiftList: self.shiftList = shiftList self.updateAfter() def updateButtons(self): if self.resonance: self.resButtons[0].enable() self.resButtons[2].enable() self.resButtons[3].enable() else: self.resButtons[0].disable() self.resButtons[2].disable() self.resButtons[3].disable() if self.spinSystem and (self.spinSystem.residue or self.spinSystem.ccpCode): self.resButtons[1].enable() else: self.resButtons[1].disable() if self.ccpCode and self.spinSystem: self.assignButton.enable() else: self.assignButton.disable() def selectResonance(self, resonance, row, col): self.resonance = resonance self.updateButtons() def selectCcpCode(self, ccpCode, row, col): self.ccpCode = ccpCode self.assignButton.enable() def clearSpinSystemCache(self): if self.spinSystem: self.spinSystem.sstTypes = [] self.spinSystem.ssScore = None self.spinSystem.codeScoreDict = {} def updateShiftAfter(self, shift): if shift.parentList is not self.shiftList: return resonance = shift.resonance if resonance.resonanceGroup is not self.spinSystem: return self.clearSpinSystemCache() self.updateAfter() def updateAfter(self, obj=None): if obj and obj is self.spinSystem: self.clearSpinSystemCache() if obj.isDeleted: self.spinSystem = None if self.spinSystem: if obj is not None: if obj.className == 'ResonanceSet': for resonance in obj.resonances: if resonance.resonanceGroup is self.spinSystem: break else: return elif obj.className == 'Resonance': if obj.resonanceGroup is not self.spinSystem: return self.clearSpinSystemCache() if self.waiting: return else: self.waiting = True self.after_idle(self.update) def update(self, spinSystem=None, chain=None, shiftList=None): if spinSystem is not None: if spinSystem is not self.spinSystem: self.ccpCode = None self.spinSystem = spinSystem if chain: self.chain = chain else: self.chain = None self.updateChains() self.getChainAtomTypes() if shiftList: self.shiftList = shiftList self.updateShiftLists() if self.resonance: if not self.spinSystem: self.resonance = None elif self.resonance.resonanceGroup is not self.spinSystem: self.resonance = None self.updateButtons() textMatrix = [] objectList = [] if self.spinSystem: if self.spinSystem.residue: resText = 'Assignment: %d%s' % (self.spinSystem.residue.seqCode, self.spinSystem.residue.ccpCode) elif self.spinSystem.ccpCode: resText = 'Type: %s' % self.spinSystem.ccpCode elif self.spinSystem.name: resText = 'Name: %s' % self.spinSystem.name else: resText = 'Unassigned' self.spinSystemLabel.set('Serial: %d %s' % (self.spinSystem.serial, resText)) for resonance in self.spinSystem.resonances: shift = resonance.findFirstShift(parentList=self.shiftList) if shift: datum = [resonance.isotopeCode, getResonanceName(resonance), shift.value, shift.error, '/'.join(resonance.assignNames)] objectList.append(resonance) textMatrix.append(datum) self.resonanceMatrix.update(textMatrix=textMatrix, objectList=objectList) textMatrix = [] objectList = [] colorMatrix = [] if self.spinSystem and self.chain and self.shiftList: shifts = [] for resonance in self.spinSystem.resonances: if resonance.isotopeCode in self.isotopes: shift = resonance.findFirstShift(parentList=self.shiftList) if shift: shifts.append(shift) scores = getShiftsChainProbabilities(shifts, self.chain) total = sum(scores.values()) scoreList = [] if total: ccpCodes = self.getCcpCodes(self.chain) baseLevel = 100.0/len(ccpCodes) for ccpCode in ccpCodes: scoreList.append( (100.0*scores[ccpCode]/total, ccpCode) ) scoreList.sort() scoreList.reverse() i = 0 for score, ccpCode in scoreList: if not score: continue i += 1 datum = [i, ccpCode,score] if score >= min(100.0,5*baseLevel): color = '#80ff80' elif score > 2*baseLevel: color = '#ffff80' elif score > baseLevel: color = '#ffc080' else: color = '#ff8080' colors = [color, color, color] objectList.append(ccpCode) textMatrix.append(datum) colorMatrix.append(colors) self.scoresMatrix.update(textMatrix=textMatrix, colorMatrix=colorMatrix, objectList=objectList) self.waiting = False def getCcpCodes(self, chain): ccpDict = {} for residue in chain.residues: ccpCode = residue.ccpCode #if (ccpCode == 'Cys') and (residue.descriptor == 'link:SG'): # ccpCode = 'Cyss' ccpDict[ccpCode] = True ccpCodes = ccpDict.keys() ccpCodes.sort() return ccpCodes def destroy(self): self.curateNotifiers(self.unregisterNotify) BasePopup.destroy(self)
class DangleFrame(Frame): def __init__(self, parent, dangleGui, project=None, *args, **kw): self.guiParent = parent self.dangleGui = dangleGui self.dangleDir = None self.dangleChain = None self.dangleResidue = None #self.outDir = OUTDIR self.row = None self.col = None self.project = project self.nmrProject = None self.colorScheme = 'red' self.chain = None self.shiftList = None self.dangleStore = False # Not None self.constraintSet = None self.ensemble = None Frame.__init__(self, parent=parent) self.grid_columnconfigure(0, weight=1) self.grid_rowconfigure(1, weight=1) row = 0 # TOP LEFT FRAME frame = LabelFrame(self, text='Options') frame.grid(row=row, column=0, sticky='nsew') frame.columnconfigure(5, weight=1) label = Label(frame, text='Chain') label.grid(row=0, column=0, sticky='w') self.chainPulldown = PulldownList( frame, callback=self.changeChain, tipText='Choose the molecular system chain to make predictions for' ) self.chainPulldown.grid(row=0, column=1, sticky='w') label = Label(frame, text='Shift List') label.grid(row=0, column=2, sticky='w') self.shiftListPulldown = PulldownList( frame, callback=self.changeShiftList, tipText='Select the shift list to take input chemical shifts from') self.shiftListPulldown.grid(row=0, column=3, sticky='w') label = Label(frame, text='Max No. of Islands:') label.grid(row=0, column=4, sticky='w') sizes = range(10) texts = [str(s) for s in sizes] + [ 'Do not reject', ] self.rejectPulldown = PulldownList( frame, texts=texts, objects=sizes + [ None, ], tipText= 'Select the maximum allowed number of disontinuous prediction islands' ) self.rejectPulldown.set(DEFAULT_MAX_ISLANDS) # Actual value not index self.rejectPulldown.grid(row=0, column=5, sticky='w') label = Label(frame, text='Dangle Run:') label.grid(row=1, column=0, sticky='w') self.dangleStorePulldown = PulldownList( frame, callback=self.changeDangleStore, tipText='Select a run number to store DANGLE results within') self.dangleStorePulldown.grid(row=1, column=1, sticky='w') label = Label(frame, text='Ensemble:') label.grid(row=1, column=2, sticky='w') self.ensemblePulldown = PulldownList( frame, callback=self.changeEnsemble, tipText= 'Select the structure ensemble for superimposition of angle values on the GLE plots' ) self.ensemblePulldown.grid(row=1, column=3, sticky='w') label = Label(frame, text='Restraint Set:') label.grid(row=1, column=4, sticky='w') self.constrSetPulldown = PulldownList( frame, callback=self.changeConstraintSet, tipText= 'Select the CCPN restraint set to store DANGLE dihedral angle restraints in' ) self.constrSetPulldown.grid(row=1, column=5, sticky='w') # TOP RIGHT FRAME outerFrame = Frame(self) outerFrame.grid(row=row, column=1, rowspan=2, sticky='nsew') outerFrame.rowconfigure(0, weight=1) outerFrame.columnconfigure(0, weight=1) frame = LabelFrame(outerFrame, text='Global Likelihood Estimates') frame.grid(row=0, column=0, sticky='nsew') frame.rowconfigure(1, weight=1) frame.columnconfigure(2, weight=1) self.prevPlot = ViewRamachandranFrame(frame, relief='sunken', defaultPlot=False, width=180, height=180, bgColor=self.cget('bg'), nullColor='#000000', titleText='Previous', xTicks=False, yTicks=False, xLabel='', yLabel='', showCoords=False) self.prevPlot.grid(row=0, column=0, sticky='nsew') self.prevPlot.getPlotColor = self.getPlotColor self.nextPlot = ViewRamachandranFrame(frame, relief='sunken', defaultPlot=False, width=180, height=180, bgColor=self.cget('bg'), nullColor='#000000', titleText='Next', xTicks=False, yTicks=False, xLabel='', yLabel='', showCoords=False) self.nextPlot.grid(row=0, column=1, sticky='nsew') self.nextPlot.getPlotColor = self.getPlotColor self.plot = ViewRamachandranFrame(frame, relief='sunken', defaultPlot=False, width=360, height=360, bgColor=self.cget('bg'), nullColor='#000000') self.plot.grid(row=1, column=0, columnspan=2, sticky='nsew') self.plot.selectColor = '#FFB0B0' self.plot.getPlotColor = self.getPlotColor # BOTTOM RIGHT FRAME frame = Frame(outerFrame) frame.grid(row=1, column=0, sticky='nsew') frame.rowconfigure(0, weight=1) frame.columnconfigure(0, weight=1) texts = ('Previous', ' Next ') commands = (self.showPrevious, self.showNext) tipTexts = [ 'Show GLE plot of angle predictions for previous residue in chain', 'Show GLE plot of angle predictions for next residue in chain' ] buttonList = ButtonList(frame, texts, commands, tipTexts=tipTexts) buttonList.grid(row=0, column=0, sticky='nsew') row += 1 # BOTTOM LEFT FRAME frame = LabelFrame(self, text='Dihedral Angle Predictions') frame.grid(row=row, column=0, sticky='nsew') frame.grid_columnconfigure(0, weight=1) frame.grid_rowconfigure(0, weight=1) self.floatEntry = FloatEntry(self, text='', returnCallback=self.setFloatEntry, width=10, formatPlaces=9) tipTexts = [ 'Residue number in chain', 'Residue type code', 'Number of high scoring discontinuous angle predictions', 'Predicted secondary structure code', 'Predicted phi dihedral angle (CO-N-CA-CO)', 'Predicted psi dihedral angle (N-CA-CO-N)', 'Upper bound of phi angle prediction', 'Lower bound of phi angle prediction', 'Upper bound of psi angle prediction', 'Lower bound of phi angle prediction', 'Chemical shifts used in prediction' ] headingList = [ 'Res\nNum', 'Res\nType', 'No. of\nIslands', 'SS', 'Phi', 'Psi', 'Phi\nUpper', 'Phi\nLower', 'Psi\nUpper', 'Psi\nLower', 'Chemical Shifts' ] editWidgets = [ None, None, None, None, self.floatEntry, self.floatEntry, self.floatEntry, self.floatEntry, self.floatEntry, self.floatEntry ] editGetCallbacks = [ None, None, None, None, self.getFloatEntry, self.getFloatEntry, self.getFloatEntry, self.getFloatEntry, self.getFloatEntry, self.getFloatEntry ] editSetCallbacks = [ None, None, None, None, self.setFloatEntry, self.setFloatEntry, self.setFloatEntry, self.setFloatEntry, self.setFloatEntry, self.setFloatEntry ] self.predictionMatrix = ScrolledMatrix( frame, headingList=headingList, multiSelect=True, callback=self.selectCell, tipTexts=tipTexts, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks) # doubleCallback=self.loadGLEs) self.predictionMatrix.grid(row=0, column=0, sticky='nsew') row += 1 tipTexts = [ 'Remove the predictions for the selected residues', 'Run the DANGLE method to predict dihedral angles and secondary structure', 'Delete the DANGLE results stored under the current run number', 'Store the angle predictions and bounds in a new CCPN dihedral angle restraint list', 'Store the secondary structure predictions in the CCPN project' ] texts = [ 'Clear\nSelected', 'Run Prediction!', 'Delete\nCurrent Run', 'Commit\nRestraints', 'Commit\nSecondary Structure' ] commands = [ self.clearSelected, self.runDangle, self.deleteRun, self.storeDihedralConstraints, self.storeSecondaryStructure ] self.buttonList = createDismissHelpButtonList( self, texts=texts, commands=commands, # dismiss_text='Quit', dismiss_cmd=self.dangleGui.quit, help_url=self.dangleGui.help_url, expands=True, tipTexts=tipTexts) self.buttonList.grid(row=row, column=0, columnspan=2, sticky='ew') self.buttonList.buttons[1].config(bg='#C0FFFF') self.updateProject(project) self.notify(dangleGui.registerNotify) def destroy(self): self.notify(self.dangleGui.unregisterNotify) Frame.destroy(self) def notify(self, notifyfunc): for func in ('__init__', 'delete'): notifyfunc(self.updateChainPulldown, 'ccp.molecule.MolSystem.Chain', func) for func in ('__init__', 'delete', 'setName'): notifyfunc(self.updateShiftListPulldown, 'ccp.nmr.Nmr.ShiftList', func) for func in ('__init__', 'delete'): notifyfunc(self.updateConstrSetPulldown, 'ccp.nmr.NmrConstraint.NmrConstraintStore', func) for func in ('__init__', 'delete'): notifyfunc(self.updateEnsemblePulldown, 'ccp.molecule.MolStructure.StructureEnsemble', func) def updateProject(self, project): if project: self.project = project self.nmrProject = project.currentNmrProject or self.project.newNmrProject( name=project.name) self.updateShiftListPulldown() self.updateChainPulldown() self.updateDangleStorePulldown() self.updateConstrSetPulldown() self.updateEnsemblePulldown() self.updatePredictionMatrixAfter() def makeDangleInput(self, filename, chain, shiftList): if (not chain) or (not shiftList): return residues = chain.sortedResidues() seq = '' for residue in residues: if residue.molResidue.chemComp.code1Letter: seq += residue.molResidue.chemComp.code1Letter else: seq += 'X' res_0 = residues[0].seqId fopen = open(filename, 'w') fopen.write('<entry>\n') fopen.write('\t<res_0>%d</res_0>\n' % res_0) fopen.write('\t<seq_1>%s</seq_1>\n' % seq) fopen.write('\t<chain>%s</chain>\n' % chain.code) fopen.write('\t<cs_data>\n') fopen.write('\t\t<!-- res_id res_name atom_name chemical_shift -->') numShift = 0 for residue in residues: for atom in residue.atoms: atomSet = atom.getAtomSet() shifts = getAtomSetShifts(atomSet, shiftList=shiftList) if (len(shifts) == 0): continue # to average ambiguous chemical shifts ??????????????????????????? value = 0. for shift in shifts: value += shift.value value = value / float(len(shifts)) at_name = atom.name res_name = residue.ccpCode res_num = residue.seqId fopen.write('\n\t\t%5s\t%s\t%-4s\t%.3f' % (res_num, res_name, at_name, value)) numShift += 1 fopen.write('\n\t</cs_data>\n') fopen.write('</entry>\n') fopen.close() return numShift def deleteRun(self): if self.dangleStore: msg = 'Really delete DANGLE run "%s"?' % self.dangleStore.name if showOkCancel('Confirm', msg, parent=self): self.dangleStore.delete() self.dangleStore = None self.dangleChain = None self.updatePredictionMatrix() self.updateDangleStorePulldown() def runDangle(self): chain = self.chain shiftList = self.shiftList if (not chain) or (not shiftList): showError('Cannot Run DANGLE', 'Please specify a chain and a shift list.', parent=self) return # check if there is a DangleChain available self.checkDangleStore() dangleStore = self.dangleStore if not dangleStore: return dangleChain = dangleStore.findFirstDangleChain(chain=chain) if dangleChain: data = (chain.code, dangleChain.shiftList.serial) msg = 'Predictions for Chain %s using Shift List %d already exist.\nReplace data?' % data if not showYesNo('Replace Data', msg, parent=self): return else: self.dangleChain = dangleChain dangleChain.shiftList = shiftList else: self.dangleChain = dangleStore.newDangleChain(chain=chain, shiftList=shiftList) #dangleStore.packageLocator.repositories[0].url.dataLocation = '/home/msc51/ccpn/NexusTestGI' #dangleStore.packageName = 'cambridge.dangle' repository = dangleStore.packageLocator.repositories[0] array = dangleStore.packageName.split('.') path = os.path.join(repository.url.dataLocation, *array) path = os.path.join(path, dangleStore.name, chain.code) # Dangle_dir/dangleStoreName/chainCode if not os.path.exists(path): os.makedirs(path) self.dangleDir = path inputFile = os.path.join(self.dangleDir, 'dangle_cs.inp') if os.path.isfile(inputFile): os.unlink(inputFile) outputFile = os.path.join(self.dangleDir, 'danglePred.txt') if os.path.isfile(outputFile): os.unlink(outputFile) numShift = self.makeDangleInput(inputFile, chain, shiftList) if not os.path.isfile(inputFile): msg = 'No DANGLE input has been generated.\nPlease check shift lists.' showError('File Does Not Exist', msg, parent=self) return if numShift == 0: msg = 'No shift data in input file.\nPerhaps shifts are not assigned.\nContinue prediction anyway?' if not showYesNo('Empty DANGLE input', msg, parent=self): return rejectThresh = self.rejectPulldown.getObject() # Use the Reference info from the main installation # location must be absolute because DANGLE could be run from anywhere location = os.path.dirname(dangleModule.__file__) progressBar = ProgressBar(self) self.update_idletasks() dangle = Dangle(location, inputFile=inputFile, outputDir=self.dangleDir, reject=rejectThresh, angleOnly=False, progressBar=progressBar, writePgm=False) #self.dangleDir = '/home/msc51/nexus/gItest/DanglePred/' #outputFile = '/home/msc51/nexus/gItest/DanglePred/danglePred.txt' predictions = dangle.predictor.predictions gleScores = dangle.predictor.gleScores self.readPredictions(predictions, gleScores) self.updatePredictionMatrix() def readPredictions(self, predictions, gleScores): progressBar = ProgressBar(self, text='Reading DANGLE predictions') progressBar.total = len(predictions) - 2 # 2 header lines residues = self.dangleChain.chain.sortedResidues() getDangleResidue = self.dangleChain.findFirstDangleResidue newDangleResidue = self.dangleChain.newDangleResidue for residue in residues: seqId = residue.seqId prediction = predictions.get(seqId) if prediction is None: continue gleMatrix = gleScores[seqId] progressBar.increment() #resNum, resName = prediction[:2]; numIsland = prediction[2] ss = prediction[10] angles = [min(179.9999, a) for a in prediction[3:10]] phi, phiUpper, phiLower, psi, psiUpper, psiLower, omega = angles # Normalise to max maxVal = max(gleMatrix) gleMatrix = [ max(0, int(val / maxVal * 65535)) / 65535.0 for val in gleMatrix ] dangleResidue = getDangleResidue(residue=residue) if not dangleResidue: dangleResidue = newDangleResidue( phiPsiLikelihoodMatrix=gleMatrix, residue=residue) else: dangleResidue.phiPsiLikelihoodMatrix = gleMatrix dangleResidue.numIslands = numIsland dangleResidue.phiValue = phi dangleResidue.phiUpper = phiUpper dangleResidue.phiLower = phiLower dangleResidue.psiValue = psi dangleResidue.psiUpper = psiUpper dangleResidue.psiLower = psiLower dangleResidue.omegaValue = omega dangleResidue.secStrucCode = ss progressBar.destroy() def readPredictionFile(self, filename, chain): try: fopen = open(filename, 'r') except: showError('File Reading Error', 'DANGLE prediction file %s cannot be open.' % filename, parent=self) return lines = fopen.readlines() progressBar = ProgressBar(self, text='Reading DANGLE predictions') progressBar.total = len(lines) - 2 # 2 header lines lines = lines[2:] for line in lines: progressBar.increment() if (line == '\n'): continue array = line.split() # keep everything as string resNum = int(array[0]) resName = array[1] numIsland = int(array[2]) phi = array[3] phiUpper = array[4] phiLower = array[5] psi = array[6] psiUpper = array[7] psiLower = array[8] omega = array[9] ss = array[10] if (phi == 'None'): phi = None else: phi = float(phi) if (psi == 'None'): psi = None else: psi = float(psi) if (omega == 'None'): omega = None else: omega = float(omega) if omega == 180: omega = 179.9 if (phiUpper == 'None'): phiUpper = None else: phiUpper = float(phiUpper) if (phiLower == 'None'): phiLower = None else: phiLower = float(phiLower) if (psiUpper == 'None'): psiUpper = None else: psiUpper = float(psiUpper) if (psiLower == 'None'): psiLower = None else: psiLower = float(psiLower) if (ss == 'None'): ss = None path = os.path.join(self.dangleDir, 'Res_%d.pgm' % resNum) gleMatrix = self.readGLE(path) residue = chain.findFirstResidue(seqId=int(resNum)) dangleResidue = self.dangleChain.findFirstDangleResidue( residue=residue) if not dangleResidue: dangleResidue = self.dangleChain.newDangleResidue( phiPsiLikelihoodMatrix=gleMatrix, residue=residue) else: dangleResidue.phiPsiLikelihoodMatrix = gleMatrix dangleResidue.numIslands = numIsland dangleResidue.phiValue = phi dangleResidue.phiUpper = phiUpper dangleResidue.phiLower = phiLower dangleResidue.psiValue = psi dangleResidue.psiUpper = psiUpper dangleResidue.psiLower = psiLower dangleResidue.omegaValue = omega dangleResidue.secStrucCode = ss # Delete temp pgm files to save space once data is in CCPN os.unlink(path) fopen.close() progressBar.destroy() def readGLE(self, gleFile): if not os.path.isfile(gleFile): msg = 'No scorogram Res_%d.pgm\nin directory %s.' % ( resNum, self.dangleDir) showError('File Reading Error', msg, parent=self) return None fopen = open(gleFile, 'r') lines = fopen.readlines() dims = lines[2].split() lines = lines[4:] fopen.close() # only read the top left corner of a 10X10 square bin # all readings in the same bin are identical binSize = 10 matrix = [] for j in range(36): x = j * binSize * binSize * 36 for i in range(36): y = i * binSize v = int(lines[x + y].strip()) matrix.append(v) maxVal = float(max(matrix)) for i in range(len(matrix)): matrix[i] = matrix[i] / maxVal return matrix def getPhiPsiPredictions(self): #if self.dangleChain: # dResidues = self.dangleChain.dangleResidues dResidues = self.predictionMatrix.objectList phiData = [] psiData = [] for dResidue in dResidues: resNum = dResidue.residue.seqCode phi = dResidue.phiValue psi = dResidue.psiValue phiData.append((resNum, phi)) psiData.append((resNum, psi)) return (phiData, psiData) def clearSelected(self): for dangleResidue in self.predictionMatrix.currentObjects: dangleResidue.numIslands = None dangleResidue.phiValue = None dangleResidue.psiValue = None dangleResidue.omegaValue = None dangleResidue.phiUpper = None dangleResidue.phiLower = None dangleResidue.psiUpper = None dangleResidue.psiLower = None dangleResidue.secStrucCode = None self.updatePredictionMatrixAfter() def storeSecondaryStructure(self): if not self.dangleChain: return getSpinSystem = self.nmrProject.findFirstResonanceGroup newSpinSystem = self.nmrProject.newResonanceGroup n = 0 for dangleResidue in self.dangleChain.dangleResidues: ssCode = dangleResidue.secStrucCode if not ssCode: continue residue = dangleResidue.residue if not residue: continue spinSystem = getSpinSystem(residue=residue) if not spinSystem: spinSystem = newSpinSystem(residue=residue, ccpCode=residue.ccpCode) spinSystem.secStrucCode = ssCode n += 1 showInfo('Info', 'Stored secondary structure types for %d residues.' % n, parent=self) def storeDihedralConstraints(self): if not self.dangleChain: return # make a new dihedralConstraintList head = self.constraintSet if not head: head = self.project.newNmrConstraintStore( nmrProject=self.nmrProject) self.constraintSet = head chain = self.dangleChain.chain shiftList = self.dangleChain.shiftList name = 'DANGLE Chain %s:%s ShiftList %d' % ( chain.molSystem.code, chain.code, shiftList.serial) constraintList = head.newDihedralConstraintList(name=name, measureListSerials=[ shiftList.serial, ]) # traverse the sequence and make appropriate constraint objects residues = chain.sortedResidues() for residue in residues: # Ensure we have atomSets etc getResidueMapping(residue) residueList = [(dr.residue.seqCode, dr.residue, dr) for dr in self.dangleChain.dangleResidues] residueList.sort() cnt = 0 for seqCode, residue, dangleResidue in residueList: phi = dangleResidue.phiValue psi = dangleResidue.psiValue if (phi is None) and (psi is None): continue # Use below functions because residues may not be sequentially numbered prevRes = getLinkedResidue(residue, 'prev') nextRes = getLinkedResidue(residue, 'next') if (prevRes is None) or (nextRes is None): continue C__1 = prevRes.findFirstAtom(name='C') # C (i-1) N_0 = residue.findFirstAtom(name='N') # N (i) CA_0 = residue.findFirstAtom(name='CA') # CA(i) C_0 = residue.findFirstAtom(name='C') # N (i) N_1 = nextRes.findFirstAtom(name='N') # C (i+1) # get fixedResonances fixedResonances = [] for atom in (C__1, N_0, CA_0, C_0, N_1): atomSet = atom.atomSet if atomSet.resonanceSets: resonance = atomSet.findFirstResonanceSet( ).findFirstResonance() else: # make new resonance if not atom.chemAtom: print 'no chem atom' ic = atom.chemAtom.elementSymbol if (ic == 'C'): ic = '13' + ic elif (ic == 'N'): ic = '15' + ic resonance = self.nmrProject.newResonance(isotopeCode=ic) assignAtomsToRes([ atomSet, ], resonance) fixedResonances.append(getFixedResonance(head, resonance)) # make dihedralConstraints phiResonances = (fixedResonances[0], fixedResonances[1], fixedResonances[2], fixedResonances[3]) phiConstraint = constraintList.newDihedralConstraint( resonances=phiResonances) psiResonances = (fixedResonances[1], fixedResonances[2], fixedResonances[3], fixedResonances[4]) psiConstraint = constraintList.newDihedralConstraint( resonances=psiResonances) # make constraint items if phi is not None: phiConstraint.newDihedralConstraintItem( targetValue=phi, upperLimit=dangleResidue.phiUpper, lowerLimit=dangleResidue.phiLower) cnt += 1 if psi is not None: psiConstraint.newDihedralConstraintItem( targetValue=psi, upperLimit=dangleResidue.psiUpper, lowerLimit=dangleResidue.psiLower) cnt += 1 showInfo('Success', 'DANGLE has generated %d dihedral restraints.' % cnt, parent=self) def loadGLEs(self, dRes, row, col): residue = dRes.residue title = '%d %s' % (residue.seqCode, residue.ccpCode) self.fillGlePlot(self.plot, dRes.phiPsiLikelihoodMatrix, title) prevDangleRes = self.getDangleResidue(dRes, 'prev') if prevDangleRes: self.fillGlePlot(self.prevPlot, prevDangleRes.phiPsiLikelihoodMatrix) else: self.fillGlePlot(self.prevPlot, [0] * 1296) # blank nextDangleRes = self.getDangleResidue(dRes, 'next') if nextDangleRes: self.fillGlePlot(self.nextPlot, nextDangleRes.phiPsiLikelihoodMatrix) else: self.fillGlePlot(self.nextPlot, [0] * 1296) # blank self.updatePhiPsi(dRes.residue) def fillGlePlot(self, plot, gleMatrix, title=None): scaleCol = plot.scaleColorQuick if self.colorScheme == 'black': plot.nullColor = '#000000' else: plot.nullColor = '#FFFFFF' itemconf = plot.canvas.itemconfigure matrix = plot.matrix for j in range(36): for i in range(36): v = gleMatrix[j * 36 + i] #if (v < 0.005): # color = plot.nullColor #else: color = self.getPlotColor(v) item = matrix[i][j] if plot.binWidth < 7: itemconf(item, fill=color, outline=color) elif plot.binWidth < 12: itemconf(item, fill=color, outline=scaleCol(color, 0.9)) else: itemconf(item, fill=color, outline=scaleCol(color, 0.8)) if title: itemconf(plot.title, text=title) def getDangleResidue(self, dRes, direction): # return a DangleResidue object located offset-residue away from dRes in sequence # Use below function to guard against non-sequentially numbered residues # the below function follows bonds, but uses a cache for speed residue = getLinkedResidue(dRes.residue, direction) if residue and self.dangleChain: return self.dangleChain.findFirstDangleResidue(residue=residue) def showPrevious(self): if not self.dangleResidue: return prevDangleResidue = self.getDangleResidue(self.dangleResidue, 'prev') if not prevDangleResidue: return self.predictionMatrix.selectObject(prevDangleResidue) #self.dangleResidue = prevDangleResidue #self.loadGLEs(self.dangleResidue, None, None) #self.predictionMatrix.currentObject = self.dangleResidue #self.predictionMatrix.hilightObject(self.predictionMatrix.currentObject) def showNext(self): if not self.dangleResidue: return nextDangleResidue = self.getDangleResidue(self.dangleResidue, 'next') if not nextDangleResidue: return self.predictionMatrix.selectObject(nextDangleResidue) #self.dangleResidue = nextDangleResidue #self.loadGLEs(self.dangleResidue, None, None) #self.predictionMatrix.currentObject = self.dangleResidue #self.predictionMatrix.hilightObject(self.predictionMatrix.currentObject) def updatePhiPsi(self, residue): if self.ensemble: phiPsiAccept = [] plotObjects = [] colors = [] cChain = self.ensemble.findFirstCoordChain(code=residue.chain.code) if cChain: cResidue = cChain.findFirstResidue(residue=residue) if cResidue: for model in self.ensemble.models: phiPsiAccept.append(self.getPhiPsi(cResidue, model)) plotObjects.append((cResidue, model)) colors.append(ENSEMBLE_COLOR) if self.colorScheme == 'rainbow': # default grey circles self.plot.updateObjects(phiPsiAccList=phiPsiAccept, objectList=plotObjects) else: # bright green circles self.plot.updateObjects(phiPsiAccList=phiPsiAccept, objectList=plotObjects, colors=colors) def getPhiPsi(self, residue, model=None): phi, psi = getResiduePhiPsi(residue, model=model) return (phi, psi, 1) def getPlotColor(self, i, maxInt=255): mode = self.colorScheme if mode == 'rainbow': if (i == 0): return '#%02x%02x%02x' % (255, 255, 255) # white bg elif (i > 0.75): red = 1 green = (1 - i) / 0.25 blue = 0 elif (i > 0.5): red = (i - 0.5) / 0.25 green = 1 blue = 0 elif (i > 0.25): red = 0 green = 1 blue = (0.5 - i) / 0.25 else: red = 0 green = i / 0.25 blue = 1 return '#%02x%02x%02x' % (red * maxInt, green * maxInt, blue * maxInt) """ elif mode == 'black': if i > 0.5: red = i green = 1 - i blue = 1 - i else: v = 0.1 + (0.9 * i) red = v green = v blue = v elif mode == 'white': if i > 0.5: red = i green = 1 - i blue = 1 - i else: v = 1.0 - (0.9 * i) red = v green = v blue = v return '#%02x%02x%02x' % (red*maxInt, green*maxInt, blue*maxInt) """ # default : red to black if (i == 0): return '#%02x%02x%02x' % (255, 255, 255) # white bg return '#%02x%02x%02x' % (((1 - i) * 255), 0, 0) def updatePredictionMatrixAfter(self, index=None, text=None): if self.chain and self.shiftList and self.dangleStore: self.dangleChain = self.dangleStore.findFirstDangleChain( chain=self.chain, shiftList=self.shiftList) else: self.dangleChain = None self.after_idle(self.updatePredictionMatrix) #if showYesNo('Not Found','No data for Chain %s in Dangle Run %s.\nMake prediction for this chain?' % (self.chain.code, text), parent=self): # self.runDangle() def updatePredictionMatrix(self): shiftList = self.shiftList objectList = [] textMatrix = [] colorMatrix = [] if self.dangleChain: residueList = [(dr.residue.seqCode, dr.residue, dr) for dr in self.dangleChain.dangleResidues] residueList.sort() else: # Chow blank table residueList = [] for seqCode, residue, dRes in residueList: objectList.append(dRes) phi = dRes.phiValue psi = dRes.psiValue ss = dRes.secStrucCode atomNames = [] for atomName in BACKBONE_ATOMS: atom = residue.findFirstAtom(name=atomName) if not atom: continue atomSet = atom.atomSet if atomSet: shifts = getAtomSetShifts(atomSet, shiftList=shiftList) if shifts: atomNames.append(atomName) atomNames.sort() atomNames = ' '.join(atomNames) textMatrix.append((seqCode, residue.ccpCode, dRes.numIslands, ss, phi, psi, dRes.phiUpper, dRes.phiLower, dRes.psiUpper, dRes.psiLower, atomNames)) if (phi is None) and (psi is None): colorMatrix.append(INACTIVE_COLORS) elif dRes.numIslands >= 5: colorMatrix.append(BAD_COLORS) else: colorMatrix.append(GOOD_COLORS) self.predictionMatrix.update(textMatrix=textMatrix, objectList=objectList, colorMatrix=colorMatrix) def selectCell(self, dRes, row, col): self.dangleResidue = dRes self.row = row self.col = col self.loadGLEs(dRes, row, col) def setFloatEntry(self, event): index = self.col - 4 # index of attribute to set in the EDIT_ATTRS list value = self.floatEntry.get() if value is not None: setattr(self.dangleResidue, EDIT_ATTRS[index], value) self.updatePredictionMatrixAfter() def getFloatEntry(self, dangleResidue): if dangleResidue: index = self.col - 4 # index of attribute to set in the EDIT_ATTRS list self.floatEntry.set(getattr(dangleResidue, EDIT_ATTRS[index])) def changeChain(self, chain): if chain is not self.chain: self.chain = chain self.updateEnsemblePulldown( ) # Ensembles are filtered by chains molSystem self.updatePredictionMatrixAfter() def changeShiftList(self, shiftList): if shiftList is not self.shiftList: self.shiftList = shiftList self.updatePredictionMatrixAfter() def changeEnsemble(self, ensemble): self.ensemble = ensemble def changeConstraintSet(self, constraintSet): if constraintSet is not self.constraintSet: self.constraintSet = constraintSet def changeDangleStore(self, dangleStore): if self.dangleStore is not dangleStore: self.dangleStore = dangleStore if dangleStore: self.dangleChain = dangleStore.findFirstDangleChain() self.chain = self.dangleChain.chain self.shiftList = self.dangleChain.shiftList else: self.dangleChain = None self.updateChainPulldown() self.updateShiftListPulldown() self.updateEnsemblePulldown( ) # Ensembles are filtered by chains molSystem self.updatePredictionMatrixAfter() def checkDangleStore(self): if not self.dangleStore: N = len(self.project.dangleStores) + 1 name = askString('Request', 'Dangle Run Name:', 'Run%d' % N, parent=self) if not name: return None for character in whitespace: if character in name: showWarning('Failure', 'Name cannot contain whitespace', parent=self) return None if self.project.findFirstDangleStore(name=name): showWarning('Failure', 'Name already used', parent=self) return None self.dangleStore = self.project.newDangleStore(name=name) self.updateDangleStorePulldown() def updateChainPulldown(self, obj=None): index = 0 names = [] chains = [] chain = self.chain for molSystem in self.project.molSystems: msCode = molSystem.code for chainA in molSystem.chains: residues = chainA.residues if not residues: continue for residue in residues: # Must have at least one protein residue if residue.molType == 'protein': names.append('%s:%s' % (msCode, chainA.code)) chains.append(chainA) break if chains: if chain not in chains: chain = chains[0] index = chains.index(chain) else: chain = None if chain is not self.chain: self.chain = chain self.updatePredictionMatrixAfter() self.chainPulldown.setup(names, chains, index) def updateShiftListPulldown(self, obj=None): index = 0 names = [] shiftLists = getShiftLists(self.nmrProject) if shiftLists: if self.shiftList not in shiftLists: self.shiftList = shiftLists[0] index = shiftLists.index(self.shiftList) names = ['%s:%d' % (sl.name, sl.serial) for sl in shiftLists] else: self.shiftList = None self.shiftListPulldown.setup(names, shiftLists, index) def updateDangleStorePulldown(self): names = [ '<New>', ] dangleStores = [ None, ] for dangleStore in self.project.sortedDangleStores(): names.append(dangleStore.name) dangleStores.append(dangleStore) if self.dangleStore not in dangleStores: self.dangleStore = dangleStores[-1] index = dangleStores.index(self.dangleStore) self.dangleStorePulldown.setup(names, dangleStores, index) def updateEnsemblePulldown(self, obj=None): index = 0 names = [ '<None>', ] ensembles = [ None, ] if self.chain: molSystem = self.chain.molSystem for ensemble in molSystem.sortedStructureEnsembles(): names.append('%s:%d' % (molSystem.code, ensemble.ensembleId)) ensembles.append(ensemble) if self.ensemble not in ensembles: self.ensemble = ensembles[0] index = ensembles.index(self.ensemble) self.ensemblePulldown.setup(names, ensembles, index) def updateConstrSetPulldown(self, obj=None): names = [ '<New>', ] constraintSets = [ None, ] # Use below later, once API speed/loading is improved # for constraintSet in self.nmrProject.sortedNmrConstraintStores(): for constraintSet in self.project.sortedNmrConstraintStores(): names.append('%d' % constraintSet.serial) constraintSets.append(constraintSet) if self.constraintSet not in constraintSets: self.constraintSet = constraintSets[0] index = constraintSets.index(self.constraintSet) self.constrSetPulldown.setup(names, constraintSets, index) def try1(self): if not self.project: return ccpCodes = [ 'Ala', 'Cys', 'Asp', 'Glu', 'Phe', 'Gly', 'His', 'Ile', 'Lys', 'Leu', 'Met', 'Asn', 'Gln', 'Arg', 'Ser', 'Thr', 'Val', 'Trp', 'Tyr', 'Pro' ] atomNames = ['HA', 'CA', 'CB', 'C', 'N'] molType = 'protein' for ccpCode in ccpCodes: for atomName in atomNames: chemAtomNmrRef = getChemAtomNmrRef(self.project, atomName, ccpCode, molType) mean = chemAtomNmrRef.meanValue sd = chemAtomNmrRef.stdDev print '%5s%5s %.3f %.3f' % (ccpCode, atomName, mean, sd)
class IsotopeSchemeEditor(BasePopup): """ **Create and Edit Per-residue Reference Isotope Schemes** This system allows the user to create schemes that describe particular patterns of atomic isotope labelling in terms of combinations of isotopically labelled forms of residues. Once constructed, these schemes may then be applied to a molecule of known residue sequence to gauge the levels of spin-active isotope incorporation in an NMR experiment. This information is useful in several places withing Analysis, including giving more intelligent assignment options and in the generation of distance restraints by matching peak positions to chemical shifts. Although the schemes may be used directly they are typically used as reference information for configuring the `Isotope Labelling`_ system; where isotope labelling patterns are tied to particular molecules and experiments. Because all of the different isotope labelled versions (isotopomers) of each residue type are described independently, a scheme can be used to estimate the specific amounts of incorporation present at multiple atom sites at the same time. For example, although a residue type may have significant levels of 13C at the CA and CB positions on average, there may be no form of the residue where CA and CB are labelled at the same time, and thus CA-CB correlations would not be observed in NMR. This popup window is divided into three main tabs, the first describes the overall schemes that are available; that would be applied to a molecule in a given situation. The second tab details the residue isotopomer components within the selected scheme, i.e. which labelled residue forms are present. The last tab displays isotopomer labelling in a graphical, three-dimensional way. If any isotope labelling schemes have been created or edited the user may immediately save these to disk via the [Save Schemes] button to the right of the tabs, although these will naturally be saved when the main CCPN project is. **Reference Schemes** This table lists all of the reference isotope schemes that are available to the project. A number of standard schemes are included by default, as part of the main CCPN installation. However, the user is free to create new schemes, either from a completely blank description or by copying and modifying one of the existing schemes. By selecting on a isttope scheme row in the table the scheme is selected to be active for the whole popup and the user can see the contents of the scheme via the other two tabs. It should be noted that the user cannot edit the standard schemes provided by CCPN, given that these are stored with the software. Any new or copied schemes that the user creates will be stored inside the current CCPN project. If a new scheme should be made available to multiple projects, its XML file can be copied into the main CCPN installation, if the user has appropriate write access. **Isotopomers** The middle tab allows the user to view, and where appropriate edit, the isotope labelling descriptions for the residues within the current scheme (selected in the pulldown menu at the top). An isotope scheme is constructed by specifying one or more isotopomers for each residue type. Each isotopomer represents a different way of incorporating spin-active atom labels into a given kind of residue. Often there will only be one labelled form of a residue, and hence one isotopomer. However, with some kinds of isotope enrichment, for example using glycerol 13C labelled at the C2 position, connected labelled and unlabelled atom sites can be incorporated in alternative ways, resulting in distinct forms of labelling patterns that are not the result of a pure random mix. Knowing which labels are present at the same time, in the same isotopomer form, can be very important for determining which NMR correlations are possible. In general use when looking through the default, immutable reference schemes that come with CCPN the user can scroll through the isotopomer versions of each residue in the upper table. By clicking on one of these rows the lower table is filled with details of the amount of each kind of isotope (on average) at each atom site. For the lower "Atom Labels" table only one kind of chemical element is shown at a time, but the user may switch to a different one via the "Chemical Element" pulldown. **Editing Isotopomers** When dealing with copied or new isotope schemes the user is allowed to edit all aspects of the scheme. With a completely new scheme there will be no isotopomer records to start with and it is common practice to fill in a standard set of isotopomers, one for each residue type, made with a base level of isotope incorporation. To set this base level the user can use [Set Default Abundances] to manually specify values, although the default is to use natural abundance levels, which is appropriate in most circumstances. With the base levels set the [Add Default Abundance Set] will automatically fill-in a starting set of isotopomers for the scheme. Extra isotopomers can be added for a specific type of residue via the [Add New:] function and adjacent pulldown menu or by copying existing ones; whichever is easier. Each isotopomer has an editable weight to enable the user to indicate the relative abundance within a given residue type. Once a new isotopomer specification is created clicking on its row allows the user to specify the isotope labelling pattern in the lower "Atom Labels" table. Here the user selects which kind of chemical element to consider and then double-clicks to edit the "Weighting" columns in the table. The weightings represent the relative abundance of a given nuclear isotope at a given atom site. The weightings could be set as ratios, fractions, or percentages; it is only the relative proportion that is important. For example if a carbon atom site was known to have 5% Carbon-12 and 95% Carbon-13 isotopes then the respective weights could be entered as 1 & 19 or 0.05 & 0.95; whatever is most convenient. For efficient setup of schemes the [Propagate Abundances] function can be used to spread the same levels of incorporation over several atom sites (from the last selected row). **Isotopomer Structure** The last tab is an alternative way of presenting the isotope patterns present within the residues of the current scheme (selected in either of the first two tabs). Here the user selects a residue type in the upper left pulldown menu and then a numbered isotopomer, or an average of all isotopomers, in the right hand pulldown menu. The structural display will show a moveable picture of the residue (in a standard conformation) where unlabelled atom sites are represented with grey spheres, labelled sites with yellow spheres and intermediate incorporation with shades in between. It should be noted that this kind of 3D display is only possible if there is an idealised structure available for a residue type. This data will be present for all of the regular biopolymer residues, but may be missing for more unusual compounds; although a lack of coordinates does not impact upon the isotopomer setup. To move and rotate the three-dimensional residue display the following keyboard controls may be used: * Rotate: Arrow keys * Zoom: Page Up & Page Down keys * Translate: Arrow keys + Control key Or alternatively the following mouse controls: * Rotate: Middle button click & drag * Zoom: Mouse wheel or middle button click + Shift key & drag up/down * Translate: Middle button click & drag + Control key Also an options menu appears when the right mouse button is clicked. .. _`Isotope Labelling`: EditMolLabellingPopup.html """ def __init__(self, parent, project=None, *args, **kw): if not project: self.project = Implementation.MemopsRoot( name='defaultUtilityProject') else: self.project = project self.waiting = False self.waitingAtom = False self.molType = 'protein' self.scheme = None self.isotopomer = None self.isotopomerV = False # Not None self.ccpCodeV = None self.element = 'C' self.atomLabelTuple = None self.isotopes = [x[0] for x in getSortedIsotopes(self.project, 'C')] self.defaultAbun = {} BasePopup.__init__(self, parent=parent, title='Molecule : Reference Isotope Schemes', **kw) def body(self, guiFrame): self.geometry('700x600') guiFrame.expandGrid(0, 0) tipTexts = [ 'A table of all of the reference isotope scheme definitions available to the project', 'A list of the residue isotopomers that comprise the selected isotope labelling scheme', 'A three-dimensional representation of residues and their isotopomer labelling' ] options = ['Reference Schemes', 'Isotopomers', 'Isotopomer Structure'] tabbedFrame = TabbedFrame(guiFrame, options=options, grid=(0, 0), tipTexts=tipTexts) self.tabbedFrame = tabbedFrame frameA, frameB, frameC = tabbedFrame.frames # # Schemes # frameA.expandGrid(0, 0) tipTexts = [ 'A short textual code that identifies the reference isotope scheme in graphical displays', 'The full name for the isotope scheme', 'A detailed description of the isotope scheme including user comments', 'The name of the CCPN data repository in which the isotope scheme is saved; "refData" is in the CCPn installation' ] headingList = ['Code', 'Name', 'Description', 'Save Location'] self.schemeNameEntry = Entry(self, text='', returnCallback=self.setSchemeName, width=20) self.schemeDetailsEntry = Entry(self, text='', returnCallback=self.setSchemeDetails, width=20) editWidgets = [ None, self.schemeNameEntry, self.schemeDetailsEntry, None ] editGetCallbacks = [ None, self.getSchemeName, self.getSchemeDetails, None ] editSetCallbacks = [ None, self.setSchemeName, self.setSchemeDetails, None ] self.schemeMatrix = ScrolledMatrix(frameA, headingList=headingList, callback=self.selectScheme, editWidgets=editWidgets, editSetCallbacks=editSetCallbacks, editGetCallbacks=editGetCallbacks, multiSelect=False, grid=(0, 0), tipTexts=tipTexts) self.schemeMatrix.doEditMarkExtraRules = self.schemeEditRules tipTexts = [ 'Make a new reference isotope scheme definition based on a copy of the scheme currently selected', 'Delete the selected isotope scheme', 'Make a new, blank isotope scheme' ] texts = ['Copy', 'Delete', 'New'] commands = [self.copyScheme, self.removeScheme, self.makeNewScheme] self.schemeButtons = ButtonList(frameA, texts=texts, commands=commands, grid=(1, 0), tipTexts=tipTexts) # # Isotopomers # frameB.expandGrid(3, 0) row = 0 frame = Frame(frameB, grid=(row, 0)) frame.expandGrid(0, 2) tipText = 'Selects which of the available isotope schemes to view/edit' label = Label(frame, text='Reference Scheme:', grid=(0, 0)) self.schemePulldown = PulldownList(frame, callback=self.setLabellingScheme, grid=(0, 1), tipText=tipText) row += 1 div = LabelDivider(frameB, text='Isotopomers', grid=(row, 0)) row += 1 frame = Frame(frameB, grid=(row, 0)) frame.expandGrid(1, 2) self.isotopomerFrame = frame self.abundanceWidget = MultiWidget(self, FloatEntry, relief='raised', borderwidth=2, callback=self.setDefaultAbundances, useImages=False) tipText = 'Opens a panel that allows you to set the basis/default abundances for C, H & N isotopes; used as the starting point for new isotopomer definitions' self.abundanceButton = Button(frame, text='Set Default\nAbundances', borderwidth=1, command=self.enterDefaultAbundances, grid=(0, 0), tipText=tipText) tipText = 'Sets the basis/default abundances for C, H & N isotopes to their natural abundance proportions' button = Button(frame, text='Set Natural\nAbundance Default', borderwidth=1, command=self.resetDefaultAbundance, grid=(0, 1), sticky='ew', tipText=tipText) label = Label(frame, text='Molecule Type:', grid=(0, 2), sticky='e') entries = standardResidueCcpCodes.keys() entries.sort() entries.reverse() tipText = 'Selects which type of bio-polymer to define residue isotopomer labelling for' self.molTypePulldown = PulldownList(frame, callback=self.setMolType, texts=entries, grid=(0, 3), tipText=tipText) row += 1 tipTexts = [ 'The CCPN code that identifies the kind of residue the isotopomer relates to', 'The number of the particular isotopomer (isotope pattern) within its residue type', 'The fraction of the total residues, of its kind, that the isotopomer make up' ] headingList = ['Ccp Code', 'Variant', 'Weight'] self.isotopomerWeightEntry = FloatEntry( self, text='', returnCallback=self.setIsotopomerWeight, width=6) editWidgets = [None, None, self.isotopomerWeightEntry] editGetCallbacks = [None, None, self.getIsotopomerWeight] editSetCallbacks = [None, None, self.setIsotopomerWeight] self.isotopomerMatrix = ScrolledMatrix( frameB, tipTexts=tipTexts, headingList=headingList, callback=self.selectIsotopomer, editWidgets=editWidgets, editSetCallbacks=editSetCallbacks, editGetCallbacks=editGetCallbacks, multiSelect=True, grid=(row, 0)) self.isotopomerMatrix.doEditMarkExtraRules = self.isotopomerEditRules row += 1 frame = Frame(frameB, grid=(row, 0), sticky='ew') frame.expandGrid(0, 0) tipTexts = [ 'Delete the selected residue isotopomers from the current isotope scheme', 'Make a new residue isotopomer definition by copying the details of the last selected isotopomer', 'Add a complete set of isotopomers to the isotope scheme, one for each residue type, based on the states default isotope abundances', 'For all residue isotopomers in the scheme, set the labelling of one kind of atom (the user is prompted) to its default isotopic incorporation ', 'Add a new residue isotopomer definition that uses the default isotopic incorporation' ] texts = [ 'Delete\nSelected', 'Copy\nSelected', 'Add Default\nAbundance Set', 'Set Atom Type\nTo Default', 'Add\nNew:' ] commands = [ self.removeIsotopomers, self.duplicateResidues, self.addDefaultIsotopomers, self.setAtomTypeDefault, self.addNewIsotopomer ] self.isotopomerButtons = ButtonList(frame, texts=texts, commands=commands, grid=(0, 0), tipTexts=tipTexts) tipText = 'Selects which kind of residue isotopomer may be added to the current isotope scheme' self.ccpCodePulldown = PulldownList(frame, callback=None, grid=(0, 1), sticky='e', tipText=tipText) row += 1 div = LabelDivider(frameB, text='Atom Labels', grid=(row, 0)) row += 1 frame = Frame(frameB, grid=(row, 0)) frame.expandGrid(1, 3) label = Label(frame, text='Chemical Element:', grid=(0, 0)) tipText = 'Selects which kind of atoms to select from the selected residue isotopomer; to display isotopic incorporation in the below table' self.elementPulldown = PulldownList(frame, callback=self.changeChemElement, grid=(0, 1), tipText=tipText) self.updateChemElements() label = Label(frame, text='Water Exchangeable Atoms:', grid=(0, 2)) tipText = 'Sets whether to show atoms considered as being "water exchangeable"; their isotopic labelling will rapidly equilibrate with aqueous solvent' self.exchangeCheck = CheckButton(frame, callback=self.updateAtomLabelsAfter, grid=(0, 3), selected=False, tipText=tipText) row += 1 # Tip texts set on update headingList = [ 'Atom\nName', 'Weighting\n13C' 'Weighting\n12C', '%12C', '%13C' ] self.atomLabelTupleWeightEntry = FloatEntry( self, text='', width=6, returnCallback=self.setAtomLabelWeight) self.atomsMatrix = ScrolledMatrix(frameB, headingList=headingList, callback=self.selectAtomLabel, multiSelect=True, grid=(row, 0)) self.atomsMatrix.doEditMarkExtraRules = self.atomsEditRules row += 1 tipTexts = [ 'For the selected atom sites, in the current isotopomer, set their isotopic incorporation to the default values', 'Spread the isotopic incorporation values from the last selected atom site to all selected atoms sites' ] texts = ['Reset Selected to Default Abundance', 'Propagate Abundances'] commands = [self.setAtomLabelsDefault, self.propagateAbundances] self.atomButtons = ButtonList(frameB, texts=texts, commands=commands, grid=(row, 0), tipTexts=tipTexts) # # View Frame # frameC.expandGrid(1, 0) row = 0 frame = Frame(frameC, grid=(row, 0), sticky='ew') frame.grid_columnconfigure(3, weight=1) label = Label(frame, text='Residue Type:', grid=(0, 0)) tipText = 'Selects which kind of residue, within the current isotope scheme, to show isotopomer structures for' self.viewCcpCodePulldown = PulldownList( frame, callback=self.selectViewCcpcode, grid=(0, 1), tipText=tipText) label = Label(frame, text='Isotopomer:', grid=(0, 2)) tipText = 'Selects which kind of isotopomer (labelling pattern) to display, from the selected residue type.' self.viewIsotopomerPulldown = PulldownList( frame, callback=self.selectViewIsotopomer, grid=(0, 3), tipText=tipText) row += 1 self.viewIsotopomerFrame = ViewIsotopomerFrame(frameC, None, grid=(row, 0)) # # Main # tipTexts = [ 'Save all changes to the reference isotope scheme to disk; the saves ALL changes to the CCPN installation for all projects to use', ] texts = ['Save Schemes'] commands = [self.saveSchemes] self.bottomButtons = UtilityButtonList(tabbedFrame.sideFrame, texts=texts, commands=commands, helpUrl=self.help_url, grid=(0, 0), sticky='e', tipTexts=tipTexts) self.updateChemElements() self.updateCcpCodes() self.updateSchemes() self.administerNotifiers(self.registerNotify) def atomsEditRules(self, atomLabel, row, col): if self.scheme: return isSchemeEditable(self.scheme) else: return False def isotopomerEditRules(self, isotopomer, row, col): if self.scheme: return isSchemeEditable(self.scheme) else: return False def schemeEditRules(self, scheme, row, col): return isSchemeEditable(scheme) def administerNotifiers(self, notifyFunc): for func in ('__init__', 'delete', 'setLongName', 'setDetails'): for clazz in ('ccp.molecule.ChemCompLabel.LabelingScheme', ): notifyFunc(self.updateSchemes, clazz, func) for func in ('__init__', 'delete', 'setWeight'): notifyFunc(self.updateIsotopomersAfter, 'ccp.molecule.ChemCompLabel.Isotopomer', func) notifyFunc(self.updateAtomLabelsAfter, 'ccp.molecule.ChemCompLabel.AtomLabel', func) def getCcpCodeIsotopomers(self, ccpCode): chemCompLabel = self.scheme.findFirstChemCompLabel( molType=self.molType, ccpCode=ccpCode) if chemCompLabel: isotopomers = list(chemCompLabel.isotopomers) else: isotopomers = [] return isotopomers def selectViewCcpcode(self, ccpCode): if ccpCode != self.ccpCodeV: self.ccpCodeV = ccpCode self.isotopomerV = False self.updateViewIsotopomerPulldown() def selectViewIsotopomer(self, isotopomer): self.isotopomerV = isotopomer if isotopomer is None: isotopomers = self.getCcpCodeIsotopomers(self.ccpCodeV) else: isotopomers = [ isotopomer, ] self.viewIsotopomerFrame.setIsotopomers(isotopomers) def updateViewCcpCodePulldown(self): if self.scheme: codes = self.getCcpCodes(self.molType) if self.ccpCodeV not in codes: self.ccpCodeV = codes[0] self.isotopomerV = False # Not None self.updateViewIsotopomerPulldown() index = codes.index(self.ccpCodeV) else: codes = [] index = 0 self.viewCcpCodePulldown.setup(codes, codes, index) def updateViewIsotopomerPulldown(self): index = 0 isotopomers = [] names = [] if self.scheme: isotopomers = self.getCcpCodeIsotopomers(self.ccpCodeV) names = ['%d' % i.serial for i in isotopomers] isotopomers.insert(0, None) names.insert(0, '<All>') if self.isotopomerV not in isotopomers: self.isotopomerV = None isotopomers = self.getCcpCodeIsotopomers(self.ccpCodeV) self.viewIsotopomerFrame.setIsotopomers(isotopomers) self.viewIsotopomerPulldown.setup(names, isotopomers, index) def updateButtons(self): buttonsA = self.schemeButtons.buttons buttonsB = self.isotopomerButtons.buttons buttonsC = self.atomButtons.buttons if self.scheme: buttonsA[0].enable() isEditable = isSchemeEditable(self.scheme) if isEditable: buttonsA[1].enable() else: buttonsA[1].disable() buttonsB[2].enable() buttonsB[3].enable() self.bottomButtons.buttons[0].enable() if isEditable: if self.isotopomer: for button in buttonsB: button.enable() for button in buttonsC: button.enable() else: buttonsB[0].disable() buttonsB[1].disable() buttonsB[3].disable() buttonsC[0].disable() buttonsC[1].disable() buttonsB[2].enable() buttonsB[4].enable() else: for button in buttonsB: button.disable() for button in buttonsC: button.disable() else: buttonsA[0].disable() buttonsA[1].disable() for button in buttonsB: button.disable() for button in buttonsC: button.disable() self.bottomButtons.buttons[0].disable() def resetDefaultAbundance(self): self.defaultAbun = {} def setDefaultAbundances(self, values): self.abundanceWidget.place_forget() if values is not None: i = 0 for element in elementSymbols: # TBD getAllIsotopes for code, isotope in getSortedIsotopes(self.project, element): self.defaultAbun[isotope] = values[i] i += 1 def enterDefaultAbundances(self): x = self.isotopomerFrame.winfo_x() y = self.isotopomerFrame.winfo_y() x0 = self.abundanceButton.winfo_x() y0 = self.abundanceButton.winfo_y() values = [] options = [] for element in elementSymbols: # TBD getAllIsotopes for code, isotope in getSortedIsotopes(self.project, element): options.append(code + ':') values.append( self.defaultAbun.get(isotope, 100.0 * isotope.abundance)) N = len(values) self.abundanceWidget.maxRows = N self.abundanceWidget.minRows = N self.abundanceWidget.set(values=values, options=options) self.abundanceWidget.place(x=x + x0, y=y + y0) def selectAtomLabel(self, obj, row, col): self.atomLabelTuple = (obj, col) def setMolType(self, molType): if molType != self.molType: self.molType = molType self.isotopomer = None self.updateCcpCodes() self.updateIsotopomers() def getCcpCodes(self, molType): codes = [] for code in standardResidueCcpCodes[molType]: codes.append(code) codes.sort() return codes def updateCcpCodes(self): codes = self.getCcpCodes(self.molType) if self.isotopomer: index = codes.index(self.isotopomer.ccpCode) else: index = 0 self.ccpCodePulldown.setup(codes, codes, index) def setIsotopomerWeight(self, event): value = self.isotopomerWeightEntry.get() if value is not None: self.isotopomer.setWeight(abs(value)) def getIsotopomerWeight(self, isotopomer): if isotopomer: self.isotopomerWeightEntry.set(isotopomer.weight) def setSchemeName(self, event): text = self.schemeNameEntry.get() if text: text = text.strip() or None else: text = None self.scheme.setLongName(text) def getSchemeName(self, scheme): if scheme: self.schemeNameEntry.set(scheme.longName) def getSchemeDetails(self, scheme): if scheme: self.schemeDetailsEntry.set(scheme.details) def setSchemeDetails(self, event): text = self.schemeDetailsEntry.get() if text: text = text.strip() or None else: text = None self.scheme.setDetails(text) def updateSchemes(self, obj=None): textMatrix = [] objectList = [] for labelingScheme in self.project.sortedLabelingSchemes(): repository = labelingScheme.findFirstActiveRepository() if repository: saveLocation = repository.name else: saveLocation = None line = [ labelingScheme.name, labelingScheme.longName, labelingScheme.details, saveLocation ] textMatrix.append(line) objectList.append(labelingScheme) self.schemeMatrix.update(textMatrix=textMatrix, objectList=objectList) self.updateSchemePulldown() self.updateIsotopomers() def updateSchemePulldown(self): scheme = self.scheme schemes = self.project.sortedLabelingSchemes() names = [ls.longName or ls.name for ls in schemes] if names: if scheme not in schemes: scheme = schemes[0] index = schemes.index(scheme) else: index = 0 scheme = None self.setLabellingScheme(scheme) self.schemePulldown.setup(names, schemes, index) def copyScheme(self): if self.scheme: name = askString('Input text', 'New Scheme Code:', '', parent=self) scheme = self.project.findFirstLabelingScheme(name=name) if scheme: showWarning('Failure', 'Scheme name already in use') return if name: newScheme = copySubTree(self.scheme, self.project, topObjectParameters={'name': name}) else: showWarning('Failure', 'No name specified') else: showWarning('Failure', 'No scheme selected to copy') def removeScheme(self): if self.scheme and isSchemeEditable(self.scheme): self.scheme.delete() self.scheme = None def makeNewScheme(self): name = askString('Input text', 'New Scheme Code:', '', parent=self) if name: scheme = self.project.findFirstLabelingScheme(name=name) if scheme: showWarning('Failure', 'Scheme name already in use') else: scheme = self.project.newLabelingScheme(name=name) self.scheme = scheme else: showWarning('Failure', 'No name specified') def setLabellingScheme(self, scheme): if scheme is not self.scheme: self.scheme = scheme self.isotopomerV = False self.isotopomer = None self.updateIsotopomers() def selectScheme(self, object, row, col): self.setLabellingScheme(object) self.updateSchemePulldown() def open(self): BasePopup.open(self) self.updateSchemes() def saveSchemes(self): schemes = [x for x in self.project.labelingSchemes if x.isModified] if schemes: for scheme in schemes: scheme.save() showInfo('Notice', 'Successfully saved %d schemes' % len(schemes)) self.updateSchemes() else: showWarning('Notice', 'No modified schemes to save') def addNewIsotopomer(self): if self.scheme: ccpCode = self.ccpCodePulldown.getObject() chemCompLabel = self.getChemCompLabel(self.molType, ccpCode) self.makeIsotopomer(chemCompLabel) def makeIsotopomer(self, chemCompLabel, weight=1.0): isotopomer = chemCompLabel.newIsotopomer(weight=weight) chemComp = chemCompLabel.chemComp for chemAtom in chemComp.chemAtoms: if chemAtom.elementSymbol: chemElement = chemAtom.chemElement for isotope in chemElement.isotopes: code = '%d%s' % (isotope.massNumber, chemAtom.elementSymbol) weight = self.defaultAbun.get(isotope, 100.0 * isotope.abundance) isotopomer.newAtomLabel(name=chemAtom.name, subType=chemAtom.subType, isotopeCode=code, weight=weight) return isotopomer def getChemCompLabel(self, molType, ccpCode): chemCompLabel = None if self.scheme: chemCompLabel = self.scheme.findFirstChemCompLabel(molType=molType, ccpCode=ccpCode) if not chemCompLabel: chemCompLabel = self.scheme.newChemCompLabel(molType=molType, ccpCode=ccpCode) return chemCompLabel def selectIsotopomer(self, obj, row, col): self.isotopomer = obj self.updateChemElements() self.updateAtomLabels() def updateIsotopomersAfter(self, obj=None): if self.waiting: return else: self.waiting = True self.after_idle(self.updateIsotopomers) def updateIsotopomers(self): self.updateViewCcpCodePulldown() self.updateViewIsotopomerPulldown() textMatrix = [] objectList = [] if self.scheme: chemCompLabels = [(label.ccpCode, label) for label in self.scheme.chemCompLabels] chemCompLabels.sort() for key, chemCompLabel in chemCompLabels: if chemCompLabel.molType == self.molType: for isotopomer in chemCompLabel.sortedIsotopomers(): line = [ chemCompLabel.ccpCode, isotopomer.serial, isotopomer.weight ] textMatrix.append(line) objectList.append(isotopomer) self.isotopomerMatrix.update(textMatrix=textMatrix, objectList=objectList) self.updateAtomLabelsAfter() self.waiting = False def setAtomTypeDefault(self): if self.scheme: atomName = askString( 'Query', 'Specify atom name to set\ndefault abundance for', 'H', parent=self) if not atomName: return atomLabels = [] for chemCompLabel in self.scheme.chemCompLabels: if chemCompLabel.molType == self.molType: for isotopomer in chemCompLabel.isotopomers: # Multiple because of isotopes and subTypes atomLabels += isotopomer.findAllAtomLabels( name=atomName) if atomLabels: for atomLabel in atomLabels: isotope = atomLabel.isotope weight = self.defaultAbun.get(isotope, 100.0 * isotope.abundance) atomLabel.weight = weight else: data = (atomName, self.scheme.name) msg = 'Atom name %s does not match any atoms in %s scheme isotopomers' % data showWarning('Failure', msg) def addDefaultIsotopomers(self): if self.scheme: codes = self.getCcpCodes(self.molType) for ccpCode in codes: chemCompLabel = self.getChemCompLabel(self.molType, ccpCode) if not chemCompLabel.isotopomers: self.makeIsotopomer(chemCompLabel) def removeIsotopomers(self): isotopomers = self.isotopomerMatrix.currentObjects if isotopomers: for isotopomer in isotopomers: isotopomer.delete() self.isotopomer = None def duplicateResidues(self): isotopomers = self.isotopomerMatrix.currentObjects for isotopomer in isotopomers: chemCompLabel = isotopomer.chemCompLabel new = copySubTree(isotopomer, chemCompLabel) def updateChemElements(self): if self.isotopomer: chemCompLabel = self.isotopomer.chemCompLabel elementDict = {} for chemAtom in chemCompLabel.chemComp.chemAtoms: symbol = chemAtom.elementSymbol if symbol: elementDict[symbol] = True names = elementDict.keys() names.sort() else: names = ['C', 'N', 'H'] if self.element not in names: index = 0 else: index = names.index(self.element) self.elementPulldown.setup(names, names, index) def updateAtomLabelsAfter(self, obj=None): if self.waitingAtom: return else: self.waitingAtom = True self.after_idle(self.updateAtomLabels) def updateAtomLabels(self): element = self.elementPulldown.getText() textMatrix = [] objectList = [] headingList = [ 'Atom Name', ] tipTexts = [ 'The name of the atom within its residue, for which to set isotopic abundances, within the selected isotopomer', ] isotopeCodes = [x[0] for x in getSortedIsotopes(self.project, element)] headingList.extend(['Weighting\n%s' % x for x in isotopeCodes]) tip = 'The amount of %s isotope incorporation; can be a ratio, percentage or fraction (the value used is relative to the sum of all weights)' tipTexts.extend([tip % x for x in isotopeCodes]) tip = 'The percentage %s isotope incorporation, calculated using stated weights' headingList.extend(['%%%s' % x for x in isotopeCodes]) tipTexts.extend([tip % x for x in isotopeCodes]) editWidgets = [ None, ] editGetCallbacks = [ None, ] editSetCallbacks = [ None, ] editWidgets.extend( [self.atomLabelTupleWeightEntry for x in isotopeCodes]) editGetCallbacks.extend( [self.getAtomLabelWeight for x in isotopeCodes]) editSetCallbacks.extend( [self.setAtomLabelWeight for x in isotopeCodes]) editWidgets.extend([None for x in isotopeCodes]) editGetCallbacks.extend([None for x in isotopeCodes]) editSetCallbacks.extend([None for x in isotopeCodes]) doExchangeable = self.exchangeCheck.get() if self.isotopomer: atomDict = {} for atomLabel in self.isotopomer.sortedAtomLabels(): if atomLabel.chemAtom.elementSymbol == element: if (not doExchangeable) and ( atomLabel.chemAtom.waterExchangeable): continue name = atomLabel.name subType = atomLabel.subType atomDict[(name, subType)] = True atomNames = atomDict.keys() atomNames = greekSortAtomNames(atomNames) for atomName, subType in atomNames: if subType == 1: name = atomName else: name = '%s:%d' % (atomName, subType) line = [ name, ] atomLabels = [] sumWeights = 0.0 for isotope in isotopeCodes: atomLabel = self.isotopomer.findFirstAtomLabel( name=atomName, subType=subType, isotopeCode=isotope) atomLabels.append(atomLabel) if atomLabel: weight = atomLabel.weight sumWeights += weight line.append(weight) else: line.append(0.0) if sumWeights: for atomLabel in atomLabels: line.append(100.0 * atomLabel.weight / sumWeights) else: for atomLabel in atomLabels: line.append(None) textMatrix.append(line) objectList.append(atomLabels) self.atomsMatrix.update(textMatrix=textMatrix, objectList=objectList, headingList=headingList, tipTexts=tipTexts, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks) self.waitingAtom = False self.updateButtons() def setAtomLabelWeight(self, event): value = self.atomLabelTupleWeightEntry.get() if value is not None: atomLabels, col = self.atomLabelTuple chemAtom = None for label in atomLabels: if label: chemAtom = label.chemAtom break atomLabel = atomLabels[col - 1] if chemAtom and (atomLabel is None): isotopeCode, isotope = getSortedIsotopes( self.project, chemAtom.elementSymbol)[col - 1] atomLabel = self.isotopomer.newAtomLabel( name=chemAtom.name, subType=chemAtom.subType, isotopeCode=isotopeCode, weight=value) atomLabel.setWeight(value) def setAtomLabelsDefault(self): atomLabelTuples = self.atomsMatrix.currentObjects for atomLabels in atomLabelTuples: for atomLabel in atomLabels: isotope = atomLabel.isotope weight = self.defaultAbun.get(isotope, 100.0 * isotope.abundance) atomLabel.weight = weight def propagateAbundances(self): atomLabels, col = self.atomLabelTuple atomLabelTuples = self.atomsMatrix.currentObjects weightDict = {} for atomLabel in atomLabels: weightDict[atomLabel.isotope] = atomLabel.weight for atomLabels0 in atomLabelTuples: if atomLabels0 != atomLabels: for atomLabel in atomLabels0: atomLabel.weight = weightDict[atomLabel.isotope] def getAtomLabelWeight(self, null): atomLabels, col = self.atomLabelTuple if atomLabels and (col > 0): atomLabel = atomLabels[col - 1] if atomLabel is None: weight = 0.0 else: weight = atomLabel.weight self.atomLabelTupleWeightEntry.set(weight) def changeChemElement(self, name): self.element = name self.isotopes = [x[0] for x in getSortedIsotopes(self.project, name)] self.updateAtomLabels() def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self)
class BrukerPseudoPopup(BasePopup): def __init__(self, parent, params, dim, *args, **kw): self.dim = dim self.params = params BasePopup.__init__(self, parent=parent, title='Bruker Pseudo Data', modal=True, **kw) def body(self, master): pseudoExpts = getSampledDimExperiments(self.parent.nmrProject) master.rowconfigure(0, weight=1) master.rowconfigure(1, weight=1) master.columnconfigure(0, weight=1) tipTexts = ['The experiment is pseudo-N dimensional, with a sampled axis', 'The experiment is the regular kind with only NMR frequency axes'] self.pseudoEntries = [x % len(self.params.npts) for x in PSEUDO_ENTRIES] self.pseudoButton = RadioButtons(master, entries=self.pseudoEntries, select_callback=self.changedPseudoMode, grid=(0,0), sticky='nw', tipTexts=tipTexts) frame = self.pseudoFrame = Frame(master) self.pseudoFrame.grid(row=1, column=0, sticky='nsew') row = 0 if pseudoExpts: tipText = 'Select from existing pseudo nD experiments to copy sampled axis values from' texts = [x.name for x in pseudoExpts] label = Label(frame, text='Existing pseudo expts: ') label.grid(row=row, column=0, sticky='e') self.pseudoList = PulldownList(frame, texts=texts, objects=pseudoExpts, tipText=tipText) self.pseudoList.grid(row=row, column=1, sticky='w') tipText = 'Transfer the sampled axis values from the existing experiment to the new one' Button(frame, text='Copy values down', command=self.copyValues, tipText=tipText, grid=(row,2)) row += 1 npts = self.params.npts[self.dim] tipText = 'Number of data points (planes) along sampled axis' label = Label(frame, text='Number of points: ') label.grid(row=row, column=0, sticky='e') self.nptsEntry = IntEntry(frame, text=npts, tipText=tipText, width=8, grid=(row,1)) tipText = 'Load the values for the sampled axis from a text file containing a list of numeric values' Button(frame, text='Load File', command=self.loadValues, tipText=tipText, grid=(row,2), sticky='ew') row += 1 tipText = 'The values (e.g. T1, T2) corresponding to each data point (plane) along sampled axis' label = Label(frame, text='Point values: ') label.grid(row=row, column=0, sticky='e') self.valueEntry = FloatEntry(frame, isArray=True, tipText=tipText) #minRows = self.params.npts[self.dim] #self.valueEntry = MultiWidget(frame, FloatEntry, callback=None, minRows=minRows, maxRows=None, # options=None, values=[], useImages=False) self.valueEntry.grid(row=row, column=1, columnspan=2, sticky='ew') row += 1 label = Label(frame, text='(requires comma-separated list, of length number of points)') label.grid(row=row, column=1, columnspan=2, sticky='w') row += 1 for n in range(row): frame.rowconfigure(n, weight=1) frame.columnconfigure(1, weight=1) buttons = UtilityButtonList(master, closeText='Ok', closeCmd=self.updateParams, helpUrl=self.help_url) buttons.grid(row=row, column=0, sticky='ew') def loadValues(self): directory = self.parent.fileSelect.getDirectory() fileSelectPopup = FileSelectPopup(self, title='Select Sampled Data File', dismiss_text='Cancel', selected_file_must_exist=True, multiSelect=False, directory=directory) fileName = fileSelectPopup.file_select.getFile() fileObj = open(fileName, 'rU') data = '' line = fileObj.readline() while line: data += line line = fileObj.readline() data = re.sub(',\s+', ',', data) data = re.sub('\s+', ',', data) data = re.sub(',,', ',', data) data = re.sub('[^0-9,.\-+eE]', '', data) self.valueEntry.set(data) def copyValues(self): expt = self.pseudoList.getObject() if expt: dataDim = getExperimentSampledDim(expt) values = dataDim.pointValues self.nptsEntry.set(len(values)) self.valueEntry.set(values) def updateParams(self): params = self.params if self.pseudoButton.get() == self.pseudoEntries[0]: npts = self.nptsEntry.get() params.npts[self.dim] = npts values = self.valueEntry.get() try: params.setSampledDim(self.dim, values) except ApiError, e: showError('Set Sampled Dim', e.error_msg, parent=self) return else:
class OpenSpectrumPopup(BasePopup): r""" **Locate Spectrum Data for Use in CCPN Project** This popup window enables the user to locate spectrum data within a file system and associate the files (typically binary) with an experiment and spectrum name so that it may be visualised and accessed within the current CCPN project. Spectra of many different origins and file formats may be loaded, which currently includes Bruker, Varian, Felix, NMRPipe, NmrView, SPARKY/UCSF, Azara and the factorised shape format "USF3". Depending upon the file format of the spectrum, data loaded the user may be required to either select a parameter file which then refers to the actual spectrum intensity data; this is true for Bruker "procs" and AZARA ".par" files, or alternatively a spectrum data file itself that contains referencing information; this is the case for SPARKY/UCSF, NmrView and NMRPipe spectra. The layout of the popup involved two sections; the upper of which is for navigating to and selecting the spectrum or parameter files within the file-system, and the lower is for specifying how each spectrum is loaded into the CCPN project. It should be noted that when spectrum parameters are read the first time, the relevant information is copied into the CCPN project, where it may be adjusted independently of the original file information. No copies of the spectrum intensity data are made, the CCPN project merely refers to the spectrum data on disk, although the data file for a loaded spectrum may subsequently be moved or replaced. In normal operation the user first selects the kind of spectrum file format that will be loaded via the upper "File format" pulldown menu and then checks that the "File type" pulldown (toward the bottom of the file browser) is set to detect the appropriate kinds of filename; if a helpful file name filter is not available the user can add one via the "Manual Select" field, taking care to add any wild-card symbols, like the asterisk in "\*.ft3". Next the spectrum data or parameter files, appropriate to the selected format, are located by navigating within the file-system browser. When the required spectrum files are visible the user selects one *or more* to load. Multiple file selections may be made using left-click with <Ctrl> (toggle selection) or <Shift> (select range). It should be noted that when selecting Bruker files, when using the standard Bruker directory structure, the user only needs to navigate to the numbered spectrum directory; by default the "procs" file two levels down is searched for, e.g. "123/pdata/1/procs" is shown in the directory containing the "123" directory. When spectrum or parameter files are selected in the file table, the lower "Spectra To Open" table is filled to reflect the selection. The user should then be mindful of the settings within this table and may choose to edit various things by double-clicking on the appropriate cell. Typically the user just adjusts the name of the independent "Experiment" and "Spectrum" records. These names are usually concatenated like "expName:specName" in CCPN graphical displays so there is no need to repeat a name in both fields; this only takes up more space. The Experiment, which is a record of *what was done experimentally*, commonly has a short name like "HNCA" or "HSQC_298K" so the user readily knows how to interpret the experimental data. The Spectrum, which is a record of *the data that was collected*, commonly has a short name to identify the spectrum number or file name. An Experiment record may contain several Spectrum records, so the spectrum's name need minimally only identify it amongst others from the same experiment. The Shift List value may be changed if the user knows that the experiment represents a distinct set of conditions, with different spectrum peak/resonance positions, to existing or other experiments being entered. Each shift list will be curated separately, to give separate chemical shift values for assignments made under different conditions (even when relating to the same atoms). The shift list that an experiment uses may also be changed at any time after loading. When all spectra and options are specified the [Open Spectrum] button will load the relevant data into the CCPN project. If the "Skip verification dialogs" option is set it is assumed that all of the spectrum point to frequency referencing information, and any data file references, are correct. Otherwise, the user will be prompted to confirm the file details and referencing information for each spectrum in turn. Finally, after loading the user is asked to set the type of NMR experiment, in terms of general magnetisation transfer pathway, that was performed. **Caveats & Tips** If the name of an Experiment that is *already within the CCPN project* is used, then the loaded spectrum will (assuming it is compatible) be entered under that existing experiment record; no new experiment entity will be defined. The user may legitimately use this feature to load several spectra that relate to the same experiment; typically where spectra are different projections. To facilitate this the "Use shared experiment" option can be selected. Although experiments and spectra may be renamed after loading, a spectrum record may not be placed under a different experiment once created; deletion and re-loading is the only mans of achieving this, and care must be taken in transferring any assignments. """ def __init__(self, parent, *args, **kw): self.experiment = None self.currentObject = None #self.currentObjects = [] # looks obsolete BasePopup.__init__(self, parent=parent, title='Experiment : Open Spectra', **kw) def open(self): self.message() BasePopup.open(self) def body(self, guiFrame): self.fileSelect = None names, objects = self.getShiftLists() self.shiftListPulldown = PulldownList(self, callback=self.setShiftList, texts=names, objects=objects) self.windowPulldown = PulldownList(self, texts=WINDOW_OPTS, callback=self.setWindow) self.experimentEntry = Entry(self, width=16, returnCallback=self.setExperiment) self.spectrumEntry = Entry(self, width=16, returnCallback=self.setSpectrum) guiFrame.grid_columnconfigure(0, weight=1) guiFrame.grid_rowconfigure(0, weight=1) guiFrame.grid_rowconfigure(1, weight=1) leftFrame = LabelFrame(guiFrame, text='File Selection') leftFrame.grid(row=0, column=0, sticky='nsew') leftFrame.grid_columnconfigure(3, weight=1) row = 0 label = Label(leftFrame, text='File format:') label.grid(row=row, column=0, sticky='w') tipText = 'Selects which kind of spectrum file is being loaded; what its data matrix format is' self.formatPulldown = PulldownList(leftFrame, callback=self.chooseFormat, texts=file_formats, tipText=tipText, grid=(row, 1)) self.detailsLabel = Label(leftFrame, text='Show details:') tipText = 'Whether to show an annotation that describes the spectrum in the file selection; currently only uses comment fields from Bruker spectra' self.detailsSelect = CheckButton(leftFrame, selected=False, callback=self.showDetails, tipText=tipText) self.titleRow = row self.detailsSelected = False row = row + 1 leftFrame.grid_rowconfigure(row, weight=1) file_types = [FileType('All', ['*'])] self.fileSelect = FileSelect(leftFrame, multiSelect=True, file_types=file_types, single_callback=self.chooseFiles, extraHeadings=('Details', ), extraJustifies=('left', ), displayExtra=False, getExtraCell=self.getDetails, manualFileFilter=True) self.fileSelect.grid(row=row, column=0, columnspan=6, sticky='nsew') rightFrame = LabelFrame(guiFrame, text='Spectra To Open') rightFrame.grid(row=1, column=0, sticky='nsew') rightFrame.grid_columnconfigure(3, weight=1) row = 0 label = Label(rightFrame, text='Skip verification dialogs:', grid=(row, 0)) tipText = 'Whether to allow the user to check file interpretation and referencing information before the spectrum is loaded' self.verifySelect = CheckButton(rightFrame, selected=False, grid=(row, 1), tipText=tipText) label = Label(rightFrame, text='Use shared experiment:', grid=(row, 2)) tipText = 'When selecting multiple spectrum files, whether the loaded spectra will all belong to (derive from) the same experiment; useful for projection spectra etc.' self.sharedExpSelect = CheckButton(rightFrame, selected=False, tipText=tipText, callback=self.useShared, grid=(row, 3)) row = row + 1 rightFrame.grid_rowconfigure(row, weight=1) tipTexts = [ 'A short textual name for the experiment record that the loaded spectrum will belong to; may be a new experiment or the name of an existing one', 'A short textual name to identify the spectrum within its experiment; typically a few characters or spectrum number, rather than a repeat of the experiment name', 'The location of the file, relative to the current directory, that the spectrum data will be loaded from', 'Sets which window or windows the spectrum will initially appear within once loaded', 'Sets which shift list the experiment (and hence loaded spectrum) will use to curate chemical shift information; can be changed after load time' ] headingList = [ 'Experiment', 'Spectrum', 'File', 'Windows', 'Shift List' ] editWidgets = [ self.experimentEntry, self.spectrumEntry, None, self.windowPulldown, self.shiftListPulldown ] editGetCallbacks = [ self.getExperiment, self.getSpectrum, None, self.getWindow, self.getShiftList ] editSetCallbacks = [ self.setExperiment, self.setSpectrum, None, self.setWindow, self.setShiftList ] self.scrolledMatrix = ScrolledMatrix(rightFrame, headingList=headingList, callback=self.selectCell, editWidgets=editWidgets, multiSelect=True, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, tipTexts=tipTexts, grid=(row, 0), gridSpan=(1, 4)) row = row + 1 tipTexts = [ 'Load spectrum or spectra into the CCPN project using the selected file(s)', ] texts = ['Open Spectrum'] commands = [self.openSpectra] bottomButtons = UtilityButtonList(guiFrame, texts=texts, tipTexts=tipTexts, doClone=False, commands=commands, helpUrl=self.help_url) bottomButtons.grid(row=row, column=0, columnspan=1, sticky='ew') self.openButton = bottomButtons.buttons[0] self.chooseFormat('Azara') self.message() def message(self): if not self.project or len(self.nmrProject.experiments) < 1: pass #self.parent.ticker.setMessage('Choose spectrum files to open.... ') def showDetails(self, isSelected): self.detailsSelected = isSelected self.fileSelect.updateDisplayExtra(isSelected) # below is so that when Details column is toggled on it will actually # be seen without having to use the scrollbar self.fileSelect.fileList.refreshSize() def useShared(self, isSelected): self.chooseFiles(forceUpdate=True) #if isSelected: #objects = self.scrolledMatrix.objectList #if len(objects) > 1: # self.currentObject = objects[0] # text = objects[0][0] # self.chooseFiles() # for oo in objects[1:]: # oo[0] = text # if self.project: # self.experiment = self.nmrProject.findFirstExperiment(name=text) #self.update() def gridDetails(self, bool): if bool: self.detailsLabel.grid(row=self.titleRow, column=2, sticky='w') self.detailsSelect.grid(row=self.titleRow, column=3, sticky='w') self.fileSelect.updateDisplayExtra(self.detailsSelected) else: self.detailsLabel.grid_forget() self.detailsSelect.grid_forget() self.fileSelect.updateDisplayExtra(False) def openSpectra(self): noVerify = self.verifySelect.getSelected() # tracks if 'add to existing experiment' has already ben OK'ed self.okExpSet = set() directory = self.fileSelect.getDirectory() spectra = [] specIndex = 0 for obj in self.scrolledMatrix.objectList: fileName = uniIo.joinPath(directory, obj.fileName) spectrum = self.openSpectrum(obj.exptName, obj.specName, fileName, obj.window, obj.shiftListName) specIndex += 1 if (spectrum): # check endianness if we are not verifying spectra.append(spectrum) if noVerify: isBigEndian = isSpectrumBigEndian( spectrum) # according to data in file if isBigEndian is not None: isBigEndianCurr = getIsSpectrumBigEndian( spectrum) # according to data model setIsSpectrumBigEndian(spectrum, isBigEndian) if isBigEndian != isBigEndianCurr: if isBigEndian: s = 'big' else: s = 'little' print 'WARNING: swapped endianess of spectrum to %s endian' % s # del self.okExpSet if noVerify and len(spectra) > 1 and self.sharedExpSelect.getSelected( ): # if we are using a shared experiment and not verifying, # set referencing to match first spectrum for all # get reference spectrum and set up data structure # use most recent pre-existing spectrum, otherwise first new one refSpec = spectra[0] for spec in spectra[0].experiment.sortedDataSources(): if spec in spectra: break else: refSpec = spec ddrLists = {} refDdrs = [] for dataDim in refSpec.sortedDataDims(): for ddr in dataDim.dataDimRefs: ddrLists[ddr.expDimRef] = [] refDdrs.append(ddr) # get dataDimRefs, store by ExpDimRef, # checking that all spectra have data dim refs for same set of xdr nTotal = len(ddrLists) for spec in spectra: nFound = 0 for dataDim in spec.sortedDataDims(): for ddr in dataDim.dataDimRefs: xdr = ddr.expDimRef ll = ddrLists.get(xdr) if ll is None: # something did not match - do nothing break else: ll.append(ddr) nFound += 1 else: if nFound == nTotal: # we are OK. Do next spectrum continue # something did not match - do nothing break else: # all spectra matched. Now reset O1 references to match reference if refSpec is spectra[0]: startAt = 1 else: startAt = 0 for refDdr in refDdrs: dataDim = refDdr.dataDim centrePoint = dataDim.numPointsOrig / 2 - dataDim.pointOffset + 1 refValue = refDdr.pointToValue(centrePoint) xdr = refDdr.expDimRef for ddr in ddrLists[xdr][startAt:]: dataDim = ddr.dataDim centrePoint = dataDim.numPointsOrig / 2 - dataDim.pointOffset + 1 ddr.refPoint = centrePoint ddr.refValue = refValue # set refExperiment if there is only one possibility experiments = [] ignoreSet = set() showPopup = False for spectrum in spectra: experiment = spectrum.experiment if experiment not in ignoreSet: ignoreSet.add(experiment) if not experiment.refExperiment: experiments.append(spectrum.experiment) if noVerify: resetCategory = False if not hasattr(experiment, 'category'): if (hasattr(experiment, 'pulProgName') and hasattr(experiment, 'pulProgType')): # this is first time we get here, and we have external name and source # use external source to set fullType experiment.category = 'use external' resetCategory = True refExperiments = getRefExperiments(experiment) if resetCategory and not refExperiments: # no refExperiments match external source. # unset 'use external' category del experiment.category if len(refExperiments) == 1: # only one possibility, just set it setRefExperiment(experiment, refExperiments[0]) # wb104: 20 Oct 2014: do not popup Experiment types dialog if noVerify #else: # showPopup = True # Pop up refExperiment verification if experiments and (showPopup or not noVerify): self.parent.initRefExperiments(experiments) # set up internal Analysis data for spectrum in spectra: self.parent.finishInitSpectrum(spectrum) print 'finished opening spectrum', spectrum.experiment.name, spectrum.name def chooseFiles(self, forceUpdate=False, *file): directory = self.fileSelect.getDirectory() fileNames = self.fileSelect.fileList.currentObjects fullFileNames1 = [uniIo.joinPath(directory, x) for x in fileNames] fullFileNames2 = [x.fileName for x in self.scrolledMatrix.objectList] fullFileNames2 = [uniIo.joinPath(directory, x) for x in fullFileNames2] if fullFileNames1 == fullFileNames2 and not forceUpdate: return objectList = [] textMatrix = [] format = self.formatPulldown.getText() shiftListName = self.getShiftLists()[0][0] windowOpt = WINDOW_OPTS[1] oneUp = os.path.dirname if format == 'Bruker': if self.sharedExpSelect.getSelected(): nameTemplate = 'Bruker_%d' next = self.getNextExpNum(nfiles=len(fileNames), nameTemplate=nameTemplate) exptName = nameTemplate % (next) for i, fileName in enumerate(fileNames): fullFileName = fullFileNames1[i] specName = os.path.basename( oneUp(oneUp(oneUp(fullFileName)))) datum = (exptName, specName, fileName, windowOpt, shiftListName) dataObj = RowObject(*datum) textMatrix.append(datum) objectList.append(dataObj) else: for i, fileName in enumerate(fileNames): fullFileName = fullFileNames1[i] try: # below should not fail ss1 = oneUp(fullFileName) specName = os.path.basename(ss1) ss2 = os.path.basename(oneUp(oneUp(ss1))) exptName = 'Bruker_' + ss2 except: # just put in something ss = os.path.basename(fullFileName) exptName = 'Bruker_' + ss specName = ss datum = (exptName, specName, fileName, windowOpt, shiftListName) dataObj = RowObject(*datum) textMatrix.append(datum) objectList.append(dataObj) else: next = self.getNextExpNum(nfiles=len(fileNames)) if self.sharedExpSelect.getSelected(): exptName = 'Expt_%d' % (next) for i, fileName in enumerate(fileNames): specName = re.sub('\.\w+$', '', fileName) datum = (exptName, specName, fileName, windowOpt, shiftListName) dataObj = RowObject(*datum) textMatrix.append(datum) objectList.append(dataObj) else: for i, fileName in enumerate(fileNames): exptName = 'Expt_%d' % (next + i) specName = re.sub('\.\w+$', '', fileName) datum = (exptName, specName, fileName, windowOpt, shiftListName) dataObj = RowObject(*datum) textMatrix.append(datum) objectList.append(dataObj) if len(fileNames) > 1: self.openButton.config(text='Open Spectra') else: self.openButton.config(text='Open Spectrum') self.scrolledMatrix.update(objectList=objectList, textMatrix=textMatrix) def getNextExpNum(self, nfiles=0, nameTemplate='Expt_%d'): """ get suitable free integer to use for exp names """ next = 1 if self.project: nmrProject = self.nmrProject ii = len(nmrProject.experiments) # find first exp number that is not taken # NBNB TBD could consider expname = specname, specname = proc dir next = ii + 1 if nfiles: while ii < next + nfiles: ii += 1 if nmrProject.findFirstExperiment(name=nameTemplate % ii): next = ii + 1 # return next def getDetails(self, fullfile): details = '' if os.path.isfile(fullfile): format = self.formatPulldown.getText() detailsDir = os.path.dirname(fullfile) detailsFile = uniIo.joinPath(detailsDir, details_file_dict[format]) if os.path.exists(detailsFile): fp = open(detailsFile) details = fp.read().strip().replace('\n', ' ').replace('\r', ' ') fp.close() return (details, ) def update(self): objectList = self.scrolledMatrix.objectList textMatrix = [(obj.exptName, obj.specName, obj.fileName, obj.window, obj.shiftListName) for obj in objectList] self.scrolledMatrix.update(objectList=objectList, textMatrix=textMatrix) def selectCell(self, obj, row, col): self.currentObject = obj if self.project: self.experiment = self.nmrProject.findFirstExperiment( name=obj.exptName) else: self.experiment = None def getWindow(self, obj): if obj: self.windowPulldown.set(obj.window) def setWindow(self, opt): if isinstance(opt, RowObject): self.currentObject.window = opt.window else: self.currentObject.window = opt self.update() def setShiftList(self, obj=None): if self.project: project = self.project shiftList = self.shiftListPulldown.getObject() if shiftList is None: shiftList = newShiftList(project, unit='ppm') if self.experiment and shiftList and ( shiftList is not self.experiment.shiftList): setExperimentShiftList(self.experiment, shiftList) self.currentObject.shiftListName = shiftList.name self.update() def getShiftList(self, object): names, shiftLists = self.getShiftLists() if names: self.shiftListPulldown.setup(names, shiftLists, 0) if self.experiment and self.experiment.shiftList: name = self.experiment.shiftList.name else: name = object.shiftListName if name is not None: self.shiftListPulldown.set(name) def getShiftLists(self): if self.project: names = [] objects = getShiftLists(self.nmrProject) for shiftList in objects: if not shiftList.name: shiftList.name = 'ShiftList %d' % shiftList.serial names.append(shiftList.name) objects.append(None) names.append('<New>') else: objects = [ None, ] names = [ 'ShiftList 1', ] return names, objects def chooseFormat(self, format): if format in ('Bruker', 'Varian'): self.gridDetails(True) else: self.gridDetails(False) file_types = [] file_type = file_type_dict.get(format) if (file_type): file_types.extend([file_type]) file_types.append(FileType('All', ['*'])) file_types.append(self.fileSelect.manualFilter) self.fileSelect.setFileTypes(file_types) def getSpectrum(self, obj): if obj: self.spectrumEntry.set(obj.specName) def setSpectrum(self, *event): if self.currentObject: text = self.spectrumEntry.get() if text and text != ' ': for data in self.scrolledMatrix.objectList: if data is self.currentObject: continue if (data.specName == text) and (data.exptName == self.currentObject.exptName): showWarning( 'Repeated name', 'Spectrum name (%s) already in use for experiment (%s)' % (data.specName, data.exptName), parent=self) return elif (self.experiment) and ( self.experiment.findFirstDataSource(name=text)): showWarning( 'Repeated name', 'Spectrum name (%s) already in use for experiment (%s)' % (data.specName, data.exptName), parent=self) return self.currentObject.specName = text self.update() def getExperiment(self, obj): if obj: self.experimentEntry.set(obj.exptName) def setExperiment(self, *event): if self.currentObject: text = self.experimentEntry.get() if text and text != ' ': if self.sharedExpSelect.getSelected(): # share one experiment for all rows for oo in self.scrolledMatrix.objectList: oo.exptName = text else: #separate experiments self.currentObject.exptName = text if self.project: self.experiment = self.nmrProject.findFirstExperiment( name=text) self.update() def updateShiftLists(self): if self.project: name = self.expt_entry.get() e = self.nmrProject.findFirstExperiment(name=name) else: e = None names, objects = self.getShiftLists() if e and e.shiftList: index = objects.index(e.shiftList) else: index = 0 self.shiftListPulldown.setup(names, objects, index) def openSpectrum(self, exptName, specName, file, windowOpt=WINDOW_OPTS[2], shiftListName='<New>', extraData=None): # input checks if not file: showError('No file', 'Need to enter file', parent=self) return None if not exptName: showError('Experiment', 'Need to enter experiment name', parent=self) return None if not specName: showError('Spectrum', 'Need to enter spectrum name', parent=self) return None # get or set up project project = self.project if not project: self.project = project = defaultProject() self.parent.initProject(project) self.nmrProject = self.parent.nmrProject self.analysisProject = self.parent.analysisProject #Default ShiftList with name 'ShiftList 1' created # set up shift list if shiftListName == '<New>': shiftList = None else: shiftList = self.nmrProject.findFirstMeasurementList( className='ShiftList', name=shiftListName) # read params format = self.formatPulldown.getText() clazz = params_class_dict[format] try: params = clazz(file, extraData=extraData) except Implementation.ApiError, e: showError('Reading params file', 'Fatal error: ' + e.error_msg, parent=self) return None dim = params.pseudoDataDim() if dim is not None: if format == 'NMRPipe': popup = NmrPipePseudoPopup(self, params, dim, file) popup.destroy() elif format == 'Bruker': popup = BrukerPseudoPopup(self, params, dim) popup.destroy() # get or set up experiment experiment = self.nmrProject.findFirstExperiment(name=exptName) if experiment: expIsNew = False if experiment.findFirstDataSource(name=specName): showError('Duplicate name', 'Duplicate spectrum name "%s" in experiment %s' % (specName, experiment.name), parent=self) return None elif (experiment.dataSources and experiment not in self.okExpSet): if showOkCancel('Multiple Spectra Warning', 'Really put multiple ' 'spectra into existing experiment %s?' % experiment.name, parent=self): self.okExpSet.add(experiment) else: return else: expIsNew = True try: # Will also work for shiftList == None experiment = Nmr.Experiment(self.nmrProject, name=exptName, numDim=params.ndim, shiftList=shiftList) except Implementation.ApiError, experiment: showError('Experiment', experiment.error_msg, parent=self) return None
class CingFrame(NmrCalcRunFrame): def __init__(self, parent, application, *args, **kw): project = application.project self.nmrProject = nmrProject = application.nmrProject if project: calcStore = project.findFirstNmrCalcStore(name=APP_NAME, nmrProject=nmrProject) or \ project.newNmrCalcStore(name=APP_NAME, nmrProject=nmrProject) else: calcStore = None self.application = application self.residue = None self.structure = None self.serverCredentials = None self.iCingBaseUrl = DEFAULT_URL self.resultsUrl = None self.chain = None self.serverDone = False NmrCalcRunFrame.__init__(self, parent, project, calcStore, *args, **kw) # # # # # # New Structure Frame # # # # # self.structureTable.grid_forget() self.structureButtons.grid_forget() self.ensemblePulldown.grid_forget() self.modelButtons.grid_forget() self.modelPulldown.grid_forget() frame = self.inputTabs.frames[0] frame.grid_rowconfigure(0, weight=0) frame.grid_rowconfigure(1, weight=1) label = Label(frame, text='Ensemble: ', grid=(0, 0)) self.structurePulldown = PulldownList( frame, callback=self.changeStructure, grid=(0, 1), tipText='The structure ensemble coordinates to submit') tipTexts = [ 'Conformational model number', 'Whether analyse this model' ] headingList = ['Model', 'Use'] editWidgets = [None, None] editGetCallbacks = [None, self.toggleModel] editSetCallbacks = [ None, None, ] self.modelTable = ScrolledMatrix(frame, grid=(1, 0), gridSpan=(1, 2), callback=self.selectStructModel, multiSelect=True, tipTexts=tipTexts, editWidgets=editWidgets, initialRows=2, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, headingList=headingList) tipTexts = [ 'Activate the selected models so that they will be consedered in the analysis', 'Deactivate the selected models so that they will not be considered in the analysis' ] texts = ['Activate Selected', 'Inactivate Selected'] commands = [self.activateModels, self.disableModels] buttons = ButtonList(frame, texts=texts, commands=commands, grid=(2, 0), gridSpan=(1, 2), tipTexts=tipTexts) # # # # # # Submission frame # # # # # # tab = self.tabbedFrame.frames[1] tab.expandGrid(1, 0) frame = LabelFrame(tab, text='Server Job Submission', grid=(0, 0)) frame.expandGrid(None, 2) srow = 0 label = Label(frame, text='iCing URL:', grid=(srow, 0)) self.iCingBaseUrlPulldown = PulldownList( frame, texts=URLS, objects=URLS, index=0, grid=(srow, 1), tipText='Web location of iCING server to use') srow += 1 label = Label(frame, text='Results File:', grid=(srow, 0)) self.resultFileEntry = Entry( frame, bd=1, text='', grid=(srow, 1), width=50, tipText='Name of file to store compressed CING results in') self.setZipFileName() button = Button(frame, text='Choose File', bd=1, sticky='ew', command=self.chooseZipFile, grid=(srow, 2), tipText='Select file to overwrite with CING results') srow += 1 label = Label(frame, text='Results URL:', grid=(srow, 0)) self.resultUrlEntry = Entry( frame, bd=1, text='', grid=(srow, 1), width=50, tipText='Web location where CING results will be posted') button = Button(frame, text='View Results HTML', bd=1, sticky='ew', command=self.viewHtmlResults, grid=(srow, 2), tipText='Open the HTML CING results in a web browser') srow += 1 tipTexts = [ 'Submit the CCPN project to the CING server', 'Determin whether the iCING job is complete, pending or has failed', 'Remove all trace of the last submissionfrom the iCING server', 'Download the compressed CING results, including HTML' ] texts = [ 'Submit Project!', 'Check Run Status', 'Purge Server Result', 'Download Results' ] commands = [ self.runCingServer, self.checkStatus, self.purgeCingServer, self.downloadResults ] self.buttonBar = ButtonList(frame, texts=texts, commands=commands, grid=(srow, 0), gridSpan=(1, 3), tipTexts=tipTexts) for button in self.buttonBar.buttons[:1]: button.config(bg=CING_BLUE) # # # # # # Residue frame # # # # # # frame = LabelFrame(tab, text='Residue Options', grid=(1, 0)) frame.expandGrid(1, 1) label = Label(frame, text='Chain: ') label.grid(row=0, column=0, sticky='w') self.chainPulldown = PulldownList( frame, callback=self.changeChain, tipText='Select the molecular system chain to consider') self.chainPulldown.grid(row=0, column=1, sticky='w') headingList = ['#', 'Residue', 'Linking', 'Decriptor', 'Use?'] tipTexts = [ 'Sequence number', 'Residue type code', 'In-chain connectivity of residue', 'Protonation and steriochemical state', 'Whether to consider the residue in the analysis' ] editWidgets = [None, None, None, None, None] editGetCallbacks = [None, None, None, None, self.toggleResidue] editSetCallbacks = [ None, None, None, None, None, ] self.residueMatrix = ScrolledMatrix(frame, headingList=headingList, multiSelect=True, tipTexts=tipTexts, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, callback=self.selectResidue) self.residueMatrix.grid(row=1, column=0, columnspan=2, sticky='nsew') tipTexts = [ 'Use the selected residues in the analysis', 'Do not use the selected residues in the analysis' ] texts = ['Activate Selected', 'Inactivate Selected'] commands = [self.activateResidues, self.deactivateResidues] self.resButtons = ButtonList(frame, texts=texts, commands=commands, tipTexts=tipTexts) self.resButtons.grid(row=2, column=0, columnspan=2, sticky='ew') """ # # # # # # Validate frame # # # # # # frame = LabelFrame(tab, text='Validation Options', grid=(2,0)) frame.expandGrid(None,2) srow = 0 self.selectCheckAssign = CheckButton(frame) self.selectCheckAssign.grid(row=srow, column=0,sticky='nw' ) self.selectCheckAssign.set(True) label = Label(frame, text='Assignments and shifts') label.grid(row=srow,column=1,sticky='nw') srow += 1 self.selectCheckResraint = CheckButton(frame) self.selectCheckResraint.grid(row=srow, column=0,sticky='nw' ) self.selectCheckResraint.set(True) label = Label(frame, text='Restraints') label.grid(row=srow,column=1,sticky='nw') srow += 1 self.selectCheckQueen = CheckButton(frame) self.selectCheckQueen.grid(row=srow, column=0,sticky='nw' ) self.selectCheckQueen.set(False) label = Label(frame, text='QUEEN') label.grid(row=srow,column=1,sticky='nw') srow += 1 self.selectCheckScript = CheckButton(frame) self.selectCheckScript.grid(row=srow, column=0,sticky='nw' ) self.selectCheckScript.set(False) label = Label(frame, text='User Python script\n(overriding option)') label.grid(row=srow,column=1,sticky='nw') self.validScriptEntry = Entry(frame, bd=1, text='') self.validScriptEntry.grid(row=srow,column=2,sticky='ew') scriptButton = Button(frame, bd=1, command=self.chooseValidScript, text='Browse') scriptButton.grid(row=srow,column=3,sticky='ew') """ # # # # # # # # # # self.update(calcStore) self.administerNotifiers(application.registerNotify) def downloadResults(self): if not self.run: msg = 'No current iCing run' showWarning('Failure', msg, parent=self) return credentials = self.serverCredentials if not credentials: msg = 'No current iCing server job' showWarning('Failure', msg, parent=self) return fileName = self.resultFileEntry.get() if not fileName: msg = 'No save file specified' showWarning('Failure', msg, parent=self) return if os.path.exists(fileName): msg = 'File %s already exists. Overwite?' % fileName if not showOkCancel('Query', msg, parent=self): return url = self.iCingBaseUrl iCingUrl = self.getServerUrl(url) logText = iCingRobot.iCingFetch(credentials, url, iCingUrl, fileName) print logText msg = 'Results saved to file %s\n' % fileName msg += 'Purge results from iCing server?' if showYesNo('Query', msg, parent=self): self.purgeCingServer() def getServerUrl(self, baseUrl): iCingUrl = os.path.join(baseUrl, 'icing/icing/serv/iCingServlet') return iCingUrl def viewHtmlResults(self): resultsUrl = self.resultsUrl if not resultsUrl: msg = 'No current iCing results URL' showWarning('Failure', msg, parent=self) return webBrowser = WebBrowser(self.application, popup=self.application) webBrowser.open(self.resultsUrl) def runCingServer(self): if not self.project: return run = self.run if not run: msg = 'No CING run setup' showWarning('Failure', msg, parent=self) return structureData = self.getStructureData() if not structureData: msg = 'No structure ensemble selected' showWarning('Failure', msg, parent=self) return if not structureData.models: msg = 'No structural models selected from ensemble' showWarning('Failure', msg, parent=self) return residueData = self.getResidueData() if not (residueData and residueData.residues): msg = 'No active residues selected in structure' showWarning('Failure', msg, parent=self) return url = self.iCingBaseUrlPulldown.getObject() url.strip() if not url: msg = 'No iCing server URL specified' showWarning('Failure', msg, parent=self) self.iCingBaseUrl = None return msg = 'Submit job now? You will be informed when the job is done.' if not showOkCancel('Confirm', msg, parent=self): return self.run.status = 'pending' self.iCingBaseUrl = url iCingUrl = self.getServerUrl(url) self.serverCredentials, results, tarFileName = iCingRobot.iCingSetup( self.project, userId='ccpnAp', url=iCingUrl) if not results: # Message already issued on failure self.run.status = 'failed' self.serverCredentials = None self.resultsUrl = None self.update() return else: credentials = self.serverCredentials os.unlink(tarFileName) entryId = iCingRobot.iCingProjectName(credentials, iCingUrl).get( iCingRobot.RESPONSE_RESULT) baseUrl, htmlUrl, logUrl, zipUrl = iCingRobot.getResultUrls( credentials, entryId, url) self.resultsUrl = htmlUrl # Save server data in this run for persistence setRunParameter(run, iCingRobot.FORM_USER_ID, self.serverCredentials[0][1]) setRunParameter(run, iCingRobot.FORM_ACCESS_KEY, self.serverCredentials[1][1]) setRunParameter(run, ICING_BASE_URL, url) setRunParameter(run, HTML_RESULTS_URL, htmlUrl) self.update() #run.inputStructures = structure.sortedModels() # select residues from the structure's chain #iCingRobot.iCingResidueSelection(credentials, iCingUrl, residueText) # Select models from ensemble #iCingRobot.iCingEnsembleSelection(credentials, iCingUrl, ensembleText) # Start the actual run self.serverDone = False iCingRobot.iCingRun(credentials, iCingUrl) # Fetch server progress occasionally, report when done # this function will call itself again and again self.after(CHECK_INTERVAL, self.timedCheckStatus) self.update() def timedCheckStatus(self): if not self.serverCredentials: return if self.serverDone: return status = iCingRobot.iCingStatus(self.serverCredentials, self.getServerUrl(self.iCingBaseUrl)) if not status: #something broke, already warned self.run.status = 'failed' return result = status.get(iCingRobot.RESPONSE_RESULT) if result == iCingRobot.RESPONSE_DONE: self.serverDone = True self.run.status = 'completed' msg = 'CING run is complete!' showInfo('Completion', msg, parent=self) return self.after(CHECK_INTERVAL, self.timedCheckStatus) def checkStatus(self): if not self.serverCredentials: return status = iCingRobot.iCingStatus(self.serverCredentials, self.getServerUrl(self.iCingBaseUrl)) if not status: #something broke, already warned return result = status.get(iCingRobot.RESPONSE_RESULT) if result == iCingRobot.RESPONSE_DONE: msg = 'CING run is complete!' showInfo('Completion', msg, parent=self) self.serverDone = True return else: msg = 'CING job is not done.' showInfo('Processing', msg, parent=self) self.serverDone = False return def purgeCingServer(self): if not self.project: return if not self.run: msg = 'No CING run setup' showWarning('Failure', msg, parent=self) return if not self.serverCredentials: msg = 'No current iCing server job' showWarning('Failure', msg, parent=self) return url = self.iCingBaseUrl results = iCingRobot.iCingPurge(self.serverCredentials, self.getServerUrl(url)) if results: showInfo('Info', 'iCing server results cleared') self.serverCredentials = None self.iCingBaseUrl = None self.serverDone = False deleteRunParameter(self.run, iCingRobot.FORM_USER_ID) deleteRunParameter(self.run, iCingRobot.FORM_ACCESS_KEY) deleteRunParameter(self.run, HTML_RESULTS_URL) else: showInfo('Info', 'Purge failed') self.update() def chooseZipFile(self): fileTypes = [ FileType('Zip', ['*.zip']), ] popup = FileSelectPopup(self, file_types=fileTypes, file=self.resultFileEntry.get(), title='Results zip file location', dismiss_text='Cancel', selected_file_must_exist=False) fileName = popup.getFile() if fileName: self.resultFileEntry.set(fileName) popup.destroy() def setZipFileName(self): if self.project: zipFile = '%s_CING_report.zip' % self.project.name self.resultFileEntry.set(zipFile) else: self.resultFileEntry.set('CING_report.zip') def selectStructModel(self, model, row, col): self.model = model def selectResidue(self, residue, row, col): self.residue = residue def getResidueData(self): chain = self.chain.chain chainCode = chain.code msCode = chain.molSystem.code dataObj = self.run.findFirstData(className=RESIDUE_DATA, ioRole='input', molSystemCode=msCode, chainCode=chainCode, name=APP_NAME) if not dataObj: dataObj = self.run.newMolResidueData(molSystemCode=msCode, chainCode=chainCode, ioRole='input', name=APP_NAME) # if not dataObj.residueSeqIds: seqIds = [r.seqId for r in self.chain.sortedResidues()] dataObj.residueSeqIds = seqIds return dataObj def getStructureData(self): eId = self.structure.ensembleId msCode = self.structure.molSystem.code dataObj = self.run.findFirstData(className=STRUCTURE_DATA, molSystemCode=msCode, ensembleId=eId, ioRole='input', name=APP_NAME) if not dataObj: dataObj = self.run.newStructureEnsembleData(ioRole='input', molSystemCode=msCode, ensembleId=eId, name=APP_NAME) # if not dataObj.modelSerials: serials = [m.serial for m in self.structure.sortedModels()] dataObj.modelSerials = serials for dataObjB in self.run.findAllData(className=STRUCTURE_DATA, name=APP_NAME, ioRole='input'): if dataObjB is not dataObj: dataObjB.delete() return dataObj def deactivateResidues(self): if self.run and self.chain: dataObj = self.getResidueData() seqIds = set(dataObj.residueSeqIds) for residue in self.residueMatrix.currentObjects: seqId = residue.seqId if seqId in seqIds: seqIds.remove(seqId) seqIds = list(seqIds) seqIds.sort() dataObj.residueSeqIds = seqIds self.updateResidues() def activateResidues(self): if self.run and self.chain: for residue in self.residueMatrix.currentObjects: dataObj = self.getResidueData() seqIds = set(dataObj.residueSeqIds) for residue in self.residueMatrix.currentObjects: seqId = residue.seqId if seqId not in seqIds: seqIds.add(seqId) seqIds = list(seqIds) seqIds.sort() dataObj.residueSeqIds = seqIds self.updateResidues() def activateModels(self): if self.run and self.structure: dataObj = self.getStructureData() serials = set(dataObj.modelSerials) for model in self.modelTable.currentObjects: serial = model.serial if serial not in serials: serials.add(serial) serials = list(serials) serials.sort() dataObj.modelSerials = serials self.updateModels() def disableModels(self): if self.run and self.structure: dataObj = self.getStructureData() serials = set(dataObj.modelSerials) for model in self.modelTable.currentObjects: serial = model.serial if serial in serials: serials.remove(serial) serials = list(serials) serials.sort() dataObj.modelSerials = serials self.updateModels() def toggleModel(self, *opt): if self.model and self.run and self.structure: dataObj = self.getStructureData() serials = set(dataObj.modelSerials) serial = self.model.serial if serial in serials: serials.remove(serial) else: serials.add(serial) serials = list(serials) serials.sort() dataObj.modelSerials = serials self.updateModels() def toggleResidue(self, *opt): if self.residue and self.run: dataObj = self.getResidueData() seqIds = set(dataObj.residueSeqIds) seqId = self.residue.seqId if seqId in seqIds: seqIds.remove(seqId) else: seqIds.add(seqId) seqIds = list(seqIds) seqIds.sort() dataObj.residueSeqIds = seqIds self.updateResidues() def updateResidues(self): if self.residue and (self.residue.topObject is not self.structure): self.residue = None textMatrix = [] objectList = [] colorMatrix = [] if self.chain: resDataObj = self.getResidueData() selectedRes = set(resDataObj.residues) chainCode = self.chain.code for residue in self.chain.sortedResidues(): msResidue = residue.residue if msResidue in selectedRes: colors = [None, None, None, None, CING_BLUE] use = 'Yes' else: colors = [None, None, None, None, None] use = 'No' datum = [ residue.seqCode, msResidue.ccpCode, msResidue.linking, msResidue.descriptor, use, ] textMatrix.append(datum) objectList.append(residue) colorMatrix.append(colors) self.residueMatrix.update(objectList=objectList, textMatrix=textMatrix, colorMatrix=colorMatrix) def updateChains(self): index = 0 names = [] chains = [] chain = self.chain if self.structure: chains = self.structure.sortedCoordChains() names = [chain.code for chain in chains] if chains: if chain not in chains: chain = chains[0] index = chains.index(chain) self.changeChain(chain) self.chainPulldown.setup(names, chains, index) def updateStructures(self): index = 0 names = [] structures = [] structure = self.structure if self.run: structures0 = [(s.ensembleId, s) for s in self.project.structureEnsembles] structures0.sort() for eId, structure0 in structures0: name = '%s:%s' % (structure0.molSystem.code, eId) structures.append(structure0) names.append(name) if structures: if structure not in structures: structure = structures[-1] index = structures.index(structure) if self.structure is not structure: self.changeStructure(structure) self.structurePulldown.setup(names, structures, index) def updateModels(self, obj=None): textMatrix = [] objectList = [] colorMatrix = [] if self.structure and self.run: strucDataObj = self.getStructureData() selectedSerials = set(strucDataObj.modelSerials) for model in self.structure.sortedModels(): if model.serial in selectedSerials: colors = [None, CING_BLUE] use = 'Yes' else: colors = [None, None] use = 'No' datum = [model.serial, use] textMatrix.append(datum) objectList.append(model) colorMatrix.append(colors) self.modelTable.update(objectList=objectList, textMatrix=textMatrix, colorMatrix=colorMatrix) def changeStructure(self, structure): if self.project and (self.structure is not structure): self.project.currentEstructureEnsemble = structure self.structure = structure if self.run: dataObj = self.getStructureData() serials = set(dataObj.modelSerials) serials2 = set([m.serial for m in structure.models]) serials = list(serials & serials2) serials.sort() dataObj.modelSerials = serials dataObj.ensembleId = structure.ensembleId dataObj.molSystemCode = structure.molSystem.code # Could clean up residue data if required # prob OK to have haning around in case structure # changes back #for dataObj in self.run.findAllData(className=RESIDUE_DATA, # ioRole='input', # molSystemCode=msCode, # name=APP_NAME) # dataObj.delete() self.updateModels() self.updateChains() def changeChain(self, chain): if self.project and (self.chain is not chain): self.chain = chain self.updateResidues() #def chooseValidScript(self): # # # Prepend default Cyana file extension below # fileTypes = [ FileType('Python', ['*.py']), ] # popup = FileSelectPopup(self, file_types = fileTypes, # title='Python file', dismiss_text='Cancel', # selected_file_must_exist = True) # fileName = popup.getFile() # self.validScriptEntry.set(fileName) # popup.destroy() def updateAll(self, project=None): if project: self.project = project self.nmrProject = nmrProject = project.currentNmrProject calcStore = project.findFirstNmrCalcStore(name='CING', nmrProject=nmrProject) or \ project.newNmrCalcStore(name='CING', nmrProject=nmrProject) else: calcStore = None if not self.project: return self.setZipFileName() if not self.project.currentNmrProject: name = self.project.name self.nmrProject = self.project.newNmrProject(name=name) else: self.nmrProject = self.project.currentNmrProject self.update(calcStore) def update(self, calcStore=None): NmrCalcRunFrame.update(self, calcStore) run = self.run urls = URLS index = 0 if run: userId = getRunTextParameter(run, iCingRobot.FORM_USER_ID) accessKey = getRunTextParameter(run, iCingRobot.FORM_ACCESS_KEY) if userId and accessKey: self.serverCredentials = [(iCingRobot.FORM_USER_ID, userId), (iCingRobot.FORM_ACCESS_KEY, accessKey)] url = getRunTextParameter(run, ICING_BASE_URL) if url: htmlUrl = getRunTextParameter(run, HTML_RESULTS_URL) self.iCingBaseUrl = url self.resultsUrl = htmlUrl # May be None self.resultUrlEntry.set(self.resultsUrl) if self.iCingBaseUrl and self.iCingBaseUrl not in urls: index = len(urls) urls.append(self.iCingBaseUrl) self.iCingBaseUrlPulldown.setup(urls, urls, index) self.updateButtons() self.updateStructures() self.updateModels() self.updateChains() def updateButtons(self, event=None): buttons = self.buttonBar.buttons if self.project and self.run: buttons[0].enable() if self.resultsUrl and self.serverCredentials: buttons[1].enable() buttons[2].enable() buttons[3].enable() else: buttons[1].disable() buttons[2].disable() buttons[3].disable() else: buttons[0].disable() buttons[1].disable() buttons[2].disable() buttons[3].disable()