Skip to content

Commit 3516ffa

Browse files
authored
Merge pull request #97 from cdrtm/up_down_sample
Add time-based up/downsample sampler, add x@Min/x@Max regular and measured
2 parents f9d8d8f + 20beb54 commit 3516ffa

5 files changed

Lines changed: 160 additions & 45 deletions

File tree

‎pydatview/GUIInfoPanel.py‎

Lines changed: 29 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,18 @@ def __init__(self, parent):
8383
self.ColsReg.append({'name':'Std/Mean (TI)', 'al':'R' , 'm':'y0TI' , 's' :False})
8484
self.ColsReg.append({'name':'Min' , 'al':'R' , 'm':'y0Min' , 's' :True})
8585
self.ColsReg.append({'name':'Max' , 'al':'R' , 'm':'y0Max' , 's' :True})
86+
self.ColsReg.append({'name':'x@Min' , 'al':'R' , 'm':'xAtYMin' , 's' :False})
87+
self.ColsReg.append({'name':'x@Max' , 'al':'R' , 'm':'xAtYMax' , 's' :False})
8688
self.ColsReg.append({'name':'Abs. Max' , 'al':'R' , 'm':'yAbsMax', 's' :False})
8789
self.ColsReg.append({'name':'Range' , 'al':'R' , 'm':'yRange', 's' :True})
8890
self.ColsReg.append({'name':'dx' , 'al':'R' , 'm':'dx' , 's' :True})
89-
self.ColsReg.append({'name':'Meas 1' , 'al':'R' , 'm':'meas' , 's' :False})
90-
self.ColsReg.append({'name':'Meas 2' , 'al':'R' , 'm':'meas' , 's' :False})
91-
self.ColsReg.append({'name':'Mean (Meas)' , 'al':'R' , 'm':'yMeas' , 's' :False})
92-
self.ColsReg.append({'name':'Min (Meas)' , 'al':'R' , 'm':'yMeas' , 's' :False})
93-
self.ColsReg.append({'name':'Max (Meas)' , 'al':'R' , 'm':'yMeas' , 's' :False})
91+
self.ColsReg.append({'name':'Meas 1' , 'al':'R' , 'm':'meas1' , 's' :False})
92+
self.ColsReg.append({'name':'Meas 2' , 'al':'R' , 'm':'meas2' , 's' :False})
93+
self.ColsReg.append({'name':'Mean (Meas)' , 'al':'R' , 'm':'yMeanMeas' , 's' :False})
94+
self.ColsReg.append({'name':'Min (Meas)' , 'al':'R' , 'm':'yMinMeas' , 's' :False})
95+
self.ColsReg.append({'name':'Max (Meas)' , 'al':'R' , 'm':'yMaxMeas' , 's' :False})
96+
self.ColsReg.append({'name':'x@Min (Meas)' , 'al':'R' , 'm':'xAtYMinMeas' , 's' :False})
97+
self.ColsReg.append({'name':'x@Max (Meas)' , 'al':'R' , 'm':'xAtYMaxMeas' , 's' :False})
9498
self.ColsReg.append({'name':'xMin' , 'al':'R' , 'm':'xMin' , 's' :False})
9599
self.ColsReg.append({'name':'xMax' , 'al':'R' , 'm':'xMax' , 's' :False})
96100
self.ColsReg.append({'name':'xRange' , 'al':'R' , 'm':'xRange', 's' :False})
@@ -117,6 +121,8 @@ def __init__(self, parent):
117121
self.ColsFFT.append({'name':'Std' , 'al':'R' , 'm':'y0Std' , 's' :True})
118122
self.ColsFFT.append({'name':'Min' , 'al':'R' , 'm':'y0Min' , 's' :True})
119123
self.ColsFFT.append({'name':'Max' , 'al':'R' , 'm':'y0Max' , 's' :True})
124+
self.ColsFFT.append({'name':'x@Min' , 'al':'R' , 'm':'xAtYMin' , 's' :False})
125+
self.ColsFFT.append({'name':'x@Max' , 'al':'R' , 'm':'xAtYMax' , 's' :False})
120126
self.ColsFFT.append({'name':'Mean(FFT)' , 'al':'R' , 'm':'yMean' , 's' :False})
121127
self.ColsFFT.append({'name':'Std(FFT)' , 'al':'R' , 'm':'yStd' , 's' :False})
122128
self.ColsFFT.append({'name':'Min(FFT)' , 'al':'R' , 'm':'yMin' , 's' :True})
@@ -130,11 +136,13 @@ def __init__(self, parent):
130136
self.ColsFFT.append({'name':'nWin(FFT)' , 'al':'R' , 'f':lambda x:x.Info('LWin') , 's' :False})
131137
self.ColsFFT.append({'name':'nFFT(FFT)' , 'al':'R' , 'f':lambda x:x.Info('nFFT') , 's' :False})
132138
self.ColsFFT.append({'name':'n(FFT)' , 'al':'R' , 'm':'ylen' , 's' :True})
133-
self.ColsFFT.append({'name':'Meas 1' , 'al':'R' , 'm':'meas' , 's' :False})
134-
self.ColsFFT.append({'name':'Meas 2' , 'al':'R' , 'm':'meas' , 's' :False})
135-
self.ColsFFT.append({'name':'Mean (Meas)' , 'al':'R' , 'm':'yMeas' , 's' :False})
136-
self.ColsFFT.append({'name':'Min (Meas)' , 'al':'R' , 'm':'yMeas' , 's' :False})
137-
self.ColsFFT.append({'name':'Max (Meas)' , 'al':'R' , 'm':'yMeas' , 's' :False})
139+
self.ColsFFT.append({'name':'Meas 1' , 'al':'R' , 'm':'meas1' , 's' :False})
140+
self.ColsFFT.append({'name':'Meas 2' , 'al':'R' , 'm':'meas2' , 's' :False})
141+
self.ColsFFT.append({'name':'Mean (Meas)' , 'al':'R' , 'm':'yMeanMeas' , 's' :False})
142+
self.ColsFFT.append({'name':'Min (Meas)' , 'al':'R' , 'm':'yMinMeas' , 's' :False})
143+
self.ColsFFT.append({'name':'Max (Meas)' , 'al':'R' , 'm':'yMaxMeas' , 's' :False})
144+
self.ColsFFT.append({'name':'x@Min (Meas)' , 'al':'R' , 'm':'xAtYMinMeas' , 's' :False})
145+
self.ColsFFT.append({'name':'x@Max (Meas)' , 'al':'R' , 'm':'xAtYMaxMeas' , 's' :False})
138146
self.ColsFFT.append({'name':'n ' , 'al':'R' , 'm':'n0' , 's' :True})
139147
self.ColsMinMax=[]
140148
self.ColsMinMax.append({'name':'Directory' , 'al':'L' , 'm':'baseDir', 's':False})
@@ -162,16 +170,20 @@ def __init__(self, parent):
162170
self.ColsPDF.append({'name':'Std' , 'al':'R' , 'm':'y0Std' , 's' :True})
163171
self.ColsPDF.append({'name':'Min' , 'al':'R' , 'm':'y0Min' , 's' :True})
164172
self.ColsPDF.append({'name':'Max' , 'al':'R' , 'm':'y0Max' , 's' :True})
173+
self.ColsPDF.append({'name':'x@Min' , 'al':'R' , 'm':'xAtYMin' , 's' :False})
174+
self.ColsPDF.append({'name':'x@Max' , 'al':'R' , 'm':'xAtYMax' , 's' :False})
165175
self.ColsPDF.append({'name':'Mean(PDF)' , 'al':'R' , 'm':'yMean' , 's' :False})
166176
self.ColsPDF.append({'name':'Std(PDF)' , 'al':'R' , 'm':'yStd' , 's' :False})
167177
self.ColsPDF.append({'name':'Min(PDF)' , 'al':'R' , 'm':'yMin' , 's' :True})
168178
self.ColsPDF.append({'name':'Max(PDF)' , 'al':'R' , 'm':'yMax' , 's' :True})
169179
self.ColsPDF.append({'name':u'\u222By(PDF)' , 'al':'R' , 'm':'inty' , 's' :True})
170-
self.ColsPDF.append({'name':'Meas 1' , 'al':'R' , 'm':'meas' , 's' :False})
171-
self.ColsPDF.append({'name':'Meas 2' , 'al':'R' , 'm':'meas' , 's' :False})
172-
self.ColsPDF.append({'name':'Mean (Meas)' , 'al':'R' , 'm':'yMeas' , 's' :False})
173-
self.ColsPDF.append({'name':'Min (Meas)' , 'al':'R' , 'm':'yMeas' , 's' :False})
174-
self.ColsPDF.append({'name':'Max (Meas)' , 'al':'R' , 'm':'yMeas' , 's' :False})
180+
self.ColsPDF.append({'name':'Meas 1' , 'al':'R' , 'm':'meas1' , 's' :False})
181+
self.ColsPDF.append({'name':'Meas 2' , 'al':'R' , 'm':'meas2' , 's' :False})
182+
self.ColsPDF.append({'name':'Mean (Meas)' , 'al':'R' , 'm':'yMeanMeas' , 's' :False})
183+
self.ColsPDF.append({'name':'Min (Meas)' , 'al':'R' , 'm':'yMinMeas' , 's' :False})
184+
self.ColsPDF.append({'name':'Max (Meas)' , 'al':'R' , 'm':'yMaxMeas' , 's' :False})
185+
self.ColsPDF.append({'name':'x@Min (Meas)' , 'al':'R' , 'm':'xAtYMinMeas' , 's' :False})
186+
self.ColsPDF.append({'name':'x@Max (Meas)' , 'al':'R' , 'm':'xAtYMaxMeas' , 's' :False})
175187
self.ColsPDF.append({'name':'n(PDF)' , 'al':'R' , 'm':'ylen' , 's' :True})
176188
self.ColsCmp=[]
177189
self.ColsCmp.append({'name':'Directory' , 'al':'L' , 'm':'baseDir' , 's':False})
@@ -307,16 +319,8 @@ def _showStats(self,erase=True):
307319
for j,c in enumerate(selCols):
308320
# TODO: could be nicer:
309321
if 'm' in c.keys():
310-
if c['name'] == 'Meas 1':
311-
v,sv=getattr(PD,c['m'])(self.meas_xy1)
312-
elif c['name'] == 'Meas 2':
313-
v,sv=getattr(PD,c['m'])(self.meas_xy2)
314-
elif c['name'] == 'Mean (Meas)':
315-
v, sv = getattr(PD, c['m'])(self.meas_xy1, self.meas_xy2, 'mean')
316-
elif c['name'] == 'Max (Meas)':
317-
v, sv = getattr(PD, c['m'])(self.meas_xy1, self.meas_xy2, 'max')
318-
elif c['name'] == 'Min (Meas)':
319-
v, sv = getattr(PD, c['m'])(self.meas_xy1, self.meas_xy2, 'min')
322+
if c['m'] in ('meas1', 'meas2'):
323+
v,sv=getattr(PD,c['m'])(self.meas_xy1, self.meas_xy2)
320324
else:
321325
v,sv=getattr(PD,c['m'])()
322326
else:

