FlowCyPy 0.7.0__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 (45) hide show
  1. FlowCyPy/__init__.py +13 -0
  2. FlowCyPy/_version.py +16 -0
  3. FlowCyPy/acquisition.py +652 -0
  4. FlowCyPy/classifier.py +208 -0
  5. FlowCyPy/coupling_mechanism/__init__.py +4 -0
  6. FlowCyPy/coupling_mechanism/empirical.py +47 -0
  7. FlowCyPy/coupling_mechanism/mie.py +207 -0
  8. FlowCyPy/coupling_mechanism/rayleigh.py +116 -0
  9. FlowCyPy/coupling_mechanism/uniform.py +40 -0
  10. FlowCyPy/coupling_mechanism.py +205 -0
  11. FlowCyPy/cytometer.py +314 -0
  12. FlowCyPy/detector.py +439 -0
  13. FlowCyPy/directories.py +36 -0
  14. FlowCyPy/distribution/__init__.py +16 -0
  15. FlowCyPy/distribution/base_class.py +79 -0
  16. FlowCyPy/distribution/delta.py +104 -0
  17. FlowCyPy/distribution/lognormal.py +124 -0
  18. FlowCyPy/distribution/normal.py +128 -0
  19. FlowCyPy/distribution/particle_size_distribution.py +132 -0
  20. FlowCyPy/distribution/uniform.py +117 -0
  21. FlowCyPy/distribution/weibull.py +115 -0
  22. FlowCyPy/flow_cell.py +198 -0
  23. FlowCyPy/helper.py +81 -0
  24. FlowCyPy/logger.py +136 -0
  25. FlowCyPy/noises.py +34 -0
  26. FlowCyPy/particle_count.py +127 -0
  27. FlowCyPy/peak_locator/__init__.py +4 -0
  28. FlowCyPy/peak_locator/base_class.py +163 -0
  29. FlowCyPy/peak_locator/basic.py +108 -0
  30. FlowCyPy/peak_locator/derivative.py +143 -0
  31. FlowCyPy/peak_locator/moving_average.py +166 -0
  32. FlowCyPy/physical_constant.py +19 -0
  33. FlowCyPy/plottings.py +269 -0
  34. FlowCyPy/population.py +136 -0
  35. FlowCyPy/populations_instances.py +65 -0
  36. FlowCyPy/scatterer_collection.py +306 -0
  37. FlowCyPy/signal_digitizer.py +90 -0
  38. FlowCyPy/source.py +249 -0
  39. FlowCyPy/units.py +30 -0
  40. FlowCyPy/utils.py +191 -0
  41. FlowCyPy-0.7.0.dist-info/LICENSE +21 -0
  42. FlowCyPy-0.7.0.dist-info/METADATA +252 -0
  43. FlowCyPy-0.7.0.dist-info/RECORD +45 -0
  44. FlowCyPy-0.7.0.dist-info/WHEEL +5 -0
  45. FlowCyPy-0.7.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,163 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+ from scipy.integrate import cumulative_trapezoid
