brainrender 2.2.1__tar.gz → 2.2.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 (45) hide show
  1. {brainrender-2.2.1 → brainrender-2.2.2}/.github/workflows/test_and_deploy.yml +10 -12
  2. {brainrender-2.2.1 → brainrender-2.2.2}/PKG-INFO +2 -3
  3. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/atlas_specific/allen_brain_atlas/gene_expression/api.py +115 -32
  4. brainrender-2.2.2/brainrender/atlas_specific/allen_brain_atlas/gene_expression/ge_utils.py +158 -0
  5. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/atlas_specific/allen_brain_atlas/streamlines.py +97 -36
  6. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/scene.py +5 -3
  7. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender.egg-info/PKG-INFO +2 -3
  8. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender.egg-info/requires.txt +1 -2
  9. brainrender-2.2.2/brainrender.egg-info/scm_version.json +8 -0
  10. {brainrender-2.2.1 → brainrender-2.2.2}/pyproject.toml +2 -31
  11. brainrender-2.2.1/brainrender/atlas_specific/allen_brain_atlas/gene_expression/ge_utils.py +0 -96
  12. brainrender-2.2.1/brainrender.egg-info/scm_version.json +0 -8
  13. {brainrender-2.2.1 → brainrender-2.2.2}/.gitignore +0 -0
  14. {brainrender-2.2.1 → brainrender-2.2.2}/CITATION.cff +0 -0
  15. {brainrender-2.2.1 → brainrender-2.2.2}/LICENSE +0 -0
  16. {brainrender-2.2.1 → brainrender-2.2.2}/MANIFEST.in +0 -0
  17. {brainrender-2.2.1 → brainrender-2.2.2}/README.md +0 -0
  18. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/__init__.py +0 -0
  19. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/_colors.py +0 -0
  20. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/_io.py +0 -0
  21. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/_jupyter.py +0 -0
  22. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/_utils.py +0 -0
  23. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/_video.py +0 -0
  24. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/actor.py +0 -0
  25. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/actors/__init__.py +0 -0
  26. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/actors/cylinder.py +0 -0
  27. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/actors/line.py +0 -0
  28. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/actors/neurons.py +0 -0
  29. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/actors/points.py +0 -0
  30. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/actors/ruler.py +0 -0
  31. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/actors/streamlines.py +0 -0
  32. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/actors/volume.py +0 -0
  33. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/atlas.py +0 -0
  34. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/atlas_specific/__init__.py +0 -0
  35. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/atlas_specific/allen_brain_atlas/gene_expression/__init__.py +0 -0
  36. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/camera.py +0 -0
  37. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/cameras.py +0 -0
  38. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/render.py +0 -0
  39. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/settings.py +0 -0
  40. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender/video.py +0 -0
  41. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender.egg-info/SOURCES.txt +0 -0
  42. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender.egg-info/dependency_links.txt +0 -0
  43. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender.egg-info/scm_file_list.json +0 -0
  44. {brainrender-2.2.1 → brainrender-2.2.2}/brainrender.egg-info/top_level.txt +0 -0
  45. {brainrender-2.2.1 → brainrender-2.2.2}/setup.cfg +0 -0
@@ -23,13 +23,12 @@ jobs:
23
23
  linting:
24
24
  runs-on: ubuntu-latest
25
25
  steps:
26
- - uses: neuroinformatics-unit/actions/lint@v2
27
-
26
+ - uses: neuroinformatics-unit/actions/lint@9b53ffe93baaafe5533e1f295a3c069bbc17ba36 # v3.0.1
28
27
  manifest:
29
28
  name: Check Manifest
30
29
  runs-on: ubuntu-latest
31
30
  steps:
32
- - uses: neuroinformatics-unit/actions/check_manifest@v2
31
+ - uses: neuroinformatics-unit/actions/check_manifest@9b53ffe93baaafe5533e1f295a3c069bbc17ba36 # v3.0.1
33
32
 
34
33
  test:
35
34
  needs: [linting, manifest]
@@ -49,7 +48,7 @@ jobs:
49
48
 
50
49
  steps:
51
50
  - name: Cache brainglobe directory
52
- uses: actions/cache@v3
51
+ uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
53
52
  with:
54
53
  path: | # ensure we don't cache any interrupted atlas download and extraction, if e.g. we cancel the workflow manually
55
54
  ~/.brainglobe
@@ -61,9 +60,6 @@ jobs:
61
60
  run: brew install hdf5
62
61
 
63
62
  # Helps set up VTK with a headless display
64
- - uses: pyvista/setup-headless-display-action@v3
65
-
66
- # Sets up ffmpeg to we can run video tests on CI
67
63
  - uses: FedericoCarboni/setup-ffmpeg@v2
