jni / zarpaint

Paint segmentations directly to on-disk/remote zarr arrays
BSD 3-Clause "New" or "Revised" License
14 stars 8 forks source link

[WIP] Add slice interpolation #32

Open jamesyan-git opened 2 years ago

jamesyan-git commented 2 years ago

Allows users to draw labels on separate slices and interpolate in-between them Still need to improve UI, add tests, etc... Currently only works for first dimension

jni commented 2 years ago

This is looking very good @jamesyan-git! :heart_eyes: Looking forward to the ready-for-review!

codecov-commenter commented 2 years ago

Codecov Report

Merging #32 (345dddc) into main (0734529) will increase coverage by 6.37%. The diff coverage is 100.00%.

:mega: This organization is not using Codecov’s GitHub App Integration. We recommend you install it so Codecov can continue to function properly for your repositories. Learn more

@@            Coverage Diff             @@
##             main      #32      +/-   ##
==========================================
+ Coverage   78.11%   84.48%   +6.37%     
==========================================
  Files          15       17       +2     
  Lines         635      896     +261     
==========================================
+ Hits          496      757     +261     
  Misses        139      139              
Impacted Files Coverage Δ
src/zarpaint/_tests/test_watershed.py 100.00% <ø> (ø)
src/zarpaint/__init__.py 83.33% <100.00%> (+1.51%) :arrow_up:
src/zarpaint/_interpolate_labels.py 100.00% <100.00%> (ø)
src/zarpaint/_tests/test_interpolate_labels.py 100.00% <100.00%> (ø)

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

GenevieveBuckley commented 2 years ago

Does the user interaction here require one to push the plugin start button before painting the first slice?

I generally have use cases where partial labels might generated with some other plugin or separate software, and want to correct any areas of innacuracy. For this use case, it's a plus if you can select an already drawn label for the first and/or last slice (or touch up a tiny region at an edge, then use the whole label area for slice interpolation).

I don't quite know how much you plan to rely on a temp intermediate memory state for the painting, so I want to mention the above workflow so we can prioritize it too.

jamesyan-git commented 2 years ago

Hi @GenevieveBuckley , I totally understand about the use case regarding editing existing labels. I will aim to get this implemented once we are happy with the "from scratch" workflow. Below is a video showing the current workflow, where the following steps are taken:

  1. Create a labels layer, and select it from the dropdown
  2. Click "start Interpolation"
  3. Paint on desired slices with desired label ID(s)
  4. Once all the annotations have been made, click "interpolate"

@jni https://user-images.githubusercontent.com/95660545/184786184-11f53309-2771-4eb6-9e9b-f5329cfacc41.mp4

Next steps:

I think this will put the annotate-from-scratch workflow in a good position and we can then start working on updating existing labels. What are your thoughts? I will keep adding docstrings and tests in the meantime.

GenevieveBuckley commented 2 years ago

I think that sounds like a reasonable plan

jamesyan-git commented 1 year ago

@DragaDoncila this is ready for review

jni commented 1 year ago

I just had a very quick look @jamesyan-git but it's looking good! I reckon add a couple of tests (interpolating 2D slices in 3D volume, and 3D in a 4D volume) and we merge!

GenevieveBuckley commented 1 year ago

It looks like there's been a change in the way napari handles events since this PR was first added.

When I try this out today with napari 0.4.16, clicking the slice interpolation button gives this error:

AttributeError: 'EmitterGroup' object has no attribute 'paint'

related to line 202 in the enter_interpolation function:

