|
| 1 | +from pathlib import Path |
| 2 | + |
| 3 | +import imio |
1 | 4 | import numpy as np
|
| 5 | +import pooch |
2 | 6 | import pytest
|
| 7 | +from bg_space import AnatomicalSpace |
| 8 | +from vedo import Volume as VedoVolume |
3 | 9 |
|
4 |
| -from brainrender import Scene |
5 |
| -from brainrender.actors import Points |
| 10 | +from brainrender import Animation, Scene, VideoMaker |
| 11 | +from brainrender.actors import ( |
| 12 | + Neuron, |
| 13 | + Points, |
| 14 | + PointsDensity, |
| 15 | + Volume, |
| 16 | + ruler, |
| 17 | + ruler_from_surface, |
| 18 | +) |
| 19 | +from brainrender.atlas_specific import GeneExpressionAPI |
6 | 20 |
|
7 | 21 |
|
8 | 22 | def get_n_points_in_region(region, N):
|
@@ -35,6 +49,7 @@ def check_bounds(bounds, parent_bounds):
|
35 | 49 | def scene():
|
36 | 50 | scene = Scene(inset=False)
|
37 | 51 | yield scene
|
| 52 | + scene.close() |
38 | 53 | del scene
|
39 | 54 |
|
40 | 55 |
|
@@ -95,6 +110,294 @@ def test_add_labels(scene):
|
95 | 110 | def test_add_mesh_from_file(scene, pytestconfig):
|
96 | 111 | root_path = pytestconfig.rootpath
|
97 | 112 | scene.add_brain_region("SCm", alpha=0.2)
|
98 |
| - scene.add( |
| 113 | + file_mesh = scene.add( |
99 | 114 | root_path / "tests" / "files" / "CC_134_1_ch1inj.obj", color="tomato"
|
100 | 115 | )
|
| 116 | + |
| 117 | + scene.render(interactive=False) |
| 118 | + |
| 119 | + file_mesh_bounds = file_mesh.bounds() |
| 120 | + root_bounds = scene.root.bounds() |
| 121 | + |
| 122 | + check_bounds(file_mesh_bounds, root_bounds) |
| 123 | + |
| 124 | + |
| 125 | +def test_animation(scene, pytestconfig): |
| 126 | + root_path = pytestconfig.rootpath |
| 127 | + |
| 128 | + scene.add_brain_region("TH") |
| 129 | + anim = Animation(scene, "./examples", "vid3") |
| 130 | + |
| 131 | + anim.add_keyframe(0, camera="top", zoom=1) |
| 132 | + anim.add_keyframe(1.5, camera="sagittal", zoom=0.95) |
| 133 | + anim.add_keyframe(3, camera="frontal", zoom=1) |
| 134 | + anim.add_keyframe(4, camera="frontal", zoom=1.2) |
| 135 | + |
| 136 | + anim.make_video(duration=5, fps=15) |
| 137 | + |
| 138 | + scene.render(interactive=False) |
| 139 | + scene.close() |
| 140 | + |
| 141 | + vid_path = Path(root_path / "tests" / "examples" / "vid3.mp4") |
| 142 | + |
| 143 | + assert vid_path.exists() |
| 144 | + vid_path.unlink() |
| 145 | + Path.rmdir(Path(root_path / "tests" / "examples")) |
| 146 | + |
| 147 | + |
| 148 | +def test_adding_multiple_brain_regions(scene): |
| 149 | + th = scene.add_brain_region("TH") |
| 150 | + brain_regions = scene.add_brain_region( |
| 151 | + "MOs", "CA1", alpha=0.2, color="green" |
| 152 | + ) |
| 153 | + |
| 154 | + scene.render(interactive=False) |
| 155 | + |
| 156 | + assert len(scene.actors) == 4 |
| 157 | + assert scene.actors[1].name == "TH" |
| 158 | + assert scene.actors[2].name == "MOs" |
| 159 | + assert scene.actors[3].name == "CA1" |
| 160 | + |
| 161 | + root_bounds = scene.root.bounds() |
| 162 | + th_bounds = th.bounds() |
| 163 | + mos_bounds = brain_regions[0].bounds() |
| 164 | + ca1_bounds = brain_regions[1].bounds() |
| 165 | + |
| 166 | + check_bounds(th_bounds, root_bounds) |
| 167 | + check_bounds(mos_bounds, root_bounds) |
| 168 | + check_bounds(ca1_bounds, root_bounds) |
| 169 | + |
| 170 | + |
| 171 | +def test_brainglobe_atlas(): |
| 172 | + scene = Scene(atlas_name="mpin_zfish_1um", title="zebrafish") |
| 173 | + |
| 174 | + scene.render(interactive=False) |
| 175 | + |
| 176 | + assert len(scene.actors) == 2 |
| 177 | + assert scene.actors[0].name == "root" |
| 178 | + assert scene.actors[1].name == "title" |
| 179 | + assert scene.atlas.atlas_name == "mpin_zfish_1um" |
| 180 | + |
| 181 | + |
| 182 | +def test_cell_density(scene): |
| 183 | + mos = scene.add_brain_region("MOs", alpha=0.0) |
| 184 | + coordinates = get_n_points_in_region(mos, 2000) |
| 185 | + |
| 186 | + points = Points(coordinates, name="CELLS", colors="salmon") |
| 187 | + points_density = PointsDensity(coordinates) |
| 188 | + scene.add(points) |
| 189 | + scene.add(points_density) |
| 190 | + |
| 191 | + scene.render(interactive=False) |
| 192 | + |
| 193 | + assert scene.actors[1] == mos |
| 194 | + assert scene.actors[2] == points |
| 195 | + assert scene.actors[3] == points_density |
| 196 | + |
| 197 | + root_bounds = scene.root.bounds() |
| 198 | + points_bounds = points.bounds() |
| 199 | + points_density_bounds = points_density.bounds() |
| 200 | + |
| 201 | + check_bounds(points_bounds, root_bounds) |
| 202 | + check_bounds(points_density_bounds, root_bounds) |
| 203 | + |
| 204 | + |
| 205 | +def test_gene_expression(scene): |
| 206 | + gene = "Gpr161" |
| 207 | + geapi = GeneExpressionAPI() |
| 208 | + expids = geapi.get_gene_experiments(gene) |
| 209 | + data = geapi.get_gene_data(gene, expids[1]) |
| 210 | + |
| 211 | + gene_actor = geapi.griddata_to_volume( |
| 212 | + data, min_quantile=99, cmap="inferno" |
| 213 | + ) |
| 214 | + ca1 = scene.add_brain_region("CA1", alpha=0.2, color="skyblue") |
| 215 | + act = scene.add(gene_actor) |
| 216 | + |
| 217 | + scene.render(interactive=False) |
| 218 | + |
| 219 | + # Expand bounds by 500 px |
| 220 | + ca1_bounds = ca1.bounds() |
| 221 | + expanded_bounds = [ |
| 222 | + bound - 500 if i % 2 == 0 else bound + 500 |
| 223 | + for i, bound in enumerate(ca1_bounds) |
| 224 | + ] |
| 225 | + |
| 226 | + gene_actor_bounds = act.bounds() |
| 227 | + |
| 228 | + assert scene.actors[1] == ca1 |
| 229 | + assert scene.actors[2] == act |
| 230 | + |
| 231 | + check_bounds(gene_actor_bounds, expanded_bounds) |
| 232 | + |
| 233 | + |
| 234 | +def test_neurons(scene, pytestconfig): |
| 235 | + root_path = pytestconfig.rootpath |
| 236 | + |
| 237 | + neuron = Neuron(root_path / "tests" / "files" / "neuron1.swc") |
| 238 | + scene.add(neuron) |
| 239 | + scene.render(interactive=False) |
| 240 | + |
| 241 | + assert len(scene.actors) == 2 |
| 242 | + assert scene.actors[1].name == "neuron1.swc" |
| 243 | + |
| 244 | + neuron_bounds = scene.actors[1].bounds() |
| 245 | + # Based on pre-calculated bounds of this specific neuron |
| 246 | + expected_bounds = (2177, 7152, 2319, 5056, -9147, -1294) |
| 247 | + |
| 248 | + check_bounds(neuron_bounds, expected_bounds) |
| 249 | + |
| 250 | + |
| 251 | +def test_ruler(scene): |
| 252 | + th, mos = scene.add_brain_region("TH", "MOs", alpha=0.3) |
| 253 | + p1 = th.center_of_mass() |
| 254 | + p2 = mos.center_of_mass() |
| 255 | + |
| 256 | + rul1 = ruler(p1, p2, unit_scale=0.01, units="mm") |
| 257 | + rul2 = ruler_from_surface(p1, scene.root, unit_scale=0.01, units="mm") |
| 258 | + |
| 259 | + scene.add(rul1, rul2) |
| 260 | + |
| 261 | + scene.render(interactive=False) |
| 262 | + |
| 263 | + assert len(scene.actors) == 5 |
| 264 | + assert scene.actors[1] == th |
| 265 | + assert scene.actors[2] == mos |
| 266 | + assert scene.actors[3] == rul1 |
| 267 | + assert scene.actors[4] == rul2 |
| 268 | + |
| 269 | + root_bounds = scene.root.bounds() |
| 270 | + th_bounds = th.bounds() |
| 271 | + mos_bounds = mos.bounds() |
| 272 | + rul1_bounds = rul1.bounds() |
| 273 | + rul2_bounds = rul2.bounds() |
| 274 | + |
| 275 | + check_bounds(th_bounds, root_bounds) |
| 276 | + check_bounds(mos_bounds, root_bounds) |
| 277 | + check_bounds(rul1_bounds, root_bounds) |
| 278 | + check_bounds(rul2_bounds, root_bounds) |
| 279 | + |
| 280 | + |
| 281 | +def test_screenshot(scene, pytestconfig): |
| 282 | + root_path = pytestconfig.rootpath |
| 283 | + screenshot_folder = root_path / "tests" |
| 284 | + scene.screenshots_folder = screenshot_folder |
| 285 | + scene.add_brain_region("TH") |
| 286 | + |
| 287 | + scene.render(interactive=False) |
| 288 | + scene.screenshot(name="test_screenshot", scale=2) |
| 289 | + screenshot_path = screenshot_folder / "test_screenshot.png" |
| 290 | + |
| 291 | + assert screenshot_folder.exists() |
| 292 | + assert screenshot_path.exists() |
| 293 | + |
| 294 | + screenshot_path.unlink() |
| 295 | + |
| 296 | + |
| 297 | +def test_slice(scene): |
| 298 | + th, mos, ca1 = scene.add_brain_region( |
| 299 | + "TH", "MOs", "CA1", alpha=0.2, color="green" |
| 300 | + ) |
| 301 | + th_clone = th._mesh.clone() |
| 302 | + mos_clone = mos._mesh.clone() |
| 303 | + ca1_clone = ca1._mesh.clone() |
| 304 | + |
| 305 | + scene.slice("frontal", actors=[mos]) |
| 306 | + plane = scene.atlas.get_plane(pos=mos.center_of_mass(), norm=(1, 1, 2)) |
| 307 | + scene.slice(plane, actors=[ca1]) |
| 308 | + scene.render(interactive=False) |
| 309 | + |
| 310 | + assert th_clone.bounds() == th.bounds() |
| 311 | + assert mos_clone.bounds() != mos.bounds() |
| 312 | + assert ca1_clone.bounds() != ca1.bounds() |
| 313 | + |
| 314 | + |
| 315 | +def test_user_volumetric_data(): |
| 316 | + scene = Scene(atlas_name="mpin_zfish_1um") |
| 317 | + retrieved_paths = pooch.retrieve( |
| 318 | + url="https://api.mapzebrain.org/media/Lines/brn3cGFP/average_data/T_AVG_s356tTg.zip", |
| 319 | + known_hash="54b59146ba08b4d7eea64456bcd67741db4b5395235290044545263f61453a61", |
| 320 | + path=Path.home() |
| 321 | + / ".brainglobe" |
| 322 | + / "brainrender-example-data", # zip will be downloaded here |
| 323 | + progressbar=True, |
| 324 | + processor=pooch.Unzip( |
| 325 | + extract_dir="" |
| 326 | + # path to unzipped dir, |
| 327 | + # *relative* to the path set in 'path' |
| 328 | + ), |
| 329 | + ) |
| 330 | + |
| 331 | + datafile = Path(retrieved_paths[1]) # [0] is zip file |
| 332 | + data = imio.load.load_any(datafile) |
| 333 | + source_space = AnatomicalSpace("ira") |
| 334 | + target_space = scene.atlas.space |
| 335 | + transformed_data = source_space.map_stack_to(target_space, data) |
| 336 | + |
| 337 | + vol = VedoVolume(transformed_data).smooth_median() |
| 338 | + |
| 339 | + mesh = vol.isosurface(value=20).decimate().clean() |
| 340 | + SHIFT = [30, 15, -20] # fine tune mesh position |
| 341 | + current_position = mesh.pos() |
| 342 | + new_position = [SHIFT[i] + current_position[i] for i in range(3)] |
| 343 | + mesh.pos(*new_position) |
| 344 | + |
| 345 | + scene.add(mesh) |
| 346 | + scene.render(interactive=False) |
| 347 | + |
| 348 | + assert len(scene.actors) == 2 |
| 349 | + |
| 350 | + root_bounds = scene.root.bounds() |
| 351 | + mesh_bounds = scene.actors[1].bounds() |
| 352 | + |
| 353 | + # Have to expand root bounds by 20 px |
| 354 | + |
| 355 | + expanded_bounds = [ |
| 356 | + bound - 20 if i % 2 == 0 else bound + 20 |
| 357 | + for i, bound in enumerate(root_bounds) |
| 358 | + ] |
| 359 | + |
| 360 | + check_bounds(mesh_bounds, expanded_bounds) |
| 361 | + |
| 362 | + |
| 363 | +def test_video(scene, pytestconfig): |
| 364 | + root_path = pytestconfig.rootpath |
| 365 | + video_directory = root_path / "tests" / "videos" |
| 366 | + |
| 367 | + scene.add_brain_region("TH") |
| 368 | + vm = VideoMaker(scene, video_directory, "vid1") |
| 369 | + vm.make_video(elevation=2, duration=2, fps=15) |
| 370 | + video_path = video_directory / "vid1.mp4" |
| 371 | + |
| 372 | + assert video_directory.exists() |
| 373 | + assert video_path.exists() |
| 374 | + |
| 375 | + video_path.unlink() |
| 376 | + Path.rmdir(video_directory) |
| 377 | + |
| 378 | + |
| 379 | +def test_volumetric_data(scene, pytestconfig): |
| 380 | + root_path = pytestconfig.rootpath |
| 381 | + data = np.load(root_path / "tests" / "files" / "volume.npy") |
| 382 | + actor = Volume( |
| 383 | + data, |
| 384 | + voxel_size=200, |
| 385 | + as_surface=False, |
| 386 | + c="Reds", |
| 387 | + ) |
| 388 | + scene.add(actor) |
| 389 | + scene.render(interactive=False) |
| 390 | + |
| 391 | + assert len(scene.actors) == 2 |
| 392 | + assert scene.actors[1] == actor |
| 393 | + |
| 394 | + root_bounds = scene.root.bounds() |
| 395 | + actor_bounds = actor.bounds() |
| 396 | + |
| 397 | + # Have to expand root bounds by 450 px |
| 398 | + expanded_bounds = [ |
| 399 | + bound - 550 if i % 2 == 0 else bound + 550 |
| 400 | + for i, bound in enumerate(root_bounds) |
| 401 | + ] |
| 402 | + |
| 403 | + check_bounds(actor_bounds, expanded_bounds) |
0 commit comments