Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
71c2110
simplify conditional
melonora Mar 28, 2024
93ca0ed
Merge branch 'main' of https://github.com/melonora/napari-spatialdata
melonora May 21, 2024
f363842
Merge branch 'main' of https://github.com/melonora/napari-spatialdata
melonora Jun 19, 2024
07f60a5
Merge branch 'main' of https://github.com/melonora/napari-spatialdata
melonora Jun 24, 2024
784e95b
Merge branch 'main' of https://github.com/melonora/napari-spatialdata
melonora Jun 24, 2024
79a2a50
Merge branch 'scverse:main' into main
melonora Jul 9, 2024
29b1579
Merge branch 'scverse:main' into main
melonora Jul 22, 2024
5a2de2e
Merge branch 'scverse:main' into main
melonora Aug 9, 2024
7576271
Merge branch 'scverse:main' into main
melonora Sep 7, 2024
85e5c61
Merge branch 'scverse:main' into main
melonora Sep 19, 2024
dcc6b68
Merge branch 'scverse:main' into main
melonora Oct 14, 2024
3036463
Merge branch 'scverse:main' into main
melonora Dec 2, 2024
044bd18
Merge branch 'scverse:main' into main
melonora Dec 17, 2024
788a328
Merge branch 'main' of https://github.com/scverse/napari-spatialdata
melonora Feb 13, 2025
16f8a2e
Merge branch 'main' of https://github.com/melonora/napari-spatialdata
melonora Feb 13, 2025
8bda7d0
Merge branch 'main' of https://github.com/scverse/napari-spatialdata
melonora Mar 16, 2025
851333c
merge main
melonora Apr 18, 2025
bb29dd6
merge main
melonora May 26, 2025
b66f1ab
add channel_widget, remove colorbar
melonora Jun 2, 2025
a3a0761
allow multiscale channel selection
melonora Jun 2, 2025
40b0e86
add test and fix
melonora Jun 2, 2025
2959acd
copy dask pin from spatialdata
melonora Jun 2, 2025
ee067ad
remove quote
melonora Jun 2, 2025
abb01c2
merge three widgets
melonora Jun 2, 2025
3484cf4
update docstring
melonora Jun 2, 2025
d9dd413
remove require_widget wrapper
melonora Jun 3, 2025
4cc8410
remove unused element attribute
melonora Jun 3, 2025
02416a0
change widget type to bool param
melonora Jun 3, 2025
003bc94
readd elements for cache but remove dict
melonora Jun 3, 2025
b04047b
Merge branch 'main' into select_channels
LucaMarconato May 15, 2026
feab299
fix tests
LucaMarconato May 15, 2026
35020e3
Replace Channels widget with 'add in new layer' checkbox on Vars
LucaMarconato May 15, 2026
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
338 changes: 300 additions & 38 deletions src/napari_spatialdata/_sdata_widgets.py

Large diffs are not rendered by default.

