From 2e910a2afb91d08bddb5c61efb0c7a9c88a4b11c Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Tue, 5 Oct 2021 13:07:22 +0200 Subject: [PATCH 01/10] Move plot functions to plot.py --- beamtime/electrochemistry/plot.py | 39 +++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/beamtime/electrochemistry/plot.py b/beamtime/electrochemistry/plot.py index d820b59..70f2b22 100644 --- a/beamtime/electrochemistry/plot.py +++ b/beamtime/electrochemistry/plot.py @@ -1 +1,40 @@ import matplotlib.pyplot as plt +import pandas as pd +import numpy as np + + +def plot_gc(cycles, which_cycles='all', chg=True, dchg=True, colours=None, x='C', y='U'): + + fig, ax = prepare_gc_plot() + + + if which_cycles == 'all': + which_cycles = [i for i, c in enumerate(cycles)] + + if not colours: + chg_colour = (40/255, 70/255, 75/255) # Dark Slate Gray #28464B + dchg_colour = (239/255, 160/255, 11/255) # Marigold #EFA00B + + + + for i, cycle in cycles: + if i in which_cycles: + if chg: + cycle[0].plot(ax=ax) + + + + + + + + + + + +def prepare_gc_plot(figsize=(14,7), dpi=None): + + fig, ax = plt.subplots(figsize=figsize, dpi=dpi) + + + return fig, ax \ No newline at end of file From ed5504449fadb406479233638eee7c84ae57cf1d Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Tue, 5 Oct 2021 14:31:32 +0200 Subject: [PATCH 02/10] Change unit conversion function and add time string conversions --- beamtime/electrochemistry/io.py | 277 ++++++++++++++++++++++---------- 1 file changed, 192 insertions(+), 85 deletions(-) diff --git a/beamtime/electrochemistry/io.py b/beamtime/electrochemistry/io.py index 23679ea..86964ea 100644 --- a/beamtime/electrochemistry/io.py +++ b/beamtime/electrochemistry/io.py @@ -19,15 +19,166 @@ def read_battsmall(path): -def unit_conversion(df, units): - C, m = units['C'].split('/') +def read_neware(path, summary=False, active_material_weight=None, molecular_weight=None): + ''' Reads electrochemistry data, currently only from the Neware battery cycler. Will convert to .csv if the filetype is .xlsx, + which is the file format the Neware provides for the backup data. In this case it matters if summary is False or not. If file + type is .csv, it will just open the datafile and it does not matter if summary is False or not.''' + + # Convert from .xlsx to .csv to make readtime faster + if filename.split('.')[-1] == 'xlsx': + csv_details = ''.join(filename.split('.')[:-1]) + '_details.csv' + csv_summary = ''.join(filename.split('.')[:-1]) + '_summary.csv' + + Xlsx2csv(filename, outputencoding="utf-8").convert(csv_summary, sheetid=3) + Xlsx2csv(filename, outputencoding="utf-8").convert(csv_details, sheetid=4) + + if summary: + df = pd.read_csv(csv_summary) + else: + df = pd.read_csv(csv_details) + + elif filename.split('.')[-1] == 'csv': + + df = pd.read_csv(filename) + + + return df + + + + + + + +#def process_battsmall_data(df, t='ms', C='mAh/g', I='mA', U='V'): + +def process_battsmall_data(df, units=None, splice_cycles=None, molecular_weight=None): + ''' Takes BATTSMALL-data in the form of a DataFrame and cleans the data up and converts units into desired units. + Splits up into individual charge and discharge DataFrames per cycle, and outputs a list where each element is a tuple with the Chg and DChg-data. E.g. cycles[10][0] gives the charge data for the 11th cycle. + + For this to work, the cycling program must be set to use the counter. + + Input: + df (required): A pandas DataFrame containing BATTSMALL-data, as obtained from read_battsmall(). + t (optional): Unit for time data. Defaults to ms. + C (optional): Unit for specific capacity. Defaults to mAh/g. + I (optional): Unit for current. Defaults mA. + U (optional): Unit for voltage. Defaults to V. + + Output: + cycles: A list with + ''' + + ######################### + #### UNIT CONVERSION #### + ######################### + + # Complete the list of units - if not all are passed, then default value will be used + required_units = ['t', 'I', 'U', 'C'] + default_units = {'t': 'h', 'I': 'mA', 'U': 'V', 'C': 'mAh/g'} + + if not units: + units = default_units + + if units: + for unit in required_units: + if unit not in units.values(): + units[unit] = default_units[unit] + + # Get the units used in the data set t_prev = df.columns[0].split()[-1].strip('[]') U_prev = df.columns[1].split()[-1].strip('[]') I_prev = df.columns[2].split()[-1].strip('[]') C_prev, m_prev = df.columns[4].split()[-1].strip('[]').split('/') + prev_units = {'t': t_prev, 'I': I_prev, 'U': U_prev, 'C': C_prev} + + # Convert all units to the desired units. + df = unit_conversion(df=df, units=units) + + # Replace NaN with empty string in the Comment-column and then remove all steps where the program changes - this is due to inconsistent values for current + df[["Comment"]] = df[["Comment"]].fillna(value={'Comment': ''}) + df = df[df["Comment"].str.contains("program")==False] + + # Creates masks for charge and discharge curves + chg_mask = df['I'] >= 0 + dchg_mask = df['I'] < 0 + + # Initiate cycles list + cycles = [] + + # Loop through all the cycling steps, change the current and capacities in the + for i in range(df["Z1"].max()): + + sub_df = df.loc[df['Z1'] == i].copy() + + sub_df.loc[dchg_mask, 'I'] *= -1 + sub_df.loc[dchg_mask, 'C'] *= -1 + + chg_df = sub_df.loc[chg_mask] + dchg_df = sub_df.loc[dchg_mask] + + # Continue to next iteration if the charge and discharge DataFrames are empty (i.e. no current) + if chg_df.empty and dchg_df.empty: + continue + + cycles.append((chg_df, dchg_df)) + + + + + return cycles + + +def process_neware_data(df, units=None, splice_cycles=None, active_material_weight=None, molecular_weight=None): + + ######################### + #### UNIT CONVERSION #### + ######################### + + # Complete the list of units - if not all are passed, then default value will be used + required_units = ['t', 'I', 'U', 'C'] + default_units = {'t': 'h', 'I': 'mA', 'U': 'V', 'C': 'mAh/g'} + + if not units: + units = default_units + + if units: + for unit in required_units: + if unit not in units.values(): + units[unit] = default_units[unit] + + + + # Get the units used in the data set + t_prev = 's' # default in + U_prev = df.columns[1].split()[-1].strip('[]') + I_prev = df.columns[2].split()[-1].strip('[]') + C_prev, m_prev = df.columns[4].split()[-1].strip('[]').split('/') + prev_units = {'t': t_prev, 'I': I_prev, 'U': U_prev, 'C': C_prev} + + # Convert all units to the desired units. + df = unit_conversion(df=df, units=units) + + + + if active_material_weight: + df["SpecificCapacity(mAh/g)"] = df["Capacity(mAh)"] / (active_material_weight / 1000) + + if molecular_weight: + faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 + seconds_per_hour = 3600 # s h^-1 + f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 + + df["IonsExtracted"] = (df["SpecificCapacity(mAh/g)"]*molecular_weight)/f + + +def unit_conversion(df, units, prev_units, kind): + + C, m = units['C'].split('/') + C_prev, m_prev = prev_units['C'].split('/') # Define matrix for unit conversion for time @@ -55,14 +206,11 @@ def unit_conversion(df, units): m_units_df = pd.DataFrame(m_units_df) m_units_df.index = ['kg', 'g', 'mg', 'ug'] - - - #print(df["TT [{}]".format(t_prev)]) df["TT [{}]".format(t_prev)] = df["TT [{}]".format(t_prev)] * t_units_df[t_prev].loc[units['t']] df["U [{}]".format(U_prev)] = df["U [{}]".format(U_prev)] * U_units_df[U_prev].loc[units['U']] df["I [{}]".format(I_prev)] = df["I [{}]".format(I_prev)] * I_units_df[I_prev].loc[units['I']] - df["C [{}/{}]".format(C_prev, m_prev)] = df["C [{}/{}]".format(C_prev, m_prev)] * (C_units_df[C_prev].loc[units['C']] / m_units_df[m_prev].loc[units['m']]) + df["C [{}/{}]".format(C_prev, m_prev)] = df["C [{}/{}]".format(C_prev, m_prev)] * (C_units_df[C_prev].loc[C] / m_units_df[m_prev].loc[m]) df.columns = ['TT', 'U', 'I', 'Z1', 'C', 'Comment'] @@ -70,106 +218,65 @@ def unit_conversion(df, units): return df -#def process_battsmall_data(df, t='ms', C='mAh/g', I='mA', U='V'): - -def process_battsmall_data(df, units=None): - ''' Takes BATTSMALL-data in the form of a DataFrame and cleans the data up and converts units into desired units. - Splits up into individual charge and discharge DataFrames per cycle, and outputs a list where each element is a tuple with the Chg and DChg-data. E.g. cycles[10][0] gives the charge data for the 11th cycle. - - For this to work, the cycling program must be set to use the counter. - - Input: - df (required): A pandas DataFrame containing BATTSMALL-data, as obtained from read_battsmall(). - t (optional): Unit for time data. Defaults to ms. - C (optional): Unit for specific capacity. Defaults to mAh/g. - I (optional): Unit for current. Defaults mA. - U (optional): Unit for voltage. Defaults to V. - - Output: - cycles: A list with - ''' - - required_units = ['t', 'I', 'U', 'C'] - default_units = {'t': 'h', 'I': 'mA', 'U': 'V', 'C': 'mAh/g'} - - if not units: - units = default_units - - if units: - for unit in required_units: - if unit not in units.values(): - units[unit] = default_units[unit] - - - # Convert all units to the desired units. - df = unit_conversion(df=df, units=units) - - # Replace NaN with empty string in the Comment-column and then remove all steps where the program changes - this is due to inconsistent values for current and - df[["Comment"]] = df[["Comment"]].fillna(value={'Comment': ''}) - df = df[df["Comment"].str.contains("program")==False] - - # Creates masks for - chg_mask = df['I'] >= 0 - dchg_mask = df['I'] < 0 - - # Initiate cycles list - cycles = [] - - # Loop through all the cycling steps, change the current and capacities in the - for i in range(df["Z1"].max()): - sub_df = df.loc[df['Z1'] == i].copy() - sub_df.loc[dchg_mask, 'I'] *= -1 - sub_df.loc[dchg_mask, 'C'] *= -1 - - chg_df = sub_df.loc[chg_mask] - dchg_df = sub_df.loc[dchg_mask] - cycles.append((chg_df, dchg_df)) +def convert_time_string(time_string, unit='ms'): + ''' Convert time string from Neware-data with the format hh:mm:ss.xx to any given unit''' + h, m, s = time_string.split(':') + ms = int(s)*1000 + int(m)*1000*60 + int(h)*1000*60*60 - return cycles + factors = {'ms': 1, 's': 1/1000, 'min': 1/(1000*60), 'h': 1/(1000*60*60)} + + t = ms*factors[unit] + + return t -def plot_gc(cycles, which_cycles='all', chg=True, dchg=True, colours=None, x='C', y='U'): +def convert_datetime_string(datetime_string, reference, unit='s'): + ''' Convert time string from Neware-data with the format yyy-mm-dd hh:mm:ss to any given unit''' - fig, ax = prepare_gc_plot() + from datetime import datetime + # Parse the + cur_date, cur_time = datetime_string.split() + cur_y, cur_mo, cur_d = cur_date.split('-') + cur_h, cur_m, cur_s = cur_time.split(':') + cur_date = datetime(int(cur_y), int(cur_mo), int(cur_d), int(cur_h), int(cur_m), int(cur_s)) - if which_cycles == 'all': - which_cycles = [i for i, c in enumerate(cycles)] + ref_date, ref_time = reference.split() + ref_y, ref_mo, ref_d = ref_date.split('-') + ref_h, ref_m, ref_s = ref_time.split(':') + ref_date = datetime(int(ref_y), int(ref_mo), int(ref_d), int(ref_h), int(ref_m), int(ref_s)) - if not colours: - chg_colour = (40/255, 70/255, 75/255) # Dark Slate Gray #28464B - dchg_colour = (239/255, 160/255, 11/255) # Marigold #EFA00B - - + days = cur_date - ref_date - for i, cycle in cycles: - if i in which_cycles: - if chg: - cycle[0].plot(ax=ax) + s = days.seconds + factors = {'ms': 1000, 's': 1, 'min': 1/(60), 'h': 1/(60*60)} + t = s * factors[unit] + return t - +def splice_cycles(first, second): + + first_chg = first[0] + first_dchg = first[1] + first + + second_chg = second[0] + second_dchg = second[1] + + chg_df = first[0].append(second[0]) + + return True -def prepare_gc_plot(figsize=(14,7), dpi=None): - - fig, ax = plt.subplots(figsize=figsize, dpi=dpi) - - - return fig, ax - - - - From 5241776df786196913e9032dc7ab053e30f532ba Mon Sep 17 00:00:00 2001 From: halvorhv Date: Wed, 6 Oct 2021 15:28:29 +0200 Subject: [PATCH 03/10] dette er koedd --- beamtime/xanes/io.py | 1 + 1 file changed, 1 insertion(+) diff --git a/beamtime/xanes/io.py b/beamtime/xanes/io.py index e69de29..d08b5c1 100644 --- a/beamtime/xanes/io.py +++ b/beamtime/xanes/io.py @@ -0,0 +1 @@ +#hello \ No newline at end of file From 4a987808fc720dd82e15ac385bf529d2b9703b93 Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Thu, 7 Oct 2021 16:25:08 +0200 Subject: [PATCH 04/10] Update unit conversion functions --- beamtime/electrochemistry/__init__.py | 2 +- beamtime/electrochemistry/io.py | 226 +++++++++++------------ beamtime/electrochemistry/unit_tables.py | 53 ++++++ 3 files changed, 160 insertions(+), 121 deletions(-) create mode 100644 beamtime/electrochemistry/unit_tables.py diff --git a/beamtime/electrochemistry/__init__.py b/beamtime/electrochemistry/__init__.py index e0c4c87..0270f1d 100644 --- a/beamtime/electrochemistry/__init__.py +++ b/beamtime/electrochemistry/__init__.py @@ -1 +1 @@ -from . import io, plot +from . import io, plot, unit_tables diff --git a/beamtime/electrochemistry/io.py b/beamtime/electrochemistry/io.py index 86964ea..3e5bf02 100644 --- a/beamtime/electrochemistry/io.py +++ b/beamtime/electrochemistry/io.py @@ -1,10 +1,11 @@ import pandas as pd import numpy as np import matplotlib.pyplot as plt +import os -def read_battsmall(path): - ''' Reads BATTSMALL-data into a DataFrame. +def read_batsmall(path): + ''' Reads BATSMALL-data into a DataFrame. Input: path (required): string with path to datafile @@ -21,47 +22,46 @@ def read_battsmall(path): def read_neware(path, summary=False, active_material_weight=None, molecular_weight=None): - ''' Reads electrochemistry data, currently only from the Neware battery cycler. Will convert to .csv if the filetype is .xlsx, - which is the file format the Neware provides for the backup data. In this case it matters if summary is False or not. If file - type is .csv, it will just open the datafile and it does not matter if summary is False or not.''' - + ''' Reads electrochemistry data, currently only from the Neware battery cycler. Will convert to .csv if the filetype is .xlsx, + which is the file format the Neware provides for the backup data. In this case it matters if summary is False or not. If file + type is .csv, it will just open the datafile and it does not matter if summary is False or not.''' + from xlsx2csv import Xlsx2csv - # Convert from .xlsx to .csv to make readtime faster - if filename.split('.')[-1] == 'xlsx': - csv_details = ''.join(filename.split('.')[:-1]) + '_details.csv' - csv_summary = ''.join(filename.split('.')[:-1]) + '_summary.csv' + # Convert from .xlsx to .csv to make readtime faster + if path.split('.')[-1] == 'xlsx': + csv_details = ''.join(path.split('.')[:-1]) + '_details.csv' + csv_summary = ''.join(path.split('.')[:-1]) + '_summary.csv' - Xlsx2csv(filename, outputencoding="utf-8").convert(csv_summary, sheetid=3) - Xlsx2csv(filename, outputencoding="utf-8").convert(csv_details, sheetid=4) - - if summary: - df = pd.read_csv(csv_summary) - else: - df = pd.read_csv(csv_details) + if not os.path.isfile(csv_summary): + Xlsx2csv(path, outputencoding="utf-8").convert(csv_summary, sheetid=3) - elif filename.split('.')[-1] == 'csv': + if not os.path.isfile(csv_details): + Xlsx2csv(path, outputencoding="utf-8").convert(csv_details, sheetid=4) - df = pd.read_csv(filename) - - - return df + if summary: + df = pd.read_csv(csv_summary) + else: + df = pd.read_csv(csv_details) + + elif path.split('.')[-1] == 'csv': + df = pd.read_csv(path) + + + return df - -#def process_battsmall_data(df, t='ms', C='mAh/g', I='mA', U='V'): - -def process_battsmall_data(df, units=None, splice_cycles=None, molecular_weight=None): - ''' Takes BATTSMALL-data in the form of a DataFrame and cleans the data up and converts units into desired units. +def process_batsmall_data(df, units=None, splice_cycles=None, molecular_weight=None): + ''' Takes BATSMALL-data in the form of a DataFrame and cleans the data up and converts units into desired units. Splits up into individual charge and discharge DataFrames per cycle, and outputs a list where each element is a tuple with the Chg and DChg-data. E.g. cycles[10][0] gives the charge data for the 11th cycle. For this to work, the cycling program must be set to use the counter. Input: - df (required): A pandas DataFrame containing BATTSMALL-data, as obtained from read_battsmall(). + df (required): A pandas DataFrame containing BATSMALL-data, as obtained from read_batsmall(). t (optional): Unit for time data. Defaults to ms. C (optional): Unit for specific capacity. Defaults to mAh/g. I (optional): Unit for current. Defaults mA. @@ -71,32 +71,13 @@ def process_battsmall_data(df, units=None, splice_cycles=None, molecular_weight= cycles: A list with ''' - ######################### - #### UNIT CONVERSION #### - ######################### - # Complete the list of units - if not all are passed, then default value will be used - required_units = ['t', 'I', 'U', 'C'] - default_units = {'t': 'h', 'I': 'mA', 'U': 'V', 'C': 'mAh/g'} + # Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new. + new_units = set_units(units=units) + old_units = get_old_units(df, kind='batsmall') + df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='batsmall') - if not units: - units = default_units - - if units: - for unit in required_units: - if unit not in units.values(): - units[unit] = default_units[unit] - - - # Get the units used in the data set - t_prev = df.columns[0].split()[-1].strip('[]') - U_prev = df.columns[1].split()[-1].strip('[]') - I_prev = df.columns[2].split()[-1].strip('[]') - C_prev, m_prev = df.columns[4].split()[-1].strip('[]').split('/') - prev_units = {'t': t_prev, 'I': I_prev, 'U': U_prev, 'C': C_prev} - - # Convert all units to the desired units. - df = unit_conversion(df=df, units=units) + df.columns = ['TT', 'U', 'I', 'Z1', 'C', 'Comment'] # Replace NaN with empty string in the Comment-column and then remove all steps where the program changes - this is due to inconsistent values for current df[["Comment"]] = df[["Comment"]].fillna(value={'Comment': ''}) @@ -138,93 +119,98 @@ def process_neware_data(df, units=None, splice_cycles=None, active_material_weig #### UNIT CONVERSION #### ######################### + # Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new. + new_units = set_units(units=units) + old_units = get_old_units(df, kind='neware') + df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='neware') + + + # if active_material_weight: + # df["SpecificCapacity(mAh/g)"] = df["Capacity(mAh)"] / (active_material_weight / 1000) + + # if molecular_weight: + # faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 + # seconds_per_hour = 3600 # s h^-1 + # f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 + + # df["IonsExtracted"] = (df["SpecificCapacity(mAh/g)"]*molecular_weight)/f + + + return df + + +def unit_conversion(df, new_units, old_units, kind): + from . import unit_tables + + if kind == 'batsmall': + + df["TT [{}]".format(old_units["time"])] = df["TT [{}]".format(old_units["time"])] * unit_tables.time()[old_units["time"]].loc[new_units['time']] + df["U [{}]".format(old_units["voltage"])] = df["U [{}]".format(old_units["voltage"])] * unit_tables.voltage()[old_units["voltage"]].loc[new_units['voltage']] + df["I [{}]".format(old_units["current"])] = df["I [{}]".format(old_units["current"])] * unit_tables.current()[old_units["current"]].loc[new_units['current']] + df["C [{}/{}]".format(old_units["capacity"], old_units["mass"])] = df["C [{}/{}]".format(old_units["capacity"], old_units["mass"])] * (unit_tables.capacity()[old_units["capacity"]].loc[new_units["capacity"]] / unit_tables.mass()[old_units["mass"]].loc[new_units["mass"]]) + + + if kind == 'neware': + df['Current({})'.format(old_units['current'])] = df['Current({})'.format(old_units['current'])] * unit_tables.current()[old_units['current']].loc[new_units['current']] + df['Voltage({})'.format(old_units['voltage'])] = df['Voltage({})'.format(old_units['voltage'])] * unit_tables.voltage()[old_units['voltage']].loc[new_units['voltage']] + df['Capacity({})'.format(old_units['capacity'])] = df['Capacity({})'.format(old_units['capacity'])] * unit_tables.capacity()[old_units['capacity']].loc[new_units['capacity']] + df['Energy({})'.format(old_units['energy'])] = df['Energy({})'.format(old_units['energy'])] * unit_tables.energy()[old_units['energy']].loc[new_units['energy']] + + df['RelativeTime({})'.format(new_units['time'])] = df.apply(lambda row : convert_time_string(row['Relative Time(h:min:s.ms)'], unit=new_units['time']), axis=1) + + return df + + +def set_units(units=None): + # Complete the list of units - if not all are passed, then default value will be used - required_units = ['t', 'I', 'U', 'C'] - default_units = {'t': 'h', 'I': 'mA', 'U': 'V', 'C': 'mAh/g'} + required_units = ['time', 'current', 'voltage', 'capacity', 'mass', 'energy'] + default_units = {'time': 'h', 'current': 'mA', 'voltage': 'V', 'capacity': 'mAh', 'mass': 'g', 'energy': 'mWh'} if not units: units = default_units if units: for unit in required_units: - if unit not in units.values(): + if unit not in units.keys(): units[unit] = default_units[unit] + + return units + + + +def get_old_units(df, kind): + if kind=='batsmall': + time = df.columns[0].split()[-1].strip('[]') + voltage = df.columns[1].split()[-1].strip('[]') + current = df.columns[2].split()[-1].strip('[]') + capacity, mass = df.columns[4].split()[-1].strip('[]').split('/') + old_units = {'time': time, 'current': current, 'voltage': voltage, 'capacity': capacity, 'mass': mass} - # Get the units used in the data set - t_prev = 's' # default in - U_prev = df.columns[1].split()[-1].strip('[]') - I_prev = df.columns[2].split()[-1].strip('[]') - C_prev, m_prev = df.columns[4].split()[-1].strip('[]').split('/') - prev_units = {'t': t_prev, 'I': I_prev, 'U': U_prev, 'C': C_prev} + if kind=='neware': + + for column in df.columns: + if 'Voltage' in column: + voltage = column.split('(')[-1].strip(')') + elif 'Current' in column: + current = column.split('(')[-1].strip(')') + elif 'Capacity' in column: + capacity = column.split('(')[-1].strip(')') + elif 'Energy' in column: + energy = column.split('(')[-1].strip(')') - # Convert all units to the desired units. - df = unit_conversion(df=df, units=units) - - - - if active_material_weight: - df["SpecificCapacity(mAh/g)"] = df["Capacity(mAh)"] / (active_material_weight / 1000) - - if molecular_weight: - faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 - seconds_per_hour = 3600 # s h^-1 - f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 - - df["IonsExtracted"] = (df["SpecificCapacity(mAh/g)"]*molecular_weight)/f - - -def unit_conversion(df, units, prev_units, kind): - - C, m = units['C'].split('/') - C_prev, m_prev = prev_units['C'].split('/') - - - # Define matrix for unit conversion for time - t_units_df = {'h': [1, 60, 3600, 3600000], 'min': [1/60, 1, 60, 60000], 's': [1/3600, 1/60, 1, 1000], 'ms': [1/3600000, 1/60000, 1/1000, 1]} - t_units_df = pd.DataFrame(t_units_df) - t_units_df.index = ['h', 'min', 's', 'ms'] - - # Define matrix for unit conversion for current - I_units_df = {'A': [1, 1000, 1000000], 'mA': [1/1000, 1, 1000], 'uA': [1/1000000, 1/1000, 1]} - I_units_df = pd.DataFrame(I_units_df) - I_units_df.index = ['A', 'mA', 'uA'] - - # Define matrix for unit conversion for voltage - U_units_df = {'V': [1, 1000, 1000000], 'mV': [1/1000, 1, 1000], 'uV': [1/1000000, 1/1000, 1]} - U_units_df = pd.DataFrame(U_units_df) - U_units_df.index = ['V', 'mV', 'uV'] - - # Define matrix for unit conversion for capacity - C_units_df = {'Ah': [1, 1000, 1000000], 'mAh': [1/1000, 1, 1000], 'uAh': [1/1000000, 1/1000, 1]} - C_units_df = pd.DataFrame(C_units_df) - C_units_df.index = ['Ah', 'mAh', 'uAh'] - - # Define matrix for unit conversion for capacity - m_units_df = {'kg': [1, 1000, 1000000, 1000000000], 'g': [1/1000, 1, 1000, 1000000], 'mg': [1/1000000, 1/1000, 1, 1000], 'ug': [1/1000000000, 1/1000000, 1/1000, 1]} - m_units_df = pd.DataFrame(m_units_df) - m_units_df.index = ['kg', 'g', 'mg', 'ug'] - - #print(df["TT [{}]".format(t_prev)]) - df["TT [{}]".format(t_prev)] = df["TT [{}]".format(t_prev)] * t_units_df[t_prev].loc[units['t']] - df["U [{}]".format(U_prev)] = df["U [{}]".format(U_prev)] * U_units_df[U_prev].loc[units['U']] - df["I [{}]".format(I_prev)] = df["I [{}]".format(I_prev)] * I_units_df[I_prev].loc[units['I']] - df["C [{}/{}]".format(C_prev, m_prev)] = df["C [{}/{}]".format(C_prev, m_prev)] * (C_units_df[C_prev].loc[C] / m_units_df[m_prev].loc[m]) - - df.columns = ['TT', 'U', 'I', 'Z1', 'C', 'Comment'] - - - - return df + old_units = {'voltage': voltage, 'current': current, 'capacity': capacity, 'energy': energy} + return old_units def convert_time_string(time_string, unit='ms'): ''' Convert time string from Neware-data with the format hh:mm:ss.xx to any given unit''' h, m, s = time_string.split(':') - ms = int(s)*1000 + int(m)*1000*60 + int(h)*1000*60*60 + ms = float(s)*1000 + int(m)*1000*60 + int(h)*1000*60*60 factors = {'ms': 1, 's': 1/1000, 'min': 1/(1000*60), 'h': 1/(1000*60*60)} diff --git a/beamtime/electrochemistry/unit_tables.py b/beamtime/electrochemistry/unit_tables.py new file mode 100644 index 0000000..c839b9b --- /dev/null +++ b/beamtime/electrochemistry/unit_tables.py @@ -0,0 +1,53 @@ +import pandas as pd + +def time(): + # Define matrix for unit conversion for time + time = {'h': [1, 60, 3600, 3600000], 'min': [1/60, 1, 60, 60000], 's': [1/3600, 1/60, 1, 1000], 'ms': [1/3600000, 1/60000, 1/1000, 1]} + time = pd.DataFrame(time) + time.index = ['h', 'min', 's', 'ms'] + + return time + +def current(): + # Define matrix for unit conversion for current + current = {'A': [1, 1000, 1000000], 'mA': [1/1000, 1, 1000], 'uA': [1/1000000, 1/1000, 1]} + current = pd.DataFrame(current) + current.index = ['A', 'mA', 'uA'] + + return current + +def voltage(): + # Define matrix for unit conversion for voltage + voltage = {'V': [1, 1000, 1000000], 'mV': [1/1000, 1, 1000], 'uV': [1/1000000, 1/1000, 1]} + voltage = pd.DataFrame(voltage) + voltage.index = ['V', 'mV', 'uV'] + + return voltage + +def capacity(): + # Define matrix for unit conversion for capacity + capacity = {'Ah': [1, 1000, 1000000], 'mAh': [1/1000, 1, 1000], 'uAh': [1/1000000, 1/1000, 1]} + capacity = pd.DataFrame(capacity) + capacity.index = ['Ah', 'mAh', 'uAh'] + + return capacity + +def mass(): + # Define matrix for unit conversion for capacity + mass = {'kg': [1, 1000, 1000000, 1000000000], 'g': [1/1000, 1, 1000, 1000000], 'mg': [1/1000000, 1/1000, 1, 1000], 'ug': [1/1000000000, 1/1000000, 1/1000, 1]} + mass = pd.DataFrame(mass) + mass.index = ['kg', 'g', 'mg', 'ug'] + + return mass + + +def energy(): + + energy = {'kWh': [1, 1000, 1000000], 'Wh': [1/1000, 1, 1000], 'mWh': [1/100000, 1/1000, 1]} + energy = pd.DataFrame(energy) + energy.index = ['kWh', 'Wh', 'mWh'] + + return energy + + + From 26bd7d8a15343b7aa46848465afc6fbdba19cdbe Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Fri, 8 Oct 2021 14:02:13 +0200 Subject: [PATCH 05/10] Update io.py --- beamtime/electrochemistry/io.py | 141 ++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 35 deletions(-) diff --git a/beamtime/electrochemistry/io.py b/beamtime/electrochemistry/io.py index 3e5bf02..0984581 100644 --- a/beamtime/electrochemistry/io.py +++ b/beamtime/electrochemistry/io.py @@ -77,26 +77,24 @@ def process_batsmall_data(df, units=None, splice_cycles=None, molecular_weight=N old_units = get_old_units(df, kind='batsmall') df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='batsmall') - df.columns = ['TT', 'U', 'I', 'Z1', 'C', 'Comment'] - # Replace NaN with empty string in the Comment-column and then remove all steps where the program changes - this is due to inconsistent values for current - df[["Comment"]] = df[["Comment"]].fillna(value={'Comment': ''}) - df = df[df["Comment"].str.contains("program")==False] + df[["comment"]] = df[["comment"]].fillna(value={'comment': ''}) + df = df[df["comment"].str.contains("program")==False] # Creates masks for charge and discharge curves - chg_mask = df['I'] >= 0 - dchg_mask = df['I'] < 0 + chg_mask = df['current'] >= 0 + dchg_mask = df['current'] < 0 # Initiate cycles list cycles = [] # Loop through all the cycling steps, change the current and capacities in the - for i in range(df["Z1"].max()): + for i in range(df["count"].max()): - sub_df = df.loc[df['Z1'] == i].copy() + sub_df = df.loc[df['count'] == i].copy() - sub_df.loc[dchg_mask, 'I'] *= -1 - sub_df.loc[dchg_mask, 'C'] *= -1 + sub_df.loc[dchg_mask, 'current'] *= -1 + sub_df.loc[dchg_mask, 'capacity'] *= -1 chg_df = sub_df.loc[chg_mask] dchg_df = sub_df.loc[dchg_mask] @@ -113,27 +111,79 @@ def process_batsmall_data(df, units=None, splice_cycles=None, molecular_weight=N return cycles -def process_neware_data(df, units=None, splice_cycles=None, active_material_weight=None, molecular_weight=None): +def process_neware_data(df, units=None, splice_cycles=None, active_material_weight=None, molecular_weight=None, reverse_discharge=False): - ######################### - #### UNIT CONVERSION #### - ######################### + """ Takes data from NEWARE in a DataFrame as read by read_neware() and converts units, adds columns and splits into cycles. + + Input: + df: pandas DataFrame containing NEWARE data as read by read_neware() + units: dictionary containing the desired units. keywords: capacity, current, voltage, mass, energy, time + splice_cycles: tuple containing index of cycles that should be spliced. Specifically designed to add two charge steps during the formation cycle with two different max voltages + active_materiale_weight: weight of the active material (in mg) used in the cell. + molecular_weight: the molar mass (in g mol^-1) of the active material, to calculate the number of ions extracted. Assumes one electron per Li+/Na+-ion """ # Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new. new_units = set_units(units=units) - old_units = get_old_units(df, kind='neware') + old_units = get_old_units(df=df, kind='neware') + + df = add_columns_neware(df=df, active_material_weight=active_material_weight, molecular_weight=molecular_weight, old_units=old_units) + df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='neware') - # if active_material_weight: - # df["SpecificCapacity(mAh/g)"] = df["Capacity(mAh)"] / (active_material_weight / 1000) + # Creates masks for charge and discharge curves + chg_mask = df['status'] == 'CC Chg' + dchg_mask = df['status'] == 'CC DChg' - # if molecular_weight: - # faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 - # seconds_per_hour = 3600 # s h^-1 - # f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 + # Initiate cycles list + cycles = [] - # df["IonsExtracted"] = (df["SpecificCapacity(mAh/g)"]*molecular_weight)/f + # Loop through all the cycling steps, change the current and capacities in the + for i in range(df["cycle"].max()): + + sub_df = df.loc[df['cycle'] == i].copy() + + #sub_df.loc[dchg_mask, 'current'] *= -1 + #sub_df.loc[dchg_mask, 'capacity'] *= -1 + + chg_df = sub_df.loc[chg_mask] + dchg_df = sub_df.loc[dchg_mask] + + # Continue to next iteration if the charge and discharge DataFrames are empty (i.e. no current) + if chg_df.empty and dchg_df.empty: + continue + + if reverse_discharge: + max_capacity = dchg_df['capacity'].max() + dchg_df['capacity'] = np.abs(dchg_df['capacity'] - max_capacity) + + if 'specific_capacity' in df.columns: + max_capacity = dchg_df['specific_capacity'].max() + dchg_df['specific_capacity'] = np.abs(dchg_df['specific_capacity'] - max_capacity) + + if 'ions' in df.columns: + max_capacity = dchg_df['ions'].max() + dchg_df['ions'] = np.abs(dchg_df['ions'] - max_capacity) + + cycles.append((chg_df, dchg_df)) + + + + return cycles + + +def add_columns_neware(df, active_material_weight, molecular_weight, old_units): + + + if active_material_weight: + df["SpecificCapacity({}/mg)".format(old_units["capacity"])] = df["Capacity({})".format(old_units['capacity'])] / (active_material_weight) + + if molecular_weight: + faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 + seconds_per_hour = 3600 # s h^-1 + f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 + + df["IonsExtracted"] = (df["SpecificCapacity({}/mg)".format(old_units['capacity'])]*molecular_weight)*1000/f return df @@ -149,14 +199,34 @@ def unit_conversion(df, new_units, old_units, kind): df["I [{}]".format(old_units["current"])] = df["I [{}]".format(old_units["current"])] * unit_tables.current()[old_units["current"]].loc[new_units['current']] df["C [{}/{}]".format(old_units["capacity"], old_units["mass"])] = df["C [{}/{}]".format(old_units["capacity"], old_units["mass"])] * (unit_tables.capacity()[old_units["capacity"]].loc[new_units["capacity"]] / unit_tables.mass()[old_units["mass"]].loc[new_units["mass"]]) + df.columns = ['time', 'voltage', 'current', 'count', 'specific_capacity', 'comment'] + if kind == 'neware': df['Current({})'.format(old_units['current'])] = df['Current({})'.format(old_units['current'])] * unit_tables.current()[old_units['current']].loc[new_units['current']] df['Voltage({})'.format(old_units['voltage'])] = df['Voltage({})'.format(old_units['voltage'])] * unit_tables.voltage()[old_units['voltage']].loc[new_units['voltage']] df['Capacity({})'.format(old_units['capacity'])] = df['Capacity({})'.format(old_units['capacity'])] * unit_tables.capacity()[old_units['capacity']].loc[new_units['capacity']] df['Energy({})'.format(old_units['energy'])] = df['Energy({})'.format(old_units['energy'])] * unit_tables.energy()[old_units['energy']].loc[new_units['energy']] + df['CycleTime({})'.format(new_units['time'])] = df.apply(lambda row : convert_time_string(row['Relative Time(h:min:s.ms)'], unit=new_units['time']), axis=1) + df['RunTime({})'.format(new_units['time'])] = df.apply(lambda row : convert_datetime_string(row['Real Time(h:min:s.ms)'], reference=df['Real Time(h:min:s.ms)'].iloc[0], unit=new_units['time']), axis=1) + columns = ['status', 'jump', 'cycle', 'steps', 'current', 'voltage', 'capacity', 'energy'] - df['RelativeTime({})'.format(new_units['time'])] = df.apply(lambda row : convert_time_string(row['Relative Time(h:min:s.ms)'], unit=new_units['time']), axis=1) + if 'SpecificCapacity({}/mg)'.format(old_units['capacity']) in df.columns: + df['SpecificCapacity({}/mg)'.format(old_units['capacity'])] = df['SpecificCapacity({}/mg)'.format(old_units['capacity'])] * unit_tables.capacity()[old_units['capacity']].loc[new_units['capacity']] / unit_tables.mass()['mg'].loc[new_units["mass"]] + columns.append('specific_capacity') + + if 'IonsExtracted' in df.columns: + columns.append('ions') + + + + columns.append('cycle_time') + columns.append('run_time') + + + df.drop(['Record number', 'Relative Time(h:min:s.ms)', 'Real Time(h:min:s.ms)'], axis=1, inplace=True) + + df.columns = columns return df @@ -226,25 +296,26 @@ def convert_datetime_string(datetime_string, reference, unit='s'): from datetime import datetime # Parse the - cur_date, cur_time = datetime_string.split() - cur_y, cur_mo, cur_d = cur_date.split('-') - cur_h, cur_m, cur_s = cur_time.split(':') - cur_date = datetime(int(cur_y), int(cur_mo), int(cur_d), int(cur_h), int(cur_m), int(cur_s)) + current_date, current_time = datetime_string.split() + current_year, current_month, current_day = current_date.split('-') + current_hour, current_minute, current_second = current_time.split(':') + current_date = datetime(int(current_year), int(current_month), int(current_day), int(current_hour), int(current_minute), int(current_second)) - ref_date, ref_time = reference.split() - ref_y, ref_mo, ref_d = ref_date.split('-') - ref_h, ref_m, ref_s = ref_time.split(':') - ref_date = datetime(int(ref_y), int(ref_mo), int(ref_d), int(ref_h), int(ref_m), int(ref_s)) + reference_date, reference_time = reference.split() + reference_year, reference_month, reference_day = reference_date.split('-') + reference_hour, reference_minute, reference_second = reference_time.split(':') + reference_date = datetime(int(reference_year), int(reference_month), int(reference_day), int(reference_hour), int(reference_minute), int(reference_second)) - days = cur_date - ref_date + days = current_date - reference_date - s = days.seconds + + s = days.days*24*60*60 + days.seconds factors = {'ms': 1000, 's': 1, 'min': 1/(60), 'h': 1/(60*60)} - t = s * factors[unit] + time = s * factors[unit] - return t + return time def splice_cycles(first, second): From 4f255fd9d5c47d319d9508aa40f70560c6e7b071 Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Tue, 12 Oct 2021 15:53:48 +0200 Subject: [PATCH 06/10] Add BioLogic-functions to io.py --- beamtime/electrochemistry/io.py | 154 +++++++++++++++++++++++++++++--- 1 file changed, 143 insertions(+), 11 deletions(-) diff --git a/beamtime/electrochemistry/io.py b/beamtime/electrochemistry/io.py index 0984581..6c6e5fc 100644 --- a/beamtime/electrochemistry/io.py +++ b/beamtime/electrochemistry/io.py @@ -21,7 +21,7 @@ def read_batsmall(path): -def read_neware(path, summary=False, active_material_weight=None, molecular_weight=None): +def read_neware(path, summary=False): ''' Reads electrochemistry data, currently only from the Neware battery cycler. Will convert to .csv if the filetype is .xlsx, which is the file format the Neware provides for the backup data. In this case it matters if summary is False or not. If file type is .csv, it will just open the datafile and it does not matter if summary is False or not.''' @@ -51,6 +51,29 @@ def read_neware(path, summary=False, active_material_weight=None, molecular_weig +def read_biologic(path): + ''' Reads Bio-Logic-data into a DataFrame. + + Input: + path (required): string with path to datafile + + Output: + df: pandas DataFrame containing the data as-is, but without additional NaN-columns.''' + + with open(path, 'r') as f: + lines = f.readlines() + + header_lines = int(lines[1].split()[-1]) - 1 + + + df = pd.read_csv(path, sep='\t', skiprows=header_lines) + df.dropna(inplace=True, axis=1) + + return df + + + + @@ -94,7 +117,7 @@ def process_batsmall_data(df, units=None, splice_cycles=None, molecular_weight=N sub_df = df.loc[df['count'] == i].copy() sub_df.loc[dchg_mask, 'current'] *= -1 - sub_df.loc[dchg_mask, 'capacity'] *= -1 + sub_df.loc[dchg_mask, 'specific_capacity'] *= -1 chg_df = sub_df.loc[chg_mask] dchg_df = sub_df.loc[dchg_mask] @@ -126,7 +149,7 @@ def process_neware_data(df, units=None, splice_cycles=None, active_material_weig new_units = set_units(units=units) old_units = get_old_units(df=df, kind='neware') - df = add_columns_neware(df=df, active_material_weight=active_material_weight, molecular_weight=molecular_weight, old_units=old_units) + df = add_columns(df=df, active_material_weight=active_material_weight, molecular_weight=molecular_weight, old_units=old_units, kind='neware') df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='neware') @@ -172,19 +195,91 @@ def process_neware_data(df, units=None, splice_cycles=None, active_material_weig return cycles -def add_columns_neware(df, active_material_weight, molecular_weight, old_units): +def process_biologic_data(df, units=None, splice_cycles=None, active_material_weight=None, molecular_weight=None, reverse_discharge=False): + + # Pick out necessary columns + df = df[['Ns changes', 'Ns', 'time/s', 'Ewe/V', 'Energy charge/W.h', 'Energy discharge/W.h', '/mA', 'Capacity/mA.h', 'cycle number']].copy() + + # Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new. + new_units = set_units(units=units) + old_units = get_old_units(df=df, kind='biologic') + + df = add_columns(df=df, active_material_weight=active_material_weight, molecular_weight=molecular_weight, old_units=old_units, kind='biologic') + + df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='biologic') - if active_material_weight: - df["SpecificCapacity({}/mg)".format(old_units["capacity"])] = df["Capacity({})".format(old_units['capacity'])] / (active_material_weight) + # Creates masks for charge and discharge curves + chg_mask = (df['status'] == 1) & (df['status_change'] != 1) + dchg_mask = (df['status'] == 2) & (df['status_change'] != 1) - if molecular_weight: - faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 - seconds_per_hour = 3600 # s h^-1 - f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 - df["IonsExtracted"] = (df["SpecificCapacity({}/mg)".format(old_units['capacity'])]*molecular_weight)*1000/f + # Initiate cycles list + cycles = [] + + # Loop through all the cycling steps, change the current and capacities in the + for i in range(int(df["cycle"].max())): + + sub_df = df.loc[df['cycle'] == i].copy() + + #sub_df.loc[dchg_mask, 'current'] *= -1 + #sub_df.loc[dchg_mask, 'capacity'] *= -1 + + chg_df = sub_df.loc[chg_mask] + dchg_df = sub_df.loc[dchg_mask] + + # Continue to next iteration if the charge and discharge DataFrames are empty (i.e. no current) + if chg_df.empty and dchg_df.empty: + continue + + if reverse_discharge: + max_capacity = dchg_df['capacity'].max() + dchg_df['capacity'] = np.abs(dchg_df['capacity'] - max_capacity) + + if 'specific_capacity' in df.columns: + max_capacity = dchg_df['specific_capacity'].max() + dchg_df['specific_capacity'] = np.abs(dchg_df['specific_capacity'] - max_capacity) + + if 'ions' in df.columns: + max_capacity = dchg_df['ions'].max() + dchg_df['ions'] = np.abs(dchg_df['ions'] - max_capacity) + + cycles.append((chg_df, dchg_df)) + + + + return cycles + + +def add_columns(df, active_material_weight, molecular_weight, old_units, kind): + + if kind == 'neware': + if active_material_weight: + df["SpecificCapacity({}/mg)".format(old_units["capacity"])] = df["Capacity({})".format(old_units['capacity'])] / (active_material_weight) + + if molecular_weight: + faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 + seconds_per_hour = 3600 # s h^-1 + f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 + + df["IonsExtracted"] = (df["SpecificCapacity({}/mg)".format(old_units['capacity'])]*molecular_weight)*1000/f + + + if kind == 'biologic': + if active_material_weight: + + capacity = old_units['capacity'].split('h')[0] + '.h' + + + df["SpecificCapacity({}/mg)".format(old_units["capacity"])] = df["Capacity/{}".format(capacity)] / (active_material_weight) + + if molecular_weight: + faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 + seconds_per_hour = 3600 # s h^-1 + f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 + + df["IonsExtracted"] = (df["SpecificCapacity({}/mg)".format(old_units['capacity'])]*molecular_weight)*1000/f return df @@ -228,6 +323,25 @@ def unit_conversion(df, new_units, old_units, kind): df.columns = columns + if kind == 'biologic': + df['time/{}'.format(old_units['time'])] = df["time/{}".format(old_units["time"])] * unit_tables.time()[old_units["time"]].loc[new_units['time']] + df["Ewe/{}".format(old_units["voltage"])] = df["Ewe/{}".format(old_units["voltage"])] * unit_tables.voltage()[old_units["voltage"]].loc[new_units['voltage']] + df["/{}".format(old_units["current"])] = df["/{}".format(old_units["current"])] * unit_tables.current()[old_units["current"]].loc[new_units['current']] + + capacity = old_units['capacity'].split('h')[0] + '.h' + df["Capacity/{}".format(capacity)] = df["Capacity/{}".format(capacity)] * (unit_tables.capacity()[old_units["capacity"]].loc[new_units["capacity"]]) + + columns = ['status_change', 'status', 'time', 'voltage', 'energy_charge', 'energy_discharge', 'current', 'capacity', 'cycle'] + + if 'SpecificCapacity({}/mg)'.format(old_units['capacity']) in df.columns: + df['SpecificCapacity({}/mg)'.format(old_units['capacity'])] = df['SpecificCapacity({}/mg)'.format(old_units['capacity'])] * unit_tables.capacity()[old_units['capacity']].loc[new_units['capacity']] / unit_tables.mass()['mg'].loc[new_units["mass"]] + columns.append('specific_capacity') + + if 'IonsExtracted' in df.columns: + columns.append('ions') + + df.columns = columns + return df @@ -274,6 +388,24 @@ def get_old_units(df, kind): old_units = {'voltage': voltage, 'current': current, 'capacity': capacity, 'energy': energy} + if kind=='biologic': + + for column in df.columns: + if 'time' in column: + time = column.split('/')[-1] + elif 'Ewe' in column: + voltage = column.split('/')[-1] + elif 'Capacity' in column: + capacity = column.split('/')[-1].replace('.', '') + elif 'Energy' in column: + energy = column.split('/')[-1].replace('.', '') + elif '' in column: + current = column.split('/')[-1] + + old_units = {'voltage': voltage, 'current': current, 'capacity': capacity, 'energy': energy, 'time': time} + + + return old_units def convert_time_string(time_string, unit='ms'): From 43e6ef27c842a9e93152ad2a57f695c60afccb13 Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Wed, 13 Oct 2021 18:06:56 +0200 Subject: [PATCH 07/10] Add plot functionality to electrochemistry --- beamtime/electrochemistry/io.py | 91 +++++++- beamtime/electrochemistry/plot.py | 375 ++++++++++++++++++++++++++++-- 2 files changed, 433 insertions(+), 33 deletions(-) diff --git a/beamtime/electrochemistry/io.py b/beamtime/electrochemistry/io.py index 6c6e5fc..35d271b 100644 --- a/beamtime/electrochemistry/io.py +++ b/beamtime/electrochemistry/io.py @@ -4,6 +4,22 @@ import matplotlib.pyplot as plt import os +def read_data(path, kind, options=None): + + if kind == 'neware': + df = read_neware(path) + cycles = process_neware_data(df, options=options) + + elif kind == 'batsmall': + df = read_batsmall(path) + cycles = process_batsmall_data(df=df, options=options) + + elif kind == 'biologic': + df = read_biologic(path) + cycles = process_biologic_data(df=df, options=options) + + return cycles + def read_batsmall(path): ''' Reads BATSMALL-data into a DataFrame. @@ -77,7 +93,7 @@ def read_biologic(path): -def process_batsmall_data(df, units=None, splice_cycles=None, molecular_weight=None): +def process_batsmall_data(df, options=None): ''' Takes BATSMALL-data in the form of a DataFrame and cleans the data up and converts units into desired units. Splits up into individual charge and discharge DataFrames per cycle, and outputs a list where each element is a tuple with the Chg and DChg-data. E.g. cycles[10][0] gives the charge data for the 11th cycle. @@ -94,12 +110,24 @@ def process_batsmall_data(df, units=None, splice_cycles=None, molecular_weight=N cycles: A list with ''' + required_options = ['splice_cycles', 'molecular_weight', 'reverse_discharge', 'units'] + default_options = {'splice_cycles': None, 'molecular_weight': None, 'reverse_discharge': False, 'units': None} + + if not options: + options = default_options + else: + for option in required_options: + if option not in options.keys(): + options[option] = default_options[option] + # Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new. - new_units = set_units(units=units) + new_units = set_units(units=options['units']) old_units = get_old_units(df, kind='batsmall') df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='batsmall') + options['units'] = new_units + # Replace NaN with empty string in the Comment-column and then remove all steps where the program changes - this is due to inconsistent values for current df[["comment"]] = df[["comment"]].fillna(value={'comment': ''}) df = df[df["comment"].str.contains("program")==False] @@ -126,6 +154,18 @@ def process_batsmall_data(df, units=None, splice_cycles=None, molecular_weight=N if chg_df.empty and dchg_df.empty: continue + if options['reverse_discharge']: + max_capacity = dchg_df['capacity'].max() + dchg_df['capacity'] = np.abs(dchg_df['capacity'] - max_capacity) + + if 'specific_capacity' in df.columns: + max_capacity = dchg_df['specific_capacity'].max() + dchg_df['specific_capacity'] = np.abs(dchg_df['specific_capacity'] - max_capacity) + + if 'ions' in df.columns: + max_capacity = dchg_df['ions'].max() + dchg_df['ions'] = np.abs(dchg_df['ions'] - max_capacity) + cycles.append((chg_df, dchg_df)) @@ -134,7 +174,7 @@ def process_batsmall_data(df, units=None, splice_cycles=None, molecular_weight=N return cycles -def process_neware_data(df, units=None, splice_cycles=None, active_material_weight=None, molecular_weight=None, reverse_discharge=False): +def process_neware_data(df, options=None): """ Takes data from NEWARE in a DataFrame as read by read_neware() and converts units, adds columns and splits into cycles. @@ -145,14 +185,27 @@ def process_neware_data(df, units=None, splice_cycles=None, active_material_weig active_materiale_weight: weight of the active material (in mg) used in the cell. molecular_weight: the molar mass (in g mol^-1) of the active material, to calculate the number of ions extracted. Assumes one electron per Li+/Na+-ion """ + required_options = ['units', 'active_material_weight', 'molecular_weight', 'reverse_discharge', 'splice_cycles'] + default_options = {'units': None, 'active_material_weight': None, 'molecular_weight': None, 'reverse_discharge': False, 'splice_cycles': None} + + if not options: + options = default_options + else: + for option in required_options: + if option not in options.keys(): + options[option] = default_options[option] + + # Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new. - new_units = set_units(units=units) + new_units = set_units(units=options['units']) old_units = get_old_units(df=df, kind='neware') - df = add_columns(df=df, active_material_weight=active_material_weight, molecular_weight=molecular_weight, old_units=old_units, kind='neware') + df = add_columns(df=df, active_material_weight=options['active_material_weight'], molecular_weight=options['molecular_weight'], old_units=old_units, kind='neware') df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='neware') + options['units'] = new_units + # Creates masks for charge and discharge curves chg_mask = df['status'] == 'CC Chg' @@ -176,7 +229,7 @@ def process_neware_data(df, units=None, splice_cycles=None, active_material_weig if chg_df.empty and dchg_df.empty: continue - if reverse_discharge: + if options['reverse_discharge']: max_capacity = dchg_df['capacity'].max() dchg_df['capacity'] = np.abs(dchg_df['capacity'] - max_capacity) @@ -195,19 +248,31 @@ def process_neware_data(df, units=None, splice_cycles=None, active_material_weig return cycles -def process_biologic_data(df, units=None, splice_cycles=None, active_material_weight=None, molecular_weight=None, reverse_discharge=False): +def process_biologic_data(df, options=None): + + required_options = ['units', 'active_material_weight', 'molecular_weight', 'reverse_discharge', 'splice_cycles'] + default_options = {'units': None, 'active_material_weight': None, 'molecular_weight': None, 'reverse_discharge': False, 'splice_cycles': None} + + if not options: + options = default_options + else: + for option in required_options: + if option not in options.keys(): + options[option] = default_options[option] # Pick out necessary columns df = df[['Ns changes', 'Ns', 'time/s', 'Ewe/V', 'Energy charge/W.h', 'Energy discharge/W.h', '/mA', 'Capacity/mA.h', 'cycle number']].copy() # Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new. - new_units = set_units(units=units) + new_units = set_units(units=options['units']) old_units = get_old_units(df=df, kind='biologic') - df = add_columns(df=df, active_material_weight=active_material_weight, molecular_weight=molecular_weight, old_units=old_units, kind='biologic') + df = add_columns(df=df, active_material_weight=options['active_material_weight'], molecular_weight=options['molecular_weight'], old_units=old_units, kind='biologic') df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='biologic') + options['units'] = new_units + # Creates masks for charge and discharge curves chg_mask = (df['status'] == 1) & (df['status_change'] != 1) @@ -233,7 +298,7 @@ def process_biologic_data(df, units=None, splice_cycles=None, active_material_we if chg_df.empty and dchg_df.empty: continue - if reverse_discharge: + if options['reverse_discharge']: max_capacity = dchg_df['capacity'].max() dchg_df['capacity'] = np.abs(dchg_df['capacity'] - max_capacity) @@ -348,8 +413,8 @@ def unit_conversion(df, new_units, old_units, kind): def set_units(units=None): # Complete the list of units - if not all are passed, then default value will be used - required_units = ['time', 'current', 'voltage', 'capacity', 'mass', 'energy'] - default_units = {'time': 'h', 'current': 'mA', 'voltage': 'V', 'capacity': 'mAh', 'mass': 'g', 'energy': 'mWh'} + required_units = ['time', 'current', 'voltage', 'capacity', 'mass', 'energy', 'specific_capacity'] + default_units = {'time': 'h', 'current': 'mA', 'voltage': 'V', 'capacity': 'mAh', 'mass': 'g', 'energy': 'mWh', 'specific_capacity': None} if not units: units = default_units @@ -359,6 +424,8 @@ def set_units(units=None): if unit not in units.keys(): units[unit] = default_units[unit] + units['specific_capacity'] = r'{} {}'.format(units['capacity'], units['mass']) + '$^{-1}$' + return units diff --git a/beamtime/electrochemistry/plot.py b/beamtime/electrochemistry/plot.py index 70f2b22..eabd105 100644 --- a/beamtime/electrochemistry/plot.py +++ b/beamtime/electrochemistry/plot.py @@ -1,40 +1,373 @@ import matplotlib.pyplot as plt +from matplotlib.ticker import (MultipleLocator, FormatStrFormatter,AutoMinorLocator) + import pandas as pd import numpy as np +import math + +import beamtime.electrochemistry as ec -def plot_gc(cycles, which_cycles='all', chg=True, dchg=True, colours=None, x='C', y='U'): +def plot_gc(path, kind, options=None): - fig, ax = prepare_gc_plot() + # Prepare plot, and read and process data + fig, ax = prepare_gc_plot(options=options) + cycles = ec.io.read_data(path=path, kind=kind, options=options) - if which_cycles == 'all': - which_cycles = [i for i, c in enumerate(cycles)] + # Update options + required_options = ['x_vals', 'y_vals', 'which_cycles', 'chg', 'dchg', 'colours', 'gradient'] + default_options = {'x_vals': 'capacity', 'y_vals': 'voltage', 'which_cycles': 'all', 'chg': True, 'dchg': True, 'colours': None, 'gradient': False} - if not colours: - chg_colour = (40/255, 70/255, 75/255) # Dark Slate Gray #28464B - dchg_colour = (239/255, 160/255, 11/255) # Marigold #EFA00B + options = update_options(options=options, required_options=required_options, default_options=default_options) + + # Update list of cycles to correct indices + update_cycles_list(cycles=cycles, options=options) + + colours = generate_colours(cycles=cycles, options=options) + + print(len(options['which_cycles'])) + print(len(colours)) + + + + for i, cycle in enumerate(cycles): + if i in options['which_cycles']: + if options['chg']: + cycle[0].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][0]) + + if options['dchg']: + cycle[1].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][1]) + + + + fig, ax = prettify_gc_plot(fig=fig, ax=ax, options=options) + + return cycles, fig, ax + + +def update_options(options, required_options, default_options): + + if not options: + options = default_options + + else: + for option in required_options: + if option not in options.keys(): + options[option] = default_options[option] + + return options + +def update_cycles_list(cycles, options): + + if not options: + options['which_cycles'] + + if options['which_cycles'] == 'all': + options['which_cycles'] = [i for i in range(len(cycles))] + + + elif type(options['which_cycles']) == list: + options['which_cycles'] = [i-1 for i in options['which_cycles']] + # Tuple is used to define an interval - as elements tuples can't be assigned, I convert it to a list here. + elif type(options['which_cycles']) == tuple: + which_cycles = list(options['which_cycles']) - for i, cycle in cycles: - if i in which_cycles: - if chg: - cycle[0].plot(ax=ax) + if which_cycles[0] <= 0: + which_cycles[0] = 1 + + elif which_cycles[1] < 0: + which_cycles[1] = len(cycles) + + + options['which_cycles'] = [i-1 for i in range(which_cycles[0], which_cycles[1]+1)] + + + return options + + +def prepare_gc_plot(options=None): + + + # First take care of the options for plotting - set any values not specified to the default values + required_options = ['columns', 'width', 'height', 'format', 'dpi', 'facecolor'] + default_options = {'columns': 1, 'width': 14, 'format': 'golden_ratio', 'dpi': None, 'facecolor': 'w'} + + # If none are set at all, just pass the default_options + if not options: + options = default_options + options['height'] = options['width'] * (math.sqrt(5) - 1) / 2 + options['figsize'] = (options['width'], options['height']) + + + # If options is passed, go through to fill out the rest. + else: + # Start by setting the width: + if 'width' not in options.keys(): + options['width'] = default_options['width'] + + # Then set height - check options for format. If not given, set the height to the width scaled by the golden ratio - if the format is square, set the same. This should possibly allow for the tweaking of custom ratios later. + if 'height' not in options.keys(): + if 'format' not in options.keys(): + options['height'] = options['width'] * (math.sqrt(5) - 1) / 2 + elif options['format'] == 'square': + options['height'] = options['width'] + + options['figsize'] = (options['width'], options['height']) + + # After height and width are set, go through the rest of the options to make sure that all the required options are filled + for option in required_options: + if option not in options.keys(): + options[option] = default_options[option] + + fig, ax = plt.subplots(figsize=(options['figsize']), dpi=options['dpi'], facecolor=options['facecolor']) + + linewidth = 1*options['columns'] + axeswidth = 3*options['columns'] + + plt.rc('lines', linewidth=linewidth) + plt.rc('axes', linewidth=axeswidth) + + return fig, ax + + +def prettify_gc_plot(fig, ax, options=None): + + + ################################################################## + ######################### UPDATE OPTIONS ######################### + ################################################################## + + # Define the required options + required_options = [ + 'columns', + 'xticks', 'yticks', + 'show_major_ticks', + 'show_minor_ticks', + 'xlim', 'ylim', + 'hide_x_axis', 'hide_y_axis', + 'x_vals', 'y_vals', + 'xlabel', 'ylabel', + 'units', 'sizes', + 'title' + ] + + + # Define the default options + default_options = { + 'columns': 1, + 'xticks': None, + 'yticks': None, + 'show_major_ticks': [True, True, True, True], + 'show_minor_ticks': [True, True, True, True], + 'xlim': None, + 'ylim': None, + 'hide_x_axis': False, + 'hide_y_axis': False, + 'x_vals': 'specific_capacity', + 'y_vals': 'voltage', + 'xlabel': None, + 'ylabel': None, + 'units': None, + 'sizes': None, + 'title': None + } + + update_options(options, required_options, default_options) + + + ################################################################## + ########################## DEFINE SIZES ########################## + ################################################################## + + # Define the required sizes + required_sizes = [ + 'labels', + 'legend', + 'title', + 'line', 'axes', + 'tick_labels', + 'major_ticks', 'minor_ticks'] + + + + # Define default sizes + default_sizes = { + 'labels': 30*options['columns'], + 'legend': 30*options['columns'], + 'title': 30*options['columns'], + 'line': 3*options['columns'], + 'axes': 3*options['columns'], + 'tick_labels': 30*options['columns'], + 'major_ticks': 20*options['columns'], + 'minor_ticks': 10*options['columns'] + } + + # Initialise dictionary if it doesn't exist + if not options['sizes']: + options['sizes'] = {} + + + # Update dictionary with default values where none is supplied + for size in required_sizes: + if size not in options['sizes']: + options['sizes'][size] = default_sizes[size] + + + ################################################################## + ########################## AXIS LABELS ########################### + ################################################################## + + + if not options['xlabel']: + print(options['x_vals']) + print(options['units']) + options['xlabel'] = prettify_labels(options['x_vals']) + ' [{}]'.format(options['units'][options['x_vals']]) + + else: + options['xlabel'] = options['xlabel'] + ' [{}]'.format(options['units'][options['x_vals']]) + + + if not options['ylabel']: + options['ylabel'] = prettify_labels(options['y_vals']) + ' [{}]'.format(options['units'][options['y_vals']]) + + else: + options['ylabel'] = options['ylabel'] + ' [{}]'.format(options['units'][options['y_vals']]) + + ax.set_xlabel(options['xlabel'], size=options['sizes']['labels']) + ax.set_ylabel(options['ylabel'], size=options['sizes']['labels']) + + ################################################################## + ###################### TICK MARKS & LABELS ####################### + ################################################################## + + ax.tick_params(direction='in', which='major', bottom=options['show_major_ticks'][0], left=options['show_major_ticks'][1], top=options['show_major_ticks'][2], right=options['show_major_ticks'][0], length=options['sizes']['major_ticks'], width=options['sizes']['axes']) + ax.tick_params(direction='in', which='minor', bottom=options['show_minor_ticks'][0], left=options['show_minor_ticks'][1], top=options['show_minor_ticks'][2], right=options['show_minor_ticks'][0], length=options['sizes']['minor_ticks'], width=options['sizes']['axes']) + # DEFINE AND SET TICK DISTANCES + + default_ticks = { + 'specific_capacity': [100, 50], + 'capacity': [0.1, 0.05], + 'voltage': [0.5, 0.25] + } + + + + # Set default tick distances for x-axis if not specified + if not options['xticks']: + + major_xtick = default_ticks[options['x_vals']][0] + minor_xtick = default_ticks[options['x_vals']][1] + + # Otherwise apply user input + else: + major_xtick = options['xticks'][0] + minor_xtick = options['xticks'][1] + + + # Set default tick distances for x-axis if not specified + if not options['yticks']: + + major_ytick = default_ticks[options['y_vals']][0] + minor_ytick = default_ticks[options['y_vals']][1] + + # Otherwise apply user input + else: + major_xtick = options['yticks'][0] + minor_xtick = options['yticks'][1] + + + # Apply values + ax.xaxis.set_major_locator(MultipleLocator(major_xtick)) + ax.xaxis.set_minor_locator(MultipleLocator(minor_xtick)) + + ax.yaxis.set_major_locator(MultipleLocator(major_ytick)) + ax.yaxis.set_minor_locator(MultipleLocator(minor_ytick)) + + + # SET FONTSIZE OF TICK LABELS + + plt.xticks(fontsize=options['sizes']['tick_labels']) + plt.yticks(fontsize=options['sizes']['tick_labels']) + + ################################################################## + ############################# TITLE ############################## + ################################################################## + + if options['title']: + ax.set_title(options['title'], size=options['sizes']['title']) + + ################################################################## + ############################# LEGEND ############################# + ################################################################## + + ax.get_legend().remove() + + return fig, ax - - - - - - -def prepare_gc_plot(figsize=(14,7), dpi=None): - - fig, ax = plt.subplots(figsize=figsize, dpi=dpi) +def prettify_labels(label): + labels_dict = { + 'capacity': 'Capacity', + 'specific_capacity': 'Specific capacity', + 'voltage': 'Voltage', + 'current': 'Current', + 'energy': 'Energy', + } - return fig, ax \ No newline at end of file + return labels_dict[label] + + + + + + + + +def generate_colours(cycles, options): + + # Assign colours from the options dictionary if it is defined, otherwise use standard colours. + if options['colours']: + charge_colour = options['colours'][0] + discharge_colour = options['colours'][1] + + else: + charge_colour = (40/255, 70/255, 75/255) # Dark Slate Gray #28464B, coolors.co + discharge_colour = (239/255, 160/255, 11/255) # Marigold #EFA00B, coolors.co + + + + # If gradient is enabled, find start and end points for each colour + if options['gradient']: + + add_charge = min([(1-x)*0.75 for x in charge_colour]) + add_discharge = min([(1-x)*0.75 for x in discharge_colour]) + + charge_colour_start = charge_colour + charge_colour_end = [x+add_charge for x in charge_colour] + + discharge_colour_start = discharge_colour + discharge_colour_end = [x+add_discharge for x in discharge_colour] + + + + # Generate lists of colours + colours = [] + + for cycle_number in range(0, len(cycles)): + if options['gradient']: + weight_start = (len(cycles) - cycle_number)/len(cycles) + weight_end = cycle_number/len(cycles) + + charge_colour = [weight_start*start_colour + weight_end*end_colour for start_colour, end_colour in zip(charge_colour_start, charge_colour_end)] + discharge_colour = [weight_start*start_colour + weight_end*end_colour for start_colour, end_colour in zip(discharge_colour_start, discharge_colour_end)] + + colours.append([charge_colour, discharge_colour]) + + return colours From 3f3486049b415914c59895c32a1359ad8d757f77 Mon Sep 17 00:00:00 2001 From: halvorhv Date: Thu, 14 Oct 2021 14:18:39 +0200 Subject: [PATCH 08/10] working on split_xanes_scan --- beamtime/xanes/calib.py | 13 ++++++++++++- beamtime/xanes/io.py | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/beamtime/xanes/calib.py b/beamtime/xanes/calib.py index 6817b69..2ef6c4d 100644 --- a/beamtime/xanes/calib.py +++ b/beamtime/xanes/calib.py @@ -5,9 +5,20 @@ import os def rbkerbest(): print("ROSENBORG!<3") +#def split_xanes_scan(filename, destination=None): -def split_xanes_scan(filename, destination=None): + # with open(filename, 'r') as f: + +##Better to make a new function that loops through the files, and performing the split_xanes_scan on + + +def split_xanes_scan(root, destination=None, replace=False): + #root is the path to the beamtime-folder + #destination should be the path to the processed data + + #insert a for-loop to go through all the folders.dat-files in the folder root\xanes\raw + with open(filename, 'r') as f: lines = f.readlines() diff --git a/beamtime/xanes/io.py b/beamtime/xanes/io.py index d08b5c1..a818d86 100644 --- a/beamtime/xanes/io.py +++ b/beamtime/xanes/io.py @@ -1 +1,2 @@ -#hello \ No newline at end of file +#hello +#yeah \ No newline at end of file From be7d153a9deb957beef981c3932c70ec1568ecf1 Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Thu, 14 Oct 2021 15:10:37 +0200 Subject: [PATCH 09/10] Update options --- beamtime/electrochemistry/io.py | 2 +- beamtime/electrochemistry/plot.py | 63 +++++++++++++++++++------------ 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/beamtime/electrochemistry/io.py b/beamtime/electrochemistry/io.py index 35d271b..510be04 100644 --- a/beamtime/electrochemistry/io.py +++ b/beamtime/electrochemistry/io.py @@ -381,7 +381,7 @@ def unit_conversion(df, new_units, old_units, kind): columns.append('cycle_time') - columns.append('run_time') + columns.append('time') df.drop(['Record number', 'Relative Time(h:min:s.ms)', 'Real Time(h:min:s.ms)'], axis=1, inplace=True) diff --git a/beamtime/electrochemistry/plot.py b/beamtime/electrochemistry/plot.py index eabd105..13bb1e1 100644 --- a/beamtime/electrochemistry/plot.py +++ b/beamtime/electrochemistry/plot.py @@ -16,8 +16,8 @@ def plot_gc(path, kind, options=None): # Update options - required_options = ['x_vals', 'y_vals', 'which_cycles', 'chg', 'dchg', 'colours', 'gradient'] - default_options = {'x_vals': 'capacity', 'y_vals': 'voltage', 'which_cycles': 'all', 'chg': True, 'dchg': True, 'colours': None, 'gradient': False} + required_options = ['x_vals', 'y_vals', 'which_cycles', 'chg', 'dchg', 'colours', 'differentiate_charge_discharge', 'gradient'] + default_options = {'x_vals': 'capacity', 'y_vals': 'voltage', 'which_cycles': 'all', 'chg': True, 'dchg': True, 'colours': None, 'differentiate_charge_discharge': True, 'gradient': False} options = update_options(options=options, required_options=required_options, default_options=default_options) @@ -26,10 +26,6 @@ def plot_gc(path, kind, options=None): colours = generate_colours(cycles=cycles, options=options) - print(len(options['which_cycles'])) - print(len(colours)) - - for i, cycle in enumerate(cycles): if i in options['which_cycles']: @@ -144,10 +140,10 @@ def prettify_gc_plot(fig, ax, options=None): required_options = [ 'columns', 'xticks', 'yticks', - 'show_major_ticks', - 'show_minor_ticks', + 'show_major_ticks', 'show_minor_ticks', 'xlim', 'ylim', 'hide_x_axis', 'hide_y_axis', + 'positions', 'x_vals', 'y_vals', 'xlabel', 'ylabel', 'units', 'sizes', @@ -158,18 +154,13 @@ def prettify_gc_plot(fig, ax, options=None): # Define the default options default_options = { 'columns': 1, - 'xticks': None, - 'yticks': None, - 'show_major_ticks': [True, True, True, True], - 'show_minor_ticks': [True, True, True, True], - 'xlim': None, - 'ylim': None, - 'hide_x_axis': False, - 'hide_y_axis': False, - 'x_vals': 'specific_capacity', - 'y_vals': 'voltage', - 'xlabel': None, - 'ylabel': None, + 'xticks': None, 'yticks': None, + 'show_major_ticks': [True, True, True, True], 'show_minor_ticks': [True, True, True, True], + 'xlim': None,'ylim': None, + 'hide_x_axis': False, 'hide_y_axis': False, + 'positions': {'xaxis': 'bottom', 'yaxis': 'left'}, + 'x_vals': 'specific_capacity', 'y_vals': 'voltage', + 'xlabel': None, 'ylabel': None, 'units': None, 'sizes': None, 'title': None @@ -222,8 +213,6 @@ def prettify_gc_plot(fig, ax, options=None): if not options['xlabel']: - print(options['x_vals']) - print(options['units']) options['xlabel'] = prettify_labels(options['x_vals']) + ' [{}]'.format(options['units'][options['x_vals']]) else: @@ -250,13 +239,21 @@ def prettify_gc_plot(fig, ax, options=None): # DEFINE AND SET TICK DISTANCES + from . import unit_tables + + # Define default ticks and scale to desired units default_ticks = { - 'specific_capacity': [100, 50], - 'capacity': [0.1, 0.05], - 'voltage': [0.5, 0.25] + 'specific_capacity': [100 * (unit_tables.capacity()['mAh'].loc[options['units']['capacity']] / unit_tables.mass()['g'].loc[options['units']['mass']]), 50 * (unit_tables.capacity()['mAh'].loc[options['units']['capacity']] / unit_tables.mass()['g'].loc[options['units']['mass']])], + 'capacity': [0.1 * (unit_tables.capacity()['mAh'].loc[options['units']['capacity']]), 0.05 * (unit_tables.capacity()['mAh'].loc[options['units']['capacity']])], + 'voltage': [0.5 * (unit_tables.voltage()['V'].loc[options['units']['voltage']]), 0.25 * (unit_tables.voltage()['V'].loc[options['units']['voltage']])], + 'time': [10 * (unit_tables.time()['h'].loc[options['units']['time']]), 5 * (unit_tables.time()['h'].loc[options['units']['time']])] } + if options['positions']['yaxis'] == 'right': + ax.yaxis.set_label_position("right") + ax.yaxis.tick_right() + # Set default tick distances for x-axis if not specified if not options['xticks']: @@ -290,11 +287,23 @@ def prettify_gc_plot(fig, ax, options=None): ax.yaxis.set_minor_locator(MultipleLocator(minor_ytick)) + + # SET FONTSIZE OF TICK LABELS plt.xticks(fontsize=options['sizes']['tick_labels']) plt.yticks(fontsize=options['sizes']['tick_labels']) + ################################################################## + ########################## AXES LIMITS ########################### + ################################################################## + + if options['xlim']: + plt.xlim(options['xlim']) + + if options['ylim']: + plt.ylim(options['ylim']) + ################################################################## ############################# TITLE ############################## ################################################################## @@ -319,6 +328,7 @@ def prettify_labels(label): 'voltage': 'Voltage', 'current': 'Current', 'energy': 'Energy', + 'time': 'Time' } return labels_dict[label] @@ -341,6 +351,9 @@ def generate_colours(cycles, options): charge_colour = (40/255, 70/255, 75/255) # Dark Slate Gray #28464B, coolors.co discharge_colour = (239/255, 160/255, 11/255) # Marigold #EFA00B, coolors.co + if not options['differentiate_charge_discharge']: + discharge_colour = charge_colour + # If gradient is enabled, find start and end points for each colour From b66b7d8ea0bbd68ff2788cad50cccf59f2ec8dea Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Thu, 14 Oct 2021 15:10:55 +0200 Subject: [PATCH 10/10] Add requirements --- beamtime/requirements.txt | 107 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 beamtime/requirements.txt diff --git a/beamtime/requirements.txt b/beamtime/requirements.txt new file mode 100644 index 0000000..3f449e8 --- /dev/null +++ b/beamtime/requirements.txt @@ -0,0 +1,107 @@ +# This file may be used to create an environment using: +# $ conda create --name --file +# platform: win-64 +backcall=0.2.0=py_0 +beamtime=0.1=pypi_0 +blas=1.0=mkl +bottleneck=1.3.2=py39h7cc1a96_1 +ca-certificates=2021.10.8=h5b45459_0 +cached-property=1.5.2=hd8ed1ab_1 +cached_property=1.5.2=pyha770c72_1 +certifi=2021.10.8=py39hcbf5309_0 +colorama=0.4.4=py_0 +cycler=0.10.0=py_2 +debugpy=1.4.1=py39hd77b12b_0 +decorator=4.4.2=py_0 +fabio=0.12.0=py39h5d4886f_0 +freetype=2.10.4=h546665d_1 +glymur=0.9.4=pyhd8ed1ab_0 +h5py=3.2.1=nompi_py39hf27771d_100 +hdf5=1.10.6=nompi_h5268f04_1114 +hdf5plugin=3.1.1=py39h71586dd_0 +icc_rt=2019.0.0=h0cc432a_1 +icu=68.1=h0e60522_0 +intel-openmp=2021.3.0=haa95532_3372 +ipykernel=6.4.1=py39haa95532_1 +ipython=7.27.0=py39hd4e2768_0 +ipython_genutils=0.2.0=pyhd3eb1b0_1 +jbig=2.1=h8d14728_2003 +jedi=0.18.0=py39haa95532_1 +jpeg=9d=h8ffe710_0 +jupyter_client=6.1.7=py_0 +jupyter_core=4.8.1=py39haa95532_0 +kiwisolver=1.3.2=py39h2e07f2f_0 +krb5=1.19.2=hbae68bd_2 +lcms2=2.12=h2a16943_0 +lerc=3.0=h0e60522_0 +libclang=11.1.0=default_h5c34c98_1 +libcurl=7.79.1=h789b8ee_1 +libdeflate=1.8=h8ffe710_0 +libiconv=1.16=he774522_0 +libpng=1.6.37=h1d00b33_2 +libssh2=1.10.0=h680486a_2 +libtiff=4.3.0=hd413186_2 +libxml2=2.9.12=hf5bbc77_0 +libxslt=1.1.33=h65864e5_2 +libzlib=1.2.11=h8ffe710_1013 +lxml=4.6.3=py39h4fd7cdf_0 +lz4-c=1.9.3=h8ffe710_1 +mako=1.1.5=pyhd8ed1ab_0 +markupsafe=2.0.1=py39hb82d6ee_0 +matplotlib=3.4.3=py39hcbf5309_1 +matplotlib-base=3.4.3=py39h581301d_1 +matplotlib-inline=0.1.2=pyhd3eb1b0_2 +mkl=2021.3.0=haa95532_524 +mkl-service=2.4.0=py39h2bbff1b_0 +mkl_fft=1.3.0=py39h277e83a_2 +mkl_random=1.2.2=py39hf11a4ad_0 +numexpr=2.7.3=py39hb80d3ca_1 +numpy=1.21.2=py39hfca59bb_0 +numpy-base=1.21.2=py39h0829f74_0 +olefile=0.46=pyh9f0ad1d_1 +openjpeg=2.4.0=hb211442_1 +openssl=1.1.1l=h8ffe710_0 +pandas=1.3.3=py39h6214cd6_0 +parso=0.8.0=py_0 +pickleshare=0.7.5=pyhd3eb1b0_1003 +pillow=8.3.2=py39h916092e_0 +pip=21.2.4=py39haa95532_0 +prompt-toolkit=3.0.8=py_0 +pyfai=0.20.0=hd8ed1ab_0 +pyfai-base=0.20.0=py39h2e25243_0 +pygments=2.7.1=py_0 +pyparsing=2.4.7=pyh9f0ad1d_0 +pyqt=5.12.3=py39hcbf5309_7 +pyqt-impl=5.12.3=py39h415ef7b_7 +pyqt5-sip=4.19.18=py39h415ef7b_7 +pyqtchart=5.12=py39h415ef7b_7 +pyqtwebengine=5.12.1=py39h415ef7b_7 +pyreadline=2.1=py39hcbf5309_1004 +python=3.9.7=h6244533_1 +python-dateutil=2.8.2=pyhd3eb1b0_0 +python_abi=3.9=2_cp39 +pytz=2021.3=pyhd3eb1b0_0 +pywin32=228=py39hbaba5e8_1 +pyzmq=22.2.1=py39hd77b12b_1 +qt=5.12.9=h5909a2a_4 +qtconsole=5.1.1=pyhd8ed1ab_0 +qtpy=1.11.2=pyhd8ed1ab_0 +scipy=1.7.1=py39hbe87c03_2 +setuptools=58.0.4=py39haa95532_0 +silx=0.15.2=hd8ed1ab_0 +silx-base=0.15.2=py39h2e25243_0 +six=1.16.0=pyhd3eb1b0_0 +sqlite=3.36.0=h2bbff1b_0 +tk=8.6.11=h8ffe710_1 +tornado=6.1=py39h2bbff1b_0 +traitlets=5.0.5=py_0 +tzdata=2021a=h5d7bf9c_0 +vc=14.2=h21ff451_1 +vs2015_runtime=14.27.29016=h5e58377_2 +wcwidth=0.2.5=py_0 +wheel=0.37.0=pyhd3eb1b0_1 +wincertstore=0.2=py39haa95532_2 +xlsx2csv=0.7.8=pypi_0 +xz=5.2.5=h62dcd97_1 +zlib=1.2.11=h8ffe710_1013 +zstd=1.5.0=h6255e5f_0