示例#1
0
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
示例#2
0
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)
示例#3
0
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
示例#4
0
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
示例#5
0
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
示例#6
0
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)
示例#7
0
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)
示例#8
0
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()
示例#9
0
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
示例#10
0
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)
示例#11
0
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)
示例#12
0
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)
示例#13
0
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:
示例#14
0
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
示例#15
0
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()