Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reading nd datasets #966

Merged
merged 50 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
6269a11
Started debuging for reading nd datasets.
markbader Nov 7, 2023
f891606
Implementation of VecNInt and NDBoundingBox.
markbader Nov 23, 2023
0681252
Merge branch 'master' into reading_nd_datasets
markbader Nov 23, 2023
7cb395e
Rename VecInt and fix some issues.
markbader Nov 27, 2023
b3c972e
Work on import of existing zarr dataset.
markbader Dec 21, 2023
fdf9c03
Merge branch 'master' into reading_nd_datasets
markbader Dec 21, 2023
89a38e6
Add nd_bounding_box to properties of Layer.
markbader Dec 21, 2023
5580631
Update hooks in properties.py to make import of 4d zarr datasets poss…
markbader Jan 4, 2024
c4d2838
Add axis_order and index of additional axes to nd_bounding box creation.
markbader Jan 8, 2024
cf36c58
Working on reading nd data with pims images.
markbader Jan 11, 2024
4033eb0
Modify pims images to support more _iter_dims.
markbader Jan 16, 2024
ae29105
Add method for expected bounding box instead of expected shape in pim…
markbader Jan 18, 2024
f7ffb6b
Adding functions for editing ndboundingbox in 3d and update import fr…
markbader Jan 18, 2024
21bb0ea
Propagade nd-bounding box to different methods that take care of writ…
markbader Jan 22, 2024
51631c0
Update object unstructuring for json and start implementing nd array …
markbader Jan 23, 2024
2a755d3
Adapt array classes and add axes information to ArrayInfo.
markbader Jan 25, 2024
58c728b
Updated zarr writing for nd arrays. Axes order still get mixed up bet…
markbader Jan 29, 2024
af249e7
Adapted buffered_slice_reader and test behaviour with different tif i…
markbader Jan 30, 2024
ad3b22e
Adding testdata and fix bugs with axes operation for writing zarr array.
markbader Jan 31, 2024
e975f08
Fixing issues with axis order.
markbader Feb 1, 2024
61146c9
Rewrite buffered_slice_writer and fix bugs to get old tests working.
markbader Feb 5, 2024
6fff638
Fix chunking in buffered slice writer.
markbader Feb 5, 2024
6e9efd6
Fix issues with old tests.
markbader Feb 6, 2024
0cb8241
Working on fixing all tests.
markbader Feb 8, 2024
bb16e83
Merge branch 'master' into reading_nd_datasets
markbader Feb 12, 2024
0d8a4f7
Fixing issues with alignment, writing with offsets and different mags.
markbader Feb 15, 2024
9fc0253
Fix reading without parameters for nd datasets and addint test.
markbader Feb 15, 2024
48f9586
Fix issue with empty additionalAxes in json and some minor issues.
markbader Feb 16, 2024
51f173c
Move script reading_nd_data to examples in docs and implement some fe…
markbader Feb 20, 2024
7340073
Do some formatting and implement requested changes.
markbader Feb 20, 2024
1a94714
Update naming for accessing attributes of nd bounding boxes.
markbader Feb 21, 2024
e1d522f
Fix buffered_slice_writer for different axis and typechecking.
markbader Feb 26, 2024
e614525
Fix issue with ensure_size in initialization.
markbader Feb 26, 2024
23b992a
Merge branch 'master' into reading_nd_datasets
markbader Feb 26, 2024
27d3a3d
Adapt pims_images to support xyz images with channels and timepoint.
markbader Feb 26, 2024
8a1ca27
run formatter
markbader Feb 26, 2024
4167b9b
Fix issues with failed tests and add comments.
markbader Mar 7, 2024
2f69c30
Fix statement with wrong VecInt initialization.
markbader Mar 8, 2024
31255e3
Insert previously deleted assertions.
markbader Mar 8, 2024
8bedec5
Merge branch 'master' into reading_nd_datasets
markbader Mar 11, 2024
c354dd6
Add docstrings and use bounding box for read in nd case instead of Ve…
markbader Mar 11, 2024
ffbe7f7
Implement requested changes.
markbader Mar 18, 2024
36fef79
Merge branch 'master' into reading_nd_datasets
markbader Mar 18, 2024
2bfe961
Changes init of NDBoundingBoxes.
markbader Mar 18, 2024
0575eb9
Add converter for VecInt attributes of nd bounding box to pass typech…
markbader Mar 18, 2024
2ce18df
Merge branch 'master' into reading_nd_datasets
markbader Mar 19, 2024
e6781d7
Merge branch 'master' into reading_nd_datasets
markbader Mar 28, 2024
1a51e9b
Enhance documentation.
markbader Mar 28, 2024
f21ac09
Update Changelog.md
markbader Mar 28, 2024
092af26
Merge branch 'master' into reading_nd_datasets
markbader Apr 2, 2024
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
3 changes: 3 additions & 0 deletions docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ nav:
- webknossos-py/examples/download_tiff_stack.md
- webknossos-py/examples/remote_datasets.md
- webknossos-py/examples/zarr_and_dask.md
- webknossos-py/examples/convert_4d_tiff.md
- Annotation Examples:
- webknossos-py/examples/apply_merger_mode.md
- webknossos-py/examples/learned_segmenter.md
Expand All @@ -112,8 +113,10 @@ nav:
- Overview: api/webknossos.md
- Geometry:
- BoundingBox: api/webknossos/geometry/bounding_box.md
- NDBoundingBox: api/webknossos/geometry/nd_bounding_box.md
- Mag: api/webknossos/geometry/mag.md
- Vec3Int: api/webknossos/geometry/vec3_int.md
- VecInt: api/webknossos/geometry/vec_int.md
- Dataset:
- Dataset: api/webknossos/dataset/dataset.md
- Layer: api/webknossos/dataset/layer.md
Expand Down
13 changes: 13 additions & 0 deletions docs/src/webknossos-py/examples/convert_4d_tiff.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Convert 4D Tiff

