tfce 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 (40) hide show
  1. tfce-0.1.0/LICENSE +30 -0
  2. tfce-0.1.0/MANIFEST.in +17 -0
  3. tfce-0.1.0/PKG-INFO +210 -0
  4. tfce-0.1.0/README.md +177 -0
  5. tfce-0.1.0/docs/README.md +35 -0
  6. tfce-0.1.0/docs/api.md +168 -0
  7. tfce-0.1.0/docs/benchmarks.md +152 -0
  8. tfce-0.1.0/docs/installation.md +78 -0
  9. tfce-0.1.0/docs/nilearn.md +107 -0
  10. tfce-0.1.0/docs/permutation.md +196 -0
  11. tfce-0.1.0/docs/theory.md +182 -0
  12. tfce-0.1.0/docs/usage.md +171 -0
  13. tfce-0.1.0/docs/validation.md +98 -0
  14. tfce-0.1.0/pyproject.toml +107 -0
  15. tfce-0.1.0/setup.cfg +4 -0
  16. tfce-0.1.0/setup.py +84 -0
  17. tfce-0.1.0/src/tfce/__init__.py +33 -0
  18. tfce-0.1.0/src/tfce/_c/tfce_batch.h +112 -0
  19. tfce-0.1.0/src/tfce/_c/tfce_capi.c +87 -0
  20. tfce-0.1.0/src/tfce/_c/tfce_capi.h +102 -0
  21. tfce-0.1.0/src/tfce/_c/tfce_maxtree.h +509 -0
  22. tfce-0.1.0/src/tfce/_c/tfce_threads.h +84 -0
  23. tfce-0.1.0/src/tfce/_maxtree.pyx +162 -0
  24. tfce-0.1.0/src/tfce/core.py +182 -0
  25. tfce-0.1.0/src/tfce/glm.py +171 -0
  26. tfce-0.1.0/src/tfce/nilearn_compat.py +88 -0
  27. tfce-0.1.0/src/tfce/tails.py +310 -0
  28. tfce-0.1.0/src/tfce.egg-info/PKG-INFO +210 -0
  29. tfce-0.1.0/src/tfce.egg-info/SOURCES.txt +38 -0
  30. tfce-0.1.0/src/tfce.egg-info/dependency_links.txt +1 -0
  31. tfce-0.1.0/src/tfce.egg-info/requires.txt +14 -0
  32. tfce-0.1.0/src/tfce.egg-info/top_level.txt +1 -0
  33. tfce-0.1.0/tests/data/matlab_reference.npz +0 -0
  34. tfce-0.1.0/tests/test_core.py +130 -0
  35. tfce-0.1.0/tests/test_docs.py +171 -0
  36. tfce-0.1.0/tests/test_exactness.py +122 -0
  37. tfce-0.1.0/tests/test_glm.py +131 -0
  38. tfce-0.1.0/tests/test_matlab_parity.py +67 -0
  39. tfce-0.1.0/tests/test_nilearn_compat.py +86 -0
  40. tfce-0.1.0/tests/test_tails.py +159 -0
