Merge branch 'master' of github.com:rasmusthog/nafuma

This commit is contained in:
halvorhv 2022-10-19 17:06:20 +02:00
commit 0c8730d5c2
6 changed files with 254 additions and 48 deletions

View file

@ -164,3 +164,22 @@ def swap_values(options: dict, key1, key2):
return options
def find_neighbours(value, df, colname, start=0, end=-1):
''' Finds closest match to a given value in colname of df. If there is an exact match, returns index of this value. Else, it returns the nearest neighbors (upper and lower)'''
df = df.iloc[start:end]
exactmatch = df[df[colname] == value]
if not exactmatch.empty:
return exactmatch.index
else:
lower_df = df[df[colname] < value][colname]
upper_df = df[df[colname] > value][colname]
lowerneighbour_ind = lower_df.idxmax()
upperneighbour_ind = upper_df.idxmin()
return [lowerneighbour_ind, upperneighbour_ind]

View file

@ -1,6 +1,7 @@
from PIL import Image
import numpy as np
import cv2
import pandas as pd
def read_image(path, weight=None, colour=None, crop=None, resize=None, brightness=None):
@ -125,3 +126,27 @@ def change_colour(image, new_colour):
return image
def read_spectrum(path):
headers = find_start(path)
spectrum = pd.read_csv(path, skiprows=headers, delim_whitespace=True)
return spectrum
def find_start(path):
with open(path, 'r') as f:
line = f.readline()
i = 0
while not line.startswith('Energy'):
line = f.readline()
i += 1
return i

View file

@ -71,3 +71,65 @@ def show_image(data, options={}):
else:
return data['image'], None, None
def plot_spectrum(data: dict, options={}):
default_options = {
'deconvolutions': None,
'lines': None,
'colours': None,
'xlabel': 'Energy', 'xunit': 'keV', 'xlim': None,
'ylabel': 'Counts', 'yunit': 'arb. u.', 'ylim': None, 'hide_y_ticklabels': True, 'hide_y_ticks': True,
}
options = aux.update_options(options=options, default_options=default_options)
fig, ax = btp.prepare_plot(options=options)
spectrum = io.read_spectrum(data['path'])
if options['deconvolutions']:
deconvolutions = []
if not isinstance(options['deconvolutions'], list):
options['deconvolutions'] = [options['deconvolutions']]
if options['colours'] and (len(options['colours']) != len(options['deconvolutions'])):
options['colours'] = None
for deconv in options['deconvolutions']:
df = io.read_spectrum(deconv)
deconvolutions.append(df)
spectrum.plot(x='Energy', y='Counts', ax=ax, color='black')
if options['deconvolutions']:
if options['colours']:
for deconv, colour in zip(deconvolutions, options['colours']):
ax.fill_between(x=deconv['Energy'], y1=deconv['Counts'], y2=0, color=colour, alpha=0.4)
else:
for deconv in deconvolutions:
ax.fill_between(x=deconv['Energy'], y1=deconv['Counts'], y2=0, alpha=0.4)
if not options['xlim']:
options['xlim'] = [spectrum['Energy'].min(), spectrum['Energy'].max()]
if not options['ylim']:
options['ylim'] = [0, 1.1*spectrum['Counts'].max()]
if options['lines']:
for i, (line, energy) in enumerate(options['lines'].items()):
ax.axvline(x=energy, ls='--', lw=0.5, c='black')
ax.text(s=line, x=energy, y=(0.9-0.1*i)*options['ylim'][1], fontsize=8)
fig, ax = btp.adjust_plot(fig=fig, ax=ax, options=options)
return spectrum, fig, ax

View file

