Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Examples: Peak Finding, Impedance #43

Merged
merged 4 commits into from
May 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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