Source code for ccpn.ui.gui.lib.GuiStrip1d

"""Module Documentation here

"""
#=========================================================================================
# Licence, Reference and Credits
#=========================================================================================
__copyright__ = "Copyright (C) CCPN project (https://www.ccpn.ac.uk) 2014 - 2022"
__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 https://ccpn.ac.uk/software/licensing/")
__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: Luca Mureddu $"
__dateModified__ = "$dateModified: 2022-04-05 12:14:15 +0100 (Tue, April 05, 2022) $"
__version__ = "$Revision: 3.1.0 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: CCPN $"
__date__ = "$Date: 2017-04-07 10:28:41 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================

from PyQt5 import QtWidgets
from ccpn.core.PeakList import PeakList
from ccpn.util import Phasing
from ccpn.ui.gui.lib.GuiStrip import GuiStrip, DefaultMenu, PeakMenu, \
    IntegralMenu, MultipletMenu, PhasingMenu, AxisMenu
from ccpn.ui.gui.widgets.PlaneToolbar import StripHeaderWidget, StripLabelWidget
import numpy as np
from ccpn.util.Logging import getLogger
from ccpn.ui.gui.lib.GuiStripContextMenus import _get1dPhasingMenu, _get1dDefaultMenu, \
    _get1dPeakMenu, _get1dIntegralMenu, _get1dMultipletMenu, _get1dAxisMenu
from ccpn.ui.gui.widgets.Frame import OpenGLOverlayFrame
from ccpn.ui.gui.widgets.Spacer import Spacer
from ccpn.util.Colour import colorSchemeTable


[docs]class GuiStrip1d(GuiStrip): """Strip class for display of 1D spectra This module inherits the following attributes from the Strip wrapper class: axisCodes Fixed string Axis codes in original display order :return <tuple>:(X, Y, Z1, Z2, ...) axisOrder String Axis codes in display order, determine axis display order axisOrder = <sequence>:(X, Y, Z1, Z2, ...) :return <tuple>:(X, Y, Z1, Z2, ...) positions Axis centre positions, in display order positions = <Tuple> :return <Tuple>:(<float>, ...) widths Axis display widths, in display order widths = <Tuple> :return <Tuple>:(<float>, ...) units Axis units, in display order :return <Tuple> spectra List of the spectra attached to the strip (whether display is currently turned on or not) :return <Tuple>:(<Spectrum>, ...) delete Delete a strip clone Create new strip that duplicates this one, appending it at the end moveTo Move strip to index newIndex in orderedStrips moveTo(newIndex:int) :param newIndex:<int> new index position findAxis Find axis findAxis(axisCode) :param axisCode: :return axis displaySpectrum Display additional spectrum on strip, with spectrum axes ordered according to axisOrder displaySpectrum(spectrum:Spectrum, axisOrder:Sequence=() :param spectrum:<Spectrum> additional spectrum to display :param axisOrder:<Sequence>=() new axis ordering peakIsInPlane Return whether the peak is in currently displayed planes for strip peakIsInPlane(peak:Peak) :param peak:<Peak> peak of interest :return <bool> peakIsInFlankingPlane Return whether the peak is in planes flanking currently displayed planes for strip peakIsInFlankingPlane(peak:Peak) :param peak:<Peak> peak of interest :return <bool> peakPickPosition Pick peak at position for all spectra currently displayed in strip peakPickPosition(position:List[float]) :param position:<List> coordinates to test :return <Tuple>:(<Peak>, ...) peakPickRegion Peak pick all spectra currently displayed in strip in selectedRegion selectedRegion:List[List[float]) :param selectedRegion:<List> of <List> of coordinates to test :return <Tuple>:(<Peak>, ...) """ # MAXPEAKLABELTYPES = 6 # MAXPEAKSYMBOLTYPES = 1 def __init__(self, spectrumDisplay): """ Initialise Nd spectra object :param spectrumDisplay Main spectrum display Module object """ GuiStrip.__init__(self, spectrumDisplay) # self.viewBox.invertX() # self.plotWidget.showGrid(x=False, y=False) # self.gridShown = True # self.viewBox.menu = _get1dDefaultMenu(self) # self._defaultMenu = self.viewBox.menu # keep a common stackItem for both menus self._stackSpectraMenuItem = None self._defaultMenu = _get1dDefaultMenu(self) self._phasingMenu = _get1dPhasingMenu(self) self._peakMenu = _get1dPeakMenu(self) self._integralMenu = _get1dIntegralMenu(self) self._multipletMenu = _get1dMultipletMenu(self) self._axisMenu = _get1dAxisMenu(self) self._contextMenus.update({DefaultMenu : self._defaultMenu, PhasingMenu : self._phasingMenu, PeakMenu : self._peakMenu, IntegralMenu : self._integralMenu, MultipletMenu: self._multipletMenu, AxisMenu : self._axisMenu }) # self.plotWidget.plotItem.setAcceptDrops(True) self.spectrumIndex = 0 self.peakItems = {} # self._hideCrosshair() self.calibrateX1DWidgets = None self.calibrateY1DWidgets = None self.offsetWidget = None self.offsetValue = (0.0, 0.0) self.widgetIndex = 4 #start adding widgets from row 4 #~~~~~~~~~~~~~~~~~~~~~~~~~~~ # TEST: ED new plane widgets self.planeToolbar = None # set the axis controlled by the wheelMouse events self.activePlaneAxis = None self.zPlaneFrame = None # a large(ish) unbound widget to contain the text - may need more rows self._frameGuide = OpenGLOverlayFrame(self, setLayout=True) # self._frameGuide.setFixedSize(400, 400) # add spacer to the top left corner self._frameGuide.addSpacer(8, 8, grid=(1, 0)) row = 2 self.stripLabel = StripLabelWidget(qtParent=self._frameGuide, mainWindow=self.mainWindow, strip=self, grid=(row, 1), gridSpan=(1, 1)) row += 1 # set the ID label in the new widget self.stripLabel._populate() self.header = StripHeaderWidget(qtParent=self._frameGuide, mainWindow=self.mainWindow, strip=self, grid=(row, 1), gridSpan=(1, 1)) row += 1 Spacer(self._frameGuide, 1, 1, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding, grid=(row, 2)) #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ self.spectrumDisplay.phasingFrame.applyCallback = self._applyPhasing self.spectrumDisplay.phasingFrame.applyButton.setEnabled(True) self._noiseThresholdLines = set() @property def symbolType(self): """Get the symbol type for the strip """ return self._CcpnGLWidget._symbolType @symbolType.setter def symbolType(self, value): """Set the symbol type for the strip """ if not isinstance(value, int): raise TypeError('Error: symbolType not an int') oldValue = self._CcpnGLWidget._symbolType self._CcpnGLWidget._symbolType = value if (value in range(self.spectrumDisplay.MAXPEAKSYMBOLTYPES)) else 0 if self._CcpnGLWidget._symbolType in [1, 2]: self._CcpnGLWidget._symbolType = 3 if value != oldValue: self._setSymbolType() if self.spectrumViews: self._emitSymbolChanged() def _resize(self): """Resize event to handle resizing of frames that overlay the OpenGL frame """ self._frameGuide._resizeFrames() def _checkMenuItems(self): """Update the menu check boxes from the strip """ if self._defaultMenu: item = self.mainWindow.getMenuAction('Stack Spectra', self._defaultMenu) item.setChecked(self._CcpnGLWidget._stackingMode) if self._phasingMenu: item = self.mainWindow.getMenuAction('Stack Spectra', self._phasingMenu) item.setChecked(self._CcpnGLWidget._stackingMode)
[docs] def showExportDialog(self): """show the export strip to file dialog """ from ccpn.ui.gui.popups.ExportStripToFile import ExportStripToFilePopup as ExportDialog self.exportPdf = ExportDialog(parent=self.mainWindow, mainWindow=self.mainWindow, strips=self.spectrumDisplay.strips, ) self.exportPdf.exec_()
def _applyPhasing(self, phasingValues): """apply the phasing values phasingValues = { 'direction': 'horizontal', 'horizontal': {'ph0': float, 'ph1': float, 'pivot': float}} """ values = phasingValues.get('horizontal') ph0 = values.get('ph0') ph1 = values.get('ph1') pivot = values.get('pivot') spectrumViews = self.spectrumViews for spectrum in [sv.spectrum for sv in spectrumViews if sv.isDisplayed]: intensities = Phasing.phaseRealData(spectrum.intensities, ph0, ph1, pivot) spectrum.intensities = intensities self.spectrumDisplay.togglePhaseConsole()
[docs] def autoRange(self): try: self._CcpnGLWidget.autoRange() except Exception as es: getLogger().debugGL('OpenGL widget not instantiated', strip=self, error=es)
[docs] def flipXYAxis(self): """ Flip the X and Y axes """ getLogger().warning('Function not permitted on 1D spectra')
[docs] def flipXZAxis(self): """ Flip the X and Y axes """ getLogger().warning('Function not permitted on 1D spectra')
[docs] def flipYZAxis(self): """ Flip the X and Y axes """ getLogger().warning('Function not permitted on 1D spectra')
def _findPeakListView(self, peakList: PeakList): #peakListView = self.peakListViewDict.get(peakList) #if peakListView: # return peakListView # NBNB TBD FIXME - why is this different from nD version? is self.peakListViews: even set? for peakListView in self.peakListViews: if peakList is peakListView.peakList: #self.peakListViewDict[peakList] = peakListView return peakListView def _addCalibrate1DXSpectrumWidget(self): """Add a new widget for calibrateX. """ from ccpn.ui.gui.widgets.CalibrateXSpectrum1DWidget import CalibrateX1DWidgets sdWid = self.spectrumDisplay.mainWidget self.widgetIndex += 1 self.calibrateX1DWidgets = CalibrateX1DWidgets(sdWid, mainWindow=self.mainWindow, strip=self, grid=(self.widgetIndex, 0), gridSpan=(1, 7))
[docs] def toggleCalibrateX(self): if self.calibrateXAction.isChecked(): if self.calibrateX1DWidgets is None: self._addCalibrate1DXSpectrumWidget() self.calibrateX1DWidgets.setVisible(True) self.calibrateX1DWidgets._toggleLines() else: self.calibrateX1DWidgets.setVisible(False) self.calibrateX1DWidgets._toggleLines()
def _addCalibrate1DYSpectrumWidget(self): """Add a new widget for calibrateY. """ from ccpn.ui.gui.widgets.CalibrateYSpectrum1DWidget import CalibrateY1DWidgets sdWid = self.spectrumDisplay.mainWidget self.widgetIndex += 1 self.calibrateY1DWidgets = CalibrateY1DWidgets(sdWid, mainWindow=self.mainWindow, strip=self, grid=(self.widgetIndex, 0), gridSpan=(1, 7)) def _removeNoiseThresholdLines(self): for line in self._noiseThresholdLines: if line is not None: self._CcpnGLWidget.removeInfiniteLine(line) def _updateNoiseThresholdLines(self): """Re-draw the lines """ self._removeNoiseThresholdLines() self._initNoiseThresholdLines()
[docs] def toggleNoiseThresholdLines(self, *args): value = self.sender().isChecked() self._noiseThresholdLinesActive = value self._updateNoiseThresholdLines()
def _updateVisibility(self): """Update visibility list in the OpenGL """ self._CcpnGLWidget.updateVisibleSpectrumViews() self._updateNoiseThresholdLines() def _initNoiseThresholdLines(self): from ccpn.util.Colour import hexToRgbRatio from functools import partial if not self._noiseThresholdLinesActive: return visibleSpectrumViews = [sv.spectrum for sv in self.spectrumViews if sv.isDisplayed] for spectrum in visibleSpectrumViews: posValue = spectrum.noiseLevel or spectrum.estimateNoise() negValue = spectrum.negativeNoiseLevel or -posValue brush = hexToRgbRatio(spectrum.sliceColour) + (0.3,) # sliceCol plus an offset positiveLine = self._CcpnGLWidget.addInfiniteLine(values=posValue, colour=brush, movable=True, lineStyle='dashed', lineWidth=2.0, obj=spectrum, orientation='h',) negativeLine = self._CcpnGLWidget.addInfiniteLine(values=negValue, colour=brush, movable=True, lineStyle='dashed', obj=spectrum, orientation='h',lineWidth=2.0) positiveLine.valuesChanged.connect(partial(self._lineThresholdChanged, positiveLine, spectrum, setPositiveThreshold=True)) negativeLine.valuesChanged.connect(partial(self._lineThresholdChanged, negativeLine, spectrum, setPositiveThreshold=False)) self._noiseThresholdLines.add(positiveLine) self._noiseThresholdLines.add(negativeLine) def _lineThresholdChanged(self, line, spectrum, setPositiveThreshold): """ set the noise threshold to the spectrum based on the line move. TODO add notifier when editing is finished (so to set the noiseLevel once only).""" value = line.values if spectrum is not None: if setPositiveThreshold: spectrum.noiseLevel = value else: spectrum.negativeNoiseLevel = value
[docs] def toggleCalibrateY(self): if self.calibrateYAction.isChecked(): if self.calibrateY1DWidgets is None: self._addCalibrate1DYSpectrumWidget() self.calibrateY1DWidgets.setVisible(True) self.calibrateY1DWidgets._toggleLines() else: self.calibrateY1DWidgets.setVisible(False) self.calibrateY1DWidgets._toggleLines()
def _closeCalibrateX(self): self.calibrateXAction.setChecked(False) self.toggleCalibrateX() def _closeCalibrateY(self): self.calibrateYAction.setChecked(False) self.toggleCalibrateY() def _getInitialOffset(self): offSets = [] offSet = 0 # Default for i, spectrumView in enumerate(self.spectrumViews): sp = spectrumView.spectrum y = sp.intensities offSet = np.std(y) offSets.append(offSet) if len(offSets) > 0: offSet = np.mean(offSets) return offSet def _toggleOffsetWidget(self): from ccpn.ui.gui.widgets.Stack1DWidget import Offset1DWidget if self.offsetWidget is None: sdWid = self.spectrumDisplay.mainWidget self.widgetIndex += 1 self.offsetWidget = Offset1DWidget(sdWid, mainWindow=self.mainWindow, strip1D=self, grid=(self.widgetIndex, 0)) initialOffset = self._getInitialOffset() # offset is now a tuple self.offsetWidget.setValue((0.0, initialOffset)) self.offsetWidget.setVisible(True) else: self.offsetWidget.setVisible(not self.offsetWidget.isVisible())
[docs] def setStackingMode(self, value): if value != self.stackAction.isChecked(): self.stackAction.setChecked(value) self._toggleStack()
[docs] def getStackingMode(self): return self.stackAction.isChecked()
def _toggleStack(self): """Toggle stacking mode for 1d spectra This vertically stacks the spectra for clarity """ if self.stackAction.isChecked(): self._toggleOffsetWidget() self._stack1DSpectra(self.offsetWidget.value()) else: self._toggleOffsetWidget() try: self._CcpnGLWidget.setStackingMode(False) except: getLogger().debugGL('OpenGL widget not instantiated') def _toggleStackPhaseFromShortCut(self): self.stackActionPhase.setChecked(not self.stackActionPhase.isChecked()) self._toggleStackPhase() def _toggleStackPhase(self): """Toggle stacking mode for 1d spectra This vertically stacks the spectra for clarity """ if self.stackActionPhase.isChecked(): self._toggleOffsetWidget() self._stack1DSpectra(self.offsetWidget.value()) else: self._toggleOffsetWidget() try: self._CcpnGLWidget.setStackingMode(False) except: getLogger().debugGL('OpenGL widget not instantiated') def _stack1DSpectra(self, offSet=(0.0, 0.0)): try: self._CcpnGLWidget.setStackingValue(offSet) self._CcpnGLWidget.setStackingMode(True) except: getLogger().debugGL('OpenGL widget not instantiated')
[docs] def toggleHorizontalTrace(self): """Toggles whether or not horizontal trace is displayed. """ pass
[docs] def toggleVerticalTrace(self): """Toggles whether or not vertical trace is displayed. """ pass
# def cycleSymbolLabelling(self): # """Toggles whether peak labelling is minimal is visible in the strip. # """ # pass # def cyclePeakSymbols(self): # """Cycle through peak symbol types. # """ # self.symbolType += 1 def _createObjectMark(self, obj, axisIndex=None): """Create a mark at the object position. Could be Peak/Multiplet """ try: _prefsGeneral = self.application.preferences.general defaultColour = _prefsGeneral.defaultMarksColour if not defaultColour.startswith('#'): colourList = colorSchemeTable[defaultColour] if defaultColour in colorSchemeTable else ['#FF0000'] _prefsGeneral._defaultMarksCount = _prefsGeneral._defaultMarksCount % len(colourList) defaultColour = colourList[_prefsGeneral._defaultMarksCount] _prefsGeneral._defaultMarksCount += 1 except: defaultColour = '#FF0000' try: # defaultColour = self._preferences.defaultMarksColour position = (obj.ppmPositions[0], obj.height) axisCodes = self.axisCodes if axisIndex is not None: if (0 <= axisIndex < 2): position = (position[axisIndex],) axisCodes = (axisCodes[axisIndex],) else: return self.mainWindow.newMark(defaultColour, position, axisCodes) except Exception as es: getLogger().warning('Error setting mark at position') raise (es)
# def changeZPlane(self, n: int = 0, planeCount: int = None, position: float = None): # """ # Changes the position of the z axis of the strip by number of planes or a ppm position, depending # on which is specified. # """ # # Not implemented for 1d strips # pass # def _setPlaneAxisWidgets(self, ignoreSpectrumView=None): # """ # # CCPN INTERNAL - Sets values for the widgets in the plane toolbar. # """ # # Not implemented for 1d strips # pass