self.selected_layer.events.paint.connect(self.paint_callback)
Error message: ```python-traceback Traceback (most recent call last): File "/Users/genevieb/mambaforge/envs/napari-empanada/lib/python3.9/site-packages/magicgui/widgets/_bases/value_widget.py", line 57, in _on_value_change self.changed.emit(value) File "psygnal/_signal.py", line 725, in psygnal._signal.SignalInstance.emit File "psygnal/_signal.py", line 767, in psygnal._signal.SignalInstance._run_emit_loop File "psygnal/_signal.py", line 768, in psygnal._signal.SignalInstance._run_emit_loop File "psygnal/_signal.py", line 788, in psygnal._signal.SignalInstance._run_emit_loop File "/Users/genevieb/Documents/GitHub/zarpaint/src/zarpaint/_interpolate_labels.py", line 252, in enter_interpolation self.selected_layer.events.paint.connect(self.paint_callback) File "/Users/genevieb/mambaforge/envs/napari-empanada/lib/python3.9/site-packages/napari/utils/_proxies.py", line 72, in __getattr__ return self.create(super().__getattr__(name)) File "/Users/genevieb/mambaforge/envs/napari-empanada/lib/python3.9/site-packages/napari/utils/events/event.py", line 971, in __getattr__ return object.__getattribute__(self, name) AttributeError: 'EmitterGroup' object has no attribute 'paint' ```
napari.sys_info(): ```python napari: 0.4.16 Platform: macOS-12.3.1-arm64-arm-64bit System: MacOS 12.3.1 Python: 3.10.6 | packaged by conda-forge | (main, Aug 22 2022, 20:41:22) [Clang 13.0.1 ] Qt: 5.15.6 PyQt5: 5.15.7 NumPy: 1.23.4 SciPy: 1.9.3 Dask: 2022.10.2 VisPy: 0.10.0 OpenGL: - GL version: 2.1 Metal - 76.3 - MAX_TEXTURE_SIZE: 16384 Screens: - screen 1: resolution 1440x900, scale 2.0 - screen 2: resolution 1920x1080, scale 1.0 Plugins: - console: 0.0.6 - napari-svg: 0.1.6 - scikit-image: 0.4.16 - zarpaint: 0.1.1.dev27+gfc48307 ```
conda list: ``` # packages in environment at /Users/genevieb/mambaforge/envs/test-zarpaint: # # Name Version Build Channel alabaster 0.7.12 py_0 conda-forge aom 3.5.0 h7ea286d_0 conda-forge appdirs 1.4.4 pyh9f0ad1d_0 conda-forge appnope 0.1.3 pyhd8ed1ab_0 conda-forge argon2-cffi 21.3.0 pyhd8ed1ab_0 conda-forge argon2-cffi-bindings 21.2.0 py310h8e9501a_3 conda-forge asciitree 0.3.3 py_2 conda-forge asttokens 2.1.0 pyhd8ed1ab_0 conda-forge attrs 22.1.0 pyh71513ae_1 conda-forge babel 2.10.3 pyhd8ed1ab_0 conda-forge backcall 0.2.0 pyh9f0ad1d_0 conda-forge backports 1.0 py_2 conda-forge backports.functools_lru_cache 1.6.4 pyhd8ed1ab_0 conda-forge beautifulsoup4 4.11.1 pyha770c72_0 conda-forge bleach 5.0.1 pyhd8ed1ab_0 conda-forge blosc 1.21.1 hd414afc_3 conda-forge bokeh 2.4.3 pyhd8ed1ab_3 conda-forge brotli 1.0.9 h1a8c8d9_8 conda-forge brotli-bin 1.0.9 h1a8c8d9_8 conda-forge brotlipy 0.7.0 py310h8e9501a_1005 conda-forge brunsli 0.1 h9f76cd9_0 conda-forge bzip2 1.0.8 h3422bc3_4 conda-forge c-ares 1.18.1 h3422bc3_0 conda-forge c-blosc2 2.4.3 h303ed30_0 conda-forge ca-certificates 2022.9.24 h4653dfc_0 conda-forge cachey 0.2.1 pyh9f0ad1d_0 conda-forge certifi 2022.9.24 pyhd8ed1ab_0 conda-forge cffi 1.15.1 py310h2399d43_2 conda-forge cfitsio 4.1.0 hd4f5c17_0 conda-forge charls 2.3.4 hbdafb3b_0 conda-forge charset-normalizer 2.1.1 pyhd8ed1ab_0 conda-forge click 8.1.3 unix_pyhd8ed1ab_2 conda-forge cloudpickle 2.2.0 pyhd8ed1ab_0 conda-forge colorama 0.4.6 pyhd8ed1ab_0 conda-forge commonmark 0.9.1 py_0 conda-forge cryptography 38.0.2 py310h4fe9c50_2 conda-forge cytoolz 0.12.0 py310h8e9501a_1 conda-forge dask 2022.10.2 pyhd8ed1ab_0 conda-forge dask-core 2022.10.2 pyhd8ed1ab_0 conda-forge dataclasses 0.8 pyhc8e2a94_3 conda-forge dav1d 1.0.0 he4db4b2_1 conda-forge debugpy 1.6.3 py310h0f1eb42_1 conda-forge decorator 5.1.1 pyhd8ed1ab_0 conda-forge defusedxml 0.7.1 pyhd8ed1ab_0 conda-forge distributed 2022.10.2 pyhd8ed1ab_0 conda-forge docstring_parser 0.13 pyhd8ed1ab_0 conda-forge docutils 0.19 py310hbe9552e_1 conda-forge entrypoints 0.4 pyhd8ed1ab_0 conda-forge executing 1.2.0 pyhd8ed1ab_0 conda-forge expat 2.5.0 hb7217d7_0 conda-forge fasteners 0.17.3 pyhd8ed1ab_0 conda-forge flit-core 3.7.1 pyhd8ed1ab_0 conda-forge font-ttf-dejavu-sans-mono 2.37 hab24e00_0 conda-forge font-ttf-inconsolata 3.000 h77eed37_0 conda-forge font-ttf-source-code-pro 2.038 h77eed37_0 conda-forge font-ttf-ubuntu 0.83 hab24e00_0 conda-forge fontconfig 2.14.1 h82840c6_0 conda-forge fonts-conda-ecosystem 1 0 conda-forge fonts-conda-forge 1 0 conda-forge freetype 2.12.1 hd633e50_0 conda-forge freetype-py 2.3.0 pyhd8ed1ab_0 conda-forge fsspec 2022.10.0 pyhd8ed1ab_0 conda-forge future 0.18.2 pyhd8ed1ab_6 conda-forge gettext 0.21.1 h0186832_0 conda-forge giflib 5.2.1 h27ca646_2 conda-forge glib 2.74.1 hb5ab8b9_0 conda-forge glib-tools 2.74.1 hb5ab8b9_0 conda-forge gst-plugins-base 1.21.1 h8b7775e_1 conda-forge gstreamer 1.21.1 hcb7b3dd_1 conda-forge heapdict 1.0.1 py_0 conda-forge hsluv 5.0.2 pyh44b312d_0 conda-forge icu 70.1 h6b3803e_0 conda-forge idna 3.4 pyhd8ed1ab_0 conda-forge imagecodecs 2022.9.26 py310h24fba19_3 conda-forge imageio 2.22.0 pyhfa7a67d_0 conda-forge imagesize 1.4.1 pyhd8ed1ab_0 conda-forge importlib-metadata 5.0.0 pyha770c72_1 conda-forge importlib_resources 5.10.0 pyhd8ed1ab_0 conda-forge ipykernel 6.17.0 pyh736e0ef_0 conda-forge ipython 8.6.0 pyhd1c38e8_1 conda-forge ipython_genutils 0.2.0 py_1 conda-forge ipywidgets 8.0.2 pyhd8ed1ab_1 conda-forge jedi 0.18.1 pyhd8ed1ab_2 conda-forge jinja2 3.1.2 pyhd8ed1ab_1 conda-forge jpeg 9e he4db4b2_2 conda-forge jsonschema 4.16.0 pyhd8ed1ab_0 conda-forge jupyter 1.0.0 py310hbe9552e_7 conda-forge jupyter_client 7.3.4 pyhd8ed1ab_0 conda-forge jupyter_console 6.4.4 pyhd8ed1ab_0 conda-forge jupyter_core 4.11.1 py310hbe9552e_1 conda-forge jupyterlab_pygments 0.2.2 pyhd8ed1ab_0 conda-forge jupyterlab_widgets 3.0.3 pyhd8ed1ab_0 conda-forge jxrlib 1.1 h27ca646_2 conda-forge kiwisolver 1.4.4 py310h2887b22_1 conda-forge krb5 1.19.3 hf9b2bbe_0 conda-forge lcms2 2.14 h8193b64_0 conda-forge lerc 4.0.0 h9a09cb3_0 conda-forge libaec 1.0.6 hbdafb3b_0 conda-forge libavif 0.11.1 h3d80962_0 conda-forge libblas 3.9.0 16_osxarm64_openblas conda-forge libbrotlicommon 1.0.9 h1a8c8d9_8 conda-forge libbrotlidec 1.0.9 h1a8c8d9_8 conda-forge libbrotlienc 1.0.9 h1a8c8d9_8 conda-forge libcblas 3.9.0 16_osxarm64_openblas conda-forge libclang 15.0.3 default_h9e54d93_0 conda-forge libclang13 15.0.3 default_h7d574e7_0 conda-forge libcurl 7.86.0 hd538317_1 conda-forge libcxx 14.0.6 h2692d47_0 conda-forge libdeflate 1.14 h1a8c8d9_0 conda-forge libedit 3.1.20191231 hc8eb9b7_2 conda-forge libev 4.33 h642e427_1 conda-forge libffi 3.4.2 h3422bc3_5 conda-forge libgfortran 5.0.0 11_3_0_hd922786_25 conda-forge libgfortran5 11.3.0 hdaf2cc0_25 conda-forge libglib 2.74.1 h14ed1c1_0 conda-forge libiconv 1.17 he4db4b2_0 conda-forge liblapack 3.9.0 16_osxarm64_openblas conda-forge libllvm15 15.0.3 h62b9111_1 conda-forge libnghttp2 1.47.0 h232270b_1 conda-forge libogg 1.3.4 h27ca646_1 conda-forge libopenblas 0.3.21 openmp_hc731615_3 conda-forge libopus 1.3.1 h27ca646_1 conda-forge libpng 1.6.38 h76d750c_0 conda-forge libpq 14.5 h3f71257_1 conda-forge libsodium 1.0.18 h27ca646_1 conda-forge libsqlite 3.39.4 h76d750c_0 conda-forge libssh2 1.10.0 hb80f160_3 conda-forge libtiff 4.4.0 hfa0b094_4 conda-forge libvorbis 1.3.7 h9f76cd9_0 conda-forge libwebp-base 1.2.4 h57fd34a_0 conda-forge libxcb 1.13 h9b22ae9_1004 conda-forge libxml2 2.10.3 h87b0503_0 conda-forge libzlib 1.2.13 h03a7124_4 conda-forge libzopfli 1.0.3 h9f76cd9_0 conda-forge llvm-openmp 14.0.4 hd125106_0 conda-forge locket 1.0.0 pyhd8ed1ab_0 conda-forge lz4 4.0.2 py310ha6df754_0 conda-forge lz4-c 1.9.3 hbdafb3b_1 conda-forge magicgui 0.6.0 pyhd8ed1ab_0 conda-forge markupsafe 2.1.1 py310h8e9501a_2 conda-forge matplotlib-inline 0.1.6 pyhd8ed1ab_0 conda-forge mistune 2.0.4 pyhd8ed1ab_0 conda-forge msgpack-python 1.0.4 py310h2887b22_1 conda-forge mysql-common 8.0.31 hab468bb_0 conda-forge mysql-libs 8.0.31 hea58576_0 conda-forge napari 0.4.16 pyh275ddea_0_pyqt conda-forge napari-console 0.0.6 pyhd8ed1ab_0 conda-forge napari-plugin-engine 0.2.0 pyhd8ed1ab_2 conda-forge napari-svg 0.1.6 pyhd8ed1ab_0 conda-forge nbclient 0.7.0 pyhd8ed1ab_0 conda-forge nbconvert 7.2.3 pyhd8ed1ab_0 conda-forge nbconvert-core 7.2.3 pyhd8ed1ab_0 conda-forge nbconvert-pandoc 7.2.3 pyhd8ed1ab_0 conda-forge nbformat 5.7.0 pyhd8ed1ab_0 conda-forge ncurses 6.3 h07bb92c_1 conda-forge nest-asyncio 1.5.6 pyhd8ed1ab_0 conda-forge networkx 2.8.7 pyhd8ed1ab_0 conda-forge notebook 6.4.12 pyha770c72_0 conda-forge npe2 0.6.1 pyhd8ed1ab_1 conda-forge nspr 4.32 hbdafb3b_1 conda-forge nss 3.78 h1483a63_0 conda-forge numcodecs 0.10.2 py310hc6dc59f_0 conda-forge numpy 1.23.4 py310h5d7c261_1 conda-forge numpydoc 1.5.0 pyhd8ed1ab_0 conda-forge openjpeg 2.5.0 h5d4e404_1 conda-forge openssl 1.1.1s h03a7124_0 conda-forge packaging 21.3 pyhd8ed1ab_0 conda-forge pandas 1.5.1 py310h2b830bf_1 conda-forge pandoc 2.19.2 hce30654_1 conda-forge pandocfilters 1.5.0 pyhd8ed1ab_0 conda-forge parso 0.8.3 pyhd8ed1ab_0 conda-forge partd 1.3.0 pyhd8ed1ab_0 conda-forge pcre2 10.37 hcf5f1cc_1 conda-forge pep517 0.12.0 pyhd8ed1ab_3 conda-forge pexpect 4.8.0 pyh1a96a4e_2 conda-forge pickleshare 0.7.5 py_1003 conda-forge pillow 9.2.0 py310h9337a76_3 conda-forge pint 0.20.1 pyhd8ed1ab_0 conda-forge pip 22.3 pyhd8ed1ab_0 conda-forge pkgutil-resolve-name 1.3.10 pyhd8ed1ab_0 conda-forge ply 3.11 py_1 conda-forge pooch 1.6.0 pyhd8ed1ab_0 conda-forge prometheus_client 0.15.0 pyhd8ed1ab_0 conda-forge prompt-toolkit 3.0.31 pyha770c72_0 conda-forge prompt_toolkit 3.0.31 hd8ed1ab_0 conda-forge psutil 5.9.3 py310h8e9501a_1 conda-forge psygnal 0.3.5 py310hd23d0e8_0 conda-forge pthread-stubs 0.4 h27ca646_1001 conda-forge ptyprocess 0.7.0 pyhd3deb0d_0 conda-forge pure_eval 0.2.2 pyhd8ed1ab_0 conda-forge pycparser 2.21 pyhd8ed1ab_0 conda-forge pydantic 1.10.2 py310h8e9501a_1 conda-forge pygments 2.13.0 pyhd8ed1ab_0 conda-forge pyopengl 3.1.6 pyhd8ed1ab_1 conda-forge pyopenssl 22.1.0 pyhd8ed1ab_0 conda-forge pyparsing 3.0.9 pyhd8ed1ab_0 conda-forge pyqt 5.15.7 py310h7aaa74b_2 conda-forge pyqt5-sip 12.11.0 py310h0f1eb42_2 conda-forge pyrsistent 0.18.1 py310h8e9501a_2 conda-forge pysocks 1.7.1 pyha2e5f31_6 conda-forge python 3.10.6 hbce4517_0_cpython conda-forge python-build 0.9.0 pyhd8ed1ab_0 conda-forge python-dateutil 2.8.2 pyhd8ed1ab_0 conda-forge python-fastjsonschema 2.16.2 pyhd8ed1ab_0 conda-forge python_abi 3.10 2_cp310 conda-forge pytomlpp 1.0.11 py310h2887b22_1 conda-forge pytz 2022.5 pyhd8ed1ab_0 conda-forge pywavelets 1.3.0 py310hf1a086a_2 conda-forge pyyaml 6.0 py310h8e9501a_5 conda-forge pyzmq 24.0.1 py310hc407298_1 conda-forge qt-main 5.15.6 hf3dd84c_1 conda-forge qtconsole-base 5.3.2 pyha770c72_0 conda-forge qtpy 2.2.1 pyhd8ed1ab_0 conda-forge readline 8.1.2 h46ed386_0 conda-forge requests 2.28.1 pyhd8ed1ab_1 conda-forge rich 12.6.0 pyhd8ed1ab_0 conda-forge scikit-image 0.19.3 py310h2b830bf_2 conda-forge scipy 1.9.3 py310ha0d8a01_1 conda-forge send2trash 1.8.0 pyhd8ed1ab_0 conda-forge setuptools 65.5.0 pyhd8ed1ab_0 conda-forge shellingham 1.5.0 pyhd8ed1ab_0 conda-forge sip 6.7.3 py310h0f1eb42_0 conda-forge six 1.16.0 pyh6c4a22f_0 conda-forge snappy 1.1.9 h39c3846_1 conda-forge snowballstemmer 2.2.0 pyhd8ed1ab_0 conda-forge sortedcontainers 2.4.0 pyhd8ed1ab_0 conda-forge soupsieve 2.3.2.post1 pyhd8ed1ab_0 conda-forge sphinx 5.3.0 pyhd8ed1ab_0 conda-forge sphinxcontrib-applehelp 1.0.2 py_0 conda-forge sphinxcontrib-devhelp 1.0.2 py_0 conda-forge sphinxcontrib-htmlhelp 2.0.0 pyhd8ed1ab_0 conda-forge sphinxcontrib-jsmath 1.0.1 py_0 conda-forge sphinxcontrib-qthelp 1.0.3 py_0 conda-forge sphinxcontrib-serializinghtml 1.1.5 pyhd8ed1ab_2 conda-forge sqlite 3.39.4 h2229b38_0 conda-forge stack_data 0.6.0 pyhd8ed1ab_0 conda-forge superqt 0.3.8 pyhd8ed1ab_0 conda-forge tblib 1.7.0 pyhd8ed1ab_0 conda-forge tensorstore 0.1.27 pypi_0 pypi terminado 0.17.0 pyhd1c38e8_0 conda-forge tifffile 2022.10.10 pyhd8ed1ab_0 conda-forge tinycss2 1.2.1 pyhd8ed1ab_0 conda-forge tk 8.6.12 he1e0b03_0 conda-forge toml 0.10.2 pyhd8ed1ab_0 conda-forge tomli 2.0.1 pyhd8ed1ab_0 conda-forge toolz 0.12.0 pyhd8ed1ab_0 conda-forge tornado 6.1 py310hf8d0d8f_3 conda-forge tqdm 4.64.1 pyhd8ed1ab_0 conda-forge traitlets 5.5.0 pyhd8ed1ab_0 conda-forge typer 0.6.1 pyhd8ed1ab_0 conda-forge typing-extensions 4.4.0 hd8ed1ab_0 conda-forge typing_extensions 4.4.0 pyha770c72_0 conda-forge tzdata 2022f h191b570_0 conda-forge urllib3 1.26.11 pyhd8ed1ab_0 conda-forge vispy 0.10.0 py310he2d1965_0 conda-forge wcwidth 0.2.5 pyh9f0ad1d_2 conda-forge webencodings 0.5.1 py_1 conda-forge wheel 0.37.1 pyhd8ed1ab_0 conda-forge widgetsnbextension 4.0.3 pyhd8ed1ab_0 conda-forge wrapt 1.14.1 py310h8e9501a_1 conda-forge xorg-libxau 1.0.9 h27ca646_0 conda-forge xorg-libxdmcp 1.1.3 h27ca646_0 conda-forge xz 5.2.6 h57fd34a_0 conda-forge yaml 0.2.5 h3422bc3_2 conda-forge zarpaint 0.1.1.dev27+gfc48307 dev_0 zarr 2.13.3 pyhd8ed1ab_0 conda-forge zeromq 4.3.4 hbdafb3b_1 conda-forge zfp 1.0.0 hb6e4faa_3 conda-forge zict 2.2.0 pyhd8ed1ab_0 conda-forge zipp 3.10.0 pyhd8ed1ab_0 conda-forge zlib-ng 2.0.6 he4db4b2_0 conda-forge zstd 1.5.2 h8128057_4 conda-forge ```
DragaDoncila commented 1 year ago

