FlowCyPy 0.7.3__tar.gz → 0.7.4__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.3 → flowcypy-0.7.4}/FlowCyPy/_version.py +2 -2
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/acquisition.py +84 -26
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/cytometer.py +7 -5
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/flow_cell.py +6 -3
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/helper.py +43 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/PKG-INFO +1 -1
- {flowcypy-0.7.3 → flowcypy-0.7.4}/PKG-INFO +1 -1
- flowcypy-0.7.4/developments/scripts/temp.py +70 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/flow_cytometer_signal.py +12 -8
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_coupling_mechanism.py +2 -2
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_population.py +1 -1
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_scatterer_distribution.py +2 -2
- flowcypy-0.7.3/developments/scripts/temp.py +0 -207
- {flowcypy-0.7.3 → flowcypy-0.7.4}/.flake8 +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/dependabot.yml +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/workflows/deploy_PyPi.yml +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/workflows/deploy_anaconda.yml +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/workflows/deploy_coverage.yml +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/.github/workflows/deploy_documentation.yml +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/.gitignore +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/__init__.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/classifier.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/__init__.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/empirical.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/mie.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/rayleigh.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/coupling_mechanism/uniform.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/detector.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/directories.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/__init__.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/base_class.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/delta.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/lognormal.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/normal.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/particle_size_distribution.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/uniform.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/distribution/weibull.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/noises.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/particle_count.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/__init__.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/base_class.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/basic.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/derivative.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/peak_locator/moving_average.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/physical_constant.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/population.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/populations_instances.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/scatterer_collection.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/signal_digitizer.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/source.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/units.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy/utils.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/SOURCES.txt +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/dependency_links.txt +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/requires.txt +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/FlowCyPy.egg-info/top_level.txt +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/LICENSE +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/README.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Deep_peak_square.ipynb +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Physics-informed_AI.ipynb +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/ROI_analysis-Copy1.ipynb +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/ROI_analysis.ipynb +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Untitled.ipynb +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Untitled1.ipynb +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/Untitled2.ipynb +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/ai_dev2.ipynb +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/best_model.h5 +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/best_model.keras +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/concentration_validation.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/doc/canto_spec.md +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/doc/internship.pdf +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/get_started.md +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/grad_cam_output.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/image.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/model.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/model_example.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/output_file.prof +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/AI_peak_detection.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/concentration_comparison.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/create_images.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/data_analysis.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_beads_analysis.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_canto.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_classifier.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_shot_noise_check.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_stats_0.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_stats_1.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_stats_2.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_study_on_ri.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/dev_study_on_size.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/mat2csv.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/scripts/profiler.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/developments/test.pdf +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/Makefile +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/README.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/distributions.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/scatterer_distribution.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/extras/signal_acquisition.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/noise_sources/README.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/noise_sources/dark_current.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/noise_sources/shot_noise.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/noise_sources/thermal.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/tutorials/README.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/tutorials/limit_of_detection.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/examples/tutorials/workflow.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/Delta.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/LogNormal.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/Normal.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/RosinRammler.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/Uniform.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/distributions/Weibull.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/example_0.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/example_1.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/example_2.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/example_3.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/flow_cytometer.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/images/logo.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/make.bat +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/_static/default.css +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/_static/logo.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/_static/thumbnail.png +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/base.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/detector.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/distributions.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/flow_cell.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/flow_cytometer.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/peak_locator.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/scatterer.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code/source.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/code.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/conf.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/examples.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/index.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/core_components.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/getting_started.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/objectives/main.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/objectives/pre.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/objectives/stretch.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/prerequisites/index.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/prerequisites/mathematics.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/prerequisites/optics.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/prerequisites/programming.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/ressources.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal/tasks.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/internal.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/references.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/sg_execution_times.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/docs/source/theory.rst +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/meta.yaml +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/notebook.ipynb +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/pyproject.toml +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/setup.cfg +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/__init__.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_classifiers.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_detector_noise.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_distribution.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_flow_cytometer.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_noises.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_peak_algorithm.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_peak_analyzer.py +0 -0
- {flowcypy-0.7.3 → flowcypy-0.7.4}/tests/test_source.py +0 -0
|
@@ -465,21 +465,32 @@ class Acquisition:
|
|
|
465
465
|
def __init__(self, acquisition: object):
|
|
466
466
|
self.acquisition = acquisition
|
|
467
467
|
|
|
468
|
-
def signals(
|
|
468
|
+
def signals(
|
|
469
|
+
self,
|
|
470
|
+
figure_size: tuple = (10, 6),
|
|
471
|
+
show: bool = True,
|
|
472
|
+
show_populations: str | List[str] = None,
|
|
473
|
+
save_filename: str = None
|
|
474
|
+
) -> None:
|
|
469
475
|
"""
|
|
470
476
|
Visualizes raw signals for all detector channels and the scatterer distribution.
|
|
471
477
|
|
|
472
478
|
Parameters
|
|
473
479
|
----------
|
|
474
480
|
figure_size : tuple, optional
|
|
475
|
-
Size of the plot (default: (10, 6)).
|
|
476
|
-
add_peak_locator : bool, optional
|
|
477
|
-
Adds peak location markers to the signals if True (default: False).
|
|
481
|
+
Size of the plot in inches (default: (10, 6)).
|
|
478
482
|
show : bool, optional
|
|
479
|
-
|
|
483
|
+
If True, displays the plot immediately (default: True).
|
|
484
|
+
show_populations : str or list of str, optional
|
|
485
|
+
List of population names to highlight in the event plot. If None, shows all populations.
|
|
486
|
+
save_filename : str, optional
|
|
487
|
+
If provided, saves the figure to the specified file.
|
|
480
488
|
"""
|
|
481
|
-
|
|
489
|
+
# Handle `show_populations` default case
|
|
490
|
+
if show_populations is None:
|
|
491
|
+
show_populations = self.acquisition.data.scatterer.index.get_level_values('Population').unique()
|
|
482
492
|
|
|
493
|
+
n_plots = self.acquisition.n_detectors + 1 # One extra plot for events
|
|
483
494
|
time_units = self.acquisition.data.continuous.Time.max().to_compact().units
|
|
484
495
|
|
|
485
496
|
with plt.style.context(mps):
|
|
@@ -488,52 +499,99 @@ class Acquisition:
|
|
|
488
499
|
nrows=n_plots,
|
|
489
500
|
figsize=figure_size,
|
|
490
501
|
sharex=True,
|
|
491
|
-
height_ratios=[1] * (n_plots - 1) + [0.5]
|
|
502
|
+
height_ratios=[1] * (n_plots - 1) + [0.5]
|
|
492
503
|
)
|
|
493
504
|
|
|
505
|
+
# Plot digitized and continuous signals for each detector
|
|
494
506
|
for ax, (detector_name, group) in zip(axes[:-1], self.acquisition.data.continuous.groupby("Detector")):
|
|
495
507
|
detector = self.get_detector(detector_name)
|
|
496
508
|
|
|
497
|
-
|
|
509
|
+
# Convert time and signal data to the appropriate units
|
|
510
|
+
time_data = group["Time"].pint.to(time_units)
|
|
511
|
+
digitized_signal = group["DigitizedSignal"]
|
|
512
|
+
|
|
513
|
+
# Plot digitized signal
|
|
514
|
+
ax.step(time_data, digitized_signal, label="Digitized Signal", where='mid')
|
|
498
515
|
ax.set_ylabel(detector_name)
|
|
499
516
|
ax.set_ylim([0, self.acquisition.cytometer.signal_digitizer._bit_depth])
|
|
500
517
|
|
|
518
|
+
# Twin axis for continuous signal
|
|
501
519
|
ax2 = ax.twinx()
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
ax2_y_units = ax2_y.max().to_compact().units
|
|
505
|
-
ax2.plot(
|
|
506
|
-
ax2_x.pint.to(time_units),
|
|
507
|
-
ax2_y.pint.to(ax2_y_units),
|
|
508
|
-
color='black',
|
|
509
|
-
linewidth=1,
|
|
510
|
-
linestyle='-',
|
|
511
|
-
label='Continuous signal',
|
|
512
|
-
zorder=0,
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
ax2.set_ylim(detector._saturation_levels if detector._saturation_levels[0] != detector._saturation_levels[1] else None)
|
|
520
|
+
cont_time, cont_signal, cont_signal_units = self._get_continuous_signal(detector_name, time_units)
|
|
521
|
+
ax2.plot(cont_time, cont_signal, color='black', linewidth=1, linestyle='-', label='Continuous Signal', zorder=0)
|
|
516
522
|
|
|
517
|
-
|
|
523
|
+
# Set y-limits for the continuous signal
|
|
524
|
+
if detector._saturation_levels[0] != detector._saturation_levels[1]:
|
|
525
|
+
ax2.set_ylim(detector._saturation_levels)
|
|
518
526
|
|
|
527
|
+
# Add event markers to the last subplot
|
|
528
|
+
self._add_event_to_ax(ax=axes[-1], time_units=time_units, show_populations=show_populations)
|
|
519
529
|
axes[-1].set_xlabel(f"Time [{time_units}]")
|
|
530
|
+
|
|
531
|
+
# Save or show the figure
|
|
532
|
+
if save_filename:
|
|
533
|
+
fig.savefig(fname=save_filename)
|
|
520
534
|
if show:
|
|
521
535
|
plt.show()
|
|
522
536
|
|
|
523
|
-
|
|
537
|
+
|
|
538
|
+
def _get_continuous_signal(self, detector_name: str, time_units: units.Quantity):
|
|
539
|
+
"""
|
|
540
|
+
Retrieves and converts the continuous signal data for a given detector.
|
|
541
|
+
|
|
542
|
+
Parameters
|
|
543
|
+
----------
|
|
544
|
+
detector_name : str
|
|
545
|
+
Name of the detector.
|
|
546
|
+
time_units : units.Quantity
|
|
547
|
+
Desired time units.
|
|
548
|
+
|
|
549
|
+
Returns
|
|
550
|
+
-------
|
|
551
|
+
tuple
|
|
552
|
+
(time array, signal array, signal units)
|
|
553
|
+
"""
|
|
554
|
+
data = self.acquisition.data.continuous.loc[detector_name]
|
|
555
|
+
cont_time = data['Time'].pint.to(time_units)
|
|
556
|
+
cont_signal = data['Signal']
|
|
557
|
+
cont_signal_units = cont_signal.max().to_compact().units
|
|
558
|
+
return cont_time, cont_signal.pint.to(cont_signal_units), cont_signal_units
|
|
559
|
+
|
|
560
|
+
def _add_event_to_ax(
|
|
561
|
+
self,
|
|
562
|
+
ax: plt.Axes,
|
|
563
|
+
time_units: units.Quantity,
|
|
564
|
+
palette: str = 'tab10',
|
|
565
|
+
show_populations: str | List[str] = None
|
|
566
|
+
) -> None:
|
|
567
|
+
"""
|
|
568
|
+
Adds vertical markers for event occurrences in the scatterer data.
|
|
569
|
+
|
|
570
|
+
Parameters
|
|
571
|
+
----------
|
|
572
|
+
ax : plt.Axes
|
|
573
|
+
The matplotlib axis to modify.
|
|
574
|
+
time_units : units.Quantity
|
|
575
|
+
Time units to use for plotting.
|
|
576
|
+
palette : str, optional
|
|
577
|
+
Color palette for different populations (default: 'tab10').
|
|
578
|
+
show_populations : str or list of str, optional
|
|
579
|
+
Populations to display. If None, all populations are shown.
|
|
580
|
+
"""
|
|
581
|
+
# Get unique population names
|
|
524
582
|
unique_populations = self.acquisition.data.scatterer.index.get_level_values('Population').unique()
|
|
525
583
|
color_mapping = dict(zip(unique_populations, sns.color_palette(palette, len(unique_populations))))
|
|
526
584
|
|
|
527
585
|
for population_name, group in self.acquisition.data.scatterer.groupby('Population'):
|
|
586
|
+
if show_populations is not None and population_name not in show_populations:
|
|
587
|
+
continue
|
|
528
588
|
x = group.Time.pint.to(time_units)
|
|
529
589
|
color = color_mapping[population_name]
|
|
530
590
|
ax.vlines(x, ymin=0, ymax=1, transform=ax.get_xaxis_transform(), label=population_name, color=color)
|
|
531
591
|
|
|
532
592
|
ax.tick_params(axis='y', left=False, labelleft=False)
|
|
533
|
-
|
|
534
593
|
ax.get_yaxis().set_visible(False)
|
|
535
594
|
ax.set_xlabel(f"Time [{time_units}]")
|
|
536
|
-
|
|
537
595
|
ax.legend()
|
|
538
596
|
|
|
539
597
|
@helper.plot_sns
|
|
@@ -13,6 +13,7 @@ from FlowCyPy.flow_cell import FlowCell
|
|
|
13
13
|
from FlowCyPy.detector import Detector
|
|
14
14
|
from FlowCyPy.acquisition import Acquisition
|
|
15
15
|
from FlowCyPy.signal_digitizer import SignalDigitizer
|
|
16
|
+
from FlowCyPy.helper import validate_units
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
# Set up logging configuration
|
|
@@ -87,7 +88,7 @@ class FlowCytometer:
|
|
|
87
88
|
for detector in detectors:
|
|
88
89
|
detector.cytometer = self
|
|
89
90
|
|
|
90
|
-
def
|
|
91
|
+
def _run_coupling_analysis(self, scatterer_dataframe: pd.DataFrame) -> None:
|
|
91
92
|
"""
|
|
92
93
|
Computes and assigns the optical coupling power for each particle-detection event.
|
|
93
94
|
|
|
@@ -184,7 +185,7 @@ class FlowCytometer:
|
|
|
184
185
|
|
|
185
186
|
scatterer_dataframe['Widths'] = PintArray(widths, dtype=widths.units)
|
|
186
187
|
|
|
187
|
-
def
|
|
188
|
+
def _initialize_signal(self, run_time: Quantity) -> None:
|
|
188
189
|
"""
|
|
189
190
|
Initializes the raw signal for each detector based on the source and flow cell configuration.
|
|
190
191
|
|
|
@@ -209,6 +210,7 @@ class FlowCytometer:
|
|
|
209
210
|
|
|
210
211
|
self.dataframe.index.names = ["Detector", "Index"]
|
|
211
212
|
|
|
213
|
+
@validate_units(run_time=units.second)
|
|
212
214
|
def get_acquisition(self, run_time: Quantity) -> None:
|
|
213
215
|
"""
|
|
214
216
|
Simulates the generation of optical signal pulses for each particle event.
|
|
@@ -231,13 +233,13 @@ class FlowCytometer:
|
|
|
231
233
|
if not run_time.check('second'):
|
|
232
234
|
raise ValueError(f"flow_speed must be in meter per second, but got {run_time.units}")
|
|
233
235
|
|
|
234
|
-
self.
|
|
236
|
+
self._initialize_signal(run_time=run_time)
|
|
235
237
|
|
|
236
|
-
scatterer_dataframe = self.flow_cell.
|
|
238
|
+
scatterer_dataframe = self.flow_cell._generate_event_dataframe(self.scatterer_collection.populations, run_time=run_time)
|
|
237
239
|
|
|
238
240
|
self.scatterer_collection.fill_dataframe_with_sampling(scatterer_dataframe)
|
|
239
241
|
|
|
240
|
-
self.
|
|
242
|
+
self._run_coupling_analysis(scatterer_dataframe)
|
|
241
243
|
|
|
242
244
|
self._generate_pulse_parameters(scatterer_dataframe)
|
|
243
245
|
|
|
@@ -12,6 +12,7 @@ from FlowCyPy.population import Population
|
|
|
12
12
|
from FlowCyPy.scatterer_collection import ScattererCollection
|
|
13
13
|
from FlowCyPy.units import meter, particle, Quantity
|
|
14
14
|
from FlowCyPy import units
|
|
15
|
+
from FlowCyPy.helper import validate_units
|
|
15
16
|
|
|
16
17
|
config_dict = dict(
|
|
17
18
|
arbitrary_types_allowed=True,
|
|
@@ -21,6 +22,7 @@ config_dict = dict(
|
|
|
21
22
|
)
|
|
22
23
|
|
|
23
24
|
|
|
25
|
+
|
|
24
26
|
@dataclass(config=config_dict)
|
|
25
27
|
class FlowCell:
|
|
26
28
|
"""
|
|
@@ -63,12 +65,14 @@ class FlowCell:
|
|
|
63
65
|
raise ValueError(f"flow_area must be in meter ** 2, but got {value.units}")
|
|
64
66
|
return value
|
|
65
67
|
|
|
68
|
+
@validate_units(run_time=units.second)
|
|
66
69
|
def get_volume(self, run_time: Quantity) -> Quantity:
|
|
67
70
|
"""
|
|
68
71
|
Computes the volume passing through the flow cell over the given run time.
|
|
69
72
|
"""
|
|
70
|
-
return self.
|
|
73
|
+
return (self.volume_flow * run_time).to_compact()
|
|
71
74
|
|
|
75
|
+
@validate_units(run_time=units.second)
|
|
72
76
|
def get_population_sampling(self, run_time: Quantity, scatterer_collection: ScattererCollection) -> list[Quantity]:
|
|
73
77
|
"""
|
|
74
78
|
Calculates the number of events (or particle counts) for each population based on the run time.
|
|
@@ -82,12 +86,11 @@ class FlowCell:
|
|
|
82
86
|
for p in scatterer_collection.populations
|
|
83
87
|
]
|
|
84
88
|
|
|
85
|
-
def
|
|
89
|
+
def _generate_event_dataframe(self, populations: List[Population], run_time: Quantity) -> pd.DataFrame:
|
|
86
90
|
"""
|
|
87
91
|
Generates a DataFrame of event times for each population based on the specified scheme.
|
|
88
92
|
"""
|
|
89
93
|
# Generate individual DataFrames for each population
|
|
90
|
-
|
|
91
94
|
population_event_frames = [
|
|
92
95
|
self._generate_poisson_events(population=population, run_time=run_time)
|
|
93
96
|
for population in populations
|
|
@@ -2,6 +2,49 @@ from typing import Callable
|
|
|
2
2
|
import matplotlib.pyplot as plt
|
|
3
3
|
from MPSPlots.styles import mps
|
|
4
4
|
|
|
5
|
+
from functools import wraps
|
|
6
|
+
import inspect
|
|
7
|
+
from FlowCyPy.units import Quantity
|
|
8
|
+
|
|
9
|
+
def validate_units(**expected_units):
|
|
10
|
+
"""
|
|
11
|
+
Decorator to enforce that function arguments of type Quantity have the correct units.
|
|
12
|
+
|
|
13
|
+
Parameters
|
|
14
|
+
----------
|
|
15
|
+
expected_units : dict
|
|
16
|
+
A dictionary where keys are argument names and values are the expected Pint units.
|
|
17
|
+
|
|
18
|
+
Raises
|
|
19
|
+
------
|
|
20
|
+
ValueError
|
|
21
|
+
If any argument does not have the expected unit.
|
|
22
|
+
"""
|
|
23
|
+
def decorator(func):
|
|
24
|
+
@wraps(func)
|
|
25
|
+
def wrapper(*args, **kwargs):
|
|
26
|
+
# Get function signature and argument values
|
|
27
|
+
sig = inspect.signature(func)
|
|
28
|
+
bound_args = sig.bind(*args, **kwargs)
|
|
29
|
+
bound_args.apply_defaults()
|
|
30
|
+
|
|
31
|
+
for arg_name, expected_unit in expected_units.items():
|
|
32
|
+
if arg_name in bound_args.arguments:
|
|
33
|
+
value = bound_args.arguments[arg_name]
|
|
34
|
+
|
|
35
|
+
# Check if the value is a Pint Quantity
|
|
36
|
+
if not isinstance(value, Quantity):
|
|
37
|
+
raise TypeError(f"Argument '{arg_name}' must be a Pint Quantity, but got {type(value)}")
|
|
38
|
+
|
|
39
|
+
# Check if the argument has the expected units
|
|
40
|
+
if not value.check(value.units):
|
|
41
|
+
raise ValueError(f"Argument '{arg_name}' must have units of {expected_unit}, but got {value.units}")
|
|
42
|
+
|
|
43
|
+
return func(*bound_args.args, **bound_args.kwargs)
|
|
44
|
+
|
|
45
|
+
return wrapper
|
|
46
|
+
return decorator
|
|
47
|
+
|
|
5
48
|
|
|
6
49
|
def plot_sns(function: Callable) -> Callable:
|
|
7
50
|
"""
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from FlowCyPy import FlowCytometer, ScattererCollection, Detector, GaussianBeam, FlowCell
|
|
2
|
+
from FlowCyPy import distribution, Population
|
|
3
|
+
from FlowCyPy.signal_digitizer import SignalDigitizer
|
|
4
|
+
from FlowCyPy import units
|
|
5
|
+
|
|
6
|
+
source = GaussianBeam(
|
|
7
|
+
numerical_aperture=0.4 * units.AU, # Numerical aperture: 0.4
|
|
8
|
+
wavelength=1550 * units.nanometer, # Wavelength: 1550 nm
|
|
9
|
+
optical_power=200 * units.milliwatt # Optical power: 2 mW
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
flow_cell = FlowCell(
|
|
13
|
+
source=source,
|
|
14
|
+
volume_flow=10 * units.microliter / units.second, # Flow speed: 10 microliter per second
|
|
15
|
+
flow_area=(40 * units.micrometer) ** 2, # Flow area: 40 x 40 micrometers
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
LP_concentration = 40_000
|
|
19
|
+
|
|
20
|
+
scatterer_collection = ScattererCollection(medium_refractive_index=1.33 * units.RIU)
|
|
21
|
+
|
|
22
|
+
population_0 = Population(
|
|
23
|
+
name='EV',
|
|
24
|
+
particle_count=1e+9 * units.particle / units.milliliter,
|
|
25
|
+
size=distribution.RosinRammler(characteristic_size=200 * units.nanometer, spread=4.5),
|
|
26
|
+
refractive_index=distribution.Normal(mean=1.42 * units.RIU, std_dev=0.05 * units.RIU)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
population_1 = Population(
|
|
30
|
+
name='LP',
|
|
31
|
+
particle_count=LP_concentration * 1e+9 * units.particle / units.milliliter,
|
|
32
|
+
size=distribution.RosinRammler(characteristic_size=100 * units.nanometer, spread=4.5),
|
|
33
|
+
refractive_index=distribution.Normal(mean=1.39 * units.RIU, std_dev=0.05 * units.RIU)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
scatterer_collection.add_population(population_1, population_0)
|
|
37
|
+
|
|
38
|
+
scatterer_collection.dilute(100)
|
|
39
|
+
|
|
40
|
+
signal_digitizer = SignalDigitizer(
|
|
41
|
+
bit_depth=1024,
|
|
42
|
+
saturation_levels='auto',
|
|
43
|
+
sampling_freq=10 * units.megahertz, # Sampling frequency: 10 MHz
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
detector_fsc = Detector(
|
|
47
|
+
name='FSC', # Forward Scatter detector
|
|
48
|
+
numerical_aperture=1.2 * units.AU, # Numerical aperture: 0.2
|
|
49
|
+
phi_angle=0 * units.degree, # Angle: 180 degrees for forward scatter
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
detector_ssc = Detector(
|
|
53
|
+
name='SSC', # Side Scatter detector
|
|
54
|
+
numerical_aperture=1.2 * units.AU, # Numerical aperture: 0.2
|
|
55
|
+
phi_angle=90 * units.degree, # Angle: 90 degrees for side scatter
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
cytometer = FlowCytometer(
|
|
59
|
+
signal_digitizer=signal_digitizer,
|
|
60
|
+
scatterer_collection=scatterer_collection,
|
|
61
|
+
flow_cell=flow_cell,
|
|
62
|
+
detectors=[detector_fsc, detector_ssc], # Detectors: FSC and SSC
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
acquisition = cytometer.get_acquisition(run_time=0.2 * units.millisecond)
|
|
66
|
+
|
|
67
|
+
acquisition.plot.signals(
|
|
68
|
+
show_populations=['EV'],
|
|
69
|
+
# save_filename=f'LP_{LP_concentration}.png'
|
|
70
|
+
)
|
|
@@ -45,19 +45,23 @@ scatterer_collection = ScattererCollection(medium_refractive_index=1.33 * units.
|
|
|
45
45
|
|
|
46
46
|
population_0 = Population(
|
|
47
47
|
name='EV',
|
|
48
|
-
particle_count=1e+9 * units.particle / units.milliliter,
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
# particle_count=1e+9 * units.particle / units.milliliter,
|
|
49
|
+
particle_count=10 * units.particle,
|
|
50
|
+
size=distribution.RosinRammler(characteristic_size=200 * units.nanometer, spread=4.5),
|
|
51
|
+
refractive_index=distribution.Normal(mean=1.42 * units.RIU, std_dev=0.05 * units.RIU)
|
|
51
52
|
)
|
|
52
53
|
|
|
53
54
|
population_1 = Population(
|
|
54
55
|
name='LP',
|
|
55
|
-
particle_count=1e+9 * units.particle / units.milliliter,
|
|
56
|
-
|
|
57
|
-
|
|
56
|
+
# particle_count=1e+9 * units.particle / units.milliliter,
|
|
57
|
+
particle_count=50_000 * units.particle,
|
|
58
|
+
size=distribution.RosinRammler(characteristic_size=100 * units.nanometer, spread=4.5),
|
|
59
|
+
refractive_index=distribution.Normal(mean=1.39 * units.RIU, std_dev=0.05 * units.RIU)
|
|
58
60
|
)
|
|
59
61
|
|
|
60
|
-
scatterer_collection.add_population(
|
|
62
|
+
scatterer_collection.add_population(population_1, population_0)
|
|
63
|
+
|
|
64
|
+
# scatterer_collection.dilute(100)
|
|
61
65
|
|
|
62
66
|
# Step 5: Set up the detectors
|
|
63
67
|
# ----------------------------
|
|
@@ -99,7 +103,7 @@ cytometer = FlowCytometer(
|
|
|
99
103
|
acquisition = cytometer.get_acquisition(run_time=0.2 * units.millisecond)
|
|
100
104
|
|
|
101
105
|
# Visualize the scatter signals from both detectors
|
|
102
|
-
acquisition.plot.signals()
|
|
106
|
+
acquisition.plot.signals(show_populations=['EV'])
|
|
103
107
|
|
|
104
108
|
# %%
|
|
105
109
|
#
|
|
@@ -83,7 +83,7 @@ def test_generate_scatterer_size(scatterer_collection, default_flow_cell):
|
|
|
83
83
|
"""
|
|
84
84
|
Test if the sizes are generated correctly in the ScattererCollection.
|
|
85
85
|
"""
|
|
86
|
-
scatterer_dataframe = default_flow_cell.
|
|
86
|
+
scatterer_dataframe = default_flow_cell._generate_event_dataframe(
|
|
87
87
|
scatterer_collection.populations,
|
|
88
88
|
run_time=0.001 * units.second
|
|
89
89
|
)
|
|
@@ -101,7 +101,7 @@ def test_rayleigh_mechanism_output(detector, scatterer_collection, source, defau
|
|
|
101
101
|
"""
|
|
102
102
|
Test the detected power output of the Rayleigh scattering mechanism.
|
|
103
103
|
"""
|
|
104
|
-
scatterer_dataframe = default_flow_cell.
|
|
104
|
+
scatterer_dataframe = default_flow_cell._generate_event_dataframe(
|
|
105
105
|
scatterer_collection.populations,
|
|
106
106
|
run_time=0.001 * units.second
|
|
107
107
|
)
|
|
@@ -62,7 +62,7 @@ def scatterer_collection(populations):
|
|
|
62
62
|
|
|
63
63
|
@pytest.fixture
|
|
64
64
|
def population_dataframe(flow_cell, populations):
|
|
65
|
-
dataframe = flow_cell.
|
|
65
|
+
dataframe = flow_cell._generate_event_dataframe(populations, run_time=RUN_TIME)
|
|
66
66
|
|
|
67
67
|
return dataframe
|
|
68
68
|
|
|
@@ -53,7 +53,7 @@ def test_generate_distribution_size(distribution, default_flow_cell):
|
|
|
53
53
|
|
|
54
54
|
scatterer_collection.add_population(population_0)
|
|
55
55
|
|
|
56
|
-
dataframe = default_flow_cell.
|
|
56
|
+
dataframe = default_flow_cell._generate_event_dataframe(
|
|
57
57
|
scatterer_collection.populations,
|
|
58
58
|
run_time=100e-4 * units.second
|
|
59
59
|
)
|
|
@@ -115,7 +115,7 @@ def test_generate_longitudinal_positions(default_flow_cell, distribution):
|
|
|
115
115
|
|
|
116
116
|
scatterer_collection.add_population(population_0)
|
|
117
117
|
|
|
118
|
-
dataframe = default_flow_cell.
|
|
118
|
+
dataframe = default_flow_cell._generate_event_dataframe(
|
|
119
119
|
scatterer_collection.populations,
|
|
120
120
|
run_time=100e-4 * units.second
|
|
121
121
|
)
|