Skip to content

Commit

Permalink
threshold widget
Browse files Browse the repository at this point in the history
  • Loading branch information
bgraedel committed Dec 13, 2023
1 parent 89ad0c6 commit 069cf6b
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 71 deletions.
6 changes: 2 additions & 4 deletions src/arcospx/_tests/test_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,14 @@ def test_track_events(make_napari_viewer, qtbot):
"arcosPx-napari", "Track Events"
)
widget.image_selector.value = viewer.layers["test_img"]
widget.threshold.value = 1
widget.eps.value = 1
widget.epsPrev.value = 1
widget.epsPrev.value = 0
widget.minClSz.value = 1
widget.minSamples.value = 1
widget.nPrev.value = 1

with qtbot.waitSignal(
viewer.layers.events.inserted,
timeout=50000,
timeout=5000,
):
widget()

Expand Down
150 changes: 89 additions & 61 deletions src/arcospx/_widget.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
"""
This module is an example of a barebones QWidget plugin for napari
It implements the Widget specification.
see: https://napari.org/stable/plugins/guides.html?#widgets
Replace code below according to your needs.
This module containst the magic factory widgets for arcospx
"""
from time import sleep
from typing import Literal

from arcos4py.tools import track_events_image
import numpy as np
from arcos4py.tools import remove_image_background
from arcos4py.tools._detect_events import ImageTracker, Linker
from magicgui import magic_factory
from napari.layers import Image
from napari.qt.threading import FunctionWorker, thread_worker
from napari.types import LayerDataTuple
from napari.utils import progress
from napari.utils.notifications import show_info
from skimage.filters import threshold_otsu


class AbortException(Exception):
def do_nothing_function():
pass


Expand All @@ -31,90 +30,119 @@ def remove_background(
dims: str = "TXY",
crop_time_axis: bool = False,
) -> FunctionWorker[LayerDataTuple]:
size_tuple = (size_0, size_1, size_2)
pbar = progress(total=0)

@thread_worker(connect={"returned": pbar.close})
def remove_image_background_2() -> LayerDataTuple:
selected_image = image.data
# removed_background = remove_image_background(
# image=selected_image, filter_type=filter_type, size=size, dims=dims, crop_time_axis=crop_time_axis
# )

removed_background = selected_image
removed_background = remove_image_background(

Check warning on line 39 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L38-L39

Added lines #L38 - L39 were not covered by tests
image=selected_image,
filter_type=filter_type,
size=size_tuple,
dims=dims,
crop_time_axis=crop_time_axis,
)
removed_background = np.clip(removed_background, 0, None)

Check warning on line 46 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L46

Added line #L46 was not covered by tests

layer_properties = {"name": f"{image.name} background removed"}
sleep(0.5)
sleep(0.1) # need this for the tests to pass ...
return (removed_background, layer_properties, "image")

Check warning on line 50 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L48-L50

Added lines #L48 - L50 were not covered by tests

return remove_image_background_2()


@magic_factory()
def _on_thresholder_init(widget):
def update_slider(image: Image):

Check warning on line 56 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L56

Added line #L56 was not covered by tests
# probably don't want to read then throw this array away...
# but just an example
min_value = np.min(image.data)
max_value = np.max(image.data)
widget.threshold.max = max_value
widget.threshold.min = min_value
widget.threshold.value = threshold_otsu(image.data)

Check warning on line 63 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L59-L63

Added lines #L59 - L63 were not covered by tests

widget.image_selector.changed.connect(update_slider)

Check warning on line 65 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L65

Added line #L65 was not covered by tests


@magic_factory(
auto_call=True,
threshold={"widget_type": "FloatSlider", "max": 1},
widget_init=_on_thresholder_init,
)
def thresholder(
image_selector: Image,
threshold: float = 0.5,
) -> LayerDataTuple:
binary_image = np.where(image_selector.data > threshold, 1, 0)
return (binary_image, {"name": f"{image_selector.name} binary"}, "image")

Check warning on line 78 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L77-L78

Added lines #L77 - L78 were not covered by tests


def _on_track_events_init(widget):
def _reset_callbutton_name():
widget.call_button.text = "Run"

def _set_widget_worker(funciton_worker):
widget.arcos_worker.value = funciton_worker
widget.call_button.text = "Abort"
widget.arcos_worker.value.finished.connect(_reset_callbutton_name)

widget.called.connect(_set_widget_worker)