@GenevieveBuckley yeah the paint event hasn't actually been released yet, so this only works with main - and will soon work with 0.4.17 when it's released in the next few days

GenevieveBuckley commented 1 year ago

Had a quick look at this again and it functions well for 2d slices in a 3d volume, provided that the slice annotations overlap sufficiently (and that last part is a limitation of the algorithm we chose, as discussed previously).

I'm a lot less concerned that I was before about the usefulness of this in cases where a user needs to edit already generated labels. It turns out all you need to do is click "Start Interpolation", then click once with the paintbrush inside the already existing labels on the first and last slices, and then it will interpolate between them. So there's no need to worry about re-drawing edges correctly, which is the thing I was worried would be time consuming and difficult to do. 🎉

GenevieveBuckley commented 1 year ago

Here's a test I wrote annotating 2D slices in a 3D volume

from napari.layers import Labels
import numpy as np
from skimage.draw import ellipse

from zarpaint._interpolate_labels import interpolate_between_slices

label_id = 2
arraysize = 512
slice_index_1 = 0
slice_index_2 = 10
# First label slice
coords_1 = ellipse(arraysize//2, arraysize//2,  # row, column (center coordinates)
                   arraysize//6, arraysize//6,  # r_radius, c_radius (ellipse radii)
                   shape=(arraysize, arraysize))