@ -41,11 +41,10 @@ def read_neware(path, options={}):
# Convert from .xlsx to .csv to make readtime faster
if path.endswith('xlsx'):
csv_details = ''.join(path[:-5]) + '_details.csv'
csv_summary = ''.join(path[:-5]) + '_summary.csv'
csv_summary = os.path.abspath(''.join(path[:-5]) + '_summary.csv')
if not os.path.isfile(csv_summary):
Xlsx2csv(path, outputencoding="utf-8").convert(csv_summary, sheetid=3)
Xlsx2csv(path, outputencoding="utf-8").convert(os.path.abspath(csv_summary), sheetid=3)
if not os.path.isfile(csv_details):
Xlsx2csv(path, outputencoding="utf-8").convert(csv_details, sheetid=4)
@ -357,6 +356,12 @@ def process_neware_data(df, options={}):
max_capacity = dchg_df['ions'].max()
dchg_df['ions'] = np.abs(dchg_df['ions'] - max_capacity)
if not chg_df.empty:
chg_df.reset_index(inplace=True)
if not dchg_df.empty:
dchg_df.reset_index(inplace=True)
cycles.append((chg_df, dchg_df))
@ -426,15 +431,17 @@ def process_biologic_data(df, options=None):
df = df[headers].copy()
# Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new.
set_units(options)
options['old_units'] = get_old_units(df=df, options=options)
df = add_columns(df=df, options=options)
df = unit_conversion(df=df, options=options)
df = unit_conversion(df=df, options=options)
# Creates masks for charge and discharge curves
if options['mode'] == 'GC':
chg_mask = (df['status'] == 1) & (df['status_change'] != 1)
@ -447,8 +454,13 @@ def process_biologic_data(df, options=None):
# Initiate cycles list
cycles = []
if df['cycle'].max() == 0:
no_cycles = 1
else:
no_cycles = int(df['cycle'].max())
# Loop through all the cycling steps, change the current and capacities in the
for i in range(int(df["cycle"].max())):
for i in range(no_cycles):
sub_df = df.loc[df['cycle'] == i].copy()

View file

