Skip to content

Commit

Permalink
Merge pull request #45 from bcliang/square-wave-voltammetry
Browse files Browse the repository at this point in the history
Impl: support for Square Wave Voltammetry experiments
  • Loading branch information
bcliang authored Jan 1, 2022
2 parents ed0c932 + a762b64 commit 470d864
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 22 deletions.
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ source =

omit =
gamry_parser/__init__.py
gamry_praser/version.py
gamry_parser/version.py

[report]
exclude_lines =
Expand Down
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,24 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Added
-

## [0.4.6] - 2021-05-07

### Fixed
- [ed0c932](https://github.com/bcliang/gamry-parser/commit/ed0c93208f5a5ce3b62d5c619e3fd6aa34158b35) Fix: Static typing for class methods, resolve pandas futurewarning

### Changed
-

### Added
- [#45](https://github.com/bcliang/gamry-parser/pull/45) Impl: support for Square Wave Voltammetry experiments

## [0.4.5] - 2021-05-07

### Fixed
-

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

### Added
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,16 @@ print(ca.get_curve_data())

#### Demos

A simple demonstration is provided in `usage.py`.

`python usage.py`

ipython notebook demonstration scripts are included in the `demo` folder.

- `notebook_gamry_parser.ipynb`: Simple example loading data from ChronoA experiment output. Instead of `gamry_parser.GamryParser()`, the parser could be instantiated with `gamry_parser.ChronoAmperometry()`
- `notebook_cyclicvoltammetry.ipynb`: Example loading data from a CV (cyclic voltammetry) experiment output. Uses the `gamry_parser.CyclicVoltammetry()` subclass.
- `notebook_cyclicvoltammetry_peakdetect.ipynb`: Another example that demonstrates loading CV data and detecting peaks in the data using `scipy.signal.find_peaks()`
- `notebook_potentiostatic_eis.ipynb`: Example loading data from an EIS (electrochemical impedance spectroscopy) experiment. Uses the `gamry_parser.Impedance()` subclass.

#### Additional Examples

Expand Down
1 change: 1 addition & 0 deletions gamry_parser/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
from .cv import CyclicVoltammetry
from .eispot import Impedance
from .ocp import OpenCircuitPotential
from .squarewave import SquareWaveVoltammetry
from .vfp600 import VFP600
56 changes: 37 additions & 19 deletions gamry_parser/gamryparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,23 @@
class GamryParser:
"""Load experiment data generated in Gamry EXPLAIN format."""

fname = None
to_timestamp = False
fname: str = None
to_timestamp: bool = False
loaded: bool = False

def __init__(self, filename=None, to_timestamp=None):
header_length: int = 0
header: dict = None

curve_count: int = 0
curves: list = []
curve_units: dict = dict()

ocv_exists: bool = False
ocv: pd.DataFrame = None

REQUIRED_UNITS: dict = dict(CV=dict(Vf="V vs. Ref.", Im="A"))

def __init__(self, filename: str = None, to_timestamp: bool = None):
"""GamryParser.__init__
Args:
Expand All @@ -27,17 +40,22 @@ def __init__(self, filename=None, to_timestamp=None):
self.to_timestamp = (
to_timestamp if to_timestamp is not None else self.to_timestamp
)
self._reset_props()

def _reset_props(self):
"re-initialize parser properties"

self.loaded = False

self.header = dict()
self.header_length = 0
self.loaded = False

self.curves = []
self.curve_count = 0
self.curve_units = dict()
self.ocv = None

self.ocv_exists = False
self.REQUIRED_UNITS = {
"CV": {"Vf": "V vs. Ref.", "Im": "A"},
}
self.ocv = None

def load(self, filename: str = None, to_timestamp: bool = None):
"""save experiment information to \"header\", then save curve data to \"curves\"
Expand Down Expand Up @@ -81,22 +99,22 @@ def _convert_T_to_Timestamp(self):
for curve in self.curves:
curve["T"] = start_time + pd.to_timedelta(curve["T"], "s")

def get_curve_count(self):
def get_curve_count(self) -> int:
"""return the number of loaded curves"""
assert self.loaded, "DTA file not loaded. Run GamryParser.load()"
return self.curve_count

def get_curve_indices(self):
def get_curve_indices(self) -> tuple:
"""return indices of curves (zero-based indexing)"""
assert self.loaded, "DTA file not loaded. Run GamryParser.load()"
return tuple(range(self.curve_count))

def get_curve_numbers(self):
def get_curve_numbers(self) -> tuple:
"""return Gamry curve numbers (one-based indexing, as in Gamry software)"""
assert self.loaded, "DTA file not loaded. Run GamryParser.load()"
return tuple(range(1, self.curve_count + 1))

def get_curve_data(self, curve: int = 0):
def get_curve_data(self, curve: int = 0) -> pd.DataFrame:
"""retrieve relevant experimental data
Args:
Expand All @@ -114,17 +132,17 @@ def get_curve_data(self, curve: int = 0):
)
return self.curves[curve]

def get_curves(self):
def get_curves(self) -> list:
"""return all loaded curves as a list of pandas DataFrames"""
assert self.loaded, "DTA file not loaded. Run GamryParser.load()"
return self.curves

def get_header(self):
def get_header(self) -> list:
"""return the experiment configuration dictionary"""
assert self.loaded, "DTA file not loaded. Run GamryParser.load()"
return self.header

def get_experiment_type(self):
def get_experiment_type(self) -> str:
"""retrieve the type of experiment that was loaded (TAG)
Args:
Expand All @@ -136,7 +154,7 @@ def get_experiment_type(self):
assert self.loaded, "DTA file not loaded. Run GamryParser.load()"
return self.header["TAG"]

def get_ocv_curve(self):
def get_ocv_curve(self) -> pd.DataFrame:
"""return the contents of OCVCURVE (if it exists). Deprecated in Framework version 7"""
if self.ocv_exists:
return self.ocv
Expand All @@ -150,7 +168,7 @@ def get_ocv_value(self):
else:
return None

def read_header(self):
def read_header(self) -> list:
"""helper function to grab data from the EXPLAIN file header, which contains the loaded experiment's configuration
Args:
Expand Down Expand Up @@ -216,7 +234,7 @@ def read_header(self):

return self.header, self.header_length

def read_curve_data(self, fid: int):
def read_curve_data(self, fid: int) -> tuple:
"""helper function to process an EXPLAIN Table
Args:
Expand Down Expand Up @@ -247,7 +265,7 @@ def read_curve_data(self, fid: int):

return keys, units, curve

def read_curves(self):
def read_curves(self) -> list:
"""helper function to iterate through curves in a dta file and save as individual dataframes
Args:
Expand Down
74 changes: 74 additions & 0 deletions gamry_parser/squarewave.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import gamry_parser as parser


class SquareWaveVoltammetry(parser.GamryParser):
"""Load a Square Wave Voltammetry (SWV) experiment generated in Gamry EXPLAIN format."""

def load(self, filename: str = None, to_timestamp: bool = None):
"""save experiment information to \"header\", then save curve data to \"curves\"
Args:
filename (str, optional): file containing VFP600 data. defaults to None.
Returns:
None
"""
super(SquareWaveVoltammetry, self).load(
filename=filename, to_timestamp=to_timestamp
)
typecheck = self.header.get("TAG", None)
assert (
typecheck == "SQUARE_WAVE"
), f"The input file does not contain data from a square wave voltammetry experiment (expected type SQUARE_WAVE, found {typecheck})."

@property
def step_size(self):
return self.header.get("STEPSIZE", None)

@property
def pulse_size(self):
return self.header.get("PULSESIZE", None)

@property
def pulse_width(self):
return self.header.get("PULSEON", None)

@property
def frequency(self):
return self.header.get("FREQUENCY", None)

@property
def v_range(self):
return (self.header.get("VINIT", 0), self.header.get("VFINAL", 0))

@property
def cycles(self):
return self.header.get("CYCLES", 0)

def get_curve_data(self, curve: int = 0):
"""retrieve relevant SWV experimental data
Args:
curve (int, optional): curve number to return. Defaults to 0.
Returns:
pandas.DataFrame:
- T: time, in seconds or Timestamp
- Vfwd: forward potential, in V
- Vrev: reverse potential, in V
- Vstep: step potential, in V
- Ifwd: peak forward current measurement, in A
- Irev: peak reverse current measurement, in A
- Idif: differential current measurement (pulse), in A
"""
assert self.loaded, "DTA file not loaded. Run CyclicVoltammetry.load()"
assert curve >= 0, "Invalid curve ({}). Indexing starts at 0".format(curve)
assert (
curve < self.curve_count
), "Invalid curve ({}). File contains {} total curves.".format(
curve, self.curve_count
)
df = self.curves[curve]

return df[["T", "Vfwd", "Vrev", "Vstep", "Ifwd", "Irev", "Idif"]]
2 changes: 1 addition & 1 deletion gamry_parser/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.4.5"
__version__ = "0.4.6"
76 changes: 76 additions & 0 deletions tests/squarewave_data.dta
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
EXPLAIN
TAG SQUARE_WAVE
TITLE LABEL test-square-wave-experiment Test &Identifier
DATE LABEL 12/31/2021 Date
TIME LABEL 12:00:00 Time
PSTAT PSTAT REF600-10000 Potentiostat
NOTES NOTES 1 &Notes...

VINIT QUANT 0.00000E+000 Initial &E (V)
VFINAL QUANT -5.00000E-001 Final &E (V)
STEPSIZE QUANT 2.00000E+000 St&ep Size (mV)
TIMERRES QUANT 3.33340E-005 &Timer Resolution
FREQUENCY QUANT 1.00000E+002 &Frequency (Hz)
PULSESIZE QUANT 2.50000E+001 Pulse Size &Epeak (mV)
PULSEON QUANT 1.00000E-002 Pulse &Time (s)
AREA QUANT 1.00000E+000 Electrode &Area (cm^2)
IMODE SELECTOR 1 I/E Range &Mode
IRANGE QUANT 1.00000E-002 &Max Current (mA)
EQDELAY IQUANT 0 Equil. &Time (s)
IRCOMP SELECTOR 0 IRCom&p
PFCOR QUANT 5.00000E+001 PF Corr. (ohm)
CYCLES IQUANT 251 C&ycles (#)
ELECTRODETYPE SELECTOR 0 Electrode Type
STRIPPING TOGGLE F Used for Stripping
POLARITY TOGGLE F Signal Polarity
LINEFREQ IQUANT 60 Line Frequency (Hz)
SAMPLINGMODE SELECTOR 0 Sampling &Mode
PSTATMODEL IQUANT 4 Pstat Model
PSTATSECTION LABEL REF600-19054 Pstat Section
PSTATSERIALNO LABEL 19054 Pstat Serial Number
CTRLMODE IQUANT 1 Control Mode
ELECTROMETER IQUANT 0 RE=0 or CS=1
IESTAB IQUANT 2 I/E Stability
CASPEED IQUANT 6 Control Amp Speed
CONVENTION IQUANT 0 Current Convention
ICHRANGE IQUANT 2 Ich Range
ICHRANGEMODE TOGGLE F Ich Auto Range
ICHOFFSETENABLE TOGGLE F Ich Offset Enable
ICHOFFSET QUANT 0 Ich Offset (V)
ICHFILTER IQUANT 2 Ich Filter
VCHRANGE IQUANT 2 Vch Range
VCHRANGEMODE TOGGLE F Vch Auto Range
VCHOFFSETENABLE TOGGLE F Vch Offset Enable
VCHOFFSET QUANT 0 Vch Offset (V)
VCHFILTER IQUANT 1 Vch Filter
ACHRANGE IQUANT 2 Ach Range
ACHOFFSETENABLE TOGGLE F Ach Offset Enable
ACHOFFSET QUANT 0 Ach Offset (V)
ACHFILTER IQUANT 1 Ach Filter
IERANGELOWERLIMIT IQUANT 1 I/E Range Lower Limit
IERANGEMODE TOGGLE F I/E AutoRange
IERANGE IQUANT 7 I/E Range
POSFEEDENABLE TOGGLE F Positive Feedback IR Comp
POSFEEDRESISTANCE QUANT 0 Positive Feedback Resistance (ohm)
ACHSELECT IQUANT 4 Ach Select
SENSECABLEID IQUANT 14 Sense Cable ID
PWRCABLEID IQUANT 14 Power Cable ID
DCCALDATE LABEL 9/10/2021 DC Calibration Date
ACCALDATE LABEL 3/30/2021 AC Calibration Date
THERMOSELECT IQUANT 1 Thermo Select
FRAMEWORKVERSION LABEL 7.8.4 Framework Version
INSTRUMENTVERSION LABEL 4.42 Instrument Version
CURVE TABLE 251
Pt T Vfwd Vrev Vstep Ifwd Irev Idif Sig Ach IERange Over Temp
# s V V V A A A V V # bits deg C
0 0.01 -2.55258E-002 3.75742E-002 6.02416E-003 1.95523E-007 -1.73274E-006 1.92826E-006 2.50000E-002 5.08394E-005 7 ........... -0.01
1 0.02 -4.32258E-002 3.87742E-002 -2.22584E-003 8.75084E-008 -3.26548E-007 4.14056E-007 2.30000E-002 8.39387E-007 7 ........... -0.01
2 0.03 -4.59258E-002 3.68742E-002 -4.52584E-003 -3.20547E-007 -8.85156E-008 -2.32032E-007 2.10000E-002 5.08394E-005 7 ........... -0.01
3 0.04 -4.79258E-002 3.48742E-002 -6.52584E-003 -3.20547E-007 -4.65099E-008 -2.74037E-007 1.90000E-002 8.39387E-007 7 ........... -0.01
4 0.05 -5.01258E-002 3.28742E-002 -8.62584E-003 -3.28548E-007 -4.50415E-009 -3.24044E-007 1.70000E-002 8.39387E-007 7 ........... -0.01
5 0.06 -5.21258E-002 3.07742E-002 -1.06758E-002 -3.16547E-007 -1.45055E-008 -3.02041E-007 1.50000E-002 8.39387E-007 7 ........... -0.01
6 0.07 -5.41258E-002 2.90742E-002 -1.25258E-002 -2.96544E-007 2.14994E-008 -3.18043E-007 1.30000E-002 8.39387E-007 7 ........... -0.01
7 0.08 -5.59258E-002 2.66742E-002 -1.46258E-002 -3.28548E-007 3.75016E-008 -3.66050E-007 1.10000E-002 -4.91606E-005 7 ........... -0.01
8 0.09 -5.79258E-002 2.46742E-002 -1.66258E-002 -3.08546E-007 3.95018E-008 -3.48047E-007 9.00000E-003 -4.91606E-005 7 ........... -0.01
9 0.1 -6.00258E-002 2.26742E-002 -1.86758E-002 -3.34549E-007 6.75057E-008 -4.02055E-007 7.00000E-003 8.39387E-007 7 ........... -0.01
EXPERIMENTABORTED TOGGLE T Experiment Aborted
Loading

0 comments on commit 470d864

Please sign in to comment.