From 7af1dc4228fe81fdb5943650bdbcd8e92f3c0904 Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Fri, 22 Apr 2022 16:31:04 +0200 Subject: [PATCH 1/7] Add initial interactive capabilities to GC-plots --- nafuma/electrochemistry/plot.py | 44 +++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/nafuma/electrochemistry/plot.py b/nafuma/electrochemistry/plot.py index a074899..a7e319e 100644 --- a/nafuma/electrochemistry/plot.py +++ b/nafuma/electrochemistry/plot.py @@ -5,40 +5,55 @@ import pandas as pd import numpy as np import math +import ipywidgets as widgets +from IPython.display import display + import nafuma.electrochemistry as ec import nafuma.plotting as btp import nafuma.auxillary as aux + + def plot_gc(data, options=None): # Update options - required_options = ['x_vals', 'y_vals', 'which_cycles', 'charge', 'discharge', 'colours', 'differentiate_charge_discharge', 'gradient', 'rc_params', 'format_params'] - + required_options = ['x_vals', 'y_vals', 'which_cycles', 'charge', 'discharge', 'colours', 'differentiate_charge_discharge', 'gradient', 'interactive', 'interactive_session_active', 'rc_params', 'format_params'] default_options = { 'x_vals': 'capacity', 'y_vals': 'voltage', 'which_cycles': 'all', 'charge': True, 'discharge': True, 'colours': None, 'differentiate_charge_discharge': True, - 'gradient': False, + 'gradient': False, + 'interactive': False, + 'interactive_session_active': False, 'rc_params': {}, 'format_params': {}} options = aux.update_options(options=options, required_options=required_options, default_options=default_options) - # Prepare plot, and read and process data + - fig, ax = btp.prepare_plot(options=options) - data['cycles'] = ec.io.read_data(data=data, options=options) + if not 'cycles' in data.keys(): + data['cycles'] = ec.io.read_data(data=data, options=options) # Update list of cycles to correct indices update_cycles_list(cycles=data['cycles'], options=options) colours = generate_colours(cycles=data['cycles'], options=options) + if options['interactive']: + options['interactive'], options['interactive_session_active'] = False, True + plot_gc_interactive(data=data, options=options) + return + + + # Prepare plot, and read and process data + + fig, ax = btp.prepare_plot(options=options) for i, cycle in enumerate(data['cycles']): if i in options['which_cycles']: @@ -50,12 +65,25 @@ def plot_gc(data, options=None): update_labels(options) - print(options['xunit']) fig, ax = btp.adjust_plot(fig=fig, ax=ax, options=options) - return data['cycles'], fig, ax + #if options['interactive_session_active']: + + return data['cycles'], fig, ax + + +def plot_gc_interactive(data, options): + + w = widgets.interactive(btp.ipywidgets_update, func=widgets.fixed(plot_gc), data=widgets.fixed(data), options=widgets.fixed(options), + charge=widgets.ToggleButton(value=True), + discharge=widgets.ToggleButton(value=True) + ) + + options['widget'] = w + + display(w) def update_labels(options): From d30c9c3b162d230cc84c0b53fd7d8290a76cd46a Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Fri, 22 Apr 2022 16:45:28 +0200 Subject: [PATCH 2/7] Add change of x-values in interactive mode --- nafuma/electrochemistry/plot.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/nafuma/electrochemistry/plot.py b/nafuma/electrochemistry/plot.py index a7e319e..9e80471 100644 --- a/nafuma/electrochemistry/plot.py +++ b/nafuma/electrochemistry/plot.py @@ -64,7 +64,10 @@ def plot_gc(data, options=None): cycle[1].plot(x=options['x_vals'], y=options['y_vals'], ax=ax, c=colours[i][1]) - update_labels(options) + if options['interactive_session_active']: + update_labels(options, force=True) + else: + update_labels(options) fig, ax = btp.adjust_plot(fig=fig, ax=ax, options=options) @@ -78,7 +81,8 @@ def plot_gc_interactive(data, options): w = widgets.interactive(btp.ipywidgets_update, func=widgets.fixed(plot_gc), data=widgets.fixed(data), options=widgets.fixed(options), charge=widgets.ToggleButton(value=True), - discharge=widgets.ToggleButton(value=True) + discharge=widgets.ToggleButton(value=True), + x_vals=widgets.Dropdown(options=['specific_capacity', 'capacity', 'ions', 'voltage', 'time', 'energy'], value='specific_capacity', description='X-values') ) options['widget'] = w @@ -86,16 +90,16 @@ def plot_gc_interactive(data, options): display(w) -def update_labels(options): +def update_labels(options, force=False): - if 'xlabel' not in options.keys(): + if 'xlabel' not in options.keys() or force: options['xlabel'] = options['x_vals'].capitalize().replace('_', ' ') - if 'ylabel' not in options.keys(): + if 'ylabel' not in options.keys() or force: options['ylabel'] = options['y_vals'].capitalize().replace('_', ' ') - if 'xunit' not in options.keys(): + if 'xunit' not in options.keys() or force: if options['x_vals'] == 'capacity': options['xunit'] = options['units']['capacity'] elif options['x_vals'] == 'specific_capacity': @@ -106,7 +110,7 @@ def update_labels(options): options['xunit'] = None - if 'yunit' not in options.keys(): + if 'yunit' not in options.keys() or force: if options['y_vals'] == 'voltage': options['yunit'] = options['units']['voltage'] From 8d8cad966df30b631fb14fe98d4cd8482b3349dd Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Tue, 10 May 2022 17:29:22 +0200 Subject: [PATCH 3/7] Allow plotting of multiple beamline scans (quickfix) --- nafuma/xrd/io.py | 2 +- nafuma/xrd/plot.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/nafuma/xrd/io.py b/nafuma/xrd/io.py index b3f3951..2511ff1 100644 --- a/nafuma/xrd/io.py +++ b/nafuma/xrd/io.py @@ -59,7 +59,7 @@ def integrate_1d(data, options={}, index=0): # Get image array from filename if not passed - if 'image' not in data.keys(): + if 'image' not in data.keys() or not data['image']: data['image'] = get_image_array(data['path'][index]) # Instanciate the azimuthal integrator from pyFAI from the calibrant (.poni-file) diff --git a/nafuma/xrd/plot.py b/nafuma/xrd/plot.py index 8dd6a27..c45582b 100644 --- a/nafuma/xrd/plot.py +++ b/nafuma/xrd/plot.py @@ -75,6 +75,9 @@ def plot_diffractogram(data, options={}): data['diffractogram'][index] = diffractogram data['wavelength'][index] = wavelength + # FIXME This is a quick fix as the image is not reloaded when passing multiple beamline datasets + data['image'] = None + # Sets the xlim if this has not bee specified if not options['xlim']: options['xlim'] = [data['diffractogram'][0][options['x_vals']].min(), data['diffractogram'][0][options['x_vals']].max()] From 8f94fa4dc6d70d3698e7f7a9ba7491a73084daec Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Tue, 10 May 2022 17:30:00 +0200 Subject: [PATCH 4/7] Remove outcommented lines in splice_cycles --- nafuma/electrochemistry/io.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/nafuma/electrochemistry/io.py b/nafuma/electrochemistry/io.py index 5abff3b..8256749 100644 --- a/nafuma/electrochemistry/io.py +++ b/nafuma/electrochemistry/io.py @@ -178,19 +178,17 @@ def process_batsmall_data(df, options=None): def splice_cycles(df, options: dict) -> pd.DataFrame: ''' Splices two cycles together - if e.g. one charge cycle are split into several cycles due to change in parameters. - Incomplete, only accomodates BatSmall so far.''' + Incomplete, only accomodates BatSmall so far, and only for charge.''' if options['kind'] == 'batsmall': # Creates masks for charge and discharge curves chg_mask = df['current'] >= 0 - dchg_mask = df['current'] < 0 # Loop through all the cycling steps, change the current and capacities in the for i in range(df["count"].max()): sub_df = df.loc[df['count'] == i+1] sub_df_chg = sub_df.loc[chg_mask] - #sub_df_dchg = sub_df.loc[dchg_mask] # get indices where the program changed chg_indices = sub_df_chg[sub_df_chg["comment"].str.contains("program")==True].index.to_list() @@ -202,34 +200,17 @@ def splice_cycles(df, options: dict) -> pd.DataFrame: if chg_indices: last_chg = chg_indices.pop() - - - #dchg_indices = sub_df_dchg[sub_df_dchg["comment"].str.contains("program")==True].index.to_list() - #if dchg_indices: - # del dchg_indices[0] - - - + if chg_indices: for i in chg_indices: add = df['specific_capacity'].iloc[i-1] df['specific_capacity'].iloc[i:last_chg] = df['specific_capacity'].iloc[i:last_chg] + add - #if dchg_indices: - # for i in dchg_indices: - # add = df['specific_capacity'].iloc[i-1] - # df['specific_capacity'].iloc[i:last_dchg] = df['specific_capacity'].iloc[i:last_dchg] + add - - - - return df - - def process_neware_data(df, options={}): """ Takes data from NEWARE in a DataFrame as read by read_neware() and converts units, adds columns and splits into cycles. From 23f037c0ef3ec65ef93834bda643dbc928068d86 Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Wed, 11 May 2022 18:35:11 +0200 Subject: [PATCH 5/7] Add function to strip headers from .xy --- nafuma/xrd/io.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/nafuma/xrd/io.py b/nafuma/xrd/io.py index 2511ff1..495f2d1 100644 --- a/nafuma/xrd/io.py +++ b/nafuma/xrd/io.py @@ -326,6 +326,39 @@ def read_xy(data, options={}, index=0): return diffractogram, wavelength + + +def strip_headers_from_xy(path: str, filename=None) -> None: + ''' Strips headers from a .xy-file''' + + + xy = [] + with open(path, 'r') as f: + lines = f.readlines() + + headerlines = 0 + for line in lines: + if line[0] == '#': + headerlines += 1 + else: + xy.append(line) + + + if not filename: + ext = path.split('.')[-1] + filename = path.split(f'.{ext}')[0] + f'_noheaders.{ext}' + + print(filename) + + with open(filename, 'w') as f: + for line in xy: + f.write(line) + + + + + + def read_data(data, options={}, index=0): beamline_extensions = ['mar3450', 'edf', 'cbf'] From 55b22d5bf19047578862b3f6d443b086d11d6ee4 Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Thu, 12 May 2022 19:17:43 +0200 Subject: [PATCH 6/7] Small improvements to integrate function --- nafuma/xrd/io.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/nafuma/xrd/io.py b/nafuma/xrd/io.py index 495f2d1..6b5a762 100644 --- a/nafuma/xrd/io.py +++ b/nafuma/xrd/io.py @@ -62,6 +62,7 @@ def integrate_1d(data, options={}, index=0): if 'image' not in data.keys() or not data['image']: data['image'] = get_image_array(data['path'][index]) + # Instanciate the azimuthal integrator from pyFAI from the calibrant (.poni-file) ai = pyFAI.load(data['calibrant']) @@ -72,6 +73,9 @@ def integrate_1d(data, options={}, index=0): if not os.path.isdir(options['extract_folder']): os.makedirs(options['extract_folder']) + if not os.path.isdir(options['save_folder']): + os.makedirs(options['save_folder']) + res = ai.integrate1d(data['image'], options['nbins'], unit=options['unit'], filename=filename) @@ -348,7 +352,6 @@ def strip_headers_from_xy(path: str, filename=None) -> None: ext = path.split('.')[-1] filename = path.split(f'.{ext}')[0] + f'_noheaders.{ext}' - print(filename) with open(filename, 'w') as f: for line in xy: From da8907083bc138a03cc7308d14187723d6b3a9bd Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Thu, 19 May 2022 21:32:10 +0200 Subject: [PATCH 7/7] Fix arcsin issue and allow plotting w/o reflection data --- nafuma/xrd/io.py | 42 ++++++++++++++++++++++++++++++++++-------- nafuma/xrd/plot.py | 25 ++++++++++++++++++------- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/nafuma/xrd/io.py b/nafuma/xrd/io.py index 6b5a762..56154ea 100644 --- a/nafuma/xrd/io.py +++ b/nafuma/xrd/io.py @@ -80,7 +80,8 @@ def integrate_1d(data, options={}, index=0): res = ai.integrate1d(data['image'], options['nbins'], unit=options['unit'], filename=filename) data['path'][index] = filename - diffractogram, wavelength = read_xy(data=data, options=options, index=index) + diffractogram, _ = read_xy(data=data, options=options, index=index) + wavelength = find_wavelength_from_poni(path=data['calibrant']) if not options['save']: os.remove(filename) @@ -282,8 +283,12 @@ def read_brml(data, options={}, index=0): #if 'wavelength' not in data.keys(): # Find wavelength - for chain in root.findall('./FixedInformation/Instrument/PrimaryTracks/TrackInfoData/MountedOptics/InfoData/Tube/WaveLengthAlpha1'): - wavelength = float(chain.attrib['Value']) + + if not data['wavelength'][index]: + for chain in root.findall('./FixedInformation/Instrument/PrimaryTracks/TrackInfoData/MountedOptics/InfoData/Tube/WaveLengthAlpha1'): + wavelength = float(chain.attrib['Value']) + else: + wavelength = data['wavelength'][index] diffractogram = pd.DataFrame(diffractogram) @@ -306,7 +311,11 @@ def read_xy(data, options={}, index=0): #if 'wavelength' not in data.keys(): # Get wavelength from scan - wavelength = find_wavelength_from_xy(path=data['path'][index]) + + if not data['wavelength'][index]: + wavelength = find_wavelength_from_xy(path=data['path'][index]) + else: + wavelength = data['wavelength'][index] with open(data['path'][index], 'r') as f: position = 0 @@ -378,7 +387,7 @@ def read_data(data, options={}, index=0): - + if options['offset'] or options['normalise']: # Make copy of the original intensities before any changes are made through normalisation or offset, to easily revert back if need to update. diffractogram['I_org'] = diffractogram['I'] @@ -387,6 +396,7 @@ def read_data(data, options={}, index=0): diffractogram = apply_offset(diffractogram, wavelength, index, options) + diffractogram = translate_wavelengths(data=diffractogram, wavelength=wavelength) return diffractogram, wavelength @@ -506,7 +516,7 @@ def translate_wavelengths(data: pd.DataFrame, wavelength: float, to_wavelength=N data['2th_cuka'] = np.NAN - data['2th_cuka'].loc[data['2th'] <= max_2th_cuka] = 2*np.arcsin(cuka/wavelength * np.sin((data['2th']/2) * np.pi/180)) * 180/np.pi + data['2th_cuka'].loc[data['2th'] <= max_2th_cuka] = 2*np.arcsin(cuka/wavelength * np.sin((data['2th'].loc[data['2th'] <= max_2th_cuka]/2) * np.pi/180)) * 180/np.pi # Translate to MoKalpha moka = 0.71073 # Å @@ -518,7 +528,7 @@ def translate_wavelengths(data: pd.DataFrame, wavelength: float, to_wavelength=N data['2th_moka'] = np.NAN - data['2th_moka'].loc[data['2th'] <= max_2th_moka] = 2*np.arcsin(moka/wavelength * np.sin((data['2th']/2) * np.pi/180)) * 180/np.pi + data['2th_moka'].loc[data['2th'] <= max_2th_moka] = 2*np.arcsin(moka/wavelength * np.sin((data['2th'].loc[data['2th'] <= max_2th_moka]/2) * np.pi/180)) * 180/np.pi # Convert to other parameters @@ -537,7 +547,7 @@ def translate_wavelengths(data: pd.DataFrame, wavelength: float, to_wavelength=N data['2th'] = np.NAN - data['2th'].loc[data['2th_cuka'] <= max_2th] = 2*np.arcsin(to_wavelength/cuka * np.sin((data['2th_cuka']/2) * np.pi/180)) * 180/np.pi + data['2th'].loc[data['2th_cuka'] <= max_2th] = 2*np.arcsin(to_wavelength/cuka * np.sin((data['2th_cuka'].loc[data['2th_cuka'] <= max_2th]/2) * np.pi/180)) * 180/np.pi @@ -564,6 +574,22 @@ def find_wavelength_from_xy(path): elif 'Wavelength' in line: wavelength = float(line.split()[2])*10**10 + else: + wavelength = None + + + + return wavelength + + +def find_wavelength_from_poni(path): + + with open(path, 'r') as f: + lines = f.readlines() + + for line in lines: + if 'Wavelength' in line: + wavelength = float(line.split()[-1])*10**10 return wavelength diff --git a/nafuma/xrd/plot.py b/nafuma/xrd/plot.py index c45582b..b562eeb 100644 --- a/nafuma/xrd/plot.py +++ b/nafuma/xrd/plot.py @@ -13,7 +13,6 @@ import nafuma.xrd as xrd import nafuma.auxillary as aux import nafuma.plotting as btp - def plot_diffractogram(data, options={}): ''' Plots a diffractogram. @@ -67,7 +66,14 @@ def plot_diffractogram(data, options={}): if not 'diffractogram' in data.keys(): # Initialise empty list for diffractograms and wavelengths data['diffractogram'] = [None for _ in range(len(data['path']))] - data['wavelength'] = [None for _ in range(len(data['path']))] + + # If wavelength is not manually passed it should be automatically gathered from the .xy-file + if 'wavelength' not in data.keys(): + data['wavelength'] = [None for _ in range(len(data['path']))] + else: + # If only a single value is passed it should be set to be the same for all diffractograms passed + if not isinstance(data['wavelength'], list): + data['wavelength'] = [data['wavelength'] for _ in range(len(data['path']))] for index in range(len(data['path'])): diffractogram, wavelength = xrd.io.read_data(data=data, options=options, index=index) @@ -117,7 +123,7 @@ def plot_diffractogram(data, options={}): options['reflections_data'] = [options['reflections_data']] # Determine number of subplots and height ratios between them - if len(options['reflections_data']) >= 1: + if options['reflections_data'] and len(options['reflections_data']) >= 1: options = determine_grid_layout(options=options) @@ -334,10 +340,10 @@ def plot_diffractogram_interactive(data, options): 'heatmap_default': {'min': xminmax['heatmap'][0], 'max': xminmax['heatmap'][1], 'value': [xminmax['heatmap'][0], xminmax['heatmap'][1]], 'step': 10} }, 'ylim': { - 'w': widgets.FloatRangeSlider(value=[yminmax['start'][2], yminmax['start'][3]], min=yminmax['start'][0], max=yminmax['start'][1], step=0.5, layout=widgets.Layout(width='95%')), + 'w': widgets.FloatRangeSlider(value=[yminmax['start'][2], yminmax['start'][3]], min=yminmax['start'][0], max=yminmax['start'][1], step=0.01, layout=widgets.Layout(width='95%')), 'state': 'heatmap' if options['heatmap'] else 'diff', - 'diff_default': {'min': yminmax['diff'][0], 'max': yminmax['diff'][1], 'value': [yminmax['diff'][2], yminmax['diff'][3]], 'step': 0.1}, - 'heatmap_default': {'min': yminmax['heatmap'][0], 'max': yminmax['heatmap'][1], 'value': [yminmax['heatmap'][0], yminmax['heatmap'][1]], 'step': 0.1} + 'diff_default': {'min': yminmax['diff'][0], 'max': yminmax['diff'][1], 'value': [yminmax['diff'][2], yminmax['diff'][3]], 'step': 0.01}, + 'heatmap_default': {'min': yminmax['heatmap'][0], 'max': yminmax['heatmap'][1], 'value': [yminmax['heatmap'][0], yminmax['heatmap'][1]], 'step': 0.01} } } @@ -359,7 +365,12 @@ def plot_diffractogram_interactive(data, options): w = widgets.interactive(btp.ipywidgets_update, func=widgets.fixed(plot_diffractogram), data=widgets.fixed(data), options=widgets.fixed(options), scatter=widgets.ToggleButton(value=False), line=widgets.ToggleButton(value=True), - xlim=options['widgets']['xlim']['w']) + heatmap=widgets.ToggleButton(value=options['heatmap']), + x_vals=widgets.Dropdown(options=['2th', 'd', '1/d', 'q', 'q2', 'q4', '2th_cuka', '2th_moka'], value='2th', description='X-values'), + xlim=options['widgets']['xlim']['w'], + ylim=options['widgets']['ylim']['w'], + offset_y=widgets.BoundedFloatText(value=options['offset_y'], min=-5, max=5, step=0.01, description='offset_y'), + offset_x=widgets.BoundedFloatText(value=options['offset_x'], min=-1, max=1, step=0.01, description='offset_x')) options['widget'] = w