pydoug-core 0.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.
- pydoug_core-0.1.0/.gitignore +50 -0
- pydoug_core-0.1.0/LICENSE +7 -0
- pydoug_core-0.1.0/PKG-INFO +29 -0
- pydoug_core-0.1.0/README.md +1 -0
- pydoug_core-0.1.0/pyproject.toml +48 -0
- pydoug_core-0.1.0/src/pydoug_core/__init__.py +24 -0
- pydoug_core-0.1.0/src/pydoug_core/_manifest.py +2 -0
- pydoug_core-0.1.0/src/pydoug_core/analyze/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/analyze/compare.py +384 -0
- pydoug_core-0.1.0/src/pydoug_core/analyze/image_info.py +636 -0
- pydoug_core-0.1.0/src/pydoug_core/analyze/measure.py +1018 -0
- pydoug_core-0.1.0/src/pydoug_core/analyze/plot_distrib.py +456 -0
- pydoug_core-0.1.0/src/pydoug_core/analyze/plot_util.py +414 -0
- pydoug_core-0.1.0/src/pydoug_core/batch/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/batch/_result_save.py +61 -0
- pydoug_core-0.1.0/src/pydoug_core/batch/_tables.py +314 -0
- pydoug_core-0.1.0/src/pydoug_core/batch/recorder.py +545 -0
- pydoug_core-0.1.0/src/pydoug_core/batch/runner.py +114 -0
- pydoug_core-0.1.0/src/pydoug_core/color/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/color/cspace.py +72 -0
- pydoug_core-0.1.0/src/pydoug_core/detect/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/detect/shapes.py +482 -0
- pydoug_core-0.1.0/src/pydoug_core/detect/sharp.py +447 -0
- pydoug_core-0.1.0/src/pydoug_core/detect/texture.py +77 -0
- pydoug_core-0.1.0/src/pydoug_core/filter/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/filter/blur.py +169 -0
- pydoug_core-0.1.0/src/pydoug_core/filter/edge.py +259 -0
- pydoug_core-0.1.0/src/pydoug_core/filter/other.py +123 -0
- pydoug_core-0.1.0/src/pydoug_core/filter/pass_band.py +260 -0
- pydoug_core-0.1.0/src/pydoug_core/filter/ridge.py +240 -0
- pydoug_core-0.1.0/src/pydoug_core/gen/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/gen/gen.py +300 -0
- pydoug_core-0.1.0/src/pydoug_core/hist/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/hist/hist.py +500 -0
- pydoug_core-0.1.0/src/pydoug_core/io/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/io/imreader.py +284 -0
- pydoug_core-0.1.0/src/pydoug_core/io/imwriter.py +153 -0
- pydoug_core-0.1.0/src/pydoug_core/io/io_util.py +33 -0
- pydoug_core-0.1.0/src/pydoug_core/manipulate/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/manipulate/crop.py +101 -0
- pydoug_core-0.1.0/src/pydoug_core/manipulate/dims.py +394 -0
- pydoug_core-0.1.0/src/pydoug_core/math/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/math/arithmetic.py +104 -0
- pydoug_core-0.1.0/src/pydoug_core/math/logic.py +175 -0
- pydoug_core-0.1.0/src/pydoug_core/morph/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/morph/features.py +439 -0
- pydoug_core-0.1.0/src/pydoug_core/morph/morph.py +711 -0
- pydoug_core-0.1.0/src/pydoug_core/payloads.py +24 -0
- pydoug_core-0.1.0/src/pydoug_core/registry.py +323 -0
- pydoug_core-0.1.0/src/pydoug_core/restore/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/restore/denoise.py +490 -0
- pydoug_core-0.1.0/src/pydoug_core/restore/misc.py +172 -0
- pydoug_core-0.1.0/src/pydoug_core/results.py +151 -0
- pydoug_core-0.1.0/src/pydoug_core/segment/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/segment/intensity.py +257 -0
- pydoug_core-0.1.0/src/pydoug_core/segment/label.py +223 -0
- pydoug_core-0.1.0/src/pydoug_core/segment/local.py +311 -0
- pydoug_core-0.1.0/src/pydoug_core/segment/rgb.py +137 -0
- pydoug_core-0.1.0/src/pydoug_core/segment/segment_util.py +82 -0
- pydoug_core-0.1.0/src/pydoug_core/testing.py +91 -0
- pydoug_core-0.1.0/src/pydoug_core/trans/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/trans/geom.py +292 -0
- pydoug_core-0.1.0/src/pydoug_core/trans/space.py +224 -0
- pydoug_core-0.1.0/src/pydoug_core/user/__init__.py +0 -0
- pydoug_core-0.1.0/src/pydoug_core/user/user_functions.py +48 -0
- pydoug_core-0.1.0/src/pydoug_core/util.py +557 -0
- pydoug_core-0.1.0/tests/__init__.py +0 -0
- pydoug_core-0.1.0/tests/conftest.py +133 -0
- pydoug_core-0.1.0/tests/test_analyze/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_analyze/test_compare.py +147 -0
- pydoug_core-0.1.0/tests/test_analyze/test_image_info.py +268 -0
- pydoug_core-0.1.0/tests/test_analyze/test_measure.py +26 -0
- pydoug_core-0.1.0/tests/test_analyze/test_measure_bulk.py +1270 -0
- pydoug_core-0.1.0/tests/test_analyze/test_measure_surf.py +1767 -0
- pydoug_core-0.1.0/tests/test_analyze/test_plot_distrib.py +161 -0
- pydoug_core-0.1.0/tests/test_analyze/test_plot_util.py +121 -0
- pydoug_core-0.1.0/tests/test_batch/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_batch/test_recorder.py +332 -0
- pydoug_core-0.1.0/tests/test_batch/test_result_save.py +40 -0
- pydoug_core-0.1.0/tests/test_batch/test_runner.py +199 -0
- pydoug_core-0.1.0/tests/test_batch/test_tables.py +445 -0
- pydoug_core-0.1.0/tests/test_color/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_color/test_cspace.py +38 -0
- pydoug_core-0.1.0/tests/test_detect/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_detect/test_shapes.py +120 -0
- pydoug_core-0.1.0/tests/test_detect/test_sharp.py +117 -0
- pydoug_core-0.1.0/tests/test_detect/test_texture.py +27 -0
- pydoug_core-0.1.0/tests/test_filter/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_filter/test_blur.py +141 -0
- pydoug_core-0.1.0/tests/test_filter/test_edge.py +394 -0
- pydoug_core-0.1.0/tests/test_filter/test_other.py +45 -0
- pydoug_core-0.1.0/tests/test_filter/test_pass_band.py +71 -0
- pydoug_core-0.1.0/tests/test_filter/test_ridge.py +157 -0
- pydoug_core-0.1.0/tests/test_gen/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_gen/test_gen.py +147 -0
- pydoug_core-0.1.0/tests/test_hist/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_hist/test_hist.py +192 -0
- pydoug_core-0.1.0/tests/test_io/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_io/conftest.py +224 -0
- pydoug_core-0.1.0/tests/test_io/test_imreader.py +140 -0
- pydoug_core-0.1.0/tests/test_io/test_imwriter.py +233 -0
- pydoug_core-0.1.0/tests/test_manipulate/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_manipulate/test_crop.py +82 -0
- pydoug_core-0.1.0/tests/test_manipulate/test_dims.py +789 -0
- pydoug_core-0.1.0/tests/test_math/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_math/test_arithmetic.py +108 -0
- pydoug_core-0.1.0/tests/test_math/test_logic.py +157 -0
- pydoug_core-0.1.0/tests/test_morph/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_morph/test_features.py +177 -0
- pydoug_core-0.1.0/tests/test_morph/test_morph.py +330 -0
- pydoug_core-0.1.0/tests/test_payloads.py +16 -0
- pydoug_core-0.1.0/tests/test_registry.py +41 -0
- pydoug_core-0.1.0/tests/test_restore/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_restore/test_denoise.py +339 -0
- pydoug_core-0.1.0/tests/test_restore/test_misc.py +68 -0
- pydoug_core-0.1.0/tests/test_results.py +74 -0
- pydoug_core-0.1.0/tests/test_segment/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_segment/test_intensity.py +99 -0
- pydoug_core-0.1.0/tests/test_segment/test_label.py +89 -0
- pydoug_core-0.1.0/tests/test_segment/test_local.py +140 -0
- pydoug_core-0.1.0/tests/test_segment/test_rgb.py +21 -0
- pydoug_core-0.1.0/tests/test_trans/__init__.py +0 -0
- pydoug_core-0.1.0/tests/test_trans/test_geom.py +161 -0
- pydoug_core-0.1.0/tests/test_trans/test_space.py +60 -0
- pydoug_core-0.1.0/tests/test_util.py +338 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
build/
|
|
2
|
+
dist/
|
|
3
|
+
.coverage
|
|
4
|
+
|
|
5
|
+
packages/pydoug-core/src/pydoug_core/_manifest.py
|
|
6
|
+
packages/pydoug-core/src/pydoug_core/__pycache__/
|
|
7
|
+
packages/pydoug-core/src/pydoug_core/analyze/__pycache__/
|
|
8
|
+
packages/pydoug-core/src/pydoug_core/batch/__pycache__/
|
|
9
|
+
packages/pydoug-core/src/pydoug_core/color/__pycache__/
|
|
10
|
+
packages/pydoug-core/src/pydoug_core/detect/__pycache__/
|
|
11
|
+
packages/pydoug-core/src/pydoug_core/filter/__pycache__/
|
|
12
|
+
packages/pydoug-core/src/pydoug_core/gen/__pycache__/
|
|
13
|
+
packages/pydoug-core/src/pydoug_core/hist/__pycache__/
|
|
14
|
+
packages/pydoug-core/src/pydoug_core/io/__pycache__/
|
|
15
|
+
packages/pydoug-core/src/pydoug_core/manipulate/__pycache__/
|
|
16
|
+
packages/pydoug-core/src/pydoug_core/math/__pycache__/
|
|
17
|
+
packages/pydoug-core/src/pydoug_core/morph/__pycache__/
|
|
18
|
+
packages/pydoug-core/src/pydoug_core/restore/__pycache__/
|
|
19
|
+
packages/pydoug-core/src/pydoug_core/segment/__pycache__/
|
|
20
|
+
packages/pydoug-core/src/pydoug_core/trans/__pycache__/
|
|
21
|
+
packages/pydoug-core/src/pydoug_core/user/__pycache__/
|
|
22
|
+
packages/pydoug-core/tests/__pycache__/
|
|
23
|
+
packages/pydoug-core/tests/test_analyze/__pycache__/
|
|
24
|
+
packages/pydoug-core/tests/test_batch/__pycache__/
|
|
25
|
+
packages/pydoug-core/tests/test_color/__pycache__/
|
|
26
|
+
packages/pydoug-core/tests/test_detect/__pycache__/
|
|
27
|
+
packages/pydoug-core/tests/test_filter/__pycache__/
|
|
28
|
+
packages/pydoug-core/tests/test_gen/__pycache__/
|
|
29
|
+
packages/pydoug-core/tests/test_hist/__pycache__/
|
|
30
|
+
packages/pydoug-core/tests/test_io/__pycache__/
|
|
31
|
+
packages/pydoug-core/tests/test_manipulate/__pycache__/
|
|
32
|
+
packages/pydoug-core/tests/test_math/__pycache__/
|
|
33
|
+
packages/pydoug-core/tests/test_morph/__pycache__/
|
|
34
|
+
packages/pydoug-core/tests/test_restore/__pycache__/
|
|
35
|
+
packages/pydoug-core/tests/test_segment/__pycache__/
|
|
36
|
+
packages/pydoug-core/tests/test_trans/__pycache__/
|
|
37
|
+
packages/pydoug-core/tests/test_user/__pycache__/
|
|
38
|
+
|
|
39
|
+
packages/pydoug-napari/src/pydoug_napari/__pycache__
|
|
40
|
+
packages/pydoug-napari/src/pydoug_napari/_operations/_manifest.py
|
|
41
|
+
packages/pydoug-napari/src/pydoug_napari/_operations/__pycache__/
|
|
42
|
+
packages/pydoug-napari/src/pydoug_napari/_widgets/__pycache__
|
|
43
|
+
packages/pydoug-napari/tests/__pycache__
|
|
44
|
+
packages/pydoug-napari/tests/test_operations/__pycache__/
|
|
45
|
+
packages/pydoug-napari/tests/test_widgets/__pycache__
|
|
46
|
+
|
|
47
|
+
packages/pydoug-standalone/src/pydoug_standalone/__pycache__/
|
|
48
|
+
packages/pydoug-standalone/src/pydoug_standalone/hooks/__pycache__/
|
|
49
|
+
|
|
50
|
+
scripts/__pycache__/
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright (c) 2026, Douglas Lars Nelson
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pydoug-core
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Core I/O, image processing and analysis, and batch logic for PyDoug
|
|
5
|
+
Project-URL: Homepage, https://github.com/LarsN-hub/PyDoug2
|
|
6
|
+
Project-URL: Issues, https://github.com/LarsN-hub/PyDoug2/issues
|
|
7
|
+
Author-email: Douglas Lars Nelson <PyDoug@referable.anonaddy.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: image processing,image segmentation,microscopy,scientific imaging,tomography
|
|
11
|
+
Requires-Python: >=3.13
|
|
12
|
+
Requires-Dist: algotom==1.7.0
|
|
13
|
+
Requires-Dist: cmasher==1.9.2
|
|
14
|
+
Requires-Dist: h5py==3.16.0
|
|
15
|
+
Requires-Dist: imageio==2.37.4
|
|
16
|
+
Requires-Dist: matplotlib==3.11.1
|
|
17
|
+
Requires-Dist: numba==0.67.0
|
|
18
|
+
Requires-Dist: numpy==2.5.2
|
|
19
|
+
Requires-Dist: openpnm==3.6.4
|
|
20
|
+
Requires-Dist: porespy==3.1.0
|
|
21
|
+
Requires-Dist: psygnal==0.15.1
|
|
22
|
+
Requires-Dist: scikit-image==0.26.0
|
|
23
|
+
Requires-Dist: scipy==1.18.0
|
|
24
|
+
Provides-Extra: testing
|
|
25
|
+
Requires-Dist: pytest-cov==7.1.0; extra == 'testing'
|
|
26
|
+
Requires-Dist: pytest==9.1.1; extra == 'testing'
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
This is the core functionality subpackage for the full PyDoug image processing software package. Visit https://github.com/LarsN-hub/PyDoug2 for the full package README.md.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This is the core functionality subpackage for the full PyDoug image processing software package. Visit https://github.com/LarsN-hub/PyDoug2 for the full package README.md.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pydoug-core"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Core I/O, image processing and analysis, and batch logic for PyDoug"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.13"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Douglas Lars Nelson", email = "PyDoug@referable.anonaddy.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"image processing",
|
|
17
|
+
"image segmentation",
|
|
18
|
+
"microscopy",
|
|
19
|
+
"tomography",
|
|
20
|
+
"scientific imaging",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"algotom==1.7.0",
|
|
24
|
+
"cmasher==1.9.2",
|
|
25
|
+
"h5py==3.16.0",
|
|
26
|
+
"ImageIO==2.37.4",
|
|
27
|
+
"matplotlib==3.11.1",
|
|
28
|
+
"numba==0.67.0",
|
|
29
|
+
"numpy==2.5.2",
|
|
30
|
+
"openpnm==3.6.4",
|
|
31
|
+
"porespy==3.1.0",
|
|
32
|
+
"psygnal==0.15.1",
|
|
33
|
+
"scikit-image==0.26.0",
|
|
34
|
+
"scipy==1.18.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.optional-dependencies]
|
|
38
|
+
testing = [
|
|
39
|
+
"pytest==9.1.1",
|
|
40
|
+
"pytest-cov==7.1.0",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/LarsN-hub/PyDoug2"
|
|
45
|
+
Issues = "https://github.com/LarsN-hub/PyDoug2/issues"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/pydoug_core"]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Auto-imports every module in this package for discovery by widget registry.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
if getattr(sys, "frozen", False): # pragma: no cover
|
|
9
|
+
from pydoug_core._manifest import OPERATION_MODULES
|
|
10
|
+
|
|
11
|
+
for name in OPERATION_MODULES:
|
|
12
|
+
importlib.import_module(name)
|
|
13
|
+
else:
|
|
14
|
+
import pkgutil
|
|
15
|
+
|
|
16
|
+
_NOT_OPERATIONS: set[str] = {"pydoug_core.testing"}
|
|
17
|
+
|
|
18
|
+
for _, module_name, _ in pkgutil.walk_packages(__path__, prefix=f"{__name__}."):
|
|
19
|
+
if (
|
|
20
|
+
module_name.rsplit(".", 1)[-1].startswith("_")
|
|
21
|
+
or module_name in _NOT_OPERATIONS
|
|
22
|
+
):
|
|
23
|
+
continue
|
|
24
|
+
importlib.import_module(module_name)
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
# Auto-generated by scripts/generate_manifests.py - DO NOT EDIT!
|
|
2
|
+
OPERATION_MODULES = ['pydoug_core.analyze', 'pydoug_core.analyze.compare', 'pydoug_core.analyze.image_info', 'pydoug_core.analyze.measure', 'pydoug_core.analyze.plot_distrib', 'pydoug_core.analyze.plot_util', 'pydoug_core.batch', 'pydoug_core.batch.recorder', 'pydoug_core.batch.runner', 'pydoug_core.color', 'pydoug_core.color.cspace', 'pydoug_core.detect', 'pydoug_core.detect.shapes', 'pydoug_core.detect.sharp', 'pydoug_core.detect.texture', 'pydoug_core.filter', 'pydoug_core.filter.blur', 'pydoug_core.filter.edge', 'pydoug_core.filter.other', 'pydoug_core.filter.pass_band', 'pydoug_core.filter.ridge', 'pydoug_core.gen', 'pydoug_core.gen.gen', 'pydoug_core.hist', 'pydoug_core.hist.hist', 'pydoug_core.io', 'pydoug_core.io.imreader', 'pydoug_core.io.imwriter', 'pydoug_core.io.io_util', 'pydoug_core.manipulate', 'pydoug_core.manipulate.crop', 'pydoug_core.manipulate.dims', 'pydoug_core.math', 'pydoug_core.math.arithmetic', 'pydoug_core.math.logic', 'pydoug_core.morph', 'pydoug_core.morph.features', 'pydoug_core.morph.morph', 'pydoug_core.payloads', 'pydoug_core.registry', 'pydoug_core.restore', 'pydoug_core.restore.denoise', 'pydoug_core.restore.misc', 'pydoug_core.results', 'pydoug_core.segment', 'pydoug_core.segment.intensity', 'pydoug_core.segment.label', 'pydoug_core.segment.local', 'pydoug_core.segment.rgb', 'pydoug_core.segment.segment_util', 'pydoug_core.trans', 'pydoug_core.trans.geom', 'pydoug_core.trans.space', 'pydoug_core.user', 'pydoug_core.user.user_functions', 'pydoug_core.util']
|
|
File without changes
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Compare two image datasets.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Annotated, Literal
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
from pydoug_core.registry import register_operation
|
|
10
|
+
|
|
11
|
+
__all__ = ["mse", "ssi", "psnr", "are", "voi", "nmi", "intersection", "hausdorff_dist"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@register_operation(
|
|
15
|
+
category="compare",
|
|
16
|
+
display_name="Mean Squared Error",
|
|
17
|
+
array_params=("Layer2",),
|
|
18
|
+
output_kind="value",
|
|
19
|
+
recordable_toggle=True,
|
|
20
|
+
)
|
|
21
|
+
def mse(
|
|
22
|
+
imarray: np.ndarray,
|
|
23
|
+
Layer2: np.ndarray,
|
|
24
|
+
NRMSE: bool = False,
|
|
25
|
+
Normalization_Method: Literal["Euclidean", "Min-Max", "Mean"] = "Euclidean",
|
|
26
|
+
) -> float:
|
|
27
|
+
"""
|
|
28
|
+
Calculate the mean squared error (MSE) between two images. Images must have the same
|
|
29
|
+
dimensions but can be 2D/3D grayscale/RGB. Useful for judging intensity differences
|
|
30
|
+
between images. Ranges from 0 (perfectly identical images) upwards (higher = less
|
|
31
|
+
similar). Convenience wrapper around scikit-image's metrics.mean_squared_error and
|
|
32
|
+
metrics.normalized_root_mse:
|
|
33
|
+
https://scikit-image.org/docs/stable/api/skimage.metrics.html#skimage.metrics.mean_squared_error
|
|
34
|
+
https://scikit-image.org/docs/stable/api/skimage.metrics.html#skimage.metrics.normalized_root_mse
|
|
35
|
+
|
|
36
|
+
Parameters
|
|
37
|
+
----------
|
|
38
|
+
imarray: np.ndarray
|
|
39
|
+
The ground-truth image.
|
|
40
|
+
Layer2: np.ndarray
|
|
41
|
+
The image to test against the ground-truth image.
|
|
42
|
+
NRMSE: bool
|
|
43
|
+
Whether to compute the normalized root MSE (True) or standard MSE (False).
|
|
44
|
+
Default is False.
|
|
45
|
+
Normalization_Method: Literal
|
|
46
|
+
The normalization method to use if "NRMSE" is "True". Ignored otherwise. Default
|
|
47
|
+
is "Euclidean".
|
|
48
|
+
- "Euclidean": Normalize by averaged Euclidean norm of ground-truth image.
|
|
49
|
+
- "Min-Max": Normalize by intensity range of ground-truth image.
|
|
50
|
+
- "Mean": Normalize by mean of ground-truth image.
|
|
51
|
+
|
|
52
|
+
Returns
|
|
53
|
+
-------
|
|
54
|
+
mse_val: float
|
|
55
|
+
The MSE between the two images.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
if NRMSE:
|
|
59
|
+
from skimage.metrics import normalized_root_mse
|
|
60
|
+
|
|
61
|
+
return normalized_root_mse(
|
|
62
|
+
imarray, Layer2, normalization=Normalization_Method.lower()
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
from skimage.metrics import mean_squared_error
|
|
66
|
+
|
|
67
|
+
return mean_squared_error(imarray, Layer2)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@register_operation(
|
|
71
|
+
category="compare",
|
|
72
|
+
display_name="Structural Similarity",
|
|
73
|
+
array_params=("Layer2",),
|
|
74
|
+
output_kind="value",
|
|
75
|
+
recordable_toggle=True,
|
|
76
|
+
)
|
|
77
|
+
def ssi(
|
|
78
|
+
imarray: np.ndarray,
|
|
79
|
+
Layer2: np.ndarray,
|
|
80
|
+
) -> float:
|
|
81
|
+
"""
|
|
82
|
+
Calculate the structural similarity index (SSI) between two images. Images must have
|
|
83
|
+
the same dimensions but can be 2D/3D grayscale/RGB. Useful for judging intensity and
|
|
84
|
+
texture differences between images. Ranges from 1 (identical images) to 0 (perfectly
|
|
85
|
+
non-similar images). Convenience wrapper around scikit-image's
|
|
86
|
+
metrics.structural_similarity:
|
|
87
|
+
https://scikit-image.org/docs/stable/api/skimage.metrics.html#skimage.metrics.structural_similarity
|
|
88
|
+
|
|
89
|
+
Parameters
|
|
90
|
+
----------
|
|
91
|
+
imarray: np.ndarray
|
|
92
|
+
The first image.
|
|
93
|
+
Layer2: np.ndarray
|
|
94
|
+
The second image.
|
|
95
|
+
|
|
96
|
+
Returns
|
|
97
|
+
-------
|
|
98
|
+
ssi_val: float
|
|
99
|
+
The SSI between the two images.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
from skimage.metrics import structural_similarity
|
|
103
|
+
|
|
104
|
+
from pydoug_core.util import chan_axis
|
|
105
|
+
|
|
106
|
+
if imarray.dtype == np.bool:
|
|
107
|
+
imrange: float | int = 1
|
|
108
|
+
else:
|
|
109
|
+
imrange = np.max(imarray) - np.min(imarray)
|
|
110
|
+
|
|
111
|
+
return structural_similarity(
|
|
112
|
+
imarray,
|
|
113
|
+
Layer2,
|
|
114
|
+
data_range=imrange,
|
|
115
|
+
channel_axis=chan_axis(imarray),
|
|
116
|
+
) # type: ignore
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@register_operation(
|
|
120
|
+
category="compare",
|
|
121
|
+
display_name="Peak Signal to Noise Ratio",
|
|
122
|
+
array_params=("Layer2",),
|
|
123
|
+
output_kind="value",
|
|
124
|
+
recordable_toggle=True,
|
|
125
|
+
)
|
|
126
|
+
def psnr(
|
|
127
|
+
imarray: np.ndarray,
|
|
128
|
+
Layer2: np.ndarray,
|
|
129
|
+
) -> float:
|
|
130
|
+
"""
|
|
131
|
+
Calculate the peak signal to noise ratio (PSNR) between two images. Images must be
|
|
132
|
+
the same dimension but can be 2D/3D grayscale/RGB. Ratio of the peak signal of an
|
|
133
|
+
image to the corrupting noise present (higher = better, PSNR -> infinity if both
|
|
134
|
+
images are identical). Convenience wrapper around scikit-image's
|
|
135
|
+
metrics.peak_signal_noise_ratio:
|
|
136
|
+
https://scikit-image.org/docs/stable/api/skimage.metrics.html#skimage.metrics.peak_signal_noise_ratio
|
|
137
|
+
|
|
138
|
+
Parameters
|
|
139
|
+
----------
|
|
140
|
+
imarray: np.ndarray
|
|
141
|
+
The ground-truth image.
|
|
142
|
+
Layer2: np.ndarray
|
|
143
|
+
The image to test against the ground-truth image.
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
psnr_val: float
|
|
148
|
+
The PSNR between the two images.
|
|
149
|
+
"""
|
|
150
|
+
|
|
151
|
+
from skimage.metrics import peak_signal_noise_ratio
|
|
152
|
+
|
|
153
|
+
if imarray.dtype == np.bool:
|
|
154
|
+
imrange: float | int = 1
|
|
155
|
+
else:
|
|
156
|
+
imrange = np.max(imarray) - np.min(imarray)
|
|
157
|
+
|
|
158
|
+
return peak_signal_noise_ratio(imarray, Layer2, data_range=imrange)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@register_operation(
|
|
162
|
+
category="compare",
|
|
163
|
+
display_name="Adapted Rand Error",
|
|
164
|
+
array_params=("Layer2",),
|
|
165
|
+
output_kind="value",
|
|
166
|
+
recordable_toggle=True,
|
|
167
|
+
)
|
|
168
|
+
def are(
|
|
169
|
+
imarray: np.ndarray,
|
|
170
|
+
Layer2: np.ndarray,
|
|
171
|
+
) -> dict[str, float]:
|
|
172
|
+
"""
|
|
173
|
+
Calculate the adapted Rand error between an image and a ground-truth image. Images
|
|
174
|
+
must have the same dimensions but can be 2D/3D grayscale/RGB. Useful for evaluating
|
|
175
|
+
segmentation similarity. Returns "Error", "Precision", and "Recall", where "Error"
|
|
176
|
+
indicates overall similarity (0 = perfectly similar). "Precision" is the number of
|
|
177
|
+
correctly segmented pixels divided by the number of segmented pixels in the test
|
|
178
|
+
image and "Recall" is the number of correctly segmented pixels divided by the number
|
|
179
|
+
of segmented pixels in the true image. If "Precision" is close to 1 but "Recall" is
|
|
180
|
+
lower, the image is undersegmented. If "Recall" is close to 1 but "Precision" is
|
|
181
|
+
lower, the image is oversegmented. Both are 1 for perfect segmentation. Non-integer
|
|
182
|
+
images are converted before calculation. Convenience wrapper around scikit-image's
|
|
183
|
+
metrics.adapted_rand_error:
|
|
184
|
+
https://scikit-image.org/docs/stable/api/skimage.metrics.html#skimage.metrics.adapted_rand_error
|
|
185
|
+
|
|
186
|
+
Parameters
|
|
187
|
+
----------
|
|
188
|
+
imarray: np.ndarray
|
|
189
|
+
The ground truth image.
|
|
190
|
+
Layer2: np.ndarray
|
|
191
|
+
The image to test against the ground truth image.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
are_dict:
|
|
196
|
+
The adapted Rand error, precision, and recall between the two images.
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
from skimage.metrics import adapted_rand_error
|
|
200
|
+
|
|
201
|
+
are, prec, rec = adapted_rand_error(
|
|
202
|
+
np.round(imarray).astype(np.int32), np.round(Layer2).astype(np.int32)
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return {"Error": are, "Precision": prec, "Recall": rec}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@register_operation(
|
|
209
|
+
category="compare",
|
|
210
|
+
display_name="Variation of Information",
|
|
211
|
+
array_params=("Layer2",),
|
|
212
|
+
output_kind="value",
|
|
213
|
+
recordable_toggle=True,
|
|
214
|
+
)
|
|
215
|
+
def voi(
|
|
216
|
+
imarray: np.ndarray,
|
|
217
|
+
Layer2: np.ndarray,
|
|
218
|
+
Rounding_Decimals: Annotated[int, {"min": 0}] = 5,
|
|
219
|
+
) -> dict[str, float]:
|
|
220
|
+
"""
|
|
221
|
+
Calculate the variation of information (VOI) between two images. Images must have
|
|
222
|
+
the same dimensions but can be 2D/3D grayscale/RGB. Useful for assessing over/under
|
|
223
|
+
segmentation with a ground-truth image. Returns "Splits" and "Merges" where "Splits"
|
|
224
|
+
indicates undersegmentation while "Merges" indicates oversegmentation. 0 = perfect
|
|
225
|
+
similarity while higher means less similar. Non-integer images are converted before
|
|
226
|
+
calculation. Convenience wrapper around scikit-image's
|
|
227
|
+
metrics.variation_of_information:
|
|
228
|
+
https://scikit-image.org/docs/stable/api/skimage.metrics.html#skimage.metrics.variation_of_information
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
imarray: np.ndarray
|
|
233
|
+
The ground-truth image.
|
|
234
|
+
Layer2: np.ndarray
|
|
235
|
+
The image to compare to the ground-truth image.
|
|
236
|
+
Rounding_Decimals: int
|
|
237
|
+
The number of decimal points to round to (returns 0 if truly perfect instead of
|
|
238
|
+
just a very small number). Default is 5, increase for more decimals points.
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
voi_vals: dict[str, float]
|
|
243
|
+
A dictionary containing the splits and merges from the VOI calculation.
|
|
244
|
+
"""
|
|
245
|
+
|
|
246
|
+
from skimage.metrics import variation_of_information
|
|
247
|
+
|
|
248
|
+
splits, merges = variation_of_information(
|
|
249
|
+
np.round(imarray).astype(np.int32),
|
|
250
|
+
np.round(Layer2).astype(np.int32),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
"Splits": round(splits, Rounding_Decimals),
|
|
255
|
+
"Merges": round(merges, Rounding_Decimals),
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@register_operation(
|
|
260
|
+
category="compare",
|
|
261
|
+
display_name="Normalized Mutual Information",
|
|
262
|
+
array_params=("Layer2",),
|
|
263
|
+
output_kind="value",
|
|
264
|
+
recordable_toggle=True,
|
|
265
|
+
)
|
|
266
|
+
def nmi(
|
|
267
|
+
imarray: np.ndarray,
|
|
268
|
+
Layer2: np.ndarray,
|
|
269
|
+
Bin_Count: Annotated[int, {"min": 1}] = 100,
|
|
270
|
+
Rounding_Decimals: Annotated[int, {"min": 0}] = 5,
|
|
271
|
+
) -> float:
|
|
272
|
+
"""
|
|
273
|
+
Calculate the normalized mutual information (NMI) between two images. Images must
|
|
274
|
+
have the same dimensions but can be 2D/3D grayscale/RGB. Useful for assessing image
|
|
275
|
+
registration quality (higher = better). Measured from 1 to 2 where 2 = perfect
|
|
276
|
+
correlation and 1 = perfect non-correlation. Convenience wrapper around
|
|
277
|
+
scikit-image's metrics.normalized_mutual_information:
|
|
278
|
+
https://scikit-image.org/docs/stable/api/skimage.metrics.html#skimage.metrics.normalized_mutual_information
|
|
279
|
+
|
|
280
|
+
Parameters
|
|
281
|
+
----------
|
|
282
|
+
imarray: np.ndarray
|
|
283
|
+
The first image.
|
|
284
|
+
Layer2: np.ndarray
|
|
285
|
+
The second image.
|
|
286
|
+
Bin_Count: int
|
|
287
|
+
The number of bins to use to calculate the joint histogram.
|
|
288
|
+
Rounding_Decimals: int
|
|
289
|
+
The number of decimal points to round to (returns 0 if truly perfect instead of
|
|
290
|
+
just a very small number). Default is 5, increase for more decimals points.
|
|
291
|
+
|
|
292
|
+
Returns
|
|
293
|
+
-------
|
|
294
|
+
nmi_val: float
|
|
295
|
+
The NMI between the two images.
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
from skimage.metrics import normalized_mutual_information
|
|
299
|
+
|
|
300
|
+
return round(
|
|
301
|
+
normalized_mutual_information(imarray, Layer2, bins=Bin_Count), # type: ignore
|
|
302
|
+
Rounding_Decimals,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@register_operation(
|
|
307
|
+
category="compare",
|
|
308
|
+
display_name="Intersection Coefficient",
|
|
309
|
+
array_params=("Layer2",),
|
|
310
|
+
output_kind="value",
|
|
311
|
+
recordable_toggle=True,
|
|
312
|
+
)
|
|
313
|
+
def intersection(
|
|
314
|
+
imarray: np.ndarray,
|
|
315
|
+
Layer2: np.ndarray,
|
|
316
|
+
) -> float:
|
|
317
|
+
"""
|
|
318
|
+
Determine the fraction of the segmented pixels in the first image that lie within
|
|
319
|
+
the segmented pixels of the second image. Accepts 2D/3D grayscale/RGB images but is
|
|
320
|
+
only meaningful for binary segmented images. All nonzero pixels in non-binary images
|
|
321
|
+
are treated as segmented pixels. Convenience wrapper around numpy's boolean indexing
|
|
322
|
+
and count_nonzero:
|
|
323
|
+
https://numpy.org/doc/1.21/reference/arrays.indexing.html#boolean-array-indexing
|
|
324
|
+
https://numpy.org/doc/stable/reference/generated/numpy.count_nonzero.html
|
|
325
|
+
|
|
326
|
+
Parameters
|
|
327
|
+
----------
|
|
328
|
+
imarray: np.ndarray
|
|
329
|
+
The first image.
|
|
330
|
+
Layer2: np.ndarray
|
|
331
|
+
The second image.
|
|
332
|
+
|
|
333
|
+
Returns
|
|
334
|
+
-------
|
|
335
|
+
inter_coeff: float
|
|
336
|
+
The intersection coefficient between the two images.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
from pydoug_core.util import convert_dtype, rgb2gray
|
|
340
|
+
|
|
341
|
+
one_total: np.intp = np.count_nonzero(convert_dtype(rgb2gray(imarray), "Bool"))
|
|
342
|
+
one_count: np.intp = np.count_nonzero(
|
|
343
|
+
convert_dtype(rgb2gray(imarray), "Bool")[
|
|
344
|
+
convert_dtype(rgb2gray(Layer2), "Bool")
|
|
345
|
+
]
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
return one_count / one_total
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
@register_operation(
|
|
352
|
+
category="compare",
|
|
353
|
+
display_name="Hausdorff Distance",
|
|
354
|
+
array_params=("Layer2",),
|
|
355
|
+
output_kind="value",
|
|
356
|
+
recordable_toggle=True,
|
|
357
|
+
)
|
|
358
|
+
def hausdorff_dist(
|
|
359
|
+
imarray: np.ndarray,
|
|
360
|
+
Layer2: np.ndarray,
|
|
361
|
+
) -> float:
|
|
362
|
+
"""
|
|
363
|
+
Calculate the Hausdorff distance between two images (the maximum distance between
|
|
364
|
+
a nonzero pixel in the first image and the nearest nonzero pixel in the second
|
|
365
|
+
image). Images must have the same dimensions but can be 2D/3D grayscale/RGB.
|
|
366
|
+
Convenience wrapper around scikit-image's metrics.hausdorff_distance:
|
|
367
|
+
https://scikit-image.org/docs/stable/api/skimage.metrics.html#skimage.metrics.hausdorff_distance
|
|
368
|
+
|
|
369
|
+
Parameters
|
|
370
|
+
----------
|
|
371
|
+
imarray: np.ndarray
|
|
372
|
+
The first image.
|
|
373
|
+
Layer2: np.ndarray
|
|
374
|
+
The second image.
|
|
375
|
+
|
|
376
|
+
Returns
|
|
377
|
+
-------
|
|
378
|
+
hd_val: float
|
|
379
|
+
The Hausdorff distance between the two images.
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
from skimage.metrics import hausdorff_distance
|
|
383
|
+
|
|
384
|
+
return hausdorff_distance(imarray, Layer2) # type: ignore
|