label_slice_1 = np.zeros(shape).astype(int)
label_slice_1[coords_1] = label_id
# Second label slice
coords_2 = ellipse(arraysize//2, arraysize//2,  # row, column (center coordinates)
                   arraysize//5, arraysize//3,  # r_radius, c_radius (ellipse radii)
                   shape=(arraysize, arraysize))
label_slice_2 = np.zeros(shape).astype(int)
label_slice_2[coords_2] = label_id
# Create labels for interpolation
labels = np.zeros((11, arraysize, arraysize)).astype(int)
labels[slice_index_1] = label_slice_1
labels[slice_index_2] = label_slice_2
labels_layer = Labels(labels)

# Check all intermediate label slices contain only zero
for labels_slice in labels_layer.data[1:-1]:
    assert np.max(labels_slice) == 0
# Check the expected number of non-zero pixels exist now 
assert np.count_nonzero(labels_layer.data[1:-1]) == 0

# Interpolate intermediate slices
interpolate_between_slices(labels_layer, slice_index_1, slice_index_2, label_id, interp_dim=0)

# Check all intermediate label slices now contain non-zero pixels
for labels_slice in labels_layer.data[1:-1]:
    assert np.max(labels_slice) == label_id
# Check the expected number of non-zero pixels exist now
assert np.count_nonzero(labels_layer.data[1:-1]) == 315045
GenevieveBuckley commented 1 year ago

