Skip to content

Commit

Permalink
Merge pull request #43 from bcliang/new-demo-notebooks
Browse files Browse the repository at this point in the history
Add Examples: Peak Finding, Impedance
  • Loading branch information
bcliang authored May 7, 2021
2 parents 33c7e2b + fee4f54 commit 400b5e9
Show file tree
Hide file tree
Showing 6 changed files with 857 additions and 9 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,24 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed
-

### Changed
-

### Added
-

## [0.4.5] - 2021-05-07

### Fixed
-

### Changed
- [#40](https://github.com/bcliang/gamry-parser/pull/40) Change: GamryParser to_timestamp param #40
- [#41](https://github.com/bcliang/gamry-parser/pull/40) Use tox as test runner
- [#41](https://github.com/bcliang/gamry-parser/pull/41) Use tox as test runner

### Added
- [#42](https://github.com/bcliang/gamry-parser/pull/42) Update read_header function to support EFM140 data Files
- [#43](https://github.com/bcliang/gamry-parser/pull/43) Add Examples: Peak Finding, Impedance

## [0.4.4] - 2021-02-28

Expand Down
10 changes: 7 additions & 3 deletions demo/notebook_cyclicvoltammetry.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@
"import matplotlib.pyplot as plt\n",
"\n",
"try:\n",
" import gamry_parser as parser\n",
" import gamry_parser\n",
"except:\n",
" !pip install -q --upgrade gamry-parser\n",
" import gamry_parser as parser\n",
" subprocess.run(\n",
" [\"pip\", \"install\", \"gamry-parser\"], \n",
" encoding=\"utf-8\", \n",
" shell=False)\n",
"finally:\n",
" import gamry_parser\n",
"\n",
"p = parser.CyclicVoltammetry()\n",
" \n",
Expand Down
253 changes: 253 additions & 0 deletions demo/notebook_cyclicvoltammetry_peakdetect.ipynb
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": []
}
]
}
12 changes: 8 additions & 4 deletions demo/notebook_gamry_parser.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,14 @@
"import matplotlib.pyplot as plt\n",
"\n",
"try:\n",
" import gamry_parser as parser\n",
" import gamry_parser\n",
"except:\n",
" !pip install -q --upgrade gamry-parser\n",
" import gamry_parser as parser\n",
" subprocess.run(\n",
" [\"pip\", \"install\", \"gamry-parser\"], \n",
" encoding=\"utf-8\", \n",
" shell=False)\n",
"finally:\n",
" import gamry_parser\n",
"\n",
"p = parser.GamryParser()\n",
" \n",
Expand Down Expand Up @@ -163,4 +167,4 @@
"outputs": []
}
]
}
}
Loading

0 comments on commit 400b5e9

Please sign in to comment.