4
+ import matplotlib.pyplot as plt
5
+ import pint_pandas
6
+ from abc import ABC, abstractmethod
7
+ from FlowCyPy.units import Quantity
8
+ from scipy.signal import peak_widths
9
+
10
+
11
+ class BasePeakLocator(ABC):
12
+ """
13
+ A base class to handle common functionality for peak detection,
14
+ including area calculation under peaks.
15
+ """
16
+
17
+ peak_properties: pd.DataFrame = None
18
+
19
+ def init_data(self, dataframe: pd.DataFrame) -> None:
20
+ """
21
+ Initialize signal and time data for peak detection.
22
+
23
+ Parameters
24
+ ----------
25
+ dataframe : pd.DataFrame
26
+ A DataFrame containing the signal data with columns 'Signal' and 'Time'.
27
+
28
+ Raises
29
+ ------
30
+ ValueError
31
+ If the DataFrame is missing required columns or is empty.
32
+ """
33
+ if not {'Signal', 'Time'}.issubset(dataframe.columns):
34
+ raise ValueError("The DataFrame must contain 'Signal' and 'Time' columns.")
35
+
36
+ if dataframe.empty:
37
+ raise ValueError("The DataFrame is empty. Please provide valid signal data.")
38
+
39
+ self.data = pd.DataFrame(index=np.arange(len(dataframe)))
40
+ self.data['Signal'] = dataframe['Signal']
41
+ self.data['Time'] = dataframe['Time']
42
+
43
+ self.dt = self.data.Time[1] - self.data.Time[0]
44
+
45
+ @abstractmethod
46
+ def _compute_algorithm_specific_features(self) -> None:
47
+ """
48
+ Abstract method for computing features specific to the detection algorithm.
49
+ This must be implemented by subclasses.
50
+ """
51
+ pass
52
+
53
+ def detect_peaks(self, compute_area: bool = True) -> pd.DataFrame:
54
+ """
55
+ Detect peaks and compute peak properties.
56
+
57
+ Parameters
58
+ ----------
59
+ compute_area : bool, optional
60
+ If True, computes the area under each peak (default is True).
61
+
62
+ Returns
63
+ -------
64
+ pd.DataFrame
65
+ A DataFrame containing peak properties (times, heights, widths, etc.).
66
+ """
67
+ peak_indices, widths_samples, width_heights, left_ips, right_ips = self._compute_algorithm_specific_features()
68
+
69
+ peak_times = self.data['Time'].values[peak_indices]
70
+ heights = self.data['Signal'].values[peak_indices]
71
+ widths = widths_samples * self.dt
72
+
73
+ # Store results in `peak_properties`
74
+ self.peak_properties = pd.DataFrame({
75
+ 'PeakTimes': peak_times,
76
+ 'Heights': heights,
77
+ 'Widths': pint_pandas.PintArray(widths, dtype=widths.units),
78
+ 'WidthHeights': pint_pandas.PintArray(width_heights, dtype=heights.units),
79
+ 'LeftIPs': pint_pandas.PintArray(left_ips * self.dt, dtype=self.dt.units),
80
+ 'RightIPs': pint_pandas.PintArray(right_ips * self.dt, dtype=self.dt.units),
81
+ })
82
+
83
+ # Compute areas if needed
84
+ if compute_area:
85
+ self._compute_peak_areas()
86
+
87
+ return self.peak_properties
88
+
89
+ def _compute_peak_areas(self) -> None:
90
+ """
91
+ Computes the areas under the detected peaks using cumulative integration.
92
+
93
+ The cumulative integral of the signal is interpolated at the left and right
94
+ interpolated positions of each peak to compute the enclosed area.
95
+
96
+ Adds an 'Areas' column to `self.peak_properties`.
97
+
98
+ Raises
99
+ ------
100
+ RuntimeError
101
+ If `peak_properties` or `data` has not been initialized.
102
+ """
103
+ if not hasattr(self, 'peak_properties') or self.peak_properties.empty:
104
+ raise RuntimeError("No peaks detected. Run `detect_peaks()` first.")
105
+
106
+ if not hasattr(self, 'data') or self.data.empty:
107
+ raise RuntimeError("Signal data is not initialized. Call `init_data()` first.")
108
+
109
+ # Compute cumulative integral of the signal
110
+ cumulative = cumulative_trapezoid(
111
+ self.data.Signal.values.quantity.magnitude,
112
+ x=self.data.Time.values.quantity.magnitude,
113
+ initial=0 # Include 0 at the start
114
+ )
115
+
116
+ # Interpolate cumulative integral at left and right interpolated positions
117
+ left_cum_integral = np.interp(
118
+ x=self.peak_properties.LeftIPs,
119
+ xp=self.data.index,
120
+ fp=cumulative
121
+ )
122
+ right_cum_integral = np.interp(
123
+ x=self.peak_properties.RightIPs,
124
+ xp=self.data.index,
125
+ fp=cumulative
126
+ )
127
+
128
+ # Compute areas under peaks
129
+ areas = right_cum_integral - left_cum_integral
130
+
131
+ # Add areas with units to peak properties
132
+ self.peak_properties['Areas'] = pint_pandas.PintArray(
133
+ areas, dtype=self.data.Signal.pint.units * self.data.Time.pint.units
134
+ )
135
+
136
+ def _add_to_ax(self, time_unit: str | Quantity, signal_unit: str | Quantity, ax: plt.Axes = None) -> None:
137
+ """
138
+ Plots the signal with detected peaks and FWHM lines.
139
+
140
+ Parameters
141
+ ----------
142
+ time_unit : str or Quantity
143
+ Unit for the time axis (e.g., 'microsecond').
144
+ signal_unit : str or Quantity
145
+ Unit for the signal axis (e.g., 'volt').
146
+ ax : plt.Axes
147
+ The matplotlib Axes to plot on. Creates a new figure if not provided.
148
+ """
149
+ # Plot vertical lines at peak times
150
+ for _, row in self.peak_properties.iterrows():
151
+ ax.axvline(row['PeakTimes'].to(time_unit), color='black', linestyle='-', lw=0.8)
152
+
153
+ self._add_custom_to_ax(ax=ax, time_unit=time_unit, signal_unit=signal_unit)
154
+
155
+ def _compute_peak_widths(self, peak_indices: list[int], values: np.ndarray) -> None:
156
+ # Compute peak properties
157
+ widths_samples, width_heights, left_ips, right_ips = peak_widths(
158
+ x=values,
159
+ peaks=peak_indices,
160
+ rel_height=self.rel_height
161
+ )
162
+
163
+ return widths_samples, width_heights, left_ips, right_ips
@@ -0,0 +1,108 @@
1
+ from dataclasses import dataclass
2
+ import matplotlib.pyplot as plt
3
+ import pandas as pd
4
+ import numpy as np
5
+ from scipy.signal import find_peaks
6
+ from FlowCyPy.peak_locator.base_class import BasePeakLocator
7
+ from FlowCyPy.units import Quantity, volt
8
+
9
+
10
+ @dataclass
11
+ class BasicPeakLocator(BasePeakLocator):
12
+ """
13
+ A basic peak detector class that identifies peaks in a signal using a threshold-based method.
14
+
15
+ Parameters
16
+ ----------
17
+ threshold : Quantity, optional
18
+ The minimum height required for a peak to be considered significant. Default is `Quantity(0.1, volt)`.
19
+ min_peak_distance : Quantity, optional
20
+ The minimum distance between detected peaks. Default is `Quantity(0.1)`.
21
+ rel_height : float, optional
22
+ The relative height at which the peak width is measured. Default is `0.5`.
23
+
24
+ """
25
+
26
+ threshold: Quantity = Quantity(0.0, volt)
27
+ rel_height: float = 0.5
28
+ min_peak_distance: Quantity = None
29
+
30
+ def init_data(self, dataframe: pd.DataFrame) -> None:
31
+ """
32
+ Initialize the data for peak detection.
33
+
34
+ Parameters
35
+ ----------
36
+ dataframe : pd.DataFrame
37
+ A DataFrame containing the signal data with columns 'Signal' and 'Time'.
38
+
39
+ Raises
40
+ ------
41
+ ValueError
42
+ If the DataFrame is missing required columns or is empty.
43
+ """
44
+ super().init_data(dataframe)
45
+
46
+ if self.threshold is not None:
47
+ self.threshold = self.threshold.to(self.data['Signal'].values.units)
48
+
49
+ if self.min_peak_distance is not None:
50
+ self.min_peak_distance = self.min_peak_distance.to(self.data['Time'].values.units)
51
+
52
+ def _compute_algorithm_specific_features(self) -> None:
53
+ """
54
+ Compute peaks based on the moving average algorithm.
55
+ """
56
+ peak_indices = self._compute_peak_positions()
57
+
58
+ widths_samples, width_heights, left_ips, right_ips = self._compute_peak_widths(
59
+ peak_indices,
60
+ self.data['Signal'].values
61
+ )
62
+
63
+ return peak_indices, widths_samples, width_heights, left_ips, right_ips
64
+
65
+ def _compute_peak_positions(self) -> pd.DataFrame:
66
+ """
67
+ Detects peaks in the signal and calculates their properties such as heights, widths, and areas.
68
+
69
+ Parameters
70
+ ----------
71
+ detector : pd.DataFrame
72
+ DataFrame with the signal data to detect peaks in.
73
+
74
+ Returns
75
+ -------
76
+ peak_times : Quantity
77
+ The times at which peaks occur.
78
+ heights : Quantity
79
+ The heights of the detected peaks.
80
+ widths : Quantity
81
+ The widths of the detected peaks.
82
+ areas : Quantity or None
83
+ The areas under each peak, if `compute_area` is True.
84
+ """
85
+ # Find peaks in the difference signal
86
+ peak_indices, _ = find_peaks(
87
+ self.data['Signal'].values,
88
+ height=None if self.threshold is None else self.threshold.magnitude,
89
+ distance=None if self.min_peak_distance is None else int(np.ceil(self.min_peak_distance / self.dt))
90
+ )
91
+
92
+ return peak_indices
93
+
94
+ def _add_custom_to_ax(self, time_unit: str | Quantity, signal_unit: str | Quantity, ax: plt.Axes = None) -> None:
95
+ """
96
+ Add algorithm-specific elements to the plot.
97
+
98
+ Parameters
99
+ ----------
100
+ time_unit : str or Quantity
101
+ The unit for the time axis (e.g., 'microsecond').
102
+ signal_unit : str or Quantity
103
+ The unit for the signal axis (e.g., 'volt').
104
+ ax : matplotlib.axes.Axes
105
+ The Axes object to add elements to.
106
+ """
107
+ # Plot the signal threshold line
108
+ ax.axhline(y=self.threshold.to(signal_unit).magnitude, color='black', linestyle='--', label='Threshold', lw=1)
@@ -0,0 +1,143 @@
1
+ from dataclasses import dataclass
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from scipy.signal import find_peaks
5
+ from FlowCyPy.peak_locator.base_class import BasePeakLocator
6
+ import pandas as pd
7
+ import pint_pandas
8
+ from FlowCyPy.units import Quantity, microsecond
9
+
10
+
11
+ @dataclass
12
+ class DerivativePeakLocator(BasePeakLocator):
13
+ """
14
+ Detects peaks in a signal using a derivative-based algorithm.
15
+ A peak is identified when the derivative exceeds a defined threshold.
16
+
17
+ Parameters
18
+ ----------
19
+ derivative_threshold : Quantity, optional
20
+ The minimum derivative value required to detect a peak.
21
+ Default is `Quantity(0.1)`.
22
+ min_peak_distance : Quantity, optional
23
+ The minimum distance between detected peaks.
24
+ Default is `Quantity(0.1)`.
25
+ rel_height : float, optional
26
+ The relative height at which the peak width is measured. Default is `0.5` (half-height).
27
+
28
+ """
29
+
30
+ derivative_threshold: Quantity = Quantity(0.1, 'volt/microsecond')
31
+ min_peak_distance: Quantity = Quantity(0.1, microsecond)
32
+ rel_height: float = 0.5
33
+
34
+ def init_data(self, dataframe: pd.DataFrame) -> None:
35
+ """
36
+ Initialize the data for peak detection.
37
+
38
+ Parameters
39
+ ----------
40
+ dataframe : pd.DataFrame
41
+ A DataFrame containing the signal data with columns 'Signal' and 'Time'.
42
+
43
+ Raises
44
+ ------
45
+ ValueError
46
+ If the DataFrame is missing required columns or is empty.
47
+ """
48
+ super().init_data(dataframe)
49
+
50
+ if self.derivative_threshold is not None:
51
+ self.derivative_threshold = self.derivative_threshold.to(
52
+ self.data['Signal'].values.units / self.data['Time'].values.units
53
+ )
54
+
55
+ if self.min_peak_distance is not None:
56
+ self.min_peak_distance = self.min_peak_distance.to(self.data['Time'].values.units)
57
+
58
+ def _compute_algorithm_specific_features(self) -> None:
59
+ """
60
+ Compute peaks based on the moving average algorithm.
61
+ """
62
+ peak_indices = self._compute_peak_positions()
63
+
64
+ widths_samples, width_heights, left_ips, right_ips = self._compute_peak_widths(
65
+ peak_indices,
66
+ self.data['Signal'].values
67
+ )
68
+
69
+ return peak_indices, widths_samples, width_heights, left_ips, right_ips
70
+
71
+ def _compute_peak_positions(self) -> None:
72
+ """
73
+ Compute peaks based on the derivative of the signal and refine their positions
74
+ to align with the actual maxima in the original signal.
75
+ """
76
+ # Compute the derivative of the signal
77
+ derivative = np.gradient(
78
+ self.data['Signal'].values.quantity.magnitude,
79
+ self.data['Time'].values.quantity.magnitude
80
+ )
81
+ derivative = pint_pandas.PintArray(
82
+ derivative, dtype=self.data['Signal'].values.units / self.data['Time'].values.units
83
+ )
84
+
85
+ # Add the derivative to the DataFrame
86
+ self.data['Derivative'] = derivative
87
+
88
+ # Detect peaks in the derivative signal
89
+ derivative_peak_indices, _ = find_peaks(
90
+ self.data['Derivative'].values.quantity.magnitude,
91
+ height=self.derivative_threshold.magnitude,
92
+ prominence=0.1, # Adjust this if needed
93
+ plateau_size=True,
94
+ distance=None if self.min_peak_distance is None else int(np.ceil(self.min_peak_distance / self.dt))
95
+ )
96
+
97
+ # Refine detected peaks to align with maxima in the original signal
98
+ refined_peak_indices = []
99
+ refinement_window = 5 # Number of samples around each derivative peak to search for the max
100
+
101
+ for idx in derivative_peak_indices:
102
+ # Define search window boundaries
103
+ window_start = max(0, idx - refinement_window)
104
+ window_end = min(len(self.data) - 1, idx + refinement_window)
105
+
106
+ # Find the maximum in the original signal within the window
107
+ true_max_idx = window_start + np.argmax(self.data['Signal'].iloc[window_start:window_end])
108
+ refined_peak_indices.append(true_max_idx)
109
+
110
+ refined_peak_indices = np.unique(refined_peak_indices) # Remove duplicates
111
+
112
+ return refined_peak_indices
113
+
114
+ def _add_custom_to_ax(self, time_unit: str | Quantity, signal_unit: str | Quantity, ax: plt.Axes) -> None:
115
+ """
116
+ Add algorithm-specific elements to the plot.
117
+
118
+ Parameters
119
+ ----------
120
+ time_unit : str or Quantity
121
+ The unit for the time axis (e.g., 'microsecond').
122
+ signal_unit : str or Quantity
123
+ The unit for the signal axis (e.g., 'volt').
124
+ ax : matplotlib.axes.Axes
125
+ The Axes object to add elements to.
126
+ """
127
+ # Plot the derivative
128
+ ax.plot(
129
+ self.data.Time,
130
+ self.data.Derivative,
131
+ linestyle='--',
132
+ color='C2',
133
+ label='Derivative'
134
+ )
135
+
136
+ # Plot the derivative threshold line
137
+ ax.axhline(
138
+ y=self.derivative_threshold.to(self.data['Derivative'].values.units).magnitude,
139
+ color='black',
140
+ linestyle='--',
141
+ label='Derivative Threshold',
142
+ lw=1
143
+ )
@@ -0,0 +1,166 @@
1
+ from dataclasses import dataclass
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from scipy.signal import find_peaks
5
+ from FlowCyPy.peak_locator.base_class import BasePeakLocator
6
+ import pandas as pd
7
+ import pint_pandas
8
+ from FlowCyPy.units import Quantity
9
+ from pint_pandas import PintArray
10
+ from typing import Tuple, Callable
11
+
12
+
13
+ @dataclass
14
+ class MovingAverage(BasePeakLocator):
15
+ """
16
+ Detects peaks in a signal using a moving average algorithm.
17
+ A peak is identified when the signal exceeds the moving average by a defined threshold.
18
+
19
+ Parameters
20
+ ----------
21
+ threshold : Quantity, optional
22
+ The minimum difference between the signal and its moving average required to detect a peak. Default is `Quantity(0.2)`.
23
+ window_size : Quantity, optional
24
+ The window size for calculating the moving average. Default is `Quantity(500)`.
25
+ min_peak_distance : Quantity, optional
26
+ The minimum distance between detected peaks. Default is `Quantity(0.1)`.
27
+ rel_height : float, optional
28
+ The relative height at which the peak width is measured. Default is `0.5` (half-height).
29
+
30
+ """
31
+
32
+ threshold: Quantity
33
+ window_size: Quantity
34
+ min_peak_distance: Quantity = None
35
+ rel_height: float = 0.1
36
+
37
+ def init_data(self, dataframe: pd.DataFrame) -> None:
38
+ """
39
+ Initialize the data for peak detection.
40
+
41
+ Parameters
42
+ ----------
43
+ dataframe : pd.DataFrame
44
+ A DataFrame containing the signal data with columns 'Signal' and 'Time'.
45
+
46
+ Raises
47
+ ------
48
+ ValueError
49
+ If the DataFrame is missing required columns or is empty.
50
+ """
51
+ super().init_data(dataframe)
52
+
53
+ if self.threshold is not None:
54
+ self.threshold = self.threshold.to(self.data['Signal'].values.units)
55
+
56
+ self.window_size = self.window_size.to(self.data['Time'].values.units)
57
+
58
+ if self.min_peak_distance is not None:
59
+ self.min_peak_distance = self.min_peak_distance.to(self.data['Time'].values.units)
60
+
61
+ def _compute_algorithm_specific_features(self) -> None:
62
+ """
63
+ Compute peaks based on the moving average algorithm.
64
+ """
65
+ peak_indices = self._compute_peak_positions()
66
+
67
+ widths_samples, width_heights, left_ips, right_ips = self._compute_peak_widths(peak_indices, self.data['Difference'].values)
68
+
69
+ return peak_indices, widths_samples, width_heights, left_ips, right_ips
70
+
71
+ def _compute_peak_positions(self) -> None:
72
+ # Calculate moving average
73
+ window_size_samples = int(np.ceil(self.window_size / self.dt))
74
+ moving_avg = self.data['Signal'].rolling(window=window_size_samples, center=True, min_periods=1).mean()
75
+
76
+ # Reattach Pint units to the moving average
77
+ moving_avg = pint_pandas.PintArray(moving_avg, dtype=self.data['Signal'].values.units)
78
+
79
+ # Add the moving average to the DataFrame
80
+ self.data['MovingAverage'] = moving_avg
81
+
82
+ # Compute the difference signal
83
+ self.data['Difference'] = self.data['Signal'] - self.data['MovingAverage']
84
+
85
+ # Detect peaks
86
+ peak_indices, _ = find_peaks(
87
+ self.data['Difference'].values.quantity.magnitude,
88
+ height=None if self.threshold is None else self.threshold.magnitude,
89
+ distance=None if self.min_peak_distance is None else int(np.ceil(self.min_peak_distance / self.dt))
90
+ )
91
+
92
+ return peak_indices
93
+
94
+ def _add_custom_to_ax(self, time_unit: str | Quantity, signal_unit: str | Quantity, ax: plt.Axes) -> None:
95
+ """
96
+ Add algorithm-specific elements to the plot.
97
+
98
+ Parameters
99
+ ----------
100
+ time_unit : str or Quantity
101
+ The unit for the time axis (e.g., 'microsecond').
102
+ signal_unit : str or Quantity
103
+ The unit for the signal axis (e.g., 'volt').
104
+ ax : matplotlib.axes.Axes
105
+ The Axes object to add elements to.
106
+ """
107
+ ax.plot(
108
+ self.data.Time,
109
+ self.data.Difference,
110
+ linestyle='--',
111
+ color='C1',
112
+ label='MA-difference'
113
+ )
114
+
115
+ # Plot the signal threshold line
116
+ ax.axhline(y=self.threshold.to(signal_unit).magnitude, color='black', linestyle='--', label='Threshold', lw=1)
117
+
118
+
119
+ def format_input(function: Callable) -> Callable:
120
+ def wrapper(self, dataframe: pd.DataFrame, y: str, x: str = None):
121
+ x = dataframe[x] if x is not None else dataframe.index
122
+
123
+ new_dataframe = pd.DataFrame(
124
+ data=dict(
125
+ x=x, y=dataframe[y]
126
+ )
127
+ )
128
+
129
+ return function(self=self, dataframe=new_dataframe)
130
+
131
+ return wrapper
132
+
133
+
134
+ @format_input
135
+ def process_data(self, dataframe: pd.DataFrame) -> Tuple[pd.DataFrame, pd.DataFrame]:
136
+ dt = dataframe.x[1] - dataframe.x[0]
137
+
138
+ window_size_samples = int(np.ceil(self.window_size / dt))
139
+
140
+
141
+ y_units = dataframe['y'].pint.units
142
+ x_units = dataframe['x'].pint.units
143
+
144
+ moving_avg = dataframe['y'].rolling(window=window_size_samples, center=True, min_periods=1).mean()
145
+
146
+ dataframe['MovingAverage'] = PintArray(moving_avg, y_units)
147
+
148
+ dataframe['Difference'] = dataframe['y'] - dataframe['MovingAverage']
149
+
150
+ peak_indices, meta = find_peaks(
151
+ x=dataframe['Difference'],
152
+ height=self.threshold.to(dataframe['Difference'].pint.units).magnitude,
153
+ distance=window_size_samples,
154
+ rel_height=0.1,
155
+ width=1
156
+ )
157
+
158
+ peak_dataframe = pd.DataFrame(
159
+ dict(
160
+ PeakPosition=dataframe['x'][peak_indices],
161
+ PeakHeight=PintArray(meta['peak_heights'], y_units),
162
+ PeakWidth=PintArray(meta['widths'], x_units),
163
+ )
164
+ )
165
+
166
+ return dataframe, peak_dataframe
@@ -0,0 +1,19 @@
1
+ from FlowCyPy.units import meter, joule, second, farad, kelvin, coulomb
2
+ import numpy as np
3
+
4
+
5
+ config_dict = dict(
6
+ arbitrary_types_allowed=True,
7
+ kw_only=True,
8
+ slots=True,
9
+ extra='forbid'
10
+ )
11
+
12
+
13
+ class PhysicalConstant:
14
+ h = 6.62607015e-34 * joule * second # Planck constant
15
+ c = 3e8 * meter / second # Speed of light
16
+ epsilon_0 = 8.8541878128e-12 * farad / meter # Permtivitty of vacuum
17
+ pi = np.pi # Pi, what else?
18
+ kb = 1.380649e-23 * joule / kelvin # Botlzmann constant
19
+ e = 1.602176634e-19 * coulomb # Electron charge