Skip to content

Commit

Permalink
Merge pull request #1 from leaBroe/main
Browse files Browse the repository at this point in the history
Merging
  • Loading branch information
leaBroe authored Nov 20, 2023
2 parents 1d3b186 + 1ff339f commit 43a5176
Show file tree
Hide file tree
Showing 13 changed files with 213 additions and 85 deletions.
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ write_to = "src/arcospx/_version.py"
line-length = 79
target-version = ['py38', 'py39', 'py310']


[tool.ruff]
line-length = 79
select = [
Expand Down
3 changes: 2 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = arcosPx-napari

version = 0.0.1
description = A plugin to track spatio-temporal correlations in images
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down Expand Up @@ -34,6 +34,7 @@ install_requires =
numpy
magicgui
qtpy
arcos4py

python_requires = >=3.8
include_package_data = True
Expand Down
6 changes: 3 additions & 3 deletions src/arcospx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
except ImportError:
__version__ = "unknown"
from ._sample_data import make_sample_data
from ._widget import ExampleQWidget, example_magic_widget
from ._widget import remove_background, track_events

__all__ = (
"make_sample_data",
"ExampleQWidget",
"example_magic_widget",
"remove_background",
"track_events"
)
15 changes: 13 additions & 2 deletions src/arcospx/_sample_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
Replace code below according to your needs.
"""

from __future__ import annotations

import numpy
Expand All @@ -16,6 +17,16 @@ def make_sample_data():
# Return list of tuples
# [(data1, add_image_kwargs1), (data2, add_image_kwargs2)]
# Check the documentation for more information about the
# add_image_kwargs
# add_image_kwargs:
# https://napari.org/stable/api/napari.Viewer.html#napari.Viewer.add_image
return [(numpy.random.rand(512, 512), {})]
image_kwargs = {
"rgb": False,
"colormap": "gray",
"contrast_limits": [0, 255],
"visible": True,
"gamma": 1,
"interpolation2d":"nearest",
"name": "sample_data",
}
return [(numpy.random.rand(512, 512), image_kwargs)]

Binary file added src/arcospx/_tests/test_data/1_growing.tif
Binary file not shown.
Binary file added src/arcospx/_tests/test_data/1_growing_true.tif
Binary file not shown.
Binary file added src/arcospx/_tests/test_data/4_colliding.tif
Binary file not shown.
Binary file not shown.
Binary file not shown.
20 changes: 16 additions & 4 deletions src/arcospx/_tests/test_sample_data.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
# from arcospx import make_sample_data
import pytest
from arcospx import make_sample_data
import numpy as np
from qtpy import QtCore

# add your tests here...

def test_make_sample_data():
"""Test make_sample_data."""
data = make_sample_data()

# Check the shape of the numpy array
assert data[0][0].shape == (512, 512)

# Check if the first element of the tuple is a numpy array
assert isinstance(data[0][0], np.ndarray)

# Check if the second element of the tuple is a dictionary
assert isinstance(data[0][1], dict)

def test_something():
pass
81 changes: 49 additions & 32 deletions src/arcospx/_tests/test_widget.py
Original file line number Diff line number Diff line change
@@ -1,36 +1,53 @@
import numpy as np

from arcospx import ExampleQWidget, example_magic_widget


# make_napari_viewer is a pytest fixture that returns a napari viewer object
# 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
import pytest
from qtpy import QtCore
from arcospx import make_sample_data, remove_background, track_events
import napari
from skimage.io import imread
from numpy.testing import assert_array_equal
from qtpy.QtCore import QTimer
from napari.layers.image import Image
from arcos4py.tools import remove_image_background, track_events_image
from pytestqt import qtbot

def test_remove_background(make_napari_viewer, qtbot):
"""
Test background removal on a simple image.
"""
viewer = make_napari_viewer()
viewer.add_image(np.random.random((100, 100)))

# create our widget, passing in the viewer
my_widget = ExampleQWidget(viewer)

# call our widget method
my_widget._on_click()

# read captured output and check that it's as we expected
captured = capsys.readouterr()
assert captured.out == "napari has 1 layers\n"


def test_example_magic_widget(make_napari_viewer, capsys):
test_img = imread('test_data/1_growing.tif')
viewer.add_image(test_img, name='test_img')
true_img = imread('test_data/1_growing_true.tif')
_,widget = viewer.window.add_plugin_dock_widget("arcosPx-napari", "Remove Background")
widget.image.value = viewer.layers['test_img']
widget.filter_type.value = "gaussian"
widget.size_0.value = 1
widget.size_1.value = 1
widget.size_2.value = 1
worker = widget()
with qtbot.waitSignal(worker.finished, timeout=10000):
pass
assert_array_equal(viewer.layers[1].data, true_img)


def test_track_events(make_napari_viewer, qtbot):
"""
Test tracking on a simple image.
"""
viewer = make_napari_viewer()
layer = viewer.add_image(np.random.random((100, 100)))

# this time, our widget will be a MagicFactory or FunctionGui instance
my_widget = example_magic_widget()

# if we "call" this object, it'll execute our function
my_widget(viewer.layers[0])
test_img = imread('test_data/test_data_track_events.tif')
viewer.add_image(test_img, name='test_img')
true_img = imread('test_data/test_track_events_true.tif')
_, widget = viewer.window.add_plugin_dock_widget("arcosPx-napari", "Track Events")
widget.image_selector.value = viewer.layers['test_img']
widget.threshold.value = 300
widget.eps.value = 10
widget.epsPrev.value = 50
widget.minClSz.value = 50
widget.minSamples.value = 2
widget.nPrev.value = 2
worker = widget()
with qtbot.waitSignal(worker.finished, timeout=10000):
pass
assert_array_equal(viewer.layers[1].data, true_img)

# read captured output and check that it's as we expected
captured = capsys.readouterr()
assert captured.out == f"you have selected {layer}\n"
138 changes: 115 additions & 23 deletions src/arcospx/_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,131 @@
"""
from typing import TYPE_CHECKING

from magicgui import magic_factory
from arcos4py.tools import remove_image_background, track_events_image
from magicgui import magic_factory, widgets
from napari import viewer
from qtpy.QtWidgets import QHBoxLayout, QPushButton, QWidget
from napari.types import LayerDataTuple
from napari.layers import Image
from napari.qt.threading import thread_worker, FunctionWorker
from napari.utils import progress
from qtpy.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel

from magicgui import magicgui


if TYPE_CHECKING:
import napari

# A global flag for aborting the process
abort_flag = False

# Optional: Define a custom exception for aborting the process
class AbortException(Exception):
pass

@magic_factory()
def remove_background(
image: Image,
filter_type: str = "gaussian",
size_0: int = 20,
size_1: int = 5,
size_2: int = 5,
dims: str = "TXY",
crop_time_axis: bool = False
) -> FunctionWorker[LayerDataTuple]:
size = (size_0, size_1, size_2)
pbar = progress(total=0)
@thread_worker(connect={'returned': pbar.close})
def remove_image_background_2() -> LayerDataTuple:
global abort_flag

# Reset the abort flag at the start of each execution
abort_flag = False

removed_background = remove_image_background(image.data, filter_type, size, dims, crop_time_axis)

if abort_flag:
pbar.close()
raise AbortException("Operation aborted by user.")

layer_properties = {
"name": f"{image.name} background removed",
"metadata": {
"filter_type": filter_type,
"size_0": size_0,
"size_1": size_1,
"size_2": size_2,
"dims": dims,
"crop_time_axis": crop_time_axis,
"filename": image.name,}}


return (removed_background, layer_properties, "image")

return remove_image_background_2()


@magic_factory()
def track_events(
image_selector: Image,
threshold: int = 300,
eps: int = 10,
epsPrev: int = 50,
minClSz: int = 50,
minSamples: int = 2,
nPrev: int = 2,
dims: str = "TXY",
) -> FunctionWorker[LayerDataTuple]:
t_filter_size = 20
pbar = progress(total=0)

@thread_worker(connect={'returned': pbar.close})
def track_events_2() -> LayerDataTuple:
global abort_flag

# Reset the abort flag at the start of each execution
abort_flag = False

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.")

class ExampleQWidget(QWidget):
# your QWidget.__init__ can optionally request the napari viewer instance
# in one of two ways:
# 1. use a parameter called `napari_viewer`, as done here
# 2. use a type annotation of 'napari.viewer.Viewer' for any parameter
def __init__(self, napari_viewer):
super().__init__()
self.viewer = napari_viewer
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)