@ -23,7 +23,6 @@ def plot_gc(data, options=None):
# Update options
required_options = ['force_reload', 'x_vals', 'y_vals', 'which_cycles', 'limit', 'exclude_cycles', 'show_plot', 'summary', 'charge', 'discharge', 'colours', 'differentiate_charge_discharge', 'gradient', 'interactive', 'interactive_session_active', 'rc_params', 'format_params', 'save_gif', 'save_path', 'fps']
default_options = {
'force_reload': False,
'x_vals': 'capacity', 'y_vals': 'voltage',
@ -34,6 +33,7 @@ def plot_gc(data, options=None):
'summary': False,
'charge': True, 'discharge': True,
'colours': None,
'markers': None,
'differentiate_charge_discharge': True,
'gradient': False,
'interactive': False,
@ -42,17 +42,19 @@ def plot_gc(data, options=None):
'format_params': {},
'save_gif': False,
'save_path': 'animation.gif',
'fps': 1
'fps': 1,
'fig': None, 'ax': None,
'edgecolor': plt.rcParams['lines.markeredgecolor'],
'plot_every': 1,
}
options = aux.update_options(options=options, required_options=required_options, default_options=default_options)
options = aux.update_options(options=options, default_options=default_options)
# Read data if not already loaded
if not 'cycles' in data.keys() or options['force_reload']:
data['cycles'] = ec.io.read_data(data=data, options=options)
# Update list of cycles to correct indices
update_cycles_list(data=data, options=options)
@ -63,7 +65,9 @@ def plot_gc(data, options=None):
colours = generate_colours(cycles=data['cycles'], options=options)
colours = generate_colours(options=options)
markers = generate_markers(options=options)
if not options['summary']:
if options['show_plot']:
@ -74,13 +78,13 @@ def plot_gc(data, options=None):
else:
fig, ax = options['fig'], options['ax']
for i, cycle in enumerate(data['cycles']):
if i in options['which_cycles']:
for i, cycle in enumerate(options['which_cycles']):
if options['charge']:
cycle[0].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][0])
data['cycles'][cycle][0].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][0])
if options['discharge']:
cycle[1].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][1])
data['cycles'][cycle][1].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][1])
if options['interactive_session_active']:
@ -134,7 +138,10 @@ def plot_gc(data, options=None):
elif options['summary'] and options['show_plot']:
# Prepare plot
fig, ax = btp.prepare_plot(options=options)
if not options['fig'] and not options['ax']:
fig, ax = btp.prepare_plot(options=options)
else:
fig, ax = options['fig'], options['ax']
mask = []
for i in range(data['cycles'].shape[0]):
@ -152,18 +159,18 @@ def plot_gc(data, options=None):
# FIXME To begin, the default is that y-values correspond to x-values. This should probably be implemented in more logical and consistent manner in the future.
if options['x_vals'] in ['coulombic_efficiency', 'energy_efficiency']:
data['cycles'].loc[mask].plot(x='cycle', y=options['x_vals'], ax=ax, color=colours[0][0], kind='scatter', marker="$\u25EF$", s=plt.rcParams['lines.markersize'])
data['cycles'].loc[mask].plot(x='cycle', y=options['x_vals'], ax=ax, color=colours[0][1], kind='scatter', s=plt.rcParams['lines.markersize']*10, marker=markers[0], edgecolor=plt.rcParams['lines.markeredgecolor'])
if options['limit']:
ax.axhline(y=options['limit'], ls='--', c='black')
else:
if options['charge']:
yval = 'charge_' + options['x_vals']
data['cycles'].loc[mask].plot(x='cycle', y=yval, ax=ax, color=colours[0][0], kind='scatter', marker="$\u25EF$", s=plt.rcParams['lines.markersize'])
data['cycles'].loc[mask].plot(x='cycle', y=yval, ax=ax, color=colours[0][0], kind='scatter', s=plt.rcParams['lines.markersize']*10, marker=markers[0], edgecolor=plt.rcParams['lines.markeredgecolor'])
if options['discharge']:
yval = 'discharge_' + options['x_vals']
data['cycles'].loc[mask].plot(x='cycle', y=yval, ax=ax, color=colours[0][1], kind='scatter', marker="$\u25EF$", s=plt.rcParams['lines.markersize'])
data['cycles'].loc[mask].plot(x='cycle', y=yval, ax=ax, color=colours[0][1], kind='scatter', s=plt.rcParams['lines.markersize']*10, marker=markers[1], edgecolor=plt.rcParams['lines.markeredgecolor'])
if options['limit']:
@ -202,7 +209,6 @@ def plot_gc_interactive(data, options):
def plot_cv(data, options):
# Update options
required_options = ['force_reload', 'x_vals', 'y_vals', 'which_cycles', 'limit', 'exclude_cycles', 'show_plot', 'charge', 'discharge', 'colours', 'differentiate_charge_discharge', 'gradient', 'interactive', 'interactive_session_active', 'rc_params', 'format_params', 'save_gif', 'save_path', 'fps']
default_options = {
'force_reload': False,
'x_vals': 'voltage', 'y_vals': 'current',
@ -220,10 +226,13 @@ def plot_cv(data, options):
'format_params': {},
'save_gif': False,
'save_path': 'animation.gif',
'fps': 1
'fps': 1,
'plot_every': 1,
'fig': None,
'ax': None
}
options = aux.update_options(options=options, required_options=required_options, default_options=default_options)
options = aux.update_options(options=options, default_options=default_options)
# Read data if not already loaded
@ -234,18 +243,29 @@ def plot_cv(data, options):
# Update list of cycles to correct indices
update_cycles_list(data=data, options=options)
colours = generate_colours(cycles=data['cycles'], options=options)
colours = generate_colours(options=options)
if options['show_plot']:
# Prepare plot
fig, ax = btp.prepare_plot(options=options)
for i, cycle in enumerate(data['cycles']):
if i in options['which_cycles']:
if options['charge']:
cycle[0].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][0])
if not options['fig'] and not options['ax']:
fig, ax = btp.prepare_plot(options=options)
else:
fig, ax = options['fig'], options['ax']
if options['discharge']:
cycle[1].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][1])
for i, cycle in enumerate(options['which_cycles']):
if options['charge']:
data['cycles'][cycle][0].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][0])
if options['discharge']:
data['cycles'][cycle][1].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][1])
# for i, cycle in enumerate(data['cycles']):
# if i in options['which_cycles']:
# if options['charge']:
# cycle[0].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][0])
# if options['discharge']:
# cycle[1].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][1])
update_labels(options)
@ -261,6 +281,7 @@ def plot_cv(data, options):
options['format_params']['dpi'] = 200
for i, cycle in enumerate(data['cycles']):
if i in options['which_cycles']:
@ -361,12 +382,14 @@ def update_cycles_list(data, options: dict) -> None:
options['which_cycles'] = [i-1 for i in range(which_cycles[0], which_cycles[1]+1)]
for i, cycle in enumerate(options['which_cycles']):
if cycle in options['exclude_cycles']:
del options['which_cycles'][i]
options['which_cycles'] = options['which_cycles'][::options['plot_every']]
def prettify_gc_plot(fig, ax, options=None):
@ -581,16 +604,27 @@ def prettify_labels(label):
def generate_colours(cycles, options):
def generate_colours(options):
default_options = {
'gradient_colours': None,
}
aux.update_options(options=options, default_options=default_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]
if isinstance(charge_colour, tuple):
charge_colour = [charge_colour]
if isinstance(discharge_colour, tuple):
discharge_colour = [discharge_colour]
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
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
@ -600,28 +634,56 @@ def generate_colours(cycles, options):
# 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])
if not options['gradient_colours']:
options['gradient_colours'] = [[None, None], [None, None]]
add_charge = min([(1-x)*0.75 for x in charge_colour])
add_discharge = min([(1-x)*0.75 for x in discharge_colour])
options['gradient_colours'][0][0] = charge_colour
options['gradient_colours'][0][1] = [x+add_charge for x in charge_colour]
options['gradient_colours'][1][0] = discharge_colour
options['gradient_colours'][1][1] = [x+add_discharge 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 len(charge_colour) != len(options['which_cycles']):
if options['gradient']:
weight_start = (len(cycles) - cycle_number)/len(cycles)
weight_end = cycle_number/len(cycles)
options['number_of_colours'] = len(options['which_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)]
charge_colours = btp.mix_colours(colour1=options['gradient_colours'][0][0], colour2=options['gradient_colours'][0][1], options=options)
discharge_colours = btp.mix_colours(colour1=options['gradient_colours'][1][0], colour2=options['gradient_colours'][1][1], options=options)
for chg, dchg in zip(charge_colours, discharge_colours):
colours.append([chg, dchg])
else:
for i in options['which_cycles']:
colours.append([charge_colour, discharge_colour])
else:
for chg, dchg in zip(charge_colour, discharge_colour):
colours.append([chg, dchg])
colours.append([charge_colour, discharge_colour])
return colours
def generate_markers(options):
if not options['markers']:
markers = ['o', 'v']
else:
markers = [options['markers'][0], options['markers'][1]]
return markers

View file

@ -486,3 +486,29 @@ def make_animation(paths, options={}):
frames[0].save(os.path.join(options['save_folder'], options['save_filename']), format='GIF', append_images=frames[1:], save_all=True, duration=(1/options['fps'])*1000, loop=0)
def mix_colours(colour1, colour2, options):
default_options = {
'number_of_colours': 10,
'weights': None
}
options = aux.update_options(options=options, default_options=default_options)
if not options['weights']:
options['weights'] = [x/options['number_of_colours'] for x in range(options['number_of_colours'])]
colours = []
for weight in options['weights']:
colour = []
for c1, c2 in zip(colour1, colour2):
colour.append(np.round(((1-weight)*c1 + weight*c2), 5))
colours.append(colour)
return colours