Skip to content

Commit

Permalink
first customization
Browse files Browse the repository at this point in the history
  • Loading branch information
Martin Schorb committed Mar 14, 2024
1 parent c767850 commit e58b37f
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 138 deletions.
7 changes: 6 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ long_description = file: README.md
long_description_content_type = text/markdown

author = Martin Schorb
author_email = [email protected]
license = MIT
license_files = LICENSE
classifiers =
Expand All @@ -23,6 +22,12 @@ classifiers =
Programming Language :: Python :: 3.10
Topic :: Scientific/Engineering :: Image Processing

url = https://github.com/mobie/mobie-napari-bridge
project_urls =
Bug Tracker = https://github.com/mobie/mobie-napari-bridge/issues
Documentation = https://github.com/mobie/mobie-napari-bridge#README.md
Source Code = https://github.com/mobie/mobie-napari-bridge
User Support = https://github.com/mobie/mobie-napari-bridge/issues

[options]
packages = find:
Expand Down
6 changes: 2 additions & 4 deletions src/mobie_napari_bridge/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from ._reader import napari_get_reader
from ._sample_data import make_sample_data
from ._widget import ExampleQWidget, ImageThreshold, threshold_autogenerate_widget, threshold_magic_widget
from ._widget import ExampleQWidget, TestBrowse
from ._writer import write_multiple, write_single_image

__all__ = (
Expand All @@ -11,7 +11,5 @@
"write_multiple",
"make_sample_data",
"ExampleQWidget",
"ImageThreshold",
"threshold_autogenerate_widget",
"threshold_magic_widget",
"TestBrowse"
)
45 changes: 1 addition & 44 deletions src/mobie_napari_bridge/_tests/test_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,10 @@

from mobie_napari_bridge._widget import (
ExampleQWidget,
ImageThreshold,
threshold_autogenerate_widget,
threshold_magic_widget,
TestBrowse
)


def test_threshold_autogenerate_widget():
# because our "widget" is a pure function, we can call it and
# test it independently of napari
im_data = np.random.random((100, 100))
thresholded = threshold_autogenerate_widget(im_data, 0.5)
assert thresholded.shape == im_data.shape
# etc.


# make_napari_viewer is a pytest fixture that returns a napari viewer object
# you don't need to import it, as long as napari is installed
# in your testing environment
def test_threshold_magic_widget(make_napari_viewer):
viewer = make_napari_viewer()
layer = viewer.add_image(np.random.random((100, 100)))

# our widget will be a MagicFactory or FunctionGui instance
my_widget = threshold_magic_widget()

# if we "call" this object, it'll execute our function
thresholded = my_widget(viewer.layers[0], 0.5)
assert thresholded.shape == layer.data.shape
# etc.


def test_image_threshold_widget(make_napari_viewer):
viewer = make_napari_viewer()
layer = viewer.add_image(np.random.random((100, 100)))
my_widget = ImageThreshold(viewer)

# because we saved our widgets as attributes of the container
# we can set their values without having to "interact" with the viewer
my_widget._image_layer_combo.value = layer
my_widget._threshold_slider.value = 0.5

# this allows us to run our functions directly and ensure
# correct results
my_widget._threshold_im()
assert len(viewer.layers) == 2


# capsys is a pytest fixture that captures stdout and stderr output streams
def test_example_q_widget(make_napari_viewer, capsys):
# make viewer and add an image layer using our fixture
Expand Down
92 changes: 19 additions & 73 deletions src/mobie_napari_bridge/_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,97 +32,43 @@

from magicgui import magic_factory
from magicgui.widgets import CheckBox, Container, create_widget
from qtpy.QtWidgets import QHBoxLayout, QPushButton, QWidget
from qtpy.QtWidgets import QFileDialog, QHBoxLayout, QPushButton, QWidget
from skimage.util import img_as_float

if TYPE_CHECKING:
import napari


# Uses the `autogenerate: true` flag in the plugin manifest
# to indicate it should be wrapped as a magicgui to autogenerate
# a widget.
def threshold_autogenerate_widget(
img: "napari.types.ImageData",
threshold: "float",
) -> "napari.types.LabelsData":
return img_as_float(img) > threshold


# the magic_factory decorator lets us customize aspects of our widget
# we specify a widget type for the threshold parameter
# and use auto_call=True so the function is called whenever
# the value of a parameter changes
@magic_factory(
threshold={"widget_type": "FloatSlider", "max": 1}, auto_call=True
)
def threshold_magic_widget(
img_layer: "napari.layers.Image", threshold: "float"
) -> "napari.types.LabelsData":
return img_as_float(img_layer.data) > threshold


