PyOPIA 2.16.1__tar.gz → 2.16.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.
Files changed (38) hide show
  1. {pyopia-2.16.1 → pyopia-2.16.3}/PKG-INFO +1 -1
  2. pyopia-2.16.3/pyopia/__init__.py +1 -0
  3. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/cli.py +15 -0
  4. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/instrument/holo.py +2 -1
  5. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/instrument/silcam.py +2 -1
  6. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/instrument/uvp.py +2 -1
  7. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/process.py +17 -4
  8. pyopia-2.16.3/pyopia/tests/test_process.py +64 -0
  9. pyopia-2.16.1/pyopia/__init__.py +0 -1
  10. {pyopia-2.16.1 → pyopia-2.16.3}/.gitignore +0 -0
  11. {pyopia-2.16.1 → pyopia-2.16.3}/LICENSE +0 -0
  12. {pyopia-2.16.1 → pyopia-2.16.3}/README.md +0 -0
  13. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/auxillarydata.py +0 -0
  14. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/background.py +0 -0
  15. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/cf_metadata.json +0 -0
  16. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/classify.py +0 -0
  17. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/classify_torch.py +0 -0
  18. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/dataexport/__init__.py +0 -0
  19. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/dataexport/ecotaxa.py +0 -0
  20. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/exampledata.py +0 -0
  21. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/instrument/__init__.py +0 -0
  22. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/instrument/common.py +0 -0
  23. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/io.py +0 -0
  24. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/metadata.py +0 -0
  25. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/pipeline.py +0 -0
  26. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/plotting.py +0 -0
  27. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/realtime.py +0 -0
  28. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/simulator/__init__.py +0 -0
  29. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/simulator/silcam.py +0 -0
  30. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/statistics.py +0 -0
  31. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/tests/__init__.py +0 -0
  32. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/tests/test_auxillarydata.py +0 -0
  33. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/tests/test_classify.py +0 -0
  34. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/tests/test_io.py +0 -0
  35. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/tests/test_notebooks.py +0 -0
  36. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/tests/test_pipeline.py +0 -0
  37. {pyopia-2.16.1 → pyopia-2.16.3}/pyopia/tests/test_realtime.py +0 -0
  38. {pyopia-2.16.1 → pyopia-2.16.3}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyOPIA
3
- Version: 2.16.1
3
+ Version: 2.16.3
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
@@ -0,0 +1 @@
1
+ __version__ = "2.16.3"
@@ -371,6 +371,21 @@ def process_realtime(config_filename: str, watch_folder: str = None):
371
371
  # Load config and setup logging
372
372
  pipeline_config = pyopia.io.load_toml(config_filename)
373
373
  setup_logging(pipeline_config)
374
+
375
+ # Create output folders
376
+ if "output" not in pipeline_config["steps"]:
377
+ raise RuntimeError(
378
+ 'The given config file is missing an "output" step.\n'
379
+ + "This is needed to setup how to save data to disc."
380
+ )
381
+ if "output_datafile" not in pipeline_config["steps"]["output"]:
382
+ raise RuntimeError(
383
+ 'The given config file is missing "output_datafile" option in the "output" step.\n'
384
+ + "This is needed to setup how to save data to disc."
385
+ )
386
+ output_datafile = pipeline_config["steps"]["output"]["output_datafile"]
387
+ os.makedirs(os.path.split(output_datafile)[:-1][0], exist_ok=True)
388
+
374
389
  pyopia.realtime.run_realtime(pipeline_config, watch_folder=watch_folder)
375
390
 
376
391
 
@@ -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.1"
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