1
+ {
2
+ "nbformat" : 4 ,
3
+ "nbformat_minor" : 0 ,
4
+ "metadata" : {
5
+ "colab" : {
6
+ "name" : " Gamry-Parser CyclicVoltammetry Peak Detection Example" ,
7
+ "provenance" : [],
8
+ "collapsed_sections" : []
9
+ },
10
+ "kernelspec" : {
11
+ "name" : " python3" ,
12
+ "display_name" : " Python 3"
13
+ }
14
+ },
15
+ "cells" : [
16
+ {
17
+ "cell_type" : " code" ,
18
+ "metadata" : {
19
+ "id" : " YyeVlSYkhahF" ,
20
+ "cellView" : " form"
21
+ },
22
+ "source" : [
23
+ " #@title Imports, initial setup (Ctrl+F9 to run all)\n " ,
24
+ " import os\n " ,
25
+ " import re\n " ,
26
+ " import pandas as pd\n " ,
27
+ " import matplotlib.pyplot as plt\n " ,
28
+ " from scipy.signal import find_peaks\n " ,
29
+ " import copy\n " ,
30
+ " \n " ,
31
+ " try:\n " ,
32
+ " import gamry_parser\n " ,
33
+ " except:\n " ,
34
+ " subprocess.run(\n " ,
35
+ " [\" pip\" , \" install\" , \" gamry-parser\" ], \n " ,
36
+ " encoding=\" utf-8\" , \n " ,
37
+ " shell=False)\n " ,
38
+ " finally:\n " ,
39
+ " import gamry_parser\n " ,
40
+ " \n " ,
41
+ " gp = gamry_parser.CyclicVoltammetry()\n " ,
42
+ " \n " ,
43
+ " print('Done.')"
44
+ ],
45
+ "execution_count" : null ,
46
+ "outputs" : []
47
+ },
48
+ {
49
+ "cell_type" : " code" ,
50
+ "metadata" : {
51
+ "id" : " ZGoqracvk9q2" ,
52
+ "cellView" : " form"
53
+ },
54
+ "source" : [
55
+ " \"\"\"\n " ,
56
+ " ### SCRIPT CONFIGURATION SETTINGS ###\n " ,
57
+ " \"\"\"\n " ,
58
+ " #@markdown **Experimental Setup**\n " ,
59
+ " \n " ,
60
+ " #@markdown Where should the notebook search for DTA files? Examples (using google colab):\n " ,
61
+ " #@markdown - Mounted google drive folder: `/content/drive/`\n " ,
62
+ " #@markdown - If uploading files manually, : `/content/`).\n " ,
63
+ " \n " ,
64
+ " data_path = \" /content/\" #@param {type:\" string\" }\n " ,
65
+ " \n " ,
66
+ " #@markdown Filter which files we want to analyze\n " ,
67
+ " file_pattern = \" Search-For-Text\" #@param {type:\" string\" }\n " ,
68
+ " \n " ,
69
+ " #@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 " ,
70
+ " file_label_xform = \" [51:]\" #@param {type:\" string\" }\n " ,
71
+ " \n " ,
72
+ " # create a \" results\" dataframe to contain the values we care about\n " ,
73
+ " data_df = pandas.DataFrame()\n " ,
74
+ " settings_df = pandas.DataFrame()\n " ,
75
+ " peaks_df = pandas.DataFrame()\n " ,
76
+ " \n " ,
77
+ " # identify files to process\n " ,
78
+ " files = [f for f in os.listdir(data_path) if \n " ,
79
+ " os.path.splitext(f)[1].lower() == \" .dta\" and\n " ,
80
+ " len(re.findall(file_pattern.upper(), f.upper())) > 0\n " ,
81
+ " ]\n "
82
+ ],
83
+ "execution_count" : null ,
84
+ "outputs" : []
85
+ },
86
+ {
87
+ "cell_type" : " code" ,
88
+ "metadata" : {
89
+ "cellView" : " form" ,
90
+ "id" : " 8MFNF2Qz6lef"
91
+ },
92
+ "source" : [
93
+ " #@markdown **Process Data and Detect Peaks**\n " ,
94
+ " \n " ,
95
+ " #@markdown Which CV curves (cycle number) should be sampled? (`0` would select the first CV curve from each file)\n " ,
96
+ " curves_to_sample = \" 0\" #@param {type:\" string\" }\n " ,
97
+ " curves_to_sample = [int(item.strip()) for item in curves_to_sample.split(\" ,\" )]\n " ,
98
+ " \n " ,
99
+ " #@markdown Peak Detection: specify the peak detection parameters\n " ,
100
+ " peak_width_mV = 75 #@param {type:\" integer\" }\n " ,
101
+ " peak_height_nA = 25 #@param {type:\" integer\" }\n " ,
102
+ " peak_thresh_max_mV = 800 #@param {type:\" integer\" }\n " ,
103
+ " peak_thresh_min_mV = -100 #@param {type:\" integer\" }\n " ,
104
+ " \n " ,
105
+ " # this method finds the row that has an index value closest to the desired time elapsed\n " ,
106
+ " def duration_lookup(df, elapsed):\n " ,
107
+ " return df.index.get_loc(elapsed, method='nearest')\n " ,
108
+ " \n " ,
109
+ " # iterate through each DTA file\n " ,
110
+ " for index, file in enumerate(files):\n " ,
111
+ " print(\" Checking File {}\" .format(file))\n " ,
112
+ " \n " ,
113
+ " label, ext = os.path.splitext(file)\n " ,
114
+ " my_label = \" -\" .join(eval(\" label{}\" .format(file_label_xform)).strip().split())\n " ,
115
+ " \n " ,
116
+ " # load the dta file using gamry parser\n " ,
117
+ " gp.load(filename=os.path.join(data_path, file))\n " ,
118
+ " \n " ,
119
+ " is_cv = gp.get_header().get(\" TAG\" ) == \" CV\"\n " ,
120
+ " if not is_cv:\n " ,
121
+ " # if the DTA file is a different experiment type, skip it and move to the next file.\n " ,
122
+ " print(\" File `{}` is not a CV experiment. Skipping\" .format(file))\n " ,
123
+ " del files[index] # remove invalid file from list\n " ,
124
+ " continue\n " ,
125
+ " \n " ,
126
+ " # for each CV file, let's extract the relevant information\n " ,
127
+ " cv = gamry_parser.CyclicVoltammetry(filename=os.path.join(data_path, file))\n " ,
128
+ " cv.load()\n " ,
129
+ " for curve_num in curves_to_sample:\n " ,
130
+ " print(\"\\ tProcessing Curve #{}\" .format(curve_num))\n " ,
131
+ " v1, v2 = cv.get_v_range()\n " ,
132
+ " settings = pandas.DataFrame({\n " ,
133
+ " \" label\" : my_label,\n " ,
134
+ " \" curves\" : cv.get_curve_count(),\n " ,
135
+ " \" v1_mV\" : v1*1000,\n " ,
136
+ " \" v2_mV\" : v2*1000,\n " ,
137
+ " \" rate_mV\" : cv.get_scan_rate(),\n " ,
138
+ " }, index=[0])\n " ,
139
+ " settings_df = settings_df.append(settings)\n " ,
140
+ " \n " ,
141
+ " data = copy.deepcopy(cv.get_curve_data(curve=curve_num))\n " ,
142
+ " data.Im = data.Im*1e9\n " ,
143
+ " data.Vf = data.Vf*1e3\n " ,
144
+ " data[\" label\" ] = my_label #\" {:03d}-{}\" .format(index, curve_num)\n " ,
145
+ " \n " ,
146
+ " data_df = data_df.append(data)\n " ,
147
+ " \n " ,
148
+ " # find peaks in the data\n " ,
149
+ " dV = cv.get_scan_rate() # in mV\n " ,
150
+ " peak_width = int(peak_width_mV/dV)\n " ,
151
+ " peaks_pos, props_pos = find_peaks(\n " ,
152
+ " data.Im, \n " ,
153
+ " width=peak_width, \n " ,
154
+ " distance=2*peak_width, \n " ,
155
+ " height=peak_height_nA\n " ,
156
+ " )\n " ,
157
+ " peaks_neg, props_neg = find_peaks(\n " ,
158
+ " -data.Im, \n " ,
159
+ " width=peak_width, \n " ,
160
+ " distance=2*peak_width, \n " ,
161
+ " height=peak_height_nA\n " ,
162
+ " )\n " ,
163
+ " peaks = list(peaks_pos) + list(peaks_neg)\n " ,
164
+ " # remove peaks that are out of min/max range\n " ,
165
+ " peaks = [peak \n " ,
166
+ " for peak in peaks \n " ,
167
+ " if data.Vf.iloc[peak] >= peak_thresh_min_mV and data.Vf.iloc[peak] <= peak_thresh_max_mV]\n " ,
168
+ " \n " ,
169
+ " # add detected peaks to aggregated peak dataframe\n " ,
170
+ " peaks = data.iloc[peaks].sort_values(by=\" Vf\" )\n " ,
171
+ " peaks[\" index\" ] = peaks.index\n " ,
172
+ " peaks.reset_index(level=0, inplace=True)\n " ,
173
+ " peaks_df = peaks_df.append(peaks)\n " ,
174
+ " peaks_df = peaks_df[[\" label\" , \" index\" , \" Vf\" , \" Im\" ]]\n " ,
175
+ " # print(\"\\ tdetected peaks (mV)\" , [int(peak) for peak in data.iloc[peaks].Vf.sort_values().tolist()])\n " ,
176
+ " \n " ,
177
+ " print(\"\\ nFile Metadata\" )\n " ,
178
+ " print(settings_df.to_string(index=False))\n " ,
179
+ " \n " ,
180
+ " print(\"\\ nPeaks Detected\" )\n " ,
181
+ " print(peaks_df.to_string(index=False))"
182
+ ],
183
+ "execution_count" : null ,
184
+ "outputs" : []
185
+ },
186
+ {
187
+ "cell_type" : " code" ,
188
+ "metadata" : {
189
+ "id" : " Ulne80RrpBrW" ,
190
+ "cellView" : " form"
191
+ },
192
+ "source" : [
193
+ " #@markdown **I-V plot**: Overlay the loaded CyclicVoltammetry Curves\n " ,
194
+ " \n " ,
195
+ " from plotly.subplots import make_subplots\n " ,
196
+ " import plotly.graph_objects as go\n " ,
197
+ " from plotly.colors import DEFAULT_PLOTLY_COLORS\n " ,
198
+ " \n " ,
199
+ " fig = make_subplots(rows=1, cols=1, shared_xaxes=True, vertical_spacing=0.02)\n " ,
200
+ " \n " ,
201
+ " for (index, exp_id) in enumerate(data_df.label.unique()):\n " ,
202
+ " data = data_df.loc[data_df.label == exp_id]\n " ,
203
+ " newTrace = go.Scatter(\n " ,
204
+ " x=data.Vf,\n " ,
205
+ " y=data.Im,\n " ,
206
+ " mode='lines',\n " ,
207
+ " name=exp_id,\n " ,
208
+ " legendgroup=files[index],\n " ,
209
+ " line=dict(color=DEFAULT_PLOTLY_COLORS[index]),\n " ,
210
+ " )\n " ,
211
+ " fig.add_trace(newTrace, row=1, col=1)\n " ,
212
+ " peak = peaks_df.loc[peaks_df.label == exp_id]\n " ,
213
+ " newTrace = go.Scatter(\n " ,
214
+ " x=peak.Vf, y=peak.Im, \n " ,
215
+ " mode=\" markers\" , \n " ,
216
+ " showlegend=False, \n " ,
217
+ " marker=dict(size=12,\n " ,
218
+ " color=DEFAULT_PLOTLY_COLORS[index],\n " ,
219
+ " )\n " ,
220
+ " )\n " ,
221
+ " fig.add_trace(newTrace, row=1, col=1)\n " ,
222
+ " \n " ,
223
+ " layout = {\n " ,
224
+ " 'title': {'text': 'Cyclic Voltammetry Overlay',\n " ,
225
+ " 'yanchor': 'top',\n " ,
226
+ " 'y': 0.95,\n " ,
227
+ " 'x': 0.5 },\n " ,
228
+ " 'xaxis': {\n " ,
229
+ " 'anchor': 'x',\n " ,
230
+ " 'title': 'voltage, mV'\n " ,
231
+ " },\n " ,
232
+ " 'yaxis': {\n " ,
233
+ " 'title': 'current, nA',\n " ,
234
+ " 'type': 'linear'\n " ,
235
+ " ''\n " ,
236
+ " },\n " ,
237
+ " 'width': 1200,\n " ,
238
+ " 'height': 500,\n " ,
239
+ " 'margin': dict(l=30, r=20, t=60, b=20),\n " ,
240
+ " }\n " ,
241
+ " fig.update_layout(layout)\n " ,
242
+ " \n " ,
243
+ " config={\n " ,
244
+ " 'displaylogo': False,\n " ,
245
+ " 'modeBarButtonsToRemove': ['select2d', 'lasso2d', 'hoverClosestCartesian', 'toggleSpikelines','hoverCompareCartesian']\n " ,
246
+ " }\n " ,
247
+ " fig.show(config=config)"
248
+ ],
249
+ "execution_count" : null ,
250
+ "outputs" : []
251
+ }
252
+ ]
253
+ }
0 commit comments