Skip to content

Commit

Permalink
Merge pull request #303 from imagej/JNumber-widgets
Browse files Browse the repository at this point in the history
Add widgets for Java Number implementations
  • Loading branch information
gselzer authored Oct 29, 2024
2 parents deaeab4 + 2a2f687 commit 25289d4
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 1 deletion.
1 change: 1 addition & 0 deletions dev-environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies:
- pandas
- pyimagej >= 1.4.1
- scyjava >= 1.8.1
- xarray < 2024.10.0
# Version rules to avoid problems
- qtconsole != 5.4.2
- typing_extensions != 4.6.0
Expand Down
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies:
- pandas
- pyimagej >= 1.4.1
- scyjava >= 1.8.1
- xarray < 2024.10.0
# Version rules to avoid problems
- qtconsole != 5.4.2
- typing_extensions != 4.6.0
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ dependencies = [
"pandas",
"pyimagej >= 1.4.1",
"scyjava >= 1.9.1",
"xarray < 2024.10.0",
# Version rules to avoid problems
"qtconsole != 5.4.2", # https://github.com/napari/napari/issues/5700
"typing_extensions != 4.6.0", # https://github.com/pydantic/pydantic/issues/5821
Expand Down
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
64 changes: 64 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,69 @@ 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 = ctor = jc.Byte
if cls == jc.Short:
cls = ctor = jc.Short
if cls == jc.Integer:
cls = ctor = jc.Integer
if cls == jc.Long:
cls = ctor = jc.Long
if cls == jc.Float:
cls = ctor = jc.Float
if cls == jc.Double:
cls = ctor = jc.Double
if cls == jc.BigInteger:
cls = jc.BigInteger
ctor = jc.BigInteger.valueOf
if cls == jc.BigDecimal:
cls = jc.BigDecimal
ctor = jc.BigDecimal.valueOf

extra_args = {}
# HACK: Best attempt for BigDecimal/BigInteger
extra_args["min"], extra_args["max"] = (
numeric_bounds(jc.Long)
if cls == jc.BigInteger
else numeric_bounds(jc.Double)
if cls == jc.BigDecimal
else numeric_bounds(cls)
)
# Case logic for implementation-specific attributes
parent = (
FloatSpinBox
if issubclass(cls, (jc.Double, jc.Float, jc.BigDecimal))
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 ctor(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
1 change: 0 additions & 1 deletion tests/utilities/test_module_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,6 @@ def test_add_scijava_metadata(metadata_module_item: DummyModuleItem):
assert param_map["label"] == "bar"
assert param_map["tooltip"] == "The foo."
assert param_map["choices"] == ["a", "b", "c"]
assert param_map["widget_type"] == "FloatSpinBox"


choiceList = [
Expand Down
28 changes: 28 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,32 @@ def test_mutable_output_add_new_image(
assert foo is output_widget.value


def test_numbers(ij):
numbers = [
jc.Byte,
jc.Short,
jc.Integer,
jc.Long,
jc.Float,
jc.Double,
jc.BigInteger,
jc.BigDecimal,
]
for number in numbers:
# NB See HACK in number_widget_for
if number == jc.BigInteger:
min_val, max_val = numeric_bounds(jc.Long)
elif number == jc.BigDecimal:
min_val, max_val = numeric_bounds(jc.Double)
else:
min_val, max_val = numeric_bounds(number)

widget = number_widget_for(number.class_)()
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 25289d4

Please sign in to comment.