# if we want even more control over our widget, we can use
# magicgui `Container`
class ImageThreshold(Container):
class ExampleQWidget(QWidget):
# your QWidget.__init__ can optionally request the napari viewer instance
# use a type annotation of 'napari.viewer.Viewer' for any parameter
def __init__(self, viewer: "napari.viewer.Viewer"):
super().__init__()
self._viewer = viewer
# use create_widget to generate widgets from type annotations
self._image_layer_combo = create_widget(
label="Image", annotation="napari.layers.Image"
)
self._threshold_slider = create_widget(
label="Threshold", annotation=float, widget_type="FloatSlider"
)
self._threshold_slider.min = 0
self._threshold_slider.max = 1
# use magicgui widgets directly
self._invert_checkbox = CheckBox(text="Keep pixels below threshold")

# connect your own callbacks
self._threshold_slider.changed.connect(self._threshold_im)
self._invert_checkbox.changed.connect(self._threshold_im)

# append into/extend the container with your widgets
self.extend(
[
self._image_layer_combo,
self._threshold_slider,
self._invert_checkbox,
]
)
self.viewer = viewer

def _threshold_im(self):
image_layer = self._image_layer_combo.value
if image_layer is None:
return
btn = QPushButton("Click me!")
btn.clicked.connect(self._on_click)

image = img_as_float(image_layer.data)
name = image_layer.name + "_thresholded"
threshold = self._threshold_slider.value
if self._invert_checkbox.value:
thresholded = image < threshold
else:
thresholded = image > threshold
if name in self._viewer.layers:
self._viewer.layers[name].data = thresholded
else:
self._viewer.add_labels(thresholded, name=name)
self.setLayout(QHBoxLayout())
self.layout().addWidget(btn)

def _on_click(self):
print("napari has", len(self.viewer.layers), "layers")

class ExampleQWidget(QWidget):
class TestBrowse(QWidget):
# your QWidget.__init__ can optionally request the napari viewer instance
# use a type annotation of 'napari.viewer.Viewer' for any parameter
def __init__(self, viewer: "napari.viewer.Viewer"):
super().__init__()
self.viewer = viewer

btn = QPushButton("Click me!")
btn.clicked.connect(self._on_click)
btn = QPushButton("Load this!")
btn.clicked.connect(self._button_click)

dlg1 = QFileDialog(caption="123")

self.setLayout(QHBoxLayout())
self.layout().addWidget(dlg1)
self.layout().addWidget(btn)

def _on_click(self):
print("napari has", len(self.viewer.layers), "layers")
def _button_click(self):
print("resxdcfghjj")
12 changes: 12 additions & 0 deletions src/mobie_napari_bridge/debug_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# launch_napari.py
from napari import Viewer, run


viewer = Viewer()
dock_widget, plugin_widget = viewer.window.add_plugin_dock_widget(
"mobie-napari-bridge", "TestBrowse QWidget"
)
# Optional steps to setup your plugin to a state of failure
# E.g. plugin_widget.parameter_name.value = "some value"
# E.g. plugin_widget.button.click()
run()
21 changes: 5 additions & 16 deletions src/mobie_napari_bridge/napari.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,12 @@ contributions:
- id: mobie-napari-bridge.make_sample_data
python_name: mobie_napari_bridge._sample_data:make_sample_data
title: Load sample data from MoBIE Bridge
- id: mobie-napari-bridge.make_container_widget
python_name: mobie_napari_bridge:ImageThreshold
title: Make threshold Container widget
- id: mobie-napari-bridge.make_magic_widget
python_name: mobie_napari_bridge:threshold_magic_widget
title: Make threshold magic widget
- id: mobie-napari-bridge.make_function_widget
python_name: mobie_napari_bridge:threshold_autogenerate_widget
title: Make threshold function widget
- id: mobie-napari-bridge.make_qwidget
python_name: mobie_napari_bridge:ExampleQWidget
title: Make example QWidget
- id: mobie-napari-bridge.make_tbwidget
python_name: mobie_napari_bridge:TestBrowse
title: Test Browse widget
readers:
- command: mobie-napari-bridge.get_reader
accepts_directories: false
Expand All @@ -46,12 +40,7 @@ contributions:
display_name: MoBIE Bridge
key: unique_id.1
widgets:
- command: mobie-napari-bridge.make_container_widget
display_name: Container Threshold
- command: mobie-napari-bridge.make_magic_widget
display_name: Magic Threshold
- command: mobie-napari-bridge.make_function_widget
autogenerate: true
display_name: Autogenerate Threshold
- command: mobie-napari-bridge.make_qwidget
display_name: Example QWidget
- command: mobie-napari-bridge.make_tbwidget
display_name: TestBrowse QWidget

0 comments on commit e58b37f

Please sign in to comment.