Added bar plots, different figures, and lots of things have been changed

This commit is contained in:
Anne de Jong 2018-06-04 15:00:27 +02:00 committed by Anne de Jong
parent 3357a0ded6
commit 475f2fa6aa
4 changed files with 388 additions and 18 deletions

View File

@ -13,6 +13,7 @@ class ReverbTime:
""" """
Tool to estimate the reverberation time Tool to estimate the reverberation time
""" """
def __init__(self, fs, level): def __init__(self, fs, level):
""" """
Initialize Reverberation time computer. Initialize Reverberation time computer.

View File

@ -12,7 +12,7 @@ from .lasp_common import (FreqWeighting, sens, calfile,
TimeWeighting, getTime, P_REF) TimeWeighting, getTime, P_REF)
from .lasp_weighcal import WeighCal from .lasp_weighcal import WeighCal
from .lasp_gui_tools import wait_cursor from .lasp_gui_tools import wait_cursor
from .lasp_figure import FigureDialog, PlotOptions from .lasp_figure import PlotOptions, Plotable
from .ui_slmwidget import Ui_SlmWidget from .ui_slmwidget import Ui_SlmWidget
__all__ = ['SLM', 'SlmWidget'] __all__ = ['SLM', 'SlmWidget']
@ -96,16 +96,25 @@ class SLM:
class SlmWidget(ComputeWidget, Ui_SlmWidget): class SlmWidget(ComputeWidget, Ui_SlmWidget):
def __init__(self): def __init__(self, parent=None):
""" """
Initialize the SlmWidget. Initialize the SlmWidget.
""" """
super().__init__() super().__init__(parent)
self.setupUi(self) self.setupUi(self)
FreqWeighting.fillComboBox(self.freqweighting)
FreqWeighting.fillComboBox(self.tfreqweighting)
FreqWeighting.fillComboBox(self.eqfreqweighting)
self.setMeas(None) self.setMeas(None)
def init(self, fm):
"""
Register combobox of the figure dialog to plot to in the FigureManager
"""
super().init(fm)
fm.registerCombo(self.tfigure)
fm.registerCombo(self.eqfigure)
def setMeas(self, meas): def setMeas(self, meas):
""" """
Set the current measurement for this widget. Set the current measurement for this widget.
@ -119,27 +128,64 @@ class SlmWidget(ComputeWidget, Ui_SlmWidget):
else: else:
self.setEnabled(True) self.setEnabled(True)
rt = meas.recTime rt = meas.recTime
self.starttime.setRange(0, rt, 0) self.tstarttime.setRange(0, rt, 0)
self.stoptime.setRange(0, rt, rt) self.tstoptime.setRange(0, rt, rt)
self.channel.clear() self.eqstarttime.setRange(0, rt, 0)
self.eqstoptime.setRange(0, rt, rt)
self.tchannel.clear()
self.eqchannel.clear()
for i in range(meas.nchannels): for i in range(meas.nchannels):
self.channel.addItem(str(i)) self.tchannel.addItem(str(i))
self.channel.setCurrentIndex(0) self.eqchannel.addItem(str(i))
self.tchannel.setCurrentIndex(0)
self.eqchannel.setCurrentIndex(0)
def compute(self): def computeEq(self):
""" """
Compute Sound Level using settings. This method is Compute equivalent levels for a piece of time
called whenever the Compute button is pushed in the SLM tab
""" """
meas = self.meas meas = self.meas
fs = meas.samplerate fs = meas.samplerate
channel = self.channel.currentIndex() channel = self.eqchannel.currentIndex()
tw = TimeWeighting.getCurrent(self.timeweighting) fw = FreqWeighting.getCurrent(self.eqfreqweighting)
fw = FreqWeighting.getCurrent(self.freqweighting)
startpos = self.eqstarttime.value
stoppos = self.eqstoptime.value
N = meas.N
istart = int(startpos*fs)
if istart >= N:
raise ValueError("Invalid start position")
istop = int(stoppos*fs)
if istart > N:
raise ValueError("Invalid stop position")
with wait_cursor():
# This one exctracts the calfile and sensitivity from global
# variables defined at the top. # TODO: Change this to a more
# robust variant.
weighcal = WeighCal(fw, nchannels=1,
fs=fs, calfile=calfile,
sens=sens)
praw = meas.praw()[istart:istop, [channel]]
pto = PlotOptions()
fig, new = self.getFigure(self.eqfigure, pto, 'bar')
fig.show()
def computeT(self):
"""
Compute sound levels as a function of time.
"""
meas = self.meas
fs = meas.samplerate
channel = self.tchannel.currentIndex()
tw = TimeWeighting.getCurrent(self.ttimeweighting)
fw = FreqWeighting.getCurrent(self.tfreqweighting)
# Downsampling factor of result # Downsampling factor of result
dsf = self.downsampling.value() dsf = self.tdownsampling.value()
# gb = self.slmFre # gb = self.slmFre
with wait_cursor(): with wait_cursor():
@ -162,8 +208,11 @@ class SlmWidget(ComputeWidget, Ui_SlmWidget):
pto = PlotOptions() pto = PlotOptions()
pto.ylabel = f'L{fw[0]} [dB({fw[0]})]' pto.ylabel = f'L{fw[0]} [dB({fw[0]})]'
pto.xlim = (time[0], time[-1]) pto.xlim = (time[0], time[-1])
fig, new = self.getFigure(pto)
fig.fig.plot(time, filtered) pta = Plotable(time, filtered)
fig, new = self.getFigure(self.tfigure, pto, 'line')
fig.fig.add(pta)
fig.show() fig.show()
stats = f"""Statistical results: stats = f"""Statistical results:
@ -175,3 +224,13 @@ Maximum level (L{fw[0]} max): {Lmax:4.4} [dB({fw[0]})]
""" """
self.results.setPlainText(stats) self.results.setPlainText(stats)
def compute(self):
"""
Compute Sound Level using settings. This method is
called whenever the Compute button is pushed in the SLM tab
"""
if self.ttab.isVisible():
self.computeT()
elif self.eqtab.isVisible():
self.computeEq()

