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.
Files changed (74) hide show
  1. pypalmsens/__init__.py +93 -0
  2. pypalmsens/_data/__init__.py +18 -0
  3. pypalmsens/_data/_shared.py +135 -0
  4. pypalmsens/_data/curve.py +315 -0
  5. pypalmsens/_data/data_array.py +101 -0
  6. pypalmsens/_data/dataset.py +252 -0
  7. pypalmsens/_data/eisdata.py +187 -0
  8. pypalmsens/_data/measurement.py +172 -0
  9. pypalmsens/_data/peak.py +166 -0
  10. pypalmsens/_fitting.py +446 -0
  11. pypalmsens/_instruments/__init__.py +17 -0
  12. pypalmsens/_instruments/_common.py +116 -0
  13. pypalmsens/_instruments/instrument_manager.py +741 -0
  14. pypalmsens/_instruments/instrument_manager_async.py +786 -0
  15. pypalmsens/_instruments/instrument_pool.py +107 -0
  16. pypalmsens/_instruments/instrument_pool_async.py +194 -0
  17. pypalmsens/_io.py +160 -0
  18. pypalmsens/_libpalmsens/__init__.py +10 -0
  19. pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.Linux.deps.json +220 -0
  20. pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.Linux.dll +0 -0
  21. pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.Linux.pdb +0 -0
  22. pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.Linux.xml +50 -0
  23. pypalmsens/_libpalmsens/linux-arm64/PalmSens.Core.dll +0 -0
  24. pypalmsens/_libpalmsens/linux-arm64/System.IO.Ports.dll +0 -0
  25. pypalmsens/_libpalmsens/linux-arm64/libSystem.IO.Ports.Native.so +0 -0
  26. pypalmsens/_libpalmsens/linux-arm64/runtimeconfig.json +9 -0
  27. pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.Linux.deps.json +220 -0
  28. pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.Linux.dll +0 -0
  29. pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.Linux.pdb +0 -0
  30. pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.Linux.xml +50 -0
  31. pypalmsens/_libpalmsens/linux-x64/PalmSens.Core.dll +0 -0
  32. pypalmsens/_libpalmsens/linux-x64/System.IO.Ports.dll +0 -0
  33. pypalmsens/_libpalmsens/linux-x64/libSystem.IO.Ports.Native.so +0 -0
  34. pypalmsens/_libpalmsens/linux-x64/runtimeconfig.json +9 -0
  35. pypalmsens/_libpalmsens/mono.py +74 -0
  36. pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.Linux.deps.json +220 -0
  37. pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.Linux.dll +0 -0
  38. pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.Linux.pdb +0 -0
  39. pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.Linux.xml +50 -0
  40. pypalmsens/_libpalmsens/osx-arm64/PalmSens.Core.dll +0 -0
  41. pypalmsens/_libpalmsens/osx-arm64/System.IO.Ports.dll +0 -0
  42. pypalmsens/_libpalmsens/osx-arm64/libSystem.IO.Ports.Native.dylib +0 -0
  43. pypalmsens/_libpalmsens/osx-arm64/runtimeconfig.json +9 -0
  44. pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.Linux.deps.json +220 -0
  45. pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.Linux.dll +0 -0
  46. pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.Linux.pdb +0 -0
  47. pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.Linux.xml +50 -0
  48. pypalmsens/_libpalmsens/osx-x64/PalmSens.Core.dll +0 -0
  49. pypalmsens/_libpalmsens/osx-x64/System.IO.Ports.dll +0 -0
  50. pypalmsens/_libpalmsens/osx-x64/libSystem.IO.Ports.Native.dylib +0 -0
  51. pypalmsens/_libpalmsens/osx-x64/runtimeconfig.json +9 -0
  52. pypalmsens/_libpalmsens/win/PalmSens.Core.Windows.BLE.dll +0 -0
  53. pypalmsens/_libpalmsens/win/PalmSens.Core.Windows.BLE.xml +3272 -0
  54. pypalmsens/_libpalmsens/win/PalmSens.Core.dll +0 -0
  55. pypalmsens/_libpalmsens/win/PalmSens.Core.xml +22678 -0
  56. pypalmsens/_libpalmsens/windows.py +55 -0
  57. pypalmsens/_methods/__init__.py +14 -0
  58. pypalmsens/_methods/_shared.py +320 -0
  59. pypalmsens/_methods/base.py +91 -0
  60. pypalmsens/_methods/method.py +74 -0
  61. pypalmsens/_methods/mixed_mode.py +270 -0
  62. pypalmsens/_methods/mixins.py +126 -0
  63. pypalmsens/_methods/settings.py +631 -0
  64. pypalmsens/_methods/techniques.py +1795 -0
  65. pypalmsens/_shared.py +14 -0
  66. pypalmsens/data.py +21 -0
  67. pypalmsens/fitting.py +17 -0
  68. pypalmsens/mixed_mode.py +19 -0
  69. pypalmsens/settings.py +51 -0
  70. pypalmsens-1.3.2.dist-info/METADATA +72 -0
  71. pypalmsens-1.3.2.dist-info/RECORD +74 -0
  72. pypalmsens-1.3.2.dist-info/WHEEL +5 -0
  73. pypalmsens-1.3.2.dist-info/licenses/LICENSE +30 -0
  74. 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