telemetry-anomdet 0.1.0__tar.gz → 0.2.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.
- telemetry_anomdet-0.2.0/PKG-INFO +222 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/README.md +59 -8
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/pyproject.toml +33 -20
- telemetry_anomdet-0.2.0/src/telemetry_anomdet/evaluation.py +388 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/ingest/csv_loader.py +13 -5
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/ingest/smap.py +29 -3
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/models/__init__.py +3 -2
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/models/base.py +7 -7
- telemetry_anomdet-0.2.0/src/telemetry_anomdet/models/deep/__init__.py +18 -0
- telemetry_anomdet-0.2.0/src/telemetry_anomdet/models/deep/_kan.py +217 -0
- telemetry_anomdet-0.2.0/src/telemetry_anomdet/models/deep/_net.py +233 -0
- telemetry_anomdet-0.2.0/src/telemetry_anomdet/models/deep/codegen.py +905 -0
- telemetry_anomdet-0.2.0/src/telemetry_anomdet/models/deep/distill.py +440 -0
- telemetry_anomdet-0.2.0/src/telemetry_anomdet/models/deep/gdn.py +421 -0
- telemetry_anomdet-0.2.0/src/telemetry_anomdet/models/deep/kan_gdn.py +99 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/models/ensemble.py +1 -1
- telemetry_anomdet-0.2.0/src/telemetry_anomdet/thresholding.py +393 -0
- telemetry_anomdet-0.2.0/src/telemetry_anomdet.egg-info/PKG-INFO +222 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet.egg-info/SOURCES.txt +20 -1
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet.egg-info/requires.txt +3 -1
- telemetry_anomdet-0.2.0/tests/test_csv_loader.py +263 -0
- telemetry_anomdet-0.2.0/tests/test_deep_codegen.py +696 -0
- telemetry_anomdet-0.2.0/tests/test_deep_distill.py +218 -0
- telemetry_anomdet-0.2.0/tests/test_deep_gdn.py +268 -0
- telemetry_anomdet-0.2.0/tests/test_deep_kan.py +85 -0
- telemetry_anomdet-0.2.0/tests/test_deep_kangdn.py +110 -0
- telemetry_anomdet-0.2.0/tests/test_deep_smoothing.py +103 -0
- telemetry_anomdet-0.2.0/tests/test_evaluation_prauc.py +87 -0
- telemetry_anomdet-0.2.0/tests/test_evaluation_sequences.py +122 -0
- telemetry_anomdet-0.2.0/tests/test_score_channels.py +167 -0
- telemetry_anomdet-0.2.0/tests/test_threshold_budget.py +112 -0
- telemetry_anomdet-0.2.0/tests/test_thresholding.py +271 -0
- telemetry_anomdet-0.1.0/PKG-INFO +0 -364
- telemetry_anomdet-0.1.0/src/telemetry_anomdet/evaluation.py +0 -167
- telemetry_anomdet-0.1.0/src/telemetry_anomdet/visualization.py +0 -13
- telemetry_anomdet-0.1.0/src/telemetry_anomdet.egg-info/PKG-INFO +0 -364
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/LICENSE +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/setup.cfg +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/__init__.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/feature_extraction/features.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/ingest/__init__.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/ingest/ccsds_loader.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/ingest/dataset.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/models/supervised/__init__.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/models/unsupervised/__init__.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/models/unsupervised/gaussian_nb.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/models/unsupervised/isolation_forest.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/models/unsupervised/kmeans.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/models/unsupervised/pca.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/preprocessing/__init__.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/preprocessing/preprocessing.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/py.typed +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet/version.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet.egg-info/dependency_links.txt +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/src/telemetry_anomdet.egg-info/top_level.txt +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/tests/test_base_detector.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/tests/test_ensemble.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/tests/test_evaluation.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/tests/test_import.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/tests/test_preprocessing.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/tests/test_smap.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/tests/test_unsup_kmeans.py +0 -0
- {telemetry_anomdet-0.1.0 → telemetry_anomdet-0.2.0}/tests/test_unsup_pca.py +0 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: telemetry-anomdet
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Spacecraft telemetry anomaly detection: classical and graph deep detectors, label-free thresholding, and distillation to C for flight hardware.
|
|
5
|
+
Author-email: Julio Anzaldo <anzaldojuliojesus@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/JulioAnzaldo/telemetry-anomdet
|
|
8
|
+
Project-URL: Repository, https://github.com/JulioAnzaldo/telemetry-anomdet
|
|
9
|
+
Keywords: telemetry,anomaly-detection,machine-learning,python
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Science/Research
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Natural Language :: English
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: C
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
26
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
27
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
28
|
+
Classifier: Topic :: System :: Monitoring
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Requires-Python: >=3.10
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
License-File: LICENSE
|
|
33
|
+
Requires-Dist: pandas<3.0.0,>=2.2.2
|
|
34
|
+
Requires-Dist: numpy<3.0,>=2.1
|
|
35
|
+
Requires-Dist: scikit-learn<2.0,>=1.3
|
|
36
|
+
Requires-Dist: typing-extensions>=4.16.0
|
|
37
|
+
Provides-Extra: ccsds
|
|
38
|
+
Requires-Dist: ccsdspy<3.0,>=2.0.0; extra == "ccsds"
|
|
39
|
+
Provides-Extra: deep
|
|
40
|
+
Requires-Dist: torch>=2.0; extra == "deep"
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
|
|
43
|
+
<h2 align="center">telemetry-anomdet</h2>
|
|
44
|
+
|
|
45
|
+
<p align="center">
|
|
46
|
+
<a href="https://julioanzaldo.github.io/telemetry-anomdet/"><img alt="Documentation" src="https://img.shields.io/badge/Docs-Online-blue"></a>
|
|
47
|
+
<a href="https://test.pypi.org/project/telemetry-anomdet/"><img alt="Test PyPI" src="https://img.shields.io/badge/Test-PyPI-yellow"></a>
|
|
48
|
+
<a href="https://pypi.org/project/telemetry-anomdet/"><img alt="PyPI" src="https://img.shields.io/pypi/v/telemetry-anomdet?label=PyPi"></a>
|
|
49
|
+
<a href="https://github.com/JulioAnzaldo/telemetry-anomdet/actions/workflows/ci.yml"><img alt="Testing" src="https://img.shields.io/github/actions/workflow/status/JulioAnzaldo/telemetry-anomdet/ci.yml?branch=main&label=Testing&logo=github&logoColor=white"></a>
|
|
50
|
+
<a href="https://github.com/JulioAnzaldo/telemetry-anomdet/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/License-Apache%202.0-blue"></a>
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
**telemetry-anomdet** is an open-source anomaly detection toolkit for spacecraft telemetry. It runs classical and graph-based deep detectors behind one interface, selects alarm thresholds without labels, and distills a trained detector down to Power of Ten conformant C that runs on flight hardware. Per-channel SHAP attribution and LLM-generated diagnostic reports are on the roadmap.
|
|
54
|
+
|
|
55
|
+
Benchmarked on SMAP (NASA). MSL (NASA) and ESA-ADB (ESA) results land in v0.3.0.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<table>
|
|
60
|
+
<tr>
|
|
61
|
+
<td align="center"><a href="https://julioanzaldo.github.io/telemetry-anomdet/getting_started.html"><b>Getting Started</b></a><br>Installation and setup</td>
|
|
62
|
+
<td align="center"><a href="https://julioanzaldo.github.io/telemetry-anomdet/api/models/base.html"><b>API Reference</b></a><br>BaseDetector, models, ensemble</td>
|
|
63
|
+
<td align="center"><a href="https://julioanzaldo.github.io/telemetry-anomdet/tutorials/real_time_example.html"><b>Tutorial</b></a><br>End-to-end detection example</td>
|
|
64
|
+
<td align="center"><a href="https://julioanzaldo.github.io/telemetry-anomdet/user_guide/pipeline_overview.html"><b>Pipeline Overview</b></a><br>Ingest, detect, explain</td>
|
|
65
|
+
<td align="center"><a href="https://github.com/JulioAnzaldo/telemetry-anomdet/discussions"><b>Discussions</b></a><br>Questions and feedback</td>
|
|
66
|
+
</tr>
|
|
67
|
+
</table>
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Quick Install
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# recommended
|
|
75
|
+
uv add telemetry-anomdet
|
|
76
|
+
|
|
77
|
+
# or with pip
|
|
78
|
+
pip install telemetry-anomdet
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The base install is torch-free. The deep detectors (`GDN`, `KANGDN`) need the
|
|
82
|
+
`deep` extra:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
uv add "telemetry-anomdet[deep]"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Example Usage
|
|
89
|
+
|
|
90
|
+
### Fit the Ensemble on Nominal Telemetry
|
|
91
|
+
|
|
92
|
+
All detectors are trained on **nominal data only**, no anomaly labels used during training. Labels are used exclusively for evaluation.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
import numpy as np
|
|
96
|
+
from telemetry_anomdet.models.unsupervised.pca import PCAAnomaly
|
|
97
|
+
from telemetry_anomdet.models.unsupervised.kmeans import KMeansAnomaly
|
|
98
|
+
from telemetry_anomdet.models.ensemble import AnomalyEnsemble
|
|
99
|
+
|
|
100
|
+
# X_train: (n_windows, window_size, n_features), produced by windowify()
|
|
101
|
+
X_train = ...
|
|
102
|
+
|
|
103
|
+
ensemble = AnomalyEnsemble(
|
|
104
|
+
models = {
|
|
105
|
+
"pca": PCAAnomaly(n_components = 10),
|
|
106
|
+
"kmeans": KMeansAnomaly(n_clusters = 5, scale = True),
|
|
107
|
+
},
|
|
108
|
+
combine = "mean",
|
|
109
|
+
normalize = "robust",
|
|
110
|
+
percentile = 95.0,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
ensemble.fit(X_train)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Inference and Anomaly Flags
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
scores = ensemble.decision_function(X_test) # higher = more anomalous
|
|
120
|
+
flags = ensemble.is_anomaly(X_test) # boolean mask at training threshold
|
|
121
|
+
|
|
122
|
+
# Runtime sensitivity override, no retraining needed
|
|
123
|
+
flags_strict = ensemble.is_anomaly(X_test, percentile = 98.0)
|
|
124
|
+
flags_custom = ensemble.is_anomaly(X_test, threshold = 0.75)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Per-Model Score Decomposition
|
|
128
|
+
|
|
129
|
+
`score_components()` returns raw per-model scores before normalization or combination. This is the SHAP hook: perturb input channels, measure how each sensor drives each model's score independently.
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
components = ensemble.score_components(X_test)
|
|
133
|
+
# {"pca": array(100,), "kmeans": array(100,)}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Getting the SMAP dataset
|
|
137
|
+
|
|
138
|
+
The benchmarks and examples need a local copy of the NASA SMAP/MSL data, which
|
|
139
|
+
is not redistributed with the toolkit. From the root of the repo:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pip install kaggle
|
|
143
|
+
|
|
144
|
+
# Requires a Kaggle API key in ~/.kaggle/kaggle.json
|
|
145
|
+
kaggle datasets download -d patrickfleith/nasa-anomaly-detection-dataset-smap-msl \
|
|
146
|
+
&& mv nasa-anomaly-detection-dataset-smap-msl.zip data.zip \
|
|
147
|
+
&& unzip -o data.zip \
|
|
148
|
+
&& rm data.zip \
|
|
149
|
+
&& mv data/data tmp && rm -r data && mv tmp data
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
That leaves `data/train/`, `data/test/` and `labeled_anomalies.csv`. Point the
|
|
153
|
+
examples at it:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
export TAD_SMAP_DIR=data # Windows: $env:TAD_SMAP_DIR = "data"
|
|
157
|
+
python examples/smap_demo.py
|
|
158
|
+
python examples/smap_benchmark.py
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Datasets are never committed; `data/` is gitignored.
|
|
162
|
+
|
|
163
|
+
### Coming in Phase 3+
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
# Per-channel SHAP attribution (Phase 3)
|
|
167
|
+
# shap_values = explainer.explain(ensemble, X_anomalous)
|
|
168
|
+
# {"sensor_01": 0.42, "sensor_02": 0.18, ...}
|
|
169
|
+
|
|
170
|
+
# LLM diagnostic report (Phase 4)
|
|
171
|
+
# report = llm_engine.explain(shap_values, context, channel_names)
|
|
172
|
+
# DiagnosticReport(
|
|
173
|
+
# anomaly_type = "power subsystem fault",
|
|
174
|
+
# severity = "high",
|
|
175
|
+
# primary_channels = ["sensor_01", "sensor_07"],
|
|
176
|
+
# explanation = "...",
|
|
177
|
+
# recommended_action = "...",
|
|
178
|
+
# confidence = 0.87,
|
|
179
|
+
# )
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Features
|
|
183
|
+
|
|
184
|
+
**Available now**
|
|
185
|
+
|
|
186
|
+
- **One interface for every detector.** Classical and deep detectors share the same `fit` / `decision_function` / `predict` / `is_anomaly` API, so stacking or swapping models needs no per-model glue.
|
|
187
|
+
- **Graph deviation detectors.** `GDN` and `KANGDN` forecast each channel from its learned top-k neighbours and score the deviation, catching readings that are individually plausible but wrong in relation to each other.
|
|
188
|
+
- **Alarms without labels.** `threshold_for_budget` and `dynamic_threshold` pick an operating point from the score distribution alone. Operations can state an alarm rate; they cannot state a recall they have no way to observe.
|
|
189
|
+
- **Honest evaluation.** Event-level scoring alongside the conventional point-adjusted F1, plus a random baseline row in the benchmark, because point-adjusted F1 rates uniform random noise above every trained configuration on SMAP.
|
|
190
|
+
- **Runs on flight hardware.** A fitted `KANGDN` distills to a torch-free NumPy evaluator, then to Power of Ten conformant C with golden vectors. Host and ESP32-S3 targets are in [`targets/`](targets/).
|
|
191
|
+
- **Stacking ensemble** with robust (median + IQR) score normalization, built for anomaly scores that are extreme by definition.
|
|
192
|
+
- **Per-model score decomposition** via `score_components()`, the hook that enables per-channel attribution without retraining.
|
|
193
|
+
- **Spacecraft-native ingestion.** SMAP and CSV load straight into a long-form `TelemetryDataset`, no schema wrangling.
|
|
194
|
+
- **Runtime sensitivity control.** `is_anomaly()` accepts a percentile or threshold override, so operators re-tune without retraining.
|
|
195
|
+
|
|
196
|
+
**On the roadmap**
|
|
197
|
+
|
|
198
|
+
- `TranAD`: transformer-based sequence reconstruction
|
|
199
|
+
- `SHAPExplainer`: per-channel attribution over `score_components()` (Phase 3)
|
|
200
|
+
- LLM diagnostic reports, SHAP chart supplied as an image (Phase 4)
|
|
201
|
+
- Human-in-the-loop threshold feedback (Phase 5)
|
|
202
|
+
- MSL results, using the existing SMAP loader (`spacecraft = "MSL"`) (v0.3.0)
|
|
203
|
+
- ESA-ADB evaluation: the multivariate benchmark that exercises the graph (v0.3.0)
|
|
204
|
+
|
|
205
|
+
## AI Transparency
|
|
206
|
+
|
|
207
|
+
AI assistants are used while writing and maintaining this project's documentation: drafting API reference pages, tightening prose, and catching claims that go stale.
|
|
208
|
+
|
|
209
|
+
What that does not change:
|
|
210
|
+
|
|
211
|
+
- **Numbers come from runs.** Benchmark figures are produced by [`examples/smap_benchmark.py`](examples/smap_benchmark.py), and the hardware results in the deployment docs are measured on an ESP32-S3 by the conformance harness in [`targets/`](targets/). Nothing quoted in these docs is estimated or inferred.
|
|
212
|
+
- **Behavior claims are backed by tests.** The suite gates CI, and the documented API is covered by it.
|
|
213
|
+
|
|
214
|
+
Everything published here is reviewed before it lands.
|
|
215
|
+
|
|
216
|
+
## Getting Help
|
|
217
|
+
|
|
218
|
+
### Discussions
|
|
219
|
+
To suggest improvements, or ask for help, please see [GitHub Discussions](https://github.com/JulioAnzaldo/telemetry-anomdet/discussions)
|
|
220
|
+
|
|
221
|
+
### Bug reports
|
|
222
|
+
To submit a report on any bugs or issues, [open an issue here](https://github.com/JulioAnzaldo/telemetry-anomdet/issues)
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
<h2 align="center">telemetry-anomdet</h2>
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
|
-
<a href="https://julioanzaldo.github.io/telemetry-anomdet/"><img alt="Documentation" src="https://img.shields.io/badge/
|
|
4
|
+
<a href="https://julioanzaldo.github.io/telemetry-anomdet/"><img alt="Documentation" src="https://img.shields.io/badge/Docs-Online-blue"></a>
|
|
5
5
|
<a href="https://test.pypi.org/project/telemetry-anomdet/"><img alt="Test PyPI" src="https://img.shields.io/badge/Test-PyPI-yellow"></a>
|
|
6
|
-
<a href="https://pypi.org/project/telemetry-anomdet/"><img alt="PyPI" src="https://img.shields.io/pypi/v/telemetry-anomdet"></a>
|
|
7
|
-
<a href="https://github.com/JulioAnzaldo/telemetry-anomdet/actions/workflows/ci.yml"><img alt="
|
|
8
|
-
<a href="https://github.com/JulioAnzaldo/telemetry-anomdet/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/
|
|
6
|
+
<a href="https://pypi.org/project/telemetry-anomdet/"><img alt="PyPI" src="https://img.shields.io/pypi/v/telemetry-anomdet?label=PyPi"></a>
|
|
7
|
+
<a href="https://github.com/JulioAnzaldo/telemetry-anomdet/actions/workflows/ci.yml"><img alt="Testing" src="https://img.shields.io/github/actions/workflow/status/JulioAnzaldo/telemetry-anomdet/ci.yml?branch=main&label=Testing&logo=github&logoColor=white"></a>
|
|
8
|
+
<a href="https://github.com/JulioAnzaldo/telemetry-anomdet/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/License-Apache%202.0-blue"></a>
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
-
**telemetry-anomdet** is an open-source anomaly detection toolkit for spacecraft telemetry. It runs a
|
|
11
|
+
**telemetry-anomdet** is an open-source anomaly detection toolkit for spacecraft telemetry. It runs classical and graph-based deep detectors behind one interface, selects alarm thresholds without labels, and distills a trained detector down to Power of Ten conformant C that runs on flight hardware. Per-channel SHAP attribution and LLM-generated diagnostic reports are on the roadmap.
|
|
12
|
+
|
|
13
|
+
Benchmarked on SMAP (NASA). MSL (NASA) and ESA-ADB (ESA) results land in v0.3.0.
|
|
12
14
|
|
|
13
15
|
---
|
|
14
16
|
|
|
@@ -34,6 +36,13 @@ uv add telemetry-anomdet
|
|
|
34
36
|
pip install telemetry-anomdet
|
|
35
37
|
```
|
|
36
38
|
|
|
39
|
+
The base install is torch-free. The deep detectors (`GDN`, `KANGDN`) need the
|
|
40
|
+
`deep` extra:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
uv add "telemetry-anomdet[deep]"
|
|
44
|
+
```
|
|
45
|
+
|
|
37
46
|
## Example Usage
|
|
38
47
|
|
|
39
48
|
### Fit the Ensemble on Nominal Telemetry
|
|
@@ -82,6 +91,33 @@ components = ensemble.score_components(X_test)
|
|
|
82
91
|
# {"pca": array(100,), "kmeans": array(100,)}
|
|
83
92
|
```
|
|
84
93
|
|
|
94
|
+
### Getting the SMAP dataset
|
|
95
|
+
|
|
96
|
+
The benchmarks and examples need a local copy of the NASA SMAP/MSL data, which
|
|
97
|
+
is not redistributed with the toolkit. From the root of the repo:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
pip install kaggle
|
|
101
|
+
|
|
102
|
+
# Requires a Kaggle API key in ~/.kaggle/kaggle.json
|
|
103
|
+
kaggle datasets download -d patrickfleith/nasa-anomaly-detection-dataset-smap-msl \
|
|
104
|
+
&& mv nasa-anomaly-detection-dataset-smap-msl.zip data.zip \
|
|
105
|
+
&& unzip -o data.zip \
|
|
106
|
+
&& rm data.zip \
|
|
107
|
+
&& mv data/data tmp && rm -r data && mv tmp data
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
That leaves `data/train/`, `data/test/` and `labeled_anomalies.csv`. Point the
|
|
111
|
+
examples at it:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
export TAD_SMAP_DIR=data # Windows: $env:TAD_SMAP_DIR = "data"
|
|
115
|
+
python examples/smap_demo.py
|
|
116
|
+
python examples/smap_benchmark.py
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Datasets are never committed; `data/` is gitignored.
|
|
120
|
+
|
|
85
121
|
### Coming in Phase 3+
|
|
86
122
|
|
|
87
123
|
```python
|
|
@@ -106,6 +142,10 @@ components = ensemble.score_components(X_test)
|
|
|
106
142
|
**Available now**
|
|
107
143
|
|
|
108
144
|
- **One interface for every detector.** Classical and deep detectors share the same `fit` / `decision_function` / `predict` / `is_anomaly` API, so stacking or swapping models needs no per-model glue.
|
|
145
|
+
- **Graph deviation detectors.** `GDN` and `KANGDN` forecast each channel from its learned top-k neighbours and score the deviation, catching readings that are individually plausible but wrong in relation to each other.
|
|
146
|
+
- **Alarms without labels.** `threshold_for_budget` and `dynamic_threshold` pick an operating point from the score distribution alone. Operations can state an alarm rate; they cannot state a recall they have no way to observe.
|
|
147
|
+
- **Honest evaluation.** Event-level scoring alongside the conventional point-adjusted F1, plus a random baseline row in the benchmark, because point-adjusted F1 rates uniform random noise above every trained configuration on SMAP.
|
|
148
|
+
- **Runs on flight hardware.** A fitted `KANGDN` distills to a torch-free NumPy evaluator, then to Power of Ten conformant C with golden vectors. Host and ESP32-S3 targets are in [`targets/`](targets/).
|
|
109
149
|
- **Stacking ensemble** with robust (median + IQR) score normalization, built for anomaly scores that are extreme by definition.
|
|
110
150
|
- **Per-model score decomposition** via `score_components()`, the hook that enables per-channel attribution without retraining.
|
|
111
151
|
- **Spacecraft-native ingestion.** SMAP and CSV load straight into a long-form `TelemetryDataset`, no schema wrangling.
|
|
@@ -113,12 +153,23 @@ components = ensemble.score_components(X_test)
|
|
|
113
153
|
|
|
114
154
|
**On the roadmap**
|
|
115
155
|
|
|
116
|
-
- `
|
|
156
|
+
- `TranAD`: transformer-based sequence reconstruction
|
|
117
157
|
- `SHAPExplainer`: per-channel attribution over `score_components()` (Phase 3)
|
|
118
158
|
- LLM diagnostic reports, SHAP chart supplied as an image (Phase 4)
|
|
119
159
|
- Human-in-the-loop threshold feedback (Phase 5)
|
|
120
|
-
-
|
|
121
|
-
-
|
|
160
|
+
- MSL results, using the existing SMAP loader (`spacecraft = "MSL"`) (v0.3.0)
|
|
161
|
+
- ESA-ADB evaluation: the multivariate benchmark that exercises the graph (v0.3.0)
|
|
162
|
+
|
|
163
|
+
## AI Transparency
|
|
164
|
+
|
|
165
|
+
AI assistants are used while writing and maintaining this project's documentation: drafting API reference pages, tightening prose, and catching claims that go stale.
|
|
166
|
+
|
|
167
|
+
What that does not change:
|
|
168
|
+
|
|
169
|
+
- **Numbers come from runs.** Benchmark figures are produced by [`examples/smap_benchmark.py`](examples/smap_benchmark.py), and the hardware results in the deployment docs are measured on an ESP32-S3 by the conformance harness in [`targets/`](targets/). Nothing quoted in these docs is estimated or inferred.
|
|
170
|
+
- **Behavior claims are backed by tests.** The suite gates CI, and the documented API is covered by it.
|
|
171
|
+
|
|
172
|
+
Everything published here is reviewed before it lands.
|
|
122
173
|
|
|
123
174
|
## Getting Help
|
|
124
175
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "telemetry-anomdet"
|
|
3
|
-
version = "0.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "Spacecraft telemetry anomaly detection: classical and graph deep detectors, label-free thresholding, and distillation to C for flight hardware."
|
|
5
5
|
readme = "README.md"
|
|
6
|
-
license =
|
|
6
|
+
license = "Apache-2.0"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
7
8
|
requires-python = ">=3.10"
|
|
8
9
|
authors = [
|
|
9
10
|
{ name = "Julio Anzaldo", email = "anzaldojuliojesus@gmail.com" }
|
|
@@ -12,50 +13,62 @@ dependencies = [
|
|
|
12
13
|
"pandas>=2.2.2,<3.0.0",
|
|
13
14
|
"numpy>=2.1,<3.0",
|
|
14
15
|
"scikit-learn>=1.3,<2.0",
|
|
15
|
-
"click>=8.4.2,<9.0",
|
|
16
16
|
"typing-extensions>=4.16.0",
|
|
17
17
|
]
|
|
18
18
|
keywords = ["telemetry", "anomaly-detection", "machine-learning", "python"]
|
|
19
|
+
# License is declared as an SPDX expression above, not a classifier: license
|
|
20
|
+
# classifiers are deprecated and setuptools warns on them.
|
|
19
21
|
classifiers = [
|
|
22
|
+
"Development Status :: 3 - Alpha",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"Intended Audience :: Developers",
|
|
25
|
+
"Natural Language :: English",
|
|
26
|
+
"Operating System :: OS Independent",
|
|
20
27
|
"Programming Language :: Python :: 3",
|
|
28
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
21
29
|
"Programming Language :: Python :: 3.10",
|
|
22
30
|
"Programming Language :: Python :: 3.11",
|
|
23
31
|
"Programming Language :: Python :: 3.12",
|
|
24
32
|
"Programming Language :: Python :: 3.13",
|
|
25
|
-
|
|
26
|
-
"
|
|
33
|
+
# The toolkit is pure Python; the artifact it generates for flight targets is C.
|
|
34
|
+
"Programming Language :: C",
|
|
27
35
|
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"
|
|
36
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
37
|
+
"Topic :: Scientific/Engineering :: Astronomy",
|
|
38
|
+
"Topic :: Software Development :: Libraries",
|
|
39
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
40
|
+
"Topic :: Software Development :: Code Generators",
|
|
41
|
+
"Topic :: System :: Monitoring",
|
|
42
|
+
"Typing :: Typed",
|
|
33
43
|
]
|
|
34
44
|
|
|
35
45
|
[project.optional-dependencies]
|
|
36
46
|
ccsds = [
|
|
37
47
|
"ccsdspy>=2.0.0,<3.0",
|
|
38
48
|
]
|
|
49
|
+
# Phase 2 deep detector (GDN). Optional so the base install stays
|
|
50
|
+
# torch-free; torch is imported lazily at fit time.
|
|
51
|
+
deep = [
|
|
52
|
+
"torch>=2.0",
|
|
53
|
+
]
|
|
39
54
|
|
|
40
55
|
[dependency-groups]
|
|
41
56
|
dev = [
|
|
42
57
|
"pytest>=9.1.1,<10.0",
|
|
43
58
|
"pytest-cov>=7.1.0,<8.0",
|
|
44
|
-
"pre-commit>=4.6.
|
|
59
|
+
"pre-commit>=4.6.2,<5.0",
|
|
45
60
|
"black>=26.5.1,<27.0",
|
|
46
|
-
"ruff>=0.16.
|
|
61
|
+
"ruff>=0.16.2,<1.0",
|
|
47
62
|
]
|
|
48
63
|
docs = [
|
|
49
64
|
"sphinx>=7.0",
|
|
50
|
-
"sphinx-
|
|
65
|
+
"pydata-sphinx-theme>=0.15",
|
|
51
66
|
"sphinx-autodoc-typehints>=2.0",
|
|
52
67
|
"numpydoc>=1.7",
|
|
68
|
+
"sphinx-sitemap>=2.6",
|
|
69
|
+
"sphinxext-opengraph>=0.9",
|
|
70
|
+
"sphinx-notfound-page>=1.0",
|
|
53
71
|
]
|
|
54
|
-
# Phase 2 -- uncomment when GDN/TranAD training starts
|
|
55
|
-
# deep = [
|
|
56
|
-
# "torch>=2.0",
|
|
57
|
-
# "torch-geometric",
|
|
58
|
-
# ]
|
|
59
72
|
# Phase 3
|
|
60
73
|
# xai = [
|
|
61
74
|
# "shap",
|
|
@@ -72,7 +85,7 @@ Homepage = "https://github.com/JulioAnzaldo/telemetry-anomdet"
|
|
|
72
85
|
Repository = "https://github.com/JulioAnzaldo/telemetry-anomdet"
|
|
73
86
|
|
|
74
87
|
[build-system]
|
|
75
|
-
requires = ["setuptools>=
|
|
88
|
+
requires = ["setuptools>=84.0.0", "wheel"]
|
|
76
89
|
build-backend = "setuptools.build_meta"
|
|
77
90
|
|
|
78
91
|
[tool.ruff]
|