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

feat: stackviewer v2 #293

Closed
wants to merge 69 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
0321817
wip
tlambert03 Apr 24, 2024
452c661
more progress
tlambert03 Apr 24, 2024
0d5b085
wip
tlambert03 Apr 24, 2024
1f42588
some fixes
tlambert03 Apr 25, 2024
1731f7c
wip
tlambert03 May 1, 2024
a332228
getting better
tlambert03 May 2, 2024
d066d77
some linting
tlambert03 May 2, 2024
be222a4
wip on composite mode
tlambert03 May 2, 2024
0c3edb5
starting pygfx
tlambert03 May 2, 2024
aa412e4
more updates
tlambert03 May 2, 2024
60101e3
more wip
tlambert03 May 3, 2024
d727dcd
wip
tlambert03 May 3, 2024
57187d2
wip
tlambert03 May 4, 2024
b066f3a
good progress
tlambert03 May 4, 2024
fb66297
more progress for xarray and numpy
tlambert03 May 4, 2024
f63328e
better pygfx
tlambert03 May 4, 2024
a7cb3e0
linting and cleanup
tlambert03 May 4, 2024
2215d50
save btn
tlambert03 May 4, 2024
4172741
remove qt backend
tlambert03 May 4, 2024
63e61d9
move and rename
tlambert03 May 4, 2024
91ecd0c
more renames
tlambert03 May 4, 2024
81758ec
remove bar color
tlambert03 May 6, 2024
b34b4df
bump superqt and add popup fps
tlambert03 May 6, 2024
d2ff45b
use async, add colors, start transform
tlambert03 May 6, 2024
c3ae898
fix typo
tlambert03 May 7, 2024
fa51abe
Merge branch 'main' into stack-viewer2
tlambert03 May 7, 2024
5ebdcc3
rename
tlambert03 May 7, 2024
d07876e
Merge branch 'stack-viewer2' of https://github.com/tlambert03/pymmcor…
tlambert03 May 7, 2024
ca4347d
hide sliders of size=1
tlambert03 May 7, 2024
f7cddf5
minor typing
tlambert03 May 7, 2024
5aae2af
additive mode
tlambert03 May 7, 2024
31cbf64
futures
tlambert03 May 7, 2024
fb66e29
more futurestuff
tlambert03 May 7, 2024
5eb5462
more cleanup
tlambert03 May 8, 2024
b0cb5da
style(pre-commit.ci): auto fixes [...]
pre-commit-ci[bot] May 8, 2024
f1fc4a2
changes to throttling
tlambert03 May 9, 2024
b4175ae
style(pre-commit.ci): auto fixes [...]
pre-commit-ci[bot] May 9, 2024
90827ff
change demo
tlambert03 May 10, 2024
cb9d031
remove print
tlambert03 May 10, 2024
c6e7ee1
add tensor store
tlambert03 May 10, 2024
5b6aed6
wip
tlambert03 May 10, 2024
f100cf9
use tensorstore backingh
tlambert03 May 11, 2024
120a47e
fix imports
tlambert03 May 11, 2024
7c895c4
channel names and docs
tlambert03 May 12, 2024
580cb20
style(pre-commit.ci): auto fixes [...]
pre-commit-ci[bot] May 12, 2024
f02d2fa
fix
tlambert03 May 12, 2024
155b45c
rearrange and fix delayed requests
tlambert03 May 12, 2024
5f84dde
style(pre-commit.ci): auto fixes [...]
pre-commit-ci[bot] May 12, 2024
5e21c23
Merge branch 'main' into stack-viewer2
tlambert03 May 24, 2024
a136f72
add xarray example
tlambert03 May 24, 2024
2efce9c
two fixes
tlambert03 May 29, 2024
857991b
tried to fix leaks. but failed
tlambert03 Jun 2, 2024
d3e3169
use data-wrapper
tlambert03 Jun 3, 2024
550d5d6
style(pre-commit.ci): auto fixes [...]
pre-commit-ci[bot] Jun 3, 2024
4441085
minor
tlambert03 Jun 3, 2024
0591afc
Merge branch 'stack-viewer2' of https://github.com/tlambert03/pymmcor…
tlambert03 Jun 3, 2024
759956d
misc
tlambert03 Jun 3, 2024
98b2717
fix one leak
tlambert03 Jun 3, 2024
18a6047
small changes
tlambert03 Jun 3, 2024
325a90e
more indexing
tlambert03 Jun 3, 2024
c7cfe6f
remove check
tlambert03 Jun 3, 2024
5bf478c
wip
tlambert03 Jun 3, 2024
88e93a6
style(pre-commit.ci): auto fixes [...]
pre-commit-ci[bot] Jun 3, 2024
2df4ce3
don't autoscale invisible
tlambert03 Jun 5, 2024
7a8d0cf
Merge branch 'stack-viewer2' of https://github.com/tlambert03/pymmcor…
tlambert03 Jun 5, 2024
527a737
start adding 3d
tlambert03 Jun 7, 2024
63c2c47
remove print
tlambert03 Jun 7, 2024
b26e0bf
better 3d
tlambert03 Jun 7, 2024
631d421
more tweaks
tlambert03 Jun 7, 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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,5 @@ repos:
- id: mypy
files: "^src/"
additional_dependencies:
- pymmcore-plus >=0.9.0
- pymmcore-plus >=0.9.5
- useq-schema >=0.4.7
32 changes: 32 additions & 0 deletions examples/mda_viewer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from __future__ import annotations

