brainstat 0.4.2__tar.gz → 0.5.2__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 (71) hide show
  1. {brainstat-0.4.2/brainstat.egg-info → brainstat-0.5.2}/PKG-INFO +38 -7
  2. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/__init__.py +1 -1
  3. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/_utils.py +28 -3
  4. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/genetics.py +131 -47
  5. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/histology.py +23 -2
  6. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/datasets/base.py +114 -3
  7. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/mesh/interpolate.py +1 -1
  8. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/_linear_model.py +3 -1
  9. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/_multiple_comparisons.py +2 -2
  10. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/_t_test.py +1 -1
  11. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/terms.py +16 -7
  12. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/utils.py +4 -2
  13. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_SLM.py +31 -0
  14. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_datasets.py +1 -0
  15. brainstat-0.5.2/brainstat/tests/test_genetics.py +33 -0
  16. brainstat-0.5.2/brainstat/tests/test_histology.py +32 -0
  17. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_mesh_edges.py +1 -1
  18. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_terms.py +26 -2
  19. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tutorial/utils.py +1 -1
  20. {brainstat-0.4.2 → brainstat-0.5.2/brainstat.egg-info}/PKG-INFO +38 -7
  21. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat.egg-info/SOURCES.txt +1 -0
  22. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat.egg-info/requires.txt +6 -5
  23. {brainstat-0.4.2 → brainstat-0.5.2}/setup.cfg +1 -1
  24. {brainstat-0.4.2 → brainstat-0.5.2}/setup.py +8 -9
  25. brainstat-0.4.2/brainstat/tests/test_histology.py +0 -21
  26. {brainstat-0.4.2 → brainstat-0.5.2}/LICENSE +0 -0
  27. {brainstat-0.4.2 → brainstat-0.5.2}/MANIFEST.in +0 -0
  28. {brainstat-0.4.2 → brainstat-0.5.2}/README.rst +0 -0
  29. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/_typing.py +0 -0
  30. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/__init__.py +0 -0
  31. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/meta_analysis.py +0 -0
  32. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/context/resting.py +0 -0
  33. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/data_urls.json +0 -0
  34. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/logging.conf +0 -0
  35. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_civet164k.nii.gz +0 -0
  36. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_civet41k.nii.gz +0 -0
  37. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_fsaverage.nii.gz +0 -0
  38. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_fsaverage5.nii.gz +0 -0
  39. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/data/nn_interp_fslr32k.nii.gz +0 -0
  40. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/datasets/__init__.py +0 -0
  41. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/mesh/__init__.py +0 -0
  42. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/mesh/data.py +0 -0
  43. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/mesh/utils.py +0 -0
  44. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/SLM.py +0 -0
  45. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/stats/__init__.py +0 -0
  46. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/__init__.py +0 -0
  47. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/conftest.py +0 -0
  48. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_f.py +0 -0
  49. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_fdr.py +0 -0
  50. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_linear_model.py +0 -0
  51. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_mesh_edges.py +0 -0
  52. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_mesh_smooth.py +0 -0
  53. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_peak_clus.py +0 -0
  54. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_random_field_theory.py +0 -0
  55. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_resels.py +0 -0
  56. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_slm.py +0 -0
  57. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_stat_threshold.py +0 -0
  58. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/datagen_t_test.py +0 -0
  59. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_f_test.py +0 -0
  60. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_fdr.py +0 -0
  61. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_linear_model.py +0 -0
  62. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_mesh_smooth.py +0 -0
  63. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_peak_clus.py +0 -0
  64. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_random_field_theory.py +0 -0
  65. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_resels.py +0 -0
  66. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_stat_threshold.py +0 -0
  67. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/test_t_test.py +0 -0
  68. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tests/testutil.py +0 -0
  69. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat/tutorial/__init__.py +0 -0
  70. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat.egg-info/dependency_links.txt +0 -0
  71. {brainstat-0.4.2 → brainstat-0.5.2}/brainstat.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: brainstat
3
- Version: 0.4.2
3
+ Version: 0.5.2
4
4
  Summary: A toolbox for statistical analysis of neuroimaging data
5
5
  Home-page: https://github.com/MICA-LAB/BrainStat
6
6
  Author: MNI-MICA Lab and MPI-CNG Lab
@@ -13,13 +13,44 @@ Classifier: Intended Audience :: Science/Research
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Topic :: Software Development :: Build Tools
15
15
  Classifier: Topic :: Scientific/Engineering
16
- Classifier: Programming Language :: Python :: 3.7
17
- Classifier: Programming Language :: Python :: 3.8
18
- Classifier: Programming Language :: Python :: 3.9
19
- Requires-Python: >=3.7.*
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Requires-Python: >=3.10
20
18
  Description-Content-Type: text/x-rst
21
- Provides-Extra: dev
22
19
  License-File: LICENSE
20
+ Requires-Dist: abagen>=0.1
21
+ Requires-Dist: brainspace>=0.1.2
22
+ Requires-Dist: h5py
23
+ Requires-Dist: matplotlib>=3.4
24
+ Requires-Dist: netneurotools<0.3.0
25
+ Requires-Dist: neurosynth
26
+ Requires-Dist: nibabel
27
+ Requires-Dist: nilearn>=0.7.0
28
+ Requires-Dist: nimare
29
+ Requires-Dist: numpy>=1.21
30
+ Requires-Dist: pandas>=1.3
31
+ Requires-Dist: scikit_learn>=1.0
32
+ Requires-Dist: scipy>=1.7
33
+ Requires-Dist: templateflow
34
+ Requires-Dist: trimesh
35
+ Provides-Extra: dev
36
+ Requires-Dist: gitpython; extra == "dev"
37
+ Requires-Dist: hcp-utils; extra == "dev"
38
+ Requires-Dist: mypy; extra == "dev"
39
+ Requires-Dist: plotly; extra == "dev"
40
+ Requires-Dist: pytest; extra == "dev"
41
+ Dynamic: author
42
+ Dynamic: author-email
43
+ Dynamic: classifier
44
+ Dynamic: description
45
+ Dynamic: description-content-type
46
+ Dynamic: home-page
47
+ Dynamic: license
48
+ Dynamic: license-file
49
+ Dynamic: project-url
50
+ Dynamic: provides-extra
51
+ Dynamic: requires-dist
52
+ Dynamic: requires-python
53
+ Dynamic: summary
23
54
 
