diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml new file mode 100644 index 0000000..a217ab9 --- /dev/null +++ b/.github/workflows/deploy_docs.yml @@ -0,0 +1,60 @@ +name: Deploy Documentation + +on: + push: + branches: + - main + tags: + - 'v*' + workflow_dispatch: + +# Only allow one docs build at a time so that overlapping stale builds will get +# cancelled automatically. +concurrency: + group: deploy_docs + cancel-in-progress: true + +jobs: + build-and-deploy: + name: Build & Deploy + runs-on: ubuntu-latest + + permissions: + contents: write # so we can write to github pages without a token + pages: write # to deploy to Pages + id-token: write # to verify the deployment originates from an appropriate source + + steps: + - name: Clone repo + uses: actions/checkout@v4 + + - uses: actions/setup-python@v4 + with: + python-version: "3.10" + + - uses: tlambert03/setup-qt-libs@v1 + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + python -m pip install "napari[all]" + python -m pip install -e ".[doc]" + + - name: Build Docs + uses: aganders3/headless-gui@v2 + with: + run: make docs + + - name: Check file tree contents + run: tree + + # At a minimum this job should upload artifacts using actions/upload-pages-artifact + - name: Upload GitHub Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + name: github-pages + path: docs/_build + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 # or specific "vX.X.X" version tag for this action diff --git a/.gitignore b/.gitignore index e82e5bb..6c2de3f 100644 --- a/.gitignore +++ b/.gitignore @@ -80,4 +80,11 @@ target/ */_version.py # vscode -.vscode \ No newline at end of file +.vscode + +# Autogenerated gallery +docs/gallery +docs/jupyter_execute +docs/sg_execution_times.rst +examples/*.mov +examples/*.mp4 \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..866ccce --- /dev/null +++ b/Makefile @@ -0,0 +1,46 @@ +.PHONY: docs clean + +SPHINXOPTS = + +mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +current_dir := $(dir $(mkfile_path)) +docs_dir := $(current_dir)docs + +clean: + echo clean + echo $(current_dir) + rm -rf $(docs_dir)/_build/ + rm -rf $(docs_dir)/gallery/ + rm -f $(docs_dir)/sg_execution_times.rst + +docs-build: + NAPARI_CONFIG="" NAPARI_APPLICATION_IPY_INTERACTIVE=0 sphinx-build -b html docs/ docs/_build $(SPHINXOPTS) + +docs-xvfb: + NAPARI_CONFIG="" NAPARI_APPLICATION_IPY_INTERACTIVE=0 xvfb-run --auto-servernum sphinx-build -b html docs/ docs/_build $(SPHINXOPTS) + +docs: clean docs-build copy-gallery-videos + +# Implies noplot, but no clean - call 'make clean' manually if needed +# Autogenerated paths need to be ignored to prevent reload loops +html-live: + NAPARI_APPLICATION_IPY_INTERACTIVE=0 \ + sphinx-autobuild \ + -b html \ + docs/ \ + docs/_build \ + -D plot_gallery=0 \ + --ignore $(docs_dir)"/gallery/*" \ + --ignore $(docs_dir)"/jupyter_execute/*" \ + --open-browser \ + --port=0 \ + $(SPHINXOPTS) + +html-noplot: clean + NAPARI_APPLICATION_IPY_INTERACTIVE=0 sphinx-build -D plot_gallery=0 -b html docs/ docs/_build $(SPHINXOPTS) + +linkcheck-files: + NAPARI_APPLICATION_IPY_INTERACTIVE=0 sphinx-build -b linkcheck -D plot_gallery=0 --color docs/ docs/_build ${FILES} $(SPHINXOPTS) + +copy-gallery-videos: + rsync -rupE docs/gallery/images docs/_build/gallery diff --git a/README.md b/README.md index 7d09349..4f71831 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![tests](https://github.com/napari/napari-animation/actions/workflows/test_and_deploy.yml/badge.svg)](https://github.com/napari/napari-animation/actions) [![codecov](https://codecov.io/gh/napari/napari-animation/branch/main/graph/badge.svg)](https://codecov.io/gh/napari/napari-animation) -**napari-animation** is a plugin for making animations in [napari]. +**napari-animation** is a plugin for making animations in [napari](https://napari.org).
@@ -18,14 +18,16 @@ ---------------------------------- -This plugin is built on [naparimovie](https://github.com/guiwitz/naparimovie) from @guiwitz. naparimovie was submitted to napari in [PR#851](https://github.com/napari/napari/pull/780) before napari plugin infrastructure existed. +This plugin is built on [`naparimovie`](https://github.com/guiwitz/naparimovie) from [@guiwitz](https://github.com/guiwitz). `naparimovie` was submitted to napari in [PR#851](https://github.com/napari/napari/pull/780) before napari plugin infrastructure existed. ---------------------------------- + ## Overview -**napari-animation** provides a framework for the creation of animations in napari, the plugin contains: -- an easy to use GUI for creating animations interactively -- a Python package for the programmatic creation of animations +**napari-animation** provides a framework for the creation of animations in napari. The plugin contains: + +- an easy to use GUI for creating animations interactively; +- a Python package for the programmatic creation of animations. This plugin remains under development and contributions are very welcome, please open an issue to discuss potential improvements. @@ -38,11 +40,12 @@ This plugin remains under development and contributions are very welcome, please pip install napari-animation ``` -> [!WARNING] -> `napari-animation` uses `ffmpeg` to export animations. If you are using a macOS arm64 computer (Apple Silicon e.g. M1, M2 processor) -> the PyPI package does not include the needed binary for your platform. You will need to install `ffmpeg` using -> `conda` from the [conda-forge channel](https://conda-forge.org/docs/#what-is-conda-forge) (`conda install -c conda-forge ffmpeg`) -> or using [`homebrew`](https://brew.sh) (`brew install ffmpeg`). +```{warning} +`napari-animation` uses `ffmpeg` to export animations. If you are using a macOS arm64 computer (Apple Silicon e.g. M1, M2 processor) +the PyPI package does not include the needed binary for your platform. You will need to install `ffmpeg` using +`conda` from the [conda-forge channel](https://conda-forge.org/docs/#what-is-conda-forge) (`conda install -c conda-forge ffmpeg`) +or using [`homebrew`](https://brew.sh) (`brew install ffmpeg`). +``` ### Conda `napari-animation` is also available for install using `conda` through the [conda-forge channel](https://conda-forge.org/docs/#what-is-conda-forge). @@ -71,8 +74,8 @@ To activate the GUI, select **napari-animation: wizard** from the *plugins menu*
-### Headless -**napari-animation** can also be run headless, allowing for reproducible, scripted creation of animations. +### Scripting +**napari-animation** can also be run programatically, allowing for reproducible, scripted creation of animations. ```python from napari_animation import Animation @@ -95,7 +98,7 @@ animation.animate('demo.mov', canvas_only=False) ``` ## Examples -Examples can be found in our [examples](examples) folder. Simple examples for both interactive and headless +Examples can be found in our [Examples gallery](https://napari-animation.github.io/gallery) folder. Simple examples for both interactive and headless use of the plugin follow. ## Contributing @@ -103,9 +106,14 @@ use of the plugin follow. Contributions are very welcome and a detailed contributing guide is coming soon. In the meantime, clone this repository and install it in editable mode using `pip`. We recommend using a virtual environment, for example `conda`. -> [!IMPORTANT] -> Ensure you have a suitable Qt backend for napari! We recommend `PyQt5`. -> For more information, see the napari [Qt backend installation guide](https://napari.org/stable/tutorials/fundamentals/installation.html#choosing-a-different-qt-backend) + + +```{important} +Ensure you have a suitable Qt backend for napari! We recommend `PyQt5`. +For more information, see the napari [Qt backend installation guide](https://napari.org/stable/tutorials/fundamentals/installation.html#choosing-a-different-qt-backend) +``` + +To set up your development installation, clone this repository, navigate to the clone folder, and install napari-animation in editable mode using `pip`. ```sh conda create -n nap-anim python=3.10 @@ -118,26 +126,42 @@ Tests are run with `pytest`. You can make sure your `[dev]` installation is working properly by running `pytest .` from within the repository. -> [!NOTE] -> We use [`pre-commit`](https://pre-commit.com) to sort imports and lint with -> [`ruff`](https://github.com/astral-sh/ruff) and format code with -> [`black`](https://github.com/psf/black) automatically prior to each commit. -> To minmize test errors when submitting pull requests, please install `pre-commit` -> in your environment as follows: -> ```sh -> pre-commit install -> ``` +```{note} +We use [`pre-commit`](https://pre-commit.com) to sort imports and lint with +[`ruff`](https://github.com/astral-sh/ruff) and format code with +[`black`](https://github.com/psf/black) automatically prior to each commit. +To minmize test errors when submitting pull requests, please install `pre-commit` +in your environment as follows: + +`pre-commit install` +``` + +## Documentation + +The documentation for napari-animation is built with [Sphinx](https://www.spinx-doc.org). After installing the documentation dependencies with + +```sh +pip install ".[doc]" +``` + +you can see a local version of the documentation by running + +```sh +make docs +``` + +Open up the `docs/_build/index.html` file in your browser, and you'll see the home page of the docs being displayed. + ## License -Distributed under the terms of the [BSD-3] license, -"napari-animation" is free and open source software +Distributed under the terms of the [BSD-3 license](http://opensource.org/licenses/BSD-3-Clause), +`napari-animation` is free and open source software. ## Issues -If you encounter any problems, please [file an issue] along with a detailed description. +If you encounter any problems, please [file an issue](https://github.com/napari/napari-animation/issues) along with a detailed description. -[napari]: https://github.com/napari/napari [Cookiecutter]: https://github.com/audreyr/cookiecutter [@napari]: https://github.com/napari [BSD-3]: http://opensource.org/licenses/BSD-3-Clause diff --git a/docs/_static/custom.css b/docs/_static/custom.css new file mode 100644 index 0000000..e69de29 diff --git a/docs/_static/favicon/logo-noborder-180.png b/docs/_static/favicon/logo-noborder-180.png new file mode 100644 index 0000000..88b21dd Binary files /dev/null and b/docs/_static/favicon/logo-noborder-180.png differ diff --git a/docs/_static/favicon/logo-silhouette-192.png b/docs/_static/favicon/logo-silhouette-192.png new file mode 100644 index 0000000..31f4f5c Binary files /dev/null and b/docs/_static/favicon/logo-silhouette-192.png differ diff --git a/docs/_static/favicon/logo-silhouette-dark-light.svg b/docs/_static/favicon/logo-silhouette-dark-light.svg new file mode 100644 index 0000000..7120e73 --- /dev/null +++ b/docs/_static/favicon/logo-silhouette-dark-light.svg @@ -0,0 +1,28 @@ + + + diff --git a/docs/_templates/navbar-project.html b/docs/_templates/navbar-project.html new file mode 100644 index 0000000..0090ea2 --- /dev/null +++ b/docs/_templates/navbar-project.html @@ -0,0 +1,4 @@ + + + napari-animation + diff --git a/docs/_toc.yml b/docs/_toc.yml new file mode 100644 index 0000000..fca364f --- /dev/null +++ b/docs/_toc.yml @@ -0,0 +1,5 @@ +root: index +subtrees: +- entries: + - file: gallery + - file: developers/contributing diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..ced66df --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,319 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +# import os +# import sys +# sys.path.insert(0, os.path.abspath('.')) + +import os +import shutil +from pathlib import Path + +import imageio.v2 as iio +import napari +from sphinx_gallery import scrapers +from sphinx_gallery.sorting import ExampleTitleSortKey + +from napari_animation._version import version as napari_animation_version + +release = napari_animation_version +if "dev" in release: # noqa: SIM108 + version = "dev" +else: + version = release + +# -- Project information ----------------------------------------------------- + +project = "napari-animation" +copyright = "2024, The napari team" # noqa: A001 +author = "The napari team" + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. + +extensions = [ + "sphinx.ext.intersphinx", + "sphinx_external_toc", + "myst_nb", + "sphinx.ext.viewcode", + "sphinx_favicon", + "sphinx_copybutton", + "sphinx_gallery.gen_gallery", + "sphinxcontrib.video", +] + +external_toc_path = "_toc.yml" + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "napari_sphinx_theme" + +# # Define the json_url for our version switcher. +# json_url = "https://napari.org/dev/_static/version_switcher.json" + +# if version == "dev": +# version_match = "latest" +# else: +# version_match = release + +html_theme_options = { + "external_links": [{"name": "napari", "url": "https://napari.org"}], + "github_url": "https://github.com/napari/napari-animation", + "navbar_start": ["navbar-logo", "navbar-project"], + "navbar_end": ["navbar-icon-links"], + # "switcher": { + # "json_url": json_url, + # "version_match": version_match, + # }, + "navbar_persistent": [], + "header_links_before_dropdown": 6, + "secondary_sidebar_items": ["page-toc"], + "pygment_light_style": "napari", + "pygment_dark_style": "napari", +} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] +html_logo = "images/logo.png" +html_sourcelink_suffix = "" +html_title = "napari animation" + +favicons = [ + { + # the SVG is the "best" and contains code to detect OS light/dark mode + "static-file": "favicon/logo-silhouette-dark-light.svg", + "type": "image/svg+xml", + }, + { + # Safari in Oct. 2022 does not support SVG + # an ICO would work as well, but PNG should be just as good + # setting sizes="any" is needed for Chrome to prefer the SVG + "sizes": "any", + "static-file": "favicon/logo-silhouette-192.png", + }, + { + # this is used on iPad/iPhone for "Save to Home Screen" + # apparently some other apps use it as well + "rel": "apple-touch-icon", + "sizes": "180x180", + "static-file": "favicon/logo-noborder-180.png", + }, +] + +html_css_files = [ + "custom.css", +] + +intersphinx_mapping = { + "python": ["https://docs.python.org/3", None], + "numpy": ["https://numpy.org/doc/stable/", None], + "napari_plugin_engine": [ + "https://napari-plugin-engine.readthedocs.io/en/latest/", + "https://napari-plugin-engine.readthedocs.io/en/latest/objects.inv", + ], + "napari": [ + "https://napari.org/dev", + "https://napari.org/dev/objects.inv", + ], +} + +myst_enable_extensions = [ + "colon_fence", + "dollarmath", + "substitution", + "tasklist", +] + +myst_heading_anchors = 4 +suppress_warnings = ["etoc.toctree"] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [ + "_build", + "Thumbs.db", + ".DS_Store", + ".jupyter_cache", + "jupyter_execute", +] + +# -- Examples gallery scrapers ------------------------------------------------- + + +class VideoScraper: + """Video file scraper class. + Scrapes video files that were saved to disk by the example scripts. + Based on the sphinx example scraper "Example 2: detecting image files on disk" + https://sphinx-gallery.github.io/stable/advanced.html#example-2-detecting-image-files-on-disk + """ + + def __init__(self): + self.seen = set() + + def __repr__(self): + return "VideoScraper" + + def __call__(self, block, block_vars, gallery_conf): + """Video file scraper. + Scrapes video files that were saved to disk by the example scripts. + Based on the sphinx example scraper "Example 2: detecting image files on disk" + https://sphinx-gallery.github.io/stable/advanced.html#example-2-detecting-image-files-on-disk + """ + # Find all video files in the directory of this example. + video_file_extensions = [".mp4", ".mov"] + path_current_example = os.path.dirname(block_vars["src_file"]) + video_paths = sorted( + str(p.resolve()) + for p in Path(path_current_example).glob("**/*") + if p.suffix in video_file_extensions + ) + + # Iterate through the videos, copy them to the sphinx-gallery output directory + video_names = [] + image_path_iterator = block_vars["image_path_iterator"] + rst = "" + for video in video_paths: + if video not in self.seen: + self.seen |= set(video) + this_video_path = image_path_iterator.next() + this_video_path = ( + os.path.splitext(this_video_path)[0] + + os.path.splitext(video)[1] + ) + video_names.append(this_video_path) + shutil.move(video, this_video_path) + assert os.path.exists(this_video_path) + self.video_thumbnail(this_video_path) + relative_path = ( + "." + + this_video_path.split(gallery_conf.get("gallery_dirs"))[ + -1 + ].strip() + ) + # Makefile copy-gallery-videos rule copies movies to _build folder + # so this relative path works correctly in the built documentation + rst += self.rst_video_template(relative_path) + + # We want to display either a video OR a napari screenshot, not both! + # If any video files are found, VideoScraper() closes any open napari windows + # so that the static screenshot napari_scraper will not find anything + # For this to work, VideoScraper() must come BEFORE napari_scraper in sphinx_gallery_conf + if len(video_paths) > 0: + napari.Viewer.close_all() + + return rst + + def video_thumbnail(self, video_filename): + """Save PNG screenshot of the first video frame.""" + reader = iio.get_reader(video_filename) + first_frame = reader.get_data(0) + first_frame_filename = os.path.splitext(video_filename)[0] + ".png" + iio.imwrite(first_frame_filename, first_frame) + return first_frame + + def rst_video_template(self, video_filepath): + """Template HTML for embedding video into webpage.""" + rst = f""".. video:: {video_filepath} + :autoplay: + :loop: + :width: 600 + :poster: {os.path.splitext(video_filepath)[0] + ".png"} + """ + return rst + + +def napari_scraper(block, block_vars, gallery_conf): + """Basic napari window scraper. + + Looks for any QtMainWindow instances and takes a screenshot of them. + + `app.processEvents()` allows Qt events to propagateo and prevents hanging. + """ + imgpath_iter = block_vars["image_path_iterator"] + + if app := napari.qt.get_app(): + app.processEvents() + else: + return "" + + img_paths = [] + for win, img_path in zip( + reversed(napari._qt.qt_main_window._QtMainWindow._instances), + imgpath_iter, + ): + img_paths.append(img_path) + win._window.screenshot(img_path, canvas_only=False) + + napari.Viewer.close_all() + app.processEvents() + + return scrapers.figure_rst(img_paths, gallery_conf["src_dir"]) + + +def reset_napari(gallery_conf, fname): + from napari.settings import get_settings + from qtpy.QtWidgets import QApplication + + settings = get_settings() + settings.appearance.theme = "dark" + + # Disabling `QApplication.exec_` means example scripts can call `exec_` + # (scripts work when run normally) without blocking example execution by + # sphinx-gallery. (from qtgallery) + QApplication.exec_ = lambda _: None + + +sphinx_gallery_conf = { + # path to your example scripts + "examples_dirs": "../examples", + "gallery_dirs": "gallery", # path to where to save gallery generated output + "filename_pattern": "/*.py", + "ignore_pattern": "README.rst|/*_.py", + "default_thumb_file": Path(__file__).parent / "images" / "logo.png", + "plot_gallery": "'True'", # https://github.com/sphinx-gallery/sphinx-gallery/pull/304/files + "download_all_examples": False, + "only_warn_on_example_error": False, + "abort_on_example_error": True, + "image_scrapers": ( + "matplotlib", + VideoScraper(), + # We want to display either a video OR a napari screenshot, not both! + # If any video files are found, VideoScraper() closes any open napari windows + # so that the static screenshot napari_scraper will not find anything + # For this to work, VideoScraper() must come BEFORE napari_scraper in sphinx_gallery_conf + napari_scraper, + ), + "reset_modules": (reset_napari,), + "reference_url": {"napari": None}, + "within_subsection_order": ExampleTitleSortKey, +} + + +def setup(app): + """Set up docs build. + + * Ignores .ipynb files to prevent sphinx from complaining about multiple + files found for document when generating the gallery + """ + app.registry.source_suffix.pop(".ipynb", None) diff --git a/docs/developers/contributing.md b/docs/developers/contributing.md new file mode 100644 index 0000000..76c1c08 --- /dev/null +++ b/docs/developers/contributing.md @@ -0,0 +1,169 @@ +# Contributing Guide + +We welcome your contributions! Please see the provided steps below and never hesitate to contact us. + +If you are a new user, we recommend checking out the detailed [Github Guides](https://guides.github.com). + +## Setting up a development installation + +In order to make changes to `napari-animation`, you will need to [fork](https://guides.github.com/activities/forking/#fork) the +[repository](https://github.com/napari/napari-animation). + +If you are not familiar with `git`, we recommend reading up on [this guide](https://guides.github.com/introduction/git-handbook/#basic-git). + +Clone the forked repository to your local machine and change directories: +```sh +git clone https://github.com/your-username/napari-animation.git +cd napari-animation +``` + +Set the `upstream` remote to the base `napari` repository: +```sh +git remote add upstream https://github.com/napari/napari-animation.git +``` + +Install the package in editable mode, along with all of the developer tools +```sh +pip install -r requirements.txt +``` + +We use [`pre-commit`](https://pre-commit.com) to sort imports with +[`isort`](https://github.com/timothycrosley/isort), format code with +[`black`](https://github.com/psf/black), and lint with +[`flake8`](https://github.com/PyCQA/flake8) automatically prior to each commit. +To minmize test errors when submitting pull requests, please install `pre-commit` +in your environment as follows: + +```sh +pre-commit install +``` + +Upon committing, your code will be formatted according to our [`black` +configuration](https://github.com/napari/napari-animation/blob/main/pyproject.toml). To learn more, +see [`black`'s documentation](https://black.readthedocs.io/en/stable/). + +Code will also be linted to enforce the stylistic and logistical rules specified +in our [`flake8` configuration](https://github.com/napari/napari/blob/master/setup.cfg), which currently ignores +[E203](https://lintlyci.github.io/Flake8Rules/rules/E203.html), +[E501](https://lintlyci.github.io/Flake8Rules/rules/E501.html), +[W503](https://lintlyci.github.io/Flake8Rules/rules/W503.html) and +[C901](https://lintlyci.github.io/Flake8Rules/rules/C901.html). For information +on any specific flake8 error code, see the [Flake8 +Rules](https://lintlyci.github.io/Flake8Rules/). You may also wish to refer to +the [PEP 8 style guide](https://www.python.org/dev/peps/pep-0008/). + +If you wish to tell the linter to ignore a specific line use the `# noqa` +comment along with the specific error code (e.g. `import sys # noqa: E402`) but +please do not ignore errors lightly. + +## Translations + +Starting with version 0.4.7, napari codebase include internationalization +(i18n) and now offers the possibility of installing language packs, which +provide localization (l10n) enabling the user interface to be displayed in +different languages. + +To learn more about the current languages that are in the process of +translation, visit the [language packs repository](https://github.com/napari/napari-language-packs) + +To make your code translatable (localizable), please use the `trans` helper +provided by the napari utilities. + +```python +from napari.utils.translations import trans + +some_string = trans._("Localizable string") +``` + +To learn more, please see the [translations guide](https://napari.org/guides/stable/translations.html). + +## Making changes + +Create a new feature branch: +```sh +git checkout master -b your-branch-name +``` + +`git` will automatically detect changes to a repository. +You can view them with: +```sh +git status +``` + +Add and commit your changed files: +```sh +git add my-file-or-directory +git commit -m "my message" +``` + +## Tests + +We use unit tests and integration tests to ensure that +napari-animation works as intended. Writing tests for new code is a critical part of +keeping napari-animation maintainable as it grows. + +Check out the dedicated documentation on testing over at [napari.org](https://napari.org/dev/developers/testing.html) that we recommend you +read as you're working on your first contribution. + +### Help us make sure it's you + +Each commit you make must have a [GitHub-registered email](https://github.com/settings/emails) +as the `author`. You can read more [here](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/setting-your-commit-email-address). + +To set it, use `git config --global user.email your-address@example.com`. + +## Keeping your branches up-to-date + +Switch to the `main` branch: +```sh +git checkout main +``` + +Fetch changes and update `main`: +```sh +git pull upstream main --tags +``` + +This is shorthand for: +```sh +git fetch upstream main --tags +git merge upstream/main +``` + +Update your other branches: +```sh +git checkout your-branch-name +git merge main +``` + +## Sharing your changes + +Update your remote branch: +```sh +git push -u origin your-branch-name +``` + +You can then make a [pull-request](https://guides.github.com/activities/forking/#making-a-pull-request) to `napari-animation`'s `main` branch. + +## Building the docs + +From the project root: +```sh +make docs +``` + +The docs will be built at `docs/_build/html`. + +Most web browsers will allow you to preview HTML pages. +Try entering `file:///absolute/path/to/napari-animation/docs/_build/html/index.html` in your address bar. + +## Code of conduct + +`napari` has a [Code of Conduct](https://napari.org/stable/community/code_of_conduct.html) that should be honored by everyone who participates in the `napari` community, including `napari-animation`. + +## Questions, comments, and feedback + +If you have questions, comments, suggestions for improvement, or any other inquiries +regarding the project, feel free to open an [issue](https://github.com/napari/napari-animation/issues). + +Issues and pull-requests are written in [Markdown](https://guides.github.com/features/mastering-markdown/#what). You can find a comprehensive guide [here](https://guides.github.com/features/mastering-markdown/#syntax). diff --git a/docs/gallery.md b/docs/gallery.md new file mode 100644 index 0000000..1b70595 --- /dev/null +++ b/docs/gallery.md @@ -0,0 +1,11 @@ +# Examples gallery + +Examples can be found in our [examples](https://github.com/napari/napari-animation/tree/main/examples) folder. Simple examples for both interactive and headless use of the plugin follow. + +There is also a [Programmatic jupyter notebook example](https://github.com/napari/napari-animation/tree/main/examples/programmatic_notebook_example.ipynb). + +```{eval-rst} +.. include:: gallery/index.rst + :start-after: :orphan: + :end-before: .. toctree:: +``` diff --git a/docs/images/logo.png b/docs/images/logo.png new file mode 100644 index 0000000..736ee20 Binary files /dev/null and b/docs/images/logo.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..451beda --- /dev/null +++ b/docs/index.md @@ -0,0 +1,2 @@ +```{include} ../README.md +``` diff --git a/examples/README.rst b/examples/README.rst new file mode 100644 index 0000000..e69de29 diff --git a/examples/animate2D.py b/examples/animate2D.py index e3346f4..047b9fc 100644 --- a/examples/animate2D.py +++ b/examples/animate2D.py @@ -1,4 +1,7 @@ """ +Animate 2D +========== + Display a labels layer above of an image layer using the add_labels and add_image APIs """ @@ -10,12 +13,12 @@ blobs = data.binary_blobs(length=128, volume_fraction=0.1, n_dim=3) -viewer = napari.view_image(blobs.astype(float), name='blobs') +viewer = napari.view_image(blobs.astype(float), name="blobs") labeled = ndi.label(blobs)[0] -viewer.add_labels(labeled, name='blob ID') +viewer.add_labels(labeled, name="blob ID") animation = Animation(viewer) -viewer.update_console({'animation': animation}) +viewer.update_console({"animation": animation}) animation.capture_keyframe() viewer.camera.zoom = 0.2 @@ -29,5 +32,4 @@ animation.capture_keyframe(steps=60) viewer.reset_view() animation.capture_keyframe() -animation.animate('demo2D.mov', canvas_only=False) - +animation.animate("animate2D.mp4", canvas_only=False) diff --git a/examples/animate3D.py b/examples/animate3D.py index 585a0cd..96c6de4 100644 --- a/examples/animate3D.py +++ b/examples/animate3D.py @@ -1,4 +1,7 @@ """ +Animate 3D +========== + Display a labels layer above of an image layer using the add_labels and add_image APIs """ @@ -10,12 +13,12 @@ blobs = data.binary_blobs(length=128, volume_fraction=0.1, n_dim=3) -viewer = napari.view_image(blobs.astype(float), name='blobs') +viewer = napari.view_image(blobs.astype(float), name="blobs") labeled = ndi.label(blobs)[0] -viewer.add_labels(labeled, name='blob ID') +viewer.add_labels(labeled, name="blob ID") animation = Animation(viewer) -viewer.update_console({'animation': animation}) +viewer.update_console({"animation": animation}) viewer.dims.ndisplay = 3 viewer.camera.angles = (0.0, 0.0, 90.0) @@ -29,4 +32,4 @@ viewer.reset_view() viewer.camera.angles = (0.0, 0.0, 90.0) animation.capture_keyframe() -animation.animate('demo3D.mov', canvas_only=False) +animation.animate("animate3D.mp4", canvas_only=False) diff --git a/examples/animateMixed.py b/examples/animateMixed.py index 5e672c1..a2f703f 100644 --- a/examples/animateMixed.py +++ b/examples/animateMixed.py @@ -1,4 +1,7 @@ """ +Animate mixed +============= + Display a labels layer above of an image layer using the add_labels and add_image APIs """ @@ -10,12 +13,12 @@ blobs = data.binary_blobs(length=128, volume_fraction=0.1, n_dim=3) -viewer = napari.view_image(blobs.astype(float), name='blobs') +viewer = napari.view_image(blobs.astype(float), name="blobs") labeled = ndi.label(blobs)[0] -viewer.add_labels(labeled, name='blob ID') +viewer.add_labels(labeled, name="blob ID") animation = Animation(viewer) -viewer.update_console({'animation': animation}) +viewer.update_console({"animation": animation}) animation.capture_keyframe() viewer.camera.zoom = 0.2 @@ -42,4 +45,4 @@ viewer.reset_view() viewer.camera.angles = (0.0, 0.0, 90.0) animation.capture_keyframe() -animation.animate('demoMixed.mov', canvas_only=False) +animation.animate("animateMixed.mp4", canvas_only=False) diff --git a/examples/ease_function.py b/examples/ease_function.py index 2d7a455..63de40d 100644 --- a/examples/ease_function.py +++ b/examples/ease_function.py @@ -1,3 +1,7 @@ +""" +Ease function +============= +""" import napari from skimage import data from napari_animation import Animation @@ -16,4 +20,4 @@ animation.capture_keyframe(steps=60) -animation.animate("hello.mp4", canvas_only=True, fps=60) +animation.animate("ease_function.mp4", canvas_only=True, fps=60) diff --git a/examples/interactive_animation.py b/examples/interactive_animation.py index e00fb87..36eb652 100644 --- a/examples/interactive_animation.py +++ b/examples/interactive_animation.py @@ -1,4 +1,7 @@ """ +Interactive animation +===================== + Display a labels layer above of an image layer using the add_labels and add_image APIs """ diff --git a/examples/example.py b/examples/layer_planes.py similarity index 95% rename from examples/example.py rename to examples/layer_planes.py index 643ed5e..366aa70 100644 --- a/examples/example.py +++ b/examples/layer_planes.py @@ -1,3 +1,7 @@ +""" +Layer planes +============ +""" from scipy import ndimage as ndi from napari_animation import Animation import napari @@ -55,5 +59,5 @@ def replace_labels_data(): image_layer.plane.position = (0, 0, 0) animation.capture_keyframe(steps=30) -animation.animate("test.mp4", canvas_only=True) +animation.animate("layer_planes.mp4", canvas_only=True) image_layer.plane.position = (0, 0, 0) \ No newline at end of file diff --git a/examples/programmatic_notebook_example.ipynb b/examples/programmatic_notebook_example.ipynb index fffa090..ddc08c5 100644 --- a/examples/programmatic_notebook_example.ipynb +++ b/examples/programmatic_notebook_example.ipynb @@ -1,5 +1,12 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Programmatic example" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/setup.cfg b/setup.cfg index 96f8b41..adfe8c8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,26 +31,37 @@ include_package_data = True install_requires = imageio imageio-ffmpeg - napari + napari>=0.4.19rc5 npe2 numpy qtpy scipy - tqdm>=4.56.0 + tqdm>=4.56.0 superqt - [options.extras_require] testing = pytest + pytest-cov pytest-qt +doc = + sphinx>6 + sphinx-autobuild + sphinx-external-toc + sphinx-copybutton + sphinx-gallery + sphinx-favicon + sphinxcontrib-video + matplotlib + myst-nb + napari-sphinx-theme>=0.3.0 dev = pre-commit black ruff check-manifest %(testing)s - + %(doc)s [options.entry_points] napari.manifest = @@ -65,13 +76,11 @@ exclude_lines = if TYPE_CHECKING: raise NotImplementedError() - [tool:pytest] addopts = --durations=10 filterwarnings = ignore::DeprecationWarning - [tool:check-manifest] ignore = pre-commit-config.yaml