diff --git a/.gitignore b/.gitignore index 3094a07..12e40dc 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,6 @@ data test.py measurements/__pycache__ -instruments/__pycache__ \ No newline at end of file +instruments/__pycache__ +build +dist \ No newline at end of file diff --git a/PyCharMem.py b/PyCharMem.py index 854dd80..6ae6896 100644 --- a/PyCharMem.py +++ b/PyCharMem.py @@ -13,7 +13,6 @@ import pyvisa import numpy as np import pyqtgraph as pg -from openpyxl.drawing.image import Image from art import text2art from rich.console import Console from rich.traceback import install @@ -27,7 +26,7 @@ # Global Variables PROGRAM_INFO = { 'name': 'PyCharMem', - 'version': '0.0.3', + 'version': '0.0.5', 'author': 'Ricardo E. Silva', 'email': 'ricardoedgarsilva@tecnico.ulisboa.pt', 'description': 'PyCharMem is a Python program that allows you to measure the charge memory of a device.', @@ -36,28 +35,29 @@ } -def verbose_debug(verbose: bool) -> logging.Logger: +def verbose_debug(verbose): log_level = "NOTSET" if verbose else 'INFO' logging.basicConfig(level=log_level, format="%(message)s", datefmt="[%X]", handlers=[RichHandler()]) return logging.getLogger("rich") -def clear_terminal() -> None: +def clear_terminal(): os.system('cls||clear') -def print_newlines(lines: int) -> None: +def print_newlines(lines): print(lines * "\n") -def splash_screen(console: Console) -> None: +def splash_screen(console): print(text2art(PROGRAM_INFO['name'])) console.print(f"Version: {PROGRAM_INFO['version']}", style='bold blue') + console.print(f"Repositoty: {PROGRAM_INFO['url']}", style='bold blue') console.print(f"Author: {PROGRAM_INFO['author']}", style='bold blue') print_newlines(5) -def open_config(logger: logging.Logger) -> None: +def open_config(logger): try: os.system({'Windows': 'start config.yml', 'Darwin': 'open config.yml', 'Linux': 'open config.yml'}.get(platform.system(), '')) except: @@ -65,7 +65,7 @@ def open_config(logger: logging.Logger) -> None: sys.exit() -def read_config(logger: logging.Logger) -> dict: +def read_config(logger): try: with open('config.yml', 'r') as file: config = yaml.safe_load(file) @@ -76,37 +76,37 @@ def read_config(logger: logging.Logger) -> dict: sys.exit() -def path_exists(path: str) -> bool: +def path_exists(path): return os.path.exists(path) -def get_path() -> str: +def get_path(): return os.path.dirname(os.path.realpath(__file__)) -def mkdir(logger: logging.Logger, path: str) -> None: +def mkdir(logger, path): if not path_exists(path): os.makedirs(path) logger.debug(f'Folder {path} created') -def get_index(path: str) -> int: +def get_index(path): return len(os.listdir(path)) -def get_filename(path: str, sample: str, device: str) -> str: +def get_filename(path, sample, device): index = get_index(path) date = datetime.datetime.now().strftime('%Y%m%d') return f'{date}_{sample}_{device}_{index}.xlsx' -def get_available_addresses() -> list: +def get_available_addresses(): rm = pyvisa.ResourceManager() addresses = rm.list_resources() return addresses -def print_available_addresses(logger: logging.Logger, console: Console, addresses: list) -> None: +def print_available_addresses(logger, console, addresses): print_newlines(1) console.print(Panel.fit("[bold]Available Addresses[/bold]", border_style="green")) @@ -117,13 +117,13 @@ def print_available_addresses(logger: logging.Logger, console: Console, addresse console.print(address) -def check_address(logger: logging.Logger, addresses: list, address: str) -> None: +def check_address(logger, addresses, address): if address not in addresses: logger.critical('Instrument address in config file not found! Please edit config file with correct address') sys.exit() -def check_missing_params(logger: logging.Logger, my_dict: dict, my_list: list) -> None: +def check_missing_params(logger, my_dict, my_list): missing_items = [item for item in my_list if item not in my_dict] if len(missing_items) == 0: @@ -135,7 +135,7 @@ def check_missing_params(logger: logging.Logger, my_dict: dict, my_list: list) - sys.exit() -def create_list(logger: logging.Logger, condition_values: list) -> np.ndarray: +def create_list(logger, condition_values): cycle, max_value, min_value, step = condition_values listp_oneway = np.arange(0, max_value, step) listn_oneway = np.arange(0, min_value, -step) @@ -150,10 +150,10 @@ def create_list(logger: logging.Logger, condition_values: list) -> np.ndarray: sys.exit() -def get_datetime() -> str: +def get_datetime(): return datetime.datetime.fromtimestamp(time.time()).strftime('%Y-%m-%d %H:%M:%S:%f') -def import_module(logger: logging.Logger, type: str, inst_type=None, measurement_type=None) -> type: +def import_module(logger, type, inst_type=None, measurement_type=None): file_name, obj_name = '', '' if type == 'instrument': @@ -172,7 +172,7 @@ def import_module(logger: logging.Logger, type: str, inst_type=None, measurement logger.debug(f'{obj_name} class imported') return obj -def menu(logger: logging.Logger, console: Console, type: str) -> str: +def menu(logger, console, type): name, message, choices = '', '', [] if type == 'main': @@ -203,32 +203,32 @@ def menu(logger: logging.Logger, console: Console, type: str) -> str: logger.debug(f'Returning answer: {answer}') return answer['choice'] -def ask_for_comment(logger: logging.Logger) -> str: +def ask_for_comment(logger): print_newlines(2) comment = [inquirer.Text('comment', message="What would you like to comment?", validate=lambda _, x: len(x) > 0),] answer = inquirer.prompt(comment) logger.debug(f'Comment: {answer}') return answer['comment'] -def repeat_measurement(logger: logging.Logger, console: Console) -> bool: +def repeat_measurement(logger, console): print_newlines(2) comment = [inquirer.Confirm('repeat', message="Would you like to repeat the measurement?")] answer = inquirer.prompt(comment) logger.debug(f'Repeat: {answer}') return answer['repeat'] -def exit(logger: logging.Logger, inst: object) -> None: +def exit(logger, inst): logger.info('Exiting program...') inst.close(logger) logger.info('Instrument closed') - time.sleep(2) + # Important Classes class FileSave: - def __init__(self, logger: logging.Logger, sample: str, device: str): - self.path = os.path.join(get_path(), "data", sample, device) + def __init__(self, logger, path, sample, device): + self.path = os.path.join(path, sample, device) mkdir(logger, self.path) self.file_name = get_filename(self.path, sample, device) @@ -239,7 +239,7 @@ def __init__(self, logger: logging.Logger, sample: str, device: str): self.wb.remove(self.wb['Sheet']) self.wb.save(self.file_path) - def save_config(self, logger: logging.Logger, config: dict, measurement_type: str): + def save_config(self, logger, config, measurement_type): self.ws = self.wb['config'] sections = ['sample', 'instrument', measurement_type] @@ -251,14 +251,14 @@ def save_config(self, logger: logging.Logger, config: dict, measurement_type: st self.wb.save(self.file_path) logger.debug('Configuration file saved in config sheet') - def save_headers(self, logger: logging.Logger, headers_list: list): + def save_headers(self, logger, headers_list): self.ws = self.wb['data'] for i in range(len(headers_list)): self.ws.cell(row=1, column=i + 1).value = headers_list[i] self.wb.save(self.file_path) logger.debug('Headers saved in data sheet') - def save_result(self, logger: logging.Logger, result: list): + def save_result(self, logger, result): self.ws = self.wb['data'] row = self.ws.max_row + 1 for i in range(len(result)): @@ -269,8 +269,8 @@ def save_result(self, logger: logging.Logger, result: list): class Logbook: - def __init__(self, logger: logging.Logger, sample: str): - self.path = os.path.join(get_path(), "data", sample) + def __init__(self, logger, path, sample): + self.path = os.path.join(path, sample) mkdir(logger, self.path) self.file_path = os.path.join(self.path, 'logbook.xlsx') @@ -292,7 +292,7 @@ def __init__(self, logger: logging.Logger, sample: str): self.wb.save(self.file_path) logger.debug('Logbook file created') - def save_log(self, logger: logging.Logger, date: str, file: str, comment: str): + def save_log(self, logger, date, file, comment): self.ws = self.wb['logbook'] row = self.ws.max_row + 1 self.ws.cell(row=row, column=1).value = date @@ -307,13 +307,13 @@ class MeasurementThread(QThread): clear_plots = pyqtSignal(list, list) close_window = pyqtSignal() - def __init__(self, logger, inst, meas, config): + def __init__(self, logger, inst, meas, config, filesave): super().__init__() self.logger = logger self.inst = inst self.meas = meas self.config = config - self.filesave = FileSave(logger, config.get('sample').get('name'), config.get('sample').get('device')) + self.filesave = filesave self.filesave.save_config(logger, config, meas.name) self.filesave.save_headers(logger, self.meas.headers) self.n_cycles = config.get(meas.name).get('n_cycles') @@ -337,21 +337,16 @@ def run(self): progress.update(value_task, advance=-self.n_vals, description=f"[purple]Value 0/{self.n_vals}") progress.update(cycle_task, advance=1, description=f"[blue]Cycle {cycle}/{self.n_cycles}") self.inst.set_output_state(self.logger,'OFF') + self.logger.info('Measurement finished! Please close the window to continue.') - logbook = Logbook(self.logger, self.config.get('sample').get('name')) - self.logger.info('Measurement finished! Please enter a comment for the logbook') - comment = ask_for_comment(self.logger) - logbook.save_log(self.logger, get_datetime(), self.filesave.file_name, comment) - self.close_window.emit() - self.logger.info('Logbook saved!') class MeasurementWindow(QMainWindow): - def __init__(self, logger, inst, meas, config): + def __init__(self, logger, inst, meas, config, filesave): super().__init__() self.initUI(meas) - self.measure = MeasurementThread(logger, inst, meas, config) + self.measure = MeasurementThread(logger, inst, meas, config, filesave) self.measure.update_data.connect(self.update_data) self.measure.clear_plots.connect(self.clear_plots) self.measure.close_window.connect(self.close) @@ -421,11 +416,11 @@ def close_window(self): # Main Functions ------------------------------------------------------------------ -def start_gui(logger: logging.Logger, inst: object, meas: object, config: dict): +def start_gui(logger, inst, meas, config, filesave): app = QApplication(sys.argv) app.setStyle(QStyleFactory.create("Fusion")) app.setStyleSheet("QMainWindow::title {background-color: #333333;}") - window = MeasurementWindow(logger, inst, meas, config) + window = MeasurementWindow(logger, inst, meas, config, filesave) window.show() app.exec() @@ -472,9 +467,24 @@ def main(): meas_class = import_module(logger=logger, type='measurement', measurement_type=option2) meas = meas_class(logger, config, inst) + filesave = FileSave(logger, config.get('sample').get('path'), config.get('sample').get('name'), config.get('sample').get('device')) + check_missing_params(logger, meas.params[meas.name], meas.nparams) logger.info('All parameters present') - start_gui(logger, inst, meas, config) + start_gui(logger, inst, meas, config, filesave) + + path = config.get('sample').get('path') + sample_name = config.get('sample').get('name') + sample_device = config.get('sample').get('device') + + logbook = Logbook(logger,path, sample_name) + + logger.info('Please enter a comment for the logbook') + comment = ask_for_comment(logger) + logbook.save_log(logger, get_datetime(), filesave.file_name, comment) + logger.info('Logbook saved!') + + option3 = repeat_measurement(logger, console) if option3: continue else: diff --git a/PyCharMem.spec b/PyCharMem.spec deleted file mode 100644 index ce1cc3f..0000000 --- a/PyCharMem.spec +++ /dev/null @@ -1,50 +0,0 @@ -# -*- mode: python ; coding: utf-8 -*- - - -block_cipher = None - - -a = Analysis( - ['PyCharMem.py'], - pathex=[], - binaries=[], - datas=[], - hiddenimports=[], - hookspath=[], - hooksconfig={}, - runtime_hooks=[], - excludes=[], - win_no_prefer_redirects=False, - win_private_assemblies=False, - cipher=block_cipher, - noarchive=False, -) -pyz = PYZ(a.pure, a.zipped_data, cipher=block_cipher) - -exe = EXE( - pyz, - a.scripts, - [], - exclude_binaries=True, - name='PyCharMem', - debug=False, - bootloader_ignore_signals=False, - strip=False, - upx=True, - console=True, - disable_windowed_traceback=False, - argv_emulation=False, - target_arch=None, - codesign_identity=None, - entitlements_file=None, -) -coll = COLLECT( - exe, - a.binaries, - a.zipfiles, - a.datas, - strip=False, - upx=True, - upx_exclude=[], - name='PyCharMem', -) diff --git a/config.yml b/config.yml index 5256ab6..97d1ee4 100644 --- a/config.yml +++ b/config.yml @@ -1,7 +1,8 @@ # Sample configuration file for the sample: - name: "MemQuD_It_7x7" # Name of the sample ex: "MRG6N1" - device: "D1" # location of the device ex: "x1y6" + name: "MemQuD_CPI_Q1S11" # Name of the sample ex: "MRG6N1" + device: "55R2" # location of the device ex: "x1y6" + path: "C:\\Users\\ricar\\OneDrive - Universidade de Lisboa\\Desktop\\Data" # path to save the data ex: "C:\\Users\\Admin\\Desktop\\Data" # Load Instrument instrument: @@ -11,16 +12,16 @@ instrument: pulsedIV: cycle: "+-" # cycle of the voltage (ex: "+-","-+", "+" or "-") n_cycles: 1 # number of cycles - v+: 3.0 # maximum voltage value of positive cycle - v-: -3.0 # minimum voltage value of negative cycle - v_step: 1.0 # voltage step - v_read: 0.05 # voltage step for the read - ccplc+: 0.1 # current compliance for positive cycle - ccplc-: 0.1 # current compliance for negative cycle + v+: 5.0 # maximum voltage value of positive cycle + v-: -5.0 # minimum voltage value of negative cycle + v_step: 0.3 # voltage step + v_read: 0.3 # voltage step for the read + ccplc+: 0.000001 # current compliance for positive cycle + ccplc-: 0.000001 # current compliance for negative cycle t_write: 0.00001 # time between each voltage step t_read: 0.00001 # time between each current read t_wait: 0.00001 # time between each cycle - nplc: 1 #number of power line cycles + nplc: 2 #number of power line cycles pulsedVI: cycle: "+-" # cycle of the voltage (ex: "+-","-+", "+" or "-") diff --git a/images/logo.png b/images/logo.png deleted file mode 100644 index 0f38ba9..0000000 Binary files a/images/logo.png and /dev/null differ diff --git a/images/screenshot.png b/images/screenshot.png deleted file mode 100644 index 3b58bcb..0000000 Binary files a/images/screenshot.png and /dev/null differ diff --git a/instruments/__pycache__/keithley_2400_sim.cpython-310.pyc b/instruments/__pycache__/keithley_2400_sim.cpython-310.pyc deleted file mode 100644 index aff0ddd..0000000 Binary files a/instruments/__pycache__/keithley_2400_sim.cpython-310.pyc and /dev/null differ diff --git a/instruments/__pycache__/keithley_2401.cpython-310.pyc b/instruments/__pycache__/keithley_2401.cpython-310.pyc deleted file mode 100644 index da384fb..0000000 Binary files a/instruments/__pycache__/keithley_2401.cpython-310.pyc and /dev/null differ diff --git a/instruments/__pycache__/sim.cpython-310.pyc b/instruments/__pycache__/sim.cpython-310.pyc deleted file mode 100644 index ad575ab..0000000 Binary files a/instruments/__pycache__/sim.cpython-310.pyc and /dev/null differ diff --git a/instruments/keithley_2401.py b/instruments/keithley_2401.py index a7e7212..daf07cd 100644 --- a/instruments/keithley_2401.py +++ b/instruments/keithley_2401.py @@ -1,7 +1,7 @@ import pyvisa class Instrument: - def __init__ (self, logger, config:dict): + def __init__ (self, logger, config): '''Opens instrument''' logger.debug('Initializing instrument...') try: @@ -44,7 +44,7 @@ def reset(self, logger): logger.critical('GPIB defaults could not reseted! Check connection!') quit() - def write(self, logger, command:str): + def write(self, logger, command): '''Writes command to instrument''' try: self.inst.write(command) @@ -53,7 +53,7 @@ def write(self, logger, command:str): print('Instrument could not be written! Check connection!') quit() - def query(self, logger, command:str): + def query(self, logger, command): '''Queries instrument''' try: query = self.inst.query(command) @@ -63,7 +63,7 @@ def query(self, logger, command:str): print('Instrument could not be queried! Check connection!') quit() - def set_output_state(self, logger, state:str): + def set_output_state(self, logger, state): '''Sets output state''' try: match state: @@ -74,7 +74,7 @@ def set_output_state(self, logger, state:str): logger.critical('Instrument output could not be set! Check connection!') quit() - def set_output_value(self, logger, func:str, value:float): + def set_output_value(self, logger, func, value): '''Sets output value of voltage or current''' try: match func: @@ -85,7 +85,7 @@ def set_output_value(self, logger, func:str, value:float): logger.critical('Instrument value could not be set! Check connection!') quit() - def get_output_value(self, logger, func:str): + def get_output_value(self, logger, func): '''Measures output value of voltage, current or resistance''' #try: match func: @@ -145,7 +145,7 @@ def reset_timer(self, logger): #Measurement mode commands ------------------------------------------------ - def set_mode_fixed(self, logger , func:str): + def set_mode_fixed(self, logger , func): '''Sets measurement mode to fixed''' try: match func: @@ -158,7 +158,7 @@ def set_mode_fixed(self, logger , func:str): #Measurement function commands ------------------------------------------- - def set_src_func(self, logger, func:str): + def set_src_func(self, logger, func): '''Sets source function of voltage or current''' try: match func: @@ -169,7 +169,7 @@ def set_src_func(self, logger, func:str): logger.critical('Instrument source function could not be set! Check connection!') quit() - def set_func_range(self, logger, func:str, range:str=':AUTO ON'): + def set_func_range(self, logger, func, range=':AUTO ON'): '''Sets range of voltage or current''' try: match func: @@ -181,7 +181,7 @@ def set_func_range(self, logger, func:str, range:str=':AUTO ON'): logger.critical('Instrument range could not be set! Check connection!') quit() - def set_func_step(self, logger, func:str, step:float): + def set_func_step(self, logger, func, step): '''Sets step''' try: match func: @@ -192,7 +192,7 @@ def set_func_step(self, logger, func:str, step:float): logger.critical('Instrument step could not be set! Check connection!') quit() - def set_sense_func(self, logger, func:str): + def set_sense_func(self, logger, func): '''Sets sense function of voltage, current or resistance''' try: match func: @@ -204,7 +204,7 @@ def set_sense_func(self, logger, func:str): logger.critical('Instrument sense function could not be set! Check connection!') quit() - def set_func_cplc(self, logger, func:str, value:float): + def set_func_cplc(self, logger, func, value): '''Sets compliance of voltage or current''' try: @@ -216,7 +216,7 @@ def set_func_cplc(self, logger, func:str, value:float): logger.critical('Instrument compliance could not be set! Check connection!') quit() - def set_func_nplc(self, logger, func:str, value:float): + def set_func_nplc(self, logger, func, value): '''Sets integration time for current or voltage''' try: diff --git a/instruments/sim.py b/instruments/sim.py index 5e83f9a..1156cb0 100644 --- a/instruments/sim.py +++ b/instruments/sim.py @@ -2,7 +2,7 @@ import time class Instrument: - def __init__ (self, logger, config:dict): + def __init__ (self, logger, config): '''Opens instrument''' logger.warning('THIS IS A SIMULATED INSTRUMENT, NO HARDWARE IS BEING USED! "[SIM]" WILL BE PRINTED BEFORE ALL LOGS') logger.debug('[SIM] Initializing instrument...') @@ -28,22 +28,22 @@ def reset(self, logger): '''Resets instrument''' logger.debug('[SIM] GPIB defaults reseted') - def write(self, logger, command: str): + def write(self, logger, command): '''Writes command to instrument''' logger.debug(f'[SIM] Instrument command: {command}') - def query(self, logger, command: str): + def query(self, logger, command): '''Queries instrument''' logger.debug(f'[SIM] Instrument query: {command}') - def set_output_state(self, logger, state: str): + def set_output_state(self, logger, state): '''Sets output state''' if state == 'ON': self.output_state = True elif state == 'OFF': self.output_state = False else: logger.critical('[SIM] Invalid output state!') logger.debug(f'Instrument output set to {state}') - def set_output_value(self, logger, func: str, value: float): + def set_output_value(self, logger, func, value): '''Sets output value of voltage or current''' if func == 'Voltage': self.voltage = value elif func == 'Current': self.current = value @@ -51,7 +51,7 @@ def set_output_value(self, logger, func: str, value: float): logger.debug(f'Instrument {func} value set to {value}') - def get_output_value(self, logger, func: str): + def get_output_value(self, logger, func): '''Measures output value of voltage, current, or resistance''' # Simulated response based on the function if func == 'Voltage': return self.voltage @@ -83,32 +83,32 @@ def reset_timer(self, logger): # Measurement mode commands ------------------------------------------------ - def set_mode_fixed(self, logger, func: str): + def set_mode_fixed(self, logger, func): '''Sets measurement mode to fixed''' logger.debug('[SIM] Instrument measurement mode set to fixed') # Measurement function commands ------------------------------------------- - def set_src_func(self, logger, func: str): + def set_src_func(self, logger, func): '''Sets source function of voltage or current''' logger.debug(f'[SIM] Instrument source function set to {func}') - def set_func_range(self, logger, func: str, range: str = ':AUTO ON'): + def set_func_range(self, logger, func, range = ':AUTO ON'): '''Sets range of voltage or current''' logger.debug(f'[SIM] Instrument range set to {range}') - def set_func_step(self, logger, func: str, step: float): + def set_func_step(self, logger, func, step): '''Sets step''' logger.debug(f'[SIM] Instrument step set to {step}') - def set_sense_func(self, logger, func: str): + def set_sense_func(self, logger, func): '''Sets sense function of voltage, current, or resistance''' logger.debug(f'[SIM] Instrument sense function set to {func}') - def set_func_cplc(self, logger, func: str, value: float): + def set_func_cplc(self, logger, func, value): '''Sets compliance of voltage or current''' logger.debug(f'[SIM] Instrument compliance set to {value}') - def set_func_nplc(self, logger, func: str, value: float): + def set_func_nplc(self, logger, func, value): '''Sets integration time for current or voltage''' logger.debug(f'[SIM] Instrument integration time set to {value}') diff --git a/measurements/__pycache__/pulsedIV.cpython-310.pyc b/measurements/__pycache__/pulsedIV.cpython-310.pyc deleted file mode 100644 index 1bcb1c6..0000000 Binary files a/measurements/__pycache__/pulsedIV.cpython-310.pyc and /dev/null differ diff --git a/measurements/pulsedIV.py b/measurements/pulsedIV.py index a7997aa..7acac70 100644 --- a/measurements/pulsedIV.py +++ b/measurements/pulsedIV.py @@ -8,10 +8,10 @@ def create_list(logger, cycle, max_val, min_val, step): try: match cycle: - case '+': return list_positive - case '-': return list_negative - case '+-': return np.concatenate((list_positive, list_negative)) - case '-+': return np.concatenate((list_negative, list_positive)) + case '+': return np.round(list_positive,5) + case '-': return np.round(list_negative,5) + case '+-': return np.round((np.concatenate((list_positive, list_negative))),5) + case '-+': return np.round(np.concatenate((list_negative, list_positive)),5) except: logger.critical('Invalid cycle type!') quit() @@ -62,7 +62,7 @@ def initialize_instrument(self, logger, instrument): instrument.set_sense_func(logger, func='Current') instrument.set_func_range(logger, func='Current') instrument.set_func_nplc(logger, func='Current', value=self.params.get(self.name).get('nplc')) - instrument.write(logger, 'INIT:IMM') + #instrument.write(logger, 'INIT:IMM') logger.debug('Instrument parameters set!') def measure_val(self, logger, instrument, val): @@ -85,7 +85,7 @@ def measure_val(self, logger, instrument, val): # Read Pulse: End result = [ - result_write[0], # Voltage Write + val, # Voltage Write result_write[1], # Current Write result_read[0], # Voltage Read result_read[1], # Current Read