Skip to content

Commit

Permalink
First pass at adding napari reader
Browse files Browse the repository at this point in the history
  • Loading branch information
dstansby committed Apr 19, 2022
1 parent 2d2c8bf commit ae47ad5
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 22 deletions.
10 changes: 1 addition & 9 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,13 @@ jobs:
with:
python-version: ${{ matrix.python-version }}

# note: if you need dependencies from conda, considering using
# setup-miniconda: https://github.com/conda-incubator/setup-miniconda
# and
# tox-conda: https://github.com/tox-dev/tox-conda
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools tox tox-gh-actions
python -m pip install setuptools tox tox-gh-actions
# this runs the platform-specific tests declared in tox.ini
- name: Test with tox
run: tox
env:
PLATFORM: ${{ matrix.platform }}

- name: Coverage
if: runner.os == 'Linux' && matrix.python-version == 3.9
uses: codecov/codecov-action@v2
Empty file added btrack/napari/__init__.py
Empty file.
96 changes: 96 additions & 0 deletions btrack/napari/reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
This module is a reader plugin btrack files for napari.
"""
from pathlib import Path
from typing import Callable, List, Optional, Sequence, Union

from napari.types import LayerDataTuple
from napari_plugin_engine import napari_hook_implementation

from btrack.dataio import HDF5FileHandler
from btrack.utils import tracks_to_napari

# Type definitions
PathLike = Union[str, Path]
PathOrPaths = Union[PathLike, Sequence[PathLike]]
ReaderFunction = Callable[[PathOrPaths], List[LayerDataTuple]]


@napari_hook_implementation
def get_reader(path: PathOrPaths) -> Optional[ReaderFunction]:
"""A basic implementation of the napari_get_reader hook specification.
Parameters
----------
path : str or list of str
Path to file, or list of paths.
Returns
-------
function or None
If the path is a recognized format, return a function that accepts the
same path or list of paths, and returns a list of layer data tuples.
"""
return reader_function


def reader_function(path: PathLike) -> List[LayerDataTuple]:
"""Take a path or list of paths and return a list of LayerData tuples.
Readers are expected to return data as a list of tuples, where each tuple
is (data, [add_kwargs, [layer_type]]), "add_kwargs" and "layer_type" are
both optional.
Parameters
----------
path : str or list of str
Path to file, or list of paths.
Returns
-------
layer_data : list of tuples
A list of LayerData tuples where each tuple in the list contains
(data, metadata, layer_type), where data is a numpy array, metadata is
a dict of keyword arguments for the corresponding viewer.add_* method
in napari, and layer_type is a lower-case string naming the type of
layer. Both "meta", and "layer_type" are optional. napari will default
to layer_type=="image" if not provided
"""
# handle both a string and a list of strings
paths = [path] if isinstance(path, str) else path

# store the layers to be generated
layers = []

for _path in paths:
with HDF5FileHandler(_path, "r") as hdf:

# get the segmentation if there is one
if "segmentation" in hdf._hdf:
segmentation = hdf.segmentation
layers.append((segmentation, {}, "labels"))

# iterate over object types and create a layer for each
for obj_type in hdf.object_types:

# set the object type, and retrieve the tracks
hdf.object_type = obj_type

if f"tracks/{obj_type}" not in hdf._hdf:
continue

tracklets = hdf.tracks
tracks, properties, graph = tracks_to_napari(tracklets, ndim=2)

# optional kwargs for the corresponding viewer.add_* method
# https://napari.org/docs/api/napari.components.html#module-napari.components.add_layers_mixin
add_kwargs = {
"properties": properties,
"graph": graph,
"name": obj_type,
"blending": "translucent",
}

layer = (tracks, add_kwargs, "tracks")
layers.append(layer)
return layers
Empty file added btrack/napari/tests/__init__.py
Empty file.
33 changes: 33 additions & 0 deletions btrack/napari/tests/test_btrack_reader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest
# Only run tests is napari is installed
pytest.importorskip("napari")

from btrack.napari.reader import get_reader


# # tmp_path is a pytest fixture
# def test_reader(tmp_path):
# """An example of how you might test your plugin."""
#
# # write some fake data using your supported file format
# my_test_file = str(tmp_path / "myfile.npy")
# original_data = np.random.rand(20, 20)
# np.save(my_test_file, original_data)
#
# # try to read it back in
# reader = napari_get_reader(my_test_file)
# assert callable(reader)
#
# # make sure we're delivering the right format
# layer_data_list = reader(my_test_file)
# assert isinstance(layer_data_list, list) and len(layer_data_list) > 0
# layer_data_tuple = layer_data_list[0]
# assert isinstance(layer_data_tuple, tuple) and len(layer_data_tuple) > 0
#
# # make sure it's the same as it started
# np.testing.assert_allclose(original_data, layer_data_tuple[0])


def test_get_reader_pass():
reader = get_reader("fake.file")
assert reader is not None
11 changes: 11 additions & 0 deletions napari.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
contributions:
commands:
- id: btrack.read_btrack
title: Read BayesianTracker files
python_name: btrack.napari.reader:get_reader

readers:
- command: btrack.read_btrack
filename_patterns:
- ['*.h5', '*.hdf', '*.hdf5']
accepts_directories: false
1 change: 1 addition & 0 deletions requirements-napari.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
napari
8 changes: 8 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
[metadata]
description-file = README.md

[options.entry_points]
napari.manifest =
example-plugin = example_plugin:napari.yaml

# make sure it gets included in your package
[options.package_data]
example-plugin = napari.yaml

[flake8]
# Ignores - https://lintlyci.github.io/Flake8Rules
# E203 Whitespace before ':' (sometimes conflicts with black)
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def get_version():
DESCRIPTION = "A framework for Bayesian multi-object tracking"
LONG_DESCRIPTION = __doc__

extras = ['docs', 'napari']
extras_require = {extra: get_install_required(extra) for extra in extras}

setup(
name="btrack",
Expand All @@ -36,7 +38,7 @@ def get_version():
packages=find_packages(),
package_data={"btrack": ["libs/libtracker*", "VERSION.txt"]},
install_requires=get_install_required(),
extras_require={"docs": get_install_required("docs")},
extras_require=extras_require,
python_requires=">=3.6",
license="LICENSE.md",
classifiers=[
Expand Down
16 changes: 4 additions & 12 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,29 +1,21 @@
# For more information about tox, see https://tox.readthedocs.io/en/latest/
[tox]
envlist = py{37,38,39}-{linux,macos,windows}
envlist = py{37,38,39}{,-napari}

[gh-actions]
python =
3.7: py37
3.8: py38
3.9: py39

[gh-actions:env]
PLATFORM =
ubuntu-latest: linux
macos-latest: macos
windows-latest: windows
3.9: py39, py39-napari

[testenv]
platform =
macos: darwin
linux: linux
windows: windows
passenv =
CI
GITHUB_ACTIONS
NUMPY_EXPERIMENTAL_ARRAY_FUNCTION
deps =
pytest # https://docs.pytest.org/en/latest/contents.html
pytest-cov # https://pytest-cov.readthedocs.io/en/latest/
extras =
napari: napari
commands = pytest -v --color=yes --cov=btrack --cov-report=xml

0 comments on commit ae47ad5

Please sign in to comment.