This example demonstrates the basic interactions with Datasets that have more than three dimensions.

In order to manipulate 4D data in WEBKNOSSOS, we first convert the 4D Tiff dataset into a Zarr3 dataset. This conversion is achieved using the [from_images method](../../api/webknossos/dataset/dataset.md#Dataset.from_images).

Once the dataset is converted, we can access specific layers and views, [read data](../../api/webknossos/dataset/mag_view.md#MagView.read) from a defined bounding box, and [write data](../../api/webknossos/dataset/mag_view.md#MagView.write) to a different position within the dataset. The [NDBoundingBox](../../api/webknossos/geometry/nd_bounding_box.md#NDBoundingBox) is utilized to select a 4D region of the dataset.

```python
--8<--
webknossos/examples/convert_4d_tiff.py
--8<--
```
1 change: 1 addition & 0 deletions webknossos/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ For upgrade instructions, please check the respective _Breaking Changes_ section
- The rules for naming the layers have been tightened to match the allowed layer names on webknossos. [#1016](https://github.com/scalableminds/webknossos-libs/pull/1016)
- Replaced PyLint linter + black formatter with Ruff for development. [#1013](https://github.com/scalableminds/webknossos-libs/pull/1013)
- The remote operations now use the WEBKNOSSOS API version 6. [#1018](https://github.com/scalableminds/webknossos-libs/pull/1018)
- The conversion of 4D Tiff files to a Zarr3 Dataset is possible. NDBoundingBoxes and VecInt classes are introduced to support working with more than 3 dimensions. [#966](https://github.com/scalableminds/webknossos-libs/pull/966)

### Fixed

Expand Down
48 changes: 48 additions & 0 deletions webknossos/examples/convert_4d_tiff.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from pathlib import Path

import webknossos as wk


def main() -> None:
# Create a WEBKNOSSOS dataset from a 4D tiff image
dataset = wk.Dataset.from_images(
Path(__file__).parent.parent / "testdata" / "4D" / "4D_series",
"testoutput/4D_series",
voxel_size=(10, 10, 10),
data_format="zarr3",
use_bioformats=True,
)

# Access the first color layer and the Mag 1 view of this layer
layer = dataset.get_color_layers()[0]
mag_view = layer.get_finest_mag()

# To get the bounding box of the dataset use layer.bounding_box
# -> NDBoundingBox(topleft=(0, 0, 0, 0), size=(7, 5, 167, 439), axes=('t', 'z', 'y', 'x'))

# Read all data of the dataset
data = mag_view.read()
# data.shape -> (1, 7, 5, 167, 439) # first value is the channel dimension

# Read data for a specific time point (t=3) of the dataset
data = mag_view.read(
absolute_bounding_box=layer.bounding_box.with_bounds("t", 3, 1)
)
# data.shape -> (1, 1, 5, 167, 439)

# Create a NDBoundingBox to read data from a specific region of the dataset
read_bbox = wk.NDBoundingBox(
topleft=(2, 0, 67, 39),
size=(2, 5, 100, 400),
axes=("t", "z", "y", "x"),
index=(1, 2, 3, 4),
)
data = mag_view.read(absolute_bounding_box=read_bbox)
# data.shape -> (1, 2, 5, 100, 400) # first value is the channel dimension

# Write some data to a given position
mag_view.write(data, absolute_bounding_box=read_bbox.offset((2, 0, 0, 0)))


if __name__ == "__main__":
main()
Binary file not shown.
34 changes: 25 additions & 9 deletions webknossos/tests/dataset/test_add_layer_from_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,31 @@ def test_compare_tifffile(tmp_path: Path) -> None:
assert np.array_equal(data[:, :, z_index], comparison_slice)


def test_compare_nd_tifffile(tmp_path: Path) -> None:
ds = wk.Dataset(tmp_path, (1, 1, 1))
layer = ds.add_layer_from_images(
"testdata/4D/4D_series/4D-series.ome.tif",
layer_name="color",
category="color",
topleft=(100, 100, 55),
use_bioformats=True,
data_format="zarr3",
chunk_shape=(8, 8, 8),
chunks_per_shard=(8, 8, 8),
)
assert layer.bounding_box.topleft == wk.VecInt(
0, 55, 100, 100, axes=("t", "z", "y", "x")
)
assert layer.bounding_box.size == wk.VecInt(
7, 5, 167, 439, axes=("t", "z", "y", "x")
)
read_with_tifffile_reader = TiffFile(
"testdata/4D/4D_series/4D-series.ome.tif"
).asarray()
read_first_channel_from_dataset = layer.get_finest_mag().read()[0]
assert np.array_equal(read_with_tifffile_reader, read_first_channel_from_dataset)


REPO_IMAGES_ARGS: List[
Tuple[Union[str, List[Path]], Dict[str, Any], str, int, Tuple[int, int, int]]
] = [
Expand Down Expand Up @@ -205,15 +230,6 @@ def download_and_unpack(
(192, 128, 9),
1,
),
(
"https://samples.scif.io/sdub.zip",
"sdub*.pic",
{"allow_multiple_layers": True},
"uint8",
1,
(192, 128, 9),
12,
),
(
"https://samples.scif.io/test-avi.zip",
"t1-rendering.avi",
Expand Down
24 changes: 14 additions & 10 deletions webknossos/webknossos/_nml/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from loxun import XmlWriter

from ..geometry import BoundingBox
from ..geometry import BoundingBox, NDBoundingBox
from ..geometry.bounding_box import _DEFAULT_BBOX_NAME
from .utils import Vector3, enforce_not_null, filter_none_values

Expand All @@ -22,22 +22,24 @@ class Parameters(NamedTuple):
editPosition: Optional[Vector3] = None
editRotation: Optional[Vector3] = None
zoomLevel: Optional[float] = None
taskBoundingBox: Optional[BoundingBox] = None
userBoundingBoxes: Optional[List[BoundingBox]] = None
taskBoundingBox: Optional[NDBoundingBox] = None
userBoundingBoxes: Optional[List[NDBoundingBox]] = None

def _dump_bounding_box(
self,
xf: XmlWriter,
bounding_box: BoundingBox,
bounding_box: NDBoundingBox,
tag_name: str,
bbox_id: Optional[int], # user bounding boxes need an id
) -> None:
color = bounding_box.color or DEFAULT_BOUNDING_BOX_COLOR

attributes = {
"name": _DEFAULT_BBOX_NAME
if bounding_box.name is None
else str(bounding_box.name),
"name": (
_DEFAULT_BBOX_NAME
if bounding_box.name is None
else str(bounding_box.name)
),
"isVisible": "true" if bounding_box.is_visible else "false",
"color.r": str(color[0]),
"color.g": str(color[1]),
Expand Down Expand Up @@ -136,7 +138,7 @@ def _dump(self, xf: XmlWriter) -> None:
xf.endTag() # parameters

@classmethod
def _parse_bounding_box(cls, bounding_box_element: Element) -> BoundingBox:
def _parse_bounding_box(cls, bounding_box_element: Element) -> NDBoundingBox:
topleft = (
int(bounding_box_element.get("topLeftX", 0)),
int(bounding_box_element.get("topLeftY", 0)),
Expand Down Expand Up @@ -165,14 +167,16 @@ def _parse_bounding_box(cls, bounding_box_element: Element) -> BoundingBox:
)

@classmethod
def _parse_user_bounding_boxes(cls, nml_parameters: Element) -> List[BoundingBox]:
def _parse_user_bounding_boxes(cls, nml_parameters: Element) -> List[NDBoundingBox]:
if nml_parameters.find("userBoundingBox") is None:
return []
bb_elements = nml_parameters.findall("userBoundingBox")
return [cls._parse_bounding_box(bb_element) for bb_element in bb_elements]

@classmethod
def _parse_task_bounding_box(cls, nml_parameters: Element) -> Optional[BoundingBox]:
def _parse_task_bounding_box(
cls, nml_parameters: Element
) -> Optional[NDBoundingBox]:
bb_element = nml_parameters.find("taskBoundingBox")
if bb_element is not None:
return cls._parse_bounding_box(bb_element)
Expand Down
8 changes: 4 additions & 4 deletions webknossos/webknossos/annotation/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
)
from ..dataset.defaults import PROPERTIES_FILE_NAME
from ..dataset.properties import DatasetProperties, dataset_converter
from ..geometry import BoundingBox, Vec3Int
from ..geometry import NDBoundingBox, Vec3Int
from ..skeleton import Skeleton
from ..utils import time_since_epoch_in_ms, warn_deprecated
from ._nml_conversion import annotation_to_nml, nml_to_skeleton
Expand Down Expand Up @@ -124,8 +124,8 @@ class Annotation:
edit_rotation: Optional[Vector3] = None
zoom_level: Optional[float] = None
metadata: Dict[str, str] = attr.Factory(dict)
task_bounding_box: Optional[BoundingBox] = None
user_bounding_boxes: List[BoundingBox] = attr.Factory(list)
task_bounding_box: Optional[NDBoundingBox] = None
user_bounding_boxes: List[NDBoundingBox] = attr.Factory(list)
_volume_layers: List[_VolumeLayer] = attr.field(factory=list, init=False)

@classmethod
Expand Down Expand Up @@ -474,7 +474,7 @@ def _load_from_zip(cls, content: Union[str, PathLike, BinaryIO]) -> "Annotation"
assert len(nml_paths) > 0, "Couldn't find an nml file in the supplied zip-file."
assert (
len(nml_paths) == 1
), f"There must be exactly one nml file in the zip-file, buf found {len(nml_paths)}."
), f"There must be exactly one nml file in the zip-file, but found {len(nml_paths)}."
with nml_paths[0].open(mode="rb") as f:
return cls._load_from_nml(nml_paths[0].stem, f, possible_volume_paths=paths)

Expand Down
6 changes: 3 additions & 3 deletions webknossos/webknossos/cli/convert_knossos.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,15 @@ def convert_cube_job(
time_start(f"Converting of {target_view.bounding_box}")
cube_size = cast(Tuple[int, int, int], (KNOSSOS_CUBE_EDGE_LEN,) * 3)

offset = target_view.bounding_box.in_mag(target_view.mag).topleft
size = target_view.bounding_box.in_mag(target_view.mag).size
offset = target_view.bounding_box.in_mag(target_view.mag).topleft_xyz
size = target_view.bounding_box.in_mag(target_view.mag).size_xyz
buffer = np.zeros(size.to_tuple(), dtype=target_view.get_dtype())
with open_knossos(source_knossos_info) as source_knossos:
for x in range(0, size.x, KNOSSOS_CUBE_EDGE_LEN):
for y in range(0, size.y, KNOSSOS_CUBE_EDGE_LEN):
for z in range(0, size.z, KNOSSOS_CUBE_EDGE_LEN):
cube_data = source_knossos.read(
(offset + Vec3Int(x, y, z)).to_tuple(), cube_size
Vec3Int(offset + (x, y, z)).to_tuple(), cube_size
)
buffer[
x : (x + KNOSSOS_CUBE_EDGE_LEN),
Expand Down
2 changes: 1 addition & 1 deletion webknossos/webknossos/cli/export_wkw_as_tiff.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def main(

mag_view = Dataset.open(source).get_layer(layer_name).get_mag(mag)

bbox = mag_view.bounding_box if bbox is None else bbox
bbox = BoundingBox.from_ndbbox(mag_view.bounding_box) if bbox is None else bbox

logging.info("Starting tiff export for bounding box: %s", bbox)
executor_args = Namespace(
Expand Down
Loading
Loading