45 changes: 35 additions & 10 deletions src/napari_spatialdata/_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
from qtpy import QtWidgets
from qtpy.QtCore import Qt
from qtpy.QtWidgets import (
QCheckBox,
QComboBox,
QDialog,
QGridLayout,
QHBoxLayout,
QInputDialog,
QLabel,
QLineEdit,
Expand All @@ -41,9 +43,7 @@
from napari_spatialdata._widgets import (
AListWidget,
AnnDataSaveDialog,
CBarWidget,
ComponentWidget,
RangeSliderWidget,
SaveDialog,
ScatterAnnotationDialog,
)
Expand Down Expand Up @@ -453,8 +453,19 @@ def __init__(self, napari_viewer: Viewer, model: DataModel | None = None) -> Non
# Vars
var_label = QLabel("Vars:")
var_label.setToolTip("Names from `adata.var_names` or `adata.raw.var_names`.")
self.add_in_new_layer_checkbox = QCheckBox("add in new layer")
self.add_in_new_layer_checkbox.setChecked(False)
var_header = QWidget()
var_header_layout = QHBoxLayout(var_header)
var_header_layout.setContentsMargins(0, 0, 0, 0)
var_header_layout.addWidget(var_label)
var_header_layout.addWidget(self.add_in_new_layer_checkbox)
var_header_layout.addStretch()

self.var_widget = AListWidget(self.viewer, self.model, attr="var")
self.var_widget.setAdataLayer("X")
self.add_in_new_layer_checkbox.toggled.connect(self._on_add_in_new_layer_toggled)
self.var_widget.load_channels.connect(self._load_channels_in_new_layer)

self.viewer.dims.events.current_step.connect(self._channel_changed)

Expand All @@ -470,7 +481,7 @@ def __init__(self, napari_viewer: Viewer, model: DataModel | None = None) -> Non

self.layout().addWidget(adata_layer_label)
self.layout().addWidget(self.adata_layer_widget)
self.layout().addWidget(var_label)
self.layout().addWidget(var_header)
self.layout().addWidget(self.var_widget)

# obsm
Expand All @@ -497,19 +508,25 @@ def __init__(self, napari_viewer: Viewer, model: DataModel | None = None) -> Non
self.color_by = QLabel("Colored by:")
self.layout().addWidget(self.color_by)

# scalebar
colorbar = CBarWidget(model=self.model)
self.slider = RangeSliderWidget(self.viewer, self.model, colorbar=colorbar)
self._viewer.window.add_dock_widget(self.slider, area="left", name="slider")
self._viewer.window.add_dock_widget(colorbar, area="left", name="colorbar")
self.viewer.layers.selection.events.active.connect(self.slider._onLayerChange)

if (layer := self.viewer.layers.selection.active) is not None and layer.metadata.get("adata") is not None:
self._on_layer_update()

self.model.events.adata.connect(self._on_layer_update)
self.model.events.color_by.connect(self._change_color_by)

def _on_add_in_new_layer_toggled(self, checked: bool) -> None:
self.var_widget.add_in_new_layer = checked

def _load_channels_in_new_layer(self, channel_names: tuple[str, ...]) -> None:
layer = self.model.layer
if layer is None:
return
layer_name = layer.name
element_name = layer_name[: layer_name.rfind("_ch:")] if "_ch:" in layer_name else layer_name
sdata_widget = self._viewer.window._dock_widgets["SpatialData"].widget()
for channel_name in channel_names:
sdata_widget._enqueue_channel(element_name, channel_name)

def _channel_changed(self, event: Event) -> None:
layer = self.model.layer
is_image = isinstance(layer, Image)
Expand All @@ -522,6 +539,14 @@ def _channel_changed(self, event: Event) -> None:
return

current_point = list(event.value)
data = layer.data[-1] if isinstance(layer.data, MultiScaleData) else layer.data
# after loading a channel into a new layer, the new layer is selected, but one can
# still load additional channels as new layers. If deselecting the checkbox for adding
# channels as new layers, without the next if block one would get a crash due to
# going out of index
if data.ndim < len(current_point):
return

displayed = self._viewer.dims.displayed
if layer.multiscale:
for i, (lo_size, hi_size, cord) in enumerate(
Expand Down
20 changes: 15 additions & 5 deletions src/napari_spatialdata/_viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
_get_ellipses_from_circles,
_get_init_metadata_adata,
_get_transform,
_obtain_channel_image,
_transform_coordinates,
get_duplicate_element_names,
get_napari_version,
Expand Down Expand Up @@ -451,10 +452,14 @@ def clean_worker(self) -> None:
"""Clean the worker."""
self.worker = None

def add_sdata_image(self, sdata: SpatialData, key: str, selected_cs: str, multi: bool) -> None:
self.add_layer(self.get_sdata_image(sdata, key, selected_cs, multi))
def add_sdata_image(
self, sdata: SpatialData, key: str, selected_cs: str, multi: bool, channel_name: str | None = None
) -> None:
self.add_layer(self.get_sdata_image(sdata, key, selected_cs, multi, channel_name))

def get_sdata_image(self, sdata: SpatialData, key: str, selected_cs: str, multi: bool) -> Image:
def get_sdata_image(
self, sdata: SpatialData, key: str, selected_cs: str, multi: bool, channel_name: str | None = None
) -> Image:
"""
Add an image in a spatial data object to the viewer.

Expand All @@ -474,15 +479,20 @@ def get_sdata_image(self, sdata: SpatialData, key: str, selected_cs: str, multi:
original_name = original_name[: original_name.rfind("_")]

affine = _get_transform(sdata.images[original_name], selected_cs, include_z=True)
rgb_image, rgb = _adjust_channels_order(element=sdata.images[original_name])
if channel_name:
image = _obtain_channel_image(element=sdata.images[original_name], channel_name=channel_name)
rgb = False
key = key + f"_ch:{channel_name}"
else:
image, rgb = _adjust_channels_order(element=sdata.images[original_name])

channels = ("RGB(A)",) if rgb else get_channel_names(sdata.images[original_name])

adata = AnnData(shape=(0, len(channels)), var=pd.DataFrame(index=channels))

# TODO: type check
return Image(
rgb_image,
image,
rgb=rgb,
name=key,
affine=affine,
Expand Down
Loading
Loading