from pymmcore_plus import CMMCorePlus, configure_logging
from qtpy import QtWidgets
from useq import MDASequence

from pymmcore_widgets._stack_viewer_v2._mda_viewer import MDAViewer

configure_logging(stderr_level="WARNING")

mmcore = CMMCorePlus.instance()
mmcore.loadSystemConfiguration()
mmcore.defineConfig("Channel", "DAPI", "Camera", "Mode", "Artificial Waves")
mmcore.defineConfig("Channel", "DAPI", "Camera", "StripeWidth", "1")
mmcore.defineConfig("Channel", "FITC", "Camera", "Mode", "Artificial Waves")
mmcore.defineConfig("Channel", "FITC", "Camera", "StripeWidth", "4")

sequence = MDASequence(
channels=({"config": "DAPI", "exposure": 1}, {"config": "FITC", "exposure": 1}),
stage_positions=[(0, 0), (1, 1)],
z_plan={"range": 9, "step": 0.4},
time_plan={"interval": 0.2, "loops": 4},
# grid_plan={"rows": 2, "columns": 1},
)


qapp = QtWidgets.QApplication([])
v = MDAViewer()
v.show()

mmcore.run_mda(sequence, output=v.data)
qapp.exec()
24 changes: 24 additions & 0 deletions examples/mda_viewer_queue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import sys
from queue import Queue

from pymmcore_plus import CMMCorePlus
from qtpy import QtWidgets
from useq import MDAEvent

from pymmcore_widgets._stack_viewer_v2._mda_viewer import MDAViewer

app = QtWidgets.QApplication(sys.argv)
mmcore = CMMCorePlus.instance()
mmcore.loadSystemConfiguration()

canvas = MDAViewer()
canvas.show()

q = Queue()
mmcore.run_mda(iter(q.get, None), output=canvas.data)
for i in range(10):
for c in range(2):
q.put(MDAEvent(index={"t": i, "c": c}, exposure=1))
q.put(None)

app.exec()
27 changes: 27 additions & 0 deletions examples/stack_viewer/dask_arr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from __future__ import annotations

import numpy as np
from dask.array.core import map_blocks
from qtpy import QtWidgets

from pymmcore_widgets._stack_viewer_v2 import StackViewer

frame_size = (1024, 1024)


def _dask_block(block_id: tuple[int, int, int, int, int]) -> np.ndarray | None:
if isinstance(block_id, np.ndarray):
return None
data = np.random.randint(0, 255, size=frame_size, dtype=np.uint8)
return data[(None,) * 3]


chunks = [(1,) * x for x in (1000, 64, 3)]
chunks += [(x,) for x in frame_size]
dask_arr = map_blocks(_dask_block, chunks=chunks, dtype=np.uint8)

if __name__ == "__main__":
qapp = QtWidgets.QApplication([])
v = StackViewer(dask_arr)
v.show()
qapp.exec()
17 changes: 17 additions & 0 deletions examples/stack_viewer/jax_arr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from __future__ import annotations

