├── overview ├── ps2pinout.png └── uart_test.png ├── requirements.txt ├── ASYNC.py ├── LICENSE ├── .gitignore ├── PCA.py ├── main.py ├── FFT.py ├── FILTERS.py ├── DIMENSIONS.py ├── CONVERSION.py └── THRESHOLD.py /overview/ps2pinout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadewg/deside/HEAD/overview/ps2pinout.png -------------------------------------------------------------------------------- /overview/uart_test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zadewg/deside/HEAD/overview/uart_test.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy==1.13.3 2 | scipy==0.19.1 3 | matplotlib==2.1.1 4 | 5 | 6 | #scipy 7 | #matplotlib 8 | #functools 9 | #random 10 | #numpy 11 | #scipy 12 | #pylab 13 | -------------------------------------------------------------------------------- /ASYNC.py: -------------------------------------------------------------------------------- 1 | class Sync: 2 | 3 | def _init__(self, x, y): 4 | self.x = x 5 | self.y = y 6 | 7 | def _isclose(self, a, b, rel_tol=1e-09, abs_tol=0.0): 8 | return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol) 9 | 10 | def check(self, x): 11 | 12 | i, f = 0 13 | while i < (len(x) - 1): 14 | if i > 1: 15 | a = (float(times[i]) - float(times[i - 1])) 16 | b = (float(times[i + 1]) - float(times[i])) 17 | if not (self.isclose(a, b)): 18 | f += 1 19 | 20 | i += 1 21 | if f: 22 | return 'True' 23 | else: 24 | return 'False' 25 | 26 | def resample(self, x, y): 27 | pass 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 zadewg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /PCA.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | import seaborn as sns; sns.set() 5 | from sklearn.decomposition import PCA 6 | 7 | 8 | #rng = np.random.RandomState(1) 9 | #X = np.dot(rng.rand(2, 2), rng.randn(2, 200)).T 10 | 11 | 12 | def draw_base(): 13 | rng = np.random.RandomState(1) 14 | X = np.dot(rng.rand(2, 2), rng.randn(2, 200)).T 15 | 16 | plt.scatter(X[:, 0], X[:, 1]) 17 | plt.axis('equal'); 18 | 19 | 20 | def DRAW_VECTOR(): 21 | rng = np.random.RandomState(1) 22 | X = np.dot(rng.rand(2, 2), rng.randn(2, 200)).T 23 | 24 | pca = PCA(n_components=2) 25 | pca.fit(X) 26 | 27 | print(pca.components_) 28 | print(pca.explained_variance_) 29 | 30 | def draw_vector(v0, v1, ax=None): 31 | ax = ax or plt.gca() 32 | arrowprops=dict(arrowstyle='->', 33 | linewidth=2, 34 | shrinkA=0, shrinkB=0) 35 | ax.annotate('', v1, v0, arrowprops=arrowprops) 36 | 37 | # plot data 38 | plt.scatter(X[:, 0], X[:, 1], alpha=0.2) 39 | for length, vector in zip(pca.explained_variance_, pca.components_): 40 | v = vector * 3 * np.sqrt(length) 41 | draw_vector(pca.mean_, pca.mean_ + v) 42 | plt.axis('equal'); 43 | 44 | 45 | def dimension_red(): 46 | rng = np.random.RandomState(1) 47 | X = np.dot(rng.rand(2, 2), rng.randn(2, 200)).T 48 | 49 | pca = PCA(n_components=1) 50 | pca.fit(X) 51 | X_pca = pca.transform(X) 52 | print("original shape: ", X.shape) 53 | print("transformed shape:", X_pca.shape) 54 | 55 | X_new = pca.inverse_transform(X_pca) 56 | plt.scatter(X[:, 0], X[:, 1], alpha=0.2) 57 | plt.scatter(X_new[:, 0], X_new[:, 1], alpha=0.8) 58 | plt.axis('equal'); 59 | 60 | 61 | plt.figure(1) 62 | draw_base() 63 | 64 | plt.figure(2) 65 | DRAW_VECTOR() 66 | 67 | plt.figure(3) 68 | dimension_red() 69 | 70 | plt.show() 71 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | from DIMENSIONS import SourceData 5 | from FILTERS import NoiseWork 6 | from THRESHOLD import hyst 7 | #from FFT import fft, inverse #FFT USED TO FILTER OUT POWER SUPPLY SINE WAVES 50MHZ FOR ELECTRICAL WIRING IN EUROPE, PLUS COMPUTER POWER SUPPLY 8 | #from DECODERS import * #IF CANT FEED BINARY TO RIPYL, USE DICTIONARY ATTACK FROM RIPYL ALFABET. CHECHK CHECKSUMS TOO. 9 | 10 | #ADD ASYNCRONOUS SAMPLE RADTE CONVERION (GRIDING?) FOR CHEAP OSCILOSCOPES # https://dsp.stackexchange.com/questions/8488/what-is-an-algorithm-to-re-sample-from-a-variable-rate-to-a-fixed-rate 11 | 12 | #ADD PLOTING COUNTER AS FOR UNASYNC, FFT, TO CUSTOMIZE PLOTTING WINDOWS 13 | #ADD COLORS 14 | 15 | x = np.linspace(0,20, 1000) 16 | yy = np.sin(x) + 4 17 | 18 | 19 | # np.random.seed(1234) 20 | # 21 | # time_step = 0.02 22 | # period = 5. 23 | # 24 | # time_vec = np.arange(0, 20, time_step) 25 | # sig = (np.sin(2 * np.pi / period * time_vec) + 0.5 * np.random.randn(time_vec.size)) 26 | # 27 | # plt.figure(figsize=(6, 5)) 28 | # plt.plot(time_vec, sig, label='Original signal') 29 | 30 | 31 | 32 | plt.figure('Demo') 33 | 34 | Y = SourceData(X=x, Y=yy).Ypoints 35 | 36 | plt.subplot(231) 37 | plt.title('Original') 38 | plt.plot(Y) 39 | 40 | 41 | n = NoiseWork(Y).AN 42 | 43 | plt.subplot(232) 44 | plt.title('Received') 45 | plt.plot(n) 46 | 47 | 48 | ma = NoiseWork(n, window_len=100).MA 49 | 50 | plt.subplot(233) 51 | plt.title('Denoised') 52 | plt.plot(ma) 53 | 54 | 55 | denoised= ma 56 | q_u = hyst(denoised, ma, 3, 60, 10).q_u 57 | q_l = hyst(denoised, ma, 3, 60, 10).q_l 58 | 59 | plt.subplot(234) 60 | plt.title('Treshold Calc.') 61 | plt.plot(ma);plt.hold(True);plt.plot(q_u,'r');plt.plot(q_l,'g') 62 | 63 | 64 | offset = hyst(Y, ma, 3, 60, 10).offset 65 | hysts = hyst(Y, ma, 3, 60, 10).hysts 66 | 67 | plt.subplot(235) 68 | plt.title('Hystheresis') 69 | plt.plot(offset+hysts[0], 'y');plt.hold(True);plt.plot(offset+hysts[1], 'orange');plt.plot(offset+hysts[2], 'black') 70 | 71 | plt.subplot(236) 72 | plt.title('Digital 1') 73 | plt.plot(hysts[0], 'o', color='C1') 74 | 75 | plt.tight_layout(True) 76 | plt.gray() 77 | plt.show() 78 | -------------------------------------------------------------------------------- /FFT.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import scipy 3 | import matplotlib.pyplot as plt 4 | 5 | np.random.seed(1234) 6 | 7 | time_step = 0.02 8 | period = 5. 9 | time_vec = np.arange(0, 20, time_step) 10 | 11 | signal = (np.sin(2 * np.pi / period * time_vec) + 0.5 * np.random.randn(time_vec.size)) 12 | 13 | 14 | class Fourier: 15 | 16 | def __init__(self, *args, **kwargs): 17 | pass 18 | 19 | 20 | def _fft(self, signal): 21 | fft = scipy.fft(signal) 22 | return fft 23 | 24 | 25 | def _ifft(self, signal): 26 | ifft = scipy.ifft(signal) 27 | return ifft 28 | 29 | 30 | def normalize(self, sig1, sig2, *args): 31 | 32 | # sig1 = sig1/max(sig1) 33 | # sig2 = sig2/max(sig2) 34 | # return sig1, sig2 35 | 36 | signals = [] 37 | signals.append(sig1, sig2, args) 38 | 39 | for sig in signals: 40 | sig = sig/max(sig) 41 | 42 | return signals 43 | 44 | def calc_err(self, sig1, sig2): 45 | err = abs(sig1) - abs(sig2) 46 | return err 47 | 48 | 49 | def degrid(self, sig, freq=[50]): #https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.iirnotch.html 50 | 51 | sig_fft = self._fft(sig) # The FFT of the signal. 52 | 53 | bp = sig_fft[:] 54 | for i in range(len(bp)): # Frequency cancelation. 55 | for f in freq: 56 | 57 | if f == 'europe': 58 | f = 50 59 | if f == 'america': 60 | f = 60 61 | 62 | if i == f: 63 | bp[i] = 0 64 | 65 | filtered = self._ifft(bp) # Band filtered out. 66 | return filtered 67 | 68 | # Required input defintions are as follows; 69 | # time: Time between samples 70 | # band: The bandwidth around the centerline freqency that you wish to filter 71 | # freq: The centerline frequency to be filtered 72 | # ripple: The maximum passband ripple that is allowed in db 73 | # order: The filter order. For FIR notch filters this is best set to 2 or 3, 74 | # IIR filters are best suited for high values of order. This algorithm 75 | # is hard coded to FIR filters 76 | # filter_type: 'butter', 'bessel', 'cheby1', 'cheby2', 'ellip' 77 | # data: the data to be filtered 78 | #def Implement_Notch_Filter(time, band, freq, ripple, order, filter_type, data): 79 | # from scipy.signal import iirfilter 80 | # fs = 1/time 81 | # nyq = fs/2.0 82 | # low = freq - band/2.0 83 | # high = freq + band/2.0 84 | # low = low/nyq 85 | # high = high/nyq 86 | # b, a = iirfilter(order, [low, high], rp=ripple, btype='bandstop', 87 | # analog=False, ftype=filter_type) 88 | # filtered_data = lfilter(b, a, data) 89 | # return filtered_data 90 | 91 | 92 | 93 | 94 | ''' 95 | fourier = Fourier() 96 | out = fourier.degrid(signal) 97 | 98 | plt.plot(out, 'r') 99 | plt.show() 100 | ''' 101 | -------------------------------------------------------------------------------- /FILTERS.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import random 3 | import numpy as np 4 | from scipy.signal import lfilter, savgol_filter 5 | 6 | 7 | def exception_handler(f): 8 | @functools.wraps(f) 9 | def func(*args, **kwargs): 10 | try: 11 | return f(*args, **kwargs) 12 | 13 | except Exception as e: 14 | print('Caught an exception in', f.__name__) 15 | try: 16 | print("\n{}".format(eval(f.__name__).__doc__)) 17 | except: 18 | pass 19 | print(e) 20 | raise SystemExit 21 | return func 22 | 23 | @exception_handler 24 | class NoiseWork(): 25 | 26 | 27 | """ 28 | Feed Y values only. 'Noise Reduction' (Function smoothing): Finite Impulse Response, Moving Average, Savitzky-Golay. 29 | Noise Generation: function adds given noise in array form. Else, it wil generate random noise. (Noise array must be exactly same length as data array) 30 | Moving Average Takes (Optional) window length and type arguments. Window vector must be smaller than input vector. 31 | Supported window types: flat, hanning, hammnig, barlett, blackman. Default: 3, flat. 32 | ALL ARGUMENTS ARE FED TO CLASS CALL; METHOD CALLS TAKE NO ARGUMENTS: 33 | 34 | y = NoiseWork(y, window_len=11, window='hannning').MA #This is correct 35 | y = NoiseWork().MA(y, window_len=11, window='hannning') #This is not correct 36 | """ 37 | 38 | 39 | def __init__(self, data, *arg, **kwargs): 40 | self.FIR = self._fir(data) 41 | self.MA = self._ma(data, **kwargs) 42 | self.SG = self._sg(data) 43 | if arg is not None: 44 | self.AN = self._an(data, *arg) 45 | else: 46 | self.AN = self._an(data) 47 | 48 | 49 | def _an(self, data, *arg): 50 | #if arg is not None and len(arg[0]) == len(y): 51 | # y = [sum(x) for x in zip(y, arg)] # delete []? 52 | #else: 53 | # y += (np.random.normal(0, 1, y.shape))/5 #+ int = + noise 54 | #return y 55 | data += (np.random.normal(0, 1, data.shape))/5 #+ int = + noise 56 | return data 57 | 58 | 59 | def _fir(self, data): 60 | n = 1000; 61 | a, b = 1, [1.0 / n] * n # the larger n is, the smoother curve will be {15} 62 | data = lfilter(b, a, data) 63 | return data 64 | 65 | 66 | def _ma(self, data, window_len=11, window='hanning'): 67 | try: 68 | data.ndim; 69 | except: 70 | data = np.array(data) 71 | 72 | s = np.r_[data[window_len-1:0:-1], data, data[-2:-window_len-1:-1]] 73 | if window == 'flat': 74 | w = np.ones(window_len, 'd') 75 | else: 76 | w = eval('np.' + window + '(window_len)') 77 | 78 | data = np.convolve(w/w.sum(), data, mode='valid') 79 | return data[int(window_len/2-1):int(-(window_len/2)-1)] 80 | 81 | 82 | def _mm(self, data, window_len=11, window='hanning'): #less vulnerable to single spikes. MOVING MEDIAN ·calulate stdev in auto mode to choose this one over MA 83 | pass 84 | 85 | 86 | def _sg(self, data): 87 | data = savgol_filter(data, 101, 2) 88 | return data 89 | 90 | 91 | -------------------------------------------------------------------------------- /DIMENSIONS.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import numpy as np 3 | 4 | 5 | def exception_handler(f): 6 | @functools.wraps(f) 7 | def func(*args, **kwargs): 8 | try: 9 | return f(*args, **kwargs) 10 | 11 | except Exception as e: 12 | print('Caught an exception in', f.__name__) 13 | try: 14 | print("\n{}".format(eval(f.__name__).__doc__)) 15 | except: 16 | pass 17 | print(e) 18 | raise SystemExit 19 | return func 20 | 21 | 22 | @exception_handler 23 | class SourceData: 24 | 25 | 26 | """ 27 | Feed collected data here, no matter dimension. 28 | Arg keys: Matrix, X, Y, CSV. 29 | Returns: Matrix, Xpoints, Ypoints. 30 | 31 | Example: y = SourceData(CSV='data.csv').Ypoints 32 | """ 33 | 34 | 35 | def __init__(self, **kwargs): 36 | if kwargs is not None: 37 | for key, value in kwargs.items(): 38 | if key == 'Matrix': 39 | self.Xpoints = self._genonedee(value)[0] 40 | self.Ypoints = self._genonedee(value)[1] 41 | self.Matrix = value 42 | elif key == 'X': 43 | self.Xpoints = value 44 | elif key == 'Y': 45 | self.Ypoints = value 46 | elif key == 'CSV': 47 | self.Xpoints = self._genonedee(self._fileinput(value))[0] 48 | self.Ypoints = self._genonedee(self._fileinput(value))[1] 49 | self.Matrix = self._fileinput(value) 50 | 51 | if len(locals()) > 1: 52 | self.Matrix = self._gentwodee(self.Xpoints, self.Ypoints) 53 | #else: raise Exception 54 | 55 | def parse_csv(self, inputfile): 56 | matrix = [] 57 | f = open(inputfile, "r") 58 | lines = f.read().split("\n") 59 | for line in lines: 60 | if line != "": # add other needed checks to skip titles 61 | array = line.split(",") 62 | matrix.append(array) 63 | return matrix 64 | 65 | def _fileinput(self, FILE): 66 | return self.parse_csv(FILE) 67 | 68 | def _genonedee(self, matrix): 69 | x, y = [], [] 70 | for i in matrix: 71 | x.append((i)[0]) 72 | y.append((i)[1]) 73 | return x, y 74 | 75 | def _gentwodee(self, Xpoints, Ypoints): 76 | w, h = 2, len(Xpoints) 77 | matrix = [[0 for x in range(w)] for y in range(h)] 78 | for i in range(0,h): 79 | matrix[i][0] = Xpoints[i] 80 | matrix[i][1] = Ypoints[i] 81 | return matrix 82 | 83 | 84 | 85 | if len(locals()) > 1: 86 | self.Matrix = self._gentwodee(self.Xpoints, self.Ypoints) 87 | #else: raise Exception 88 | 89 | def parse_csv(inputfile): 90 | matrix = [] 91 | f = open(inputfile, "r") 92 | lines = f.read().split("\n") 93 | for line in lines: 94 | if line != "": # add other needed checks to skip titles 95 | array = line.split(",") 96 | matrix.append(array) 97 | return matrix 98 | 99 | def _fileinput(FILE): 100 | return parse_csv(FILE) 101 | 102 | def _genonedee(self, matrix): 103 | x, y = [], [] 104 | for i in matrix: 105 | x.append((i)[0]) 106 | y.append((i)[1]) 107 | return x, y 108 | 109 | def _gentwodee(self, Xpoints, Ypoints): 110 | w, h = 2, len(Xpoints) 111 | matrix = [[0 for x in range(w)] for y in range(h)] 112 | for i in range(0,h): 113 | matrix[i][0] = Xpoints[i] 114 | matrix[i][1] = Ypoints[i] 115 | return matrix 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /CONVERSION.py: -------------------------------------------------------------------------------- 1 | 2 | import csv 3 | import argparse 4 | 5 | def read_lecroy_csv(fname): 6 | raw_samples = [] 7 | raw_times = [] 8 | with open(fname, 'rb') as csvfile: 9 | c = csv.reader(csvfile) 10 | 11 | # Sample period: cell B2 (1,1) 12 | # Time: column D (3) 13 | # Samples: column E (4) 14 | 15 | csvfile.seek(0) 16 | for row in c: 17 | raw_samples.append(float(row[4])) 18 | raw_times.append(float(row[3])) 19 | 20 | return raw_samples, raw_times 21 | 22 | 23 | def read_rigol_csv(fname, channel=1): 24 | sample_period = 0.0 25 | raw_samples = [] 26 | raw_times = [0] 27 | sample_count = 0 28 | 29 | with open(fname, 'rb') as csvfile: 30 | c = csv.reader(csvfile) 31 | 32 | for row_num, row in enumerate(c): 33 | if row_num == 1: 34 | sample_period = float(row[1].split(':')[1]) 35 | sample_count = int(row[3].split(':')[1]) 36 | 37 | if len(row) > 0 and row[0] == 'X': 38 | break 39 | 40 | for row in c: 41 | if sample_count > 0: 42 | raw_samples.append(float(row[(channel-1)*2 + 1])) 43 | 44 | sample_count -= 1 45 | 46 | for i in len(raw_samples): 47 | raw_times.append(sample_period * i) 48 | 49 | return raw_samples, raw_times 50 | 51 | 52 | def read_tek_tds2000_csv(fname): 53 | sample_period = 0.0 54 | raw_samples = [] 55 | raw_times = [0] 56 | 57 | with open(fname, 'rb') as csvfile: 58 | c = csv.reader(csvfile) 59 | 60 | # Sample period is in cell B2 (1,1) 61 | 62 | for row_num, row in enumerate(c): 63 | if row_num == 1: # get the sample period 64 | sample_period = float(row[1]) 65 | break 66 | 67 | # Sample data starts after the last header line 68 | # containing the firmware version. 69 | in_header = True 70 | for row in c: 71 | if in_header: 72 | if row[0] == 'Firmware Version': 73 | in_header = False 74 | else: 75 | raw_samples.append(float(row[4])) 76 | 77 | for i in len(raw_samples): 78 | raw_times.append(sample_period * i) 79 | 80 | return raw_samples, raw_times 81 | 82 | def parse(): 83 | global OSCIL, CHAN, IFILE, OFILE 84 | 85 | parser = argparse.ArgumentParser(description='https://github.com/zadewg/deside') 86 | 87 | parser.add_argument('-if','--infile', help='Input data filename\n', required=True) 88 | parser.add_argument('-of','--outfile', help='Output data filename\n', required=True) 89 | parser.add_argument('-o','--oscilloscope', help='Oscilloscope brand. Supported: Rigol, LeCroix, Tektronix\n', required=True) 90 | parser.add_argument('-c','--channel', help='Specify channel if neccesary. Default=1\n', required=False) 91 | 92 | args = vars(parser.parse_args()) 93 | 94 | OSCIL = args['oscilloscope'] 95 | CHAN = args['channel'] or 1 96 | IFILE = args['infile'] 97 | OFILE = args['infile'] 98 | 99 | 100 | def main(): 101 | 102 | parse() 103 | 104 | if OSCIL.lower() == 'rigol': 105 | data = read_rigol_csv(IFILE, channel=CHAN); 106 | times = data[1] 107 | values = data[0] 108 | 109 | elif OSCIL.lower() == 'lecroix': 110 | data = read_lecroy_csv(IFILE); 111 | times = data[1] 112 | values = data[0] 113 | 114 | elif OSCIL.lower() == 'tektronix': 115 | data = read_tek_tds2000_csv(IFILE); 116 | times = data[1] 117 | values = data[0] 118 | 119 | else: 120 | print('Oscilloscope not supported') 121 | raise SystemExit 122 | 123 | 124 | ofile = open(('{}.csv'.format(OFILE)), "wb") 125 | writer = csv.writer(ofile, delimiter='', quotechar='"', quoting=csv.QUOTE_ALL) 126 | 127 | for i in len(times): 128 | writer.writerow(times[i], values[i]) 129 | 130 | ofile.close() 131 | 132 | if __name__ == "__main__": 133 | main() 134 | 135 | -------------------------------------------------------------------------------- /THRESHOLD.py: -------------------------------------------------------------------------------- 1 | import functools 2 | import numpy as np 3 | from scipy.interpolate import interp1d 4 | 5 | 6 | def exception_handler(f): 7 | @functools.wraps(f) 8 | def func(*args, **kwargs): 9 | try: 10 | return f(*args, **kwargs) 11 | 12 | except Exception as e: 13 | print('Caught an exception in', f.__name__) 14 | 15 | try: 16 | print("\n{}".format(eval(f.__name__).__doc__)) 17 | except: 18 | pass 19 | 20 | print(e) 21 | raise SystemExit 22 | return func 23 | 24 | @exception_handler 25 | class hyst: #make this class object oriented 26 | 27 | 28 | """ 29 | Aaight fellas, meaningful docstring is it? I've just had a mare night, some massive flying circus geezer came running out the bushes at me, 30 | God knows why. Anyways, I've lamped him, he's gone flying now... I think I might have killed him. 31 | """ 32 | 33 | 34 | def __init__(self, y, MA, nth, uth, lth): 35 | ENV = self._env(y) 36 | self.q_u = ENV[0] 37 | self.q_l = ENV[1] 38 | 39 | out = self._threshold(y, MA, nth=3, uth=60, lth=10) 40 | self.th_his = out[0] 41 | self.th_los = out[1] 42 | self.offset = out[2] 43 | 44 | self.hysts = [] 45 | for i in range(0, nth): 46 | self.hysts.append(self._hystheresis(y, self.th_los[i], self.th_his[i])) 47 | 48 | 49 | def _env(self, y): 50 | try: 51 | y.ndim; 52 | except: 53 | y = np.array(y) 54 | 55 | q_u, q_l = np.zeros(y.shape), np.zeros(y.shape) 56 | 57 | u_x = [0,] 58 | u_y = [y[0],] 59 | l_x = [0,] 60 | l_y = [y[0],] 61 | 62 | for k in range(1,len(y)-1): 63 | if (np.sign(y[k]-y[k-1])==1) and (np.sign(y[k]-y[k+1])==1): 64 | u_x.append(k) 65 | u_y.append(y[k]) 66 | 67 | if (np.sign(y[k]-y[k-1])==-1) and ((np.sign(y[k]-y[k+1]))==-1): 68 | l_x.append(k) 69 | l_y.append(y[k]) 70 | 71 | u_x.append(len(y)-1) 72 | u_y.append(y[-1]) 73 | l_x.append(len(y)-1) 74 | l_y.append(y[-1]) 75 | 76 | #Fit suitable models to the data. cubic splines, similar to the MATLAB example. 77 | u_p = interp1d(u_x,u_y, kind = 'cubic',bounds_error = False, fill_value=0.0) 78 | l_p = interp1d(l_x,l_y,kind = 'cubic',bounds_error = False, fill_value=0.0) 79 | 80 | for k in range(0, len(y)): 81 | q_u[k] = u_p(k) 82 | q_l[k] = l_p(k) 83 | 84 | return q_u, q_l 85 | 86 | 87 | def _threshold(self, y, ma, nth=3, uth=60, lth=10): #number of triggers, max/min th (%) 88 | 89 | """ Y points, Moving Average, number of thresholds, maximum th, minimum th (%)""" 90 | 91 | utrigs, ltrigs = [], [] 92 | 93 | #[1:-1] because lateral boundaries are not representative 94 | ma = (ma[1:-1]).mean() 95 | uenv = (self.q_u[1:-1]).mean() 96 | lenv = (self.q_l[1:-1]).mean() 97 | 98 | uth = uth / 100.0 99 | lth = lth / 100.0 100 | 101 | pbase = ma + (ma * lth) # min th_lotohi 102 | nbase = ma - (ma * lth) # min th_hitolo 103 | 104 | avamp = (((uenv - ma) + (ma - lenv)) / 2.0) 105 | offset = ma - (avamp / 2.0) 106 | 107 | pbound = ma + (avamp * uth) #max th_lotohi 108 | nbound = ma + (avamp * uth) #max th_hitolo 109 | 110 | wr = (pbound - pbase) / float(nth) 111 | 112 | for i in range(0, nth): 113 | utrigs.append(pbase + (wr * i)) 114 | ltrigs.append(nbase - (wr * i)) 115 | 116 | return utrigs, ltrigs, offset 117 | 118 | 119 | def _hystheresis(self, y, th_lo, th_hi, initial = False): 120 | 121 | # y : Numpy Array 122 | # Series to apply hysteresis to. 123 | # th_lo : float or int 124 | # Below this threshold the value of hyst will be False (0). 125 | # th_hi : float or int 126 | # Above this threshold the value of hyst will be True (1). 127 | 128 | try: 129 | y.ndim; 130 | except: 131 | y = np.array(y) 132 | 133 | if th_lo > th_hi: # If thresholds are reversed, x must be reversed as well 134 | y = y[::-1] 135 | th_lo, th_hi = th_hi, th_lo 136 | rev = True 137 | else: 138 | rev = False 139 | 140 | hi = y >= th_hi 141 | lo_or_hi = (y <= th_lo) | hi 142 | 143 | ind = np.nonzero(lo_or_hi)[0] 144 | 145 | if not ind.size: # prevent index error if ind is empty 146 | y_hyst = np.zeros_like(y, dtype=bool) | initial 147 | else: 148 | cnt = np.cumsum(lo_or_hi) # from 0 to len(x) 149 | y_hyst = np.where(cnt, hi[ind[cnt-1]], initial) 150 | 151 | if rev: 152 | y_hyst = y_hyst[::-1] 153 | return y_hyst 154 | 155 | --------------------------------------------------------------------------------