-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #43 from bcliang/new-demo-notebooks
Add Examples: Peak Finding, Impedance
- Loading branch information
Showing
6 changed files
with
857 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,253 @@ | ||
{ | ||
"nbformat": 4, | ||
"nbformat_minor": 0, | ||
"metadata": { | ||
"colab": { | ||
"name": "Gamry-Parser CyclicVoltammetry Peak Detection Example", | ||
"provenance": [], | ||
"collapsed_sections": [] | ||
}, | ||
"kernelspec": { | ||
"name": "python3", | ||
"display_name": "Python 3" | ||
} | ||
}, | ||
"cells": [ | ||
{ | ||
"cell_type": "code", | ||
"metadata": { | ||
"id": "YyeVlSYkhahF", | ||
"cellView": "form" | ||
}, | ||
"source": [ | ||
"#@title Imports, initial setup (Ctrl+F9 to run all)\n", | ||
"import os\n", | ||
"import re\n", | ||
"import pandas as pd\n", | ||
"import matplotlib.pyplot as plt\n", | ||
"from scipy.signal import find_peaks\n", | ||
"import copy\n", | ||
"\n", | ||
"try:\n", | ||
" import gamry_parser\n", | ||
"except:\n", | ||
" subprocess.run(\n", | ||
" [\"pip\", \"install\", \"gamry-parser\"], \n", | ||
" encoding=\"utf-8\", \n", | ||
" shell=False)\n", | ||
"finally:\n", | ||
" import gamry_parser\n", | ||
"\n", | ||
"gp = gamry_parser.CyclicVoltammetry()\n", | ||
"\n", | ||
"print('Done.')" | ||
], | ||
"execution_count": null, | ||
"outputs": [] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"metadata": { | ||
"id": "ZGoqracvk9q2", | ||
"cellView": "form" | ||
}, | ||
"source": [ | ||
"\"\"\"\n", | ||
"### SCRIPT CONFIGURATION SETTINGS ###\n", | ||
"\"\"\"\n", | ||
"#@markdown **Experimental Setup**\n", | ||
"\n", | ||
"#@markdown Where should the notebook search for DTA files? Examples (using google colab):\n", | ||
"#@markdown - Mounted google drive folder: `/content/drive/`\n", | ||
"#@markdown - If uploading files manually, : `/content/`).\n", | ||
"\n", | ||
"data_path = \"/content/\" #@param {type:\"string\"}\n", | ||
"\n", | ||
"#@markdown Filter which files we want to analyze\n", | ||
"file_pattern = \"Search-For-Text\" #@param {type:\"string\"}\n", | ||
"\n", | ||
"#@markdown Extract trace labels from file name (e.g. `[17:].lower()` => drop the first 17 characters from the filename and convert to lowercase). The trace labels are used for category labeling (and plot legends)\n", | ||
"file_label_xform = \"[51:]\" #@param {type:\"string\"}\n", | ||
"\n", | ||
"# create a \"results\" dataframe to contain the values we care about\n", | ||
"data_df = pandas.DataFrame()\n", | ||
"settings_df = pandas.DataFrame()\n", | ||
"peaks_df = pandas.DataFrame()\n", | ||
"\n", | ||
"# identify files to process\n", | ||
"files = [f for f in os.listdir(data_path) if \n", | ||
" os.path.splitext(f)[1].lower() == \".dta\" and\n", | ||
" len(re.findall(file_pattern.upper(), f.upper())) > 0\n", | ||
" ]\n" | ||
], | ||
"execution_count": null, | ||
"outputs": [] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"metadata": { | ||
"cellView": "form", | ||
"id": "8MFNF2Qz6lef" | ||
}, | ||
"source": [ | ||
"#@markdown **Process Data and Detect Peaks**\n", | ||
"\n", | ||
"#@markdown Which CV curves (cycle number) should be sampled? (`0` would select the first CV curve from each file)\n", | ||
"curves_to_sample = \"0\" #@param {type:\"string\"}\n", | ||
"curves_to_sample = [int(item.strip()) for item in curves_to_sample.split(\",\")]\n", | ||
"\n", | ||
"#@markdown Peak Detection: specify the peak detection parameters\n", | ||
"peak_width_mV = 75 #@param {type:\"integer\"}\n", | ||
"peak_height_nA = 25 #@param {type:\"integer\"}\n", | ||
"peak_thresh_max_mV = 800 #@param {type:\"integer\"}\n", | ||
"peak_thresh_min_mV = -100 #@param {type:\"integer\"}\n", | ||
"\n", | ||
"# this method finds the row that has an index value closest to the desired time elapsed\n", | ||
"def duration_lookup(df, elapsed):\n", | ||
" return df.index.get_loc(elapsed, method='nearest')\n", | ||
"\n", | ||
"# iterate through each DTA file\n", | ||
"for index, file in enumerate(files):\n", | ||
" print(\"Checking File {}\".format(file))\n", | ||
"\n", | ||
" label, ext = os.path.splitext(file)\n", | ||
" my_label = \"-\".join(eval(\"label{}\".format(file_label_xform)).strip().split())\n", | ||
"\n", | ||
" # load the dta file using gamry parser\n", | ||
" gp.load(filename=os.path.join(data_path, file))\n", | ||
"\n", | ||
" is_cv = gp.get_header().get(\"TAG\") == \"CV\"\n", | ||
" if not is_cv:\n", | ||
" # if the DTA file is a different experiment type, skip it and move to the next file.\n", | ||
" print(\"File `{}` is not a CV experiment. Skipping\".format(file))\n", | ||
" del files[index] # remove invalid file from list\n", | ||
" continue\n", | ||
" \n", | ||
" # for each CV file, let's extract the relevant information\n", | ||
" cv = gamry_parser.CyclicVoltammetry(filename=os.path.join(data_path, file))\n", | ||
" cv.load()\n", | ||
" for curve_num in curves_to_sample:\n", | ||
" print(\"\\tProcessing Curve #{}\".format(curve_num))\n", | ||
" v1, v2 = cv.get_v_range()\n", | ||
" settings = pandas.DataFrame({\n", | ||
" \"label\": my_label,\n", | ||
" \"curves\": cv.get_curve_count(),\n", | ||
" \"v1_mV\": v1*1000,\n", | ||
" \"v2_mV\": v2*1000,\n", | ||
" \"rate_mV\": cv.get_scan_rate(),\n", | ||
" }, index=[0])\n", | ||
" settings_df = settings_df.append(settings)\n", | ||
"\n", | ||
" data = copy.deepcopy(cv.get_curve_data(curve=curve_num))\n", | ||
" data.Im = data.Im*1e9\n", | ||
" data.Vf = data.Vf*1e3\n", | ||
" data[\"label\"] = my_label #\"{:03d}-{}\".format(index, curve_num)\n", | ||
"\n", | ||
" data_df = data_df.append(data)\n", | ||
"\n", | ||
" # find peaks in the data\n", | ||
" dV = cv.get_scan_rate() # in mV\n", | ||
" peak_width = int(peak_width_mV/dV)\n", | ||
" peaks_pos, props_pos = find_peaks(\n", | ||
" data.Im, \n", | ||
" width=peak_width, \n", | ||
" distance=2*peak_width, \n", | ||
" height=peak_height_nA\n", | ||
" )\n", | ||
" peaks_neg, props_neg = find_peaks(\n", | ||
" -data.Im, \n", | ||
" width=peak_width, \n", | ||
" distance=2*peak_width, \n", | ||
" height=peak_height_nA\n", | ||
" )\n", | ||
" peaks = list(peaks_pos) + list(peaks_neg)\n", | ||
" # remove peaks that are out of min/max range\n", | ||
" peaks = [peak \n", | ||
" for peak in peaks \n", | ||
" if data.Vf.iloc[peak] >= peak_thresh_min_mV and data.Vf.iloc[peak] <= peak_thresh_max_mV]\n", | ||
"\n", | ||
" # add detected peaks to aggregated peak dataframe\n", | ||
" peaks = data.iloc[peaks].sort_values(by=\"Vf\")\n", | ||
" peaks[\"index\"] = peaks.index\n", | ||
" peaks.reset_index(level=0, inplace=True)\n", | ||
" peaks_df = peaks_df.append(peaks)\n", | ||
" peaks_df = peaks_df[[\"label\", \"index\", \"Vf\", \"Im\"]]\n", | ||
" # print(\"\\tdetected peaks (mV)\", [int(peak) for peak in data.iloc[peaks].Vf.sort_values().tolist()])\n", | ||
"\n", | ||
"print(\"\\nFile Metadata\")\n", | ||
"print(settings_df.to_string(index=False))\n", | ||
"\n", | ||
"print(\"\\nPeaks Detected\")\n", | ||
"print(peaks_df.to_string(index=False))" | ||
], | ||
"execution_count": null, | ||
"outputs": [] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"metadata": { | ||
"id": "Ulne80RrpBrW", | ||
"cellView": "form" | ||
}, | ||
"source": [ | ||
"#@markdown **I-V plot**: Overlay the loaded CyclicVoltammetry Curves\n", | ||
"\n", | ||
"from plotly.subplots import make_subplots\n", | ||
"import plotly.graph_objects as go\n", | ||
"from plotly.colors import DEFAULT_PLOTLY_COLORS\n", | ||
"\n", | ||
"fig = make_subplots(rows=1, cols=1, shared_xaxes=True, vertical_spacing=0.02)\n", | ||
"\n", | ||
"for (index, exp_id) in enumerate(data_df.label.unique()):\n", | ||
" data = data_df.loc[data_df.label == exp_id]\n", | ||
" newTrace = go.Scatter(\n", | ||
" x=data.Vf,\n", | ||
" y=data.Im,\n", | ||
" mode='lines',\n", | ||
" name=exp_id,\n", | ||
" legendgroup=files[index],\n", | ||
" line=dict(color=DEFAULT_PLOTLY_COLORS[index]),\n", | ||
" )\n", | ||
" fig.add_trace(newTrace, row=1, col=1)\n", | ||
" peak = peaks_df.loc[peaks_df.label == exp_id]\n", | ||
" newTrace = go.Scatter(\n", | ||
" x=peak.Vf, y=peak.Im, \n", | ||
" mode=\"markers\", \n", | ||
" showlegend=False, \n", | ||
" marker=dict(size=12,\n", | ||
" color=DEFAULT_PLOTLY_COLORS[index],\n", | ||
" )\n", | ||
" )\n", | ||
" fig.add_trace(newTrace, row=1, col=1)\n", | ||
"\n", | ||
"layout = {\n", | ||
" 'title': {'text': 'Cyclic Voltammetry Overlay',\n", | ||
" 'yanchor': 'top',\n", | ||
" 'y': 0.95,\n", | ||
" 'x': 0.5 },\n", | ||
" 'xaxis': {\n", | ||
" 'anchor': 'x',\n", | ||
" 'title': 'voltage, mV'\n", | ||
" },\n", | ||
" 'yaxis': {\n", | ||
" 'title': 'current, nA',\n", | ||
" 'type': 'linear'\n", | ||
" ''\n", | ||
" },\n", | ||
" 'width': 1200,\n", | ||
" 'height': 500,\n", | ||
" 'margin': dict(l=30, r=20, t=60, b=20),\n", | ||
"}\n", | ||
"fig.update_layout(layout)\n", | ||
"\n", | ||
"config={\n", | ||
" 'displaylogo': False,\n", | ||
" 'modeBarButtonsToRemove': ['select2d', 'lasso2d', 'hoverClosestCartesian', 'toggleSpikelines','hoverCompareCartesian']\n", | ||
"}\n", | ||
"fig.show(config=config)" | ||
], | ||
"execution_count": null, | ||
"outputs": [] | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.