brainrender 2.2.0__tar.gz → 2.2.1__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 (54) hide show
  1. {brainrender-2.2.0 → brainrender-2.2.1}/PKG-INFO +2 -2
  2. brainrender-2.2.1/brainrender/_colors.py +131 -0
  3. brainrender-2.2.1/brainrender/_io.py +185 -0
  4. brainrender-2.2.1/brainrender/_utils.py +82 -0
  5. brainrender-2.2.1/brainrender/_video.py +60 -0
  6. brainrender-2.2.1/brainrender/actors/cylinder.py +50 -0
  7. brainrender-2.2.1/brainrender/actors/line.py +35 -0
  8. brainrender-2.2.1/brainrender/actors/neurons.py +181 -0
  9. brainrender-2.2.1/brainrender/actors/points.py +230 -0
  10. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/actors/ruler.py +54 -15
  11. brainrender-2.2.1/brainrender/actors/streamlines.py +163 -0
  12. brainrender-2.2.1/brainrender/actors/volume.py +173 -0
  13. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender.egg-info/PKG-INFO +2 -2
  14. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender.egg-info/requires.txt +1 -1
  15. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender.egg-info/scm_file_list.json +90 -90
  16. brainrender-2.2.1/brainrender.egg-info/scm_version.json +8 -0
  17. {brainrender-2.2.0 → brainrender-2.2.1}/pyproject.toml +1 -1
  18. brainrender-2.2.0/brainrender/_colors.py +0 -83
  19. brainrender-2.2.0/brainrender/_io.py +0 -110
  20. brainrender-2.2.0/brainrender/_utils.py +0 -44
  21. brainrender-2.2.0/brainrender/_video.py +0 -36
  22. brainrender-2.2.0/brainrender/actors/cylinder.py +0 -34
  23. brainrender-2.2.0/brainrender/actors/line.py +0 -22
  24. brainrender-2.2.0/brainrender/actors/neurons.py +0 -106
  25. brainrender-2.2.0/brainrender/actors/points.py +0 -155
  26. brainrender-2.2.0/brainrender/actors/streamlines.py +0 -111
  27. brainrender-2.2.0/brainrender/actors/volume.py +0 -111
  28. brainrender-2.2.0/brainrender.egg-info/scm_version.json +0 -8
  29. {brainrender-2.2.0 → brainrender-2.2.1}/.github/workflows/test_and_deploy.yml +0 -0
  30. {brainrender-2.2.0 → brainrender-2.2.1}/.gitignore +0 -0
  31. {brainrender-2.2.0 → brainrender-2.2.1}/CITATION.cff +0 -0
  32. {brainrender-2.2.0 → brainrender-2.2.1}/LICENSE +0 -0
  33. {brainrender-2.2.0 → brainrender-2.2.1}/MANIFEST.in +0 -0
  34. {brainrender-2.2.0 → brainrender-2.2.1}/README.md +0 -0
  35. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/__init__.py +0 -0
  36. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/_jupyter.py +0 -0
  37. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/actor.py +0 -0
  38. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/actors/__init__.py +0 -0
  39. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/atlas.py +0 -0
  40. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/atlas_specific/__init__.py +0 -0
  41. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/atlas_specific/allen_brain_atlas/gene_expression/__init__.py +0 -0
  42. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/atlas_specific/allen_brain_atlas/gene_expression/api.py +0 -0
  43. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/atlas_specific/allen_brain_atlas/gene_expression/ge_utils.py +0 -0
  44. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/atlas_specific/allen_brain_atlas/streamlines.py +0 -0
  45. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/camera.py +0 -0
  46. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/cameras.py +0 -0
  47. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/render.py +0 -0
  48. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/scene.py +0 -0
  49. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/settings.py +0 -0
  50. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender/video.py +0 -0
  51. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender.egg-info/SOURCES.txt +0 -0
  52. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender.egg-info/dependency_links.txt +0 -0
  53. {brainrender-2.2.0 → brainrender-2.2.1}/brainrender.egg-info/top_level.txt +0 -0
  54. {brainrender-2.2.0 → brainrender-2.2.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brainrender
3
- Version: 2.2.0
3
+ Version: 2.2.1
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
@@ -23,7 +23,7 @@ Requires-Python: >=3.11
23
23
  Description-Content-Type: text/markdown
24
24
  License-File: LICENSE
25
25
  Requires-Dist: brainglobe-atlasapi>=2.0.1
26
- Requires-Dist: cloud-volume>=3.11.0
26
+ Requires-Dist: cloud-volume>=12
27
27
  Requires-Dist: brainglobe-space>=1.0.0
28
28
  Requires-Dist: brainglobe-utils>=0.5.0
29
29
  Requires-Dist: h5py
@@ -0,0 +1,131 @@
1
+ """Color mapping and palette utilities for brainrender."""
2
+
3
+ import random
4
+
5
+ import matplotlib as mpl
6
+ import numpy as np
7
+ import numpy.typing as npt
8
+ from vedo.colors import colors as vcolors
9
+ from vedo.colors import get_color as getColor
10
+
11
+
12
+ def map_color(
13
+ value: float,
14
+ name: str = "jet",
15
+ vmin: float | None = None,
16
+ vmax: float | None = None,
17
+ ) -> tuple[float, float, float]:
18
+ """
19
+ Map a scalar value in ``[vmin, vmax]`` to an RGB colour.
20
+
21
+ Parameters
22
+ ----------
23
+ value
24
+ Scalar value to transform into a colour.
25
+ name
26
+ Colormap name or matplotlib colormap. Default ``"jet"``.
27
+ vmin
28
+ Lower bound of the value range.
29
+ vmax
30
+ Upper bound of the value range.
31
+
32
+ Returns
33
+ -------
34
+ tuple of float
35
+ ``(r, g, b)`` colour.
36
+
37
+ Raises
38
+ ------
39
+ ValueError
40
+ If ``vmax`` is smaller than ``vmin``.
41
+ """
42
+ if vmax < vmin:
43
+ raise ValueError("vmax should be larger than vmin")
44
+
45
+ mp = mpl.colormaps.get_cmap(name)
46
+
47
+ value -= vmin
48
+ value /= vmax - vmin
49
+ if value > 0.999:
50
+ value = 0.999
51
+ elif value < 0:
52
+ value = 0
53
+ return mp(value)[0:3]
54
+
55
+
56
+ def make_palette(N: int, *colors: str) -> list[npt.NDArray]:
57
+ """
58
+ Generate N colours interpolated across the given input colours.
59
+
60
+ Adapted from vedo's ``make_palette`` function. Interpolation is
61
+ performed in RGB space.
62
+
63
+ Parameters
64
+ ----------
65
+ N
66
+ Number of output colours.
67
+ *colors
68
+ Input colours. Any number between 1 and N is accepted.
69
+
70
+ Returns
71
+ -------
72
+ list of numpy.ndarray
73
+ List of ``(r, g, b)`` colour arrays.
74
+
75
+ Raises
76
+ ------
77
+ ValueError
78
+ If no colours are passed or more colours than N are passed.
79
+ """
80
+ N = int(N)
81
+
82
+ N_input_colors = len(colors)
83
+ if not N_input_colors:
84
+ raise ValueError("No colors where passed to make_palette")
85
+ if N_input_colors > N:
86
+ raise ValueError(
87
+ "More input colors than out colors (N) where passed to make_palette"
88
+ )
89
+
90
+ if N_input_colors == N:
91
+ return colors
92
+ else:
93
+ # Get how many colors for each pair of colors we are interpolating over
94
+ fractions = [
95
+ N // N_input_colors + (1 if x < N % N_input_colors else 0)
96
+ for x in range(N_input_colors)
97
+ ]
98
+
99
+ # Get pairs of colors
100
+ cs = [np.array(getColor(col)) for col in colors]
101
+ cs += [cs[-1]]
102
+
103
+ output = []
104
+ for n, (c1, c2) in enumerate(zip(cs, cs[1:])):
105
+ cols = []
106
+ for f in np.linspace(0, 1, fractions[n], endpoint=True):
107
+ c = c1 * (1 - f) + c2 * f
108
+ cols.append(c)
109
+ output.extend(cols)
110
+ return output
111
+
112
+
113
+ def get_random_colors(n_colors: int = 1) -> str | list[str]:
114
+ """
115
+ Return one or more random colour names from vedo's colour palette.
116
+
117
+ Parameters
118
+ ----------
119
+ n_colors
120
+ Number of colours to return. Default 1.
121
+
122
+ Returns
123
+ -------
124
+ str or list of str
125
+ A single colour name if ``n_colors == 1``, otherwise a list.
126
+ """
127
+ col_names = list(vcolors.keys())
128
+ if n_colors == 1:
129
+ return random.choice(col_names)
130
+ else:
131
+ return list(random.choices(col_names, k=n_colors))
@@ -0,0 +1,185 @@
1
+ """File I/O and network utilities for brainrender."""
2
+
3
+ from collections.abc import Callable
4
+ from pathlib import Path
5
+ from typing import Any, ParamSpec, TypeVar
6
+
7
+ import requests
8
+ from vedo import Mesh, Volume, load
9
+
10
+ P = ParamSpec("P")
11
+ R = TypeVar("R")
12
+
13
+
14
+ def connected_to_internet(
15
+ url: str = "http://www.google.com/",
16
+ timeout: int = 5,
17
+ ) -> bool:
18
+ """
19
+ Check that there is an internet connection.
20
+
21
+ Parameters
22
+ ----------
23
+ url
24
+ URL to use for testing. Default ``"http://www.google.com/"``.
25
+ timeout
26
+ Timeout in seconds. Default 5.
27
+
28
+ Returns
29
+ -------
30
+ bool
31
+ ``True`` if an internet connection is available, otherwise ``False``.
32
+ """
33
+
34
+ try:
35
+ _ = requests.get(url, timeout=timeout)
36
+ return True
37
+ except requests.ConnectionError: # pragma: no cover
38
+ print("No internet connection available.") # pragma: no cover
39
+ return False
40
+
41
+
42
+ def fail_on_no_connection(func: Callable[P, R]) -> Callable[P, R]:
43
+ """
44
+ Decorator that raises an error if no internet connection is available.
45
+
46
+ Parameters
47
+ ----------
48
+ func
49
+ Function to wrap.
50
+
51
+ Returns
52
+ -------
53
+ collections.abc.Callable
54
+
55
+ Raises
56
+ ------
57
+ ConnectionError
58
+ If no internet connection is found.
59
+ """
60
+ if not connected_to_internet(): # pragma: no cover
61
+ raise ConnectionError(
62
+ "No internet connection found."
63
+ ) # pragma: no cover
64
+
65
+ def inner(*args: Any, **kwargs: Any) -> Any:
66
+ return func(*args, **kwargs)
67
+
68
+ return inner
69
+
70
+
71
+ def request(url: str) -> requests.Response:
72
+ """
73
+ Send a GET request to a URL.
74
+
75
+ Parameters
76
+ ----------
77
+ url
78
+ URL to request.
79
+
80
+ Returns
81
+ -------
82
+ requests.Response
83
+
84
+ Raises
85
+ ------
86
+ ConnectionError
87
+ If no internet connection is found.
88
+ ValueError
89
+ If the request fails.
90
+ """
91
+ if not connected_to_internet(): # pragma: no cover
92
+ raise ConnectionError(
93
+ "No internet connection found."
94
+ ) # pragma: no cover
95
+
96
+ response = requests.get(url)
97
+ if response.ok:
98
+ return response
99
+ else: # pragma: no cover
100
+ exception_string = "URL request failed: {}".format(
101
+ response.reason
102
+ ) # pragma: no cover
103
+ raise ValueError(exception_string)
104
+
105
+
106
+ def check_file_exists(
107
+ func: Callable[P, R],
108
+ ) -> Callable[P, R]: # pragma: no cover
109
+ """
110
+ Decorator that raises an error if a function's first argument
111
+ is not a path to an existing file.
112
+
113
+ Parameters
114
+ ----------
115
+ func
116
+ Function to wrap.
117
+
118
+ Returns
119
+ -------
120
+ collections.abc.Callable
121
+
122
+ Raises
123
+ ------
124
+ FileNotFoundError
125
+ If the file does not exist.
126
+ """
127
+
128
+ def inner(*args: Any, **kwargs: Any) -> Any:
129
+ if not Path(args[0]).exists():
130
+ raise FileNotFoundError(
131
+ f"File {args[0]} not found"
132
+ ) # pragma: no cover
133
+ return func(*args, **kwargs)
134
+
135
+ return inner
136
+
137
+
138
+ @check_file_exists
139
+ def load_mesh_from_file(
140
+ filepath: str | Path,
141
+ color: str | None = None,
142
+ alpha: float | None = None,
143
+ ) -> Mesh | Volume:
144
+ """
145
+ Load a mesh or volume from a file (e.g. .obj, .stl).
146
+
147
+ Parameters
148
+ ----------
149
+ filepath
150
+ Path to the mesh file.
151
+ color
152
+ Colour to apply to the mesh.
153
+ alpha
154
+ Transparency to apply to the mesh.
155
+
156
+ Returns
157
+ -------
158
+ vedo.Mesh or vedo.Volume
159
+ """
160
+ actor = load(str(filepath))
161
+ actor.c(color).alpha(alpha)
162
+ return actor
163
+
164
+
165
+ def convert_meshio_to_vedo(meshio_mesh, color=None, alpha=None):
166
+ """
167
+ Convert a meshio mesh to a vedo mesh.
168
+
169
+ Parameters
170
+ ----------
171
+ meshio_mesh : meshio.Mesh
172
+ The meshio mesh to convert.
173
+ color : str, optional
174
+ The color to apply to the mesh. Default is None.
175
+ alpha : float, optional
176
+ The transparency to apply to the mesh. Default is None.
177
+
178
+ Returns
179
+ -------
180
+ vedo.Mesh
181
+ The converted vedo mesh.
182
+ """
183
+ actor = Mesh([meshio_mesh.points, meshio_mesh.cells_dict["triangle"]])
184
+ actor.c(color).alpha(alpha)
185
+ return actor
@@ -0,0 +1,82 @@
1
+ """General utility helpers for file system traversal and list manipulation."""
2
+
3
+ from pathlib import Path
4
+ from typing import TypeVar
5
+
6
+ T = TypeVar("T")
7
+
8
+
9
+ def listdir(fld: str | Path) -> list[str]:
10
+ """
11
+ List the files into a folder with the complete file path instead of the relative file path like os.listdir.
12
+
13
+ Parameters
14
+ ----------
15
+ fld
16
+ Path to the folder.
17
+
18
+ Returns
19
+ -------
20
+ list of str
21
+ """
22
+ return [str(f) for f in Path(fld).glob("**/*") if f.is_file()]
23
+
24
+
25
+ def get_subdirs(folderpath: str | Path) -> list[str]:
26
+ """
27
+ Return all subdirectories in a given folder.
28
+
29
+ Parameters
30
+ ----------
31
+ folderpath
32
+ Path to the folder.
33
+
34
+ Returns
35
+ -------
36
+ list of str
37
+ """
38
+ return [str(f) for f in Path(folderpath).glob("**/*") if f.is_dir()]
39
+
40
+
41
+ def listify(obj: T | list[T] | tuple[T, ...]) -> list[T]:
42
+ """
43
+ Ensure the object is a list.
44
+
45
+ Parameters
46
+ ----------
47
+ obj
48
+ Object to listify.
49
+
50
+ Returns
51
+ -------
52
+ list
53
+ """
54
+ if isinstance(obj, list):
55
+ return obj
56
+ elif isinstance(obj, tuple):
57
+ return list(obj)
58
+ else:
59
+ return [obj]
60
+
61
+
62
+ def return_list_smart(lst: list[T]) -> list[T] | T | None:
63
+ """
64
+ Return a list, single element, or None depending on list length.
65
+
66
+ Parameters
67
+ ----------
68
+ lst
69
+ Input list.
70
+
71
+ Returns
72
+ -------
73
+ list, Any, or None
74
+ The list if length > 1, the single item if length == 1,
75
+ or None if empty.
76
+ """
77
+ if len(lst) > 1:
78
+ return lst
79
+ elif len(lst) == 1:
80
+ return lst[0]
81
+ else:
82
+ return None
@@ -0,0 +1,60 @@
1
+ """Thin wrapper around vedo.Video for rendering brainrender scenes to video."""
2
+
3
+ import os
4
+ from typing import Any
5
+
6
+ from loguru import logger
7
+ from myterial import amber_light
8
+ from rich import print
9
+ from vedo import Video as VtkVideo
10
+
11
+
12
+ class Video(VtkVideo):
13
+ def __init__(
14
+ self,
15
+ *args: Any,
16
+ fmt: str = "mp4",
17
+ size: str = "1620x1050",
18
+ **kwargs: Any,
19
+ ) -> None:
20
+ """
21
+ Store screenshots as frames and merge them into a video on close.
22
+
23
+ Parameters
24
+ ----------
25
+ *args
26
+ Positional arguments forwarded to ``vedo.Video``.
27
+ fmt
28
+ Video format. Default ``"mp4"``.
29
+ size
30
+ Frame size in pixels. Default ``"1620x1050"``.
31
+ **kwargs
32
+ Keyword arguments forwarded to ``vedo.Video``.
33
+ """
34
+ super().__init__(*args, **kwargs)
35
+ self.format = fmt
36
+ self.size = size
37
+
38
+ def close(self) -> tuple[int, str]:
39
+ """
40
+ Render the video and write to file.
41
+
42
+ Returns
43
+ -------
44
+ tuple
45
+ The FFmpeg exit code and the executed command string.
46
+ """
47
+ print(f"[{amber_light}]Saving video")
48
+ logger.debug(f"[{amber_light}]Saving video")
49
+
50
+ fld = os.path.join(self.tmp_dir.name, "%d.png")
51
+ fps = int(self.fps)
52
+ name = f"{self.name}.{self.format}"
53
+ fmt = "-vcodec libx264 -crf 28 -pix_fmt yuv420p"
54
+ if self.size:
55
+ fmt += f" -s {self.size}"
56
+
57
+ command = f'ffmpeg -hide_banner -loglevel panic -y -r {fps} -start_number 0 -i "{fld}" {fmt} "{name}"'
58
+ out = os.system(command)
59
+ self.tmp_dir.cleanup()
60
+ return out, command
@@ -0,0 +1,50 @@
1
+ """Cylinder actor for brainrender scenes."""
2
+
3
+ import numpy.typing as npt
4
+ from loguru import logger
5
+ from vedo import Mesh, shapes
6
+
7
+ from brainrender.actor import Actor
8
+
9
+
10
+ class Cylinder(Actor):
11
+ """Actor representing a cylinder between a point and the brain's surface."""
12
+
13
+ def __init__(
14
+ self,
15
+ pos: npt.ArrayLike | Mesh | Actor,
16
+ root: Actor,
17
+ color: str = "powderblue",
18
+ alpha: float = 1,
19
+ radius: float = 350,
20
+ ) -> None:
21
+ """
22
+ Parameters
23
+ ----------
24
+ pos
25
+ AP, DV, ML coordinates. If a Mesh or Actor is passed,
26
+ the centre of mass is used instead.
27
+ root
28
+ Brain root Actor or mesh.
29
+ color
30
+ Colour name. Default ``"powderblue"``.
31
+ alpha
32
+ Transparency. Default 1.
33
+ radius
34
+ Cylinder radius. Default 350.
35
+ """
36
+
37
+ # Get pos
38
+ if isinstance(pos, Mesh):
39
+ pos = pos.center_of_mass()
40
+ elif isinstance(pos, Actor):
41
+ pos = pos.center
42
+ logger.debug(f"Creating Cylinder actor at: {pos}")
43
+
44
+ # Get point at top of cylinder
45
+ top = pos.copy()
46
+ top[1] = root.bounds()[2] - 500
47
+
48
+ # Create mesh and Actor
49
+ mesh = shapes.Cylinder(pos=[top, pos], c=color, r=radius, alpha=alpha)
50
+ Actor.__init__(self, mesh, name="Cylinder", br_class="Cylinder")
@@ -0,0 +1,35 @@
1
+ """Line actor for brainrender scenes."""
2
+
3
+ import numpy.typing as npt
4
+ from vedo import shapes
5
+
6
+ from brainrender.actor import Actor
7
+
8
+
9
+ class Line(Actor):
10
+ """Actor representing a line through a sequence of coordinates."""
11
+
12
+ def __init__(
13
+ self,
14
+ coordinates: npt.ArrayLike,
15
+ color: str | tuple = "black",
16
+ alpha: float = 1,
17
+ linewidth: float = 2,
18
+ name: str | None = None,
19
+ ) -> None:
20
+ """
21
+ Parameters
22
+ ----------
23
+ coordinates
24
+ Array of shape (N, 3) with AP, DV, ML coordinates.
25
+ color
26
+ CSS colour name, hex code, or RGB tuple. Default ``"black"``.
27
+ alpha
28
+ Transparency in range [0, 1]. Default 1.
29
+ linewidth
30
+ Line width. Default 2.
31
+ name
32
+ Actor name.
33
+ """
34
+ mesh = shapes.Line(p0=coordinates, lw=linewidth, c=color, alpha=alpha)
35
+ Actor.__init__(self, mesh, name=name, br_class="Line")