Commit bb7ae909 authored by Jameson Graef Rollins's avatar Jameson Graef Rollins
Browse files

WIP

parent 93ed14ee
......@@ -25,10 +25,25 @@ DATETIME_FMT = '%a %b %d %Y %H:%M:%S %Z'
DATETIME_FMT_OFFLINE = '%Y/%m/%d %H:%M:%S %Z'
# thresholds in seconds where requests translate to second and minute
# trends
TREND_THRESHOLD_S = 120
TREND_THRESHOLD_M = 3600
# transformations for the time axis. will transform when span is x10
# larger than the scale factor. should be ordered largest to
# smallest
TIME_AXIS_TRANSFORMS = [
(31536000, 'years'),
(2592000, 'months'),
(86400, 'days'),
(86400, 'days'),
(3600, 'hours'),
(60, 'minutes'),
]
# percentage of full span to add as additional padding when fetching
# new data
DATA_SPAN_PADDING = 0.5
......
......@@ -14,7 +14,6 @@ import logging
from . import const
from . import nds
class DataBuffer(object):
"""data storage
......@@ -330,18 +329,18 @@ class DataStore(QtCore.QObject):
self.remote_cmd('raw', 'extendleft', (start, end))
def update(self, start_end, trend):
logging.debug("DATA UPDATE: {} {}".format(start_end, trend))
logging.log(5, "DATA UPDATE: {} {}".format(start_end, trend))
# if we're online only accept updates for raw data
if self.online and trend != 'raw':
return
# expand range to ints
rstart = int(start_end[0])
rend = int(np.ceil(start_end[1]))
rstart = start_end[0]
rend = start_end[1]
# add padding
pad = int((rend - rstart) * const.DATA_SPAN_PADDING)
pad = (rend - rstart) * const.DATA_SPAN_PADDING
rstart -= pad
rend += pad
# FIXME: this is to prevent requesting data from the future.
......@@ -382,6 +381,10 @@ class DataStore(QtCore.QObject):
##########
def remote_cmd(self, trend, cmd, start_end=None, channels=None):
if start_end and start_end[0] == start_end[1]:
print("NO REQ")
return
self.init = False
# this is a thread ID used as a kind of primitive lock. the
......@@ -394,9 +397,9 @@ class DataStore(QtCore.QObject):
tid = '{}-{}'.format(trend, cmd)
if tid in self.threads and self.threads[tid]:
logging.debug("DATA BUSY: {} {}".format(tid, start_end))
logging.log(5, "DATA BUSY: {} {}".format(tid, start_end))
return
logging.debug("DATA CMND: {} {}".format(tid, start_end))
logging.log(5, "DATA CMND: {} {}".format(tid, start_end))
if cmd == 'online':
desc = 'online '
......
......@@ -235,7 +235,8 @@ class NDSThread(QtCore.QThread):
end = int(np.ceil(start_end[1]))
if trend == 'min':
start -= start % 60
end -= (end % 60)
end -= end % 60
end += 60
self.start_end = (start, end)
else:
self.start_end = None
......
......@@ -123,7 +123,8 @@ class NDScopePlot(PlotItem):
for curve in self.channels.values():
curve.clear_data()
def update(self, data, t0):
# @profile
def update(self, data, t0, tscale):
"""update all channel
`data` should be a DataBufferDict object, and `t0` is the GPS
......@@ -153,13 +154,15 @@ class NDScopePlot(PlotItem):
self.removeItem(cc.curves['fill'])
cc.set_ctype(cd.ctype)
bds = cc.set_data(cd, t0)
bds = cc.set_data(cd, t0, tscale)
# FIXME: we're not actually combining these segments, just
# excluding exact duplicates. slightly unaligned segments
# from multiple channels will just be overlayed
bad_data_segs |= set(bds)
# FIXME: profiler reveals that this is bad data marking is a
# quite expensive operation
for seg in bad_data_segs:
bdr = pg.LinearRegionItem(
values=seg,
......@@ -184,6 +187,13 @@ class NDScopePlot(PlotItem):
class NDScopePlotChannel(QtCore.QObject):
label_changed = QtCore.Signal(str)
PLOT_ARGS = {
'connect': "finite", # this is for ignoring NaNs
# 'autoDownsample': True,
# 'clipToView': True,
# 'downsampleMethod': 'peak',
}
def __init__(self, channel,
color=None, width=1,
scale=1, offset=0):
......@@ -230,14 +240,13 @@ class NDScopePlotChannel(QtCore.QObject):
self.curves['y'] = PlotDataItem([0, 0], pen=pen, name=self.label)
self.curves['min'] = PlotDataItem([0, 0], pen=mmc)
self.curves['max'] = PlotDataItem([0, 0], pen=mmc)
# FIXME: HACK for bug in old pyqtgraph 0.9
try:
if False:
self.curves['fill'] = FillBetweenItem(
self.curves['min'],
self.curves['max'],
brush=mmc
)
except:
else:
self.curves['fill'] = None
def set_ctype(self, ctype):
......@@ -280,7 +289,8 @@ class NDScopePlotChannel(QtCore.QObject):
self.curves['min'].setData(np.array([0, 0]))
self.curves['max'].setData(np.array([0, 0]))
def set_data(self, data, t0):
# @profile
def set_data(self, data, t0, tscale):
"""set data for curves
Data should be DataBuffer object. Returns list of
......@@ -288,6 +298,7 @@ class NDScopePlotChannel(QtCore.QObject):
"""
t = data.tarray - t0
t /= tscale
if not data.is_trend:
y = data['raw']
......@@ -297,28 +308,30 @@ class NDScopePlotChannel(QtCore.QObject):
self.curves['min'].setData(
x=t,
y=self.transform(data['min']),
connect="finite",
**self.PLOT_ARGS
)
self.curves['max'].setData(
x=t,
y=self.transform(data['max']),
connect="finite",
**self.PLOT_ARGS
)
self.curves['y'].setData(
x=t,
y=self.transform(y),
connect="finite",
**self.PLOT_ARGS
)
# look for any bad (nan) data
# FIXME: bad data handling is expensive. see plot.update() above
bdsegs = []
bad_data = np.isnan(y)
if np.any(bad_data):
nani = np.where(bad_data)[0]
bi = np.where(np.diff(nani) > 1)[0]
bstart = np.append(nani[0], nani[bi + 1])
bend = np.append(nani[bi], nani[-1])
bdsegs = zip(t[bstart], t[bend])
if False:
bad_data = np.isnan(y)
if np.any(bad_data):
nani = np.where(bad_data)[0]
bi = np.where(np.diff(nani) > 1)[0]
bstart = np.append(nani[0], nani[bi + 1])
bend = np.append(nani[bi], nani[-1])
bdsegs = zip(t[bstart], t[bend])
return bdsegs
......@@ -92,6 +92,7 @@ class NDScope(QMainWindow, Ui_MainWindow):
self.last_fetch_cmd = None
self.t0 = 0
self.tscale = (1, 'seconds')
self.plots = []
self.Crosshair = Crosshair()
......@@ -323,10 +324,10 @@ class NDScope(QMainWindow, Ui_MainWindow):
self.data.set_lookback(abs(window[0]))
self.data.start_online()
if window:
self.setXRange(*window)
self.set_tspan(*window)
else:
# FIXME: how do we not hard code the stride here
self.setXRange(-self.data.lookback, 0.0625)
self.set_tspan(-self.data.lookback, 0.0625)
def stop(self, message=None):
"""Stop online mode
......@@ -367,7 +368,7 @@ class NDScope(QMainWindow, Ui_MainWindow):
self.triggerGroup.setChecked(False)
self.data.reset()
self.set_t0(t0)
self.setXRange(window[0], window[1])
self.set_tspan(window[0], window[1])
self._data_update(start, end)
def reset_span(self):
......@@ -378,21 +379,23 @@ class NDScope(QMainWindow, Ui_MainWindow):
for plot in self.plots:
plot.enableAutoRange(axis='y')
if self.data.online:
self.setXRange(-self.data.lookback, 0.0625)
self.set_tspan(-self.data.lookback, 0.0625)
elif self.last_fetch_cmd:
self.fetch(**self.last_fetch_cmd)
def update_range(self):
def update_range(self, *args):
"""update time range on mouse pan/zoom"""
self.updateGPS()
if not self.updateOnRange.isChecked():
return
(xmin, xmax), (ymin, ymax) = self.plot0.viewRange()
start = self.t0 + xmin
end = self.t0 + xmax
lookback = np.abs(xmin)
tmin, tmax = self.get_tspan()
start = self.t0 + tmin
end = self.t0 + tmax
lookback = np.abs(tmin)
self.data.set_lookback(lookback)
self._data_update(start, end)
if self.update_tscale():
self.set_tspan(start, end)
def _data_update(self, start, end):
......@@ -469,45 +472,73 @@ class NDScope(QMainWindow, Ui_MainWindow):
# current preferred for the span. if not, ignore the
# update under the assumption that more appropriate trend
# blocks are on the way (or are already being displayed)
(xmin, xmax), (ymin, ymax) = self.plot0.viewRange()
span = xmax - xmin
if trend != _preferred_trend_for_span(span):
logging.log(5, "DROP")
return
# (xmin, xmax), (ymin, ymax) = self.plot0.viewRange()
# span = (xmax - xmin) * self.tscale[0]
# if trend != _preferred_trend_for_span(span):
# logging.debug("DROP")
# return
pass
for plot in self.plots:
plot.update(data, self.t0)
plot.update(data, self.t0, self.tscale[0])
if trigger and self.trigger.single:
self.stop()
def set_t0(self, t0):
label = 'seconds from {:0.4f} [{}]'.format(
t0,
util.gpstime_str_greg(util.gpstime_parse(t0), fmt=const.DATETIME_FMT),
)
self.referenceTimeLabel.setText(label)
self.t0 = t0
self.update_tlabel()
self.updateGPS()
def reset_t0(self):
(xmin, xmax), (ymin, ymax) = self.plot0.viewRange()
t0 = self.t0 + (xmax+xmin)/2
xd = np.abs(xmax-xmin)/2
tmin, tmax = self.get_t_span()
t0 = self.t0 + (tmax+tmin)/2
td = np.abs(tmax-tmin)/2
self.set_t0(t0)
self.setXRange(-xd, xd)
self._data_update(t0-xd, t0+xd)
self.set_tspan(-td, td)
self._data_update(t0-td, t0+td)
def get_tspan(self):
"""return current relative (start, end) time in seconds"""
(xmin, xmax), (ymin, ymax) = self.plot0.viewRange()
return xmin*self.tscale[0], xmax*self.tscale[0]
def setXRange(self, start, end):
logging.debug('RANGE: {} {}'.format(start, end))
def update_tscale(self):
start, end = self.get_tspan()
span = end - start
for t in const.TIME_AXIS_TRANSFORMS:
if span > 10 * t[0]:
break
if t == self.tscale:
return False
else:
self.tscale = t
return True
def set_tspan(self, start, end):
start /= self.tscale[0]
end /= self.tscale[0]
logging.debug('RANGE: {} {} {}'.format(start, end, self.tscale[1]))
self.plot0.sigXRangeChanged.disconnect()
self.plot0.setXRange(start, end, padding=0, update=False)
self.plot0.sigXRangeChanged.connect(self.update_range)
def update_tlabel(self):
self.referenceTimeLabel.setText(
'{} from {:0.4f} [{}]'.format(
self.tscale[1],
self.t0,
util.gpstime_str_greg(
util.gpstime_parse(self.t0),
fmt=const.DATETIME_FMT,
),
)
)
def updateGPS(self):
(xmin, xmax), (ymin, ymax) = self.plot0.viewRange()
start = self.t0 + xmin
end = self.t0 + xmax
tmin, tmax = self.get_tspan()
start = self.t0 + tmin
end = self.t0 + tmax
self.set_offlineStart(start)
self.set_offlineEnd(end)
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment