PyOPIA 2.16.0__tar.gz → 2.16.2__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.
Files changed (38) hide show
  1. {pyopia-2.16.0 → pyopia-2.16.2}/PKG-INFO +69 -1
  2. {pyopia-2.16.0 → pyopia-2.16.2}/README.md +68 -0
  3. pyopia-2.16.2/pyopia/__init__.py +1 -0
  4. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/instrument/holo.py +2 -1
  5. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/instrument/silcam.py +2 -1
  6. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/instrument/uvp.py +2 -1
  7. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/process.py +17 -4
  8. pyopia-2.16.2/pyopia/tests/test_process.py +64 -0
  9. pyopia-2.16.0/pyopia/__init__.py +0 -1
  10. {pyopia-2.16.0 → pyopia-2.16.2}/.gitignore +0 -0
  11. {pyopia-2.16.0 → pyopia-2.16.2}/LICENSE +0 -0
  12. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/auxillarydata.py +0 -0
  13. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/background.py +0 -0
  14. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/cf_metadata.json +0 -0
  15. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/classify.py +0 -0
  16. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/classify_torch.py +0 -0
  17. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/cli.py +0 -0
  18. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/dataexport/__init__.py +0 -0
  19. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/dataexport/ecotaxa.py +0 -0
  20. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/exampledata.py +0 -0
  21. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/instrument/__init__.py +0 -0
  22. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/instrument/common.py +0 -0
  23. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/io.py +0 -0
  24. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/metadata.py +0 -0
  25. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/pipeline.py +0 -0
  26. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/plotting.py +0 -0
  27. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/realtime.py +0 -0
  28. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/simulator/__init__.py +0 -0
  29. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/simulator/silcam.py +0 -0
  30. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/statistics.py +0 -0
  31. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/tests/__init__.py +0 -0
  32. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/tests/test_auxillarydata.py +0 -0
  33. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/tests/test_classify.py +0 -0
  34. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/tests/test_io.py +0 -0
  35. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/tests/test_notebooks.py +0 -0
  36. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/tests/test_pipeline.py +0 -0
  37. {pyopia-2.16.0 → pyopia-2.16.2}/pyopia/tests/test_realtime.py +0 -0
  38. {pyopia-2.16.0 → pyopia-2.16.2}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyOPIA
3
- Version: 2.16.0
3
+ Version: 2.16.2
4
4
  Summary: A Python Ocean Particle Image Analysis toolbox.
5
5
  Project-URL: Repository, https://github.com/sintef/pyopia
6
6
  Project-URL: Documentation, https://pyopia.readthedocs.io
@@ -86,6 +86,74 @@ Will show you a montage of all the processed particle images in one.
86
86
 
87
87
  See the documentation for more information on how to install and use PyOPIA.
88
88
 
89
+ # Running with Docker
90
+
91
+ A prebuilt container image is published to GitHub Container Registry on every release, for users who prefer not to install PyOPIA's dependencies directly.
92
+
93
+ ## One-off invocation
94
+
95
+ From any directory that contains a `config.toml`:
96
+
97
+ ```bash
98
+ docker run --rm \
99
+ --user $(id -u):$(id -g) \
100
+ -v "$PWD:$PWD" -w "$PWD" \
101
+ ghcr.io/sintef/pyopia:latest \
102
+ process config.toml
103
+ ```
104
+
105
+ The 1:1 volume mount (`-v "$PWD:$PWD"`) makes the container see your current directory at the same path as the host, so an existing `config.toml` with absolute paths under `$PWD` works unchanged.
106
+
107
+ **If your config references paths outside `$PWD`** — most commonly a classifier weights file — add a matching bind mount for each of them.
108
+ The target (right of the colon) must equal the source (left) so the path in the config resolves unchanged.
109
+ For a config like
110
+
111
+ ```toml
112
+ [steps.classifier]
113
+ pipeline_class = "pyopia.classify_torch.Classify"
114
+ model_path = "/home/you/models/classifier.pt"
115
+ ```
116
+
117
+ the invocation becomes
118
+
119
+ ```bash
120
+ docker run --rm \
121
+ --user $(id -u):$(id -g) \
122
+ -v "$PWD:$PWD" -w "$PWD" \
123
+ -v /home/you/models:/home/you/models:ro \
124
+ ghcr.io/sintef/pyopia:latest \
125
+ process config.toml
126
+ ```
127
+
128
+ Any PyOPIA CLI command works, e.g. `docker run --rm ghcr.io/sintef/pyopia:latest --help`.
129
+
130
+ ## Using docker compose
131
+
132
+ Each GitHub release attaches a `compose.yaml` as an asset.
133
+ Download it once per project:
134
+
135
+ ```bash
136
+ curl -LO https://github.com/SINTEF/pyopia/releases/latest/download/compose.yaml
137
+ docker compose run --rm pyopia # runs `pyopia process config.toml`
138
+ docker compose run --rm pyopia --help # or any other CLI argument
139
+ ```
140
+
141
+ The compose file has commented-out stubs for paths that live outside `$PWD` — in particular the classifier weights file.
142
+ Edit `compose.yaml` to uncomment the line and point it at your actual host path, e.g.
143
+
144
+ ```yaml
145
+ volumes:
146
+ - ${PWD}:${PWD}
147
+ - /home/you/models:/home/you/models:ro # classifier weights
148
+ ```
149
+
150
+ The target must match the source so the config-referenced path resolves unchanged.
151
+ Override the default config filename with `PYOPIA_CONFIG`:
152
+
153
+ ```bash
154
+ PYOPIA_CONFIG=my_run.toml docker compose run --rm pyopia
155
+ ```
156
+
89
157
  # Documentation:
90
158
 
91
159
  [![Jupyter Book Badge](https://jupyterbook.org/badge.svg)](https://pyopia.readthedocs.io) [![Documentation](https://readthedocs.org/projects/pyopia/badge/?version=latest)](https://pyopia.readthedocs.io/en/latest/?badge=latest)
@@ -34,6 +34,74 @@ Will show you a montage of all the processed particle images in one.
34
34
 
35
35
  See the documentation for more information on how to install and use PyOPIA.
36
36
 
37
+ # Running with Docker
38
+
39
+ A prebuilt container image is published to GitHub Container Registry on every release, for users who prefer not to install PyOPIA's dependencies directly.
40
+
41
+ ## One-off invocation
42
+
43
+ From any directory that contains a `config.toml`:
44
+
45
+ ```bash
46
+ docker run --rm \
47
+ --user $(id -u):$(id -g) \
48
+ -v "$PWD:$PWD" -w "$PWD" \
49
+ ghcr.io/sintef/pyopia:latest \
50
+ process config.toml
51
+ ```
52
+
53
+ The 1:1 volume mount (`-v "$PWD:$PWD"`) makes the container see your current directory at the same path as the host, so an existing `config.toml` with absolute paths under `$PWD` works unchanged.
54
+
55
+ **If your config references paths outside `$PWD`** — most commonly a classifier weights file — add a matching bind mount for each of them.
56
+ The target (right of the colon) must equal the source (left) so the path in the config resolves unchanged.
57
+ For a config like
58
+
59
+ ```toml
60
+ [steps.classifier]
61
+ pipeline_class = "pyopia.classify_torch.Classify"
62
+ model_path = "/home/you/models/classifier.pt"
63
+ ```
64
+
65
+ the invocation becomes
66
+
67
+ ```bash
68
+ docker run --rm \
69
+ --user $(id -u):$(id -g) \
70
+ -v "$PWD:$PWD" -w "$PWD" \
71
+ -v /home/you/models:/home/you/models:ro \
72
+ ghcr.io/sintef/pyopia:latest \
73
+ process config.toml
74
+ ```
75
+
76
+ Any PyOPIA CLI command works, e.g. `docker run --rm ghcr.io/sintef/pyopia:latest --help`.
77
+
78
+ ## Using docker compose
79
+
80
+ Each GitHub release attaches a `compose.yaml` as an asset.
81
+ Download it once per project:
82
+
83
+ ```bash
84
+ curl -LO https://github.com/SINTEF/pyopia/releases/latest/download/compose.yaml
85
+ docker compose run --rm pyopia # runs `pyopia process config.toml`
86
+ docker compose run --rm pyopia --help # or any other CLI argument
87
+ ```
88
+
89
+ The compose file has commented-out stubs for paths that live outside `$PWD` — in particular the classifier weights file.
90
+ Edit `compose.yaml` to uncomment the line and point it at your actual host path, e.g.
91
+
92
+ ```yaml
93
+ volumes:
94
+ - ${PWD}:${PWD}
95
+ - /home/you/models:/home/you/models:ro # classifier weights
96
+ ```
97
+
98
+ The target must match the source so the config-referenced path resolves unchanged.
99
+ Override the default config filename with `PYOPIA_CONFIG`:
100
+
101
+ ```bash
102
+ PYOPIA_CONFIG=my_run.toml docker compose run --rm pyopia
103
+ ```
104
+
37
105
  # Documentation:
38
106
 
39
107
  [![Jupyter Book Badge](https://jupyterbook.org/badge.svg)](https://pyopia.readthedocs.io) [![Documentation](https://readthedocs.org/projects/pyopia/badge/?version=latest)](https://pyopia.readthedocs.io/en/latest/?badge=latest)
@@ -0,0 +1 @@
1
+ __version__ = "2.16.2"
@@ -668,7 +668,8 @@ def generate_config(raw_files: str, model_path: str, outfolder: str, output_pref
668
668
  'segmentation': {
669
669
  'pipeline_class': 'pyopia.process.Segment',
670
670
  'threshold': 0.9,
671
- 'segment_source': 'im_focussed'
671
+ 'segment_source': 'im_focussed',
672
+ 'segmentation_method': 'fast'
672
673
  },
673
674
  'statextract': {
674
675
  'pipeline_class': 'pyopia.process.CalculateStats',
@@ -341,7 +341,8 @@ def generate_config(raw_files: str, model_path: str, outfolder: str, output_pref
341
341
  'segmentation': {
342
342
  'pipeline_class': 'pyopia.process.Segment',
343
343
  'threshold': 0.85,
344
- 'segment_source': 'im_minimum'
344
+ 'segment_source': 'im_minimum',
345
+ 'segmentation_method': 'fast'
345
346
  },
346
347
  'statextract': {
347
348
  'pipeline_class': 'pyopia.process.CalculateStats',
@@ -110,7 +110,8 @@ def generate_config(raw_files: str, model_path: str, outfolder: str, output_pref
110
110
  'segmentation': {
111
111
  'pipeline_class': 'pyopia.process.Segment',
112
112
  'threshold': 0.95,
113
- 'segment_source': 'imraw'
113
+ 'segment_source': 'imraw',
114
+ 'segmentation_method': 'fast'
114
115
  },
115
116
  'statextract': {
116
117
  'pipeline_class': 'pyopia.process.CalculateStats',
@@ -376,7 +376,7 @@ def measure_particles(imbw, max_particles=5000):
376
376
  return region_properties
377
377
 
378
378
 
379
- def segment(img, threshold=0.98, minimum_area=12, fill_holes=True):
379
+ def segment(img, threshold=0.98, minimum_area=12, fill_holes=True, segmentation_method='fast'):
380
380
  '''Create a binary image from a background-corrected image.
381
381
 
382
382
  Parameters
@@ -389,6 +389,9 @@ def segment(img, threshold=0.98, minimum_area=12, fill_holes=True):
389
389
  minimum number of pixels to be considered a particle, by default 12
390
390
  fill_holes : bool, optional
391
391
  runs ndi.binary_fill_holes if True, by default True
392
+ segmentation_method : str, optional
393
+ Segmentation function to use. Supports ``'fast'`` and ``'accurate'``.
394
+ By default ``'fast'``.
392
395
 
393
396
  Returns
394
397
  -------
@@ -397,7 +400,12 @@ def segment(img, threshold=0.98, minimum_area=12, fill_holes=True):
397
400
  '''
398
401
  logger.info('segment')
399
402
 
400
- imbw = image2blackwhite_fast(img, threshold)
403
+ if segmentation_method == 'fast':
404
+ imbw = image2blackwhite_fast(img, threshold)
405
+ elif segmentation_method == 'accurate':
406
+ imbw = image2blackwhite_accurate(img, threshold)
407
+ else:
408
+ raise ValueError(f"Unknown segmentation_method '{segmentation_method}'. Supported values are 'fast' and 'accurate'.")
401
409
 
402
410
  logger.info('clean')
403
411
 
@@ -493,6 +501,9 @@ class Segment():
493
501
  segment_source: (str, optional)
494
502
  The key in Pipeline.data of the image to be segmented.
495
503
  Defaults to 'im_corrected'
504
+ segmentation_method: (str, optional)
505
+ Segmentation method to use: ``'fast'`` or ``'accurate'``.
506
+ Defaults to ``'fast'``.
496
507
 
497
508
  Returns
498
509
  -------
@@ -506,16 +517,18 @@ class Segment():
506
517
  minimum_area=12,
507
518
  threshold=0.98,
508
519
  fill_holes=True,
509
- segment_source='im_corrected'):
520
+ segment_source='im_corrected',
521
+ segmentation_method='fast'):
510
522
 
511
523
  self.minimum_area = minimum_area
512
524
  self.threshold = threshold
513
525
  self.fill_holes = fill_holes
514
526
  self.segment_source = segment_source
527
+ self.segmentation_method = segmentation_method
515
528
 
516
529
  def __call__(self, data):
517
530
  data['imbw'] = segment(data[self.segment_source], threshold=self.threshold, fill_holes=self.fill_holes,
518
- minimum_area=self.minimum_area)
531
+ minimum_area=self.minimum_area, segmentation_method=self.segmentation_method)
519
532
  return data
520
533
 
521
534
 
@@ -0,0 +1,64 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from pyopia.pipeline import Pipeline
5
+ import pyopia.process
6
+
7
+
8
+ def test_segment_selects_accurate_method(monkeypatch):
9
+ img = np.ones((4, 4), dtype=float)
10
+ call_state = {"fast": 0, "accurate": 0}
11
+
12
+ def fake_fast(_img, _threshold):
13
+ call_state["fast"] += 1
14
+ return np.zeros_like(_img, dtype=bool)
15
+
16
+ def fake_accurate(_img, _threshold):
17
+ call_state["accurate"] += 1
18
+ return np.ones_like(_img, dtype=bool)
19
+
20
+ monkeypatch.setattr(pyopia.process, "image2blackwhite_fast", fake_fast)
21
+ monkeypatch.setattr(pyopia.process, "image2blackwhite_accurate", fake_accurate)
22
+ monkeypatch.setattr(pyopia.process, "clean_bw", lambda imbw, _minimum_area: imbw)
23
+
24
+ pyopia.process.segment(img, segmentation_method="accurate")
25
+
26
+ assert call_state["fast"] == 0
27
+ assert call_state["accurate"] == 1
28
+
29
+
30
+ def test_segment_raises_on_unknown_method():
31
+ img = np.ones((4, 4), dtype=float)
32
+
33
+ with pytest.raises(ValueError, match="Unknown segmentation_method"):
34
+ pyopia.process.segment(img, segmentation_method="unknown")
35
+
36
+
37
+ def test_pipeline_segmentation_step_accepts_segmentation_method_config(monkeypatch):
38
+ captured = {}
39
+
40
+ def fake_segment(img, threshold, minimum_area, fill_holes, segmentation_method):
41
+ captured["segmentation_method"] = segmentation_method
42
+ return np.zeros_like(img, dtype=bool)
43
+
44
+ monkeypatch.setattr(pyopia.process, "segment", fake_segment)
45
+
46
+ config = {
47
+ "general": {"raw_files": []},
48
+ "steps": {
49
+ "segmentation": {
50
+ "pipeline_class": "pyopia.process.Segment",
51
+ "threshold": 0.9,
52
+ "minimum_area": 5,
53
+ "fill_holes": True,
54
+ "segment_source": "im_corrected",
55
+ "segmentation_method": "accurate",
56
+ }
57
+ },
58
+ }
59
+
60
+ pipeline = Pipeline(config, initial_steps=[])
61
+ pipeline.data["im_corrected"] = np.ones((4, 4), dtype=float)
62
+ pipeline.run_step("segmentation")
63
+
64
+ assert captured["segmentation_method"] == "accurate"
@@ -1 +0,0 @@
1
- __version__ = "2.16.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes