def render(self, _interactive=True): """ """ mv = Plotter( N=2, axes=4 if brainrender.SHOW_AXES else 0, size="full" if brainrender.WHOLE_SCREEN else "auto", pos=brainrender.WINDOW_POS, bg=brainrender.BACKGROUND_COLOR, sharecam=True, ) actors = [] for scene in self.scenes: scene.apply_render_style() actors.append(scene.actors) mv.add(scene.actors) mv.show( actors[0], at=0, zoom=1.15, axes=4 if brainrender.SHOW_AXES else 0, roll=180, interactive=False, ) mv.show(actors[1], at=1, interactive=False) if _interactive: interactive()
def export_for_web(self, filepath="brexport.html"): """ This function is used to export a brainrender scene for hosting it online. It saves an html file that can be opened in a web browser to show an interactive brainrender scene """ if not filepath.endswith(".html"): raise ValueError("Filepath should point to a .html file") # prepare settings vedosettings.notebookBackend = "k3d" # Create new plotter and save to file plt = Plotter() plt.add(self.actors) plt = plt.show(interactive=False) plt.camera[-2] = -1 if self.verbose: print( "Ready for exporting. Exporting scenes with many actors might require a few minutes" ) with open(filepath, "w") as fp: fp.write(plt.get_snapshot()) if self.verbose: print( f"The brainrender scene has been exported for web. The results are saved at {filepath}" ) # Reset settings vedosettings.notebookBackend = None self.jupyter = False
def render(self, _interactive=True, **kwargs): """ :param _interactive: (Default value = True) :param **kwargs: """ camera = kwargs.pop("camera", None) for scene in self.scenes: scene.apply_render_style() if camera is None: if scene.atlas.default_camera is None: scene_camera = brainrender.CAMERA else: scene_camera = scene.atlas.default_camera else: if camera: scene_camera = camera else: scene_camera = None if scene_camera is not None: set_camera(scene, scene_camera) mv = Plotter( N=self.N, axes=4 if brainrender.SHOW_AXES else 0, size="full" if brainrender.WHOLE_SCREEN else "auto", sharecam=True, bg=brainrender.BACKGROUND_COLOR, ) actors = [] for i, scene in enumerate(self.scenes): scene.apply_render_style() actors.append(scene.actors) mv.add(scene.actors) for i, scene.actors in enumerate(actors): mv.show(scene.actors, at=i, interactive=False) print("Rendering complete") if _interactive: interactive()
def export(self, savepath): """ Exports the scene to a .html file for online renderings. :param savepath: str, Path to a .html file to save the export """ logger.debug(f"Exporting scene to {savepath}") _backend = self.backend if not self.is_rendered: self.render(interactive=False) path = Path(savepath) if path.suffix != ".html": raise ValueError("Savepath should point to a .html file") # prepare settings vsettings.notebookBackend = "k3d" # Create new plotter and save to file plt = Plotter() plt.add(self.renderables) plt = plt.show(interactive=False) plt.camera[-2] = -1 with open(path, "w") as fp: fp.write(plt.get_snapshot()) print( f"The brainrender scene has been exported for web. The results are saved at {path}" ) # Reset settings vsettings.notebookBackend = None self.backend = _backend return str(path)
# Create the initial positions and velocitites (0,0) of the bobs bob_x = [0] bob_y = [0] x_dot = np.zeros(N + 1) # velocities y_dot = np.zeros(N + 1) for k in range(1, N + 1): alpha = np.pi / 5 * k / 10 bob_x.append(bob_x[k - 1] + np.cos(alpha) + np.random.normal(0, 0.1)) bob_y.append(bob_y[k - 1] + np.sin(alpha) + np.random.normal(0, 0.1)) # Create the bobs vp = Plotter(title="Multiple Pendulum", axes=0, interactive=0) vp += Box(pos=(0, -5, 0), length=12, width=12, height=0.7, c="k").wireframe(1) bob = [vp.add(Sphere(pos=(bob_x[0], bob_y[0], 0), r=R / 2, c="gray"))] for k in range(1, N + 1): bob.append( vp.add(Cylinder(pos=(bob_x[k], bob_y[k], 0), r=R, height=0.3, c=k))) # Create the springs out of N links link = [None] * N for k in range(N): p0 = bob[k].pos() p1 = bob[k + 1].pos() link[k] = vp.add(Spring(p0, p1, thickness=0.015, r=R / 3, c="gray")) # Create some auxiliary variables x_dot_m = np.zeros(N + 1) y_dot_m = np.zeros(N + 1) dij = np.zeros(N + 1) # array with distances to previous bob
class Render: is_rendered = False plotter = None axes_names = ("AP", "DV", "LR") axes_lookup = {"x": "AP", "y": "DV", "z": "LR"} axes_indices = {"AP": 0, "DV": 1, "LR": 2} def __init__(self): """ Backend for Scene, handles all rendering and exporting related tasks. """ self._get_plotter() def _get_plotter(self): """ Make a vedo plotter with fancy axes and all """ self.plotter = Plotter( axes=self._make_axes() if settings.SHOW_AXES else None, pos=(0, 0), title="brainrender", bg=settings.BACKGROUND_COLOR, offscreen=settings.OFFSCREEN, size="full" if settings.WHOLE_SCREEN else "auto", ) self.plotter.keyPressFunction = self.keypress def _make_axes(self): """ Returns a dictionary with axes parameters for the vedo plotter """ ax_idx = self.atlas.space.axes_order.index("frontal") # make acustom axes dict atlas_shape = np.array(self.atlas.metadata["shape"]) * np.array( self.atlas.metadata["resolution"] ) z_range = np.array([-atlas_shape[2], 0]) z_ticks = [ (-v, str(np.abs(v).astype(np.int32))) for v in np.linspace( 0, atlas_shape[ax_idx], 10, ) ] if self.atlas.atlas_name == "allen_human_500um": z_range = None z_ticks = None logger.debug( "RENDER: manually forcing axes size for human atlas, atlas needs fixing" ) # make custom axes dict axes = dict( axesLineWidth=3, tipSize=0, xtitle="AP (μm)", ytitle="DV (μm)", ztitle="LR (μm)", textScale=0.8, xTitleRotation=180, zrange=z_range, zValuesAndLabels=z_ticks, xyGrid=False, yzGrid=False, zxGrid=False, xUseBounds=True, yUseBounds=True, zUseBounds=True, xLabelRotation=180, yLabelRotation=180, zLabelRotation=90, ) return axes def _prepare_actor(self, actor): """ When an actor is first rendered, a transform matrix is applied to its points to correct axes orientation mismatches: https://github.com/brainglobe/bg-atlasapi/issues/73 Once an actor is 'corrected' it spawns labels and silhouettes as needed """ # Flip every actor's orientation if not actor._is_transformed: try: actor._mesh = actor.mesh.clone() actor._mesh.applyTransform(mtx) except AttributeError: # some types of actors dont trasform actor._is_transformed = True else: try: actor.mesh.reverse() except AttributeError: # Volumes don't have reverse pass actor._is_transformed = True # Add silhouette and labels if actor._needs_silhouette and not self.backend: self.plotter.add(actor.make_silhouette().mesh) if actor._needs_label and not self.backend: self.labels.extend(actor.make_label(self.atlas)) def _apply_style(self): """ Sets the rendering style for each mesh """ for actor in self.clean_actors: if settings.SHADER_STYLE != "cartoon": style = settings.SHADER_STYLE else: if self.backend: # notebook backend print( 'Shader style "cartoon" cannot be used in a notebook' ) style = "off" try: actor.mesh.reverse() # flip normals actor.mesh.lighting(style=style) actor._mesh.reverse() actor._mesh.lighting(style=style) except AttributeError: pass def render( self, interactive=None, camera=None, zoom=None, update_camera=True, **kwargs, ): """ Renders the scene. :param interactive: bool. If note settings.INTERACTIVE is used. If true the program's execution is stopped and users can interact with scene. :param camera: str, dict. If none the default camera is used. Pass a valid camera input to specify the camera position when the scene is rendered. :param zoom: float, if None atlas default is used :param update_camera: bool, if False the camera is not changed :param kwargs: additional arguments to pass to self.plotter.show """ logger.debug( f"Rendering scene. Interactive: {interactive}, camera: {camera}, zoom: {zoom}" ) # get zoom zoom = zoom or self.atlas.zoom # get vedo plotter if self.plotter is None: self._get_plotter() # Get camera camera = camera or settings.DEFAULT_CAMERA if isinstance(camera, str): camera = get_camera(camera) else: camera = check_camera_param(camera) if camera["focalPoint"] is None: camera["focalPoint"] = self.root._mesh.centerOfMass() if not self.backend and camera is not None: camera = set_camera(self, camera) # Apply axes correction for actor in self.clean_actors: if not actor._is_transformed: self._prepare_actor(actor) self.plotter.add(actor.mesh) if actor._needs_silhouette or actor._needs_label: self._prepare_actor(actor) # add labels to the scene for label in self.labels: if label._is_added: continue else: label._mesh = label.mesh.clone() self._prepare_actor(label) self.plotter.add(label._mesh) label._is_added = True # Apply style self._apply_style() if self.inset and not self.is_rendered: self._get_inset() # render self.is_rendered = True if not self.backend: # not running in a python script if interactive is None: interactive = settings.INTERACTIVE for txt in self.labels: txt.followCamera(self.plotter.camera) self.plotter.show( interactive=interactive, zoom=zoom, bg=settings.BACKGROUND_COLOR, offscreen=settings.OFFSCREEN, camera=camera.copy() if update_camera else None, interactorStyle=0, rate=40, ) elif self.backend == "k3d": # pragma: no cover # Remove silhouettes self.remove(*self.get_actors(br_class="silhouette")) print( f"[{teal}]Your scene is ready for rendering, use:\n", Syntax("from vedo import show", lexer_name="python"), Syntax("vedo.show(*scene.renderables)", lexer_name="python"), sep="\n", ) else: # pragma: no cover print( f"[{teal}]Your scene is ready for rendering, use:\n", Syntax("from itkwidgets import view", lexer_name="python"), Syntax( "view(scene.plotter.show(*scene.renderables))", lexer_name="python", ), sep="\n", ) def close(self): closePlotter() def export(self, savepath): """ Exports the scene to a .html file for online renderings. :param savepath: str, Path to a .html file to save the export """ logger.debug(f"Exporting scene to {savepath}") _backend = self.backend if not self.is_rendered: self.render(interactive=False) path = Path(savepath) if path.suffix != ".html": raise ValueError("Savepath should point to a .html file") # prepare settings vsettings.notebookBackend = "k3d" # Create new plotter and save to file plt = Plotter() plt.add(self.renderables) plt = plt.show(interactive=False) plt.camera[-2] = -1 with open(path, "w") as fp: fp.write(plt.get_snapshot()) print( f"The brainrender scene has been exported for web. The results are saved at {path}" ) # Reset settings vsettings.notebookBackend = None self.backend = _backend return str(path) def screenshot(self, name=None, scale=None): """ Takes a screenshot of the current view and save it to file. Screenshots are saved in `screenshots_folder` (see Scene) :param name: str, name of png file :param scale: float, >1 for higher resolution """ if not self.is_rendered: self.render(interactive=False) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") name = name or f"brainrender_screenshot_{timestamp}" if ".png" not in name: name += ".png" scale = scale or settings.SCREENSHOT_SCALE print(f"\nSaving new screenshot at {name}\n") savepath = str(self.screenshots_folder / name) logger.debug(f"Saving scene at {savepath}") self.plotter.screenshot(filename=savepath, scale=scale) return savepath def _print_camera(self): pms = get_camera_params(scene=self) focal = pms.pop("focalPoint", None) dst = pms.pop("distance", None) names = [ f"[green bold] '{k}'[/green bold]: [{amber}]{v}," for k, v in pms.items() ] print( f"[{deep_purple_light}]Camera parameters:", f"[{orange}] {{", *names, f"[{orange}] }}", f"[{deep_purple_light}]Additional, (optional) parameters:", f"[green bold] 'focalPoint'[/green bold]: [{amber}]{focal},", f"[green bold] 'distance'[/green bold]: [{amber}]{dst},", sep="\n", ) def keypress(self, key): # pragma: no cover """ Hanles key presses for interactive view -s: take's a screenshot -q: closes the window -c: prints the current camera parameters """ if key == "s": self.screenshot() elif key == "q" or key == "Esc": self.close() elif key == "c": self._print_camera()
# Create the initial positions and velocitites (0,0) of the bobs bob_x = [0] bob_y = [0] x_dot = np.zeros(N+1) # velocities y_dot = np.zeros(N+1) for k in range(1, N + 1): alpha = np.pi / 5 * k / 10 bob_x.append(bob_x[k - 1] + np.cos(alpha) + np.random.normal(0, 0.1)) bob_y.append(bob_y[k - 1] + np.sin(alpha) + np.random.normal(0, 0.1)) # Create the bobs vp = Plotter(title="Multiple Pendulum", axes=0, interactive=0, bg2='ly') vp += Box(pos=(0, -5, 0), length=12, width=12, height=0.7, c="k").wireframe(1) bob = [vp.add(Sphere(pos=(bob_x[0], bob_y[0], 0), r=R / 2, c="gray"))] for k in range(1, N + 1): c = Cylinder(pos=(bob_x[k], bob_y[k], 0), r=R, height=0.3, c=k) vp += c bob.append(c) # Create the springs out of N links link = [None] * N for k in range(N): p0 = bob[k].pos() p1 = bob[k + 1].pos() link[k] = Spring(p0, p1, thickness=0.015, r=R / 3, c="gray") vp += link[k] # Create some auxiliary variables x_dot_m = np.zeros(N+1)
class Morpher: def __init__(self, mesh1, mesh2, n): ############################### init self.n = n # desired nr. of intermediate shapes self.mode = '2d' self.mesh1 = mesh1 self.mesh2 = mesh2 self.merged_meshes = merge(mesh1, mesh2) self.mesh1.lw(4).c('grey2').pickable(False) self.mesh2.lw(4).c('grey1').pickable(False) self.arrow_starts = [] self.arrow_stops = [] self.dottedln = None self.toggle = False self.instructions = ( "Click to add arrows interactively on the left panel\n" "right-click to remove last arrow. Then press:\n" "- m to morph the plane\n" "- c to clear\n" "- g to generate interpolation") self.msg1 = Text2D(self.instructions, pos='top-left', font="VictorMono", bg='g2', alpha=0.6) self.msg2 = Text2D('[output will show here]', pos='top-left', font="VictorMono") sz = self.merged_meshes.diagonalSize() self.plane1 = Grid(sx=sz, sy=sz, resx=50, resy=50).pos(self.merged_meshes.centerOfMass()) self.plane1.wireframe(False).alpha(1).lineWidth(0.1).c('white').lc( 'grey5') self.plane2 = self.plane1.clone().pickable(False) self.plotter = Plotter(N=2, bg='light blue', size=(2000, 1000)) self.plotter.addCallback('left click', self.onleftclick) self.plotter.addCallback('right click', self.onrightclick) self.plotter.addCallback('key press', self.onkeypress) def start( self): ################################################ show stuff paxes = Axes(self.plane1, xyGrid=0, textScale=0.6) self.plotter.show(self.plane1, paxes, self.msg1, self.mesh1, self.mesh2, at=0) self.plotter.show(self.plane2, self.msg2, at=1, mode='image') if len(self.arrow_starts): self.draw(True) self.draw(False) self.msg1.text(self.instructions) self.plotter.show(interactive=True, zoom=1.3).close() def draw(self, toggle=None): #################################### update scene if toggle is None: toggle = self.toggle if toggle: self.msg1.text( "Choose start point or press:\nm to morph the shapes\ng to interpolate" ) self.plotter.remove("displacementArrows", at=0) if not len(self.arrow_starts): return arrows = Arrows2D(self.arrow_starts, self.arrow_stops, c='red4') arrows.name = "displacementArrows" self.plotter.add(arrows, at=0) else: self.msg1.text("Click to choose an end point") self.plotter.remove("displacementPoints", at=0) points = Points(self.arrow_starts, r=15, c='green3', alpha=0.5) points.name = "displacementPoints" self.plotter.add(points, at=0) def onleftclick( self, evt): ############################################ add points msh = evt.actor if not msh or msh.name != "Grid": return pt = self.merged_meshes.closestPoint( evt.picked3d) # get the closest pt on the line self.arrow_stops.append( pt) if self.toggle else self.arrow_starts.append(pt) self.draw() self.toggle = not self.toggle def onrightclick( self, evt): ######################################## remove points if not self.arrow_starts: return self.arrow_starts.pop() if not self.toggle: self.arrow_stops.pop() self.plotter.clear(at=0).addRendererFrame() self.plotter.add([self.plane1, self.msg1, self.mesh1, self.mesh2], at=0, render=False) self.draw(False) self.draw(True) def onkeypress( self, evt): ###################################### MORPH & GENERATE if evt.keyPressed == 'm': ##--------- morph mesh1 based on the existing arrows if len(self.arrow_starts) != len(self.arrow_stops): printc("You must select your end point first!", c='y') return output = [self.mesh1.clone().c('grey4'), self.mesh2, self.msg2] warped_plane = self.plane1.clone().pickable(False) warped_plane.warp(self.arrow_starts, self.arrow_stops, mode=self.mode) output.append(warped_plane + Axes(warped_plane, xyGrid=0, textScale=0.6)) mw = self.mesh1.clone().applyTransform( warped_plane.transform).c('red4') output.append(mw) T_inv = warped_plane.transform.GetInverse() a = Points(self.arrow_starts, r=10).applyTransform(warped_plane.transform) b = Points(self.arrow_stops, r=10).applyTransform(warped_plane.transform) self.dottedln = Lines( a, b, res=self.n).applyTransform(T_inv).pointSize(5) output.append(self.dottedln) self.msg1.text(self.instructions) self.msg2.text("Morphed output:") self.plotter.clear(at=1).addRendererFrame().add(output, at=1) elif evt.keyPressed == 'g': ##------- generate intermediate shapes if not self.dottedln: return intermediates = [] allpts = self.dottedln.points() allpts = allpts.reshape(len(self.arrow_starts), self.n + 1, 3) for i in range(self.n + 1): pi = allpts[:, i, :] m_nterp = self.mesh1.clone().warp(self.arrow_starts, pi, mode=self.mode).c('b3').lw(1) intermediates.append(m_nterp) self.msg2.text("Morphed output + Interpolation:") self.plotter.add(intermediates, at=1) self.dottedln = None elif evt.keyPressed == 'c': ##------- clear all self.arrow_starts = [] self.arrow_stops = [] self.toggle = False self.dottedln = None self.msg1.text(self.instructions) self.msg2.text("[output will show here]") self.plotter.clear(at=0).clear(at=1).addRendererFrame() self.plotter.add([self.plane1, self.msg1, self.mesh1, self.mesh2], at=0) self.plotter.add([self.plane2, self.msg2], at=1)