FlowCyPy 0.7.1__tar.gz → 0.7.3__tar.gz
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.
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/_version.py +2 -2
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/acquisition.py +74 -26
- flowcypy-0.7.3/FlowCyPy/classifier.py +182 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/cytometer.py +63 -24
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/detector.py +7 -64
- flowcypy-0.7.3/FlowCyPy/flow_cell.py +137 -0
- flowcypy-0.7.3/FlowCyPy/helper.py +89 -0
- flowcypy-0.7.3/FlowCyPy/noises.py +87 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/particle_count.py +3 -2
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/population.py +3 -4
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/scatterer_collection.py +7 -7
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/signal_digitizer.py +1 -3
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/source.py +4 -7
- flowcypy-0.7.3/FlowCyPy/utils.py +74 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy.egg-info/PKG-INFO +2 -2
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy.egg-info/SOURCES.txt +16 -4
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy.egg-info/requires.txt +1 -1
- {flowcypy-0.7.1 → flowcypy-0.7.3}/PKG-INFO +2 -2
- flowcypy-0.7.3/developments/Deep_peak_square.ipynb +1049 -0
- flowcypy-0.7.3/developments/Physics-informed_AI.ipynb +876 -0
- flowcypy-0.7.3/developments/ROI_analysis-Copy1.ipynb +639 -0
- flowcypy-0.7.3/developments/ROI_analysis.ipynb +778 -0
- flowcypy-0.7.3/developments/Untitled.ipynb +227 -0
- flowcypy-0.7.3/developments/Untitled1.ipynb +668 -0
- flowcypy-0.7.3/developments/Untitled2.ipynb +313 -0
- flowcypy-0.7.3/developments/ai_dev2.ipynb +1745 -0
- flowcypy-0.7.3/developments/best_model.h5 +0 -0
- flowcypy-0.7.3/developments/best_model.keras +0 -0
- flowcypy-0.7.3/developments/concentration_validation.py +100 -0
- flowcypy-0.7.3/developments/grad_cam_output.png +0 -0
- flowcypy-0.7.3/developments/model.png +0 -0
- flowcypy-0.7.3/developments/model_example.png +0 -0
- flowcypy-0.7.3/developments/scripts/AI_peak_detection.py +85 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/temp.py +18 -30
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/extras/flow_cytometer_signal.py +2 -2
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/extras/scatterer_distribution.py +3 -2
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/extras/signal_acquisition.py +1 -1
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/tutorials/limit_of_detection.py +1 -1
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/tutorials/workflow.py +2 -4
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/sg_execution_times.rst +15 -15
- {flowcypy-0.7.1 → flowcypy-0.7.3}/pyproject.toml +1 -1
- flowcypy-0.7.3/tests/test_classifiers.py +83 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_coupling_mechanism.py +1 -1
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_flow_cytometer.py +26 -8
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_peak_analyzer.py +1 -1
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_population.py +2 -2
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_scatterer_distribution.py +1 -1
- flowcypy-0.7.1/FlowCyPy/classifier.py +0 -210
- flowcypy-0.7.1/FlowCyPy/coupling_mechanism.py +0 -205
- flowcypy-0.7.1/FlowCyPy/flow_cell.py +0 -197
- flowcypy-0.7.1/FlowCyPy/helper.py +0 -166
- flowcypy-0.7.1/FlowCyPy/logger.py +0 -136
- flowcypy-0.7.1/FlowCyPy/noises.py +0 -34
- flowcypy-0.7.1/FlowCyPy/plottings.py +0 -269
- flowcypy-0.7.1/FlowCyPy/utils.py +0 -191
- flowcypy-0.7.1/tests/test_extra.py +0 -55
- {flowcypy-0.7.1 → flowcypy-0.7.3}/.flake8 +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/.github/dependabot.yml +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/.github/workflows/deploy_PyPi.yml +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/.github/workflows/deploy_anaconda.yml +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/.github/workflows/deploy_coverage.yml +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/.github/workflows/deploy_documentation.yml +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/.gitignore +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/__init__.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/__init__.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/empirical.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/mie.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/rayleigh.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/coupling_mechanism/uniform.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/directories.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/distribution/__init__.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/distribution/base_class.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/distribution/delta.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/distribution/lognormal.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/distribution/normal.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/distribution/particle_size_distribution.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/distribution/uniform.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/distribution/weibull.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/peak_locator/__init__.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/peak_locator/base_class.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/peak_locator/basic.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/peak_locator/derivative.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/peak_locator/moving_average.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/physical_constant.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/populations_instances.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy/units.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy.egg-info/dependency_links.txt +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/FlowCyPy.egg-info/top_level.txt +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/LICENSE +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/README.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/doc/canto_spec.md +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/doc/internship.pdf +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/get_started.md +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/image.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/output_file.prof +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/concentration_comparison.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/create_images.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/data_analysis.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/dev_beads_analysis.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/dev_canto.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/dev_classifier.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/dev_shot_noise_check.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/dev_stats_0.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/dev_stats_1.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/dev_stats_2.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/dev_study_on_ri.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/dev_study_on_size.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/mat2csv.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/scripts/profiler.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/developments/test.pdf +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/Makefile +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/extras/README.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/extras/distributions.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/noise_sources/README.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/noise_sources/dark_current.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/noise_sources/shot_noise.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/noise_sources/thermal.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/examples/tutorials/README.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/distributions/Delta.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/distributions/LogNormal.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/distributions/Normal.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/distributions/RosinRammler.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/distributions/Uniform.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/distributions/Weibull.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/example_0.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/example_1.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/example_2.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/example_3.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/flow_cytometer.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/images/logo.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/make.bat +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/_static/default.css +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/_static/logo.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/_static/thumbnail.png +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/code/base.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/code/detector.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/code/distributions.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/code/flow_cell.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/code/flow_cytometer.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/code/peak_locator.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/code/scatterer.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/code/source.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/code.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/conf.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/examples.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/index.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/core_components.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/getting_started.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/objectives/main.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/objectives/pre.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/objectives/stretch.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/prerequisites/index.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/prerequisites/mathematics.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/prerequisites/optics.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/prerequisites/programming.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/ressources.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal/tasks.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/internal.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/references.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/docs/source/theory.rst +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/meta.yaml +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/notebook.ipynb +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/setup.cfg +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/__init__.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_detector_noise.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_distribution.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_noises.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_peak_algorithm.py +0 -0
- {flowcypy-0.7.1 → flowcypy-0.7.3}/tests/test_source.py +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
import warnings
|
|
2
3
|
from typing import Optional, Union, List
|
|
3
4
|
from MPSPlots.styles import mps
|
|
4
5
|
import pandas as pd
|
|
@@ -9,8 +10,9 @@ from scipy.signal import find_peaks
|
|
|
9
10
|
import matplotlib.pyplot as plt
|
|
10
11
|
import seaborn as sns
|
|
11
12
|
from tabulate import tabulate
|
|
12
|
-
|
|
13
|
+
|
|
13
14
|
from FlowCyPy import helper
|
|
15
|
+
from FlowCyPy.classifier import BaseClassifier
|
|
14
16
|
|
|
15
17
|
class DataAccessor:
|
|
16
18
|
def __init__(self, outer):
|
|
@@ -94,13 +96,13 @@ class Acquisition:
|
|
|
94
96
|
results = results.reset_index(drop=True)
|
|
95
97
|
|
|
96
98
|
# Check for multiple peaks and issue a warning
|
|
97
|
-
peak_counts = results.groupby(['Detector', 'SegmentID']).size()
|
|
98
|
-
multiple_peak_segments = peak_counts[peak_counts > 1]
|
|
99
|
-
if not multiple_peak_segments.empty:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
# peak_counts = results.groupby(['Detector', 'SegmentID']).size()
|
|
100
|
+
# multiple_peak_segments = peak_counts[peak_counts > 1]
|
|
101
|
+
# if not multiple_peak_segments.empty:
|
|
102
|
+
# warnings.warn(
|
|
103
|
+
# f"Multiple peaks detected in the following segments: {multiple_peak_segments.index.tolist()}",
|
|
104
|
+
# UserWarning
|
|
105
|
+
# )
|
|
104
106
|
|
|
105
107
|
_temp = results.reset_index()[['Detector', 'SegmentID', 'Height']].pint.dequantify().droplevel('unit', axis=1)
|
|
106
108
|
|
|
@@ -172,22 +174,47 @@ class Acquisition:
|
|
|
172
174
|
post_buffer: int = 64,
|
|
173
175
|
max_triggers: int = None) -> None:
|
|
174
176
|
"""
|
|
175
|
-
|
|
177
|
+
Execute triggered acquisition analysis for signal data.
|
|
178
|
+
|
|
179
|
+
This method identifies segments of signal data based on a triggering threshold
|
|
180
|
+
and specified detector. It extracts segments of interest from the signal,
|
|
181
|
+
including a pre-trigger buffer and post-trigger buffer, and stores the results
|
|
182
|
+
in `self.data.triggered`.
|
|
176
183
|
|
|
177
184
|
Parameters
|
|
178
185
|
----------
|
|
179
186
|
threshold : units.Quantity
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
187
|
+
The threshold value for triggering. Only signal values exceeding this threshold
|
|
188
|
+
will be considered as trigger events.
|
|
189
|
+
trigger_detector_name : str
|
|
190
|
+
The name of the detector used for triggering. This determines which detector's
|
|
191
|
+
signal is analyzed for trigger events.
|
|
185
192
|
pre_buffer : int, optional
|
|
186
|
-
|
|
193
|
+
The number of points to include before the trigger point in each segment.
|
|
194
|
+
Default is 64.
|
|
187
195
|
post_buffer : int, optional
|
|
188
|
-
|
|
196
|
+
The number of points to include after the trigger point in each segment.
|
|
197
|
+
Default is 64.
|
|
189
198
|
max_triggers : int, optional
|
|
190
|
-
|
|
199
|
+
The maximum number of triggers to process. If None, all triggers will be processed.
|
|
200
|
+
Default is None.
|
|
201
|
+
|
|
202
|
+
Raises
|
|
203
|
+
------
|
|
204
|
+
ValueError
|
|
205
|
+
If the specified `trigger_detector_name` is not found in the dataset.
|
|
206
|
+
|
|
207
|
+
Warnings
|
|
208
|
+
--------
|
|
209
|
+
UserWarning
|
|
210
|
+
If no triggers are detected for the specified threshold, the method raises a warning
|
|
211
|
+
indicating that no signals met the criteria.
|
|
212
|
+
|
|
213
|
+
Notes
|
|
214
|
+
-----
|
|
215
|
+
- Triggered segments are stored in `self.data.triggered` as a pandas DataFrame with a hierarchical index on `['Detector', 'SegmentID']`.
|
|
216
|
+
- This method modifies `self.data.triggered` in place.
|
|
217
|
+
- The peak detection function `self.detect_peaks` is automatically called at the end of this method to analyze triggered segments.
|
|
191
218
|
"""
|
|
192
219
|
self.threshold = threshold
|
|
193
220
|
self.trigger_detector_name = trigger_detector_name
|
|
@@ -226,7 +253,28 @@ class Acquisition:
|
|
|
226
253
|
|
|
227
254
|
self.detect_peaks()
|
|
228
255
|
|
|
229
|
-
def classify_dataset(self, classifier:
|
|
256
|
+
def classify_dataset(self, classifier: BaseClassifier, features: List[str], detectors: list[str]) -> None:
|
|
257
|
+
"""
|
|
258
|
+
Classify the dataset using the specified classifier and features.
|
|
259
|
+
|
|
260
|
+
This method applies a classification algorithm to the dataset by first unstacking
|
|
261
|
+
the "Detector" level of the DataFrame's index. It then uses the provided classifier
|
|
262
|
+
object to classify the dataset based on the specified features and detectors.
|
|
263
|
+
|
|
264
|
+
Parameters
|
|
265
|
+
----------
|
|
266
|
+
classifier : BaseClassifier
|
|
267
|
+
An object implementing a `run` method for classification.
|
|
268
|
+
features : List[str]
|
|
269
|
+
A list of column names corresponding to the features to be used for classification (e.g., 'Height', 'Width', 'Area').
|
|
270
|
+
detectors : list[str]
|
|
271
|
+
A list of detector names to filter the data before classification. Only data from these detectors will be included in the classification process.
|
|
272
|
+
|
|
273
|
+
Returns
|
|
274
|
+
-------
|
|
275
|
+
None
|
|
276
|
+
This method updates the `self.data.peaks` attribute in place with the classified data.
|
|
277
|
+
"""
|
|
230
278
|
self.data.peaks = self.data.peaks.unstack('Detector')
|
|
231
279
|
self.classifier = classifier
|
|
232
280
|
|
|
@@ -464,7 +512,7 @@ class Acquisition:
|
|
|
464
512
|
zorder=0,
|
|
465
513
|
)
|
|
466
514
|
|
|
467
|
-
ax2.set_ylim(detector._saturation_levels)
|
|
515
|
+
ax2.set_ylim(detector._saturation_levels if detector._saturation_levels[0] != detector._saturation_levels[1] else None)
|
|
468
516
|
|
|
469
517
|
self._add_event_to_ax(ax=axes[-1], time_units=time_units)
|
|
470
518
|
|
|
@@ -489,7 +537,7 @@ class Acquisition:
|
|
|
489
537
|
ax.legend()
|
|
490
538
|
|
|
491
539
|
@helper.plot_sns
|
|
492
|
-
def coupling_distribution(self, x_detector: str, y_detector: str,
|
|
540
|
+
def coupling_distribution(self, x_detector: str, y_detector: str, bandwidth_adjust: float = 1) -> None:
|
|
493
541
|
"""
|
|
494
542
|
Plots the density distribution of optical coupling between two detector channels.
|
|
495
543
|
|
|
@@ -512,7 +560,7 @@ class Acquisition:
|
|
|
512
560
|
y = df[y_detector].pint.to(y_units)
|
|
513
561
|
|
|
514
562
|
with plt.style.context(mps):
|
|
515
|
-
grid = sns.jointplot(data=df, x=x, y=y, hue="Population", alpha=0.8)
|
|
563
|
+
grid = sns.jointplot(data=df, x=x, y=y, hue="Population", alpha=0.8, marginal_kws=dict(bw_adjust=bandwidth_adjust))
|
|
516
564
|
|
|
517
565
|
grid.ax_joint.set_xlabel(f"Signal {x_detector} [{x_units}]")
|
|
518
566
|
grid.ax_joint.set_ylabel(f"Signal {y_detector} [{y_units}]")
|
|
@@ -522,7 +570,7 @@ class Acquisition:
|
|
|
522
570
|
return grid
|
|
523
571
|
|
|
524
572
|
@helper.plot_sns
|
|
525
|
-
def scatterer(self, alpha: float = 0.8, bandwidth_adjust: float = 1,
|
|
573
|
+
def scatterer(self, alpha: float = 0.8, bandwidth_adjust: float = 1, color_palette: Optional[Union[str, dict]] = None) -> None:
|
|
526
574
|
"""
|
|
527
575
|
Visualizes the joint distribution of scatterer sizes and refractive indices using a Seaborn jointplot.
|
|
528
576
|
|
|
@@ -616,6 +664,7 @@ class Acquisition:
|
|
|
616
664
|
def trigger(self, show: bool = True) -> None:
|
|
617
665
|
"""Plot detected peaks on signal segments."""
|
|
618
666
|
n_plots = self.acquisition.n_detectors + 1
|
|
667
|
+
|
|
619
668
|
with plt.style.context(mps):
|
|
620
669
|
_, axes = plt.subplots(
|
|
621
670
|
nrows=n_plots,
|
|
@@ -628,12 +677,12 @@ class Acquisition:
|
|
|
628
677
|
|
|
629
678
|
time_units = self.acquisition.data.triggered['Time'].max().to_compact().units
|
|
630
679
|
|
|
631
|
-
for ax, (detector_name, group) in zip(axes, self.acquisition.data.triggered.groupby(level=
|
|
680
|
+
for ax, (detector_name, group) in zip(axes, self.acquisition.data.triggered.groupby(level='Detector')):
|
|
632
681
|
detector = self.get_detector(detector_name)
|
|
633
682
|
|
|
634
683
|
ax.set_ylabel(detector_name)
|
|
635
684
|
|
|
636
|
-
for _, sub_group in group.groupby(level=
|
|
685
|
+
for _, sub_group in group.groupby(level='SegmentID'):
|
|
637
686
|
x = sub_group['Time'].pint.to(time_units)
|
|
638
687
|
digitized = sub_group['DigitizedSignal']
|
|
639
688
|
ax.step(x, digitized, where='mid', linewidth=2)
|
|
@@ -661,7 +710,7 @@ class Acquisition:
|
|
|
661
710
|
ax2.legend()
|
|
662
711
|
|
|
663
712
|
|
|
664
|
-
for ax, (detector_name, group) in zip(axes, self.acquisition.data.peaks.groupby(level=
|
|
713
|
+
for ax, (detector_name, group) in zip(axes, self.acquisition.data.peaks.groupby(level='Detector')):
|
|
665
714
|
x = group['Time'].pint.to(time_units)
|
|
666
715
|
y = group['Height']
|
|
667
716
|
ax.scatter(x, y, color='C1')
|
|
@@ -700,7 +749,6 @@ class Acquisition:
|
|
|
700
749
|
|
|
701
750
|
# Set the plotting style
|
|
702
751
|
with plt.style.context(mps):
|
|
703
|
-
# Generate a scatter plot using seaborn's jointplot
|
|
704
752
|
grid = sns.jointplot(
|
|
705
753
|
data=self.acquisition.data.peaks,
|
|
706
754
|
x=(feature, x_detector),
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
from sklearn.cluster import KMeans
|
|
2
|
+
from sklearn.cluster import DBSCAN
|
|
3
|
+
from sklearn.mixture import GaussianMixture
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from typing import Dict, Tuple
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseClassifier:
|
|
9
|
+
def filter_dataframe(self, dataframe: pd.DataFrame, features: list, detectors: list = None) -> object:
|
|
10
|
+
"""
|
|
11
|
+
Filter the DataFrame based on the selected features and detectors.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
features : list
|
|
16
|
+
List of features to use for filtering. Options include 'Heights', 'Widths', 'Areas'.
|
|
17
|
+
detectors : list, optional
|
|
18
|
+
List of detectors to use. If None, use all detectors.
|
|
19
|
+
|
|
20
|
+
Returns
|
|
21
|
+
-------
|
|
22
|
+
DataFrame
|
|
23
|
+
A filtered DataFrame containing only the selected detectors and features.
|
|
24
|
+
|
|
25
|
+
Raises
|
|
26
|
+
------
|
|
27
|
+
ValueError
|
|
28
|
+
If no matching features are found for the given detectors and features.
|
|
29
|
+
"""
|
|
30
|
+
# Determine detectors to use
|
|
31
|
+
|
|
32
|
+
if detectors is None:
|
|
33
|
+
detectors = dataframe.columns.get_level_values(1).unique().tolist()
|
|
34
|
+
|
|
35
|
+
return dataframe.loc[:, (features, detectors)]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class KmeansClassifier(BaseClassifier):
|
|
39
|
+
def __init__(self, number_of_cluster: int) -> None:
|
|
40
|
+
"""
|
|
41
|
+
Initialize the Classifier.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
dataframe : DataFrame
|
|
46
|
+
The input dataframe with multi-index columns.
|
|
47
|
+
"""
|
|
48
|
+
self.number_of_cluster = number_of_cluster
|
|
49
|
+
|
|
50
|
+
def run(self, dataframe: pd.DataFrame, features: list = ['Height'], detectors: list = None, random_state: int = 42) -> pd.DataFrame:
|
|
51
|
+
"""
|
|
52
|
+
Run KMeans clustering on the selected features and detectors.
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
dataframe : pd.DataFrame
|
|
57
|
+
The input DataFrame with multi-index (e.g., by 'Detector').
|
|
58
|
+
features : list
|
|
59
|
+
List of features to use for clustering. Options include 'Height', 'Width', 'Area'.
|
|
60
|
+
detectors : list, optional
|
|
61
|
+
List of detectors to use. If None, use all detectors.
|
|
62
|
+
random_state : int, optional
|
|
63
|
+
Random state for KMeans, by default 42.
|
|
64
|
+
|
|
65
|
+
Returns
|
|
66
|
+
-------
|
|
67
|
+
pd.DataFrame
|
|
68
|
+
DataFrame with clustering labels added.
|
|
69
|
+
"""
|
|
70
|
+
# Filter the DataFrame
|
|
71
|
+
sub_dataframe = self.filter_dataframe(dataframe=dataframe, features=features, detectors=detectors)
|
|
72
|
+
|
|
73
|
+
# Ensure data is dequantified if it uses Pint quantities
|
|
74
|
+
if hasattr(sub_dataframe, 'pint'):
|
|
75
|
+
sub_dataframe = sub_dataframe.pint.dequantify().droplevel('unit', axis=1)
|
|
76
|
+
|
|
77
|
+
# Run KMeans
|
|
78
|
+
kmeans = KMeans(n_clusters=self.number_of_cluster, random_state=random_state)
|
|
79
|
+
labels = kmeans.fit_predict(sub_dataframe)
|
|
80
|
+
|
|
81
|
+
dataframe['Label'] = labels
|
|
82
|
+
|
|
83
|
+
return labels
|
|
84
|
+
|
|
85
|
+
class GaussianMixtureClassifier(BaseClassifier):
|
|
86
|
+
def __init__(self, number_of_components: int) -> None:
|
|
87
|
+
"""
|
|
88
|
+
Initialize the Gaussian Mixture Classifier.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
number_of_components : int
|
|
93
|
+
Number of Gaussian components (clusters) to use for the model.
|
|
94
|
+
"""
|
|
95
|
+
self.number_of_components = number_of_components
|
|
96
|
+
|
|
97
|
+
def run(self, dataframe: pd.DataFrame, features: list = ['Height'], detectors: list = None, random_state: int = 42) -> pd.DataFrame:
|
|
98
|
+
"""
|
|
99
|
+
Run Gaussian Mixture Model (GMM) clustering on the selected features and detectors.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
dataframe : pd.DataFrame
|
|
104
|
+
The input DataFrame with multi-index (e.g., by 'Detector').
|
|
105
|
+
features : list
|
|
106
|
+
List of features to use for clustering. Options include 'Height', 'Width', 'Area'.
|
|
107
|
+
detectors : list, optional
|
|
108
|
+
List of detectors to use. If None, use all detectors.
|
|
109
|
+
random_state : int, optional
|
|
110
|
+
Random state for reproducibility, by default 42.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
pd.DataFrame
|
|
115
|
+
DataFrame with clustering labels added.
|
|
116
|
+
"""
|
|
117
|
+
# Filter the DataFrame
|
|
118
|
+
sub_dataframe = self.filter_dataframe(dataframe=dataframe, features=features, detectors=detectors)
|
|
119
|
+
|
|
120
|
+
# Ensure data is dequantified if it uses Pint quantities
|
|
121
|
+
if hasattr(sub_dataframe, 'pint'):
|
|
122
|
+
sub_dataframe = sub_dataframe.pint.dequantify().droplevel('unit', axis=1)
|
|
123
|
+
|
|
124
|
+
# Run Gaussian Mixture Model
|
|
125
|
+
gmm = GaussianMixture(n_components=self.number_of_components, random_state=random_state)
|
|
126
|
+
labels = gmm.fit_predict(sub_dataframe)
|
|
127
|
+
|
|
128
|
+
# Add labels to the original DataFrame
|
|
129
|
+
dataframe['Label'] = labels
|
|
130
|
+
|
|
131
|
+
return labels
|
|
132
|
+
|
|
133
|
+
class DBSCANClassifier(BaseClassifier):
|
|
134
|
+
def __init__(self, epsilon: float = 0.5, min_samples: int = 5) -> None:
|
|
135
|
+
"""
|
|
136
|
+
Initialize the DBSCAN Classifier.
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
epsilon : float, optional
|
|
141
|
+
The maximum distance between two samples for them to be considered as neighbors.
|
|
142
|
+
Default is 0.5.
|
|
143
|
+
min_samples : int, optional
|
|
144
|
+
The number of samples in a neighborhood for a point to be considered a core point.
|
|
145
|
+
Default is 5.
|
|
146
|
+
"""
|
|
147
|
+
self.epsilon = epsilon
|
|
148
|
+
self.min_samples = min_samples
|
|
149
|
+
|
|
150
|
+
def run(self, dataframe: pd.DataFrame, features: list = ['Height'], detectors: list = None) -> pd.DataFrame:
|
|
151
|
+
"""
|
|
152
|
+
Run DBSCAN clustering on the selected features and detectors.
|
|
153
|
+
|
|
154
|
+
Parameters
|
|
155
|
+
----------
|
|
156
|
+
dataframe : pd.DataFrame
|
|
157
|
+
The input DataFrame with multi-index (e.g., by 'Detector').
|
|
158
|
+
features : list
|
|
159
|
+
List of features to use for clustering. Options include 'Height', 'Width', 'Area'.
|
|
160
|
+
detectors : list, optional
|
|
161
|
+
List of detectors to use. If None, use all detectors.
|
|
162
|
+
|
|
163
|
+
Returns
|
|
164
|
+
-------
|
|
165
|
+
pd.DataFrame
|
|
166
|
+
DataFrame with clustering labels added. Noise points are labeled as -1.
|
|
167
|
+
"""
|
|
168
|
+
# Filter the DataFrame
|
|
169
|
+
sub_dataframe = self.filter_dataframe(dataframe=dataframe, features=features, detectors=detectors)
|
|
170
|
+
|
|
171
|
+
# Ensure data is dequantified if it uses Pint quantities
|
|
172
|
+
if hasattr(sub_dataframe, 'pint'):
|
|
173
|
+
sub_dataframe = sub_dataframe.pint.dequantify().droplevel('unit', axis=1)
|
|
174
|
+
|
|
175
|
+
# Run DBSCAN
|
|
176
|
+
dbscan = DBSCAN(eps=self.epsilon, min_samples=self.min_samples)
|
|
177
|
+
labels = dbscan.fit_predict(sub_dataframe)
|
|
178
|
+
|
|
179
|
+
# Add labels to the original DataFrame
|
|
180
|
+
dataframe['Label'] = labels
|
|
181
|
+
|
|
182
|
+
return labels
|
|
@@ -4,14 +4,13 @@
|
|
|
4
4
|
import logging
|
|
5
5
|
import numpy as np
|
|
6
6
|
from typing import List, Callable, Optional
|
|
7
|
-
from MPSPlots.styles import mps
|
|
8
|
-
from FlowCyPy.flow_cell import FlowCell
|
|
9
|
-
from FlowCyPy.detector import Detector
|
|
10
7
|
import pandas as pd
|
|
11
|
-
import
|
|
8
|
+
from pint_pandas import PintArray
|
|
9
|
+
|
|
12
10
|
from FlowCyPy import units
|
|
13
11
|
from FlowCyPy.units import Quantity, milliwatt
|
|
14
|
-
from
|
|
12
|
+
from FlowCyPy.flow_cell import FlowCell
|
|
13
|
+
from FlowCyPy.detector import Detector
|
|
15
14
|
from FlowCyPy.acquisition import Acquisition
|
|
16
15
|
from FlowCyPy.signal_digitizer import SignalDigitizer
|
|
17
16
|
|
|
@@ -117,33 +116,73 @@ class FlowCytometer:
|
|
|
117
116
|
medium_refractive_index=self.scatterer_collection.medium_refractive_index
|
|
118
117
|
)
|
|
119
118
|
|
|
120
|
-
scatterer_dataframe[detector.name] =
|
|
119
|
+
scatterer_dataframe[detector.name] = PintArray(self.coupling_power, dtype=self.coupling_power.units)
|
|
121
120
|
|
|
122
121
|
def _generate_pulse_parameters(self, scatterer_dataframe: pd.DataFrame) -> None:
|
|
123
|
-
"""
|
|
122
|
+
r"""
|
|
124
123
|
Generates and assigns random Gaussian pulse parameters for each particle event.
|
|
125
124
|
|
|
126
|
-
The
|
|
127
|
-
- Centers: The time at which each pulse occurs.
|
|
128
|
-
- Widths: The standard deviation (spread) of each pulse in seconds.
|
|
125
|
+
The pulse shape follows the Gaussian beam’s spatial intensity profile:
|
|
129
126
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
.. math::
|
|
128
|
+
|
|
129
|
+
I(r) = I_0 \exp\left(-\frac{2r^2}{w_0^2}\right),
|
|
130
|
+
|
|
131
|
+
where :math:`w_0` is the beam waist (the :math:`1/e^2` radius of the intensity distribution).
|
|
132
|
+
This profile can be rewritten in standard Gaussian form:
|
|
133
|
+
|
|
134
|
+
.. math::
|
|
135
|
+
|
|
136
|
+
I(r) = I_0 \exp\left(-\frac{r^2}{2\sigma_x^2}\right),
|
|
139
137
|
|
|
140
|
-
|
|
138
|
+
which implies the spatial standard deviation:
|
|
141
139
|
|
|
142
|
-
|
|
140
|
+
.. math::
|
|
143
141
|
|
|
144
|
-
|
|
142
|
+
\sigma_x = \frac{w_0}{2}.
|
|
145
143
|
|
|
146
|
-
|
|
144
|
+
When a particle moves at a constant flow speed :math:`v`, the spatial coordinate :math:`r`
|
|
145
|
+
is related to time :math:`t` via :math:`r = v t`. Substituting this into the intensity profile
|
|
146
|
+
gives a temporal Gaussian:
|
|
147
|
+
|
|
148
|
+
.. math::
|
|
149
|
+
|
|
150
|
+
I(t) = I_0 \exp\left(-\frac{2 (v t)^2}{w_0^2}\right).
|
|
151
|
+
|
|
152
|
+
This is equivalent to a Gaussian in time:
|
|
153
|
+
|
|
154
|
+
.. math::
|
|
155
|
+
|
|
156
|
+
I(t) = I_0 \exp\left(-\frac{t^2}{2\sigma_t^2}\right),
|
|
157
|
+
|
|
158
|
+
so that the temporal standard deviation is:
|
|
159
|
+
|
|
160
|
+
.. math::
|
|
161
|
+
|
|
162
|
+
\sigma_t = \frac{\sigma_x}{v} = \frac{w_0}{2v}.
|
|
163
|
+
|
|
164
|
+
The full width at half maximum (FWHM) in time is then:
|
|
165
|
+
|
|
166
|
+
.. math::
|
|
167
|
+
|
|
168
|
+
\text{FWHM} = 2\sqrt{2 \ln2} \, \sigma_t = \frac{w_0}{v} \sqrt{2 \ln2}.
|
|
169
|
+
|
|
170
|
+
**Generated Parameters:**
|
|
171
|
+
- **Centers:** The time at which each pulse occurs (randomly determined).
|
|
172
|
+
- **Widths:** The pulse width (:math:`\sigma_t`) in seconds, computed as :math:`w_0 / (2 v)`.
|
|
173
|
+
|
|
174
|
+
**Effects**
|
|
175
|
+
-----------
|
|
176
|
+
Modifies `scatterer_dataframe` in place by adding:
|
|
177
|
+
- A `'Centers'` column with the pulse center times.
|
|
178
|
+
- A `'Widths'` column with the computed pulse widths.
|
|
179
|
+
"""
|
|
180
|
+
# Calculate the pulse width (standard deviation in time, σₜ) based on the beam waist and flow speed.
|
|
181
|
+
pulse_width = self.source.waist / (2 * self.flow_cell.flow_speed)
|
|
182
|
+
|
|
183
|
+
widths = pulse_width * np.ones(len(scatterer_dataframe))
|
|
184
|
+
|
|
185
|
+
scatterer_dataframe['Widths'] = PintArray(widths, dtype=widths.units)
|
|
147
186
|
|
|
148
187
|
def initialize_signal(self, run_time: Quantity) -> None:
|
|
149
188
|
"""
|
|
@@ -221,11 +260,11 @@ class FlowCytometer:
|
|
|
221
260
|
time = self.dataframe.xs(detector.name)['Time'].pint.magnitude
|
|
222
261
|
|
|
223
262
|
time_grid = np.expand_dims(time, axis=0) * units.second
|
|
224
|
-
|
|
225
263
|
centers = np.expand_dims(_centers, axis=1) * units.second
|
|
226
264
|
widths = np.expand_dims(_widths, axis=1) * units.second
|
|
227
265
|
|
|
228
266
|
# Compute the Gaussian for each height, center, and width using broadcasting
|
|
267
|
+
# To be noted that widths is defined as: waist / (2 * flow_speed)
|
|
229
268
|
power_gaussians = _coupling_power[:, np.newaxis] * np.exp(- (time_grid - centers) ** 2 / (2 * widths ** 2))
|
|
230
269
|
|
|
231
270
|
total_power = np.sum(power_gaussians, axis=0) + self.background_power
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from copy import copy
|
|
1
3
|
import numpy as np
|
|
2
4
|
import pandas as pd
|
|
3
5
|
from typing import Optional, Union
|
|
4
6
|
import matplotlib.pyplot as plt
|
|
5
|
-
from FlowCyPy import units
|
|
6
|
-
from FlowCyPy.units import AU, volt, watt, degree, ampere, coulomb, particle, meter
|
|
7
|
-
from FlowCyPy.utils import PropertiesReport
|
|
8
7
|
from pydantic.dataclasses import dataclass
|
|
9
8
|
from pydantic import field_validator
|
|
10
9
|
import pint_pandas
|
|
11
|
-
|
|
10
|
+
|
|
12
11
|
from PyMieSim.units import Quantity
|
|
12
|
+
from FlowCyPy import units
|
|
13
|
+
from FlowCyPy.units import AU, volt, watt, degree, ampere, coulomb
|
|
13
14
|
from FlowCyPy.noises import NoiseSetting
|
|
14
|
-
from FlowCyPy.helper import plot_helper
|
|
15
15
|
from FlowCyPy.peak_locator import BasePeakLocator
|
|
16
|
-
import logging
|
|
17
|
-
from copy import copy
|
|
18
16
|
from FlowCyPy.signal_digitizer import SignalDigitizer
|
|
17
|
+
from FlowCyPy.physical_constant import PhysicalConstant
|
|
19
18
|
|
|
20
19
|
config_dict = dict(
|
|
21
20
|
arbitrary_types_allowed=True,
|
|
@@ -26,7 +25,7 @@ config_dict = dict(
|
|
|
26
25
|
|
|
27
26
|
|
|
28
27
|
@dataclass(config=config_dict, unsafe_hash=True)
|
|
29
|
-
class Detector(
|
|
28
|
+
class Detector():
|
|
30
29
|
"""
|
|
31
30
|
A class representing a signal detector used in flow cytometry.
|
|
32
31
|
|
|
@@ -337,62 +336,6 @@ class Detector(PropertiesReport):
|
|
|
337
336
|
|
|
338
337
|
return digitized_signal
|
|
339
338
|
|
|
340
|
-
@plot_helper
|
|
341
|
-
def plot_raw(
|
|
342
|
-
self,
|
|
343
|
-
ax: Optional[plt.Axes] = None,
|
|
344
|
-
time_unit: Optional[Union[str, Quantity]] = None,
|
|
345
|
-
signal_unit: Optional[Union[str, Quantity]] = None,
|
|
346
|
-
add_peak_locator: bool = False
|
|
347
|
-
) -> None:
|
|
348
|
-
"""
|
|
349
|
-
Visualizes the signal and optional components (peaks, raw signal) over time.
|
|
350
|
-
|
|
351
|
-
This method generates a customizable plot of the processed signal as a function of time.
|
|
352
|
-
Additional components like raw signals and detected peaks can also be overlaid.
|
|
353
|
-
|
|
354
|
-
Parameters
|
|
355
|
-
----------
|
|
356
|
-
ax : matplotlib.axes.Axes, optional
|
|
357
|
-
An existing Matplotlib Axes object to plot on. If None, a new Axes will be created.
|
|
358
|
-
time_unit : str or Quantity, optional
|
|
359
|
-
Desired unit for the time axis. If None, defaults to the most compact unit of the `Time` column.
|
|
360
|
-
signal_unit : str or Quantity, optional
|
|
361
|
-
Desired unit for the signal axis. If None, defaults to the most compact unit of the `Signal` column.
|
|
362
|
-
add_peak_locator : bool, optional
|
|
363
|
-
If True, adds the detected peaks (if available) to the plot. Default is False.
|
|
364
|
-
|
|
365
|
-
Returns
|
|
366
|
-
-------
|
|
367
|
-
tuple[Quantity, Quantity]
|
|
368
|
-
A tuple containing the units used for the time and signal axes, respectively.
|
|
369
|
-
|
|
370
|
-
Notes
|
|
371
|
-
-----
|
|
372
|
-
- The `Time` and `Signal` data are automatically converted to the specified units for consistency.
|
|
373
|
-
- If no `ax` is provided, a new figure and axis will be generated.
|
|
374
|
-
- Warnings are logged if peak locator data is unavailable when `add_peak_locator` is True.
|
|
375
|
-
"""
|
|
376
|
-
# Set default units if not provided
|
|
377
|
-
signal_unit = signal_unit or self.dataframe['Signal'].max().to_compact().units
|
|
378
|
-
time_unit = time_unit or self.dataframe['Time'].max().to_compact().units
|
|
379
|
-
|
|
380
|
-
x = self.dataframe['Time'].pint.to(time_unit)
|
|
381
|
-
|
|
382
|
-
ax.plot(x, self.dataframe['Signal'].pint.to(signal_unit), color='C1', linestyle='--', label=f'{self.name}: Raw', linewidth=1)
|
|
383
|
-
ax.legend(loc='upper right')
|
|
384
|
-
|
|
385
|
-
# Overlay peak locator positions, if requested
|
|
386
|
-
if add_peak_locator:
|
|
387
|
-
if not hasattr(self, 'algorithm'):
|
|
388
|
-
logging.warning("The detector does not have a peak locator algorithm. Peaks cannot be plotted.")
|
|
389
|
-
|
|
390
|
-
self.algorithm._add_to_ax(ax=ax, signal_unit=signal_unit, time_unit=time_unit)
|
|
391
|
-
|
|
392
|
-
# Customize labels
|
|
393
|
-
ax.set_xlabel(f"Time [{time_unit:P}]")
|
|
394
|
-
ax.set_ylabel(f"{self.name} [{signal_unit:P}]")
|
|
395
|
-
|
|
396
339
|
def set_peak_locator(self, algorithm: BasePeakLocator, compute_peak_area: bool = True) -> None:
|
|
397
340
|
"""
|
|
398
341
|
Assigns a peak detection algorithm to the detector, analyzes the signal,
|