3
lasp/plot/__init__.py Normal file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from .bar import BarScene

307
lasp/plot/bar.py Normal file
View File

@ -0,0 +1,307 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Author: J.A. de Jong - ASCEE
Description:
Class for plotting bars on a QGraphicsScene.
"""
from ..lasp_gui_tools import ASCEEColors
from PySide.QtGui import (
QGraphicsScene, QGraphicsView, QPen, QBrush, QGraphicsRectItem,
QGraphicsTextItem, QPainter, QImage
)
# from PySide.QtOpenGL import
from PySide.QtCore import Qt, QRectF, QLineF, QSize, QRect, QPointF
import numpy as np
import os
leftoffset = .1 # Left offset of the figure
rightoffset = 0
topoffset = .05
bottomoffset = .1
nyticks = 6
ticklength = .01
# Distance between two bar groups in units of bar thicknesses
dxbars = 2
DEFAULT_COLORS = [ASCEEColors.blue, ASCEEColors.green, Qt.red]
class BarScene(QGraphicsScene):
"""
Graphhics Scene for plotting bars
"""
def __init__(self, parent, xvals, G, ylim=(0, 1),
grid=True,
xlabel=None,
ylabel=None,
title=None,
colors=DEFAULT_COLORS, size=(800, 600),
legend=None):
"""
Initialize a bar scene
Args:
xvals: labels and x positions of the bars
G: Number of bars per x value
ylim: y limits of the figure
"""
super().__init__(parent=parent)
# self.setBackgroundBrush(ASCEEColors.bgBrush(0, size[0]))
self.ylim = ylim
N = len(xvals)
self.N = N
self.G = G
self.bgs = []
self.size = size
self.colors = colors
# Size of the frame
Lx = 1 - rightoffset - leftoffset
Ly = 1 - topoffset - bottomoffset
# The main frame where the bars are in.
mainframe = self.createRect(leftoffset,
bottomoffset,
Lx,
Ly)
self.addItem(mainframe)
# Set the y ticks and ticklabels
self.yticks = []
txtmaxwidth = 0
for i in range(nyticks):
y = bottomoffset+Ly*i/(nyticks-1)
ytick = self.addLine(leftoffset,
y,
leftoffset-ticklength,
y)
if grid:
ygrid = self.addLine(leftoffset,
y,
1-rightoffset,
y,pen=QPen(Qt.gray))
range_ = ylim[1]-ylim[0]
ytickval = i/(nyticks-1)*range_ + ylim[0]
yticklabel = f'{ytickval:2}'
txt = QGraphicsTextItem(yticklabel)
txtwidth = txt.boundingRect().width()
txtmaxwidth = max(txtmaxwidth, txtwidth)
txt.setPos((leftoffset-.03)*self.xscale-txtwidth,
(1-y-.022)*self.yscale)
self.addItem(txt)
self.yticks.append(ytick)
# Create the bars
for g in range(G):
bg = []
for n in range(N):
barrect = self.getBarRect(n, g, 0)
baritem = QGraphicsRectItem(barrect, brush=QBrush(Qt.blue))
self.addItem(baritem)
bg.append(baritem)
self.bgs.append(bg)
# Add x ticks and ticklabels
for n in range(N):
xticklabel = f'{xvals[n]}'
txt = QGraphicsTextItem(xticklabel)
txtxpos = self.getBarGroupMidPos(n)-0.01*self.xscale
txt.setPos(txtxpos,
self.yscale*(1-bottomoffset+.1))
txt.rotate(-90)
self.addItem(txt)
# Set xlabel
if xlabel is not None:
xlabel = QGraphicsTextItem(xlabel)
width = xlabel.boundingRect().width()
txtxpos = self.xscale/2-width/2
txtypos = .998*self.yscale
xlabel.setPos(txtxpos, txtypos)
self.addItem(xlabel)
# Set ylabel
if ylabel is not None:
ylabel = QGraphicsTextItem(ylabel)
ylabel.setPos((leftoffset-.01)*self.xscale-txtmaxwidth,
((1-topoffset-bottomoffset)/2+topoffset)*self.yscale)
ylabel.rotate(-90)
self.addItem(ylabel)
# Set title
if title is not None:
title = QGraphicsTextItem(title)
width = xlabel.boundingRect().width()
txtxpos = self.xscale/2-width/2
txtypos = (1-.998)*self.yscale
title.setPos(txtxpos, txtypos)
self.addItem(title)
legpos = (1-rightoffset-.3, 1-topoffset-.05)
dyleg = 0.03
dylegtxt = dyleg
Lyleg = .02
Lxleg = .05
legrectmarginpix = 5
boxtopleft = QPointF(legpos[0]*self.xscale-legrectmarginpix,
(1-legpos[1]-Lyleg)*self.yscale-legrectmarginpix)
if legend is not None:
nlegs = len(legend)
maxlegtxtwidth = 0
for i,leg in enumerate(legend):
leglabel = legend[i]
# The position of the legend, in our coordinates
pos = (legpos[0], legpos[1] - i*dyleg)
color = self.colors[i]
legrect = self.createRect(*pos,Lxleg,Lyleg)
legrectwidth = legrect.boundingRect().width()
legrect.setBrush(QBrush(color))
legtxt = QGraphicsTextItem(leglabel)
maxlegtxtwidth = max(maxlegtxtwidth,
legtxt.boundingRect().width())
self.addItem(legrect)
self.addItem(legtxt)
legtxt.setPos(legpos[0]*self.xscale+legrectwidth,
(1-pos[1]-dylegtxt)*self.yscale)
boxbottomright = legtxt.boundingRect().topRight()
legboxsize = QSize(maxlegtxtwidth+legrectwidth+2*legrectmarginpix,
(i+1)*dyleg*self.yscale+legrectmarginpix)
legboxrect = QRectF(boxtopleft,legboxsize)
legbox = self.addRect(legboxrect)
def saveAsPng(self, fn, force=False):
"""
Save bar image as a jpg file.
https://stackoverflow.com/questions/7451183/how-to-create-image-file\
-from-qgraphicsscene-qgraphicsview#11642517
"""
if os.path.exists(fn) and not force:
raise RuntimeError(f"File {fn} already exists in filesystem.")
# self.clearSelection()
image = QImage(*self.size,
QImage.Format_ARGB32_Premultiplied)
# image = QImage()
painter = QPainter(image)
# painter.begin()
painter.setRenderHint(QPainter.Antialiasing)
painter.setBrush(Qt.white)
painter.setPen(Qt.white)
painter.drawRect(QRect(0,0,*self.size))
targetrect = QRectF(0,0,*self.size)
sourcerect = QRectF(0,0,*self.size)
self.render(painter,targetrect,sourcerect)
painter.end()
# print('saving image')
image.save(fn)
def getBarGroupMidPos(self,n):
"""
Returns the mid x position below each bar group
"""
Lx = 1-rightoffset-leftoffset
Ly = 1 - topoffset - bottomoffset
start = .05
S = Lx - 2*start
L = S/(self.N*self.G+dxbars*(self.N-1))
xL = leftoffset+start
return (n*(self.G*L+dxbars*L) + xL + self.G*L/2)*self.xscale
def getBarRect(self, n, g, yval):
Lx = 1-rightoffset-leftoffset
Ly = 1 - topoffset - bottomoffset
start = .05
S = Lx - 2*start
L = S/(self.N*self.G+dxbars*(self.N-1))
xL = leftoffset+start
x = g*L + n*(self.G*L+dxbars*L) + xL
return QRectF(x*self.xscale,
(1-bottomoffset-yval*Ly)*self.yscale,
L*self.xscale,
yval*Ly*self.yscale)
def addLine(self, x1, y1, x2, y2, pen=QPen(), brush=QBrush()):
line = QLineF(x1*self.xscale,
(1-y1)*self.yscale,
(x2)*self.xscale,
(1-y2)*self.yscale)
return super().addLine(line, pen=pen, brush=brush)
def createRect(self, x, y, Lx, Ly, pen=QPen(), brush=QBrush()):
"""
Create a rectangle somewhere, in relative coordinates originating
from the lower left position.
"""
x1 = x
# Y-position from the top, these are the coordinates used to create a
# rect item.
y1 = 1-y-Ly
return QGraphicsRectItem(x1*self.xscale,
y1*self.yscale,
Lx*self.xscale,
Ly*self.yscale,
pen=pen,
brush=brush)
@property
def xscale(self):
return self.size[0]
@property
def yscale(self):
return self.size[1]
def set_ydata(self, newydata):
G = len(self.bgs)
N = len(self.bgs[0])
assert newydata.shape[0] == N
assert newydata.shape[1] == G
# Crop values to be between 0 and 1
scalefac = self.ylim[1]-self.ylim[0]
yvals = np.clip(newydata, self.ylim[0], self.ylim[1])/scalefac
for g in range(G):
color = self.colors[g]
for n in range(N):
bar = self.bgs[g][n]
bar.setRect(self.getBarRect(n, g, yvals[n, g]))
bar.setBrush(color)