Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merging #1

Merged
merged 17 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading