Esempio n. 1
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
Esempio n. 2
0
class BrowseReferenceShiftsPopup(BasePopup):
  """
  **Graphs and Charts of Database Chemical Shift Data**
  
  This popup window presents the user with the distributions of known chemical
  shift values, for various kinds of atom, as they appear in the BioMagResBank
  or re-referenced RefDB databases. This information is useful when attempting
  to determine the type of a residue from its NMR spectra, for example when
  performing protein sequence assignment.

  The popup is divided into two tabs for two different kinds of graphical
  display. The first "1D graphs" tab shows the distributions of chemical shift
  value for either hydrogens or other "heavy" atoms within a given kind of
  residue. The buttons that carry atom names above the main graph can be toggled
  two switch the display for different kinds of atom on or off.

  The second "Amino Acid CA CB" tab is a special display to assist in the 
  assignment of protein sequences when using triple-resonance experiments
  like HNCA/CB. Data for all of the common amino acids is displayed, but
  only for the alpha and beta carbons.

  **Caveats & Tips**

  Two dimensional correlations, e.g. between a 1H resonance and a covalently
  bound 13C will be added to Analysis in the future.

  DNA and RNA chemical shift distributions is not present in the RefDB data, but
  are present in the BMRB data; to view the BMRB data change the pulldown menu
  at the top-right.

  Some of the atom types have a second, minor chemical shift distribution, far
  from the main peak, that is erroneous (this data is still present in the
  source databases). An example of this is for the NE atom of the amino acid
  arginine at around 115 ppm, which in this instance is probably caused by peaks
  being assigned in HSQC spectra in the normal backbone amide ppm range when the
  NE peak is really an aliased harmonic and should be a spectrum width away,
  nearer 85 ppm.

  Distributions that are notably jagged, due to lack of data for a given atom
  type should naturally not be relied upon to any great degree.

  """

  def __init__(self, parent, *args, **kw):

    self.guiParent = parent
    self.sourceName = 'RefDB'
    self.chemAtomNmrRefs = {}
    
    BasePopup.__init__(self, parent=parent, title="Resonance : Reference Chemical Shifts", **kw)

  def body(self, guiFrame):
    
    self.geometry("775x500")
    guiFrame.expandGrid(0,0)
    
    options = ['1D Graphs','Amino Acid CA CB']
    tipTexts = ['Simple graphs of how chemical shift values are distributed for a given atom type',
                'Density plots of alpha a & beta carbon shift distributions for common amnio acids']
    tabbedFrame = TabbedFrame(guiFrame, options=options, grid=(0,0), tipTexts=tipTexts)
    frameA, frameB = tabbedFrame.frames
    
    # # # # # # #  1D GRAPHS  # # # # # # # 
    
 
    row = 0
     
    MolType = self.project.metaclass.metaObjFromQualName('ccp.molecule.ChemComp.MolType')
    molTypes = MolType.enumeration
    molTypes.remove('other')
    molTypes.remove('carbohydrate')
   
    self.molType = 'protein'
    
    ccpCodes = self.getCcpCodes(self.molType) or [None,]
    self.ccpCode = 'Ala'
    
    self.atomType = ATOM_TYPES[0]
    
    self.atomNamesDict = {}
    self.chemAtomNmrRefs = self.getCcpCodeData(self.ccpCode, atomType=self.atomType)
    
    
    tipText = 'Which of the common bio-polymer types to show data for'
    self.molTypeLabel     = Label(frameA, text = 'Molecule Type:', grid=(row,0))
    self.molTypePulldown  = PulldownList(frameA, callback=self.changeMolType,
                                         texts=molTypes, grid=(row,1), tipText=tipText)

    tipText = 'Which residue code to show chemical shift distributions for'
    self.ccpCodeLabel     = Label(frameA, text = 'Residue Code:', grid=(row,2))
    self.ccpCodePulldown  = PulldownList(frameA, callback=self.changeCcpCode, texts=ccpCodes,
                                         index=ccpCodes.index(self.ccpCode), 
                                         grid=(row,3), tipText=tipText)

    tipText = 'Whether to show distributions for hydrogen atoms or other atoms'
    self.atomTypeLabel    = Label(frameA, text = 'Atom Type:', grid=(row,4))
    self.atomTypePulldown = PulldownList(frameA, callback=self.changeAtomType,
                                         texts=ATOM_TYPES, tipText=tipText,
                                         grid=(row,5))

    row += 1

    tipText = 'The selection of atom name to display distributions for'
    self.atomSelector = PartitionedSelector(frameA, self.toggleAtom,
                                            tipText=tipText, maxRowObjects=20)
    self.atomSelector.grid(row=row, column=0, columnspan=6, sticky='ew')

    row += 1

    frameA.expandGrid(row,5)
    self.scrolledGraph = ScrolledGraph(frameA, symbolSize=2, reverseX=True, width=650,
                                       height=300, title='Chemical shift distribution',
                                       xLabel='Chemical shift', yLabel='proportion',
                                       motionCallback=self.updateCrosshairs)
    self.scrolledGraph.grid(row=row, column=0, columnspan=6, sticky='nsew')
    
    # # # # # # #  PROTEIN CA CB   # # # # # # # 
    
    frameB.expandGrid(0,0)
    matrix, ppms = self.getCaCbMatrix()
    title = 'Amino Acid CA & CB Chemical Shifts'
    self.cacbMatrix = ScrolledDensityMatrix(frameB, matrix=matrix, boxSize=14,
                                            title=title,
                                            xLabels=ppms, yLabels=AMINO_ACIDS,
                                            borderColor='grey', zoom=1.0,
                                            labelAxes=True, barPlot=False,
                                            doLegend=False, grid=(0,0))
    
    sdm = self.cacbMatrix
    font = sdm.boldFont
    
    x0, y0 = (470,370)
    sdm.canvas.create_rectangle(x0+170,y0-6,x0+182,y0+6,fill='#4040A0',
                                outline=sdm.borderColor, width=1)
    sdm.canvas.create_text(x0+200, y0, text='CA', font=font)
    sdm.canvas.create_rectangle(x0+220,y0-6,x0+232,y0+6,fill='#A04040',
                                outline=sdm.borderColor, width=1)
    sdm.canvas.create_text(x0+250, y0, text='CB', font=font)
    sdm.canvas.create_text(x0, y0, text='13C PPM +/- 0.5', font=font)
    
    # # # # # # #  M A I N   # # # # # # # 
    
    tipText = 'Whether to use chemical shift data from the BMRB (unfiltered) or RefDB sets'
    label = Label(tabbedFrame.sideFrame, text='Source Database:', grid=(0,0), sticky='e')
    index = SOURCE_NAMES.index(self.sourceName)
    self.sourcePulldown = PulldownList(tabbedFrame.sideFrame, self.changeSource,
                                       texts=SOURCE_NAMES, index=index, 
                                       grid=(0,1), sticky='e', tipText=tipText)

    self.bottomButtons = UtilityButtonList(tabbedFrame.sideFrame, expands=True,
                                           helpUrl=self.help_url, sticky='e',
                                           grid=(0,2))
                                               
    self.waiting = False
    self.updateAfter()

    for func in ('__init__', 'delete'):
      self.registerNotify(self.updateAfter, 'ccp.nmr.NmrReference.NmrReferenceStore', func)

  def open(self):
  
    self.updateAfter()
    BasePopup.open(self)
  
  def changeSource(self, sourceName):
  
    if sourceName is not self.sourceName:
      self.sourceName = sourceName
      self.updateCcpCodes()
      self.updateAfter()
    
  def getAtomColor(self, i,N):
    
    h = i/float(N)
    s = 1
    v = 0.8
    [r,g,b] = hsbToRgb(h,s,v)
   
    return hexRepr(r,g,b)

  def toggleAtom(self, atomName):
  
    if self.atomNamesDict.get(atomName):
      self.atomNamesDict[atomName] = False
    else:
      self.atomNamesDict[atomName] = True

    self.updateAfter()

  def getCcpCodes(self, molType):
  
    sourceName = self.sourceName

    ccpCodes = []
    for nmrRef in self.project.findAllNmrReferenceStores(molType=molType):
      chemCompNmrRef = nmrRef.findFirstChemCompNmrRef(sourceName=sourceName)
      
      if chemCompNmrRef:
        ccpCodes.append(nmrRef.ccpCode)
    
    ccpCodes.sort()
      
    return ccpCodes
  
  def getCcpCodeData(self, ccpCode, molType=None, atomType=None):

    if not molType:
      molType = self.molType

    dataDict = {}
    project = self.project
    sourceName = self.sourceName
    
    nmrRefStore = project.findFirstNmrReferenceStore(molType=molType,ccpCode=ccpCode)
     
    chemCompNmrRef = nmrRefStore.findFirstChemCompNmrRef(sourceName=sourceName)
    if chemCompNmrRef:
    
      chemCompVarNmrRef = chemCompNmrRef.findFirstChemCompVarNmrRef(linking='any',descriptor='any')
      if chemCompVarNmrRef:
        for chemAtomNmrRef in chemCompVarNmrRef.chemAtomNmrRefs:
          atomName = chemAtomNmrRef.name
          element  = chemAtomNmrRef.findFirstChemAtom().elementSymbol
  
          if not atomType:
            dataDict[atomName] = chemAtomNmrRef
  
          elif (atomType == 'Hydrogen' and element == 'H') or \
               (atomType == 'Heavy' and element != 'H'):
            dataDict[atomName] = chemAtomNmrRef
 
    return dataDict  
 
  def changeMolType(self, molType):
  
    self.molType = molType
    self.updateCcpCodes()
  
  def changeCcpCode(self, ccpCode):
  
    self.ccpCode = ccpCode
    self.updateAtomNames()
       
  def changeAtomType(self, atomType):
  
    self.atomType = atomType
    self.updateAtomNames()
       
  def updateAfter(self, *opt):
  
    if self.waiting:
      return
    else:
      self.waiting = True
      self.after_idle(self.update)

  def updateCcpCodes(self):
    
    ccpCodes = self.getCcpCodes(self.molType)
    if ccpCodes:
      self.ccpCodePulldown.setup(ccpCodes, ccpCodes, 0)
      self.changeCcpCode(ccpCodes[0])
    else:
      self.ccpCodePulldown.setup([], [], 0)
      self.changeCcpCode(None)
      
  
  def updateAtomNames(self):
  
    if self.ccpCode:
      self.chemAtomNmrRefs = self.getCcpCodeData(self.ccpCode, atomType=self.atomType)
      atomNames = self.chemAtomNmrRefs.keys()
      atomNames = greekSortAtomNames(atomNames, self.molType)
      N = len(atomNames)

      for atomName in atomNames:
        if self.atomNamesDict.get(atomName) is None:
          self.atomNamesDict[atomName] = True

      colors = []
      for i in range(N):
        colors.append( self.getAtomColor(i,N) )

 
    else:
      colors = []
      atomNames = []
      N = 0
 
    self.atomSelector.update(objects=atomNames,labels=atomNames,colors=colors)
        
    for i in range(N):
      if self.atomNamesDict[atomNames[i]]:
        self.atomSelector.setButtonState(i, True)
      else:
        self.atomSelector.setButtonState(i, False)

    self.updateAfter()
  
  def destroy(self):

    for func in ('__init__', 'delete'):
      self.unregisterNotify(self.updateAfter, 'ccp.nmr.NmrReference.NmrReferenceStore', func)

    BasePopup.destroy(self)
    
  def update(self):

    matrix, ppms = self.getCaCbMatrix()
    self.cacbMatrix.xLabels = ppms
    self.cacbMatrix.update(matrix)
       
    self.updateAtomNames()
    dataSets = []
    colorList = []
    atomsString = ''
    
    if self.ccpCode:
      c = 0
      atomNames = self.chemAtomNmrRefs.keys()
      N = len(atomNames)
      for atomName in greekSortAtomNames(atomNames, self.molType):
        if self.atomNamesDict[atomName]:
          atomsString += '%s ' % atomName
          chemAtomNmrRef = self.chemAtomNmrRefs[atomName]
          distribution   = chemAtomNmrRef.distribution
          refPoint       = chemAtomNmrRef.refPoint
          refValue       = chemAtomNmrRef.refValue
          valuePerPoint  = chemAtomNmrRef.valuePerPoint

          data = []
          for i in range(len(distribution)):
            x = refValue + valuePerPoint*( i-refPoint)
            y = distribution[i]
            data.append( (x,y) )
 
          dataSets.append(data)
          colorList.append(self.getAtomColor(c,N))
        c += 1
 
      self.scrolledGraph.title = '%s %s Chemical shift distribution' % (self.ccpCode,atomsString)
    else:
      dataSets = [[(0,0)],]
      self.scrolledGraph.title = 'Chemical shift distribution'
    
    self.scrolledGraph.zoom  = 1.0
    
    self.scrolledGraph.update(dataSets=dataSets, dataColors=colorList)  
    
    self.waiting = False
    
  def getCaCbMatrix(self):  
    
    ppms = range(73,14,-1)
    blankCol = [0.0]*len(ppms)
    matrix = [[0.0]*len(AMINO_ACIDS) for x in ppms]
    
    for i, ccpCode in enumerate(AMINO_ACIDS):
      atomRefDict = self.getCcpCodeData(ccpCode, molType='protein')
      valueDict = {}
      
      for atomName in ('CA','CB'):
        valueDict[atomName] = blankCol[:]
        
        chemAtomNmrRef = atomRefDict.get(atomName)
        if not chemAtomNmrRef:
          continue
          
        distribution   = chemAtomNmrRef.distribution
        refPoint       = chemAtomNmrRef.refPoint
        refValue       = chemAtomNmrRef.refValue
        valuePerPoint  = chemAtomNmrRef.valuePerPoint
        
        
        for j, ppm in enumerate(ppms):
          ppmMin = ppm-0.5
          ppmMax = ppm+0.5
 
          v = 0.0
          n = 0.0
          for k in range(len(distribution)):
            y = refValue + valuePerPoint*(k-refPoint)
 
            if ppmMin < y <= ppmMax:
              v += distribution[k]
              n += 1.0
            elif y > ppmMax:
              break
 
          if n:
            valueDict[atomName][j] = v/n
      
      for j, ppm in enumerate(ppms):
        matrix[j][i] = valueDict['CA'][j] - valueDict['CB'][j]

    return matrix, ppms

  def updateCrosshairs(self, event):

    # code below is a bit dangerous

    position = self.scrolledGraph.getPlotCoords(event)[0]

    typeLocation = []
    isHydrogen = (self.atomType == 'Hydrogen')
    for panelType in self.analysisProject.panelTypes:
      axisType = panelType.axisType
      if isHydrogen:
        if axisType.name in ('1H', '2H'):
          typeLocation.append((panelType, position))
      else:
        if axisType.name not in ('1H', '2H'):
          typeLocation.append((panelType, position))

    self.scrolledGraph.mouseEnter(event)
    self.parent.drawWindowCrosshairs(typeLocation)

  def drawCrosshairs(self, typeLocation):
    """ Draw crosshairs at specified locations.
        typeLocation = tuple of (PanelType, position)
    """

    isHydrogen = (self.atomType == ATOM_TYPES[0])

    positions = []
    for (panelType, position) in typeLocation:
      axisType = panelType.axisType
      if axisType.measurementType != 'Shift':
        continue
        
      if position is None:
        continue

      if isHydrogen:
        if axisType.name in ('1H', '2H'):
          positions.append(position)
      else:
        if axisType.name not in ('1H', '2H'):
          positions.append(position)

    self.scrolledGraph.drawVerticalLines(positions)
