def window(series, start=0, end=None): """ desc: Extracts a window from a signal. arguments: series: desc: The signal to get a window from. type: SeriesColumn keywords: start: desc: The window start. type: int end: desc: The window end, or None to go to the signal end. type: [int, None] returns: desc: A window of the signal. type: SeriesColumn """ if end is None: end = series.depth a = series[:,start:end] depth = a.shape[1] window_series = _SeriesColumn(series._datamatrix, depth) window_series[:] = a return window_series
def _apply_fnc(series, fnc, **kwdict): """ visible: False desc: Applies a function to each cell. arguments: series: desc: A signal to apply the function to. type: SeriesColumn fnc: desc: The function to apply. keyword-dict: kwdict: A dict with keyword arguments for fnc. returns: desc: A new signal. type: SeriesColumn """ new_series = _SeriesColumn(series._datamatrix, depth=series.depth) for i, cell in enumerate(series): new_series[i] = fnc(cell, **kwdict) return new_series
def endlock(series): """ desc: Locks a series to the end, so that any nan-values that were at the end are moved to the front. arguments: series: desc: The signal to end-lock. type: SeriesColumn returns: desc: An end-locked signal. type: SeriesColumn """ endlock_series = _SeriesColumn(series._datamatrix, series.depth) endlock_series[:] = np.nan for i in range(len(series)): for j in range(series.depth-1, -1, -1): if not np.isnan(series[i,j]): break endlock_series[i,-j-1:] = series[i,:j+1] return endlock_series
def threshold(series, fnc, min_length=1): """ desc: Finds samples that satisfy some threshold criterion for a given period. arguments: series: desc: A signal to threshold. type: SeriesColumn fnc: desc: A function that takes a single value and returns True if this value exceeds a threshold, and False otherwise. type: FunctionType keywords: min_length: desc: The minimum number of samples for which `fnc` must return True. type: int returns: desc: A series where 0 indicates below threshold, and 1 indicates above threshold. type: SeriesColumn """ threshold_series = _SeriesColumn(series._datamatrix, series.depth) threshold_series[:] = 0 # First walk through all rows for i, trace in enumerate(series): print() # Then walk through all samples within a row nhit = 0 for j, val in enumerate(trace): hit = fnc(val) if hit: nhit += 1 continue if nhit >= min_length: threshold_series[i,j-nhit:j] = 1 nhit = 0 if nhit >= min_length: threshold_series[i,j-nhit:j] = 1 return threshold_series
def concatenate(*series): """ desc: | Concatenates multiple series such that a new series is created with a depth that is equal to the sum of the depths of all input series. __Example:__ %-- python: | from datamatrix import series as srs dm = DataMatrix(length=1) dm.s1 = SeriesColumn(depth=3) dm.s1[:] = 1,2,3 dm.s2 = SeriesColumn(depth=3) dm.s2[:] = 3,2,1 dm.s = srs.concatenate(dm.s1, dm.s2) print(dm.s) --% argument-list: series: A list of series. returns: desc: A new series. type: SeriesColumn """ if not series or not all(isinstance(s, _SeriesColumn) for s in series): raise TypeError(u'Expecting one or more SeriesColumn objects') if not all(s.dm is series[0].dm for s in series): raise ValueError( u'SeriesColumn objects don\'t belong to the same DataMatrix') newseries = _SeriesColumn(series[0]._datamatrix, depth=sum(s.depth for s in series)) i = 0 for s in series: newseries[:,i:i+s.depth] = s i += s.depth return newseries
def endlock(series): """ desc: | Locks a series to the end, so that any nan-values that were at the end are moved to the start. __Example:__ %-- python: | import numpy as np from matplotlib import pyplot as plt from datamatrix import DataMatrix, SeriesColumn, series LENGTH = 5 # Number of rows DEPTH = 10 # Depth (or length) of SeriesColumns sinewave = np.sin(np.linspace(0, 2*np.pi, DEPTH)) dm = DataMatrix(length=LENGTH) # First create five identical rows with a sinewave dm.y = SeriesColumn(depth=DEPTH) dm.y.setallrows(sinewave) # Add a random offset to the Y values dm.y += np.random.random(LENGTH) # Set some observations at the end to nan for i, row in enumerate(dm): row.y[-i:] = np.nan # Lock the degraded traces to the end, so that all nans # now come at the start of the trace dm.y2 = series.endlock(dm.y) plt.clf() plt.subplot(121) plt.title('Original (nans at end)') plt.plot(dm.y.plottable) plt.subplot(122) plt.title('Endlocked (nans at start)') plt.plot(dm.y2.plottable) plt.savefig('content/pages/img/series/endlock.png') --% %-- figure: source: endlock.png id: FigEndLock --% arguments: series: desc: The signal to end-lock. type: SeriesColumn returns: desc: An end-locked signal. type: SeriesColumn """ endlock_series = _SeriesColumn(series._datamatrix, series.depth) endlock_series[:] = np.nan src = series._seq dst = endlock_series._seq for rownr, row in enumerate(src): nancols = np.where(np.isnan(row))[0] for nancol in nancols: if np.any(~np.isnan(row[nancol:])): continue dst[rownr, -nancol:] = row[:nancol] break else: dst[rownr] = row return endlock_series
def threshold(series, fnc, min_length=1): """ desc: | Finds samples that satisfy some threshold criterion for a given period. __Example:__ %-- python: | import numpy as np from matplotlib import pyplot as plt from datamatrix import DataMatrix, SeriesColumn, series LENGTH = 1 # Number of rows DEPTH = 100 # Depth (or length) of SeriesColumns sinewave = np.sin(np.linspace(0, 2*np.pi, DEPTH)) dm = DataMatrix(length=LENGTH) # First create five identical rows with a sinewave dm.y = SeriesColumn(depth=DEPTH) dm.y.setallrows(sinewave) # And also a bit of random jitter dm.y += np.random.random( (LENGTH, DEPTH) ) # Threshold the signal by > 0 for at least 10 samples dm.t = series.threshold(dm.y, fnc=lambda y: y > 0, min_length=10) plt.clf() # Mark the thresholded signal plt.fill_between(np.arange(DEPTH), dm.t[0], color='black', alpha=.25) plt.plot(dm.y.plottable) plt.savefig('content/pages/img/series/threshold.png') print(dm) --% %-- figure: source: threshold.png id: FigThreshold --% arguments: series: desc: A signal to threshold. type: SeriesColumn fnc: desc: A function that takes a single value and returns True if this value exceeds a threshold, and False otherwise. type: FunctionType keywords: min_length: desc: The minimum number of samples for which `fnc` must return True. type: int returns: desc: A series where 0 indicates below threshold, and 1 indicates above threshold. type: SeriesColumn """ threshold_series = _SeriesColumn(series._datamatrix, series.depth) threshold_series[:] = 0 # First walk through all rows for i, trace in enumerate(series): # Then walk through all samples within a row nhit = 0 for j, val in enumerate(trace): hit = fnc(val) if hit: nhit += 1 continue if nhit >= min_length: threshold_series[i, j - nhit:j] = 1 nhit = 0 if nhit >= min_length: threshold_series[i, j - nhit + 1:j + 1] = 1 return threshold_series
def normalize_time(dataseries, timeseries): """ desc: | *New in v0.7.0* Creates a new series in which a series of timestamps (`timeseries`) is used as the indices for a series of data point (`dataseries`). This is useful, for example, if you have a series of measurements and a separate series of timestamps, and you want to combine the two. The resulting series will generally contain a lot of `nan` values, which you can interpolate with `interpolate()`. __Example:__ %-- python: | from matplotlib import pyplot as plt from datamatrix import DataMatrix, SeriesColumn, series as srs, NAN # Create a DataMatrix with one series column that contains samples # and one series column that contains timestamps. dm = DataMatrix(length=2) dm.samples = SeriesColumn(depth=3) dm.time = SeriesColumn(depth=3) dm.samples[0] = 3, 1, 2 dm.time[0] = 1, 2, 3 dm.samples[1] = 1, 3, 2 dm.time[1] = 0, 5, 10 # Create a normalized column with samples spread out according to # the timestamps, and also create an interpolate version of this # column for smooth plotting. dm.normalized = srs.normalize_time( dataseries=dm.samples, timeseries=dm.time ) dm.interpolated = srs.interpolate(dm.normalized) # And plot! plt.clf() plt.plot(dm.normalized.plottable, 'o') plt.plot(dm.interpolated.plottable, ':') plt.xlabel('Time') plt.ylabel('Data') plt.savefig('content/pages/img/series/normalize_time.png') --% %-- figure: source: normalize_time.png id: FigNormalizeTime --% arguments: dataseries: desc: A column with datapoints. type: SeriesColumn timeseries: desc: A column with timestamps. This should be an increasing list of the same depth as `dataseries`. NAN values are allowed, but only at the end. type: SeriesColumn returns: desc: A new series in which the data points are spread according to the timestamps. type: SeriesColumn """ if (not isinstance(dataseries, _SeriesColumn) or not isinstance(timeseries, _SeriesColumn)): raise TypeError( 'dataseries and timeseries should be SeriesColumn objects') if dataseries.dm is not timeseries.dm: raise ValueError( 'dataseries and timeseries should belong to the same DataMatrix') if dataseries.depth != timeseries.depth: raise ValueError( 'dataseries and timeseries should have the same depth') if max(timeseries.max) < 0 or min(timeseries.min) < 0: raise ValueError('timeseries should contain only positive values') series = _SeriesColumn(dataseries.dm, depth=int(max(timeseries.max)) + 1) haystack = np.arange(series.depth, dtype=int) for row in range(series._seq.shape[0]): needle = timeseries._seq[row] values = dataseries._seq[row] while len(needle) and np.isnan(needle)[-1]: needle = needle[:-1] values = values[:-1] if np.any(np.isnan(needle)): raise ValueError( 'timeseries should not contain NAN values, except at the end') if not np.all(np.diff(needle) > 0): raise ValueError('timeseries should contain increasing values ' '(i.e. time should go forward)') indices = np.searchsorted(haystack, needle) series._seq[row, indices] = values return series
def lock(series, lock): """ desc: | Shifts each row from a series by a certain number of steps along its depth. This is useful to lock, or align, a series based on a sequence of values. __Example:__ %-- python: | import numpy as np from matplotlib import pyplot as plt from datamatrix import DataMatrix, SeriesColumn, series as srs LENGTH = 5 # Number of rows DEPTH = 10 # Depth (or length) of SeriesColumns dm = DataMatrix(length=LENGTH) # First create five traces with a partial cosinewave. Each row is # offset slightly on the x and y axes dm.y = SeriesColumn(depth=DEPTH) dm.x_offset = -1 dm.y_offset = -1 for row in dm: row.x_offset = np.random.randint(0, DEPTH) row.y_offset = np.random.random() row.y = np.roll(np.cos(np.linspace(0, np.pi, DEPTH)), row.x_offset)+row.y_offset # Now use the x offset to lock the traces to the 0 point of the cosine, # i.e. to their peaks. dm.y2, zero_point = srs.lock(dm.y, lock=dm.x_offset) plt.clf() plt.subplot(121) plt.title('Original') plt.plot(dm.y.plottable) plt.subplot(122) plt.title('Locked to peak') plt.plot(dm.y2.plottable) plt.axvline(zero_point, color='black', linestyle=':') plt.savefig('content/pages/img/series/lock.png') --% %-- figure: source: lock.png id: FigLock --% arguments: series: desc: The signal to lock. type: SeriesColumn lock: desc: A sequence of lock values with the same length as the Series. This can be a column, a list, a numpy array, etc. returns: desc: A `(series, zero_point)` tuple, in which `series` is a `SeriesColumn` and `zero_point` is the zero point to which the signal has been locked. """ if not isinstance(series, _SeriesColumn): raise TypeError('series should be a SeriesColumn') if len(series) != len(lock): raise ValueError('lock and series should be the same length') try: zero_point = int(max(lock)) except TypeError: raise TypeError('lock should be a sequence of integers') lpad = [int(zero_point - l) for l in lock] lock_series = _SeriesColumn(series.dm, series.depth + max(lpad)) for lpad, lock_row, orig_row in zip(lpad, lock_series, series): lock_row[lpad:lpad + series.depth] = orig_row return lock_series, zero_point
def fft(series, truncate=True): """ desc: | *New in v0.9.2* Performs a fast-fourrier transform (FFT) for the signal. For more information, see [`numpy.fft`](https://docs.scipy.org/doc/numpy/reference/routines.fft.html#module-numpy.fft). __Example:__ %-- python: | import numpy as np from matplotlib import pyplot as plt from datamatrix import DataMatrix, SeriesColumn, series LENGTH = 3 DEPTH = 200 # Create one fast oscillation, and two combined fast and slow oscillations dm = DataMatrix(length=LENGTH) dm.s = SeriesColumn(depth=DEPTH) dm.s[0] = np.sin(np.linspace(0, 150 * np.pi, DEPTH)) dm.s[1] = np.sin(np.linspace(0, 75 * np.pi, DEPTH)) dm.s[2] = np.sin(np.linspace(0, 10 * np.pi, DEPTH)) dm.f = series.fft(dm.s) # Plot the original signal plt.clf() plt.subplot(121) plt.title('Original') plt.plot(dm.s[0]) plt.plot(dm.s[1]) plt.plot(dm.s[2]) plt.subplot(122) # And the filtered signal! plt.title('FFT') plt.plot(dm.f[0]) plt.plot(dm.f[1]) plt.plot(dm.f[2]) plt.savefig('content/pages/img/series/fft.png') --% %-- figure: source: fft.png id: FigFFT --% arguments: series: desc: A signal to determine the FFT for. type: SeriesColumn keywords: truncate: desc: FFT series of real signals are symmetric. The `truncate` keyword indicates whether the last (symmetric) part of the FFT should be removed. type: bool returns: desc: The FFT of the signal. type: SeriesColumn """ newseries = _SeriesColumn(series._datamatrix, depth=series.depth) newseries[:] = np.fft.fft(series, axis=1) if truncate: newseries.depth = newseries.depth // 2 return newseries