fdnkit 1.0.0__py3-none-any.whl

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.
fdnkit/viz.py ADDED
@@ -0,0 +1,146 @@
1
+ """Plotting utilities (matplotlib, optional dependency).
2
+
3
+ Import matplotlib lazily so the core library has no hard plotting dependency.
4
+ Install with ``pip install fdnkit[viz]``. Every function accepts an optional
5
+ ``ax`` and returns the Axes it drew on, so plots compose into larger figures.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import numpy as np
11
+
12
+ __all__ = [
13
+ "plot_fluctuation",
14
+ "plot_hq",
15
+ "plot_multifractal_spectrum",
16
+ "plot_hurst_over_time",
17
+ "plot_alpha_distribution",
18
+ "plot_coupling_matrix",
19
+ "plot_eigenvector_hubs",
20
+ ]
21
+
22
+
23
+ def _get_ax(ax):
24
+ try:
25
+ import matplotlib.pyplot as plt
26
+ except ImportError as exc: # pragma: no cover
27
+ raise ImportError(
28
+ "Plotting requires matplotlib. Install with `pip install fdnkit[viz]`."
29
+ ) from exc
30
+ if ax is None:
31
+ _, ax = plt.subplots(figsize=(6, 4))
32
+ return ax
33
+
34
+
35
+ def plot_fluctuation(result, ax=None, **kwargs):
36
+ """Log-log fluctuation function ``F`` vs scale, with the fitted Hurst slope.
37
+
38
+ Parameters
39
+ ----------
40
+ result : DFAResult | MFDFAResult
41
+ Must expose ``scales``, ``fluct``, and ``hurst``.
42
+ """
43
+ ax = _get_ax(ax)
44
+ scales = np.asarray(result.scales, dtype=float)
45
+ fluct = np.asarray(result.fluct, dtype=float)
46
+ good = np.isfinite(fluct) & (fluct > 0)
47
+ ax.plot(np.log2(scales[good]), np.log2(fluct[good]), "o-", **kwargs)
48
+ ax.set_xlabel("log2(scale)")
49
+ ax.set_ylabel("log2(F)")
50
+ ax.set_title(f"DFA fluctuation (H = {result.hurst:.3f})")
51
+ return ax
52
+
53
+
54
+ def plot_hq(result, ax=None, **kwargs):
55
+ """Generalized Hurst exponent ``h(q)`` against ``q``."""
56
+ ax = _get_ax(ax)
57
+ order = np.argsort(result.q)
58
+ ax.plot(np.asarray(result.q)[order], np.asarray(result.hq)[order], "s-", **kwargs)
59
+ ax.set_xlabel("q")
60
+ ax.set_ylabel("h(q)")
61
+ ax.set_title(f"Generalized Hurst (delta_h = {result.delta_h:.3f})")
62
+ return ax
63
+
64
+
65
+ def plot_multifractal_spectrum(result, ax=None, **kwargs):
66
+ """Singularity spectrum ``f(alpha)`` from an :class:`~fdnkit.mfdfa.MFDFAResult`."""
67
+ from .mfdfa import multifractal_spectrum
68
+
69
+ ax = _get_ax(ax)
70
+ alpha, f_alpha = multifractal_spectrum(result)
71
+ ax.plot(alpha, f_alpha, "o-", **kwargs)
72
+ ax.set_xlabel(r"$\alpha$ (Holder exponent)")
73
+ ax.set_ylabel(r"$f(\alpha)$")
74
+ ax.set_title("Multifractal spectrum")
75
+ return ax
76
+
77
+
78
+ def plot_hurst_over_time(times, hurst_values, ax=None, *, label=None, **kwargs):
79
+ """Trace of a scaling exponent (Hurst / alpha) across analysis windows.
80
+
81
+ Parameters
82
+ ----------
83
+ times : array-like
84
+ Window centre times (or indices).
85
+ hurst_values : array-like
86
+ One value per window.
87
+ """
88
+ ax = _get_ax(ax)
89
+ ax.plot(np.asarray(times), np.asarray(hurst_values), "-o", label=label, **kwargs)
90
+ ax.set_xlabel("time (s)")
91
+ ax.set_ylabel("Hurst / exponent")
92
+ ax.set_title("Scaling exponent over time")
93
+ if label:
94
+ ax.legend()
95
+ return ax
96
+
97
+
98
+ def plot_alpha_distribution(alphas, ax=None, *, bins=20, **kwargs):
99
+ """Histogram of per-channel (or per-chunk) fractional orders ``alpha``."""
100
+ ax = _get_ax(ax)
101
+ a = np.asarray(alphas, dtype=float).ravel()
102
+ a = a[np.isfinite(a)]
103
+ ax.hist(a, bins=bins, **kwargs)
104
+ ax.set_xlabel(r"fractional order $\alpha$")
105
+ ax.set_ylabel("count")
106
+ ax.set_title("FODN alpha distribution")
107
+ return ax
108
+
109
+
110
+ def plot_coupling_matrix(coupling, channel_names=None, ax=None, *, cmap="RdBu_r", **kwargs):
111
+ """Heatmap of a FODN coupling matrix ``A``.
112
+
113
+ Parameters
114
+ ----------
115
+ coupling : array-like, shape (n, n)
116
+ channel_names : sequence of str, optional
117
+ """
118
+ ax = _get_ax(ax)
119
+ a = np.asarray(coupling, dtype=float)
120
+ vmax = np.max(np.abs(a)) or 1.0
121
+ im = ax.imshow(a, cmap=cmap, vmin=-vmax, vmax=vmax, **kwargs)
122
+ ax.figure.colorbar(im, ax=ax, fraction=0.046, pad=0.04, label="coupling")
123
+ ax.set_title("FODN coupling matrix A")
124
+ ax.set_xlabel("source channel")
125
+ ax.set_ylabel("target channel")
126
+ if channel_names is not None:
127
+ ax.set_xticks(range(len(channel_names)))
128
+ ax.set_yticks(range(len(channel_names)))
129
+ ax.set_xticklabels(channel_names, rotation=90, fontsize=7)
130
+ ax.set_yticklabels(channel_names, fontsize=7)
131
+ return ax
132
+
133
+
134
+ def plot_eigenvector_hubs(dominant_eigvec, channel_names=None, ax=None, **kwargs):
135
+ """Bar chart of per-channel hub scores (dominant-eigenvector magnitude)."""
136
+ ax = _get_ax(ax)
137
+ v = np.asarray(dominant_eigvec, dtype=float).ravel()
138
+ idx = np.arange(v.size)
139
+ ax.bar(idx, v, **kwargs)
140
+ ax.set_xlabel("channel")
141
+ ax.set_ylabel("hub score |eigvec|")
142
+ ax.set_title("FODN eigenvector hubs")
143
+ if channel_names is not None:
144
+ ax.set_xticks(idx)
145
+ ax.set_xticklabels(channel_names, rotation=90, fontsize=7)
146
+ return ax
@@ -0,0 +1,192 @@
1
+ Metadata-Version: 2.5
2
+ Name: fdnkit
3
+ Version: 1.0.0
4
+ Summary: Fractional Dynamical Network & Multifractal toolkit for intracranial EEG
5
+ Project-URL: Homepage, https://github.com/SamirHossain099/fdnkit
6
+ Project-URL: Documentation, https://github.com/SamirHossain099/fdnkit#readme
7
+ Project-URL: Repository, https://github.com/SamirHossain099/fdnkit
8
+ Project-URL: Issues, https://github.com/SamirHossain099/fdnkit/issues
9
+ Author: Samir Hossain
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: DFA,EEG,MFDFA,fractional dynamics,iEEG,multifractal,network physiology,neuroscience
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
23
+ Requires-Python: >=3.9
24
+ Requires-Dist: numpy>=1.22
25
+ Requires-Dist: pandas>=1.4
26
+ Requires-Dist: scikit-learn>=1.1
27
+ Requires-Dist: scipy>=1.8
28
+ Provides-Extra: all
29
+ Requires-Dist: h5py>=3.0; extra == 'all'
30
+ Requires-Dist: matplotlib>=3.5; extra == 'all'
31
+ Requires-Dist: mne>=1.0; extra == 'all'
32
+ Provides-Extra: dev
33
+ Requires-Dist: h5py>=3.0; extra == 'dev'
34
+ Requires-Dist: matplotlib>=3.5; extra == 'dev'
35
+ Requires-Dist: pytest-cov>=4.0; extra == 'dev'
36
+ Requires-Dist: pytest>=7.0; extra == 'dev'
37
+ Requires-Dist: ruff>=0.1; extra == 'dev'
38
+ Provides-Extra: io
39
+ Requires-Dist: h5py>=3.0; extra == 'io'
40
+ Requires-Dist: mne>=1.0; extra == 'io'
41
+ Provides-Extra: viz
42
+ Requires-Dist: matplotlib>=3.5; extra == 'viz'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # FDNkit
46
+
47
+ **Fractional Dynamical Network & Multifractal toolkit for intracranial EEG**
48
+
49
+ [![CI](https://github.com/SamirHossain099/fdnkit/actions/workflows/ci.yml/badge.svg)](https://github.com/SamirHossain099/fdnkit/actions/workflows/ci.yml)
50
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/SamirHossain099/fdnkit/blob/main/LICENSE)
51
+ [![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-blue.svg)](https://www.python.org/)
52
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.22366240.svg)](https://doi.org/10.5281/zenodo.22366240)
53
+
54
+ FDNkit turns intracranial-EEG (iEEG) recordings into **fractal** and
55
+ **fractional-dynamical-network** features and evaluates them **honestly**. It
56
+ packages methods validated in Beeram et al. (2026, *Front. Netw. Physiol.*
57
+ 6:1768476) and the fractional-dynamical-network line of work of Gupta et al.
58
+ (2018) and Xue & Bogdan (2017) as a clean, documented, tested library, not a one-off GUI welded to a single dataset.
59
+
60
+ It computes:
61
+
62
+ - **DFA**: monofractal Hurst exponent `H`.
63
+ - **MFDFA**: generalized Hurst `h(q)`, multifractal width `Δh`, and the
64
+ singularity spectrum `f(α)`.
65
+ - **FODN**: a fractional-order dynamical network: per-channel fractional orders
66
+ `α`, a sparse directed coupling matrix `A`, and eigenvector "hub" scores.
67
+ - **Feature tables**: tidy, one-row-per-trial pandas DataFrames.
68
+ - **Honest classification**: a logistic-regression harness that defaults to
69
+ **leave-one-subject-out** cross-validation with a subject-level permutation
70
+ test, because row-wise splits leak patient identity and inflate accuracy.
71
+
72
+ ## Install
73
+
74
+ ```bash
75
+ pip install fdnkit # core: numpy, scipy, pandas, scikit-learn
76
+ pip install "fdnkit[viz]" # + matplotlib for plots
77
+ pip install "fdnkit[io]" # + mne (EDF) and h5py (HDF5) readers
78
+ pip install "fdnkit[all]" # everything
79
+ ```
80
+
81
+ From source:
82
+
83
+ ```bash
84
+ git clone https://github.com/SamirHossain099/fdnkit
85
+ cd fdnkit
86
+ pip install -e ".[dev]"
87
+ pytest
88
+ ```
89
+
90
+ ## 60-second example (no data download)
91
+
92
+ ```python
93
+ from fdnkit.synthetic import synthetic_ieeg
94
+ from fdnkit.features import extract_features
95
+
96
+ # A small, sparsely-coupled synthetic iEEG trial (8 channels, 5 s @ 1 kHz).
97
+ signals, channel_names = synthetic_ieeg(n_channels=8, n_samples=5000, seed=0)
98
+
99
+ # One tidy feature row: DFA H, MFDFA h(q)/Δh, FODN α / leading eigenvalue / hubs.
100
+ features = extract_features(signals)
101
+ print(features["MF_DFA_H"], features["MeanAlpha"], features["LeadingEig"])
102
+ ```
103
+
104
+ Analyze a single signal directly:
105
+
106
+ ```python
107
+ import numpy as np
108
+ from fdnkit.dfa import dfa
109
+ from fdnkit.mfdfa import mfdfa
110
+ from fdnkit.fodn import fit_fodn
111
+
112
+ x = signals[0]
113
+ print("Hurst:", dfa(x).hurst)
114
+ print("multifractal width Δh:", mfdfa(x).delta_h)
115
+
116
+ fodn = fit_fodn(signals) # (channels, timepoints)
117
+ print("leading eigenvalue:", fodn.leading_eig)
118
+ print("hub scores:", np.round(fodn.dominant_eigvec, 3))
119
+ ```
120
+
121
+ ## Honest classification
122
+
123
+ ```python
124
+ from fdnkit.classify import classify_dataframe
125
+
126
+ # df has feature columns plus 'label' and 'group' (e.g. subject id) columns.
127
+ result = classify_dataframe(df, label_col="label", group_col="group", cv="loso")
128
+ print(result.summary())
129
+ # Leave-one-subject-out balanced accuracy, ROC-AUC, a subject-level
130
+ # permutation p-value, and a bootstrap 95% CI.
131
+ ```
132
+
133
+ `cv="loso"` (the default) holds out whole subjects and **requires** `groups`.
134
+ Trial-wise `cv="loo"` is available but must be requested explicitly and is
135
+ labeled *optimistic*: it is the leakage-prone scheme FDNkit exists to warn about.
136
+
137
+ ## Command line
138
+
139
+ ```bash
140
+ # Self-contained demo: synthesize a labeled cohort and classify it honestly.
141
+ fdnkit demo --out demo_features.csv
142
+ fdnkit classify demo_features.csv --label label --group group
143
+
144
+ # Extract features from your own recording (EDF via MNE, or HDF5).
145
+ fdnkit extract recording.edf --window 1.0 --drop-bad --zscore --out features.csv
146
+ ```
147
+
148
+ ## Design principles
149
+
150
+ - **Array-first core.** `dfa(signal)`, `mfdfa(signal)`, `fit_fodn(signals)` are
151
+ pure functions on NumPy arrays. Pandas/IO/plotting layer on top.
152
+ - **Depend, don't duplicate.** IO, montages, and filtering defer to
153
+ [MNE-Python](https://mne.tools); FDNkit adds only the fractal/FODN methods.
154
+ - **Deterministic and seedable.** Bad channels log a warning instead of crashing.
155
+ - **Honest by default.** Subject-wise CV and permutation testing are the
156
+ headline, not an afterthought.
157
+
158
+ ## Validation
159
+
160
+ FDNkit's numerical core is checked against ground truth (see `tests/`):
161
+
162
+ - DFA recovers the Hurst exponent of fractional Gaussian noise across
163
+ `H = 0.3…0.9`; white noise → `H ≈ 0.5`, Brownian motion → `H ≈ 1.5`.
164
+ - MFDFA reports a wide `h(q)` for a multiplicative binomial cascade and a narrow
165
+ one for a monofractal signal; `h(q=2)` matches the DFA Hurst exponent exactly.
166
+ - FODN recovers finite fractional orders, coupling, and hubs on synthetic
167
+ coupled systems.
168
+
169
+ ## Citation
170
+
171
+ If you use FDNkit, please cite the software:
172
+
173
+ > Hossain, S. (2026). *FDNkit: Fractional Dynamical Network & Multifractal
174
+ > toolkit for intracranial EEG* (v1.0.0). Zenodo.
175
+ > https://doi.org/10.5281/zenodo.22366240
176
+
177
+ (`10.5281/zenodo.22366240` always resolves to the latest release; cite
178
+ `10.5281/zenodo.22366241` for v1.0.0 specifically. See
179
+ [`CITATION.cff`](https://github.com/SamirHossain099/fdnkit/blob/main/CITATION.cff).)
180
+
181
+ Please also cite the methods paper:
182
+
183
+ > Beeram, S. P., Farris, M., Hossain, S., Rethans, N., Kang, J. Y., & Pereira,
184
+ > E. A. (2026). *Quantifying cognitive effort's impact on suppression of
185
+ > epilepsy-associated after discharges.* Frontiers in Network Physiology, 6,
186
+ > 1768476. https://doi.org/10.3389/fnetp.2026.1768476
187
+
188
+ ## License
189
+
190
+ MIT; see [LICENSE](https://github.com/SamirHossain099/fdnkit/blob/main/LICENSE). The underlying fractional-dynamical-network method
191
+ is due to Gupta, Pequito & Bogdan (2018) and Xue & Bogdan (2017); please cite
192
+ them when using the FODN module.
@@ -0,0 +1,16 @@
1
+ fdnkit/__init__.py,sha256=oOb6f0ykH8WpQvnck1C_24qsSupOP2dbfl4oEzj2lXg,2772
2
+ fdnkit/classify.py,sha256=-pahHHUCcEuuNidhCRdxfgNKvl9RlTOZb2LPqMQdsqk,11301
3
+ fdnkit/cli.py,sha256=bZ17hSDUsRy7JtgFAN-X7DzMegpgKd9Uaf1hBEbIL_c,6388
4
+ fdnkit/dfa.py,sha256=4N9cO-GTrbKKmgf00Cc6KQCc6cMLfQmNWVIJ0sTwwz4,2764
5
+ fdnkit/features.py,sha256=nWSey1kD0yXzFFC0NVf_JO1A8sYlkk53K_3ExQhhEoI,7545
6
+ fdnkit/fodn.py,sha256=6-AQdkhmtEOdb_2iW29Y4_Pqy0QiXohCM0dIJfgHQqg,12715
7
+ fdnkit/io.py,sha256=7kHIk1kdLWCdqa8iFfh6CialQR4Ot-NDrSqw4u_OHN4,5273
8
+ fdnkit/mfdfa.py,sha256=tX5aT6LwKEYmCwtf3BiFptClFjaX8SY8TdOjwb5FMtM,8533
9
+ fdnkit/preprocessing.py,sha256=vboQ7A3tx-aCk6PrvjopzkOLKmGrjhHMk06HoVCtozg,4315
10
+ fdnkit/synthetic.py,sha256=t0G0UE3HaaIzV1sPqhpwCnuGH4GkJCae9UHX_gQz0CA,6174
11
+ fdnkit/viz.py,sha256=5ffZS07GGeylbGYXsIi4GHuXj5Ga64Hqp7DKaTzhwRU,4807
12
+ fdnkit-1.0.0.dist-info/METADATA,sha256=jOKLOTnIymFpcMnvKuG0LFfwRgGXBaKQ-X62pmfUrx8,7739
13
+ fdnkit-1.0.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
14
+ fdnkit-1.0.0.dist-info/entry_points.txt,sha256=f87Fzkl7fFwzmB4gghGg9xXyESTt5Jtuic2RbiaoKms,43
15
+ fdnkit-1.0.0.dist-info/licenses/LICENSE,sha256=svWXpNvhapALKx_4mnGhEP7AIDhIgWP1egNv0U6SUtc,1070
16
+ fdnkit-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ fdnkit = fdnkit.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Samir Hossain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.