From ed5504449fadb406479233638eee7c84ae57cf1d Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Tue, 5 Oct 2021 14:31:32 +0200 Subject: [PATCH] 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 - - - -