Source code for ccpn.ui.gui.popups.EstimateNoisePopup
"""
Module Documentation here
"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (http://www.ccpn.ac.uk) 2014 - 2021"
__credits__ = ("Ed Brooksbank, Joanna Fox, Victoria A Higman, Luca Mureddu, Eliza Płoskoń",
"Timothy J Ragan, Brian O Smith, Gary S Thompson & Geerten W Vuister")
__licence__ = ("CCPN licence. See http://www.ccpn.ac.uk/v3-software/downloads/license")
__reference__ = ("Skinner, S.P., Fogh, R.H., Boucher, W., Ragan, T.J., Mureddu, L.G., & Vuister, G.W.",
"CcpNmr AnalysisAssign: a flexible platform for integrated NMR analysis",
"J.Biomol.Nmr (2016), 66, 111-124, http://doi.org/10.1007/s10858-016-0060-y")
#=========================================================================================
# Last code modification
#=========================================================================================
__modifiedBy__ = "$modifiedBy: Ed Brooksbank $"
__dateModified__ = "$dateModified: 2021-06-04 19:38:31 +0100 (Fri, June 04, 2021) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: Ed Brooksbank $"
__date__ = "$Date: 2017-07-04 09:28:16 +0000 (Tue, July 04, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================
from ccpn.core.lib.SpectrumLib import setContourLevelsFromNoise, DEFAULTLEVELS, DEFAULTMULTIPLIER
from ccpn.core.lib.SpectrumLib import getNoiseEstimate, getNoiseEstimateFromRegion, getClippedRegion
from ccpn.util.OrderedSet import OrderedSet
from ccpn.ui.gui.widgets.Button import Button
from ccpn.ui.gui.widgets.ButtonList import ButtonList
from ccpn.ui.gui.widgets.Label import Label
from ccpn.ui.gui.widgets.Tabs import Tabs
from ccpn.ui.gui.widgets.DoubleSpinbox import ScientificDoubleSpinBox
from ccpn.ui.gui.widgets.Widget import Widget
from ccpn.ui.gui.widgets.Frame import Frame
from ccpn.ui.gui.widgets.HLine import HLine, LabeledHLine
from ccpn.ui.gui.widgets.CompoundWidgets import CheckBoxCompoundWidget, RadioButtonsCompoundWidget
from ccpn.ui.gui.popups.Dialog import CcpnDialogMainWidget
from ccpn.ui.gui.guiSettings import getColours, SOFTDIVIDER
COL_WIDTH = 140
NONE_TEXT = '-'
MINIMUM_WIDTH_PER_TAB = 100
MINIMUM_WIDTH = 400
MAXIMUM_WIDTH = 700
ESTIMATEMETHOD = 'estimateMethod'
ESTIMATECONTOURS = 'estimateContours'
ESTIMATEPOSITIVE = 'estimatePositive'
ESTIMATENEGATIVE = 'estimateNegative'
ESTIMATEMULTIPLIER = 'estimateMultiplier'
ESTIMATEDEFAULT = 'estimateDefault'
ESTIMATEAUTO = 'estimateAuto'
lineColour = getColours()[SOFTDIVIDER]
[docs]class EstimateNoisePopup(CcpnDialogMainWidget):
"""
Class to implement a popup for estimating noise in a set of spectra
"""
def __init__(self, parent=None, mainWindow=None, title='Estimate Noise',
strip=None, orderedSpectrumViews=None, **kwds):
"""
Initialise the widget
"""
super().__init__(parent, setLayout=True, windowTitle=title, **kwds)
# Derive application, project, and current from mainWindow
self.mainWindow = mainWindow
if mainWindow:
self.application = mainWindow.application
self.project = mainWindow.application.project
self.current = mainWindow.application.current
else:
self.application = None
self.project = None
self.current = None
# label the current strip
self.strip = strip
self.orderedSpectrumViews = orderedSpectrumViews
self.orderedSpectra = OrderedSet([spec.spectrum for spec in self.orderedSpectrumViews])
# create the list of widgets and set the callbacks for each
self._setWidgets()
# set up the required buttons for the dialog
self.setCloseButton(callback=self.accept, enabled=True)
self.setHelpButton(callback=self.reject, enabled=False)
self.setDefaultButton(CcpnDialogMainWidget.CLOSEBUTTON)
# populate the widgets
self._populate()
# set the links to the buttons
self.__postInit__()
self._okButton = self.dialogButtons.button(self.OKBUTTON)
self._helpButton = self.dialogButtons.button(self.HELPBUTTON)
# make automatic estimates
self._autoEstimate()
def _accept(self):
"""Close button pressed
"""
self.accept()
def _cleanupWidget(self):
"""Cleanup the notifiers that are left behind after the widget is closed
"""
self.close()
def _setWidgets(self):
row = -1
row += 1
self.topFrame = Frame(self.mainWidget, setLayout=True, grid=(row, 0), gridSpan=(1, 1), hPolicy='minimal')
row += 1
HLine(self.mainWidget, grid=(row, 0), gridSpan=(1, 4), colour=lineColour, height=20)
row += 1
self.stripLabel = Label(self.mainWidget, grid=(row, 0), gridSpan=(1, 4), bold=True)
self.stripLabel.setMinimumHeight(20)
row += 1
self.tabWidget = Tabs(self.mainWidget, setLayout=True, grid=(row, 0), gridSpan=(1, 4))
row += 1
self.contourFrame = Frame(self.mainWidget, setLayout=True, grid=(row, 0), gridSpan=(1, 4))
# add a tab for each spectrum in the spectrumDisplay
self._noiseTab = []
for specNum, thisSpec in enumerate(self.orderedSpectra):
if thisSpec.dimensionCount > 1:
self._noiseTab.append(NoiseTabNd(parent=self, mainWindow=self.mainWindow, spectrum=thisSpec, strip=self.strip))
else:
self._noiseTab.append(NoiseTab(parent=self, mainWindow=self.mainWindow, spectrum=thisSpec, strip=self.strip))
self.tabWidget.addTab(self._noiseTab[specNum], thisSpec.name)
self._setTopWidgets()
self._setContourWidgets()
if self.strip.spectrumDisplay.is1D:
self.contourFrame.hide()
def _setTopWidgets(self):
"Populate the top-frame"
row = 0
texts = ['Visible Area', 'Random Sampling']
tipTexts = ['Estimate the noise from the visible plane',
'Estimate the noise from a random sampling of the whole spectrum']
self.estimateOption = RadioButtonsCompoundWidget(self.topFrame, labelText='Estimation method',
grid=(row, 0), gridSpan=(1, 1), stretch=(0, 0),
compoundKwds={'direction': 'h',
'hPolicy' : 'minimal',
# 'selectedInd': 0,
'texts' : texts,
'tipTexts' : tipTexts},
callback=self._autoEstimate
)
# checkbox to recalculate on first popup - True to start
row += 1
self.autoCalculate = CheckBoxCompoundWidget(self.topFrame,
grid=(row, 0), vAlign='top', stretch=(0, 0), hAlign='left',
orientation='right',
labelText='Automatically estimate noise on popup',
checked=True)
def _setContourWidgets(self):
row = -1
row += 1
Label(self.contourFrame, text='Contour Options:', grid=(row, 0), gridSpan=(1, 3), vAlign='t', hAlign='l')
from ccpn.ui.gui.widgets.CompoundWidgets import CheckBoxCompoundWidget
row += 1
self.setPositiveContours = CheckBoxCompoundWidget(self.contourFrame, grid=(row, 0), gridSpan=(1, 3),
vAlign='top', stretch=(0, 0), hAlign='left',
orientation='right', margins=(15, 0, 0, 0),
labelText='Set positive contour levels',
checked=True
)
row += 1
self.setNegativeContours = CheckBoxCompoundWidget(self.contourFrame, grid=(row, 0), gridSpan=(1, 3),
vAlign='top', stretch=(0, 0), hAlign='left',
orientation='right', margins=(15, 0, 0, 0),
labelText='Set negative contour levels',
checked=True
)
row += 1
self.setUseSameMultiplier = CheckBoxCompoundWidget(self.contourFrame, grid=(row, 0), gridSpan=(1, 3),
vAlign='top', stretch=(0, 0), hAlign='left',
orientation='right', margins=(15, 0, 0, 0),
labelText='Use same (positive) multiplier for negative contours',
checked=True
)
row += 1
self.setDefaults = CheckBoxCompoundWidget(self.contourFrame, grid=(row, 0), gridSpan=(1, 3),
vAlign='top', stretch=(0, 0), hAlign='left',
orientation='right', margins=(15, 0, 0, 0),
labelText='Use default multiplier (%0.3f) and contour level count (%i)' % (
DEFAULTMULTIPLIER, DEFAULTLEVELS),
checked=True
)
# row += 1
# self.noiseLevelButton = Button(frame, grid=(row, 2), callback=self._setContourLevels, text=buttonLabel)
def _populate(self):
# populate any settings in the popup and the tabs
self.stripLabel.setText(f'Current strip: {self.strip.id}')
for tab in self._noiseTab:
tab._populate()
def _autoEstimate(self):
# calculate an estimate for each of the tabs
if self.autoCalculate.isChecked():
for tab in self._noiseTab:
if not tab._noiseFromCurrentCursorPosition:
tab._estimateNoise()
[docs] def storeWidgetState(self):
"""Store the state of the checkBoxes between popups
"""
for tab in self._noiseTab:
# may not be necessary
tab._storeWidgetState()
if ESTIMATECONTOURS not in EstimateNoisePopup._storedState:
EstimateNoisePopup._storedState[ESTIMATECONTOURS] = {ESTIMATEMETHOD: self.estimateOption.getIndex(),
ESTIMATEAUTO : self.autoCalculate.isChecked()
}
else:
EstimateNoisePopup._storedState[ESTIMATECONTOURS].update({ESTIMATEMETHOD: self.estimateOption.getIndex(),
ESTIMATEAUTO : self.autoCalculate.isChecked()
})
if not self.strip.spectrumDisplay.is1D:
EstimateNoisePopup._storedState[ESTIMATECONTOURS].update({ESTIMATEPOSITIVE : self.setPositiveContours.isChecked(),
ESTIMATENEGATIVE : self.setNegativeContours.isChecked(),
ESTIMATEMULTIPLIER: self.setUseSameMultiplier.isChecked(),
ESTIMATEDEFAULT : self.setDefaults.isChecked(),
})
[docs] def restoreWidgetState(self):
"""Restore the state of the checkBoxes
"""
for tab in self._noiseTab:
# may not be necessary
tab._restoreWidgetState()
states = EstimateNoisePopup._storedState.get(ESTIMATECONTOURS)
if states:
self.estimateOption.setIndex(states.get(ESTIMATEMETHOD))
self.autoCalculate.set(states.get(ESTIMATEAUTO))
if not self.strip.spectrumDisplay.is1D:
self.setPositiveContours.set(states.get(ESTIMATEPOSITIVE, False))
self.setNegativeContours.set(states.get(ESTIMATENEGATIVE, False))
self.setUseSameMultiplier.set(states.get(ESTIMATEMULTIPLIER, False))
self.setDefaults.set(states.get(ESTIMATEDEFAULT, False))
[docs]class NoiseTab(Widget):
"""Class to contain the information for a single pectrum in the spectrum display
Holds the common values for 1d and Nd spectra
"""
def __init__(self, parent=None, mainWindow=None, spectrum=None, strip=None, **kwds):
"""Initialise the tab settings
"""
super().__init__(parent, setLayout=True, **kwds)
self.setContentsMargins(5, 5, 5, 5)
self._parent = parent
self.mainWindow = mainWindow
self.current = self.mainWindow.current
self.spectrum = spectrum
self.strip = strip
self.noiseLevel = None
# create the list of widgets and set the callbacks for each
self._setWidgets()
self._noiseFromCurrentCursorPosition = False
self._setFromCurrentCursor()
def _setWidgets(self):
# set up the common widgets
row = -1
row += 1
LabeledHLine(self, text='Visible Area', style=HLine.DASH_LINE, colour=lineColour,
grid=(row,0), gridSpan=(1,3), height=10)
self.axisCodeLabels = []
for ii, axis in enumerate(self.strip.axisCodes):
row += 1
Label(self, text=axis, grid=(row, 0), vAlign='t', hAlign='l')
self.axisCodeLabels.append(Label(self, text=NONE_TEXT, grid=(row, 1), gridSpan=(1, 2), vAlign='t', hAlign='l'))
row += 1
LabeledHLine(self, text='Noise', style=HLine.DASH_LINE, colour=lineColour,
grid=(row,0), gridSpan=(1,3), height=10)
for label, text in zip(['meanLabel', 'SDLabel', 'maxLabel', 'minLabel'], ['Mean', 'SD', 'Max', 'Min']):
row += 1
Label(self, text=text, grid=(row, 0), vAlign='t', hAlign='l')
setattr(self, label, Label(self, text=NONE_TEXT, grid=(row, 1), gridSpan=(1, 2), vAlign='t', hAlign='l'))
row += 1
Label(self, text='Current noise level', grid=(row, 0), vAlign='c', hAlign='l')
self.currentNoiseLabel = Label(self, text=NONE_TEXT, grid=(row, 1), gridSpan=(1, 2), vAlign='c', hAlign='l')
row += 1
Label(self, text='Estimated noise level', grid=(row, 0), vAlign='c', hAlign='l')
self.noiseLevelSpinBox = ScientificDoubleSpinBox(self, grid=(row, 1), vAlign='t', decimals=1)
self.noiseLevelSpinBox.setMaximum(1e12)
self.noiseLevelSpinBox.setMinimum(0.1)
self.noiseLevelSpinBox.setMinimumCharacters(15)
self.recalculateLevelsButton = Button(self, grid=(row, 2), callback=self._estimateNoise, text='Re-estimate')
row += 1
self.addSpacer(20, 20, expandX=True, expandY=True, grid=(row,0), gridSpan=(1,3))
options = {}
row += 1
self.noiseLevelButtons = Button(self, grid=(row, 0), callback=self._setNoiseLevel,
text='Set Noise Level', **options)
self.noiseLevelToAllButtons = Button(self, grid=(row, 1), callback=self._setNoiseLevelToAll,
text='Set Noise Level To All', **options)
self.contoursButton = Button(self, grid=(row, 2), callback=self._setContourLevels,
text='Generate Contours', **options)
self.contoursButton.hide() # un-hidden for nD
# remember the row for subclassed Nd below
self.row = row
def _populate(self):
# populate the widgets, but don't perform any calculations
if self.spectrum.noiseLevel is not None:
self.currentNoiseLabel.setText(f'{self.spectrum.noiseLevel:8.1e}')
def _setFromCurrentCursor(self):
"""Add the initial spinbox value from the current cursor position. Implemented only for 1D.
"""
if self.mainWindow.current is not None:
if self.spectrum.dimensionCount == 1:
if self.current.cursorPosition:
self.noiseLevelSpinBox.set(float(self.current.cursorPosition[-1]))
self._noiseFromCurrentCursorPosition = True
def _estimateNoise(self):
# get the current mode and call the relevant estimate routine
ind = self._parent.estimateOption.getIndex()
if ind == 0:
self._estimateFromRegion()
elif ind == 1:
self._estimateFromRandomSamples()
def _estimateFromRegion(self):
# get the noise estimate for the region displayed in the strip
noise = getNoiseEstimateFromRegion(self.spectrum, self.strip)
if noise:
regions = getClippedRegion(self.spectrum, self.strip)
# populate the widgets
for ii, region in enumerate(regions):
self.axisCodeLabels[ii].setText('( ' + ', '.join(['%.1f' % rr for rr in region]) + ' )')
self._setLabels(noise.mean, noise.std, noise.min, noise.max, noise.noiseLevel)
def _estimateFromRandomSamples(self):
# populate the widgets
noise = getNoiseEstimate(self.spectrum)
# clear the range labels (full range is implied)
for lbl in self.axisCodeLabels:
lbl.setText('-')
self._setLabels(noise.mean, noise.std, noise.min, noise.max, noise.noiseLevel)
def _setLabels(self, mean, std, min, max, noiseLevel):
# fill the labels with the new values
self.meanLabel.setText(f'{mean:8.1e}')
self.SDLabel.setText(f'{std:8.1e}')
self.maxLabel.setText(f'{max:8.1e}')
self.minLabel.setText(f'{min:8.1e}')
self.noiseLevelSpinBox.setValue(noiseLevel)
def _setNoiseLevel(self):
"""Apply the current noiseLevel to the spectrum
"""
value = float(self.noiseLevelSpinBox.value())
self.spectrum.noiseLevel = value
self.spectrum.negativeNoiseLevel = -value if value > 0 else value * 2
self._populate()
def _setNoiseLevelToAll(self):
"""
Set the noise level from the current tab to all spectra.
"""
from ccpn.core.lib.ContextManagers import undoBlockWithoutSideBar
spectra = self._parent.orderedSpectra
value = float(self.noiseLevelSpinBox.value())
with undoBlockWithoutSideBar():
for spectrum in spectra:
spectrum.noiseLevel = value
spectrum.negativeNoiseLevel = -value if value > 0 else value * 2
for tab in self._parent._noiseTab:
tab._populate()
def _setContourLevels(self):
"""Estimate the contour levels for the current spectrum
"""
# get the settings from the parent checkboxes
setContourLevelsFromNoise(self.spectrum, setNoiseLevel=False,
setPositiveContours=self._parent.setPositiveContours.isChecked(),
setNegativeContours=self._parent.setNegativeContours.isChecked(),
useSameMultiplier=self._parent.setUseSameMultiplier.isChecked(),
useDefaultLevels=self._parent.setDefaults.isChecked(),
useDefaultMultiplier=self._parent.setDefaults.isChecked())
def _storeWidgetState(self):
"""Store the state of the checkBoxes between popups
"""
pass
def _restoreWidgetState(self):
"""Restore the state of the checkBoxes
"""
pass
[docs]class NoiseTabNd(NoiseTab):
"""Class to contain the information for a single spectrum in the spectrum display
Holds the extra widgets for changing Nd contour settings
"""
def __init__(self, parent=None, mainWindow=None, spectrum=None, strip=None, **kwds):
"""Initialise the tab settings
"""
super().__init__(parent=parent, mainWindow=mainWindow, spectrum=spectrum, strip=strip, **kwds)
self._parent = parent
def _setWidgets(self):
super()._setWidgets()
self.contoursButton.show()
# def _setContourLevels(self):
# """Estimate the contour levels for the current spectrum
# """
# # get the settings from the parent checkboxes
# setContourLevelsFromNoise(self.spectrum, setNoiseLevel=False,
# setPositiveContours=self._parent.setPositiveContours.isChecked(),
# setNegativeContours=self._parent.setNegativeContours.isChecked(),
# useSameMultiplier=self._parent.setUseSameMultiplier.isChecked(),
# useDefaultLevels=self._parent.setDefaults.isChecked(),
# useDefaultMultiplier=self._parent.setDefaults.isChecked())
# def _addContourNoiseButtons(self, row, frame, buttonLabel='Generate Contours'):
# row += 1
# self.noiseLevelButton = Button(frame, grid=(row, 2), callback=self._setContourLevels, text=buttonLabel)