import jax.numpy as jnp
from numpy_arr import generate_5d_sine_wave
from qtpy import QtWidgets

from pymmcore_widgets._stack_viewer_v2._stack_viewer import StackViewer

# Example usage
array_shape = (10, 3, 5, 512, 512) # Specify the desired dimensions
sine_wave_5d = jnp.asarray(generate_5d_sine_wave(array_shape))

if __name__ == "__main__":
qapp = QtWidgets.QApplication([])
v = StackViewer(sine_wave_5d, channel_axis=1)
v.show()
qapp.exec()
63 changes: 63 additions & 0 deletions examples/stack_viewer/numpy_arr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from __future__ import annotations

import numpy as np
from qtpy import QtWidgets

from pymmcore_widgets._stack_viewer_v2._stack_viewer import StackViewer


def generate_5d_sine_wave(
shape: tuple[int, int, int, int, int],
amplitude: float = 240,
base_frequency: float = 5,
) -> np.ndarray:
"""5D dataset."""
# Unpack the dimensions
angle_dim, freq_dim, phase_dim, ny, nx = shape

# Create an empty array to hold the data
output = np.zeros(shape)

# Define spatial coordinates for the last two dimensions
half_per = base_frequency * np.pi
x = np.linspace(-half_per, half_per, nx)
y = np.linspace(-half_per, half_per, ny)
y, x = np.meshgrid(y, x)

# Iterate through each parameter in the higher dimensions
for phase_idx in range(phase_dim):
for freq_idx in range(freq_dim):
for angle_idx in range(angle_dim):
# Calculate phase and frequency
phase = np.pi / phase_dim * phase_idx
frequency = 1 + (freq_idx * 0.1) # Increasing frequency with each step

# Calculate angle
angle = np.pi / angle_dim * angle_idx
# Rotate x and y coordinates
xr = np.cos(angle) * x - np.sin(angle) * y
np.sin(angle) * x + np.cos(angle) * y

# Compute the sine wave
sine_wave = (amplitude * 0.5) * np.sin(frequency * xr + phase)
sine_wave += amplitude * 0.5

# Assign to the output array
output[angle_idx, freq_idx, phase_idx] = sine_wave

return output


try:
from skimage import data

img = data.cells3d()
except Exception:
img = generate_5d_sine_wave((10, 3, 8, 512, 512))


if __name__ == "__main__":
qapp = QtWidgets.QApplication([])
v = StackViewer(img)
v.show()
qapp.exec()
23 changes: 23 additions & 0 deletions examples/stack_viewer/tensorstore_arr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from __future__ import annotations

import numpy as np
import tensorstore as ts
from qtpy import QtWidgets

from pymmcore_widgets._stack_viewer_v2 import StackViewer

shape = (10, 4, 3, 512, 512)
ts_array = ts.open(
{"driver": "zarr", "kvstore": {"driver": "memory"}},
create=True,
shape=shape,
dtype=ts.uint8,
).result()
ts_array[:] = np.random.randint(0, 255, size=shape, dtype=np.uint8)
ts_array = ts_array[ts.d[:].label["t", "c", "z", "y", "x"]]

if __name__ == "__main__":
qapp = QtWidgets.QApplication([])
v = StackViewer(ts_array)
v.show()
qapp.exec()
14 changes: 14 additions & 0 deletions examples/stack_viewer/xarray_arr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from __future__ import annotations

import xarray as xr
from qtpy import QtWidgets

from pymmcore_widgets._stack_viewer_v2 import StackViewer

da = xr.tutorial.open_dataset("air_temperature").air

if __name__ == "__main__":
qapp = QtWidgets.QApplication([])
v = StackViewer(da, colormaps=["thermal"], channel_mode="composite")
v.show()
qapp.exec()
16 changes: 16 additions & 0 deletions examples/stack_viewer/zarr_arr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from __future__ import annotations

import zarr
import zarr.storage
from qtpy import QtWidgets

from pymmcore_widgets._stack_viewer_v2 import StackViewer

URL = "https://s3.embl.de/i2k-2020/ngff-example-data/v0.4/tczyx.ome.zarr"
zarr_arr = zarr.open(URL, mode="r")

