Skip to content

Commit

Permalink
Add widgets for Java Number implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
gselzer committed Oct 29, 2024
1 parent deaeab4 commit 01c3032
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 0 deletions.
9 changes: 9 additions & 0 deletions src/napari_imagej/types/widget_mappings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from napari_imagej.widgets.parameter_widgets import (
ShapeWidget,
file_widget_for,
number_widget_for,
numeric_type_widget_for,
)

Expand Down Expand Up @@ -71,6 +72,14 @@ def _numeric_type_preference(
return numeric_type_widget_for(item.getType())


@_widget_preference
def _number_preference(
item: "jc.ModuleItem", type_hint: Union[type, str]
) -> Optional[Union[type, str]]:
if issubclass(item.getType(), jc.Number):
return number_widget_for(item.getType())


@_widget_preference
def _mutable_output_preference(
item: "jc.ModuleItem", type_hint: Union[type, str]
Expand Down
47 changes: 47 additions & 0 deletions src/napari_imagej/widgets/parameter_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from napari.layers import Layer
from napari.utils._magicgui import get_layers
from numpy import dtype
from scyjava import numeric_bounds

from napari_imagej.java import jc

Expand Down Expand Up @@ -83,6 +84,52 @@ def value(self):
return Widget


@lru_cache(maxsize=None)
def number_widget_for(cls: type):
if not issubclass(cls, jc.Number):
return None
# Sensible default for Number iface
if cls == jc.Number:
cls = jc.Double

# NB cls in this instance is jc.Byte.class_, NOT jc.Byte
# We want the latter for use in numeric_bounds and in the widget subclass
if cls == jc.Byte:
cls = jc.Byte
if cls == jc.Short:
cls = jc.Short
if cls == jc.Integer:
cls = jc.Integer
if cls == jc.Long:
cls = jc.Long
if cls == jc.Float:
cls = jc.Float
if cls == jc.Double:
cls = jc.Double

extra_args = {}
extra_args["min"], extra_args["max"] = numeric_bounds(cls)
# Case logic for implementation-specific attributes
parent = FloatSpinBox if issubclass(cls, (jc.Double, jc.Float)) else SpinBox

# Define the new widget
class Widget(parent):
def __init__(self, **kwargs):
for k, v in extra_args.items():
kwargs.setdefault(k, v)
super().__init__(**kwargs)

@property
def value(self):
return cls(parent.value.fget(self))

@value.setter
def value(self, value: Any):
parent.value.fset(self, value)

return Widget


class MutableOutputWidget(Container):
"""
A ComboBox widget combined with a button that creates new layers.
Expand Down
19 changes: 19 additions & 0 deletions tests/widgets/test_parameter_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@
)
from napari import current_viewer
from napari.layers import Image
from scyjava import numeric_bounds

from napari_imagej.widgets.parameter_widgets import (
DirectoryWidget,
MutableOutputWidget,
OpenFileWidget,
SaveFileWidget,
ShapeWidget,
number_widget_for,
numeric_type_widget_for,
)
from tests.utils import jc
Expand Down Expand Up @@ -164,6 +166,23 @@ def test_mutable_output_add_new_image(
assert foo is output_widget.value


def test_numbers():
numbers = [
(jc.Byte),
(jc.Short),
(jc.Integer),
(jc.Long),
(jc.Float),
(jc.Double),
]
for number in numbers:
widget = number_widget_for(number.class_)()
min_val, max_val = numeric_bounds(number)
assert min_val == widget.min
assert max_val == widget.max
assert isinstance(widget.value, number)


def test_realType():
real_types = [
(jc.BitType),
Expand Down

0 comments on commit 01c3032

Please sign in to comment.