brainstat 0.6.0__tar.gz → 0.7.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 (76) hide show
  1. {brainstat-0.6.0/brainstat.egg-info → brainstat-0.7.0}/PKG-INFO +7 -5
  2. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/__init__.py +1 -1
  3. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/_utils.py +6 -2
  4. brainstat-0.7.0/brainstat/context/genetics.py +351 -0
  5. brainstat-0.7.0/brainstat/reporting/__init__.py +5 -0
  6. brainstat-0.7.0/brainstat/reporting/_slm.py +316 -0
  7. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/SLM.py +16 -0
  8. brainstat-0.7.0/brainstat/tests/test_download.py +65 -0
  9. brainstat-0.7.0/brainstat/tests/test_genetics.py +215 -0
  10. brainstat-0.7.0/brainstat/tests/test_reporting.py +136 -0
  11. {brainstat-0.6.0 → brainstat-0.7.0/brainstat.egg-info}/PKG-INFO +7 -5
  12. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat.egg-info/SOURCES.txt +4 -0
  13. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat.egg-info/requires.txt +3 -1
  14. {brainstat-0.6.0 → brainstat-0.7.0}/setup.py +6 -4
  15. brainstat-0.6.0/brainstat/context/genetics.py +0 -301
  16. brainstat-0.6.0/brainstat/tests/test_genetics.py +0 -33
  17. {brainstat-0.6.0 → brainstat-0.7.0}/LICENSE +0 -0
  18. {brainstat-0.6.0 → brainstat-0.7.0}/MANIFEST.in +0 -0
  19. {brainstat-0.6.0 → brainstat-0.7.0}/README.rst +0 -0
  20. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/_typing.py +0 -0
  21. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/context/__init__.py +0 -0
  22. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/context/histology.py +0 -0
  23. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/context/meta_analysis.py +0 -0
  24. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/context/resting.py +0 -0
  25. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/data_urls.json +0 -0
  26. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/logging.conf +0 -0
  27. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_civet164k.nii.gz +0 -0
  28. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_civet41k.nii.gz +0 -0
  29. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_fsaverage.nii.gz +0 -0
  30. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_fsaverage5.nii.gz +0 -0
  31. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/data/nn_interp_fslr32k.nii.gz +0 -0
  32. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/datasets/__init__.py +0 -0
  33. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/datasets/base.py +0 -0
  34. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/mesh/__init__.py +0 -0
  35. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/mesh/data.py +0 -0
  36. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/mesh/interpolate.py +0 -0
  37. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/mesh/utils.py +0 -0
  38. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/__init__.py +0 -0
  39. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/_linear_model.py +0 -0
  40. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/_multiple_comparisons.py +0 -0
  41. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/_t_test.py +0 -0
  42. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/terms.py +0 -0
  43. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/stats/utils.py +0 -0
  44. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/__init__.py +0 -0
  45. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/conftest.py +0 -0
  46. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_f.py +0 -0
  47. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_fdr.py +0 -0
  48. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_linear_model.py +0 -0
  49. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_mesh_edges.py +0 -0
  50. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_mesh_smooth.py +0 -0
  51. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_peak_clus.py +0 -0
  52. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_random_field_theory.py +0 -0
  53. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_resels.py +0 -0
  54. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_slm.py +0 -0
  55. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_stat_threshold.py +0 -0
  56. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/datagen_t_test.py +0 -0
  57. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_SLM.py +0 -0
  58. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_datasets.py +0 -0
  59. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_f_test.py +0 -0
  60. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_fdr.py +0 -0
  61. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_histology.py +0 -0
  62. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_linear_model.py +0 -0
  63. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_mesh_edges.py +0 -0
  64. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_mesh_smooth.py +0 -0
  65. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_peak_clus.py +0 -0
  66. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_random_field_theory.py +0 -0
  67. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_resels.py +0 -0
  68. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_stat_threshold.py +0 -0
  69. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_t_test.py +0 -0
  70. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/test_terms.py +0 -0
  71. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tests/testutil.py +0 -0
  72. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tutorial/__init__.py +0 -0
  73. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat/tutorial/utils.py +0 -0
  74. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat.egg-info/dependency_links.txt +0 -0
  75. {brainstat-0.6.0 → brainstat-0.7.0}/brainstat.egg-info/top_level.txt +0 -0
  76. {brainstat-0.6.0 → brainstat-0.7.0}/setup.cfg +0 -0
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainstat
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: A toolbox for statistical analysis of neuroimaging data
5
- Home-page: https://github.com/MICA-LAB/BrainStat
5
+ Home-page: https://github.com/MICA-MNI/BrainStat
6
6
  Author: MNI-MICA Lab and MPI-CNG Lab
7
7
  Author-email: saratheriver@gmail.com, sheymaba@gmail.com
8
8
  License: BSD 3-Clause License
9
9
  Project-URL: Documentation, https://brainstat.readthedocs.io
10
- Project-URL: Bug Reports, https://github.com/MICA-LAB/BrainStat/issues
11
- Project-URL: Source, https://github.com/MICA-LAB/BrainStat/
10
+ Project-URL: Bug Reports, https://github.com/MICA-MNI/BrainStat/issues
11
+ Project-URL: Source, https://github.com/MICA-MNI/BrainStat/
12
12
  Classifier: Intended Audience :: Science/Research
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Topic :: Software Development :: Build Tools
@@ -18,13 +18,15 @@ Requires-Python: >=3.10
18
18
  Description-Content-Type: text/x-rst
19
19
  License-File: LICENSE
20
20
  Requires-Dist: abagen>=0.1
21
+ Requires-Dist: setuptools<81
22
+ Requires-Dist: statsmodels
21
23
  Requires-Dist: brainspace>=0.1.2
22
24
  Requires-Dist: h5py
23
25
  Requires-Dist: matplotlib>=3.4
24
26
  Requires-Dist: netneurotools<0.3.0
25
27
  Requires-Dist: neurosynth
26
28
  Requires-Dist: nibabel
27
- Requires-Dist: nilearn>=0.7.0
29
+ Requires-Dist: nilearn>=0.10.4
28
30
  Requires-Dist: nimare
29
31
  Requires-Dist: numpy<2,>=1.21
30
32
  Requires-Dist: pandas<2.1,>=1.3
@@ -1,2 +1,2 @@
1
1
  """Neuroimaging statistics toolbox."""
2
- __version__ = "0.6.0"
2
+ __version__ = "0.7.0"
@@ -6,6 +6,7 @@ import shutil
6
6
  import urllib.request
7
7
  import warnings
8
8
  from pathlib import Path
9
+ from tempfile import TemporaryDirectory
9
10
  from typing import Callable
10
11
 
11
12
  import brainstat
@@ -179,8 +180,11 @@ def _download_file(
179
180
 
180
181
  for attempt in range(max_retries):
181
182
  try:
182
- with urllib.request.urlopen(url, timeout=30) as response, open(output_file, "wb") as out_file:
183
- shutil.copyfileobj(response, out_file)
183
+ with TemporaryDirectory(dir=output_file.parent) as tmpdir:
184
+ temporary_file = Path(tmpdir) / output_file.name
185
+ with urllib.request.urlopen(url, timeout=30) as response, open(temporary_file, "wb") as out_file:
186
+ shutil.copyfileobj(response, out_file)
187
+ temporary_file.replace(output_file)
184
188
  return # Success, exit function
185
189
  except (RemoteDisconnected, URLError, TimeoutError) as e:
186
190
  if attempt < max_retries - 1:
@@ -0,0 +1,351 @@
1
+ """Genetic decoding using abagen."""
2
+
3
+ import tempfile
4
+ from contextlib import contextmanager
5
+ from pathlib import Path
6
+ from threading import RLock
7
+ from typing import Optional, Sequence, Union
8
+
9
+ import abagen.images
10
+ import nibabel as nib
11
+ import numpy as np
12
+ import pandas as pd
13
+ from abagen import check_atlas, get_expression_data
14
+ from brainspace.mesh.mesh_io import read_surface, write_surface
15
+ from nibabel.gifti import GiftiDataArray, GiftiImage
16
+ from sklearn.model_selection import ParameterGrid
17
+
18
+ from brainstat._utils import data_directories, logger
19
+ from brainstat.datasets.base import (
20
+ _fetch_template_surface_files,
21
+ _valid_parcellations,
22
+ fetch_parcellation,
23
+ )
24
+
25
+ _COMPAT_LOCK = RLock()
26
+
27
+
28
+ def _labeltable_to_df(labels):
29
+ rows = [
30
+ dict(id=key, label=value, hemisphere=hemi, structure="cortex")
31
+ for table, hemi in zip(labels, ("L", "R"))
32
+ for key, value in table.items()
33
+ if key != 0
34
+ ]
35
+ if rows:
36
+ return pd.DataFrame(rows).set_index("id").sort_index()
37
+ return None
38
+
39
+
40
+ @contextmanager
41
+ def _abagen_compat():
42
+ """Scope legacy abagen adapters to a call and always restore originals.
43
+
44
+ The lock serializes BrainStat genetics calls. The pandas adapters are
45
+ process-wide while the call runs; callers using abagen/pandas directly in
46
+ other threads should avoid running them concurrently with this workflow.
47
+ Dependency bounds remain in place until upstream supports newer pandas.
48
+ """
49
+ with _COMPAT_LOCK:
50
+ original_append = getattr(pd.DataFrame, "append", None)
51
+ original_set_axis = pd.DataFrame.set_axis
52
+ original_labels = abagen.images.labeltable_to_df
53
+
54
+ def append(
55
+ frame, other, ignore_index=False, verify_integrity=False, sort=False
56
+ ):
57
+ if isinstance(other, pd.Series):
58
+ other = other.to_frame().T
59
+ objects = [frame] + (
60
+ list(other) if isinstance(other, (list, tuple)) else [other]
61
+ )
62
+ return pd.concat(
63
+ objects,
64
+ ignore_index=ignore_index,
65
+ verify_integrity=verify_integrity,
66
+ sort=sort,
67
+ )
68
+
69
+ def set_axis(frame, labels, *args, **kwargs):
70
+ inplace = kwargs.pop("inplace", False)
71
+ if inplace:
72
+ raise ValueError(
73
+ "abagen compatibility supports set_axis(inplace=False) only"
74
+ )
75
+ return original_set_axis(frame, labels, *args, **kwargs)
76
+
77
+ try:
78
+ if original_append is None:
79
+ pd.DataFrame.append = append
80
+ pd.DataFrame.set_axis = set_axis
81
+ abagen.images.labeltable_to_df = _labeltable_to_df
82
+ yield
83
+ finally:
84
+ if original_append is None:
85
+ del pd.DataFrame.append
86
+ else:
87
+ pd.DataFrame.append = original_append
88
+ pd.DataFrame.set_axis = original_set_axis
89
+ abagen.images.labeltable_to_df = original_labels
90
+
91
+
92
+ def _surface_size(surface):
93
+ if isinstance(surface, (str, Path)):
94
+ surface = read_surface(str(surface))
95
+ return surface.n_points
96
+
97
+
98
+ def surface_genetic_expression(
99
+ labels: Union[Sequence[str], np.ndarray],
100
+ surfaces: Optional[Union[str, Sequence[str]]] = None,
101
+ space: Optional[str] = None,
102
+ *,
103
+ atlas_info: Optional[str] = None,
104
+ ibf_threshold: float = 0.5,
105
+ probe_selection: str = "diff_stability",
106
+ donor_probes: str = "aggregate",
107
+ lr_mirror: Optional[bool] = None,
108
+ missing: Optional[str] = None,
109
+ tolerance: float = 2,
110
+ sample_norm: str = "srs",
111
+ gene_norm: str = "srs",
112
+ norm_matched: bool = True,
113
+ norm_structures: bool = False,
114
+ region_agg: str = "donors",
115
+ agg_metric: str = "mean",
116
+ corrected_mni: bool = True,
117
+ reannotated: bool = True,
118
+ return_counts: bool = False,
119
+ return_donors: bool = False,
120
+ return_report: bool = False,
121
+ donors: str = "all",
122
+ data_dir: Optional[str] = None,
123
+ verbose: float = 0,
124
+ n_proc: int = 1,
125
+ ) -> Union[pd.DataFrame, dict, tuple]:
126
+ """Computes genetic expression of surface parcels.
127
+
128
+ Parameters
129
+ ----------
130
+ labels : list-of-str or numpy.ndarray
131
+ List of paths to label files for the parcellation, or numpy array
132
+ containing the pre-loaded labels
133
+ surfaces : list-of-image, optional
134
+ List of paths to surface files or preloaded surfaces. If not specified
135
+ assumes that `labels` are on the `fsaverage5` surface. Default: None
136
+ space : {'fsaverage', 'fslr'}
137
+ What template space `surfaces` are aligned to. If not specified assumes
138
+ that `labels` are on the `fsaverage5` surface. Default: None
139
+
140
+ For details of the remaining parameters please consult the
141
+ abagen.get_expression_data() documentation. All its parameters bar "atlas"
142
+ are valid input parameters.
143
+
144
+ Returns
145
+ -------
146
+ pandas.DataFrame, dict or tuple
147
+ Expression data returned unchanged from abagen. The return_counts,
148
+ return_donors and return_report options can change the return type.
149
+
150
+ Examples
151
+ --------
152
+ >>> from brainstat.context.genetics import surface_genetic_expression
153
+ >>> from nilearn import datasets
154
+ >>> import numpy as np
155
+
156
+ >>> destrieux = datasets.fetch_atlas_surf_destrieux()
157
+ >>> labels = np.hstack((destrieux['map_left'], destrieux['map_right']))
158
+ >>> fsaverage = datasets.fetch_surf_fsaverage()
159
+ >>> surfaces = (fsaverage['pial_left'], fsaverage['pial_right'])
160
+ >>> expression = surface_genetic_expression(labels, surfaces,
161
+ ... space='fsaverage')
162
+ """
163
+
164
+ if isinstance(surfaces, (str, Path)):
165
+ surfaces = [surfaces]
166
+ elif surfaces is not None:
167
+ surfaces = list(surfaces)
168
+
169
+ if surfaces is not None and len(surfaces) != 2:
170
+ raise ValueError("Provide both left and right hemisphere surfaces.")
171
+
172
+ if isinstance(labels, np.ndarray):
173
+ if (
174
+ labels.ndim != 1
175
+ or labels.size == 0
176
+ or not np.issubdtype(labels.dtype, np.number)
177
+ or np.iscomplexobj(labels)
178
+ or not np.all(np.isfinite(labels))
179
+ or np.any(labels < 0)
180
+ or np.any(labels > np.iinfo(np.int32).max)
181
+ or not np.all(labels == np.floor(labels))
182
+ ):
183
+ raise ValueError(
184
+ "labels must be a nonempty 1D array of nonnegative int32 label IDs."
185
+ )
186
+ if surfaces is None:
187
+ if labels.size != 20484:
188
+ raise ValueError(
189
+ "Without surfaces, labels must match fsaverage5 (20484 vertices)."
190
+ )
191
+ surfaces = list(
192
+ _fetch_template_surface_files(
193
+ "fsaverage5",
194
+ data_dir=(
195
+ Path(data_dir)
196
+ if data_dir
197
+ else data_directories["SURFACE_DATA_DIR"]
198
+ ),
199
+ )
200
+ )
201
+ if space is None:
202
+ space = "fsaverage"
203
+ sizes = [_surface_size(surface) for surface in surfaces]
204
+ if sum(sizes) != labels.size:
205
+ raise ValueError(
206
+ "Label count does not match the supplied surface geometry."
207
+ )
208
+
209
+ # A directory owns every temporary file, including files whose save fails.
210
+ with tempfile.TemporaryDirectory(prefix="brainstat-genetics-") as tmpdir:
211
+ if isinstance(labels, np.ndarray):
212
+ arrays = np.split(labels.astype(np.int32), [sizes[0]])
213
+ label_files = []
214
+ for index, array in enumerate(arrays):
215
+ path = str(Path(tmpdir) / f"labels-{index}.label.gii")
216
+ image = GiftiImage(
217
+ darrays=[GiftiDataArray(data=array, intent="NIFTI_INTENT_LABEL")]
218
+ )
219
+ nib.save(image, path)
220
+ label_files.append(path)
221
+ labels = tuple(label_files)
222
+ else:
223
+ labels = tuple(str(path) for path in labels)
224
+
225
+ surfaces_gii = None
226
+ if surfaces is not None:
227
+ surfaces_gii = []
228
+ for index, surface in enumerate(surfaces):
229
+ if isinstance(surface, (str, Path)):
230
+ surfaces_gii.append(str(surface))
231
+ else:
232
+ # Cast a copy: converting the surface must not alter caller geometry.
233
+ surface = surface.copy()
234
+ surface.Points = surface.Points.astype(np.float32)
235
+ path = str(Path(tmpdir) / f"surface-{index}.surf.gii")
236
+ write_surface(surface, path, otype="gii")
237
+ surfaces_gii.append(path)
238
+
239
+ # Use abagen to grab expression data.
240
+ logger.info(
241
+ "If you use BrainStat's genetics functionality, please cite abagen (https://abagen.readthedocs.io/en/stable/citing.html)."
242
+ )
243
+ with _abagen_compat():
244
+ atlas = check_atlas(labels, geometry=surfaces_gii, space=space)
245
+ expression = get_expression_data(
246
+ atlas,
247
+ atlas_info=atlas_info,
248
+ ibf_threshold=ibf_threshold,
249
+ probe_selection=probe_selection,
250
+ donor_probes=donor_probes,
251
+ lr_mirror=lr_mirror,
252
+ missing=missing,
253
+ tolerance=tolerance,
254
+ sample_norm=sample_norm,
255
+ gene_norm=gene_norm,
256
+ norm_matched=norm_matched,
257
+ norm_structures=norm_structures,
258
+ region_agg=region_agg,
259
+ agg_metric=agg_metric,
260
+ corrected_mni=corrected_mni,
261
+ reannotated=reannotated,
262
+ return_counts=return_counts,
263
+ return_donors=return_donors,
264
+ return_report=return_report,
265
+ donors=donors,
266
+ data_dir=data_dir,
267
+ verbose=verbose,
268
+ n_proc=n_proc,
269
+ )
270
+
271
+ return expression
272
+
273
+
274
+ def __create_precomputed(
275
+ data_dir: Optional[Union[str, Path]] = None,
276
+ output_dir: Optional[Union[str, Path]] = None,
277
+ ) -> None:
278
+ """Creates precomputed matrices of genetic expression for standard atlases. These are
279
+ used for the MATLAB implementation."""
280
+
281
+ if output_dir is None:
282
+ output_dir = data_dir = (
283
+ Path(data_dir) if data_dir else data_directories["BRAINSTAT_DATA_DIR"]
284
+ )
285
+
286
+ # Get all parcellations in a format compatible with ParameterGrid.
287
+ parcellations = _valid_parcellations()
288
+ for key in parcellations:
289
+ parcellations[key].update({"atlas": [key]})
290
+ if key == "schaefer":
291
+ parcellations[key].update({"seven_networks": (True, False)})
292
+ else:
293
+ parcellations[key].update({"seven_networks": (True,)})
294
+
295
+ # We only really need to compute this for one template surface.
296
+ # We'll use fsaverage5 for all.
297
+ parcellations[key].update({"surfaces": ["fsaverage5"]})
298
+
299
+ param_grid = ParameterGrid(list(parcellations.values()))
300
+
301
+ # Compute expression for all parcellations.
302
+ for params in param_grid:
303
+ if params["atlas"] == "schaefer":
304
+ network_tag = "7Networks" if params["seven_networks"] else "17Networks"
305
+ else:
306
+ network_tag = ""
307
+
308
+ filename_components = filter(
309
+ None,
310
+ (
311
+ "expression",
312
+ params["atlas"],
313
+ str(params["n_regions"]),
314
+ network_tag,
315
+ ),
316
+ )
317
+
318
+ filename = "_".join(filename_components) + ".csv.gz"
319
+ output_file = Path(output_dir) / filename
320
+ if output_file.exists():
321
+ print('Skipping "{}"'.format(output_file))
322
+ continue
323
+ else:
324
+ print('Computing "{}"'.format(output_file))
325
+
326
+ surface_files = _fetch_template_surface_files(
327
+ params["surfaces"], data_dir=data_dir # type: ignore
328
+ )
329
+ space = "fslr" if params["surfaces"] == "fslr32k" else "fsaverage"
330
+
331
+ labels = fetch_parcellation(
332
+ params["surfaces"],
333
+ params["atlas"],
334
+ params["n_regions"],
335
+ seven_networks=params["seven_networks"],
336
+ data_dir=data_dir,
337
+ )
338
+
339
+ surf_lh = tempfile.NamedTemporaryFile(suffix=".surf.gii")
340
+ surf_rh = tempfile.NamedTemporaryFile(suffix=".surf.gii")
341
+ for i, surf in enumerate((surf_lh, surf_rh)):
342
+ __freesurfer_to_surfgii(surface_files[i], surf.name)
343
+ surface_paths = [surf_lh.name, surf_rh.name]
344
+
345
+ expression = surface_genetic_expression(labels, surface_paths, space=space) # type: ignore
346
+ expression.to_csv(output_file) # type: ignore
347
+
348
+
349
+ def __freesurfer_to_surfgii(freesurfer_file: str, gifti_file: str) -> None:
350
+ surf = read_surface(freesurfer_file, itype="fs")
351
+ write_surface(surf, gifti_file, otype="gii")
@@ -0,0 +1,5 @@
1
+ """Reporting helpers for BrainStat results."""
2
+
3
+ from ._slm import make_slm_report
4
+
5
+ __all__ = ["make_slm_report"]