And here's a similar test, extended to a 4d array using ellispoid shapes. I've cut this down to interpolate across only three intermediate 3d volumes, so it runs in a reasonable amount of time (my %%timeit results: 1.6 s ± 37.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each))

from napari.layers import Labels
import numpy as np
from skimage.draw import ellipse

from zarpaint._interpolate_labels import interpolate_between_slices

label_id = 2
arraysize = 100
slice_index_1 = 0
slice_index_2 = 4
# First label slice
ellipse_1 = ellipsoid(20, 35, 25) * label_id
padding = np.array((arraysize, arraysize, arraysize)) - np.array(ellipse_1.shape)
pad_width = [(i//2, i//2 + 1) for i in padding]
label_slice_1 = np.pad(ellipse_1, pad_width)
# Second label slice
ellipse_2 = ellipsoid(28, 33, 40) * label_id
padding = np.array((arraysize, arraysize, arraysize)) - np.array(ellipse_2.shape)
pad_width = [(i//2, i//2 + 1) for i in padding]
label_slice_2 = np.pad(ellipse_2, pad_width)
# Create labels for interpolation
labels = np.zeros((5, arraysize, arraysize, arraysize)).astype(int)
labels[slice_index_1] = label_slice_1
labels[slice_index_2] = label_slice_2
labels_layer = Labels(labels)

# Check all intermediate label slices contain only zero
for labels_slice in labels_layer.data[1:-1]:
    assert np.max(labels_slice) == 0
# Check the expected number of non-zero pixels exist now 
assert np.count_nonzero(labels_layer.data[1:-1]) == 0

# Interpolate intermediate slices
interpolate_between_slices(labels_layer, slice_index_1, slice_index_2, label_id, interp_dim=0)

# Check all intermediate label slices now contain non-zero pixels
for labels_slice in labels_layer.data[1:-1]:
    assert np.max(labels_slice) == label_id
# Check the expected number of non-zero pixels exist now
assert np.count_nonzero(labels_layer.data[1:-1]) == 297885
jni commented 1 year ago

Wow, super cool @GenevieveBuckley, thanks for those tests! @jamesyan-git do you want to implement them and then we can merge this? 🎉

jamesyan-git commented 1 year ago

Look amazing, I will add this now. I'll also include some that I've been writing but forgot to push.

jni commented 1 year ago

@jamesyan-git it looks like there's some bits that are still not tested — check out the link for the "codecov/patch" failed check. It's things to do with the viewer widget itself. You can test them by calling viewer.window.add_plugin_dock_widget (from memory — there's a function like that somewhere in the hierarchy). Then the widget is returned and you can call particular methods in it.

jamesyan-git commented 1 year ago

Thanks Juan! I'm working on covering all the lines now.

jamesyan-git commented 1 year ago

hey @jni, I'm not sure why the macos tests are failing. any ideas?

GenevieveBuckley commented 1 year ago

hey @jni, I'm not sure why the macos tests are failing. any ideas?

Looking at the log, it looks like there was problem during Run GabrielBB/xvfb-action@v1

It seems that github action is deprecated (see here, and recommends switching to https://github.com/coactions/setup-xvfb. So give that a go, just try swapping it over in the gihutb actions workflow files and see what happens.