Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
48609f4
wip
fdrgsp Jan 20, 2025
880d173
.gitignore uv.lock
fdrgsp Jan 20, 2025
b78d8ba
Stop tracking uv.lock
fdrgsp Jan 20, 2025
65c2530
update viewer logic
fdrgsp Jan 21, 2025
8c8b556
uv.lock
fdrgsp Jan 22, 2025
d4ceff9
fix
fdrgsp Jan 22, 2025
fe55fdb
Merge remote-tracking branch 'upstream/main' into ndv2
fdrgsp Jan 23, 2025
e2a74a1
wip
fdrgsp Jan 23, 2025
5a9b5fb
Merge remote-tracking branch 'upstream/main' into ndv2
fdrgsp Jan 25, 2025
78bf256
wip
fdrgsp Jan 25, 2025
63cd7d1
wip
fdrgsp Jan 26, 2025
8a3fc4f
wip
fdrgsp Jan 26, 2025
b1bbd4d
wip
fdrgsp Jan 27, 2025
5720072
wealval
tlambert03 Jan 27, 2025
c6cae25
Merge pull request #1 from tlambert03/ndv2
fdrgsp Jan 28, 2025
4d6d764
pymmcore-nano
fdrgsp Jan 28, 2025
6210231
wip
fdrgsp Jan 28, 2025
83323a5
Merge remote-tracking branch 'upstream/main' into ndv2
fdrgsp Jan 28, 2025
183b190
add comment MDAWidget
fdrgsp Jan 29, 2025
c37f6db
update
fdrgsp Jan 29, 2025
a2866ad
update viewer
fdrgsp Jan 29, 2025
dc65f37
add comments
fdrgsp Jan 29, 2025
dfd706f
wip: viewer v2
fdrgsp Jan 29, 2025
a2330d9
fix
fdrgsp Jan 29, 2025
68689e1
fix: update ViewersCoreLink to also work out of MDAWidget
fdrgsp Jan 29, 2025
0754c46
fix: docstring
fdrgsp Jan 29, 2025
ea5d133
style(pre-commit.ci): auto fixes [...]
pre-commit-ci[bot] Jan 29, 2025
c29aa96
uv.lock
fdrgsp Jan 29, 2025
63540fc
uv.lock
fdrgsp Jan 29, 2025
4ebcaa9
Merge branch 'main' into ndv2
tlambert03 Jan 29, 2025
d34e56b
undo gitignore
tlambert03 Jan 29, 2025
19c78df
all the stuff we did together today
tlambert03 Jan 29, 2025
9d603b8
Merge branch 'main' into ndv2
fdrgsp Jan 29, 2025
7767aaa
uv.lock
fdrgsp Jan 29, 2025
00af7f2
remove unused
fdrgsp Jan 29, 2025
797078e
bump
tlambert03 Jan 29, 2025
ad75a65
Merge branch 'main' into ndv2
tlambert03 Jan 31, 2025
130fb07
merge in tl-ndv2
tlambert03 Feb 2, 2025
fed901b
cleanup
tlambert03 Feb 2, 2025
e88ea3c
more cleanup
tlambert03 Feb 2, 2025
0ea8673
use main ndv
tlambert03 Feb 2, 2025
715d65b
test: add test
tlambert03 Feb 2, 2025
1da51cc
back to vispy
tlambert03 Feb 2, 2025
efd6842
skip test
tlambert03 Feb 2, 2025
3e92adb
skip
tlambert03 Feb 2, 2025
7653d4c
don't test vispy
tlambert03 Feb 3, 2025
08a0a53
test: unskip again
tlambert03 Feb 3, 2025
40c68bb
unskip test
tlambert03 Feb 3, 2025
ac3482a
Merge branch 'main' into ndv2
tlambert03 Feb 5, 2025
62ab946
add cmap hook
tlambert03 Feb 5, 2025
d91672a
fix
tlambert03 Feb 5, 2025
6d0d1a0
move into new file, and support 5DBase
tlambert03 Feb 5, 2025
e12ceb7
test: remove comment
tlambert03 Feb 5, 2025
b050442
update tes
tlambert03 Feb 5, 2025
ab7ef36
Merge branch 'main' into support-ome-tiff
tlambert03 Feb 5, 2025
0a94fc5
Merge branch 'main' into support-ome-tiff
tlambert03 Mar 24, 2025
a8c8fe1
lint
tlambert03 Mar 24, 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
67 changes: 64 additions & 3 deletions src/pymmcore_gui/_ndv_viewers.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
from __future__ import annotations

import sys
import warnings
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Any, TypeGuard
from weakref import WeakValueDictionary

import ndv
import numpy as np
import useq
from ndv import DataWrapper
from pymmcore_plus.mda.handlers import TensorStoreHandler
from pymmcore_plus.mda.handlers._5d_writer_base import _5DWriterBase
from PyQt6.QtCore import QObject, QTimer, pyqtSignal
from PyQt6.QtWidgets import (
QWidget,
)

if TYPE_CHECKING:
from collections.abc import Iterator
from collections.abc import Hashable, Iterator, Mapping, Sequence

import numpy as np
from ndv.models._array_display_model import IndexMap
from pymmcore_plus import CMMCorePlus
from pymmcore_plus.mda import SupportsFrameReady
Expand Down Expand Up @@ -103,6 +109,8 @@ def _on_frame_ready(
if isinstance(handler, TensorStoreHandler):
# TODO: temporary. maybe create the DataWrapper for the handlers
viewer.data = handler.store
elif isinstance(handler, _5DWriterBase):
viewer.data = _OME5DWrapper(handler)
else:
warnings.warn(
f"don't know how to show data of type {type(handler)}",
Expand Down Expand Up @@ -147,3 +155,56 @@ def __len__(self) -> int:

def viewers(self) -> Iterator[ndv.ArrayViewer]:
yield from (self._seq_viewers.values())


# --------------------------------------------------------------------------------
# this could be improved. Just a quick Datawrapper for the pymmcore-plus 5D writer
# indexing and isel is particularly ugly at the moment. TODO...


class _OME5DWrapper(DataWrapper["_5DWriterBase"]):
@classmethod
def supports(cls, obj: Any) -> TypeGuard[_5DWriterBase]:
if "pymmcore_plus.mda" in sys.modules:
from pymmcore_plus.mda.handlers._5d_writer_base import _5DWriterBase

return isinstance(obj, _5DWriterBase)
return False

@property
def dims(self) -> tuple[Hashable, ...]:
"""Return the dimension labels for the data."""
if not self.data.current_sequence:
return ()
return (*tuple(self.data.current_sequence.sizes), "y", "x")

@property
def coords(self) -> Mapping[Hashable, Sequence]:
"""Return the coordinates for the data."""
if not self.data.current_sequence or not self.data.position_arrays:
return {}
coords: dict[Hashable, Sequence] = {
dim: range(size) for dim, size in self.data.current_sequence.sizes.items()
}
ary = next(iter(self.data.position_arrays.values()))
coords.update({"y": range(ary.shape[-2]), "x": range(ary.shape[-1])})
return coords

def isel(self, index: Mapping[int, int | slice]) -> np.ndarray:
# oh lord look away.
# this is a mess, partially caused by the ndv slice/model

idx = [index.get(k, slice(None)) for k in range(len(self.dims))]
try:
pidx = self.dims.index("p")
except ValueError:
pidx = 0

_pcoord: int | slice = index[pidx]
pcoord: int = _pcoord.start if isinstance(_pcoord, slice) else _pcoord

del idx[pidx]
key = self.data.get_position_key(pcoord)
data = self.data.position_arrays[key][tuple(idx)]
# add back position dimension
return np.expand_dims(data, axis=pidx)
14 changes: 12 additions & 2 deletions tests/test_ndv_viewers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,17 @@
from pymmcore_gui._ndv_viewers import NDVViewersManager

if TYPE_CHECKING:
from pathlib import Path

from pymmcore_plus import CMMCorePlus
from pytestqt.qtbot import QtBot


def test_viewers_manager(mmcore: CMMCorePlus, qtbot: QtBot) -> None:
# "test.ome.zarr" still fails because of call-order issues
@pytest.mark.parametrize("fname", ["test.ome.tiff", None])
def test_viewers_manager(
fname: str, mmcore: CMMCorePlus, qtbot: QtBot, tmp_path: Path
) -> None:
"""Ensure that the viewers manager creates and cleans up viewers during MDA."""
dummy = QWidget()
manager = NDVViewersManager(dummy, mmcore)
Expand All @@ -30,14 +36,18 @@ def test_viewers_manager(mmcore: CMMCorePlus, qtbot: QtBot) -> None:
channels=["DAPI", "FITC"], # pyright: ignore
z_plan=useq.ZRangeAround(range=4, step=1),
),
output=(tmp_path / fname) if fname else None,
)
assert len(manager) == 1

with qtbot.waitSignal(dummy.destroyed, timeout=1000):
dummy.deleteLater()
QApplication.processEvents()
gc.collect()
if len(manager):
# only checking for strong references when WE have created the datahandler.
# otherwise... the NDV datawrapper itself may be holding a strong ref?
# need to look into this...
if fname is None and len(manager):
for viewer in manager.viewers():
if "vispy" in type(viewer._canvas).__name__.lower():
# don't even bother... vispy is a mess of hard references
Expand Down
Loading