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

Impl: support for Square Wave Voltammetry experiments #45

Merged
merged 6 commits into from
Jan 1, 2022
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
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