From d75067dfe52a649652b98b9158ede6874ca8ecee Mon Sep 17 00:00:00 2001 From: Draga Doncila Pop Date: Thu, 22 Oct 2020 11:11:44 +1100 Subject: [PATCH] Initial commit --- .github/workflows/test_and_deploy.yml | 73 +++++++++++++++++++ .gitignore | 79 +++++++++++++++++++++ LICENSE | 28 ++++++++ MANIFEST.in | 6 ++ README.md | 57 +++++++++++++++ napari_nd2_reader/__init__.py | 10 +++ napari_nd2_reader/_tests/__init__.py | 0 napari_nd2_reader/_tests/test_nd2_reader.py | 30 ++++++++ napari_nd2_reader/nd2_reader.py | 79 +++++++++++++++++++++ requirements.txt | 2 + setup.py | 60 ++++++++++++++++ tox.ini | 21 ++++++ 12 files changed, 445 insertions(+) create mode 100644 .github/workflows/test_and_deploy.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.md create mode 100644 napari_nd2_reader/__init__.py create mode 100644 napari_nd2_reader/_tests/__init__.py create mode 100644 napari_nd2_reader/_tests/test_nd2_reader.py create mode 100644 napari_nd2_reader/nd2_reader.py create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 tox.ini diff --git a/.github/workflows/test_and_deploy.yml b/.github/workflows/test_and_deploy.yml new file mode 100644 index 0000000..3fb72cc --- /dev/null +++ b/.github/workflows/test_and_deploy.yml @@ -0,0 +1,73 @@ +# This workflows will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: tests + +on: push + +jobs: + ubuntu-latest: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v1 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools tox tox-gh-actions + - name: Test with tox + run: tox + - name: Coverage + uses: codecov/codecov-action@v1 + + # minimizing matrix by only testing 3.6 on mac & windows + mac-win: + name: ${{ matrix.platform }} (3.6) + runs-on: ${{ matrix.platform }} + strategy: + matrix: + platform: [macos-latest, windows-latest] + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.6 + uses: actions/setup-python@v1 + with: + python-version: 3.6 + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools tox tox-gh-actions + - name: Test with tox + run: tox + env: + PLATFORM: ${{ matrix.platform }} + + deploy: + needs: [ubuntu-latest, mac-win] + runs-on: ubuntu-latest + if: contains(github.ref, 'tags') + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v1 + with: + python-version: "3.x" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -U setuptools setuptools_scm wheel twine + - name: Build and publish + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.TWINE_API_KEY }} + run: | + git tag + python setup.py sdist bdist_wheel + twine upload dist/* diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..69e5c1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ +.napari_cache + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask instance folder +instance/ + +# Sphinx documentation +docs/_build/ + +# MkDocs documentation +/site/ + +# PyBuilder +target/ + +# IPython Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# OS +.DS_Store + +# written by setuptools_scm +*/_version.py diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7b360ac --- /dev/null +++ b/LICENSE @@ -0,0 +1,28 @@ + +Copyright (c) 2020, Draga Doncila Pop +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of napari-nd2-reader nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..0439384 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include LICENSE +include README.md +include requirements.txt + +recursive-exclude * __pycache__ +recursive-exclude * *.py[co] diff --git a/README.md b/README.md new file mode 100644 index 0000000..e27d588 --- /dev/null +++ b/README.md @@ -0,0 +1,57 @@ +# napari-nd2-reader + +[![License](https://img.shields.io/pypi/l/napari-nd2-reader.svg?color=green)](https://github.com/napari/napari-nd2-reader/raw/master/LICENSE) +[![PyPI](https://img.shields.io/pypi/v/napari-nd2-reader.svg?color=green)](https://pypi.org/project/napari-nd2-reader) +[![Python Version](https://img.shields.io/pypi/pyversions/napari-nd2-reader.svg?color=green)](https://python.org) +[![tests](https://github.com/DragaDoncila/napari-nd2-reader/workflows/tests/badge.svg)](https://github.com/DragaDoncila/napari-nd2-reader/actions) +[![codecov](https://codecov.io/gh/DragaDoncila/napari-nd2-reader/branch/master/graph/badge.svg)](https://codecov.io/gh/DragaDoncila/napari-nd2-reader) + +Plugin to load nd2 data into napari + +---------------------------------- + +This [napari] plugin was generated with [Cookiecutter] using with [@napari]'s [cookiecutter-napari-plugin] template. + + + +## Installation + +You can install `napari-nd2-reader` via [pip]: + + pip install napari-nd2-reader + +## Contributing + +Contributions are very welcome. Tests can be run with [tox], please ensure +the coverage at least stays the same before you submit a pull request. + +## License + +Distributed under the terms of the [BSD-3] license, +"napari-nd2-reader" is free and open source software + +## Issues + +If you encounter any problems, please [file an issue] along with a detailed description. + +[napari]: https://github.com/napari/napari +[Cookiecutter]: https://github.com/audreyr/cookiecutter +[@napari]: https://github.com/napari +[MIT]: http://opensource.org/licenses/MIT +[BSD-3]: http://opensource.org/licenses/BSD-3-Clause +[GNU GPL v3.0]: http://www.gnu.org/licenses/gpl-3.0.txt +[GNU LGPL v3.0]: http://www.gnu.org/licenses/lgpl-3.0.txt +[Apache Software License 2.0]: http://www.apache.org/licenses/LICENSE-2.0 +[Mozilla Public License 2.0]: https://www.mozilla.org/media/MPL/2.0/index.txt +[cookiecutter-napari-plugin]: https://github.com/napari/cookiecutter-napari-plugin +[file an issue]: https://github.com/DragaDoncila/napari-nd2-reader/issues +[napari]: https://github.com/napari/napari +[tox]: https://tox.readthedocs.io/en/latest/ +[pip]: https://pypi.org/project/pip/ +[PyPI]: https://pypi.org/ \ No newline at end of file diff --git a/napari_nd2_reader/__init__.py b/napari_nd2_reader/__init__.py new file mode 100644 index 0000000..4b9ae9b --- /dev/null +++ b/napari_nd2_reader/__init__.py @@ -0,0 +1,10 @@ +try: + from ._version import version as __version__ +except ImportError: + __version__ = "unknown" + +# replace the asterisk with named imports +from .nd2_reader import napari_get_reader + + +__all__ = ["napari_get_reader"] diff --git a/napari_nd2_reader/_tests/__init__.py b/napari_nd2_reader/_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/napari_nd2_reader/_tests/test_nd2_reader.py b/napari_nd2_reader/_tests/test_nd2_reader.py new file mode 100644 index 0000000..73f7ded --- /dev/null +++ b/napari_nd2_reader/_tests/test_nd2_reader.py @@ -0,0 +1,30 @@ +import numpy as np +from napari_nd2_reader import napari_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 = napari_get_reader("fake.file") + assert reader is None diff --git a/napari_nd2_reader/nd2_reader.py b/napari_nd2_reader/nd2_reader.py new file mode 100644 index 0000000..60f5073 --- /dev/null +++ b/napari_nd2_reader/nd2_reader.py @@ -0,0 +1,79 @@ +""" +This module is an example of a barebones numpy reader plugin for napari. + +It implements the ``napari_get_reader`` hook specification, (to create +a reader plugin) but your plugin may choose to implement any of the hook +specifications offered by napari. +see: https://napari.org/docs/plugins/hook_specifications.html + +Replace code below accordingly. For complete documentation see: +https://napari.org/docs/plugins/for_plugin_developers.html +""" +import numpy as np +from napari_plugin_engine import napari_hook_implementation + + +@napari_hook_implementation +def napari_get_reader(path): + """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. + """ + if isinstance(path, list): + # reader plugins may be handed single path, or a list of paths. + # if it is a list, it is assumed to be an image stack... + # so we are only going to look at the first file. + path = path[0] + + # if we know we cannot read the file, we immediately return None. + if not path.endswith(".npy"): + return None + + # otherwise we return the *function* that can read ``path``. + return reader_function + + +def reader_function(path): + """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 + # load all files into array + arrays = [np.load(_path) for _path in paths] + # stack arrays into single array + data = np.squeeze(np.stack(arrays)) + + # optional kwargs for the corresponding viewer.add_* method + # https://napari.org/docs/api/napari.components.html#module-napari.components.add_layers_mixin + add_kwargs = {} + + layer_type = "image" # optional, default is "image" + return [(data, add_kwargs, layer_type)] diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..3f3e0b8 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +napari-plugin-engine>=0.1.4 +numpy diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..460b756 --- /dev/null +++ b/setup.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import os +import codecs +from setuptools import setup, find_packages + + +def read(fname): + file_path = os.path.join(os.path.dirname(__file__), fname) + return codecs.open(file_path, encoding='utf-8').read() + + +# Add your dependencies in requirements.txt +# Note: you can add test-specific requirements in tox.ini +requirements = [] +with open('requirements.txt') as f: + for line in f: + stripped = line.split("#")[0].strip() + if len(stripped) > 0: + requirements.append(stripped) + + +# https://github.com/pypa/setuptools_scm +use_scm = {"write_to": "napari_nd2_reader/_version.py"} + +setup( + name='napari-nd2-reader', + author='Draga Doncila Pop', + author_email='ddon0001@student.monash.edu', + license='BSD-3', + url='https://github.com/DragaDoncila/napari-nd2-reader', + description='Plugin to load nd2 data into napari', + long_description=read('README.md'), + long_description_content_type='text/markdown', + packages=find_packages(), + python_requires='>=3.6', + install_requires=requirements, + use_scm_version=use_scm, + setup_requires=['setuptools_scm'], + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Testing', + 'Programming Language :: Python', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Operating System :: OS Independent', + 'License :: OSI Approved :: BSD License', + ], + entry_points={ + 'napari.plugin': [ + 'nd2-reader = napari_nd2_reader', + ], + }, +) diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..5cbeb41 --- /dev/null +++ b/tox.ini @@ -0,0 +1,21 @@ +# For more information about tox, see https://tox.readthedocs.io/en/latest/ +[tox] +envlist = py{36,37,38}-{linux,macos,windows} + +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38 + +[gh-actions:env] +PLATFORM = + ubuntu-latest: linux + macos-latest: macos + windows-latest: windows + +[testenv] +commands = pytest -v --cov=./ --cov-report=xml +deps = + pytest-cov # https://pytest-cov.readthedocs.io/en/latest/ + pytest # https://docs.pytest.org/en/latest/contents.html \ No newline at end of file