btn = QPushButton("Click me!")
btn.clicked.connect(self._on_click)
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.")

self.setLayout(QHBoxLayout())
self.layout().addWidget(btn)
# 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")

def _on_click(self):
print("napari has", len(self.viewer.layers), "layers")
# return the layer data tuple
return track_events_2()


@magic_factory
def example_magic_widget(img_layer: "napari.layers.Image"):
print(f"you have selected {img_layer}")
@magic_factory()
def abort_process():
global abort_flag
abort_flag = True


# Uses the `autogenerate: true` flag in the plugin manifest
# to indicate it should be wrapped as a magicgui to autogenerate
# a widget.
def example_function_widget(img_layer: "napari.layers.Image"):
print(f"you have selected {img_layer}")
34 changes: 15 additions & 19 deletions src/arcospx/napari.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,23 @@ visibility: public
categories: ["Annotation", "Segmentation", "Acquisition"]
contributions:
commands:
- id: arcosPx-napari.make_sample_data
python_name: arcospx._sample_data:make_sample_data
title: Load sample data from arcosPx
- id: arcosPx-napari.make_qwidget
python_name: arcospx._widget:ExampleQWidget
title: Make example QWidget
- id: arcosPx-napari.make_magic_widget
python_name: arcospx._widget:example_magic_widget
title: Make example magic widget
- id: arcosPx-napari.make_func_widget
python_name: arcospx._widget:example_function_widget
title: Make example function widget
- id: arcosPx-napari.remove_background
python_name: arcospx._widget:remove_background
title: Remove Background
- 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
sample_data:
- command: arcosPx-napari.make_sample_data
display_name: arcosPx
key: unique_id.1
widgets:
- command: arcosPx-napari.make_qwidget
display_name: Example QWidget
- command: arcosPx-napari.make_magic_widget
display_name: Example Magic Widget
- command: arcosPx-napari.make_func_widget
autogenerate: true
display_name: Example Function Widget
- command: arcosPx-napari.remove_background
display_name: Remove Background
- command: arcosPx-napari.track_events
display_name: Track Events
- command: arcosPx-napari.abort_process
display_name: Abort Current Process

0 comments on commit 43a5176

Please sign in to comment.