PyPalmSens 1.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pypalmsens/__init__.py +93 -0
- pypalmsens/_data/__init__.py +18 -0
- pypalmsens/_data/_shared.py +135 -0
- pypalmsens/_data/curve.py +315 -0
- pypalmsens/_data/data_array.py +101 -0
- pypalmsens/_data/dataset.py +252 -0
- pypalmsens/_data/eisdata.py +187 -0
- pypalmsens/_data/measurement.py +172 -0
- pypalmsens/_data/peak.py +166 -0
- pypalmsens/_fitting.py +446 -0
- pypalmsens/_instruments/__init__.py +17 -0
- pypalmsens/_instruments/_common.py +116 -0
- pypalmsens/_instruments/instrument_manager.py +741 -0
- pypalmsens/_instruments/instrument_manager_async.py +786 -0
- pypalmsens/_instruments/instrument_pool.py +107 -0
- pypalmsens/_instruments/instrument_pool_async.py +194 -0
- pypalmsens/_io.py +160 -0
- pypalmsens/_libpalmsens/__init__.py +10 -0
- pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.Linux.deps.json +220 -0
- pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.Linux.dll +0 -0
- pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.Linux.pdb +0 -0
- pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.Linux.xml +50 -0
- pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.dll +0 -0
- pypalmsens/_libpalmsens/linux-arm64/System.IO.Ports.dll +0 -0
- pypalmsens/_libpalmsens/linux-arm64/libSystem.IO.Ports.Native.so +0 -0
- pypalmsens/_libpalmsens/linux-arm64/runtimeconfig.json +9 -0
- pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.Linux.deps.json +220 -0
- pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.Linux.dll +0 -0
- pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.Linux.pdb +0 -0
- pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.Linux.xml +50 -0
- pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.dll +0 -0
- pypalmsens/_libpalmsens/linux-x64/System.IO.Ports.dll +0 -0
- pypalmsens/_libpalmsens/linux-x64/libSystem.IO.Ports.Native.so +0 -0
- pypalmsens/_libpalmsens/linux-x64/runtimeconfig.json +9 -0
- pypalmsens/_libpalmsens/mono.py +74 -0
- pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.Linux.deps.json +220 -0
- pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.Linux.dll +0 -0
- pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.Linux.pdb +0 -0
- pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.Linux.xml +50 -0
- pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.dll +0 -0
- pypalmsens/_libpalmsens/osx-arm64/System.IO.Ports.dll +0 -0
- pypalmsens/_libpalmsens/osx-arm64/libSystem.IO.Ports.Native.dylib +0 -0
- pypalmsens/_libpalmsens/osx-arm64/runtimeconfig.json +9 -0
- pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.Linux.deps.json +220 -0
- pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.Linux.dll +0 -0
- pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.Linux.pdb +0 -0
- pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.Linux.xml +50 -0
- pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.dll +0 -0
- pypalmsens/_libpalmsens/osx-x64/System.IO.Ports.dll +0 -0
- pypalmsens/_libpalmsens/osx-x64/libSystem.IO.Ports.Native.dylib +0 -0
- pypalmsens/_libpalmsens/osx-x64/runtimeconfig.json +9 -0
- pypalmsens/_libpalmsens/win/PalmSens.Core.Windows.BLE.dll +0 -0
- pypalmsens/_libpalmsens/win/PalmSens.Core.Windows.BLE.xml +3272 -0
- pypalmsens/_libpalmsens/win/PalmSens.Core.dll +0 -0
- pypalmsens/_libpalmsens/win/PalmSens.Core.xml +22678 -0
- pypalmsens/_libpalmsens/windows.py +55 -0
- pypalmsens/_methods/__init__.py +14 -0
- pypalmsens/_methods/_shared.py +320 -0
- pypalmsens/_methods/base.py +91 -0
- pypalmsens/_methods/method.py +74 -0
- pypalmsens/_methods/mixed_mode.py +270 -0
- pypalmsens/_methods/mixins.py +126 -0
- pypalmsens/_methods/settings.py +631 -0
- pypalmsens/_methods/techniques.py +1795 -0
- pypalmsens/_shared.py +14 -0
- pypalmsens/data.py +21 -0
- pypalmsens/fitting.py +17 -0
- pypalmsens/mixed_mode.py +19 -0
- pypalmsens/settings.py +51 -0
- pypalmsens-1.3.2.dist-info/METADATA +72 -0
- pypalmsens-1.3.2.dist-info/RECORD +74 -0
- pypalmsens-1.3.2.dist-info/WHEEL +5 -0
- pypalmsens-1.3.2.dist-info/licenses/LICENSE +30 -0
- pypalmsens-1.3.2.dist-info/top_level.txt +1 -0
pypalmsens/__init__.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from . import _libpalmsens
|
|
4
|
+
|
|
5
|
+
__sdk_version__ = _libpalmsens.load()
|
|
6
|
+
__version__ = '1.3.2'
|
|
7
|
+
|
|
8
|
+
from . import (
|
|
9
|
+
data,
|
|
10
|
+
fitting,
|
|
11
|
+
mixed_mode,
|
|
12
|
+
settings,
|
|
13
|
+
)
|
|
14
|
+
from ._instruments.instrument_manager import (
|
|
15
|
+
InstrumentManager,
|
|
16
|
+
connect,
|
|
17
|
+
discover,
|
|
18
|
+
)
|
|
19
|
+
from ._instruments.instrument_manager_async import (
|
|
20
|
+
InstrumentManagerAsync,
|
|
21
|
+
connect_async,
|
|
22
|
+
discover_async,
|
|
23
|
+
)
|
|
24
|
+
from ._instruments.instrument_pool import InstrumentPool
|
|
25
|
+
from ._instruments.instrument_pool_async import InstrumentPoolAsync
|
|
26
|
+
from ._io import load_method_file, load_session_file, save_method_file, save_session_file
|
|
27
|
+
from ._methods.techniques import (
|
|
28
|
+
ACVoltammetry,
|
|
29
|
+
ChronoAmperometry,
|
|
30
|
+
ChronoCoulometry,
|
|
31
|
+
ChronoPotentiometry,
|
|
32
|
+
CyclicVoltammetry,
|
|
33
|
+
DifferentialPulseVoltammetry,
|
|
34
|
+
ElectrochemicalImpedanceSpectroscopy,
|
|
35
|
+
FastAmperometry,
|
|
36
|
+
FastCyclicVoltammetry,
|
|
37
|
+
FastGalvanostaticImpedanceSpectroscopy,
|
|
38
|
+
FastImpedanceSpectroscopy,
|
|
39
|
+
GalvanostaticImpedanceSpectroscopy,
|
|
40
|
+
LinearSweepPotentiometry,
|
|
41
|
+
LinearSweepVoltammetry,
|
|
42
|
+
MethodScript,
|
|
43
|
+
MultiplePulseAmperometry,
|
|
44
|
+
MultiStepAmperometry,
|
|
45
|
+
MultiStepPotentiometry,
|
|
46
|
+
NormalPulseVoltammetry,
|
|
47
|
+
OpenCircuitPotentiometry,
|
|
48
|
+
PulsedAmperometricDetection,
|
|
49
|
+
SquareWaveVoltammetry,
|
|
50
|
+
StrippingChronoPotentiometry,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
'settings',
|
|
55
|
+
'data',
|
|
56
|
+
'fitting',
|
|
57
|
+
'mixed_mode',
|
|
58
|
+
'connect',
|
|
59
|
+
'connect_async',
|
|
60
|
+
'discover',
|
|
61
|
+
'discover_async',
|
|
62
|
+
'load_method_file',
|
|
63
|
+
'load_session_file',
|
|
64
|
+
'save_method_file',
|
|
65
|
+
'save_session_file',
|
|
66
|
+
'InstrumentManager',
|
|
67
|
+
'FastAmperometry',
|
|
68
|
+
'InstrumentManagerAsync',
|
|
69
|
+
'InstrumentPool',
|
|
70
|
+
'InstrumentPoolAsync',
|
|
71
|
+
'ACVoltammetry',
|
|
72
|
+
'ChronoAmperometry',
|
|
73
|
+
'ChronoCoulometry',
|
|
74
|
+
'ChronoPotentiometry',
|
|
75
|
+
'CyclicVoltammetry',
|
|
76
|
+
'DifferentialPulseVoltammetry',
|
|
77
|
+
'ElectrochemicalImpedanceSpectroscopy',
|
|
78
|
+
'FastCyclicVoltammetry',
|
|
79
|
+
'FastGalvanostaticImpedanceSpectroscopy',
|
|
80
|
+
'FastImpedanceSpectroscopy',
|
|
81
|
+
'GalvanostaticImpedanceSpectroscopy',
|
|
82
|
+
'LinearSweepPotentiometry',
|
|
83
|
+
'LinearSweepVoltammetry',
|
|
84
|
+
'MethodScript',
|
|
85
|
+
'MultiplePulseAmperometry',
|
|
86
|
+
'MultiStepAmperometry',
|
|
87
|
+
'MultiStepPotentiometry',
|
|
88
|
+
'NormalPulseVoltammetry',
|
|
89
|
+
'OpenCircuitPotentiometry',
|
|
90
|
+
'PulsedAmperometricDetection',
|
|
91
|
+
'SquareWaveVoltammetry',
|
|
92
|
+
'StrippingChronoPotentiometry',
|
|
93
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .curve import Curve
|
|
4
|
+
from .data_array import DataArray
|
|
5
|
+
from .dataset import DataSet
|
|
6
|
+
from .eisdata import EISData
|
|
7
|
+
from .measurement import DeviceInfo, Measurement
|
|
8
|
+
from .peak import Peak
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
'Curve',
|
|
12
|
+
'DataArray',
|
|
13
|
+
'DataSet',
|
|
14
|
+
'DeviceInfo',
|
|
15
|
+
'EISData',
|
|
16
|
+
'Measurement',
|
|
17
|
+
'Peak',
|
|
18
|
+
]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ArrayType(Enum):
|
|
8
|
+
"""Data array type for standard arrays."""
|
|
9
|
+
|
|
10
|
+
Unspecified = -1
|
|
11
|
+
"""Unspecified"""
|
|
12
|
+
Time = 0
|
|
13
|
+
"""Time / s"""
|
|
14
|
+
Potential = 1
|
|
15
|
+
"""Potential / V"""
|
|
16
|
+
Current = 2
|
|
17
|
+
"""Current / μA"""
|
|
18
|
+
Charge = 3
|
|
19
|
+
"""Charge"""
|
|
20
|
+
ExtraValue = 4
|
|
21
|
+
"""ExtraValue"""
|
|
22
|
+
Frequency = 5
|
|
23
|
+
"""Frequency"""
|
|
24
|
+
Phase = 6
|
|
25
|
+
"""Phase"""
|
|
26
|
+
ZRe = 7
|
|
27
|
+
"""Z real"""
|
|
28
|
+
ZIm = 8
|
|
29
|
+
"""Z imaginary"""
|
|
30
|
+
Iac = 9
|
|
31
|
+
"""I AC values"""
|
|
32
|
+
Z = 10
|
|
33
|
+
"""Z"""
|
|
34
|
+
Y = 11
|
|
35
|
+
"""Y"""
|
|
36
|
+
YRe = 12
|
|
37
|
+
"""Y real"""
|
|
38
|
+
YIm = 13
|
|
39
|
+
"""Y imaginary"""
|
|
40
|
+
Cs = 14
|
|
41
|
+
"""Cs"""
|
|
42
|
+
CsRe = 15
|
|
43
|
+
"""Cs real"""
|
|
44
|
+
CsIm = 16
|
|
45
|
+
"""Cs imaginary"""
|
|
46
|
+
Index = 17
|
|
47
|
+
"""Index"""
|
|
48
|
+
Admittance = 18
|
|
49
|
+
"""Admittance"""
|
|
50
|
+
Concentration = 19
|
|
51
|
+
"""Concentration"""
|
|
52
|
+
Signal = 20
|
|
53
|
+
"""Signal"""
|
|
54
|
+
Func = 21
|
|
55
|
+
"""Func"""
|
|
56
|
+
Integral = 22
|
|
57
|
+
"""Integral"""
|
|
58
|
+
AuxInput = 23
|
|
59
|
+
"""Auxillary input"""
|
|
60
|
+
BipotCurrent = 24
|
|
61
|
+
"""Bipot current"""
|
|
62
|
+
BipotPotential = 25
|
|
63
|
+
"""Bipot potential"""
|
|
64
|
+
ReverseCurrent = 26
|
|
65
|
+
"""Reverse current"""
|
|
66
|
+
CEPotential = 27
|
|
67
|
+
"""CE potential"""
|
|
68
|
+
DCCurrent = 28
|
|
69
|
+
"""DC current"""
|
|
70
|
+
ForwardCurrent = 29
|
|
71
|
+
"""Forward current"""
|
|
72
|
+
PotentialExtraRE = 30
|
|
73
|
+
"""Potential setpoint measured back on RE"""
|
|
74
|
+
CurrentExtraWE = 31
|
|
75
|
+
"""Current setpoint measured back on WE"""
|
|
76
|
+
InverseDerative_dtdE = 32
|
|
77
|
+
"""Inverse derivative dt/dE"""
|
|
78
|
+
mEdc = 33
|
|
79
|
+
"""Measured applied DC"""
|
|
80
|
+
Eac = 34
|
|
81
|
+
"""E AC values"""
|
|
82
|
+
MeasuredStepStartIndex = 35
|
|
83
|
+
"""MeasuredStepStartIndex"""
|
|
84
|
+
miDC = 36
|
|
85
|
+
"""Measured I DC values"""
|
|
86
|
+
SE2vsXPotential = 37
|
|
87
|
+
"""SE2 vs XPotential"""
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def _missing_(cls, value):
|
|
91
|
+
return cls.Unspecified
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Status(Enum):
|
|
95
|
+
Unknown = -1
|
|
96
|
+
OK = 0
|
|
97
|
+
Overload = 1
|
|
98
|
+
Underload = 2
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _get_values_from_NETArray(array, start: int = 0, count: Optional[int] = None):
|
|
102
|
+
if not count:
|
|
103
|
+
count = array.Count
|
|
104
|
+
|
|
105
|
+
values = []
|
|
106
|
+
for i in range(start, start + count):
|
|
107
|
+
value = array.get_Item(i)
|
|
108
|
+
values.append(float(value.Value))
|
|
109
|
+
return values
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def __get_currentranges_from_currentarray(
|
|
113
|
+
arraycurrents, start: int = 0, count: Optional[int] = None
|
|
114
|
+
):
|
|
115
|
+
if not count:
|
|
116
|
+
count = arraycurrents.Count
|
|
117
|
+
values = []
|
|
118
|
+
if ArrayType(arraycurrents.ArrayType) == ArrayType.Current:
|
|
119
|
+
for i in range(start, count):
|
|
120
|
+
value = arraycurrents.get_Item(i)
|
|
121
|
+
values.append(str(value.CurrentRange.ToString()))
|
|
122
|
+
return values
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def __get_status_from_current_or_potentialarray(
|
|
126
|
+
array, start: int = 0, count: Optional[int] = None
|
|
127
|
+
):
|
|
128
|
+
if not count:
|
|
129
|
+
count = array.Count
|
|
130
|
+
|
|
131
|
+
values = []
|
|
132
|
+
for i in range(start, count):
|
|
133
|
+
value = array.get_Item(i)
|
|
134
|
+
values.append(str(Status(value.ReadingStatus)))
|
|
135
|
+
return values
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Optional, Union
|
|
4
|
+
|
|
5
|
+
import PalmSens.Analysis as PSAnalysis
|
|
6
|
+
import System
|
|
7
|
+
from PalmSens.Plottables import Curve as PSCurve
|
|
8
|
+
|
|
9
|
+
from .data_array import DataArray
|
|
10
|
+
from .peak import Peak
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from matplotlib import axes, figure
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Curve:
|
|
17
|
+
"""Python wrapper for .NET Curve class.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
pscurve
|
|
22
|
+
Reference to .NET curve object.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, *, pscurve: PSCurve):
|
|
26
|
+
self._pscurve = pscurve
|
|
27
|
+
|
|
28
|
+
def __repr__(self):
|
|
29
|
+
return f'{self.__class__.__name__}(title={self.title}, n_points={self.n_points})'
|
|
30
|
+
|
|
31
|
+
def copy(self) -> Curve:
|
|
32
|
+
"""Return a copy of this curve."""
|
|
33
|
+
return Curve(pscurve=PSCurve(self._pscurve, cloneData=True))
|
|
34
|
+
|
|
35
|
+
def smooth(self, smooth_level: int = 0):
|
|
36
|
+
"""Smooth the .y_array using a Savitsky-Golay filter with the specified smooth
|
|
37
|
+
level.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
smooth_level : int
|
|
42
|
+
The smooth level to be used. -1 = none, 0 = no smooth (spike rejection only),
|
|
43
|
+
1 = 5 points, 2 = 9 points, 3 = 15 points, 4 = 25 points
|
|
44
|
+
"""
|
|
45
|
+
success = self._pscurve.Smooth(smoothLevel=smooth_level)
|
|
46
|
+
if not success:
|
|
47
|
+
raise ValueError('Something went wrong.')
|
|
48
|
+
|
|
49
|
+
def savitsky_golay(self, window_size: int = 3):
|
|
50
|
+
"""Smooth the .y_array using a Savitsky-Golay filter with the specified window
|
|
51
|
+
size.
|
|
52
|
+
|
|
53
|
+
(i.e. window size 2 will filter points based on the values of the next/previous 2 points)
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
window_size : int
|
|
58
|
+
Size of the window
|
|
59
|
+
"""
|
|
60
|
+
self._pscurve.SavitskyGolay(windowSize=window_size)
|
|
61
|
+
|
|
62
|
+
def find_peaks(
|
|
63
|
+
self,
|
|
64
|
+
min_peak_width: float = 0.1,
|
|
65
|
+
min_peak_height: float = 0.0,
|
|
66
|
+
peak_shoulders: bool = False,
|
|
67
|
+
merge_overlapping_peaks: bool = True,
|
|
68
|
+
) -> list[Peak]:
|
|
69
|
+
"""Find peaks in a curve in all directions.
|
|
70
|
+
|
|
71
|
+
CV can have 1 or 2 direction changes.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
min_peak_width : float
|
|
76
|
+
Minimum width of the peak in V
|
|
77
|
+
min_peak_height : float
|
|
78
|
+
Minimum height of the peak in uA
|
|
79
|
+
peak_shoulders : bool, optional
|
|
80
|
+
Use alternative peak search algorithm optimized for finding peaks on slopes
|
|
81
|
+
merge_overlapping_peaks : bool, optional
|
|
82
|
+
Two or more peaks that overlap will be identified as a single
|
|
83
|
+
base peak and as shoulder peaks on the base peak.
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
peak_list : list[Peak]
|
|
88
|
+
"""
|
|
89
|
+
pspeaks = self._pscurve.FindPeaks(
|
|
90
|
+
minPeakWidth=min_peak_width,
|
|
91
|
+
minPeakHeight=min_peak_height,
|
|
92
|
+
peakShoulders=peak_shoulders,
|
|
93
|
+
mergeOverlappingPeaks=merge_overlapping_peaks,
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
peaks_list = [Peak(pspeak=peak) for peak in pspeaks]
|
|
97
|
+
|
|
98
|
+
return peaks_list
|
|
99
|
+
|
|
100
|
+
def find_peaks_semiderivative(
|
|
101
|
+
self,
|
|
102
|
+
min_peak_height: float = 0.0,
|
|
103
|
+
) -> list[Peak]:
|
|
104
|
+
"""
|
|
105
|
+
Find peaks in a curve using the semi-derivative algorithm.
|
|
106
|
+
|
|
107
|
+
Used for detecting non-overlapping peaks in LSV and CV curves.
|
|
108
|
+
The peaks are also assigned to the curve, updating `Curve.peaks`.
|
|
109
|
+
Existing peaks are overwritten.
|
|
110
|
+
|
|
111
|
+
For more info, see this
|
|
112
|
+
[Wikipedia page](https://en.wikipedia.org/wiki/Neopolarogram).
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
min_peak_height : float
|
|
117
|
+
Minimum height of the peak in uA
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
peak_list : list[Peak]
|
|
122
|
+
"""
|
|
123
|
+
dct = System.Collections.Generic.Dictionary[PSCurve, System.Double]()
|
|
124
|
+
dct[self._pscurve] = min_peak_height
|
|
125
|
+
|
|
126
|
+
pd = PSAnalysis.SemiDerivativePeakDetection()
|
|
127
|
+
pd.GetNonOverlappingPeaks(dct)
|
|
128
|
+
|
|
129
|
+
return self.peaks
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def max_x(self) -> float:
|
|
133
|
+
"""Maximum X value found in this curve."""
|
|
134
|
+
return self._pscurve.MaxX
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def max_y(self) -> float:
|
|
138
|
+
"""Maximum Y value found in this curve."""
|
|
139
|
+
return self._pscurve.MaxY
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def min_x(self) -> float:
|
|
143
|
+
"""Minimum X value found in this curve."""
|
|
144
|
+
return self._pscurve.MinX
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def min_y(self) -> float:
|
|
148
|
+
"""Minimum Y value found in this curve."""
|
|
149
|
+
return self._pscurve.MinY
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def mux_channel(self) -> int:
|
|
153
|
+
"""The corresponding MUX channel number with the curve starting at 0.
|
|
154
|
+
Return -1 when no MUX channel used."""
|
|
155
|
+
return self._pscurve.MuxChannel
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def n_points(self) -> int:
|
|
159
|
+
"""Number of points for this curve."""
|
|
160
|
+
return len(self)
|
|
161
|
+
|
|
162
|
+
def __len__(self):
|
|
163
|
+
return self._pscurve.NPoints
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def ocp_value(self) -> float:
|
|
167
|
+
"""OCP value for curve."""
|
|
168
|
+
return self._pscurve.OCPValue
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def reference_electrode_name(self) -> Union[None, str]:
|
|
172
|
+
"""The name of the reference electrode. Return None if not set."""
|
|
173
|
+
if ret := self._pscurve.ReferenceElectrodeName:
|
|
174
|
+
return str(ret)
|
|
175
|
+
return None
|
|
176
|
+
|
|
177
|
+
@property
|
|
178
|
+
def reference_electrode_potential(self) -> Union[None, str]:
|
|
179
|
+
"""The reference electrode potential offset. Return None if not set."""
|
|
180
|
+
if ret := self._pscurve.ReferenceElectrodePotential:
|
|
181
|
+
return str(ret)
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
@property
|
|
185
|
+
def x_unit(self) -> str:
|
|
186
|
+
"""Units for X dimension."""
|
|
187
|
+
return self._pscurve.XUnit.ToString()
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def x_label(self) -> str:
|
|
191
|
+
"""Label for X dimension."""
|
|
192
|
+
return self._pscurve.XUnit.Quantity
|
|
193
|
+
|
|
194
|
+
@property
|
|
195
|
+
def y_unit(self) -> str:
|
|
196
|
+
"""Units for Y dimension."""
|
|
197
|
+
return self._pscurve.YUnit.ToString()
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def y_label(self) -> str:
|
|
201
|
+
"""Label for Y dimension."""
|
|
202
|
+
return self._pscurve.YUnit.Quantity
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def z_unit(self) -> Union[None, str]:
|
|
206
|
+
"""Units for Z dimension. Returns None if not set."""
|
|
207
|
+
if ret := self._pscurve.ZUnit:
|
|
208
|
+
return ret.ToString()
|
|
209
|
+
return None
|
|
210
|
+
|
|
211
|
+
@property
|
|
212
|
+
def z_label(self) -> Union[None, str]:
|
|
213
|
+
"""Units for Z dimension. Returns None if not set."""
|
|
214
|
+
if ret := self._pscurve.ZUnit:
|
|
215
|
+
return ret.Quantity
|
|
216
|
+
return None
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def title(self) -> str:
|
|
220
|
+
"""Title for the curve."""
|
|
221
|
+
return self._pscurve.Title
|
|
222
|
+
|
|
223
|
+
@title.setter
|
|
224
|
+
def title(self, title: str):
|
|
225
|
+
"""Set the title for the curve."""
|
|
226
|
+
self._pscurve.Title = title
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def peaks(self) -> list[Peak]:
|
|
230
|
+
"""Return peaks stored on object."""
|
|
231
|
+
try:
|
|
232
|
+
peaks = [Peak(pspeak=peak) for peak in self._pscurve.Peaks]
|
|
233
|
+
except TypeError:
|
|
234
|
+
peaks = []
|
|
235
|
+
return peaks
|
|
236
|
+
|
|
237
|
+
def clear_peaks(self):
|
|
238
|
+
"""Clear peaks stored on object."""
|
|
239
|
+
self._pscurve.ClearPeaks()
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def x_array(self) -> DataArray:
|
|
243
|
+
"""Y data for the curve."""
|
|
244
|
+
return DataArray(psarray=self._pscurve.XAxisDataArray)
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def y_array(self) -> DataArray:
|
|
248
|
+
"""Y data for the curve."""
|
|
249
|
+
return DataArray(psarray=self._pscurve.YAxisDataArray)
|
|
250
|
+
|
|
251
|
+
def linear_slope(
|
|
252
|
+
self, start: Optional[int] = None, stop: Optional[int] = None
|
|
253
|
+
) -> tuple[float, float, float]:
|
|
254
|
+
"""Calculate linear line parameters for this curve between two indexes.
|
|
255
|
+
|
|
256
|
+
current = a + b * x
|
|
257
|
+
|
|
258
|
+
Parameters
|
|
259
|
+
----------
|
|
260
|
+
start : int, optional
|
|
261
|
+
begin index
|
|
262
|
+
stop : int, optional
|
|
263
|
+
end index
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
a : float
|
|
268
|
+
b : float
|
|
269
|
+
coefdet : float
|
|
270
|
+
Coefficient of determination (R2)
|
|
271
|
+
"""
|
|
272
|
+
if start and stop:
|
|
273
|
+
return self._pscurve.LLS(start, stop)
|
|
274
|
+
else:
|
|
275
|
+
return self._pscurve.LLS()
|
|
276
|
+
|
|
277
|
+
def plot(
|
|
278
|
+
self,
|
|
279
|
+
ax: Optional[axes.Axes] = None,
|
|
280
|
+
legend: bool = True,
|
|
281
|
+
**plot_kwargs,
|
|
282
|
+
) -> figure.Figure:
|
|
283
|
+
"""Generate simple plot for this curve using matplotlib.
|
|
284
|
+
|
|
285
|
+
Parameters
|
|
286
|
+
----------
|
|
287
|
+
ax : Optional[axes.Axes]
|
|
288
|
+
Add plot to this ax if specified.
|
|
289
|
+
legend : bool
|
|
290
|
+
If True, add legend.
|
|
291
|
+
plot_kwargs
|
|
292
|
+
These keyword arguments are passed to `ax.plot`.
|
|
293
|
+
|
|
294
|
+
Returns
|
|
295
|
+
-------
|
|
296
|
+
fig : fig.Figure
|
|
297
|
+
Matplotlib figure. Use `fig.show()` to render plot.
|
|
298
|
+
"""
|
|
299
|
+
import matplotlib.pyplot as plt
|
|
300
|
+
|
|
301
|
+
if not ax:
|
|
302
|
+
fig, ax = plt.subplots()
|
|
303
|
+
|
|
304
|
+
ax.plot(self.x_array, self.y_array, label=self.title, **plot_kwargs)
|
|
305
|
+
ax.set_xlabel(f'{self.x_label} ({self.x_unit})')
|
|
306
|
+
ax.set_ylabel(f'{self.y_label} ({self.y_unit})')
|
|
307
|
+
|
|
308
|
+
if peaks := self.peaks:
|
|
309
|
+
x, y = list(zip(*((peak.x, peak.y) for peak in peaks)))
|
|
310
|
+
ax.scatter(x, y, label='Peaks')
|
|
311
|
+
|
|
312
|
+
if legend:
|
|
313
|
+
ax.legend()
|
|
314
|
+
|
|
315
|
+
return ax.figure
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ._shared import ArrayType
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DataArray(Sequence):
|
|
11
|
+
"""Python wrapper for .NET DataArray class.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
psarray
|
|
16
|
+
Reference to .NET DataArray object.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, *, psarray):
|
|
20
|
+
self._psarray = psarray
|
|
21
|
+
|
|
22
|
+
def __repr__(self):
|
|
23
|
+
return (
|
|
24
|
+
f'{self.__class__.__name__}('
|
|
25
|
+
f'name={self.name}, '
|
|
26
|
+
f'unit={self.unit}, '
|
|
27
|
+
f'n_points={len(self)})'
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def __getitem__(self, index):
|
|
31
|
+
if isinstance(index, int):
|
|
32
|
+
if index >= len(self) or index < -len(self):
|
|
33
|
+
raise IndexError('list index out of range')
|
|
34
|
+
index = index % len(self)
|
|
35
|
+
return self._psarray[index].Value
|
|
36
|
+
|
|
37
|
+
return self.to_list()[index]
|
|
38
|
+
|
|
39
|
+
def __len__(self):
|
|
40
|
+
return len(self._psarray)
|
|
41
|
+
|
|
42
|
+
def copy(self) -> DataArray:
|
|
43
|
+
"""Return a copy of the array."""
|
|
44
|
+
return DataArray(psarray=self._psarray.Clone())
|
|
45
|
+
|
|
46
|
+
def min(self) -> float:
|
|
47
|
+
"""Return min value."""
|
|
48
|
+
return self._psarray.MinValue
|
|
49
|
+
|
|
50
|
+
def max(self) -> float:
|
|
51
|
+
"""Return max value."""
|
|
52
|
+
return self._psarray.MaxValue
|
|
53
|
+
|
|
54
|
+
def savitsky_golay(self, window_size: int = 3) -> DataArray:
|
|
55
|
+
"""Smooth the array using a Savitsky-Golay filter with the window size.
|
|
56
|
+
|
|
57
|
+
(i.e. window size 2 will filter points based on the values of the next/previous 2 points)
|
|
58
|
+
|
|
59
|
+
Parameters
|
|
60
|
+
----------
|
|
61
|
+
window_size : int
|
|
62
|
+
Size of the window
|
|
63
|
+
"""
|
|
64
|
+
new = self.copy()
|
|
65
|
+
success = new._psarray.Smooth(window_size, False)
|
|
66
|
+
if not success:
|
|
67
|
+
raise ValueError('Something went wrong.')
|
|
68
|
+
return new
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def name(self) -> str:
|
|
72
|
+
"""Name of the array."""
|
|
73
|
+
return self._psarray.Description
|
|
74
|
+
|
|
75
|
+
def to_numpy(self) -> np.ndarray:
|
|
76
|
+
"""Export data array to numpy."""
|
|
77
|
+
return np.array(self._psarray.GetValues())
|
|
78
|
+
|
|
79
|
+
def to_list(self) -> list[float]:
|
|
80
|
+
"""Export data array to list."""
|
|
81
|
+
return list(self._psarray.GetValues())
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def type(self) -> ArrayType:
|
|
85
|
+
"""ArrayType enum."""
|
|
86
|
+
return ArrayType(self._psarray.ArrayType)
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def unit(self) -> str:
|
|
90
|
+
"""Unit for array."""
|
|
91
|
+
return self._psarray.Unit.ToString()
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def quantity(self) -> str:
|
|
95
|
+
"""Quantity for array."""
|
|
96
|
+
return self._psarray.Unit.Quantity
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def ocp_value(self) -> float:
|
|
100
|
+
"""OCP Value."""
|
|
101
|
+
return self._psarray.OCPValue
|