‎pydatview/GUITools.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ def __init__(self, parent):
403403
msizer = wx.FlexGridSizer(rows=2, cols=4, hgap=2, vgap=0)
404404
msizer.Add(wx.StaticText(self, -1, 'Table:') , 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM, 1)
405405
msizer.Add(self.cbTabs , 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM, 1)
406-
msizer.Add(wx.StaticText(self, -1, 'Current x:'), 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM, 1)
406+
msizer.Add(wx.StaticText(self, -1, 'Current x: '), 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM, 1)
407407
msizer.Add(self.textOldX , 1, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM|wx.EXPAND, 1)
408408
msizer.Add(wx.StaticText(self, -1, 'Method:') , 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM, 1)
409409
msizer.Add(self.cbMethods , 0, wx.ALIGN_LEFT|wx.ALIGN_CENTER_VERTICAL|wx.TOP|wx.BOTTOM, 1)
@@ -582,6 +582,8 @@ def onHelp(self,event=None):
582582
- insert : insert a list of x values to the existing ones
583583
- delete : delete a list of x values from the existing ones
584584
- every-n : use every n values
585+
- time-based: downsample using sample averaging or upsample using
586+
linear interpolation, x-axis must already be in seconds
585587
- delta x : specify a delta for uniform spacing of x values
586588
587589
- Specify the x values as a space or comma separated list

‎pydatview/plotdata.py‎

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,16 @@ def _post_init(PD, Options={}):
105105
PD._y0Max = PD._yMax
106106
PD._x0Min = PD._xMin
107107
PD._x0Max = PD._xMax
108+
PD._x0AtYMin = PD._xAtYMin
109+
PD._x0AtYMax = PD._xAtYMax
108110
PD._y0Std = PD.yStd()
109111
PD._y0Mean = PD.yMean()
110112
PD._n0 = (n,'{:d}'.format(n))
111113
PD.x0 =PD.x
112114
PD.y0 =PD.y
115+
# Store xyMeas input values so we don't need to recompute xyMeas in case they didn't change
116+
PD.xyMeasInput1, PD.xyMeasInput2 = None, None
117+
PD.xyMeas1, PD.xyMeas2 = None, None
113118

114119
def __repr__(s):
115120
s1='id:{}, it:{}, ix:{}, iy:{}, sx:"{}", sy:"{}", st:{}, syl:{}\n'.format(s.id,s.it,s.ix,s.iy,s.sx,s.sy,s.st,s.syl)
@@ -261,8 +266,10 @@ def computeRange(PD):
261266
"""
262267
PD._xMin = PD._xMinCalc()
263268
PD._xMax = PD._xMaxCalc()
264-
PD._yMin = PD._yMinCalc()
269+
PD._yMin = PD._yMinCalc()
265270
PD._yMax = PD._yMaxCalc()
271+
PD._xAtYMin = PD._xAtYMinCalc(PD._yMin[0])
272+
PD._xAtYMax = PD._xAtYMaxCalc(PD._yMax[0])
266273

267274

268275
# --------------------------------------------------------------------------------}
@@ -288,6 +295,26 @@ def _yMaxCalc(PD):
288295
s=pretty_num(v)
289296
return (v,s)
290297

298+
def _xAtYMinCalc(PD, yMin):
299+
if PD.xIsString:
300+
return PD.x[0],PD.x[0].strip()
301+
elif PD.xIsDate:
302+
return PD.x[0],'{}'.format(PD.x[0])
303+
else:
304+
v = PD.x[np.where(PD.y == yMin)[0][0]]
305+
s=pretty_num(v)
306+
return (v,s)
307+
308+
def _xAtYMaxCalc(PD, yMax):
309+
if PD.xIsString:
310+
return PD.x[-1],PD.x[-1].strip()
311+
elif PD.xIsDate:
312+
return PD.x[-1],'{}'.format(PD.x[-1])
313+
else:
314+
v = PD.x[np.where(PD.y == yMax)[0][0]]
315+
s=pretty_num(v)
316+
return (v,s)
317+
291318
def _xMinCalc(PD):
292319
if PD.xIsString:
293320
return PD.x[0],PD.x[0].strip()
@@ -314,6 +341,12 @@ def xMin(PD):
314341
def xMax(PD):
315342
return PD._xMax
316343

344+
def xAtYMin(PD):
345+
return PD._xAtYMin
346+
347+
def xAtYMax(PD):
348+
return PD._xAtYMax
349+
317350
def yMin(PD):
318351
return PD._yMin
319352

@@ -468,39 +501,80 @@ def intyx2(PD):
468501
s=pretty_num(v)
469502
return v,s
470503

471-
def meas(PD, xymeas):
504+
def meas1(PD, xymeas1, xymeas2):
505+
if PD.xyMeasInput1 is not None and PD.xyMeasInput1 == xymeas1:
506+
yv = PD.xyMeas1[1]
507+
s = pretty_num(yv)
508+
else:
509+
xv, yv, s = PD._meas(xymeas1)
510+
PD.xyMeas1 = [xv, yv]
511+
PD.xyMeasInput1 = xymeas1
512+
return yv, s
513+
514+
def meas2(PD, xymeas1, xymeas2):
515+
if PD.xyMeasInput2 is not None and PD.xyMeasInput2 == xymeas2:
516+
yv = PD.xyMeas2[1]
517+
s = pretty_num(yv)
518+
else:
519+
xv, yv, s = PD._meas(xymeas2)
520+
PD.xyMeas2 = [xv, yv]
521+
PD.xyMeasInput2 = xymeas2
522+
return yv, s
523+
524+
def yMeanMeas(PD):
525+
return PD._measCalc('mean')
526+
527+
def yMinMeas(PD):
528+
return PD._measCalc('min')
529+
530+
def yMaxMeas(PD):
531+
return PD._measCalc('max')
532+
533+
def xAtYMinMeas(PD):
534+
return PD._measCalc('xmin')
535+
536+
def xAtYMaxMeas(PD):
537+
return PD._measCalc('xmax')
538+
539+
def _meas(PD, xymeas):
472540
try:
473-
v='NA'
541+
xv, yv = 'NA', 'NA'
474542
xy = np.array([PD.x, PD.y]).transpose()
475543
points = find_closest(xy, [xymeas[0], xymeas[1]], False)
476544
if points.ndim == 1:
477-
v = points[1]
478-
s=pretty_num(v)
545+
xv, yv = points[0:2]
546+
s = pretty_num(yv)
479547
else:
480-
v = points[0, 1]
548+
xv, yv = points[0, 0], points[0, 1]
481549
s = ' / '.join([str(p) for p in points[:, 1]])
482550
except (IndexError, TypeError):
483-
v='NA'
551+
xv, yv = 'NA', 'NA'
484552
s='NA'
485-
return v,s
553+
return xv, yv, s
486554

487-
def yMeas(PD, xymeas1, xymeas2, mode):
555+
def _measCalc(PD, mode):
556+
if PD.xyMeas1 is None or PD.xyMeas2 is None:
557+
return 'NA', 'NA'
488558
try:
489-
xy = np.array([PD.x, PD.y]).transpose()
490-
points_left = find_closest(xy, [xymeas1[0], xymeas1[1]], True)
491-
points_right = find_closest(xy, [xymeas2[0], xymeas2[1]], True)
492559
v = 'NA'
493-
left_index = np.where(PD.x == points_left[0])[0][0]
494-
right_index = np.where(PD.x == points_right[0])[0][0]
560+
left_index = np.where(PD.x == PD.xyMeas1[0])[0][0]
561+
right_index = np.where(PD.x == PD.xyMeas2[0])[0][0]
562+
if left_index == right_index:
563+
raise IndexError
564+
if left_index > right_index:
565+
left_index, right_index = right_index, left_index
495566
if mode == 'mean':
496-
y_val = np.mean(PD.y[left_index:right_index])
567+
v = np.nanmean(PD.y[left_index:right_index])
497568
elif mode == 'min':
498-
y_val = np.min(PD.y[left_index:right_index])
569+
v = np.nanmin(PD.y[left_index:right_index])
499570
elif mode == 'max':
500-
y_val = np.max(PD.y[left_index:right_index])
571+
v = np.nanmax(PD.y[left_index:right_index])
572+
elif mode == 'xmin':
573+
v = PD.x[left_index + np.where(PD.y[left_index:right_index] == np.nanmin(PD.y[left_index:right_index]))[0][0]]
574+
elif mode == 'xmax':
575+
v = PD.x[left_index + np.where(PD.y[left_index:right_index] == np.nanmax(PD.y[left_index:right_index]))[0][0]]
501576
else:
502577
raise NotImplementedError('Error: Mode ' + mode + ' not implemented')
503-
v = y_val
504578
s = pretty_num(v)
505579
except (IndexError, TypeError):
506580
v = 'NA'

‎pydatview/tools/signal.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
{'name':'Insert', 'param':[], 'paramName':'Insert list'},
1717
{'name':'Remove', 'param':[], 'paramName':'Remove list'},
1818
{'name':'Every n', 'param':2 , 'paramName':'n'},
19+
{'name':'Time-based', 'param':0.01 , 'paramName':'Sample time (s)'},
1920
{'name':'Delta x', 'param':0.1, 'paramName':'dx'},
2021
]
2122

@@ -150,6 +151,25 @@ def applySampler(x_old, y_old, sampDict, df_old=None):
150151
if y_old is not None:
151152
return x_new, y_old[::n]
152153

154+
elif sampDict['name'] == 'Time-based':
155+
if len(param) == 0:
156+
raise Exception('Error: provide value for new sampling time')
157+
sample_time = float(param[0])
158+
if sample_time <= 0:
159+
raise Exception('Error: sample time must be positive')
160+
161+
time_index = pd.TimedeltaIndex(x_old, unit="S")
162+
x_new = pd.Series(x_old, index=time_index).resample("{:f}S".format(sample_time)).mean().interpolate().values
163+
164+
if df_old is not None:
165+
df_new = df_old.set_index(time_index, inplace=False).resample("{:f}S".format(sample_time)).mean()
166+
df_new = df_new.interpolate().reset_index(drop=True)
167+
return x_new, df_new
168+
if y_old is not None:
169+
y_new = pd.Series(y_old, index=time_index).resample("{:f}S".format(sample_time)).mean()
170+
y_new = y_new.interpolate().values
171+
return x_new, y_new
172+
153173
else:
154174
raise NotImplementedError('{}'.format(sampDict))
155175
pass

‎tests/test_signal.py‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import unittest
22
import numpy as np
3+
import pandas as pd
34
from pydatview.tools.signal import *
45

56
# --------------------------------------------------------------------------------}
@@ -22,6 +23,20 @@ def test_zero_crossings(self):
2223
self.assertEqual(zero_crossings(np.array([ 1,-1]),direction='up' )[0].size,0)
2324
self.assertEqual(zero_crossings(np.array([-1, 1]),direction='down')[0].size,0)
2425

25-
26+
def test_up_down_sample(self):
27+
name = 'Time-based'
28+
x, y = applySampler(range(0, 4), [5, 0, 5, 0], {'name': name, 'param': [2]})
29+
self.assertTrue(np.all(x==[0.5, 2.5]))
30+
self.assertTrue(np.all(y==[2.5, 2.5]))
31+
x, y = applySampler(range(0, 3), [5, 0, 5], {'name': name, 'param': [0.5]})
32+
self.assertTrue(np.all(x==[0, 0.5, 1, 1.5, 2]))
33+
self.assertTrue(np.all(y==[5, 2.5, 0, 2.5, 5]))
34+
x, df = applySampler(range(0, 6), None, {'name': name, 'param': [3]}, pd.DataFrame({"y": [0, 6, 6, 2, -4, -4]}))
35+
self.assertTrue(np.all(x==[1, 4]))
36+
self.assertTrue(np.all(df["y"]==[4, -2]))
37+
x, df = applySampler(range(0, 3), None, {'name': name, 'param': [0.5]}, pd.DataFrame({"y": [0, 6, -6]}))
38+
self.assertTrue(np.all(x==[0, 0.5, 1, 1.5, 2]))
39+
self.assertTrue(np.all(df["y"]==[0, 3, 6, 0, -6]))
40+
2641
if __name__ == '__main__':
2742
unittest.main()

0 commit comments

Comments
 (0)