tfce-0.1.0/LICENSE ADDED
@@ -0,0 +1,30 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2020-2026, Christian Gaser
4
+ Structural Brain Mapping Group, Jena University Hospital
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ 1. Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ 2. Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ 3. Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
tfce-0.1.0/MANIFEST.in ADDED
@@ -0,0 +1,17 @@
1
+ # The C core is vendored into src/tfce/_c/ by setup.py. It MUST go into the sdist:
2
+ # without it there is nothing to compile, and `pip install tfce` from source fails.
3
+ include src/tfce/_c/*.c
4
+ include src/tfce/_c/*.h
5
+ include src/tfce/*.pyx
6
+
7
+ include README.md
8
+ include LICENSE
9
+
10
+ # the docs the PyPI page links to, so an sdist is self-documenting
11
+ recursive-include docs *.md
12
+
13
+ recursive-include tests *.py
14
+ recursive-include tests/data *.npz
15
+
16
+ exclude src/tfce/_maxtree.c
17
+ global-exclude *.so *.pyd __pycache__/*
tfce-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.4
2
+ Name: tfce
3
+ Version: 0.1.0
4
+ Summary: Exact threshold-free cluster enhancement (TFCE) with no step size, and the permutation inference around it
5
+ Author-email: Christian Gaser <christian.gaser@uni-jena.de>
6
+ License: BSD-3-Clause
7
+ Project-URL: Homepage, https://github.com/ChristianGaser/tfce
8
+ Project-URL: Source, https://github.com/ChristianGaser/tfce
9
+ Project-URL: Issues, https://github.com/ChristianGaser/tfce/issues
10
+ Keywords: neuroimaging,TFCE,permutation,statistics,fMRI,VBM
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: BSD License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: C
16
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
17
+ Classifier: Topic :: Scientific/Engineering :: Image Processing
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: numpy>=1.22
22
+ Requires-Dist: scipy>=1.8
23
+ Provides-Extra: io
24
+ Requires-Dist: nibabel>=4.0; extra == "io"
25
+ Provides-Extra: test
26
+ Requires-Dist: pytest>=7.0; extra == "test"
27
+ Provides-Extra: dev
28
+ Requires-Dist: pytest>=7.0; extra == "dev"
29
+ Requires-Dist: nibabel>=4.0; extra == "dev"
30
+ Requires-Dist: nilearn>=0.10; extra == "dev"
31
+ Requires-Dist: cython>=3.0; extra == "dev"
32
+ Dynamic: license-file
33
+
34
+ # tfce
35
+
36
+ **Exact threshold-free cluster enhancement, and the permutation inference around it.**
37
+
38
+ [![PyPI](https://img.shields.io/pypi/v/tfce.svg)](https://pypi.org/project/tfce/)
39
+ [![Python](https://img.shields.io/pypi/pyversions/tfce.svg)](https://pypi.org/project/tfce/)
40
+ [![License](https://img.shields.io/pypi/l/tfce.svg)](https://github.com/ChristianGaser/tfce/blob/master/python/LICENSE)
41
+
42
+ TFCE combines focal effects of large height with broad effects of large extent, and needs **no
43
+ cluster-forming threshold** - the arbitrary choice that cluster-based inference forces on you, and
44
+ that the result can depend on heavily.
45
+
46
+ ```bash
47
+ pip install tfce
48
+ ```
49
+
50
+ Wheels for Linux, macOS and Windows. No compiler needed.
51
+
52
+ ---
53
+
54
+ ## Exact, not stepped
55
+
56
+ The TFCE of an element is an integral:
57
+
58
+ ```text
59
+ TFCE(v) = ∫ e_v(h)^E · h^H dh
60
+ ```
61
+
62
+ over the extent `e_v(h)` of the cluster containing `v` at height `h`. Implementations normally
63
+ approximate it by stepping `h` over a grid and summing. That costs a step size `dh`, a discretisation
64
+ error that depends on it, and an accuracy parameter you have to guess.
65
+
66
+ This one doesn't. It builds the **max-tree** (the component tree) with union-find, and because
67
+ `e_v(h)` is piecewise constant, integrates each piece in closed form. The answer is the integral, not
68
+ a sample of it. **There is nothing to tune.**
69
+
70
+ The difference is not academic. Against a stepped implementation on a 60×72×60 volume:
71
+
72
+ | steps | error vs exact |
73
+ | --- | --- |
74
+ | 50 | 3.4% |
75
+ | 100 | **1.7%** |
76
+ | 200 | 0.8% |
77
+ | 400 | 0.4% |
78
+
79
+ The error halves every time the steps double - first order, exactly as a step-size approximation must
80
+ behave. The exact transform has no such term.
81
+
82
+ ## Quick start
83
+
84
+ ```python
85
+ import numpy as np
86
+ import tfce
87
+
88
+ # a volume: (nx, ny, nz)
89
+ t = tfce.tfce(stat_map, E=0.5, H=2.0)
90
+
91
+ # a surface: faces are 1-based, as GIFTI stores them
92
+ adj = tfce.adjacency_from_faces(faces, n_vertices)
93
+ t = tfce.tfce(surf_map, adjacency=adj, E=1.0, H=2.0)
94
+
95
+ # a block of permutations, one per thread
96
+ # (nx, ny, nz, n_perm) -> (nx, ny, nz, n_perm)
97
+ t = tfce.tfce(perms, E=0.5, H=2.0, n_jobs=-1)
98
+ ```
99
+
100
+ Volumes take `connectivity=6 | 18 | 26` (26 is the default, and what fslmaths and the MATLAB toolbox
101
+ use). Surfaces take the mesh, so the neighbourhood is whatever the mesh says it is.
102
+
103
+ Everything is arrays in, arrays out. No image objects, no file I/O, no design parsing - those belong
104
+ in a layer above, so the core can be dropped anywhere.
105
+
106
+ ## Speed
107
+
108
+ Permutations are independent, so they are TFCE'd a block at a time, one per thread, with the GIL
109
+ released. On the same 60×72×60 volume:
110
+
111
+ | | one map | 16 permutations |
112
+ | --- | --- | --- |
113
+ | stepped (100 steps) | 0.38 s | 9.1 s - 569 ms/perm |
114
+ | **tfce** | **0.03 s** (14×) | **0.13 s** - 8 ms/perm (**72×**) |
115
+
116
+ ## Using it with nilearn
117
+
118
+ nilearn has `permuted_ols(..., tfce=True)`, and its TFCE is a stepped approximation. `tfce` ships a
119
+ drop-in with the same signature:
120
+
121
+ ```python
122
+ from nilearn.mass_univariate import _utils
123
+ import tfce.nilearn_compat as tc
124
+
125
+ _utils.calculate_tfce = tc.calculate_tfce # now exact, and much faster
126
+ ```
127
+
128
+ It reads the neighbourhood out of the `bin_struct` it is handed, so it reproduces whichever
129
+ connectivity the caller meant.
130
+
131
+ ## Fewer permutations
132
+
133
+ Counting exceedances cannot report a p-value below `1/n_perm`. That floor - not the statistic - is
134
+ what forces a permutation test to run many thousands of permutations, and it caps FDR too, since FDR
135
+ is computed from the uncorrected p-values. `tfce.tails` removes it:
136
+
137
+ - **Gamma** fit to the null of the *maximum*, for FWE-corrected p-values.
138
+ - **Generalised Pareto** fit to each element's own tail, for the uncorrected ones - with the *shape*
139
+ pooled across elements, since they all carry the same statistic under the same design and so differ
140
+ in scale, not in shape.
141
+
142
+ From 1000 permutations this recovers `p ≈ 1e-4` with the median right, where plain counting returns
143
+ zero for 91% of elements and tells you nothing at all.
144
+
145
+ ## What's in the box
146
+
147
+ | | |
148
+ | --- | --- |
149
+ | `tfce.core` | the exact transform - arrays in, arrays out |
150
+ | `tfce.tails` | Gamma and Generalised Pareto tail approximations |
151
+ | `tfce.glm` | the permuted GLM, which never forms the permuted data |
152
+ | `tfce.nilearn_compat` | drop-in for nilearn's `calculate_tfce` |
153
+
154
+ Dependencies are deliberately thin: **numpy and scipy**, nothing else. `nibabel` is an optional extra,
155
+ wanted only by I/O.
156
+
157
+ ## Trust
158
+
159
+ This is not a reimplementation. It is the same C core that the
160
+ [MATLAB TFCE toolbox](https://github.com/ChristianGaser/tfce) has been running for years, with a
161
+ Cython binding instead of a MEX one - so the two give **bit-identical** results, and the test suite
162
+ holds them to it. It ships a validation suite of its own: the exactness of the max-tree is established
163
+ against an *independent* stepped implementation, which must converge onto it at first order.
164
+
165
+ ## Citing
166
+
167
+ If you use this, please cite the method:
168
+
169
+ > Smith SM, Nichols TE (2009). *Threshold-free cluster enhancement: addressing problems of smoothing,
170
+ > threshold dependence and localisation in cluster inference.* NeuroImage 44:83–98.
171
+ > [doi:10.1016/j.neuroimage.2008.03.061](https://doi.org/10.1016/j.neuroimage.2008.03.061)
172
+
173
+ The tail approximations are from:
174
+
175
+ > Winkler AM, Ridgway GR, Douaud G, Nichols TE, Smith SM (2016). *Faster permutation inference in
176
+ > brain imaging.* NeuroImage 141:502–516.
177
+ > [doi:10.1016/j.neuroimage.2016.05.068](https://doi.org/10.1016/j.neuroimage.2016.05.068)
178
+
179
+ ## Status
180
+
181
+ Early, but the parts that are here are tested and are the parts that matter. The transform, the tail
182
+ approximations and the permuted GLM are done. Not yet done: the **design layer** - turning a
183
+ [BIDS Stats Model](https://bids-standard.github.io/stats-models/) into a design matrix, contrasts and,
184
+ crucially, an *exchangeability* structure. That last one is the real work, because BIDS-SM specifies
185
+ `X`, `Formula`, `Contrasts` and `GroupBy` but has **no** concept of exchangeability blocks, so what may
186
+ be permuted with what has to be defined on top of it rather than read out of it.
187
+
188
+ ## Documentation
189
+
190
+ | | |
191
+ | --- | --- |
192
+ | [Installation](https://github.com/ChristianGaser/tfce/blob/master/python/docs/installation.md) | Wheels, building from source, extras |
193
+ | [Usage](https://github.com/ChristianGaser/tfce/blob/master/python/docs/usage.md) | Volumes, surfaces, batches, connectivity, `E` and `H` |
194
+ | [A permutation test, end to end](https://github.com/ChristianGaser/tfce/blob/master/python/docs/permutation.md) | The whole thing, as a worked example you can run |
195
+ | [Theory](https://github.com/ChristianGaser/tfce/blob/master/python/docs/theory.md) | Why the integral is exact, and why you need fewer permutations |
196
+ | [Using it with nilearn](https://github.com/ChristianGaser/tfce/blob/master/python/docs/nilearn.md) | The drop-in - and a bug in nilearn's stepped TFCE |
197
+ | [API reference](https://github.com/ChristianGaser/tfce/blob/master/python/docs/api.md) | Every public function |
198
+ | [Benchmarks](https://github.com/ChristianGaser/tfce/blob/master/python/docs/benchmarks.md) | Accuracy and speed, with scripts to reproduce |
199
+ | [Validation](https://github.com/ChristianGaser/tfce/blob/master/python/docs/validation.md) | What the test suite establishes, and how |
200
+
201
+ ## Links
202
+
203
+ - **Source, issues, MATLAB toolbox:** <https://github.com/ChristianGaser/tfce>
204
+ - **Licence:** BSD-3-Clause. Permissive on purpose: `nilearn` is BSD-3 and
205
+ `nipreps` is Apache-2.0, and neither can take on a GPL dependency. Nothing here
206
+ is derived from SPM, so nothing here has to be GPL. (The MATLAB/SPM toolbox in
207
+ the same repository *is* GPL, because it genuinely is derived from SPM - see
208
+ [LICENSE.md](https://github.com/ChristianGaser/tfce/blob/master/LICENSE.md).)
209
+
210
+ Developed by Christian Gaser, Structural Brain Mapping Group, Jena University Hospital.
tfce-0.1.0/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # tfce
2
+
3
+ **Exact threshold-free cluster enhancement, and the permutation inference around it.**
4
+
5
+ [![PyPI](https://img.shields.io/pypi/v/tfce.svg)](https://pypi.org/project/tfce/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/tfce.svg)](https://pypi.org/project/tfce/)
7
+ [![License](https://img.shields.io/pypi/l/tfce.svg)](https://github.com/ChristianGaser/tfce/blob/master/python/LICENSE)
8
+
9
+ TFCE combines focal effects of large height with broad effects of large extent, and needs **no
10
+ cluster-forming threshold** - the arbitrary choice that cluster-based inference forces on you, and
11
+ that the result can depend on heavily.
12
+
13
+ ```bash
14
+ pip install tfce
15
+ ```
16
+
17
+ Wheels for Linux, macOS and Windows. No compiler needed.
18
+
19
+ ---
20
+
21
+ ## Exact, not stepped
22
+
23
+ The TFCE of an element is an integral:
24
+
25
+ ```text
26
+ TFCE(v) = ∫ e_v(h)^E · h^H dh
27
+ ```
28
+
29
+ over the extent `e_v(h)` of the cluster containing `v` at height `h`. Implementations normally
30
+ approximate it by stepping `h` over a grid and summing. That costs a step size `dh`, a discretisation
31
+ error that depends on it, and an accuracy parameter you have to guess.
32
+
33
+ This one doesn't. It builds the **max-tree** (the component tree) with union-find, and because
34
+ `e_v(h)` is piecewise constant, integrates each piece in closed form. The answer is the integral, not
35
+ a sample of it. **There is nothing to tune.**
36
+
37
+ The difference is not academic. Against a stepped implementation on a 60×72×60 volume:
38
+
39
+ | steps | error vs exact |
40
+ | --- | --- |
41
+ | 50 | 3.4% |
42
+ | 100 | **1.7%** |
43
+ | 200 | 0.8% |
44
+ | 400 | 0.4% |
45
+
46
+ The error halves every time the steps double - first order, exactly as a step-size approximation must
47
+ behave. The exact transform has no such term.
48
+
49
+ ## Quick start
50
+
51
+ ```python
52
+ import numpy as np
53
+ import tfce
54
+
55
+ # a volume: (nx, ny, nz)
56
+ t = tfce.tfce(stat_map, E=0.5, H=2.0)
57
+
58
+ # a surface: faces are 1-based, as GIFTI stores them
59
+ adj = tfce.adjacency_from_faces(faces, n_vertices)
60
+ t = tfce.tfce(surf_map, adjacency=adj, E=1.0, H=2.0)
61
+
62
+ # a block of permutations, one per thread
63
+ # (nx, ny, nz, n_perm) -> (nx, ny, nz, n_perm)
64
+ t = tfce.tfce(perms, E=0.5, H=2.0, n_jobs=-1)
65
+ ```
66
+
67
+ Volumes take `connectivity=6 | 18 | 26` (26 is the default, and what fslmaths and the MATLAB toolbox
68
+ use). Surfaces take the mesh, so the neighbourhood is whatever the mesh says it is.
69
+
70
+ Everything is arrays in, arrays out. No image objects, no file I/O, no design parsing - those belong
71
+ in a layer above, so the core can be dropped anywhere.
72
+
73
+ ## Speed
74
+
75
+ Permutations are independent, so they are TFCE'd a block at a time, one per thread, with the GIL
76
+ released. On the same 60×72×60 volume:
77
+
78
+ | | one map | 16 permutations |
79
+ | --- | --- | --- |
80
+ | stepped (100 steps) | 0.38 s | 9.1 s - 569 ms/perm |
81
+ | **tfce** | **0.03 s** (14×) | **0.13 s** - 8 ms/perm (**72×**) |
82
+
83
+ ## Using it with nilearn
84
+
85
+ nilearn has `permuted_ols(..., tfce=True)`, and its TFCE is a stepped approximation. `tfce` ships a
86
+ drop-in with the same signature:
87
+
88
+ ```python
89
+ from nilearn.mass_univariate import _utils
90
+ import tfce.nilearn_compat as tc
91
+
92
+ _utils.calculate_tfce = tc.calculate_tfce # now exact, and much faster
93
+ ```
94
+
95
+ It reads the neighbourhood out of the `bin_struct` it is handed, so it reproduces whichever
96
+ connectivity the caller meant.
97
+
98
+ ## Fewer permutations
99
+
100
+ Counting exceedances cannot report a p-value below `1/n_perm`. That floor - not the statistic - is
101
+ what forces a permutation test to run many thousands of permutations, and it caps FDR too, since FDR
102
+ is computed from the uncorrected p-values. `tfce.tails` removes it:
103
+
104
+ - **Gamma** fit to the null of the *maximum*, for FWE-corrected p-values.
105
+ - **Generalised Pareto** fit to each element's own tail, for the uncorrected ones - with the *shape*
106
+ pooled across elements, since they all carry the same statistic under the same design and so differ
107
+ in scale, not in shape.
108
+
109
+ From 1000 permutations this recovers `p ≈ 1e-4` with the median right, where plain counting returns
110
+ zero for 91% of elements and tells you nothing at all.
111
+
112
+ ## What's in the box
113
+
114
+ | | |
115
+ | --- | --- |
116
+ | `tfce.core` | the exact transform - arrays in, arrays out |
117
+ | `tfce.tails` | Gamma and Generalised Pareto tail approximations |
118
+ | `tfce.glm` | the permuted GLM, which never forms the permuted data |
119
+ | `tfce.nilearn_compat` | drop-in for nilearn's `calculate_tfce` |
120
+
121
+ Dependencies are deliberately thin: **numpy and scipy**, nothing else. `nibabel` is an optional extra,
122
+ wanted only by I/O.
123
+
124
+ ## Trust
125
+
126
+ This is not a reimplementation. It is the same C core that the
127
+ [MATLAB TFCE toolbox](https://github.com/ChristianGaser/tfce) has been running for years, with a
128
+ Cython binding instead of a MEX one - so the two give **bit-identical** results, and the test suite
129
+ holds them to it. It ships a validation suite of its own: the exactness of the max-tree is established
130
+ against an *independent* stepped implementation, which must converge onto it at first order.
131
+
132
+ ## Citing
133
+
134
+ If you use this, please cite the method:
135
+
136
+ > Smith SM, Nichols TE (2009). *Threshold-free cluster enhancement: addressing problems of smoothing,
137
+ > threshold dependence and localisation in cluster inference.* NeuroImage 44:83–98.
138
+ > [doi:10.1016/j.neuroimage.2008.03.061](https://doi.org/10.1016/j.neuroimage.2008.03.061)
139
+
140
+ The tail approximations are from:
141
+
142
+ > Winkler AM, Ridgway GR, Douaud G, Nichols TE, Smith SM (2016). *Faster permutation inference in
143
+ > brain imaging.* NeuroImage 141:502–516.
144
+ > [doi:10.1016/j.neuroimage.2016.05.068](https://doi.org/10.1016/j.neuroimage.2016.05.068)
145
+
146
+ ## Status
147
+
148
+ Early, but the parts that are here are tested and are the parts that matter. The transform, the tail
149
+ approximations and the permuted GLM are done. Not yet done: the **design layer** - turning a
150
+ [BIDS Stats Model](https://bids-standard.github.io/stats-models/) into a design matrix, contrasts and,
151
+ crucially, an *exchangeability* structure. That last one is the real work, because BIDS-SM specifies
152
+ `X`, `Formula`, `Contrasts` and `GroupBy` but has **no** concept of exchangeability blocks, so what may
153
+ be permuted with what has to be defined on top of it rather than read out of it.
154
+
155
+ ## Documentation
156
+
157
+ | | |
158
+ | --- | --- |
159
+ | [Installation](https://github.com/ChristianGaser/tfce/blob/master/python/docs/installation.md) | Wheels, building from source, extras |
160
+ | [Usage](https://github.com/ChristianGaser/tfce/blob/master/python/docs/usage.md) | Volumes, surfaces, batches, connectivity, `E` and `H` |
161
+ | [A permutation test, end to end](https://github.com/ChristianGaser/tfce/blob/master/python/docs/permutation.md) | The whole thing, as a worked example you can run |
162
+ | [Theory](https://github.com/ChristianGaser/tfce/blob/master/python/docs/theory.md) | Why the integral is exact, and why you need fewer permutations |
163
+ | [Using it with nilearn](https://github.com/ChristianGaser/tfce/blob/master/python/docs/nilearn.md) | The drop-in - and a bug in nilearn's stepped TFCE |
164
+ | [API reference](https://github.com/ChristianGaser/tfce/blob/master/python/docs/api.md) | Every public function |
165
+ | [Benchmarks](https://github.com/ChristianGaser/tfce/blob/master/python/docs/benchmarks.md) | Accuracy and speed, with scripts to reproduce |
166
+ | [Validation](https://github.com/ChristianGaser/tfce/blob/master/python/docs/validation.md) | What the test suite establishes, and how |
167
+
168
+ ## Links
169
+
170
+ - **Source, issues, MATLAB toolbox:** <https://github.com/ChristianGaser/tfce>
171
+ - **Licence:** BSD-3-Clause. Permissive on purpose: `nilearn` is BSD-3 and
172
+ `nipreps` is Apache-2.0, and neither can take on a GPL dependency. Nothing here
173
+ is derived from SPM, so nothing here has to be GPL. (The MATLAB/SPM toolbox in
174
+ the same repository *is* GPL, because it genuinely is derived from SPM - see
175
+ [LICENSE.md](https://github.com/ChristianGaser/tfce/blob/master/LICENSE.md).)
176
+
177
+ Developed by Christian Gaser, Structural Brain Mapping Group, Jena University Hospital.
@@ -0,0 +1,35 @@
1
+ # tfce - documentation
2
+
3
+ Exact threshold-free cluster enhancement, and the permutation inference around it.
4
+
5
+ The [package README](../README.md) is the two-minute version. These pages are the rest.
6
+
7
+ ## Start here
8
+
9
+ | | |
10
+ | --- | --- |
11
+ | **[Installation](installation.md)** | `pip install tfce`, wheels, building from source, optional extras. |
12
+ | **[Usage](usage.md)** | Volumes, surfaces, batches of permutations, connectivity, `E` and `H`. |
13
+ | **[A permutation test, end to end](permutation.md)** | The whole thing: GLM → TFCE → FWE and uncorrected p-values. A worked example you can run. |
14
+
15
+ ## Going deeper
16
+
17
+ | | |
18
+ | --- | --- |
19
+ | **[Theory](theory.md)** | Why the integral is exact, what a max-tree is, and why the tail approximations mean you can run far fewer permutations. |
20
+ | **[Using it with nilearn](nilearn.md)** | The drop-in, the connectivity mismatch, and a bug in nilearn's stepped TFCE worth knowing about. |
21
+ | **[API reference](api.md)** | Every public function, with its arguments and what they do. |
22
+ | **[Benchmarks](benchmarks.md)** | Accuracy and speed, with the scripts to reproduce them. |
23
+ | **[Validation](validation.md)** | What the test suite establishes, and how it establishes it. |
24
+
25
+ ## The one-paragraph summary
26
+
27
+ The TFCE of an element is an integral over the extent of the cluster containing it, taken across all
28
+ heights. Implementations normally approximate that integral by stepping the height over a grid and
29
+ summing, which costs a step size, a discretisation error that depends on it, and an accuracy
30
+ parameter the caller has to guess. This one builds the **max-tree** (the component tree) with
31
+ union-find and integrates each piece of the extent function in closed form, because that function is
32
+ piecewise constant. The result is the integral, not a sample of it, and there is nothing to tune.
33
+
34
+ It is the same C the [MATLAB TFCE toolbox](https://github.com/ChristianGaser/tfce) runs - not a
35
+ reimplementation - so the two are bit-identical, and the test suite holds them to it.
tfce-0.1.0/docs/api.md ADDED
@@ -0,0 +1,168 @@
1
+ # API reference
2
+
3
+ Everything public, with what it takes and what it gives back.
4
+
5
+ ```python
6
+ import tfce
7
+ from tfce.glm import PermutedGLM, partition_design
8
+ from tfce.tails import gamma_pvalue, pareto_pvalue, gpd_fit_pwm, gpd_sf
9
+ from tfce.nilearn_compat import calculate_tfce, connectivity_from_bin_struct
10
+ ```
11
+
12
+ ---
13
+
14
+ ## `tfce`
15
+
16
+ ### `tfce(data, *, adjacency=None, connectivity=26, E=0.5, H=2.0, two_sided=True, n_jobs=1)`
17
+
18
+ Threshold-free cluster enhancement of one map or of many.
19
+
20
+ | argument | | |
21
+ | --- | --- | --- |
22
+ | `data` | ndarray | `(nx, ny, nz)` or `(nx, ny, nz, B)` for volumes; `(n_vertices,)` or `(n_vertices, B)` for surfaces. Any dtype - used as `float32`. |
23
+ | `adjacency` | sparse or `(indptr, indices)` | The mesh, for surface data. Omit for volumes. |
24
+ | `connectivity` | `6`, `18`, `26` | Volume neighbourhood. Ignored for surfaces. |
25
+ | `E`, `H` | float | Extent and height weights. `0.5, 2` for volumes; `1, 2` for surfaces and TBSS. |
26
+ | `two_sided` | bool | Also enhance the negative part, giving it a negative TFCE value. |
27
+ | `n_jobs` | int | Threads across the `B` maps. `-1` = one per map. Releases the GIL. |
28
+
29
+ **Returns** `float32` array in the shape the data came in.
30
+
31
+ **Raises** `ValueError` on a bad shape or connectivity; `TfceError` if the C core refuses the call.
32
+
33
+ ### `adjacency_from_faces(faces, n_vertices)`
34
+
35
+ CSR adjacency of a triangulated surface.
36
+
37
+ | argument | | |
38
+ | --- | --- | --- |
39
+ | `faces` | `(n_faces, 3)` int | **1-based** vertex indices, as GIFTI stores them. |
40
+ | `n_vertices` | int | |
41
+
42
+ **Returns** `(indptr, indices)`, both `int32` and 0-based - ready for `scipy.sparse.csr_matrix`, or to
43
+ pass straight back to `tfce()` as `adjacency`.
44
+
45
+ **Raises** `TfceError` if any index names a vertex that does not exist. This is a real check, not a
46
+ formality: an out-of-range index used to be written past the end of the adjacency arrays.
47
+
48
+ ### `TfceError`
49
+
50
+ Raised when the C core refuses a call. Subclasses `RuntimeError`.
51
+
52
+ ---
53
+
54
+ ## `tfce.tails`
55
+
56
+ ### `gamma_pvalue(stat, null_max, floor=None)`
57
+
58
+ FWE-corrected p-values, from a Gamma fitted to the distribution of the **maximum**.
59
+
60
+ | argument | | |
61
+ | --- | --- | --- |
62
+ | `stat` | ndarray | Observed statistic, one value per element. |
63
+ | `null_max` | `(n_perm,)` | The maximum of the statistic over the image, one per permutation. |
64
+ | `floor` | float | Smallest p-value to report. Defaults to `1/n_perm`. |
65
+
66
+ **Returns** p-values, same shape as `stat`.
67
+
68
+ The Gamma is fitted by matching the first three moments, which inverts in closed form. If the sample
69
+ skewness comes out non-positive there is nothing for a Gamma to hold on to, and the empirical count is
70
+ returned instead.
71
+
72
+ ### `pareto_pvalue(stat, tail, cnt, n_perm, *, n_exc_min=25, n_shape=20000, ad_max=5.0)`
73
+
74
+ Uncorrected p-values, resolved **below** the `1/n_perm` floor of counting.
75
+
76
+ | argument | | |
77
+ | --- | --- | --- |
78
+ | `stat` | `(n_elements,)` | Observed statistic, **already an upper tail** - flip the sign of the negative elements first. |
79
+ | `tail` | `(K, n_elements)` | The largest `K` permuted values of each element. `K ≈ 100` is plenty. |
80
+ | `cnt` | `(n_elements,)` | How often the permutations reached or exceeded `stat`, counted over **all** `n_perm` - not just the ones in `tail`. |
81
+ | `n_perm` | int | How many permutations were drawn. |
82
+ | `n_exc_min` | int | Elements exceeded at least this often keep their count; the fit has nothing to add. |
83
+ | `n_shape` | int | How many elements the shape is pooled over. |
84
+ | `ad_max` | float | A tail no candidate fit describes this well is counted, not extrapolated. |
85
+
86
+ **Returns** p-values, `(n_elements,)`.
87
+
88
+ > **Why `cnt` and `n_perm` are arguments.** Storing the whole `n_perm × n_elements` null is impossible
89
+ > at any real size, so only the tail is kept - but a truncated tail cannot say *how often* an element
90
+ > was exceeded, and that count is what the p-value is made of. Accumulate it in your permutation loop:
91
+ > it costs one comparison per element. See [permutation.md](permutation.md).
92
+
93
+ ### `gpd_fit_pwm(y)`
94
+
95
+ Generalised Pareto parameters by probability-weighted moments (Hosking & Wallis).
96
+
97
+ `y` is `(m, n)` - exceedances above a threshold, **ascending** down each column, one column per
98
+ element. Returns `(k, sigma)`, each `(n,)`, parameterised as `F(y) = 1 − (1 − k·y/σ)^(1/k)`.
99
+
100
+ ### `gpd_sf(z, k, sigma)`
101
+
102
+ Survival function of that distribution. Handles the `k → 0` limit (exponential) and clamps to zero
103
+ beyond the upper endpoint of a `k > 0` fit.
104
+
105
+ ---
106
+
107
+ ## `tfce.glm`
108
+
109
+ ### `PermutedGLM(Y, design, contrast)`
110
+
111
+ Fit `Y = X·B`, and re-fit it under permutations **without ever forming the permuted data**.
112
+
113
+ | argument | | |
114
+ | --- | --- | --- |
115
+ | `Y` | `(n_elements, n_samples)` | One **row** per element, one column per observation. Note the orientation: elements are the long axis and this keeps them contiguous. |
116
+ | `design` | `(n_samples, n_regressors)` | |
117
+ | `contrast` | `(n_regressors,)` or `(n_regressors, q)` | A vector gives a t-contrast, a matrix an F-contrast. |
118
+
119
+ **Attributes**: `df`, `rank`, `pinvX`, `XtX`, `ssy`, `is_t`.
120
+
121
+ **Raises** `ValueError` if the design leaves no residual degrees of freedom, or if `Y` and the design
122
+ disagree about the number of observations.
123
+
124
+ #### `.fit(perm=None)`
125
+
126
+ Statistic under one permutation. `perm` is read the way numpy reads a fancy index: the permuted data
127
+ is `Y[:, perm]`. `None` is the identity - the unpermuted fit.
128
+
129
+ **Returns** `(n_elements,)`.
130
+
131
+ #### `.fit_signs(signs)`
132
+
133
+ Statistic under one sign-flip, for a one-sample design. `signs` is `(n_samples,)` of ±1.
134
+
135
+ A one-sample design is not permuted by shuffling - there is nothing to shuffle it with. It is permuted
136
+ by flipping signs, which is exchangeable under the null of "mean zero".
137
+
138
+ ### `partition_design(design, contrast)`
139
+
140
+ Guttman partition: which columns the contrast tests, and which are nuisance.
141
+
142
+ **Returns** `(idx_interest, idx_nuisance)`.
143
+
144
+ ---
145
+
146
+ ## `tfce.nilearn_compat`
147
+
148
+ ### `calculate_tfce(arr4d, bin_struct, E=0.5, H=2, dh="auto", two_sided_test=True)`
149
+
150
+ Drop-in for `nilearn.mass_univariate._utils.calculate_tfce`, with the same signature and an exact
151
+ transform. See [nilearn.md](nilearn.md).
152
+
153
+ `dh` is **accepted and ignored** - there is no step size. Passing one is not an error; it simply has
154
+ nothing to change.
155
+
156
+ ### `connectivity_from_bin_struct(bin_struct)`
157
+
158
+ Read `6`, `18` or `26` out of a `scipy.ndimage.generate_binary_structure` array, by counting the
159
+ neighbours it actually marks.
160
+
161
+ ---
162
+
163
+ ## Module-level
164
+
165
+ | | |
166
+ | --- | --- |
167
+ | `tfce.__version__` | the installed version |
168
+ | `tfce.core.VALID_CONNECTIVITY` | `(6, 18, 26)` |