68
64
  if: matrix.os != 'macos-latest'
69
65
  id: setup-ffmpeg
@@ -73,15 +69,17 @@ jobs:
73
69
  run: brew install ffmpeg
74
70
 
75
71
  # Run tests
76
- - uses: neuroinformatics-unit/actions/test@v2
72
+ - name: Run tests
73
+ uses: neuroinformatics-unit/actions/test-uv@9b53ffe93baaafe5533e1f295a3c069bbc17ba36 #v3.0.1
77
74
  with:
78
- python-version: ${{ matrix.python-version }}
75
+ python-version: "${{ matrix.python-version }}"
79
76
  secret-codecov-token: ${{ secrets.CODECOV_TOKEN }}
80
-
77
+ use-headless: 'true'
78
+ install-mesa3d-offscreen: ${{ matrix.os != 'windows-latest' }}
81
79
 
82
80
  - name: Notify slack on scheduled failure
83
81
  if: failure() && github.event_name == 'schedule'
84
- uses: ravsamhq/notify-slack-action@v2
82
+ uses: ravsamhq/notify-slack-action@be814b201e233b2dc673608aa46e5447c8ab13f2 # v2.5.0
85
83
  with:
86
84
  status: ${{ job.status }} # required
87
85
  notify_when: 'failure'
@@ -95,7 +93,7 @@ jobs:
95
93
  if: github.event_name == 'push' && github.ref_type == 'tag'
96
94
  runs-on: ubuntu-latest
97
95
  steps:
98
- - uses: neuroinformatics-unit/actions/build_sdist_wheels@v2
96
+ - uses: neuroinformatics-unit/actions/build_sdist_wheels@9b53ffe93baaafe5533e1f295a3c069bbc17ba36 # v3.0.1
99
97
 
100
98
 
101
99
  upload_all:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainrender
3
- Version: 2.2.1
3
+ Version: 2.2.2
4
4
  Summary: Visualisation and exploration of brain atlases and other anatomical data
5
5
  Author-email: "Federico Claudi, Adam Tyson, Luigi Petrucco" <hello@brainglobe.info>
6
6
  License: BSD-3-Clause
@@ -41,12 +41,11 @@ Requires-Dist: pyyaml>=5.3
41
41
  Requires-Dist: requests
42
42
  Requires-Dist: tables
43
43
  Requires-Dist: vedo>=2025.5.3
44
- Requires-Dist: vtk
44
+ Requires-Dist: vtk<9.7.0
45
45
  Provides-Extra: dev
46
46
  Requires-Dist: pytest; extra == "dev"
47
47
  Requires-Dist: pytest-cov; extra == "dev"
48
48
  Requires-Dist: coverage; extra == "dev"
49
- Requires-Dist: tox; extra == "dev"
50
49
  Requires-Dist: black; extra == "dev"
51
50
  Requires-Dist: mypy; extra == "dev"
52
51
  Requires-Dist: pre-commit; extra == "dev"
@@ -1,7 +1,10 @@
1
+ """API client for downloading Allen Brain Atlas gene expression data."""
2
+
1
3
  import os
2
4
  import sys
3
5
  from time import sleep
4
6
 
7
+ import numpy.typing as npt
5
8
  import pandas as pd
6
9
  import requests
7
10
  from loguru import logger
@@ -17,6 +20,8 @@ from brainrender.atlas_specific.allen_brain_atlas.gene_expression.ge_utils impor
17
20
 
18
21
 
19
22
  class GeneExpressionAPI:
23
+ """Client for querying and downloading Allen Brain Atlas gene expression data."""
24
+
20
25
  voxel_size = 200 # um
21
26
  grid_size = [58, 41, 67] # number of voxels along each direction
22
27
 
@@ -38,22 +43,41 @@ class GeneExpressionAPI:
38
43
  download_url = "http://api.brain-map.org/grid_data/download/EXP_ID?include=energy,intensity,density"
39
44
 
40
45
  gene_expression_cache = base_dir / "GeneExpressionCache"
41
- gene_name = None
46
+ gene_name: str | None = None
42
47
 
43
- def __init__(self):
48
+ def __init__(self) -> None:
44
49
  # Get metadata about all available genes
45
- self.genes = None # when necessary gene data can be downloaded with self.get_all_genes
50
+ self.genes: pd.DataFrame | None = (
51
+ None # when necessary gene data can be downloaded with self.get_all_genes
52
+ )
46
53
  self.gene_expression_cache.mkdir(exist_ok=True)
47
54
 
48
55
  @fail_on_no_connection