24
55
  BrainStat : A toolbox for statistical analysis of neuroimaging data
25
56
  ===================================================================
@@ -1,2 +1,2 @@
1
1
  """Neuroimaging statistics toolbox."""
2
- __version__ = "0.4.2"
2
+ __version__ = "0.5.2"
@@ -150,7 +150,7 @@ def deprecated(message: str) -> Callable:
150
150
  def _download_file(
151
151
  url: str, output_file: Path, overwrite: bool = False, verbose=True
152
152
  ) -> None:
153
- """Downloads a file.
153
+ """Downloads a file with retry logic for network failures.
154
154
 
155
155
  Parameters
156
156
  ----------
@@ -163,14 +163,39 @@ def _download_file(
163
163
  verbose : bool
164
164
  If true, print a download message, defaults to True.
165
165
  """
166
+ import time
167
+ from http.client import RemoteDisconnected
168
+ from urllib.error import URLError
166
169
 
167
170
  if output_file.exists() and not overwrite:
168
171
  return
169
172
 
170
173
  if verbose:
171
174
  logger.info("Downloading " + str(output_file) + " from " + url + ".")
172
- with urllib.request.urlopen(url) as response, open(output_file, "wb") as out_file:
173
- shutil.copyfileobj(response, out_file)
175
+
176
+ # Retry logic for intermittent network failures
177
+ max_retries = 3
178
+ retry_delay = 2 # seconds
179
+
180
+ for attempt in range(max_retries):
181
+ try:
182
+ with urllib.request.urlopen(url, timeout=30) as response, open(output_file, "wb") as out_file:
183
+ shutil.copyfileobj(response, out_file)
184
+ return # Success, exit function
185
+ except (RemoteDisconnected, URLError, TimeoutError) as e:
186
+ if attempt < max_retries - 1:
187
+ logger.warning(
188
+ f"Download attempt {attempt + 1}/{max_retries} failed: {e}. "
189
+ f"Retrying in {retry_delay} seconds..."
190
+ )
191
+ time.sleep(retry_delay)
192
+ retry_delay *= 2 # Exponential backoff
193
+ else:
194
+ logger.error(f"Download failed after {max_retries} attempts.")
195
+ raise # Re-raise the exception after all retries fail
196
+
197
+
198
+
174
199
 
175
200
 
176
201
  if __name__ == "__main__":
@@ -2,13 +2,59 @@
2
2
  import tempfile
3
3
  from pathlib import Path
4
4
  from typing import Optional, Sequence, Union
5
-
5
+ import os
6
6
  import nibabel as nib
7
7
  import numpy as np
8
8
  import pandas as pd
9
+
10
+ # Monkeypatch for abagen compatibility with pandas 2.0+
11
+ if not hasattr(pd.DataFrame, 'append'):
12
+ def _append(self, other, ignore_index=False, verify_integrity=False, sort=False):
13
+ from pandas import concat
14
+ if isinstance(other, (list, tuple)):
15
+ to_concat = [self] + list(other)
16
+ else:
17
+ to_concat = [self, other]
18
+ return concat(to_concat, ignore_index=ignore_index, verify_integrity=verify_integrity, sort=sort)
19
+ pd.DataFrame.append = _append
20
+
21
+ # Monkeypatch for abagen compatibility with pandas 2.0+ (set_axis inplace)
22
+ _original_set_axis = pd.DataFrame.set_axis
23
+ def _set_axis_patched(self, labels, *args, **kwargs):
24
+ if 'inplace' in kwargs:
25
+ del kwargs['inplace']
26
+ return _original_set_axis(self, labels, *args, **kwargs)
27
+ pd.DataFrame.set_axis = _set_axis_patched
28
+
29
+ import abagen.images
30
+
31
+ def _labeltable_to_df_patched(labels):
32
+ """
33
+ Patched version of abagen.utils.labeltable_to_df to handle missing 0 index
34
+ and use pd.concat instead of append.
35
+ """
36
+ info = pd.DataFrame(columns=['id', 'label', 'hemisphere', 'structure'])
37
+ for table, hemi in zip(labels, ('L', 'R')):
38
+ if len(table) == 0:
39
+ continue
40
+ ids, label = zip(*table.items())
41
+ new_df = pd.DataFrame(dict(id=ids, label=label, hemisphere=hemi, structure='cortex'))
42
+ info = pd.concat([info, new_df], ignore_index=True)
43
+
44
+ # Use errors='ignore' to handle missing 0
45
+ info = info.set_index('id').drop([0], axis=0, errors='ignore').sort_index()
46
+
47
+ if len(info) != 0:
48
+ return info
49
+
50
+ abagen.images.labeltable_to_df = _labeltable_to_df_patched
51
+
52
+
53
+ import collections
9
54
  from abagen import check_atlas, get_expression_data
10
55
  from brainspace.mesh.mesh_io import read_surface, write_surface
11
56
  from sklearn.model_selection import ParameterGrid
57
+ from nibabel.gifti import GiftiImage, GiftiDataArray
12
58
 
13
59
  from brainstat._utils import data_directories, logger
14
60
  from brainstat.datasets.base import (
@@ -23,7 +69,7 @@ def surface_genetic_expression(
23
69
  surfaces: Optional[Union[str, Sequence[str]]] = None,
24
70
  space: Optional[str] = None,
25
71
  *,
26
- atlas_info: str = None,
72
+ atlas_info: Optional[str] = None,
27
73
  ibf_threshold: float = 0.5,
28
74
  probe_selection: str = "diff_stability",
29
75
  donor_probes: str = "aggregate",
@@ -89,52 +135,90 @@ def surface_genetic_expression(
89
135
  elif surfaces is None:
90
136
  surfaces = []
91
137
 
92
- surfaces_gii = []
93
- for surface in surfaces:
94
- if not isinstance(surface, str) and not isinstance(surface, Path):
95
- # Rather roundabout deletion of the temporary file for Windows compatibility.
138
+ temp_files = []
139
+ try:
140
+ if isinstance(labels, np.ndarray):
141
+ # Assuming 'labels' is a 1D NumPy array of length 20484
142
+ num_vertices = len(labels) # Should be 20484
143
+ half_size = num_vertices // 2 # Half of 20484, which is 10242
144
+
145
+ # Split the array into two halves
146
+ labels_left = labels[:half_size] # First half for the left hemisphere
147
+ labels_right = labels[half_size:] # Second half for the right hemisphere
148
+
149
+ # Create GiftiDataArrays for each hemisphere
150
+ data_array_left = GiftiDataArray(data=labels_left)
151
+ data_array_right = GiftiDataArray(data=labels_right)
152
+
153
+ # Create separate GiftiImages for each hemisphere
154
+ labels_left_gii = GiftiImage(darrays=[data_array_left])
155
+ labels_right_gii = GiftiImage(darrays=[data_array_right])
156
+
157
+ # Save to temporary files for abagen compatibility
158
+ labels_files = []
159
+ for img in [labels_left_gii, labels_right_gii]:
160
+ f = tempfile.NamedTemporaryFile(suffix=".gii", delete=False)
161
+ f.close()
162
+ nib.save(img, f.name)
163
+ labels_files.append(f.name)
164
+ temp_files.append(f.name)
165
+ labels = tuple(labels_files)
166
+
167
+ surfaces_gii = []
168
+ for surface in surfaces:
169
+ if not isinstance(surface, str) and not isinstance(surface, Path):
170
+ # Cast surface data to float32 to comply with GIFTI standard
171
+ # GIFTI only supports uint8, int32, and float32 datatypes
172
+ if hasattr(surface, 'Points') and surface.Points.dtype != np.float32:
173
+ surface = surface.copy()
174
+ surface.Points = surface.Points.astype(np.float32)
175
+
176
+ f = tempfile.NamedTemporaryFile(suffix=".gii", delete=False)
177
+ f.close()
178
+ write_surface(surface, f.name, otype="gii")
179
+ surfaces_gii.append(f.name)
180
+ temp_files.append(f.name)
181
+ else:
182
+ surfaces_gii.append(surface)
183
+
184
+ # Use abagen to grab expression data.
185
+ logger.info(
186
+ "If you use BrainStat's genetics functionality, please cite abagen (https://abagen.readthedocs.io/en/stable/citing.html)."
187
+ )
188
+ atlas = check_atlas(labels, geometry=surfaces_gii, space=space)
189
+ expression = get_expression_data(
190
+ atlas,
191
+ atlas_info=atlas_info,
192
+ ibf_threshold=ibf_threshold,
193
+ probe_selection=probe_selection,
194
+ donor_probes=donor_probes,
195
+ lr_mirror=lr_mirror,
196
+ missing=missing,
197
+ tolerance=tolerance,
198
+ sample_norm=sample_norm,
199
+ gene_norm=gene_norm,
200
+ norm_matched=norm_matched,
201
+ norm_structures=norm_structures,
202
+ region_agg=region_agg,
203
+ agg_metric=agg_metric,
204
+ corrected_mni=corrected_mni,
205
+ reannotated=reannotated,
206
+ return_counts=return_counts,
207
+ return_donors=return_donors,
208
+ return_report=return_report,
209
+ donors=donors,
210
+ data_dir=data_dir,
211
+ verbose=verbose,
212
+ n_proc=n_proc,
213
+ )
214
+
215
+ return expression
216
+ finally:
217
+ for f in temp_files:
96
218
  try:
97
- with tempfile.NamedTemporaryFile(suffix=".gii", delete=False) as f:
98
- name = f.name
99
- write_surface(surface, name, otype="gii")
100
- surfaces_gii.append(nib.load(name))
101
- finally:
102
- Path(name).unlink()
103
- else:
104
- surfaces_gii.append(nib.load(surface))
105
-
106
- # Use abagen to grab expression data.
107
- logger.info(
108
- "If you use BrainStat's genetics functionality, please cite abagen (https://abagen.readthedocs.io/en/stable/citing.html)."
109
- )
110
- atlas = check_atlas(labels, geometry=surfaces_gii, space=space)
111
- expression = get_expression_data(
112
- atlas,
113
- atlas_info=atlas_info,
114
- ibf_threshold=ibf_threshold,
115
- probe_selection=probe_selection,
116
- donor_probes=donor_probes,
117
- lr_mirror=lr_mirror,
118
- missing=missing,
119
- tolerance=tolerance,
120
- sample_norm=sample_norm,
121
- gene_norm=gene_norm,
122
- norm_matched=norm_matched,
123
- norm_structures=norm_structures,
124
- region_agg=region_agg,
125
- agg_metric=agg_metric,
126
- corrected_mni=corrected_mni,
127
- reannotated=reannotated,
128
- return_counts=return_counts,
129
- return_donors=return_donors,
130
- return_report=return_report,
131
- donors=donors,
132
- data_dir=data_dir,
133
- verbose=verbose,
134
- n_proc=n_proc,
135
- )
136
-
137
- return expression
219
+ Path(f).unlink()
220
+ except FileNotFoundError:
221
+ pass
138
222
 
139
223
 
140
224
  def __create_precomputed(
@@ -156,7 +156,19 @@ def read_histology_profile(
156
156
  )
157
157
 
158
158
  with h5py.File(histology_file, "r") as h5_file:
159
- profiles = h5_file.get(template)[...]
159
+ if template == "fslr32k":
160
+ # Known issue #369: The fslr32k data file on the server actually contains
161
+ # fs_LR_64k resolution data (64984 vertices) instead of fs_LR_32k (32492 vertices).
162
+ # This is a data hosting issue. Users expecting 32k resolution data should be aware
163
+ # that they will receive 64k resolution data until this is fixed on the server.
164
+ logger.warning(
165
+ "Known issue: The fslr32k histology profile data currently contains "
166
+ "fs_LR_64k resolution (64984 vertices) instead of fs_LR_32k (32492 vertices). "
167
+ "See https://github.com/MICA-MNI/BrainStat/issues/369 for details."
168
+ )
169
+ profiles = h5_file.get("fs_LR_64k")[...]
170
+ else:
171
+ profiles = h5_file.get(template)[...]
160
172
  if civet_template:
161
173
  fsaverage_surface = fetch_template_surface("fsaverage")
162
174
  civet_surface = fetch_template_surface(civet_template)
@@ -191,7 +203,16 @@ def download_histology_profiles(
191
203
 
192
204
  data_dir = Path(data_dir) if data_dir else data_directories["BIGBRAIN_DATA_DIR"]
193
205
  data_dir.mkdir(parents=True, exist_ok=True)
194
- output_file = data_dir / ("histology_" + template + ".h5")
206
+ if template == "fslr32k":
207
+ output_file = data_dir / "histology_fslr32k.h5"
208
+ # Known issue #369: warn users about data resolution mismatch
209
+ logger.warning(
210
+ "Note: The fslr32k histology profile data currently contains "
211
+ "fs_LR_64k resolution (64984 vertices) instead of fs_LR_32k. "
212
+ "See https://github.com/MICA-MNI/BrainStat/issues/369 for details."
213
+ )
214
+ else:
215
+ output_file = data_dir / ("histology_" + template + ".h5")
195
216
 
196
217
  url = read_data_fetcher_json()["bigbrain_profiles"][template]["url"]
197
218
 
@@ -1,6 +1,8 @@
1
1
  """ Load external datasets. """
2
+ import json
2
3
  import tempfile
3
4
  import zipfile
5
+ from collections import namedtuple
4
6
  from pathlib import Path
5
7
  from typing import List, Optional, Tuple, Union
6
8
 
@@ -11,9 +13,19 @@ from brainspace.mesh.mesh_io import read_surface
11
13
  from brainspace.mesh.mesh_operations import combine_surfaces
12
14
  from brainspace.vtk_interface.wrappers.data_object import BSPolyData
13
15
  from netneurotools import datasets as nnt_datasets
14
- from netneurotools.civet import read_civet
15
16
  from nibabel import load as nib_load
16
17
  from nibabel.freesurfer.io import read_annot, read_geometry
18
+ from sklearn.utils import Bunch
19
+
20
+ try:
21
+ from netneurotools.datasets.utils import _get_data_dir, _get_dataset_info
22
+ except ImportError:
23
+ from netneurotools.datasets.datasets_utils import _get_data_dir, _get_dataset_info
24
+
25
+ try:
26
+ from nilearn.datasets._utils import fetch_files as _fetch_files
27
+ except ImportError:
28
+ from nilearn.datasets.utils import fetch_files as _fetch_files
17
29
 
18
30
  from brainstat._utils import (
19
31
  _download_file,
@@ -24,6 +36,65 @@ from brainstat._utils import (
24
36
  from brainstat.mesh.interpolate import _surf2surf
25
37
 
26
38
 
39
+ def read_civet(fname: Union[str, Path]) -> Tuple[np.ndarray, np.ndarray]:
40
+ """
41
+ Reads a CIVET surface file (MNI OBJ format).
42
+
43
+ Parameters
44
+ ----------
45
+ fname : str or Path
46
+ Path to the CIVET surface file.
47
+
48
+ Returns
49
+ -------
50
+ vertices : numpy.ndarray
51
+ (N, 3) array of vertex coordinates.
52
+ faces : numpy.ndarray
53
+ (M, 3) array of face indices.
54
+ """
55
+ fname = str(fname)
56
+ with open(fname, "rb") as f:
57
+ first_char = f.read(1)
58
+
59
+ if first_char == b"P":
60
+ with open(fname, "r") as f:
61
+ content = f.read().split()
62
+
63
+ n_vertices = int(content[6])
64
+
65
+ idx = 7
66
+ # Vertices
67
+ vertices = np.array(content[idx : idx + n_vertices * 3], dtype=float).reshape(
68
+ -1, 3
69
+ )
70
+ idx += n_vertices * 3
71
+
72
+ # Normals
73
+ idx += n_vertices * 3
74
+
75
+ n_triangles = int(content[idx])
76
+ idx += 1
77
+
78
+ color_flag = int(content[idx])
79
+ idx += 1
80
+
81
+ if color_flag == 0:
82
+ idx += 4
83
+ else:
84
+ idx += n_vertices * 4
85
+
86
+ # Check remaining items to decide if we need to skip
87
+ remaining = len(content) - idx
88
+ if remaining == n_triangles * 3 + n_triangles:
89
+ idx += n_triangles
90
+
91
+ triangles = np.array(content[idx:], dtype=int).reshape(-1, 3)
92
+
93
+ return vertices, triangles
94
+ else:
95
+ raise NotImplementedError("Binary CIVET files are not supported yet.")
96
+
97
+
27
98
  def fetch_parcellation(
28
99
  template: str,
29
100
  atlas: str,
@@ -147,7 +218,7 @@ def fetch_template_surface(
147
218
  surfaces_fs = [read_geometry(file) for file in surface_files]
148
219
  surfaces = [build_polydata(surface[0], surface[1]) for surface in surfaces_fs]
149
220
  elif template == "fslr32k":
150
- surfaces = [read_surface(file) for file in surface_files]
221
+ surfaces = [read_surface(str(file)) for file in surface_files]
151
222
  else:
152
223
  surfaces_obj = [read_civet(file) for file in surface_files]
153
224
  surfaces = [build_polydata(surface[0], surface[1]) for surface in surfaces_obj]
@@ -384,7 +455,7 @@ def _fetch_template_surface_files(
384
455
 
385
456
  if template == "fslr32k":
386
457
  layer = layer if layer else "midthickness"
387
- bunch = nnt_datasets.fetch_conte69(data_dir=str(data_dir))
458
+ bunch = _fetch_conte69_fixed(data_dir=str(data_dir))
388
459
  elif template == "civet41k" or template == "civet164k":
389
460
  layer = layer if layer else "mid"
390
461
  if layer == "sphere":
@@ -515,3 +586,43 @@ def _fetch_civet_spheres(template: str, data_dir: Path) -> Tuple[str, str]:
515
586
 
516
587
  # Return two filenames to conform to other left/right hemisphere functions.
517
588
  return (str(filename), str(filename))
589
+
590
+
591
+ SURFACE = namedtuple('Surface', ('lh', 'rh'))
592
+
593
+
594
+ def _fetch_conte69_fixed(data_dir=None, url=None, resume=True, verbose=1):
595
+ """
596
+ Download files for Van Essen et al., 2012 Conte69 template.
597
+ Fixed version to handle encoding on Windows.
598
+ """
599
+ dataset_name = 'tpl-conte69'
600
+ keys = ['midthickness', 'inflated', 'vinflated']
601
+
602
+ data_dir = _get_data_dir(data_dir=data_dir)
603
+ info = _get_dataset_info(dataset_name)
604
+ if url is None:
605
+ url = info['url']
606
+
607
+ opts = {
608
+ 'uncompress': True,
609
+ 'md5sum': info['md5'],
610
+ 'move': '{}.tar.gz'.format(dataset_name)
611
+ }
612
+
613
+ filenames = [
614
+ 'tpl-conte69/tpl-conte69_space-MNI305_variant-fsLR32k_{}.{}.surf.gii'
615
+ .format(res, hemi) for res in keys for hemi in ['L', 'R']
616
+ ] + ['tpl-conte69/template_description.json']
617
+
618
+ data = _fetch_files(data_dir, files=[(f, url, opts) for f in filenames],
619
+ resume=resume, verbose=verbose)
620
+
621
+ # Fix: Specify encoding='utf-8'
622
+ with open(data[-1], 'r', encoding='utf-8') as src:
623
+ data[-1] = json.load(src)
624
+
625
+ # bundle hemispheres together
626
+ data = [SURFACE(*data[:-1][i:i + 2]) for i in range(0, 6, 2)] + [data[-1]]
627
+
628
+ return Bunch(**dict(zip(keys + ['info'], data)))
@@ -13,7 +13,7 @@ from brainspace.mesh.mesh_elements import get_cells, get_points
13
13
  from brainspace.mesh.mesh_io import read_surface
14
14
  from brainspace.vtk_interface.wrappers.data_object import BSPolyData
15
15
  from nilearn.datasets import load_mni152_brain_mask
16
- from scipy.interpolate.ndgriddata import LinearNDInterpolator, NearestNDInterpolator
16
+ from scipy.interpolate import LinearNDInterpolator, NearestNDInterpolator
17
17
  from scipy.spatial import cKDTree
18
18
 
19
19
  from brainstat._typing import ArrayLike
@@ -442,7 +442,9 @@ def _compute_resls(self, Y: np.ndarray) -> Tuple[np.ndarray, dict]:
442
442
  for j in range(Y.shape[2]):
443
443
  normr = np.sqrt(self.SSE[((j + 1) * (j + 2) // 2) - 1])
444
444
  for i in range(Y.shape[0]):
445
- u = Y[i, :, j] / normr
445
+ u = np.divide(
446
+ Y[i, :, j], normr, out=np.zeros_like(Y[i, :, j]), where=normr != 0
447
+ )
446
448
  resl[:, j] += np.diff(u[edges], axis=1).ravel() ** 2
447
449
 
448
450
  return resl, mesh_connections
@@ -951,7 +951,7 @@ def peak_clus(
951
951
  cluster_val = cluster_val + 1
952
952
 
953
953
  vox = np.argwhere(excurset) + 1
954
- ivox = np.argwhere(np.in1d(vox, lmvox)) + 1
954
+ ivox = np.argwhere(np.isin(vox.flatten(), lmvox)) + 1
955
955
  clmid = nf[ivox - 1]
956
956
  uclmid, iclmid, jclmid = np.unique(clmid, return_index=True, return_inverse=True)
957
957
  iclmid = iclmid + 1
@@ -959,7 +959,7 @@ def peak_clus(
959
959
  ucid = np.unique(nf)
960
960
  nclus = len(ucid)
961
961
  # implementing matlab's histc function ###
962
- bin_edges = np.r_[-np.Inf, 0.5 * (ucid[:-1] + ucid[1:]), np.Inf]
962
+ bin_edges = np.r_[-np.inf, 0.5 * (ucid[:-1] + ucid[1:]), np.inf]
963
963
  ucvol, ucvol_edges = np.histogram(nf, bin_edges)
964
964
 
965
965
  if reselspvert is None:
@@ -173,6 +173,6 @@ def _t_test(self) -> None:
173
173
  M[M_ef] = ef_duplicate[:, i]
174
174
  M[M_sse] = sse_matrix.flatten()
175
175
 
176
- self.t[i] = np.sqrt(-np.linalg.det(M) / det_sse / vf)
176
+ self.t[i] = float(np.sqrt(-np.linalg.det(M) / det_sse / vf))
177
177
 
178
178
  self.t = np.atleast_2d(self.t)
@@ -240,7 +240,7 @@ def remove_duplicate_columns(df: pd.DataFrame, tol: float = 1e-8) -> List[str]:
240
240
  df = df / df.abs().sum(0)
241
241
  df *= 1 / tol
242
242
  # keep = df.round().T.drop_duplicates(keep="last").T.columns # Slow!!
243
- idx = np.unique(df.round().values, axis=1, return_index=True)[-1]
243
+ idx = np.unique(df.round().to_numpy(dtype=float), axis=1, return_index=True)[-1]
244
244
  keep = df.columns[sorted(idx)]
245
245
  return keep
246
246
 
@@ -365,9 +365,14 @@ class FixedEffect(object):
365
365
  return self._add(t)
366
366
 
367
367
  def __radd__(
368
- self, t: Union[ArrayLike, "FixedEffect", "MixedEffect"]
368
+ self, t: Union[int, ArrayLike, "FixedEffect", "MixedEffect"]
369
369
  ) -> "FixedEffect":
370
- return self._add(t, side="right")
370
+ if isinstance(t, int) and t == 0:
371
+ # When t is 0, return self without adding an extra intercept
372
+ return self
373
+ else:
374
+ # For other cases, proceed with the addition
375
+ return self._add(t, side="right")
371
376
 
372
377
  def __sub__(self, t: Union[ArrayLike, "FixedEffect"]) -> "FixedEffect":
373
378
  if self.empty:
@@ -377,12 +382,16 @@ class FixedEffect(object):
377
382
  return self
378
383
  df /= df.abs().sum(0)
379
384
  df.index = self.m.index
380
-
381
385
  m = self.m / self.m.abs().sum(0)
382
- merged = m.T.merge(df.T, how="outer", indicator=True)
383
- mask = (merged._merge.values == "left_only")[: self.m.shape[1]]
386
+
387
+ m_normalized_T = m.T
388
+ df_normalized_T = df.T
389
+
390
+ merged = m_normalized_T.merge(df_normalized_T, how="outer", indicator=True)
391
+ merged_index = m_normalized_T.iloc[:, [1]].reset_index().merge(merged, how="left")
392
+ mask = merged_index.loc[merged_index['_merge'] == 'left_only','index']
384
393
  return FixedEffect(
385
- self.m[self.m.columns[mask]], add_intercept=False, _check_categorical=False
394
+ self.m[mask], add_intercept=False, _check_categorical=False
386
395
  )
387
396
 
388
397
  def _mul(
@@ -101,7 +101,7 @@ def ismember(
101
101
 
102
102
  else:
103
103
  # Get values of A that are in B.
104
- bool_vector = np.in1d(A, B)
104
+ bool_vector = np.isin(A, B)
105
105
  bool_array = np.reshape(bool_vector, A.shape)
106
106
 
107
107
  # Get location of elements in B. Transpose B and A to get MATLAB behavior (i.e. column first)
@@ -110,7 +110,9 @@ def ismember(
110
110
  locations = np.zeros(A.size) + np.nan
111
111
  Aflat = A.T.flat
112
112
  for i in range(0, idx.size):
113
- locations[idx[i]] = locB[np.argwhere(val == Aflat[idx[i]])]
113
+ matches = np.argwhere(val == Aflat[idx[i]])
114
+ if len(matches) > 0:
115
+ locations[idx[i]] = locB[matches[0, 0]]
114
116
  locations = np.reshape(locations, A.shape)
115
117
  return bool_array, locations
116
118
 
@@ -7,10 +7,12 @@ import numpy as np
7
7
  import pandas as pd
8
8
  import pytest
9
9
  import templateflow.api as tflow
10
+ from sklearn.linear_model import LinearRegression
10
11
 
11
12
  from brainstat.stats.SLM import SLM, _onetailed_to_twotailed
12
13
  from brainstat.stats.terms import FixedEffect, MixedEffect
13
14
  from brainstat.tests.testutil import datadir
15
+ from brainstat.tutorial.utils import fetch_mics_data
14
16
 
15
17
 
16
18
  def recursive_comparison(X1, X2):
@@ -123,6 +125,31 @@ def dummy_test(infile, expfile):
123
125
  assert all(flag == True for (flag) in testout)
124
126
 
125
127
 
128
+ def dummy_test2():
129
+
130
+ # BrainStat
131
+ thickness, demographics = fetch_mics_data()
132
+ term_age = FixedEffect(demographics.AGE_AT_SCAN)
133
+ model = term_age
134
+ slm_age = SLM(
135
+ model,
136
+ np.ones(thickness.shape[0]),
137
+ surf="fsaverage5",
138
+ )
139
+ slm_age.fit(thickness)
140
+
141
+ # Scikit Learn
142
+ model_age = LinearRegression()
143
+ model_age.fit(demographics.AGE_AT_SCAN.to_numpy().reshape(-1, 1), thickness)
144
+
145
+ # Compare
146
+ comp2 = np.allclose(
147
+ np.squeeze(model_age.coef_), slm_age.coef[1, :], rtol=1e-05, equal_nan=True
148
+ )
149
+
150
+ assert comp2 == True
151
+
152
+
126
153
  expected_number_of_tests = 22
127
154
  parametrize = pytest.mark.parametrize
128
155
 
@@ -134,6 +161,10 @@ def test_run_all(test_number):
134
161
  dummy_test(infile, expfile)
135
162
 
136
163
 
164
+ # Test against scikit learn
165
+ dummy_test2()
166
+
167
+
137
168
  @pytest.mark.skipif(
138
169
  sys.platform.startswith("win"), reason="Template flow has issues on windows."
139
170
  )
@@ -19,6 +19,7 @@ parametrize = pytest.mark.parametrize
19
19
  def test_load_surfaces(template):
20
20
  """Test loading surface templates. For now this only tests whether it runs
21
21
  without error and whether the output type is correct."""
22
+
22
23
  surface = fetch_template_surface(template, join=True)
23
24
  assert isinstance(surface, BSPolyData)
24
25
 
@@ -0,0 +1,33 @@
1
+ from brainstat.context.genetics import surface_genetic_expression
2
+ from brainstat.datasets import fetch_parcellation, fetch_template_surface, fetch_parcellation
3
+ import numpy as np
4
+ from nilearn import datasets
5
+ import pytest
6
+ from urllib.error import HTTPError
7
+
8
+ # Get Schaefer-100 genetic expression.
9
+ # def test_surface_genetic_expression():
10
+ # schaefer_100_fs5 = fetch_parcellation("fsaverage5", "schaefer", 100)
11
+ # surfaces = fetch_template_surface("fsaverage5", join=False)
12
+ # expression = surface_genetic_expression(schaefer_100_fs5, surfaces, space="fsaverage")
13
+ # assert expression is not None
14
+
15
+
16
+ def test_surface_genetic_expression2():
17
+ destrieux = datasets.fetch_atlas_surf_destrieux()
18
+ labels = np.hstack((destrieux['map_left'], destrieux['map_right']))
19
+ fsaverage = datasets.fetch_surf_fsaverage()
20
+ surfaces = (fsaverage['pial_left'], fsaverage['pial_right'])
21
+
22
+ try:
23
+ expression = surface_genetic_expression(labels, surfaces, space='fsaverage')
24
+ assert expression is not None
25
+ except HTTPError as e:
26
+ if e.code == 503:
27
+ pytest.skip(f"Allen Brain Institute server unavailable (503): {e}")
28
+ else:
29
+ raise
30
+
31
+ if __name__ == "__main__":
32
+ test_surface_genetic_expression2()
33
+ # test_surface_genetic_expression()
@@ -0,0 +1,32 @@
1
+ """Unit tests for the histology module."""
2
+ import pytest
3
+ import requests
4
+ import numpy as np
5
+
6
+ from brainstat._utils import read_data_fetcher_json
7
+ from brainstat.context.histology import read_histology_profile
8
+ parametrize = pytest.mark.parametrize
9
+ json = read_data_fetcher_json()
10
+
11
+
12
+ @parametrize("template", list(json["bigbrain_profiles"].keys()))
13
+ def test_urls(template):
14
+ """Tests whether the histology files can be downloaded.
15
+
16
+ Parameters
17
+ ----------
18
+ template : list
19
+ Template names.
20
+ """
21
+ try:
22
+ r = requests.get(json["bigbrain_profiles"][template]["url"], timeout=10, stream=True)
23
+ assert r.status_code == 200
24
+ except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
25
+ pytest.skip(f"Network connection issue when testing URL for {template}")
26
+
27
+
28
+ def test_histology_profiles_is_ndarray():
29
+ histology_profiles = read_histology_profile(template="fslr32k")
30
+
31
+ # Assert that histology_profiles is an ndarray
32
+ assert isinstance(histology_profiles, np.ndarray), "histology_profiles should be a NumPy ndarray"
@@ -81,4 +81,4 @@ def test_nifti_input():
81
81
  edg = mesh_edges(nifti)
82
82
 
83
83
  assert edg.shape[1] == 2
84
- assert np.amax(edg) <= nifti.get_data().sum() - 1
84
+ assert np.amax(edg) <= nifti.get_fdata().sum() - 1
@@ -2,7 +2,8 @@
2
2
 
3
3
  import numpy as np
4
4
  import pandas as pd
5
-
5
+ from functools import reduce
6
+ from brainstat.tutorial.utils import fetch_mics_data
6
7
  from brainstat.stats.terms import FixedEffect, MixedEffect
7
8
 
8
9
 
@@ -29,8 +30,10 @@ def test_fixed_init():
29
30
 
30
31
  def test_fixed_overload():
31
32
  """Tests the overloads of the FixedEffect class."""
33
+ np.random.seed(2)
32
34
  random_data = np.random.random_sample((10, 3))
33
35
  fix01 = FixedEffect(random_data[:, :2], ["x0", "x1"], add_intercept=False)
36
+ fix012 = FixedEffect(random_data, ["x0", "x1", "x2"], add_intercept=False)
34
37
  fix12 = FixedEffect(random_data[:, 1:], ["x2", "x3"], add_intercept=False)
35
38
  fix2 = FixedEffect(random_data[:, 2], ["x2"], add_intercept=False)
36
39
  fixi0 = FixedEffect(random_data[:, 0], ["x0"], add_intercept=True)
@@ -47,7 +50,20 @@ def test_fixed_overload():
47
50
  assert np.array_equal(fix_add_intercept.m, expected)
48
51
 
49
52
  fix_sub = fix01 - fix12
50
- assert np.array_equal(fix_sub.m, random_data[:, 0][:, None])
53
+ np.testing.assert_allclose(
54
+ fix_sub.m,
55
+ random_data[:, 0][:, None],
56
+ atol=1e-8,
57
+ rtol=1e-5,
58
+ )
59
+
60
+ fix_sub2 = fix012 - fix2
61
+ np.testing.assert_allclose(
62
+ fix_sub2.m,
63
+ random_data[:, :2],
64
+ atol=1e-8,
65
+ rtol=1e-5,
66
+ )
51
67
 
52
68
  fix_mul = fix01 * fix2
53
69
  assert np.array_equal(fix_mul.m, random_data[:, :2] * random_data[:, 2][:, None])
@@ -129,3 +145,11 @@ def test_identity_detection():
129
145
  def as_variance(M):
130
146
  var = [np.reshape(x[:, None] @ x[None, :], (-1, 1)) for x in M.T]
131
147
  return np.squeeze(var, axis=2).T
148
+
149
+
150
+ def test_sum():
151
+ _, demographics = fetch_mics_data()
152
+ model = []
153
+ term_age = FixedEffect(demographics.AGE_AT_SCAN)
154
+ model.append(term_age)
155
+ assert reduce(lambda x, y: x + y, model) == sum(model)
@@ -56,7 +56,7 @@ def fetch_mics_data(
56
56
 
57
57
  def fetch_abide_data(
58
58
  data_dir: Optional[Union[str, Path]] = None,
59
- sites: Sequence[str] = None,
59
+ sites: Optional[Sequence[str]] = None,
60
60
  keep_control: bool = True,
61
61
  keep_patient: bool = True,
62
62
  overwrite: bool = False,
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: brainstat
3
- Version: 0.4.2
3
+ Version: 0.5.2
4
4
  Summary: A toolbox for statistical analysis of neuroimaging data
5
5
  Home-page: https://github.com/MICA-LAB/BrainStat
6
6
  Author: MNI-MICA Lab and MPI-CNG Lab
@@ -13,13 +13,44 @@ Classifier: Intended Audience :: Science/Research
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: Topic :: Software Development :: Build Tools
15
15
  Classifier: Topic :: Scientific/Engineering
16
- Classifier: Programming Language :: Python :: 3.7
17
- Classifier: Programming Language :: Python :: 3.8
18
- Classifier: Programming Language :: Python :: 3.9
19
- Requires-Python: >=3.7.*
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Requires-Python: >=3.10
20
18
  Description-Content-Type: text/x-rst
21
- Provides-Extra: dev
22
19
  License-File: LICENSE
20
+ Requires-Dist: abagen>=0.1
21
+ Requires-Dist: brainspace>=0.1.2
22
+ Requires-Dist: h5py
23
+ Requires-Dist: matplotlib>=3.4
24
+ Requires-Dist: netneurotools<0.3.0
25
+ Requires-Dist: neurosynth
26
+ Requires-Dist: nibabel
27
+ Requires-Dist: nilearn>=0.7.0
28
+ Requires-Dist: nimare
29
+ Requires-Dist: numpy>=1.21
30
+ Requires-Dist: pandas>=1.3
31
+ Requires-Dist: scikit_learn>=1.0
32
+ Requires-Dist: scipy>=1.7
33
+ Requires-Dist: templateflow
34
+ Requires-Dist: trimesh
35
+ Provides-Extra: dev
36
+ Requires-Dist: gitpython; extra == "dev"
37
+ Requires-Dist: hcp-utils; extra == "dev"
38
+ Requires-Dist: mypy; extra == "dev"
39
+ Requires-Dist: plotly; extra == "dev"
40
+ Requires-Dist: pytest; extra == "dev"
41
+ Dynamic: author
42
+ Dynamic: author-email
43
+ Dynamic: classifier
44
+ Dynamic: description
45
+ Dynamic: description-content-type
46
+ Dynamic: home-page
47
+ Dynamic: license
48
+ Dynamic: license-file
49
+ Dynamic: project-url
50
+ Dynamic: provides-extra
51
+ Dynamic: requires-dist
52
+ Dynamic: requires-python
53
+ Dynamic: summary
23
54
 
24
55
  BrainStat : A toolbox for statistical analysis of neuroimaging data
25
56
  ===================================================================
@@ -53,6 +53,7 @@ brainstat/tests/test_SLM.py
53
53
  brainstat/tests/test_datasets.py
54
54
  brainstat/tests/test_f_test.py
55
55
  brainstat/tests/test_fdr.py
56
+ brainstat/tests/test_genetics.py
56
57
  brainstat/tests/test_histology.py
57
58
  brainstat/tests/test_linear_model.py
58
59
  brainstat/tests/test_mesh_edges.py
@@ -1,15 +1,16 @@
1
1
  abagen>=0.1
2
2
  brainspace>=0.1.2
3
3
  h5py
4
- netneurotools
4
+ matplotlib>=3.4
5
+ netneurotools<0.3.0
5
6
  neurosynth
6
7
  nibabel
7
8
  nilearn>=0.7.0
8
9
  nimare
9
- numpy>=1.16.5
10
- pandas
11
- scikit_learn
12
- scipy>=1.3.3
10
+ numpy>=1.21
11
+ pandas>=1.3
12
+ scikit_learn>=1.0
13
+ scipy>=1.7
13
14
  templateflow
14
15
  trimesh
15
16
 
@@ -1,5 +1,5 @@
1
1
  [metadata]
2
- description-file = README.rst
2
+ description_file = README.rst
3
3
  license_files = LICENSE
4
4
 
5
5
  [mypy]
@@ -31,24 +31,23 @@ setuptools.setup(
31
31
  "Intended Audience :: Developers",
32
32
  "Topic :: Software Development :: Build Tools",
33
33
  "Topic :: Scientific/Engineering",
34
- "Programming Language :: Python :: 3.7",
35
- "Programming Language :: Python :: 3.8",
36
- "Programming Language :: Python :: 3.9",
34
+ "Programming Language :: Python :: 3.10",
37
35
  ],
38
- python_requires=">=3.7.*",
36
+ python_requires=">=3.10",
39
37
  install_requires=[
40
38
  "abagen>=0.1",
41
39
  "brainspace>=0.1.2",
42
40
  "h5py",
43
- "netneurotools",
41
+ "matplotlib>=3.4",
42
+ "netneurotools<0.3.0",
44
43
  "neurosynth",
45
44
  "nibabel",
46
45
  "nilearn>=0.7.0",
47
46
  "nimare",
48
- "numpy>=1.16.5",
49
- "pandas",
50
- "scikit_learn",
51
- "scipy>=1.3.3",
47
+ "numpy>=1.21",
48
+ "pandas>=1.3",
49
+ "scikit_learn>=1.0",
50
+ "scipy>=1.7",
52
51
  "templateflow",
53
52
  "trimesh",
54
53
  ],
@@ -1,21 +0,0 @@
1
- """Unit tests for the histology module."""
2
- import pytest
3
- import requests
4
-
5
- from brainstat._utils import read_data_fetcher_json
6
-
7
- parametrize = pytest.mark.parametrize
8
- json = read_data_fetcher_json()
9
-
10
-
11
- @parametrize("template", list(json["bigbrain_profiles"].keys()))
12
- def test_urls(template):
13
- """Tests whether the histology files can be downloaded.
14
-
15
- Parameters
16
- ----------
17
- template : list
18
- Template names.
19
- """
20
- r = requests.head(json["bigbrain_profiles"][template]["url"])
21
- assert r.status_code == 200
File without changes
File without changes
File without changes