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: Add ROI selection tool #23

Merged
merged 7 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
84 changes: 75 additions & 9 deletions src/ndv/viewer/_backends/_protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,88 @@
from typing import TYPE_CHECKING, Any, Literal, Protocol

if TYPE_CHECKING:
from typing import Sequence

import cmap
import numpy as np
from qtpy.QtCore import Qt
from qtpy.QtWidgets import QWidget


class PImageHandle(Protocol):
class CanvasElement(Protocol):
"""Protocol defining an interactive element on the Canvas."""

@property
def visible(self) -> bool:
"""Defines whether the element is visible on the canvas."""

@visible.setter
def visible(self, visible: bool) -> None:
"""Sets element visibility."""

@property
def can_select(self) -> bool:
"""Defines whether the element can be selected."""

@property
def selected(self) -> bool:
"""Returns element selection status."""

@selected.setter
def selected(self, selected: bool) -> None:
"""Sets element selection status."""

def cursor_at(self, pos: Sequence[float]) -> Qt.CursorShape | None:
"""Returns the element's cursor preference at the provided position."""

def start_move(self, pos: Sequence[float]) -> None:
"""
Behavior executed at the beginning of a "move" operation.

In layman's terms, this is the behavior executed during the the "click"
of a "click-and-drag".
"""

def move(self, pos: Sequence[float]) -> None:
"""
Behavior executed throughout a "move" operation.

In layman's terms, this is the behavior executed during the "drag"
of a "click-and-drag".
"""

def remove(self) -> None:
"""Removes the element from the canvas."""


class PImageHandle(CanvasElement, Protocol):
@property
def data(self) -> np.ndarray: ...
@data.setter
def data(self, data: np.ndarray) -> None: ...
@property
def visible(self) -> bool: ...
@visible.setter
def visible(self, visible: bool) -> None: ...
@property
def clim(self) -> Any: ...
@clim.setter
def clim(self, clims: tuple[float, float]) -> None: ...
@property
def cmap(self) -> Any: ...
def cmap(self) -> cmap.Colormap: ...
@cmap.setter
def cmap(self, cmap: Any) -> None: ...
def remove(self) -> None: ...
def cmap(self, cmap: cmap.Colormap) -> None: ...


class PRoiHandle(CanvasElement, Protocol):
@property
def vertices(self) -> Sequence[Sequence[float]]: ...
@vertices.setter
def vertices(self, data: Sequence[Sequence[float]]) -> None: ...
@property
def color(self) -> Any: ...
@color.setter
def color(self, color: cmap.Color) -> None: ...
@property
def border_color(self) -> Any: ...
@border_color.setter
def border_color(self, color: cmap.Color) -> None: ...


class PCanvas(Protocol):
Expand All @@ -46,8 +105,15 @@ def add_image(
def add_volume(
self, data: np.ndarray | None = ..., cmap: cmap.Colormap | None = ...
) -> PImageHandle: ...

def canvas_to_world(
self, pos_xy: tuple[float, float]
) -> tuple[float, float, float]:
"""Map XY canvas position (pixels) to XYZ coordinate in world space."""

def elements_at(self, pos_xy: tuple[float, float]) -> list[CanvasElement]: ...
def add_roi(
self,
vertices: Sequence[tuple[float, float]] | None = None,
color: cmap.Color | None = None,
border_color: cmap.Color | None = None,
) -> PRoiHandle: ...
Loading