@magic_factory(
arcos_worker={"visible": False}, widget_init=_on_track_events_init
)
def track_events(
image_selector: Image,
threshold: int = 300,
eps: int = 10,
epsPrev: int = 50,
minClSz: int = 50,
minSamples: int = 2,
nPrev: int = 2,
arcos_worker: FunctionWorker | None = None,
eps: float = 1.5,
epsPrev: float = 0,
minClSz: int = 9,
nPrev: int = 1,
dims: str = "TXY",
) -> FunctionWorker[LayerDataTuple]:
pbar = progress(total=0)

@thread_worker(connect={"returned": pbar.close})
def track_events_2() -> LayerDataTuple:
global abort_flag
if arcos_worker is not None and arcos_worker.is_running:
arcos_worker.quit()
show_info("Operation aborted by user.")
return FunctionWorker(do_nothing_function)

Check warning on line 108 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L106-L108

Added lines #L106 - L108 were not covered by tests

# Reset the abort flag at the start of each execution
abort_flag = False
pbar = progress(total=image_selector.data.shape[0])

if abort_flag:
# Return an error message
# return "Interrupt error: Operation aborted by user."
# Or raise a custom exception
pbar.close()
raise AbortException("Operation aborted by user.")
@thread_worker(connect={"finished": pbar.close, "yielded": pbar.update})
def track_events_2() -> LayerDataTuple:
# Reset the abort flag at the start of each executio

selected_image = image_selector.data
img_tracked = track_events_image(
selected_image >= threshold,
eps=eps,
epsPrev=epsPrev,
minClSz=minClSz,
minSamples=minSamples,
nPrev=nPrev,
dims=dims,
)

if abort_flag:
# Return an error message
# return "Interrupt error: Operation aborted by user."
# Or raise a custom exception
pbar.close()
raise AbortException("Operation aborted by user.")
# Adjust parameters based on dimensionality

# Like this we create the layer as a layer-data-tuple object which will automatically be parsed by napari and added to the viewer
# This is more flexible and does not require the function to know about the viewer directly
# Additionally like this you can now set the metadata of the layer
layer_properties = {
"name": f"{image_selector.name} tracked",
"metadata": {
"threshold": threshold,
"eps": eps,
"epsPrev": epsPrev,
"minClSz": minClSz,
"nPrev": nPrev,
"filename": image_selector.name, ## eg. setting medatada to the name of the image layer
},
}
return (img_tracked, layer_properties, "labels")

# return the layer data tuple
return track_events_2()
linker = Linker(

Check warning on line 130 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L130

Added line #L130 was not covered by tests
eps=eps,
epsPrev=epsPrev if epsPrev else None,
minClSz=minClSz,
nPrev=nPrev,
predictor=False,
)
tracker = ImageTracker(linker)

Check warning on line 137 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L137

Added line #L137 was not covered by tests
# find indices of T in dims
img_tracked = np.zeros_like(selected_image, dtype=np.uint16)
for idx, timepoint in enumerate(tracker.track(selected_image, dims)):
img_tracked[idx] = timepoint
yield 1

Check warning on line 142 in src/arcospx/_widget.py

View check run for this annotation

Codecov / codecov/patch

src/arcospx/_widget.py#L139-L142

Added lines #L139 - L142 were not covered by tests

return (img_tracked, layer_properties, "labels")

@magic_factory()
def abort_process():
global abort_flag
abort_flag = True
# return the layer data tuple
arcos_worker = track_events_2()
return arcos_worker
12 changes: 6 additions & 6 deletions src/arcospx/napari.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name: arcosPx-napari
display_name: arcosPx
# use 'hidden' to remove plugin from napari hub search results
visibility: public
visibility: hidden
categories: ["Segmentation"]
contributions:
commands:
Expand All @@ -11,13 +11,13 @@ contributions:
- id: arcosPx-napari.track_events
python_name: arcospx._widget:track_events
title: Track Events
- id: arcosPx-napari.abort_process
python_name: arcospx._widget:abort_process
title: Abort Process
- id: arcosPx-napari.simple_thresholder
python_name: arcospx._widget:thresholder
title: Thresholding
widgets:
- command: arcosPx-napari.remove_background
display_name: Remove Background
- command: arcosPx-napari.simple_thresholder
display_name: Thresholding
- command: arcosPx-napari.track_events
display_name: Track Events
- command: arcosPx-napari.abort_process
display_name: Abort Current Process

0 comments on commit 069cf6b

Please sign in to comment.