You are here: Home V2 Software Software Tutorials Python API Course Example CCPN Graphical Interface

Example CCPN Graphical Interface

An example graphical interface that links to the CCPN data model

General Principles

This final section goes step-by-step through a real program with a graphical interface that has been written to extend CcpNmr Analysis. This will use some of the graphical widgets discussed previously, and will connect them to real manipulations of the CCPN data model, via the Python API. When undertaking such a tasks there are a few guiding principles to be mindful of:

Data Model Event Notification

In order to assist with the design of user interfaces, CCPN has an inbuilt system to give notification of when objects in the CCPN data model change. In this way the graphical interface can be kept up-to-date with the current state of the underlying data; an imperative for good design. The notification system is fairly easy to use; one simply makes a registration in the system that links a particular user-defined function (e.g. a visual update) with a data model call (e.g. deleting an object, setting its name etc.). With this system you can separate the functions within your program into those that change the state of the data and those that reflect the state graphically. Thus you rarely need to explicitly manage the links between the two.


Update Funnelling

This is

GUI Example: A RADAR reader


import os
from memops.gui.Entry import Entry
from memops.gui.FileSelect import FileType
from memops.gui.FileSelectPopup import FileSelectPopup
from memops.gui.Label import Label
from memops.gui.PulldownList import PulldownList
from memops.gui.ButtonList import ButtonList, UtilityButtonList
from memops.gui.Button import Button
from memops.gui.LabelFrame import LabelFrame
from memops.gui.MessageReporter import showWarning, showOkCancel, showYesNo, showInfo
from ccpnmr.analysis.popups.BasePopup import BasePopup
from ccpnmr.analysis.core.PeakBasic import pickPeak
from ccpnmr.analysis.core.ExperimentBasic import getDataDimIsotopes, getOnebondDataDims
from ccpnmr.analysis.core.ExperimentBasic import getThroughSpacePeakLists
from ccpnmr.analysis.core.Util import getAnalysisPeakList


TEMP_LIST_NAME = '__RadarSuperimposeTemp'


def radarSuperimposeMacro(argServer):
  popup = RadarSuperimposePopup(argServer.parent)


class RadarSuperimposePopup(BasePopup):

def __init__(self, parent, *args, **kw):
self.spectrum = None
self.dir = '.'

BasePopup.__init__(self, parent, *args, **kw)


  def body(self, guiFrame):

frame = LabelFrame(guiFrame, text='Options', grid=(0,0))

label = Label(frame, text='Destination Spectrum:', grid=(0,0))
self.specPulldown = PulldownList(frame, callback=self.changeSpec, grid=(0,1))

label = Label(frame, text='Peaklist File:', grid=(1,0))
self.peakListEntry = Entry(frame, text='', width=64, grid=(1,1))
button = Button(frame, command=self.importPeakFile,
grid=(1,2), text='Choose File')

label = Label(frame, text='Resonance file:', grid=(2,0))
self.resonanceFileEntry = Entry(frame, text='', width=64, grid=(2,1))
button = Button(frame, command=self.importResonanceFile,
grid=(2,2), text='Choose File')

texts = ['Make Peak List','Remove Peak List']
commands = [self.makePeakList, self.removePeakList]
buttons = UtilityButtonList(guiFrame, texts=texts,
commands=commands, grid=(1,0))


# Notifiers


  def updateSpecPulldown(self):

names = []
index = 0
spectra = []

peakLists = getThroughSpacePeakLists(self.project)
for peakList in peakLists:
spectrum = peakList.dataSource

if spectrum not in spectra:
names.append('%s:%s' % (,

if spectra:
if self.spectrum not in spectra:
self.spectrum = spectra[0]

index = spectra.index(self.spectrum)

self.spectrum = None

self.specPulldown.setup(names, spectra, index)


  def changeSpec(self, spectrum):

if spectrum is not self.spectrum:
self.spectrum = spectrum


  def importResonanceFile(self):

fileTypes = [ FileType('XEasy', ['*.resonances']), FileType('All', ['*']) ]
fileSelectPopup = FileSelectPopup(self, file_types=fileTypes, directory=self.dir,
title='Import Xeasy Resonance file', dismiss_text='Cancel',
selected_file_must_exist=True, multiSelect=False,)

file = fileSelectPopup.getFile()

self.dir = fileSelectPopup.getDirectory()


  def importPeakFile(self):

fileTypes = [ FileType('XEasy', ['*.peaks']), FileType('All', ['*'])]
fileSelectPopup = FileSelectPopup(self, file_types=fileTypes, directory=self.dir,
title='Import XEasy peak file', dismiss_text='Cancel',
selected_file_must_exist=True, multiSelect=False,)

file = fileSelectPopup.getFile()

self.dir = fileSelectPopup.getDirectory()


  def warning(self, msg, title='Failure'):

showWarning(title, msg, parent=self)


  def makePeakList(self):

if not self.spectrum:
self.warning('No spectrum')

peakFile = self.peakListEntry.get()
resonanceFile = self.resonanceFileEntry.get()

if not peakFile:
self.warning('No peak file specified')

if not resonanceFile:
self.warning('No resonance file specified')

if not os.path.exists(peakFile):
self.warning('Specified peak file does not exist')

if not os.path.exists(resonanceFile):
self.warning('Specified resonance file does not exist',)

if not os.access(peakFile, READABLE):
self.warning('Specified peak file not readable')

if not os.access(resonanceFile, READABLE):
self.warning('Specified resonance file not readable')


    if peakList:
peaks = peakList.peaks

if peaks:
msg = 'Destination peak list already contains %d peaks.' % (len(peaks))
msg += ' Remove these first?'
if showYesNo('Query', msg, parent=self):
for peak in peaks:

peakList = self.spectrum.newPeakList(isSimulated=True,
analysisPeakList = getAnalysisPeakList(peakList)
analysisPeakList.symbolStyle = '+'
analysisPeakList.symbolColor = '#FF0000'
analysisPeakList.textColor = '#BB0000'


resonanceDict = readResonanceData(resonanceFile)
nDim, dTypes, peakData = readPeakData(peakFile)


    # Work out dim mapping

xIsotope = None
xAtom = None
for atom, isotope in (('N', '15N'), ('C', '13C')):
if atom in dTypes:
xIsotope = isotope
xAtom = atom

dataDims = self.spectrum.sortedDataDims()
boundDims = {}
for dataDim0, dataDim1 in getOnebondDataDims(self.spectrum):
boundDims[dataDim0] = True
boundDims[dataDim1] = True

dimCols = []
if xAtom:
for dataDim in dataDims:
isotopes = getDataDimIsotopes(dataDim)

if '1H' in isotopes:
if boundDims.get(dataDim):
col = dTypes.index('H'+xAtom)
col = dTypes.index('H')

elif xIsotope in isotopes:
col = dTypes.index(xAtom)


# 2D NOESY - symmetric, can flip if wrong
dimCols = [0,1]


    # Write peaks
nDim = len(dataDims)
dims = range(nDim)

c = 0
for num, ppms, inten, assign, dist in peakData:

position = [None] * nDim
for i in dims:
position[i] = ppms[dimCols[i]]

peak = pickPeak(peakList, position, unit='ppm')
c += 1

labels = ['-'] * nDim
for i, peakDim in enumerate(peak.sortedPeakDims()):
j = assign[dimCols[i]]

if j: # Not zero either
resonanceInfo = resonanceDict.get(j)

if resonanceInfo:
resNum, atom, ppm, sd = resonanceInfo
labels[i] = '%d%s' % (resNum, atom)

peak.annotation = dist + ':' + ','.join(labels)

showInfo('Done', 'Made %d peaks' % c, parent=self)


  def removePeakList(self):

if self.spectrum:
peakList = self.spectrum.findFirstPeakList(name=TEMP_LIST_NAME,

if not peakList:
self.warning('No temporary peak list found in this spectrum')

name = '%s:%s' % (,
msg = 'Really remove temporary peak list in spectrum %s?' % name

if showOkCancel('Confirm', msg, parent=self):


def readResonanceData(resonanceFile):
resonanceDict = {}
fileObj = open(resonanceFile, 'r')
line = fileObj.readline()

while line:
array = line.split()

if (len(array) == 5) and (array[0][0] != '#'):
num, ppm, sd, atom, resNum = array
resonanceDict[int(num)] = (int(resNum), atom, ppm, sd)

line = fileObj.readline()

return resonanceDict


def readPeakData(peakFile):

nDim = 3
dTypes = [None] * 4
peakData = []
fileObj = open(peakFile, 'r')
line = fileObj.readline()
while line:
array = line.split()

if array:
if array[0][0] == '#':
if 'dimensions' in line.lower():
nDim = int(array[-1])

elif 'INAME' in line.upper():
dTypes[int(array[-2])-1] = array[-1]

elif len(array) == 2*nDim+11:
num = int(array[0])
ppms = [float(x) for x in array[1:nDim+1]]
inten = [float(x) for x in array[nDim+3:nDim+4]]
assign = [int(x) for x in array[nDim+7:nDim+nDim+7]]

dist = array[-1]
peakData.append((num, ppms, inten, assign, dist))

line = fileObj.readline()

while dTypes[-1] is None:

return nDim, dTypes, peakData