Esempio n. 3
0
class CalcCouplingPopup(BasePopup):
    def __init__(self, parent, *args, **kw):

        self.waiting = False
        self.peakList = None
        self.peakLists = []
        self.windowPane = None
        self.cluster = None
        self.clusters = {}
        self.multiplet = None
        self.guiParent = parent

        BasePopup.__init__(self,
                           parent=parent,
                           title='Measure Couplings',
                           **kw)

        self.updateWindows()
        self.updateAfter()

    def body(self, guiFrame):

        guiFrame.grid_columnconfigure(0, weight=1)

        row = 0
        frame = LabelFrame(guiFrame, text='Options')
        frame.grid(row=row, column=0, sticky='ew')
        frame.grid_columnconfigure(1, weight=1)
        frame.grid_rowconfigure(1, weight=1)

        label = Label(frame, text='Window:')
        label.grid(row=0, column=0, sticky='nw')
        self.windowPulldown = PulldownList(frame, callback=self.changeWindow)
        self.windowPulldown.grid(row=0, column=1, sticky='nw')

        label = Label(frame, text='Multiplet Pattern:')
        label.grid(row=1, column=0, columnspan=2, sticky='nw')
        self.multipletButtons = PartitionedSelector(frame,
                                                    callback=self.setMultiplet,
                                                    radio=True)
        self.multipletButtons.grid(row=2, column=0, columnspan=2, sticky='ew')

        row += 1
        frame = LabelFrame(guiFrame, text='Active Peak Lists')
        frame.grid(row=row, column=0, sticky='ew')
        frame.grid_columnconfigure(0, weight=1)
        frame.grid_rowconfigure(0, weight=1)

        headingList = [
            'Experiment', 'Spectrum', 'List', 'Coupled Dims', 'Experiment Type'
        ]
        self.peakListMatrix = ScrolledMatrix(frame,
                                             headingList=headingList,
                                             callback=None,
                                             multiSelect=False)
        self.peakListMatrix.grid(row=0, column=0, sticky='nsew')

        row += 1
        guiFrame.grid_rowconfigure(row, weight=1)
        frame = LabelFrame(guiFrame, text='Multiplet Peak Clusters')
        frame.grid(row=row, column=0, sticky='nsew')
        frame.grid_columnconfigure(0, weight=1)
        frame.grid_rowconfigure(0, weight=1)

        headingList = [
            '#', 'Main\nAssignment', 'Num\nPeaks', 'Coupling\nAssignment',
            'Value', 'Value'
        ]
        self.clusterMatrix = ScrolledMatrix(frame,
                                            headingList=headingList,
                                            callback=self.selectCluster,
                                            multiSelect=True)
        self.clusterMatrix.grid(row=0, column=0, sticky='nsew')

        row += 1
        texts = [
            'Cluster Selected\nPeaks', 'Assign\nCouplings', 'List\nPeaks',
            'Find\nPeaks', 'Delete\nClusters'
        ]
        commands = [
            self.clusterSelectedPeaks, self.assignCouplings, self.showPeaks,
            self.findPeaks, self.deleteClusters
        ]
        self.bottomButtons = UtilityButtonList(guiFrame,
                                               texts=texts,
                                               expands=True,
                                               commands=commands,
                                               helpUrl=self.help_url)
        self.bottomButtons.grid(row=row, column=0, sticky='ew')

        self.administerNotifiers(self.registerNotify)

    def administerNotifiers(self, notifyFunc):

        for clazz in ('ccp.nmr.Nmr.DataSource', 'ccp.nmr.Nmr.Experiment'):
            notifyFunc(self.updatePeakListsAfter, clazz, 'setName')

        for func in ('__init__', 'delete', 'setName'):
            notifyFunc(self.updateWindows, 'ccpnmr.Analysis.SpectrumWindow',
                       func)

        for func in ('__init__', 'delete'):
            notifyFunc(self.updatePeakListsAfter, 'ccp.nmr.Nmr.PeakList', func)

        for func in ('__init__', 'delete', 'addPeak', 'setPeaks', 'removePeak',
                     'setAnnotation'):
            notifyFunc(self.updateAfter, 'ccp.nmr.Nmr.PeakCluster', func)

    def deleteClusters(self):

        clusters = self.clusterMatrix.currentObjects
        for cluster in clusters:
            deleteCluster(cluster)

    def findPeaks(self):

        if self.windowPane and self.cluster:
            peaks = list(self.cluster.peaks)
            if not peaks:
                return

            spectrum = peaks[0].peakList.dataSource
            analysisSpectrum = spectrum.analysisSpectrum
            view = self.windowPane.findFirstSpectrumWindowView(
                analysisSpectrum=analysisSpectrum)
            windowFrame = self.windowPane.getWindowFrame()

            position = {}
            n = float(len(peaks))

            for axisMapping in view.axisMappings:
                dim = axisMapping.analysisDataDim.dataDim.dim
                xyz = axisMapping.label

                mean = 0.0
                for peak in peaks:
                    peakDim = peak.findFirstPeakDim(dim=dim)
                    mean += peakDim.realValue

                mean /= n
                position[xyz] = mean

            windowFrame.gotoPosition(position)

    def updatePeakListsAfter(self, object):

        if object.className == 'Experiment':
            for spectrum in object.dataSources:
                for peakList in spectrum.peakLists:
                    if peakList in self.peakLists:
                        self.updatePeakLists()

        elif object.className == 'DataSource':
            for peakList in object.peakLists:
                if peakList in self.peakLists:
                    self.updatePeakLists()

        else:
            if object in self.peakLists:
                self.updatePeakLists(object)

    def open(self):

        BasePopup.open(self)

        self.updateWindows()
        self.updateAfter()

    def showPeaks(self):

        peaks = {}
        for peakCluster in self.clusterMatrix.currentObjects:
            for peak in peakCluster.peaks:
                peaks[peak] = True

        if peaks:
            self.parent.viewPeaks(peaks.keys())

    def getWindows(self):

        windowPanes = []

        for window in self.analysisProject.sortedSpectrumWindows():
            for windowPane in window.sortedSpectrumWindowPanes():
                for view in windowPane.spectrumWindowViews:
                    if view.isPosVisible or view.isNegVisible:
                        spectrum = view.analysisSpectrum.dataSource

                        if self.getCoupledExpDims(spectrum.experiment):
                            windowPanes.append(windowPane)
                            break

        return windowPanes

    def getCoupledExpDims(self, experiment):
        """ Descrn: List the dimensions (ExpDims) of an experiment which carry couplings.
        Inputs: Nmr.Experiment
        Output: List of Nmr.ExpDims
    """
        COUPLING_TYPES = ('JCoupling', 'Rdc', 'DipolarCoupling')

        expDims = []
        refExperiment = experiment.refExperiment

        if refExperiment:
            for expDim in experiment.expDims:
                refExpDim = expDim.refExpDim

                if not refExpDim:  # Could be sampled dim
                    continue

                for refExpDimRef in refExpDim.refExpDimRefs:
                    if refExpDimRef.coupledIsotopeCodes:
                        expDims.append(expDim)
                        break

        if not expDims:
            for expDim in experiment.expDims:
                for expDimRef in expDim.expDimRefs:
                    if expDimRef.measurementType in COUPLING_TYPES:
                        expDims.append(expDim)
                        break

        return expDims

    def changeWindow(self, windowPane):

        if windowPane is not self.windowPane:
            self.windowPane = windowPane
            self.updatePeakLists()

    def updateWindows(self, *object):

        panes = self.getWindows()
        index = 0
        names = []
        pane = self.windowPane

        if panes:
            names = [getWindowPaneName(wp) for wp in panes]

            if pane not in panes:
                pane = panes[0]

            index = panes.index(pane)

        if pane is not self.windowPane:
            self.windowPane = pane
            self.updatePeakLists()

        self.windowPulldown.setup(names, panes, index)

    def getPeakLists(self):

        peakLists = []

        if self.windowPane:
            for view in self.windowPane.spectrumWindowViews:
                try:
                    spectrum = view.analysisSpectrum.dataSource
                except:
                    continue

                if spectrum.peakLists:
                    peakList = spectrum.activePeakList

                    if not peakList:
                        peakList = spectrum.findFirstPeakList()

                    peakLists.append(peakList)

        return peakLists

    def updatePeakLists(self, peakList=None):

        objectList = self.getPeakLists()
        textMatrix = []
        spectra = {}

        for peakList0 in objectList:
            spectrum = peakList0.dataSource
            experiment = spectrum.experiment
            spectra[spectrum] = True
            refExperiment = experiment.refExperiment
            coupledDims = []
            if refExperiment:
                experimentType = refExperiment.name

                for expDim in experiment.expDims:
                    refExpDim = expDim.refExpDim
                    isotopes = {}

                    for refExpDimRef in refExpDim.refExpDimRefs:
                        for isotope in refExpDimRef.coupledIsotopeCodes:
                            isotopes[isotope] = True

                    if isotopes:
                        isoString = ','.join(isotopes)
                        coupledDims.append('%d (%s)' % (expDim.dim, isoString))

            else:
                experimentType = None

            if not coupledDims:
                coupledDims = None
            else:
                coupledDims = ' '.join(coupledDims)

            datum = [
                experiment.name, spectrum.name, peakList0.serial, coupledDims,
                experimentType
            ]
            textMatrix.append(datum)

        self.peakLists = objectList
        self.peakListMatrix.update(objectList=objectList,
                                   textMatrix=textMatrix)

        self.multiplet = None
        for spectrum in spectra.keys():
            multiplet = self.getSpectrumMultiplet(spectrum)

            if multiplet:
                self.multiplet = MULTIPLET_PEAK_DICT.get(multiplet)
                break

        self.updateMultipletButtons()
        self.updateAfter()

    def setMultiplet(self, pattern):

        for peakList in self.peakLists:
            spectrum = peakList.dataSource

            for name in MULTIPLET_PEAK_DICT.keys():
                if MULTIPLET_PEAK_DICT[name] == pattern:
                    setSpectrumMultipletPattern(spectrum, name)
                    break

        self.multiplet = pattern

    def setSpectrumMultiplet(self, spectrum, pattern):

        prevPattern = getSpectrumMultipletPattern(spectrum)

        if prevPattern and (prevPattern != pattern):
            if showOkCancel('Query',
                            'Really change multiplet pattern?',
                            parent=self):

                setSpectrumMultipletPattern(spectrum, pattern)

    def getSpectrumMultiplet(self, spectrum):

        name = getSpectrumMultipletPattern(spectrum)

        if not name:
            name = self.predictSpectrumMultiplet(spectrum)
            setSpectrumMultipletPattern(spectrum, name)

        return name

    def predictSpectrumMultiplet(self, spectrum):

        name = None

        refExperiment = spectrum.experiment.refExperiment
        if refExperiment:
            name = EXPT_MULTIPLETS.get(refExperiment.name)

        return name

    def updateMultipletButtons(self):

        import re

        labels = []
        objects = []
        colors = []
        selected = None

        if self.windowPane:
            coupledAxes = []
            spectra = {}
            for peakList in self.peakLists:
                spectra[peakList.dataSource] = True
                multiplet = self.getSpectrumMultiplet(peakList.dataSource)
                selected = MULTIPLET_PEAK_DICT[multiplet]

            for spectrum in spectra.keys():
                mapping = getDataDimAxisMapping(spectrum, self.windowPane)

                for axisLabel in ('x', 'y'):
                    dataDim = mapping[axisLabel]

                    for expDimRef in dataDim.expDim.expDimRefs:
                        if expDimRef.refExpDimRef and expDimRef.refExpDimRef.coupledIsotopes:
                            if axisLabel not in coupledAxes:
                                coupledAxes.append(axisLabel)
                                break

        else:
            coupledAxes = ['x', 'y']

        for name in MULTIPLET_PEAK_DICT.keys():

            pattern = MULTIPLET_PEAK_DICT[name]

            if type(pattern[0]) in (type(()), type([])):
                if 'y' not in coupledAxes:
                    continue

                if len(pattern[0]) > 1:  # x & y
                    if 'x' not in coupledAxes:
                        continue

                else:  # y only
                    if 'x' in coupledAxes:
                        continue

            else:  # x only
                if 'x' not in coupledAxes:
                    continue

                if 'y' in coupledAxes:
                    continue

            label = re.sub('\|', '\n', name)

            objects.append(pattern)
            labels.append(label)
            colors.append('#8080FF')

        self.multipletButtons.update(objects=objects,
                                     colors=colors,
                                     labels=labels)

        self.multipletButtons.setSelected([
            selected,
        ])

    def selectPeakList(self, object, row, col):

        self.peakList = object

        self.updateButtons()

    def selectCluster(self, object, row, col):

        self.cluster = object

        self.updateButtons()

    def assignCouplings(self):

        for cluster in self.clusterMatrix.currentObjects:
            assignPrimaryClusterCoupling(cluster)

    def assignAllCouplings(self):

        for cluster in self.clusters.keys():
            assignPrimaryClusterCoupling(cluster)

    def clusterSelectedPeaks(self):

        peaks0 = self.parent.currentPeaks
        peaks = []
        for peak in peaks0:
            if peak.peakList in self.peakLists:
                peaks.append(peak)

        if not peaks:
            showWarning('Cluster Failure',
                        'No peaks selected from active peak lists',
                        parent=self)
            return

        if not self.multiplet:
            showWarning('Cluster Failure',
                        'No multiplet pattern selected',
                        parent=self)
            return

        cluster = makeMultipletPeakCluster(peaks, self.multiplet,
                                           self.windowPane)

        if cluster:
            self.clusterMatrix.selectObject(cluster)

    def getActiveClusterCouplings(self):

        clusters = {}
        if self.peakLists:
            dims = {}

            for peakList in self.peakLists:
                experiment = peakList.dataSource.experiment

                for expDim in getCoupledExpDims(experiment):
                    dims[expDim.dim] = True

            dims = dims.keys()

            for cluster in getPeakListsPeakClusters(self.peakLists):
                values = []

                for dim in dims:
                    values.append(getClusterCoupling(cluster, dim))
                    # Allow Nones?

                clusters[cluster] = values

        self.clusters = clusters

        return clusters

    def updateButtons(self):

        pass

    def updateAfter(self, cluster=None):

        if self.waiting:
            return

        if cluster:
            for peak in cluster.peaks:
                if peak.peakList in self.peakLists:
                    self.waiting = True
                    self.after_idle(self.update)
                    break

        else:
            self.waiting = True
            self.after_idle(self.update)

    def update(self):

        clusters = self.getActiveClusterCouplings()

        textMatrix = []
        objectList = []

        headingList = [
            '#', 'Main\nAssignment', 'Num\nPeaks', 'Coupling\nAssignment'
        ]

        if clusters:
            cluster0 = clusters.keys()[0]
            i = 1

            for value in clusters[cluster0]:
                headingList.append('F%d Value\n(Hz)' % i)
                i += 1

        else:
            headingList.append('Value')

        for cluster in clusters.keys():

            couplingAssign = ','.join(getCouplingAnnotations(cluster))

            datum = [
                cluster.serial, cluster.annotation,
                len(cluster.peaks), couplingAssign
            ]

            values = clusters[cluster]
            for value in values:
                datum.append(value)

            textMatrix.append(datum)
            objectList.append(cluster)

        self.clusterMatrix.update(headingList=headingList,
                                  textMatrix=textMatrix,
                                  objectList=objectList)

        self.updateButtons()
        self.waiting = False

    def destroy(self):

        self.administerNotifiers(self.unregisterNotify)

        BasePopup.destroy(self)