49
- def get_all_genes(self):
56
+ def get_all_genes(self) -> pd.DataFrame:
50
57
  """
51
- Download metadata about all the genes available in the Allen gene expression dataset
58
+ Download metadata about all genes in the Allen gene expression dataset.
59
+
60
+ Returns
61
+ -------
62
+ pd.DataFrame
52
63
  """
53
64
  res = request(self.all_genes_url)
54
65
  return pd.DataFrame(res.json()["msg"])
55
66
 
56
- def get_gene_id_by_name(self, gene_name):
67
+ def get_gene_id_by_name(self, gene_name: str) -> int | None:
68
+ """
69
+ Return the Allen gene ID for a given gene symbol.
70
+
71
+ Parameters
72
+ ----------
73
+ gene_name
74
+ Gene symbol.
75
+
76
+ Returns
77
+ -------
78
+ int or None
79
+ Gene ID, or None if the gene is not found.
80
+ """
57
81
  self.gene_name = self.gene_name or gene_name
58
82
  if self.genes is None:
59
83
  self.genes = self.get_all_genes()
@@ -71,7 +95,19 @@ class GeneExpressionAPI:
71
95
  ]
72
96
  )
73
97
 
74
- def get_gene_symbol_by_id(self, gene_id):
98
+ def get_gene_symbol_by_id(self, gene_id: int | str) -> str:
99
+ """
100
+ Return the gene symbol for a given Allen gene ID.
101
+
102
+ Parameters
103
+ ----------
104
+ gene_id
105
+ Allen gene ID.
106
+
107
+ Returns
108
+ -------
109
+ str
110
+ """
75
111
  if self.genes is None:
76
112
  self.genes = self.get_all_genes()
77
113
 
@@ -80,12 +116,19 @@ class GeneExpressionAPI:
80
116
  ].gene_symbol.values[0]
81
117
 
82
118
  @fail_on_no_connection
83
- def get_gene_experiments(self, gene):
119
+ def get_gene_experiments(self, gene: str) -> list[int] | None:
84
120
  """
85
- Given a gene_symbol it returns the list of ISH
86
- experiments for this gene
121
+ Return ISH experiment IDs for a given gene symbol.
122
+
123
+ Parameters
124
+ ----------
125
+ gene
126
+ Gene symbol.
87
127
 
88
- :param gene_symbol: str
128
+ Returns
129
+ -------
130
+ list of int or None
131
+ List of experiment IDs, or None if no experiments are found.
89
132
  """
90
133
  url = self.gene_experiments_url.replace("-GENE_SYMBOL-", gene)
91
134
  max_retries = 8
@@ -108,13 +151,15 @@ class GeneExpressionAPI:
108
151
  return [d["id"] for d in data]
109
152
 
110
153
  @fail_on_no_connection
111
- def download_gene_data(self, gene):
154
+ def download_gene_data(self, gene: str) -> None:
112
155
  """
113
- Downloads a gene's data from the Allen Institute
114
- Gene Expression dataset and saves to cache.
115
- See: http://help.brain-map.org/display/api/Downloading+3-D+Expression+Grid+Data
156
+ Download a gene's expression data from the Allen Institute and save to cache.
157
+ See http://help.brain-map.org/display/api/Downloading+3-D+Expression+Grid+Data.
116
158
 
117
- :param gene: int, the gene_id for the gene being downloaded.
159
+ Parameters
160
+ ----------
161
+ gene
162
+ Gene symbol to download data for.
118
163
  """
119
164
  # Get the gene's experiment id
120
165
  exp_ids = self.get_gene_experiments(gene)
@@ -130,9 +175,37 @@ class GeneExpressionAPI:
130
175
  url, os.path.join(self.gene_expression_cache, f"{gene}-{eid}")
131
176
  )
132
177
 
133
- def get_gene_data(self, gene, exp_id, use_cache=True, metric="energy"):
178
+ def get_gene_data(
179
+ self,
180
+ gene: str,
181
+ exp_id: int,
182
+ use_cache: bool = True,
183
+ metric: str = "energy",
184
+ ) -> npt.NDArray | None:
134
185
  """
135
- Given a list of gene ids
186
+ Load gene expression data for a given gene and experiment.
187
+
188
+ Parameters
189
+ ----------
190
+ gene
191
+ Gene symbol.
192
+ exp_id
193
+ Experiment ID.
194
+ use_cache
195
+ If True, load from cache if available. Default True.
196
+ metric
197
+ Expression metric to load. Default ``"energy"``.
198
+
199
+ Returns
200
+ -------
201
+ numpy.ndarray or None
202
+ Gene expression data, or None if no data is available for the
203
+ requested metric.
204
+
205
+ Raises
206
+ ------
207
+ ValueError
208
+ If data could not be cached after downloading.
136
209
  """
137
210
  logger.debug(f"Getting gene data for gene: {gene} experiment {exp_id}")
138
211
  self.gene_name = self.gene_name or gene
@@ -161,21 +234,31 @@ class GeneExpressionAPI:
161
234
 
162
235
  def griddata_to_volume(
163
236
  self,
164
- griddata,
165
- min_quantile=None,
166
- min_value=None,
167
- cmap="bwr",
168
- ):
237
+ griddata: npt.NDArray,
238
+ min_quantile: float | None = None,
239
+ min_value: float | None = None,
240
+ cmap: str = "bwr",
241
+ ) -> Volume:
169
242
  """
170
- Takes a 3d numpy array with volumetric gene expression
171
- and returns a vedo.Volume.isosurface actor.
172
- The isosurface needs a lower bound threshold, this can be
173
- either a user defined hard value (min_value) or the value
174
- corresponding to some percentile of the gene expression data.
175
-
176
- :param griddata: np.ndarray, 3d array with gene expression data
177
- :param min_quantile: float, percentile for threshold
178
- :param min_value: float, value for threshold
243
+ Convert a 3D gene expression array to a Volume actor.
244
+
245
+ The isosurface threshold can be set as a hard value or as a
246
+ percentile of the expression data.
247
+
248
+ Parameters
249
+ ----------
250
+ griddata
251
+ 3D array with gene expression data.
252
+ min_quantile
253
+ Percentile threshold for isosurface extraction.
254
+ min_value
255
+ Hard value threshold for isosurface extraction.
256
+ cmap
257
+ Colormap name. Default ``"bwr"``.
258
+
259
+ Returns
260
+ -------
261
+ Volume
179
262
  """
