brainrender 2.2.1__tar.gz → 2.2.3__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.
- {brainrender-2.2.1 → brainrender-2.2.3}/.github/workflows/test_and_deploy.yml +13 -15
- {brainrender-2.2.1 → brainrender-2.2.3}/PKG-INFO +4 -5
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/atlas_specific/allen_brain_atlas/gene_expression/api.py +115 -32
- brainrender-2.2.3/brainrender/atlas_specific/allen_brain_atlas/gene_expression/ge_utils.py +158 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/atlas_specific/allen_brain_atlas/streamlines.py +97 -36
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/render.py +18 -21
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/scene.py +5 -3
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender.egg-info/PKG-INFO +4 -5
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender.egg-info/requires.txt +1 -2
- brainrender-2.2.3/brainrender.egg-info/scm_version.json +8 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/pyproject.toml +5 -34
- brainrender-2.2.1/brainrender/atlas_specific/allen_brain_atlas/gene_expression/ge_utils.py +0 -96
- brainrender-2.2.1/brainrender.egg-info/scm_version.json +0 -8
- {brainrender-2.2.1 → brainrender-2.2.3}/.gitignore +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/CITATION.cff +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/LICENSE +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/MANIFEST.in +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/README.md +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/__init__.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/_colors.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/_io.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/_jupyter.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/_utils.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/_video.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/actor.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/actors/__init__.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/actors/cylinder.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/actors/line.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/actors/neurons.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/actors/points.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/actors/ruler.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/actors/streamlines.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/actors/volume.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/atlas.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/atlas_specific/__init__.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/atlas_specific/allen_brain_atlas/gene_expression/__init__.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/camera.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/cameras.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/settings.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender/video.py +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender.egg-info/SOURCES.txt +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender.egg-info/dependency_links.txt +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender.egg-info/scm_file_list.json +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/brainrender.egg-info/top_level.txt +0 -0
- {brainrender-2.2.1 → brainrender-2.2.3}/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@
|
|
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@
|
|
31
|
+
- uses: neuroinformatics-unit/actions/check_manifest@9b53ffe93baaafe5533e1f295a3c069bbc17ba36 # v3.0.1
|
|
33
32
|
|
|
34
33
|
test:
|
|
35
34
|
needs: [linting, manifest]
|
|
@@ -40,16 +39,16 @@ jobs:
|
|
|
40
39
|
matrix:
|
|
41
40
|
# Run all supported Python versions on linux
|
|
42
41
|
os: [ubuntu-latest]
|
|
43
|
-
python-version: ["3.
|
|
42
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
44
43
|
include:
|
|
45
44
|
- os: macos-latest
|
|
46
|
-
python-version: "3.
|
|
45
|
+
python-version: "3.14"
|
|
47
46
|
- os: windows-latest
|
|
48
|
-
python-version: "3.
|
|
47
|
+
python-version: "3.14"
|
|
49
48
|
|
|
50
49
|
steps:
|
|
51
50
|
- name: Cache brainglobe directory
|
|
52
|
-
uses: actions/cache@
|
|
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
|
-
-
|
|
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@
|
|
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.
|
|
3
|
+
Version: 2.2.3
|
|
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
|
|
@@ -12,14 +12,14 @@ Project-URL: User Support, https://forum.image.sc/tag/brainglobe
|
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Programming Language :: Python
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
18
|
Classifier: Operating System :: OS Independent
|
|
19
19
|
Classifier: License :: OSI Approved :: BSD License
|
|
20
20
|
Classifier: Intended Audience :: Developers
|
|
21
21
|
Classifier: Intended Audience :: Science/Research
|
|
22
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: brainglobe-atlasapi>=2.0.1
|
|
@@ -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
|
|
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
|
|
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
|
-
|
|
86
|
-
|
|
121
|
+
Return ISH experiment IDs for a given gene symbol.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
gene
|
|
126
|
+
Gene symbol.
|
|
87
127
|
|
|
88
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
|
|
172
|
-
The isosurface
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
{brainrender-2.2.1 → brainrender-2.2.3}/brainrender/atlas_specific/allen_brain_atlas/streamlines.py
RENAMED
|
@@ -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
|
-
|
|
45
|
-
dynamically from the brainglobe atlas API.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
62
|
-
|
|
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(
|
|
99
|
+
def _get_injection_site_um(
|
|
100
|
+
eid: int,
|
|
101
|
+
ml_extent_um: float,
|
|
102
|
+
) -> dict[str, float] | None:
|
|
85
103
|
"""
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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(
|
|
143
|
+
def _skeleton_to_dataframe(
|
|
144
|
+
skeleton: Any,
|
|
145
|
+
eid: int,
|
|
146
|
+
ml_extent_um: float,
|
|
147
|
+
) -> pd.DataFrame:
|
|
118
148
|
"""
|
|
119
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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(
|
|
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,
|
|
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
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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(
|
|
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
|
-
|
|
222
|
-
|
|
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)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import weakref
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
from typing import TYPE_CHECKING, Any
|
|
@@ -52,7 +53,8 @@ class Render:
|
|
|
52
53
|
self._get_plotter()
|
|
53
54
|
else:
|
|
54
55
|
self.plotter = plotter
|
|
55
|
-
|
|
56
|
+
|
|
57
|
+
self._bind_screenshot_key()
|
|
56
58
|
|
|
57
59
|
def _get_plotter(self) -> None:
|
|
58
60
|
"""
|
|
@@ -67,7 +69,21 @@ class Render:
|
|
|
67
69
|
size="full" if settings.WHOLE_SCREEN else (1600, 1200),
|
|
68
70
|
)
|
|
69
71
|
|
|
70
|
-
|
|
72
|
+
def _bind_screenshot_key(self) -> None:
|
|
73
|
+
"""
|
|
74
|
+
Save a screenshot when ``s`` is pressed in the render window.
|
|
75
|
+
"""
|
|
76
|
+
# Weak reference: holding self here makes Scene <-> plotter a
|
|
77
|
+
# reference cycle, so a dropped Scene is only closed by the cyclic
|
|
78
|
+
# garbage collector, on whichever thread happens to run it.
|
|
79
|
+
scene_ref = weakref.ref(self)
|
|
80
|
+
|
|
81
|
+
def on_keypress(event) -> None:
|
|
82
|
+
scene = scene_ref()
|
|
83
|
+
if scene is not None and event.keypress == "s":
|
|
84
|
+
scene.screenshot()
|
|
85
|
+
|
|
86
|
+
self.plotter.add_callback("KeyPress", on_keypress)
|
|
71
87
|
|
|
72
88
|
def _make_axes(self) -> dict:
|
|
73
89
|
"""
|
|
@@ -419,22 +435,3 @@ class Render:
|
|
|
419
435
|
logger.debug(f"Saving scene at {savepath}")
|
|
420
436
|
self.plotter.screenshot(filename=savepath, scale=scale)
|
|
421
437
|
return savepath
|
|
422
|
-
|
|
423
|
-
def keypress(self, key: str) -> None: # pragma: no cover
|
|
424
|
-
"""
|
|
425
|
-
Handle key presses during interactive rendering.
|
|
426
|
-
|
|
427
|
-
- ``s``: take a screenshot
|
|
428
|
-
- ``q`` / ``Esc``: close the window
|
|
429
|
-
- ``c``: print current camera parameters
|
|
430
|
-
|
|
431
|
-
Parameters
|
|
432
|
-
----------
|
|
433
|
-
key
|
|
434
|
-
Key identifier string from vedo.
|
|
435
|
-
"""
|
|
436
|
-
if key == "s":
|
|
437
|
-
self.screenshot()
|
|
438
|
-
|
|
439
|
-
elif key in ("q", "Esc"):
|
|
440
|
-
self.close()
|
|
@@ -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
|
-
|
|
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.
|
|
3
|
+
Version: 2.2.3
|
|
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
|
|
@@ -12,14 +12,14 @@ Project-URL: User Support, https://forum.image.sc/tag/brainglobe
|
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Programming Language :: Python
|
|
14
14
|
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
16
15
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
18
|
Classifier: Operating System :: OS Independent
|
|
19
19
|
Classifier: License :: OSI Approved :: BSD License
|
|
20
20
|
Classifier: Intended Audience :: Developers
|
|
21
21
|
Classifier: Intended Audience :: Science/Research
|
|
22
|
-
Requires-Python: >=3.
|
|
22
|
+
Requires-Python: >=3.12
|
|
23
23
|
Description-Content-Type: text/markdown
|
|
24
24
|
License-File: LICENSE
|
|
25
25
|
Requires-Dist: brainglobe-atlasapi>=2.0.1
|
|
@@ -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"
|
|
@@ -5,7 +5,7 @@ authors = [
|
|
|
5
5
|
]
|
|
6
6
|
description = "Visualisation and exploration of brain atlases and other anatomical data"
|
|
7
7
|
readme = "README.md"
|
|
8
|
-
requires-python = ">=3.
|
|
8
|
+
requires-python = ">=3.12"
|
|
9
9
|
dynamic = ["version"]
|
|
10
10
|
|
|
11
11
|
dependencies = [
|
|
@@ -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" }
|
|
@@ -36,9 +36,9 @@ classifiers = [
|
|
|
36
36
|
"Development Status :: 3 - Alpha",
|
|
37
37
|
"Programming Language :: Python",
|
|
38
38
|
"Programming Language :: Python :: 3",
|
|
39
|
-
"Programming Language :: Python :: 3.11",
|
|
40
39
|
"Programming Language :: Python :: 3.12",
|
|
41
40
|
"Programming Language :: Python :: 3.13",
|
|
41
|
+
"Programming Language :: Python :: 3.14",
|
|
42
42
|
"Operating System :: OS Independent",
|
|
43
43
|
"License :: OSI Approved :: BSD License",
|
|
44
44
|
"Intended Audience :: Developers",
|
|
@@ -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,16 +76,15 @@ 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)",
|
|
87
84
|
]
|
|
88
85
|
|
|
89
86
|
[tool.black]
|
|
90
|
-
target-version = ['
|
|
87
|
+
target-version = ['py312', 'py313', 'py314']
|
|
91
88
|
skip-string-normalization = false
|
|
92
89
|
line-length = 79
|
|
93
90
|
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|