Saludos, hace varios días que estoy intentando modificar la escala de la grilla de un software en Python pero por más que lo intento no lo logro, agradecería muchísimo si alguien me ayuda a realizar la modificación o si me guía en el proceso, lo que necesito es que la grilla esté dividida en cuadros de 40 mili segundos, en la pantalla se muestran 5 segundos, eso quiere decir que dentro de cada segundo tendría que haber 25 cuadritos.
Esta es la primera parte del código. El programa tiene varios archivos.
Esta es la segunda parte.
Esta la tercera parte.
Básicamente necesito que se vea así.
Ya que originalmente se ve así.
Este es el software en github.
En la documentación aquí se explica cómo lograrlo, pero aún así no ha sido posible.
Muchas gracias de ante mano .
Esta es la primera parte del código. El programa tiene varios archivos.
Código:
"""
diyECG GUI for monitoring live ECG through sound card input (by Scott Harden).
If you haven't built the circuit, test this software by playing demo_ecg.wav
while you run this software. If you don't use a virtual audio cable to connect
the output (speaker jack) to the input (default microphone jack), consider
running an actual cable to connect these two.
"""
from PyQt4 import QtGui,QtCore
import sys
import ui_main
import numpy as np
import pyqtgraph
import swhear
import time
import pyqtgraph.exporters
import webbrowser
class ExampleApp(QtGui.QMainWindow, ui_main.Ui_MainWindow):
def __init__(self, parent=None):
pyqtgraph.setConfigOption('background', 'w') #before loading widget
super(ExampleApp, self).__init__(parent)
self.setupUi(self)
self.grECG.plotItem.showGrid(True, True, 0.7)
self.btnSave.clicked.connect(self.saveFig)
self.btnSite.clicked.connect(self.website)
stamp="DIY ECG by Scott Harden"
self.stamp = pyqtgraph.TextItem(stamp,anchor=(-.01,1),color=(150,150,150),
fill=pyqtgraph.mkBrush('w'))
self.ear = swhear.Ear(chunk=int(100)) # determines refresh rate
# optionally you can manually set the audio input device to use like this:
# self.ear = swhear.Ear(chunk=int(100), device=5) # use audio input device 5
if len(self.ear.valid_input_devices()):
self.ear.stream_start()
self.lblDevice.setText(self.ear.msg)
self.update()
def closeEvent(self, event):
self.ear.close()
event.accept()
def saveFig(self):
fname="ECG_%d.png"%time.time()
exp = pyqtgraph.exporters.ImageExporter(self.grECG.plotItem)
exp.parameters()['width'] = 1000
exp.export(fname)
print("saved",fname)
def update(self):
t1,timeTook=time.time(),0
if len(self.ear.data) and not self.btnPause.isChecked():
freqHighCutoff=0
if self.spinLowpass.value()>0:
freqHighCutoff=self.spinLowpass.value()
data=self.ear.getFiltered(freqHighCutoff)
if self.chkInvert.isChecked():
data=np.negative(data)
if self.chkAutoscale.isChecked():
self.Yscale=np.max(np.abs(data))*1.1
self.grECG.plotItem.setRange(xRange=[0,self.ear.maxMemorySec],
yRange=[-self.Yscale,self.Yscale],padding=0)
self.grECG.plot(np.arange(len(data))/float(self.ear.rate),data,clear=True,
pen=pyqtgraph.mkPen(color='r'),antialias=True)
self.grECG.plotItem.setTitle(self.lineTitle.text(),color=(0,0,0))
self.stamp.setPos(0,-self.Yscale)
self.grECG.plotItem.addItem(self.stamp)
timeTook=(time.time()-t1)*1000
print("plotting took %.02f ms"%(timeTook))
msTillUpdate=int(self.ear.chunk/self.ear.rate*1000)-timeTook
QtCore.QTimer.singleShot(max(0,msTillUpdate), self.update)
def website(self):
webbrowser.open("http://www.SWHarden.com")
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
form = ExampleApp()
form.show()
app.exec_()
Esta es la segunda parte.
Código:
"""
The core SWHEar class for continuously monitoring microphone data.
The Ear class is the primary method used to access microphone data.
It has extra routines to audomatically detec/test sound card, channel,
and rate combinations to maximize likelyhood of finding one that
works on your system (all without requiring user input!)
Although this was designed to be a package, it's easy enough to work
as an entirely standalone python script. Just drop this .py file in
your project and import it by its filename. Done!
"""
import pyaudio
import time
import numpy as np
import threading
import scipy.io.wavfile
def FFT(data,rate):
"""given some data points and a rate, return [freq,power]"""
data=data*np.hamming(len(data))
fft=np.fft.fft(data)
fft=10*np.log10(np.abs(fft))
freq=np.fft.fftfreq(len(fft),1/rate)
return freq[:int(len(freq)/2)],fft[:int(len(fft)/2)]
class Ear(object):
def __init__(self,device=None,rate=None,chunk=4096,maxMemorySec=5):
"""
Prime the Ear class to access audio data. Recording won't start
until stream_start() is called.
- if device is none, will use first valid input (not system default)
- if a rate isn't specified, the lowest usable rate will be used.
"""
# configuration
self.chunk = chunk # doesn't have to be a power of 2
self.maxMemorySec = maxMemorySec # delete if more than this around
self.device=device
self.rate=rate
# internal variables
self.chunksRecorded=0
self.p=pyaudio.PyAudio() #keep this forever
self.t=False #later will become threads
### SOUND CARD TESTING
def valid_low_rate(self,device):
"""set the rate to the lowest supported audio rate."""
for testrate in [8000, 9600, 11025, 12000, 16000, 22050, 24000,
32000, 44100, 48000, 88200, 96000, 192000]:
if self.valid_test(device,testrate):
return testrate
print("SOMETHING'S WRONG! I can't figure out how to use DEV",device)
return None
def valid_test(self,device,rate=44100):
"""given a device ID and a rate, return TRUE/False if it's valid."""
try:
self.info=self.p.get_device_info_by_index(device)
if not self.info["maxInputChannels"]>0:
return False
stream=self.p.open(format=pyaudio.paInt16,channels=1,
input_device_index=device,frames_per_buffer=self.chunk,
rate=int(self.info["defaultSampleRate"]),input=True)
stream.close()
return True
except:
return False
def valid_input_devices(self):
"""
See which devices can be opened for microphone input.
call this when no PyAudio object is loaded.
"""
mics=[]
for device in range(self.p.get_device_count()):
if self.valid_test(device):
mics.append(device)
if len(mics)==0:
print("no microphone devices found!")
else:
print("found %d microphone devices: %s"%(len(mics),mics))
return mics
### SETUP AND SHUTDOWN
def initiate(self):
"""
run this after changing settings (like rate) before recording.
mostly just ensures the sound card settings are good.
"""
if self.device is None:
self.device=self.valid_input_devices()[0] #pick the first one
if self.rate is None:
self.rate=self.valid_low_rate(self.device)
if not self.valid_test(self.device,self.rate):
print("guessing a valid microphone device/rate...")
self.device=self.valid_input_devices()[0] #pick the first one
self.rate=self.valid_low_rate(self.device)
self.msg='recording from "%s" '%self.info["name"]
self.msg+='(device %d) '%self.device
self.msg+='at %d Hz'%self.rate
self.data=np.array([])
print(self.msg)
def close(self):
"""gently detach from things."""
print(" -- sending stream termination command...")
self.keepRecording=False #the threads should self-close
if self.t:
while(self.t.isAlive()):
time.sleep(.1) #wait for all threads to close
self.stream.stop_stream()
self.p.terminate()
### LIVE AUDIO STREAM HANDLING
def stream_readchunk(self):
"""reads some audio and re-launches itself"""
try:
data = np.fromstring(self.stream.read(self.chunk),dtype=np.int16)
self.data=np.concatenate((self.data,data))
self.chunksRecorded+=1
self.dataFirstI=self.chunksRecorded*self.chunk-len(self.data)
if len(self.data)>self.maxMemorySec*self.rate:
pDump=len(self.data)-self.maxMemorySec*self.rate
#print(" -- too much data in memory! dumping %d points."%pDump)
self.data=self.data[pDump:]
self.dataFirstI+=pDump
except Exception as E:
print(" -- exception! terminating...")
print(E,"\n"*5)
self.keepRecording=False
if self.keepRecording==True:
self.stream_thread_new()
else:
self.stream.close()
self.p.terminate()
self.keepRecording=None
print(" -- stream STOPPED")
def stream_thread_new(self):
self.t=threading.Thread(target=self.stream_readchunk)
self.t.start()
def stream_start(self):
"""adds data to self.data until termination signal"""
self.initiate()
print(" -- starting stream")
self.keepRecording=True # set this to False later to terminate stream
self.dataFiltered=None #same
self.stream=self.p.open(format=pyaudio.paInt16,channels=1,
rate=self.rate,input=True,frames_per_buffer=self.chunk)
self.stream_thread_new()
def stream_stop(self,waitForIt=True):
"""send the termination command and (optionally) hang till its done"""
self.keepRecording=False
if waitForIt==False:
return
while self.keepRecording is False:
time.sleep(.1)
### WAV FILE AUDIO
def loadWAV(self,fname):
"""Add audio into the buffer (self.data) from a WAV file"""
self.rate,self.data=scipy.io.wavfile.read(fname)
print("loaded %.02f sec of data (rate=%dHz)"%(len(self.data)/self.rate,
self.rate))
self.initiate()
return
### DATA RETRIEVAL
def getPCMandFFT(self):
"""returns [data,fft,sec,hz] from current memory buffer."""
if not len(self.data):
return
data=np.array(self.data) # make a copy in case processing is slow
sec=np.arange(len(data))/self.rate
hz,fft=FFT(data,self.rate)
return data,fft,sec,hz
def softEdges(self,data,fracEdge=.05):
"""multiple edges by a ramp of a certain percentage."""
rampSize=int(len(data)*fracEdge)
mult = np.ones(len(data))
window=np.hanning(rampSize*2)
mult[:rampSize]=window[:rampSize]
mult[-rampSize:]=window[-rampSize:]
return data*mult
def getFiltered(self,freqHighCutoff=50):
if freqHighCutoff<=0:
return self.data
fft=np.fft.fft(self.softEdges(self.data)) #todo: filter needed?
trim=len(fft)/self.rate*freqHighCutoff
fft[int(trim):-int(trim)]=0
return np.real(np.fft.ifft(fft))
if __name__=="__main__":
print("This script is intended to be imported, not run directly!")
Esta la tercera parte.
Código:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'ui_main.ui'
#
# Created by: PyQt4 UI code generator 4.11.4
#
# WARNING! All changes made in this file will be lost!
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(993, 692)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.verticalLayout_2 = QtGui.QVBoxLayout(self.centralwidget)
self.verticalLayout_2.setMargin(10)
self.verticalLayout_2.setSpacing(10)
self.verticalLayout_2.setObjectName(_fromUtf8("verticalLayout_2"))
self.frame_4 = QtGui.QFrame(self.centralwidget)
self.frame_4.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_4.setFrameShadow(QtGui.QFrame.Raised)
self.frame_4.setObjectName(_fromUtf8("frame_4"))
self.horizontalLayout = QtGui.QHBoxLayout(self.frame_4)
self.horizontalLayout.setMargin(0)
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.label_3 = QtGui.QLabel(self.frame_4)
font = QtGui.QFont()
font.setPointSize(12)
font.setBold(True)
font.setWeight(75)
self.label_3.setFont(font)
self.label_3.setObjectName(_fromUtf8("label_3"))
self.horizontalLayout.addWidget(self.label_3)
self.label_4 = QtGui.QLabel(self.frame_4)
self.label_4.setEnabled(True)
self.label_4.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
self.label_4.setObjectName(_fromUtf8("label_4"))
self.horizontalLayout.addWidget(self.label_4)
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.btnSite = QtGui.QPushButton(self.frame_4)
self.btnSite.setStyleSheet(_fromUtf8("color: rgb(0, 0, 255);"))
self.btnSite.setCheckable(False)
self.btnSite.setFlat(True)
self.btnSite.setObjectName(_fromUtf8("btnSite"))
self.horizontalLayout.addWidget(self.btnSite)
self.verticalLayout_2.addWidget(self.frame_4)
self.frame_5 = QtGui.QFrame(self.centralwidget)
self.frame_5.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_5.setFrameShadow(QtGui.QFrame.Raised)
self.frame_5.setObjectName(_fromUtf8("frame_5"))
self.horizontalLayout_3 = QtGui.QHBoxLayout(self.frame_5)
self.horizontalLayout_3.setMargin(0)
self.horizontalLayout_3.setSpacing(0)
self.horizontalLayout_3.setObjectName(_fromUtf8("horizontalLayout_3"))
self.lblDevice = QtGui.QLabel(self.frame_5)
self.lblDevice.setEnabled(False)
self.lblDevice.setObjectName(_fromUtf8("lblDevice"))
self.horizontalLayout_3.addWidget(self.lblDevice)
spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout_3.addItem(spacerItem1)
self.verticalLayout_2.addWidget(self.frame_5)
self.frame = QtGui.QFrame(self.centralwidget)
self.frame.setFrameShape(QtGui.QFrame.NoFrame)
self.frame.setFrameShadow(QtGui.QFrame.Plain)
self.frame.setObjectName(_fromUtf8("frame"))
self.verticalLayout = QtGui.QVBoxLayout(self.frame)
self.verticalLayout.setMargin(0)
self.verticalLayout.setSpacing(10)
self.verticalLayout.setObjectName(_fromUtf8("verticalLayout"))
self.frame_2 = QtGui.QFrame(self.frame)
self.frame_2.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_2.setFrameShadow(QtGui.QFrame.Raised)
self.frame_2.setObjectName(_fromUtf8("frame_2"))
self.horizontalLayout_2 = QtGui.QHBoxLayout(self.frame_2)
self.horizontalLayout_2.setMargin(0)
self.horizontalLayout_2.setObjectName(_fromUtf8("horizontalLayout_2"))
self.chkInvert = QtGui.QCheckBox(self.frame_2)
self.chkInvert.setObjectName(_fromUtf8("chkInvert"))
self.horizontalLayout_2.addWidget(self.chkInvert)
self.chkAutoscale = QtGui.QCheckBox(self.frame_2)
self.chkAutoscale.setChecked(True)
self.chkAutoscale.setObjectName(_fromUtf8("chkAutoscale"))
self.horizontalLayout_2.addWidget(self.chkAutoscale)
self.line = QtGui.QFrame(self.frame_2)
self.line.setFrameShape(QtGui.QFrame.VLine)
self.line.setFrameShadow(QtGui.QFrame.Sunken)
self.line.setObjectName(_fromUtf8("line"))
self.horizontalLayout_2.addWidget(self.line)
self.label_2 = QtGui.QLabel(self.frame_2)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
self.label_2.setSizePolicy(sizePolicy)
self.label_2.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_2.setObjectName(_fromUtf8("label_2"))
self.horizontalLayout_2.addWidget(self.label_2)
self.spinLowpass = QtGui.QSpinBox(self.frame_2)
self.spinLowpass.setPrefix(_fromUtf8(""))
self.spinLowpass.setMinimum(0)
self.spinLowpass.setMaximum(999999)
self.spinLowpass.setSingleStep(1)
self.spinLowpass.setProperty("value", 45)
self.spinLowpass.setObjectName(_fromUtf8("spinLowpass"))
self.horizontalLayout_2.addWidget(self.spinLowpass)
self.label_5 = QtGui.QLabel(self.frame_2)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(0)
sizePolicy.setHeightForWidth(self.label_5.sizePolicy().hasHeightForWidth())
self.label_5.setSizePolicy(sizePolicy)
self.label_5.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.label_5.setObjectName(_fromUtf8("label_5"))
self.horizontalLayout_2.addWidget(self.label_5)
self.lineTitle = QtGui.QLineEdit(self.frame_2)
self.lineTitle.setObjectName(_fromUtf8("lineTitle"))
self.horizontalLayout_2.addWidget(self.lineTitle)
self.line_2 = QtGui.QFrame(self.frame_2)
self.line_2.setFrameShape(QtGui.QFrame.VLine)
self.line_2.setFrameShadow(QtGui.QFrame.Sunken)
self.line_2.setObjectName(_fromUtf8("line_2"))
self.horizontalLayout_2.addWidget(self.line_2)
self.btnPause = QtGui.QPushButton(self.frame_2)
self.btnPause.setCheckable(True)
self.btnPause.setObjectName(_fromUtf8("btnPause"))
self.horizontalLayout_2.addWidget(self.btnPause)
self.btnSave = QtGui.QPushButton(self.frame_2)
self.btnSave.setObjectName(_fromUtf8("btnSave"))
self.horizontalLayout_2.addWidget(self.btnSave)
self.verticalLayout.addWidget(self.frame_2)
self.frame_3 = QtGui.QFrame(self.frame)
self.frame_3.setFrameShape(QtGui.QFrame.StyledPanel)
self.frame_3.setFrameShadow(QtGui.QFrame.Plain)
self.frame_3.setObjectName(_fromUtf8("frame_3"))
self.verticalLayout_3 = QtGui.QVBoxLayout(self.frame_3)
self.verticalLayout_3.setMargin(0)
self.verticalLayout_3.setSpacing(0)
self.verticalLayout_3.setObjectName(_fromUtf8("verticalLayout_3"))
self.grECG = PlotWidget(self.frame_3)
self.grECG.setFrameShape(QtGui.QFrame.NoFrame)
self.grECG.setFrameShadow(QtGui.QFrame.Plain)
self.grECG.setLineWidth(0)
self.grECG.setObjectName(_fromUtf8("grECG"))
self.verticalLayout_3.addWidget(self.grECG)
self.verticalLayout.addWidget(self.frame_3)
self.verticalLayout_2.addWidget(self.frame)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "diyECG", None))
self.label_3.setText(_translate("MainWindow", "diyECG", None))
self.label_4.setText(_translate("MainWindow", " open-source live soundcard monitor with realtime iFFT filtering by Scott Harden", None))
self.btnSite.setText(_translate("MainWindow", "www.SWHarden.com", None))
self.lblDevice.setText(_translate("MainWindow", "!!! ERROR !!! no valid input sound devices found. Plug in a microphone and restart this program!", None))
self.chkInvert.setText(_translate("MainWindow", "invert", None))
self.chkAutoscale.setText(_translate("MainWindow", "autoscale", None))
self.label_2.setText(_translate("MainWindow", "lowpass:", None))
self.spinLowpass.setToolTip(_translate("MainWindow", "set to 0 to disable", None))
self.spinLowpass.setSuffix(_translate("MainWindow", " Hz", None))
self.label_5.setText(_translate("MainWindow", "title:", None))
self.lineTitle.setText(_translate("MainWindow", "DIY ECG", None))
self.btnPause.setText(_translate("MainWindow", "Pause", None))
self.btnSave.setText(_translate("MainWindow", "Save Figure", None))
from pyqtgraph import PlotWidget
Básicamente necesito que se vea así.
Ya que originalmente se ve así.
Este es el software en github.
En la documentación aquí se explica cómo lograrlo, pero aún así no ha sido posible.
Muchas gracias de ante mano .