Skip to content

Segmented Nuclei in a Mouse Blastocyst

Segmentation labels over a z-stack, with one slider driving both layers.

Data: IDR idr0062A · EBI

ts
import GUI from "lil-gui";
import {
  Idetik,
  ImageLayer,
  LabelLayer,
  OmeZarrImageSource,
  OrthographicCamera,
  Color,
  PanZoomControls,
  Viewport,
} from "idetik";

// A 3D (z-stack) OME-Zarr image with a matching label segmentation overlaid on
// top. The image and labels share the same XY/Z geometry, so a single z-slice
// slider drives both layers.
const imageUrl =
  "https://uk1s3.embassy.ebi.ac.uk/idr/zarr/v0.4/idr0062A/6001240.zarr";
const labelsUrl = `${imageUrl}/labels/0`;

const imageSource = await OmeZarrImageSource.fromHttp({ url: imageUrl });
const labelsSource = await OmeZarrImageSource.fromHttp({ url: labelsUrl });

const lod = 0;
const dimensions = imageSource.getDimensions();

const zLod = dimensions.z!.lods[lod];
const zTranslation = zLod.translation;
const zScale = zLod.scale;
const zSize = zLod.size;
const zMidIndex = Math.floor((zSize - 1) / 2);
const zMidPoint = zTranslation + zMidIndex * zScale;
const xLod = dimensions.x!.lods[lod];
const xStopPoint = xLod.size * xLod.scale;
const yLod = dimensions.y!.lods[lod];
const yStopPoint = yLod.size * yLod.scale;

const sliceCoords = {
  c: [0, 1],
  z: zMidPoint,
};

const labelsSliceCoords = {
  c: [0],
  z: zMidPoint,
};

const imageLayer = new ImageLayer({
  source: imageSource,
  sliceCoords,
  channelProps: [
    {
      visible: true,
      color: Color.GREEN,
      contrastLimits: [0, 1024],
    },
    {
      visible: true,
      color: Color.MAGENTA,
      contrastLimits: [0, 1024],
    },
  ],
});

const pickInfoEl = document.querySelector<HTMLDivElement>("#pick-info")!;
const controls = {
  zIndex: zMidIndex,
  pickMode: "Fill",
  colorCycle: "Default",
};
let outlineMode = false;

function createLabelsLayer() {
  return new LabelLayer({
    source: labelsSource,
    sliceCoords: labelsSliceCoords,
    opacity: 0.55,
    blendMode: "normal",
    outlineSelected: outlineMode,
    onPickValue: (info) => {
      const { world, value } = info;
      pickInfoEl.innerHTML = `
        World: (${world[0].toFixed(1)}, ${world[1].toFixed(1)}, ${world[2].toFixed(1)})<br/>
        Label: ${value}
      `;
      if (!outlineMode) {
        labelsLayer.setColorMap({
          cycle: Array.from(labelsLayer.colorMap.cycle),
          lookupTable: new Map([[value, Color.WHITE]]),
        });
      }
    },
  });
}

let labelsLayer = createLabelsLayer();

const camera = new OrthographicCamera({
  left: 0,
  right: xStopPoint,
  top: 0,
  bottom: yStopPoint,
});
const canvas = document.querySelector<HTMLCanvasElement>("canvas")!;
const viewport = new Viewport({
  domElement: canvas,
  camera,
  cameraControls: new PanZoomControls(camera),
  layers: [imageLayer, labelsLayer],
});
const idetik = new Idetik({ canvas, viewports: [viewport] });

function setZIndex(index: number) {
  const z = zTranslation + index * zScale;
  sliceCoords.z = z;
  labelsSliceCoords.z = z;
}

const colorCycles: Record<string, Color[] | undefined> = {
  Default: undefined,
  CMY: [Color.CYAN, Color.MAGENTA, Color.YELLOW],
  RGB: [Color.RED, Color.GREEN, Color.BLUE],
};

function applyColorCycle() {
  const cycle = colorCycles[controls.colorCycle];
  labelsLayer.setColorMap(cycle ? { cycle } : {});
}

const gui = new GUI({ width: 280 }).close();
gui
  .add(controls, "zIndex", 0, zSize - 1, 1)
  .name("Z slice")
  .onChange(setZIndex);
gui
  .add(controls, "pickMode", ["Fill", "Outline"])
  .name("Pick mode")
  .onChange((mode: string) => {
    outlineMode = mode === "Outline";
    viewport.removeLayer(labelsLayer);
    labelsLayer = createLabelsLayer();
    applyColorCycle();
    viewport.addLayer(labelsLayer);
  });
gui
  .add(controls, "colorCycle", Object.keys(colorCycles))
  .name("Color cycle")
  .onChange(applyColorCycle);

idetik.start();

Released under the MIT License. © 2026-present Chan Zuckerberg Biohub, Inc.