Skip to content

Instantly share code, notes, and snippets.

@dstansby
Created May 14, 2025 10:07
Show Gist options
  • Select an option

  • Save dstansby/dada2403669d97770c81ca0d445ec453 to your computer and use it in GitHub Desktop.

Select an option

Save dstansby/dada2403669d97770c81ca0d445ec453 to your computer and use it in GitHub Desktop.
"""
Code for generating and working with neuroglancer links.
"""
import json
from typing import TYPE_CHECKING, Any
import neuroglancer
import neuroglancer.random_token
import numpy as np
import numpy.typing as npt
from hipct_data_tools.registration.inventory import RegDataset
if TYPE_CHECKING:
from hipct_data_tools.models.dataset_model import HiPCTDataSet
NEUROGLANCER_INSTANCE = "https://neuroglancer-demo.appspot.com"
class RegistrationNotFoundError(Exception): ...
def dataset_to_layer(
dataset: "HiPCTDataSet", *, opacity: float = 1
) -> neuroglancer.ManagedLayer:
"""
Convert a HiP-CT dataset to a neuroglancer image layer.
"""
invlerp_params = neuroglancer.InvlerpParameters(
range=(dataset.contrast_low, dataset.contrast_high)
)
shader_controls = {"normalized": invlerp_params.to_json()}
source = neuroglancer.LayerDataSource(url=dataset.gcs_url)
layer = neuroglancer.ManagedLayer(
name=dataset.name,
layer=neuroglancer.ImageLayer(
shader_controls=shader_controls,
source=source,
opacity=opacity,
volume_rendering="max",
volume_rendering_depth_samples=256,
),
)
layer.visible = True
return layer
def _get_ng_matrix(
reg_dataset: RegDataset, *, parent_res_um: float, voi_res_um: float
) -> npt.NDArray[Any]:
translation = np.array([[reg_dataset.tx, reg_dataset.ty, reg_dataset.tz]]).T
# The transform maps from child to parent, but we want the other way around
rotation_matrix = reg_dataset.rotation_obj.inv().as_matrix()
translation = np.dot(rotation_matrix, -translation)
return (
parent_res_um
/ voi_res_um
* np.column_stack(
(
reg_dataset.scale * rotation_matrix,
translation,
)
)
)
def add_registration_transform(
*,
voi_dataset: "HiPCTDataSet",
parent_dataset: "HiPCTDataSet",
parent_layer: neuroglancer.ManagedLayer,
) -> None:
"""
Add a registration transform to a neuroglancer layer.
Modifies the layer in place.
Parameters
----------
voi_dataset :
VOI dataset to add transform from.
parent_dataset :
Parent dataset that will be transformed in neuroglancer.
parent_layer :
Neuroglancer layer of the parent dataset to add transform to.
"""
reg_dataset = voi_dataset.registration_dataset
if reg_dataset is None:
raise RegistrationNotFoundError(f"No registration found for {voi_dataset.name}")
if not (
reg_dataset.tx is not None
and reg_dataset.ty is not None
and reg_dataset.tz is not None
and reg_dataset.rotation is not None
and reg_dataset.scale is not None
):
raise RegistrationNotFoundError(
f"At least one registration parameter missing for {reg_dataset.name}"
)
ng_matrix = _get_ng_matrix(
reg_dataset,
parent_res_um=parent_dataset.resolution_um,
voi_res_um=voi_dataset.resolution_um,
)
dimensions = neuroglancer.CoordinateSpace(
names=["x", "y", "z"], units=["um"] * 3, scales=[voi_dataset.resolution_um] * 3
)
parent_layer.source[0].transform = neuroglancer.CoordinateSpaceTransform(
output_dimensions=dimensions, matrix=ng_matrix
)
def neuroglancer_state(
dataset: "HiPCTDataSet",
*,
with_parent: bool = False,
with_child_boxes: bool = False,
) -> neuroglancer.ViewerState:
"""
Get the viewer state for an individual dataset.
Parameters
----------
dataset :
Dataset to create state for.
with_parent :
If `True`, put this dataset inside the parent dataset it is registered to.
Only works for VOI datasets that are registered.
with_child_boxes :
If `True`, include any registered child datasets as bounding box annotations.
"""
layer = dataset_to_layer(dataset, opacity=1)
dimensions = neuroglancer.CoordinateSpace(
names=["x", "y", "z"], units=["um"] * 3, scales=[dataset.resolution_um] * 3
)
selected_layer = neuroglancer.SelectedLayerState(layer=layer.name, visible=True)
viewer_state = neuroglancer.ViewerState(
dimensions=dimensions,
layout="4panel",
projection_orientation=(0.3, 0.2, 0, -0.9),
projectionScale=4096,
selected_layer=selected_layer.to_json(),
position=[dataset.nx / 2, dataset.ny / 2, dataset.nz / 2],
cross_section_scale=10,
show_slices=False,
)
if with_parent:
# Get parent dataset as a neuroglancer layer, transformed
# into the coordinate system of the VOI dataset
reg_dataset = dataset.registration_dataset
assert reg_dataset is not None
parents = {d.name: d for d in dataset.parent_datasets()}
parent = parents[reg_dataset.parent_name]
parent_layer = dataset_to_layer(parent, opacity=0.5)
# Add transform to this dataset
add_registration_transform(
voi_dataset=dataset,
parent_dataset=parent,
parent_layer=parent_layer,
)
# Make sure we append parent layer first, so the main
# layer appears on top of this layer in the slice view
viewer_state.layers.append(parent_layer)
viewer_state.layers.append(layer)
if with_child_boxes:
# Get all children that are registered to this dataset
children = [
child
for child in dataset.child_datasets()
if (
(child.registration_dataset is not None)
and child.registration_dataset.is_registered
and (child.registration_dataset.parent_name == dataset.name)
)
]
if len(children):
new_layer = neuroglancer.LocalAnnotationLayer(
dimensions=viewer_state.dimensions
)
annotations = []
for child in children:
reg_dataset = child.registration_dataset
assert reg_dataset is not None
assert reg_dataset.scale is not None
center = np.array([child.nx, child.ny, child.nz]) / 2
center_transformed = reg_dataset.scale * np.dot(
reg_dataset.rotation_obj.as_matrix(), center
) + np.array([reg_dataset.tx, reg_dataset.ty, reg_dataset.tz])
lower_corner = center_transformed - reg_dataset.scale * center
upper_corner = center_transformed + reg_dataset.scale * center
annotation = neuroglancer.AxisAlignedBoundingBoxAnnotation(
id=f"{child.roi}",
pointA=lower_corner.tolist(),
pointB=upper_corner.tolist(),
description=f"{child.roi} ({child._resolution_str()} um)",
)
annotations.append(annotation)
# new_layer.annotations = annotations
viewer_state.layers["Zoom datasets"] = new_layer
viewer_state.layers["Zoom datasets"].annotations = annotations
viewer_state.layers["Zoom datasets"].tab = "annotations"
viewer_state.selected_layer.layer = "Zoom datasets"
return viewer_state
def neuroglancer_link(
dataset: "HiPCTDataSet",
*,
with_parent: bool = False,
with_child_boxes: bool = False,
) -> str:
"""
Nueroglancer link for an individual dataset.
Parameters
----------
dataset :
Dataset to create link for.
with_parent :
If `True`, put this dataset inside the parent dataset it is registered to.
Only works for VOI datasets that are registered.
"""
state = neuroglancer_state(
dataset, with_parent=with_parent, with_child_boxes=with_child_boxes
)
return f"{NEUROGLANCER_INSTANCE}/#!{json.dumps(state.to_json(), separators=(',', ':'))}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment