Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
007243d
make VolumeLayer non-protected
brokkoli71 Jul 16, 2025
f7deda1
add VolumeLayer.edit contextmanager
brokkoli71 Jul 22, 2025
69daad3
Merge branch 'master' into create-volume-annotations
brokkoli71 Jul 22, 2025
9e80718
format
brokkoli71 Jul 22, 2025
cd63d9f
lint
brokkoli71 Jul 22, 2025
96cfa07
add @pytest.mark.use_proxay
brokkoli71 Jul 22, 2025
6481415
format
brokkoli71 Jul 22, 2025
a7e8211
Merge branch 'refs/heads/master' into create-volume-annotations
brokkoli71 Jul 29, 2025
c6cf61e
Merge branch 'refs/heads/master' into create-volume-annotations
brokkoli71 Jul 30, 2025
892773a
rechunk mags with Zarr3Config to have expected volume annotations format
brokkoli71 Jul 30, 2025
d85c715
Enum for VolumeLayerEditMode
brokkoli71 Jul 30, 2025
487a1c5
test_edit_volume_annotation, test_save_edited_volume_annotation and t…
brokkoli71 Jul 30, 2025
393e9ee
format and lint
brokkoli71 Jul 30, 2025
f76231e
Merge branch 'master' into create-volume-annotations
brokkoli71 Jul 30, 2025
82cc58c
edited annotation uploadable
brokkoli71 Aug 9, 2025
88c5220
Merge branch 'master' into create-volume-annotations
brokkoli71 Aug 9, 2025
32542c1
set zarr3 datatype for wk.Annotation.download
brokkoli71 Aug 12, 2025
cab2679
implement VolumeLayerEditMode.MEMORY
brokkoli71 Aug 12, 2025
70f3b0d
get voxel_size from nml
brokkoli71 Aug 12, 2025
e7871d6
init zip when creating volume layer
brokkoli71 Aug 17, 2025
94e1626
store volume layer in zip of zips and save downloaded annotation volu…
brokkoli71 Aug 29, 2025
7b7e6fa
use volume_layers_root argument of Annotation.download
brokkoli71 Aug 29, 2025
c9bc2ca
Merge branch 'master' into create-volume-annotations
brokkoli71 Aug 29, 2025
b44b14a
update cassettes
brokkoli71 Aug 29, 2025
15d526b
load editing layer into tensorstore, as MemoryFileSystem is not compa…
brokkoli71 Aug 31, 2025
c103a9f
typing and format
brokkoli71 Aug 31, 2025
286fd80
avoid tensorstore array memory path collision
brokkoli71 Aug 31, 2025
bbfc7a3
rename nml to skeleton in offline_merger_mode.py
brokkoli71 Sep 5, 2025
ff05931
replace volume_layers_root with temp dir and incorporate further review
brokkoli71 Sep 5, 2025
635dcc0
Merge branch 'master' into create-volume-annotations
brokkoli71 Sep 5, 2025
cd58e96
fix format mistakes
brokkoli71 Sep 5, 2025
3b5fb00
update cassettes
brokkoli71 Sep 5, 2025
f628748
extract VolumeLayer to volume_layer.py
brokkoli71 Sep 5, 2025
aa66ed8
update cassette
brokkoli71 Sep 5, 2025
76bf92a
faster VolumeLayer.edit with SequentialExecutor
brokkoli71 Sep 5, 2025
93e9272
improve test_edit_volume_annotation
brokkoli71 Sep 5, 2025
e8c157c
document changes
brokkoli71 Sep 5, 2025
e77a40f
update learned_segmenter.py to use VolumeLayer.edit
brokkoli71 Sep 6, 2025
e56cc7e
lint
brokkoli71 Sep 6, 2025
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
1 change: 1 addition & 0 deletions webknossos/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ For upgrade instructions, please check the respective _Breaking Changes_ section
### Breaking Changes

### Added
- Added context manager `VolumeLayer.edit` for creating and modifying volume annotations. [#1340](https://github.com/scalableminds/webknossos-libs/pull/1340)

### Changed

Expand Down
12 changes: 6 additions & 6 deletions webknossos/examples/WIP/merge_trees_at_closest_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@

import webknossos as wk

nml = wk.Skeleton.load("trees-in-groups.nml")
skeleton = wk.Skeleton.load("trees-in-groups.nml")

# Probably we want to keep groups and normal trees in distinct collections (groups/trees).
# For many use-cases a common view groups_and_trees would be great, but not here:
for group in nml.groups.values(): # groups is a dict with the name as keys
for group in skeleton.groups.values(): # groups is a dict with the name as keys
min_distance_graph = G = nx.Graph()
for (tree_idx_a, tree_a), (tree_idx_b, tree_b) in combinations(
enumerate(group.flattened_trees()), 2
):
pos_a = (
tree_a.get_node_positions() * nml.voxel_size
tree_a.get_node_positions() * skeleton.voxel_size
) # or tree_a.get_node_positions_nm?
pos_b = tree_b.get_node_positions() * nml.voxel_size
pos_b = tree_b.get_node_positions() * skeleton.voxel_size
node_idx_a, node_idx_b, distance = wk.geometry.closest_pair(pos_a, pos_b)
G.add_edge((tree_idx_a, node_idx_a), (tree_idx_b, node_idx_b), weight=distance)
new_edges = nx.algorithms.tree.mst.minimum_spanning_edges()
Expand All @@ -35,7 +35,7 @@
final_tree.name = group.name
final_tree.group = None

del nml.groups[group.name]
del skeleton.groups[group.name]
# or
group.delete()
# The latter only works if everything is double-linked.
Expand All @@ -44,4 +44,4 @@
# to do the double-linking. Simply dict-like insertions can't work then:
# nml["tree-name"] = Tree()

nml.save("merged-trees.nml")
skeleton.save("merged-trees.nml")
18 changes: 9 additions & 9 deletions webknossos/examples/WIP/offline_merger_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@

import webknossos as wk

# A merger mode nml with every tree corresponding to a new merged segment is available.
# A merger mode skeleton with every tree corresponding to a new merged segment is available.
# All segments in which a node is placed should be merged and saved as a new dataset.

# for local nml:
nml = wk.open("merger-mode.nml")
# for local skeleton:
skeleton = wk.open("merger-mode.skeleton")
# wk.Skeleton.load or wk.open_skeleton works, too (and is type-safe)

# for online annotation:
annotation = wk.Annotation.download(
"https://webknossos.org/annotations/Explorational/6114d9410100009f0096c640"
)
nml = annotation.skeleton
skeleton = wk.Annotation.download(
"https://webknossos.org/annotations/Explorational/6114d9410100009f0096c640",
skip_volume_data=True,
).skeleton
# should this save anything to disk, or just happen in memory?

dataset = wk.download(nml.dataset_name, organization=nml.dataset_organization)
dataset = wk.download(skeleton.dataset_name, organization=skeleton.dataset_organization)
# asks for auth token, persisted into .env or similar config file (maybe use xdg-path?)

# sub-part access via dicts or dict-like classes
Expand All @@ -28,7 +28,7 @@

segmentation_data = view.read()

for tree in nml.trees(): # nml.trees() is a flattened iterator of all trees
for tree in skeleton.trees: # skeleton.trees() is a flattened iterator of all trees
segment_ids_in_tree = set(
segmentation_data[tuple(node.position - view.topleft)] for node in tree.nodes
)
Expand Down
9 changes: 5 additions & 4 deletions webknossos/examples/apply_merger_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ def main() -> None:
# Opening a merger mode annotation #
####################################

nml = wk.Annotation.download(
"https://webknossos.org/annotations/6748612b0100001101c81156"
skeleton = wk.Annotation.download(
"https://webknossos.org/annotations/6748612b0100001101c81156",
skip_volume_data=True,
).skeleton

###############################################
Expand All @@ -33,7 +34,7 @@ def main() -> None:
##############################

segment_id_mapping = {}
for tree in nml.flattened_trees():
for tree in skeleton.flattened_trees():
base = None
for node in tree.nodes:
segment_id = in_mag1.read(
Expand All @@ -44,7 +45,7 @@ def main() -> None:
segment_id_mapping[segment_id] = base

print(
f"Found {len(list(nml.flattened_trees()))} segment id groups with {len(segment_id_mapping)} nodes"
f"Found {len(list(skeleton.flattened_trees()))} segment id groups with {len(segment_id_mapping)} nodes"
)
print(segment_id_mapping)

Expand Down
26 changes: 5 additions & 21 deletions webknossos/examples/learned_segmenter.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import os
from functools import partial
from tempfile import TemporaryDirectory

import numpy as np
from skimage import feature
Expand All @@ -19,7 +17,6 @@ def main() -> None:
# Step 1: Read the training data from the annotation and the dataset's color
# layer (the data will be streamed from WEBKNOSSOS to our local computer)
training_data_bbox = annotation.user_bounding_boxes[0] # type: ignore[index]
new_dataset_name = f"{annotation.dataset_name.replace(' ', '_')}_segmented"
with wk.webknossos_context("https://webknossos.org"):
dataset = annotation.get_remote_annotation_dataset()

Expand Down Expand Up @@ -58,28 +55,15 @@ def main() -> None:
assert segmentation.max() < 256
segmentation = segmentation.astype("uint8")

# Step 5: Bundle everything as a WEBKNOSSOS layer and upload to wK for viewing and further work
with TemporaryDirectory() as tempdir:
new_dataset = wk.Dataset(
tempdir, voxel_size=dataset.voxel_size, name=new_dataset_name
)
segmentation_layer = new_dataset.add_layer(
"segmentation",
wk.SEGMENTATION_CATEGORY,
dtype_per_channel=segmentation.dtype,
largest_segment_id=int(segmentation.max()),
)
# Step 5: Upload the segmentation to WEBKNOSSOS
print("Uploading segmentation…")
volume_layer = annotation.add_volume_layer("segmentation", dtype=segmentation.dtype)
with volume_layer.edit() as segmentation_layer:
segmentation_layer.bounding_box = dataset.layers["color"].bounding_box
segmentation_layer.add_mag(mag, compress=True).write(segmentation)
segmentation_layer.downsample(sampling_mode="constant_z")

remote_ds = new_dataset.upload(
layers_to_link=[annotation.get_remote_base_dataset().layers["color"]]
if "PYTEST_CURRENT_TEST" not in os.environ
else None
)

url = remote_ds.url
url = annotation.upload()
print(f"Successfully uploaded {url}")


Expand Down
4 changes: 1 addition & 3 deletions webknossos/examples/skeleton_path_length.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
def calculate_path_length(annotation_url: str, auth_token: str) -> None:
with wk.webknossos_context(token=auth_token):
# Download a annotation directly from the WEBKNOSSOS server
annotation = wk.Annotation.download(
annotation_url,
)
annotation = wk.Annotation.download(annotation_url, skip_volume_data=True)

skeleton = annotation.skeleton
voxel_size = annotation.voxel_size
Expand Down

Large diffs are not rendered by default.

Loading
Loading