dropdrop 1.1.0__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.
@@ -0,0 +1,22 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ publish:
9
+ runs-on: ubuntu-latest
10
+ permissions:
11
+ id-token: write
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Install uv
16
+ uses: astral-sh/setup-uv@v4
17
+
18
+ - name: Build package
19
+ run: uv build
20
+
21
+ - name: Publish to PyPI
22
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,13 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # Cache
13
+ .cache/
@@ -0,0 +1 @@
1
+ 3.12
dropdrop-1.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Oleksii Stroganov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: dropdrop
3
+ Version: 1.1.0
4
+ Summary: Python pipeline script for detecting droplets with beads and other inclusions via cellpose
5
+ License-Expression: MIT
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.12
8
+ Requires-Dist: cellpose>=4.0.6
9
+ Requires-Dist: matplotlib>=3.10.6
10
+ Requires-Dist: numpy>=2.3.3
11
+ Requires-Dist: opencv-python>=4.11.0.86
12
+ Requires-Dist: pandas>=2.3.3
13
+ Requires-Dist: scipy>=1.16.2
14
+ Requires-Dist: seaborn>=0.13.2
15
+ Requires-Dist: tqdm>=4.67.1
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest>=8.0; extra == 'dev'
18
+ Description-Content-Type: text/markdown
19
+
20
+ # DropDrop
21
+
22
+ Automated Python pipeline for detecting droplets and inclusions (beads) in microscopy z-stacks using Cellpose segmentation and morphological analysis.
23
+
24
+ Tailored for the EVOS M5000 Imaging System.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ # Using uv (recommended)
30
+ uv pip install dropdrop
31
+
32
+ # Using pip
33
+ pip install dropdrop
34
+ ```
35
+
36
+ ### From source
37
+
38
+ ```bash
39
+ git clone https://github.com/yourusername/dropdrop.git
40
+ cd dropdrop
41
+ uv pip install -e .
42
+ ```
43
+
44
+ ## Quick Start
45
+
46
+ ```bash
47
+ # Run with interactive prompts
48
+ dropdrop ./images
49
+
50
+ # Run with settings
51
+ dropdrop ./images --settings "d=1000,p=on,l=experiment1"
52
+
53
+ # Process only first 5 frames (for testing)
54
+ dropdrop ./images -n 5
55
+ ```
56
+
57
+ ## Usage
58
+
59
+ ### Basic Commands
60
+
61
+ ```bash
62
+ # Run pipeline with compact settings
63
+ dropdrop ./images --settings "d=1000,p=on,c=6.5e5,l=experiment1"
64
+
65
+ # Custom output directory
66
+ dropdrop ./images ./results/my_project --settings "d=500"
67
+
68
+ # Interactive viewer (view results after processing)
69
+ dropdrop ./images --view
70
+
71
+ # Interactive editor (manually correct inclusions)
72
+ dropdrop ./images --interactive
73
+
74
+ # Archive output as tar.gz
75
+ dropdrop ./images -z
76
+ ```
77
+
78
+ ### Settings Format
79
+
80
+ Compact settings string: `d=dilution,p=poisson,c=count,l=label`
81
+
82
+ | Key | Full name | Description | Default |
83
+ |-----|-----------|-------------|---------|
84
+ | `d` | `dilution` | Dilution factor | 500 |
85
+ | `p` | `poisson` | Enable Poisson analysis (on/off) | on |
86
+ | `c` | `count` | Stock bead count per uL | 6.5e5 |
87
+ | `l` | `label` | Project label for output naming | None |
88
+
89
+ ### Cache Control
90
+
91
+ ```bash
92
+ dropdrop ./images --no-cache # Disable caching
93
+ dropdrop ./images --clear-cache # Clear cache before run
94
+ ```
95
+
96
+ ## Interactive Editor
97
+
98
+ The editor allows manual correction of detected inclusions:
99
+
100
+ | Key | Action |
101
+ |-----|--------|
102
+ | Left-click | Add inclusion |
103
+ | Right-click (hold) | Remove inclusions |
104
+ | `s` | Toggle droplet selection (hover over droplet) |
105
+ | `u` | Undo last action |
106
+ | `c` | Clear all inclusions in frame |
107
+ | `d` | Toggle droplet visibility |
108
+ | Arrow keys / Space | Navigate frames |
109
+ | `q` / Esc | Exit |
110
+
111
+ Disabled droplets (gray with X) are excluded from the final results.
112
+
113
+ ## Output Structure
114
+
115
+ ```
116
+ results/<YYYYMMDD>_<label>/
117
+ data.csv # Raw detection data
118
+ summary.txt # Settings and statistics
119
+ size_distribution.png # Droplet diameter histogram
120
+ poisson_comparison.png # Bead distribution vs theoretical
121
+ ```
122
+
123
+ ### data.csv columns
124
+
125
+ | Column | Description |
126
+ |--------|-------------|
127
+ | `frame` | Frame index |
128
+ | `droplet_id` | Droplet ID within frame |
129
+ | `center_x`, `center_y` | Droplet center coordinates (px) |
130
+ | `diameter_px`, `diameter_um` | Droplet diameter |
131
+ | `area_px`, `area_um2` | Droplet area |
132
+ | `inclusions` | Number of inclusions detected |
133
+
134
+ ## Configuration
135
+
136
+ Create `config.json` in your working directory to customize detection parameters:
137
+
138
+ ```json
139
+ {
140
+ "cellpose_flow_threshold": 0.4,
141
+ "cellpose_cellprob_threshold": 0.0,
142
+ "erosion_pixels": 5,
143
+ "kernel_size": 7,
144
+ "tophat_threshold": 30,
145
+ "min_inclusion_area": 7,
146
+ "max_inclusion_area": 50,
147
+ "edge_buffer": 5,
148
+ "min_droplet_diameter": 80,
149
+ "max_droplet_diameter": 200,
150
+ "px_to_um": 1.14,
151
+ "cache": {
152
+ "enabled": true,
153
+ "max_frames": 100
154
+ }
155
+ }
156
+ ```
157
+
158
+ ### Parameters
159
+
160
+ | Parameter | Description |
161
+ |-----------|-------------|
162
+ | `cellpose_flow_threshold` | Cellpose flow threshold for segmentation |
163
+ | `cellpose_cellprob_threshold` | Cellpose cell probability threshold |
164
+ | `erosion_pixels` | Pixels to erode droplet mask before inclusion detection |
165
+ | `kernel_size` | Morphological kernel size for black-hat transform |
166
+ | `tophat_threshold` | Threshold for inclusion detection |
167
+ | `min/max_inclusion_area` | Inclusion size constraints (px) |
168
+ | `edge_buffer` | Buffer from image edge to ignore inclusions |
169
+ | `min/max_droplet_diameter` | Droplet size constraints (px) |
170
+ | `px_to_um` | Pixel to micrometer conversion factor |
171
+
172
+ ## Requirements
173
+
174
+ - Python 3.12+
175
+ - CUDA-capable GPU (recommended for Cellpose)
176
+
177
+ ## License
178
+
179
+ MIT
@@ -0,0 +1,160 @@
1
+ # DropDrop
2
+
3
+ Automated Python pipeline for detecting droplets and inclusions (beads) in microscopy z-stacks using Cellpose segmentation and morphological analysis.
4
+
5
+ Tailored for the EVOS M5000 Imaging System.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ # Using uv (recommended)
11
+ uv pip install dropdrop
12
+
13
+ # Using pip
14
+ pip install dropdrop
15
+ ```
16
+
17
+ ### From source
18
+
19
+ ```bash
20
+ git clone https://github.com/yourusername/dropdrop.git
21
+ cd dropdrop
22
+ uv pip install -e .
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```bash
28
+ # Run with interactive prompts
29
+ dropdrop ./images
30
+
31
+ # Run with settings
32
+ dropdrop ./images --settings "d=1000,p=on,l=experiment1"
33
+
34
+ # Process only first 5 frames (for testing)
35
+ dropdrop ./images -n 5
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ### Basic Commands
41
+
42
+ ```bash
43
+ # Run pipeline with compact settings
44
+ dropdrop ./images --settings "d=1000,p=on,c=6.5e5,l=experiment1"
45
+
46
+ # Custom output directory
47
+ dropdrop ./images ./results/my_project --settings "d=500"
48
+
49
+ # Interactive viewer (view results after processing)
50
+ dropdrop ./images --view
51
+
52
+ # Interactive editor (manually correct inclusions)
53
+ dropdrop ./images --interactive
54
+
55
+ # Archive output as tar.gz
56
+ dropdrop ./images -z
57
+ ```
58
+
59
+ ### Settings Format
60
+
61
+ Compact settings string: `d=dilution,p=poisson,c=count,l=label`
62
+
63
+ | Key | Full name | Description | Default |
64
+ |-----|-----------|-------------|---------|
65
+ | `d` | `dilution` | Dilution factor | 500 |
66
+ | `p` | `poisson` | Enable Poisson analysis (on/off) | on |
67
+ | `c` | `count` | Stock bead count per uL | 6.5e5 |
68
+ | `l` | `label` | Project label for output naming | None |
69
+
70
+ ### Cache Control
71
+
72
+ ```bash
73
+ dropdrop ./images --no-cache # Disable caching
74
+ dropdrop ./images --clear-cache # Clear cache before run
75
+ ```
76
+
77
+ ## Interactive Editor
78
+
79
+ The editor allows manual correction of detected inclusions:
80
+
81
+ | Key | Action |
82
+ |-----|--------|
83
+ | Left-click | Add inclusion |
84
+ | Right-click (hold) | Remove inclusions |
85
+ | `s` | Toggle droplet selection (hover over droplet) |
86
+ | `u` | Undo last action |
87
+ | `c` | Clear all inclusions in frame |
88
+ | `d` | Toggle droplet visibility |
89
+ | Arrow keys / Space | Navigate frames |
90
+ | `q` / Esc | Exit |
91
+
92
+ Disabled droplets (gray with X) are excluded from the final results.
93
+
94
+ ## Output Structure
95
+
96
+ ```
97
+ results/<YYYYMMDD>_<label>/
98
+ data.csv # Raw detection data
99
+ summary.txt # Settings and statistics
100
+ size_distribution.png # Droplet diameter histogram
101
+ poisson_comparison.png # Bead distribution vs theoretical
102
+ ```
103
+
104
+ ### data.csv columns
105
+
106
+ | Column | Description |
107
+ |--------|-------------|
108
+ | `frame` | Frame index |
109
+ | `droplet_id` | Droplet ID within frame |
110
+ | `center_x`, `center_y` | Droplet center coordinates (px) |
111
+ | `diameter_px`, `diameter_um` | Droplet diameter |
112
+ | `area_px`, `area_um2` | Droplet area |
113
+ | `inclusions` | Number of inclusions detected |
114
+
115
+ ## Configuration
116
+
117
+ Create `config.json` in your working directory to customize detection parameters:
118
+
119
+ ```json
120
+ {
121
+ "cellpose_flow_threshold": 0.4,
122
+ "cellpose_cellprob_threshold": 0.0,
123
+ "erosion_pixels": 5,
124
+ "kernel_size": 7,
125
+ "tophat_threshold": 30,
126
+ "min_inclusion_area": 7,
127
+ "max_inclusion_area": 50,
128
+ "edge_buffer": 5,
129
+ "min_droplet_diameter": 80,
130
+ "max_droplet_diameter": 200,
131
+ "px_to_um": 1.14,
132
+ "cache": {
133
+ "enabled": true,
134
+ "max_frames": 100
135
+ }
136
+ }
137
+ ```
138
+
139
+ ### Parameters
140
+
141
+ | Parameter | Description |
142
+ |-----------|-------------|
143
+ | `cellpose_flow_threshold` | Cellpose flow threshold for segmentation |
144
+ | `cellpose_cellprob_threshold` | Cellpose cell probability threshold |
145
+ | `erosion_pixels` | Pixels to erode droplet mask before inclusion detection |
146
+ | `kernel_size` | Morphological kernel size for black-hat transform |
147
+ | `tophat_threshold` | Threshold for inclusion detection |
148
+ | `min/max_inclusion_area` | Inclusion size constraints (px) |
149
+ | `edge_buffer` | Buffer from image edge to ignore inclusions |
150
+ | `min/max_droplet_diameter` | Droplet size constraints (px) |
151
+ | `px_to_um` | Pixel to micrometer conversion factor |
152
+
153
+ ## Requirements
154
+
155
+ - Python 3.12+
156
+ - CUDA-capable GPU (recommended for Cellpose)
157
+
158
+ ## License
159
+
160
+ MIT
@@ -0,0 +1,18 @@
1
+ {
2
+ "cellpose_flow_threshold": 0.4,
3
+ "cellpose_cellprob_threshold": 0.0,
4
+ "erosion_pixels": 5,
5
+ "kernel_size": 7,
6
+ "tophat_threshold": 30,
7
+ "min_inclusion_area": 7,
8
+ "max_inclusion_area": 50,
9
+ "edge_buffer": 5,
10
+ "min_droplet_diameter": 80,
11
+ "max_droplet_diameter": 200,
12
+ "px_to_um": 1.14,
13
+ "cache": {
14
+ "enabled": true,
15
+ "max_frames": 100,
16
+ "strategy": "lru"
17
+ }
18
+ }
@@ -0,0 +1,33 @@
1
+ [project]
2
+ name = "dropdrop"
3
+ version = "1.1.0"
4
+ description = """Python pipeline script for detecting droplets with beads and
5
+ other inclusions via cellpose"""
6
+ readme = "README.md"
7
+ license = "MIT"
8
+ requires-python = ">=3.12"
9
+ dependencies = [
10
+ "cellpose>=4.0.6",
11
+ "matplotlib>=3.10.6",
12
+ "numpy>=2.3.3",
13
+ "opencv-python>=4.11.0.86",
14
+ "pandas>=2.3.3",
15
+ "scipy>=1.16.2",
16
+ "seaborn>=0.13.2",
17
+ "tqdm>=4.67.1",
18
+ ]
19
+
20
+ [project.optional-dependencies]
21
+ dev = [
22
+ "pytest>=8.0",
23
+ ]
24
+
25
+ [project.scripts]
26
+ dropdrop = "dropdrop.cli:main"
27
+
28
+ [build-system]
29
+ requires = ["hatchling"]
30
+ build-backend = "hatchling.build"
31
+
32
+ [tool.hatch.build.targets.wheel]
33
+ packages = ["src/dropdrop"]
@@ -0,0 +1,16 @@
1
+ """DropDrop - Droplet and Inclusion Detection Pipeline."""
2
+
3
+ from .cache import CacheManager
4
+ from .config import load_config
5
+ from .pipeline import DropletInclusionPipeline
6
+ from .stats import DropletStatistics
7
+ from .ui import BaseWindow, InclusionEditor, Viewer
8
+
9
+ __all__ = [
10
+ "CacheManager",
11
+ "DropletInclusionPipeline",
12
+ "DropletStatistics",
13
+ "InclusionEditor",
14
+ "Viewer",
15
+ "load_config",
16
+ ]
@@ -0,0 +1,133 @@
1
+ """Cache management for expensive computations."""
2
+
3
+ import hashlib
4
+ import json
5
+ import shutil
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+
11
+
12
+ class CacheManager:
13
+ """Global LRU cache for expensive computations, stored in project root."""
14
+
15
+ def __init__(self, config, cache_dir=None):
16
+ cache_cfg = config.get("cache", {})
17
+ self.enabled = cache_cfg.get("enabled", True)
18
+ self.max_frames = cache_cfg.get("max_frames", 100)
19
+ # Cache in project root by default
20
+ if cache_dir:
21
+ self.cache_dir = Path(cache_dir)
22
+ else:
23
+ self.cache_dir = Path(__file__).parent.parent.parent / ".cache"
24
+ self.metadata_path = self.cache_dir / "metadata.json"
25
+ self.metadata = self._load_metadata()
26
+ self.config = config
27
+
28
+ def _load_metadata(self):
29
+ """Load cache metadata from disk."""
30
+ if self.metadata_path.exists():
31
+ try:
32
+ with open(self.metadata_path) as f:
33
+ return json.load(f)
34
+ except (json.JSONDecodeError, IOError):
35
+ return self._default_metadata()
36
+ return self._default_metadata()
37
+
38
+ def _default_metadata(self):
39
+ """Return default metadata structure."""
40
+ return {"version": "1.0", "config_hash": None, "frames": {}, "access_order": []}
41
+
42
+ def _save_metadata(self):
43
+ """Save cache metadata to disk."""
44
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
45
+ with open(self.metadata_path, "w") as f:
46
+ json.dump(self.metadata, f, indent=2)
47
+
48
+ def _enforce_lru(self):
49
+ """Remove oldest frames if over max_frames limit."""
50
+ while len(self.metadata["access_order"]) > self.max_frames:
51
+ oldest_key = self.metadata["access_order"].pop(0)
52
+ cache_file = self.cache_dir / f"{oldest_key}.npz"
53
+ if cache_file.exists():
54
+ cache_file.unlink()
55
+ self.metadata["frames"].pop(oldest_key, None)
56
+
57
+ def get_config_hash(self):
58
+ """Hash detection-related config keys that affect caching."""
59
+ keys = [
60
+ "cellpose_flow_threshold",
61
+ "cellpose_cellprob_threshold",
62
+ "min_droplet_diameter",
63
+ "max_droplet_diameter",
64
+ ]
65
+ data = {k: self.config.get(k) for k in keys}
66
+ return hashlib.sha256(json.dumps(data, sort_keys=True).encode()).hexdigest()[:16]
67
+
68
+ def _get_cache_key(self, source_filename):
69
+ """Generate cache key from source filename (not full path)."""
70
+ name = Path(source_filename).stem
71
+ return hashlib.sha256(name.encode()).hexdigest()[:16]
72
+
73
+ def is_valid(self, source_filename):
74
+ """Check if cache is valid for frame by source filename."""
75
+ if not self.enabled:
76
+ return False
77
+ current_hash = self.get_config_hash()
78
+ if self.metadata.get("config_hash") != current_hash:
79
+ return False
80
+ cache_key = self._get_cache_key(source_filename)
81
+ cache_file = self.cache_dir / f"{cache_key}.npz"
82
+ return cache_file.exists()
83
+
84
+ def load_frame(self, source_filename):
85
+ """Load cached data by source filename and update access order."""
86
+ cache_key = self._get_cache_key(source_filename)
87
+ cache_file = self.cache_dir / f"{cache_key}.npz"
88
+ data = np.load(cache_file, allow_pickle=True)
89
+
90
+ # Update LRU order
91
+ if cache_key in self.metadata["access_order"]:
92
+ self.metadata["access_order"].remove(cache_key)
93
+ self.metadata["access_order"].append(cache_key)
94
+ self._save_metadata()
95
+
96
+ return {
97
+ "min_projection": data["min_projection"],
98
+ "droplet_coords": list(data["droplet_coords"]),
99
+ }
100
+
101
+ def save_frame(self, source_filename, min_proj, droplet_coords):
102
+ """Save frame data by source filename and enforce LRU limit."""
103
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
104
+ cache_key = self._get_cache_key(source_filename)
105
+ cache_file = self.cache_dir / f"{cache_key}.npz"
106
+
107
+ np.savez(
108
+ cache_file,
109
+ min_projection=min_proj,
110
+ droplet_coords=np.array(droplet_coords, dtype=object),
111
+ )
112
+
113
+ # Update metadata
114
+ self.metadata["config_hash"] = self.get_config_hash()
115
+ self.metadata["frames"][cache_key] = {
116
+ "source": str(source_filename),
117
+ "cached_at": datetime.now().isoformat(),
118
+ }
119
+
120
+ # Update LRU order
121
+ if cache_key in self.metadata["access_order"]:
122
+ self.metadata["access_order"].remove(cache_key)
123
+ self.metadata["access_order"].append(cache_key)
124
+
125
+ self._enforce_lru()
126
+ self._save_metadata()
127
+
128
+ def clear(self):
129
+ """Clear entire cache."""
130
+ if self.cache_dir.exists():
131
+ shutil.rmtree(self.cache_dir)
132
+ self.metadata = self._default_metadata()
133
+ print("Cache cleared.")