if __name__ == "__main__":
qapp = QtWidgets.QApplication([])
v = StackViewer(zarr_arr["s0"])
v.show()
qapp.exec()
4 changes: 2 additions & 2 deletions examples/stack_viewer.py → examples/stack_viewer_v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from qtpy import QtWidgets
from useq import MDASequence

from pymmcore_widgets.experimental import StackViewer
from pymmcore_widgets._stack_viewer_v1 import StackViewer

size = 1028

Expand All @@ -20,7 +20,7 @@

sequence = MDASequence(
channels=(
# {"config": "DAPI", "exposure": 10},
{"config": "DAPI", "exposure": 10},
# {"config": "FITC", "exposure": 1},
{"config": "Cy5", "exposure": 1},
),
Expand Down
17 changes: 13 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,24 @@ classifiers = [
dynamic = ["version"]
dependencies = [
'fonticon-materialdesignicons6',
'pymmcore-plus[cli] >=0.9.0',
'pymmcore-plus[cli] >=0.9.5',
'qtpy >=2.0',
'superqt[quantity] >=0.5.3',
'superqt[quantity] >=0.6.5',
'useq-schema >=0.4.7',
]

# extras
# https://peps.python.org/pep-0621/#dependencies-optional-dependencies
[project.optional-dependencies]
test = ["pytest>=6.0", "pytest-cov", "pytest-qt", "PyYAML", "vispy", "cmap", "zarr"]
test = [
"pytest>=6.0",
"pytest-cov",
"pytest-qt",
"PyYAML",
"vispy",
"cmap",
"zarr",
]
pyqt5 = ["PyQt5"]
pyside2 = ["PySide2"]
pyqt6 = ["PyQt6"]
Expand Down Expand Up @@ -127,6 +135,7 @@ docstring-code-format = true

# https://docs.pytest.org/en/6.2.x/customize.html
[tool.pytest.ini_options]
markers = ["allow_leaks"]
minversion = "6.0"
testpaths = ["tests"]
filterwarnings = [
Expand Down Expand Up @@ -174,4 +183,4 @@ ignore = [
]

[tool.typos.default]
extend-ignore-identifiers-re = ["(?i)ome"]
extend-ignore-identifiers-re = ["(?i)nd2?.*", "(?i)ome"]
Original file line number Diff line number Diff line change
Expand Up @@ -464,11 +464,9 @@ def _reload_position(self) -> None:
self.cmap_names = self.qt_settings.value("cmaps", ["gray", "cyan", "magenta"])

def _collapse_view(self) -> None:
w, h = self.img_size
view_rect = (
(
self.view_rect[0][0] - self.img_size[0] / 2,
self.view_rect[0][1] + self.img_size[1] / 2,
),
(self.view_rect[0][0] - w / 2, self.view_rect[0][1] + h / 2),
self.view_rect[1],
)
self.view.camera.rect = view_rect
Expand Down
4 changes: 4 additions & 0 deletions src/pymmcore_widgets/_stack_viewer_v2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from ._mda_viewer import MDAViewer
from ._stack_viewer import StackViewer

__all__ = ["StackViewer", "MDAViewer"]
36 changes: 36 additions & 0 deletions src/pymmcore_widgets/_stack_viewer_v2/_backends/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from __future__ import annotations

import importlib
import importlib.util
import os
import sys
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from pymmcore_widgets._stack_viewer_v2._protocols import PCanvas


def get_canvas(backend: str | None = None) -> type[PCanvas]:
backend = backend or os.getenv("CANVAS_BACKEND", None)
if backend == "vispy" or (backend is None and "vispy" in sys.modules):
from ._vispy import VispyViewerCanvas

return VispyViewerCanvas

if backend == "pygfx" or (backend is None and "pygfx" in sys.modules):
from ._pygfx import PyGFXViewerCanvas

return PyGFXViewerCanvas

if backend is None:
if importlib.util.find_spec("vispy") is not None:
from ._vispy import VispyViewerCanvas

return VispyViewerCanvas

if importlib.util.find_spec("pygfx") is not None:
from ._pygfx import PyGFXViewerCanvas

return PyGFXViewerCanvas

raise RuntimeError("No canvas backend found")
Loading
Loading