diff --git a/k-teng/k-teng-interactive.py b/k-teng/k_teng_interactive.py similarity index 96% rename from k-teng/k-teng-interactive.py rename to k-teng/k_teng_interactive.py index 665ff1b..a0d06f3 100644 --- a/k-teng/k-teng-interactive.py +++ b/k-teng/k_teng_interactive.py @@ -30,6 +30,7 @@ if __name__ == "__main__": from .keithley import keithley as _keithley from .utility import data as _data +from .utility.data import load_dataframe from .utility import file_io from .utility import testing @@ -155,6 +156,15 @@ def measure(max_measurements=None): You can take the data from the buffer afterwards, using save_csv """ _measure(max_measurements=max_measurements, monitor=False) +def automeasure(repeat, repeat_delay=0, max_measurements=None, max_points_shown=120, monitor=True): + """ + Measure and save to csv multiple times + """ + for i in range(repeat): + _measure(max_measurements=max_measurements, max_points_shown=max_points_shown, monitor=monitor) + save_csv() + sleep(repeat_delay) + def get_dataframe(): """ @@ -199,20 +209,6 @@ def save_pickle(): df.to_pickle(filename) print(f"Saved as '{filename}'") -def load_dataframe(p:str): - """ - Load a dataframe from file. - @param p : path of the file. If it has 'csv' extension, pandas.read_csv is used, pandas.read_pickle otherwise - """ - if not path.isfile(p): - print(f"ERROR: load_dataframe: File does not exist: {p}") - return None - if p.endswith(".csv"): - df = pd.read_csv(p) - else: - df = pd.read_pickle(p) - return df - def run_script(script_path): """ Run a lua script on the Keithley device @@ -253,6 +249,7 @@ def help(topic=None): Functions: measure - measure the voltage monitor - measure the voltage with live monitoring in a matplotlib window + automeasure - measure and save to csv multiple times get_dataframe - return smua.nvbuffer1 as pandas dataframe save_csv - save the last measurement as csv file save_pickle - save the last measurement as pickled pandas dataframe diff --git a/k-teng/keithley/keithley.py b/k-teng/keithley/keithley.py index e5b33cd..4c86d69 100644 --- a/k-teng/keithley/keithley.py +++ b/k-teng/keithley/keithley.py @@ -14,7 +14,6 @@ for key,val in scripts.items(): scripts[key] = script_dir + scripts[key] - def init_keithley(beep_success=True): rm = pyvisa.ResourceManager('@py') resources = rm.list_resources() diff --git a/k-teng/materials b/k-teng/materials new file mode 100644 index 0000000..36fc873 --- /dev/null +++ b/k-teng/materials @@ -0,0 +1,4 @@ +pdms +kapton +plastic + diff --git a/k-teng/prepare.py b/k-teng/prepare.py new file mode 100644 index 0000000..93051fe --- /dev/null +++ b/k-teng/prepare.py @@ -0,0 +1,151 @@ +import pandas as pd +import numpy as np +import scipy.signal as signal +import matplotlib.pyplot as plt +from time import sleep +from random import choice as r_choice + +if __name__ == "__main__": + if __package__ is None: + # make relative imports work as described here: https://peps.python.org/pep-0366/#proposed-change + __package__ = "k-teng" + import sys + from os import path + filepath = path.realpath(path.abspath(__file__)) + sys.path.insert(0, path.dirname(path.dirname(filepath))) +from .utility.data import load_dataframe + +file = "/home/matth/data/gel_big_gap000.csv" + +class PeakInfo: + """ + Helper class for "iterating" through selected peaks. + """ + def __init__(self): + self.reset() + + def reset(self): + self._peak_names = [ "first", "second", "last", "lowest" ] + self._peaks = { p: None for p in self._peak_names } + self._iter = 0 + + def current(self): + # return (self._peak_names[self._iter]), self._peaks[self._peak_names[self._iter]] + return self._peaks[self._peak_names[self._iter]] + def name(self): + return self._peak_names[self._iter] + + def next(self): + if self._iter < len(self._peak_names) - 1: self._iter += 1 + return self.current() + def prev(self): + if self._iter > 0: self._iter -= 1 + return self.current() + + def set(self, value): + """Assign a value to the crrent peak""" + self._peaks[self._peak_names[self._iter]] = value + def is_done(self): + for peak in self._peaks.values(): + if peak is None: return False + return True + + def __getitem__(self, key): + return self._peaks[key] + def __setitem__(self, key, value): + self._peaks[key] = value + def __repr__(self): + return f"{self._peak_names[self._iter]} peak" + + + +def find_peaks(a): + peaks = signal.find_peaks(a) + + +def on_click(fig, ax, peaks, event): + """ + Let the user select first, second and last peak by clicking on them in this order. + Right click undos the last selection + """ + select = None + if event.button == 1: # left click + peaks.set((event.xdata, event.ydata)) + print(f"{peaks}: {event.xdata} - {event.ydata}") + ax.set_title(f"{peaks}: {event.xdata} - {event.ydata}") + peaks.next() + elif event.button == 3: # right click + ax.set_title(f"Undo {peaks.name()}") + if not peaks.is_done(): + peaks.prev() + peaks.set(None) + if peaks.is_done(): message = "Close window when done" + else: message = f"Click on {peaks}" + fig.suptitle(message) + fig.canvas.draw() + # fig1.canvas.flush_events() + +def calc_peaks(peaks): + # get the peak points from the information of a Peaks object + # 90% distance between first and second + min_distance = max(1, (peaks["second"][0] - peaks["first"][0]) * 0.9) + min_height = peaks["lowest"][1] * 0.99 + vpeaks = signal.find_peaks(vdata, height=min_height, distance=min_distance) + return vpeaks + + +def normalize(a): + """ + normalize so that all values are between 0 and 1 + """ + min_ = np.min(a) + a = a - min_ + max_ = np.max(a) + if max_ != 0: + a = a / max_ + return a + +if __name__ == "__main__": + """ + Peak identification: + plot, let user choose first, second, last and lowest peak for identification + """ + df = load_dataframe(file) + a = df.to_numpy() + vdata = normalize(a[:,2]) + plt.ion() + # vpeaks[0] is the list of the peaks + vpeaks = signal.find_peaks(vdata)[0] + fig, ax = plt.subplots() + ax.plot(vdata) + peak_lines = ax.vlines(vpeaks, 0, 1, colors="r") + ax.grid(True) + fig.suptitle("Click on first peak") + peak_info = PeakInfo() + # handle clicks + fig.canvas.mpl_connect("button_press_event", lambda ev: on_click(fig, ax, peak_info, ev)) + # run until user closes, events are handled with on_click function + print(vdata.size) + while plt.fignum_exists(fig.number): + plt.pause(0.01) + if (peak_info.is_done()): + vpeaks = calc_peaks(peak_info)[0] + x_margin = (a[-1,0] - a[0,0]) * 0.05 # allow some margin if user clicked not close enough on peak + vpeaks = vpeaks[(vpeaks >= peak_info["first"][0] - x_margin) & (vpeaks <= peak_info["last"][0] + x_margin)] # remove peaks before first and after last + peak_lines.remove() + peak_lines = ax.vlines(vpeaks, 0, 1, colors="r") + peak_info.reset() + print(a[:,0], vpeaks) + + # separate peaks + indices = np.arange(0, a[:,0].size) + peak_datas = [] + for i in range(len(vpeaks) - 1): + # TODO: user <= or < + peak_datas.append(vdata[(indices >= vpeaks[i]) & (indices < vpeaks[i+1])]) + plt.plot(peak_datas[i]) + print(peak_datas) + plt.pause(20) + + + diff --git a/k-teng/utility/data.py b/k-teng/utility/data.py index 629ee5f..1980429 100644 --- a/k-teng/utility/data.py +++ b/k-teng/utility/data.py @@ -1,5 +1,6 @@ import pandas as pd import numpy as np +from os import path def buffer2dataframe(buffer): df = pd.DataFrame(buffer) @@ -15,3 +16,17 @@ def buffers2dataframe(ibuffer, vbuffer): df = pd.DataFrame(np.vstack((ibuffer[:,0], ibuffer[:,1], vbuffer[:,1])).T) df.columns = ["Time [s]", "Current [A]", "Voltage [V]"] return df + +def load_dataframe(p:str): + """ + Load a dataframe from file. + @param p : path of the file. If it has 'csv' extension, pandas.read_csv is used, pandas.read_pickle otherwise + """ + if not path.isfile(p): + print(f"ERROR: load_dataframe: File does not exist: {p}") + return None + if p.endswith(".csv"): + df = pd.read_csv(p) + else: + df = pd.read_pickle(p) + return df