180
263
  return Volume(
181
264
  griddata,
@@ -0,0 +1,158 @@
1
+ """Utilities for caching and loading Allen Brain Atlas gene expression data."""
2
+
3
+ import io
4
+ import os
5
+ import sys
6
+ import zipfile
7
+ from pathlib import Path
8
+ from typing import Literal
9
+
10
+ import numpy as np
11
+ import numpy.typing as npt
12
+
13
+ from brainrender._io import check_file_exists, request
14
+ from brainrender._utils import get_subdirs, listdir
15
+
16
+ # ----------------------------------- Cache ---------------------------------- #
17
+
18
+
19
+ def check_gene_cached(
20
+ cache_folder: str | Path,
21
+ gene_id: str,
22
+ exp_id: str | int,
23
+ ) -> str | Literal[False]:
24
+ """
25
+ Check whether a gene experiment is already cached.
26
+
27
+ A gene is cached in a subfolder of ``cache_folder`` named
28
+ ``{gene_id}-{exp_id}``.
29
+
30
+ Parameters
31
+ ----------
32
+ cache_folder
33
+ Path to the general cache folder.
34
+ gene_id
35
+ Gene name.
36
+ exp_id
37
+ Experiment ID.
38
+
39
+ Returns
40
+ -------
41
+ str or False
42
+ Path to the cached folder if found, False if not cached.
43
+
44
+ Raises
45
+ ------
46
+ ValueError
47
+ If more than one matching folder is found.
48
+ """
49
+ cache = [
50
+ sub
51
+ for sub in get_subdirs(cache_folder)
52
+ if f"{gene_id}-{exp_id}" == os.path.basename(sub)
53
+ ]
54
+ if not cache:
55
+ return False
56
+ elif len(cache) > 1:
57
+ raise ValueError("Found too many folders")
58
+ else:
59
+ return cache[0]
60
+
61
+
62
+ def download_and_cache(url: str, cachedir: str | Path) -> None:
63
+ """
64
+ Download and unzip a gene's ISH experiment data to a cache directory.
65
+
66
+ Parameters
67
+ ----------
68
+ url
69
+ URL to download the data from.
70
+ cachedir
71
+ Path to the folder where data will be saved.
72
+ """
73
+ # Get data
74
+ req = request(url)
75
+
76
+ # Create cache dir
77
+ if not os.path.isdir(cachedir):
78
+ os.mkdir(cachedir)
79
+
80
+ # Unzip to cache dir
81
+ z = zipfile.ZipFile(io.BytesIO(req.content))
82
+ z.extractall(cachedir)
83
+
84
+
85
+ def load_cached_gene(
86
+ cache: str | Path,
87
+ metric: str,
88
+ grid_size: tuple[int, int, int],
89
+ ) -> npt.NDArray | None:
90
+ """
91
+ Load a gene's data from cache.
92
+
93
+ Parameters
94
+ ----------
95
+ cache
96
+ Path to the gene's cache folder.
97
+ metric
98
+ Metric name used to filter files (e.g. ``"energy"``).
99
+ grid_size
100
+ Shape to use when reshaping the raw data array.
101
+
102
+ Returns
103
+ -------
104
+ numpy.ndarray or None
105
+ Array of gene expression values, or None if no file is found.
106
+
107
+ Raises
108
+ ------
109
+ NotImplementedError
110
+ If more than one matching file is found.
111
+ """
112
+ files = [
113
+ f for f in listdir(cache) if metric in f and not f.endswith(".mhd")
114
+ ]
115
+ if not files:
116
+ return None
117
+ if len(files) > 1:
118
+ raise NotImplementedError("Deal with more than one file found")
119
+ else:
120
+ return read_raw(files[0], grid_size)
121
+
122
+
123
+ # --------------------------------- Open .raw -------------------------------- #
124
+ @check_file_exists
125
+ def read_raw(
126
+ filepath: str | Path,
127
+ grid_size: tuple[int, int, int],
128
+ ) -> npt.NDArray:
129
+ """
130
+ Read a ``.raw`` gene expression file from the Allen Brain Atlas.
131
+
132
+ See http://help.brain-map.org/display/mousebrain/API#API-Expression3DGridsz
133
+ for the file format reference.
134
+
135
+ Parameters
136
+ ----------
137
+ filepath
138
+ Path to the ``.raw`` file.
139
+ grid_size
140
+ Shape to use when reshaping the data array.
141
+
142
+ Returns
143
+ -------
144
+ numpy.ndarray
145
+ """
146
+ filepath = str(filepath)
147
+
148
+ # Read bytes
149
+ with open(filepath, "rb") as test:
150
+ content = test.read()
151
+
152
+ # Create np array and return
153
+ data = np.frombuffer(content, dtype="float32").reshape(grid_size)
154
+
155
+ if sys.platform == "darwin":
156
+ data = data.T # TODO figure out why this is necessary on Mac OS?
157
+
158
+ return data
@@ -1,3 +1,7 @@
1
+ """Utilities for downloading and processing streamlines aligned to the Allen Mouse Brain Atlas."""
2
+
3
+ from typing import Any
4
+
1
5
  import pandas as pd
2
6
  import requests
3
7
  from loguru import logger
@@ -36,18 +40,20 @@ ALLEN_MESOSCALE_URL = (
36
40
  ALLEN_API_URL = "https://api.brain-map.org/api/v2/data/query.json"
37
41
  VOXEL_SIZE_NM = 1000 # skeleton vertices are in nanometers
38
42
 
39
- _ml_extent_um_cache = None
43
+ _ml_extent_um_cache: float | None = None
40
44
 
41
45
 
42
- def _get_ml_extent_um():
46
+ def _get_ml_extent_um() -> float:
43
47
  """
44
- Derives the full medial-lateral extent of the Allen CCF atlas in microns
45
- dynamically from the brainglobe atlas API. Used to flip the Z (ML) axis
46
- when converting from Allen CCF space to brainrender's coordinate system,
47
- where left and right hemispheres are mirrored relative to the Allen CCF.
48
-
49
- Result is cached after the first call to avoid reinstantiating the atlas
50
- on every experiment download.
48
+ Derive the full medial-lateral extent of the Allen CCF atlas in microns
49
+ dynamically from the brainglobe atlas API. The computed extent is cached
50
+ for subsequent calls and used to flip the Z (ML) axis when converting
51
+ from Allen CCF space to brainrender's coordinate system.
52
+
53
+ Returns
54
+ -------
55
+ float
56
+ Full medial-lateral extent of the atlas in microns.
51
57
  """
52
58
  global _ml_extent_um_cache
53
59
  if _ml_extent_um_cache is None:
@@ -56,10 +62,19 @@ def _get_ml_extent_um():
56
62
  return _ml_extent_um_cache
57
63
 
58
64
 
59
- def experiments_source_search(SOI):
65
+ def experiments_source_search(SOI: str) -> pd.DataFrame | None:
60
66
  """
61
- Returns data about experiments whose injection was in the SOI, structure of interest
62
- :param SOI: str, structure of interest. Acronym of structure to use as seed for the search
67
+ Return data about experiments whose injection was in the structure of interest.
68
+
69
+ Parameters
70
+ ----------
71
+ SOI
72
+ Acronym of the structure of interest to use as the search seed.
73
+
74
+ Returns
75
+ -------
76
+ pd.DataFrame or None
77
+ DataFrame of matching experiments, or None if AllenSDK is not installed.
63
78
  """
64
79
  transgenic_id = 0 # id = 0 means use only wild type
65
80
  primary_structure_only = True
@@ -81,15 +96,26 @@ def experiments_source_search(SOI):
81
96
  )
82
97
 
83
98
 
84
- def _get_injection_site_um(eid, ml_extent_um):
99
+ def _get_injection_site_um(
100
+ eid: int,
101
+ ml_extent_um: float,
102
+ ) -> dict[str, float] | None:
85
103
  """
86
- Fetches the injection site coordinates for an experiment from the Allen
87
- Brain Atlas API. Coordinates are in Allen CCF um space with the Z (ML)
88
- axis flipped to match brainrender's hemisphere convention.
89
-
90
- :param eid: int, experiment ID
91
- :param ml_extent_um: float, full ML extent of the atlas in um for LR flip
92
- :return: dict with x, y, z keys or None if not found
104
+ Fetch injection site coordinates for an experiment from the Allen Brain Atlas API.
105
+ Coordinates are returned in Allen CCF µm space with the Z (ML) axis
106
+ flipped to match brainrender's hemisphere convention.
107
+
108
+ Parameters
109
+ ----------
110
+ eid
111
+ Experiment ID.
112
+ ml_extent_um
113
+ Full ML extent of the atlas in µm, used for the left-right flip.
114
+
115
+ Returns
116
+ -------
117
+ dict or None
118
+ Dict with ``x``, ``y``, ``z`` keys in µm, or None if not found.
93
119
  """
94
120
  try:
95
121
  url = (
@@ -114,10 +140,13 @@ def _get_injection_site_um(eid, ml_extent_um):
114
140
  return None
115
141
 
116
142
 
117
- def _skeleton_to_dataframe(skeleton, eid, ml_extent_um):
143
+ def _skeleton_to_dataframe(
144
+ skeleton: Any,
145
+ eid: int,
146
+ ml_extent_um: float,
147
+ ) -> pd.DataFrame:
118
148
  """
119
- Converts a cloudvolume Skeleton object to the pd.DataFrame format
120
- expected by brainrender's Streamlines actor.
149
+ Convert a cloudvolume Skeleton object to a DataFrame for the Streamlines actor.
121
150
 
122
151
  Vertices are in nanometers in Allen CCF space. We:
123
152
  1. Convert nm -> um (divide by VOXEL_SIZE_NM)
@@ -126,10 +155,19 @@ def _skeleton_to_dataframe(skeleton, eid, ml_extent_um):
126
155
  X (AP) and Y (DV) are passed through as-is because brainrender's
127
156
  brain mesh uses the same orientation as the Allen CCF for those axes.
128
157
 
129
- :param skeleton: cloudvolume Skeleton object
130
- :param eid: int, experiment ID used to fetch real injection coordinates
131
- :param ml_extent_um: float, full ML extent of the atlas in um for LR flip
132
- :return: pd.DataFrame with 'lines' and 'injection_sites' columns
158
+ Parameters
159
+ ----------
160
+ skeleton
161
+ cloudvolume Skeleton object.
162
+ eid
163
+ Experiment ID, used to fetch the real injection site coordinates.
164
+ ml_extent_um
165
+ Full ML extent of the atlas in µm, used for the left-right flip.
166
+
167
+ Returns
168
+ -------
169
+ pd.DataFrame
170
+ DataFrame with ``lines`` and ``injection_sites`` columns.
133
171
  """
134
172
  components = skeleton.components()
135
173
 
@@ -164,14 +202,25 @@ def _skeleton_to_dataframe(skeleton, eid, ml_extent_um):
164
202
  )
165
203
 
166
204
 
167
- def get_streamlines_data(eids, force_download=False):
205
+ def get_streamlines_data(
206
+ eids: list[int],
207
+ force_download: bool = False,
208
+ ) -> list[pd.DataFrame]:
168
209
  """
169
- Given a list of experiment IDs, downloads streamline data from the
210
+ Given a list of experiment IDs, download streamline data from the
170
211
  Allen mesoscale connectivity dataset hosted on Google Cloud Storage
171
- via cloud-volume, and saves them as JSON files.
172
-
173
- :param eids: list of integers with experiment IDs
174
- :param force_download: bool, if True re-download even if cached
212
+ via cloud-volume, and save them as JSON files.
213
+
214
+ Parameters
215
+ ----------
216
+ eids
217
+ Experiment IDs to download.
218
+ force_download
219
+ If True, re-download even if a cached file exists. Default False.
220
+
221
+ Returns
222
+ -------
223
+ list of pd.DataFrame
175
224
  """
176
225
  if not cloudvolume_installed:
177
226
  print(
@@ -210,7 +259,10 @@ def get_streamlines_data(eids, force_download=False):
210
259
  return data
211
260
 
212
261
 
213
- def get_streamlines_for_region(region, force_download=False):
262
+ def get_streamlines_for_region(
263
+ region: str,
264
+ force_download: bool = False,
265
+ ) -> list[pd.DataFrame] | None:
214
266
  """
215
267
  Using the Allen Mouse Connectivity data and corresponding API, this function finds experiments
216
268
  whose injections were targeted to the region of interest and downloads the corresponding
@@ -218,8 +270,17 @@ def get_streamlines_for_region(region, force_download=False):
218
270
  By default, experiments are selected for only WT mice and only when the region was
219
271
  the primary injection target.
220
272
 
221
- :param region: str with region to use for search
222
- :param force_download: bool, if True re-download even if cached
273
+ Parameters
274
+ ----------
275
+ region
276
+ Acronym of the brain region to search for.
277
+ force_download
278
+ If True, re-download even if cached. Default False.
279
+
280
+ Returns
281
+ -------
282
+ list of pd.DataFrame or None
283
+ Streamlines data, or None if no experiments are found.
223
284
  """
224
285
  logger.debug(f"Getting streamlines data for region: {region}")
225
286
  region_experiments = experiments_source_search(region)
@@ -78,9 +78,11 @@ class Scene(JupyterMixIn, Render):
78
78
  # Initialise render class
79
79
  Render.__init__(self, plotter)
80
80
 
81
+ root_name = self.atlas.structures[self.atlas.hierarchy.root]["acronym"]
82
+
81
83
  # Get root mesh
82
84
  self.root = self.add_brain_region(
83
- "root",
85
+ root_name,
84
86
  alpha=settings.ROOT_ALPHA,
85
87
  color=settings.ROOT_COLOR,
86
88
  silhouette=bool(root and settings.SHADER_STYLE == "cartoon"),
@@ -283,7 +285,7 @@ class Scene(JupyterMixIn, Render):
283
285
 
284
286
  def add_brain_region(
285
287
  self,
286
- *regions: str,
288
+ *regions: str | int,
287
289
  alpha: float = 1,
288
290
  color: str | None = None,
289
291
  silhouette: bool | None = None,
@@ -296,7 +298,7 @@ class Scene(JupyterMixIn, Render):
296
298
  Parameters
297
299
  ----------
298
300
  *regions
299
- Region names.
301
+ Region names or IDs.
300
302
  alpha
301
303
  How opaque the regions are rendered.
302
304
  color
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainrender
3
- Version: 2.2.1
3
+ Version: 2.2.2
4
4
  Summary: Visualisation and exploration of brain atlases and other anatomical data
5
5
  Author-email: "Federico Claudi, Adam Tyson, Luigi Petrucco" <hello@brainglobe.info>
6
6
  License: BSD-3-Clause
@@ -41,12 +41,11 @@ Requires-Dist: pyyaml>=5.3
41
41
  Requires-Dist: requests
42
42
  Requires-Dist: tables
43
43
  Requires-Dist: vedo>=2025.5.3
44
- Requires-Dist: vtk
44
+ Requires-Dist: vtk<9.7.0
45
45
  Provides-Extra: dev
46
46
  Requires-Dist: pytest; extra == "dev"
47
47
  Requires-Dist: pytest-cov; extra == "dev"
48
48
  Requires-Dist: coverage; extra == "dev"
49
- Requires-Dist: tox; extra == "dev"
50
49
  Requires-Dist: black; extra == "dev"
51
50
  Requires-Dist: mypy; extra == "dev"
52
51
  Requires-Dist: pre-commit; extra == "dev"
@@ -17,13 +17,12 @@ pyyaml>=5.3
17
17
  requests
18
18
  tables
19
19
  vedo>=2025.5.3
20
- vtk
20
+ vtk<9.7.0
21
21
 
22
22
  [dev]
23
23
  pytest
24
24
  pytest-cov
25
25
  coverage
26
- tox
27
26
  black
28
27
  mypy
29
28
  pre-commit
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.2.2",
3
+ "distance": 0,
4
+ "node": "g60cf6b76b8a5bfca51f843069ffbe0068f9ae0d2",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-21"
8
+ }
@@ -28,7 +28,7 @@ dependencies = [
28
28
  "requests",
29
29
  "tables",
30
30
  "vedo>=2025.5.3",
31
- "vtk"
31
+ "vtk<9.7.0"
32
32
  ]
33
33
 
34
34
  license = { text = "BSD-3-Clause" }
@@ -57,7 +57,6 @@ dev = [
57
57
  "pytest",
58
58
  "pytest-cov",
59
59
  "coverage",
60
- "tox",
61
60
  "black",
62
61
  "mypy",
63
62
  "pre-commit",
@@ -66,7 +65,6 @@ dev = [
66
65
  ]
67
66
  nb = ["jupyter", "k3d"]
68
67
 
69
-
70
68
  [build-system]
71
69
  requires = ["setuptools>=45", "wheel", "setuptools_scm[toml]>=6.2"]
72
70
  build-backend = "setuptools.build_meta"
@@ -78,9 +76,8 @@ include-package-data = true
78
76
  include = ["brainrender*"]
79
77
  exclude = ["tests*", "docs*", "examples*", "imgs*"]
80
78
 
81
-
82
79
  [tool.pytest.ini_options]
83
- addopts = "--cov=brainrender"
80
+ addopts = "--cov=brainrender --cov-report=xml --color=yes -vv"
84
81
  markers = [
85
82
  "slow: marks tests as slow (deselect with '-m \"not slow\"')",
86
83
  "local: marks test as local (not for CI)",
@@ -113,29 +110,3 @@ ignore = ["E501", "E402"]
113
110
 
114
111
  [tool.ruff.lint]
115
112
  select = ["I", "E", "F"]
116
-
117
- [tool.tox]
118
- legacy_tox_ini = """
119
- [tox]
120
- envlist = py{311,312,313}
121
- isolated_build = True
122
-
123
- [gh-actions]
124
- python =
125
- 3.11: py311
126
- 3.12: py312
127
- 3.13: py313
128
-
129
- [testenv]
130
- extras =
131
- dev
132
- commands =
133
- pytest -v --color=yes --cov=brainrender --cov-report=xml
134
- passenv =
135
- CI
136
- GITHUB_ACTIONS
137
- DISPLAY
138
- XAUTHORITY
139
- NUMPY_EXPERIMENTAL_ARRAY_FUNCTION
140
- PYVISTA_OFF_SCREEN
141
- """
@@ -1,96 +0,0 @@
1
- import io
2
- import os
3
- import sys
4
- import zipfile
5
-
6
- import numpy as np
7
-
8
- from brainrender._io import check_file_exists, request
9
- from brainrender._utils import get_subdirs, listdir
10
-
11
- # ----------------------------------- Cache ---------------------------------- #
12
-
13
-
14
- def check_gene_cached(cache_folder, gene_id, exp_id):
15
- """
16
- A gene is saved in a folder in cache_folder
17
- with gene_id-exp_id as name. If the folder doesn't
18
- exist the gene is not cached.
19
-
20
- :param cache_folder: str, path to general cache folder for all data
21
- :param gene_id: str name of gene
22
- :param exp_id: id of experiment
23
- """
24
- cache = [
25
- sub
26
- for sub in get_subdirs(cache_folder)
27
- if f"{gene_id}-{exp_id}" == os.path.basename(sub)
28
- ]
29
- if not cache:
30
- return False
31
- elif len(cache) > 1:
32
- raise ValueError("Found too many folders")
33
- else:
34
- return cache[0]
35
-
36
-
37
- def download_and_cache(url, cachedir):
38
- """
39
- Given a url to download a gene's ISH experiment data,
40
- this function download and unzips the data
41
-
42
- :param url: str, utl to download data
43
- :param cachedir: str, path to folder where data will be downloaded
44
- """
45
- # Get data
46
- req = request(url)
47
-
48
- # Create cache dir
49
- if not os.path.isdir(cachedir):
50
- os.mkdir(cachedir)
51
-
52
- # Unzip to cache dir
53
- z = zipfile.ZipFile(io.BytesIO(req.content))
54
- z.extractall(cachedir)
55
-
56
-
57
- def load_cached_gene(cache, metric, grid_size):
58
- """
59
- Loads a gene's data from cache
60
- """
61
- files = [
62
- f for f in listdir(cache) if metric in f and not f.endswith(".mhd")
63
- ]
64
- if not files:
65
- return None
66
- if len(files) > 1:
67
- raise NotImplementedError("Deal with more than one file found")
68
- else:
69
- return read_raw(files[0], grid_size)
70
-
71
-
72
- # --------------------------------- Open .raw -------------------------------- #
73
- @check_file_exists
74
- def read_raw(filepath, grid_size):
75
- """
76
- reads a .raw file with gene expression data
77
- downloaded from the Allen atlas and returns
78
- a numpy array with the correct grid_size.
79
- See as reference:
80
- http://help.brain-map.org/display/mousebrain/API#API-Expression3DGridsz
81
-
82
- :param filepath: str or Path object
83
- """
84
- filepath = str(filepath)
85
-
86
- # Read bytes
87
- with open(filepath, "rb") as test:
88
- content = test.read()
89
-
90
- # Create np array and return
91
- data = np.frombuffer(content, dtype="float32").reshape(grid_size)
92
-
93
- if sys.platform == "darwin":
94
- data = data.T # TODO figure out why this is necessary on Mac OS?
95
-
96
- return data
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "2.2.1",
3
- "distance": 0,
4
- "node": "gbdcdf90b2b8e814449900042e232e53f18b66227",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-08-10"
8
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes