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

"""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: Geerten Vuister $"
__dateModified__ = "$dateModified: 2021-12-23 13:15:21 +0000 (Thu, December 23, 2021) $"
__version__ = "$Revision: 3.0.4 $"
#=========================================================================================
# Created
#=========================================================================================
__author__ = "$Author: CCPN $"
__date__ = "$Date: 2017-04-07 10:28:41 +0000 (Fri, April 07, 2017) $"
#=========================================================================================
# Start of code
#=========================================================================================


import collections
import traceback
from ccpn.util import Colour
from PyQt5 import QtCore, QtGui, QtWidgets
from ccpn.util.Logging import getLogger
from ccpn.core.lib.Notifiers import Notifier
from typing import Optional, Tuple


SpectrumViewParams = collections.namedtuple('SpectrumViewParams', 'valuePerPoint pointCount minAliasedFrequency maxAliasedFrequency '
                                                                  'minSpectrumFrequency maxSpectrumFrequency axisReversed '
                                                                  'spectralWidth aliasingIndex foldingMode regionBounds '
                                                                  'minFoldingFrequency maxFoldingFrequency isTimeDomain ')
TraceParameters = collections.namedtuple('TraceParameters', 'inRange pointPositions startPoint, endPoint')


[docs]class GuiSpectrumView(QtWidgets.QGraphicsObject): #def __init__(self, guiSpectrumDisplay, apiSpectrumView, dimMapping=None): def __init__(self): """ spectrumPane is the parent spectrum is the Spectrum object dimMapping is from spectrum numerical dimensions to spectrumPane numerical dimensions (for example, xDim is what gets mapped to 0 and yDim is what gets mapped to 1) """ QtWidgets.QGraphicsItem.__init__(self) self.spectrumGroupsToolBar = None action = self.strip.spectrumDisplay.spectrumActionDict.get(self.spectrum) if action and not action.isChecked(): self.setVisible(False) self._showContours = True # To write your own graphics item, you first create a subclass of QGraphicsItem, and # then start by implementing its two pure virtual public functions: # boundingRect(), which returns an estimate of the area painted by the item, # and paint(), which implements the actual painting. For example: # mandatory function to override for QGraphicsItem # Implemented in GuiSpectrumViewNd or GuiSpectrumView1d
[docs] def paint(self, painter, option, widget=None): pass
# def updateGeometryChange(self): # ejb - can we call this? # self._currentBoundingRect = self.strip.plotWidget.sceneRect() # self.prepareGeometryChange() # # print ('>>>prepareGeometryChange', self._currentBoundingRect) # mandatory function to override for QGraphicsItem # def boundingRect(self): # seems necessary to have # return self._currentBoundingRect # Earlier versions too large value (~1400,1000); # i.e larger then initial MainWindow size; reduced to (900, 700); but (100, 150) appears # to give less flicker in Scrolled Strips. # override of Qt setVisible
[docs] def setVisible(self, visible): """Change visible state of the spectrumView """ QtWidgets.QGraphicsItem.setVisible(self, visible) self.isDisplayed = visible try: action = self.strip.spectrumDisplay.spectrumActionDict.get(self.spectrum) if action: action.setChecked(visible) except Exception as es: getLogger().debug(f'Error in setVisible {es}') # gwv changed to debug to reduce output self._notifyChange()
[docs] def setDisplayed(self, visible): """Change visible state of the spectrumView """ QtWidgets.QGraphicsItem.setVisible(self, visible) self.isDisplayed = visible try: action = self.strip.spectrumDisplay.spectrumActionDict.get(self.spectrum) if action: action.setChecked(visible) except Exception as es: getLogger().debug(f'Error in setDisplayed {es}') # gwv changed to debug to reduce output self._notifyChange()
def _notifyChange(self): # repaint all displays - this is called for each spectrumView in the spectrumDisplay # all are attached to the same click from ccpn.ui.gui.lib.OpenGL.CcpnOpenGL import GLNotifier GLSignals = GLNotifier(parent=self) GLSignals.emitPaintEvent() # notify that the spectrumView has changed # self._finaliseAction('change') def _getSpectrumViewParams(self, axisDim: int) -> Optional[Tuple]: """Get parameters for axisDim'th axis (zero-origin) of spectrum. Returns None or namedTuple of the form: valuePerPoint ppm width spanning a point value pointCount number of points in the dimension minAliasingFrequency minimum aliasing frequency maxAliasingFrequency maximum aliasing frequency aliasing frequencies define the span of the spectrum, beyond the range of the defined points for aliasingIndex (0, 0) this will correspond to points [0.5], [pointCount + 0.5] minSpectrumFrequency minimum spectrum frequency maxSpectrumFrequency maximum spectrum frequency spectrum frequencies defined by the ppm positions of points [1] and [pointCount] axisReversed True if the point [pointCount] corresponds to the maximum spectrum frequency spectralWidth maxSpectrumFrequency - minSpectrumFrequency aliasingIndex a tuple (min, max) defining how many integer multiples the aliasing frequencies span the spectrum (0, 0) implies the aliasing range matches the spectral range (s, t) implies: the minimum limit = minSpectrumFrequency - 's' spectral widths - should always be negative or zero the maximum limit = maxSpectrumFrequency + 't' spectral widths - should always be positive or zero foldingMode the type of folding: 'circular', 'mirror' or None regionBounds a tuple of ppm values (min, ..., max) in multiples of the spectral width from the lower aliasingLimit to the upper aliasingLimit minFoldingFrequency minimum folding frequency maxFoldingFrequency maximum folding frequency folding frequencies define the ppm positions of points [0.5] and [pointCount + 0.5] currently the exact ppm at which the spectrum is folded isTimeDomain True if the axis is a time domain, otherwise False """ ii = self.dimensionIndices[axisDim] if ii is not None: _spectrum = self.spectrum # extent of the spectrum minAliasedFrequency, maxAliasedFrequency = (_spectrum.aliasingLimits)[ii] # ppmPositions of the first and last point minSpectrumFrequency, maxSpectrumFrequency = sorted(_spectrum.spectrumLimits[ii]) # spectrumLimits extended by 0.5point on either end minFoldingFrequency, maxFoldingFrequency = sorted(_spectrum.foldingLimits[ii]) pointCount = (_spectrum.pointCounts)[ii] valuePerPoint = (_spectrum.ppmPerPoints)[ii] axisReversed = (_spectrum.axesReversed)[ii] spectralWidth = (_spectrum.spectralWidths)[ii] aliasingIndex = (_spectrum.aliasingIndexes)[ii] foldingMode = (_spectrum.foldingModes)[ii] regionBounds = tuple(minFoldingFrequency + ii * spectralWidth for ii in range(aliasingIndex[0], aliasingIndex[1] + 2)) isTimeDomain = (_spectrum.isTimeDomains)[ii] return SpectrumViewParams(valuePerPoint, pointCount, minAliasedFrequency, maxAliasedFrequency, minSpectrumFrequency, maxSpectrumFrequency, axisReversed, spectralWidth, aliasingIndex, foldingMode, regionBounds, minFoldingFrequency, maxFoldingFrequency, isTimeDomain)
[docs] def getTraceParameters(self, position, dim): # dim = spectrumView index, i.e. 0 for X, 1 for Y _indices = self.dimensionIndices index = _indices[dim] if index is None: getLogger().warning('getTraceParameters: bad index') return pointCount = self.spectrum.pointCounts[index] minSpectrumFrequency, maxSpectrumFrequency = sorted(self.spectrum.spectrumLimits[index]) inRange = (minSpectrumFrequency <= position[index] <= maxSpectrumFrequency) pointPos = (self.spectrum.ppm2point(position[index], dimension=index + 1) - 1) % pointCount return TraceParameters(inRange, pointPos, 0, pointCount - 1)
def _getColour(self, colourAttr, defaultColour=None): colour = getattr(self, colourAttr) if not colour: colour = defaultColour colour = Colour.colourNameToHexDict.get(colour, colour) # works even if None return colour
[docs] def refreshData(self): raise Exception('Needs to be implemented in subclass')
def _spectrumViewHasChanged(data): """Change action icon colour and other changes when spectrumView changes. NB SpectrumView change notifiers are triggered when either DataSource or ApiSpectrumView change. """ self = data[Notifier.OBJECT] if self.isDeleted: return spectrumDisplay = self.strip.spectrumDisplay # Update action icon colour action = spectrumDisplay.spectrumActionDict.get(self.spectrum) if action: # add spectrum action for non-grouped action _addActionIcon(action, self, spectrumDisplay) # if spectrumDisplay.isGrouped and self in spectrumDisplay.spectrumViews: # if hasattr(self, '_guiChanged'): if self in spectrumDisplay.spectrumViews and hasattr(self, '_guiChanged'): del self._guiChanged from ccpn.ui.gui.lib.OpenGL.CcpnOpenGL import GLNotifier GLSignals = GLNotifier(parent=self) self.buildContoursOnly = True # repaint GLSignals.emitPaintEvent() # Update strip self.strip.update() def _addActionIcon(action, self, spectrumDisplay): _iconX = int(60 / spectrumDisplay.devicePixelRatio()) _iconY = int(10 / spectrumDisplay.devicePixelRatio()) pix = QtGui.QPixmap(QtCore.QSize(_iconX, _iconY)) if getattr(self, '_showContours', True) or spectrumDisplay.isGrouped: if spectrumDisplay.is1D: _col = self.sliceColour else: _col = self.positiveContourColour if _col and _col.startswith('#'): pix.fill(QtGui.QColor(_col)) elif _col in Colour.colorSchemeTable: colourList = Colour.colorSchemeTable[_col] step = _iconX stepX = _iconX stepY = len(colourList) - 1 jj = 0 painter = QtGui.QPainter(pix) for ii in range(_iconX): _interp = (stepX - step) / stepX _intCol = Colour.interpolateColourHex(colourList[min(jj, stepY)], colourList[min(jj + 1, stepY)], _interp) painter.setPen(QtGui.QColor(_intCol)) painter.drawLine(ii, 0, ii, _iconY) step -= stepY while step < 0: step += stepX jj += 1 painter.end() else: pix.fill(QtGui.QColor('gray')) else: pix.fill(QtGui.QColor('gray')) action.setIcon(QtGui.QIcon(pix))