Changelog#
This is a selection of features added, changes made and bugs fixed with each version. For a full list of changes please see the commits history on the Github repository.
dev#
Date: ongoing
Breaking#
Octarinenow requirespygfx>=0.17-0.17renamed two symbols in the shader API thatoctarine's custom shaders build on (lighting_phong()no longer takesis_front, andphysical_albeidois now spelledphysical_albedo)- the
octarine.video_helpersmodule (i.e.make_rotation_video) has been removed in favour ofoctarine.Animation, which does the same job with better output (no stray alpha channel, no rescaling of odd-sized frames) and without the undeclaredscipy/tqdmimports.make_rotation_video(v, "rotation.mp4", n_frames=100, fps=30, axis="z")becomesoc.Animation(v, fps=30).orbit(axis="view", recenter=False, duration=100 / 30) .render("rotation.mp4")- though for most scenes the new default,oc.Animation(v).orbit().render("rotation.mp4"), is what you actually want: a turntable rotation around the scene rather than a roll about the camera's own axis (see Animations)
Improvements#
- new
octarine.Animationclass: a timeline of camera moves that can be played in the viewer or rendered to a video. Segments are appended one after the other -orbit(around the whole scene or given objects, about any axis, optionally re-framing the target first),move_to(a named view or a camera state grabbed fromViewer.get_view, swinging around what you are looking at rather than through it),zoomandhold- each with its own duration and easing.renderwrites.mp4/.gif(viaimageio), a folder of numbered PNGs (no extra dependencies) or plain numpy arrays, frame by frame off the timeline rather than the wall clock, and works on an offscreen viewer (see Animations) - new
Viewer.get_boundsmethod: like theViewer.boundsproperty (which now delegates to it) but for individual objects -v.get_bounds("neuron_1") Viewer.get_viewgained aviewparameter:v.get_view("XZ")returns the camera state thatset_view("XZ")would produce, without moving the camera (or nudging any linked viewers)- control panel: new "Animation" tab - set up an orbit or capture the views you want to fly through as keyframes, preview it in the viewer and record it to file. Recording renders one frame per event-loop tick, so the window stays responsive and long renders can be cancelled (see Animations)
- new
Viewer.set_ambient_occlusionmethod (also available asadd_effect('ao')): screen-space ambient occlusion, i.e. the shadowing that ambient light would produce in creases, cavities and where objects touch -pygfxhas no ambient occlusion of its own beyond bakedao_maptextures. Occlusion is estimated from the depth buffer, so no preprocessing and no extra geometry pass is needed, and the whole thing costs well under a millisecond per frame at the default 16 samples.radius(defaulting to 4% of the scene's diagonal) is the parameter that has to match the scene;intensity,bias,samples,powerandblurtune the look, anddebug=Truerenders the raw occlusion (see Ambient occlusion). With an automatic radius the effect now also tracks the scene, i.e. the radius is re-derived as objects are added or removed - control panel: the "Effects" tab now has one collapsible section per effect (the checkbox switches the effect on, the arrow expands its parameters) and gained controls for
Viewer.shadowsandViewer.set_ambient_occlusion(see GUI Controls) - new
Viewer.link/Viewer.unlinkmethods (plus aViewer.linkedproperty): keep the camera synchronised between two or more viewers, so that panning, rotating or zooming in one of them does the same in all the others - handy for comparing things side by side. Links are symmetrical and transitive, andsync/excludelet you share only part of the camera state (e.g. rotate together but zoom separately). Viewers need not show the same data or use the same controls (see Linking viewers) - new
Viewer.set_subsurfacemethod (plus asubsurfaceparameter forViewer.add_mesh): render meshes as translucent, so that backlit regions glow and shading eases past the terminator instead of dropping off abruptly - the look of skin, wax, marble or thin neurites. Tunable viascatter_color,thickness,distortion,falloff,wrapandglow; composes withset_silhouette(see Subsurface scattering) - new
Viewer.set_bg_gradientmethod: use a radial ("studio") gradient as background - a soft pool of light behind the object fading into near-black towards the edges of the frame. Comes with six presets (graphite,cinematic,warm,olive,burgundy,halo), each of which can be tweaked viacolors,center,radius,falloffandvignette(see Background). The presets are also available from a newBackgrounddropdown in the GUI controls (both the Qt panel and the Jupyter toolbar) Viewer.set_bgcolornow also takes two colors (for a bottom-to-top gradient) or four colors (one per corner)- new
Viewer.add_tubesmethod: render skeletons with a per-node radial profile as tubes, straight from the coefficients with no mesh in between. The surface is generated in the vertex shader, so angular level of detail vian_theta,kandk_normalis a re-draw rather than a re-upload;axial_lodthins the skeleton without touching its topology. Frames are realigned onto one rotation-minimizing chain over the whole tree, so the quads spanning a branch point are no longer twisted. Note that a swept surface self-intersects wherever the radius outruns the centreline's curvature, which rasterised skeletons routinely trigger; dropping harmonics viakis currently the only effective lever against it (see Tubes) - new
Viewer.headlightproperty (plus aheadlightparameter foroctarine.Viewerand a checkbox in the GUI controls): link the light to the camera such that objects are always lit from the front; accepts a float or tuple to also set the light's offset from the camera's axis (see Lighting) - new
Viewer.lightsproperty: list all light sources illuminating the scene Viewer.add_sparse_volume: newmode="surface"which renders the volume as a shaded isosurface (normals from the volume's gradient) instead of a flat blob; the level is set via the newthresholdparameter (see Sparse Volumes)Viewer.add_sparse_volume: newsmoothingparameter formode="surface"which widens the filter the surface normal is taken from, removing the voxel-scale stipple from the shading. The isosurface itself is left alone, so the silhouette is unchanged and no thin structures are lost (see Smoothing)-
Viewer.add_sparse_volume:mode="density"now accumulates properly instead of saturating on the first voxel; the extinction per voxel is set via the newdensityparameter (previously it was tied toopacity, whose default of1made the volume opaque at the first sample) -
Viewer.add_sparse_volumenow accepts run-length encoded voxels as an(N, 4)array of(x, y, z, x_run_length)- the layout DVID'ssparsevolendpoint returns (plus theoctarine.VoxelRunscontainer). Runs are rendered from a bit-per-voxel bitmask with a sparse two-level index, which uses ~23x less GPU memory than the byte-per-voxel atlas (14 MB vs 328 MB for a 31M-voxel neuron) and never needs the coordinates materialized. Binary occupancy only - passingvaluesalongside runs raises. Binary(N, 3)coordinates can opt in withmethod="bitmask"(see Sparse Volumes) -
Viewer.screenshotgained asupersampleparameter: the frame is rendered at N times the requested resolution and filtered back down, which - unlike the post-processing anti-aliasing that runs during interaction - actually resolves sub-pixel detail instead of smoothing over it. Thin lines, dense point clouds and curved silhouettes benefit the most. It defaults to2(4is as good as it realistically gets) and the image dimensions are unaffected. Effects that are sized in pixels (outlines, depth of field, EDL, the occlusion blur) are scaled along, so the picture keeps its look. Note thatAnimation.renderdefaults tosupersample=1, since there the cost is paid per frame
Fixes#
- control panel: the "Browse..." buttons no longer flash up a file dialog that closes again immediately. The static
QFileDialoghelpers are modal, i.e. they run an event loop of their own - which does not survive every way of hosting the Qt loop (IPython's Qt input hook quits it as soon as the prompt wants control back, taking the dialog with it). The dialogs are now opened non-modally and report back through a signal - adding many objects one by one is no longer quadratic in the number of objects: the bounding box visual, the camera centering, the fit of the shadow-casting lights, the ambient occlusion radius and the environment all have to follow the scene, and each of them walks every visual on the canvas. They are now re-fitted once, immediately before the next frame, rather than on every single
add. Filling a viewer with 800 objects went from ~11 s to ~0.3 s; the effect is most pronounced with shadows and ambient occlusion (now on by default), but the centering alone accounted for most of it. Note that this means those properties only catch up on the next draw -Viewer.boundsis unaffected and always reports the scene as it currently stands, as is an explicitViewer.center_camera, which centers there and then. A camera you set up yourself after anadd(e.g. viacamera.show_object) also still wins, i.e. the deferred centering does not overrule it add_volume/add_sparse_volume: a hex color string (e.g."#ff9955") raisedColormap '#ff9955' not foundinstead of being used as a single color- fixed colormaps wrapping around: values at the very top of
climsampled the colormap at texcoord1.0, where pygfx's defaultwrap="repeat"blended the last color with the (transparent, ifhide_zero=True) first one - halving both color and alpha. Affects volumes and sparse volumes Viewer.screenshotraisedAttributeErroron an on-screen canvas: it forced the frame through a privaterendercanvasmethod that has since been renamed. It now uses the publiccanvas.force_draw()
Breaking#
- shadows, the camera-linked headlight and ambient occlusion are now on by default, i.e. scenes look different out of the box: objects cast shadows onto each other, are always lit from the front and get contact shadows in their creases and cavities. Each can be switched off individually via the new
shadows/ambient_occlusionparameters and the existingheadlightparameter (or the matching properties/methods), e.g.oc.Viewer(shadows=False, headlight=False, ambient_occlusion=False)gets you the previous look. Note that fitting the shadows and the occlusion radius to the scene costs an extra pass over the objects, which is made once per frame in which the scene changed opacityno longer scales the extinction per voxel inadd_sparse_volume(mode="density"); it is now a plain global opacity in all modes anddensitytakes its place
To install the current dev version of octarine:
pip uninstall octarine3d -y
pip install git+https://github.com/schlegelp/octarine.git
Version 0.7.0#
Date: 02/08/26
Improvements#
- new
Viewer.set_scalebarmethod: overlay a scale bar that either shows a fixed distance or dynamically adjusts to the zoom level (see Scale bar) Viewer.add_meshnow exposes pygfx's mesh shaders via the newshaderparameterViewer.add_linesnow accepts an array of line widths (one per point)
Fixes#
- fixed issue in
Viewer.hide_selected
Version 0.6.0#
Date: 18/07/26
Breaking#
Octarinenow requirespygfx>=0.16(needed for the new custom shaders)
Improvements#
- new
Viewer.add_sparse_volumemethod (plus theoctarine.VoxelCloudcontainer): render tens of millions of voxels from an(N, 3)array of coordinates using a custom brick-based raycasting shader (see Sparse Volumes) - new
Viewer.set_silhouettemethod (plus asilhouetteparameter foradd_mesh): Neuroglancer-style silhouette rendering for meshes (see Effects & Shading) - new
Viewer.set_depth_of_fieldmethod: focal-blur post-processing with continuous autofocus (see Effects & Shading) Viewer.add_effect: new effects"noise","fog","depth"(normalized depth / depth cueing),"normal"and"bloom"; newdisableparameter to remove effectsViewer.add_points: new marker options (marker,size_space,edge_size_space,min_size,max_size,edge_width,edge_color,edge_mode,min_edge_width) backed by a custom points shader (see Markers, sizes and edges)- control panel: new "Screenshot" (save to file or clipboard) and "Effects" tabs
- IPython: the event loop is now started with
%gui qtinstead of%gui qt6
Fixes#
- legend: color button and visibility checkbox are shown from the start; the color picker is synced to the active object
Full Changelog: v0.5.0...v0.6.0
Version 0.5.0#
Date: 12/06/26
Improvements#
Viewer.on_double_clicknow also accepts a custom callable (see Custom callbacks)- new
Viewer.objects_groupedproperty - legend improvements: group contents are created lazily, new filter field, hovering over an entry highlights the object in the viewer, group labels show member counts
Full Changelog: v0.4.1...v0.5.0
Version 0.4.1#
Date: 03/06/26
Fixes#
- don't import the
controlsmodule (and hencePySide6) at top level
Full Changelog: v0.4.0...v0.4.1
Version 0.4.0#
Date: 10/04/26
Improvements#
- objects can now be assigned to a
group(e.g.Viewer.add(x, group="my group")); groups show up as collapsible entries in the legend (see Grouping objects) Viewer.add_animation: newon_error="log"option
Fixes#
- legend: the color button now follows programmatic color changes; long labels are truncated
- fixed the color picker when multiple viewers are open
Full Changelog: v0.3.5...v0.4.0
Version 0.3.5#
Date: 24/03/26
Improvements#
- new
octarine.video_helpersmodule withmake_rotation_video(removed again indev, seeoctarine.Animation) - the event-loop warning can now be suppressed
Fixes#
- fixed Jupyter detection
Full Changelog: v0.3.4...v0.3.5
Version 0.3.4#
Date: 27/02/26
Improvements#
- relaxed the pinned
pygfxversion requirement
Fixes#
Viewer.add: check for a converter before trying to iterate (e.g. don't treatgfx.Geometryas an iterable)
Full Changelog: v0.3.3...v0.3.4
Version 0.3.3#
Date: 22/10/25
Breaking#
- dropped support for Python 3.9 -
Octarinenow requires Python>=3.10
Improvements#
- new
Viewer.add_effectmethod for post-processing effects such as Eye-Dome Lighting
Fixes#
- fixed setting/getting
Viewer.max_fps - fixed an issue in the screenshot function
Full Changelog: v0.3.2...v0.3.3
Version 0.3.2#
Date: 17/10/25
Breaking#
- adapted to
pygfx0.14: theViewer.blend_modeproperty is deprecated in favour of the newViewer.set_alpha_modemethod (alpha modes are also set automatically based on object opacity)
Fixes#
Viewer.screenshot: fixed transparency whenalpha=True
Full Changelog: v0.3.1...v0.3.2
Version 0.3.1#
Date: 27/05/25
Fixes#
- follow changes to
Texture/Mapin newerpygfxversions Viewer.screenshotnow resolves file paths (e.g.~/screenshot.png)- don't try to start an event loop for offscreen canvases
- handle
ImportErrorgracefully when initializing the GUI event loop
Full Changelog: v0.3.0...v0.3.1
Version 0.3.0#
Date: 19/03/25
Breaking#
- drop support for Python 3.8 (follows
pygfx)
Improvements#
- bumps minimum version of pygfx to
0.9.0. - added render trigger options (see Render Triggers)
- new selection widget (see Selecting Objects)
- new transform widget (see Moving Objects)
- various improvements in the documentation
Full Changelog: v0.2.5...v0.3.0
Version 0.2.5#
Date: 31/09/24
Fixes#
- fixed an segfault issue
Full Changelog: v0.2.4...v0.2.5
Version 0.2.4#
Date: 28/09/24
Fixes#
- fix an issue when trimesh is installed without the optional scipy dependency
Full Changelog: v0.2.3...v0.2.4
Version 0.2.3#
Date: 27/09/24
Fixes#
- fixes an issue with requirements
Full Changelog: v0.2.2...v0.2.3
Version 0.2.2#
Date: 27/09/24
Improvements#
- existing viewers are tracked in
octarine.viewers - allow using matplotlib-style line patterns (
-,--, etc.)
Full Changelog: v0.2.1...v0.2.2
Version 0.2.1#
Date: 19/09/24
Fixes#
- fixes an issue with
importlib-metadatadependency
Full Changelog: v0.2.0...v0.2.1
Version 0.2.0#
Date: 19/09/24
Improvements#
- added a basic picking system
- color picker now shows alpha channel
- general improvements to volume rendering
- use
Viewer.blend_modeto set blend mode Viewer.set_viewnow also accepts a dictionary with camera state
Fixes#
- fixes an issue with
Viewer.screenshot
Full Changelog: v0.1.4...v0.2.0
Version <0.2.0#
For earlier versions, please see the commit history.