visbrain
visbrain copied to clipboard
Distinguishing N3 from N2 by computing length of slow waves with a line
Hey everybody, first of all congrats to such a nice toolbox! My team is considering using your toolbox as the new default program for sleep scoring data. However, for distinguishing between N2 and N3, we need some kind of ruler functionality, with which we can measure the length of slow waves (20% of slow waves in an epoch --> N3). The amplitude is easy to determine with the grid. But how do you determine whether 20% of the epoch contains slow waves? Our old program had a ruler or line (in green) that we could draw from positive to positive peak of a slow wave, which then added up the time in seconds of counted slow waves (top right corner). When 6s were reached, we staged the epoch as N3. Can such a feature be implemented? Thanks a lot!
(Note that negative is up in this recording)
@SvennoNito
Hey everybody, first of all congrats to such a nice toolbox!
Thanks, it's always pleasant to hear that !
However, for distinguishing between N2 and N3, we need some kind of ruler functionality, with which we can measure the length of slow waves (20% of slow waves in an epoch --> N3). The amplitude is easy to determine with the grid. But how do you determine whether 20% of the epoch contains slow waves?
@raphaelvallat and @TomBugnon are the two sleep experts. Could it be interesting to add it in Sleep?
Can such a feature be implemented?
Well, yes, since visbrain is an open source toolbox, you can fork it, make your changes and then we can discuss for a pull request to integrate it. One thing that should be noticed is that you can change the default detection algorithms of Sleep using your own detections. See replace_detections
Hey @EtienneCmb, thanks for your quick response. I wish I was that fluent in python that I could simply implement it, but I'll let you know if I accomplish it nevertheless. It's not a change in the detection algorithm though, but rather a new feature than when you click and hold at the recording, that you draw a horizontal line that measures time in seconds. If there's another way to distinguish N2 from N3, I'm of course also happy to hear that, the suggested idea was just the way we have done it so far and I haven't seen any alternative feature in the toolbox.
Hi @SvennoNito! I've worked with several sleep scoring software and never seen such a ruler to calculate the 20% threshold, but I can only admire such exactitude in determining the sleep stages!
I think it's gonna require some fundamental changes in the toolbox though, which also means a lot of time, which I'm not sure we have right now...That said, one feature that I've always wanted to add to sleep, and that could perhaps represent a first step here would be a similar "click-and-hold" marker to visually mark sleep spindles, arousals, etc.. Once we have that, I think it will be easy to just create a textbox that represents the cumulated duration of marked data ("label: slow-waves") in the current epoch. Does that make any sense? Any thoughts @EtienneCmb @TomBugnon @SvennoNito
Thanks again, Raphael
Hey @raphaelvallat. Yes such a feature would totally satisfy our need to compute the duration of slow waves per epoch. And it would be even more powerful in case you want to count other events. I like the idea. Especially if you could optionally export those event later on.
Best, Sven
Hey @raphaelvallat, just wanted to ask whether there has been any steps made into the direction of this feature? I think our lab is going to decide soon on a new program and I still would like to advert SLEEP ;-)
Best, Sven
Hi @SvennoNito! I am so sorry but I doubt we will implement this feature in the near future... I've too many other projects at the time.
Thanks, Raphael
Thank you @raphaelvallat, I can understand that, no worries.
Best, Sven
Dear @raphaelvallat,
I am still eager to introduce the toolbox to our lab and now tried to implement this feature myself, counting the length of slow waves per epoch. I managed to write code that effectively draws horizontal green lines between a mouse click and a mouse release and to show the accumulated length on the top right corner. What I terribly fail at, however, is integrating it into your toolbox. Maybe you could give me a hint?
I found that in visbrain\gui\sleep\visuals\visuals.py
, the class ChannelPlot(PrepareData)
seems to plot the EEG data. I tried to somehow implement the functions to draw a line in this class, but it's probably not the right way. Maybe it's already obvious for you where such a feature needs to go?
Here's the code:
import sys
from PyQt5.QtWidgets import QDialog, QApplication
from PyQt5.QtGui import QPainter, QPen, QPixmap
from PyQt5.QtCore import Qt, QLine, QPoint
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Dialog(object):
def setupUi(self, Dialog):
Dialog.resize(500, 340) # Build the surface
self.labelRelease = QtWidgets.QLabel(Dialog) # Dialog probably needs to be replaced with something from the toolbox
self.labelRelease.setGeometry(QtCore.QRect(450, 10, 45, 40)) # Size of green rectancle around the text showing the length of all lines drawn
font = QtGui.QFont()
font.setPointSize(12) # Font Size
self.labelRelease.setFont(font)
self.labelRelease.setText("") # Initialize empty text
self.labelRelease.setStyleSheet("border: 3px solid green;") # Green rectangle witch a thickness of 3 px
class GreenLines(QDialog):
def __init__(self):
super().__init__()
self.ui = Ui_Dialog()
self.ui.setupUi(self) # The setupUi() method sets up the widgets; it creates the widgets that you use while defining the user interface in Qt Designer.
self.pos1 = [0,0] # Mouse position at mouse click
self.pos2 = [0,0] # Mouse position at mouse release
self.totalLineLength = 0 # Length of all lines drawn
self.lines = [] # Stores all lines drawn
self.eraseLinesKeys = ['n', 'b', 'a', 'w', '1', '2', '3'] # Pressing one of these buttons will erase all lines drawn (should be adaptive if the user changes those keys)
self.reset = 0 # If 1, reset erase lines drawn
self.show()
def paintEvent(self, event):
# Draw horizontal lines between mouse click and mouse release
qp = QPainter()
qp.begin(self)
pen = QPen(Qt.darkGreen, 5) # Dark green line of width 5 px
pen.setStyle(Qt.SolidLine)
qp.setPen(pen)
if self.reset == 1:
qp.drawLine(0,0,0,0) # Erase all lines
self.ui.labelRelease.setText("") # Erase text showing length of lines
self.reset = 0
else:
p1 = QPoint(self.pos1[0], self.pos2[1]) # Line start
p2 = QPoint(self.pos2[0], self.pos2[1]) # Line end
self.lines.append(QLine(p1, p2)) # Store this line
for line in self.lines:
qp.drawLine(line) # Draw all lines
qp.end()
def mousePressEvent(self, event):
# Get mouse position at button press
if event.buttons() & QtCore.Qt.LeftButton:
self.pos1[0], self.pos1[1] = event.pos().x(), event.pos().y() # Mouse position
def mouseReleaseEvent(self, event):
# Get mouse position at button release
self.pos2[0], self.pos2[1] = event.pos().x(), event.pos().y() # Mouse position
self.totalLineLength += self.pos2[0] - self.pos1[0] # Length between button release and button press (still needs to converted into seconds)
self.ui.labelRelease.setText("{0}s".format(self.totalLineLength)) # Show line length
self.update() # Evoke paintEvent
def keyPressEvent(self, event):
# When keys are pressed, evoke the following functions
if event.text() in self.eraseLinesKeys:
self.resetLines()
def resetLines(self):
# Erases all lines drawn
self.pos1 = [0,0] # Reset mouse position at click
self.pos2 = [0,0] # Reset mouse position at release
self.lines = [] # Erases all lines drawn
self.totalLineLength = 0 # Set length of lines to 0
self.reset = 1 # To erase all lines in paintEvent
self.update() # Evple paintEvent
if __name__=="__main__":
app = QApplication(sys.argv)
w = GreenLines()
w.show()
sys.exit(app.exec_())
Thanks! I'm looking forward to your ideas and all best from Zurich, Sven
Hi @SvennoNito, thanks for sharing! The main PyQt interface was designed by @EtienneCmb so he will probably know better than me where such a code snippet should be integrated.
Best, Raphael
Thanks @raphaelvallat . What do you think @EtienneCmb ?