hyppo-hsi 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.
- hyppo_hsi-0.1.0/CHANGELOG.md +31 -0
- hyppo_hsi-0.1.0/LICENSE +28 -0
- hyppo_hsi-0.1.0/MANIFEST.in +32 -0
- hyppo_hsi-0.1.0/PKG-INFO +107 -0
- hyppo_hsi-0.1.0/README.md +35 -0
- hyppo_hsi-0.1.0/hyppo/__init__.py +16 -0
- hyppo_hsi-0.1.0/hyppo/core/__init__.py +14 -0
- hyppo_hsi-0.1.0/hyppo/core/_feature_space/__init__.py +1 -0
- hyppo_hsi-0.1.0/hyppo/core/_feature_space/dependency_graph.py +178 -0
- hyppo_hsi-0.1.0/hyppo/core/_feature_space/feature.py +183 -0
- hyppo_hsi-0.1.0/hyppo/core/_feature_space/feature_space.py +195 -0
- hyppo_hsi-0.1.0/hyppo/core/_hsi.py +202 -0
- hyppo_hsi-0.1.0/hyppo/core/_hsi_plot.py +48 -0
- hyppo_hsi-0.1.0/hyppo/extractor/__init__.py +61 -0
- hyppo_hsi-0.1.0/hyppo/extractor/_dwt_utils.py +23 -0
- hyppo_hsi-0.1.0/hyppo/extractor/_spectral_utils.py +50 -0
- hyppo_hsi-0.1.0/hyppo/extractor/_validators.py +62 -0
- hyppo_hsi-0.1.0/hyppo/extractor/base.py +56 -0
- hyppo_hsi-0.1.0/hyppo/extractor/dwt1d.py +149 -0
- hyppo_hsi-0.1.0/hyppo/extractor/dwt2d.py +151 -0
- hyppo_hsi-0.1.0/hyppo/extractor/dwt3d.py +132 -0
- hyppo_hsi-0.1.0/hyppo/extractor/gabor.py +274 -0
- hyppo_hsi-0.1.0/hyppo/extractor/geometricmoment.py +212 -0
- hyppo_hsi-0.1.0/hyppo/extractor/glcm.py +276 -0
- hyppo_hsi-0.1.0/hyppo/extractor/ica.py +143 -0
- hyppo_hsi-0.1.0/hyppo/extractor/lbp.py +194 -0
- hyppo_hsi-0.1.0/hyppo/extractor/legendremoment.py +212 -0
- hyppo_hsi-0.1.0/hyppo/extractor/mnf.py +171 -0
- hyppo_hsi-0.1.0/hyppo/extractor/mp.py +244 -0
- hyppo_hsi-0.1.0/hyppo/extractor/ndvi.py +104 -0
- hyppo_hsi-0.1.0/hyppo/extractor/ndwi.py +105 -0
- hyppo_hsi-0.1.0/hyppo/extractor/pca.py +117 -0
- hyppo_hsi-0.1.0/hyppo/extractor/pp.py +274 -0
- hyppo_hsi-0.1.0/hyppo/extractor/registry.py +130 -0
- hyppo_hsi-0.1.0/hyppo/extractor/savi.py +113 -0
- hyppo_hsi-0.1.0/hyppo/extractor/zernikemoment.py +232 -0
- hyppo_hsi-0.1.0/hyppo/io/__init__.py +24 -0
- hyppo_hsi-0.1.0/hyppo/io/_config/__init__.py +13 -0
- hyppo_hsi-0.1.0/hyppo/io/_config/config.py +100 -0
- hyppo_hsi-0.1.0/hyppo/io/_config/loader.py +284 -0
- hyppo_hsi-0.1.0/hyppo/io/_config/saver.py +169 -0
- hyppo_hsi-0.1.0/hyppo/io/_features/__init__.py +3 -0
- hyppo_hsi-0.1.0/hyppo/io/_features/h5.py +92 -0
- hyppo_hsi-0.1.0/hyppo/io/_hsi/__init__.py +1 -0
- hyppo_hsi-0.1.0/hyppo/io/_hsi/h5.py +177 -0
- hyppo_hsi-0.1.0/hyppo/runner/__init__.py +31 -0
- hyppo_hsi-0.1.0/hyppo/runner/base.py +41 -0
- hyppo_hsi-0.1.0/hyppo/runner/dask.py +278 -0
- hyppo_hsi-0.1.0/hyppo/runner/local_process.py +366 -0
- hyppo_hsi-0.1.0/hyppo/runner/registry.py +165 -0
- hyppo_hsi-0.1.0/hyppo/runner/sequential.py +67 -0
- hyppo_hsi-0.1.0/hyppo/utils/__init__.py +1 -0
- hyppo_hsi-0.1.0/hyppo/utils/bunch.py +135 -0
- hyppo_hsi-0.1.0/hyppo_hsi.egg-info/PKG-INFO +107 -0
- hyppo_hsi-0.1.0/hyppo_hsi.egg-info/SOURCES.txt +58 -0
- hyppo_hsi-0.1.0/hyppo_hsi.egg-info/dependency_links.txt +1 -0
- hyppo_hsi-0.1.0/hyppo_hsi.egg-info/requires.txt +20 -0
- hyppo_hsi-0.1.0/hyppo_hsi.egg-info/top_level.txt +1 -0
- hyppo_hsi-0.1.0/pyproject.toml +89 -0
- hyppo_hsi-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# What's new?
|
|
2
|
+
|
|
3
|
+
<!-- BODY -->
|
|
4
|
+
|
|
5
|
+
## Version 0.1.0
|
|
6
|
+
|
|
7
|
+
First public release.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **HSI container** (`hyppo.core.HSI`) with reflectance, wavelengths,
|
|
12
|
+
mask, and metadata. Includes `describe`, `pseudo_rgb`, `crop`, and
|
|
13
|
+
plot accessor.
|
|
14
|
+
- **17 feature extractors**:
|
|
15
|
+
- **Dimensionality reduction:** PCA, ICA, MNF
|
|
16
|
+
- **Spectral indices:** NDVI, NDWI, SAVI
|
|
17
|
+
- **Texture:** GLCM, LBP, Gabor
|
|
18
|
+
- **Moments:** GeometricMoment, LegendreMoment, ZernikeMoment
|
|
19
|
+
- **Morphological / projection:** MP (Morphological Profile),
|
|
20
|
+
PP (Projection Pursuit)
|
|
21
|
+
- **Wavelet:** DWT1D, DWT2D, DWT3D
|
|
22
|
+
- **4 runners**: `SequentialRunner`, `DaskThreadsRunner`,
|
|
23
|
+
`DaskProcessesRunner`, `LocalProcessRunner` (shared-memory).
|
|
24
|
+
- **`FeatureSpace` orchestrator** with NetworkX-based dependency
|
|
25
|
+
graph and topological sort over extractor inputs.
|
|
26
|
+
- **I/O**:
|
|
27
|
+
- HDF5 loader for HSI data with heuristic dataset discovery.
|
|
28
|
+
- HDF5 saver for `FeatureCollection`.
|
|
29
|
+
- YAML and JSON config support (`Config`).
|
|
30
|
+
- **Extractor registry** for plugin-style discovery.
|
|
31
|
+
- **Python 3.11–3.14 support**, including free-threaded builds.
|
hyppo_hsi-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, QuatroPe
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
include LICENSE
|
|
2
|
+
include README.md
|
|
3
|
+
include CHANGELOG.md
|
|
4
|
+
include pyproject.toml
|
|
5
|
+
|
|
6
|
+
recursive-include hyppo *.py
|
|
7
|
+
|
|
8
|
+
exclude tox.ini
|
|
9
|
+
exclude requirements.txt
|
|
10
|
+
exclude requirements_dev.txt
|
|
11
|
+
exclude requirements_no_gil.txt
|
|
12
|
+
exclude CLAUDE.md
|
|
13
|
+
exclude INFORMATION.md
|
|
14
|
+
exclude audit.md
|
|
15
|
+
exclude coverage.xml
|
|
16
|
+
exclude example.config.yaml
|
|
17
|
+
exclude interpreters_test.py
|
|
18
|
+
exclude NEON_subset.h5
|
|
19
|
+
exclude hyppo.pdf
|
|
20
|
+
exclude demo.ipynb
|
|
21
|
+
exclude demo_free_threaded.ipynb
|
|
22
|
+
exclude notebook_old.ipynb
|
|
23
|
+
|
|
24
|
+
recursive-exclude tests *
|
|
25
|
+
recursive-exclude docs *
|
|
26
|
+
recursive-exclude demo *
|
|
27
|
+
recursive-exclude benchmark *
|
|
28
|
+
recursive-exclude _build *
|
|
29
|
+
recursive-exclude _static *
|
|
30
|
+
recursive-exclude _templates *
|
|
31
|
+
recursive-exclude build *
|
|
32
|
+
recursive-exclude __pycache__ *
|
hyppo_hsi-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hyppo-hsi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: HYPPO (Hyperspectral Processing) - A modular feature extractor for hyperspectral images
|
|
5
|
+
Author-email: Tomás Ponce Gessi <tomaspgessi@mi.unc.edu.ar>
|
|
6
|
+
License: BSD 3-Clause License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026, QuatroPe
|
|
9
|
+
|
|
10
|
+
Redistribution and use in source and binary forms, with or without
|
|
11
|
+
modification, are permitted provided that the following conditions are met:
|
|
12
|
+
|
|
13
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
14
|
+
list of conditions and the following disclaimer.
|
|
15
|
+
|
|
16
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
17
|
+
this list of conditions and the following disclaimer in the documentation
|
|
18
|
+
and/or other materials provided with the distribution.
|
|
19
|
+
|
|
20
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
21
|
+
contributors may be used to endorse or promote products derived from
|
|
22
|
+
this software without specific prior written permission.
|
|
23
|
+
|
|
24
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
25
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
26
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
27
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
28
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
29
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
30
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
31
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
32
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
33
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
34
|
+
|
|
35
|
+
Project-URL: Repository, https://github.com/quatrope/hyppo
|
|
36
|
+
Project-URL: Issues, https://github.com/quatrope/hyppo/issues
|
|
37
|
+
Keywords: hyperspectral,image-processing,feature-extraction,remote-sensing
|
|
38
|
+
Classifier: Development Status :: 3 - Alpha
|
|
39
|
+
Classifier: Intended Audience :: Education
|
|
40
|
+
Classifier: Intended Audience :: Science/Research
|
|
41
|
+
Classifier: License :: OSI Approved :: BSD License
|
|
42
|
+
Classifier: Operating System :: OS Independent
|
|
43
|
+
Classifier: Programming Language :: Python :: 3
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
45
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
46
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
47
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
48
|
+
Classifier: Topic :: Scientific/Engineering :: Image Processing
|
|
49
|
+
Requires-Python: >=3.11
|
|
50
|
+
Description-Content-Type: text/markdown
|
|
51
|
+
License-File: LICENSE
|
|
52
|
+
Requires-Dist: dask[distributed]>=2024.1
|
|
53
|
+
Requires-Dist: h5py>=3.10
|
|
54
|
+
Requires-Dist: numpy>=2.0
|
|
55
|
+
Requires-Dist: scipy>=1.13
|
|
56
|
+
Requires-Dist: networkx>=3.0
|
|
57
|
+
Requires-Dist: pyyaml>=6.0
|
|
58
|
+
Requires-Dist: attrs>=22.0
|
|
59
|
+
Requires-Dist: scikit-learn>=1.3
|
|
60
|
+
Requires-Dist: scikit-image>=0.22
|
|
61
|
+
Requires-Dist: PyWavelets>=1.5
|
|
62
|
+
Requires-Dist: pandas>=2.2
|
|
63
|
+
Requires-Dist: matplotlib>=3.7
|
|
64
|
+
Provides-Extra: dev
|
|
65
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
66
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
67
|
+
Requires-Dist: flake8>=6.0; extra == "dev"
|
|
68
|
+
Requires-Dist: tox>=4.0; extra == "dev"
|
|
69
|
+
Requires-Dist: radon>=5.1; extra == "dev"
|
|
70
|
+
Requires-Dist: xenon>=0.9; extra == "dev"
|
|
71
|
+
Dynamic: license-file
|
|
72
|
+
|
|
73
|
+
# hyppo-hsi
|
|
74
|
+
|
|
75
|
+
**Modular feature extractor for hyperspectral images**
|
|
76
|
+
|
|
77
|
+
[](https://quatrope.github.io/)
|
|
78
|
+
[](https://pypi.org/project/hyppo-hsi/)
|
|
79
|
+
[](https://www.tldrlegal.com/l/bsd3)
|
|
80
|
+
[](https://pypi.org/project/hyppo-hsi/)
|
|
81
|
+
|
|
82
|
+
**HYPPO** is a modular feature extraction library for hyperspectral images (HSI). It provides a uniform, configurable interface for computing spectral and spatial features used in hyperspectral image classification.
|
|
83
|
+
|
|
84
|
+
## Features
|
|
85
|
+
|
|
86
|
+
- 17 feature extractors (spectral indices, dimensionality reduction, texture, morphological, wavelet, moment-based)
|
|
87
|
+
- Automatic dependency resolution between extractors via DAG
|
|
88
|
+
- Multiple execution backends: sequential, Dask threads/processes, multiprocessing with shared memory
|
|
89
|
+
- HDF5 I/O for HSI loading and feature saving
|
|
90
|
+
- YAML/JSON configuration for reproducible pipelines
|
|
91
|
+
- Python 3.11–3.14 support
|
|
92
|
+
|
|
93
|
+
## Installation
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install hyppo-hsi
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Code Repository & Issues
|
|
100
|
+
|
|
101
|
+
<https://github.com/quatrope/hyppo>
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
HYPPO is under the [BSD 3-Clause License](https://raw.githubusercontent.com/quatrope/hyppo/master/LICENSE).
|
|
106
|
+
|
|
107
|
+
Copyright (c) 2026, QuatroPe.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# hyppo-hsi
|
|
2
|
+
|
|
3
|
+
**Modular feature extractor for hyperspectral images**
|
|
4
|
+
|
|
5
|
+
[](https://quatrope.github.io/)
|
|
6
|
+
[](https://pypi.org/project/hyppo-hsi/)
|
|
7
|
+
[](https://www.tldrlegal.com/l/bsd3)
|
|
8
|
+
[](https://pypi.org/project/hyppo-hsi/)
|
|
9
|
+
|
|
10
|
+
**HYPPO** is a modular feature extraction library for hyperspectral images (HSI). It provides a uniform, configurable interface for computing spectral and spatial features used in hyperspectral image classification.
|
|
11
|
+
|
|
12
|
+
## Features
|
|
13
|
+
|
|
14
|
+
- 17 feature extractors (spectral indices, dimensionality reduction, texture, morphological, wavelet, moment-based)
|
|
15
|
+
- Automatic dependency resolution between extractors via DAG
|
|
16
|
+
- Multiple execution backends: sequential, Dask threads/processes, multiprocessing with shared memory
|
|
17
|
+
- HDF5 I/O for HSI loading and feature saving
|
|
18
|
+
- YAML/JSON configuration for reproducible pipelines
|
|
19
|
+
- Python 3.11–3.14 support
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
pip install hyppo-hsi
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Code Repository & Issues
|
|
28
|
+
|
|
29
|
+
<https://github.com/quatrope/hyppo>
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
HYPPO is under the [BSD 3-Clause License](https://raw.githubusercontent.com/quatrope/hyppo/master/LICENSE).
|
|
34
|
+
|
|
35
|
+
Copyright (c) 2026, QuatroPe.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""HYPPO: Hyperspectral Processing for feature extraction."""
|
|
2
|
+
|
|
3
|
+
import importlib.metadata
|
|
4
|
+
|
|
5
|
+
import hyppo.core as core
|
|
6
|
+
import hyppo.extractor as extractor
|
|
7
|
+
import hyppo.io as io
|
|
8
|
+
import hyppo.runner as runner
|
|
9
|
+
|
|
10
|
+
NAME = "hyppo-hsi"
|
|
11
|
+
|
|
12
|
+
__version__ = importlib.metadata.version(NAME)
|
|
13
|
+
|
|
14
|
+
__all__ = ["core", "runner", "io", "extractor", "__version__"]
|
|
15
|
+
|
|
16
|
+
del importlib
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Core module for hyperspectral feature extraction."""
|
|
2
|
+
|
|
3
|
+
from ._feature_space.dependency_graph import FeatureDependencyGraph
|
|
4
|
+
from ._feature_space.feature import Feature, FeatureCollection
|
|
5
|
+
from ._feature_space.feature_space import FeatureSpace
|
|
6
|
+
from ._hsi import HSI
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"FeatureSpace",
|
|
10
|
+
"HSI",
|
|
11
|
+
"Feature",
|
|
12
|
+
"FeatureCollection",
|
|
13
|
+
"FeatureDependencyGraph",
|
|
14
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Feature space subpackage."""
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
import networkx as nx
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from hyppo.extractor.base import Extractor
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FeatureDependencyGraph:
|
|
10
|
+
"""Manages feature extraction dependencies as a directed acyclic graph."""
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
"""Initialize dependency graph with empty graph and mappings."""
|
|
14
|
+
self.graph = nx.DiGraph()
|
|
15
|
+
self.extractors = {}
|
|
16
|
+
self.input_mappings = {}
|
|
17
|
+
|
|
18
|
+
def add_extractor(
|
|
19
|
+
self,
|
|
20
|
+
name: str,
|
|
21
|
+
extractor: "Extractor",
|
|
22
|
+
input_mapping: dict | None = None,
|
|
23
|
+
):
|
|
24
|
+
"""
|
|
25
|
+
Add extractor with input mapping to dependency graph.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
name : Unique name for this extractor instance
|
|
30
|
+
extractor : The extractor instance
|
|
31
|
+
input_mapping : dict mapping {input_name: source_extractor_name}
|
|
32
|
+
"""
|
|
33
|
+
if input_mapping is None:
|
|
34
|
+
input_mapping = {}
|
|
35
|
+
|
|
36
|
+
# Add node to graph
|
|
37
|
+
self.graph.add_node(name, extractor=extractor)
|
|
38
|
+
self.extractors[name] = extractor
|
|
39
|
+
self.input_mappings[name] = input_mapping
|
|
40
|
+
|
|
41
|
+
# Add edges for dependencies
|
|
42
|
+
for input_name, source_name in input_mapping.items():
|
|
43
|
+
# Add edge from source to this extractor
|
|
44
|
+
self.graph.add_edge(source_name, name, input_name=input_name)
|
|
45
|
+
|
|
46
|
+
def validate(self) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Validate the dependency graph for cycles and type compatibility.
|
|
49
|
+
|
|
50
|
+
Raises
|
|
51
|
+
------
|
|
52
|
+
ValueError : If circular dependencies are detected
|
|
53
|
+
TypeError : If input type mismatches are found
|
|
54
|
+
ValueError : If required inputs are missing
|
|
55
|
+
"""
|
|
56
|
+
# Check for cycles using NetworkX
|
|
57
|
+
if not nx.is_directed_acyclic_graph(self.graph):
|
|
58
|
+
cycles = list(nx.simple_cycles(self.graph))
|
|
59
|
+
raise ValueError(f"Circular dependencies detected: {cycles}")
|
|
60
|
+
|
|
61
|
+
# Validate types and requirements
|
|
62
|
+
self._validate_types_and_requirements()
|
|
63
|
+
|
|
64
|
+
def _check_required_input(
|
|
65
|
+
self, node_name, input_name, dep_spec, input_mapping
|
|
66
|
+
):
|
|
67
|
+
"""Check that a required input is present in the mapping."""
|
|
68
|
+
is_required = dep_spec.get("required", True)
|
|
69
|
+
if is_required and input_name not in input_mapping:
|
|
70
|
+
msg = (
|
|
71
|
+
f"Extractor '{node_name}' requires input "
|
|
72
|
+
f"'{input_name}' but it was not provided"
|
|
73
|
+
)
|
|
74
|
+
raise ValueError(msg)
|
|
75
|
+
|
|
76
|
+
def _validate_single_dependency(
|
|
77
|
+
self, node_name, input_name, dep_spec, input_mapping
|
|
78
|
+
) -> None:
|
|
79
|
+
"""Validate a single input dependency for an extractor."""
|
|
80
|
+
self._check_required_input(
|
|
81
|
+
node_name, input_name, dep_spec, input_mapping
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
if input_name not in input_mapping:
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
source_name = input_mapping[input_name]
|
|
88
|
+
if source_name not in self.extractors:
|
|
89
|
+
msg = (
|
|
90
|
+
f"Source extractor '{source_name}' not found "
|
|
91
|
+
f"for input '{input_name}' of '{node_name}'"
|
|
92
|
+
)
|
|
93
|
+
raise ValueError(msg)
|
|
94
|
+
|
|
95
|
+
source_extractor = self.extractors[source_name]
|
|
96
|
+
expected_type = dep_spec["extractor"]
|
|
97
|
+
if not isinstance(source_extractor, expected_type):
|
|
98
|
+
msg = (
|
|
99
|
+
f"Type mismatch for input '{input_name}' "
|
|
100
|
+
f"of '{node_name}': "
|
|
101
|
+
f"expected {expected_type.__name__}, "
|
|
102
|
+
f"got {type(source_extractor).__name__}"
|
|
103
|
+
)
|
|
104
|
+
raise TypeError(msg)
|
|
105
|
+
|
|
106
|
+
def _validate_types_and_requirements(self) -> None:
|
|
107
|
+
"""Validate input types and requirements for all extractors."""
|
|
108
|
+
for node_name in self.graph.nodes():
|
|
109
|
+
extractor = self.extractors[node_name]
|
|
110
|
+
input_deps = extractor.get_input_dependencies()
|
|
111
|
+
input_mapping = self.input_mappings.get(node_name, {})
|
|
112
|
+
|
|
113
|
+
for input_name, dep_spec in input_deps.items():
|
|
114
|
+
self._validate_single_dependency(
|
|
115
|
+
node_name, input_name, dep_spec, input_mapping
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def get_execution_order(self) -> list[str]:
|
|
119
|
+
"""
|
|
120
|
+
Get the execution order for the provided dependencies.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
list of extractor names in execution order
|
|
125
|
+
"""
|
|
126
|
+
try:
|
|
127
|
+
return list(nx.topological_sort(self.graph))
|
|
128
|
+
except (nx.NetworkXError, nx.NetworkXUnfeasible) as e:
|
|
129
|
+
raise ValueError(f"Cannot determine execution order: {e}")
|
|
130
|
+
|
|
131
|
+
def get_execution_layers(self) -> list[list[str]]:
|
|
132
|
+
"""
|
|
133
|
+
Get extractors organized in layers for parallel execution.
|
|
134
|
+
|
|
135
|
+
Each layer has dependencies on the previous layer.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
List of layers with extractors that can run in parallel
|
|
140
|
+
"""
|
|
141
|
+
layers = []
|
|
142
|
+
remaining_nodes = set(self.graph.nodes())
|
|
143
|
+
|
|
144
|
+
while remaining_nodes:
|
|
145
|
+
# Find nodes with no incoming edges from remaining nodes
|
|
146
|
+
current_layer = []
|
|
147
|
+
for node in list(remaining_nodes):
|
|
148
|
+
# Check if all predecessors are already processed
|
|
149
|
+
predecessors = set(self.graph.predecessors(node))
|
|
150
|
+
if predecessors.isdisjoint(remaining_nodes):
|
|
151
|
+
current_layer.append(node)
|
|
152
|
+
|
|
153
|
+
msg = (
|
|
154
|
+
f"Cannot compute execution layers. "
|
|
155
|
+
f"Remaining nodes: {remaining_nodes}"
|
|
156
|
+
)
|
|
157
|
+
assert current_layer, msg
|
|
158
|
+
|
|
159
|
+
layers.append(current_layer)
|
|
160
|
+
remaining_nodes.difference_update(current_layer)
|
|
161
|
+
|
|
162
|
+
return layers
|
|
163
|
+
|
|
164
|
+
def get_dependencies_for(self, extractor_name: str) -> set[str]:
|
|
165
|
+
"""Get all transitive dependencies for an extractor."""
|
|
166
|
+
if extractor_name not in self.graph:
|
|
167
|
+
return set()
|
|
168
|
+
return set(nx.ancestors(self.graph, extractor_name))
|
|
169
|
+
|
|
170
|
+
def get_dependents_of(self, extractor_name: str) -> set[str]:
|
|
171
|
+
"""Get all extractors that depend on this one."""
|
|
172
|
+
if extractor_name not in self.graph:
|
|
173
|
+
return set()
|
|
174
|
+
return set(nx.descendants(self.graph, extractor_name))
|
|
175
|
+
|
|
176
|
+
def get_input_mapping_for(self, extractor_name: str) -> dict[str, str]:
|
|
177
|
+
"""Get the input mapping for a specific extractor."""
|
|
178
|
+
return self.input_mappings.get(extractor_name, {}).copy()
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from hyppo.utils.bunch import Bunch
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Feature(Bunch):
|
|
7
|
+
"""
|
|
8
|
+
Dictionary for feature extraction results.
|
|
9
|
+
|
|
10
|
+
Examples
|
|
11
|
+
--------
|
|
12
|
+
>>> result = Feature({'mean': [1, 2, 3], 'std': [0.1, 0.2, 0.3]})
|
|
13
|
+
>>> result.mean
|
|
14
|
+
[1, 2, 3]
|
|
15
|
+
>>> result['mean']
|
|
16
|
+
[1, 2, 3]
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(self, data, extractor, inputs_used):
|
|
20
|
+
"""Initialize Feature with data, extractor, and inputs used."""
|
|
21
|
+
mapping = {
|
|
22
|
+
"result": data.get("features", None),
|
|
23
|
+
"data": data,
|
|
24
|
+
"extractor": extractor,
|
|
25
|
+
"inputs_used": inputs_used,
|
|
26
|
+
}
|
|
27
|
+
super().__init__("Feature", mapping)
|
|
28
|
+
|
|
29
|
+
@staticmethod
|
|
30
|
+
def _get_features_shape(data):
|
|
31
|
+
"""Extract shape from features entry if available."""
|
|
32
|
+
if not isinstance(data, dict) or "features" not in data:
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
features = data["features"]
|
|
36
|
+
if hasattr(features, "shape"):
|
|
37
|
+
return features.shape
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def describe(self):
|
|
41
|
+
"""
|
|
42
|
+
Get summary information about this feature result.
|
|
43
|
+
|
|
44
|
+
Returns
|
|
45
|
+
-------
|
|
46
|
+
Dictionary with dimensions and extra_data keys
|
|
47
|
+
"""
|
|
48
|
+
data = self.get("data", {})
|
|
49
|
+
features_shape = self._get_features_shape(data)
|
|
50
|
+
|
|
51
|
+
extra_keys = []
|
|
52
|
+
if isinstance(data, dict):
|
|
53
|
+
extra_keys = [k for k in data.keys() if k != "features"]
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
"dimensions": features_shape,
|
|
57
|
+
"extra_data": ", ".join(extra_keys) if extra_keys else "",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class FeatureCollection(Bunch):
|
|
62
|
+
"""
|
|
63
|
+
Collection of Feature objects.
|
|
64
|
+
|
|
65
|
+
This class manages results from multiple feature extractors.
|
|
66
|
+
|
|
67
|
+
Examples
|
|
68
|
+
--------
|
|
69
|
+
>>> results = FeatureCollection()
|
|
70
|
+
>>> results['mean'] = Feature({'data': [1, 2, 3]})
|
|
71
|
+
>>> results.mean.data
|
|
72
|
+
[1, 2, 3]
|
|
73
|
+
>>> results['mean']['data']
|
|
74
|
+
[1, 2, 3]
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
def __init__(self, data: dict[str, Feature]):
|
|
78
|
+
"""Initialize FeatureCollection with dictionary of Feature objects."""
|
|
79
|
+
super().__init__("FeatureCollection", data)
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def from_features(
|
|
83
|
+
cls, features: dict[str, Feature]
|
|
84
|
+
) -> "FeatureCollection":
|
|
85
|
+
"""
|
|
86
|
+
Create a FeatureCollection from a dictionary of features.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
features : Dictionary of features (extractor_name -> Feature)
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
FeatureCollection
|
|
95
|
+
"""
|
|
96
|
+
return cls(features)
|
|
97
|
+
|
|
98
|
+
def get_all_features(self):
|
|
99
|
+
"""Get all extracted feature data (without metadata)."""
|
|
100
|
+
features = {}
|
|
101
|
+
for extractor_name, result in self.items():
|
|
102
|
+
if hasattr(result, "data") and isinstance(result.data, dict):
|
|
103
|
+
features.update(result.data)
|
|
104
|
+
else:
|
|
105
|
+
features[extractor_name] = (
|
|
106
|
+
result.data if hasattr(result, "data") else result
|
|
107
|
+
)
|
|
108
|
+
return features
|
|
109
|
+
|
|
110
|
+
def get_metadata(self):
|
|
111
|
+
"""Get metadata about extractors and their usage."""
|
|
112
|
+
metadata: dict[str, dict] = {}
|
|
113
|
+
for extractor_name, result in self.items():
|
|
114
|
+
if isinstance(result, Feature):
|
|
115
|
+
metadata[extractor_name] = {
|
|
116
|
+
"extractor_type": (
|
|
117
|
+
type(result.extractor).__name__
|
|
118
|
+
if result.extractor
|
|
119
|
+
else None
|
|
120
|
+
),
|
|
121
|
+
"inputs_used": result.inputs_used,
|
|
122
|
+
"feature_keys": (
|
|
123
|
+
list(result.data.keys())
|
|
124
|
+
if isinstance(result.data, dict)
|
|
125
|
+
else []
|
|
126
|
+
),
|
|
127
|
+
}
|
|
128
|
+
return metadata
|
|
129
|
+
|
|
130
|
+
def get_extractor_names(self) -> list:
|
|
131
|
+
"""Get list of all extractor names."""
|
|
132
|
+
return list(self.keys())
|
|
133
|
+
|
|
134
|
+
def to_dict(self):
|
|
135
|
+
"""Convert to a regular nested dictionary."""
|
|
136
|
+
return {
|
|
137
|
+
name: (
|
|
138
|
+
result.to_dict()
|
|
139
|
+
if isinstance(result, Feature)
|
|
140
|
+
else dict(result)
|
|
141
|
+
)
|
|
142
|
+
for name, result in self.items()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
def describe(self) -> pd.DataFrame:
|
|
146
|
+
"""
|
|
147
|
+
Get summary information for all feature results.
|
|
148
|
+
|
|
149
|
+
Returns
|
|
150
|
+
-------
|
|
151
|
+
DataFrame with columns:
|
|
152
|
+
- feature_name: Name of each feature
|
|
153
|
+
- dimensions: Shape of the 'features' array
|
|
154
|
+
- extra_data: Comma-separated list of extra data keys
|
|
155
|
+
"""
|
|
156
|
+
rows = []
|
|
157
|
+
for feature_name, result in self.items():
|
|
158
|
+
info = result.describe()
|
|
159
|
+
info["feature_name"] = feature_name
|
|
160
|
+
rows.append(info)
|
|
161
|
+
|
|
162
|
+
columns = ["feature_name", "dimensions", "extra_data"]
|
|
163
|
+
return pd.DataFrame(rows, columns=columns)
|
|
164
|
+
|
|
165
|
+
def save(self, path) -> None:
|
|
166
|
+
"""
|
|
167
|
+
Save this FeatureCollection to HDF5 file.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
path : Output file path (must have .h5 extension)
|
|
172
|
+
|
|
173
|
+
Examples
|
|
174
|
+
--------
|
|
175
|
+
>>> results = fs.extract(hsi)
|
|
176
|
+
>>> results.save("output.h5")
|
|
177
|
+
"""
|
|
178
|
+
from hyppo import io
|
|
179
|
+
|
|
180
|
+
io.save_feature_collection(self, path)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
__all__ = ["Feature", "FeatureCollection"]
|