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.
Files changed (60) hide show
  1. hyppo_hsi-0.1.0/CHANGELOG.md +31 -0
  2. hyppo_hsi-0.1.0/LICENSE +28 -0
  3. hyppo_hsi-0.1.0/MANIFEST.in +32 -0
  4. hyppo_hsi-0.1.0/PKG-INFO +107 -0
  5. hyppo_hsi-0.1.0/README.md +35 -0
  6. hyppo_hsi-0.1.0/hyppo/__init__.py +16 -0
  7. hyppo_hsi-0.1.0/hyppo/core/__init__.py +14 -0
  8. hyppo_hsi-0.1.0/hyppo/core/_feature_space/__init__.py +1 -0
  9. hyppo_hsi-0.1.0/hyppo/core/_feature_space/dependency_graph.py +178 -0
  10. hyppo_hsi-0.1.0/hyppo/core/_feature_space/feature.py +183 -0
  11. hyppo_hsi-0.1.0/hyppo/core/_feature_space/feature_space.py +195 -0
  12. hyppo_hsi-0.1.0/hyppo/core/_hsi.py +202 -0
  13. hyppo_hsi-0.1.0/hyppo/core/_hsi_plot.py +48 -0
  14. hyppo_hsi-0.1.0/hyppo/extractor/__init__.py +61 -0
  15. hyppo_hsi-0.1.0/hyppo/extractor/_dwt_utils.py +23 -0
  16. hyppo_hsi-0.1.0/hyppo/extractor/_spectral_utils.py +50 -0
  17. hyppo_hsi-0.1.0/hyppo/extractor/_validators.py +62 -0
  18. hyppo_hsi-0.1.0/hyppo/extractor/base.py +56 -0
  19. hyppo_hsi-0.1.0/hyppo/extractor/dwt1d.py +149 -0
  20. hyppo_hsi-0.1.0/hyppo/extractor/dwt2d.py +151 -0
  21. hyppo_hsi-0.1.0/hyppo/extractor/dwt3d.py +132 -0
  22. hyppo_hsi-0.1.0/hyppo/extractor/gabor.py +274 -0
  23. hyppo_hsi-0.1.0/hyppo/extractor/geometricmoment.py +212 -0
  24. hyppo_hsi-0.1.0/hyppo/extractor/glcm.py +276 -0
  25. hyppo_hsi-0.1.0/hyppo/extractor/ica.py +143 -0
  26. hyppo_hsi-0.1.0/hyppo/extractor/lbp.py +194 -0
  27. hyppo_hsi-0.1.0/hyppo/extractor/legendremoment.py +212 -0
  28. hyppo_hsi-0.1.0/hyppo/extractor/mnf.py +171 -0
  29. hyppo_hsi-0.1.0/hyppo/extractor/mp.py +244 -0
  30. hyppo_hsi-0.1.0/hyppo/extractor/ndvi.py +104 -0
  31. hyppo_hsi-0.1.0/hyppo/extractor/ndwi.py +105 -0
  32. hyppo_hsi-0.1.0/hyppo/extractor/pca.py +117 -0
  33. hyppo_hsi-0.1.0/hyppo/extractor/pp.py +274 -0
  34. hyppo_hsi-0.1.0/hyppo/extractor/registry.py +130 -0
  35. hyppo_hsi-0.1.0/hyppo/extractor/savi.py +113 -0
  36. hyppo_hsi-0.1.0/hyppo/extractor/zernikemoment.py +232 -0
  37. hyppo_hsi-0.1.0/hyppo/io/__init__.py +24 -0
  38. hyppo_hsi-0.1.0/hyppo/io/_config/__init__.py +13 -0
  39. hyppo_hsi-0.1.0/hyppo/io/_config/config.py +100 -0
  40. hyppo_hsi-0.1.0/hyppo/io/_config/loader.py +284 -0
  41. hyppo_hsi-0.1.0/hyppo/io/_config/saver.py +169 -0
  42. hyppo_hsi-0.1.0/hyppo/io/_features/__init__.py +3 -0
  43. hyppo_hsi-0.1.0/hyppo/io/_features/h5.py +92 -0
  44. hyppo_hsi-0.1.0/hyppo/io/_hsi/__init__.py +1 -0
  45. hyppo_hsi-0.1.0/hyppo/io/_hsi/h5.py +177 -0
  46. hyppo_hsi-0.1.0/hyppo/runner/__init__.py +31 -0
  47. hyppo_hsi-0.1.0/hyppo/runner/base.py +41 -0
  48. hyppo_hsi-0.1.0/hyppo/runner/dask.py +278 -0
  49. hyppo_hsi-0.1.0/hyppo/runner/local_process.py +366 -0
  50. hyppo_hsi-0.1.0/hyppo/runner/registry.py +165 -0
  51. hyppo_hsi-0.1.0/hyppo/runner/sequential.py +67 -0
  52. hyppo_hsi-0.1.0/hyppo/utils/__init__.py +1 -0
  53. hyppo_hsi-0.1.0/hyppo/utils/bunch.py +135 -0
  54. hyppo_hsi-0.1.0/hyppo_hsi.egg-info/PKG-INFO +107 -0
  55. hyppo_hsi-0.1.0/hyppo_hsi.egg-info/SOURCES.txt +58 -0
  56. hyppo_hsi-0.1.0/hyppo_hsi.egg-info/dependency_links.txt +1 -0
  57. hyppo_hsi-0.1.0/hyppo_hsi.egg-info/requires.txt +20 -0
  58. hyppo_hsi-0.1.0/hyppo_hsi.egg-info/top_level.txt +1 -0
  59. hyppo_hsi-0.1.0/pyproject.toml +89 -0
  60. 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.
@@ -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__ *
@@ -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
+ [![QuatroPe](https://img.shields.io/badge/QuatroPe-Applications-1c5896)](https://quatrope.github.io/)
78
+ [![PyPI](https://img.shields.io/pypi/v/hyppo-hsi)](https://pypi.org/project/hyppo-hsi/)
79
+ [![License](https://img.shields.io/pypi/l/hyppo-hsi?color=blue)](https://www.tldrlegal.com/l/bsd3)
80
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](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
+ [![QuatroPe](https://img.shields.io/badge/QuatroPe-Applications-1c5896)](https://quatrope.github.io/)
6
+ [![PyPI](https://img.shields.io/pypi/v/hyppo-hsi)](https://pypi.org/project/hyppo-hsi/)
7
+ [![License](https://img.shields.io/pypi/l/hyppo-hsi?color=blue)](https://www.tldrlegal.com/l/bsd3)
8
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](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"]