Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions docs/cli_tut_scripts/evaluate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env python
"""Evaluate segmentation accuracy against pseudo ground truth.

Compares segments to labels at a given Z-slice and reports false merges,
false splits, and overall accuracy.

Usage:
python evaluate.py # default z=30
python evaluate.py -z 15 # choose slice
"""

import argparse

import numpy as np
import zarr


def evaluate(z: int = 30) -> None:
segments = np.array(zarr.open("cells3d.zarr/segments", mode="r")[z])
labels = np.array(zarr.open("cells3d.zarr/labels", mode="r")[z])

s_to_l: dict[int, int] = {}
false_merges = 0
l_to_s: dict[int, int] = {}
false_splits = 0

for s, l in zip(segments.flat, labels.flat):
if s not in s_to_l:
s_to_l[s] = l
elif s_to_l[s] != l:
false_merges += 1
print(f"Falsely merged labels: ({l}, {s_to_l[s]}) with segment {s}")
if l not in l_to_s:
l_to_s[l] = s
elif l_to_s[l] != s:
false_splits += 1
print(f"Falsely split label: {l} into segments ({s}, {l_to_s[l]})")

print(f"False merges: {false_merges}")
print(f"False splits: {false_splits}")
accuracy = (len(s_to_l) - (false_merges + false_splits)) / len(s_to_l)
print(f"Accuracy: {accuracy:.4f}")


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Evaluate segmentation accuracy.")
parser.add_argument("-z", type=int, default=30, help="Z-slice index (default: 30)")
args = parser.parse_args()
evaluate(args.z)
94 changes: 94 additions & 0 deletions docs/cli_tut_scripts/prepare_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
#!/usr/bin/env python
"""Prepare toy data for the Volara CLI tutorial.

Creates a zarr container (cells3d.zarr) with:
- raw: 2-channel fluorescence microscopy (nuclei + membranes)
- mask: binary mask from simple thresholding
- labels: pseudo ground truth via connected components
- affs: perfect affinities derived from labels

Usage:
python prepare_data.py
"""

import numpy as np
from funlib.geometry import Coordinate
from funlib.persistence import prepare_ds
from scipy.ndimage import label
from skimage import data
from skimage.filters import gaussian

from volara.tmp import seg_to_affgraph

# Download the cells3d sample dataset and rearrange to (C, Z, Y, X)
cell_data = (data.cells3d().transpose((1, 0, 2, 3)) / 256).astype(np.uint8)

# Metadata
offset = Coordinate(0, 0, 0)
voxel_size = Coordinate(290, 260, 260)
axis_names = ["c^", "z", "y", "x"]
units = ["nm", "nm", "nm"]

# Raw data
print("Writing raw data...")
cell_array = prepare_ds(
"cells3d.zarr/raw",
cell_data.shape,
offset=offset,
voxel_size=voxel_size,
axis_names=axis_names,
units=units,
mode="w",
dtype=np.uint8,
)
cell_array[:] = cell_data

# Binary mask
print("Writing mask...")
mask_array = prepare_ds(
"cells3d.zarr/mask",
cell_data.shape[1:],
offset=offset,
voxel_size=voxel_size,
axis_names=axis_names[1:],
units=units,
mode="w",
dtype=np.uint8,
)
cell_mask = np.clip(gaussian(cell_data[1] / 255.0, sigma=1), 0, 255) * 255 > 30
not_membrane_mask = (
np.clip(gaussian(cell_data[0] / 255.0, sigma=1), 0, 255) * 255 < 10
)
mask_array[:] = cell_mask * not_membrane_mask

# Labels via connected components
print("Writing labels...")
labels_array = prepare_ds(
"cells3d.zarr/labels",
cell_data.shape[1:],
offset=offset,
voxel_size=voxel_size,
axis_names=axis_names[1:],
units=units,
mode="w",
dtype=np.uint8,
)
labels_array[:] = label(mask_array[:])[0]

# Affinities from ground truth
print("Writing affinities...")
affs_array = prepare_ds(
"cells3d.zarr/affs",
(3,) + cell_data.shape[1:],
offset=offset,
voxel_size=voxel_size,
axis_names=["offset^"] + axis_names[1:],
units=units,
mode="w",
dtype=np.uint8,
)
affs_array[:] = (
seg_to_affgraph(labels_array[:], nhood=[[1, 0, 0], [0, 1, 0], [0, 0, 1]]) * 255
)

print("Done! Created cells3d.zarr with datasets: raw, mask, labels, affs")
67 changes: 67 additions & 0 deletions docs/cli_tut_scripts/show_slice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env python
"""Save a 2D slice of a zarr array as a PNG image.

Usage:
python show_slice.py cells3d.zarr/raw # default z=30, output=slice.png
python show_slice.py cells3d.zarr/raw -z 15 # choose slice
python show_slice.py cells3d.zarr/raw -o raw.png # choose output filename
python show_slice.py cells3d.zarr/fragments -z 30 -o frags.png
"""

import argparse
import random

import matplotlib.pyplot as plt
import numpy as np
import zarr


def save_slice(store_path: str, z: int = 30, output: str = "slice.png") -> None:
arr = zarr.open(store_path, mode="r")
if not isinstance(arr, zarr.Array):
raise SystemExit(f"Expected a zarr array at '{store_path}', got a group.")

# Determine the slice based on array shape.
# Shapes we expect:
# (Z, Y, X) -> labels, fragments, segments
# (C, Z, Y, X) -> raw (2-ch), affinities (3-ch)
ndim = arr.ndim
if ndim == 3:
data = np.array(arr[z])
elif ndim == 4:
data = np.array(arr[:, z])
else:
raise SystemExit(f"Unsupported array dimensions: {ndim} (expected 3 or 4)")

# Render based on data type and shape.
if data.dtype in (np.uint32, np.uint64):
# Label data: randomize non-zero label colors for visibility.
data = data.astype(np.float32)
labels = [x for x in np.unique(data) if x != 0]
relabelling = random.sample(range(1, len(labels) + 1), len(labels))
for old, new in zip(labels, relabelling):
data[data == old] = new
plt.imsave(output, data, cmap="jet")
elif data.ndim == 2:
# Single-channel grayscale.
plt.imsave(output, data, cmap="gray")
elif data.shape[0] == 2:
# 2-channel: map to (ch0, ch1, ch0) -> magenta/green composite.
rgb = np.stack([data[0], data[1], data[0]], axis=-1)
plt.imsave(output, rgb)
elif data.shape[0] == 3:
# 3-channel (e.g. affinities): treat as RGB.
plt.imsave(output, np.moveaxis(data, 0, -1))
else:
raise SystemExit(f"Don't know how to display {data.shape[0]}-channel data")

print(f"Saved {output}")


if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Save a zarr slice as PNG.")
parser.add_argument("store", help="Path to zarr array (e.g. cells3d.zarr/raw)")
parser.add_argument("-z", type=int, default=30, help="Z-slice index (default: 30)")
parser.add_argument("-o", "--output", default="slice.png", help="Output PNG path")
args = parser.parse_args()
save_slice(args.store, args.z, args.output)
Loading