Skip to content

Commit

Permalink
Add functionality for specifying channel names when saving to spatial…
Browse files Browse the repository at this point in the history
…data Zarr (#151)

* Add ability to specify image channel names.

If provided, the channel names are saved as metadata in the Zarr. When
reading the resulting Zarr file with spatialdata, the channel names will
be read too and accessible in the corresponding spatialdata object via
the "c" field of "coords".

Channel names are also passed correctly to different image processing functions.

---------

Co-authored-by: ArneDefauw <[email protected]>
  • Loading branch information
SilverViking and ArneDefauw authored Oct 24, 2023
1 parent bbda4d0 commit b33e9b0
Show file tree
Hide file tree
Showing 17 changed files with 258 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
{
"channels_metadata": {
"channels": [
{
"label": 0
},
{
"label": 1
}
]
},
"multiscales": [
{
"axes": [
{
"name": "c",
"type": "channel"
},
{
"name": "y",
"type": "space"
},
{
"name": "x",
"type": "space"
}
],
"coordinateTransformations": [
{
"input": {
"axes": [
{
"name": "c",
"type": "channel"
},
{
"name": "y",
"type": "space",
"unit": "unit"
},
{
"name": "x",
"type": "space",
"unit": "unit"
}
],
"name": "cyx"
},
"output": {
"axes": [
{
"name": "c",
"type": "channel"
},
{
"name": "y",
"type": "space",
"unit": "unit"
},
{
"name": "x",
"type": "space",
"unit": "unit"
}
],
"name": "global"
},
"translation": [
0.0,
0.0,
0.0
],
"type": "translation"
}
],
"datasets": [
{
"coordinateTransformations": [
{
"scale": [
1.0,
1.0,
1.0
],
"type": "scale"
}
],
"path": "0"
}
],
"name": "/images/combine",
"version": "0.4"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"zarr_format": 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"chunks": [
1,
512,
512
],
"compressor": {
"blocksize": 0,
"clevel": 5,
"cname": "lz4",
"id": "blosc",
"shuffle": 1
},
"dimension_separator": "/",
"dtype": "<f4",
"fill_value": 0.0,
"filters": null,
"order": "C",
"shape": [
2,
512,
512
],
"zarr_format": 2
}
Binary file not shown.
Binary file not shown.
16 changes: 16 additions & 0 deletions src/napari_sparrow/_tests/test_image/test_combine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from spatialdata import SpatialData

from napari_sparrow.image._combine import combine

def test_combine( sdata_multi_c ):

sdata_multi_c=combine( sdata_multi_c,
img_layer="raw_image",
output_layer="combine",
nuc_channels=[ 15, 14 ],
mem_channels=[ 0,6,11 ],
overwrite=True
)

assert "combine" in sdata_multi_c.images
assert isinstance(sdata_multi_c, SpatialData)
9 changes: 4 additions & 5 deletions src/napari_sparrow/_tests/test_widget.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"""This file tests the napari widgets and should be used for development purposes."""
import unittest
import pytest

import tifffile as tiff
from hydra.core.hydra_config import HydraConfig


@unittest.skip
@pytest.mark.skip
def test_sparrow_widgets(make_napari_viewer, cfg_pipeline, caplog):
"""
Integration test for sparrow plugin in napari
Expand Down Expand Up @@ -85,8 +85,7 @@ def test_sparrow_widgets(make_napari_viewer, cfg_pipeline, caplog):
assert "Annotation metadata added" in caplog.text



@unittest.skip
@pytest.mark.skip
def test_load_widget(make_napari_viewer, cfg_pipeline, caplog):
"""Test if the load works."""
from napari_sparrow import utils as utils
Expand All @@ -112,7 +111,7 @@ def test_load_widget(make_napari_viewer, cfg_pipeline, caplog):
assert f"Added {utils.LOAD}" in caplog.text


@unittest.skip
@pytest.mark.skip
def test_clean_widget(make_napari_viewer, cfg_pipeline, caplog):
"""Tests if the clean widget works."""

Expand Down
4 changes: 3 additions & 1 deletion src/napari_sparrow/image/_apply.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ def adjust_depth(depth, chunksize, depth_dim):
crd = _substract_translation_crd(se, crd)

for ch, _fn_kwargs in zip(channel, _fn_kwargs_channel):
arr = se.isel(c=ch).data
channel_idx = list(se.c.data).index(ch)
arr = se.isel(c=channel_idx).data
if len(arr.shape) != 2:
raise ValueError(
f"Array is of dimension {arr.shape}, currently only 2D images are supported."
Expand Down Expand Up @@ -218,6 +219,7 @@ def adjust_depth(depth, chunksize, depth_dim):
chunks=arr.chunksize,
transformation=translation,
scale_factors=scale_factors,
c_coords=channel,
overwrite=overwrite,
)

Expand Down
27 changes: 19 additions & 8 deletions src/napari_sparrow/image/_combine.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ def combine(
sdata: SpatialData,
img_layer: Optional[str] = None,
output_layer: Optional[str] = None,
nuc_channels: Optional[int | Iterable[int]] = None,
mem_channels: Optional[int | Iterable[int]] = None,
nuc_channels: Optional[int | str | Iterable[int | str]] = None,
mem_channels: Optional[int | str | Iterable[int | str]] = None,
crd: Optional[Tuple[int, int, int, int]] = None,
scale_factors: Optional[ScaleFactors_t] = None,
overwrite: bool = False,
) -> SpatialData:
"""
Combines specific channels within an image layer of a SpatialData object.
Combines specific channels within an image layer of a SpatialData object.
When given, nuc_channels are aggregated together, as are mem_channels.
Parameters
Expand All @@ -43,9 +43,9 @@ def combine(
The image layer in `sdata` to process. If not provided, the last image layer in `sdata` is used.
output_layer : Optional[str]
The name of the output layer where results will be stored. This must be specified.
nuc_channels : Optional[int | Iterable[int]], default=None
nuc_channels : Optional[int | str | Iterable[int | str ]], default=None
Specifies which channel(s) to consider as nuclear channels.
mem_channels : Optional[int | Iterable[int]], default=None
mem_channels : Optional[int | str | Iterable[int | str ]], default=None
Specifies which channel(s) to consider as membrane channels.
crd : Optional[Tuple[int, int, int, int]], default=None
The coordinates specifying the region of the image to be processed. Defines the bounds (x_min, x_max, y_min, y_max).
Expand Down Expand Up @@ -108,14 +108,25 @@ def _process_channels(
if isinstance(channels, Iterable) and not isinstance(channels, str)
else [channels]
)
arr = se.isel(c=channels).data

all_channels = list(se.c.data)
ch_indices = [
all_channels.index(_ch) for _ch in channels if _ch in all_channels
]

if len(ch_indices) == 0:
raise ValueError(
f"No matching channels between provided channels '{channels}' and channels in '{img_layer}': '{all_channels}'."
)

arr = se.isel(c=ch_indices).data
if len(arr.shape) != 3:
raise ValueError(
f"Array is of dimension {arr.shape}, currently only 2D images are supported."
)
if crd is not None:
arr = arr[:,crd[2] : crd[3], crd[0] : crd[1]]
arr=arr.rechunk( arr.chunksize )
arr = arr[:, crd[2] : crd[3], crd[0] : crd[1]]
arr = arr.rechunk(arr.chunksize)
arr = arr.sum(axis=0)
arr = arr[None, ...]
return arr
Expand Down
4 changes: 3 additions & 1 deletion src/napari_sparrow/image/_image.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import Optional, Tuple, Union
from typing import List, Optional, Tuple, Union

import numpy as np
import xarray as xr
Expand Down Expand Up @@ -212,6 +212,7 @@ def _add_image_layer(
chunks: Optional[str | tuple[int, int, int] | int] = None,
transformation: Union[BaseTransformation, dict[str, BaseTransformation]] = None,
scale_factors: Optional[ScaleFactors_t] = None,
c_coords: Optional[List[str]] = None,
overwrite: bool = False,
):
manager = ImageLayerManager()
Expand All @@ -222,6 +223,7 @@ def _add_image_layer(
chunks=chunks,
transformation=transformation,
scale_factors=scale_factors,
c_coords=c_coords,
overwrite=overwrite,
)

Expand Down
Loading

0 comments on commit b33e9b0

Please sign in to comment.