class SequenceShiftPredictPopup(BasePopup): """ **Predict Protein Shifts from Sequence** This popup window is designed to allow the prediction of chemical shifts for a protein chain from the sequence (so with no structural information), using the (external) program CamCoil. The Options to select are the Chain for which the prediction is made, and the prediction type and the pH used for the prediction, and also the Shift List, which is not used for the prediction but is used for the comparison with the prediction. CamCoil has two variations, one for the prediction of random coil chemical shifts and one for prediction of protein loops chemical shifts. The Chemical Shift Predictions table lists the atoms in the chain. For each atom the data listed is the residue number, residue type, atom name, first shift found for that atom in the chosen shiftList, chemical shift predicted by CamCoil, and the difference between the actual shift and the predicted shift (if both exist). To run the prediction click on the "Run CamCoil Prediction!" button. This does not store any predicted shifts in the project. **Caveats & Tips** **References** The CamCoil programme: http://www-vendruscolo.ch.cam.ac.uk/camcoil.php *A. De Simone, A. Cavalli, S-T. D. Hsu, W. Vranken and M. Vendruscolo Accurate random coil chemical shifts from an analysis of loop regions in native states of proteins. J. Am. Chem. Soc. 131(45):16332-3 """ def __init__(self, parent, *args, **kw): self.chain = None self.shiftList = None self.predictionDict = {} BasePopup.__init__( self, parent=parent, title='Data Analysis : Predict Shifts from Sequence') def body(self, guiFrame): self.geometry('700x500') guiFrame.expandGrid(1, 0) row = 0 # TOP LEFT FRAME frame = LabelFrame(guiFrame, text='Options') frame.grid(row=row, column=0, sticky='nsew') frame.columnconfigure(7, 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='Type') label.grid(row=0, column=4, sticky='w') self.scriptPulldown = PulldownList( frame, texts=SCRIPT_TEXTS, callback=self.changeScript, tipText='Select the algorithm script for this chain') self.scriptPulldown.grid(row=0, column=5, sticky='w') self.pHLabel = Label(frame, text='pH') self.pHLabel.grid(row=0, column=6, sticky='w') self.pHPulldown = PulldownList( frame, texts=SCRIPT_PHS, tipText='Select the pH to make the prediction for') self.pHPulldown.grid(row=0, column=7, sticky='w') row += 1 # BOTTOM LEFT FRAME frame = LabelFrame(guiFrame, text='Chemical Shift Predictions') frame.grid(row=row, column=0, sticky='nsew') frame.grid_columnconfigure(0, weight=1) frame.grid_rowconfigure(0, weight=1) tipTexts = [ 'Residue number in chain', 'Residue type code', 'Atom name', 'Actual shift (first one it finds for atom in chosen shiftList)', 'CamCoil predicted shift', 'Predicted - Actual' ] headingList = [ 'Res\nNum', 'Res\nType', 'Atom\nName', 'Actual\nShift', 'Predicted\nShift', 'Difference' ] n = len(headingList) editWidgets = n * [None] editGetCallbacks = n * [None] editSetCallbacks = n * [None] self.predictionMatrix = ScrolledMatrix( frame, headingList=headingList, tipTexts=tipTexts, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks) self.predictionMatrix.grid(row=0, column=0, sticky='nsew') row += 1 tipTexts = [ 'Run the CamCoil method to predict chemical shifts from sequence' ] texts = ['Run CamCoil Prediction!'] commands = [self.runCamCoil] self.buttonList = createDismissHelpButtonList(guiFrame, texts=texts, commands=commands, help_url=self.help_url, expands=True, tipTexts=tipTexts) self.buttonList.grid(row=row, column=0) self.update() self.notify(self.registerNotify) def destroy(self): self.notify(self.unregisterNotify) BasePopup.destroy(self) def notify(self, notifyfunc): for func in ('__init__', 'delete'): notifyfunc(self.updateChainPulldown, 'ccp.molecule.MolSystem.Chain', func) for func in ('setValue', ): notifyfunc(self.updatePredictionMatrixAfter, 'ccp.nmr.Nmr.Shift', func) def update(self): self.updateShiftListPulldown() self.updateChainPulldown() self.updatePredictionMatrixAfter() def runCamCoil(self): chain = self.chain script = self.scriptPulldown.getText() if script == LFP_SCRIPT: pH = '' else: pH = self.pHPulldown.getText() if not chain: showError('Cannot Run CamCoil', 'Please specify a chain.', parent=self) return self.predictionDict[chain] = runCamCoil(chain, pH=pH, script=script) self.updatePredictionMatrix() def updatePredictionMatrixAfter(self, index=None, text=None): self.after_idle(self.updatePredictionMatrix) def updatePredictionMatrix(self): objectList = [] textMatrix = [] chain = self.chain shiftList = self.shiftList if chain: atomShiftDict = self.predictionDict.get(chain, {}) for residue in chain.sortedResidues(): for atom in residue.sortedAtoms(): currShift = shiftList and findFirstAtomShiftInShiftList( atom, shiftList) value = currShift and currShift.value predShift = atomShiftDict.get(atom) if currShift and predShift: delta = predShift - value else: delta = None data = [ residue.seqCode, residue.ccpCode, atom.name, value, predShift, delta ] textMatrix.append(data) objectList.append(atom) self.predictionMatrix.update(textMatrix=textMatrix, objectList=objectList) def changeChain(self, chain): if chain is not self.chain: self.chain = chain self.updatePredictionMatrixAfter() def changeShiftList(self, shiftList): if shiftList is not self.shiftList: self.shiftList = shiftList self.updatePredictionMatrixAfter() def changeScript(self, script): if script == LFP_SCRIPT: self.pHLabel.grid_forget() self.pHPulldown.grid_forget() else: self.pHLabel.grid(row=0, column=6, sticky='w') self.pHPulldown.grid(row=0, column=7, sticky='w') 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)
class EditPeakFindParamsPopup(BasePopup): """ ** Peak Settings and Non-Interactive Peak Finding ** The purpose of this dialog is to allow the user to select settings for finding and integrating peaks, and also to be able to find peaks in an arbitrary region that is specified in a table rather than via a spectrum window. ** Find Parameters tab ** This can be used to specify how peak finding works. First of all, you can search for just positive peaks, just negative peaks or both, and the default is that it is just positive peaks. However, this is further filtered by what the contour levels are. If there are no positive contour levels for a given spectrum then positive peaks are not found even if this dialog says they can be, and similarly if there are no negative contour levels for a given spectrum then negative peaks are not found even if this dialog says they can be. The peak finding algorithm looks for local extrema (maximum for positive peaks and minima for negative peaks). But on a grid there are various ways to define what you mean by an extremum. Suppose you are trying to determine if point p is a maximum (similar considerations apply for minimum). You would want the intensity at all nearby points to be less than or equal to the intensity at p. You can just check points that are just +- one point from p in each dimension, or you can also check "diagonal" points. For example, if you are looking at point p = (x, y) in 2D, then the former would mean checking the four points (x-1, y), (x+1, y) (x, y-1) and (x, y+1), whereas for the latter you would also have to check (x-1, y-1), (x-1, y+1), (x+1, y-1) and (x+1, y+1). In N dimensions the "diagonal" method involves checking 3^N-1 points whereas the "non-diagonal" method involves checking only 2N points. In general the "non-diagonal" method is probably the one to use, and it is the default. Peaks are only found above (for positive peaks) or below (for negative peaks) some threshold. By default this is determined by the contour level for the spectrum. For positive peaks the threshold is the minimum positive contour level, and for negative peaks the threshold is the maximum negative contour level. However these levels can be scaled up (or down) using the "Scale relative to contour levels" option (default value 1). For example, if you have drawn the contour levels low to show a bit of noise, but do not want the noise picked as peaks, then you could select a scale of 2 (or whatever) to increase the threshold. The "Exclusion buffer around peaks" is so that in crowded regions you do not get too many peaks near one location. By default the exclusion buffer is 1 point in each dimension, but this can be increased to make the algorithm find fewer peaks. By default the peak finding only looks at the orthogonal region that is displayed in the given window where peak finding is taking place. Sometimes it looks like a peak should be found because in x, y you can see an extremum, but unless it is also an extremum in the orthogonal dimensions it is not picked. You can widen out the points being examined in the orthogonal dimensions by using the "Extra thickness in orthogonal dims" option, which is specified in points. The "Minimum drop factor" is by what factor the intensity needs to drop from its extreme value for there to be considered to be a peak. This could help remove sinc wiggle peaks, for example. The default is that the drop factor is 0, which in effect means that there is no condition. The "Volume method" is what is used to estimate the volume of peaks that are found. The default is "box sum", which just looks at a fixed size box around the peak centre and sums the intensities in that. The size of the box is set in the table in the Spectrum Widths tab. The "truncated box sum" is the same as "box sum" except that the summing stops in a given direction when (if) the intensities start increasing. The "parabolic" fit fits a quadratic equation in each dimension to the intensity at the peak centre and ad +- 1 points and then uses the equivalent Gaussian fit to estimate the volume. ** Spectrum Widths ** This can be used to specify minimum linewidths (in Hz) for there to be considered a peak to exist in the peak finding algorithm. It is also where the Boxwidth for each dimension in each spectrum is specified. ** Diagonal Exclusions ** This can be used to exclude peaks from being found in regions near the diagonal (so in homonuclear experiments). The exclusion region is specified in ppm and is independent of spectrum. ** Region Peak Find ** This can be used to find peaks non-interactively (so not having to control shift drag inside a spectrum window). The region being analysed is specified in the table. There are two types of conditions that can be specified, "include" for regions that should be included and "exclude" for regions that should be excluded. The regions are specified in ppm. The "Whole Region" button will set the selected row in the table to be the entire fundamental region of the spectrum. The "Add Region" button adds an extra row to the table, and the "Delete Region" button removes the selected row. The "Adjust Params" button goes to the Find Parameters tab. The "Find Peaks!" button does the peak finding. """ def __init__(self, parent, *args, **kw): self.spectrum = None BasePopup.__init__(self, parent=parent, title='Peak : Peak Finding', **kw) def body(self, guiFrame): self.geometry('600x350') guiFrame.expandGrid(0, 0) tipTexts = ['', '', '', ''] options = [ 'Find Parameters', 'Spectrum Widths', 'Diagonal Exclusions', 'Region Peak Find' ] tabbedFrame = TabbedFrame(guiFrame, options=options, grid=(0, 0)) frameA, frameB, frameC, frameD = tabbedFrame.frames self.tabbedFrame = tabbedFrame # Find Params frameA.expandGrid(2, 0) row = 0 label = LabelFrame(frameA, text='Extrema to search for:', grid=(row, 0), gridSpan=(1, 2)) label.expandGrid(0, 1) entries = ['positive and negative', 'positive only', 'negative only'] tipTexts = [ 'Sets whether peak picking within spectra find intensity maxima, minima or both maxima and minima', ] self.extrema_buttons = RadioButtons(label, entries=entries, select_callback=self.apply, direction='horizontal', grid=(0, 0), tipTexts=tipTexts) row += 1 label = LabelFrame(frameA, text='Nearby points to check:', grid=(row, 0), gridSpan=(1, 2)) label.expandGrid(None, 1) entries = ['+-1 in at most one dim', '+-1 allowed in any dim'] tipTexts = [ 'Sets how permissive the peak picking in when searching for intensity extrema; by adding extra points to the selected search region', ] self.adjacent_buttons = RadioButtons(label, entries=entries, select_callback=self.apply, direction='horizontal', grid=(0, 0), tipTexts=tipTexts) row += 1 labelFrame = LabelFrame(frameA, text='Other parameters:', grid=(row, 0), gridSpan=(1, 2)) labelFrame.expandGrid(5, 2) frow = 0 label = Label(labelFrame, text='Scale relative to contour levels:', grid=(frow, 0), sticky='e') tipText = 'Threshold above which peaks are picked, relative to the lowest displayed contour; 1.0 means picking exactly what is visible' self.scale_entry = FloatEntry(labelFrame, grid=(frow, 1), tipText=tipText, returnCallback=self.apply, width=10) self.scale_entry.bind('<Leave>', self.apply, '+') frow += 1 label = Label(labelFrame, text='Exclusion buffer around peaks (in points):', grid=(frow, 0), sticky='e') tipText = 'The size of the no-pick region, in data points, around existing picked peaks; eliminates duplicate picking' self.buffer_entry = IntEntry(labelFrame, returnCallback=self.apply, grid=(frow, 1), width=10, tipText=tipText) self.buffer_entry.bind('<Leave>', self.apply, '+') frow += 1 label = Label(labelFrame, text='Extra thickness in orthogonal dims (in points):', grid=(frow, 0), sticky='e') tipText = 'Sets whether to consider any additional planes (Z dimension) when calculating peak volume integrals' self.thickness_entry = IntEntry(labelFrame, returnCallback=self.apply, width=10, grid=(frow, 1), tipText=tipText) self.thickness_entry.bind('<Leave>', self.apply, '+') frow += 1 label = Label(labelFrame, text='Minimum drop factor (0.0-1.0):', grid=(frow, 0), sticky='e') tipText = '' self.drop_entry = FloatEntry(labelFrame, returnCallback=self.apply, width=10, grid=(frow, 1), tipText=tipText) self.drop_entry.bind('<Leave>', self.apply, '+') frow += 1 label = Label(labelFrame, text='Volume method:', grid=(frow, 0), sticky='e') tipText = 'Selects which method to use to calculate peak volume integrals when peaks are picked; box sizes are specified in "Spectrum Widths"' self.method_menu = PulldownList(labelFrame, texts=PeakBasic.PEAK_VOLUME_METHODS, grid=(frow, 1), callback=self.apply, tipText=tipText) # Spectrum widths frameB.expandGrid(1, 1) label = Label(frameB, text='Spectrum: ') label.grid(row=0, column=0, sticky='e') tipText = 'The spectrum which determines the widths being shown' self.expt_spectrum = PulldownList(frameB, tipText=tipText, callback=self.setSpectrumProperties) self.expt_spectrum.grid(row=0, column=1, sticky='w') self.editLinewidthEntry = FloatEntry(self, text='', returnCallback=self.setLinewidth, width=10) self.editBoxwidthEntry = FloatEntry(self, text='', returnCallback=self.setBoxwidth, width=10) tipTexts = [ 'The number of the spectrum dimension to which the settings apply', 'The nuclear isotope measures in the spectrum dimension', 'The smallest value for the linewidth of a peak for it to be picked', 'The size of the spectrum region to perform the volume integral over' ] headingList = [ 'Dimension', 'Isotope', 'Minimum Linewidth (Hz)', 'Boxwidth' ] editSetCallbacks = [None, None, self.setLinewidth, self.setBoxwidth] editGetCallbacks = [None, None, self.getLinewidth, self.getBoxwidth] editWidgets = [ None, None, self.editLinewidthEntry, self.editBoxwidthEntry ] self.spectrumMatrix = ScrolledMatrix(frameB, initialRows=6, editSetCallbacks=editSetCallbacks, editGetCallbacks=editGetCallbacks, editWidgets=editWidgets, headingList=headingList, callback=self.selectCell, tipTexts=tipTexts) self.spectrumMatrix.grid(row=1, column=0, columnspan=2, sticky='nsew') # Diagonal Exclusions frameC.expandGrid(0, 0) tipTexts = [ 'The isotope as measures on the axis of a spectrum window', 'The distance from the homonuclear diagonal line within which no peak picking can occur' ] self.exclusionEntry = FloatEntry(self, text='', returnCallback=self.setExclusion, width=10) headingList = ['Isotope', 'Diagonal Exclusion (ppm)'] editSetCallbacks = [None, self.setExclusion] editGetCallbacks = [None, self.getExclusion] editWidgets = [None, self.exclusionEntry] self.isotopeMatrix = ScrolledMatrix(frameC, editSetCallbacks=editSetCallbacks, editGetCallbacks=editGetCallbacks, editWidgets=editWidgets, headingList=headingList, grid=(0, 0), tipTexts=tipTexts) # Region peak find self.regionFindPeakList = None self.regionCondition = None self.regionConditions = [] self.regionCol = 1 row = 0 label = Label(frameD, text='Peak List: ', grid=(0, 0)) tipText = 'Selects which peak list to perform region-wide peak picking for' self.regionPeakListPulldown = PulldownList( frameD, callback=self.changeRegionPeakList, grid=(0, 1), tipText=tipText) row += 1 frameD.expandGrid(row, 1) self.regionEntry = FloatEntry(self, text='', returnCallback=self.setRegion, width=10) self.conditionMenu = PulldownList(self, texts=('include', 'exclude'), callback=self.setCondition) tipTexts = [ 'Whether to include or exclude the states region from region-wide peak picking', ] headingList = ['Condition'] editSetCallbacks = [None] editGetCallbacks = [None] editWidgets = [self.conditionMenu] self.regionFindMatrix = ScrolledMatrix( frameD, headingList=headingList, callback=self.selectRegionCell, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, grid=(row, 0), gridSpan=(1, 2)) row += 1 tipTexts = [ 'Sets the currently selected region row to cover the whole spectrum', 'Add a new region row, which may them be set for exclusion or inclusion when peak picking large areas', 'Remove the selected region specification', 'Go to the panel for setting the parameters that control how peaks extrema are picked', 'Using the stated regions and parameters, perform region-wide peak picking' ] texts = [ 'Whole Region', 'Add Region', 'Delete Region', 'Adjust Params', 'Find Peaks!' ] commands = [ self.wholeRegion, self.addCondition, self.deleteCondition, self.adjustParams, self.regionFindPeaks ] buttons = ButtonList(frameD, texts=texts, commands=commands, grid=(row, 0), gridSpan=(1, 2), tipTexts=tipTexts) buttons.buttons[4].config(bg='#B0FFB0') utilButtons = UtilityButtonList(tabbedFrame.sideFrame, grid=(0, 0), helpUrl=self.help_url, sticky='e') self.dataDim = None self.setParamsEntries() self.updateSpectrum() self.setIsotopeProperties() self.updateRegionPeakLists() self.administerNotifiers(self.registerNotify) def administerNotifiers(self, notifyFunc): # Many more needed here, esp on the AnalysisProject prams for func in ('__init__', 'delete', 'setName'): notifyFunc(self.updateRegionPeakLists, 'ccp.nmr.Nmr.DataSource', func) notifyFunc(self.updateRegionPeakLists, 'ccp.nmr.Nmr.Experiment', func) for func in ('__init__', 'delete'): notifyFunc(self.updateRegionPeakLists, 'ccp.nmr.Nmr.PeakList', func) for clazz in ('Experiment', 'DataSource'): for func in ('__init__', 'delete', 'setName'): notifyFunc(self.updateSpectrumTable, 'ccp.nmr.Nmr.%s' % clazz, func) for func in ('setPeakFindBoxWidth', 'setPeakFindMinLineWidth'): notifyFunc(self.updateSpectrumTable, 'ccpnmr.Analysis.AnalysisDataDim', func) def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self) def updateSpectrum(self, spectrum=None): if not spectrum: spectrum = self.spectrum spectra = self.parent.getSpectra() if spectra: if spectrum not in spectra: spectrum = spectra[0] index = spectra.index(spectrum) names = ['%s:%s' % (x.experiment.name, x.name) for x in spectra] else: index = 0 names = [] self.expt_spectrum.setup(names, spectra, index) self.setSpectrumProperties(spectrum) def updateNotifier(self, *extra): self.updateSpectrum() def setLinewidth(self, *event): value = self.editLinewidthEntry.get() if value is not None: PeakFindParams.setPeakFindMinLinewidth(self.dataDim, value) self.setSpectrumProperties(self.dataDim.dataSource) def getLinewidth(self, dataDim): if dataDim: self.editLinewidthEntry.set( PeakFindParams.getPeakFindMinLinewidth(self.dataDim)) def setBoxwidth(self, *event): value = self.editBoxwidthEntry.get() if value is not None: PeakFindParams.setPeakFindBoxwidth(self.dataDim, value) self.setSpectrumProperties(self.dataDim.dataSource) def getBoxwidth(self, dataDim): if dataDim: self.editBoxwidthEntry.set( PeakFindParams.getPeakFindBoxwidth(self.dataDim)) def selectCell(self, object, row, col): self.dataDim = object def setExclusion(self, *extra): isotope = self.isotopeMatrix.currentObject if not isotope: return value = self.exclusionEntry.get() if value is not None: setIsotopeExclusion(isotope, value) self.setIsotopeProperties() def getExclusion(self, isotope): value = getIsotopeExclusion(isotope) self.exclusionEntry.set(value) def setParamsEntries(self): project = self.project params = PeakFindParams.getPeakFindParams(project) self.scale_entry.set(params['scale']) self.buffer_entry.set(params['buffer']) self.thickness_entry.set(params['thickness']) self.drop_entry.set(params['drop']) volumeMethod = params['volumeMethod'] if volumeMethod == 'parabolic fit': volumeMethod = PeakBasic.PEAK_VOLUME_METHODS[0] self.method_menu.set(params['volumeMethod']) if (params['nonadjacent']): n = 1 else: n = 0 self.adjacent_buttons.setIndex(n) have_high = params['haveHigh'] have_low = params['haveLow'] if (have_high and have_low): n = 0 elif (have_high): n = 1 else: n = 2 self.extrema_buttons.setIndex(n) def apply(self, *extra): params = {} params['scale'] = self.scale_entry.get() params['buffer'] = self.buffer_entry.get() params['thickness'] = self.thickness_entry.get() params['drop'] = self.drop_entry.get() params['volumeMethod'] = self.method_menu.getText() n = self.adjacent_buttons.getIndex() if (n == 0): nonadjacent = False else: nonadjacent = True params['nonadjacent'] = nonadjacent n = self.extrema_buttons.getIndex() if (n == 0): have_high = True have_low = True elif (n == 1): have_high = True have_low = False elif (n == 2): have_high = False have_low = True params['haveHigh'] = have_high params['haveLow'] = have_low project = self.project try: PeakFindParams.setPeakFindParams(project, params) except Implementation.ApiError, e: showError('Parameter error', e.error_msg, parent=self)
class CreateContourFilePopup(BasePopup): """ **Create Contour Files for Project** The purpose of this dialog is to allow the user to create new contour files for the project. Not only the spectrum has to be specified, but also the two dimensions (X, Y) that are going to be contoured. The table specifies the conditions that specify the region being contoured, in terms of what is included and excluded. For now, only one condition (which is an "include" condition) is allowed. This also means that the "Add Condition" and "Delete Condition" buttons are disabled. The contour directory is specified by the program, using the project directory but the file name can be specified by the user. The contour levels used are the ones that are current for the spectrum, as set in the `Contour Levels dialog`_. See also: `Spectrum Contour Files`_, `Add Existing Contour Files`_. .. _`Contour Levels dialog`: EditContourLevelsPopup.html .. _`Spectrum Contour Files`: EditContourFilesPopup.html .. _`Add Existing Contour Files`: AddContourFilePopup.html """ def __init__(self, parent, *args, **kw): self.spectrumConditions = [] self.col = None self.spectrum = None BasePopup.__init__(self, parent=parent, title='New Contour File', **kw) def body(self, master): self.geometry('650x200') master.grid_columnconfigure(1, weight=1) row = 0 frame = Frame(master, grid=(row, 0), gridSpan=(1,2)) label = Label(frame, text='Spectrum: ', grid=(row, 0)) tipText = 'Selects the experiment and spectrum for which to make a contour file' self.expt_spectrum = PulldownList(frame, grid=(row, 1), tipText=tipText, callback=self.update) label = Label(frame, text=' (X-dim, Y-dim): ', grid=(row, 2)) tipText = 'Selects which dimensions (projection) of the spectrum form the X and Y axes of the contour file' self.dim_menu = PulldownList(frame, grid=(row, 3), tipText=tipText, callback=self.updateFile) row = row + 1 master.grid_rowconfigure(row, weight=1) #### for now not editable because only allow one include region ###self.conditionMenu = PulldownMenu(self, entries=('include', 'exclude'), ### callback=self.selectedCondition, do_initial_callback=False) self.regionEntry = FloatEntry(self, text='', returnCallback=self.setRegion, width=10) tipTexts = ['Whether to include or exclude the region in the contour file', '', '', ''] headingList = ['Condition','','','',''] editSetCallbacks = [None] editGetCallbacks = [None] ###editWidgets = [self.conditionMenu] editWidgets = [None] self.conditionTable = ScrolledMatrix(master, initialRows=6, grid=(row, 0), gridSpan=(1,2), tipTexts=tipTexts, headingList=headingList, callback=self.selectCell, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks) # TBD: make directory editable row = row + 1 label = Label(master, text='Contour dir: ', grid=(row, 0), sticky='e') tipText = 'The directory location on disk into which contour files are saved' self.dir_label = Label(master, text='', grid=(row, 1), tipText=tipText) row = row + 1 label = Label(master, text='File name: ', grid=(row, 0), sticky='e') tipText = 'Sets the name of the contour file to save to disk' self.file_entry = Entry(master, grid=(row, 1), tipText=tipText) ##row = row + 1 ##label = Label(master, text='Contour levels: ') ##label.grid(row=row, column=0, sticky='e') ##self.levels_entry = FloatEntry(master, isArray=True, returnCallback=self.saveLevels) ##self.levels_entry.grid(row=row, column=1, sticky='ew') row = row + 1 tipTexts = ['Add a new contour region for including in or excluding from the contour file ', 'Remove the selected contour region from the table', 'Make the specified contour file using the input settings & regions'] texts = [ 'Add Condition', 'Delete Condition', 'Contour and Save' ] commands = [ self.addCondition, self.deleteCondition, self.contourAndSaveSpectrum ] self.buttons = UtilityButtonList(master, texts=texts, commands=commands, doClone=False, helpUrl=self.help_url, grid=(row, 0), gridSpan=(1,2), tipTexts=tipTexts) ### TBD disabled for now for n in range(2): self.buttons.buttons[n].config(state='disabled') self.curateNotifiers(self.registerNotify) self.updateSpectrum() self.updateDimMenu() def destroy(self): self.curateNotifiers(self.unregisterNotify) BasePopup.destroy(self) def curateNotifiers(self, notifyFunc): for clazz in ('Experiment', 'DataSource'): for func in ('__init__', 'delete', 'setName'): notifyFunc(self.updateNotifier, 'ccp.nmr.Nmr.%s' % clazz, func) def update(self, spectrum): if spectrum is self.spectrum: return self.spectrum = spectrum self.updateDimMenu() self.updateConditionTable() self.updateFile() ##self.updateLevels() def updateSpectrum(self, spectrum=None): if not spectrum: spectrum = self.spectrum spectra = self.parent.getSpectra() if spectra: if spectrum not in spectra: spectrum = spectra[0] index = spectra.index(spectrum) names = ['%s:%s' % (x.experiment.name, x.name) for x in spectra] else: index = 0 names = [] self.expt_spectrum.setup(names, spectra, index) self.update(spectrum) def updateNotifier(self, *extra): self.updateSpectrum() def updateFile(self, *extra): spectrum = self.spectrum if not spectrum: return analysisSpectrum = spectrum.analysisSpectrum if not analysisSpectrum: return dims = self.dim_menu.getText() dims = [int(dim) for dim in dims.split(', ')] (xdim, ydim) = dims storedContour = analysisSpectrum.findFirstStoredContour(dims=dims) if not storedContour: storedContour = analysisSpectrum.findFirstStoredContour() if storedContour: fileName = storedContour.path else: fileName = '%s_%s_%d_%d.cnt' % (spectrum.experiment.name, spectrum.name, xdim, ydim) path = self.getContourDir() self.dir_label.set(path) self.file_entry.set(fileName) def getContourDir(self): spectrum = self.spectrum if not spectrum: return '' analysisSpectrum = spectrum.analysisSpectrum if not analysisSpectrum: return '' url = analysisSpectrum.contourDir if url: # should be the case since set in Analysis.py path = url.dataLocation else: path = '' return path """ def saveFile(self, *extra): spectrum = self.spectrum if not spectrum: return analysisSpectrum = spectrum.analysisSpectrum if not analysisSpectrum: return fileName = self.file_entry.get() if fileName: application = self.project.application application.setValue(spectrum, keyword='contourFileName', value=fileName) """ def updateDimMenu(self): entries = [] spectrum = self.spectrum if spectrum: ndim = spectrum.numDim for xdim in range(1, ndim): dataDim = spectrum.findFirstDataDim(dim=xdim) if dataDim.className != 'SampledDataDim': for ydim in range(xdim+1, ndim+1): dataDim = spectrum.findFirstDataDim(dim=ydim) if dataDim.className != 'SampledDataDim': entries.append('%d, %d' % (xdim, ydim)) self.dim_menu.setup(entries, objects=None, index=0) def updateConditionTable(self, *extra): spectrum = self.spectrum if spectrum: ndim = spectrum.numDim else: ndim = 0 tipTexts = ['Whether to include or exclude the region in the contour file',] headingList = ['Condition'] ###editWidgets = [self.conditionMenu] + 2*ndim*[self.regionEntry] ###editGetCallbacks = [self.getCondition] ###editSetCallbacks = [self.setCondition] editWidgets = [None] + 2*ndim*[self.regionEntry] editGetCallbacks = [None] editSetCallbacks = [None] for i in range(ndim): dim = i + 1 headingList.extend(['Dim %d Min' % dim, 'Dim %d Max' % dim]) editGetCallbacks.append(lambda obj, i=i: self.getRegionMin(obj, i)) editGetCallbacks.append(lambda obj, i=i: self.getRegionMax(obj, i)) editSetCallbacks.append(lambda obj, i=i: self.setRegionMin(obj, i)) editSetCallbacks.append(lambda obj, i=i: self.setRegionMax(obj, i)) tipTexts.append('Lower bound of dimension %d region to include or exclude' % dim) tipTexts.append('Upper bound of dimension %d region to include or exclude' % dim) objectList = [] textMatrix = [] self.spectrumConditions = self.getSpectrumConditions() for spectrumCondition in self.spectrumConditions: (condition, region) = spectrumCondition objectList.append(spectrumCondition) textRow = [condition] for i in range(ndim): r = region[i] if r: rMin = r[0] rMax = r[1] else: rMin = rMax = None textRow.append(rMin) textRow.append(rMax) textMatrix.append(textRow) self.conditionTable.update(objectList=objectList, textMatrix=textMatrix, headingList=headingList, editSetCallbacks=editSetCallbacks, editGetCallbacks=editGetCallbacks, editWidgets=editWidgets) """ def updateLevels(self): spectrum = self.getSpectrum() if spectrum: analysisSpectrum = spectrum.analysisSpectrum levels = analysisSpectrum.posLevels + analysisSpectrum.negLevels scale = spectrum.scale / self.analysisProject.globalContourScale levels = [ level/scale for level in levels ] else: levels = [] self.levels_entry.set(levels) def saveLevels(self): spectrum = self.spectrum if not spectrum: return """ def getRegionMin(self, spectrumCondition, dim): #print 'getRegionMin' (condition, region) = spectrumCondition self.regionEntry.set(region[dim][0]) def setRegionMin(self, spectrumCondition, dim): #print 'setRegionMin' (condition, region) = spectrumCondition (r0, r1) = region[dim] r = self.regionEntry.get() region[dim] = (r, r1) self.setSpectrumConditions() self.updateConditionTable() def getRegionMax(self, spectrumCondition, dim): #print 'getRegionMax' (condition, region) = spectrumCondition self.regionEntry.set(region[dim][1]) def setRegionMax(self, spectrumCondition, dim): #print 'setRegionMax' (condition, region) = spectrumCondition (r0, r1) = region[dim] r = self.regionEntry.get() region[dim] = (r0, r) self.setSpectrumConditions() self.updateConditionTable() def setRegion(self, *extra): spectrumCondition = self.getCurrentObject() if spectrumCondition and self.col is not None: col = self.col - 1 # -1 because of condition dim = col / 2 if col % 2: # max self.setRegionMax(spectrumCondition, dim) else: # min self.setRegionMin(spectrumCondition, dim) """ not needed for now def getCondition(self, spectrumCondition): #print 'getCondition' (condition, region) = spectrumCondition self.conditionMenu.set(condition) def setCondition(self, spectrumCondition): #print 'setCondition' spectrumCondition[0] = self.conditionMenu.get() self.setSpectrumConditions() def selectedCondition(self, ind, condition): spectrumCondition = self.getCurrentObject() if spectrumCondition is not None: self.setCondition(spectrumCondition) """ def getSpectrumConditions(self): spectrum = self.spectrum if not spectrum: return [] application = self.project.application spectrumConditions = application.getValue(spectrum, keyword='contourFileConditions') if spectrumConditions: spectrumConditions = eval(spectrumConditions) else: region = [] for i in range(spectrum.numDim): dim = i + 1 region.append(self.getWholeRegion(spectrum, dim)) spectrumCondition = ['include', region] spectrumConditions = [spectrumCondition] self.setSpectrumConditions(spectrumConditions) return spectrumConditions def setSpectrumConditions(self, spectrumConditions = None): spectrum = self.spectrum if spectrum: if not spectrumConditions: spectrumConditions = self.spectrumConditions application = self.project.application application.setValue(spectrum, keyword='contourFileConditions', value=str(spectrumConditions)) def getDimMin(self, spectrum, dim): dataDim = spectrum.findFirstDataDim(dim=dim) if dataDim.className == 'SampledDataDim': r = 1.0 else: converter = UnitConverter.pnt2ppm dataDimRef = ExperimentBasic.getPrimaryDataDimRef(dataDim) r = converter(float(dataDim.numPoints), dataDimRef) return r def getDimMax(self, spectrum, dim): dataDim = spectrum.findFirstDataDim(dim=dim) if dataDim.className == 'SampledDataDim': r = float(dataDim.numPoints) else: converter = UnitConverter.pnt2ppm dataDimRef = ExperimentBasic.getPrimaryDataDimRef(dataDim) r = converter(1.0, dataDimRef) return r def getWholeRegion(self, spectrum, dim): rMin = self.getDimMin(spectrum, dim) rMax = self.getDimMax(spectrum, dim) return (rMin, rMax) def getCurrentObject(self): # sometimes row highlighting stops so obj passed into selectCell might no longer be current # so instead use direct interrogation of scrolledMatrix obj = self.conditionTable.currentObject return obj def selectCell(self, obj, row, col): # see note about obj in getCurrentObject() self.col = col def addCondition(self): spectrum = self.spectrum if spectrum: ndim = spectrum.numDim spectrumCondition = ['exclude', ndim*[(None,None)]] self.spectrumConditions.append(spectrumCondition) self.setSpectrumConditions() self.updateConditionTable() def deleteCondition(self): spectrumCondition = self.getCurrentObject() if spectrumCondition: self.spectrumConditions.remove(spectrumCondition) self.setSpectrumConditions() self.updateConditionTable() def continueIfFileNameUsed(self, fileName): result = True storedContoursToDelete = [] for analysisSpectrum in self.analysisProject.analysisSpectra: for storedContour in analysisSpectrum.storedContours: if storedContour.fullPath == fileName: storedContoursToDelete.append(storedContour) if storedContoursToDelete: if len(storedContoursToDelete) == 1: s = '' t = 's' else: s = 's' t = '' result = showYesNo('File used', 'Stored contour%s already use%s this fileName, and will be deleted: continue?' % (s, t), parent=self) if result: for storedContour in storedContoursToDelete: try: storedContour.delete() except: pass return result def contourAndSaveSpectrum(self): spectrum = self.spectrum if not spectrum: return if not self.spectrumConditions: return ###self.saveFile() fileName = self.file_entry.get() if not fileName: showError('No filename', 'No filename given', parent=self) return contourDir = self.getContourDir() fullPath = joinPath(contourDir, fileName) if not self.continueIfFileNameUsed(fullPath): return directory = os.path.dirname(fullPath) if not os.path.exists(directory): os.makedirs(directory) dims = self.dim_menu.getText() (xdim, ydim) = [int(dim) for dim in dims.split(', ')] ##self.saveLevels() ##levels = self.levels_entry.get() ##if not levels: ## showError('No levels', 'No contour levels given', parent=self) ## return analysisSpectrum = spectrum.analysisSpectrum levels = analysisSpectrum.posLevels + analysisSpectrum.negLevels scale = spectrum.scale / self.analysisProject.globalContourScale levels = [ level/scale for level in levels ] spectrumCondition = self.spectrumConditions[0] (condition, region) = spectrumCondition ndim = spectrum.numDim firstInt = ndim * [0] lastInt = ndim * [0] for i in range(ndim): try: (firstInt[i], lastInt[i]) = self.convertToPoints(spectrum, i, region[i]) except Exception, e: showError('Invalid region', str(e), parent=self) try: #print 'about to saveSpectrumContours', fullPath, xdim, ydim, levels, firstInt, lastInt saveSpectrumContours(spectrum, fullPath, xdim, ydim, levels, firstInt, lastInt, mem_cache=self.parent.mem_cache) except Exception, e: showError('Save error', str(e), parent=self) return
class ItemSelectPopup(BasePopup): def __init__(self, parent, entries, label='', message='', select_text='Select', default=0, *args, **kw): self.entries = entries self.label = label self.message = message self.select_text = select_text self.default = default self.item = None kw['title'] = 'Select Item' kw['transient'] = True kw['modal'] = True BasePopup.__init__(self, parent=parent, *args, **kw) def body(self, guiFrame): guiFrame.grid_rowconfigure(0, weight=1) guiFrame.grid_columnconfigure(1, weight=1) row = 0 if self.message: label = Label(guiFrame, text=self.message, gridSpan=(1, 2), grid=(row, 0)) row += 1 if self.label: label = Label(guiFrame, text=self.label, grid=(row, 0)) self.itemMenu = PulldownList(guiFrame, texts=self.entries, objects=self.entries, index=self.default, grid=(row, 1)) row += 1 texts = [self.select_text, 'Cancel'] commands = [self.ok, self.cancel] buttons = ButtonList(guiFrame, texts=texts, commands=commands, gridSpan=(1, 2), grid=(row, 0)) def cancel(self): self.destroy() return None def apply(self): self.item = self.itemMenu.getText() return True
class IsotopeSchemeEditor(BasePopup): """ **Create and Edit Per-residue Reference Isotope Schemes** This system allows the user to create schemes that describe particular patterns of atomic isotope labelling in terms of combinations of isotopically labelled forms of residues. Once constructed, these schemes may then be applied to a molecule of known residue sequence to gauge the levels of spin-active isotope incorporation in an NMR experiment. This information is useful in several places withing Analysis, including giving more intelligent assignment options and in the generation of distance restraints by matching peak positions to chemical shifts. Although the schemes may be used directly they are typically used as reference information for configuring the `Isotope Labelling`_ system; where isotope labelling patterns are tied to particular molecules and experiments. Because all of the different isotope labelled versions (isotopomers) of each residue type are described independently, a scheme can be used to estimate the specific amounts of incorporation present at multiple atom sites at the same time. For example, although a residue type may have significant levels of 13C at the CA and CB positions on average, there may be no form of the residue where CA and CB are labelled at the same time, and thus CA-CB correlations would not be observed in NMR. This popup window is divided into three main tabs, the first describes the overall schemes that are available; that would be applied to a molecule in a given situation. The second tab details the residue isotopomer components within the selected scheme, i.e. which labelled residue forms are present. The last tab displays isotopomer labelling in a graphical, three-dimensional way. If any isotope labelling schemes have been created or edited the user may immediately save these to disk via the [Save Schemes] button to the right of the tabs, although these will naturally be saved when the main CCPN project is. **Reference Schemes** This table lists all of the reference isotope schemes that are available to the project. A number of standard schemes are included by default, as part of the main CCPN installation. However, the user is free to create new schemes, either from a completely blank description or by copying and modifying one of the existing schemes. By selecting on a isttope scheme row in the table the scheme is selected to be active for the whole popup and the user can see the contents of the scheme via the other two tabs. It should be noted that the user cannot edit the standard schemes provided by CCPN, given that these are stored with the software. Any new or copied schemes that the user creates will be stored inside the current CCPN project. If a new scheme should be made available to multiple projects, its XML file can be copied into the main CCPN installation, if the user has appropriate write access. **Isotopomers** The middle tab allows the user to view, and where appropriate edit, the isotope labelling descriptions for the residues within the current scheme (selected in the pulldown menu at the top). An isotope scheme is constructed by specifying one or more isotopomers for each residue type. Each isotopomer represents a different way of incorporating spin-active atom labels into a given kind of residue. Often there will only be one labelled form of a residue, and hence one isotopomer. However, with some kinds of isotope enrichment, for example using glycerol 13C labelled at the C2 position, connected labelled and unlabelled atom sites can be incorporated in alternative ways, resulting in distinct forms of labelling patterns that are not the result of a pure random mix. Knowing which labels are present at the same time, in the same isotopomer form, can be very important for determining which NMR correlations are possible. In general use when looking through the default, immutable reference schemes that come with CCPN the user can scroll through the isotopomer versions of each residue in the upper table. By clicking on one of these rows the lower table is filled with details of the amount of each kind of isotope (on average) at each atom site. For the lower "Atom Labels" table only one kind of chemical element is shown at a time, but the user may switch to a different one via the "Chemical Element" pulldown. **Editing Isotopomers** When dealing with copied or new isotope schemes the user is allowed to edit all aspects of the scheme. With a completely new scheme there will be no isotopomer records to start with and it is common practice to fill in a standard set of isotopomers, one for each residue type, made with a base level of isotope incorporation. To set this base level the user can use [Set Default Abundances] to manually specify values, although the default is to use natural abundance levels, which is appropriate in most circumstances. With the base levels set the [Add Default Abundance Set] will automatically fill-in a starting set of isotopomers for the scheme. Extra isotopomers can be added for a specific type of residue via the [Add New:] function and adjacent pulldown menu or by copying existing ones; whichever is easier. Each isotopomer has an editable weight to enable the user to indicate the relative abundance within a given residue type. Once a new isotopomer specification is created clicking on its row allows the user to specify the isotope labelling pattern in the lower "Atom Labels" table. Here the user selects which kind of chemical element to consider and then double-clicks to edit the "Weighting" columns in the table. The weightings represent the relative abundance of a given nuclear isotope at a given atom site. The weightings could be set as ratios, fractions, or percentages; it is only the relative proportion that is important. For example if a carbon atom site was known to have 5% Carbon-12 and 95% Carbon-13 isotopes then the respective weights could be entered as 1 & 19 or 0.05 & 0.95; whatever is most convenient. For efficient setup of schemes the [Propagate Abundances] function can be used to spread the same levels of incorporation over several atom sites (from the last selected row). **Isotopomer Structure** The last tab is an alternative way of presenting the isotope patterns present within the residues of the current scheme (selected in either of the first two tabs). Here the user selects a residue type in the upper left pulldown menu and then a numbered isotopomer, or an average of all isotopomers, in the right hand pulldown menu. The structural display will show a moveable picture of the residue (in a standard conformation) where unlabelled atom sites are represented with grey spheres, labelled sites with yellow spheres and intermediate incorporation with shades in between. It should be noted that this kind of 3D display is only possible if there is an idealised structure available for a residue type. This data will be present for all of the regular biopolymer residues, but may be missing for more unusual compounds; although a lack of coordinates does not impact upon the isotopomer setup. To move and rotate the three-dimensional residue display the following keyboard controls may be used: * Rotate: Arrow keys * Zoom: Page Up & Page Down keys * Translate: Arrow keys + Control key Or alternatively the following mouse controls: * Rotate: Middle button click & drag * Zoom: Mouse wheel or middle button click + Shift key & drag up/down * Translate: Middle button click & drag + Control key Also an options menu appears when the right mouse button is clicked. .. _`Isotope Labelling`: EditMolLabellingPopup.html """ def __init__(self, parent, project=None, *args, **kw): if not project: self.project = Implementation.MemopsRoot( name='defaultUtilityProject') else: self.project = project self.waiting = False self.waitingAtom = False self.molType = 'protein' self.scheme = None self.isotopomer = None self.isotopomerV = False # Not None self.ccpCodeV = None self.element = 'C' self.atomLabelTuple = None self.isotopes = [x[0] for x in getSortedIsotopes(self.project, 'C')] self.defaultAbun = {} BasePopup.__init__(self, parent=parent, title='Molecule : Reference Isotope Schemes', **kw) def body(self, guiFrame): self.geometry('700x600') guiFrame.expandGrid(0, 0) tipTexts = [ 'A table of all of the reference isotope scheme definitions available to the project', 'A list of the residue isotopomers that comprise the selected isotope labelling scheme', 'A three-dimensional representation of residues and their isotopomer labelling' ] options = ['Reference Schemes', 'Isotopomers', 'Isotopomer Structure'] tabbedFrame = TabbedFrame(guiFrame, options=options, grid=(0, 0), tipTexts=tipTexts) self.tabbedFrame = tabbedFrame frameA, frameB, frameC = tabbedFrame.frames # # Schemes # frameA.expandGrid(0, 0) tipTexts = [ 'A short textual code that identifies the reference isotope scheme in graphical displays', 'The full name for the isotope scheme', 'A detailed description of the isotope scheme including user comments', 'The name of the CCPN data repository in which the isotope scheme is saved; "refData" is in the CCPn installation' ] headingList = ['Code', 'Name', 'Description', 'Save Location'] self.schemeNameEntry = Entry(self, text='', returnCallback=self.setSchemeName, width=20) self.schemeDetailsEntry = Entry(self, text='', returnCallback=self.setSchemeDetails, width=20) editWidgets = [ None, self.schemeNameEntry, self.schemeDetailsEntry, None ] editGetCallbacks = [ None, self.getSchemeName, self.getSchemeDetails, None ] editSetCallbacks = [ None, self.setSchemeName, self.setSchemeDetails, None ] self.schemeMatrix = ScrolledMatrix(frameA, headingList=headingList, callback=self.selectScheme, editWidgets=editWidgets, editSetCallbacks=editSetCallbacks, editGetCallbacks=editGetCallbacks, multiSelect=False, grid=(0, 0), tipTexts=tipTexts) self.schemeMatrix.doEditMarkExtraRules = self.schemeEditRules tipTexts = [ 'Make a new reference isotope scheme definition based on a copy of the scheme currently selected', 'Delete the selected isotope scheme', 'Make a new, blank isotope scheme' ] texts = ['Copy', 'Delete', 'New'] commands = [self.copyScheme, self.removeScheme, self.makeNewScheme] self.schemeButtons = ButtonList(frameA, texts=texts, commands=commands, grid=(1, 0), tipTexts=tipTexts) # # Isotopomers # frameB.expandGrid(3, 0) row = 0 frame = Frame(frameB, grid=(row, 0)) frame.expandGrid(0, 2) tipText = 'Selects which of the available isotope schemes to view/edit' label = Label(frame, text='Reference Scheme:', grid=(0, 0)) self.schemePulldown = PulldownList(frame, callback=self.setLabellingScheme, grid=(0, 1), tipText=tipText) row += 1 div = LabelDivider(frameB, text='Isotopomers', grid=(row, 0)) row += 1 frame = Frame(frameB, grid=(row, 0)) frame.expandGrid(1, 2) self.isotopomerFrame = frame self.abundanceWidget = MultiWidget(self, FloatEntry, relief='raised', borderwidth=2, callback=self.setDefaultAbundances, useImages=False) tipText = 'Opens a panel that allows you to set the basis/default abundances for C, H & N isotopes; used as the starting point for new isotopomer definitions' self.abundanceButton = Button(frame, text='Set Default\nAbundances', borderwidth=1, command=self.enterDefaultAbundances, grid=(0, 0), tipText=tipText) tipText = 'Sets the basis/default abundances for C, H & N isotopes to their natural abundance proportions' button = Button(frame, text='Set Natural\nAbundance Default', borderwidth=1, command=self.resetDefaultAbundance, grid=(0, 1), sticky='ew', tipText=tipText) label = Label(frame, text='Molecule Type:', grid=(0, 2), sticky='e') entries = standardResidueCcpCodes.keys() entries.sort() entries.reverse() tipText = 'Selects which type of bio-polymer to define residue isotopomer labelling for' self.molTypePulldown = PulldownList(frame, callback=self.setMolType, texts=entries, grid=(0, 3), tipText=tipText) row += 1 tipTexts = [ 'The CCPN code that identifies the kind of residue the isotopomer relates to', 'The number of the particular isotopomer (isotope pattern) within its residue type', 'The fraction of the total residues, of its kind, that the isotopomer make up' ] headingList = ['Ccp Code', 'Variant', 'Weight'] self.isotopomerWeightEntry = FloatEntry( self, text='', returnCallback=self.setIsotopomerWeight, width=6) editWidgets = [None, None, self.isotopomerWeightEntry] editGetCallbacks = [None, None, self.getIsotopomerWeight] editSetCallbacks = [None, None, self.setIsotopomerWeight] self.isotopomerMatrix = ScrolledMatrix( frameB, tipTexts=tipTexts, headingList=headingList, callback=self.selectIsotopomer, editWidgets=editWidgets, editSetCallbacks=editSetCallbacks, editGetCallbacks=editGetCallbacks, multiSelect=True, grid=(row, 0)) self.isotopomerMatrix.doEditMarkExtraRules = self.isotopomerEditRules row += 1 frame = Frame(frameB, grid=(row, 0), sticky='ew') frame.expandGrid(0, 0) tipTexts = [ 'Delete the selected residue isotopomers from the current isotope scheme', 'Make a new residue isotopomer definition by copying the details of the last selected isotopomer', 'Add a complete set of isotopomers to the isotope scheme, one for each residue type, based on the states default isotope abundances', 'For all residue isotopomers in the scheme, set the labelling of one kind of atom (the user is prompted) to its default isotopic incorporation ', 'Add a new residue isotopomer definition that uses the default isotopic incorporation' ] texts = [ 'Delete\nSelected', 'Copy\nSelected', 'Add Default\nAbundance Set', 'Set Atom Type\nTo Default', 'Add\nNew:' ] commands = [ self.removeIsotopomers, self.duplicateResidues, self.addDefaultIsotopomers, self.setAtomTypeDefault, self.addNewIsotopomer ] self.isotopomerButtons = ButtonList(frame, texts=texts, commands=commands, grid=(0, 0), tipTexts=tipTexts) tipText = 'Selects which kind of residue isotopomer may be added to the current isotope scheme' self.ccpCodePulldown = PulldownList(frame, callback=None, grid=(0, 1), sticky='e', tipText=tipText) row += 1 div = LabelDivider(frameB, text='Atom Labels', grid=(row, 0)) row += 1 frame = Frame(frameB, grid=(row, 0)) frame.expandGrid(1, 3) label = Label(frame, text='Chemical Element:', grid=(0, 0)) tipText = 'Selects which kind of atoms to select from the selected residue isotopomer; to display isotopic incorporation in the below table' self.elementPulldown = PulldownList(frame, callback=self.changeChemElement, grid=(0, 1), tipText=tipText) self.updateChemElements() label = Label(frame, text='Water Exchangeable Atoms:', grid=(0, 2)) tipText = 'Sets whether to show atoms considered as being "water exchangeable"; their isotopic labelling will rapidly equilibrate with aqueous solvent' self.exchangeCheck = CheckButton(frame, callback=self.updateAtomLabelsAfter, grid=(0, 3), selected=False, tipText=tipText) row += 1 # Tip texts set on update headingList = [ 'Atom\nName', 'Weighting\n13C' 'Weighting\n12C', '%12C', '%13C' ] self.atomLabelTupleWeightEntry = FloatEntry( self, text='', width=6, returnCallback=self.setAtomLabelWeight) self.atomsMatrix = ScrolledMatrix(frameB, headingList=headingList, callback=self.selectAtomLabel, multiSelect=True, grid=(row, 0)) self.atomsMatrix.doEditMarkExtraRules = self.atomsEditRules row += 1 tipTexts = [ 'For the selected atom sites, in the current isotopomer, set their isotopic incorporation to the default values', 'Spread the isotopic incorporation values from the last selected atom site to all selected atoms sites' ] texts = ['Reset Selected to Default Abundance', 'Propagate Abundances'] commands = [self.setAtomLabelsDefault, self.propagateAbundances] self.atomButtons = ButtonList(frameB, texts=texts, commands=commands, grid=(row, 0), tipTexts=tipTexts) # # View Frame # frameC.expandGrid(1, 0) row = 0 frame = Frame(frameC, grid=(row, 0), sticky='ew') frame.grid_columnconfigure(3, weight=1) label = Label(frame, text='Residue Type:', grid=(0, 0)) tipText = 'Selects which kind of residue, within the current isotope scheme, to show isotopomer structures for' self.viewCcpCodePulldown = PulldownList( frame, callback=self.selectViewCcpcode, grid=(0, 1), tipText=tipText) label = Label(frame, text='Isotopomer:', grid=(0, 2)) tipText = 'Selects which kind of isotopomer (labelling pattern) to display, from the selected residue type.' self.viewIsotopomerPulldown = PulldownList( frame, callback=self.selectViewIsotopomer, grid=(0, 3), tipText=tipText) row += 1 self.viewIsotopomerFrame = ViewIsotopomerFrame(frameC, None, grid=(row, 0)) # # Main # tipTexts = [ 'Save all changes to the reference isotope scheme to disk; the saves ALL changes to the CCPN installation for all projects to use', ] texts = ['Save Schemes'] commands = [self.saveSchemes] self.bottomButtons = UtilityButtonList(tabbedFrame.sideFrame, texts=texts, commands=commands, helpUrl=self.help_url, grid=(0, 0), sticky='e', tipTexts=tipTexts) self.updateChemElements() self.updateCcpCodes() self.updateSchemes() self.administerNotifiers(self.registerNotify) def atomsEditRules(self, atomLabel, row, col): if self.scheme: return isSchemeEditable(self.scheme) else: return False def isotopomerEditRules(self, isotopomer, row, col): if self.scheme: return isSchemeEditable(self.scheme) else: return False def schemeEditRules(self, scheme, row, col): return isSchemeEditable(scheme) def administerNotifiers(self, notifyFunc): for func in ('__init__', 'delete', 'setLongName', 'setDetails'): for clazz in ('ccp.molecule.ChemCompLabel.LabelingScheme', ): notifyFunc(self.updateSchemes, clazz, func) for func in ('__init__', 'delete', 'setWeight'): notifyFunc(self.updateIsotopomersAfter, 'ccp.molecule.ChemCompLabel.Isotopomer', func) notifyFunc(self.updateAtomLabelsAfter, 'ccp.molecule.ChemCompLabel.AtomLabel', func) def getCcpCodeIsotopomers(self, ccpCode): chemCompLabel = self.scheme.findFirstChemCompLabel( molType=self.molType, ccpCode=ccpCode) if chemCompLabel: isotopomers = list(chemCompLabel.isotopomers) else: isotopomers = [] return isotopomers def selectViewCcpcode(self, ccpCode): if ccpCode != self.ccpCodeV: self.ccpCodeV = ccpCode self.isotopomerV = False self.updateViewIsotopomerPulldown() def selectViewIsotopomer(self, isotopomer): self.isotopomerV = isotopomer if isotopomer is None: isotopomers = self.getCcpCodeIsotopomers(self.ccpCodeV) else: isotopomers = [ isotopomer, ] self.viewIsotopomerFrame.setIsotopomers(isotopomers) def updateViewCcpCodePulldown(self): if self.scheme: codes = self.getCcpCodes(self.molType) if self.ccpCodeV not in codes: self.ccpCodeV = codes[0] self.isotopomerV = False # Not None self.updateViewIsotopomerPulldown() index = codes.index(self.ccpCodeV) else: codes = [] index = 0 self.viewCcpCodePulldown.setup(codes, codes, index) def updateViewIsotopomerPulldown(self): index = 0 isotopomers = [] names = [] if self.scheme: isotopomers = self.getCcpCodeIsotopomers(self.ccpCodeV) names = ['%d' % i.serial for i in isotopomers] isotopomers.insert(0, None) names.insert(0, '<All>') if self.isotopomerV not in isotopomers: self.isotopomerV = None isotopomers = self.getCcpCodeIsotopomers(self.ccpCodeV) self.viewIsotopomerFrame.setIsotopomers(isotopomers) self.viewIsotopomerPulldown.setup(names, isotopomers, index) def updateButtons(self): buttonsA = self.schemeButtons.buttons buttonsB = self.isotopomerButtons.buttons buttonsC = self.atomButtons.buttons if self.scheme: buttonsA[0].enable() isEditable = isSchemeEditable(self.scheme) if isEditable: buttonsA[1].enable() else: buttonsA[1].disable() buttonsB[2].enable() buttonsB[3].enable() self.bottomButtons.buttons[0].enable() if isEditable: if self.isotopomer: for button in buttonsB: button.enable() for button in buttonsC: button.enable() else: buttonsB[0].disable() buttonsB[1].disable() buttonsB[3].disable() buttonsC[0].disable() buttonsC[1].disable() buttonsB[2].enable() buttonsB[4].enable() else: for button in buttonsB: button.disable() for button in buttonsC: button.disable() else: buttonsA[0].disable() buttonsA[1].disable() for button in buttonsB: button.disable() for button in buttonsC: button.disable() self.bottomButtons.buttons[0].disable() def resetDefaultAbundance(self): self.defaultAbun = {} def setDefaultAbundances(self, values): self.abundanceWidget.place_forget() if values is not None: i = 0 for element in elementSymbols: # TBD getAllIsotopes for code, isotope in getSortedIsotopes(self.project, element): self.defaultAbun[isotope] = values[i] i += 1 def enterDefaultAbundances(self): x = self.isotopomerFrame.winfo_x() y = self.isotopomerFrame.winfo_y() x0 = self.abundanceButton.winfo_x() y0 = self.abundanceButton.winfo_y() values = [] options = [] for element in elementSymbols: # TBD getAllIsotopes for code, isotope in getSortedIsotopes(self.project, element): options.append(code + ':') values.append( self.defaultAbun.get(isotope, 100.0 * isotope.abundance)) N = len(values) self.abundanceWidget.maxRows = N self.abundanceWidget.minRows = N self.abundanceWidget.set(values=values, options=options) self.abundanceWidget.place(x=x + x0, y=y + y0) def selectAtomLabel(self, obj, row, col): self.atomLabelTuple = (obj, col) def setMolType(self, molType): if molType != self.molType: self.molType = molType self.isotopomer = None self.updateCcpCodes() self.updateIsotopomers() def getCcpCodes(self, molType): codes = [] for code in standardResidueCcpCodes[molType]: codes.append(code) codes.sort() return codes def updateCcpCodes(self): codes = self.getCcpCodes(self.molType) if self.isotopomer: index = codes.index(self.isotopomer.ccpCode) else: index = 0 self.ccpCodePulldown.setup(codes, codes, index) def setIsotopomerWeight(self, event): value = self.isotopomerWeightEntry.get() if value is not None: self.isotopomer.setWeight(abs(value)) def getIsotopomerWeight(self, isotopomer): if isotopomer: self.isotopomerWeightEntry.set(isotopomer.weight) def setSchemeName(self, event): text = self.schemeNameEntry.get() if text: text = text.strip() or None else: text = None self.scheme.setLongName(text) def getSchemeName(self, scheme): if scheme: self.schemeNameEntry.set(scheme.longName) def getSchemeDetails(self, scheme): if scheme: self.schemeDetailsEntry.set(scheme.details) def setSchemeDetails(self, event): text = self.schemeDetailsEntry.get() if text: text = text.strip() or None else: text = None self.scheme.setDetails(text) def updateSchemes(self, obj=None): textMatrix = [] objectList = [] for labelingScheme in self.project.sortedLabelingSchemes(): repository = labelingScheme.findFirstActiveRepository() if repository: saveLocation = repository.name else: saveLocation = None line = [ labelingScheme.name, labelingScheme.longName, labelingScheme.details, saveLocation ] textMatrix.append(line) objectList.append(labelingScheme) self.schemeMatrix.update(textMatrix=textMatrix, objectList=objectList) self.updateSchemePulldown() self.updateIsotopomers() def updateSchemePulldown(self): scheme = self.scheme schemes = self.project.sortedLabelingSchemes() names = [ls.longName or ls.name for ls in schemes] if names: if scheme not in schemes: scheme = schemes[0] index = schemes.index(scheme) else: index = 0 scheme = None self.setLabellingScheme(scheme) self.schemePulldown.setup(names, schemes, index) def copyScheme(self): if self.scheme: name = askString('Input text', 'New Scheme Code:', '', parent=self) scheme = self.project.findFirstLabelingScheme(name=name) if scheme: showWarning('Failure', 'Scheme name already in use') return if name: newScheme = copySubTree(self.scheme, self.project, topObjectParameters={'name': name}) else: showWarning('Failure', 'No name specified') else: showWarning('Failure', 'No scheme selected to copy') def removeScheme(self): if self.scheme and isSchemeEditable(self.scheme): self.scheme.delete() self.scheme = None def makeNewScheme(self): name = askString('Input text', 'New Scheme Code:', '', parent=self) if name: scheme = self.project.findFirstLabelingScheme(name=name) if scheme: showWarning('Failure', 'Scheme name already in use') else: scheme = self.project.newLabelingScheme(name=name) self.scheme = scheme else: showWarning('Failure', 'No name specified') def setLabellingScheme(self, scheme): if scheme is not self.scheme: self.scheme = scheme self.isotopomerV = False self.isotopomer = None self.updateIsotopomers() def selectScheme(self, object, row, col): self.setLabellingScheme(object) self.updateSchemePulldown() def open(self): BasePopup.open(self) self.updateSchemes() def saveSchemes(self): schemes = [x for x in self.project.labelingSchemes if x.isModified] if schemes: for scheme in schemes: scheme.save() showInfo('Notice', 'Successfully saved %d schemes' % len(schemes)) self.updateSchemes() else: showWarning('Notice', 'No modified schemes to save') def addNewIsotopomer(self): if self.scheme: ccpCode = self.ccpCodePulldown.getObject() chemCompLabel = self.getChemCompLabel(self.molType, ccpCode) self.makeIsotopomer(chemCompLabel) def makeIsotopomer(self, chemCompLabel, weight=1.0): isotopomer = chemCompLabel.newIsotopomer(weight=weight) chemComp = chemCompLabel.chemComp for chemAtom in chemComp.chemAtoms: if chemAtom.elementSymbol: chemElement = chemAtom.chemElement for isotope in chemElement.isotopes: code = '%d%s' % (isotope.massNumber, chemAtom.elementSymbol) weight = self.defaultAbun.get(isotope, 100.0 * isotope.abundance) isotopomer.newAtomLabel(name=chemAtom.name, subType=chemAtom.subType, isotopeCode=code, weight=weight) return isotopomer def getChemCompLabel(self, molType, ccpCode): chemCompLabel = None if self.scheme: chemCompLabel = self.scheme.findFirstChemCompLabel(molType=molType, ccpCode=ccpCode) if not chemCompLabel: chemCompLabel = self.scheme.newChemCompLabel(molType=molType, ccpCode=ccpCode) return chemCompLabel def selectIsotopomer(self, obj, row, col): self.isotopomer = obj self.updateChemElements() self.updateAtomLabels() def updateIsotopomersAfter(self, obj=None): if self.waiting: return else: self.waiting = True self.after_idle(self.updateIsotopomers) def updateIsotopomers(self): self.updateViewCcpCodePulldown() self.updateViewIsotopomerPulldown() textMatrix = [] objectList = [] if self.scheme: chemCompLabels = [(label.ccpCode, label) for label in self.scheme.chemCompLabels] chemCompLabels.sort() for key, chemCompLabel in chemCompLabels: if chemCompLabel.molType == self.molType: for isotopomer in chemCompLabel.sortedIsotopomers(): line = [ chemCompLabel.ccpCode, isotopomer.serial, isotopomer.weight ] textMatrix.append(line) objectList.append(isotopomer) self.isotopomerMatrix.update(textMatrix=textMatrix, objectList=objectList) self.updateAtomLabelsAfter() self.waiting = False def setAtomTypeDefault(self): if self.scheme: atomName = askString( 'Query', 'Specify atom name to set\ndefault abundance for', 'H', parent=self) if not atomName: return atomLabels = [] for chemCompLabel in self.scheme.chemCompLabels: if chemCompLabel.molType == self.molType: for isotopomer in chemCompLabel.isotopomers: # Multiple because of isotopes and subTypes atomLabels += isotopomer.findAllAtomLabels( name=atomName) if atomLabels: for atomLabel in atomLabels: isotope = atomLabel.isotope weight = self.defaultAbun.get(isotope, 100.0 * isotope.abundance) atomLabel.weight = weight else: data = (atomName, self.scheme.name) msg = 'Atom name %s does not match any atoms in %s scheme isotopomers' % data showWarning('Failure', msg) def addDefaultIsotopomers(self): if self.scheme: codes = self.getCcpCodes(self.molType) for ccpCode in codes: chemCompLabel = self.getChemCompLabel(self.molType, ccpCode) if not chemCompLabel.isotopomers: self.makeIsotopomer(chemCompLabel) def removeIsotopomers(self): isotopomers = self.isotopomerMatrix.currentObjects if isotopomers: for isotopomer in isotopomers: isotopomer.delete() self.isotopomer = None def duplicateResidues(self): isotopomers = self.isotopomerMatrix.currentObjects for isotopomer in isotopomers: chemCompLabel = isotopomer.chemCompLabel new = copySubTree(isotopomer, chemCompLabel) def updateChemElements(self): if self.isotopomer: chemCompLabel = self.isotopomer.chemCompLabel elementDict = {} for chemAtom in chemCompLabel.chemComp.chemAtoms: symbol = chemAtom.elementSymbol if symbol: elementDict[symbol] = True names = elementDict.keys() names.sort() else: names = ['C', 'N', 'H'] if self.element not in names: index = 0 else: index = names.index(self.element) self.elementPulldown.setup(names, names, index) def updateAtomLabelsAfter(self, obj=None): if self.waitingAtom: return else: self.waitingAtom = True self.after_idle(self.updateAtomLabels) def updateAtomLabels(self): element = self.elementPulldown.getText() textMatrix = [] objectList = [] headingList = [ 'Atom Name', ] tipTexts = [ 'The name of the atom within its residue, for which to set isotopic abundances, within the selected isotopomer', ] isotopeCodes = [x[0] for x in getSortedIsotopes(self.project, element)] headingList.extend(['Weighting\n%s' % x for x in isotopeCodes]) tip = 'The amount of %s isotope incorporation; can be a ratio, percentage or fraction (the value used is relative to the sum of all weights)' tipTexts.extend([tip % x for x in isotopeCodes]) tip = 'The percentage %s isotope incorporation, calculated using stated weights' headingList.extend(['%%%s' % x for x in isotopeCodes]) tipTexts.extend([tip % x for x in isotopeCodes]) editWidgets = [ None, ] editGetCallbacks = [ None, ] editSetCallbacks = [ None, ] editWidgets.extend( [self.atomLabelTupleWeightEntry for x in isotopeCodes]) editGetCallbacks.extend( [self.getAtomLabelWeight for x in isotopeCodes]) editSetCallbacks.extend( [self.setAtomLabelWeight for x in isotopeCodes]) editWidgets.extend([None for x in isotopeCodes]) editGetCallbacks.extend([None for x in isotopeCodes]) editSetCallbacks.extend([None for x in isotopeCodes]) doExchangeable = self.exchangeCheck.get() if self.isotopomer: atomDict = {} for atomLabel in self.isotopomer.sortedAtomLabels(): if atomLabel.chemAtom.elementSymbol == element: if (not doExchangeable) and ( atomLabel.chemAtom.waterExchangeable): continue name = atomLabel.name subType = atomLabel.subType atomDict[(name, subType)] = True atomNames = atomDict.keys() atomNames = greekSortAtomNames(atomNames) for atomName, subType in atomNames: if subType == 1: name = atomName else: name = '%s:%d' % (atomName, subType) line = [ name, ] atomLabels = [] sumWeights = 0.0 for isotope in isotopeCodes: atomLabel = self.isotopomer.findFirstAtomLabel( name=atomName, subType=subType, isotopeCode=isotope) atomLabels.append(atomLabel) if atomLabel: weight = atomLabel.weight sumWeights += weight line.append(weight) else: line.append(0.0) if sumWeights: for atomLabel in atomLabels: line.append(100.0 * atomLabel.weight / sumWeights) else: for atomLabel in atomLabels: line.append(None) textMatrix.append(line) objectList.append(atomLabels) self.atomsMatrix.update(textMatrix=textMatrix, objectList=objectList, headingList=headingList, tipTexts=tipTexts, editWidgets=editWidgets, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks) self.waitingAtom = False self.updateButtons() def setAtomLabelWeight(self, event): value = self.atomLabelTupleWeightEntry.get() if value is not None: atomLabels, col = self.atomLabelTuple chemAtom = None for label in atomLabels: if label: chemAtom = label.chemAtom break atomLabel = atomLabels[col - 1] if chemAtom and (atomLabel is None): isotopeCode, isotope = getSortedIsotopes( self.project, chemAtom.elementSymbol)[col - 1] atomLabel = self.isotopomer.newAtomLabel( name=chemAtom.name, subType=chemAtom.subType, isotopeCode=isotopeCode, weight=value) atomLabel.setWeight(value) def setAtomLabelsDefault(self): atomLabelTuples = self.atomsMatrix.currentObjects for atomLabels in atomLabelTuples: for atomLabel in atomLabels: isotope = atomLabel.isotope weight = self.defaultAbun.get(isotope, 100.0 * isotope.abundance) atomLabel.weight = weight def propagateAbundances(self): atomLabels, col = self.atomLabelTuple atomLabelTuples = self.atomsMatrix.currentObjects weightDict = {} for atomLabel in atomLabels: weightDict[atomLabel.isotope] = atomLabel.weight for atomLabels0 in atomLabelTuples: if atomLabels0 != atomLabels: for atomLabel in atomLabels0: atomLabel.weight = weightDict[atomLabel.isotope] def getAtomLabelWeight(self, null): atomLabels, col = self.atomLabelTuple if atomLabels and (col > 0): atomLabel = atomLabels[col - 1] if atomLabel is None: weight = 0.0 else: weight = atomLabel.weight self.atomLabelTupleWeightEntry.set(weight) def changeChemElement(self, name): self.element = name self.isotopes = [x[0] for x in getSortedIsotopes(self.project, name)] self.updateAtomLabels() def destroy(self): self.administerNotifiers(self.unregisterNotify) BasePopup.destroy(self)
class BrowsePeakGroupsPopup(BasePopup): """ **Display Groups of Peaks Linked by a Root Assignment** This popup window is used to display the results of operations in Analysis that generate grouped peaks, usually linked by virtue of a common 'root' assignment. For example a selected set of peaks may be macthed to other peaks with similar positions in a spectrum dimension, where each group corresponds to a separate C-H or N-H position. For example the right-click window menu option "Locate Peaks::Match multiple peaks" generates such groupings to find rows or columns of peaks that share chemical shifts. Whatever operation generated the groups, they are listed in ranked order in the table; best scoring at the top. The number of peaks in the group and any common assigment and position is also listed. The idea is that the groups are candidates for some searh or macthing process that the user triggered. The best scoring groups may be used to control the spectrum window display, thus displaying any peak matches, by using [Display Groups in Strips] after selecting an approprate window. This system is often used for NOESY experiments where resonances that are close in space share common sets of peak positions; they are close to the same set of other resonances. """ def __init__(self, parent, *args, **kw): self.groups = [] self.variableDim = 1 self.windowPane = None self.rulers = [] self.peakLists = [] self.orthogonalDict = {} self.parent = parent self.searchPeaks = [] BasePopup.__init__(self, parent, title= "Peak Groups", borderwidth=5, **kw) def body(self, guiFrame): guiFrame.grid_rowconfigure(0, weight=1) guiFrame.grid_columnconfigure(0, weight=1) row = 0 frame = LabelFrame(guiFrame, text='Matched Peak Groups') frame.grid_rowconfigure(0, weight=1) frame.grid_columnconfigure(0, weight=1) frame.grid(row=row, sticky='nsew') headingList, tipTexts = self.getHeadingList() self.scrolledMatrix = ScrolledMatrix(frame, initialRows=15, multiSelect=True, headingList=headingList, tipTexts=tipTexts, grid=(0,0), gridSpan=(1,3)) tipTexts = ['Remove the selected peak groups from the table', 'Show 1D positional ruler lines for the selected groups', 'Remove any ruler lines previously added for peak group', 'Display selected peak groups within strips of selected window'] texts = ['Remove\nGroups','Show\nRulers', 'Delete\nRulers','Display Groups\nIn Strips'] commands = [self.removeGroups,self.addRulers, self.removeRulers,self.stripGroups] self.buttons = ButtonList(frame, texts=texts, commands=commands, tipTexts=tipTexts) self.buttons.grid(row=1, column=0, sticky='ew') tipText = 'Selects the spectrum window in which to display strips & ruler lines' label = Label(frame, text='Target window:', grid=(1,1)) self.windowPulldown = PulldownList(frame, callback=None, grid=(1,2), tipText=tipText) row+= 1 bottomButtons = UtilityButtonList(guiFrame, helpUrl=self.help_url, expands=True, doClone=False) bottomButtons.grid(row = row, sticky = 'ew') self.update() for func in ('__init__', 'delete', 'setName'): self.registerNotify(self.updateWindowsAfter, 'ccpnmr.Analysis.SpectrumWindow', func) def open(self): self.update() BasePopup.open(self) def updateWindowsAfter(self, opt=None): self.after_idle(self.updateWindows) def updateWindows(self): if not self.groups: self.windowPulldown.clear() return selected = self.windowPulldown.getText() groups = self.scrolledMatrix.currentObjects or self.groups self.orthogonalDict = {} windowPane = self.windowPane if windowPane and groups: meanVarPos = 0.0 for peak in groups[0][2]: meanVarPos += peak.sortedPeakDims()[self.variableDim].value meanVarPos /= float(len(groups[0][2])) xyz = [] for score, coords0, peaks in groups: peak = peaks[0] peakDims = peak.sortedPeakDims() dimMapping = getPeakDimAxisMapping(peak, self.windowPane) coords = list(coords0) coords.insert(self.variableDim, meanVarPos) posDict = {} for i, peakDim in enumerate(peakDims): posDict[peakDim] = coords[i] pos = [] for axisPanel in windowPane.sortedAxisPanels(): peakDim = dimMapping[axisPanel.label] pos.append( posDict[peakDim] ) xyz.append(pos) windowZPlanes = findOrthogonalWindows(windowPane, xyz, excludeQuery=False) for windowPosition in windowZPlanes: if windowPosition: key, pane, xyz = windowPosition self.orthogonalDict[key] = (pane, xyz) xyPlaneKeys = self.orthogonalDict.keys() xyPlaneKeys.sort() if xyPlaneKeys: index = min(self.windowPulldown.index, len(xyPlaneKeys)-1) if selected in xyPlaneKeys: index = xyPlaneKeys.index(selected) self.windowPulldown.setup(xyPlaneKeys, xyPlaneKeys, index) def removeGroups(self): groups = self.scrolledMatrix.currentObjects if groups: newGroups = [] for group in self.groups: if group not in groups: newGroups.append(group) self.update(groups=newGroups) def stripGroups(self): self.updateWindows() name = self.windowPulldown.getText() groups = self.scrolledMatrix.currentObjects if groups and name and (name != '<None>'): selected = self.windowPulldown.getText() windowPane, positions = self.orthogonalDict[selected] window = windowPane.spectrumWindow N = len(positions) msg = '%d positions selected. Really make %d strips in window %s' if N > 10 and not showOkCancel('Warning', msg % (N,N,window.name ), parent=self): return displayStrips(self.parent, positions, orthoPositions=None, spectrum=None, windowPane=windowPane) # often fails first time... self.update_idletasks() displayStrips(self.parent, positions, orthoPositions=None, spectrum=None, windowPane=windowPane) def removeRulers(self): for ruler in self.rulers: if not ruler.isDeleted: ruler.delete() self.rulers = [] def addRulers(self): groups = self.scrolledMatrix.currentObjects if groups and self.windowPane: positions = [] panelTypes = [] for peak in self.searchPeaks: peakDim = peak.sortedPeakDims()[self.variableDim] dimMapping = getPeakDimAxisMapping(peak, self.windowPane) for axisPanel in self.windowPane.axisPanels: if dimMapping[axisPanel.label] is peakDim: positions.append(peakDim.value) panelTypes.append(axisPanel.panelType) break color = '#808080' for i in range(len(positions)): ruler = createRuler(positions[i], panelTypes[i], dashLength=3, gapLength=1, color=color, remove=False) self.rulers.append(ruler) def update(self, groups=None, variableDim=None, peakLists=None, searchPeaks=None): self.removeRulers() if groups is not None: self.groups = groups if variableDim is not None: self.variableDim = variableDim if peakLists: self.peakLists = peakLists spectrum = self.peakLists[0].dataSource for spectrumWindow in self.analysisProject.spectrumWindows: for windowPane in spectrumWindow.spectrumWindowPanes: if isSpectrumInWindowPane(windowPane, spectrum): if len(windowPane.axisPanels) == len(spectrum.dataDims): self.windowPane = windowPane break self.searchPeaks = searchPeaks or [] objectList = [] textMatrix = [] for i, group in enumerate(self.groups): (score,positions,peaks) = group datum = ['%d' % (i+1), '%f' % score, '%d' % len(peaks)] annotations = [] for j in range(len(peaks[0].peakDims)): if j != self.variableDim: annotation = '' for peak in peaks: peakDims = peak.sortedPeakDims() peakDim = peakDims[j] if peakDim.annotation: if annotation and (annotation != peakDim.annotation): annotation = '*' else: annotation = peakDim.annotation annotations.append(annotation) datum.append(' '.join(annotations)) for position in positions: datum.append('%f' % position) objectList.append(group) textMatrix.append(datum) headingList, tipTexts = self.getHeadingList() self.scrolledMatrix.update(textMatrix=textMatrix, objectList=objectList, headingList=headingList, tipTexts=tipTexts) self.updateWindows() def getHeadingList(self): tipTexts = ['The ranking of the peak group, comparing its score with others', 'The match score of the peak group', 'Number of peaks within the matched peak group', 'The root (usually amide) assignment, common to peaks in a group'] headingList = ['Rank','Score','Num\npeaks','Root\nAssignment'] if self.groups: n = len( self.groups[0][2][0].peakDims ) for i in range(n): if i != self.variableDim: headingList.append( 'F%d position' % (i+1) ) tipTexts.append('Average location of the group in the F%d dimension' % (i+1)) else: headingList.extend(['F1 position','F2 position']) tipTexts.append('Average location of the group in the F1 dimension') tipTexts.append('Average location of the group in the F2 dimension') return headingList, tipTexts def destroy(self): for func in ('__init__', 'delete', 'setName'): self.unregisterNotify(self.updateWindowsAfter, 'ccpnmr.Analysis.SpectrumWindow', func) BasePopup.destroy(self)
class OpenSpectrumPopup(BasePopup): r""" **Locate Spectrum Data for Use in CCPN Project** This popup window enables the user to locate spectrum data within a file system and associate the files (typically binary) with an experiment and spectrum name so that it may be visualised and accessed within the current CCPN project. Spectra of many different origins and file formats may be loaded, which currently includes Bruker, Varian, Felix, NMRPipe, NmrView, SPARKY/UCSF, Azara and the factorised shape format "USF3". Depending upon the file format of the spectrum, data loaded the user may be required to either select a parameter file which then refers to the actual spectrum intensity data; this is true for Bruker "procs" and AZARA ".par" files, or alternatively a spectrum data file itself that contains referencing information; this is the case for SPARKY/UCSF, NmrView and NMRPipe spectra. The layout of the popup involved two sections; the upper of which is for navigating to and selecting the spectrum or parameter files within the file-system, and the lower is for specifying how each spectrum is loaded into the CCPN project. It should be noted that when spectrum parameters are read the first time, the relevant information is copied into the CCPN project, where it may be adjusted independently of the original file information. No copies of the spectrum intensity data are made, the CCPN project merely refers to the spectrum data on disk, although the data file for a loaded spectrum may subsequently be moved or replaced. In normal operation the user first selects the kind of spectrum file format that will be loaded via the upper "File format" pulldown menu and then checks that the "File type" pulldown (toward the bottom of the file browser) is set to detect the appropriate kinds of filename; if a helpful file name filter is not available the user can add one via the "Manual Select" field, taking care to add any wild-card symbols, like the asterisk in "\*.ft3". Next the spectrum data or parameter files, appropriate to the selected format, are located by navigating within the file-system browser. When the required spectrum files are visible the user selects one *or more* to load. Multiple file selections may be made using left-click with <Ctrl> (toggle selection) or <Shift> (select range). It should be noted that when selecting Bruker files, when using the standard Bruker directory structure, the user only needs to navigate to the numbered spectrum directory; by default the "procs" file two levels down is searched for, e.g. "123/pdata/1/procs" is shown in the directory containing the "123" directory. When spectrum or parameter files are selected in the file table, the lower "Spectra To Open" table is filled to reflect the selection. The user should then be mindful of the settings within this table and may choose to edit various things by double-clicking on the appropriate cell. Typically the user just adjusts the name of the independent "Experiment" and "Spectrum" records. These names are usually concatenated like "expName:specName" in CCPN graphical displays so there is no need to repeat a name in both fields; this only takes up more space. The Experiment, which is a record of *what was done experimentally*, commonly has a short name like "HNCA" or "HSQC_298K" so the user readily knows how to interpret the experimental data. The Spectrum, which is a record of *the data that was collected*, commonly has a short name to identify the spectrum number or file name. An Experiment record may contain several Spectrum records, so the spectrum's name need minimally only identify it amongst others from the same experiment. The Shift List value may be changed if the user knows that the experiment represents a distinct set of conditions, with different spectrum peak/resonance positions, to existing or other experiments being entered. Each shift list will be curated separately, to give separate chemical shift values for assignments made under different conditions (even when relating to the same atoms). The shift list that an experiment uses may also be changed at any time after loading. When all spectra and options are specified the [Open Spectrum] button will load the relevant data into the CCPN project. If the "Skip verification dialogs" option is set it is assumed that all of the spectrum point to frequency referencing information, and any data file references, are correct. Otherwise, the user will be prompted to confirm the file details and referencing information for each spectrum in turn. Finally, after loading the user is asked to set the type of NMR experiment, in terms of general magnetisation transfer pathway, that was performed. **Caveats & Tips** If the name of an Experiment that is *already within the CCPN project* is used, then the loaded spectrum will (assuming it is compatible) be entered under that existing experiment record; no new experiment entity will be defined. The user may legitimately use this feature to load several spectra that relate to the same experiment; typically where spectra are different projections. To facilitate this the "Use shared experiment" option can be selected. Although experiments and spectra may be renamed after loading, a spectrum record may not be placed under a different experiment once created; deletion and re-loading is the only mans of achieving this, and care must be taken in transferring any assignments. """ def __init__(self, parent, *args, **kw): self.experiment = None self.currentObject = None #self.currentObjects = [] # looks obsolete BasePopup.__init__(self, parent=parent, title='Experiment : Open Spectra', **kw) def open(self): self.message() BasePopup.open(self) def body(self, guiFrame): self.fileSelect = None names, objects = self.getShiftLists() self.shiftListPulldown = PulldownList(self, callback=self.setShiftList, texts=names, objects=objects) self.windowPulldown = PulldownList(self, texts=WINDOW_OPTS, callback=self.setWindow) self.experimentEntry = Entry(self, width=16, returnCallback=self.setExperiment) self.spectrumEntry = Entry(self, width=16, returnCallback=self.setSpectrum) guiFrame.grid_columnconfigure(0, weight=1) guiFrame.grid_rowconfigure(0, weight=1) guiFrame.grid_rowconfigure(1, weight=1) leftFrame = LabelFrame(guiFrame, text='File Selection') leftFrame.grid(row=0, column=0, sticky='nsew') leftFrame.grid_columnconfigure(3, weight=1) row = 0 label = Label(leftFrame, text='File format:') label.grid(row=row, column=0, sticky='w') tipText = 'Selects which kind of spectrum file is being loaded; what its data matrix format is' self.formatPulldown = PulldownList(leftFrame, callback=self.chooseFormat, texts=file_formats, tipText=tipText, grid=(row, 1)) self.detailsLabel = Label(leftFrame, text='Show details:') tipText = 'Whether to show an annotation that describes the spectrum in the file selection; currently only uses comment fields from Bruker spectra' self.detailsSelect = CheckButton(leftFrame, selected=False, callback=self.showDetails, tipText=tipText) self.titleRow = row self.detailsSelected = False row = row + 1 leftFrame.grid_rowconfigure(row, weight=1) file_types = [FileType('All', ['*'])] self.fileSelect = FileSelect(leftFrame, multiSelect=True, file_types=file_types, single_callback=self.chooseFiles, extraHeadings=('Details', ), extraJustifies=('left', ), displayExtra=False, getExtraCell=self.getDetails, manualFileFilter=True) self.fileSelect.grid(row=row, column=0, columnspan=6, sticky='nsew') rightFrame = LabelFrame(guiFrame, text='Spectra To Open') rightFrame.grid(row=1, column=0, sticky='nsew') rightFrame.grid_columnconfigure(3, weight=1) row = 0 label = Label(rightFrame, text='Skip verification dialogs:', grid=(row, 0)) tipText = 'Whether to allow the user to check file interpretation and referencing information before the spectrum is loaded' self.verifySelect = CheckButton(rightFrame, selected=False, grid=(row, 1), tipText=tipText) label = Label(rightFrame, text='Use shared experiment:', grid=(row, 2)) tipText = 'When selecting multiple spectrum files, whether the loaded spectra will all belong to (derive from) the same experiment; useful for projection spectra etc.' self.sharedExpSelect = CheckButton(rightFrame, selected=False, tipText=tipText, callback=self.useShared, grid=(row, 3)) row = row + 1 rightFrame.grid_rowconfigure(row, weight=1) tipTexts = [ 'A short textual name for the experiment record that the loaded spectrum will belong to; may be a new experiment or the name of an existing one', 'A short textual name to identify the spectrum within its experiment; typically a few characters or spectrum number, rather than a repeat of the experiment name', 'The location of the file, relative to the current directory, that the spectrum data will be loaded from', 'Sets which window or windows the spectrum will initially appear within once loaded', 'Sets which shift list the experiment (and hence loaded spectrum) will use to curate chemical shift information; can be changed after load time' ] headingList = [ 'Experiment', 'Spectrum', 'File', 'Windows', 'Shift List' ] editWidgets = [ self.experimentEntry, self.spectrumEntry, None, self.windowPulldown, self.shiftListPulldown ] editGetCallbacks = [ self.getExperiment, self.getSpectrum, None, self.getWindow, self.getShiftList ] editSetCallbacks = [ self.setExperiment, self.setSpectrum, None, self.setWindow, self.setShiftList ] self.scrolledMatrix = ScrolledMatrix(rightFrame, headingList=headingList, callback=self.selectCell, editWidgets=editWidgets, multiSelect=True, editGetCallbacks=editGetCallbacks, editSetCallbacks=editSetCallbacks, tipTexts=tipTexts, grid=(row, 0), gridSpan=(1, 4)) row = row + 1 tipTexts = [ 'Load spectrum or spectra into the CCPN project using the selected file(s)', ] texts = ['Open Spectrum'] commands = [self.openSpectra] bottomButtons = UtilityButtonList(guiFrame, texts=texts, tipTexts=tipTexts, doClone=False, commands=commands, helpUrl=self.help_url) bottomButtons.grid(row=row, column=0, columnspan=1, sticky='ew') self.openButton = bottomButtons.buttons[0] self.chooseFormat('Azara') self.message() def message(self): if not self.project or len(self.nmrProject.experiments) < 1: pass #self.parent.ticker.setMessage('Choose spectrum files to open.... ') def showDetails(self, isSelected): self.detailsSelected = isSelected self.fileSelect.updateDisplayExtra(isSelected) # below is so that when Details column is toggled on it will actually # be seen without having to use the scrollbar self.fileSelect.fileList.refreshSize() def useShared(self, isSelected): self.chooseFiles(forceUpdate=True) #if isSelected: #objects = self.scrolledMatrix.objectList #if len(objects) > 1: # self.currentObject = objects[0] # text = objects[0][0] # self.chooseFiles() # for oo in objects[1:]: # oo[0] = text # if self.project: # self.experiment = self.nmrProject.findFirstExperiment(name=text) #self.update() def gridDetails(self, bool): if bool: self.detailsLabel.grid(row=self.titleRow, column=2, sticky='w') self.detailsSelect.grid(row=self.titleRow, column=3, sticky='w') self.fileSelect.updateDisplayExtra(self.detailsSelected) else: self.detailsLabel.grid_forget() self.detailsSelect.grid_forget() self.fileSelect.updateDisplayExtra(False) def openSpectra(self): noVerify = self.verifySelect.getSelected() # tracks if 'add to existing experiment' has already ben OK'ed self.okExpSet = set() directory = self.fileSelect.getDirectory() spectra = [] specIndex = 0 for obj in self.scrolledMatrix.objectList: fileName = uniIo.joinPath(directory, obj.fileName) spectrum = self.openSpectrum(obj.exptName, obj.specName, fileName, obj.window, obj.shiftListName) specIndex += 1 if (spectrum): # check endianness if we are not verifying spectra.append(spectrum) if noVerify: isBigEndian = isSpectrumBigEndian( spectrum) # according to data in file if isBigEndian is not None: isBigEndianCurr = getIsSpectrumBigEndian( spectrum) # according to data model setIsSpectrumBigEndian(spectrum, isBigEndian) if isBigEndian != isBigEndianCurr: if isBigEndian: s = 'big' else: s = 'little' print 'WARNING: swapped endianess of spectrum to %s endian' % s # del self.okExpSet if noVerify and len(spectra) > 1 and self.sharedExpSelect.getSelected( ): # if we are using a shared experiment and not verifying, # set referencing to match first spectrum for all # get reference spectrum and set up data structure # use most recent pre-existing spectrum, otherwise first new one refSpec = spectra[0] for spec in spectra[0].experiment.sortedDataSources(): if spec in spectra: break else: refSpec = spec ddrLists = {} refDdrs = [] for dataDim in refSpec.sortedDataDims(): for ddr in dataDim.dataDimRefs: ddrLists[ddr.expDimRef] = [] refDdrs.append(ddr) # get dataDimRefs, store by ExpDimRef, # checking that all spectra have data dim refs for same set of xdr nTotal = len(ddrLists) for spec in spectra: nFound = 0 for dataDim in spec.sortedDataDims(): for ddr in dataDim.dataDimRefs: xdr = ddr.expDimRef ll = ddrLists.get(xdr) if ll is None: # something did not match - do nothing break else: ll.append(ddr) nFound += 1 else: if nFound == nTotal: # we are OK. Do next spectrum continue # something did not match - do nothing break else: # all spectra matched. Now reset O1 references to match reference if refSpec is spectra[0]: startAt = 1 else: startAt = 0 for refDdr in refDdrs: dataDim = refDdr.dataDim centrePoint = dataDim.numPointsOrig / 2 - dataDim.pointOffset + 1 refValue = refDdr.pointToValue(centrePoint) xdr = refDdr.expDimRef for ddr in ddrLists[xdr][startAt:]: dataDim = ddr.dataDim centrePoint = dataDim.numPointsOrig / 2 - dataDim.pointOffset + 1 ddr.refPoint = centrePoint ddr.refValue = refValue # set refExperiment if there is only one possibility experiments = [] ignoreSet = set() showPopup = False for spectrum in spectra: experiment = spectrum.experiment if experiment not in ignoreSet: ignoreSet.add(experiment) if not experiment.refExperiment: experiments.append(spectrum.experiment) if noVerify: resetCategory = False if not hasattr(experiment, 'category'): if (hasattr(experiment, 'pulProgName') and hasattr(experiment, 'pulProgType')): # this is first time we get here, and we have external name and source # use external source to set fullType experiment.category = 'use external' resetCategory = True refExperiments = getRefExperiments(experiment) if resetCategory and not refExperiments: # no refExperiments match external source. # unset 'use external' category del experiment.category if len(refExperiments) == 1: # only one possibility, just set it setRefExperiment(experiment, refExperiments[0]) # wb104: 20 Oct 2014: do not popup Experiment types dialog if noVerify #else: # showPopup = True # Pop up refExperiment verification if experiments and (showPopup or not noVerify): self.parent.initRefExperiments(experiments) # set up internal Analysis data for spectrum in spectra: self.parent.finishInitSpectrum(spectrum) print 'finished opening spectrum', spectrum.experiment.name, spectrum.name def chooseFiles(self, forceUpdate=False, *file): directory = self.fileSelect.getDirectory() fileNames = self.fileSelect.fileList.currentObjects fullFileNames1 = [uniIo.joinPath(directory, x) for x in fileNames] fullFileNames2 = [x.fileName for x in self.scrolledMatrix.objectList] fullFileNames2 = [uniIo.joinPath(directory, x) for x in fullFileNames2] if fullFileNames1 == fullFileNames2 and not forceUpdate: return objectList = [] textMatrix = [] format = self.formatPulldown.getText() shiftListName = self.getShiftLists()[0][0] windowOpt = WINDOW_OPTS[1] oneUp = os.path.dirname if format == 'Bruker': if self.sharedExpSelect.getSelected(): nameTemplate = 'Bruker_%d' next = self.getNextExpNum(nfiles=len(fileNames), nameTemplate=nameTemplate) exptName = nameTemplate % (next) for i, fileName in enumerate(fileNames): fullFileName = fullFileNames1[i] specName = os.path.basename( oneUp(oneUp(oneUp(fullFileName)))) datum = (exptName, specName, fileName, windowOpt, shiftListName) dataObj = RowObject(*datum) textMatrix.append(datum) objectList.append(dataObj) else: for i, fileName in enumerate(fileNames): fullFileName = fullFileNames1[i] try: # below should not fail ss1 = oneUp(fullFileName) specName = os.path.basename(ss1) ss2 = os.path.basename(oneUp(oneUp(ss1))) exptName = 'Bruker_' + ss2 except: # just put in something ss = os.path.basename(fullFileName) exptName = 'Bruker_' + ss specName = ss datum = (exptName, specName, fileName, windowOpt, shiftListName) dataObj = RowObject(*datum) textMatrix.append(datum) objectList.append(dataObj) else: next = self.getNextExpNum(nfiles=len(fileNames)) if self.sharedExpSelect.getSelected(): exptName = 'Expt_%d' % (next) for i, fileName in enumerate(fileNames): specName = re.sub('\.\w+$', '', fileName) datum = (exptName, specName, fileName, windowOpt, shiftListName) dataObj = RowObject(*datum) textMatrix.append(datum) objectList.append(dataObj) else: for i, fileName in enumerate(fileNames): exptName = 'Expt_%d' % (next + i) specName = re.sub('\.\w+$', '', fileName) datum = (exptName, specName, fileName, windowOpt, shiftListName) dataObj = RowObject(*datum) textMatrix.append(datum) objectList.append(dataObj) if len(fileNames) > 1: self.openButton.config(text='Open Spectra') else: self.openButton.config(text='Open Spectrum') self.scrolledMatrix.update(objectList=objectList, textMatrix=textMatrix) def getNextExpNum(self, nfiles=0, nameTemplate='Expt_%d'): """ get suitable free integer to use for exp names """ next = 1 if self.project: nmrProject = self.nmrProject ii = len(nmrProject.experiments) # find first exp number that is not taken # NBNB TBD could consider expname = specname, specname = proc dir next = ii + 1 if nfiles: while ii < next + nfiles: ii += 1 if nmrProject.findFirstExperiment(name=nameTemplate % ii): next = ii + 1 # return next def getDetails(self, fullfile): details = '' if os.path.isfile(fullfile): format = self.formatPulldown.getText() detailsDir = os.path.dirname(fullfile) detailsFile = uniIo.joinPath(detailsDir, details_file_dict[format]) if os.path.exists(detailsFile): fp = open(detailsFile) details = fp.read().strip().replace('\n', ' ').replace('\r', ' ') fp.close() return (details, ) def update(self): objectList = self.scrolledMatrix.objectList textMatrix = [(obj.exptName, obj.specName, obj.fileName, obj.window, obj.shiftListName) for obj in objectList] self.scrolledMatrix.update(objectList=objectList, textMatrix=textMatrix) def selectCell(self, obj, row, col): self.currentObject = obj if self.project: self.experiment = self.nmrProject.findFirstExperiment( name=obj.exptName) else: self.experiment = None def getWindow(self, obj): if obj: self.windowPulldown.set(obj.window) def setWindow(self, opt): if isinstance(opt, RowObject): self.currentObject.window = opt.window else: self.currentObject.window = opt self.update() def setShiftList(self, obj=None): if self.project: project = self.project shiftList = self.shiftListPulldown.getObject() if shiftList is None: shiftList = newShiftList(project, unit='ppm') if self.experiment and shiftList and ( shiftList is not self.experiment.shiftList): setExperimentShiftList(self.experiment, shiftList) self.currentObject.shiftListName = shiftList.name self.update() def getShiftList(self, object): names, shiftLists = self.getShiftLists() if names: self.shiftListPulldown.setup(names, shiftLists, 0) if self.experiment and self.experiment.shiftList: name = self.experiment.shiftList.name else: name = object.shiftListName if name is not None: self.shiftListPulldown.set(name) def getShiftLists(self): if self.project: names = [] objects = getShiftLists(self.nmrProject) for shiftList in objects: if not shiftList.name: shiftList.name = 'ShiftList %d' % shiftList.serial names.append(shiftList.name) objects.append(None) names.append('<New>') else: objects = [ None, ] names = [ 'ShiftList 1', ] return names, objects def chooseFormat(self, format): if format in ('Bruker', 'Varian'): self.gridDetails(True) else: self.gridDetails(False) file_types = [] file_type = file_type_dict.get(format) if (file_type): file_types.extend([file_type]) file_types.append(FileType('All', ['*'])) file_types.append(self.fileSelect.manualFilter) self.fileSelect.setFileTypes(file_types) def getSpectrum(self, obj): if obj: self.spectrumEntry.set(obj.specName) def setSpectrum(self, *event): if self.currentObject: text = self.spectrumEntry.get() if text and text != ' ': for data in self.scrolledMatrix.objectList: if data is self.currentObject: continue if (data.specName == text) and (data.exptName == self.currentObject.exptName): showWarning( 'Repeated name', 'Spectrum name (%s) already in use for experiment (%s)' % (data.specName, data.exptName), parent=self) return elif (self.experiment) and ( self.experiment.findFirstDataSource(name=text)): showWarning( 'Repeated name', 'Spectrum name (%s) already in use for experiment (%s)' % (data.specName, data.exptName), parent=self) return self.currentObject.specName = text self.update() def getExperiment(self, obj): if obj: self.experimentEntry.set(obj.exptName) def setExperiment(self, *event): if self.currentObject: text = self.experimentEntry.get() if text and text != ' ': if self.sharedExpSelect.getSelected(): # share one experiment for all rows for oo in self.scrolledMatrix.objectList: oo.exptName = text else: #separate experiments self.currentObject.exptName = text if self.project: self.experiment = self.nmrProject.findFirstExperiment( name=text) self.update() def updateShiftLists(self): if self.project: name = self.expt_entry.get() e = self.nmrProject.findFirstExperiment(name=name) else: e = None names, objects = self.getShiftLists() if e and e.shiftList: index = objects.index(e.shiftList) else: index = 0 self.shiftListPulldown.setup(names, objects, index) def openSpectrum(self, exptName, specName, file, windowOpt=WINDOW_OPTS[2], shiftListName='<New>', extraData=None): # input checks if not file: showError('No file', 'Need to enter file', parent=self) return None if not exptName: showError('Experiment', 'Need to enter experiment name', parent=self) return None if not specName: showError('Spectrum', 'Need to enter spectrum name', parent=self) return None # get or set up project project = self.project if not project: self.project = project = defaultProject() self.parent.initProject(project) self.nmrProject = self.parent.nmrProject self.analysisProject = self.parent.analysisProject #Default ShiftList with name 'ShiftList 1' created # set up shift list if shiftListName == '<New>': shiftList = None else: shiftList = self.nmrProject.findFirstMeasurementList( className='ShiftList', name=shiftListName) # read params format = self.formatPulldown.getText() clazz = params_class_dict[format] try: params = clazz(file, extraData=extraData) except Implementation.ApiError, e: showError('Reading params file', 'Fatal error: ' + e.error_msg, parent=self) return None dim = params.pseudoDataDim() if dim is not None: if format == 'NMRPipe': popup = NmrPipePseudoPopup(self, params, dim, file) popup.destroy() elif format == 'Bruker': popup = BrukerPseudoPopup(self, params, dim) popup.destroy() # get or set up experiment experiment = self.nmrProject.findFirstExperiment(name=exptName) if experiment: expIsNew = False if experiment.findFirstDataSource(name=specName): showError('Duplicate name', 'Duplicate spectrum name "%s" in experiment %s' % (specName, experiment.name), parent=self) return None elif (experiment.dataSources and experiment not in self.okExpSet): if showOkCancel('Multiple Spectra Warning', 'Really put multiple ' 'spectra into existing experiment %s?' % experiment.name, parent=self): self.okExpSet.add(experiment) else: return else: expIsNew = True try: # Will also work for shiftList == None experiment = Nmr.Experiment(self.nmrProject, name=exptName, numDim=params.ndim, shiftList=shiftList) except Implementation.ApiError, experiment: showError('Experiment', experiment.error_msg, parent=self) return None
class PrintFrame(LabelFrame): def __init__(self, parent, getOption=None, setOption=None, text='Print Options', haveTicks=False, doOutlineBox=True, *args, **kw): self.getOption = getOption self.setOption = setOption self.haveTicks = haveTicks self.doOutlineBox = doOutlineBox LabelFrame.__init__(self, parent=parent, text=text, *args, **kw) self.file_select_popup = None self.getOptionValues() try: size_index = self.paper_types.index(self.paper_type) except: size_index = 0 try: other_unit_index = self.paper_units.index(self.other_unit) except: other_unit_index = 0 try: orientation_index = self.paper_orientations.index(self.orientation) except: orientation_index = 0 try: style_index = self.style_choices.index(self.output_style) except: style_index = 0 try: format_index = self.format_choices.index(self.output_format) except: format_index = 0 if haveTicks: try: tick_location_index = self.tick_locations.index( self.tick_location) except: tick_location_index = 0 self.grid_columnconfigure(2, weight=1) row = 0 label = Label(self, text='File:') label.grid(row=row, column=0, sticky='e') self.file_entry = Entry(self, width=40, text=self.file_name) self.file_entry.grid(row=row, column=1, columnspan=2, sticky='ew') button = Button(self, text='Choose File', command=self.findFile) button.grid(row=row, column=3, rowspan=2, sticky='nsew') row += 1 label = Label(self, text='Title:') label.grid(row=row, column=0, sticky='e') self.title_entry = Entry(self, width=40, text=self.title) self.title_entry.grid(row=row, column=1, columnspan=2, sticky='ew') row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='ew') frame.grid_columnconfigure(4, weight=1) label = Label(frame, text='Paper size:') label.grid(row=0, column=0, sticky='e') entries = [] for t in paper_types: if t == Output.other_paper_type: entry = t else: (w, h, u) = paper_sizes[t] entry = t + ' (%2.1f %s x %2.1f %s)' % (w, u, h, u) entries.append(entry) self.size_menu = PulldownList(frame, callback=self.changedSize, texts=entries, index=size_index) self.size_menu.grid(row=0, column=1, sticky='w') self.other_frame = Frame(frame) self.other_frame.grid_columnconfigure(0, weight=1) self.other_entry = FloatEntry(self.other_frame, text=self.other_size, isArray=True) self.other_entry.grid(row=0, column=0, sticky='ew') self.other_unit_menu = PulldownList(self.other_frame, texts=paper_units, index=other_unit_index) self.other_unit_menu.grid(row=0, column=1, sticky='ew') row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='ew') frame.grid_columnconfigure(1, weight=1) frame.grid_columnconfigure(3, weight=1) frame.grid_columnconfigure(5, weight=1) label = Label(frame, text='Orientation:') label.grid(row=0, column=0, sticky='e') self.orientation_menu = PulldownList(frame, texts=paper_orientations, index=orientation_index) self.orientation_menu.grid(row=0, column=1, sticky='w') label = Label(frame, text=' Style:') label.grid(row=0, column=2, sticky='e') self.style_menu = PulldownList(frame, texts=style_choices, index=style_index) self.style_menu.grid(row=0, column=3, sticky='w') label = Label(frame, text=' Format:') label.grid(row=0, column=4, sticky='e') self.format_menu = PulldownList(frame, callback=self.changedFormat, texts=format_choices, index=format_index) self.format_menu.grid(row=0, column=5, sticky='w') if haveTicks: row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='ew') frame.grid_columnconfigure(1, weight=1) frame.grid_columnconfigure(3, weight=1) label = Label(frame, text='Tick Location:') label.grid(row=0, column=0, sticky='e') self.tick_menu = PulldownList(frame, texts=tick_locations, index=tick_location_index) self.tick_menu.grid(row=0, column=1, sticky='w') label = Label(frame, text=' Tick Placement:') label.grid(row=0, column=2, sticky='e') self.tick_buttons = CheckButtons(frame, entries=tick_placements, selected=self.tick_placement) self.tick_buttons.grid(row=0, column=3, sticky='w') row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='ew') frame.grid_columnconfigure(3, weight=1) label = Label(frame, text='Include:') label.grid(row=0, column=0, sticky='e') self.border_buttons = CheckButtons(frame, entries=border_decorations, selected=self.border_decoration) self.border_buttons.grid(row=0, column=1, sticky='w') label = Label(frame, text=' Scaling:') label.grid(row=0, column=2, sticky='e') self.scaling_scale = Scale(frame, orient=Tkinter.HORIZONTAL, value=self.scaling) self.scaling_scale.grid(row=0, column=3, sticky='ew') def destroy(self): self.setOptionValues() if self.file_select_popup: self.file_select_popup.destroy() Frame.destroy(self) def getOptionValues(self): getOption = self.getOption if getOption: file_name = getOption('FileName', defaultValue='') title = getOption('Title', defaultValue='') paper_type = getOption('PaperSize', defaultValue=paper_types[0]) paper_type = paper_type_dict.get(paper_type, paper_types[0]) other_height = getOption('OtherHeight', defaultValue=10) other_width = getOption('OtherWidth', defaultValue=10) other_size = [other_height, other_width] other_unit = getOption('OtherUnit', defaultValue=paper_units[0]) orientation = getOption('Orientation', defaultValue=paper_orientations[0]) in_color = getOption('InColor', defaultValue=True) if in_color: output_style = style_choices[0] else: output_style = style_choices[1] format_option = getOption('OutputFormat', defaultValue=format_options[0]) output_format = format_choices[format_options.index(format_option)] if self.haveTicks: tick_outside = getOption('TickOutside', defaultValue=tick_locations[0]) if tick_outside: tick_location = tick_locations.index(PrintTicks.Outside) else: tick_location = tick_locations.index(PrintTicks.Inside) tick_placement = getTickPlacement1( getOption('TickPlacement', defaultValue='nsew')) dateTime = getOption('ShowsDateTime', defaultValue=True) fileName = getOption('ShowsFileName', defaultValue=True) border_decoration = [] if dateTime: border_decoration.append(border_decorations[0]) if fileName: border_decoration.append(border_decorations[1]) scaling = getOption('Scaling', defaultValue=0.9) scaling = int(round(100.0 * scaling)) else: file_name = '' title = '' paper_type = paper_types[0] other_unit = paper_units[0] other_size = '' orientation = paper_orientations[0] output_style = style_choices[0] output_format = format_choices[0] if self.haveTicks: tick_location = tick_locations[0] tick_placement = tick_placements border_decoration = border_decorations scaling = 90 if not self.haveTicks: tick_location = None tick_placement = None self.file_name = file_name self.title = title self.paper_type = paper_type self.other_unit = other_unit self.other_size = other_size self.orientation = orientation self.output_style = output_style self.output_format = output_format self.tick_location = tick_location self.tick_placement = tick_placement self.border_decoration = border_decoration self.scaling = scaling def setOptionValues(self): self.file_name = file_name = self.file_entry.get() self.title = title = self.title_entry.get() n = self.size_menu.getSelectedIndex() self.paper_type = paper_type = paper_types[n] if paper_type == Output.other_paper_type: other_size = self.other_entry.get() other_unit = self.other_unit_menu.getText() else: other_size = None other_unit = None self.other_size = other_size self.other_unit = other_unit self.paper_orientation = paper_orientation = self.orientation_menu.getText( ) self.output_style = output_style = self.style_menu.getText() self.output_format = output_format = self.format_menu.getText() if self.haveTicks: tick_location = self.tick_menu.getText() tick_placement = self.tick_buttons.getSelected() else: tick_location = tick_placement = None self.tick_location = tick_location self.tick_placement = tick_placement self.border_decoration = border_decoration = self.border_buttons.getSelected( ) scaling = self.scaling_scale.get() self.scaling = scaling = int(round(scaling)) setOption = self.setOption if setOption: setOption('FileName', value=file_name) setOption('Title', value=title) if paper_type == Output.other_paper_type: setOption('OtherHeight', value=other_size[0]) setOption('OtherWidth', value=other_size[1]) setOption('OtherUnit', value=other_unit) else: paper_type = paper_type_inverse_dict[paper_type] setOption('PaperSize', value=paper_type) setOption('Orientation', value=paper_orientation) in_color = (output_style == style_choices[0]) setOption('InColor', value=in_color) output_format = format_options[format_choices.index(output_format)] setOption('OutputFormat', value=output_format) if self.haveTicks: tick_outside = (tick_location == PrintTicks.Outside) setOption('TickOutside', value=tick_outside) tick_placement = getTickPlacement2(tick_placement) setOption('TickPlacement', value=tick_placement) dateTime = (border_decorations[0] in border_decoration) fileName = (border_decorations[1] in border_decoration) setOption('ShowsDateTime', value=dateTime) setOption('ShowsFileName', value=fileName) setOption('Scaling', value=0.01 * scaling) def findFile(self): if self.file_select_popup: self.file_select_popup.open() else: file_types = [ FileType('All', ['*']), FileType('PostScript', ['*.ps', '*.eps']), FileType('PDF', ['*.pdf', '*.ai']) ] self.file_select_popup = FileSelectPopup(self, file_types=file_types) file = self.file_select_popup.getFile() if file: self.file_entry.set(file) def changedSize(self, entry): if entry == Output.other_paper_type: self.other_frame.grid(row=0, column=2, columnspan=2, sticky='w') else: self.other_frame.grid_forget() def changedFormat(self, entry): file_suffix = file_suffixes.get(entry) if not file_suffix: return file_name = self.file_entry.get() if not file_name: return for suffix in format_suffixes: if file_name.endswith(suffix): if suffix != file_suffix: n = len(suffix) file_name = file_name[:-n] + file_suffix self.file_entry.set(file_name) break else: file_name = file_name + file_suffix self.file_entry.set(file_name) # width and height are of plot, in pixels def getOutputHandler(self, width, height, fonts=None): if not fonts: fonts = [] else: fonts = list(fonts) for n in range(len(fonts)): if fonts[n] == 'Times': fonts[n] = 'Times-Roman' self.setOptionValues() if not self.file_name: showError('No file', 'No file specified', parent=self) return None if os.path.exists(self.file_name): if not showYesNo('File exists', 'File "%s" exists, overwrite?' % self.file_name, parent=self): return None if (self.paper_type == Output.other_paper_type): paper_size = self.other_size + [self.other_unit] else: paper_size = paper_sizes[self.paper_type] output_scaling = self.scaling / 100.0 font = 'Times-Roman' border_text = {} for decoration in self.border_decoration: if (decoration == 'Time & date'): location = 'se' text = time.ctime(time.time()) elif (decoration == 'File name'): location = 'sw' text = self.file_name else: continue # should not be here border_text[location] = (text, font, 12) if (self.title): location = 'n' border_text[location] = (self.title, font, 18) if font not in fonts: fonts.append(font) outputHandler = PrintHandler.getOutputHandler( self.file_name, width, height, output_scaling=output_scaling, paper_size=paper_size, paper_orientation=self.paper_orientation, output_style=self.output_style, output_format=self.output_format, border_text=border_text, fonts=fonts, do_outline_box=self.doOutlineBox) return outputHandler def getAspectRatio(self): self.setOptionValues() if self.paper_type == Output.other_paper_type: paper_size = self.other_size else: paper_size = paper_sizes[self.paper_type] r = paper_size[1] / paper_size[0] if self.paper_orientation == 'Landscape': r = 1.0 / r return r
class PrintFrame(Frame): def __init__(self, parent, getOption = None, setOption = None, haveTicks = False, doOutlineBox = True, *args, **kw): self.getOption = getOption self.setOption = setOption self.haveTicks = haveTicks self.doOutlineBox = doOutlineBox Frame.__init__(self, parent=parent, *args, **kw) self.file_select_popup = None self.getOptionValues() try: size_index = paper_types.index(self.paper_type) except: size_index = 0 try: other_unit_index = paper_units.index(self.other_unit) except: other_unit_index = 0 try: orientation_index = paper_orientations.index(self.paper_orientation) except: orientation_index = 0 try: style_index = style_choices.index(self.output_style) except: style_index = 0 try: format_index = format_choices.index(self.output_format) except: format_index = 0 if haveTicks: try: tick_location_index = tick_locations.index(self.tick_location) except: tick_location_index = 0 self.grid_columnconfigure(1, weight=1) row = 0 button = Button(self, text='File:', command=self.findFile, tipText='Select location to save print file') button.grid(row=row, column=0, sticky='e') self.file_entry = Entry(self, width=40, text=self.file_name, tipText='Location where file is saved on disk') self.file_entry.grid(row=row, column=1, sticky='ew') row += 1 label = Label(self, text='Title:') label.grid(row=row, column=0, sticky='e') self.title_entry = Entry(self, width=40, text=self.title, tipText='Title of the printout, displayed at top') self.title_entry.grid(row=row, column=1, sticky='ew') row += 1 label = Label(self, text='X axis label:') label.grid(row=row, column=0, sticky='e') self.x_axis_entry = Entry(self, width=40, text=self.x_axis_label, tipText='X axis label for the printout') self.x_axis_entry.grid(row=row, column=1, sticky='ew') row += 1 label = Label(self, text='Y axis label:') label.grid(row=row, column=0, sticky='e') self.y_axis_entry = Entry(self, width=40, text=self.y_axis_label, tipText='Y axis label for the printout') self.y_axis_entry.grid(row=row, column=1, sticky='ew') row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=2, sticky='ew') frame.grid_columnconfigure(4, weight=1) label = Label(frame, text='Paper size:') label.grid(row=0, column=0, sticky='e') entries = [] for t in paper_types: if t == Output.other_paper_type: entry = t else: (w, h, u) = paper_sizes[t] entry = t + ' (%2.1f %s x %2.1f %s)' % (w, u, h, u) entries.append(entry) self.size_menu = PulldownList(frame, callback=self.changedSize, texts=entries, index=size_index, tipText='The paper size for the printout') self.size_menu.grid(row=0, column=1, sticky='w') self.other_frame = Frame(frame) self.other_frame.grid_columnconfigure(0, weight=1) self.other_entry = FloatEntry(self.other_frame, text=self.other_size, isArray=True, tipText='The size of the Other paper in both dimensions; this requires two values, space or comma separated') self.other_entry.grid(row=0, column=0, sticky='ew') self.other_unit_menu= PulldownList(self.other_frame, texts=paper_units, index=other_unit_index, tipText='The unit for the Other paper size') self.other_unit_menu.grid(row=0, column=1, sticky='ew') row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='ew') frame.grid_columnconfigure(1, weight=1) frame.grid_columnconfigure(3, weight=1) frame.grid_columnconfigure(5, weight=1) label = Label(frame, text='Orientation:') label.grid(row=0, column=0, sticky='e') self.orientation_menu = PulldownList(frame, texts=paper_orientations, index=orientation_index, tipText='Whether the paper should be set in Portrait or Landscape mode') self.orientation_menu.grid(row=0, column=1, sticky='w') label = Label(frame, text=' Style:') label.grid(row=0, column=2, sticky='e') self.style_menu = PulldownList(frame, texts=style_choices, index=style_index, tipText='Whether the printout should be in colour or black and white') self.style_menu.grid(row=0, column=3, sticky='w') label = Label(frame, text=' Format:') label.grid(row=0, column=4, sticky='e') self.format_menu = PulldownList(frame, callback=self.changedFormat, texts=format_choices, index=format_index, tipText='Whether to save as PS, EPS or PDF') self.format_menu.grid(row=0, column=5, sticky='w') if haveTicks: row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='ew') frame.grid_columnconfigure(1, weight=1) frame.grid_columnconfigure(3, weight=1) label = Label(frame, text='Tick Location:') label.grid(row=0, column=0, sticky='e') self.tick_menu = PulldownList(frame, texts=tick_locations, index=tick_location_index, tipText='Whether the tick marks appear on the inside or outside of the frame') self.tick_menu.grid(row=0, column=1, sticky='w') label = Label(frame, text=' Tick Placement:') label.grid(row=0, column=2, sticky='e') if self.tick_placement is None: selected = None else: selected = [(x in self.tick_placement) for x in tick_placements] self.tick_buttons = CheckButtons(frame, entries=tick_placements, selected=selected, tipTexts=('Whether the tick marks appear on the top and/or bottom and/or left and/or right',)) self.tick_buttons.grid(row=0, column=3, sticky='w') row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='ew') frame.grid_columnconfigure(1, weight=1) frame.grid_columnconfigure(3, weight=1) label = Label(frame, text='Tick Font:') label.grid(row=0, column=0, sticky='e') self.tick_font_list = FontList(frame, mode='Print', selected=self.tick_font, extraTexts=[PrintTicks.no_tick_text], tipText='The font used for the tick mark labels') self.tick_font_list.grid(row=0, column=1, sticky='w') label = Label(frame, text='Tick Spacing:') label.grid(row=0, column=2, sticky='e') # TBD: put preferred choice in data model self.spacing_menu = PulldownList(frame, texts=spacing_choices, index=0, callback=self.changedSpacing, tipText='Whether the program should automatically calculate the major/minor tick spacings and how many decimal places are used for the ticks, or whether the these are specified manually') self.spacing_menu.grid(row=0, column=3, sticky='w') ff = self.spacing_frame = Frame(frame) ff.grid_columnconfigure(1, weight=1) ff.grid_columnconfigure(2, weight=1) label = Label(ff, text='Tick Spacing') label.grid(row=0, column=0, sticky='w') label = Label(ff, text='Major') label.grid(row=0, column=1, sticky='ew') label = Label(ff, text='Minor') label.grid(row=0, column=2, sticky='ew') label = Label(ff, text='Decimals') label.grid(row=0, column=3, sticky='ew') label = Label(ff, text='X:') label.grid(row=1, column=0, sticky='w') self.x_major_entry = FloatEntry(ff, tipText='The spacing in display units of the major tick marks in the X dimension') self.x_major_entry.grid(row=1, column=1, sticky='ew') self.x_minor_entry = FloatEntry(ff, tipText='The spacing in display units of the minor tick marks in the X dimension (not printed if left blank)') self.x_minor_entry.grid(row=1, column=2, sticky='ew') self.x_decimal_entry = IntEntry(ff, tipText='The number of decimal places for the tick numbers in the X dimension') self.x_decimal_entry.grid(row=1, column=3, sticky='ew') label = Label(ff, text='Y:') label.grid(row=2, column=0, sticky='w') self.y_major_entry = FloatEntry(ff, tipText='The spacing in display units of the major tick marks in the Y dimension') self.y_major_entry.grid(row=2, column=1, sticky='ew') self.y_minor_entry = FloatEntry(ff, tipText='The spacing in display units of the minor tick marks in the Y dimension (not printed if left blank)') self.y_minor_entry.grid(row=2, column=2, sticky='ew') self.y_decimal_entry = IntEntry(ff, tipText='The number of decimal places for the tick numbers in the Y dimension') self.y_decimal_entry.grid(row=2, column=3, sticky='ew') row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='ew') frame.grid_columnconfigure(1, weight=1) label = Label(frame, text='Tick Length:') label.grid(row=0, column=0, sticky='e') # TBD: put preferred choice in data model self.tick_length_menu = PulldownList(frame, texts=tick_length_choices, index=0, callback=self.changedLength, tipText='Whether the program should automatically calculate the major/minor tick lengths, or whether the these are specified manually') self.tick_length_menu.grid(row=0, column=1, sticky='w') ff = self.length_frame = Frame(frame) ff.grid_columnconfigure(1, weight=1) label = Label(ff, text=' Major length:') label.grid(row=0, column=0, sticky='w') self.length_major_entry = FloatEntry(ff, tipText='The length in points of the major tick marks') self.length_major_entry.grid(row=0, column=1, sticky='w') label = Label(ff, text='Minor length:') label.grid(row=0, column=2, sticky='w') self.length_minor_entry = FloatEntry(ff, tipText='The length in points of the minor tick marks') self.length_minor_entry.grid(row=0, column=3, sticky='w') row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='ew') frame.grid_columnconfigure(3, weight=1) frame.grid_columnconfigure(4, weight=1) label = Label(frame, text='Scaling:') label.grid(row=0, column=0, sticky='e') # TBD: put preferred choice in data model self.scaling_menu = PulldownList(frame, texts=scaling_choices, index=0, callback=self.changedScaling, tipText='Whether the plot should be scaled as a percentage of the maximum size that would fit on the paper, or instead should be specified by the number of cms or inches per unit') self.scaling_menu.grid(row=0, column=1, sticky='ew') self.scaling_scale = Scale(frame, orient=Tkinter.HORIZONTAL, value=self.scaling, tipText='The percentage of the maximum size that would fit on the paper that the plot is scaled by') self.scaling_scale.grid(row=0, column=2, columnspan=3, sticky='ew') self.x_scaling_label = Label(frame, text='X:') self.x_scaling_entry = FloatEntry(frame, tipText='The scaling that should be used in the X dimension as cms or inches per unit') self.y_scaling_label = Label(frame, text='Y:') self.y_scaling_entry = FloatEntry(frame, tipText='The scaling that should be used in the Y dimension as cms or inches per unit') row += 1 frame = Frame(self) frame.grid(row=row, column=0, columnspan=4, sticky='w') frame.grid_columnconfigure(2, weight=1) label = Label(frame, text='Include:') label.grid(row=0, column=0, sticky='e') tipTexts = ('Whether the time and date should be included in the printout', 'Whether the file name should be included in the printout') if self.border_decoration is None: selected = None else: selected = [(x in self.border_decoration) for x in border_decorations] self.border_buttons = CheckButtons(frame, entries=border_decorations, selected=selected, tipTexts=tipTexts) self.border_buttons.grid(row=0, column=1, sticky='w') label = Label(frame, text=' Using Font:') label.grid(row=0, column=2, sticky='e') self.border_font_list = FontList(frame, mode='Print', selected=self.border_font, tipText='The font used for the border texts') self.border_font_list.grid(row=0, column=3, sticky='w') row += 1 label = Label(self, text='Line width:') label.grid(row=row, column=0, sticky='w') self.linewidth_entry = FloatEntry(self, width=10, text=self.linewidth, tipText='Line width for drawing') self.linewidth_entry.grid(row=row, column=1, sticky='w') def destroy(self): self.setOptionValues() if self.file_select_popup: self.file_select_popup.destroy() Frame.destroy(self) def getOptionValues(self): getOption = self.getOption if getOption: file_name = getOption('FileName', defaultValue='') title = getOption('Title', defaultValue='') x_axis_label = getOption('XAxisLabel', defaultValue='') y_axis_label = getOption('YAxisLabel', defaultValue='') paper_type = getOption('PaperSize', defaultValue=paper_types[0]) paper_type = paper_type_dict.get(paper_type, paper_types[0]) other_height = getOption('OtherHeight', defaultValue=10) other_width = getOption('OtherWidth', defaultValue=10) other_size = [other_height, other_width] other_unit = getOption('OtherUnit', defaultValue=paper_units[0]) paper_orientation = getOption('Orientation', defaultValue=paper_orientations[0]) in_color = getOption('InColor', defaultValue=True) if in_color: output_style = style_choices[0] else: output_style = style_choices[1] format_option = getOption('OutputFormat', defaultValue=format_options[0]) output_format = format_choices[format_options.index(format_option)] if self.haveTicks: tick_outside = getOption('TickOutside', defaultValue=tick_locations[0]) if tick_outside: tick_location = tick_locations.index(PrintTicks.Outside) else: tick_location = tick_locations.index(PrintTicks.Inside) tick_placement = getTickPlacement1(getOption('TickPlacement', defaultValue='nsew')) dateTime = getOption('ShowsDateTime', defaultValue=True) fileName = getOption('ShowsFileName', defaultValue=True) border_font = getOption('BorderFont', defaultValue='Helvetica 10') border_decoration = [] if dateTime: border_decoration.append(border_decorations[0]) if fileName: border_decoration.append(border_decorations[1]) if self.haveTicks: spacing_choice = getOption('SpacingChoice', defaultValue=spacing_choices[0]) x_major = getOption('XMajor', defaultValue=1.0) x_minor = getOption('XMinor', defaultValue=1.0) x_decimal = getOption('XDecimal', defaultValue=3) y_major = getOption('YMajor', defaultValue=1.0) y_minor = getOption('YMinor', defaultValue=1.0) y_decimal = getOption('YDecimal', defaultValue=3) tick_length_choice = getOption('TickLengthChoice', defaultValue=tick_length_choices[0]) tick_major = getOption('TickMajor', defaultValue=10) tick_minor = getOption('TickMinor', defaultValue=5) scaling_choice = getOption('ScalingChoice', defaultValue=scaling_choices[0]) scaling = getOption('Scaling', defaultValue=0.7) scaling = int(round(100.0 * scaling)) x_scaling = getOption('XScaling', defaultValue=1.0) y_scaling = getOption('YScaling', defaultValue=1.0) if self.haveTicks: tick_font = getOption('TickFont', defaultValue='Helvetica 10') linewidth = getOption('LineWidth', defaultValue=Output.default_linewidth) else: file_name = '' title = '' x_axis_label = '' y_axis_label = '' paper_type = paper_types[0] other_unit = paper_units[0] other_size = '' paper_orientation = paper_orientations[0] output_style = style_choices[0] output_format = format_choices[0] if self.haveTicks: tick_location = tick_locations[0] tick_placement = tick_placements border_decoration = border_decorations border_font = 'Helvetica 10' if self.haveTicks: spacing_choice = spacing_choices[0] x_major = 1.0 x_minor = 1.0 x_decimal = 3 y_major = 1.0 y_minor = 1.0 y_decimal = 3 tick_length_choice = tick_length_choices[0] tick_major = 10 tick_minor = 5 scaling_choice = scaling_choices[0] scaling = 70 x_scaling = 1.0 y_scaling = 1.0 if self.haveTicks: tick_font = 'Helvetica 10' linewidth = Output.default_linewidth if not self.haveTicks: tick_location = None tick_placement = None spacing_choice = spacing_choices[0] x_major = 1.0 x_minor = 1.0 x_decimal = 3 y_major = 1.0 y_minor = 1.0 y_decimal = 3 tick_font = 'Helvetica 10' tick_length_choice = tick_length_choices[0] tick_major = 10 tick_minor = 5 self.file_name = file_name self.title = title self.x_axis_label = x_axis_label self.y_axis_label = y_axis_label self.paper_type = paper_type self.other_unit = other_unit self.other_size = other_size self.paper_orientation = paper_orientation self.output_style = output_style self.output_format = output_format self.tick_location = tick_location self.tick_placement = tick_placement self.border_decoration = border_decoration self.border_font = border_font self.spacing_choice = spacing_choice self.x_major = x_major self.x_minor = x_minor self.x_decimal = x_decimal self.y_major = y_major self.y_minor = y_minor self.y_decimal = y_decimal self.scaling_choice = scaling_choices[0] self.scaling = scaling self.x_scaling = x_scaling self.y_scaling = y_scaling self.tick_font = tick_font self.linewidth = linewidth self.tick_length_choice = tick_length_choice self.tick_major = tick_major self.tick_minor = tick_minor def setOptionValues(self): if not hasattr(self, 'file_entry'): # it looks like on destroy can have function called but file_entry deleted already return self.file_name = file_name = self.file_entry.get() self.title = title = self.title_entry.get() self.x_axis_label = x_axis_label = self.x_axis_entry.get() self.y_axis_label = y_axis_label = self.y_axis_entry.get() n = self.size_menu.getSelectedIndex() self.paper_type = paper_type = paper_types[n] if paper_type == Output.other_paper_type: other_size = self.other_entry.get() other_unit = self.other_unit_menu.getText() else: other_size = None other_unit = None self.other_size = other_size self.other_unit = other_unit self.paper_orientation = paper_orientation = self.orientation_menu.getText() self.output_style = output_style = self.style_menu.getText() self.output_format = output_format = self.format_menu.getText() if self.haveTicks: tick_location = self.tick_menu.getText() tick_placement = self.tick_buttons.getSelected() else: tick_location = tick_placement = None self.tick_location = tick_location self.tick_placement = tick_placement self.border_decoration = border_decoration = self.border_buttons.getSelected() self.border_font = border_font = self.border_font_list.getText() if self.haveTicks: self.spacing_choice = spacing_choice = self.spacing_menu.getText() if spacing_choice != spacing_choices[0]: self.x_major = self.x_major_entry.get() self.x_minor = self.x_minor_entry.get() self.x_decimal = self.x_decimal_entry.get() self.y_major = self.y_major_entry.get() self.y_minor = self.y_minor_entry.get() self.y_decimal = self.y_decimal_entry.get() self.tick_length_choice = tick_length_choice = self.tick_length_menu.getText() if tick_length_choice != tick_length_choices[0]: self.tick_major = self.length_major_entry.get() self.tick_minor = self.length_minor_entry.get() self.scaling_choice = scaling_choice = self.scaling_menu.getText() if self.scaling_choice == scaling_choices[0]: scaling = self.scaling_scale.get() self.scaling = int(round(scaling)) else: self.x_scaling = self.x_scaling_entry.get() self.y_scaling = self.y_scaling_entry.get() if self.haveTicks: self.tick_font = self.tick_font_list.getText() self.linewidth = self.linewidth_entry.get() setOption = self.setOption if setOption: setOption('FileName', value=file_name) setOption('Title', value=title) setOption('XAxisLabel', value=x_axis_label) setOption('YAxisLabel', value=y_axis_label) if paper_type == Output.other_paper_type: setOption('OtherHeight', value=other_size[0]) setOption('OtherWidth', value=other_size[1]) setOption('OtherUnit', value=other_unit) else: paper_type = paper_type_inverse_dict[paper_type] setOption('PaperSize', value=paper_type) setOption('Orientation', value=paper_orientation) in_color = (output_style == style_choices[0]) setOption('InColor', value=in_color) output_format = format_options[format_choices.index(output_format)] setOption('OutputFormat', value=output_format) if self.haveTicks: tick_outside = (tick_location == PrintTicks.Outside) setOption('TickOutside', value=tick_outside) tick_placement = getTickPlacement2(tick_placement) setOption('TickPlacement', value=tick_placement) dateTime = (border_decorations[0] in border_decoration) fileName = (border_decorations[1] in border_decoration) setOption('ShowsDateTime', value=dateTime) setOption('ShowsFileName', value=fileName) setOption('BorderFont', value=border_font) if self.haveTicks: setOption('SpacingChoice', value=spacing_choice) if spacing_choice != spacing_choices[0]: setOption('XMajor', self.x_major) setOption('XMinor', self.x_minor) setOption('XDecimal', self.x_decimal) setOption('YMajor', self.y_major) setOption('YMinor', self.y_minor) setOption('YDecimal', self.y_decimal) setOption('TickLengthChoice', value=tick_length_choice) if tick_length_choice != tick_length_choices[0]: setOption('TickMajor', self.tick_major) setOption('TickMinor', self.tick_minor) setOption('ScalingChoice', value=scaling_choice) if scaling_choice == scaling_choices[0]: setOption('Scaling', value=0.01*self.scaling) else: setOption('XScaling', value=self.x_scaling) setOption('YScaling', value=self.y_scaling) if self.haveTicks: setOption('TickFont', self.tick_font) setOption('LineWidth', self.linewidth) def findFile(self): if self.file_select_popup: self.file_select_popup.open() else: file_types = [ FileType('All', ['*']), FileType('PostScript', ['*.ps', '*.eps']), FileType('PDF', ['*.pdf', '*.ai']) ] self.file_select_popup = FileSelectPopup(self, file_types=file_types) file = self.file_select_popup.getFile() if file: self.file_entry.set(file) def changedSize(self, entry): if entry == Output.other_paper_type: self.other_frame.grid(row=0, column=2, columnspan=2, sticky='w') else: self.other_frame.grid_forget() def changedFormat(self, entry): file_suffix = file_suffixes.get(entry) if not file_suffix: return file_name = self.file_entry.get() if not file_name: return for suffix in format_suffixes: if file_name.endswith(suffix): if suffix != file_suffix: n = len(suffix) file_name = file_name[:-n] + file_suffix self.file_entry.set(file_name) break else: file_name = file_name + file_suffix self.file_entry.set(file_name) def changedScaling(self, choice): if choice == scaling_choices[0]: self.scaling_scale.grid(row=0, column=2, columnspan=3, sticky='ew') self.x_scaling_label.grid_forget() self.x_scaling_entry.grid_forget() self.y_scaling_label.grid_forget() self.y_scaling_entry.grid_forget() else: self.scaling_scale.grid_forget() self.x_scaling_label.grid(row=0, column=2, sticky='w') self.x_scaling_entry.grid(row=0, column=3, columnspan=2, sticky='ew') self.y_scaling_label.grid(row=1, column=2, sticky='w') self.y_scaling_entry.grid(row=1, column=3, columnspan=2, sticky='ew') self.setOptionValues() def changedSpacing(self, choice): if choice == spacing_choices[0]: self.spacing_frame.grid_forget() else: self.spacing_frame.grid(row=1, column=1, columnspan=3, sticky='ew') self.setOptionValues() def changedLength(self, choice): if choice == tick_length_choices[0]: self.length_frame.grid_forget() else: self.length_frame.grid(row=1, column=0, columnspan=4, sticky='ew') self.setOptionValues() # width and height are of plot, in pixels def getOutputHandler(self, pixel_width, pixel_height, unit_width=1.0, unit_height=1.0, fonts=None): if not fonts: fonts = [] else: fonts = list(fonts) for n in range(len(fonts)): if fonts[n] == 'Times': fonts[n] = 'Times-Roman' self.setOptionValues() if not self.file_name: showError('No file', 'No file specified', parent=self) return None x_scaling = y_scaling = 1.0 if self.scaling_choice != scaling_choices[0]: try: x_scaling = float(self.x_scaling) except: showError('Bad X Scaling', 'Specified X Scaling must be floating point', parent=self) return None try: y_scaling = float(self.y_scaling) except: showError('Bad Y Scaling', 'Specified Y Scaling must be floating point', parent=self) return None if os.path.exists(self.file_name): if not showYesNo('File exists', 'File "%s" exists, overwrite?' % self.file_name, parent=self): return None if self.paper_type == Output.other_paper_type: paper_size = self.other_size + [ self.other_unit ] else: paper_size = paper_sizes[self.paper_type] output_scaling = self.scaling / 100.0 border_font = self.border_font (font, size) = border_font.split() size = int(size) border_text = {} for decoration in self.border_decoration: if decoration == 'Time and Date': location = 'se' text = time.ctime(time.time()) elif decoration == 'File Name': location = 'sw' text = self.file_name else: continue # should not be here border_text[location] = (text, font, size) if self.title: location = 'n' border_text[location] = (self.title, font, size+6) if font not in fonts: fonts.append(font) if self.haveTicks and self.tick_location == PrintTicks.Outside: axis_label_offset = 2 else: axis_label_offset = 0 outputHandler = PrintHandler.getOutputHandler(self.file_name, pixel_width, pixel_height, unit_width, unit_height, scaling_choice=self.scaling_choice, output_scaling=output_scaling, w_scaling=x_scaling, h_scaling=y_scaling, paper_size=paper_size, paper_orientation=self.paper_orientation, output_style=self.output_style, output_format=self.output_format, border_text=border_text, x_axis_label=self.x_axis_label, y_axis_label=self.y_axis_label, axis_label_font=font, axis_label_size=size, axis_label_offset=axis_label_offset, fonts=fonts, linewidth=self.linewidth, do_outline_box=self.doOutlineBox) return outputHandler def getAspectRatio(self): self.setOptionValues() if self.paper_type == Output.other_paper_type: paper_size = self.other_size else: paper_size = paper_sizes[self.paper_type] r = paper_size[1] / paper_size[0] if self.paper_orientation == 'Landscape': r = 1.0 / r return r
class CreateAxisTypePopup(BasePopup): def __init__(self, parent, *args, **kw): self.measurementType = None BasePopup.__init__(self, parent=parent, title='Create axis type', modal=True, **kw) def body(self, master): master.grid_columnconfigure(1, weight=1) row = 0 label = Label(master, text='Axis name: ', grid=(row, 0)) tipText = 'Short text name for new type of axis e.g. "17O"' self.name_entry = Entry(master, width=15, grid=(row, 1), tipText=tipText) row += 1 label = Label(master, text='Axis region: ', grid=(row, 0)) tipText = 'Comma separated values for the upper and lower bound of the axis allowed range of values' self.region_entry = FloatEntry(master, text=[0.0, 1.0], isArray=True, width=15, grid=(row, 1), tipText=tipText) row += 1 label = Label(master, text='Measurement type:', grid=(row, 0)) tipText = 'The physical entity that is being measured along the axis' self.measurement_list = PulldownList(master, tipText=tipText) self.measurement_list.grid(row=row, column=1, sticky='w') row += 1 label = Label(master, text='Dimension is sampled: ', grid=(row, 0)) tipText = 'Whether the axis is discretely sampled or a continuous range (albeit on a grid)' self.sampled_popup = BooleanPulldownMenu(master, grid=(row, 1), tipText=tipText) row += 1 label = Label(master, text='Decimal places: ', grid=(row, 0)) tipText = 'The number of decimal places that the axis values are rounded to for display purposes' self.decimals_entry = IntEntry(master, text=0, width=15, grid=(row, 1), tipText=tipText) row += 1 label = Label(master, text='Peak size: ', grid=(row, 0)) tipText = 'The relative scale for the peak symbol (i.e the "X" shape) size compared to other axes' self.peak_size_entry = FloatEntry(master, text=1.0, width=15, grid=(row, 1), tipText=tipText) row += 1 label = Label(master, text='Allowed axis units:', grid=(row, 0)) tipTexts = [ 'Units of measurement allowed for this kind of axis', ] units = [au.unit for au in self.parent.getAxisUnits()] selected = [True] * len(units) self.units_list = CheckButtons(master, units, selected=selected, direction='vertical', grid=(row, 1), tipTexts=tipTexts) row += 1 tipTexts = [ 'Make a new axis specification of the selected type and close this 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.update() def update(self, *extra): measurementType = self.measurementType measurementTypes = self.parent.getMeasurementTypes() if measurementTypes: if measurementType not in measurementTypes: self.measurementType = measurementType = measurementTypes[0] index = measurementTypes.index(measurementType) else: index = 0 self.measurementType = None self.measurement_list.setup(measurementTypes, None, index) def apply(self): name = self.name_entry.get() if (not name): showError('No name', 'Need to enter name', self) return False names = [axisType.name for axisType in self.analysisProject.axisTypes] if (name in names): showError('Repeated name', 'Name already used', self) return False region = self.region_entry.get() if ((region is None) or (len(region) != 2)): showError('Region error', 'Region must be float array of length two', self) return False if (region[0] >= region[1]): showError('Region error', 'Region must have first number < second number', self) return False measurementType = self.measurement_list.getText() isSampled = self.sampled_popup.getSelected() numDecimals = self.decimals_entry.get() if ((numDecimals is None) or (numDecimals < 0)): showError('Decimals error', 'Number of decimal places must be >= 0', self) return False peakSize = self.peak_size_entry.get() if ((peakSize is None) or (peakSize <= 0)): showError('Peak size error', 'Peak size must be > 0', self) return False selected = self.units_list.getSelected() allUnits = self.parent.getAxisUnits() axisUnits = [au for au in allUnits if au.unit in selected] self.analysisProject.newAxisType(name=name, region=region, isSampled=isSampled, axisUnits=axisUnits, numDecimals=numDecimals, peakSize=peakSize, measurementType=measurementType) return True
class SelectionListPopup(TemporaryBasePopup): def __init__(self, parent, selectionList, title = 'Select', text = 'Select', topText = None, dismissText = None, selected = None, selectionDict = None, urlFile = None, dismissButton = True, modal = False): self.selectionList = selectionList self.selectionDict = selectionDict self.text = text self.dismissButton = dismissButton if dismissButton: if dismissText: self.dismissText = dismissText else: self.dismissText = 'dismiss' self.topText = topText self.isSelected = None if not selected: self.selectedIndex = 0 else: self.selectedIndex = self.selectionList.index(selected) if urlFile: self.help_url = joinPath(getHelpUrlDir(),urlFile + '.html') else: self.help_url = None TemporaryBasePopup.__init__(self,parent = parent, title = title, modal = modal, transient=True) def body(self, master): # # Popup window # row = 0 if self.topText: label = Label(master, text= self.topText) label.grid(row=row, column=0, columnspan = 2, sticky=Tkinter.EW) row = row + 1 label = Label(master, text= self.text) label.grid(row=row, column=0, sticky=Tkinter.EW) self.menu = PulldownList(master, texts = self.selectionList, index = self.selectedIndex) self.menu.grid(row=row, column=1, sticky=Tkinter.E, ipadx = 20) row = row + 1 texts = [ 'OK' ] commands = [ self.ok ] # This calls 'ok' in BasePopup, this then calls 'apply' in here if self.dismissButton: buttons = createDismissHelpButtonList(master, texts=texts, commands=commands, dismiss_text = self.dismissText, help_url=self.help_url) else: buttons = createHelpButtonList(master, texts=texts, commands=commands, help_url=self.help_url) buttons.grid(row=row, column=0, columnspan = 3) def apply(self): self.isSelected = self.menu.getText() if self.selectionDict: self.selection = self.selectionDict[self.isSelected] else: self.selection = self.isSelected return True