Merge branch 'master' of github.com:rasmusthog/nafuma
This commit is contained in:
commit
0c8730d5c2
6 changed files with 254 additions and 48 deletions
|
|
@ -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]
|
||||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue