diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 462e4dd99..27a6f8e55 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,24 +1,28 @@ ---- -name: "\U0001F41B Bug Report" +______________________________________________________________________ + +name: "\\U0001F41B Bug Report" about: Report typos, innacuracies or errors to help us improve the napari documentation title: '' labels: bug assignees: '' ---- +______________________________________________________________________ ## 🐛 Bug + + + - + ## Environment - - Please copy and paste the information at napari info option in help menubar here: +- Please copy and paste the information at napari info option in help menubar here: - - Any other relevant information: +- Any other relevant information: ## Additional context diff --git a/.github/ISSUE_TEMPLATE/new_content.md b/.github/ISSUE_TEMPLATE/new_content.md index 5c1a5d104..02bfc3f19 100644 --- a/.github/ISSUE_TEMPLATE/new_content.md +++ b/.github/ISSUE_TEMPLATE/new_content.md @@ -1,15 +1,17 @@ ---- -name: "\U0001F4DA New content request" +______________________________________________________________________ + +name: "\\U0001F4DA New content request" about: Suggest new content pages to be added to the napari documentation title: '' labels: content assignees: '' ---- +______________________________________________________________________ ## 📚 New content request - + ### Outline + diff --git a/.github/ISSUE_TEMPLATE/task.md b/.github/ISSUE_TEMPLATE/task.md index c7bf04d60..de43a5bec 100644 --- a/.github/ISSUE_TEMPLATE/task.md +++ b/.github/ISSUE_TEMPLATE/task.md @@ -1,10 +1,13 @@ ---- -name: "\U0001F9F0 Task" +______________________________________________________________________ + +name: "\\U0001F9F0 Task" about: Submit a proposal/request for improvements to workflows, documentation build or deployment. title: '' labels: task assignees: '' ---- +______________________________________________________________________ + ## 🧰 Task + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1667249d3..9b9ad0843 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,21 +1,31 @@ # Description + + + + + ## Type of change + + - [ ] Fixes or improves existing content - [ ] Adds new content page(s) - [ ] Fixes or improves workflow, documentation build or deployment # References + + ## Final checklist: + - [ ] My PR is the minimum possible work for the desired functionality - [ ] I have commented my code, particularly in hard-to-understand areas -- [ ] I have added [alt text](https://webaim.org/techniques/alttext/) to new images included in this PR \ No newline at end of file +- [ ] I have added [alt text](https://webaim.org/techniques/alttext/) to new images included in this PR diff --git a/.github/TEST_FAIL_TEMPLATE.md b/.github/TEST_FAIL_TEMPLATE.md index e02e399a0..bc8d63748 100644 --- a/.github/TEST_FAIL_TEMPLATE.md +++ b/.github/TEST_FAIL_TEMPLATE.md @@ -1,7 +1,7 @@ ---- -title: "{{ env.TITLE }}" -labels: [bug] ---- +______________________________________________________________________ + +## title: "{{ env.TITLE }}" labels: \[bug\] + The {{ workflow }} workflow failed on {{ date | date("YYYY-MM-DD HH:mm") }} UTC The most recent failing test was on {{ env.PLATFORM }} py{{ env.PYTHON }} {{ env.BACKEND }} @@ -9,4 +9,4 @@ with commit: {{ sha }} Full run: https://github.com/{{ payload.repository.full_name }}/actions/runs/{{ env.RUN_ID }} -(This post will be updated if another test fails, as long as this issue remains open.) \ No newline at end of file +(This post will be updated if another test fails, as long as this issue remains open.) diff --git a/.github/missing_translations.md b/.github/missing_translations.md index 490f68819..ce1ba9cf4 100644 --- a/.github/missing_translations.md +++ b/.github/missing_translations.md @@ -1,7 +1,6 @@ ---- -title: [Automatic issue] Missing `_.trans()`. -labels: "good first issue" ---- +______________________________________________________________________ + +## title: \[Automatic issue\] Missing `_.trans()`. labels: "good first issue" It looks like one of our test cron detected missing translations. You can see the latest output [here](https://github.com/napari/napari/actions/workflows/test_translations.yml). @@ -11,4 +10,3 @@ You can also Update the cron script to update this issue with better information Note that this issue will be automatically updated if kept open, or a new one will be created when necessary, if no open issue is found and new `_.trans` call are missing. - diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5c39aa38a..8ccbb92f0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,3 +3,12 @@ repos: rev: v4.4.0 hooks: - id: trailing-whitespace +- repo: https://github.com/executablebooks/mdformat + rev: 0.7.16 # Use the ref you want to point at + hooks: + - id: mdformat + # Optionally add plugins + args: ["--number"] + additional_dependencies: + - mdformat-gfm + - mdformat-black diff --git a/EULA.md b/EULA.md index 378366f75..4081a8553 100644 --- a/EULA.md +++ b/EULA.md @@ -7,9 +7,8 @@ https://napari.org. -[napari_installers]: https://napari.org/stable/index.html#installation - + diff --git a/docs/developers/profiling.md b/docs/developers/profiling.md index ca727a2a1..13a0ccdd4 100644 --- a/docs/developers/profiling.md +++ b/docs/developers/profiling.md @@ -35,6 +35,7 @@ The output will be a table similar to this one: 18283 0.027 0.000 0.278 0.000 fromnumeric.py:70(_wrapreduction) ... ``` + The format of this table is documented in the Python docs [here](https://docs.python.org/3/library/profile.html#instant-user-s-manual). Because the output for complex programs such as napari can be very long, @@ -47,54 +48,59 @@ python -m cProfile -o result.pstat path_to_script.py Once the file is saved, you can investigate using free tools. Some options include: -1. The Stat object. +1. The Stat object. + + You can parse the profile output using the `Stats` object from the `pstats` built-in library. For example: + + ```python + from pstats import Stats + + stat = Stats("path/to/result.pstat") + stat.sort_stats("tottime").print_stats(10) + ``` - You can parse the profile output using the `Stats` object from the `pstats` built-in library. For example: - ```python - from pstats import Stats - stat = Stats("path/to/result.pstat") - stat.sort_stats("tottime").print_stats(10) - ``` - You can find more documentation about the Stats object in the Python documentation [here](https://docs.python.org/3/library/profile.html#the-stats-class). + You can find more documentation about the Stats object in the Python documentation [here](https://docs.python.org/3/library/profile.html#the-stats-class). -2. Snakeviz. +2. Snakeviz. - Snakeviz is a third party library designed to visualize profiling output interactively. - You can install it with pip like any other Python library using `python -m pip install snakeviz`. - This will provide the `snakeviz` command, which you can call to create an in-browser - visualization of your profiling data. Use the command: - ```bash - snakeviz path/to/result.pstat - ``` - This should cause a new page to open in your browser with your profiling information. - You can read more about how to interpret this visualization on the - [snakeviz homepage](https://jiffyclub.github.io/snakeviz/). + Snakeviz is a third party library designed to visualize profiling output interactively. + You can install it with pip like any other Python library using `python -m pip install snakeviz`. + This will provide the `snakeviz` command, which you can call to create an in-browser + visualization of your profiling data. Use the command: -3. gprof2dot + ```bash + snakeviz path/to/result.pstat + ``` - You can visualize the call graph with [`graphviz`](https://www.graphviz.org/), - a third party graph visualization library. - You can install graphviz with system package managers: + This should cause a new page to open in your browser with your profiling information. + You can read more about how to interpret this visualization on the + [snakeviz homepage](https://jiffyclub.github.io/snakeviz/). - * Ubuntu: `sudo apt install graphviz` - * MacOS with brew: `brew install graphviz` - * Windows with choco `choco install graphviz` +3. gprof2dot - You can then use `gprof2dot`, a Python library, to convert the `.pstat` - statistics to a `.dot` graph file and use graphviz: + You can visualize the call graph with [`graphviz`](https://www.graphviz.org/), + a third party graph visualization library. + You can install graphviz with system package managers: - ```bash - $ python -m gprof2dot -f pstats -n 5 result.pstat -o result.dot - $ dot -Tpng -o result.png result.dot - ``` + - Ubuntu: `sudo apt install graphviz` + - MacOS with brew: `brew install graphviz` + - Windows with choco `choco install graphviz` - If your shell supports piping, this can all be combined into one command: + You can then use `gprof2dot`, a Python library, to convert the `.pstat` + statistics to a `.dot` graph file and use graphviz: - ```bash - $ python -m gprof2dot -f pstats -n 5 result.pstat -o | dot -Tpng -o result.png - ``` + ```bash + $ python -m gprof2dot -f pstats -n 5 result.pstat -o result.dot + $ dot -Tpng -o result.png result.dot + ``` -4. Some IDEs have built in profiling visualization tools. For example, PyCharm Professional, which is free for academics and open source maintainers, allows viewing `.pstat` files using Tools > Open CProfile snapshot. + If your shell supports piping, this can all be combined into one command: + + ```bash + $ python -m gprof2dot -f pstats -n 5 result.pstat -o | dot -Tpng -o result.png + ``` + +4. Some IDEs have built in profiling visualization tools. For example, PyCharm Professional, which is free for academics and open source maintainers, allows viewing `.pstat` files using Tools > Open CProfile snapshot. cProfile also allows profiling only specific parts of the code. You can restrict profiling to particular code sections using @@ -121,6 +127,7 @@ def testing_fun(): code_for_profile() pr.dump_stats("camera_layers.pstat") + testing_btn = QPushButton("Profile") testing_btn.clicked.connect(testing_fun) viewer.window.add_dock_widget(testing_btn) @@ -128,7 +135,7 @@ viewer.window.add_dock_widget(testing_btn) In addition to cProfile, third-party profilers are available in Python: -* `yappi` with support for multithreading -* `vmprof` +- `yappi` with support for multithreading +- `vmprof` Both can be installed with `pip`. diff --git a/docs/developers/release.md b/docs/developers/release.md index edd62d87b..ea141641a 100644 --- a/docs/developers/release.md +++ b/docs/developers/release.md @@ -5,6 +5,7 @@ Currently, it only handles distribution, but as the project matures, it will include generating release notes, documentation, etc. # Timeline + New versions of `napari` will be released every two months. The first release candidate will be available one week prior to release for testing purposes. Multiple release candidates may become available during the week prior to release. Upcoming releases can be found in our public calendar. The latest release candidate can be installed with @@ -12,9 +13,11 @@ The latest release candidate can be installed with `python -m pip install --pre napari` # Release management + The release will be coordinated by a release manager whose responsibilities include... ## Two weeks before release (one week before release candidate) + - Look through currently open PRs and get a sense of what would be good to merge before the first release candidate - Ensure `conda-recipe/meta.yaml` in `napari/packaging` is up-to-date (e.g. `run` dependencies match `setup.cfg` requirements). - Create a zulip thread in the release channel letting people know the release candidate is coming and pointing out PRs that would be nice to merge before release @@ -22,24 +25,28 @@ The release will be coordinated by a release manager whose responsibilities incl At this stage, bug fixes and features that are close to landing should be prioritized. The release manager will follow up with PR authors, reviewing and merging as needed. ## Nine days before release (two days before release candidate) + - Generate release notes with the script in the release folder - Fill in the release highlights and make a PR with the release notes At this point the release manager should ideally be the only person merging PRs on the repo for the next week. ## One week before release + - Add any recently merged PRs to release notes - Merge release notes - Make the release candidate - Announce to release stream on zulip that the first release candidate is available for testing ## The week before release + - Merge any PRs and update release notes accordingly - Make new release candidates as necessary and announce them on zulip At this stage PRs merged should focus mainly on regressions and bug fixes. New features should wait until after release. ## The day of release + - make sure final rc has been tested - ensure all PRs have been added to release notes and then make release and announce on zulip @@ -69,7 +76,7 @@ release though we need to generate the release notes. ## Generating release notes 1. Make a list of merges, contributors, and reviewers by running - ``python docs/release/generate_release_notes.py -h`` and following that file's usage. + `python docs/release/generate_release_notes.py -h` and following that file's usage. For each release generate the list to include everything since the last release for which there are release notes (which should just be the last release). For example making the release notes for the `0.2.1` release can be done as follows: @@ -84,7 +91,7 @@ release though we need to generate the release notes. the affected functions, elaborating on the changes and their consequences. If possible, organize semantically close PRs in groups. -3. Make sure the file name is of the form ``doc/release/release___.md``. +3. Make sure the file name is of the form `doc/release/release___.md`. 4. Make and merge a PR with these release notes before moving onto the next steps. @@ -102,16 +109,16 @@ file `/tools/strings_list.py` to include strings to skip safely from translation The test checks: - 1. **Untranslated strings**: not using the `trans` methods. - 2. **Outdated skip strings**: should no longer be included in the `/tools/strings_list.py` file. - 3. **Translation usage errors**: where translation strings may be missing interpolation variables. +1. **Untranslated strings**: not using the `trans` methods. +2. **Outdated skip strings**: should no longer be included in the `/tools/strings_list.py` file. +3. **Translation usage errors**: where translation strings may be missing interpolation variables. You can execute tests locally from the repository root, and follow the instructions printed on the `stdout` if any test fails. - ```bash - pytest tools/ --tb=short - ``` +```bash +pytest tools/ --tb=short +``` ## Tagging the new release candidate @@ -203,11 +210,11 @@ when the metadata of an existing package is proven wrong after it has been relea To amend the metadata, we need to: -* Encode the patch instructions as a PR to +- Encode the patch instructions as a PR to [`conda-forge/conda-forge-repodata-patches-feedstock`](https://github.com/conda-forge/conda-forge-repodata-patches-feedstock): - Add the required changes to `recipe/gen_patch_json.py`, under the [`record_name == 'napari'` section](https://github.com/conda-forge/conda-forge-repodata-patches-feedstock/blob/6aa624be7fe4e3627daea095c8d92b7379b3bb66/recipe/gen_patch_json.py#L1562). - Use a [timestamp condition](https://github.com/conda-forge/conda-forge-repodata-patches-feedstock/blob/6aa624be7fe4e3627daea095c8d92b7379b3bb66/recipe/gen_patch_json.py#L1564) to ensure only existing releases are patched. -* If necessary, make sure the metadata is amended in the feedstock too. +- If necessary, make sure the metadata is amended in the feedstock too. Usually this is not needed until a new release is made, but it's important to remember! Some previous examples include: diff --git a/docs/developers/testing.md b/docs/developers/testing.md index c93ef2bcc..7c031f71c 100644 --- a/docs/developers/testing.md +++ b/docs/developers/testing.md @@ -1,4 +1,5 @@ (napari-testing)= + # Testing ## Overview @@ -7,13 +8,13 @@ We use unit tests, integration tests, and functional tests to ensure that `napari` works as intended. We have - Unit tests which test if individual modules or functions work correctly -in isolation. + in isolation. - Integration tests which test if different modules or functions work properly -when combined. + when combined. - Functional tests which test if slices of `napari` functionality work as -intended in the whole system. + intended in the whole system. To get the most return on investment (ROI) from our coding, we strive to test as much as we can with unit tests, requiring fewer integration tests, and the least number @@ -45,7 +46,7 @@ of tests. To run our test suite locally, run `pytest` on the command line. If, for some reason you don't already have the test requirements in your environment, run `python -m pip install -e .[testing]`. -There are a very small number of tests (<5) that require showing GUI elements, (such +There are a very small number of tests (\<5) that require showing GUI elements, (such as testing screenshots). By default, these are only run during continuous integration. If you'd like to include them in local tests, set the environment variable "CI": @@ -113,16 +114,16 @@ you create during testing are cleaned up at the end of each test: 1. If you need a `QApplication` to be running for your test, you can use the [`qtbot`](https://pytest-qt.readthedocs.io/en/latest/reference.html#pytestqt.qtbot.QtBot) fixture from `pytest-qt` - > note: fixtures in pytest can be a little mysterious, since it's not always - > clear where they are coming from. In this case, using a pytest-qt fixture - > looks like this: + > note: fixtures in pytest can be a little mysterious, since it's not always + > clear where they are coming from. In this case, using a pytest-qt fixture + > looks like this: - ```python - # just by putting `qtbot` in the list of arguments - # pytest-qt will start up an event loop for you - def test_something(qtbot): - ... - ``` + ```python + # just by putting `qtbot` in the list of arguments + # pytest-qt will start up an event loop for you + def test_something(qtbot): + ... + ``` `qtbot` provides a convenient [`addWidget`](https://pytest-qt.readthedocs.io/en/latest/reference.html#pytestqt.qtbot.QtBot.addWidget) @@ -131,13 +132,13 @@ you create during testing are cleaned up at the end of each test: convenient methods for interacting with your GUI tests (clicking, waiting signals, etc...). See the [`qtbot` docs](https://pytest-qt.readthedocs.io/en/latest/reference.html#pytestqt.qtbot.QtBot) for details. - ```python - # the qtbot provides convenience methods like addWidget - def test_something_else(qtbot): - widget = QWidget() - qtbot.addWidget(widget) # tell qtbot to clean this widget later - ... - ``` + ```python + # the qtbot provides convenience methods like addWidget + def test_something_else(qtbot): + widget = QWidget() + qtbot.addWidget(widget) # tell qtbot to clean this widget later + ... + ``` 2. When writing a test that requires a `napari.Viewer` object, we provide a [pytest fixture](https://docs.pytest.org/en/stable/explanation/fixtures.html) called @@ -147,15 +148,15 @@ you create during testing are cleaned up at the end of each test: using `qtbot.addWidget` or calling `viewer.close()`) at the end of the test. Duplicate cleanup may cause an error. Use the fixture as follows: - ```python - # the make_napari_viewer fixture is defined in napari/utils/_testsupport.py - def test_something_with_a_viewer(make_napari_viewer): - # make_napari_viewer takes any keyword arguments that napari.Viewer() takes - viewer = make_napari_viewer() + ```python + # the make_napari_viewer fixture is defined in napari/utils/_testsupport.py + def test_something_with_a_viewer(make_napari_viewer): + # make_napari_viewer takes any keyword arguments that napari.Viewer() takes + viewer = make_napari_viewer() - # do stuff with the viewer, no qtbot or viewer.close() methods needed. - ... - ``` + # do stuff with the viewer, no qtbot or viewer.close() methods needed. + ... + ``` > If you're curious to see the actual `make_napari_viewer` fixture definition, it's > in `napari/utils/_testsupport.py` diff --git a/docs/developers/translations.md b/docs/developers/translations.md index 18679017e..8aac99814 100644 --- a/docs/developers/translations.md +++ b/docs/developers/translations.md @@ -48,7 +48,6 @@ from napari.utils.translations import trans class SomeWidget(QWidget): - def __init__(self): self.channel_combo_box = QComboBox(self) self.channel_combo_box.addItem(trans._("red"), "red") @@ -65,15 +64,15 @@ For the `English (US)` translation the options displayed would be the same, since `gettext` uses the source language as the key to find translations. In this case: - * red - * green - * blue +- red +- green +- blue For the `Spanish (Spain)` translation the options displayed would be: - * rojo - * verde - * azul +- rojo +- verde +- azul `trans._` is a wrapper on top of [gettext.gettext](https://docs.python.org/3/library/gettext.html#gettext.gettext) with enhanced functionality. @@ -83,10 +82,11 @@ Strings that need some additional context to disambiguate the source string, can use the `trans._p` method: The word **Tab** can mean different things in the english language: - * A spacer, when the `Tab` key of a keyboard is used inside a text editor. - * A tablature, a simplified version of sheet music used for stringed - insruments. - * A user interface graphical element, like the one provide by `QTabWidget`. + +- A spacer, when the `Tab` key of a keyboard is used inside a text editor. +- A tablature, a simplified version of sheet music used for stringed + insruments. +- A user interface graphical element, like the one provide by `QTabWidget`. ```python from qtpy.QtWidgets import QComboBox, QWidget @@ -95,10 +95,15 @@ from napari.utils.translations import trans class SomeWidget(QWidget): - def __init__(self): self.context_combo_box = QComboBox(self) - self.context_combo_box.addItem(trans._p("character", "tab", ), "tab") + self.context_combo_box.addItem( + trans._p( + "character", + "tab", + ), + "tab", + ) self.context_combo_box.addItem(trans._p("music", "tab"), "tab") self.context_combo_box.addItem(trans._p("ui-element", "tab"), "tab") ``` @@ -111,15 +116,15 @@ For the `English (US)` translation the options displayed would be the same, since `gettext` uses the source language as the key to find translations. In this case: - * tab - * tab - * tab +- tab +- tab +- tab For the `Spanish (Spain)` translation the options displayted would be: - * tabulaciĂłn - * tablatura - * pestaña +- tabulaciĂłn +- tablatura +- pestaña `trans._p` is a wrapper on top of [gettext.pgettext](https://docs.python.org/3/library/gettext.html#gettext.pgettext) with enhanced functionality. @@ -136,7 +141,6 @@ from napari.utils.translations import trans class SomeWidget(QWidget): - def __init__(self, amount): string = trans._n("{n} item", "{n} items", n=amount) self.label = QLabel(string) @@ -151,14 +155,16 @@ interpolate the value of `n` in the string, if found. For the `English (US)` translation the string displayed for different values of `amount` would be: - * For `amount=0`, `"0 items"` - * For `amount=1`, `"1 item"` - * For `amount=2`, `"2 items"` + +- For `amount=0`, `"0 items"` +- For `amount=1`, `"1 item"` +- For `amount=2`, `"2 items"` For the `Spanish (Spain)` translation the options displayted would be: - * For `amount=0`, `"0 Ă­tems"` - * For `amount=1`, `"1 Ă­tem"` - * For `amount=2`, `"2 Ă­tems"` + +- For `amount=0`, `"0 Ă­tems"` +- For `amount=1`, `"1 Ă­tem"` +- For `amount=2`, `"2 Ă­tems"` Take into account that different languages will handle pluralization differently. Having clear variable names within strings (e.g. `{amount}`) of diff --git a/docs/further-resources/glossary.md b/docs/further-resources/glossary.md index 561615b6c..85c76330d 100644 --- a/docs/further-resources/glossary.md +++ b/docs/further-resources/glossary.md @@ -1,22 +1,22 @@ # Glossary -| napari term | description | synonym/closely related term | -|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| -| canvas | The canvas in the center of napari viewer contains the visual display of the data passed to napari, including images, points, shapes, and other supported data types. | | -| channel | A channel is an image where each pixel only contains its intensity value (i.e. grayscale image). In fluorescence microscopy, each channel corresponds to a range of wavelengths. An RGB image from digital camera consists of 3 channels - red, green, blue (RGB). In napari, an image dimension that is not spatial or temporal can be considered as channels, and is typically represented as individual [image layer](https://napari.org/stable/howtos/layers/image.html). See also [this wiki page](https://en.wikipedia.org/wiki/Channel_(digital_image)) for specific examples. | raster band in geoscience | -| colormap | A colormap represents the value of each pixel as a particular color. In fluorescence microscopy, the colormap can be selected to mirror the color of the collected wavelength. In other applications, colormaps can be used to provide the visual representation of the measured signal. There are many different types of colormaps available, see [choosing colormaps in matplotlib](https://matplotlib.org/stable/tutorials/colors/colormaps.html). | look up table, LUT | -| event | Events and connections are a way to tell napari "If this event occurs, then call this function". [Hooking up your own events](https://napari.org/stable/howtos/connecting_events.html). | | -| hook | Hooks are places in napari where plugins can offer extended functionality. See the [napari hook specification reference](https://napari.org/stable/plugins/npe1.html#hook-specification-reference) in the [napari plugin documentation](https://napari.org/stable/plugins/index.html). | | -| hook specification | A function signature that defines the application programming interface (API) that a plugin developer must adhere to when writing the function that napari promises to call somewhere in the napari codebase. See also [napari hook specifications](https://napari.org/stable/plugins/npe1.html#hook-specification-reference). | | -| keybinding | The specific key or key combination that when pressed causes some function to occur. For example, command/control + C triggers "copy" in many operating systems. You can make your own custom keybindings, see this page on [hooking up your own events](https://napari.org/stable/howtos/connecting_events.html). | keyboard short cut | -| mousebinding | The specific mouse move, click, or drag event that causes some function to occur. You can make your own custom mousebindings, see this page on [hooking up your own events](https://napari.org/stable/howtos/connecting_events.html). | mouse short cut | -| layer | Layers are the viewable objects that can be added to napari viewer. Currently napari supports the following layers: [image](https://napari.org/stable/howtos/layers/image.html), [labels](https://napari.org/stable/howtos/layers/labels.html), [points](https://napari.org/stable/howtos/layers/points.html), [shapes](https://napari.org/stable/howtos/layers/shapes.html), [surface](https://napari.org/stable/howtos/layers/surface.html), [tracks](https://napari.org/stable/howtos/layers/tracks.html), [vectors](https://napari.org/stable/howtos/layers/vectors.html). | | -| label | A label is a region of an integer image where the pixels are the same unique value. It is implemented in napari in the [labels layer](https://napari.org/stable/howtos/layers/labels.html) where each label represents a unique object, feature or classification. | | -| point | A point is defined by the coordinate in space. The [points layer](https://napari.org/stable/howtos/layers/points.html) allows you to display an NxD array of N points in D coordinates. | | -| shape | Shapes are visual representations of N points in D coordinates. Shapes can be used as a visualization tool to indicate region of interest (ROI) or for downstream operations. In napari, the [shapes layer](https://napari.org/stable/howtos/layers/shapes.html) is a viewable object that permits visualization of the following supported shapes: rectangle, ellipses, polygons, paths, and lines. | [area selection tools in ImageJ](https://imagej.nih.gov/ij/docs/tools.html) | -| surface | A surface comprises a set of triangles (typically in three dimensions) that are connected by their common edges or corners to form the mesh. See [napari surface layer tutorial](https://napari.org/stable/howtos/layers/surface.html) for current implementation and [this wiki page](https://en.wikipedia.org/wiki/Surface_triangulation) for additional information. | | -| track | A track describes object trajectories along the time dimension. The [tracks layer](https://napari.org/stable/howtos/layers/tracks.html) defines a track by the list containing object coordinates and the time point, with each track assigned a unique track ID. | | -| vector | A vector is a line defined by its start and end positions in space. The [vectors layer](https://napari.org/stable/howtos/layers/vectors.html) renders lines onto the canvas, with input data format as Nx2xD numpy array representing N vectors in D dimensions. | | -| widget | A small, composable graphical user interface component that can extend user's ability to interact with layers and other napari functions. A widget could be a simple one parameter/control (e.g. a button) or a collection of multiple controls with data output. | [dialog in ImageJ](https://imagej.net/scripting/generic-dialog) | -| dock widget | A widget that can be attached (docked) to the main napari viewer at a specified position. | | -| plugin | A napari plugin is a specialized Python package that extends napari's functionality. Examples of extended functionality include: additional file i/o support, image analysis, and other utilities. Plugin developers specify this functionality through a combination of Python entry points and a specialized manifest file. Plugins for napari can be found at [napari hub](https://www.napari-hub.org/). For a guide on how to make your own custom plugin, see [creating a napari plugin](https://napari.org/stable/plugins/index.html). | | +| napari term | description | synonym/closely related term | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | +| canvas | The canvas in the center of napari viewer contains the visual display of the data passed to napari, including images, points, shapes, and other supported data types. | | +| channel | A channel is an image where each pixel only contains its intensity value (i.e. grayscale image). In fluorescence microscopy, each channel corresponds to a range of wavelengths. An RGB image from digital camera consists of 3 channels - red, green, blue (RGB). In napari, an image dimension that is not spatial or temporal can be considered as channels, and is typically represented as individual [image layer](https://napari.org/stable/howtos/layers/image.html). See also [this wiki page]() for specific examples. | raster band in geoscience | +| colormap | A colormap represents the value of each pixel as a particular color. In fluorescence microscopy, the colormap can be selected to mirror the color of the collected wavelength. In other applications, colormaps can be used to provide the visual representation of the measured signal. There are many different types of colormaps available, see [choosing colormaps in matplotlib](https://matplotlib.org/stable/tutorials/colors/colormaps.html). | look up table, LUT | +| event | Events and connections are a way to tell napari "If this event occurs, then call this function". [Hooking up your own events](https://napari.org/stable/howtos/connecting_events.html). | | +| hook | Hooks are places in napari where plugins can offer extended functionality. See the [napari hook specification reference](https://napari.org/stable/plugins/npe1.html#hook-specification-reference) in the [napari plugin documentation](https://napari.org/stable/plugins/index.html). | | +| hook specification | A function signature that defines the application programming interface (API) that a plugin developer must adhere to when writing the function that napari promises to call somewhere in the napari codebase. See also [napari hook specifications](https://napari.org/stable/plugins/npe1.html#hook-specification-reference). | | +| keybinding | The specific key or key combination that when pressed causes some function to occur. For example, command/control + C triggers "copy" in many operating systems. You can make your own custom keybindings, see this page on [hooking up your own events](https://napari.org/stable/howtos/connecting_events.html). | keyboard short cut | +| mousebinding | The specific mouse move, click, or drag event that causes some function to occur. You can make your own custom mousebindings, see this page on [hooking up your own events](https://napari.org/stable/howtos/connecting_events.html). | mouse short cut | +| layer | Layers are the viewable objects that can be added to napari viewer. Currently napari supports the following layers: [image](https://napari.org/stable/howtos/layers/image.html), [labels](https://napari.org/stable/howtos/layers/labels.html), [points](https://napari.org/stable/howtos/layers/points.html), [shapes](https://napari.org/stable/howtos/layers/shapes.html), [surface](https://napari.org/stable/howtos/layers/surface.html), [tracks](https://napari.org/stable/howtos/layers/tracks.html), [vectors](https://napari.org/stable/howtos/layers/vectors.html). | | +| label | A label is a region of an integer image where the pixels are the same unique value. It is implemented in napari in the [labels layer](https://napari.org/stable/howtos/layers/labels.html) where each label represents a unique object, feature or classification. | | +| point | A point is defined by the coordinate in space. The [points layer](https://napari.org/stable/howtos/layers/points.html) allows you to display an NxD array of N points in D coordinates. | | +| shape | Shapes are visual representations of N points in D coordinates. Shapes can be used as a visualization tool to indicate region of interest (ROI) or for downstream operations. In napari, the [shapes layer](https://napari.org/stable/howtos/layers/shapes.html) is a viewable object that permits visualization of the following supported shapes: rectangle, ellipses, polygons, paths, and lines. | [area selection tools in ImageJ](https://imagej.nih.gov/ij/docs/tools.html) | +| surface | A surface comprises a set of triangles (typically in three dimensions) that are connected by their common edges or corners to form the mesh. See [napari surface layer tutorial](https://napari.org/stable/howtos/layers/surface.html) for current implementation and [this wiki page](https://en.wikipedia.org/wiki/Surface_triangulation) for additional information. | | +| track | A track describes object trajectories along the time dimension. The [tracks layer](https://napari.org/stable/howtos/layers/tracks.html) defines a track by the list containing object coordinates and the time point, with each track assigned a unique track ID. | | +| vector | A vector is a line defined by its start and end positions in space. The [vectors layer](https://napari.org/stable/howtos/layers/vectors.html) renders lines onto the canvas, with input data format as Nx2xD numpy array representing N vectors in D dimensions. | | +| widget | A small, composable graphical user interface component that can extend user's ability to interact with layers and other napari functions. A widget could be a simple one parameter/control (e.g. a button) or a collection of multiple controls with data output. | [dialog in ImageJ](https://imagej.net/scripting/generic-dialog) | +| dock widget | A widget that can be attached (docked) to the main napari viewer at a specified position. | | +| plugin | A napari plugin is a specialized Python package that extends napari's functionality. Examples of extended functionality include: additional file i/o support, image analysis, and other utilities. Plugin developers specify this functionality through a combination of Python entry points and a specialized manifest file. Plugins for napari can be found at [napari hub](https://www.napari-hub.org/). For a guide on how to make your own custom plugin, see [creating a napari plugin](https://napari.org/stable/plugins/index.html). | | diff --git a/docs/further-resources/napari-workshops.md b/docs/further-resources/napari-workshops.md index 70575f228..8245aefce 100644 --- a/docs/further-resources/napari-workshops.md +++ b/docs/further-resources/napari-workshops.md @@ -1,4 +1,5 @@ (napari-workshops)= + # napari workshops There have been many workshops and tutorials given about napari. @@ -21,45 +22,55 @@ or contact the core developers on [zulip chat](https://napari.zulipchat.com/logi ## Workshops *Workshops are listed from newest to oldest.* -* November 2022, napari foundation grant onboarding - * [Getting started with napari plugin development slide deck](https://docs.google.com/presentation/d/15lrFRLPm9bfmU4hgcVwoduIJr5bqhoHo7ZeWLO6H_Us/edit?usp=sharing) - * [Watch it here](https://drive.google.com/file/d/1IYDV-GTGEYh5j_tvBaWYEZ_tQXTqmJkr/view?usp=share_link) +- November 2022, napari foundation grant onboarding + + - [Getting started with napari plugin development slide deck](https://docs.google.com/presentation/d/15lrFRLPm9bfmU4hgcVwoduIJr5bqhoHo7ZeWLO6H_Us/edit?usp=sharing) + - [Watch it here](https://drive.google.com/file/d/1IYDV-GTGEYh5j_tvBaWYEZ_tQXTqmJkr/view?usp=share_link) + +- September 2022, Helmholtz Imaging Summer Academy + + - [Image analysis with Python and Napari](https://biapol.github.io/HIP_Introduction_to_Napari_and_image_processing_with_Python_2022/intro.html) + - [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7102242.svg)](https://doi.org/10.5281/zenodo.7102242) + +- July 2022, SciPy 2022 + + - [watch it here](https://www.youtube.com/watch?v=vismuuc4y1I&list=PLYx7XA2nY5Gfxu98P_HL1MnFb_BSkpxLV&index=10) + - [Workshop materials available here](https://alisterburt.github.io/napari-workshops/SciPy-0722/intro.html) + +- January to May 2022, [accelerator grantee plugin workshop](https://chanzuckerberg.github.io/napari-plugin-accel-workshops/) + +- May 2022, I2K napari workshop + + - [Read the workshop materials online](https://github.com/haesleinhuepf/I2K2022-napari-workshop) + +- April 2022, ABRF LMRG image analysis workshop + + - [watch it here](https://www.youtube.com/watch?v=lkw5di8NgUA) -* September 2022, Helmholtz Imaging Summer Academy - * [Image analysis with Python and Napari](https://biapol.github.io/HIP_Introduction_to_Napari_and_image_processing_with_Python_2022/intro.html) - * [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.7102242.svg)](https://doi.org/10.5281/zenodo.7102242) +- March 2022, [Alt text workshop](https://hackmd.io/vDsWUBqQSxW_7TNxF-RKKA) -* July 2022, SciPy 2022 - * [watch it here](https://www.youtube.com/watch?v=vismuuc4y1I&list=PLYx7XA2nY5Gfxu98P_HL1MnFb_BSkpxLV&index=10) - * [Workshop materials available here](https://alisterburt.github.io/napari-workshops/SciPy-0722/intro.html) +- February 2022, Modern microscopy guest lecture at Berkerley -* January to May 2022, [accelerator grantee plugin workshop](https://chanzuckerberg.github.io/napari-plugin-accel-workshops/) + - [Watch it here](https://drive.google.com/file/d/1EjJJZ3sy4mogcGwTao97sHzU9SDJeC3r/view?usp=sharing) + - [Slide available here](https://docs.google.com/presentation/d/1sqd_CYckEwmgQO6y9VffwnmT0RSlRYIAUBixRTMYcig/view) -* May 2022, I2K napari workshop - * [Read the workshop materials online](https://github.com/haesleinhuepf/I2K2022-napari-workshop) +- August 2021, [NDCN](https://chanzuckerberg.com/science/programs-resources/neurodegeneration-challenge/) Workshop / September 2021 [ZIDAS](https://www.zidas.org/) Workshop -* April 2022, ABRF LMRG image analysis workshop - * [watch it here](https://www.youtube.com/watch?v=lkw5di8NgUA) + - [Read the workshop materials online](https://alisterburt.github.io/napari-workshops/home.html) + - [Workshop materials available here](https://github.com/alisterburt/napari-workshops) -* March 2022, [Alt text workshop](https://hackmd.io/vDsWUBqQSxW_7TNxF-RKKA) +- June 2021, Bioimage analysis fundamentals in Python workshop at [SciPy 2021](https://www.scipy2021.scipy.org/) -* February 2022, Modern microscopy guest lecture at Berkerley - * [Watch it here](https://drive.google.com/file/d/1EjJJZ3sy4mogcGwTao97sHzU9SDJeC3r/view?usp=sharing) - * [Slide available here](https://docs.google.com/presentation/d/1sqd_CYckEwmgQO6y9VffwnmT0RSlRYIAUBixRTMYcig/view) + - [Watch it here](https://www.youtube.com/watch?v=kXdy_Tp12zA) (2 hour and 20 minute video) + - [Workshop materials available here](https://github.com/sofroniewn/tutorial-scipy2021-bioimage-analysis-fundamentals) -* August 2021, [NDCN](https://chanzuckerberg.com/science/programs-resources/neurodegeneration-challenge/) Workshop / September 2021 [ZIDAS](https://www.zidas.org/) Workshop - * [Read the workshop materials online](https://alisterburt.github.io/napari-workshops/home.html) - * [Workshop materials available here](https://github.com/alisterburt/napari-workshops) +- November 2020, scikit-image, napari, & friends workshop at the [I2K conference](https://www.janelia.org/you-janelia/conferences/from-images-to-knowledge-with-imagej-friends) -* June 2021, Bioimage analysis fundamentals in Python workshop at [SciPy 2021](https://www.scipy2021.scipy.org/) - * [Watch it here](https://www.youtube.com/watch?v=kXdy_Tp12zA) (2 hour and 20 minute video) - * [Workshop materials available here](https://github.com/sofroniewn/tutorial-scipy2021-bioimage-analysis-fundamentals) + - [Watch it here](https://www.youtube.com/watch?v=NZWSGXb3_Mg) + - [Workshop materials available here](https://github.com/jni/i2k-skimage-napari) -* November 2020, scikit-image, napari, & friends workshop at the [I2K conference](https://www.janelia.org/you-janelia/conferences/from-images-to-knowledge-with-imagej-friends) - * [Watch it here](https://www.youtube.com/watch?v=NZWSGXb3_Mg) - * [Workshop materials available here](https://github.com/jni/i2k-skimage-napari) +- June 2020, [NEUBIAS Academy@Home workshop](http://eubias.org/NEUBIAS/training-schools/neubias-academy-home/neubias-academy-archive-spring2020/) -* June 2020, [NEUBIAS Academy@Home workshop](http://eubias.org/NEUBIAS/training-schools/neubias-academy-home/neubias-academy-archive-spring2020/) - * [Watch it here](https://www.youtube.com/watch?v=VgvDSq5aCDQ) (1 hour and 30 minute video) - * [Workshop materials available here](https://github.com/sofroniewn/napari-training-course) + - [Watch it here](https://www.youtube.com/watch?v=VgvDSq5aCDQ) (1 hour and 30 minute video) + - [Workshop materials available here](https://github.com/sofroniewn/napari-training-course) diff --git a/docs/further-resources/sample_data.md b/docs/further-resources/sample_data.md index 40c0712a5..8cba493b2 100644 --- a/docs/further-resources/sample_data.md +++ b/docs/further-resources/sample_data.md @@ -4,29 +4,27 @@ Public sample image databases across various imaging modalities. If you come across image databases that are missing from the list below, create an issue or submit a pull request on [napari/docs](https://github.com/napari/docs) to help us expand it. - - -| **Database** | **Description** | -|------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [Allen Brain Atlases](http://portal.brain-map.org) | Human and mouse brain atlas containing gene expression, anatomic, and genomic data | -| [Allen Cell Explorer](https://www.allencell.org) | Database of cell data, segmentations, and analyses with multiple experimental parameters. | +| **Database** | **Description** | +| ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Allen Brain Atlases](http://portal.brain-map.org) | Human and mouse brain atlas containing gene expression, anatomic, and genomic data | +| [Allen Cell Explorer](https://www.allencell.org) | Database of cell data, segmentations, and analyses with multiple experimental parameters. | | [Berkeley Drosophila Genome Project (BDGP) in situ database](http://insitu.fruitfly.org/cgi-bin/ex/insitu.pl) | Patterns of gene expression in Drosophila embryogenesis. | -| [The Cell Image Library](http://www.cellimagelibrary.org/home) | Public resource database of images, videos, and animations of cells, capturing a wide diversity of organisms, cell types, and cellular processes. | -| [FlyLight Imagery](https://open.quiltdata.com/b/janelia-flylight-imagery) | 3D fly's central nervous system captured by confocal laser scanning microscope. | -| [OpenCell](https://opencell.czbiohub.org/download) | A proteome-scale collection of protein localization and interaction measurements in human cells. Contains fluorescence microscopy and IP-MS data. | -| [Whole Slide Imaging Repository](https://digitalpathologyassociation.org/whole-slide-imaging-repository) | Collections of whole slide imaging repositories. | -| [BioImage Archive](https://www.ebi.ac.uk/biostudies/bioimages/studies) | Description and data of biological studies; links to databases at EMBL-EBI (European Molecular Biology Laboratory–European Bioinformatics Institute). | -| [Image Data Resource (IDR)](https://idr.openmicroscopy.org) | Public data repository for storing and integrating image data sets from published scientific studies. | -| [Systems Science of Biological Dynamics (SSBD)](https://ssbd.riken.jp/database/) | Set of quantitative data and microscopy images from a variety of species, sources, and methods. | -| [Cancer Imaging Archive](https://www.cancerimagingarchive.net/collections/) | Collections of cancer medical images. | -| [Brain Image Library](https://www.brainimagelibrary.org/) | A public resource to deposit, analyze, mine, share, and interact with large brain image datasets. | -| [EMPIAR](https://www.ebi.ac.uk/empiar/) | The Electron Microscopy Public Image Archive (EMPIAR) is a public resource for raw images underpinning 3D cryo-EM maps and tomograms. | -| [OME-NGFF files on IDR](https://idr.github.io/ome-ngff-samples/) | Catalog of IDR images formatted as OME-NGFF. | -| [Cell Tracking Challenge](http://celltrackingchallenge.net/datasets) | Repository of annotated videos from the Cell Tracking Challenge; contains contrast-enhancing (PhC, DIC) or fluorescence (widefield, confocal, multiphoton, and light sheet microsocopy) data. | -| [Bio-Formats supported formats](https://docs.openmicroscopy.org/bio-formats/6.9.1/supported-formats.html) | Example files for Bio-Formats supported formats. | -| [BioImage Informatics Index (BIII) datasets](http://biii.eu/dataset) | Mix of datasets and links to other data repositories. | -| [SCIFIO](https://scif.io/images/) | Example images with CC0 licenses. | -| [EPFL Super-resolution microscopy datasets](https://srm.epfl.ch/Datasets) | Super-resolution microscopy datasets. | -| [Brain Observatory Storage Service & Database](https://bossdb.org/) | BossDB is a volumetric database for 3D and 4D neuroscience data, with API access. | -| [Mouse Brain Atlas](http://mousebrain.org/) | Spatiotranscriptomics data of mouse brain. | -| [Broad Bioimage Benchmark Collection](https://bbbc.broadinstitute.org/image_sets) | Benchmark images with ground truth segmentation. | \ No newline at end of file +| [The Cell Image Library](http://www.cellimagelibrary.org/home) | Public resource database of images, videos, and animations of cells, capturing a wide diversity of organisms, cell types, and cellular processes. | +| [FlyLight Imagery](https://open.quiltdata.com/b/janelia-flylight-imagery) | 3D fly's central nervous system captured by confocal laser scanning microscope. | +| [OpenCell](https://opencell.czbiohub.org/download) | A proteome-scale collection of protein localization and interaction measurements in human cells. Contains fluorescence microscopy and IP-MS data. | +| [Whole Slide Imaging Repository](https://digitalpathologyassociation.org/whole-slide-imaging-repository) | Collections of whole slide imaging repositories. | +| [BioImage Archive](https://www.ebi.ac.uk/biostudies/bioimages/studies) | Description and data of biological studies; links to databases at EMBL-EBI (European Molecular Biology Laboratory–European Bioinformatics Institute). | +| [Image Data Resource (IDR)](https://idr.openmicroscopy.org) | Public data repository for storing and integrating image data sets from published scientific studies. | +| [Systems Science of Biological Dynamics (SSBD)](https://ssbd.riken.jp/database/) | Set of quantitative data and microscopy images from a variety of species, sources, and methods. | +| [Cancer Imaging Archive](https://www.cancerimagingarchive.net/collections/) | Collections of cancer medical images. | +| [Brain Image Library](https://www.brainimagelibrary.org/) | A public resource to deposit, analyze, mine, share, and interact with large brain image datasets. | +| [EMPIAR](https://www.ebi.ac.uk/empiar/) | The Electron Microscopy Public Image Archive (EMPIAR) is a public resource for raw images underpinning 3D cryo-EM maps and tomograms. | +| [OME-NGFF files on IDR](https://idr.github.io/ome-ngff-samples/) | Catalog of IDR images formatted as OME-NGFF. | +| [Cell Tracking Challenge](http://celltrackingchallenge.net/datasets) | Repository of annotated videos from the Cell Tracking Challenge; contains contrast-enhancing (PhC, DIC) or fluorescence (widefield, confocal, multiphoton, and light sheet microsocopy) data. | +| [Bio-Formats supported formats](https://docs.openmicroscopy.org/bio-formats/6.9.1/supported-formats.html) | Example files for Bio-Formats supported formats. | +| [BioImage Informatics Index (BIII) datasets](http://biii.eu/dataset) | Mix of datasets and links to other data repositories. | +| [SCIFIO](https://scif.io/images/) | Example images with CC0 licenses. | +| [EPFL Super-resolution microscopy datasets](https://srm.epfl.ch/Datasets) | Super-resolution microscopy datasets. | +| [Brain Observatory Storage Service & Database](https://bossdb.org/) | BossDB is a volumetric database for 3D and 4D neuroscience data, with API access. | +| [Mouse Brain Atlas](http://mousebrain.org/) | Spatiotranscriptomics data of mouse brain. | +| [Broad Bioimage Benchmark Collection](https://bbbc.broadinstitute.org/image_sets) | Benchmark images with ground truth segmentation. | diff --git a/docs/gallery.md b/docs/gallery.md index e3d857c9f..27e843e41 100644 --- a/docs/gallery.md +++ b/docs/gallery.md @@ -1,4 +1,5 @@ (gallery)= + # Gallery Examples of napari usage. diff --git a/docs/guides/3D_interactivity.md b/docs/guides/3D_interactivity.md index 509691cbd..d837c9919 100644 --- a/docs/guides/3D_interactivity.md +++ b/docs/guides/3D_interactivity.md @@ -3,11 +3,13 @@ # 3D interactivity ## Coordinate systems in napari + In napari, there are three main coordinate systems: (1) canvas, (2) world, and (3) layer. The canvas coordinates system is the 2D coordinate system of the canvas on which the scene is rendered. World coordinates are the nD coordinates of the entire scene. As the name suggests, layer coordinates are the nD coordinate system of the data in a given layer. Layer coordinates are specific to each layer's data and are related to the world coordinate system via the layer transforms. ![A diagram of the coordinate systems and components involved when interacting with layers in napari. The camera faces the 3D scene to generate a 2D image that is presented on the canvas.](images/3d_interaction_coordianates.png) ## In 3D mode, clicks are lines + Since the 3D scene is rendered on a 2D surface (your screen), your mouse click does not map to a specific point in space. As the view is a [parallel projection](https://en.wikipedia.org/wiki/Parallel_projection), napari can determine a line through 3D space that intersects the canvas where the user clicked. ![A diagram that shows how clicking on a 2D position on the canvas corresponds to a 3D click line in the scene that starts from the 3D camera position.](images/3d_interaction_click_line.png) @@ -17,12 +19,13 @@ When a user clicks or moves the mouse in the canvas, napari emits a mouse event - **pos**: the position of the click in canvas coordinates. - **position**: the position of the click in world coordinates. The point is located at the intersection of the click line (`view_direction`) and a plane parallel to the camera plane (i.e,. a plane normal to `view_direction`). - **view_direction**: a unit vector giving the direction of the camera in - world coordinates. + world coordinates. - **dims_displayed**: a list of the dimensions currently being displayed - in the viewer. This comes from `viewer.dims.displayed`. + in the viewer. This comes from `viewer.dims.displayed`. - **dims_point**: the indices for the data in view in world coordinates. This comes from `viewer.dims.point`. ## Determining where the click intersects the data + Each napari layer has a method called `get_ray_intersections()` that will return the points on the data bounding box that a given line will intersect (`start_point ` and `end_point `). When the click line (`view_direction`) and position (`position`) are used as inputs, `start_point` and `end_point` are the end points of the segment click line that intersects the layer's axis-alinged data bounding box. `start_point` is the end point that is closest to the camera (i.e, the "first" intersection) and `end_point` is the end point that is farthest from the camera (i.e., the "last" intersection). You can use the line segment between `start_point` and `end_point` to interrogate the layer data that is "under" your cursor. ![A diagram that shows how the 3D click line starting from the camera position intersects with the 3D bounding box associated with a layer's data. There are two intersection points. A start point, which is the first intersection point, and the end point which is the second.](images/3d_interaction_ray_intersection.png) @@ -72,6 +75,7 @@ def get_ray_intersections( ``` ## Adding 3D interactivity via mouse events + Custom 3D interactivity can be added via mouse callbacks. The `layer.get_ray_intersections()` function has been designed to work seamlessly with the napari mouse callback event. You can pass the mouse callback event properties to `layer.get_ray_intersections()` get the `start_point` and `end_point` of where the click line intersects the layer data. ``` @@ -91,6 +95,7 @@ def on_click(layer, event): For an example implementation, see [`cursor_ray.py`](https://github.com/napari/napari/blob/main/examples/cursor_ray.py). ## Getting the layer data under the cursor + There are convenience methods in the layer objects (`layer.get_value()`) to get the layer data value underneath the cursor that is "on top" (i.e., closest to `start_point`). Like `layer.get_ray_intersections()`, `layer.get_value()` takes the click position, view direction, dims_displayed in either world or layer coordinates (see `world` argument) as input. Thus, it can be easily integrated into a mouse event callback. Note that `layer.get_value()` returns `None` if the layer is not currently visible. See the docstring below for details. ``` diff --git a/docs/guides/app_model.md b/docs/guides/app_model.md index 2df2d7326..74364c794 100644 --- a/docs/guides/app_model.md +++ b/docs/guides/app_model.md @@ -130,7 +130,6 @@ type. It composes an and [`app_model.types.KeyBindingRule`](https://app-model.readthedocs.io/en/latest/types/#app_model.types.KeyBindingRule). - The following code would register a new "Split RGB" command, to be added to a specific section of the layerlist context menu, with a `Cmd+Alt+T` keybinding. @@ -146,7 +145,7 @@ from napari._app_model import get_app # `layers` will be injected layer when this action is invoked -def split_rgb_layer(layers: 'LayerList'): +def split_rgb_layer(layers: "LayerList"): ... @@ -154,14 +153,14 @@ action = Action( id=CommandId.LAYER_SPLIT_RGB, title=CommandId.LAYER_SPLIT_RGB.title, callback=split_rgb_layer, - menus = [ + menus=[ { - 'id': MenuId.LAYERLIST_CONTEXT, - 'group': MenuGroup.LAYERLIST_CONTEXT.SPLIT_MERGE, - 'when': LLCK.active_layer_is_rgb, + "id": MenuId.LAYERLIST_CONTEXT, + "group": MenuGroup.LAYERLIST_CONTEXT.SPLIT_MERGE, + "when": LLCK.active_layer_is_rgb, } ], - keybindings=[{'primary': KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyT }] + keybindings=[{"primary": KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyT}], ) get_app().register_action(action) @@ -198,7 +197,7 @@ A user/plugin provides a function ```python # some user provided function declares a need # for Points by using type annotations. -def process_points(points: 'Points'): +def process_points(points: "Points"): # do something with points print(points.name) ``` @@ -209,17 +208,16 @@ the `get_app().injection_store` ```python from napari._app_model import get_app + # return annotation indicates what this provider provides -def provide_points() -> Optional['Points']: +def provide_points() -> Optional["Points"]: import napari.viewer from napari.layers import Points viewer = napari.viewer.current_viewer() if viewer is not None: - return next( - (i for i in viewer.layers if isinstance(i, Points)), - None - ) + return next((i for i in viewer.layers if isinstance(i, Points)), None) + get_app().injection_store.register_provider(provide_points) ``` @@ -268,6 +266,6 @@ plugins/users may use to request dependencies. While it's certainly possible that there will be cases where this abstraction proves to be a bit more annoying than the previous "procedural" approach, there are a number of motivations for adopting this abstraction. 1. It gives us an abstraction layer on top of Qt that will make it much easier to explore different application backends (such as a web-based app, etc..) -1. It's easier to test: `app-model` can take care of making sure that commands, menus, keybindings, and actions are rendered, updated, and triggered correctly, and napari can focus on testing the napari-specific logic. -1. It's becomes **much** easier to add & remove contributions from plugins if our internal representation of a command, menu, keybinding is similar to the schema that plugins use. The previous procedural approach made this marriage much more cumbersome. -1. **The Dream**: The unification of napari commands and plugin commands into a registry that can execute commands in response to user input provides an excellent base for "recording" a user workflow. If all GUI user interactions go through dependency-injected commands, then it becomes much easier to export a script that reproduces a set of interactions. +2. It's easier to test: `app-model` can take care of making sure that commands, menus, keybindings, and actions are rendered, updated, and triggered correctly, and napari can focus on testing the napari-specific logic. +3. It's becomes **much** easier to add & remove contributions from plugins if our internal representation of a command, menu, keybinding is similar to the schema that plugins use. The previous procedural approach made this marriage much more cumbersome. +4. **The Dream**: The unification of napari commands and plugin commands into a registry that can execute commands in response to user input provides an excellent base for "recording" a user workflow. If all GUI user interactions go through dependency-injected commands, then it becomes much easier to export a script that reproduces a set of interactions. diff --git a/docs/guides/contexts_expressions.md b/docs/guides/contexts_expressions.md index ecf4ccd8a..0709b0898 100644 --- a/docs/guides/contexts_expressions.md +++ b/docs/guides/contexts_expressions.md @@ -34,8 +34,7 @@ with a concrete set of keys and values (the `Context`). In Python, **expressions** are simple combinations of **values** and **operations** that can be reduced to a single value. For example, `1 > 5` is an -expression that always reduces to the value `False` when evaluated. `x > 5 and -y == 'hello'` is also an expression that reduces to a boolean value; however, in +expression that always reduces to the value `False` when evaluated. `x > 5 and y == 'hello'` is also an expression that reduces to a boolean value; however, in order to evaluate that expression, we need to be able to fill in the values for the variable **names** "`x`" and "`y`". Those values are provided by some **context** (or "namespace"), which maps the variable names to their values. @@ -43,16 +42,16 @@ the variable **names** "`x`" and "`y`". Those values are provided by some The value of an `expression` depends on the context in which it is evaluated. ```python -In [1]: expression = "x > 5 and y == 'hello'" +In[1]: expression = "x > 5 and y == 'hello'" -In [2]: context_a = {'x': 7, 'y': 'hello'} +In[2]: context_a = {"x": 7, "y": "hello"} -In [3]: context_b = {'x': 8, 'y': 'howdie!'} +In[3]: context_b = {"x": 8, "y": "howdie!"} -In [4]: eval(expression, context_a) +In[4]: eval(expression, context_a) Out[4]: True -In [5]: eval(expression, context_b) +In[5]: eval(expression, context_b) Out[5]: False ``` @@ -104,7 +103,7 @@ The expression object can be evaluated by passing a context (a Mapping) to its `eval` method: ```python -In [9]: expr.eval({'x': 7, 'y': 'hello'}) +In[9]: expr.eval({"x": 7, "y": "hello"}) Out[9]: True ``` @@ -120,15 +119,16 @@ Out[11]: False The following operators are supported: ``` + | Operator | Symbol | Example | -|--------------|---------|-----------------------------------------------------| +| ------------ | ------- | --------------------------------------------------- | | Equality | == | "active_layer_type == image" | | Inequality | != | "active_layer_type != labels" | | Or | \| | "active_layer_is_rgb \| all_layers_same_shape" | | And | & | "active_layer_is_rgb & all_layers_same_shape" | | Not | ~ | ~active_layer_is_rgb | | Greater than | > >= | "unselected_linked_layers >= 1" | -| Less than | < <= | "layers_selection_count < 2" | +| Less than | \< \<= | "layers_selection_count \< 2" | | Math | + - * / | "layers_selection_count + unselected_linked_layers" | ### napari context keys @@ -148,16 +148,15 @@ command: Some example context key names (currently) include: -| Name | Description | -| -------- | -------- | -| `layers_selection_count` | Number of layers currently selected | -| `all_layers_linked` | True when all selected layers are linked | -| `active_layer_is_rgb` | True when the active layer is RGB | -| `active_layer_type` | Lowercase name of active layer type, or None if no layer is active. | -| `only_images_selected` | True when there is at least one selected layer and all selected layers are images | -| `active_layer_ndim` | Number of dimensions in the active layer, or `None` if nothing is active | -... many more - +| Name | Description | +| ------------------------ | --------------------------------------------------------------------------------- | +| `layers_selection_count` | Number of layers currently selected | +| `all_layers_linked` | True when all selected layers are linked | +| `active_layer_is_rgb` | True when the active layer is RGB | +| `active_layer_type` | Lowercase name of active layer type, or None if no layer is active. | +| `only_images_selected` | True when there is at least one selected layer and all selected layers are images | +| `active_layer_ndim` | Number of dimensions in the active layer, or `None` if nothing is active | +| ... many more | | ### `ContextKey` objects @@ -176,6 +175,7 @@ declared as class attributes on a `ContextNamespace` class: ```python # all of the getters here receive an instance of viewer.layers.selection + class LayerListContextKeys(ContextNamespace): layers_selection_count = ContextKey( default_value=0, @@ -211,9 +211,9 @@ mappingproxy({ A nice aspect of `ContextKeys` is that they can be used in expressions: ```python -In [14]: expr = LayerListContextKeys.active_layer_ndim >= 3 +In[14]: expr = LayerListContextKeys.active_layer_ndim >= 3 -In [15]: expr.eval({'active_layer_ndim': 2}) +In[15]: expr.eval({"active_layer_ndim": 2}) Out[15]: False ``` @@ -277,21 +277,22 @@ Out[3]: Context( SettingsAwareContext({}) ) ``` + The "root" context is a special `SettingsAwareContext` that can access keys in the global `settings`. Because contexts are `ChainMaps`, they can all access the settings: ```python -In [4]: ctx['settings.appearance.theme'] -Out[4]: 'dark' +In[4]: ctx["settings.appearance.theme"] +Out[4]: "dark" ``` When we evaluate an expression, we can provide it one of these context objects: ```python -In [5]: expr = LayerListContextKeys.layers_selection_count > 0 +In[5]: expr = LayerListContextKeys.layers_selection_count > 0 -In [6]: expr.eval(ctx) +In[6]: expr.eval(ctx) Out[6]: False ``` @@ -304,19 +305,19 @@ the keys in their contexts. The aforementioned instance. ```python -In [6]: ctx = get_context(viewer.layers) +In[6]: ctx = get_context(viewer.layers) -In [7]: llck = LayerListContextKeys(ctx) +In[7]: llck = LayerListContextKeys(ctx) ``` Attributes of an instantiated `ContextNamespace` now act as getters (and setters!) of their respective `ContextKey` in the associated `Context`. ```python -In [8]: llck.layers_selection_count +In[8]: llck.layers_selection_count Out[8]: 0 -In [9]: ctx['layers_selection_count'] +In[9]: ctx["layers_selection_count"] Out[9]: 0 ``` @@ -345,19 +346,19 @@ Out[12]: 1 1. napari creates special context "names" using `ContextKey` and `ContextNamespace` - ```python - class LayerListContextKeys(ContextNamespace): - active_layer_type = ContextKey( - None, - "Lowercase name of active layer type, or None of none active.", - lambda s: s.active and s.active._type_string - ) - active_layer_is_rgb = ContextKey( - False, - "True when the active layer is RGB", - lambda s: getattr(s.active, "rgb", False) - ) - ``` + ```python + class LayerListContextKeys(ContextNamespace): + active_layer_type = ContextKey( + None, + "Lowercase name of active layer type, or None of none active.", + lambda s: s.active and s.active._type_string, + ) + active_layer_is_rgb = ContextKey( + False, + "True when the active layer is RGB", + lambda s: getattr(s.active, "rgb", False), + ) + ``` 2. _Internally_ (in napari code), we can use those objects directly to declare expressions in an IDE-friendly way. For example, here we are declaratively @@ -365,29 +366,29 @@ Out[12]: 1 the current stack into multiple layers, but it is only enabled when the selected image is a (non-RGB) `Image` layer. - ```python - 'napari:split_stack': { - 'description': trans._('Split Stack'), - 'action': _split_stack, - 'enable_when': LLCK.active_layer_type == "image", - 'show_when': ~LLCK.active_layer_is_rgb, - } - ``` + ```python + "napari:split_stack": { + "description": trans._("Split Stack"), + "action": _split_stack, + "enable_when": LLCK.active_layer_type == "image", + "show_when": ~LLCK.active_layer_is_rgb, + } + ``` 3. _Externally_ (in plugin manifests), plugin developers use the string form to express conditions. For example, this plugin manifest offers up a command (just a callable) that is only enabled when the the active layer is an RGB image. - ```yaml - name: my_plugin - commands: - id: my_plugin.some_command - when: active_layer_is_rgb - ``` + ```yaml + name: my_plugin + commands: + id: my_plugin.some_command + when: active_layer_is_rgb + ``` - When this manifest is parsed, those expressions will be converted into - napari `Expr` objects internally. + When this manifest is parsed, those expressions will be converted into + napari `Expr` objects internally. 4. During runtime, napari maintains and [updates contexts](#updating-contexts) @@ -403,10 +404,10 @@ Out[12]: 1 updates all of the "action" items in the menu according to their `when` clauses (declared internally or externally in steps 3 and 4) - ```python - # pseudocode - def update_from_context(self, context): - for item in self.actions(): - expression = item.when # or however you get the expression - item.setEnabled(expression.eval(ctx)) - ``` + ```python + # pseudocode + def update_from_context(self, context): + for item in self.actions(): + expression = item.when # or however you get the expression + item.setEnabled(expression.eval(ctx)) + ``` diff --git a/docs/guides/event_loop.md b/docs/guides/event_loop.md index 481fd95b9..1dafb3465 100644 --- a/docs/guides/event_loop.md +++ b/docs/guides/event_loop.md @@ -56,7 +56,7 @@ napari.run() # Anything below here will execute only after the viewer is closed. ``` ------------ +______________________________________________________________________ ## More in depth... @@ -75,7 +75,7 @@ event_queue = Queue() while True: # infinite loop! if not event_queue.is_empty(): event = get_next_event() - if event.value == 'Quit': + if event.value == "Quit": break else: process_event(event) @@ -94,7 +94,7 @@ A deep dive into the Qt event loop is beyond the scope of this document, but it's worth being aware of two critical steps in the "lifetime" of a Qt Application: -1) Any program that would like to create a +1. Any program that would like to create a [`QWidget`](https://doc.qt.io/qt-5/qwidget.html) (the class from which all napari's graphical elements are subclassed), must create a [`QApplication`](https://doc.qt.io/qt-5/qapplication.html) instance *before* @@ -106,7 +106,7 @@ Application: app = QApplication([]) # where [] is a list of args passed to the App ``` -2) In order to actually show and interact with widgets, one must start the +2. In order to actually show and interact with widgets, one must start the application's event loop: ```python diff --git a/docs/guides/events_reference.md b/docs/guides/events_reference.md index 906d2a4b2..758bd4d90 100644 --- a/docs/guides/events_reference.md +++ b/docs/guides/events_reference.md @@ -1,4 +1,5 @@ (events-reference)= + # Events reference The following tables contain events that you may connect to. For example, to have a specific function called whenever the users @@ -9,9 +10,11 @@ from 2D to 3D), you can use `.connect(your_callback)`: from napari.utils.events import Event from napari import Viewer + def my_callback(event: Event): print("The number of dims shown is now:", event.value) + viewer = Viewer() viewer.dims.events.ndim.connect(my_callback) ``` @@ -19,6 +22,7 @@ viewer.dims.events.ndim.connect(my_callback) ## Viewer events + ```{include} _viewer_events.md ``` diff --git a/docs/guides/index.md b/docs/guides/index.md index dd7063b4e..9909e2a23 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -1,4 +1,5 @@ (explanations)= + # In-depth explanations ```{note} diff --git a/docs/guides/layers.md b/docs/guides/layers.md index 5071a9af4..077f05c68 100644 --- a/docs/guides/layers.md +++ b/docs/guides/layers.md @@ -1,4 +1,5 @@ (layers-glance)= + # Layers at a glance [Layers](napari.layers) are the basic viewable objects that can be added to a @@ -18,7 +19,7 @@ object created from `data`, you can do ```python viewer = napari.Viewer() -viewer.add_image(data, name='astronaut') +viewer.add_image(data, name="astronaut") ``` To learn more about the layers available, see the @@ -37,6 +38,7 @@ one layer, hiding all others. If you then Option/Alt-click on the `visibility` button of a layer a second time, the visibility state of all layers will be restored. (layer_opacity)= + ## Layer opacity All our layers support an opacity slider and `opacity` property that allow you @@ -60,17 +62,17 @@ All our layers support three blending modes: `translucent`, `additive`, and `opaque`. These modes determine how the visuals for this layer get mixed with the visuals from the other layers. -* An `opaque` layer hides any layer data below it. -* A `translucent` setting will cause the layer to blend with the layers below -it if you decrease its opacity but will fully block those layers if its opacity -is `1`. This is a reasonable default, useful for many applications. -* A `minimum` blending mode will cause the layer to blend using the minimum of each pixel's R, G, and B values. This mode is uniquely useful for -blending multiple layers with inverted colormaps/LUTs, which represent measured signal with color on a white background. For some inspiration, see the twitter hashtag [#invertedLUT](https://twitter.com/hashtag/invertedLUT). -* An `additive` blending mode will cause the layer to blend with the layers -below even when it has full opacity. This mode is especially useful for -visualizing multiple layers at the same time, such as cell biology applications -where you have multiple different components of a cell labeled in different -colors. +- An `opaque` layer hides any layer data below it. +- A `translucent` setting will cause the layer to blend with the layers below + it if you decrease its opacity but will fully block those layers if its opacity + is `1`. This is a reasonable default, useful for many applications. +- A `minimum` blending mode will cause the layer to blend using the minimum of each pixel's R, G, and B values. This mode is uniquely useful for + blending multiple layers with inverted colormaps/LUTs, which represent measured signal with color on a white background. For some inspiration, see the twitter hashtag [#invertedLUT](https://twitter.com/hashtag/invertedLUT). +- An `additive` blending mode will cause the layer to blend with the layers + below even when it has full opacity. This mode is especially useful for + visualizing multiple layers at the same time, such as cell biology applications + where you have multiple different components of a cell labeled in different + colors. For example: @@ -134,14 +136,13 @@ existing layer using the `scale` as a keyword argument or property respectively. ```python # scaling while creating the image layer -napari.view_image(retina, name='retina', scale=[1,10,1,1]) +napari.view_image(retina, name="retina", scale=[1, 10, 1, 1]) # scaling an existing layer -viewer.layers['retina'].scale = [1,10,1,1] +viewer.layers["retina"].scale = [1, 10, 1, 1] ``` ![napari viewer with an image where all layers are scaled equally; when rotated, the image appears flat. By using console below the canvas and applying a scale factor to one of the dimensions, the image's volume becomes apparent.](images/scaling.webm) - ## Translating layers All our layers support a `translate` property and keyword argument that you can diff --git a/docs/guides/magicgui.md b/docs/guides/magicgui.md index 3fc09e893..a15430b37 100644 --- a/docs/guides/magicgui.md +++ b/docs/guides/magicgui.md @@ -1,17 +1,7 @@ ---- -jupytext: - cell_metadata_filter: -all - formats: md:myst - text_representation: - extension: .md - format_name: myst - format_version: 0.12 - jupytext_version: 1.8.2 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: cell_metadata_filter: -all formats: md:myst text_representation: extension: .md format_name: myst format_version: 0.12 jupytext_version: 1.8.2 kernelspec: display_name: Python 3 language: python name: python3 + (magicgui)= # Using `magicgui` in napari @@ -48,6 +38,7 @@ def widget_demo( widget_demo.show() ``` + *Add caption here* For more information on the features and usage of `magicgui`, see the [magicgui @@ -98,6 +89,7 @@ in the viewer. ```python from napari.layers import Image + @magicgui def my_widget(image: Image): # do something with whatever image layer the user has selected @@ -120,6 +112,7 @@ def my_widget(image: Image): viewer = napari.view_image(np.random.rand(64, 64), name="My Image") viewer.window.add_dock_widget(my_widget) ``` + *Note the widget at the bottom with "My Image" as the currently selected option* ```{code-cell} python @@ -141,6 +134,7 @@ user to pick from *all* layers in the layer list, annotate your parameter as ```python from napari.layers import Layer + @magicgui def my_widget(layer: Layer): # do something with whatever layer the user has selected @@ -149,6 +143,7 @@ def my_widget(layer: Layer): ``` (annotating-as-napari-types-data)= + ### Annotating as `napari.types.*Data` In the previous example, the object passed to your function will be the actual @@ -163,11 +158,12 @@ example using {attr}`napari.types.ImageData` from napari.types import ImageData import numpy as np + @magicgui def my_widget(array: ImageData): # note: it *may* be None! so your function should handle the null case if array is not None: - assert isinstance(array, np.ndarray) # it will be! + assert isinstance(array, np.ndarray) # it will be! ``` ### Annotating as `napari.Viewer` @@ -179,9 +175,10 @@ in which the widget is docked, you can annotate one of your parameters as a ```python from napari import Viewer + @magicgui def my_widget(viewer: Viewer): - ... + ... ``` ```{caution} @@ -218,9 +215,10 @@ function must be an actual {class}`~napari.layers.Layer` instance. from napari.layers import Image import numpy as np + @magicgui -def my_widget(ny: int=64, nx: int=64) -> Image: - return Image(np.random.rand(ny, nx), name='my Image') +def my_widget(ny: int = 64, nx: int = 64) -> Image: + return Image(np.random.rand(ny, nx), name="my Image") ``` Here's a complete example @@ -275,6 +273,7 @@ your return type must match your return annotation. ``` (returning-napari-types-data)= + ### Returning `napari.types.*Data` In the previous example, the object returned by the function had to be an actual @@ -341,13 +340,13 @@ The following are all valid {attr}`napari.types.LayerDataTuple` examples: (np.random.rand(64, 64),) # an image with name and custom blending mode -(np.random.rand(64, 64), {'name': 'My Image', 'blending': 'additive'}) +(np.random.rand(64, 64), {"name": "My Image", "blending": "additive"}) # an empty points layer -(None, {}, 'points') +(None, {}, "points") # points with properties -(np.random.rand(20, 2), {'properties': {'values': np.random.rand(20)}}, 'points') +(np.random.rand(20, 2), {"properties": {"values": np.random.rand(20)}}, "points") ``` An example of using a {attr}`~napari.types.LayerDataTuple` return annotation in @@ -442,7 +441,7 @@ reference](https://peps.python.org/pep-0484/#forward-references): ```python @magicgui -def my_func(data: 'napari.types.ImageData') -> 'napari.types.ImageData': +def my_func(data: "napari.types.ImageData") -> "napari.types.ImageData": ... ``` @@ -456,10 +455,11 @@ clause: from typing import TYPE_CHECKING if TYPE_CHECKING: - import napari + import napari + @magicgui -def my_func(data: 'napari.types.ImageData') -> 'napari.types.ImageData': +def my_func(data: "napari.types.ImageData") -> "napari.types.ImageData": ... ``` @@ -497,12 +497,14 @@ could be provided as a napari plugin as follows: from magicgui import magic_factory from napari_plugin_engine import napari_hook_implementation -@magic_factory(auto_call=True, threshold={'max': 2 ** 16}) + +@magic_factory(auto_call=True, threshold={"max": 2**16}) def threshold( - data: 'napari.types.ImageData', threshold: int -) -> 'napari.types.LabelsData': + data: "napari.types.ImageData", threshold: int +) -> "napari.types.LabelsData": return (data > threshold).astype(int) + @napari_hook_implementation def napari_experimental_provide_dock_widget(): return threshold @@ -520,8 +522,9 @@ also be overridden when creating a widget from a factory: def my_factory(x: int): ... + widget1 = my_factory() -widget2 = my_factory(call_button=False, x={'widget_type': 'Slider'}) +widget2 = my_factory(call_button=False, x={"widget_type": "Slider"}) ``` ::: diff --git a/docs/guides/napari_models.md b/docs/guides/napari_models.md index ac3dcf715..74f898fd3 100644 --- a/docs/guides/napari_models.md +++ b/docs/guides/napari_models.md @@ -8,25 +8,25 @@ familiar with basic usage of napari. The three main components: -* python models describing components in the napari application - these are able +- python models describing components in the napari application - these are able to operate without the GUI interface and do not have any dependencies on user interface classes - * this code lives in `napari/components` (utility objects) and - `napari/layers` (objects that contain data) -* Qt classes that handle the interactive GUI aspect of the napari viewer - * the private Qt code lives in `napari/_qt` and the smaller public Qt - interface code lives in `napari/qt` -* vispy classes that handle rendering - * the code for this is private and lives in `napari/_vispy` + - this code lives in `napari/components` (utility objects) and + `napari/layers` (objects that contain data) +- Qt classes that handle the interactive GUI aspect of the napari viewer + - the private Qt code lives in `napari/_qt` and the smaller public Qt + interface code lives in `napari/qt` +- vispy classes that handle rendering + - the code for this is private and lives in `napari/_vispy` The separation of the python models from viewer GUI code allows: -* analysis plugins to be developed without worrying about the GUI +- analysis plugins to be developed without worrying about the GUI aspect -* napari to have the option to move away from the rendering backend currently +- napari to have the option to move away from the rendering backend currently used -* tests to be easily run headlessly -* the python models to be run headlessly (see +- tests to be easily run headlessly +- the python models to be run headlessly (see [Running napari headlessly](../howtos/headless) for more) ## Python models and events @@ -43,6 +43,7 @@ for example the `Dims` class with a selected few attributes: ```python from napari.utils.events import EmitterGroup + class Dims: """Dimensions object modeling slicing and displaying. @@ -52,6 +53,7 @@ class Dims: Number of displayed dimensions. ... """ + def __init__(self, ndisplay): self._ndisplay = ndisplay @@ -82,6 +84,7 @@ to watch: # create an instance of the model dims = Dims(ndisplay=2) + # define some callback that should respond to changes in the model # if the function takes a single parameter it will receive the event # as first value. @@ -102,6 +105,7 @@ def _update_display(event): assert ndisplay == event.value print(f"Update number of dimensions displayed to {ndisplay}") + # register our callback with the model dims.events.ndisplay.connect(_update_display) @@ -114,8 +118,8 @@ generic base model `EventedModel` was added to reduce this and "standardize" this change/emit pattern. The `EventedModel` provides the following features: -* type validation and coercion on class instantiation and attribute assignment -* event emission after successful attribute assignment +- type validation and coercion on class instantiation and attribute assignment +- event emission after successful attribute assignment Using `EventedModel` would reduce the above `Dim` class code to: @@ -129,6 +133,7 @@ class Dim(EventedModel): Number of displayed dimensions. ... """ + ndisplay: float ``` @@ -137,7 +142,7 @@ changes. Other classes interested in the `Dim` class can register a callback function that will be executed when an attribute changes. ```python -class DimsDependentClass(): +class DimsDependentClass: """A class that needs to 'do something' when Dims attributes change. Parameters @@ -210,14 +215,11 @@ the function `_on_ndisplay_change`: ```python class VispyCamera: - """Vipsy camera for both 2D and 3D rendering. - """ + """Vipsy camera for both 2D and 3D rendering.""" def __init__(self, dims: Dims): self._dims = dims ... - self._dims.events.ndisplay.connect( - self._on_ndisplay_change, position='first' - ) + self._dims.events.ndisplay.connect(self._on_ndisplay_change, position="first") ``` diff --git a/docs/guides/performance.md b/docs/guides/performance.md index b465d82d3..ffd94388a 100644 --- a/docs/guides/performance.md +++ b/docs/guides/performance.md @@ -1,4 +1,5 @@ (napari-performance)= + # napari performance With offline analysis tools performance dictates how long the user has to wait @@ -15,13 +16,13 @@ There are two main types of performance: 1. [Objective performance](#objective-performance) - * How long operations take when timed with a stopwatch. - * Most times will vary based on the data being viewed. + - How long operations take when timed with a stopwatch. + - Most times will vary based on the data being viewed. 2. [Subjective performance](#subjective-performance) - * The user’s experience as it relates to performance. - * Is the user’s experience pleasant or frustrating? Does napari "seem fast"? + - The user’s experience as it relates to performance. + - Is the user’s experience pleasant or frustrating? Does napari "seem fast"? Both types of performance are important. No amount of slickness can make up for an application that is fundamentally too slow. And even a relatively fast @@ -33,41 +34,41 @@ How to keep napari objectively fast: ### Focus on real use cases -* Focus on cases that matter to lots of people. -* It’s easy to waste time optimizing things no one cares about or no one will +- Focus on cases that matter to lots of people. +- It’s easy to waste time optimizing things no one cares about or no one will notice. -* If a dataset is unreasonable or out of scope or fringe, don’t spend too +- If a dataset is unreasonable or out of scope or fringe, don’t spend too many resources trying to make it run fast. ### Always be timing -* Build timers into the software that always run. -* If not always visible, power users and developers should be able to toggle them on. -* This gives people an ambient awareness of how long things take. -* Allows users to report concrete performance numbers: - * *it seemed slow* → *it ran at 10Hz*. - * *it took a long time* → *it took 2 minutes and 30 seconds*. -* Teaches users how different hardware impacts performance. - * For example seek times with SSD are radically faster than HDD. - * Become familiar with the impact of local vs. networked file systems. +- Build timers into the software that always run. +- If not always visible, power users and developers should be able to toggle them on. +- This gives people an ambient awareness of how long things take. +- Allows users to report concrete performance numbers: + - *it seemed slow* → *it ran at 10Hz*. + - *it took a long time* → *it took 2 minutes and 30 seconds*. +- Teaches users how different hardware impacts performance. + - For example seek times with SSD are radically faster than HDD. + - Become familiar with the impact of local vs. networked file systems. ### Performance system tests -* Create automatic tests that time specific operations in specific known datasets. -* Time many different operations on a nice selection of different datasets. +- Create automatic tests that time specific operations in specific known datasets. +- Time many different operations on a nice selection of different datasets. ### Performance unit tests -* Time one small operation to monitor for regressions. -* Napari has some of these today as "benchmarks". -* Interesting to see how different hardware performs as time goes on. +- Time one small operation to monitor for regressions. +- Napari has some of these today as "benchmarks". +- Interesting to see how different hardware performs as time goes on. ### Run all tests every merge -* Save results to a database maybe using [ASV](https://asv.readthedocs.io/en/stable/index.html). -* Catch a regression right when it happens and not weeks or +- Save results to a database maybe using [ASV](https://asv.readthedocs.io/en/stable/index.html). +- Catch a regression right when it happens and not weeks or months later. -* See how new features run on large datasets no one tested. +- See how new features run on large datasets no one tested. ## Subjective performance @@ -75,40 +76,40 @@ Napari should strive to have these properties: ### Responsive -* React to input one of two ways: - * The full operation happens right away. - * The interface clearly indicates the input was received and the operation was +- React to input one of two ways: + - The full operation happens right away. + - The interface clearly indicates the input was received and the operation was started. -* For click or keypress events the **ideal response is 100ms**. -* For drag events or animations the **ideal refresh is 60Hz** which is 16.7ms per +- For click or keypress events the **ideal response is 100ms**. +- For drag events or animations the **ideal refresh is 60Hz** which is 16.7ms per frame. -* The UI should never seem dead, the user should never be left wondering if +- The UI should never seem dead, the user should never be left wondering if napari has crashed. ### Interruptible -* Modeless operations are best. They can interrupted by simply performing some +- Modeless operations are best. They can interrupted by simply performing some other action. For example if imagery is loading in the background you can interrupt it just by navigating to somewhere else. -* Modal operations that disable the UI should have a cancel button when possible +- Modal operations that disable the UI should have a cancel button when possible unless they are very short. -* The user should never feel “trapped”. +- The user should never feel “trapped”. ### Progressive -* Show intermediate results as they become available instead of showing nothing +- Show intermediate results as they become available instead of showing nothing until the full result is ready. -* Sometimes progressive results are better even if they slow things down a bit, +- Sometimes progressive results are better even if they slow things down a bit, which is not necessarily intuitive. ### Informative -* Clearly show what controls are enabled or disabled. -* If progressive display is not possible, show a progress bar. -* Show a busy animation as the last resort, never look totally locked up. -* Show time estimates for super long operations. -* Let power users see timings, bandwidth, FPS, etc. -* Revealing internal state that explains why it's taking time is helpful. +- Clearly show what controls are enabled or disabled. +- If progressive display is not possible, show a progress bar. +- Show a busy animation as the last resort, never look totally locked up. +- Show time estimates for super long operations. +- Let power users see timings, bandwidth, FPS, etc. +- Revealing internal state that explains why it's taking time is helpful. ## Performance is never done @@ -116,25 +117,25 @@ Performance is never "done" for several reasons: ### New features -* The objective and subjective performance of new features should be scrutinized +- The objective and subjective performance of new features should be scrutinized before merging to main. -* New features should be tested on a variety of data types and sizes, including the largest data sets that are supported. -* The new feature should scale to large datasets, or the performance limitations of the feature should be well documented. -* It can be hard to impossible to "add performance in later". The best time to +- New features should be tested on a variety of data types and sizes, including the largest data sets that are supported. +- The new feature should scale to large datasets, or the performance limitations of the feature should be well documented. +- It can be hard to impossible to "add performance in later". The best time to ensure the new feature performs well is when the feature is first added. -### Regressions* +### Regressions\* -* We must be on guard for new features slowing down existing features. -* New versions of dependencies can slow things down. -* New hardware generally helps performance but not always. +- We must be on guard for new features slowing down existing features. +- New versions of dependencies can slow things down. +- New hardware generally helps performance but not always. ### Scope changes -* As new types of users adopt napari they will have new use cases. -* Existing users will change their usage over time such as more remote viewing. -* New file formats will be invented or become more common. -* New data types or sizes will become more common. +- As new types of users adopt napari they will have new use cases. +- Existing users will change their usage over time such as more remote viewing. +- New file formats will be invented or become more common. +- New data types or sizes will become more common. ## Conclusion diff --git a/docs/guides/rendering-explanation.md b/docs/guides/rendering-explanation.md index f9b65ddeb..127358f13 100644 --- a/docs/guides/rendering-explanation.md +++ b/docs/guides/rendering-explanation.md @@ -1,4 +1,5 @@ (rendering-explanation)= + # Rendering in napari ## Status @@ -20,11 +21,11 @@ framerate gets slower: | Framerate | Milliseconds | User Experience | | --------: | -----------: | :-------------- | -| 60Hz | 16.7 | Great | -| 30Hz | 33.3 | Good | -| 20Hz | 50 | Acceptable | -| 10Hz | 100 | Bad | -| 5Hz | 200 | Unusable | +| 60Hz | 16.7 | Great | +| 30Hz | 33.3 | Good | +| 20Hz | 50 | Acceptable | +| 10Hz | 100 | Bad | +| 5Hz | 200 | Unusable | The issue is not just aesthetic. Manipulating user interface elements like sliders becomes almost impossible if the framerate is really slow. This @@ -74,7 +75,7 @@ to choose. Issues that napari has without asynchronous rendering include [#845](https://github.com/napari/napari/issues/845), [#1300](https://github.com/napari/napari/issues/1300), and -[#1320](https://github.com/napari/napari/issues/1320]). +[#1320](https://github.com/napari/napari/issues/1320%5D). ## RAM and VRAM diff --git a/docs/guides/rendering.md b/docs/guides/rendering.md index 754595423..9c7ed91d5 100644 --- a/docs/guides/rendering.md +++ b/docs/guides/rendering.md @@ -97,7 +97,7 @@ subclass of which is Octree-specific, see {ref}`future-work-atlas-2D`. The {class}`~napari._vispy.experimental.texture_atlas.TextureAtlas2D` class -is a subclass of the generic Vispy ``Texture2D`` class. Like ``Texture2D`` +is a subclass of the generic Vispy `Texture2D` class. Like `Texture2D` the {class}`~napari._vispy.experimental.texture_atlas.TextureAtlas2D` class owns one texture. However {class}`~napari._vispy.experimental.texture_atlas.TextureAtlas2D` uses this @@ -172,15 +172,16 @@ shows how many ideal chunks are "covered" by a chunk at a higher level: | Levels Above Ideal | Coverage | | -----------------: | -------: | -| 1 | 4 | -| 2 | 16 | -| 3 | 64 | +| 1 | 4 | +| 2 | 16 | +| 3 | 64 | Although data 3 levels above will be quite blurry, it's pretty amazing you can load one chunk and it will cover 64 ideal chunks. This is the heart of the power of Octrees, Quadtrees or multiscale images. (octree-config)= + ### Octree configuration file Setting `NAPARI_OCTREE=1` enables Octree rendering with the default @@ -215,24 +216,24 @@ config file format: The `loader_defaults` key contains settings that will be used by the {class}`~napari.components.experimental.chunk._loader.ChunkLoader`. -| Setting | Description | -| :-------------------- | :--------------------------------------------------------- | -| `log_path` | Write `ChunkLoader` log file to this path. For debugging. | -| `force_synchronous` | If `true` the `ChunkLoader` loads synchronously. | -| `num_workers` | The number of worker threads or processes. | -| `use_processes` | If `true` use worker processes instead of threads. | -| `auto_async_ms` | Switch to synchronous if loads are faster than this. | -| `delay_queue_ms` | Delay loads by this much. | -| `num_workers` | The number of worker threads or processes. | +| Setting | Description | +| :------------------ | :-------------------------------------------------------- | +| `log_path` | Write `ChunkLoader` log file to this path. For debugging. | +| `force_synchronous` | If `true` the `ChunkLoader` loads synchronously. | +| `num_workers` | The number of worker threads or processes. | +| `use_processes` | If `true` use worker processes instead of threads. | +| `auto_async_ms` | Switch to synchronous if loads are faster than this. | +| `delay_queue_ms` | Delay loads by this much. | +| `num_workers` | The number of worker threads or processes. | The `octree` key contains these settings: -| Setting | Description | -| :-------------------- | :--------------------------------------------------------- | -| `enabled` | If `false` then use the old `Image` class. | -| `tile_size` | Size of render tiles to use for rending. | -| `log_path` | Octree specific log file for debugging. | -| `loaders` | Optional custom loaders, see below. | +| Setting | Description | +| :---------- | :----------------------------------------- | +| `enabled` | If `false` then use the old `Image` class. | +| `tile_size` | Size of render tiles to use for rending. | +| `log_path` | Octree specific log file for debugging. | +| `loaders` | Optional custom loaders, see below. | The `loaders` key lets you define and configure multiple {class}`~napari.components.experimental.chunk._pool.LoaderPool` pools. The @@ -355,8 +356,8 @@ to stop creating the "extra" downsampled levels, as described in {ref}`future-wo tearing down the Octree is fast enough, and make sure loads for the previous slices are canceled and everything is cleaned up. - (future-work-atlas-2D)= + ### Future work: Extending TextureAtlas2D We could improve our diff --git a/docs/guides/threading.md b/docs/guides/threading.md index 7527ec931..2c01e5b48 100644 --- a/docs/guides/threading.md +++ b/docs/guides/threading.md @@ -53,7 +53,6 @@ immediately leverage them in napari. `napari` also provides a few convenience functions that allow you to easily run your long-running methods in another thread. - ## Threading in napari with `@thread_worker` The simplest way to run a function in another thread in napari is to decorate @@ -102,10 +101,12 @@ the above example: ```python viewer = napari.Viewer() + @thread_worker(connect={"returned": viewer.add_image}) def average_large_image(): return np.random.rand(1024, 512, 512).mean(0) + average_large_image() napari.run() ``` @@ -126,10 +127,10 @@ As shown above, the `worker` object returned by a function decorated with signals that are emitted in response to certain events. The base signals provided by the `worker` are: -* `started` - emitted when the work is started -* `finished` - emitted when the work is finished -* `returned` [*value*] - emitted with return value when the function returns -* `errored` [*exception*] - emitted with an `Exception` object if an +- `started` - emitted when the work is started +- `finished` - emitted when the work is finished +- `returned` \[*value*\] - emitted with return value when the function returns +- `errored` \[*exception*\] - emitted with an `Exception` object if an exception is raised in the thread. ### Example: Custom exception handler @@ -147,6 +148,7 @@ def my_handler(exc): else: raise exc + @thread_worker(connect={"errored": my_handler}) def error_prone_function(): ... @@ -173,18 +175,18 @@ def my_generator(): the end, we gain a number of valuable features, and a few extra signals and methods on the `worker`. -* `yielded` [*value*]- emitted with a value when a value is yielded -* `paused` - emitted when a running job has successfully paused -* `resumed` - emitted when a paused job has successfully resumed -* `aborted` - emitted when a running job is successfully aborted +- `yielded` \[*value*\]- emitted with a value when a value is yielded +- `paused` - emitted when a running job has successfully paused +- `resumed` - emitted when a paused job has successfully resumed +- `aborted` - emitted when a running job is successfully aborted Additionally, generator `workers` will also have a few additional methods: -* `send` - send a value *into* the thread (see below) -* `pause` - send a request to pause a running worker -* `resume` - send a request to resume a paused worker -* `toggle_pause` - send a request to toggle the running state of the worker -* `quit` - send a request to abort the worker +- `send` - send a value *into* the thread (see below) +- `pause` - send a request to pause a running worker +- `resume` - send a request to resume a paused worker +- `toggle_pause` - send a request to toggle the running state of the worker +- `quit` - send a request to abort the worker ### Retrieving intermediate results @@ -194,7 +196,6 @@ taking the mean projection of a large stack, if we yield the cumulative average as it is generated (rather than taking the average of the fully generated stack) we can watch the mean projection as it builds: - ```{code-block} python --- emphasize-lines: 19,25 @@ -388,7 +389,7 @@ Let's break it down: it to create a `worker`. 2. The most interesting line in this example is where we both - `yield` the current ``total`` to the main thread (`yield total`), *and* + `yield` the current `total` to the main thread (`yield total`), *and* receive a new value from the main thread (with `new = yield`). 3. In the main thread, we have connected that `worker.yielded` event @@ -403,7 +404,7 @@ Let's break it down: into the thread for multiplication by the existing total. 6. Lastly, if the thread total ever goes to "0", we stop the thread by - returning the string ``"Game Over"``. In the main thread, the + returning the string `"Game Over"`. In the main thread, the `worker.returned` event is connected to a callback that disables the `line_edit` widget and shows the string returned from the thread. @@ -425,11 +426,13 @@ depending on your function type. The following three examples are equivalent: ```python from napari.qt.threading import thread_worker + @thread_worker def my_function(arg1, arg2=None): ... -worker = my_function('hello', arg2=42) + +worker = my_function("hello", arg2=42) ``` **Using the** `create_worker` **function:** @@ -437,21 +440,25 @@ worker = my_function('hello', arg2=42) ```python from napari.qt.threading import create_worker + def my_function(arg1, arg2=None): ... -worker = create_worker(my_function, 'hello', arg2=42) + +worker = create_worker(my_function, "hello", arg2=42) ``` -**Using a** ``Worker`` **class:** +**Using a** `Worker` **class:** ```python from napari.qt.threading import FunctionWorker + def my_function(arg1, arg2=None): ... -worker = FunctionWorker(my_function, 'hello', arg2=42) + +worker = FunctionWorker(my_function, "hello", arg2=42) ``` (the main difference between using `create_worker` and directly instantiating @@ -480,6 +487,7 @@ keep in mind the following guidelines: it is important that you periodically check `self.abort_requested` in your thread loop, and exit the thread accordingly, otherwise `napari` will not be able to gracefully exit a long-running thread. + ```python def work(self): i = 0 @@ -488,7 +496,7 @@ keep in mind the following guidelines: self.aborted.emit() break time.sleep(0.5) - ``` + ``` 3. It is also important to be mindful of the fact that the {meth}`worker.start() ` method adds @@ -519,14 +527,17 @@ worker namespace). To add custom signals to a ```python from qtpy.QtCore import QObject, Signal + class MyWorkerSignals(QObject): signal_name = Signal() + # or subclass one of the existing signals objects to "add" # additional signals: from napari.qt.threading import WorkerBaseSignals + # WorkerBaseSignals already has started, finished, errored... class MyWorkerSignals(WorkerBaseSignals): signal_name = Signal() @@ -536,10 +547,8 @@ and then either directly override the `self._signals` attribute on the {class}`~napari.qt.threading.WorkerBase` class with an instance of your signals class: - ```python class MyWorker(WorkerBase): - def __init__(self): super().__init__() self._signals = MyWorkerSignals() @@ -550,7 +559,6 @@ initializing the superclass in your `__init__` method: ```python class MyWorker(WorkerBase): - def __init__(self): super().__init__(SignalsClass=MyWorkerSignals) ``` diff --git a/docs/howtos/connecting_events.md b/docs/howtos/connecting_events.md index c431b1aac..c7cb55132 100644 --- a/docs/howtos/connecting_events.md +++ b/docs/howtos/connecting_events.md @@ -8,7 +8,7 @@ strange at times. Often we write code in a very procedural way: "do this ... then do that, etc...". With napari and other GUI programs however, usually you hook up a bunch of conditions to callback functions (e.g. "If this event happens, then call this function") and *then* start the loop and hope you hooked -everything up correctly! Indeed, much of the ``napari`` source code is +everything up correctly! Indeed, much of the `napari` source code is dedicated to creating and handling events: search the codebase for [`.emit(`](https://github.com/napari/napari/search?q=%22.emit%28%22&type=code) and @@ -33,17 +33,20 @@ import napari viewer = napari.Viewer() -@viewer.bind_key('i') + +@viewer.bind_key("i") def add_layer(viewer): viewer.add_image(np.random.random((512, 512))) -@viewer.bind_key('k') + +@viewer.bind_key("k") def delete_layer(viewer): try: viewer.layers.pop(0) except IndexError: pass + napari.run() ``` @@ -63,19 +66,21 @@ import napari viewer = napari.Viewer() layer = viewer.add_image(np.random.random((512, 512))) + @layer.mouse_drag_callbacks.append def update_layer(layer, event): layer.data = np.random.random((512, 512)) + napari.run() ``` As of this writing `MouseProvider`s have 4 list of callbacks that can be registered: - - `mouse_move_callbacks` - - `mouse_wheel_callbacks` - - `mouse_drag_callbacks` - - `mouse_double_click_callbacks` +- `mouse_move_callbacks` +- `mouse_wheel_callbacks` +- `mouse_drag_callbacks` +- `mouse_double_click_callbacks` Please look at the documentation of `MouseProvider` for a more in depth discussion of when each callback is triggered. In particular single click can be @@ -96,7 +101,7 @@ provides for you to "connect" to. Until we have centralized documentation for all of the events offered by napari objects, the best way to find these is to browse the source code. Take for instance, the base {class}`~napari.layers.Layer` class: you'll find in the `__init__` method a -``self.events`` section that looks like this: +`self.events` section that looks like this: ```python self.events = EmitterGroup( @@ -116,6 +121,7 @@ function that accepts the event object: def print_layer_name(event): print(f"{event.source.name} changed its data!") + layer.events.data.connect(print_layer_name) ``` diff --git a/docs/howtos/docker.md b/docs/howtos/docker.md index 6fea186f0..bbae4c0cd 100644 --- a/docs/howtos/docker.md +++ b/docs/howtos/docker.md @@ -7,8 +7,8 @@ Builds are available in the [GitHub Container Registry](https://github.com/orgs/ A dockerfile is added to napari root to allow build of a docker image using official napari release. It contains two targets built on top of Ubuntu 20.04: -* `napari`: The result of `python -m pip install napari[all] scikit-image` for Python 3.8, including all the system libraries required by PyQt. -* `napari-xpra`: Same as above, plus a preconfigured Xpra server. +- `napari`: The result of `python -m pip install napari[all] scikit-image` for Python 3.8, including all the system libraries required by PyQt. +- `napari-xpra`: Same as above, plus a preconfigured Xpra server. Note that napari in Docker is still in alpha stage and not working universally. Feedback and contributions are welcomed! @@ -29,8 +29,9 @@ which would build a Docker image tagged with napari version. First, make sure there's a running X server on the host machine. These can be useful if you are looking for options: -* Windows: [vcxsrc](https://sourceforge.net/projects/vcxsrv/) -* MacOS: [xquartz](https://www.xquartz.org/) (may not work due to graphical driver issue with opengl) + +- Windows: [vcxsrc](https://sourceforge.net/projects/vcxsrv/) +- MacOS: [xquartz](https://www.xquartz.org/) (may not work due to graphical driver issue with opengl) To run a container with external mapping of display, an example being: @@ -46,16 +47,16 @@ With this image you don't need X running on the host. A browser is sufficient! docker run -it --rm -p 9876:9876 ghcr.io/napari/napari-xpra ``` -Once that's running, you can open a tab on your browser of choice and go to ``http://localhost:9876``. +Once that's running, you can open a tab on your browser of choice and go to `http://localhost:9876`. You'll be presented with a virtual desktop already running running napari. The desktop features a basic menu at the top with some extra items, like a `Xterm` terminal. This image features a series of environment variables you can use to customize its behaviour: -* `XPRA_PORT=9876`: Port where Xpra will publish the display feed (if you change this, make sure to use the new port in your `docker run`) -* `XPRA_START="python3 -m napari"`: Xpra will run this command once it has started -* `XPRA_EXIT_WITH_CLIENT="yes"`: By default, Xpra will exit if you close the browser tab -* `XPRA_XVFB_SCREEN="1920x1080x24+32"`: The resolution and bit depth of the virtual display created by Xvfb +- `XPRA_PORT=9876`: Port where Xpra will publish the display feed (if you change this, make sure to use the new port in your `docker run`) +- `XPRA_START="python3 -m napari"`: Xpra will run this command once it has started +- `XPRA_EXIT_WITH_CLIENT="yes"`: By default, Xpra will exit if you close the browser tab +- `XPRA_XVFB_SCREEN="1920x1080x24+32"`: The resolution and bit depth of the virtual display created by Xvfb ## For development @@ -86,20 +87,20 @@ $ python3 -m napari Making the Docker image run seamlessly with your host graphical stack can be tricky. You might find issues like these: -* napari seems to be running, but no window appears at all -* napari does run and the UI works as intended, but the viewer portion is completely black -* napari does not start due to missing libraries or mismatched ABIs (e.g. drivers in host are incompatible with guest) +- napari seems to be running, but no window appears at all +- napari does run and the UI works as intended, but the viewer portion is completely black +- napari does not start due to missing libraries or mismatched ABIs (e.g. drivers in host are incompatible with guest) In these cases, the best option is to stop relying on the host graphics and let the guest handle everything. To do this, we need several pieces in place: -* A headless X server running on the guest; e.g. `Xvfb` (X virtual frame buffer) -* A way of "seeing" that X server from the host. Some options include: - * VNC (might have issues with OpenGL) - * NX (NoMachine;, [should work](https://github.com/napari/napari/issues/886#issuecomment-873178682)) - * Microsoft Remote Desktop Protocol (e.g. XRDP; [should work](https://github.com/napari/napari/issues/886#issuecomment-875959941)) - * Chrome Remote Desktop ([should work](https://github.com/napari/napari/issues/886#issue-551159225)) - * Xpra (part of the Docker image detailed above) +- A headless X server running on the guest; e.g. `Xvfb` (X virtual frame buffer) +- A way of "seeing" that X server from the host. Some options include: + - VNC (might have issues with OpenGL) + - NX (NoMachine;, [should work](https://github.com/napari/napari/issues/886#issuecomment-873178682)) + - Microsoft Remote Desktop Protocol (e.g. XRDP; [should work](https://github.com/napari/napari/issues/886#issuecomment-875959941)) + - Chrome Remote Desktop ([should work](https://github.com/napari/napari/issues/886#issue-551159225)) + - Xpra (part of the Docker image detailed above) Since napari relies on OpenGL for its (hardware) accelerated parts, we need to ensure that the piece we choose are OpenGL compatible. Xvfb itself is compatible, but the display "exporter" needs to know how to deal with that part of the graphics too! diff --git a/docs/howtos/index.md b/docs/howtos/index.md index 81ccddd48..7301792be 100644 --- a/docs/howtos/index.md +++ b/docs/howtos/index.md @@ -1,4 +1,5 @@ (how-tos)= + # How-to guides These guides show you how to complete tasks with napari. They assume you have @@ -13,12 +14,12 @@ dimension sliders see our [napari viewer](../tutorials/fundamentals/viewer) tutorial. - [Using layers](layers/index): Learn about how to use the layers currently -supported by napari. + supported by napari. - [Hooking up your own events](./connecting_events): Learn about the event loop -and how to use it to interact with napari. + and how to use it to interact with napari. - [napari + ImageJ how-to guide](./napari_imageJ): Learn about how to use napari -and ImageJ simultaneously. + and ImageJ simultaneously. - [napari in Docker](./docker): Learn about using napari in docker. - [Performance monitoring](./perfmon): Learn about how to monitor napari -performance and diagnose potential problems. + performance and diagnose potential problems. - [napari headless](./headless): Tips on how to run napari headlessly. diff --git a/docs/howtos/layers/image.md b/docs/howtos/layers/image.md index ea94803df..7fc42738f 100644 --- a/docs/howtos/layers/image.md +++ b/docs/howtos/layers/image.md @@ -1,15 +1,6 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.10.3 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.10.3 kernelspec: display_name: Python 3 language: python name: python3 # Using the image layer @@ -297,8 +288,8 @@ extent of the contrast limits range slider will be set to those values. ## Saving without image compression When saving an image layer, lossless zlib compression is applied by default. - To save with a different level of compression, consider using - [imageio.imwrite](https://imageio.readthedocs.io/en/stable/_autosummary/imageio.v3.imwrite.html). +To save with a different level of compression, consider using +[imageio.imwrite](https://imageio.readthedocs.io/en/stable/_autosummary/imageio.v3.imwrite.html). Adjusting compression can be accomplished by including the appropriate kwargs as outlined in the following locations for [tiff](https://imageio.readthedocs.io/en/stable/_autosummary/imageio.plugins.tifffile.html#metadata-for-writing) or diff --git a/docs/howtos/layers/index.md b/docs/howtos/layers/index.md index 98cdfbede..d8db92e33 100644 --- a/docs/howtos/layers/index.md +++ b/docs/howtos/layers/index.md @@ -1,4 +1,5 @@ (using-layers)= + # Using layers [Layers](napari.layers) are the viewable objects that can be added to a viewer. @@ -9,4 +10,4 @@ following how-to guides: ``` For a high-level overview of the napari Layers, see -[Layers at a glance](../../guides/layers). \ No newline at end of file +[Layers at a glance](../../guides/layers). diff --git a/docs/howtos/layers/labels.md b/docs/howtos/layers/labels.md index 26c8b8c24..1f685c67d 100644 --- a/docs/howtos/layers/labels.md +++ b/docs/howtos/layers/labels.md @@ -1,15 +1,6 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.10.3 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.10.3 kernelspec: display_name: Python 3 language: python name: python3 # Using the labels layer @@ -98,7 +89,7 @@ same numpy-like arrays, including [xarrays](https://docs.xarray.dev/en/stable/generated/xarray.DataArray.html), and [zarr arrays](https://zarr.readthedocs.io/en/stable/api/core.html). A `Labels` layer though must be integer valued, and the background label must be -0. +0\. Because the labels layer subclasses the image layer it inherits the great properties of the image layer, like supporting lazy loading and multiscale @@ -265,34 +256,34 @@ layer is selected. ## Drawing using polygons in the labels layer -Another tool that can be used to quickly add or edit image segmentations is the -`polygon` tool. It combines functionality of the `paintbrush` and `fill bucket` -tool by allowing for readily drawing enclosed instance segmentations. The `polygon` tool -can be activated by clicking on the icon resembling a polygon in the layer control +Another tool that can be used to quickly add or edit image segmentations is the +`polygon` tool. It combines functionality of the `paintbrush` and `fill bucket` +tool by allowing for readily drawing enclosed instance segmentations. The `polygon` tool +can be activated by clicking on the icon resembling a polygon in the layer control panel or by pressing `3`. Once activated, the user actions are as follows: 1. Left-click anywhere on the canvas to start drawing the polygon. 2. Move the mouse to the location where you want the next vertex to be. 3. Click again to set the vertex that is tracking the mouse cursor. 4. After this step a polygon overlay will appear when moving the mouse. Repeat step 2 and 3 - until the shape to be segmented is enclosed by the polygon overlay. + until the shape to be segmented is enclosed by the polygon overlay. 5. To undo the last added vertex right-click.' 6. To cancel the drawing at any time without making a permanent change on the labels layer press `escape`. This will delete the polygon overlay 7. Press `enter` to finish drawing at any time or double click within a radius of 20 screen pixels of the first vertex. This will add the polygon overlay to the labels layer. -The polygon overlay will have the color of the label. The polygon overlay also has an opacity -that can be adjusted the value of the `opacity` slider in the layer control panel. -Furthermore, while the polygon overlay may be visible outside the canvas space during drawing, upon +The polygon overlay will have the color of the label. The polygon overlay also has an opacity +that can be adjusted the value of the `opacity` slider in the layer control panel. +Furthermore, while the polygon overlay may be visible outside the canvas space during drawing, upon finishing drawing the polygon will be cut off so that the part outside the canvas space is removed. This ensures that the dimensions of the label image are not larger than the image for which you are segmenting of for which you are editing the segmentations. -Note: if you use the `polygon` tool for adding or editing segmentations of 3D image data, you can only -adjust labels in one plane, with the exception when viewing the image data as RGB. +Note: if you use the `polygon` tool for adding or editing segmentations of 3D image data, you can only +adjust labels in one plane, with the exception when viewing the image data as RGB. The `polygon` tool cannot be activated if the number of displayed dimensions is higher than two. -If already active upon toggling the number of displayed dimensions, the `polygon` tool will be +If already active upon toggling the number of displayed dimensions, the `polygon` tool will be automatically deactivated. ## Creating, deleting, merging, and splitting connected components diff --git a/docs/howtos/layers/points.md b/docs/howtos/layers/points.md index e3222ad32..d42170826 100644 --- a/docs/howtos/layers/points.md +++ b/docs/howtos/layers/points.md @@ -1,15 +1,6 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.10.3 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.10.3 kernelspec: display_name: Python 3 language: python name: python3 # Using the points layer @@ -262,8 +253,7 @@ each property are stored in a numpy ndarray with length 3 since there were three coordinates provided in `points`. We set the edge color as a function of the `good_point` property by providing the keyword argument `edge_color='good_point'` to the `viewer.add_points()` method. We set the color -cycle via the `edge_color_cycle` keyword argument (`edge_color_cycle=['magenta', -'green']`). The color cycle can be provided as a list of colors (a list of +cycle via the `edge_color_cycle` keyword argument (`edge_color_cycle=['magenta', 'green']`). The color cycle can be provided as a list of colors (a list of strings or a (M x 4) array of M RGBA colors). ### Setting edge or face color with a colormap diff --git a/docs/howtos/layers/shapes.md b/docs/howtos/layers/shapes.md index 41979aa65..8879dedaf 100644 --- a/docs/howtos/layers/shapes.md +++ b/docs/howtos/layers/shapes.md @@ -1,15 +1,6 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.10.3 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.10.3 kernelspec: display_name: Python 3 language: python name: python3 # Using the shapes layer @@ -171,7 +162,7 @@ The default is 10 and can be any integer higher than 0 and lower than 50. As wit tool drawing the shape can also be finished by pressing the `escape` key. After finishing drawing a polygon using the polygon lasso tool, an implementation of the [Ramer–Douglas–Peucker -algorithm](https://en.wikipedia.org/wiki/Ramer–Douglas–Peucker_algorithm) is applied to reduce the +algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm) is applied to reduce the number of vertices that make up the shape, while preserving its contours. The aggressiveness with which the algorithm reduces the number of vertices of the polygon is determined by an `epsilon` parameter, which is a perpendicular distance threshold. Any vertices beyond the threshold will be preserved, so diff --git a/docs/howtos/layers/surface.md b/docs/howtos/layers/surface.md index cae122eb5..011923dcf 100644 --- a/docs/howtos/layers/surface.md +++ b/docs/howtos/layers/surface.md @@ -1,15 +1,6 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.10.3 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.10.3 kernelspec: display_name: Python 3 language: python name: python3 # Using the surface layer diff --git a/docs/howtos/layers/tracks.md b/docs/howtos/layers/tracks.md index 58025285c..30ac4b6e7 100644 --- a/docs/howtos/layers/tracks.md +++ b/docs/howtos/layers/tracks.md @@ -1,15 +1,6 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.10.3 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.10.3 kernelspec: display_name: Python 3 language: python name: python3 # Using the tracks layer @@ -57,11 +48,11 @@ tracks_data = [ [3, 1, 636, 100], [3, 2, 636, 200], [3, 3, 636, 500], - [3, 4, 636, 1000] + [3, 4, 636, 1000], ] -viewer = napari.view_image(hubble_image, name='image') -viewer.add_tracks(tracks_data, name='tracks') +viewer = napari.view_image(hubble_image, name="image") +viewer.add_tracks(tracks_data, name="tracks") napari.run() ``` @@ -145,23 +136,22 @@ single track. In this case, we have defined 2 tracks: track 0, which goes from `[10, 10, 10]` to `[20, 10, 10]` and track 1, which goes from `[10, 8, 5]` to `[7, 8, 10]` (coordinates written as `[x, y, z]`). -| track_id | t | z | y | x | -|----------|---|----|----|----| -| 0 | 0 | 10 | 10 | 10 | -| 0 | 1 | 10 | 10 | 20 | -| 1 | 0 | 5 | 8 | 10 | -| 1 | 1 | 10 | 8 | 7 | +| track_id | t | z | y | x | +| -------- | --- | --- | --- | --- | +| 0 | 0 | 10 | 10 | 10 | +| 0 | 1 | 10 | 10 | 20 | +| 1 | 0 | 5 | 8 | 10 | +| 1 | 1 | 10 | 8 | 7 | The data in the array must be sorted by increasing `track_id` then time, as shown above. We can pass the example data above to the tracks layer as follows: ```python - tracks_data = [ [0, 0, 10, 10, 10], [0, 1, 10, 10, 20], [1, 0, 5, 8, 10], - [1, 1, 10, 8, 7] + [1, 1, 10, 8, 7], ] viewer = napari.view_tracks(tracks_data) @@ -179,21 +169,16 @@ For example, if we have a track 0, which splits into tracks 1 and 2 (i.e., track 0 is the parent of tracks 1 and 2), we would define the graph as: ```python -graph = { - 1: [0], - 2: [0] -} +graph = {1: [0], 2: [0]} ``` + If later tracks 1 and 2 merge into track 3 (i.e,. tracks 1 and 2 are the parent of track 3), the dictionary would become ```python -graph = { - 1: [0], - 2: [0], - 3: [1, 2] -} +graph = {1: [0], 2: [0], 3: [1, 2]} ``` + For a full example of 3d+t tracks data with a parent graph, please see our [`tracks_3d_with_graph.py` example](https://github.com/napari/napari/blob/main/examples/tracks_3d_with_graph.py). @@ -225,7 +210,6 @@ viewer = napari.view_tracks(data, tail_width=5, name="my_tracks") # update the tail width to 3 pixels viewer.layers["my_tracks"].tail_width = 3 - ``` Additionally, we can adjust the width of the track in the GUI using the "tail width" slider in the Tracks layer controls. @@ -242,7 +226,6 @@ viewer = napari.view_tracks(data, tail_length=5, name="my_tracks") # update the tail width to 3 pixels viewer.layers["my_tracks"].tail_length = 3 - ``` Additionally, we can adjust the width of the track in the GUI using the "tail length" slider in the Tracks layer controls. @@ -259,28 +242,27 @@ from skimage import data hubble_image = data.hubble_deep_field() -tracks_data = np.asarray([ - [1, 0, 236, 0], - [1, 1, 236, 100], - [1, 2, 236, 200], - [1, 3, 236, 500], - [1, 4, 236, 1000], - [2, 0, 436, 0], - [2, 1, 436, 100], - [2, 2, 436, 200], - [2, 3, 436, 500], - [2, 4, 436, 1000], - [3, 0, 636, 0], - [3, 1, 636, 100], - [3, 2, 636, 200], - [3, 3, 636, 500], - [3, 4, 636, 1000] -]) -track_confidence = np.array(5*[0.9] + 5*[0.3] + 5 * [0.1]) -properties = { - 'time': tracks_data[:, 1], - 'confidence': track_confidence -} +tracks_data = np.asarray( + [ + [1, 0, 236, 0], + [1, 1, 236, 100], + [1, 2, 236, 200], + [1, 3, 236, 500], + [1, 4, 236, 1000], + [2, 0, 436, 0], + [2, 1, 436, 100], + [2, 2, 436, 200], + [2, 3, 436, 500], + [2, 4, 436, 1000], + [3, 0, 636, 0], + [3, 1, 636, 100], + [3, 2, 636, 200], + [3, 3, 636, 500], + [3, 4, 636, 1000], + ] +) +track_confidence = np.array(5 * [0.9] + 5 * [0.3] + 5 * [0.1]) +properties = {"time": tracks_data[:, 1], "confidence": track_confidence} viewer = napari.view_image(hubble_image) viewer.add_tracks(tracks_data, properties=properties) diff --git a/docs/howtos/layers/vectors.md b/docs/howtos/layers/vectors.md index a3152f3cb..213f56a64 100644 --- a/docs/howtos/layers/vectors.md +++ b/docs/howtos/layers/vectors.md @@ -1,15 +1,6 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.10.3 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.10.3 kernelspec: display_name: Python 3 language: python name: python3 # Using the vectors layer diff --git a/docs/howtos/napari_imageJ.md b/docs/howtos/napari_imageJ.md index a3d89d128..aa5a6cef1 100644 --- a/docs/howtos/napari_imageJ.md +++ b/docs/howtos/napari_imageJ.md @@ -13,21 +13,23 @@ and then display them with Napari. import napari, sys if len(sys.argv) <= 1: - print('Please specify one or more images as arguments.') + print("Please specify one or more images as arguments.") exit(1) try: import imagej except ModuleNotFoundError: - raise ModuleNotFoundError("""This example uses ImageJ but pyimagej is not - installed. To install try 'conda install pyimagej'.""") + raise ModuleNotFoundError( + """This example uses ImageJ but pyimagej is not + installed. To install try 'conda install pyimagej'.""" + ) -print('--> Initializing imagej') -ij = imagej.init('sc.fiji:fiji') # Fiji includes Bio-Formats. +print("--> Initializing imagej") +ij = imagej.init("sc.fiji:fiji") # Fiji includes Bio-Formats. viewer = napari.Viewer() for path in sys.argv[1:]: - print(f'--> Reading {path}') + print(f"--> Reading {path}") dataset = ij.io().open(path) image = ij.py.from_java(dataset) @@ -60,6 +62,7 @@ Firstly import napari: ```python import napari + viewer = napari.Viewer() ``` @@ -67,6 +70,7 @@ When napari comes up, open the Jupyter Qt console and type: ```python import imagej + ij = imagej.init(headless=False) ij.ui().showUI() ``` @@ -74,47 +78,56 @@ ij.ui().showUI() This works because the console in napari is running in the correctly initialized Qt GUI/main thread. However,if we even touch the Java UI from Python it locks up i.e. Python will now lock up even for the simplest line of code such as: ```python -ij.ui().showDialog('hello') +ij.ui().showDialog("hello") ``` To fix this we can use either of these 3 following methods : 1. Use Java’s EventQueue to queue the task on the Java event dispatch thread: - ```python - from jnius import PythonJavaClass, java_method, autoclass - class JavaRunnable(PythonJavaClass): - __javainterfaces__ = ['java/lang/Runnable'] - def __init__(self, f): - super(JavaRunnable, self).__init__() - self._f = f - - @java_method('()V') - def run(self): - self._f() - - EventQueue = autoclass('java.awt.EventQueue') - EventQueue.invokeLater(JavaRunnable(lambda: ij.ui().showDialog('hello'))) - ``` + + ```python + from jnius import PythonJavaClass, java_method, autoclass + + + class JavaRunnable(PythonJavaClass): + __javainterfaces__ = ["java/lang/Runnable"] + + def __init__(self, f): + super(JavaRunnable, self).__init__() + self._f = f + + @java_method("()V") + def run(self): + self._f() + + + EventQueue = autoclass("java.awt.EventQueue") + EventQueue.invokeLater(JavaRunnable(lambda: ij.ui().showDialog("hello"))) + ``` 2. Use the SciJava ScriptService: - ```python - ij.script().run('.groovy', "#@ ImageJ ij\nij.ui().showDialog('hello')", True) - ``` + + ```python + ij.script().run(".groovy", "#@ ImageJ ij\nij.ui().showDialog('hello')", True) + ``` 3. Using ImageJ’s [Script Editor](https://imagej.net/scripting/script-editor) #### 2. Starting napari + ImageJ from plain Python (without napari's Qt Console) + Here is a plain Python script that starts up Qt and spins up ImageJ without use of napari's Qt Console. -``` python +```python from PyQt5 import QtCore, QtWidgets + def main(): app = QtWidgets.QApplication([]) - app.setQuitOnLastWindowClosed( True ) + app.setQuitOnLastWindowClosed(True) def start_imagej(): import imagej + ij = imagej.init(headless=False) print(ij.getVersion()) ij.launch() @@ -123,11 +136,12 @@ def main(): QtCore.QTimer.singleShot(0, start_imagej) app.exec_() + if __name__ == "__main__": main() ``` -Note that the app.exec_() call blocks the main thread, because Qt takes it over as its GUI/main thread. On macOS, the main thread is the only thread that works for Qt to use as its GUI/main thread. +Note that the app.exec\_() call blocks the main thread, because Qt takes it over as its GUI/main thread. On macOS, the main thread is the only thread that works for Qt to use as its GUI/main thread. #### 3. Starting napari+ImageJ from IPython @@ -138,11 +152,15 @@ A code that successfully starts ImageJ from IPython ```python def start_imagej(): import imagej + global ij ij = imagej.init(headless=False) ij.ui().showUI() print(ij.getVersion()) + + from PyQt5 import QtCore + QtCore.QTimer.singleShot(0, start_imagej) ``` diff --git a/docs/howtos/perfmon.md b/docs/howtos/perfmon.md index 020328386..c1d2ec623 100644 --- a/docs/howtos/perfmon.md +++ b/docs/howtos/perfmon.md @@ -30,7 +30,6 @@ cases. This document discusses only napari's performance monitoring features. Profiling napari might be useful as well, but it is not discussed here. - ## Enabling perfmon There are two ways to enable performance monitoring. Set the environment diff --git a/docs/index.md b/docs/index.md index 33f5527b9..2f6dbaa73 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,47 +1,51 @@ ---- +______________________________________________________________________ + theme: - metaDescription: napari is a fast multi-dimensional image viewer for Python. It can help you **explore** any image-like data, be it 2D, 3D, or even higher-dimensional. It can also help you **overlay** downstream or **associated data**, such as point coordinates or segmentations, which you can use to **annotate** and **proofread** your image data. - quickLinks: - - title: Community - content: Meet the team, our mission, and our values - url: /community/index.html +metaDescription: napari is a fast multi-dimensional image viewer for Python. It can help you **explore** any image-like data, be it 2D, 3D, or even higher-dimensional. It can also help you **overlay** downstream or **associated data**, such as point coordinates or segmentations, which you can use to **annotate** and **proofread** your image data. +quickLinks: +\- title: Community +content: Meet the team, our mission, and our values +url: /community/index.html - - title: Tutorials - content: Step by step guides for common napari workflows - url: /tutorials/index.html +``` +- title: Tutorials + content: Step by step guides for common napari workflows + url: /tutorials/index.html - - title: Plugins - content: Learn how to create a plugin that works with the napari ecosystem - url: /plugins/index.html +- title: Plugins + content: Learn how to create a plugin that works with the napari ecosystem + url: /plugins/index.html - - title: Release notes - content: See what’s been updated in the latest releases - url: /release/index.html +- title: Release notes + content: See what’s been updated in the latest releases + url: /release/index.html - - title: API reference - content: Information on specific functions, classes, and methods - url: /api/index.html +- title: API reference + content: Information on specific functions, classes, and methods + url: /api/index.html - - title: Roadmaps - content: Find out what we plan to build next and into the near future - url: /roadmaps/index.html +- title: Roadmaps + content: Find out what we plan to build next and into the near future + url: /roadmaps/index.html - - title: Developer guides - content: Explanations about how napari works behind the screen - url: /guides/index.html +- title: Developer guides + content: Explanations about how napari works behind the screen + url: /guides/index.html - - title: Developer resources - content: All you need to know to contribute to the napari codebase - url: /developers/index.html +- title: Developer resources + content: All you need to know to contribute to the napari codebase + url: /developers/index.html - - title: Source code - content: Jump out to GitHub to take a look at the code that runs napari - url: https://github.com/napari/napari +- title: Source code + content: Jump out to GitHub to take a look at the code that runs napari + url: https://github.com/napari/napari - - title: napari hub - content: Discover, install, and share napari plugins - url: https://www.napari-hub.org ---- +- title: napari hub + content: Discover, install, and share napari plugins + url: https://www.napari-hub.org +``` + +______________________________________________________________________ # napari: a fast, interactive viewer for multi-dimensional images in Python @@ -64,6 +68,7 @@ theme: Napari is a Python library for n-dimensional image visualisation, annotation, and analysis. With napari you can: + - **view and explore** 2D, 3D, and higher-dimensional arrays on a canvas; - **overlay** derived data such as *points*, *polygons*, *segmentations*, and more; diff --git a/docs/naps/0-nap-process.md b/docs/naps/0-nap-process.md index 6bd84b80e..b89f91fce 100644 --- a/docs/naps/0-nap-process.md +++ b/docs/naps/0-nap-process.md @@ -27,7 +27,7 @@ dissenting opinions. Because the NAPs are maintained as text files in a versioned repository, their revision history is the historical record of the feature proposal -[^id3]. +\[^id3\]. ### Scope of NAPs @@ -121,7 +121,7 @@ Each NAP must have a champion---someone who writes the NAP using the style and format described below, shepherds the discussions in the appropriate forums, and attempts to build community consensus around the idea. The NAP champion (a.k.a. Author) should first attempt to ascertain whether the idea -is suitable for a NAP. Posting to the napari [issues list] is the best +is suitable for a NAP. Posting to the napari \[issues list\] is the best way to do this. The proposal should be submitted as a draft NAP via a [GitHub pull @@ -279,7 +279,7 @@ these cases will depend on the nature and purpose of the NAP being updated. NAPs are UTF-8 encoded text files using the [MyST markdown] format. Please see the {ref}`nap-template` file and the [MyST markdown cheat sheet] for more information. We use [Sphinx] to convert NAPs to HTML for viewing on -the web [^id4]. +the web \[^id4\]. ### Header Preamble @@ -318,12 +318,12 @@ on a separate line. ## References and Footnotes -[^id3]: This historical record is available by the normal git commands - for retrieving older revisions, and can also be browsed on - [GitHub](https://github.com/napari/docs/tree/main/docs/naps). +\[^id3\]: This historical record is available by the normal git commands +for retrieving older revisions, and can also be browsed on +[GitHub](https://github.com/napari/docs/tree/main/docs/naps). -[^id4]: The URL for viewing NAPs on the web is - +\[^id4\]: The URL for viewing NAPs on the web is + ## Acknowledgements @@ -334,11 +334,9 @@ This process was based on existing process from the scikit-image (SKIPs), NumPy This document has been placed in the public domain. -[developer forum]: https://forum.image.sc/tag/napari [github pull request]: https://github.com/napari/napari/pulls -[issue tracker]: https://github.com/napari/napari/issues -[repo]: https://github.com/napari/napari -[MyST markdown]: https://myst-parser.readthedocs.io/en/latest/index.html -[MyST markdown cheat sheet]: https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html +[myst markdown]: https://myst-parser.readthedocs.io/en/latest/index.html +[myst markdown cheat sheet]: https://myst-parser.readthedocs.io/en/latest/syntax/syntax.html [napari steering council]: https://napari.org/community/governance.html +[repo]: https://github.com/napari/napari [sphinx]: https://www.sphinx-doc.org/en/stable/ diff --git a/docs/naps/1-institutional-funding-partners.md b/docs/naps/1-institutional-funding-partners.md index 9a2fb9aee..bc3a2b7fa 100644 --- a/docs/naps/1-institutional-funding-partners.md +++ b/docs/naps/1-institutional-funding-partners.md @@ -64,6 +64,7 @@ napari as part of their official duties, or by committing significant funding to napari, as determined by the SC. This NAP proposes the following benefits for IFPs: + - acknowledgement on the napari website, and in talks about the napari project. - ability to promote their contribution on their own sites and communications. - ability to improve the project for their specific use cases via their @@ -71,7 +72,6 @@ This NAP proposes the following benefits for IFPs: - ability to provide input to the project via the IFPAC and the IFPAC-elected SC member. - ## Related Work The model of an Institutional and Funding Partner Advisory Council is inspired @@ -106,8 +106,7 @@ An initial proposal for the additions to the governance is included below: > a member elected by the Institutional and Funding Partner Advisory Council, > as detailed below. This member need not be an existing core developer. - ->### Institutional and Funding Partners +> ### Institutional and Funding Partners > > The SC is the primary leadership body for napari. No outside institution, > individual or legal entity has the ability to own or control the project @@ -213,7 +212,6 @@ Agreed, the SC can help promote this rationale. NAP-1 does not prescribe the rules for the IFP Advisory Council, but does ask them to describe their processes openly. - ## References and Footnotes - [Open Force Field Consortium @@ -224,13 +222,11 @@ processes openly. ## Copyright This document is dedicated to the public domain with the Creative Commons CC0 -license[^id3]. Attribution to this source is encouraged where appropriate, as +license\[^id3\]. Attribution to this source is encouraged where appropriate, as per CC0+BY[^id4]. -[^id3]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, +\[^id3\]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, -[^id4]: - -[^NumFOCUS-Fiscal-Sponsorship]: https://numfocus.org/projects-overview - +[^id4]: https://dancohen.org/2013/11/26/cc0-by/ +[^numfocus-fiscal-sponsorship]: https://numfocus.org/projects-overview diff --git a/docs/naps/2-conda-based-packaging.md b/docs/naps/2-conda-based-packaging.md index 3722f0ee1..2d7621b04 100644 --- a/docs/naps/2-conda-based-packaging.md +++ b/docs/naps/2-conda-based-packaging.md @@ -34,7 +34,7 @@ Briefcase [^briefcase]. Briefcase relies on PyPI packaging to assemble the installer. The PyPI packaging strategy, however, presents a series of limitations for the napari ecosystem: -* No standardized building infrastructure. PyPI accepts submissions from any user without requiring +- No standardized building infrastructure. PyPI accepts submissions from any user without requiring any validation or review. As a result, packages can be built using arbitrary toolchains or expecting different libraries in the system. `cibuildwheel` [^cibuildwheel] and related tools [^audithwheel] [^delocate] [^delvewheel] can definitely help users who want @@ -43,15 +43,15 @@ however, presents a series of limitations for the napari ecosystem: This can result in ABI incompatibilities with the target system and within the plugin ecosystem, especially when some packages vendor specific libraries [^pypi-parallelism-abi]. -* PyPI metadata is often not detailed enough. This is a byproduct of the previous point, which +- PyPI metadata is often not detailed enough. This is a byproduct of the previous point, which makes it difficult for the different clients (pip, poetry, etc) to guarantee that the resulting installation is self-consistent and all the packages involved are compatible with each other. -* PyPI is Python specific. While it's possible to find non-Python packages in the repository, +- PyPI is Python specific. While it's possible to find non-Python packages in the repository, it was not designed to do so. In scientific projects, researchers often combine packages from different languages to build their pipeline. If we want a thriving plugin ecosystem, restricting the packaging options to a language-specific repository can be limiting. -* PyPI only provides Python _packages_. It does not distribute Python itself, leaving that to the +- PyPI only provides Python _packages_. It does not distribute Python itself, leaving that to the installer infrastructure. In the case of Briefcase, this is obtained via their own distribution mechanisms [^briefcase-python]. This presents one more moving piece that can result in incompatibilities with the target system if not controlled properly @@ -59,34 +59,34 @@ however, presents a series of limitations for the napari ecosystem: In contrast, `conda`-based packaging offers some benefits in those points: -* `conda` is language agnostic. It can package things for Python and other languages, but also the +- `conda` is language agnostic. It can package things for Python and other languages, but also the language runtimes/interpreters themselves! This allows plugins to depend on compiled libraries with no Python counterparts or wrappers, or maybe even different language interpreters. -* `conda` maintains its own package metadata separate from PyPI, allowing solvers to do their +- `conda` maintains its own package metadata separate from PyPI, allowing solvers to do their work better and more efficiently. It can also be patched after a package is released, allowing corrections to be made over time without building new artifacts. -* `conda-forge` is a community-driven effort to build `conda` packages in a supervised and +- `conda-forge` is a community-driven effort to build `conda` packages in a supervised and automated way that ensures binary compatibility across packages and languages. Every submission needs to be reviewed and approved by humans after successfully passing the CI. This adds guarantees for provenance, transparency and debugging. -* `conda` has the notion of optional version constrains. A package can provide constraints for other +- `conda` has the notion of optional version constrains. A package can provide constraints for other packages that _could_ be installed alongside, without depending on them. This offers a lot of flexibility to manage a plugin ecosystem with potentially wildly different requirements, which would risk conflicts. `conda` packaging also has its own downsides compared to `pip`, though: -* pip and PyPI are the *de facto* standard in Python packaging, which means +- pip and PyPI are the *de facto* standard in Python packaging, which means more developers are aware of them and familiar with them, and how to create packages for them. Conda in contrast presents a community education challenge. -* Even with a well-developed community of practice among napari and napari +- Even with a well-developed community of practice among napari and napari plugin developers, some significant industry-provided packages, such as Apple's tensorflow-metal, may *never* be available no conda-forge. -* Although the conda-forge review process is an advantage with regards to +- Although the conda-forge review process is an advantage with regards to correctness and reliability, it presents a scalability challenge in the absence of broader community education about conda packaging. -* pip can install packages from a simple local directory, from a zip file, or +- pip can install packages from a simple local directory, from a zip file, or from a GitHub repository. These simple installation methods are favored by small labs and institutions that want to create plugins for internal use, rather than for broad distribution. @@ -187,18 +187,18 @@ option for those projects that are not (yet) available on conda-forge. Anaconda releases their Anaconda and Miniconda distributions with platform specific installers: -* On Windows, they offer an EXE built with NSIS -* On Linux, a text-based installer is offered as a fat SH script -* On macOS, a native, graphical PKG installer is provided in addition to the text-based option +- On Windows, they offer an EXE built with NSIS +- On Linux, a text-based installer is offered as a fat SH script +- On macOS, a native, graphical PKG installer is provided in addition to the text-based option These three products are created using `constructor` [^constructor], their own tool to gather the required conda dependencies and add the logic to install them on the target machine. However, `constructor` hasn't been well maintained in recent years (only small fixes), which means that some work is needed to meet our needs. More specifically: -* Application shortcut creation is only supported on Windows -* PKG installers are created with hardcoded Anaconda branding -* Some conda-specific options cannot be removed (only disabled by default), which might distract +- Application shortcut creation is only supported on Windows +- PKG installers are created with hardcoded Anaconda branding +- Some conda-specific options cannot be removed (only disabled by default), which might distract users in the installers In order to have `constructor` cover our needs, we need to add the features ourselves. Upstream @@ -207,30 +207,30 @@ a result, we are temporarily forking the project and developing the features as submitting PRs upstream [^constructor-upstream] to keep things tidy. Our improved `constructor` fork has the following features: -* Cross-platform shortcut creation for the distributed application thanks to a complete `menuinst` +- Cross-platform shortcut creation for the distributed application thanks to a complete `menuinst` [^menuinst] rewrite -* PKG installers on macOS can be customized, signed and notarized -* `constructor` can deploy more than one conda environment at once now -* Extra files can be added to the installation without having to use an extra conda package -* Improvements for license collection and analysis -* Small fixes for reported installation size, progress reports and other cosmetic details +- PKG installers on macOS can be customized, signed and notarized +- `constructor` can deploy more than one conda environment at once now +- Extra files can be added to the installation without having to use an extra conda package +- Improvements for license collection and analysis +- Small fixes for reported installation size, progress reports and other cosmetic details The resulting product now is able to install napari across operating systems with fully working shortcuts that respect the activation mechanism of `conda` environments to guarantee that all dependencies work properly. Having `constructor` distribute multiple environments at once allow us to split the installation like this: -* A `base` environment with `conda`, `mamba` and `pip` to manage the other `conda` environments. -* A `napari-X.Y.Z` environment (X.Y.Z being the version of the bundled release) with napari and +- A `base` environment with `conda`, `mamba` and `pip` to manage the other `conda` environments. +- A `napari-X.Y.Z` environment (X.Y.Z being the version of the bundled release) with napari and its dependencies. This separation allows us to: -* Handle napari updates by simply creating a fresh environment with the new version (more on this +- Handle napari updates by simply creating a fresh environment with the new version (more on this in Milestone 4). -* Apply reparations in the environment without worrying that a faulty plugin install can render +- Apply reparations in the environment without worrying that a faulty plugin install can render the whole environment non-functional. -* If necessary, adding support for "napari projects": a napari installation with a specific +- If necessary, adding support for "napari projects": a napari installation with a specific combination of plugins. Useful in the event that a user wants to use plugins that cannot be installed in the same environment due to conflicting dependencies (more details on this in Milestone 3). @@ -243,29 +243,29 @@ can be built thanks to the nightlies available on the napari channel in Anaconda napari has its own plugin manager, which so far has relied on `pip` to install packages available on PyPI. To make it compatible with conda packaging, three key changes are needed: -1. The list of packages on conda-forge does not necessarily match the one coming from PyPI. Right - now, the plugins on conda-forge are a _subset_ of those on PyPI, but this might change in the - future if some napari plugins become available on conda-forge but not PyPI due to packaging - limitations (e.g. availability of dependencies). As a result, the plugin manager needs to source - the list of plugins from a repository-agnostic source: the napari hub API. It must be noted that - napari hub currently uses PyPI as the ground truth for the list of published plugins and the - available versions. -2. Once the napari hub API is feeding the list, the plugin manager will also list those available - on conda-forge. Packages that are only available on PyPI can also be installed as long as: - * The dependencies of the PyPI package are on conda-forge - * The PyPI package is pure Python (no compiled libraries) - In the future, we might explore how to deal with PyPI packages within conda in a safer way, but - this is an open packaging question that is extremely difficult to tackle robustly. -3. Instrument the plugin manager backend so it can use `conda` or `mamba` to run the plugin - installation, update or removal. Some level of customizability is needed to configure extra - channels (e.g. a laboratory published their conda packages into their own private channel) and - local sources (e.g. drag&drop a conda tarball). -4. Add some control to the dependency landscape of the plugin ecosystem using the - `napari-pinnings` metapackage mentioned in Milestone 1. +1. The list of packages on conda-forge does not necessarily match the one coming from PyPI. Right + now, the plugins on conda-forge are a _subset_ of those on PyPI, but this might change in the + future if some napari plugins become available on conda-forge but not PyPI due to packaging + limitations (e.g. availability of dependencies). As a result, the plugin manager needs to source + the list of plugins from a repository-agnostic source: the napari hub API. It must be noted that + napari hub currently uses PyPI as the ground truth for the list of published plugins and the + available versions. +2. Once the napari hub API is feeding the list, the plugin manager will also list those available + on conda-forge. Packages that are only available on PyPI can also be installed as long as: + - The dependencies of the PyPI package are on conda-forge + - The PyPI package is pure Python (no compiled libraries) + In the future, we might explore how to deal with PyPI packages within conda in a safer way, but + this is an open packaging question that is extremely difficult to tackle robustly. +3. Instrument the plugin manager backend so it can use `conda` or `mamba` to run the plugin + installation, update or removal. Some level of customizability is needed to configure extra + channels (e.g. a laboratory published their conda packages into their own private channel) and + local sources (e.g. drag&drop a conda tarball). +4. Add some control to the dependency landscape of the plugin ecosystem using the + `napari-pinnings` metapackage mentioned in Milestone 1. There are some technical limitations we need to work out as well, namely: -* Some plugin updates might fail because some files are in use already. For example, a plugin +- Some plugin updates might fail because some files are in use already. For example, a plugin requires a more recent build of numpy (still allowed in our pinnings); however, numpy has been imported already and Windows has blocked the library files. An off-process update will be needed on Windows for the installation to succeed. On Unix systems this might not be a problem, but the @@ -274,7 +274,7 @@ There are some technical limitations we need to work out as well, namely: transaction. On Windows, we can write a one-off activation script that will run before `napari` starts the next time. On Unix systems, a notification saying "Restart needed for full effect" might be enough. -* The plugin manager was designed to install one package at a time with `pip`. We have extended it +- The plugin manager was designed to install one package at a time with `pip`. We have extended it to use `conda` or `mamba`, but it still works on a package-by-package basis. It would be preferred to offer the possibility of installing several packages together for more efficient solves, but this involves some UI/UX research first. @@ -289,7 +289,7 @@ this reason, Anaconda users are recommended to run `conda` itself to handle the Our preferred approach is to create a fresh environment for the new version of napari. The reasons are multiple: -* Performance and solving complexity: `conda` keeps track of the actions performed in the +- Performance and solving complexity: `conda` keeps track of the actions performed in the environment in a `conda-meta/history` file. The contents of this file are parsed by the solver, which prioritizes the packaged listed there in the solves. For example: if you started your environment with `conda create -n something python=3.9 numpy=1.21`, `numpy` will be _soft-pinned_ @@ -298,11 +298,11 @@ are multiple: explicitly (thus overriding the historic preference). For napari, this means that every plugin installation will be recorded in the history file, accumulating over time. If we compound this with `napari` updates, the problem gets larger with every new release. -* Guarantee of success: updating an environment to the latest napari release might not work right +- Guarantee of success: updating an environment to the latest napari release might not work right away, specially if the user has installed plugins that have conflicting packaging metadata. Even if the installation succeeds, insufficient/incorrect metadata might result in the wrong packages being installed, rendering the napari installation non-operational! -* Using several versions side-by-side: the multiple environments setup fits nicely in contexts +- Using several versions side-by-side: the multiple environments setup fits nicely in contexts where several napari versions (or even "flavors") are needed. Since we install plugins to the napari environments, we need to guarantee that the new version @@ -310,9 +310,9 @@ will be compatible with the installed plugins. To do so, we instruct `conda` to install. If the environment can be solved, we can proceed with no interruptions. If the environment is not solvable, then we can offer the user two options: -* Start a fresh environment only containing napari, with no extra plugins, keeping the old version +- Start a fresh environment only containing napari, with no extra plugins, keeping the old version in place. -* (Experimental idea / suggestion) Run a potentially time-consuming analysis on which package(s) +- (Experimental idea / suggestion) Run a potentially time-consuming analysis on which package(s) are creating the installation conflicts and suggest which plugins cannot be installed. This kind of analysis would entail a series of dry runs, removing one plugin at a time, hoping that the environment gets solved eventually. @@ -342,9 +342,9 @@ in napari itself. napari has been using Briefcase to build its installers so far, as discussed in the "Motivation and scope" section. Other alternatives we considered before choosing `conda` were: -* Adapting Briefcase to use `conda` packages, but it felt like we were rewriting `constructor` at +- Adapting Briefcase to use `conda` packages, but it felt like we were rewriting `constructor` at that point, only to get a barely working prototype. -* Freezing `napari` directly with PyOxidizer [^pyoxidizer] and Nuitka [^nuitka]. These experiments +- Freezing `napari` directly with PyOxidizer [^pyoxidizer] and Nuitka [^nuitka]. These experiments were not successful either and didn't allow for a good plugin installation story (the frozen executables are immutable). @@ -377,8 +377,8 @@ projects facing our `constructor` improvements already [^mne-constructor]. This work can be divided in two different tasks: adjusting the conda-forge feedstock for napari, and then migrating all the plugins over to conda-forge. -* [Adjust outputs for multiple qt backends and menuinst=2](https://github.com/conda-forge/napari-feedstock/pull/32) -* [Plugin migration search query](https://github.com/conda-forge/staged-recipes/pulls?q=is%3Apr+author%3Agoanpeca+created%3A2022-01-01..2022-05-01) +- [Adjust outputs for multiple qt backends and menuinst=2](https://github.com/conda-forge/napari-feedstock/pull/32) +- [Plugin migration search query](https://github.com/conda-forge/staged-recipes/pulls?q=is%3Apr+author%3Agoanpeca+created%3A2022-01-01..2022-05-01) (~200 PRs) At the time of writing, the plugin migration to conda-forge can be considered 90% done, but a long @@ -387,38 +387,37 @@ non-compliant licensing schemes or bad packaging practices. #### Tasks -* [ ] Add documentation about conda-forge in the release guide -* [ ] Add documentation about conda-forge in the plugin developer guide -* [ ] Ensure the cookiecutter template has some notion of conda packaging -* [ ] Decide which packages need to be governed by the _napari pinnings_ metapackage +- [ ] Add documentation about conda-forge in the release guide +- [ ] Add documentation about conda-forge in the plugin developer guide +- [ ] Ensure the cookiecutter template has some notion of conda packaging +- [ ] Decide which packages need to be governed by the _napari pinnings_ metapackage ### Milestone 2: Building conda-based installers for napari Implementing the `constructor` workflow on napari was mainly done in a single, long-lived PR, that has seen a couple of minor updates in the recent releases: -* Main PR: [#3378](https://github.com/napari/napari/pull/3378), superseded by +- Main PR: [#3378](https://github.com/napari/napari/pull/3378), superseded by [#3555](https://github.com/napari/napari/pull/3555) (Prototype a conda-based bundle) -* Other PRs: - * [#3462](https://github.com/napari/napari/pull/3462) (Move icons to package source) - * [#4185](https://github.com/napari/napari/pull/4185) (Add licensing page) - * [#4210](https://github.com/napari/napari/pull/4210) (Fix EULA/licensing/signing issues) - * [#4221](https://github.com/napari/napari/pull/4221) (Adjust conditions that trigger signing) - * [#4307](https://github.com/napari/napari/pull/4307) (Test installers in CI) - * [#4309](https://github.com/napari/napari/pull/4309) (Use conda-forge/napari-feedstock) - * [#4387](https://github.com/napari/napari/pull/4387) (Fix unlink errors on cleanup) - * [#4444](https://github.com/napari/napari/pull/4444) (Add versioning to installer itself) - * [#4447](https://github.com/napari/napari/pull/4447) (Use custom `.condarc` file) - * [#4525](https://github.com/napari/napari/pull/4525) (Revert to napari-versioned default paths) - +- Other PRs: + - [#3462](https://github.com/napari/napari/pull/3462) (Move icons to package source) + - [#4185](https://github.com/napari/napari/pull/4185) (Add licensing page) + - [#4210](https://github.com/napari/napari/pull/4210) (Fix EULA/licensing/signing issues) + - [#4221](https://github.com/napari/napari/pull/4221) (Adjust conditions that trigger signing) + - [#4307](https://github.com/napari/napari/pull/4307) (Test installers in CI) + - [#4309](https://github.com/napari/napari/pull/4309) (Use conda-forge/napari-feedstock) + - [#4387](https://github.com/napari/napari/pull/4387) (Fix unlink errors on cleanup) + - [#4444](https://github.com/napari/napari/pull/4444) (Add versioning to installer itself) + - [#4447](https://github.com/napari/napari/pull/4447) (Use custom `.condarc` file) + - [#4525](https://github.com/napari/napari/pull/4525) (Revert to napari-versioned default paths) Adding the missing pieces to `constructor` involves changes in four different projects: -* `conda/constructor` itself, the tool that builds the installer -* `conda/menuinst`, responsible of creating the shortcuts -* `conda/conda`, which relies on `menuinst` to delegate the shortcut creation, but this code path +- `conda/constructor` itself, the tool that builds the installer +- `conda/menuinst`, responsible of creating the shortcuts +- `conda/conda`, which relies on `menuinst` to delegate the shortcut creation, but this code path was only enabled on Windows so far -* `conda-forge/conda-standalone-feedstock`, which freezes a `conda` install along with their +- `conda-forge/conda-standalone-feedstock`, which freezes a `conda` install along with their dependencies and adds a thin layer on top to communicate with the `constructor` installer during the installation process. @@ -428,36 +427,37 @@ A complete list of features can be found in the packaging documentation [^napari #### Tasks -* [X] Create `napari/packaging` and migrate the conda bundle CI there, along with the relevant - metadata -* [ ] Add detailed documentation about how the installers are structured and how they work - internally -* [ ] Add PyPI-conda consistency checks to ensure the metadata at `napari/napari` and - `napari/packaging` match +- [x] Create `napari/packaging` and migrate the conda bundle CI there, along with the relevant + metadata +- [ ] Add detailed documentation about how the installers are structured and how they work + internally +- [ ] Add PyPI-conda consistency checks to ensure the metadata at `napari/napari` and + `napari/packaging` match ### Milestone 3: Adding support for conda packages in the plugin manager Support for conda/mamba handling of plugin installations was implemented in a base PR and then extended with different PRs: -* Initial PR: [#2943](https://github.com/napari/napari/pull/2943) (Add initial support to install +- Initial PR: [#2943](https://github.com/napari/napari/pull/2943) (Add initial support to install plugins with conda/mamba) -* Other PRs that improved and extended the functionality: - * [#3288](https://github.com/napari/napari/pull/3288) (Fix plugin updates) - * [#3369](https://github.com/napari/napari/pull/3369) (Add cancel actions to plugin manager) - * [#4074](https://github.com/napari/napari/pull/4074) (Use napari hub to list plugins) - * [#4520](https://github.com/napari/napari/pull/4520) (Rework how subprocesses are launched) +- Other PRs that improved and extended the functionality: + - [#3288](https://github.com/napari/napari/pull/3288) (Fix plugin updates) + - [#3369](https://github.com/napari/napari/pull/3369) (Add cancel actions to plugin manager) + - [#4074](https://github.com/napari/napari/pull/4074) (Use napari hub to list plugins) + - [#4520](https://github.com/napari/napari/pull/4520) (Rework how subprocesses are launched) Some more work is needed to offer full support to the plugin ecosystem, as detailed below. + #### Tasks -* [X] Make the `Installer` class [^napari-installer-classs] conda/mamba-aware -* [X] Populate the plugin listing with data obtained from the napari hub API [^napari-hub-api] -* [ ] Detect which plugins can be installed directly from conda-forge and which ones need a - combination of `conda` channels and PyPI sources -* [ ] Add support for custom conda channels and local sources in the UI -* [ ] Allow for simultaneous install of plugins. Currently multiple plugins can be selected for - install, uninstall and update, but each on of these actions, are queued and run sequentially. +- [x] Make the `Installer` class \[^napari-installer-classs\] conda/mamba-aware +- [x] Populate the plugin listing with data obtained from the napari hub API [^napari-hub-api] +- [ ] Detect which plugins can be installed directly from conda-forge and which ones need a + combination of `conda` channels and PyPI sources +- [ ] Add support for custom conda channels and local sources in the UI +- [ ] Allow for simultaneous install of plugins. Currently multiple plugins can be selected for + install, uninstall and update, but each on of these actions, are queued and run sequentially. > Note: Using both `conda` and `pip` presents certain risks that could irreversibly disrupt the > installation, so it needs to be studied very carefully. Dependencies should be @@ -485,11 +485,11 @@ for more details), but the update checks will mimic something closer to a local or, more accurately, a `napari-updater` process (name subject to change; other options include `napari-launcher` or `napari-manager`) that can be queried by a running `napari` instance. -* `napari-updater`: We will create a separate standalone package to handle updates outside of the +- `napari-updater`: We will create a separate standalone package to handle updates outside of the `napari` process. This will be designed in an application agnostic way while targeting the needs of the napari project. It will allow the user to handle updates but also manage the existing napari installations (investigate issues, remove old ones, clean expired caches, etc). -* `napari` itself: Each napari version will have a very basic notion about `napari-updater` and +- `napari` itself: Each napari version will have a very basic notion about `napari-updater` and will query for updates every time it starts, and also periodically while running. If one is found, the server will run the update on its own. In essence, all `napari` has to do is run `napari-updater` on a separate, detached process every now and then. @@ -500,15 +500,15 @@ adopted for the constructor work. #### Tasks -* [ ] Start a simple CLI tool to prototype the role of `napari-updater`, initially under the - `napari/packaging` repo. This will contain the new version detection code as well as the - logic to install the new napari environment. Note that we will need to provide a way to - distribute a "frozen" napari environment, identical to the one being bundled in the - installers, to ensure that every user gets the same napari installation regardless the - mechanism used (fresh install from downloaded executable, or updated via `napari-updater`). -* [ ] Refactor the prototype into its own separate project and evolve its feature set to satisfy - the community feedback (this might include adding a UI, environment management tools, - installation diagnostics, plugin "co-installability" analysis, etc). +- [ ] Start a simple CLI tool to prototype the role of `napari-updater`, initially under the + `napari/packaging` repo. This will contain the new version detection code as well as the + logic to install the new napari environment. Note that we will need to provide a way to + distribute a "frozen" napari environment, identical to the one being bundled in the + installers, to ensure that every user gets the same napari installation regardless the + mechanism used (fresh install from downloaded executable, or updated via `napari-updater`). +- [ ] Refactor the prototype into its own separate project and evolve its feature set to satisfy + the community feedback (this might include adding a UI, environment management tools, + installation diagnostics, plugin "co-installability" analysis, etc). ### Milestone 5: Deprecating Briefcase-based installers @@ -541,10 +541,10 @@ the future, if it makes sense, we can talk about adding a UI on top. For Milestone 4 "Enabling in-app napari version updates", we considered other options before deciding to use the currently proposed one. Namely: -* Each napari installation only contains a single conda environment and version. Users can update +- Each napari installation only contains a single conda environment and version. Users can update by downloading the newer installer, possibly after having received a notification in a running napari instance. This was discarded because it required too many user actions to succeed. -* Each napari installation contains several environments, one per napari version. Each napari +- Each napari installation contains several environments, one per napari version. Each napari version can prompt the creation of a new environment in the same base installation if a new update is available. Discarded because the update logic could change across napari versions, potentially causing issues over time; e.g. old versions are not up-to-date with the latest @@ -562,94 +562,54 @@ deciding to use the currently proposed one. Namely: ## References and Footnotes -* Napari on PyPI [^pypi-napari] -* Napari on conda-forge [^napari-feedstock] -* Installation analysis of all napari plugins [^installability-notebook] -* Scientific packaging glossary [^glossary] +- Napari on PyPI [^pypi-napari] +- Napari on conda-forge [^napari-feedstock] +- Installation analysis of all napari plugins [^installability-notebook] +- Scientific packaging glossary [^glossary] ## Copyright This document is dedicated to the public domain with the Creative Commons CC0 -license [^cc0]. Attribution to this source is encouraged where appropriate, as per +license \[^cc0\]. Attribution to this source is encouraged where appropriate, as per CC0+BY [^cc0by]. -[^staged-recipes-napari]: - -[^napari-feedstock-creation]: - -[^pypi-napari]: - -[^napari-feedstock]: - -[^briefcase]: - -[^cibuildwheel]: - -[^staged-recipes-all-plugins]: - -[^constructor]: - -[^constructor-upstream]: - -[^menuinst]: - -[^napari-channel]: - -[^briefcase-workaround-example]: - -[^briefcase-utilities-example]: - -[^pyoxidizer]: - -[^nuitka]: - -[^napari-releases-json]: - -[^mne-constructor]: - -[^appimage-crash]: - -[^appimage-crash2]: - -[^napari-packaging-docs]: - -[^napari-hub-api]: https://api.napari-hub.org/plugins - -[^vscode-extensions-ui]: https://code.visualstudio.com/docs/editor/extension-marketplace - -[^in-app-update-pr]: https://github.com/napari/napari/pull/4422 - -[^installability-notebook]: https://colab.research.google.com/drive/1QxbBZYe9-AThGuRsTfwYzT72_UkamXmk - -[^glossary]: https://jaimergp.github.io/scientific-packaging-glossary/ +\[^cc0\]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, + +[^appimage-crash2]: https://github.com/napari/napari/issues/3816 +[^audithwheel]: https://github.com/pypa/auditwheel +[^briefcase]: https://beeware.org/project/projects/tools/briefcase/ [^briefcase-python]: https://github.com/beeware?q=Python+support&type=all&language=&sort= - -[^pypi-parallelism-abi]: https://twitter.com/ralfgommers/status/1517410559972589569 - -[^release-guide]: https://napari.org/stable/developers/release.html - -[^scijava-pinnings]: https://github.com/scijava/pom-scijava/blob/ff35ca810a8717c4f461ef24df4986bf1914c673/pom.xml#L307 - -[^maxiconda]: https://github.com/Semi-ATE/maxiconda-envs - +[^briefcase-utilities-example]: https://github.com/napari/napari/blob/be43e127a079f999d830c457d6d69b7c2b56875d/napari/utils/misc.py#L49 +[^briefcase-workaround-example]: https://github.com/napari/napari/blob/be43e127a079f999d830c457d6d69b7c2b56875d/napari/plugins/__init__.py#L34-L42 +[^cc0by]: https://dancohen.org/2013/11/26/cc0-by/ +[^cibuildwheel]: https://github.com/pypa/cibuildwheel [^conda-forge-pinnings]: https://github.com/conda-forge/conda-forge-pinning-feedstock/blob/32f93dd/recipe/conda_build_config.yaml - -[^audithwheel]: https://github.com/pypa/auditwheel - +[^conda-pip-issue-comment]: https://github.com/napari/napari/issues/3223#issuecomment-972189348 +[^constructor]: https://github.com/conda/constructor +[^constructor-upstream]: https://github.com/napari/packaging/issues/15#issuecomment-1203806825 [^delocate]: https://github.com/matthew-brett/delocate - [^delvewheel]: https://github.com/adang1345/delvewheel - -[^conda-pip-issue-comment]: https://github.com/napari/napari/issues/3223#issuecomment-972189348 - +[^glossary]: https://jaimergp.github.io/scientific-packaging-glossary/ +[^in-app-update-pr]: https://github.com/napari/napari/pull/4422 +[^installability-notebook]: https://colab.research.google.com/drive/1QxbBZYe9-AThGuRsTfwYzT72_UkamXmk +[^maxiconda]: https://github.com/Semi-ATE/maxiconda-envs +[^menuinst]: https://github.com/conda/menuinst +[^mne-constructor]: https://twitter.com/mne_news/status/1506212014993162247 +[^napari-channel]: https://anaconda.org/napari/ +[^napari-feedstock]: https://github.com/conda-forge/napari-feedstock +[^napari-feedstock-creation]: https://github.com/conda-forge/napari-feedstock/commit/815a24cadb9522f4fc81a41c9eb89d45b2e284eb +[^napari-hub-api]: https://api.napari-hub.org/plugins +[^napari-packaging-docs]: https://napari.org/stable/developers/packaging.html +[^napari-releases-json]: https://api.github.com/repos/napari/napari/releases +[^nuitka]: https://nuitka.net/ [^pamba]: https://github.com/tlambert03/pamba - -[^napari-installer-class]: https://github.com/napari/napari/blob/5c10022337601f350ad64ce56eddf6664306e40e/napari/_qt/dialogs/qt_plugin_dialog.py#L64 - -[^cc0]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, - - -[^cc0by]: +[^pyoxidizer]: https://pyoxidizer.readthedocs.io/en/stable/ +[^pypi-napari]: https://pypi.org/project/napari/ +[^pypi-parallelism-abi]: https://twitter.com/ralfgommers/status/1517410559972589569 +[^release-guide]: https://napari.org/stable/developers/release.html +[^scijava-pinnings]: https://github.com/scijava/pom-scijava/blob/ff35ca810a8717c4f461ef24df4986bf1914c673/pom.xml#L307 +[^staged-recipes-all-plugins]: https://github.com/conda-forge/staged-recipes/pulls?q=is%3Apr+author%3Agoanpeca+created%3A2022-01-01..2022-05-01 +[^staged-recipes-napari]: https://github.com/conda-forge/staged-recipes/pull/9983 diff --git a/docs/naps/3-spaces.md b/docs/naps/3-spaces.md index f07375bb3..ec755bc9c 100644 --- a/docs/naps/3-spaces.md +++ b/docs/naps/3-spaces.md @@ -9,19 +9,18 @@ :Type: Standards Track ``` - ## Abstract `napari` is currently limited to holding (and rendering) data belonging to a single, universal space (which we often refer to as *world space*). However, it is often useful to have quick and easy access to different parts of a dataset that do not belong to the same coordinate system. In these cases, forcing data to live in the same world not only makes no sense, but it can make navigating the data and interacting with the viewer slower and less intuitive. This NAP discusses the reasons why a native napari approach would be better than the currently available workarounds. It then proposes the introduction of `spaces` as a way to manage different coordinate spaces in the same viewer. - ## Motivation and scope This NAP aims to address a few problems that arise (especially with big datasets), when working with many layers that do not necessarily belong to the same *absolute* coordinate space. For example, there is no reason to relate between the *absolute* coordinates of `image 1` and `image 2` from the same microscopy data collection, but it might be useful to quickly switch between the two to -- for example -- compare the effectiveness of a processing step on different images, or to visually inspect qualitative differences. Currently, to do so, a user is forced to either: + - load everything into the layer list (thus "pretending" that the absolute coordinates do indeed match) and then develop a plugin or widget that manages layer visibility - or develop a plugin or widget that manages layers externally and feeds them to `Viewer.layers` as desired - do nothing and deal with hundreds of layers manually @@ -29,6 +28,7 @@ Currently, to do so, a user is forced to either: While this might not seem problematic with few images, it quickly degenerates when working with big datasets, especially in 3D (for an example use case, check out [this issue comment](https://github.com/napari/napari/issues/4419#issuecomment-1113090992)). The above workarounds have the following issues: + - *usability*: adding too many layers to the viewer makes navigating the layerlist unwieldy, a problem that cannot be solved by layer-groups when there is no reasonable way to group layers - *usability*: forcing the creation of a plugin or widget with custom logic is one more barrier for non-developers, and possibly one more meta-object (e.g: a "dataset") that the user has to juggle around and that napari is unaware of. - *performance*: many layers perform worse than few layers. This might be addressed separately, but is currently unresolved. @@ -50,22 +50,26 @@ There are a few ways to tackle this issue (see [](nap-3:alternatives)), with dif ### API `ViewerModel.spaces` would be a (selectable) `EventedList` or similar evented collection, each containing a `Space` object, with the following attributes: + - `layers`: a `layerlist` (or top-level `layergroup`, in the future) - `camera`: a snapshot of the state of the `Camera` model (i.e: `Camera.dict()`) - `dims`: a snapshot of the state of the `Dims` model (i.e: `Dims.dict()`) Additionally, it would be useful and intuitive for users to mirror some of the basic `Layer` API: + - `name`: a unique name - `metadata`: to hold any extra information about the `Space` (for example, experimental conditions, or workflow description) - `source`: to hold the source `path` and reader plugin, in case a plugin [generated the space](nap-3:plugins). A few more `ViewerModel` attributes are worth considering for being tracked by `State`s, depending on if we think they should be considered "global" settings, or local to the `Space`. I propose the following: + - `grid`: local, if you leave a state in grid mode, you probably want it back that way - `scale_bar`: global - `text_overlay`: local, as text is likely to refer to the contents of the viewer - `overlays`: tricky; this is currently only the `InteractionBox` (and therefore should probably *not* be serialized, since it's dynamically changed when transforming layers), but might become more complicated in the future, if something like [napari/napari#3763](https://github.com/napari/napari/pull/3763) gets merged). The remaining fields should probably not be serialized: + - `cursor` - `help` - `status` @@ -75,24 +79,25 @@ The remaining fields should probably not be serialized: In the end, all `spaces` would do is effectively provide a quick way to swap some of the `ViewerModel` state in and out. ---- +______________________________________________________________________ At the level of `ViewerModel` itself, we would have an `active_space` attribute: similar to how a layer in a `layerlist` can be active, a `Space` in the `spaces` can be active (with the important distinction that only *one* space can be active); this will determine which space is loaded into the layerlist and used to populate the canvas. - - in a future with multi-canvas (or multi-viewer), this could be on a per-canvas (or per-viewer) basis. +\- in a future with multi-canvas (or multi-viewer), this could be on a per-canvas (or per-viewer) basis. ### GUI The GUI could expose this as a (searchable) dropdown menu above the layerlist, and provide shortcuts to navigate easility through spaces, such as `page-up`, `page-down`. (nap-3:plugins)= + ### Plugins Reader/writer plugins should be able to provide/consume spaces. If unspecified, they should act on the active `Space`, which would be backwards compatible. Widget plugins would be backwards compatible, as they simply act on the active `Space`. On the other hand, new plugins would be able to access other spaces as well, allowing for easier abstraction of "batch" workflows. - (nap-3:alternatives)= + ## Alternatives ### Naming @@ -103,6 +108,7 @@ Instead of `Space`, we could use a different name: - `State`: better conveys that non only layerlist state is retained. A bit generic. (nap-3:multiple-viewers)= + ### Multiple viewers, `app` interface These problems could be also solved by allowing multiple `Viewer` objects, each with its own `ViewerModel`, by separating out the `QtViewer` logic to an `Application` level [^application]. @@ -139,7 +145,6 @@ A downside is that we lose the single point of truth for the `Spaces`. If a spac These changes should be backwards compatible, since they would only expand the `ViewerModel` API by adding spaces. - ## Future work ### Tabbed access @@ -147,31 +152,26 @@ These changes should be backwards compatible, since they would only expand the ` As part of the "multiple viewers" proposals in the past, the idea of accessing them through tabs in the GUI was often floated [^multiple-viewers-tabbed]. The same idea can be applied to `spaces`. This is not necessarily mutually exclusive with the searchable dropdown approach: a space could be "pinned", allowing easier access through the GUI. However, the primary access should not be tabs, which would the defeat one of the goals of `spaces`: de-cluttering the GUI. (nap-3:multicanvas)= + ### Multicanvas While multicanvas is still some ways off, this NAP can provide the basis for that functionality. For example, a future multicanvas-capable viewer could associate each `Canvas` to a `Space`; this way, we already have the machinery for multiple layer lists, as well as the ability to re-use the same layer in multiple canvases, while having a different `Camera` and `Dims` setup for the different copies (note that this would rely on the current efforts in separating the slicing logic from the `Layer` object). - ## References For the original discussion, see [napari/napari#4419](https://github.com/napari/napari/issues/4419). - ## Copyright This document is dedicated to the public domain with the Creative Commons CC0 -license [^cc0]. Attribution to this source is encouraged where appropriate, as per +license \[^cc0\]. Attribution to this source is encouraged where appropriate, as per CC0+BY [^cc0by]. -[^workspaces]: https://github.com/napari/napari/issues/4419#issuecomment-1126375339 +\[^cc0\]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, + [^application]: https://github.com/napari/napari/issues/4419#issuecomment-1129443846 - +[^cc0by]: https://dancohen.org/2013/11/26/cc0-by/ [^multiple-viewers]: https://github.com/napari/napari/issues/3955 - [^multiple-viewers-tabbed]: https://github.com/napari/napari/issues/3956 - -[^cc0]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, - - -[^cc0by]: +[^workspaces]: https://github.com/napari/napari/issues/4419#issuecomment-1126375339 diff --git a/docs/naps/4-async-slicing.md b/docs/naps/4-async-slicing.md index 9d1a15ac8..9b0172c5a 100644 --- a/docs/naps/4-async-slicing.md +++ b/docs/naps/4-async-slicing.md @@ -31,7 +31,6 @@ so they can be safely updated. This approach allows us to gradually introduce asynchronous slicing to napari layer by layer without breaking other functionality that relies on existing layer slice state. - ## Motivation and scope Currently, all slicing in napari is performed synchronously. @@ -55,9 +54,9 @@ Introducing asynchrony to software often complicates program flow and logic. However, the existing technical design of slicing is already complicated and often causes issues for developers. -- Layers have too much state [^issue-792] [^issue-1353] [^issue-1775]. -- The logic is hard to understand and debug [^issue-2156]. -- The names of the classes are not self-explanatory [^issue-1574]. +- Layers have too much state \[^issue-792\] \[^issue-1353\] \[^issue-1775\]. +- The logic is hard to understand and debug \[^issue-2156\]. +- The names of the classes are not self-explanatory \[^issue-1574\]. Some of these issues were caused by a previous effort around asynchronous slicing in an effort to keep it isolated from the core code base. @@ -72,89 +71,83 @@ Each goal has many prioritized features where P0 is a must-have, P1 is a should- and P2 is a could-have. Some of these goals may already be achieved by napari in its current form, but are captured here to prevent any regression caused by this work. - #### 1. Keep responding when slicing slow data - P0. When moving a dimension slider, the slider can still be controlled so that I can navigate to the desired location. - - Slider can be moved when data is in the middle of loading. - - Slider location does not return to position of last loaded slice after it was moved to a different position. + - Slider can be moved when data is in the middle of loading. + - Slider location does not return to position of last loaded slice after it was moved to a different position. - P0. When the slider is dragged, only slice at some positions so that I don’t wait for unwanted intermediate slicing positions. - - Once slider is moved, wait before performing slicing operation, and cancel any prior pending slices (i.e. be lazy). - - If we can reliably infer that slicing will be fast (e.g. data is a numpy array), consider skipping this delay. + - Once slider is moved, wait before performing slicing operation, and cancel any prior pending slices (i.e. be lazy). + - If we can reliably infer that slicing will be fast (e.g. data is a numpy array), consider skipping this delay. - P0. When slicing fails, I am notified so that I can understand what went wrong. - - May want to limit the number of notifications (e.g. lost network connection for remote data). + - May want to limit the number of notifications (e.g. lost network connection for remote data). - P1. When moving a dimension slider and the slice doesn’t immediately load, I am notified that it is being generated, so that I am aware that my action is being handled. - - Need a visual cue that a slice is loading. - - Show visual cue to identify the specific layer(s) that are loading in the case where one layer loads faster than another. - + - Need a visual cue that a slice is loading. + - Show visual cue to identify the specific layer(s) that are loading in the case where one layer loads faster than another. #### 2. Clean up slice state and logic in layers - P0. Encapsulate the slice input and output state for each layer type, so that I can quickly and clearly identify those. - - Minimize number of (nested) classes per layer-type (e.g. `ImageSlice`, `ImageSliceData`, `ImageView`, `ImageLoader`). + - Minimize number of (nested) classes per layer-type (e.g. `ImageSlice`, `ImageSliceData`, `ImageView`, `ImageLoader`). - P0. Simplify the program flow of slicing, so that developing and debugging against allows for faster implementation. - - Reduce the complexity of the call stack associated with slicing a layer. - - The implementation details for some layer/data types might be complex (e.g. multi-scale image), but the high level logic should be simple. + - Reduce the complexity of the call stack associated with slicing a layer. + - The implementation details for some layer/data types might be complex (e.g. multi-scale image), but the high level logic should be simple. - P1. Move the slice state off the layer, so that its attributes only represent the whole data. - - Layer may still have a function to get a slice. - - May need alternatives to access currently private state, though doesn't necessarily need to be in the Layer (e.g. a plugin with an ND layer, that gets interaction data from 3D visualization , needs some way to get that data back to ND). + - Layer may still have a function to get a slice. + - May need alternatives to access currently private state, though doesn't necessarily need to be in the Layer (e.g. a plugin with an ND layer, that gets interaction data from 3D visualization , needs some way to get that data back to ND). - P2. Store multiple slices associated with each layer, so that I can cache previously generated slices. - - Pick a default cache size that should not strain most machines (e.g. 0-1GB). - - Make cache size a user defined preference. - + - Pick a default cache size that should not strain most machines (e.g. 0-1GB). + - Make cache size a user defined preference. #### 3. Measure slicing latencies on representative examples - P0. Define representative examples that currently cause *desirable* behavior in napari, so that I can check that async slicing does not degrade those. - - E.g. 2D slice of a 3D image layer where all data fits in RAM, but not VRAM. + - E.g. 2D slice of a 3D image layer where all data fits in RAM, but not VRAM. - P0. Define representative examples that currently cause *undesirable* behavior in napari, so that I can check that async slicing improves those. - - E.g. 2D slice of a 3D points layer where all data fits in RAM, but not VRAM. - - E.g. 2D slice of a 3D image layer where all data is not on local storage. + - E.g. 2D slice of a 3D points layer where all data fits in RAM, but not VRAM. + - E.g. 2D slice of a 3D image layer where all data is not on local storage. - P0. Define slicing benchmarks, so that I can understand if my changes impact overall timing or memory usage. - - E.g. Do not increase the latency of generating a single slice more than 10%. - - E.g. Decrease the latency of dealing with 25 slice requests over 1 second by 50%. + - E.g. Do not increase the latency of generating a single slice more than 10%. + - E.g. Decrease the latency of dealing with 25 slice requests over 1 second by 50%. - P1. Log specific slicing latencies, so that I can summarize important measurements beyond granular profile timings. - - Latency logs are local only (i.e. not sent/stored remotely). - - Add an easy way for users to enable writing these latency measurements. - + - Latency logs are local only (i.e. not sent/stored remotely). + - Add an easy way for users to enable writing these latency measurements. ### Non-goals To help clarify the scope, we also define some things that were are not explicit goals of this project and briefly explain why they were omitted. - Make a single slicing operation faster. - - Useful, but can be done independently of this work. + - Useful, but can be done independently of this work. - Improve slicing functionality. - - Useful, but can be done independently of this work. + - Useful, but can be done independently of this work. - Make async a toggleable setting (i.e. async is always on). - - May complicate the program flow of slicing. - - May still automatically infer that slicing can be synchronously. + - May complicate the program flow of slicing. + - May still automatically infer that slicing can be synchronously. - When a slice doesn’t immediately load, show a low level of detail version of it, so that I can preview what is upcoming. - - Requires a low level of detail version to exist. - - Should be part of a to-be-defined multi-scale project. + - Requires a low level of detail version to exist. + - Should be part of a to-be-defined multi-scale project. - Store multiple slices associated with each layer, so that I can easily implement a multi-canvas mode for napari. - - Should be part of a to-be-defined multi-canvas project. - - Solutions for goal (2) should not block this in the future. + - Should be part of a to-be-defined multi-canvas project. + - Solutions for goal (2) should not block this in the future. - Open, save, or process layers asynchronously. - - More related to plugin execution. + - More related to plugin execution. - Lazily load parts of data based on the canvas' current field of view. - - An optimization that is dependent on specific data formats. + - An optimization that is dependent on specific data formats. - Identify and assign dimensions to layers and transforms. - - Should be part of a to-be-defined dimensions project. - - Solutions for goal (2) should not block this in the future. + - Should be part of a to-be-defined dimensions project. + - Solutions for goal (2) should not block this in the future. - Thick slices of non-visualized dimensions. - - Currently being prototyped in [^pull-4334]. - - Solutions for goal (2) should not block this in the future. + - Currently being prototyped in \[^pull-4334\]. + - Solutions for goal (2) should not block this in the future. - Keep the experimental async fork working. - - Nice to have, but should not put too much effort into this. - - May want to remove it to avoid confusion. - + - Nice to have, but should not put too much effort into this. + - May want to remove it to avoid confusion. ## Related work As this project focuses on re-designing slicing in napari, this section contains information on how slicing in napari currently works. - ### Existing slice logic The following diagram shows the simplified call sequence generated by moving the position of a dimension slider in napari. @@ -183,7 +176,7 @@ which gets the new sliced data in the layer, may post-process it, and finally pa It's important to understand what state is currently used by and generated by slicing because solutions for this project may cause this state to be read and write from multiple threads. -Rather than exhaustively list all of the slice state of all layer types and their dependencies [^slice-class-diagram], +Rather than exhaustively list all of the slice state of all layer types and their dependencies \[^slice-class-diagram\], we group and highlight some of the more important state. #### Input state @@ -205,26 +198,26 @@ Other input state comes from more permanent and public properties of `Layer`, wh Finally, there are some layer-type specific properties that are used to customize the visualized slice. - `_ImageBase` - - `rgb: bool` True if the leading dimension contains RGB(A) channels that should be visualized as such. + - `rgb: bool` True if the leading dimension contains RGB(A) channels that should be visualized as such. - `Points` - - `face_color, edge_color: Array[float, (N, 4)]`, the face and edge colors of each point. - - `size: Array[float, (N, D)]`, the size of each point. - - `edge_width: Array[float, (N,)]`, the width of each point's edge. - - `shown: Array[bool, (N,)]`, the visibility of each point. - - `out_of_slice_display: bool`, if True some points may be included in more than one slice based on their size. + - `face_color, edge_color: Array[float, (N, 4)]`, the face and edge colors of each point. + - `size: Array[float, (N, D)]`, the size of each point. + - `edge_width: Array[float, (N,)]`, the width of each point's edge. + - `shown: Array[bool, (N,)]`, the visibility of each point. + - `out_of_slice_display: bool`, if True some points may be included in more than one slice based on their size. - `Shapes` - - `face_color, edge_color: Array[float, (N, 4)]`, the face and edge colors of each shape. - - `edge_width: Array[float, (N,)]`, the width of shape's edges. - - `_data_view: ShapeList`, stores all shapes' data. - - `_mesh: Mesh`, stores concatenated meshes of all shapes. - - `vertices: Array[float, (Q, D)]`, the vertices of all shapes. + - `face_color, edge_color: Array[float, (N, 4)]`, the face and edge colors of each shape. + - `edge_width: Array[float, (N,)]`, the width of shape's edges. + - `_data_view: ShapeList`, stores all shapes' data. + - `_mesh: Mesh`, stores concatenated meshes of all shapes. + - `vertices: Array[float, (Q, D)]`, the vertices of all shapes. - `Vectors` - - `edge_color: Array[float, (N,)]`, the color of each vector. - - `edge_width: Array[float, (N,)]`, the width of each vector. - - `length: numeric`, multiplicative length scaling all vectors. - - `out_of_slice_display: bool`, if True some vectors may be included in more than one slice based on their length. - - `_mesh_vertices`, output from `generate_vector_meshes`. - - `_mesh_triangles`, output from `generate_vector_meshes`. + - `edge_color: Array[float, (N,)]`, the color of each vector. + - `edge_width: Array[float, (N,)]`, the width of each vector. + - `length: numeric`, multiplicative length scaling all vectors. + - `out_of_slice_display: bool`, if True some vectors may be included in more than one slice based on their length. + - `_mesh_vertices`, output from `generate_vector_meshes`. + - `_mesh_triangles`, output from `generate_vector_meshes`. Many of these are just indexed as part of slicing. For example, `Points.face_color` is indexed by the points that are visible in the current slice. @@ -235,20 +228,20 @@ The output of slicing is typically layer-type specific, stored as state on the layer, and mostly consumed by the corresponding vispy layer. - `_ImageBase` - - `_slice: ImageSlice`, contains a loader, and the sliced image and thumbnail - - much complexity encapsulated here and other related classes like `ImageSliceData`. + - `_slice: ImageSlice`, contains a loader, and the sliced image and thumbnail + - much complexity encapsulated here and other related classes like `ImageSliceData`. - `Points` - - `__indices_view: Array[int, (M,)]`, indices of points (i.e. rows of `data`) that are in the current slice. - - many private properties derived from this (e.g. `_indices_view`, `_view_data`). - - `_view_size_scale: Union[float, Array[float, (M,)]]`, used with thick slices of points `_view_size` to make out of slice points appear smaller. + - `__indices_view: Array[int, (M,)]`, indices of points (i.e. rows of `data`) that are in the current slice. + - many private properties derived from this (e.g. `_indices_view`, `_view_data`). + - `_view_size_scale: Union[float, Array[float, (M,)]]`, used with thick slices of points `_view_size` to make out of slice points appear smaller. - `Shapes` - - `_data_view: ShapeList`, stores all shapes' data. - - `_mesh: Mesh`, stores concatenated meshes of all shapes. - - `displayed_triangles: Array[int, (M, 3)]`, triangles to be drawn. - - `displayed_triangles_colors: Array[float, (M, 4)]`, per triangle color. + - `_data_view: ShapeList`, stores all shapes' data. + - `_mesh: Mesh`, stores concatenated meshes of all shapes. + - `displayed_triangles: Array[int, (M, 3)]`, triangles to be drawn. + - `displayed_triangles_colors: Array[float, (M, 4)]`, per triangle color. - `Vectors` - - `_view_indices: Array[int, (-1)]`, indices of vectors that are in the current slice. - - lots of private properties derived from this (e.g. `_view_face_color`, `_view_faces`). + - `_view_indices: Array[int, (-1)]`, indices of vectors that are in the current slice. + - lots of private properties derived from this (e.g. `_view_face_color`, `_view_faces`). The vispy layers also read other state from their corresponding layer. In particular they read `Layer._transforms` to produce a transform that can be used to properly @@ -340,6 +333,7 @@ class PointsSliceRequest(LayerSliceRequest): edge_color: np.ndarray edge_width: np.ndarray + class PointsSliceResponse(LayerSliceResponse): indices: np.ndarray size: np.ndarray @@ -357,7 +351,6 @@ For example, we may want to generate point face colors lazily, which could mean that `PointsSliceRequest.face_color` would become a callable instead of a materialized array as in the response. - ### Layer methods We require that each Layer type implements two methods related to slicing. @@ -515,16 +508,15 @@ The other reason is because `QtViewer` is a `QObject` that lives in the main thread, which makes it easier to ensure that the slice response is handled on the main thread. That's useful because Qt widgets and vispy nodes can only be safely updated -on the main thread [^vispy-faq-threads], both of which occur when consuming +on the main thread \[^vispy-faq-threads\], both of which occur when consuming slice output. - ## Implementation -A tracking issue [^tracking-issue] serves as a way to communicate high level +A tracking issue \[^tracking-issue\] serves as a way to communicate high level progress and discussion. -A prototype [^prototype-pr] has been created that implements the approach described +A prototype \[^prototype-pr\] has been created that implements the approach described above to show its basic technical feasibility in napari's existing code base. It only implements it for some layer types, some slicing operations, and may not work when layer data is mutated. @@ -577,7 +569,6 @@ In order to do this, we plan to encapsulate the input and output state of each s private dataclasses. There are no API changes, but this forces any read/write access of this state to acquire an associated lock. - ## Future work ### Render each slice as soon as it is ready @@ -587,7 +578,7 @@ it emits the `slice_ready` event. There are a few reasons for that. 1. We only use one slicing thread to keep behavior simple and to avoid GIL contention. 2. It's closer to the existing behavior of napari -3. Shouldn't introduce any new potential bugs, such as [^issue-2862]. +3. Shouldn't introduce any new potential bugs, such as \[^issue-2862\]. 4. It needs less UX design work to decide what should be shown while we are waiting for slices to be ready. In some cases, rendering slices as soon as possible will provide a better user experience, @@ -604,7 +595,6 @@ To implement this behavior as a small extension to this proposal, we could do so This would cause a [more complex call sequence](https://raw.githubusercontent.com/andy-sweet/napari-diagrams/main/napari-slicing-async-calls-asap.drawio.svg) and could make cancellation behavior more complex, but may be worth it regardless. - ## Alternatives ### Extend the existing experimental async code @@ -652,9 +642,9 @@ Instead of making the `ViewerModel` the driver of slicing, we could instead driv A rough implementation of this approach could look like the following. ```python - class QtViewer: ... + def __init__(self): ... self.viewer.dims.current_step.connect(self.slice_layers) @@ -663,15 +653,20 @@ class QtViewer: for layer in self.viewer.layers: self.layer_to_visual[layer]._slice(self.viewer.dims) + class Image: ... + def _make_slice_request(self, dims: Dims) -> ImageSliceRequest: ... + def _get_slice(self, request: ImageSliceRequest) -> ImageSliceResponse: ... + class VispyImageLayer: ... + def _slice(self, dims: Dims) -> None: request = self.layer._make_slice_request(dims) task = self.slice_executor.submit(self.layer._get_slice, request) @@ -702,97 +697,97 @@ updating all the vispy layers. Another disadvantage is that this implies that slice state should not live in the model in the future, which might cause issues with things like selection that may depend on that state. - ## Discussion - [Initial announcement on Zulip](https://napari.zulipchat.com/#narrow/stream/296574-working-group-architecture/topic/Async.20slicing.20project). - - Consider (re)sampling instead of slicing as the name for the operation discussed here. + - Consider (re)sampling instead of slicing as the name for the operation discussed here. - [Problems with `NAPARI_ASYNC=1`](https://forum.image.sc/t/even-with-napari-async-1-data-loading-is-blocking-the-ui-thread/68097/4) - - The existing experimental async code doesn't handle some seemingly simple usage. + - The existing experimental async code doesn't handle some seemingly simple usage. - [Remove slice state from layer](https://github.com/napari/napari/issues/4682) - - Feature request to remove slice/render state from the layer types. - - Motivated by creating multiple slices of the same layers for multiple canvases. - - Decision: postpone. - - There are too many behaviors that depend on slice state (e.g. `Layer._get_value`) and it's easy enough to set it after an async slicing task is done, so leave it for now. - - This work should simplify removing it in the future. + - Feature request to remove slice/render state from the layer types. + - Motivated by creating multiple slices of the same layers for multiple canvases. + - Decision: postpone. + - There are too many behaviors that depend on slice state (e.g. `Layer._get_value`) and it's easy enough to set it after an async slicing task is done, so leave it for now. + - This work should simplify removing it in the future. - [Draft PR on andy-sweet's fork](https://github.com/andy-sweet/napari/pull/16) - - Initial feedback before sharing more widely. + - Initial feedback before sharing more widely. - Define a class that encapsulates the slicing bounding box in world coordinates. - - [Define a class that only encapsulates the dims and camera info needed for slicing](https://github.com/napari/napari/pull/4892/files#r935771292) - - [Replace `point` and similar with `bbox`](https://github.com/napari/napari/pull/4892/files#r935877117) - - The idea here is to collapse some of the existing input slicing state like `point` and `dims_displayed` into one class that describes the bounding box for slicing in world coordinates. - - Decision: postpone. - - This can be done independently of this work and the order is not important enough to slow this work down. + - [Define a class that only encapsulates the dims and camera info needed for slicing](https://github.com/napari/napari/pull/4892/files#r935771292) + - [Replace `point` and similar with `bbox`](https://github.com/napari/napari/pull/4892/files#r935877117) + - The idea here is to collapse some of the existing input slicing state like `point` and `dims_displayed` into one class that describes the bounding box for slicing in world coordinates. + - Decision: postpone. + - This can be done independently of this work and the order is not important enough to slow this work down. - [Replace layer slice request and response types with a single slice type](https://github.com/napari/napari/pull/4892/files#r935773848) - - Motivation is make it easier to add a new layer type by reducing the number of class types needed. - - Some disagreement here as one type for two purposes (input vs. output) seems confusing. - - Keeping the request and response types separate may also allow us to better target alternatives to vispy. - - Decision: keep request and response types separate for now. - - Adding a new layer type is not a frequent occurrence. - - Having one class may reduce the number of classes, but implementing a new layer type is already quite complex. + - Motivation is make it easier to add a new layer type by reducing the number of class types needed. + - Some disagreement here as one type for two purposes (input vs. output) seems confusing. + - Keeping the request and response types separate may also allow us to better target alternatives to vispy. + - Decision: keep request and response types separate for now. + - Adding a new layer type is not a frequent occurrence. + - Having one class may reduce the number of classes, but implementing a new layer type is already quite complex. - [Slicing in a shader](https://github.com/napari/napari/pull/4892/files#r935322222) - - How to handle future cases where layer data has been pre-loaded onto the GPU and we want to slice in the shader? - - Less about large/slow data, and more about not blocking approaches to handling small/fast data efficiently. - - May be particularly useful for multi-canvas. - - Decision: postpone. - - Keep related types private or vague to not blocking this in the future. + - How to handle future cases where layer data has been pre-loaded onto the GPU and we want to slice in the shader? + - Less about large/slow data, and more about not blocking approaches to handling small/fast data efficiently. + - May be particularly useful for multi-canvas. + - Decision: postpone. + - Keep related types private or vague to not blocking this in the future. - [Support existing usage and plugins that depends on synchronous slicing](https://github.com/napari/napari/pull/4892/files#r934961214) - - Main example is [the napari-animation plugin](https://www.napari-hub.org/plugins/napari-animation), but there may be others. - - This was explored [^pull-4969] with the following main findings. - - Forcing synchronous slicing when combining the prototype and `napari-animation` seems to work for the basic examples. - - We only need to ensure synchronous slicing before using `Viewer.screenshot`, as keyframes just capture the value of `current_step` and similar. - - `screenshot` is only called a few times in `napari-animation`, so wouldn't require large changes if asynchronous slicing was the default. - - Decision: always have some way to force synchronous slicing. - - But no need to support synchronous slicing by default, which is harder to implement. + - Main example is [the napari-animation plugin](https://www.napari-hub.org/plugins/napari-animation), but there may be others. + - This was explored \[^pull-4969\] with the following main findings. + - Forcing synchronous slicing when combining the prototype and `napari-animation` seems to work for the basic examples. + - We only need to ensure synchronous slicing before using `Viewer.screenshot`, as keyframes just capture the value of `current_step` and similar. + - `screenshot` is only called a few times in `napari-animation`, so wouldn't require large changes if asynchronous slicing was the default. + - Decision: always have some way to force synchronous slicing. + - But no need to support synchronous slicing by default, which is harder to implement. - [What should `Viewer.screenshot` do when the canvas is not fully rendered?](https://github.com/napari/napari/pull/5052) - - Initial consensus is that it should wait for pending slicing tasks and for the associated pushes to vispy. - - There are some ideas of how to implement that, though it is not straightforward. - - In the API, may want a keyword argument to control behavior. In the GUI, could have a dialog if there are pending tasks. + - Initial consensus is that it should wait for pending slicing tasks and for the associated pushes to vispy. + - There are some ideas of how to implement that, though it is not straightforward. + - In the API, may want a keyword argument to control behavior. In the GUI, could have a dialog if there are pending tasks. = Decision: want to support both, with blocking behavior as default. - - No need for a design or prototype before acceptance. + - No need for a design or prototype before acceptance. - Should `Dims.current_step` (and `corner_pixels`) represent the last slice position request or the last slice response? - - With sync slicing, there is no distinction. - - With async slicing, `current` is ambiguous. - - [Initial small consensus around last request](https://github.com/napari/napari/pull/4892/files#r935336654) - - Decision: last request. - - The implementation is simpler if this represents the last request. - - Can introduce an additional public attribute later if last response is needed. + - With sync slicing, there is no distinction. + - With async slicing, `current` is ambiguous. + - [Initial small consensus around last request](https://github.com/napari/napari/pull/4892/files#r935336654) + - Decision: last request. + - The implementation is simpler if this represents the last request. + - Can introduce an additional public attribute later if last response is needed. ## References and footnotes All NAPs should be declared as dedicated to the public domain with the CC0 -license [^cc0], as in `Copyright`, below, with attribution encouraged with -CC0+BY [^cc0-by]. +license \[^cc0\], as in `Copyright`, below, with attribution encouraged with +CC0+BY \[^cc0-by\]. + +\[^cc0\]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, -[^cc0]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, +\[^cc0-by\]: CO0+BY, -[^cc0-by]: CO0+BY, +\[^issue-792\]: napari issue 792, -[^issue-792]: napari issue 792, +\[^issue-1353\]: napari issue 1353, -[^issue-1353]: napari issue 1353, +\[^issue-1574\]: napari issue 1574, -[^issue-1574]: napari issue 1574, +\[^issue-1775\]: napari issue 1775, -[^issue-1775]: napari issue 1775, +\[^issue-2156\]: napari issue 2156, -[^issue-2156]: napari issue 2156, +\[^issue-2862\]: napari issue 2862, -[^issue-2862]: napari issue 2862, +\[^pull-4334\]: napari pull request 4334, -[^pull-4334]: napari pull request 4334, +\[^pull-4969\]: napari pull request 4969, -[^pull-4969]: napari pull request 4969, +\[^vispy-faq-threads\]: Vispy FAQs: Is VisPy multi-threaded or thread-safe?, -[^vispy-faq-threads]: Vispy FAQs: Is VisPy multi-threaded or thread-safe?, +\[^slice-class-diagram\]: napari slicing class and state dependency diagram, -[^slice-class-diagram]: napari slicing class and state dependency diagram, +\[^prototype-pr\]: A PR that implements a prototype for the approach described here, -[^prototype-pr]: A PR that implements a prototype for the approach described here, +\[^tracking-issue\]: The tracking issue for this work, -[^tracking-issue]: The tracking issue for this work, ## Copyright This document is dedicated to the public domain with the Creative Commons CC0 -license [^cc0]. Attribution to this source is encouraged where appropriate, as per -CC0+BY [^cc0-by]. +license \[^cc0\]. Attribution to this source is encouraged where appropriate, as per +CC0+BY \[^cc0-by\]. diff --git a/docs/naps/5-new-logo.md b/docs/naps/5-new-logo.md index c18c2f10c..267a62222 100644 --- a/docs/naps/5-new-logo.md +++ b/docs/naps/5-new-logo.md @@ -46,14 +46,14 @@ quite beautiful and has the added bonus of looking a bit like a dividing cell. However, it goes much further in the styling. The proposed logo draws the two "lobes" of the island as two touching circles, -with the radius of the smaller (northwest) circle being a factor of $\phi$ -smaller than the larger one, where $\phi = \frac{1 + \sqrt{5}}{2}$ denotes the +with the radius of the smaller (northwest) circle being a factor of $\\phi$ +smaller than the larger one, where $\\phi = \\frac{1 + \\sqrt{5}}{2}$ denotes the [golden ratio](https://en.wikipedia.org/wiki/Golden_ratio). The "neck" of the island is *also* defined by two *external* circles: one -smaller again by $\phi$ than the smaller circle, and one larger again than the +smaller again by $\\phi$ than the smaller circle, and one larger again than the bigger circle. In this way, the island is defined by four circles of increasing -radius, with each increase a factor of $\phi$: +radius, with each increase a factor of $\\phi$: ![napari-logo-3-with-guides](../images/napari-logo-3-with-guides.png) @@ -94,28 +94,28 @@ The logo proposed above is generated *entirely within napari*. It makes use of: - Scaling, translation, and rotation on each layer: the arithmetic for finding the exact centers and contact points of all the circles is much easier with the origin at the center of the north-west circle, a radius of - $1/\phi = \phi - 1$, and the island aligned along the 0th (vertical) axis. + $1/\\phi = \\phi - 1$, and the island aligned along the 0th (vertical) axis. Similarly, the squircle is easier to draw with the origin at the center. I then apply scale, translation and rotation so that the island is at 45° and - the logo fills the space in [[0, 1024], [0, 1024]]. + the logo fills the space in \[\[0, 1024\], \[0, 1024\]\]. - NumPy for computation of all the lines and shapes. Additionally, napari was used extensively during the development, to debug errors in calculating the contact points and so on. 😅 -It's very fun to work with powers of $\phi$: $1/\phi = \phi - 1$, which implies -$1/\phi^2 = (\phi - 1)/\phi = 1 - 1/\phi = 2 - \phi$, and so on. Similarly, -$\phi^2 = 1 + \phi$, which again lets you reduce all polynomials of $\phi$ to +It's very fun to work with powers of $\\phi$: $1/\\phi = \\phi - 1$, which implies +$1/\\phi^2 = (\\phi - 1)/\\phi = 1 - 1/\\phi = 2 - \\phi$, and so on. Similarly, +$\\phi^2 = 1 + \\phi$, which again lets you reduce all polynomials of $\\phi$ to degree 1. The original implementation is at https://github.com/jni/new-napari-logo, but if this NAP is accepted, it would add the logo-generating script as a gallery example. -Another bit of mathematical fun: Veritasium calls $\phi$ "the fiveiest number" +Another bit of mathematical fun: Veritasium calls $\\phi$ "the fiveiest number" (in [this fantastic video](https://youtu.be/48sCx-wBs34)): it is the ratio of the diagonal of a regular pentagon to its side, and it can be written as -$\phi = 0.5 + 0.5 \times 5^{0.5}$! So it is a happy not-quite-coincidence that +$\\phi = 0.5 + 0.5 \\times 5^{0.5}$! So it is a happy not-quite-coincidence that this is NAP-5. 😉 ## Backward Compatibility @@ -133,6 +133,7 @@ themes to match. Or we could simply pick a more natural color for the lagoon, again from the satellite image, and forget about matching them. I will take the opportunity to express two strong opinions: + - I think some form of "ocean purple" is absolutely necessary for the background, as I think it's become part of our identity on account of our existing logo. @@ -181,10 +182,10 @@ This section will be updated with links as we discuss the NAP. ## Copyright This document is dedicated to the public domain with the Creative Commons CC0 -license [^id3]. Attribution to this source is encouraged where appropriate, as per +license \[^id3\]. Attribution to this source is encouraged where appropriate, as per CC0+BY [^id4]. -[^id3]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, - +\[^id3\]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, + -[^id4]: +[^id4]: https://dancohen.org/2013/11/26/cc0-by/ diff --git a/docs/naps/6-contributable-menus.md b/docs/naps/6-contributable-menus.md index 2cebc5af6..6d3e34684 100644 --- a/docs/naps/6-contributable-menus.md +++ b/docs/naps/6-contributable-menus.md @@ -112,7 +112,6 @@ This NAP proposes new menu IDs and new top level menus to open for contribution. ![Right click layer context menu with new menu item and submenu contributed by a plugin](./_static/layer-context-menu.png) - ### What do Menu Contributions do? `MenuItem` contributions can be thought of as auxiliary contributions that @@ -151,6 +150,7 @@ being acted upon by the actions within the menu, and the likely output of those actions. ### The `Layers` Menu + Currently the foremost example of such an object is the napari `Layers`, and this menu therefore contains five submenus organized by the types of processing the user may wish to perform on the selected `Layer` or `Layers`. @@ -160,14 +160,14 @@ feeling of what might happen to their `Layers` as a result of clicking one of these menu items. 1. `Visualization` - Items in this submenu allow you to generate visualizations from selected layer or layers. -They do not change the layer data. + They do not change the layer data. 2. `Measure` - Items in this submenu provide utilities for summarising information about your layer's data. 3. `Edit` - The items in this submenu change the data of your layer through `Filters` or `Transformations`. -Additionally `Annotation Tools` provide a location for convenience layer editing tools e.g. `Labels` split/merge actions. -Items in this submenu **should not** generate new layers, but rather act upon the existing layer data. -3. `Generate` - Items in this submenu are the main *analysis* actions you can take on your layer. -These items should add new layers to the viewer based on analyses and processing of data in your selected layer(s). -The five proposed submenus are `Projection`, `Segmentation`, `Classification`, `Registration` and `Tracks`. + Additionally `Annotation Tools` provide a location for convenience layer editing tools e.g. `Labels` split/merge actions. + Items in this submenu **should not** generate new layers, but rather act upon the existing layer data. +4. `Generate` - Items in this submenu are the main *analysis* actions you can take on your layer. + These items should add new layers to the viewer based on analyses and processing of data in your selected layer(s). + The five proposed submenus are `Projection`, `Segmentation`, `Classification`, `Registration` and `Tracks`. Many of the actions in this menu exist in the right click layer context menu. These items should be replicated in the `Layers` menu as needed, both to aid discoverability and @@ -485,8 +485,7 @@ containing individual submenus that make sense: - does not give the user a good indication of what inputs an action takes and what its output will be - is not semantically structured and is rather just a one stop shop for "plugin stuff" - will be difficult to extend further in meaningful ways as we develop more complex viewer -interactions and plugin contributions e.g. multi canvas - + interactions and plugin contributions e.g. multi canvas ## Discussion @@ -500,16 +499,16 @@ interactions and plugin contributions e.g. multi canvas ## References and Footnotes All NAPs should be declared as dedicated to the public domain with the CC0 -license [^id3], as in `Copyright`, below, with attribution encouraged with +license \[^id3\], as in `Copyright`, below, with attribution encouraged with CC0+BY [^id4]. -[^id3]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, - - -[^id4]: +\[^id3\]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, + ## Copyright This document is dedicated to the public domain with the Creative Commons CC0 -license [^id3]. Attribution to this source is encouraged where appropriate, as per +license \[^id3\]. Attribution to this source is encouraged where appropriate, as per CC0+BY [^id4]. + +[^id4]: https://dancohen.org/2013/11/26/cc0-by/ diff --git a/docs/naps/7-key-binding-dispatch.md b/docs/naps/7-key-binding-dispatch.md index ac500e759..a75f3ed4e 100644 --- a/docs/naps/7-key-binding-dispatch.md +++ b/docs/naps/7-key-binding-dispatch.md @@ -11,7 +11,7 @@ ## Abstract -With the switching of the internal key binding system to use app-model's representation[^id1], there is discussion as to what exactly constitutes a valid key binding and how conflicts are handled[^id2]. +With the switching of the internal key binding system to use app-model's representation\[^id1\], there is discussion as to what exactly constitutes a valid key binding and how conflicts are handled\[^id2\]. This NAP seeks to clarify and propose a solution for how key bindings will be dispatched according to their priority, enablement, and potential conflicts. @@ -32,6 +32,7 @@ Conditional evaluation allows plugin developers to tie their keybinding to a spe **modifier keys** refer to `ctrl`, `shift`, `alt`, and `meta`. `meta` is also known as `cmd`, `win`, or `super` on osx, windows, or linux, respectively a **base key** is a key that when pressed without a modifier key, produces one of the following [key codes](https://w3c.github.io/uievents-code/#keyboard-101): + - `a-z`, `0-9` - `f1-f12` - `` ` ``, `-`, `=`, `[`, `]`, `\`, `;`, `'`, `,`, `.`, `/` @@ -50,7 +51,7 @@ a **key binding** binds a key sequence to a command with conditional activation ### Key binding validity: convenience vs. complexity -Some users want to use traditional modifier keys as a base key in key binding for convenience purposes [^id2]. However, this can lead to conflicts since many key bindings may include the modifier key in their key sequence and thus cause confusion and cost extra engineering effort. +Some users want to use traditional modifier keys as a base key in key binding for convenience purposes \[^id2\]. However, this can lead to conflicts since many key bindings may include the modifier key in their key sequence and thus cause confusion and cost extra engineering effort. The proposed restrictions on what key sequences can be used in a key binding aim to allow for the simplest user need while cutting down on any unnecessary complexities: @@ -58,10 +59,11 @@ The proposed restrictions on what key sequences can be used in a key binding aim - key chords cannot contain a base key that is a modifier key (aka a single modifier) Here are some examples: + - `alt` is **valid** as a base key because it contains no other modifiers and no other parts - `alt+meta` is **invalid** because it is a key combination comprised of only modifiers - `alt+t` is **valid** as a key combination -- `alt t` is **invalid** because it is a key chord whose first part is a single modifier +- `alt t` is **invalid** because it is a key chord whose first part is a single modifier - `ctrl+x alt` is **invalid** because it is a key chord whose second part is a single modifier - `ctrl+x alt+v` is **valid** as a key chord - `meta meta` is **invalid** because it is a key chord comprised of only single modifier parts @@ -73,6 +75,7 @@ Even with conditional activation, many key bindings may find that they share the ### Key binding properties All key binding entries contain the following information: + - `command_id` is the unique identifier of the command that will be executed by this key binding - `weight` is the main determinant of key binding priority. high value means a higher priority - `when` is the context expression that is evaluated to determine whether the rule is active; if not provided, the rule is always considered active @@ -84,6 +87,7 @@ from dataclasses import dataclass, field from app_model.expressions import Expr + @dataclass(order=True) class KeyBindingEntry: command_id: str = field(compare=False) @@ -91,10 +95,10 @@ class KeyBindingEntry: when: Optional[Expr] = field(compare=False) block_rule: bool = field(init=False) negate_rule: bool = field(init=False) - + def __post_init__(self): - self.block_rule = self.command_id == '' - self.negate_rule = self.command_id.startswith('-') + self.block_rule = self.command_id == "" + self.negate_rule = self.command_id.startswith("-") ``` ### Types of key binding rules @@ -102,6 +106,7 @@ class KeyBindingEntry: There are three ways to modify how a key binding interacts with a command: an assign rule, a negate rule, and a block rule. Note that negate and block rules only affect key bindings of their weight and below. An assign rule tells the dispatcher to execute the given command when the rule is enabled: + ```json { "key": "ctrl+y", @@ -110,6 +115,7 @@ An assign rule tells the dispatcher to execute the given command when the rule i ``` A negate rule is denoted by prefixing the `command_id` with `-` and effectively cancels out assign rules to the same command for that key sequence. For example, to rebind the example for the `redo` command above from `ctrl+y` to `ctrl+shift+z`, one would need the following rules: + ```json [ { @@ -124,6 +130,7 @@ A negate rule is denoted by prefixing the `command_id` with `-` and effectively ``` A block rule is denoted by simply leaving the `command_id` as blank and prevents any commands for that key sequence from being executed. This cannot be set via the GUI. The below example includes a block rule that disables the previous two rules bound to `tab`: + ```json [ { @@ -153,6 +160,7 @@ Key bindings will automatically be assigned weights depending on who set them, p ```python from enum import IntEnum + class KeyBindingWeights(IntEnum): CORE = 0 PLUGIN = 300 @@ -178,6 +186,7 @@ In case (B), the corresponding command will never be triggered so long as it ind When checking if an active key binding matches the entered key sequence, the resolver will fetch the pre-sorted list of direct conflicts and check if the last entry is active using its `when` property, moving to the next entry if it is not. When it encounters a blocking rule, it will return no match, and for a negate rule, it will store the affected command in an ignore list and continue to the next entry. If no special rules are present, it will return a match if the command is not in an ignore list, otherwise continuing to the next entry, and so on, until no more entries remain. In pseudo-code this reads as: + ```python def find_active_match(entries: List[KeyBindingEntry]) -> Optional[KeyBindingEntry]: ignored_commands = [] @@ -195,6 +204,7 @@ def find_active_match(entries: List[KeyBindingEntry]) -> Optional[KeyBindingEntr ### Lookup and partial matches Key bindings can be stored in a map in integer form, as `KeyMod`, `KeyCode`, `KeyCombo`, and `KeyChord` are all represented as unique `int`s with 16 bits per part: + ```python keymap = Dict[int, List[KeyBindingEntry]] = { KeyMod.CtrlCmd | KeyCode.KeyZ: ..., @@ -202,23 +212,27 @@ keymap = Dict[int, List[KeyBindingEntry]] = { KeyMod.CtrlCmd | KeyCode.KeyX: ..., KeyChord(KeyMod.CtrlCmd | KeyCode.KeyX, KeyCode.KeyC): ..., KeyChord(KeyMod.CtrlCmd | KeyCode.KeyX, KeyCode.KeyV): ..., - KeyMod.Shift : ..., + KeyMod.Shift: ..., } ``` Due to the ability of key sequences to be encoded as 32-bit integers, bitwise operations can be performed to determine certain properties of these sequences: + ```python def has_shift(key: int) -> bool: return bool(key & KeyMod.Shift) + def starts_with_ctrl_cmd_x(key: int) -> bool: return key & 0x0000FFFF == (KeyMod.CtrlCmd | KeyCode.KeyX) + def multi_part(key: int) -> bool: return key > 0x0000FFFF ``` As such, entries in the keymap can be filtered to find conflicts: + ```python > list(filter(has_shift, keymap)) [, ] @@ -238,25 +252,30 @@ As such, entries in the keymap can be filtered to find conflicts: ``` Note that because modifiers are encoded in the `(8, 12]`-bit range, querying for modifiers will only check the first part unless they are shifted by 16: + ```python > has_shift(KeyChord(KeyMod.CtrlCmd | KeyCode.KeyX, KeyMod.Shift | KeyCode.KeyY)) False ``` In a more generic form: + ```python KEY_MOD_MASK = 0x00000F00 PART_0_MASK = 0x0000FFFF + def create_conflict_filter(conflict_key: int) -> Callable[[int], bool]: if conflict_key & KEY_MOD_MASK == conflict_key: # only comprised of modifier keys in first part def inner(key: int) -> bool: return key != conflict_key and key & conflict_key + elif conflict_key <= PART_0_MASK: # one-part key sequence def inner(key: int) -> bool: return key > PART_0_MASK and key & PART_0_MASK == conflict_key + else: # don't handle anything more complex def inner(key: int) -> bool: @@ -264,13 +283,14 @@ def create_conflict_filter(conflict_key: int) -> Callable[[int], bool]: return inner + def has_conflicts(key: int, keymap: Dict[int, List[KeyBindingEntry]]) -> bool: conflict_filter = create_conflict_filter(key) for _, entries in filter(conflict_filter, keymap.items()): if find_active_match(entries): return True - + return False ``` @@ -331,7 +351,7 @@ class KeyBindingDispatcher: key_seq = mods | key if self.prefix: key_seq = KeyChord(self.prefix, key_seq) - + if (entries := self.keymap.get(key_seq) and (match := find_active_match(entries)): self.active_combo = mods | key if not self.prefix and has_conflicts(key_seq, self.keymap): @@ -379,27 +399,34 @@ The entire key binding system is heavily influenced by [VSCode's keyboard shortc ## Backward Compatibility -A change in the key binding dispatch system would affect anyone using `keymap` or `class_keymap` from the original `KeymapProvider`, as well as `bind_key` [^id3]. +A change in the key binding dispatch system would affect anyone using `keymap` or `class_keymap` from the original `KeymapProvider`, as well as `bind_key` \[^id3\]. While `keymap` and `class_keymap` are unlikely to be commonly used, `bind_key` is, and thus will receive proper deprecation and continue to work by creating an equivalent entry in the new key binding dispatch system. For example, following is how a user might have defined a key binding for an `Image` layer: + ```python -@Image.bind_key('Control-C') +@Image.bind_key("Control-C") def foo(layer): ... ``` An entry would be created equivalent to: + ```python def wrapper(layer: Image): yield from foo(layer) + action = Action(id=foo.__qualname__, title=foo.__name__, callback=wrapper) -entry = KeyBindingEntry(command_id=foo.__qualname__, weight=KeyBindingWeight.USER, when=parse_expression("active_layer_type == 'image'")) +entry = KeyBindingEntry( + command_id=foo.__qualname__, + weight=KeyBindingWeight.USER, + when=parse_expression("active_layer_type == 'image'"), +) register_action(action) -register_key_binding('Ctrl+C', entry) +register_key_binding("Ctrl+C", entry) ``` ## Future Work @@ -410,7 +437,7 @@ Out of scope is work related to the GUI and how it may have to handle the new sy ## Alternatives -Although a mapping approach is very effective for looking up individual keys, it loses its efficiency when performing a partial search, since its items are traversed like a list to perform that search. +Although a mapping approach is very effective for looking up individual keys, it loses its efficiency when performing a partial search, since its items are traversed like a list to perform that search. This inefficiency can be mitigated by using a data structure where entries are stored similar to a _[trie](https://en.wikipedia.org/wiki/Trie)_ (aka a _prefix tree_). Since modifier keys do not care about what order they are pressed in, we will use a [directed acyclic graph](https://en.wikipedia.org/wiki/Directed_acyclic_graph) instead of a traditional tree, essentially making this a _prefix [multitree](https://en.wikipedia.org/wiki/Multitree)_. @@ -426,6 +453,7 @@ This effectively breaks the key sequences of the key bindings into their respect ```python from app_model.types import KeyBinding + @dataclass class Node: value: KeyBinding @@ -461,18 +489,18 @@ However, the mapping approach is a lot cleaner code-wise, as it requires no addi ## Copyright This document is dedicated to the public domain with the Creative Commons CC0 -license [^id4]. Attribution to this source is encouraged where appropriate, as per +license \[^id4\]. Attribution to this source is encouraged where appropriate, as per CC0+BY [^id5]. ## References and Footnotes -[^id1]: napari #5103, +\[^id1\]: napari #5103, -[^id2]: napari #5747, +\[^id2\]: napari #5747, -[^id3]: KeymapProvider implementation, +\[^id3\]: KeymapProvider implementation, -[^id4]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, - +\[^id4\]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, + -[^id5]: \ No newline at end of file +[^id5]: https://dancohen.org/2013/11/26/cc0-by/ diff --git a/docs/naps/index.md b/docs/naps/index.md index 3609b737f..b08442b7e 100644 --- a/docs/naps/index.md +++ b/docs/naps/index.md @@ -1,4 +1,5 @@ (nap_list)= + # napari Advancement Proposals (NAPs) NAPs document any major changes or proposals to the napari project. diff --git a/docs/naps/template.md b/docs/naps/template.md index 226579990..2405a9c97 100644 --- a/docs/naps/template.md +++ b/docs/naps/template.md @@ -80,16 +80,16 @@ discussion: ## References and Footnotes All NAPs should be declared as dedicated to the public domain with the CC0 -license [^id3], as in `Copyright`, below, with attribution encouraged with +license \[^id3\], as in `Copyright`, below, with attribution encouraged with CC0+BY [^id4]. -[^id3]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, - - -[^id4]: +\[^id3\]: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication, + ## Copyright This document is dedicated to the public domain with the Creative Commons CC0 -license [^id3]. Attribution to this source is encouraged where appropriate, as per +license \[^id3\]. Attribution to this source is encouraged where appropriate, as per CC0+BY [^id4]. + +[^id4]: https://dancohen.org/2013/11/26/cc0-by/ diff --git a/docs/plugins/_layer_data_guide.md b/docs/plugins/_layer_data_guide.md index 12fb656dc..a4ef33eb6 100644 --- a/docs/plugins/_layer_data_guide.md +++ b/docs/plugins/_layer_data_guide.md @@ -1,4 +1,5 @@ (layer-data-tuples)= + ## The LayerData tuple When transfering data to and from plugins, napari does not pass `Layer` objects @@ -15,12 +16,12 @@ up often in plugins and is explained here. A `LayerData` tuple is a tuple of length 1, 2, or 3 whose items, in order, are: 1. The `data` object that would be used for `layer.data` (such as a numpy array -for the `Image` layer) + for the `Image` layer) 2. *(Optional).* A {class}`dict` of layer attributes, suitable for passing as -keyword arguments to the corresponding layer constructor (e.g. `{'opacity': 0.7}`) + keyword arguments to the corresponding layer constructor (e.g. `{'opacity': 0.7}`) 3. *(Optional).* A lower case {class}`str` indicating the layer type (e.g.`'image'`, -`'labels'`, etc...). If not provided (i.e. if the tuple is only of length 2), the -layer type is assumed to be `'image`'. + `'labels'`, etc...). If not provided (i.e. if the tuple is only of length 2), the + layer type is assumed to be `'image`'. ### Formal type definition @@ -49,8 +50,13 @@ class ArrayLike(Protocol): shape: Tuple[int, ...] ndim: int dtype: np.dtype - def __array__(self) -> np.ndarray: ... - def __getitem__(self, key) -> ArrayLike: ... + + def __array__(self) -> np.ndarray: + ... + + def __getitem__(self, key) -> ArrayLike: + ... + # the main point is that we're more concerned with structural # typing than literal array types (e.g. numpy, dask, xarray, etc...) @@ -62,6 +68,7 @@ Assume that `data` is a numpy array: ```python import numpy as np + data = np.random.rand(64, 64) ``` @@ -71,13 +78,13 @@ All of the following are valid `LayerData` tuples: # the first three are equivalent, just an image array with default settings (data,) (data, {}) -(data, {}, 'image') +(data, {}, "image") # provide kwargs for image contructor -(data, {'name': 'My Image', 'colormap': 'red'}) +(data, {"name": "My Image", "colormap": "red"}) # labels layer instead of image: -(data.astype(int), {'name': 'My Labels', 'blending': 'additive'}, 'labels') +(data.astype(int), {"name": "My Labels", "blending": "additive"}, "labels") ``` ### Creation from a `Layer` instance. @@ -128,4 +135,5 @@ To add a `LayerData` tuple to the napari viewer, use :meth:`Layer.create`: >>> viewer = napari.current_viewer() >>> viewer.add_layer(napari.layers.Layer.create(*image_layer_data)) ``` + The only attribute that can't be passed to `napari.layers.Layer.create` that is otherwise valid for a `LayerData` tuple is 'channel_axis'. diff --git a/docs/plugins/best_practices.md b/docs/plugins/best_practices.md index 091fd61c3..f320449d7 100644 --- a/docs/plugins/best_practices.md +++ b/docs/plugins/best_practices.md @@ -6,7 +6,6 @@ There are a number of good and bad practices that may not be immediately obvious when developing a plugin. This page covers some known practices that could affect the ability to install or use your plugin effectively. - (best-practices-no-qt-backend)= ## Don't include `PySide2` or `PyQt5` in your plugin's dependencies. @@ -14,8 +13,7 @@ affect the ability to install or use your plugin effectively. *This is important!* Napari supports *both* PyQt and PySide backends for Qt. It is up to the -end-user to choose which one they want. If they installed napari with `pip -install napari[all]`, then the `[all]` extra will (currently) install `PyQt5` +end-user to choose which one they want. If they installed napari with `pip install napari[all]`, then the `[all]` extra will (currently) install `PyQt5` for them from pypi. If they installed via `conda install napari`, then they'll have `PyQt5`, but via anaconda cloud instead of pypi. Lastly, they may have installed napari with PySide2. @@ -32,22 +30,22 @@ Here's what can go wrong if you *also* declare one of these backends in the [package naming decisions](https://github.com/ContinuumIO/anaconda-issues/issues/1554), and it's not something napari can fix. + - Alternatively, they may end up with *both* PyQt and PySide in their environment, and while that's not always guaranteed to break things, it can lead to unexpected and difficult to debug problems. - **Don't import from PyQt5 or PySide2 in your plugin: use `qtpy`.** - If you use `from PyQt5 import QtCore` (or similar) in your plugin, but the - end-user has chosen to use `PySide2` for their Qt backend — or vice versa — - then your plugin will fail to import. Instead use `from qtpy import - QtCore`. `qtpy` is a [Qt compatibility - layer](https://github.com/spyder-ide/qtpy) that will import from whatever - backend is installed in the environment. + If you use `from PyQt5 import QtCore` (or similar) in your plugin, but the + end-user has chosen to use `PySide2` for their Qt backend — or vice versa — + then your plugin will fail to import. Instead use `from qtpy import QtCore`. `qtpy` is a [Qt compatibility + layer](https://github.com/spyder-ide/qtpy) that will import from whatever + backend is installed in the environment. ## Try not to depend on packages that require C compilation if these packages do not offer wheels -````{tip} +```{tip} This requires some awareness of how your dependencies are built and distributed... Some python packages write a portion of their code in lower level languages like @@ -63,8 +61,7 @@ software that is not always installed on every computer. (If you've ever tried to `python -m pip install` a package and had it fail with a big wall of red text saying something about `gcc`, then you've run into a package that doesn't distribute wheels, and you didn't have the software required to compile it). -```` - +``` As a plugin developer, if you depend on a package that uses C extensions but doesn't distribute a pre-compiled wheel, then it's very likely that your users @@ -96,7 +93,7 @@ will run into difficulties installing your plugin: (for example, [see here in pytorch](https://github.com/pytorch/pytorch/blob/master/setup.py#L914)) -````{admonition} What about conda? +```{admonition} What about conda? **conda** also distributes & installs pre-compiled packages, though they aren't wheels. While this is definitely a fine way to install binary dependencies in a reliable way, the built-in napari plugin installer doesn't currently work with @@ -104,21 +101,18 @@ conda. If your dependency is only available on conda, but does not offer wheels,you *may* guide your users in using conda to install your package or one of your dependencies. Just know that it may not work with the built-in plugin installer. -```` - +``` ## Don't import heavy dependencies at the top of your module -````{note} +```{note} This point will be less relevant when we move to the second generation [manifest-based plugin declaration](https://github.com/napari/napari/issues/3115), but it's still a good idea to delay importing your plugin-specific dependencies and modules until *after* your hookspec has been called. This helps napari stay quick and responsive at startup. -```` - - +``` Consider the following example plugin: @@ -195,8 +189,6 @@ with open("some_data_in_my_plugin.json") as data_file: data = json.load(data_file) ``` - - ## Write extensive tests for your plugin! Programmer and author Bruce Eckel famously wrote: @@ -241,6 +233,7 @@ already docked in the viewer. ```python from qtpy.QtWidgets import QDialog, QWidget, QSpinBox, QPushButton, QGridLayout, QLabel + class MyInputDialog(QDialog): def __init__(self, parent: QWidget): super().__init__(parent) @@ -259,6 +252,7 @@ class MyInputDialog(QDialog): self.ok_btn.clicked.connect(self.accept) self.cancel_btn.clicked.connect(self.reject) + class MyWidget(QWidget): def __init__(self, viewer: "napari.Viewer"): super().__init__() @@ -288,20 +282,22 @@ from magicgui import magicgui from napari.qt import get_current_stylesheet from napari.settings import get_settings + def sample_add(a: int, b: int) -> int: return a + b + @magicgui def sample_add(a: int, b: int) -> int: return a + b + def change_style(): sample_add.native.setStyleSheet(get_current_stylesheet()) get_settings().appearance.events.theme.connect(change_style) change_style() - ``` ## Do not package your tests as a top-level package @@ -370,7 +366,6 @@ Note this also applies to other top-level directories, like `test`, `_tests`, `t You can find more information in the [package discovery documentation for `setuptools`](https://setuptools.pypa.io/en/latest/userguide/package_discovery.html). - ## License issues when including code from 3rd parties Plugins will often depend on 3rd party packages beyond `napari` itself. diff --git a/docs/plugins/debug_plugins.md b/docs/plugins/debug_plugins.md index 7c30e7eda..090d8f62d 100644 --- a/docs/plugins/debug_plugins.md +++ b/docs/plugins/debug_plugins.md @@ -15,24 +15,27 @@ When developing plugins in napari, you may encounter mistakes or bugs in your co To quickly get started with debugging your plugin, you can do the following: 1. Install your plugin in [editable mode](https://packaging.python.org/en/latest/guides/distributing-packages-using-setuptools/#working-in-development-mode) in your virtual environment. For example, you could do this by running `pip install -e .` in the root directory of your plugin's repository. + 2. Write a Python script to launch napari with your plugin loaded, like so: - ```python - # launch_napari.py - from napari import Viewer, run + ```python + # launch_napari.py + from napari import Viewer, run - viewer = Viewer() - dock_widget, plugin_widget = viewer.window.add_plugin_dock_widget( - "YOUR_PLUGIN_NAME", "YOUR_WIDGET_NAME" - ) - # Optional steps to setup your plugin to a state of failure - # E.g. plugin_widget.parameter_name.value = "some value" - # E.g. plugin_widget.button.click() - run() - ``` + viewer = Viewer() + dock_widget, plugin_widget = viewer.window.add_plugin_dock_widget( + "YOUR_PLUGIN_NAME", "YOUR_WIDGET_NAME" + ) + # Optional steps to setup your plugin to a state of failure + # E.g. plugin_widget.parameter_name.value = "some value" + # E.g. plugin_widget.button.click() + run() + ``` 3. Setup the [pdb](https://docs.python.org/3/library/pdb.html) or the debugger in your IDE (such as [VSCode](https://code.visualstudio.com/docs/editor/debugging) or [PyCharm](https://www.jetbrains.com/help/pycharm/debugging-code.html#general-procedure)) to run this script in debug mode with any desired breakpoints set. For example, in VSCode, you can [set a breakpoint](https://code.visualstudio.com/Docs/editor/debugging#_breakpoints) by clicking on the line number in the script. + 4. Run the created napari launch script in debug mode. For example, in VSCode, you can do this by opening the script in the editor, [selecting your napari virtual environment as the python interpreter](https://code.visualstudio.com/docs/python/environments) and then clicking the `Run and Debug` button in the left hand toolbar, selecting `Python: File` as the run configuration. + 5. At a breakpoint or exception (in VSCode, tick the `Raised Exceptions` box in the bottom left under the `Breakpoints` menu to see exceptions) you can then step through the code, inspect variables, and see the state of the napari viewer and your plugin. When you are done done debugging hit the continue button and napari will resume normal execution. See the image below for an example of a napari plugin debugging session in VSCode paused on a breakpoint. ![debugging_in_vscode](../images/vs_code_debug.png) @@ -42,9 +45,9 @@ To quickly get started with debugging your plugin, you can do the following: It is possible that after installing your plugin, napari will fail to launch - or your plugin won't show up. The following commands will report any issues napari detects with your plugin that may prevent napari from launching or prevent napari from discovering your plugin: -* `napari --plugin-info -v` prints installed napari plugins, what they provide, and any issues related to these plugins. -* `napari --info` prints key environment information related to napari, and the version of installed plugins. -* `npe2 validate YOUR_PLUGIN_NAME` ensures that your plugin has a valid manifest file. +- `napari --plugin-info -v` prints installed napari plugins, what they provide, and any issues related to these plugins. +- `napari --info` prints key environment information related to napari, and the version of installed plugins. +- `npe2 validate YOUR_PLUGIN_NAME` ensures that your plugin has a valid manifest file. ```{note} In general, `napari --info` is a good first step to debugging any environment issues and providing the output from this command is useful when raising bugs. @@ -315,7 +318,6 @@ DEBUG: 20/09/2022 05:59:23 PM The input string was (logging): fast 'You entered fast!' ``` - The full code changes and new files after applying the changes to the plugin in each step of the examples are [here](https://github.com/seankmartin/napari-plugin-debug/tree/full_code/napari-simple-reload). ## Debugging segfaults/memory violation errors diff --git a/docs/plugins/find_and_install_plugin.md b/docs/plugins/find_and_install_plugin.md index ea90ace4e..e6e911a62 100644 --- a/docs/plugins/find_and_install_plugin.md +++ b/docs/plugins/find_and_install_plugin.md @@ -1,9 +1,9 @@ (find-and-install-plugins)= + # Finding and installing a napari plugin napari plugins are Python packages distributed on the Python Package Index -(PyPI), and annotated with the tag [`Framework :: -napari`](https://pypi.org/search/?q=&o=&c=Framework+%3A%3A+napari). The +(PyPI), and annotated with the tag [`Framework :: napari`](https://pypi.org/search/?q=&o=&c=Framework+%3A%3A+napari). The [napari hub](https://napari-hub.org) uses this data, together with additional metadata, to produce a more user friendly way to find napari plugins. @@ -26,11 +26,10 @@ directly from within napari: ![napari viewer's Plugins menu with Install/Uninstall Plugins as the first item.](/images/plugin-menu.png) 2. In the Plugin dialog that opens, where it says “Install by name/URL”, - enter the name of the plugin you want to install (or *any* valid pip - [requirement - specifier](https://pip.pypa.io/en/stable/reference/requirement-specifiers/) - or [VCS scheme](https://pip.pypa.io/en/stable/topics/vcs-support)) - + enter the name of the plugin you want to install (or *any* valid pip + [requirement + specifier](https://pip.pypa.io/en/stable/reference/requirement-specifiers/) + or [VCS scheme](https://pip.pypa.io/en/stable/topics/vcs-support)) ![napari viewer's Plugin dialog. At the bottom of the dialog, there is a place to install by name, URL, or dropping in a file.](/images/plugin-install-dialog.png) diff --git a/docs/plugins/first_plugin.md b/docs/plugins/first_plugin.md index adc5ed612..0e1bf920d 100644 --- a/docs/plugins/first_plugin.md +++ b/docs/plugins/first_plugin.md @@ -7,11 +7,11 @@ At the end, we'll point you to a "cookiecutter" template repository that helps automate the creation of new plugins, and adds a number of conveniences for testing, maintaining, and deploying your plugin. -````{admonition} new plugin format! +```{admonition} new plugin format! :class: important This page describes the creation of a plugin targeting `npe2`, the second generation plugin engine. -```` +``` ## Before you start @@ -28,7 +28,6 @@ environment to use and test your plugin. See the [installation guide](installation) if this is your first time installing napari. - ## What is a plugin? Napari plugins are just Python packages. *Minimally*, they must: @@ -38,7 +37,6 @@ Napari plugins are just Python packages. *Minimally*, they must: 2. Declare a `napari.manifest` [entry point][entry_points] that allows napari to detect the plugin at runtime. - ## 1. Create a new directory Let's create a new folder called `napari-hello` for your plugin files, @@ -57,13 +55,16 @@ then create a `napari_hello` directory with a single `__init__.py` file inside o ::::{tab-set} :::{tab-item} macOS / Linux + ```sh mkdir napari_hello touch napari_hello/__init__.py napari_hello/napari.yaml pyproject.toml setup.cfg ``` + ::: :::{tab-item} Windows + ```bat mkdir napari_hello copy /b napari_hello\__init__.py +,, @@ -71,6 +72,7 @@ copy /b napari_hello\napari.yaml +,, copy /b pyproject.toml +,, copy /b setup.cfg +,, ``` + ::: :::: @@ -166,17 +168,16 @@ It just uses the napari notifications API to show a message: ```python from napari.utils.notifications import show_info + def show_hello_message(): - show_info('Hello, world!') + show_info("Hello, world!") ``` *(It doesn't look like a widget yet! We're going to use napari's widget autogeneration capabilities to turn this function into a widget)* - ### Add a `napari.yaml` manifest - If you haven't already, create an empty [plugin manifest](./manifest) file at `napari_hello/napari.yaml` We will use this file to tell napari: @@ -229,12 +230,12 @@ Lastly, let's make a few small changes to `setup.cfg`. **`[options.entry_points]`** that points to the `napari.yaml` file we added to the `napari_hello` module. - ```{tip} - Entry points are a standard Python mechanism for an installed distribution to - advertise components it provides to be discovered and used by other code. + ```{tip} + Entry points are a standard Python mechanism for an installed distribution to + advertise components it provides to be discovered and used by other code. - See the [Entry points specification][entry_points] for details. - ``` + See the [Entry points specification][entry_points] for details. + ``` With the above changes, your final `setup.cfg` file should look like this: @@ -299,8 +300,8 @@ specific contribution type in the [Guides](./guides). Review the [Best Practices](./best_practices) when developing plugins and, when you're ready to share your plugin, see [Testing and Deployment](./test_deploy). -[miniconda]: https://docs.conda.io/projects/conda/en/latest/user-guide/install/download.html -[python_env]: https://docs.conda.io/projects/conda/en/latest/user-guide/getting-started.html#managing-python -[editable_mode]: https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs [cookiecutter]: https://github.com/napari/cookiecutter-napari-plugin +[editable_mode]: https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs [entry_points]: https://packaging.python.org/en/latest/specifications/entry-points/ +[miniconda]: https://docs.conda.io/projects/conda/en/latest/user-guide/install/download.html +[python_env]: https://docs.conda.io/projects/conda/en/latest/user-guide/getting-started.html#managing-python diff --git a/docs/plugins/guides.md b/docs/plugins/guides.md index b661a1ed7..e4a2bf1e1 100644 --- a/docs/plugins/guides.md +++ b/docs/plugins/guides.md @@ -6,22 +6,25 @@ an example implementation. For details on the type and meaning of each field in a specific contribution, See the [contributions reference](./contributions) - ```{include} _npe2_readers_guide.md ``` ----------------- + +______________________________________________________________________ ```{include} _npe2_writers_guide.md ``` ----------------- + +______________________________________________________________________ ```{include} _npe2_widgets_guide.md ``` ----------------- + +______________________________________________________________________ ```{include} _npe2_sample_data_guide.md ``` ----------------- + +______________________________________________________________________ ```{include} _layer_data_guide.md ``` diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 3a8c4ea7e..4ff87a4bd 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -1,6 +1,6 @@ (plugins-index)= -# Plugins +# Plugins ```{note} These pages describe the process of **building** a plugin. @@ -39,6 +39,7 @@ recommend migrating to `npe2`. See the ``` (how-to-build-a-plugin)= + ## How to build plugins If you're just getting started with napari plugins, try our @@ -57,13 +58,10 @@ For special considerations when building a napari plugin, see If you have questions, try asking on the [zulip chat][napari_zulip]. Submit issues to the [napari github repository][napari_issues]. -[npe1]: https://github.com/napari/napari-plugin-engine -[npe2]: https://github.com/napari/npe2 [napari_issues]: https://github.com/napari/napari/issues/new/choose [napari_zulip]: https://napari.zulipchat.com/ -[napari_hub]: https://napari-hub.org [readers]: contributions-readers -[writers]: contributions-writers -[widgets]: contributions-widgets [sample_data]: contributions-sample-data [theme]: contributions-themes +[widgets]: contributions-widgets +[writers]: contributions-writers diff --git a/docs/plugins/npe1.md b/docs/plugins/npe1.md index c8423e840..8dd808f8f 100644 --- a/docs/plugins/npe1.md +++ b/docs/plugins/npe1.md @@ -1,4 +1,5 @@ (napari-plugin-engine)= + # 1st Gen Plugin Guide (*Deprecated*) ```{Admonition} DEPRECATED @@ -16,7 +17,6 @@ The content below describes the original and exists for archival reference purposes during the deprecation period. ``` - ## Overview `napari` supports plugin development through **hooks**: @@ -61,7 +61,6 @@ A single plugin package may implement more than one _hook specification_, and each _hook specification_ could have multiple _hook implementations_ within a single package. - Let's take the {func}`~napari.plugins.hook_specifications.napari_get_reader` hook (our primary "reader plugin" hook) as an example. It is defined as: @@ -139,7 +138,7 @@ directly from `napari_plugin_engine` (a very lightweight dependency that uses only standard lib python). ```python - from napari_plugin_engine import napari_hook_implementation +from napari_plugin_engine import napari_hook_implementation ``` ##### Matching hook implementations to specifications @@ -207,12 +206,12 @@ functionality by simply installing your package along with napari: ``` (plugin-sharing)= + ### Step 4: Deploy your plugin See [testing and deploying](./test_deploy) your plugin. (This hasn't changed significantly with the secod generation (`npe2`) plugin engine). - (plugin-cookiecutter-template)= ## Cookiecutter template @@ -233,12 +232,11 @@ cookiecutter https://github.com/napari/cookiecutter-napari-plugin See the [readme](https://github.com/napari/cookiecutter-napari-plugin) for details - ----------------------------- +______________________________________________________________________ (hook-specifications-reference)= -## Hook Specification Reference +## Hook Specification Reference ```{eval-rst} .. automodule:: napari.plugins.hook_specifications @@ -296,5 +294,3 @@ If you run into trouble creating your plugin, please don't hesitate to reach out for help in the [Image.sc Forum](https://forum.image.sc/tag/napari). Alternatively, if you find a bug or have a specific feature request for plugin support, please open an issue at our [GitHub issue tracker](https://github.com/napari/napari/issues/new/choose). - -[npe2]: https://github.com/napari/npe2 diff --git a/docs/plugins/npe2_migration_guide.md b/docs/plugins/npe2_migration_guide.md index 7cb2b38f7..766a80b0b 100644 --- a/docs/plugins/npe2_migration_guide.md +++ b/docs/plugins/npe2_migration_guide.md @@ -117,16 +117,15 @@ Now, update the local package metadata by repeating: The next time napari is run, your plugin should be discovered as an `npe2` plugin. ----------------- +______________________________________________________________________ ## Migration Reference > *This section goes into detail on the differences between first-generation and -second-generation implementations. In many cases, this will be more detail than -you need. If you are still struggling with a specific conversion after using -`npe2 convert` and reading the [contributions](./contributions) reference and -[guides](./guides), this section may be of help.* - +> second-generation implementations. In many cases, this will be more detail than +> you need. If you are still struggling with a specific conversion after using +> `npe2 convert` and reading the [contributions](./contributions) reference and +> [guides](./guides), this section may be of help.* Existing `napari-plugin-engine` plugins expose functionality via *hook implementations*. These are functions decorated to indicate they fullfil a @@ -483,12 +482,4 @@ napari-plugin-engine API, the npe2 adapter will likely become the only way that npe1 plugins are supported in the future, and the option to *not* use the npe2 adaptor will be removed. - -[epg]: https://packaging.python.org/specifications/entry-points/ -[pd]: https://setuptools.pypa.io/en/latest/userguide/datafiles.html -[npe1]: https://github.com/napari/napari-plugin-engine -[npe2]: https://github.com/tlambert03/npe2 -[json]: https://www.json.org/ -[yaml]: https://yaml.org/ -[toml]: https://toml.io/ [magicgui]: https://napari.org/magicgui diff --git a/docs/plugins/test_deploy.md b/docs/plugins/test_deploy.md index 3ce7a5d80..c33ad1a2e 100644 --- a/docs/plugins/test_deploy.md +++ b/docs/plugins/test_deploy.md @@ -1,7 +1,9 @@ (plugin-test-deploy)= + # Test and Deploy (plugin-testing-tips)= + ## Tips for testing napari plugins Testing is a big topic! If you are completely new to writing tests in python, @@ -131,14 +133,13 @@ napari hub preview page service. Check out [this guide](https://github.com/chanz ## Deployment When you are ready to share your plugin, [upload the Python package to -PyPI][pypi-upload] after which it will be installable using `python -m pip install -`, or (assuming you added the `Framework :: napari` classifier) +PyPI][pypi-upload] after which it will be installable using `python -m pip install `, or (assuming you added the `Framework :: napari` classifier) in the builtin plugin installer dialog. If you used the {ref}`plugin-cookiecutter-template`, you can also [setup automated deployments][autodeploy] on github for every tagged commit. -````{admonition} What about conda? +```{admonition} What about conda? While you are free to distribute your plugin on anaconda cloud in addition to or instead of PyPI, the built-in napari plugin installer doesn't currently install from conda. In this case, you may guide your users to install your package on the @@ -146,16 +147,12 @@ command line using conda in your readme or documentation. A future version of napari and the napari stand-alone application may support directly installing from conda. -```` +``` When you are ready for users, announce your plugin on the [Image.sc forum](https://forum.image.sc/tag/napari). - +[autodeploy]: https://github.com/napari/cookiecutter-napari-plugin#set-up-automatic-deployments [classifier]: https://pypi.org/classifiers/ [pypi]: https://pypi.org/ [pypi-upload]: https://packaging.python.org/tutorials/packaging-projects/#uploading-the-distribution-archives -[hubguide]: https://github.com/chanzuckerberg/napari-hub/blob/main/docs/customizing-plugin-listing.md -[hub-guide-custom-viz]: https://github.com/chanzuckerberg/napari-hub/wiki/Customizing-your-plugin's-listing#visibility -[hub-guide-preview]: https://github.com/chanzuckerberg/napari-hub/blob/main/docs/setting-up-preview.md -[autodeploy]: https://github.com/napari/cookiecutter-napari-plugin#set-up-automatic-deployments diff --git a/docs/plugins/testing_workshop_docs/1-pythons-assert-keyword.md b/docs/plugins/testing_workshop_docs/1-pythons-assert-keyword.md index 87324422b..6b1d2f4c0 100644 --- a/docs/plugins/testing_workshop_docs/1-pythons-assert-keyword.md +++ b/docs/plugins/testing_workshop_docs/1-pythons-assert-keyword.md @@ -4,21 +4,24 @@ This tutorial defines the assert keyword in Python and shows how it can be used ## Other lessons in this tutorial: -* 1: This lesson (Python's assert keyword) -* 2: [Pytest testing framework](./2-pytest-testing-frameworks) -* 3: [Readers and fixtures](./3-readers-and-fixtures) -* 4: [Test coverage](./4-test-coverage) -* Resource links: [Testing resources](./testing-resources) +- 1: This lesson (Python's assert keyword) +- 2: [Pytest testing framework](./2-pytest-testing-frameworks) +- 3: [Readers and fixtures](./3-readers-and-fixtures) +- 4: [Test coverage](./4-test-coverage) +- Resource links: [Testing resources](./testing-resources) ### This lesson covers: + [Assert keyword](#assert-keyword) [Test for Pass](#test-for-the-pass-case) [Test for Fail](#test-for-the-fail-case) ### Resources + The example plugin and all the tests discussed in this lesson are available in [this GitHub repository](https://github.com/DragaDoncila/plugin-tests). ## Assert keyword + The key to testing in Python is the [assert](https://realpython.com/python-assert-statement/) keyword. We *assert* a Boolean expression is true and create an error message that appears when that expression is false. ```python @@ -39,8 +42,8 @@ def get_grade_from_mark(mark): `get_grade_from_mark` can be tested by writing two test functions. The first one is for when the mark is > 50. - ## Test for the Pass case + When the mark is > 50, call `get_grade_from_mark` and assert that the grade is what we expect (either `Pass` or `Fail`). When testing the passing case, test that the grade is `Pass`. If it's not `Pass`, the best practice is to write a helpful error message like, `"Expected {mark} to pass but result was {grade}."` ```python @@ -50,6 +53,7 @@ def test_get_grade_pass(mark): ``` ## Test for the Fail case + Test the same thing for `Fail` to test all options. Everything is almost the same, so copy and paste and change a few words to create the second test. ```python @@ -59,26 +63,32 @@ def test_get_grade_fail(mark): ``` We can now write code to run both of the functions with expected values. For example, we expect 65 to pass and 43 to fail. After running both functions, if no exception has been raised we print “All passing.” + ```python if __name__ == "__main__": test_get_grade_pass(65) test_get_grade_fail(43) print("All passing.") ``` + We can place all this code in a Python file, e.g. [example_test.py](https://github.com/DragaDoncila/plugin-tests/blob/main/example_func.py), and run it from the command line. + ```console (napari-env) user@directory % python example_test.py All passing. ``` We now update the test to see what a failure looks like: + ```python if __name__ == "__main__": test_get_grade_pass(65) test_get_grade_fail(70) # updated this to 70 to force failure print("All passing.") ``` + If we assert that 70 fails, the `AssertionError`, “Expected something to fail, but the result was pass.” would be thrown, which is correct. It would look like this: + ```console (napari-env) user@directory % python example_func.py Traceback (most recent call last): @@ -87,6 +97,7 @@ File “/Users/user/directory/example_test.py” line 16, in test_get_g Assert grade == “Fail”, f”Expected {mark} to fail, but result was {grade}” AssertionError: Expected 70 to fail, but result was Pass. ``` + Note that when the assertion fails, a traceback occurs. This example is a simple way to demonstrate the use of the assert keyword, but it’s not particularly useful for testing a larger codebase. This test function has to be called explicitly to test different marks. There’s not much detail when the code is running. We just get `“All passing.”` and there’s no information about other tests when one of the tests fails. diff --git a/docs/plugins/testing_workshop_docs/2-pytest-testing-frameworks.md b/docs/plugins/testing_workshop_docs/2-pytest-testing-frameworks.md index eab3a7e18..44386fd37 100644 --- a/docs/plugins/testing_workshop_docs/2-pytest-testing-frameworks.md +++ b/docs/plugins/testing_workshop_docs/2-pytest-testing-frameworks.md @@ -4,26 +4,31 @@ This lesson explains how to use the [pytest testing framework](https://docs.pyte ## Other lessons in this tutorial: -* 1: [Python’s assert keyword](./1-pythons-assert-keyword.md) -* 2: This lesson (Pytest testing framework) -* 3: [Readers and fixtures](./3-readers-and-fixtures.md) -* 4: [Test coverage](./4-test-coverage.md) -* Resource links: [Testing resources](./testing-resources.md) +- 1: [Python’s assert keyword](./1-pythons-assert-keyword.md) +- 2: This lesson (Pytest testing framework) +- 3: [Readers and fixtures](./3-readers-and-fixtures.md) +- 4: [Test coverage](./4-test-coverage.md) +- Resource links: [Testing resources](./testing-resources.md) ### This lesson covers: -* [Testing framework features](#testing-framework-features) -* [Parametrization](#parametrization) + +- [Testing framework features](#testing-framework-features) +- [Parametrization](#parametrization) ### Resources + The example plugin and all the tests discussed in this lesson are available in [this GitHub repository](https://github.com/DragaDoncila/plugin-tests). ## Introduction + We are using pytest as a testing framework. It provides convenience tools to assist with testing. For example, it can discover tests for you if you point it to a directory or a file. It can be installed using `pip install pytest`. ## Testing framework features + Testing frameworks provide a whole host of useful features, including: -* Test discovery - directories can be crawled (searched) to find things that look like tests and run them -* Housekeeping and ease of use - convenient methods for writing tests and cleaning up after running the tests + +- Test discovery - directories can be crawled (searched) to find things that look like tests and run them +- Housekeeping and ease of use - convenient methods for writing tests and cleaning up after running the tests Pytest goes through the target destination, such as a file or directory, finding any method or function prefaced with the word `test`. It runs all the methods and functions prefaced with the word `test` but _not_ the code under the main block. When `pytest` runs against `example_test.py` (refer to the [Python's assert keyword](./1-pythons-assert-keyword.md) lesson), it finds several tests that all pass. @@ -61,6 +66,7 @@ FAILED example_func_py::test_get_grade_fail - AssertionError: Expected 65 to fai ``` ## Parametrization + Another very useful tool that pytest provides is parametrization. We've tested these functions with a single value. We need to be more thorough. Pytest allows us to parametrize tests. We decorate our function with `@pytest.mark.parametrize` and pass the decorator a parameter name, `mark`, as a string, and a list of values for which we’d like to run the test function. Note that we pass in 50 as an edge case; it's the lowest mark that will pass. diff --git a/docs/plugins/testing_workshop_docs/3-readers-and-fixtures.md b/docs/plugins/testing_workshop_docs/3-readers-and-fixtures.md index 1d82c7ef8..c1c794fec 100644 --- a/docs/plugins/testing_workshop_docs/3-readers-and-fixtures.md +++ b/docs/plugins/testing_workshop_docs/3-readers-and-fixtures.md @@ -4,27 +4,31 @@ This lesson explains how to use and test a plugin's reader function, built-in fi ## Other lessons in this tutorial: -* 1: [Python’s assert keyword](./1-pythons-assert-keyword.md) -* 2: [Pytest testing framework](./2-pytest-testing-frameworks.md) -* 3: This lesson (Readers and fixtures) -* 4: [Test coverage](./4-test-coverage.md) -* Resource links: [Testing resources](./testing-resources.md) +- 1: [Python’s assert keyword](./1-pythons-assert-keyword.md) +- 2: [Pytest testing framework](./2-pytest-testing-frameworks.md) +- 3: This lesson (Readers and fixtures) +- 4: [Test coverage](./4-test-coverage.md) +- Resource links: [Testing resources](./testing-resources.md) ### This lesson covers: -* [Readers](#reader) -* [Built-in fixtures](#built-in-fixtures) -* [Custom fixtures and round-trip tests](#custom-fixtures-and-round-trip-tests) -* [Enclosed testing](#enclosed-testing) + +- [Readers](#reader) +- [Built-in fixtures](#built-in-fixtures) +- [Custom fixtures and round-trip tests](#custom-fixtures-and-round-trip-tests) +- [Enclosed testing](#enclosed-testing) ### Resources + The example plugin and all the tests discussed in this lesson are available in [this GitHub repository](https://github.com/DragaDoncila/plugin-tests). ## Introduction + In this lesson, we discuss a napari plugin called [plugin_tests](https://github.com/DragaDoncila/plugin-tests/tree/main/src/plugin_tests), generated using the [cookiecutter](https://github.com/napari/cookiecutter-napari-plugin), which has a reader and a widget. The reader is the cookiecutter [NumPy `.npy` file](https://numpy.org/doc/stable/reference/generated/numpy.lib.format.html#npy-format) reader, `napari_get_reader`. It checks whether a path ends in `.npy`. If it doesn't, it returns `None`, and if it does, it returns the `reader_function`, which loads the data. ![napari_get_reader](../../images/napari_plugins_1st_napari_get_reader.png) ## Reader + The `napari_get_reader` function is the first thing to test. In the top-level directory under `src`, we have the `plugin_tests` module. Inside `plugin_tests` is the `_tests` directory. This is a typical structure when writing tests. There is also a `test_reader.py` file, which is empty. We will populate it with tests. ![reader_function](../../images/napari_plugins_2nd_reader_function.png) @@ -36,13 +40,13 @@ Test as much as possible and focus on writing small tests that look at one indiv ``` ## Built-in fixtures + We use [tmp_path](https://docs.pytest.org/en/7.1.x/how-to/tmp_path.html#the-tmp-path-fixture) to manage the writing and reading of files during test execution. `tmp_path` is a `pytest` fixture; it is not imported, it comes with `pytest`. We pass `tmp_path` as a parameter to our test function, and `pytest` will inject it when the tests run. `tmp_path` provides a temporary path used to save and manipulate files. Temporary paths, files, and directories created in this way during the testing process are automatically removed by `pytest` when the tests are completed. We create a file to read. First, we’ll build a file path, then generate a small amount of data. We’re just testing to see if the function returns a `callable` as expected, so we don't need a large array. There are no specific requirements for the contents of the array in this case. We just need some sort of file to save to this temporary directory. The test file will not appear anywhere unless there is a pause during test execution. - Using `napari_get_reader` with this path, we assert that the reader is callable. A function should be returned. If it isn’t, we could put an error message here. ```python @@ -65,13 +69,14 @@ Running the command `pytest .` in the root directory of the plugin, we discover ![pytest passed](../../images/napari_plugins_3rd_pytest_passed.png) If the file did not end in `.npy` the test would fail because what was returned wasn't callable. This code has been modified to produce an error: + ```python # tmp_path is a pytest fixture def test_get_reader_returns_callable(tmp_path): """Calling get_reader on numpy file returns callable""" # write some fake data - my_test_file = str(tmp_path / "myfile.np") # note ends in .np + my_test_file = str(tmp_path / "myfile.np") # note ends in .np original_data = np.random.rand(20, 20) np.save(my_test_file, original_data) @@ -79,11 +84,13 @@ def test_get_reader_returns_callable(tmp_path): reader = napari_get_reader(my_test_file) assert callable(reader) ``` + Once we run `pytest` we can see that it traced back that the callable of `reader` is `False` and it has filled in the fact that `reader` at the time of the assertion was `None`. This is useful in debugging. ![test_get_reader_returns_callable Failed](../../images/napari_plugins_4th_test_get_reader_returns_callable-failed.png) ## Custom fixtures and round-trip tests + Next, we test to see if this function reads the data. This is a round-trip test. We will create a fixture to write the data to make things easier for ourselves. This fixture will be called [test_reader_round_trip](https://github.com/DragaDoncila/plugin-tests/blob/effb32d6e3b191ad83e69813b26ae8695210f5ad/src/plugin_tests/_tests/test_reader.py#L39). Whatever is returned out of a `@pytest.fixture` decorated function is passed as an argument with the name of the fixture, to the test. We are going to call this `pytest.fixture` decorated function `write_im_to_file`. We’re going to give this fixture the `tmp_path` fixture - fixtures can use fixtures! @@ -95,10 +102,10 @@ We will have access to what `write_func` returns once it’s been called inside The benefit of creating this fixture is that whenever we want to write our own test data we don't have to copy three lines of code, we can just use the fixture. This is useful in testing data with different structures like integers or a specific layer type. Those arguments could be passed to further customize your fixture. We still want to make sure we get a reader when we call `napari_get_reader` with the file. We call that `reader` function with the file we created to see if it returns what we expect. Based on the [reader spec](https://napari.org/stable/plugins/contributions.html#contributions-readers), it should return a layer data list. Here is the full test, with the fixture: + ```python @pytest.fixture def write_im_to_file(tmp_path): - def write_func(filename): my_test_file = str(tmp_path / filename) original_data = np.random.rand(20, 20) @@ -108,6 +115,7 @@ def write_im_to_file(tmp_path): return write_func + def test_reader_round_trip(write_im_to_file): my_test_file, original_data = write_im_to_file("myfile.npy") @@ -121,11 +129,13 @@ def test_reader_round_trip(write_im_to_file): layer_data = layer_data_tuple[0] np.testing.assert_allclose(layer_data, original_data) ``` + We’re going to assert a list length greater than zero. There must be a layer in there; otherwise, we didn't read it correctly. We also assert that it is a list. We will test that inside that list is what we expected - layer data tuples. The first item of a layer data tuple is the actual data. We’re going to test that explicitly. Then we assert, using `numpy`’s asserting mechanism, `np.testing.assert_allclose` that they are all close, even though they should be exactly the same. This is standard practice when working with floating point precision. NumPy also has [other assertion options](https://numpy.org/doc/stable/reference/routines.testing.html) you may find useful. The layer data we read back with the reader function should be the same as the original data. If that's true, then we made the entire round trip. We saved the file and we used the reader to read the file. + ```python def test_reader_round_trip(write_im_to_file): my_test_file, original_data = write_im_to_file("myfile.npy") @@ -140,12 +150,13 @@ def test_reader_round_trip(write_im_to_file): layer_data = layer_data_tuple[0] np.testing.assert_allclose(layer_data, original_data) ``` + We run our tests again, and now two are collected, both passing. ![pytest - tests passed](../../images/napari_plugins_5th_tests_passed.png) - ## Enclosed testing + Note that although we're testing a `napari` plugin, we did not need a viewer or napari to test this. It's important that we didn't need those because napari and the napari viewer are out of our control. What we can control is the code _we_ wrote. We wrote that data by simply mocking up an array and getting a temporary path to it. We could thoroughly test our functions in an enclosed way without relying on other people's code or mocking up many complicated objects. The next lesson in this series on testing is [Test coverage](./4-test-coverage). diff --git a/docs/plugins/testing_workshop_docs/4-test-coverage.md b/docs/plugins/testing_workshop_docs/4-test-coverage.md index 737f02a1a..813009cce 100644 --- a/docs/plugins/testing_workshop_docs/4-test-coverage.md +++ b/docs/plugins/testing_workshop_docs/4-test-coverage.md @@ -2,20 +2,23 @@ ## Other lessons in this tutorial: -* 1: [Python’s assert keyword](./1-pythons-assert-keyword.md) -* 2: [Pytest testing framework](./2-pytest-testing-frameworks.md) -* 3: [Readers and fixtures](./3-readers-and-fixtures.md) -* 4: This lesson (Test coverage) -* Resource links: [testing resources](./testing-resources.md) +- 1: [Python’s assert keyword](./1-pythons-assert-keyword.md) +- 2: [Pytest testing framework](./2-pytest-testing-frameworks.md) +- 3: [Readers and fixtures](./3-readers-and-fixtures.md) +- 4: This lesson (Test coverage) +- Resource links: [testing resources](./testing-resources.md) ### This lesson covers: -* [Coverage](#coverage) -* [pytest --cov](#pytest---cov) + +- [Coverage](#coverage) +- [pytest --cov](#pytest---cov) #### Resources + The example plugin and all the tests discussed in this lesson are available in [this GitHub repository](https://github.com/DragaDoncila/plugin-tests). ## Coverage + How do we know when we have tested everything? This is where _coverage_ comes in. `pytest-cov` can find out what the coverage of our tests is. Install using @@ -23,7 +26,7 @@ This is where _coverage_ comes in. `pytest-cov` can find out what the coverage o ## pytest --cov -To run tests with coverage, run `pytest` and add `--cov` pointing to the module you're covering. There is also an option to generate an html report, which we do in this case. [Note that the `.` (period character) is part of the command.] +To run tests with coverage, run `pytest` and add `--cov` pointing to the module you're covering. There is also an option to generate an html report, which we do in this case. \[Note that the `.` (period character) is part of the command.\] ```console (napari-env) user@directory % pytest --cov=plugin_tests --cov-report=html . @@ -75,17 +78,20 @@ We are interested in `_reader.py`. The file containing the reader code has 86% c Because we never provided a list of paths, we don't know what will happen in that case. We also never ran code that tests not returning a reader. In other words, we never tested the failed cases. We can and should add those tests. The first one is `test_get_reader_pass`. We'll call it with a file that doesn't end in `.npy` and assert that it returned `None`. Then we'll create a second test to call with a list of paths. Using the `write_im_to_file` fixture again, we can write two files, call `napari_get_reader` with two paths and assert that it still returns a callable. + ```python def test_get_reader_pass(): """Calling get_reader with non-numpy file path returns None""" reader = napari_get_reader("fake.file") assert reader is None + def test_get_reader_path_list(write_im_to_file): """Calling get_reader on list of numpy files returns callable""" pth1, _ = write_im_to_file("myfile1.npy") pth2, _ = write_im_to_file("myfile2.npy") + reader = napari_get_reader([pth1, pth2]) assert callable(reader) ``` diff --git a/docs/plugins/testing_workshop_docs/index.md b/docs/plugins/testing_workshop_docs/index.md index 6ed21ab5a..0a71cbb73 100644 --- a/docs/plugins/testing_workshop_docs/index.md +++ b/docs/plugins/testing_workshop_docs/index.md @@ -1,8 +1,9 @@ # In-depth guide to plugin testing This is the frontpage for the tutorial developed from the [January 2022 workshop on testing](https://youtu.be/IsHYnI8Tbfw?list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK). This tutorial will be more meaningful to you if you are familiar with the Python programming language and napari software. These lessons summarize the information in the video and should stand on their own. The lessons are listed here with the timings on the video next to each: -* 1: [Python’s assert keyword](./1-pythons-assert-keyword.md) - starts at minute [5:23](https://youtu.be/IsHYnI8Tbfw?list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK&t=333). -* 2: [Pytest testing framework](./2-pytest-testing-frameworks.md) - starts at minute [9:21](https://youtu.be/IsHYnI8Tbfw?list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK&t=561). -* 3: [Readers and fixtures](./3-readers-and-fixtures.md) - starts at minute [15:42](https://youtu.be/IsHYnI8Tbfw?list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK&t=942). -* 4: [Test coverage](./4-test-coverage.md) - starts at minute [28:26](https://youtu.be/IsHYnI8Tbfw?list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK&t=1706). -* Resource links: [Testing resources](./testing-resources.md) \ No newline at end of file + +- 1: [Python’s assert keyword](./1-pythons-assert-keyword.md) - starts at minute [5:23](https://youtu.be/IsHYnI8Tbfw?list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK&t=333). +- 2: [Pytest testing framework](./2-pytest-testing-frameworks.md) - starts at minute [9:21](https://youtu.be/IsHYnI8Tbfw?list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK&t=561). +- 3: [Readers and fixtures](./3-readers-and-fixtures.md) - starts at minute [15:42](https://youtu.be/IsHYnI8Tbfw?list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK&t=942). +- 4: [Test coverage](./4-test-coverage.md) - starts at minute [28:26](https://youtu.be/IsHYnI8Tbfw?list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK&t=1706). +- Resource links: [Testing resources](./testing-resources.md) diff --git a/docs/plugins/testing_workshop_docs/testing-resources.md b/docs/plugins/testing_workshop_docs/testing-resources.md index 4c0ed5fef..3118ef975 100644 --- a/docs/plugins/testing_workshop_docs/testing-resources.md +++ b/docs/plugins/testing_workshop_docs/testing-resources.md @@ -1,17 +1,18 @@ # Testing resources These resources may be helpful in developing tests: -* [Today’s plugin](https://github.com/DragaDoncila/plugin-tests) - Plugin Tests -* [pytest](https://docs.pytest.org/en/6.2.x/) - pytest is a mature full-featured Python testing tool that helps you write better programs. +- [Today’s plugin](https://github.com/DragaDoncila/plugin-tests) - Plugin Tests -* [qtbot](https://pytest-qt.readthedocs.io/en/latest/reference.html#module-pytestqt.qtbot) - `class pytestqt.qtbot.QtBot`: Instances of this class are responsible for sending events to Qt objects (usually widgets), simulating user input. -Important: Instances of this class should be accessed only by using a qtbot fixture, never instantiated directly. +- [pytest](https://docs.pytest.org/en/6.2.x/) - pytest is a mature full-featured Python testing tool that helps you write better programs. -* [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) - This plugin produces coverage reports. Compared to just using coverage run this plugin provides some extras. +- [qtbot](https://pytest-qt.readthedocs.io/en/latest/reference.html#module-pytestqt.qtbot) - `class pytestqt.qtbot.QtBot`: Instances of this class are responsible for sending events to Qt objects (usually widgets), simulating user input. + Important: Instances of this class should be accessed only by using a qtbot fixture, never instantiated directly. -* [codecov](https://about.codecov.io/) - As long as your code has tests and your coverage tool can output coverage results you can use Codecov. +- [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) - This plugin produces coverage reports. Compared to just using coverage run this plugin provides some extras. -* [GitHub workflow docs](https://docs.github.com/en/actions/quickstart) - You need only a GitHub repository to create and run a GitHub Actions workflow. +- [codecov](https://about.codecov.io/) - As long as your code has tests and your coverage tool can output coverage results you can use Codecov. -* [Slides from workshop presentation](https://docs.google.com/presentation/d/1RFja0o6cZ8lAalAve8heuJ-Lrb4nOSUnfdpOSEhqqNo/) - These are the actual slides that were used during the testing workshop. \ No newline at end of file +- [GitHub workflow docs](https://docs.github.com/en/actions/quickstart) - You need only a GitHub repository to create and run a GitHub Actions workflow. + +- [Slides from workshop presentation](https://docs.google.com/presentation/d/1RFja0o6cZ8lAalAve8heuJ-Lrb4nOSUnfdpOSEhqqNo/) - These are the actual slides that were used during the testing workshop. diff --git a/docs/plugins/virtual_environment_docs/1-virtual-environments.md b/docs/plugins/virtual_environment_docs/1-virtual-environments.md index 9e6d18f31..55dd06749 100644 --- a/docs/plugins/virtual_environment_docs/1-virtual-environments.md +++ b/docs/plugins/virtual_environment_docs/1-virtual-environments.md @@ -3,25 +3,29 @@ This guide explains the value of using virtual environments and how to create and remove them. ## This guide covers: -* [The importance of virtual environments](#overview) -* [Creating environments](#creating-environments) -* [Removing environments](#removing-environments) + +- [The importance of virtual environments](#overview) +- [Creating environments](#creating-environments) +- [Removing environments](#removing-environments) ## Overview + A virtual environment is an isolated collection of packages, settings, and an associated Python interpreter, that allows multiple different collections to exist on the same system. They are created on top of an existing Python installation, known as the virtual environment's “base” python, and may optionally be isolated from the packages in the base environment, so only those explicitly installed in the virtual environment are available. - More information on why virtual environments are created and how they can help you can be found on the [python website](https://docs.python.org/3/library/venv.html#creating-virtual-environments) and in [this introductory workshop](https://hackmd.io/@talley/SJB_lObBi#What-is-a-virtual-environment). +More information on why virtual environments are created and how they can help you can be found on the [python website](https://docs.python.org/3/library/venv.html#creating-virtual-environments) and in [this introductory workshop](https://hackmd.io/@talley/SJB_lObBi#What-is-a-virtual-environment). Virtual environments are super important! They allow you to isolate your project from other Python projects. They allow you to experiment with various packages and versions without fear of breaking your entire system (and needing to reinstall everything). As you install packages over time, you will inevitably install something that doesn’t “play well” with something else that is already installed. In some cases this can be hard to recover from. With virtual environments, you can just create a fresh environment and start again – without needing to do major surgery on your system. -There are several tools available for creating and managing virtual environments. One of the most popular, comprehensive tools is Conda. Wikipedia explains that [Conda is an open-source, cross-platform, language-agnostic package manager and environment management system.](https://en.wikipedia.org/wiki/Conda_(package_manager)) It was originally developed to solve difficult package management challenges faced by Python data scientists. The Conda package and environment manager is included in all versions of **Anaconda**, **Miniconda**, and **Anaconda Repository**. +There are several tools available for creating and managing virtual environments. One of the most popular, comprehensive tools is Conda. Wikipedia explains that [Conda is an open-source, cross-platform, language-agnostic package manager and environment management system.]() It was originally developed to solve difficult package management challenges faced by Python data scientists. The Conda package and environment manager is included in all versions of **Anaconda**, **Miniconda**, and **Anaconda Repository**. ## Install and Config + Install [miniconda](https://docs.conda.io/en/latest/miniconda.html) or [mini forge](https://github.com/conda-forge/miniforge) (comes pre-configured with `conda-forge`) in the home directory. Adding the `conda-forge` channel to the conda config makes packages in `conda-forge` visible to the conda installer. Setting `channel_priority` to strict ensures packages in high priority channels are always installed over packages of the same name in lower priority channels. See [this guide](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-channels.html) for more details. Make sure the `conda-forge` channel is in your config by using the following commands: + ```console $conda config --add channels conda-forge $conda config --set channel_priority strict @@ -44,6 +48,7 @@ To create an environment, use the following commands at the command prompt (term Virtual environments are made to be ephemeral. ## Removing environments + Consider your environment to be disposable. If you are ever having weird problems, nuke your environment and start over using the following commands: @@ -59,9 +64,9 @@ Encourage your users to do the same. You can waste a lot of time trying to debug ## Other topics in this series: -* [Deploying your plugin](./2-deploying-your-plugin.md) -* [Version management](./3-version-management.md) -* [Developer tools](./4-developer-tools.md) -* [Survey/Q&A](./5-survey.md) +- [Deploying your plugin](./2-deploying-your-plugin.md) +- [Version management](./3-version-management.md) +- [Developer tools](./4-developer-tools.md) +- [Survey/Q&A](./5-survey.md) The next topic in this series is [Deploying your plugin](./2-deploying-your-plugin.md). diff --git a/docs/plugins/virtual_environment_docs/2-deploying-your-plugin.md b/docs/plugins/virtual_environment_docs/2-deploying-your-plugin.md index 6c7bff815..98af03335 100644 --- a/docs/plugins/virtual_environment_docs/2-deploying-your-plugin.md +++ b/docs/plugins/virtual_environment_docs/2-deploying-your-plugin.md @@ -3,18 +3,20 @@ This guide explains some of the techniques you can use to deploy your plugin. ## This guide covers: -* [Overview of PyPI and Anaconda](#overview-of-pypi-and-anaconda) -* [Building your package](#building-your-package) -* [Deploying plugins to PyPI](#deploying-plugins-to-pypi) - - [Manually via twine](#manually-via-twine) - - [Automatically via GitHub actions](#automatically-via-github-actions) -* [Deploying plugins to Anaconda](#deploying-to-anaconda) +- [Overview of PyPI and Anaconda](#overview-of-pypi-and-anaconda) +- [Building your package](#building-your-package) +- [Deploying plugins to PyPI](#deploying-plugins-to-pypi) + - [Manually via twine](#manually-via-twine) + - [Automatically via GitHub actions](#automatically-via-github-actions) +- [Deploying plugins to Anaconda](#deploying-to-anaconda) ## Overview of PyPI and Anaconda + PyPI and Anaconda are two options for how you distribute your package and allow your users to more easily find and install it. Try to deploy to both! But for now, try to at least use PyPI. You can always also provide your users with manual installation instructions (e.g. if you want them to use `conda` or have specific dependencies). ### Building your package + `sdist` means source distribution. An `sdist` includes all of the files that are required to *build* your package. An `sdist` may require specific additional software (e.g. compilers) to actually build. `wheel` is a prebuilt package, ready to drop into your `site-packages` directory. It includes compiled OS-specific extensions (if applicable). @@ -23,7 +25,7 @@ You are *strongly* encouraged to ship both! If the `wheel` is not present, `pip` **Note:** This goes for dependencies too! Check all your dependencies for wheel availability. -**[build](https://pypa-build.readthedocs.io/en/latest/ )** is the recommended package builder that bundles your source code into `sdist` or `wheel` distributions. Install `build` into your local environment and then run it at the root of your package to build your package, as shown below: +**[build](https://pypa-build.readthedocs.io/en/latest/)** is the recommended package builder that bundles your source code into `sdist` or `wheel` distributions. Install `build` into your local environment and then run it at the root of your package to build your package, as shown below: ```console pip install build @@ -33,7 +35,8 @@ You are *strongly* encouraged to ship both! If the `wheel` is not present, `pip` ## Deploying plugins to PyPI ### Manually via **twine**. -[twine](https://twine.readthedocs.io/en/latest/ ) is a command line client you can use to upload your distribution to PyPI. Note that you will need to set up a PyPI account and authenticate yourself when uploading. See [this great guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/) for a detailed tutorial to building and sharing your first Python packages. + +[twine](https://twine.readthedocs.io/en/latest/) is a command line client you can use to upload your distribution to PyPI. Note that you will need to set up a PyPI account and authenticate yourself when uploading. See [this great guide](https://packaging.python.org/en/latest/tutorials/packaging-projects/) for a detailed tutorial to building and sharing your first Python packages. ```console @@ -51,13 +54,16 @@ You are *strongly* encouraged to ship both! If the `wheel` is not present, `pip` $ twine upload dist/* ``` + **Note:** `python -m build` is the modern alternative to `setuptools`' `python setup.py sdist bdist_wheel`. It calls `setuptools` behind the scenes. ### Automatically via GitHub actions + This requires either: -* Running `twine` as above in a workflow after setting up Python and installing it -or -* Using a pre-made [GitHub action](https://github.com/pypa/gh-action-pypi-publish) + +- Running `twine` as above in a workflow after setting up Python and installing it + or +- Using a pre-made [GitHub action](https://github.com/pypa/gh-action-pypi-publish) Here is an example workflow that manually deploys using `twine` when tests pass and you push a tagged commit. @@ -90,9 +96,10 @@ jobs: ``` - **Note:** Gate this action on some criterion, e.g. a git tag as above, or [some other criterion](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows). +**Note:** Gate this action on some criterion, e.g. a git tag as above, or [some other criterion](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows). ## Deploying to Anaconda + This is only a brief guide to deploying to `conda-forge`. More information can be found in the [conda-forge docs](https://conda-forge.org/docs/maintainer/adding_pkgs.html). 1. Fork https://github.com/conda-forge/staged-recipes @@ -106,9 +113,10 @@ Once your recipe is approved and merged, the rest happens *automagically*, and y This is **far** easier if you already have an `sdist` published to PyPI. ## Other topics in this series: -* [Virtual environments](./1-virtual-environments.md) -* [Version management](./3-version-management.md) -* [Developer tools](./4-developer-tools.md) -* [Survey/Q&A](./5-survey.md) + +- [Virtual environments](./1-virtual-environments.md) +- [Version management](./3-version-management.md) +- [Developer tools](./4-developer-tools.md) +- [Survey/Q&A](./5-survey.md) The next topic in this series is [Version management](./3-version-management.md). diff --git a/docs/plugins/virtual_environment_docs/3-version-management.md b/docs/plugins/virtual_environment_docs/3-version-management.md index d79708d1d..f596d4d24 100644 --- a/docs/plugins/virtual_environment_docs/3-version-management.md +++ b/docs/plugins/virtual_environment_docs/3-version-management.md @@ -3,26 +3,30 @@ This guide covers the methods of updating your version string everywhere. ## This guide covers: -* [Using git tags](#using-git-tags) -* [Using a local script to edit files](#using-a-local-script-to-edit-files) -* [Manually](#manually) + +- [Using git tags](#using-git-tags) +- [Using a local script to edit files](#using-a-local-script-to-edit-files) +- [Manually](#manually) Your goal is to make sure that you bump your version string everywhere it may appear, in unison, prior to publishing your package. A version number can be in `init.py`, `setup.cfg`, etc. In increasing order of work, but decreasing order of magic, the methods of bumping your version string are listed below. ## Using git tags: + You can use [setuptools_scm](https://github.com/pypa/setuptools_scm) to automatically generate version numbers for your package based on tagged commits. - ```console - # configure in pyproject.toml, then
 - $ git tag -a v0.1.0 -m v0.1.0 - ``` +```console +# configure in pyproject.toml, then
 +$ git tag -a v0.1.0 -m v0.1.0 +``` - The next time you run `python -m build`, either locally or in GitHub actions, your package version will be based on the latest git tag. +The next time you run `python -m build`, either locally or in GitHub actions, your package version will be based on the latest git tag. ## Using a local script to edit files: + One tool for doing this is [bump2version](https://github.com/c4urself/bump2version). For example: + ```console $ pip install bump2version # configure all the places you use your version, then, to update: @@ -30,17 +34,19 @@ One tool for doing this is [bump2version](https://github.com/c4urself/bump2versi ``` ## Manually + Updating the version number manually involves going through everywhere your version is declared and changing the version number before building your distribution. This is ***not*** recommended, you *will* eventually make mistakes and have mismatched version/metadata somewhere. In some cases this will lead to your build process failing, but it can fail silently too. ## Tips: -* The "best" versioning and deployment workflow is the one you will actually use! -* Get comfortable with at least one workflow for versioning and deploying your package *otherwise, you won't do it.* + +- The "best" versioning and deployment workflow is the one you will actually use! +- Get comfortable with at least one workflow for versioning and deploying your package *otherwise, you won't do it.* The next topic in this series is [Developer tools](./4-developer-tools.md). ## Other topics in this series: -* [Virtual environments](./1-virtual-environments) -* [Deploying your plugin](./2-deploying-your-plugin.md) -* [Developer tools](./4-developer-tools.md) -* [Survey](./5-survey.md) +- [Virtual environments](./1-virtual-environments) +- [Deploying your plugin](./2-deploying-your-plugin.md) +- [Developer tools](./4-developer-tools.md) +- [Survey](./5-survey.md) diff --git a/docs/plugins/virtual_environment_docs/4-developer-tools.md b/docs/plugins/virtual_environment_docs/4-developer-tools.md index 620949e8d..4bf86b56c 100644 --- a/docs/plugins/virtual_environment_docs/4-developer-tools.md +++ b/docs/plugins/virtual_environment_docs/4-developer-tools.md @@ -3,37 +3,45 @@ This guide explains the different types of tools that can help you develop and test your code. ## This guide covers: -* [General tools](#general-tools) - - [Linting tools](#linting-tools) - - [Formatting tools](#formatting-tools) - - [Pre-commit tools](#pre-commit-tools) -* [napari plugin-specific tools](#napari-plugin-specific-tools) + +- [General tools](#general-tools) + - [Linting tools](#linting-tools) + - [Formatting tools](#formatting-tools) + - [Pre-commit tools](#pre-commit-tools) +- [napari plugin-specific tools](#napari-plugin-specific-tools) ## General tools + All of these are *optional*. Many are very helpful, but they do take a little time to learn. The more time you spend coding, the greater the return-on-investment for using them. It's a personal decision on whether the time saved by using these outweighs the time required to understand the tools. ### Linting tools + These _check_ your code. -* [flake8](https://flake8.pycqa.org/) - checks various code style conventions, unused variables, line spacings, etc
 -* [mypy](https://github.com/python/mypy) - - Static type checker: enforces proper usage of types. - - Super useful once you get the hang of it, but definitely an intermediate-advanced tool. - - Along with high test coverage, probably the best time-saver and project robustness tool. + +- [flake8](https://flake8.pycqa.org/) - checks various code style conventions, unused variables, line spacings, etc
 +- [mypy](https://github.com/python/mypy) + - Static type checker: enforces proper usage of types. + - Super useful once you get the hang of it, but definitely an intermediate-advanced tool. + - Along with high test coverage, probably the best time-saver and project robustness tool. ### Formatting tools + These _auto-modify_ your code. -* [black](https://github.com/psf/black) + +- [black](https://github.com/psf/black) Forces code to follow specific style, indentations, etc... -* [autoflake](https://github.com/myint/autoflake) +- [autoflake](https://github.com/myint/autoflake) Auto-fixes some flake8 failures. -* [isort](https://github.com/PyCQA/isort) +- [isort](https://github.com/PyCQA/isort) Auto-sorts and formats your imports. -* [setup-cfg-fmt](https://github.com/asottile/setup-cfg-fmt) +- [setup-cfg-fmt](https://github.com/asottile/setup-cfg-fmt) Sorts and enforces conventions in setup.cfg. ### Pre-commit tools -* [pre-commit](https://pre-commit.com/), runs all your checks each time you run git commit, preventing bad code from ever getting checked in. + +- [pre-commit](https://pre-commit.com/), runs all your checks each time you run git commit, preventing bad code from ever getting checked in. + ```console $ pip install pre-commit # install the pre-commit "hook" @@ -43,25 +51,27 @@ These _auto-modify_ your code. $ pre-commit run --all-files ``` -* [pre-commit-ci](https://pre-commit.ci/) - - Runs all your pre-commit hooks on CI (Continuous Integration). - - Useful even if contributors don't install and run your pre-commit hooks locally before they open a PR. +- [pre-commit-ci](https://pre-commit.ci/) + - Runs all your pre-commit hooks on CI (Continuous Integration). + - Useful even if contributors don't install and run your pre-commit hooks locally before they open a PR. ## Napari plugin-specific tools -* [Static plugin checks](https://github.com/tlambert03/napari-plugin-checks) - - This is a *pre-commit hook*. It is intended to be added to your +- [Static plugin checks](https://github.com/tlambert03/napari-plugin-checks) + - This is a *pre-commit hook*. It is intended to be added to your `.pre-commit-config.yaml` file. - - It *statically* (without importing) checks various best practices about your plugin: + - It *statically* (without importing) checks various best practices about your plugin: + ```yaml repo: https://github.com/tlambert03/napari-plugin-action rev: v0.2.0 hooks: id: napari-plugin-checks ``` -* [Plugin check GitHub action](https://github.com/tlambert03/napari-plugin-action) (work in progress) - - It is intended to be added to your GitHub workflow. - - It (currently) checks that your plugin is installable, and performs a few sanity checks about Qt backends and dock widgets. +- [Plugin check GitHub action](https://github.com/tlambert03/napari-plugin-action) (work in progress) + - It is intended to be added to your GitHub workflow. + - It (currently) checks that your plugin is installable, and performs a few sanity checks about Qt backends and dock widgets. + ```yaml uses: tlambert03/napari-plugin-action@main with: package_name: @@ -70,7 +80,8 @@ These _auto-modify_ your code. The next topic in this series is the [Survey/Q&A](./5-survey.md). ## Other topics in this series: -* [Virtual environments](./1-virtual-environments) -* [Deploying your plugin](./2-deploying-your-plugin.md) -* [Version management](./3-version-management.md) -* [Survey/Q&A](./5-survey.md) + +- [Virtual environments](./1-virtual-environments) +- [Deploying your plugin](./2-deploying-your-plugin.md) +- [Version management](./3-version-management.md) +- [Survey/Q&A](./5-survey.md) diff --git a/docs/plugins/virtual_environment_docs/5-survey.md b/docs/plugins/virtual_environment_docs/5-survey.md index 8248efdb6..3c73ba40f 100644 --- a/docs/plugins/virtual_environment_docs/5-survey.md +++ b/docs/plugins/virtual_environment_docs/5-survey.md @@ -3,6 +3,7 @@ This guide contains questions that were submitted to our survey on testing. ## This guide covers: + - [What are the best practices to test a plugin with multiple sequential steps?](#what-are-the-best-practices-to-test-a-plugin-with-multiple-sequential-steps) - [How do you test widgets, the napari viewer, graphical user interfaces, and Qt in general?](#how-do-you-test-widgets-the-napari-viewer-graphical-user-interfaces-and-qt-in-general) - [How to find the different signals or slots?](#how-to-find-the-different-signals-or-slots) @@ -13,26 +14,32 @@ This guide contains questions that were submitted to our survey on testing. - [What is the optimal setup to quickly iterate in widget development?](#what-is-the-optimal-setup-to-quickly-iterate-in-widget-development) ## What are the best practices to test a plugin with multiple sequential steps? + e.g. Is it ok to rely on the "results" of a test to run the next test or should they all be fully independent? Answer: -* Ideally, aim for unit testing. -* Tests should not pass/fail together. -* Use [fixtures](https://docs.pytest.org/en/6.2.x/fixture.html) to provide a test with inputs, even if you have to make them up. -* Use [mocks (mock-ups)](https://docs.python.org/3/library/unittest.mock.html) to assert that specific calls are made, without necessarily caring about what happens after that call is made. + +- Ideally, aim for unit testing. +- Tests should not pass/fail together. +- Use [fixtures](https://docs.pytest.org/en/6.2.x/fixture.html) to provide a test with inputs, even if you have to make them up. +- Use [mocks (mock-ups)](https://docs.python.org/3/library/unittest.mock.html) to assert that specific calls are made, without necessarily caring about what happens after that call is made. *This is definitely an art form. It takes time. Be patient.* ## How do you test widgets, the napari viewer, graphical user interfaces, and Qt in general? + Answer: -* Try not to! -* You should generally trust that a button click (for example) will call your callback and focus on testing that your callback does what it's supposed to do given that it gets called following some UI interaction. -* However: If you have a scenario where you are actually creating a complicated widget directly in Qt, see `pytest-qt` for lots of tips, specifically `qtbot`. - - [pytest-qt](https://pytest-qt.readthedocs.io/en/latest/intro.html) - - [qtbot](https://pytest-qt.readthedocs.io/en/latest/reference.html?highlight=qtbot#module-pytestqt.qtbot) -* Oftentimes, this comes down to knowing and/or learning the Qt API really well. -* Please see also the [In-depth guide to plugin testing](../testing_workshop_docs/index.md). + +- Try not to! +- You should generally trust that a button click (for example) will call your callback and focus on testing that your callback does what it's supposed to do given that it gets called following some UI interaction. +- However: If you have a scenario where you are actually creating a complicated widget directly in Qt, see `pytest-qt` for lots of tips, specifically `qtbot`. + - [pytest-qt](https://pytest-qt.readthedocs.io/en/latest/intro.html) + - [qtbot](https://pytest-qt.readthedocs.io/en/latest/reference.html?highlight=qtbot#module-pytestqt.qtbot) +- Oftentimes, this comes down to knowing and/or learning the Qt API really well. +- Please see also the [In-depth guide to plugin testing](../testing_workshop_docs/index.md). + ## How to find the different signals or slots? + Question: How can we find the different signals/slots we can connect callbacks to as the user interacts with the core napari interface e.g. creating/editing/deleting a `points` or `shapes` layer? Answer: @@ -41,6 +48,7 @@ Answer: Granted, this is a work in progress. For example, these events are emitted when the user interacts with the layer list: + ```console Viewer.layers.events.inserted Viewer.layers.events.removed @@ -52,46 +60,55 @@ For example, these events are emitted when the user interacts with the layer lis Getting an event when the user is editing the data inside a `points` or `shapes` layer (outside of the GUI interface) is complicated, because the user will be directly editing the native array object. ## How do you avoid github tests failing? + Answer: -* First make sure all your tests are passing locally. -* After that, it's complicated. More background or context is needed to answer this question. + +- First make sure all your tests are passing locally. +- After that, it's complicated. More background or context is needed to answer this question. ## How do you make a process cancellable? + Question: How do you make a process cancellable to interrupt a method that is running in a for loop, for example? Answer: -* In single-threaded python, use `Ctrl-C` -* In multithreaded python, there are many different patterns. Consider using a [generator-based thread worker](https://napari.org/stable/guides/threading.html#generators-for-the-win). + +- In single-threaded python, use `Ctrl-C` +- In multithreaded python, there are many different patterns. Consider using a [generator-based thread worker](https://napari.org/stable/guides/threading.html#generators-for-the-win). ## Are there testing environments in napari? + Answer: Napari does not create or otherwise manage environments. ## Introduction to npe2? Migrating to new plugin architecture? + Answer: -* The primary difference is in how plugins are discovered: - - npe1 used decorators, requiring module import. - - npe2 uses static manifests (`napari.yaml`), describing contributions without requiring import. - - See also the [Your First Plugin tutorial](https://napari.org/stable/plugins/first_plugin.html) + +- The primary difference is in how plugins are discovered: + - npe1 used decorators, requiring module import. + - npe2 uses static manifests (`napari.yaml`), describing contributions without requiring import. + - See also the [Your First Plugin tutorial](https://napari.org/stable/plugins/first_plugin.html) Additional resources: -* [Contributions Reference](https://napari.org/stable/plugins/contributions.html) -* [Guides for each type of contribution](https://napari.org/stable/plugins/guides.html) -* [Migration guide](https://napari.org/stable/plugins/npe2_migration_guide.html) + +- [Contributions Reference](https://napari.org/stable/plugins/contributions.html) +- [Guides for each type of contribution](https://napari.org/stable/plugins/guides.html) +- [Migration guide](https://napari.org/stable/plugins/npe2_migration_guide.html) ## What is the optimal setup to quickly iterate in widget development? + Answer: -* Create a script that will start napari and load your widget without any UI interaction. -* Don't test as a plugin. Start by directly calling `viewer.window.add_dock_widget` with a manually created widget. -* Familiarize yourself with the [IPython auto-reload features](https://ipython.readthedocs.io/en/stable/config/extensions/autoreload.html). -* Consider using `watchmedo` from [watchdog](https://github.com/gorakhargosh/watchdog). - This will monitor a file/directory for changes, and re-run a command each time (which is why step #1 is also useful). +- Create a script that will start napari and load your widget without any UI interaction. +- Don't test as a plugin. Start by directly calling `viewer.window.add_dock_widget` with a manually created widget. +- Familiarize yourself with the [IPython auto-reload features](https://ipython.readthedocs.io/en/stable/config/extensions/autoreload.html). +- Consider using `watchmedo` from [watchdog](https://github.com/gorakhargosh/watchdog). + This will monitor a file/directory for changes, and re-run a command each time (which is why step #1 is also useful). ## Other guides in this series: -* [Virtual environments](./1-virtual-environments.md) -* [Deploying your plugin](./2-deploying-your-plugin.md) -* [Version management](./3-version-management.md) -* [Developer tools](./4-developer-tools.md) +- [Virtual environments](./1-virtual-environments.md) +- [Deploying your plugin](./2-deploying-your-plugin.md) +- [Version management](./3-version-management.md) +- [Developer tools](./4-developer-tools.md) This is the last guide in this series. diff --git a/docs/plugins/virtual_environment_docs/index.md b/docs/plugins/virtual_environment_docs/index.md index 136cb4037..4a2cde824 100644 --- a/docs/plugins/virtual_environment_docs/index.md +++ b/docs/plugins/virtual_environment_docs/index.md @@ -2,8 +2,8 @@ These guides will be more valuable if you are familiar with the Python programming language and the napari software. They are taken from the [January 2022 Testing workshop](https://www.youtube.com/watch?v=IsHYnI8Tbfw&list=PLilvrWT8aLuYID3YZ7KddS5ky2SaH4DKK) video. These guides stand on their own and are summaries of the information in the video. They are listed here in the order they were presented but they do not necessarily build on each other. -* 1: [Virtual environments](./1-virtual-environments) Starts at about minute 49:08. -* 2: [Deploying your plugin](./2-deploying-your-plugin.md) Starts at about minute 54:00. -* 3: [Version management](./3-version-management.md) Starts at about timestamp 1:01:00. -* 4: [Developer tools](./4-developer-tools.md) Starts at about timestamp 1:04:12. -* 5: [Survey/Q&A](./5-survey.md) Starts at about timestamp 1:15:27. +- 1: [Virtual environments](./1-virtual-environments) Starts at about minute 49:08. +- 2: [Deploying your plugin](./2-deploying-your-plugin.md) Starts at about minute 54:00. +- 3: [Version management](./3-version-management.md) Starts at about timestamp 1:01:00. +- 4: [Developer tools](./4-developer-tools.md) Starts at about timestamp 1:04:12. +- 5: [Survey/Q&A](./5-survey.md) Starts at about timestamp 1:15:27. diff --git a/docs/release/release_0_1_0.md b/docs/release/release_0_1_0.md index 89a752616..6742afa86 100644 --- a/docs/release/release_0_1_0.md +++ b/docs/release/release_0_1_0.md @@ -111,7 +111,7 @@ https://github.com/napari/napari - fix selected default (#361) - Add dims test and fix 5D images (#362) - Shape thumbnails (#364) -- [FIX] setting remote upstream in contributing guidelines (#366) +- \[FIX\] setting remote upstream in contributing guidelines (#366) - Refactor thumbnail type conversion (#370) - Selectable points (#371) - Test layers list model and view (#373) diff --git a/docs/release/release_0_1_3.md b/docs/release/release_0_1_3.md index f1cf99870..5be429227 100644 --- a/docs/release/release_0_1_3.md +++ b/docs/release/release_0_1_3.md @@ -24,7 +24,7 @@ https://github.com/napari/napari - fix clim setter (#411) - switch to pyside2 (#412) - fix delete markers (#413) -- [FIX] paint color inidicator update when shuffle color (#416) +- \[FIX\] paint color inidicator update when shuffle color (#416) - QT returns a warning instead of an error (#418) - Fix Crash with stacked binary tiffs. (#422) - cleanup shape classes (#423) diff --git a/docs/release/release_0_2_0.md b/docs/release/release_0_2_0.md index f47770f49..c464a5a80 100644 --- a/docs/release/release_0_2_0.md +++ b/docs/release/release_0_2_0.md @@ -38,7 +38,7 @@ https://github.com/napari/napari - fix clim setter (#411) - switch to pyside2 (#412) - fix delete markers (#413) -- [FIX] paint color inidicator update when shuffle color (#416) +- \[FIX\] paint color inidicator update when shuffle color (#416) - QT returns a warning instead of an error (#418) - Fix Crash with stacked binary tiffs. (#422) - cleanup shape classes (#423) @@ -52,7 +52,7 @@ https://github.com/napari/napari - more dims fixes (#435) - fix screenshot (#437) - fix dims mixing (#438) -- test add_* signatures and improve docstring testing (#439) +- test add\_\* signatures and improve docstring testing (#439) - add qt console (#443) - adapt existing keybindings to use new system (#444) - fix aspect ratio (#446) diff --git a/docs/release/release_0_2_11.md b/docs/release/release_0_2_11.md index 6be063b2b..fa6af3370 100644 --- a/docs/release/release_0_2_11.md +++ b/docs/release/release_0_2_11.md @@ -13,8 +13,7 @@ https://github.com/napari/napari - Point face color and edge color are now settable as a property in a columnar data table, mapped using a colormap (continuous values) or a color cycle - (categorical values). See `this example - `_ + (categorical values). See `this example `\_ for syntax details. - Python 3.8 is now supported. diff --git a/docs/release/release_0_2_12.md b/docs/release/release_0_2_12.md index fb1fae528..8b8e71757 100644 --- a/docs/release/release_0_2_12.md +++ b/docs/release/release_0_2_12.md @@ -33,7 +33,7 @@ https://github.com/napari/napari - Fix 4D ellipses (#950) - Fix Points highlight bug index (Points-data6-3 test) (#972) - Fix labels colormap by updating computation of low discrepancy images (#985) -- Pin jupyter-client<6.0.0 (#997) +- Pin jupyter-client\<6.0.0 (#997) ## Support @@ -49,7 +49,7 @@ https://github.com/napari/napari - Fix docs version tag (#964) - Disallow sphinx 2.4.0; bug fixed in 2.4.1 (#965) - Remove duplicated imports in setup.py (#969) -- Fix viewer view_* func signature parity (#976) +- Fix viewer view\_\* func signature parity (#976) - Fix ability to test released distributions (#1002) - Fix recursive-include in manifest.in (#1003) diff --git a/docs/release/release_0_2_4.md b/docs/release/release_0_2_4.md index 5eedf0a92..b74c9f071 100644 --- a/docs/release/release_0_2_4.md +++ b/docs/release/release_0_2_4.md @@ -26,8 +26,7 @@ https://github.com/napari/napari ## Deprecations -- napari.Viewer.add_multichannel was removed. Use `napari.Viewer.add_image(..., - channel_axis=num)` (#619) +- napari.Viewer.add_multichannel was removed. Use `napari.Viewer.add_image(..., channel_axis=num)` (#619) ## 4 authors added to this release (alphabetical) diff --git a/docs/release/release_0_2_7.md b/docs/release/release_0_2_7.md index 06ce838b5..3e3754107 100644 --- a/docs/release/release_0_2_7.md +++ b/docs/release/release_0_2_7.md @@ -55,7 +55,7 @@ https://github.com/napari/napari - Updating qt_console with better resource management (#784) - Respect vispy max texture limits (#788) - Fix (minor) deprecation warnings (#800) -- Fix FPS spin box on Qt < 5.12 (#803) +- Fix FPS spin box on Qt \< 5.12 (#803) - Bumpy vispy dependency to 0.6.4 (#807) - Set threshold for codecov failure (#806) - Rename util to utils in MANIFEST.in (#811) diff --git a/docs/release/release_0_3_0.md b/docs/release/release_0_3_0.md index 8909ed365..a4162c796 100644 --- a/docs/release/release_0_3_0.md +++ b/docs/release/release_0_3_0.md @@ -171,12 +171,13 @@ with napari.gui_qt(): ``` ## New Features + - Hook up reader plugins (#937) - Support for magicgui (#981) - Writer plugins (#1104) - ## Improvements + - Generalize keybindings (#791) - Points view data refactor (#951) - Add magic name guessing (#1008) @@ -217,11 +218,12 @@ with napari.gui_qt(): - Change screenshot hotkey and open menubar names (#1201) ## Bug Fixes + - Refactor cleanup, prevent leaked widgets, add viewer.close method (#1014) - Allow StringEnum to accept instances of self as argument (#1027) - Close the canvas used to retrieve maximum texture size. (#1028) - Fix points color cycle refresh (#1034) -- Fix styles for Qt < 5.12. Fix styles in manifest for pip install (#1037) +- Fix styles for Qt \< 5.12. Fix styles in manifest for pip install (#1037) - Fix label styles in contrast limit popup (#1039) - Fix pyramid clipping (#1053) - Fix resources/build_icons:build_resources_qrc (#1060) @@ -246,14 +248,15 @@ with napari.gui_qt(): - Only assert that dask config returns to original value in test (#1202) ## Breaking API Changes + - Allow add_path() to accept any layer-specific kwarg and rename to open() (#1111) - Remove path arg from add_image and add_labels (#1149) - Drop pyramid autogeneration (#1159) - Replace pyramid with multiscale (#1170) - Add napari-svg plugin support (#1171) - ## Support + - Publish developer resources (#967) - Fix recursive include in manifest.in (#1003) - Fix pip install with older versions of pip (#1011) @@ -265,12 +268,12 @@ with napari.gui_qt(): - Roadmap for 0.3 releases (#1095) - Add installed plugins to sys_info() (#1096) - Avoid pillow 7.1.0 (#1099) -- Pin pillow at <= 7.0.0 (#1108) +- Pin pillow at \<= 7.0.0 (#1108) - Fix a number of sphinxdocs errors (#1113) - Fix miniconda download in CI (#1119) - Convert old release notes to md (#1135) - Automate release process (#1138) -- Open up Pillow again after 7.1.* (#1146) +- Open up Pillow again after 7.1.\* (#1146) - Fix black consistency (#1152) - Fix sphinx (#1155) - Change release notes source file to use .md ext (#1156) @@ -280,7 +283,6 @@ with napari.gui_qt(): - Update manifest.in for plugin code removal (#1187) - Fix pip-missing-reqs step (#1189) - ## 13 authors added to this release (alphabetical) - [Bhavya Chopra](https://github.com/napari/napari/commits?author=BhavyaC16) - @BhavyaC16 @@ -297,7 +299,6 @@ with napari.gui_qt(): - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Tony Tung](https://github.com/napari/napari/commits?author=ttung) - @ttung - ## 13 reviewers added to this release (alphabetical) - [Ahmet Can Solak](https://github.com/napari/napari/commits?author=AhmetCanSolak) - @AhmetCanSolak diff --git a/docs/release/release_0_3_1.md b/docs/release/release_0_3_1.md index d0b4d46dc..083807c8a 100644 --- a/docs/release/release_0_3_1.md +++ b/docs/release/release_0_3_1.md @@ -11,13 +11,14 @@ This is a bug fix release to address issues that snuck through into 0.3.0. For more information, examples, and documentation, please visit our website: https://github.com/napari/napari - ## Improvements -- CLI accepts --plugin or any add_* kwargs (#1220) + +- CLI accepts --plugin or any add\_\* kwargs (#1220) - Specify viewer.open(plugins='builtins') for all tests (#1222) - Unify user/plugin kwargs. Use filename for layer name (#1232) ## Bug Fixes + - rework dask cache (#1206) - Use grayscale when n_channels=1 (#1217) - Better error on magic_imread with no files (#1218) @@ -28,20 +29,18 @@ https://github.com/napari/napari - Don't try to get an event.key name if there is no event.key (#1241) - Update guess_multiscale to deal with strange inputs (#1244) - ## Support + - Don't build wheels with releases (#1215) - Update github issues templates with links to image.sc and zulip (#1234) - add new performance doc in new "explanations" directory (#1239) - ## 3 authors added to this release (alphabetical) - [Juan Nunez-Iglesias](https://github.com/napari/napari/commits?author=jni) - @jni - [Philip Winston](https://github.com/napari/napari/commits?author=pwinston) - @pwinston - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - ## 5 reviewers added to this release (alphabetical) - [Juan Nunez-Iglesias](https://github.com/napari/napari/commits?author=jni) - @jni diff --git a/docs/release/release_0_3_2.md b/docs/release/release_0_3_2.md index 0b39d5b0e..32790a9e1 100644 --- a/docs/release/release_0_3_2.md +++ b/docs/release/release_0_3_2.md @@ -6,17 +6,17 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari - ## New Features + - General multithreading API and @thread_worker decorator (#1210) - Rich jupyter display for napari screenshots (#1269) - Allow add_dock_widget to accept a list of widgets (#1296) ## Improvements + - Make Qt component non private module (#1122) - Docs on threading and the event loop (#1258) - Add option to disable overwrite of labels during painting (#1264) @@ -26,6 +26,7 @@ https://github.com/napari/napari - Blue colored active layer (#1284) ## Bug Fixes + - Automatically set high DPI scaling in Qt (#820) - Use napari logo as taskbar icon on windows when run as python script (#1208) - Remove scipy.stats import (#1250) @@ -40,6 +41,7 @@ https://github.com/napari/napari - Fix shift-click for selecting shapes (#1297) ## Build Tools + - Add pooch to requirements/test.txt (#1249) - Prefer rcc binary at front of path (#1261) - Pin napari-svg to 0.1.2 (#1275) @@ -57,7 +59,6 @@ https://github.com/napari/napari - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [ziyangczi](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 11 reviewers added to this release (alphabetical) - [Chris Wood](https://github.com/napari/napari/commits?author=cwood1967) - @cwood1967 diff --git a/docs/release/release_0_3_3.md b/docs/release/release_0_3_3.md index be7c3d402..d8abe5d91 100644 --- a/docs/release/release_0_3_3.md +++ b/docs/release/release_0_3_3.md @@ -6,8 +6,7 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - -This is a small bug fix PR that pins our Qt version at < 5.15.0, due to +This is a small bug fix PR that pins our Qt version at \< 5.15.0, due to incompatibilities with their latest release until we fix them. See #1312 for discussion and the latest progress. @@ -15,20 +14,23 @@ For more information, examples, and documentation, please visit our website: https://github.com/napari/napari ## New Features + - Adding properties attribute to Labels layers (#1281) - Add eraser button and functionality to Labels layer (#1288) ## Improvements + - Shapes colors refactor (#1248) - Allow drag filling of labels (#1299) - Make Qt window public (#1306) ## Bug Fixes + - Exit context before return on `_repr_png` (#1298) ## Build Tools -- Pin PySide2 and PyQt5 at <5.15 (#1316) +- Pin PySide2 and PyQt5 at \<5.15 (#1316) ## 4 authors added to this release (alphabetical) @@ -37,7 +39,6 @@ https://github.com/napari/napari - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 4 reviewers added to this release (alphabetical) - [Draga Doncila](https://github.com/napari/napari/commits?author=DragaDoncila) - @DragaDoncila diff --git a/docs/release/release_0_3_5.md b/docs/release/release_0_3_5.md index c832e868c..7acd3e704 100644 --- a/docs/release/release_0_3_5.md +++ b/docs/release/release_0_3_5.md @@ -6,11 +6,11 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari ## Highlights + This release contains a number of bug fixes on various platforms. For those interested in napari performance, we have added a new performance monitoring mode, that can be activated by the `NAPARI_PERFMON` environment variable, see @@ -18,17 +18,18 @@ mode, that can be activated by the `NAPARI_PERFMON` environment variable, see our docs on napari's [rendering](https://napari.org/stable/guides/rendering.html) including plans for the future. - ## New Features + - Allow using of custom color dictionary in labels layer (#1339 and #1362) - Allow Shapes face and edge colors to be mapped to properties (#1342) - Add performance monitoring widget (#1262) - ## Improvements + - Factor out ImageSlice and ImageView from Image (#1343) ## Bug Fixes + - Fix warning for python 3.8 (#1335) - Fix range slider position (#1344) - Fix Linux and Windows key hold detection (#1350) @@ -36,15 +37,14 @@ including plans for the future. - Fix deleting layers changing dims (#1359) - Revert "remove scipy.stats import (#1250)" (#1371) - ## Build Tools and Support + - Remove broken link from BENCHMARKS.md (#1236) - New documentation on rendering (#1328) - Remove incorrect dashes in cirrus push_docs task (#1330) - Use correct pyqt version in tests (#1331) - Fix docs version, reformat, and add explanations (#1368) - ## 8 authors added to this release (alphabetical) - [Genevieve Buckley](https://github.com/napari/napari/commits?author=GenevieveBuckley) - @GenevieveBuckley @@ -56,7 +56,6 @@ including plans for the future. - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 8 reviewers added to this release (alphabetical) - [Davis Bennett](https://github.com/napari/napari/commits?author=d-v-b) - @d-v-b diff --git a/docs/release/release_0_3_6.md b/docs/release/release_0_3_6.md index 9abed934c..1f253c43d 100644 --- a/docs/release/release_0_3_6.md +++ b/docs/release/release_0_3_6.md @@ -6,11 +6,11 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari ## Highlights + This release contains the long awaited addition of text to both the points and shapes layers (#1374). Checkout our `examples/*_with_text.py` for simple usage and this [segmentation annotation tutorial](https://napari.org/tutorials/segmentation/annotate_segmentation) @@ -27,14 +27,14 @@ to the SciPy conference sprints. We’re delighted to welcome new contributors t the codebase. If you want help contributing to napari, reach out to us on our chat room at https://napari.zulipchat.com! - ## New Features + - Functions to split/combine multiple layers along an axis (#1322) - Add text to shapes and points via TextManager (#1374) - Add circle/spherical brush to Labels paint brush (#1429) - ## Improvements + - Add ability to run napari script with/without gui_qt from CLI (#1373) - Event handler refactor for image layer "reverted by (#1416)" (#1376) - Add ndim as a keyword argument to shapes to support creating empty layers (#1379) @@ -45,8 +45,8 @@ room at https://napari.zulipchat.com! - Move contrast limits and gamma to the shader by vendoring vispy code (#1456) - Reduce attenuation default and range (#1460) - ## Bug Fixes + - Revert "remove scipy.stats import (#1250)" (#1371) - Fix vispy volume colormap changing (#1402) - Fix vertical alignment of QLabel in QtDimSliderWidget (#1415) @@ -61,8 +61,8 @@ room at https://napari.zulipchat.com! - Fix edge color select bug (#1464) - Proper recognition tiff files with ".TIF" extension (#1472) - ## Build Tools + - Updates plugin dev docs to encourage github topic (#1366) - Refactor all tests to hide GUI (#1372) - Fix two remaining tests that try to show the viewer. (#1375) @@ -81,7 +81,6 @@ room at https://napari.zulipchat.com! - Fix events docstring type (#1481) - Don't look for release notes in pre-releases (#1483) - ## 14 authors added to this release (alphabetical) - [Cameron Lloyd](https://github.com/napari/napari/commits?author=camlloyd) - @camlloyd @@ -99,7 +98,6 @@ room at https://napari.zulipchat.com! - [Trevor Manz](https://github.com/napari/napari/commits?author=manzt) - @manzt - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 12 reviewers added to this release (alphabetical) - [Chris Wood](https://github.com/napari/napari/commits?author=cwood1967) - @cwood1967 diff --git a/docs/release/release_0_3_7.md b/docs/release/release_0_3_7.md index 162190096..d688d5406 100644 --- a/docs/release/release_0_3_7.md +++ b/docs/release/release_0_3_7.md @@ -35,6 +35,7 @@ This is in addition to many bug fixes and usability improvements — see below for the full list! Thank you to everyone who contributed to this release! ## New Features + - Add briefcase app bundles to release assets (#1289) - Evented list (#1444) - Async rendering part 1 (#1565) @@ -42,9 +43,10 @@ for the full list! Thank you to everyone who contributed to this release! - Add SELECTED color-mode for individual label visibility (#1555) ## Improvements -- auto generate view_* methods (#978) + +- auto generate view\_\* methods (#978) - Add world extents for layers (#1360) -- Reorganize _qt module (#1431) +- Reorganize \_qt module (#1431) - Add CTRL+ Mouse Scroll to scroll through last active stack (#1434) - Perfmon System version 2 (#1453) - Add global exception catching in Qt (#1476) @@ -55,8 +57,8 @@ for the full list! Thank you to everyone who contributed to this release! - Improve Shapes layer performance (#1561) - Add support for deprecate signal in EmitterGroup (#1582) - ## Bug Fixes + - Prevent napari crashing after layer is dragged into trash (#1487) - Fix URL parsing scheme for view_path (#1515) - Fix potentially-confusing typo in comment (#1521) @@ -74,8 +76,8 @@ for the full list! Thank you to everyone who contributed to this release! - Restore multiple command-line arguments (#1597) - Lock briefcase version (#1599) - ## Build Tools + - Points slicing benchmark (#1435) - Fix 0.3.6 release notes to indicate reversions (#1498) - format imports with isort (#1505) @@ -106,7 +108,6 @@ for the full list! Thank you to everyone who contributed to this release! - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 12 reviewers added to this release (alphabetical) - [Davis Bennett](https://github.com/napari/napari/commits?author=d-v-b) - @d-v-b @@ -121,4 +122,3 @@ for the full list! Thank you to everyone who contributed to this release! - [Philip Winston](https://github.com/napari/napari/commits?author=pwinston) - @pwinston - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - diff --git a/docs/release/release_0_3_8.md b/docs/release/release_0_3_8.md index 695b5a860..cf1b2506a 100644 --- a/docs/release/release_0_3_8.md +++ b/docs/release/release_0_3_8.md @@ -9,19 +9,20 @@ rendering), and the scientific Python stack (numpy, scipy). For more information, examples, and documentation, please visit our website: https://napari.org - ## Highlights + This release is mainly a bug fix release, with a number of small improvements including around our contrast limits updates (#1622) and points coloring (#1641) and (#1643). This will also be our last release supporting Python3.6. - ## Improvements + - Async-2.5: Vispy Changes (#1607) - Increase screenshot performance (#1615) - Speed up points selection and selection display (#1648) ## Bug Fixes + - Remove silence overwritte during screenshot (#1567) - Fix usage nan_to_num to work with numpy 1.16 (#1613) - Update colortransform when building texture (#1622) @@ -30,8 +31,8 @@ and (#1643). This will also be our last release supporting Python3.6. - Fix new color point off (#1643) - Prevent bundle fail when name is not napari (#1647) - ## Build Tools and Docs + - Add test matrix entry to test minimum requirements (#1617) - Change bundle deps approach (#1619) - Use pyside2-rcc if pyrcc5 fail (#1626) @@ -40,7 +41,6 @@ and (#1643). This will also be our last release supporting Python3.6. - Remove Python version requirement for pre-commit (#1645) - Add github retry action to try and fix flaky app bundling (#1649) - ## 6 authors added to this release (alphabetical) - [Genevieve Buckley](https://github.com/napari/napari/commits?author=GenevieveBuckley) - @GenevieveBuckley @@ -52,7 +52,6 @@ and (#1643). This will also be our last release supporting Python3.6. - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 5 reviewers added to this release (alphabetical) - [Grzegorz Bokota](https://github.com/napari/napari/commits?author=Czaki) - @Czaki diff --git a/docs/release/release_0_4_0.md b/docs/release/release_0_4_0.md index 2ee6ac09b..84621e74c 100644 --- a/docs/release/release_0_4_0.md +++ b/docs/release/release_0_4_0.md @@ -15,6 +15,7 @@ For more information, examples, and documentation, please visit our website at https://napari.org ## Highlights + napari 0.4.0 is the culmination of months of improvements to our data models. It finally brings the data from all layers into a consistent, global coordinate system. This means our display is more accurate (we aim for pixel-perfect @@ -39,6 +40,7 @@ issues at https://github.com/napari/napari/issues. We thank the many contributors who have made this release possible! ## New Features + - Add camera model (#854) - Tracks layer (#1361) - Affine transforms (#1616) @@ -48,8 +50,8 @@ We thank the many contributors who have made this release possible! - Add BOP (blue/orange/purple) colormaps (#1743) - Add cursor model (#1763) - ## Improvements + - Dataclass decorator with events and properties (#1475) - Docker Integration by adding dockerfile and docker.md (#1496) - Don't show colormap for rgb images (#1586) @@ -67,7 +69,7 @@ We thank the many contributors who have made this release possible! - Async-8: config cleanup (#1684) - Async-9: async unit testing infra (#1685) - Allow dask development version to work with napari (#1692) -- Autogenerate add_* methods (#1694) +- Autogenerate add\_\* methods (#1694) - Async-10: add experimental vendor package humanize (#1698) - Async-11: IPython commands (#1699) - Async-12: Processes (#1704) @@ -80,6 +82,7 @@ We thank the many contributors who have made this release possible! - Add warning on failed save (#1770) ## Bug Fixes + - Status bar value bug for automatically down-sampled images (#1577) - Magic name handle attribute exception (#1635) - Speed up points selection and selection display (#1648) @@ -91,7 +94,7 @@ We thank the many contributors who have made this release possible! - Fix image extent (#1696) - Fix for tracks layer properties as dataframe (#1711) - Fixes magic_name for all layers, fixes #1709 due to #1694 autogen (#1714) -- Add tests for #1709 for magic_name with add_* layers (#1716) +- Add tests for #1709 for magic_name with add\_\* layers (#1716) - Fix delete all shapes (#1718) - Fix nD mutliscale (#1723) - Fix initialization of tracks color_by (#1725) @@ -115,12 +118,14 @@ We thank the many contributors who have made this release possible! - Fix zoom of scale bar and axes visuals on launch (#1791) ## API Changes and Deprecations + - Make layer dims private (#1581) - Drop Python 3.6 (#1652) - Make signals public attribute of WorkerBase (#1681) - Add extent named tuple (#1771) ## Build Tools and Docs + - New perfmon doc (#1634) - Build docs action (#1638) - Increase retry action timeout from 10 to 30 mins (#1651) @@ -129,7 +134,6 @@ We thank the many contributors who have made this release possible! - Fix top-level links referenced in README.md and published on napari.org (#1750) - Update installation readme (#1758) - ## 20 authors added to this release (alphabetical) - [Abhishek Patil](https://github.com/napari/napari/commits?author=zeroth) - @zeroth @@ -153,7 +157,6 @@ We thank the many contributors who have made this release possible! - [Volker Hilsenstein](https://github.com/napari/napari/commits?author=VolkerH) - @VolkerH - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 18 reviewers added to this release (alphabetical) - [Alan R Lowe](https://github.com/napari/napari/commits?author=quantumjot) - @quantumjot @@ -174,4 +177,3 @@ We thank the many contributors who have made this release possible! - [Philip Winston](https://github.com/napari/napari/commits?author=pwinston) - @pwinston - [Volker Hilsenstein](https://github.com/napari/napari/commits?author=VolkerH) - @VolkerH - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - diff --git a/docs/release/release_0_4_1.md b/docs/release/release_0_4_1.md index 750ea5c10..8f40446c8 100644 --- a/docs/release/release_0_4_1.md +++ b/docs/release/release_0_4_1.md @@ -84,10 +84,10 @@ investigate some crashes that it seemed to be contributing to. See #1905. ## API Changes -- ``Viewer.grid_view()`` and ``Viewer.stack_view()`` are deprecated. - Instead, use ``viewer.grid.enabled = ``. (#1821) -- ``Viewer.grid_stride`` and ``Viewer.grid_size`` are deprecated. Instead, - use ``Viewer.grid.stride`` and ``Viewer.grid.shape``. (#1821, #1847) +- `Viewer.grid_view()` and `Viewer.stack_view()` are deprecated. + Instead, use `viewer.grid.enabled = `. (#1821) +- `Viewer.grid_stride` and `Viewer.grid_size` are deprecated. Instead, + use `Viewer.grid.stride` and `Viewer.grid.shape`. (#1821, #1847) ## Build Tools and Docs @@ -130,7 +130,6 @@ investigate some crashes that it seemed to be contributing to. See #1905. - [Will Moore](https://github.com/napari/napari/commits?author=will-moore) - @will-moore - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 10 reviewers added to this release (alphabetical) - [Abhishek Patil](https://github.com/napari/napari/commits?author=zeroth) - @zeroth diff --git a/docs/release/release_0_4_10.md b/docs/release/release_0_4_10.md index a54a6a6eb..57d75f5ba 100644 --- a/docs/release/release_0_4_10.md +++ b/docs/release/release_0_4_10.md @@ -6,18 +6,18 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari ## Highlights + This is a fairly small release, that follows on quickly from 0.4.9 to fix a regression in our ability to save layer data (fixed in #2876). It also contains some improvements to our progress bars (in #2654) and how we compose affine and scale/translate transforms on the layers (in #2855). - ## Improvements + - Add nesting support for progress bars (#2654) - Auto generate documentation for preferences (#2672) - Add support for setting the settings configuration path via CLI and import (#2760) @@ -27,8 +27,8 @@ layers (in #2855). - Make `EventedModel` compatible with `dask.Delayed` objects (#2879) - Add more shortcuts to settings (#2882) - ## Bug Fixes + - Typo in action_manager.py (#2869) - Tifffile compress' kwargs deprecated. Update to compression. (#2872) - Fix save and update tests (#2876) @@ -37,14 +37,14 @@ layers (in #2855). - Fix for too-late magicgui type registration #2891 ## API Changes -- In #2855 we have now changed the composition behavior of the affine kwarg and the individual -scale, translate, rotate, and shear kwargs on the layers. Before this release if affine was passed -the others would be ignored. Now they will be composed as `affine * (rotate * shear * scale + translate)`. +- In #2855 we have now changed the composition behavior of the affine kwarg and the individual + scale, translate, rotate, and shear kwargs on the layers. Before this release if affine was passed + the others would be ignored. Now they will be composed as `affine * (rotate * shear * scale + translate)`. ## Build Tools -- Call import-linter only in CI (#2878) +- Call import-linter only in CI (#2878) ## 8 authors added to this release (alphabetical) @@ -57,7 +57,6 @@ the others would be ignored. Now they will be composed as `affine * (rotate * sh - [Pam](https://github.com/napari/napari/commits?author=ppwadhwa) - @ppwadhwa - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - ## 10 reviewers added to this release (alphabetical) - [alisterburt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt diff --git a/docs/release/release_0_4_11.md b/docs/release/release_0_4_11.md index e18d4a49c..6131baf91 100644 --- a/docs/release/release_0_4_11.md +++ b/docs/release/release_0_4_11.md @@ -6,7 +6,6 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari @@ -37,7 +36,6 @@ Lambert for these features! Read on below for the full list of new features, improvements, bug fixes, and more! Thanks to our incredible user and contributor community. - ## New Features - Add context menu on layer list, introduce `QtActionContextMenu`. (#2556) @@ -58,7 +56,6 @@ more! Thanks to our incredible user and contributor community. - Mask image from points layer (#3151) - Add .npy reader to builtin reader (#3271) - ## Improvements - Add `assign_[plugin]_to_extension` methods on plugin_manager. (#2695) @@ -80,7 +77,7 @@ more! Thanks to our incredible user and contributor community. - Emit data event when moving, adding or removing shapes or points (#2992) - Add TypedMutableMapping and EventedDict (#2994) - Add isosurface rendering to Labels (#3006) -- Remove mentions of _mode_history (2987) (#3008) +- Remove mentions of \_mode_history (2987) (#3008) - Change opacity slider to float slider (#3016) - Refactor the Point, Label and Shape Layer Mode logic. (#3050) - Make flash effect feel more instant (#3060) @@ -122,7 +119,6 @@ more! Thanks to our incredible user and contributor community. - Mesh depth (#3265) - Make notification text selectable (#3310) - ## Bug Fixes - Fix notification manager threading test (#2892) @@ -168,7 +164,7 @@ more! Thanks to our incredible user and contributor community. - Fix memory leak in napari (#3217) - Disable space bar on layer list (#3234) - Close napari window on Ctrl+C without geting window focus (#3239) -- Skip labeled sliders for <5.14 (#3243) +- Skip labeled sliders for \<5.14 (#3243) - Don't pass interpolation when creating a new projection layer (#3247) - Prevent greedy dask array calculation when creating an Image layer (#3248) - Fix plane normal inconsistency (#3264) @@ -182,7 +178,7 @@ more! Thanks to our incredible user and contributor community. - Fix connect_setattr to handle single arguments better (#3324) - Fix objectName being an empty string (#3326) - Fix napari.run aborting due to IPython being imported during script (#3328) -- Fix _old_size attribute error in main window (#3329) +- Fix \_old_size attribute error in main window (#3329) ## API Changes @@ -190,14 +186,12 @@ more! Thanks to our incredible user and contributor community. - Enforce layer.metadata as dict (#3020) - Use enum objects in EventedModel (#3112) - ## UI Changes - Remove keybindings dialog from help menu (#3048) - Remove plugin sorter from plugin install dialog (#3069) - Update Labels layer keybindings to be more ergonomic (#3072) - ## Build Tools, Tests, Documentation, and other Tasks - Add imagecodecs to the bundle to open additional tiffs (#2895) @@ -245,7 +239,6 @@ more! Thanks to our incredible user and contributor community. - Pin furo version (#3315) - Update the affine parameter description in several classes (#3319) - ## 21 authors added to this release (alphabetical) - [Abigail McGovern](https://github.com/napari/napari/commits?author=AbigailMcGovern) - @AbigailMcGovern @@ -272,7 +265,6 @@ more! Thanks to our incredible user and contributor community. - [Volker Hilsenstein](https://github.com/napari/napari/commits?author=VolkerH) - @VolkerH - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 19 reviewers added to this release (alphabetical) - [Alister Burt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt @@ -294,4 +286,3 @@ more! Thanks to our incredible user and contributor community. - [Pam](https://github.com/napari/napari/commits?author=ppwadhwa) - @ppwadhwa - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - diff --git a/docs/release/release_0_4_12.md b/docs/release/release_0_4_12.md index 22428f124..a6d9fbe72 100644 --- a/docs/release/release_0_4_12.md +++ b/docs/release/release_0_4_12.md @@ -6,7 +6,6 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari @@ -23,11 +22,12 @@ be converted from a context menu on the layer list (#3402). See the full list of merged pull requests below for further delails! ## New Features + - Add progress bar when opening list of files (#3355) - Add right-click context menu to convert label data type. (#3402) - ## Improvements + - Support for `Future` return type in magicgui widget (#2581) - Don't register dask cache globally - but do use cache as context manager when slicing (#3285) - Hide or Destroy dock widgets (#3331) @@ -59,8 +59,8 @@ See the full list of merged pull requests below for further delails! - Drop pythonw patch in windows bundle (#3479) - Revert "drop pythonw patch (#3479)" (#3501) - ## Bug Fixes + - Fix `_old_size` attribute error in main window (#3329) - Fix problem with local function signal binding (#3352) - Fix __getattr__ in WorkerBase (#3368) @@ -73,19 +73,17 @@ See the full list of merged pull requests below for further delails! - Fix naming inconsistency for windows bundle (#3476) - Add ability to provide empty data to vectors layer (#2995) - ## API Changes - ## Deprecations - ## Documentation -- Fix docs order in _toc.yml for 0.4.11 (#3330) -- Add example with data of mixed dimensionality (#3392) +- Fix docs order in \_toc.yml for 0.4.11 (#3330) +- Add example with data of mixed dimensionality (#3392) ## Build Tools and Support + - Split windows pyside/pyqt into own GitHub action check (#2989) - Attempt to cache tox virtualenv. (#2996) - Update references to master to point to main (#3351) @@ -139,7 +137,6 @@ See the full list of merged pull requests below for further delails! - [Tim-Oliver Buchholz](https://github.com/napari/napari/commits?author=tibuch) - @tibuch - [Ziyang Liu](https://github.com/napari/napari/commits?author=potating-potato) - @potating-potato - ## 20 reviewers added to this release (alphabetical) - [Alister Burt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt @@ -162,4 +159,3 @@ See the full list of merged pull requests below for further delails! - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Tim-Oliver Buchholz](https://github.com/napari/napari/commits?author=tibuch) - @tibuch - [Ziyang Liu](https://github.com/napari/napari/commits?author=potating-potato) - @potating-potato - diff --git a/docs/release/release_0_4_13.md b/docs/release/release_0_4_13.md index 716dc2795..209d78e9b 100644 --- a/docs/release/release_0_4_13.md +++ b/docs/release/release_0_4_13.md @@ -30,13 +30,14 @@ https://github.com/napari/napari Complete list of changes below: ## Highlights + - Spherical Points (#3430) - Point selection in 3d (#3508) - Surface normals and wireframe (#3689) - Add dialog for selecting reader plugin when dragging & dropping a file (#3799) - ## New Features + - 3D click + drag interactivity API (#3205) - Initial npe2 writer support (#3426) - Add glossary (#3569) @@ -44,8 +45,8 @@ Complete list of changes below: - Add `experimental_canvas_size_limits` argument to points (#3734) - 3D point selection with drag box (#3840) - ## Improvements + - Raise AttributeError when labels.contrast_limits are set to anything other than (0, 1) (#2573) - Allow magicgui to return a worker object (#2593) - Proposition of save as_dict in get_theme but with change default behaviour in future (#3429) @@ -57,7 +58,7 @@ Complete list of changes below: - Add Context object, and LayerListContextKeys (#3513) - Add class method to viewers to simplify closing all of them. (#3516) - Ndisplay async (#3517) -- Minor LayerList refactor to remove unneeded NumPy<=1.17 fallback code (#3522) +- Minor LayerList refactor to remove unneeded NumPy\<=1.17 fallback code (#3522) - Establish LayerDataProtocol and basic Multiscale wrapper (resuscitate #2683) (#3560) - Add checked `QpushButton` qss (#3561) - Allow disconnect from EventEmiter using object (#3566) @@ -137,8 +138,8 @@ Complete list of changes below: - Better Notification __str__ method (#3933) - Fix ndisplay button highlight (#3935) - ## Bug Fixes + - Fix removing selected points with derived text (#3505) - Fix for wrong bounding box definition (#3511) - Fix missing class annotations from stubgen (#3514) @@ -180,19 +181,18 @@ Complete list of changes below: - Switch append to concat (#3963) - Update plugin docs index (#3964) - ## API Changes - ## Deprecations + - Deprecate public `window.qt_viewer` (remove in 0.5.0) (#3748) - Deprecate `qt_viewer.screenshot` & `clipboard` (#3765) - Restrict PublicOnlyProxy to napari namespace, allow napari-internal private usage (#3777) - Change PublicOnlyProxy deprecation expiry to 0.5.0 (#3788) - Remove deprecation of sceenshot in qt_viewer (#3937) - ## Build Tools and Docs + - Auto generate event reference docs (#2750) - Try to run docs under xvfb-run. (#3497) - Patch plugin manager in `plugins.io` during test (#3515) @@ -229,7 +229,7 @@ Complete list of changes below: - Update best_practices.md (#3744) - Pin scikit image (!=0.19.0) in test suite (#3749) - Moved files to agree with ToC reorganization. (#3751) -- [pre-commit.ci] pre-commit autoupdate (#3756) +- \[pre-commit.ci\] pre-commit autoupdate (#3756) - Fix outdated API in eventloop docs (#3772) - Add glossary to copy-docs list (#3791) - Update glossary.md links (#3797) @@ -240,7 +240,7 @@ Complete list of changes below: - Readme install update (#3862) - Link "development status" button in readme to explanation of "Alpha", "Beta",... (#3885) - Use packaging.version, DeprecationWarnings since setuptools 59+ (#3894) -- [pre-commit.ci] pre-commit autoupdate (#3902) +- \[pre-commit.ci\] pre-commit autoupdate (#3902) - npe2 doc fix: remove outdated text (#3905) - Fix docs calendar (#3912) - Update plugin docs (#3916) @@ -254,7 +254,6 @@ Complete list of changes below: - Add descriptive information to assertion checking if QtViewer is cleaned properly (#3960) - Fix preference docs (#3967) - ## 28 authors added to this release (alphabetical) - [Ahmet Can Solak](https://github.com/napari/napari/commits?author=AhmetCanSolak) - @AhmetCanSolak @@ -286,7 +285,6 @@ Complete list of changes below: - [Subhashree Mishra](https://github.com/napari/napari/commits?author=Mishrasubha) - @Mishrasubha - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - ## 30 reviewers added to this release (alphabetical) - [Ahmet Can Solak](https://github.com/napari/napari/commits?author=AhmetCanSolak) - @AhmetCanSolak @@ -319,4 +317,3 @@ Complete list of changes below: - [Robert Haase](https://github.com/napari/napari/commits?author=haesleinhuepf) - @haesleinhuepf - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=potating-potato) - @potating-potato - diff --git a/docs/release/release_0_4_14.md b/docs/release/release_0_4_14.md index d8d266a34..7d82f14ba 100644 --- a/docs/release/release_0_4_14.md +++ b/docs/release/release_0_4_14.md @@ -18,6 +18,7 @@ https://github.com/napari/napari Complete list of changes below: ## Highlights + - Enable shapes layer (right click) > convert to labels (#3978) - Out-of-slice rendering for Vectors (#2902) - Hide points individually (#3625) @@ -25,11 +26,12 @@ Complete list of changes below: ## New Features ## Improvements + - Update tests to remove warnings (#3974) - Add test to check that things are removed properly (#3996) - Change base point slice thickness to half a unit (#3997) - Change base vector slice thickness to half a unit (#4001) -- Use scikit-image[data] in the bundle (#4024) +- Use scikit-image\[data\] in the bundle (#4024) - Rename n_dimensional to out_of_slice_display in Points and Vectors (#4007) - Raise reader plugin errors sooner, (avoid cryptic error messages) (#4026) - Unlink close button from plugins (#4027) @@ -37,6 +39,7 @@ Complete list of changes below: - Update on merge to stack action ordering (#4033) ## Bug Fixes + - Fix about on python 3.10 (#3972) - Revert changes to scikit-image test API (#3979) - Fix missing builtins in bundle (#3982) @@ -51,8 +54,8 @@ Complete list of changes below: ## Deprecations - ## Build Tools and Docs + - DOC: Misc doc syntax. (#3985) - Fix include_package_data inclusion in the "your first plugin" docs page (#4022) - 0.4.14 translation strings (#4029) @@ -60,6 +63,7 @@ Complete list of changes below: - Update packaging files, remove setup.py and requirements.txt (#4014) ## 11 authors added to this release (alphabetical) + - [Alister Burt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt - [Andy Sweet](https://github.com/napari/napari/commits?author=andy-sweet) - @andy-sweet - [Draga Doncila Pop](https://github.com/napari/napari/commits?author=DragaDoncila) - @DragaDoncila @@ -72,8 +76,8 @@ Complete list of changes below: - [Pam](https://github.com/napari/napari/commits?author=ppwadhwa) - @ppwadhwa - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - ## 11 reviewers added to this release (alphabetical) + - [Alister Burt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt - [Andy Sweet](https://github.com/napari/napari/commits?author=andy-sweet) - @andy-sweet - [Chi-li Chiu](https://github.com/napari/napari/commits?author=chili-chiu) - @chili-chiu @@ -85,5 +89,3 @@ Complete list of changes below: - [Nathan Clack](https://github.com/napari/napari/commits?author=nclack) - @nclack - [Nicholas Sofroniew](https://github.com/napari/napari/commits?author=sofroniewn) - @sofroniewn - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - - diff --git a/docs/release/release_0_4_15.md b/docs/release/release_0_4_15.md index 1aea3fa86..3ab123773 100644 --- a/docs/release/release_0_4_15.md +++ b/docs/release/release_0_4_15.md @@ -62,7 +62,7 @@ Keep reading below for the full list of changes! - Prevent errors in qt/test_plugin_widgets from installed plugins (#4058) - Add a `recurse` option to `EventedModel.update` (#4062) - Only add progress widget on worker start (#4068) -- minor refactor of Layer._slice_indices (#4072) +- minor refactor of Layer.\_slice_indices (#4072) - Improve missing widget name error for npe2 plugins (#4080) - Update shapes color mapping when changing property (#4081) - reduce NumPy overhead in ScaleTranslate and Affine transform calls (#4094) @@ -104,7 +104,7 @@ Keep reading below for the full list of changes! - Fix json usage when setting preferences from nested environment variables (#4078) - Fix import in plugin_manager that is causing typing fails (#4106) - Contrast limits popup fix (#4122) -- Prevent deepcopy of layer._source (#4128) +- Prevent deepcopy of layer.\_source (#4128) - Always pass list of strings to npe2. (#4130) - Don't validate defaults on `EventedModel` (#4138) - support conda-style (pyside2-)rcc locations (#4144) @@ -125,10 +125,8 @@ Keep reading below for the full list of changes! ## API Changes - ## Deprecations - ## Build Tools - Don't use git+https to install on CI (#4116) @@ -141,7 +139,7 @@ Keep reading below for the full list of changes! ## Other Pull Requests -- [pre-commit.ci] pre-commit autoupdate (#4032) +- \[pre-commit.ci\] pre-commit autoupdate (#4032) - remove leftover comments on `Points.n_dimensional` (#4052) - Drop python 3.7 (#4063) - avoid scikit-image deprecation warnings in test suite (#4071) @@ -170,7 +168,7 @@ Keep reading below for the full list of changes! - [Matthias Bussonnier](https://github.com/napari/napari/commits?author=Carreau) - @Carreau - [Melissa Weber Mendonça](https://github.com/napari/napari/commits?author=melissawm) - @melissawm - [Nicholas Sofroniew](https://github.com/napari/napari/commits?author=sofroniewn) - @sofroniewn -- [pre-commit-ci[bot]](https://github.com/napari/napari/commits?author=pre-commit-ci[bot]) - @pre-commit-ci[bot] +- [pre-commit-ci\[bot\]](https://github.com/napari/napari/commits?author=pre-commit-ci%5Bbot%5D) - @pre-commit-ci\[bot\] - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 ## 25 reviewers added to this release (alphabetical) @@ -200,4 +198,3 @@ Keep reading below for the full list of changes! - [shahidhaider](https://github.com/napari/napari/commits?author=shahidhaider) - @shahidhaider - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=potating-potato) - @potating-potato - diff --git a/docs/release/release_0_4_16.md b/docs/release/release_0_4_16.md index 9938a86be..96e6a8037 100644 --- a/docs/release/release_0_4_16.md +++ b/docs/release/release_0_4_16.md @@ -21,57 +21,72 @@ After discussion in [#4102](https://github.com/napari/napari/pull/4102), [#4111] This has led to the following API and GUI changes -- `builtins` is now the default value for the `plugin` argument in `viewer.open`. This means - - you should **always** explicitly pass a plugin to `viewer.open`, if you don't want to use `builtins` (and we encourage you to pass the argument anyway). +- `builtins` is now the default value for the `plugin` argument in `viewer.open`. This means - - To specify a plugin in a Python script: + - you should **always** explicitly pass a plugin to `viewer.open`, if you don't want to use `builtins` (and we encourage you to pass the argument anyway). - ```python - import napari + - To specify a plugin in a Python script: - viewer = napari.Viewer() - viewer.open('my-path.tif') # this will throw MultipleReaderError if napari_tifffile is installed as both it and builtins could open the file - viewer.open('my-path.tif', plugin='napari_tifffile') # this won't - ``` + ```python + import napari - - `viewer.open` will **not** inspect your file extension preferences, and will not choose among available plugins - - if you wish to opt into the "gui-like" behavior where your preferences are respected and we infer a plugin if just one is compatible with your file path, you must explicitly use `plugin=None` + viewer = napari.Viewer() + viewer.open( + "my-path.tif" + ) # this will throw MultipleReaderError if napari_tifffile is installed as both it and builtins could open the file + viewer.open("my-path.tif", plugin="napari_tifffile") # this won't + ``` - - To opt into plugin inference behavior: + - `viewer.open` will **not** inspect your file extension preferences, and will not choose among available plugins - ```python - import napari + - if you wish to opt into the "gui-like" behavior where your preferences are respected and we infer a plugin if just one is compatible with your file path, you must explicitly use `plugin=None` - viewer = napari.Viewer() - viewer.open('my-path.nd2', plugin=None) - ``` - - If multiple plugins could read your file, you will see a `MultipleReaderError` - - A preferred reader missing from current plugins will trigger a warning, but the preference will be otherwise ignored - - A preferred reader failing to read your file will result in an error e.g. if you saved `napari_tifffile` as a preference for TIFFs but then tried to open a broken file + - To opt into plugin inference behavior: - - To save a preference for a file pattern in Python, use: + ```python + import napari - ```python - from napari.settings import get_settings - get_settings().plugins.extension2reader['*.tif'] = 'napari_tifffile' - get_settings().plugins.extension2reader['*.zarr'] = 'napari-ome-zarr' - ``` + viewer = napari.Viewer() + viewer.open("my-path.nd2", plugin=None) + ``` + + - If multiple plugins could read your file, you will see a `MultipleReaderError` + + - A preferred reader missing from current plugins will trigger a warning, but the preference will be otherwise ignored + + - A preferred reader failing to read your file will result in an error e.g. if you saved `napari_tifffile` as a preference for TIFFs but then tried to open a broken file + + - To save a preference for a file pattern in Python, use: + + ```python + from napari.settings import get_settings + + get_settings().plugins.extension2reader["*.tif"] = "napari_tifffile" + get_settings().plugins.extension2reader["*.zarr"] = "napari-ome-zarr" + ``` - When opening a file through a GUI pathway (drag & drop, File -> Open, Open Sample) with no preferences saved, you are provided with a dialog allowing you to choose among the various plugins that are compatible with your file - - This dialog also allows you to save a preference for files and folders with extensions - - This dialog also pops up if a preferred reader fails to open your file - - This dialog does not pop up if only one plugin can open your file + + - This dialog also allows you to save a preference for files and folders with extensions + - This dialog also pops up if a preferred reader fails to open your file + - This dialog does not pop up if only one plugin can open your file + - Running `napari path` in the shell will also provide the reader dialog. You can still pass through a plugin choice, or layer keyword arguments - - To specify a plugin at the command line, use: - ```sh - napari my-path.tif --plugin napari_tifffile - ``` + - To specify a plugin at the command line, use: + + ```sh + napari my-path.tif --plugin napari_tifffile + ``` + - Preference saving for file reading is now supported for filename patterns accepted by `npe2` readers, rather than strictly file extensions - - Existing preferences for file extensions will be automatically updated e.g. `.tif` will become `*.tif` + + - Existing preferences for file extensions will be automatically updated e.g. `.tif` will become `*.tif` + - Reader preferences for filename patterns can be saved in the GUI via the preference dialog - - Reader preferences for folders are not yet supported in the GUI preference dialog - use the Python method above - - This will be addressed by the next release + + - Reader preferences for folders are not yet supported in the GUI preference dialog - use the Python method above + - This will be addressed by the next release We have thought carefully about these choices, but there are still some open questions to address, and features to implement. Some of these are captured across the issues listed below, and we'd love to hear any feedback you have about the new behavior! @@ -126,27 +141,27 @@ We have thought carefully about these choices, but there are still some open que - Bugfix: Divide by zero error making empty shapes layer (#4267) - Bugfix: Conversion between Label and Image with original scaling (#4272) - Address concurrent refresh in plugin list during multiple (un)installs (#4283) -- Delay import of _npe2 module in napari.__main__ to prevent duplicate discovery of plugins (#4311) +- Delay import of \_npe2 module in napari.__main__ to prevent duplicate discovery of plugins (#4311) - Fix black line ellipse (#4312) - Fix Labels.fill when Labels.data is an xarray.DataArray (#4314) - Fix image and label layer values reported in GUI status bar when display is 3D (#4315) - Quick fix for colormap updates not updating QtColorBox. (#4321) - Update `black` version because of break of private API in its dependency (#4327) - Fix progress update signature (#4333) -- move pixel center offset code into _ImageBase (#4352) +- move pixel center offset code into \_ImageBase (#4352) - Fix TextManager to work with vispy when using string constants (#4362) - Fix format string encoding for all numeric features (#4363) - Bugfix/broadcast projections by reducing number of axes (keepdims=False) (#4376) - Correctly order vispy layers on insertion (#4433) - napari --info: list npe2 plugins (#4445) -- Bugfix/Add affine to base_dict via _get_base_state() (#4453) +- Bugfix/Add affine to base_dict via \_get_base_state() (#4453) - Fix layer control pop-up issue (#4460) - fix Re-setting shapes data to initial data fails, but only in 3D (#4550) - Make sure we pass plugin through if opening file as stack (#4515) - Fix update of plugins and disable update button if not available on conda forge (for bundle) (#4512) - Connect napari events first to EventEmitter (#4480) - Fix AttributeError: 'LayerList' object has no attribute 'name' (#4276) -- Fix _BaseEventedItemModel.flags (#4558) +- Fix \_BaseEventedItemModel.flags (#4558) - Bug fix: blending multichannel images and 3D points (#4567) - Fix checkable menu entries when using PySide2 backend (#4581) @@ -210,7 +225,6 @@ We have thought carefully about these choices, but there are still some open que ## Deprecations - ## Build Tools - singularity and docker container images from CI (#3965) @@ -226,31 +240,31 @@ We have thought carefully about these choices, but there are still some open que - Set `TMP` on Windows+Mamba subprocesses if not set (#4462) - Update test_typing.yml (#4475) - Fix make-typestubs: use union for type hint instead of '|' (#4476) -- [conda] rework how plugin install/remove subprocesses receive the parent environment (#4520) -- [conda] revert default installation path (#4525) -- Pin vispy to <0.11 to prevent future breakages (#4594) +- \[conda\] rework how plugin install/remove subprocesses receive the parent environment (#4520) +- \[conda\] revert default installation path (#4525) +- Pin vispy to \<0.11 to prevent future breakages (#4594) ## Other Pull Requests - adds citation file (#3470) -- Add tests for _npe2.py (#4103) +- Add tests for \_npe2.py (#4103) - Decrease LFS size, gif -> webm. (#4207) - Run PNG crush on all Pngs. (#4208) - Refactor toward fixing local value capturing. (#4212) - Minor error message improvement. (#4219) - Bump npe2 to 0.2.0 and fix typing tests (#4241) - Remove headless test ignore, move orient_plane_normal test (#4245) -- [pre-commit.ci] pre-commit autoupdate (#4255) +- \[pre-commit.ci\] pre-commit autoupdate (#4255) - catch elementwise comparison warning that now shows frequently on layer creation (#4256) - fix octree imports (#4264) - Raise error when binding a button to a generator function (#4265) - MAINT: coverage lines +1 (#4297) - bump scipy minimum requirement from 1.4.0 to 1.4.1 (#4310) - MAINT: separate ImportError from ModuleNotFoundError (#4339) -- [pre-commit.ci] pre-commit autoupdate (#4354) +- \[pre-commit.ci\] pre-commit autoupdate (#4354) - Remove 'of' from 'in this example of we will' (#4356) - Fix npe2 import according to 0.3.0 deprecation warning (#4367) -- [pre-commit.ci] pre-commit autoupdate (#4378) +- \[pre-commit.ci\] pre-commit autoupdate (#4378) - add test for generate_3D_edge_meshes (#4416) - Fix mypy error in CI (#4439) - Make npe2 writer test more lenient (#4457) @@ -284,7 +298,7 @@ We have thought carefully about these choices, but there are still some open que - [Melissa Weber Mendonça](https://github.com/napari/napari/commits?author=melissawm) - @melissawm - [Pam](https://github.com/napari/napari/commits?author=ppwadhwa) - @ppwadhwa - [Peter Sobolewski](https://github.com/napari/napari/commits?author=psobolewskiPhD) - @psobolewskiPhD -- [pre-commit-ci[bot]](https://github.com/napari/napari/commits?author=pre-commit-ci[bot]) - @pre-commit-ci[bot] +- [pre-commit-ci\[bot\]](https://github.com/napari/napari/commits?author=pre-commit-ci%5Bbot%5D) - @pre-commit-ci\[bot\] - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Tom di Mino](https://github.com/napari/napari/commits?author=tdimino) - @tdimino - [Tru Huynh](https://github.com/napari/napari/commits?author=truatpasteurdotfr) - @truatpasteurdotfr @@ -335,4 +349,3 @@ We have thought carefully about these choices, but there are still some open que - [Tru Huynh](https://github.com/napari/napari/commits?author=truatpasteurdotfr) - @truatpasteurdotfr - [Volker Hilsenstein](https://github.com/napari/napari/commits?author=VolkerH) - @VolkerH - [Ziyang Liu](https://github.com/napari/napari/commits?author=potating-potato) - @potating-potato - diff --git a/docs/release/release_0_4_17.md b/docs/release/release_0_4_17.md index c376d236e..1416a02d4 100644 --- a/docs/release/release_0_4_17.md +++ b/docs/release/release_0_4_17.md @@ -6,7 +6,6 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari @@ -53,7 +52,6 @@ This uses the API that we intend to spread elsewhere for other style attributes so if you are a heavy user of those types of attributes you should try this out and leave feedback on Zulip or create issues on GitHub. - ## New Features - Multi-color text with color encodings (#4464) @@ -78,7 +76,7 @@ or create issues on GitHub. - Use custom color field classes in all models (#4704) - napari.viewer.current_viewer fallback ancestor (#4715) - Add public API for dims transpose (#4727) -- Add a public API for the setGeometry method of _qt_window (#4729) +- Add a public API for the setGeometry method of \_qt_window (#4729) - Expose points layer antialiasing control publicly (#4735) - Use `npe2 list` to show plugin info (#4739) - Allow pandas.Series as properties values (#4755) @@ -90,7 +88,7 @@ or create issues on GitHub. - Restore Labels, Points and Shapes layer mode after hold key (#4814) - Confirmation on close, alternative approach (#4820) - add path specificity scoring (#4830) -- [Automatic] Update albertosottile/darkdetect vendored module (#4845) +- \[Automatic\] Update albertosottile/darkdetect vendored module (#4845) - Add plugin reader warning dialog (#4852) - Fix `python` name in macOS menu bar (#4989) - Explicit convert color to string when compile resources (#4997) @@ -106,7 +104,7 @@ or create issues on GitHub. - Fix: Always save settings after migration (#4490) - Make sure reader dialog pops open if File -> Open Sample is compatible with multiple readers (#4500) - Do not add duplicate layers (#4544) -- Disconnect a removed slider's _pull_label callback from the axis label change event (#4557) +- Disconnect a removed slider's \_pull_label callback from the axis label change event (#4557) - Clean status message after leave canvas (#4607) - Alternate fix for alt-text issues (#4617) - Fix numpy version comparison for dev versions of numpy (#4622) @@ -116,9 +114,9 @@ or create issues on GitHub. - Do not use keyword argument for `QListWidgetItem` parent set in constructor (#4661) - fix: fix manifest reader extensions, ensure that all builtins go through npe2 (#4668) - Fix source of copied layer events by use `layer.as_layer_data_tuple` (#4681) -- Tracks tail update fix beyond _max_length (#4688) +- Tracks tail update fix beyond \_max_length (#4688) - Keep order of layers when select to save multiple selected layers (#4689) -- Apply shown mask to points._view_size_scale (#4699) +- Apply shown mask to points.\_view_size_scale (#4699) - Color edit without tooltip fix (#4717) - fix presentation and enablement of npe1 plugins (#4721) - fix clim init for dask arrays (#4724) @@ -158,9 +156,9 @@ or create issues on GitHub. ## API Changes -- Removed unused private Layer._position (#4604) +- Removed unused private Layer.\_position (#4604) - Add public API for dims transpose (#4727) -- Add a public API for the setGeometry method of _qt_window (#4729) +- Add a public API for the setGeometry method of \_qt_window (#4729) - Expose points layer antialiasing control publicly (#4735) ## Deprecations @@ -171,9 +169,9 @@ or create issues on GitHub. ## Build Tools -- [pre-commit.ci] pre-commit autoupdate (#4506) +- \[pre-commit.ci\] pre-commit autoupdate (#4506) - Limit workflows runs based on file changes (#4555) -- [Automatic] Update albertosottile/darkdetect vendored module (#4561) +- \[Automatic\] Update albertosottile/darkdetect vendored module (#4561) - Dockerfile: stay on 20.04/LTS for python3.8 (#4572) - Block lxml 4.9.0 version (#4616) - Remove meshzoo from napari (#4620) @@ -188,7 +186,7 @@ or create issues on GitHub. - ci(dependabot): bump bruceadams/get-release from 1.2.2 to 1.2.3 (#4773) - ci(dependabot): bump docker/login-action from 1.9.0 to 2 (#4774) - Bump vispy 0.11.0 (#4778) -- [pre-commit.ci] pre-commit autoupdate (#4779) +- \[pre-commit.ci\] pre-commit autoupdate (#4779) - ci(dependabot): bump toshimaru/auto-author-assign from 1.5.0 to 1.6.1 (#4890) - Docker: Update xpra's apt too (#4901) @@ -227,7 +225,7 @@ or create issues on GitHub. - Accept NAP-2: Distributing napari with conda-based packaging (#4810) - Fixes duplicate label warnings in the documentation (#4823) - added description of how to save layers without compression (#4832) -- Add the ability to return a List[Layer] in magicgui (#4851) +- Add the ability to return a List\[Layer\] in magicgui (#4851) - Fix NAP-3 table of contents (#4872) - Feature: Minimum blending (#4875) - NAP 4: asynchronous slicing (#4892) @@ -278,12 +276,12 @@ or create issues on GitHub. - Test if events for all properties are defined (#4486) - throttle status update (#4488) - use inject_napari_dependencies in layer_actions commands (#4489) -- Add _get_value_3d to surface (#4492) +- Add \_get_value_3d to surface (#4492) - Reduce canvas margin (#4496) - Fix trailing comma fixes black formatting (#4498) - Update layer list context keys (#4499) - Do not allow None when coercing encodings (#4504) -- [Automatic] Update albertosottile/darkdetect vendored module (#4508) +- \[Automatic\] Update albertosottile/darkdetect vendored module (#4508) - Try to upload pytest json report as artifact. (#4518) - Try to speedup tests. (#4521) - change default blending mode on image layer to translucent no depth (#4523) @@ -315,13 +313,13 @@ or create issues on GitHub. - Do not run `asv` on push (#4656) - ci: fix tox factor names in tests (#4660) - test: speed up magicgui forward ref tests (#4662) -- Remove unused points _color state (#4666) +- Remove unused points \_color state (#4666) - test: Cleanup usage of npe2 plugin manager in tests (#4669) - add dependabot for github actions dependencies (#4671) - Use `cache: pip` from actions setup-python (#4672) - Remove `restore_settings_on_exit` test util (#4673) - bump npe2 version, use new features (#4686) -- [pre-commit.ci] pre-commit autoupdate (#4687) +- \[pre-commit.ci\] pre-commit autoupdate (#4687) - Remove unnecessary uses of make_napari_viewer (replace with ViewerModel) (#4690) - Design issue template assignees (#4701) - Refactor label painting by moving code from mouse bindings onto layer, adding staged history and paint event (#4702) @@ -341,7 +339,7 @@ or create issues on GitHub. - Allow dunder methods use in `PublicOnlyProxy` (#4792) - add update_mesh method (#4793) - mock npe1 plugin manager during tests (fix tests with npe1 plugins installed) (#4806) -- [Automatic] Update albertosottile/darkdetect vendored module (#4808) +- \[Automatic\] Update albertosottile/darkdetect vendored module (#4808) - Improve using Qt flags by direct use enums. (#4817) - Move searching parent outside NapariQtNotification constructor (#4841) - Enable plugin menu contributions with app-model (#4847) @@ -360,7 +358,7 @@ or create issues on GitHub. - Adjust the slider value to include current_size (#4951) - MAINT: re-enable type checking workflow (#4960) - MAINT: bump napari-console to 0.0.6. (#4967) -- [pre-commit.ci] pre-commit autoupdate (#4968) +- \[pre-commit.ci\] pre-commit autoupdate (#4968) - Fix: bump app-model, add test for layer buttons (#4972) - Add `FUNDING.yml` file to enable sponsor button and increase discoverability of option to sponsor napari (#4978) - Start adding a CODEOWNERS file. (#4993) @@ -368,13 +366,13 @@ or create issues on GitHub. - Set fixpoint before move for shapes (#4999) - Fix sys.path issue with subprocess relaunch in macOS (#5007) - MAINT: update issue failing template (#5012) -- [pre-commit.ci] pre-commit autoupdate (#5015) +- \[pre-commit.ci\] pre-commit autoupdate (#5015) - Fix shift selection for points (#5022) - Revert "Fix sys.path issue with subprocess relaunch in macOS" (#5027) - Use Source object to track if a layer is duplicated (#5028) - Revert "Revert "Fix sys.path issue with subprocess relaunch in macOS"" (#5029) - update some of the ignored translations (#5032) -- MAINT: fix a couple of trans._. (#5034) +- MAINT: fix a couple of trans.\_. (#5034) - MAINT: Update Future Warning. (#5037) - MAINT: Unnecessary formatting of constant. (#5044) - Small shortcuts preference pane wording update after #5018 (#5049) @@ -385,7 +383,6 @@ or create issues on GitHub. - Update config directory paths for perfmon tools (#5081) - Update some strings to be translated, some to be ignored (#5082) - ## 48 authors added to this release (alphabetical) - [alisterburt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt @@ -396,11 +393,11 @@ or create issues on GitHub. - [cnstt](https://github.com/napari/napari/commits?author=cnstt) - @cnstt - [Curtis Rueden](https://github.com/napari/napari/commits?author=ctrueden) - @ctrueden - [David Stansby](https://github.com/napari/napari/commits?author=dstansby) - @dstansby -- [dependabot[bot]](https://github.com/napari/napari/commits?author=dependabot[bot]) - @dependabot[bot] +- [dependabot\[bot\]](https://github.com/napari/napari/commits?author=dependabot%5Bbot%5D) - @dependabot\[bot\] - [Draga Doncila Pop](https://github.com/napari/napari/commits?author=DragaDoncila) - @DragaDoncila - [Eric Perlman](https://github.com/napari/napari/commits?author=perlman) - @perlman - [Gabriel Selzer](https://github.com/napari/napari/commits?author=gselzer) - @gselzer -- [github-actions[bot]](https://github.com/napari/napari/commits?author=github-actions[bot]) - @github-actions[bot] +- [github-actions\[bot\]](https://github.com/napari/napari/commits?author=github-actions%5Bbot%5D) - @github-actions\[bot\] - [Gonzalo Peña-Castellanos](https://github.com/napari/napari/commits?author=goanpeca) - @goanpeca - [Grzegorz Bokota](https://github.com/napari/napari/commits?author=Czaki) - @Czaki - [Guillaume Witz](https://github.com/napari/napari/commits?author=guiwitz) - @guiwitz @@ -427,7 +424,7 @@ or create issues on GitHub. - [Pam](https://github.com/napari/napari/commits?author=ppwadhwa) - @ppwadhwa - [Peter Sobolewski](https://github.com/napari/napari/commits?author=psobolewskiPhD) - @psobolewskiPhD - [Pierre Thibault](https://github.com/napari/napari/commits?author=pierrethibault) - @pierrethibault -- [pre-commit-ci[bot]](https://github.com/napari/napari/commits?author=pre-commit-ci[bot]) - @pre-commit-ci[bot] +- [pre-commit-ci\[bot\]](https://github.com/napari/napari/commits?author=pre-commit-ci%5Bbot%5D) - @pre-commit-ci\[bot\] - [Robert Haase](https://github.com/napari/napari/commits?author=haesleinhuepf) - @haesleinhuepf - [Robin Koch](https://github.com/napari/napari/commits?author=RobAnKo) - @RobAnKo - [rwkozar](https://github.com/napari/napari/commits?author=rwkozar) - @rwkozar @@ -437,7 +434,6 @@ or create issues on GitHub. - [vcwai](https://github.com/napari/napari/commits?author=victorcwai) - @victorcwai - [Ziyang Liu](https://github.com/napari/napari/commits?author=potating-potato) - @potating-potato - ## 32 reviewers added to this release (alphabetical) - [Ahmet Can Solak](https://github.com/napari/napari/commits?author=AhmetCanSolak) - @AhmetCanSolak @@ -472,4 +468,3 @@ or create issues on GitHub. - [Robin Koch](https://github.com/napari/napari/commits?author=RobAnKo) - @RobAnKo - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=potating-potato) - @potating-potato - diff --git a/docs/release/release_0_4_18.md b/docs/release/release_0_4_18.md index 86ddd96a5..c0a1303cc 100644 --- a/docs/release/release_0_4_18.md +++ b/docs/release/release_0_4_18.md @@ -102,10 +102,10 @@ https://napari.org - Check strictly increasing values when clipping contrast limits to a new range ([napari/napari/#5258](https://github.com/napari/napari/pull/5258)) - UI Bugfix: Make disabled QPushButton more distinct ([napari/napari/#5262](https://github.com/napari/napari/pull/5262)) - Respect background color when calculating scale bar color ([napari/napari/#5270](https://github.com/napari/napari/pull/5270)) -- Fix circular import in _vispy module ([napari/napari/#5276](https://github.com/napari/napari/pull/5276)) +- Fix circular import in \_vispy module ([napari/napari/#5276](https://github.com/napari/napari/pull/5276)) - Use only data dimensions for cord in status bar ([napari/napari/#5283](https://github.com/napari/napari/pull/5283)) - Prevent obsolete reports about failure of cleaning viewer instances ([napari/napari/#5317](https://github.com/napari/napari/pull/5317)) -- Add scikit-image[data] to install_requires, because it's required by builtins ([napari/napari/#5329](https://github.com/napari/napari/pull/5329)) +- Add scikit-image\[data\] to install_requires, because it's required by builtins ([napari/napari/#5329](https://github.com/napari/napari/pull/5329)) - Fix repeating close dialog on macOS and qt 5.12 ([napari/napari/#5337](https://github.com/napari/napari/pull/5337)) - Disable napari-console if napari launched from vanilla python REPL ([napari/napari/#5350](https://github.com/napari/napari/pull/5350)) - For npe2 plugin, use manifest display_name for File > Open Samples ([napari/napari/#5351](https://github.com/napari/napari/pull/5351)) @@ -115,8 +115,8 @@ https://napari.org - fix theme id not being used correctly ([napari/napari/#5412](https://github.com/napari/napari/pull/5412)) - Clarify layer's editable property and separate interaction with visible property ([napari/napari/#5413](https://github.com/napari/napari/pull/5413)) - Fix theme reference to get image for `success_label` style ([napari/napari/#5447](https://github.com/napari/napari/pull/5447)) -- Bugfix: Ensure layer._fixed_vertex is set when rotating ([napari/napari/#5449](https://github.com/napari/napari/pull/5449)) -- Fix `_n_selected_points` in _layerlist_context.py ([napari/napari/#5450](https://github.com/napari/napari/pull/5450)) +- Bugfix: Ensure layer.\_fixed_vertex is set when rotating ([napari/napari/#5449](https://github.com/napari/napari/pull/5449)) +- Fix `_n_selected_points` in \_layerlist_context.py ([napari/napari/#5450](https://github.com/napari/napari/pull/5450)) - Refactor Main Window status bar to improve information presentation ([napari/napari/#5451](https://github.com/napari/napari/pull/5451)) - Bugfix: Fix test_get_system_theme test for `name` to `id` change ([napari/napari/#5456](https://github.com/napari/napari/pull/5456)) - Bugfix: POLL_INTERVAL_MS used in QTimer needs to be an int on python 3.10 ([napari/napari/#5467](https://github.com/napari/napari/pull/5467)) @@ -165,7 +165,7 @@ https://napari.org - Maint: Bump mypy ([napari/napari/#5727](https://github.com/napari/napari/pull/5727)) - Style `QGroupBox` indicator ([napari/napari/#5729](https://github.com/napari/napari/pull/5729)) - Fix centering of non-displayed dimensions ([napari/napari/#5736](https://github.com/napari/napari/pull/5736)) -- Don't attempt to use npe1 readers in napari.plugins._npe2.read ([napari/napari/#5739](https://github.com/napari/napari/pull/5739)) +- Don't attempt to use npe1 readers in napari.plugins.\_npe2.read ([napari/napari/#5739](https://github.com/napari/napari/pull/5739)) - Prevent canvas micro-panning on point add ([napari/napari/#5742](https://github.com/napari/napari/pull/5742)) - Use text opacity to signal that widget is disabled ([napari/napari/#5745](https://github.com/napari/napari/pull/5745)) - Bugfix: Add the missed keyReleaseEvent method in QtViewerDockWidget ([napari/napari/#5746](https://github.com/napari/napari/pull/5746)) @@ -187,7 +187,7 @@ https://napari.org - Add compatibility to PySide in file dialogs by using positional arguments ([napari/napari/#5834](https://github.com/napari/napari/pull/5834)) - Bugfix: fix broken "show selected" in the Labels layer (because of caching) ([napari/napari/#5841](https://github.com/napari/napari/pull/5841)) - Add tests for popup widgets and fix perspective popup slider initialization ([napari/napari/#5848](https://github.com/napari/napari/pull/5848)) -- [Qt6] Fix AttributeError on renaming layer ([napari/napari/#5850](https://github.com/napari/napari/pull/5850)) +- \[Qt6\] Fix AttributeError on renaming layer ([napari/napari/#5850](https://github.com/napari/napari/pull/5850)) - Bugfix: Ensure QTableWidgetItem(action.description) item is enabled ([napari/napari/#5854](https://github.com/napari/napari/pull/5854)) - Add constraints file during installation of packages from pip in docs workflow ([napari/napari/#5862](https://github.com/napari/napari/pull/5862)) - Bugfix: link the Labels model to the "show selected" checkbox ([napari/napari/#5867](https://github.com/napari/napari/pull/5867)) @@ -197,11 +197,11 @@ https://napari.org - Fix `napari-svg` version parsing in `conftest.py` ([napari/napari/#5947](https://github.com/napari/napari/pull/5947)) - Fix issue in utils.progress for disable=True ([napari/napari/#5964](https://github.com/napari/napari/pull/5964)) - Set high DPI attributes when using PySide2 ([napari/napari/#5968](https://github.com/napari/napari/pull/5968)) -- [0.4.18rc1] Bugfix/event proxy ([napari/napari/#5994](https://github.com/napari/napari/pull/5994)) +- \[0.4.18rc1\] Bugfix/event proxy ([napari/napari/#5994](https://github.com/napari/napari/pull/5994)) - Fix behavior of PublicOnlyProxy in setattr, wrapped methods, and calling ([napari/napari/#5997](https://github.com/napari/napari/pull/5997)) - Bugfix: Fix regression from #5739 for passing plugin name and reader plus add test ([napari/napari/#6013](https://github.com/napari/napari/pull/6013)) - Avoid passing empty string to `importlib.metadata.metadata` ([napari/napari/#6018](https://github.com/napari/napari/pull/6018)) -- Use tuple for pip constraints to avoid LRU cache error ([napari/napari#6036](https://github.com/napari/napari/pull/6036) +- Use tuple for pip constraints to avoid LRU cache error ([napari/napari#6036](https://github.com/napari/napari/pull/6036) ## API Changes @@ -211,7 +211,6 @@ https://napari.org ## Deprecations - ## Build Tools - ci(dependabot): bump styfle/cancel-workflow-action from 0.10.0 to 0.10.1 ([napari/napari/#5158](https://github.com/napari/napari/pull/5158)) @@ -261,7 +260,7 @@ https://napari.org - Added environment creation and doc tools install ([napari/docs/#72](https://github.com/napari/docs/pull/72)) - Feature: add `copy` button for code blocks using `sphinx-copybutton` ([napari/docs/#76](https://github.com/napari/docs/pull/76)) - Add NAP-6 - Proposal for contributable menus ([napari/docs/#77](https://github.com/napari/docs/pull/77)) -- Update contributing docs for [dev] install change needing Qt backend install ([napari/docs/#78](https://github.com/napari/docs/pull/78)) +- Update contributing docs for \[dev\] install change needing Qt backend install ([napari/docs/#78](https://github.com/napari/docs/pull/78)) - Update theme related documentation ([napari/docs/#81](https://github.com/napari/docs/pull/81)) - Feature: implement python version substitution in conf.py ([napari/docs/#84](https://github.com/napari/docs/pull/84)) - Fixes gallery ToC ([napari/docs/#85](https://github.com/napari/docs/pull/85)) @@ -302,7 +301,7 @@ https://napari.org - MAINT: increase min numpy version. ([napari/napari/#5089](https://github.com/napari/napari/pull/5089)) - Refactor qt notification and its test solve problem of segfaults ([napari/napari/#5138](https://github.com/napari/napari/pull/5138)) - Decouple changing viewer.theme from changing theme settings/preferences ([napari/napari/#5143](https://github.com/napari/napari/pull/5143)) -- [DOCS] misc invalid syntax updates. ([napari/napari/#5176](https://github.com/napari/napari/pull/5176)) +- \[DOCS\] misc invalid syntax updates. ([napari/napari/#5176](https://github.com/napari/napari/pull/5176)) - MAINT: remove vendored colorconv from skimage. ([napari/napari/#5180](https://github.com/napari/napari/pull/5180)) - Re-add README screenshot ([napari/napari/#5220](https://github.com/napari/napari/pull/5220)) - MAINT: remove requirements.txt and cache actions based on setup.cfg. ([napari/napari/#5234](https://github.com/napari/napari/pull/5234)) @@ -321,13 +320,13 @@ https://napari.org - Suppress color conversion warning when converting invalid LAB coordinates ([napari/napari/#5386](https://github.com/napari/napari/pull/5386)) - Fix warning when fail to import qt binding. ([napari/napari/#5388](https://github.com/napari/napari/pull/5388)) - Update `MANIFEST.in` to remove warning when run tox ([napari/napari/#5393](https://github.com/napari/napari/pull/5393)) -- [Automatic] Update albertosottile/darkdetect vendored module ([napari/napari/#5394](https://github.com/napari/napari/pull/5394)) +- \[Automatic\] Update albertosottile/darkdetect vendored module ([napari/napari/#5394](https://github.com/napari/napari/pull/5394)) - Update citation metadata ([napari/napari/#5398](https://github.com/napari/napari/pull/5398)) - Feature: making the Help menu more helpful via weblinks (re-do of #5094) ([napari/napari/#5399](https://github.com/napari/napari/pull/5399)) -- [pre-commit.ci] pre-commit autoupdate ([napari/napari/#5403](https://github.com/napari/napari/pull/5403)) +- \[pre-commit.ci\] pre-commit autoupdate ([napari/napari/#5403](https://github.com/napari/napari/pull/5403)) - Fix flaky dims playback test by waiting for playing condition ([napari/napari/#5414](https://github.com/napari/napari/pull/5414)) -- [Automatic] Update albertosottile/darkdetect vendored module ([napari/napari/#5416](https://github.com/napari/napari/pull/5416)) -- [pre-commit.ci] pre-commit autoupdate ([napari/napari/#5422](https://github.com/napari/napari/pull/5422)) +- \[Automatic\] Update albertosottile/darkdetect vendored module ([napari/napari/#5416](https://github.com/napari/napari/pull/5416)) +- \[pre-commit.ci\] pre-commit autoupdate ([napari/napari/#5422](https://github.com/napari/napari/pull/5422)) - Avoid setting corner pixels for empty layers ([napari/napari/#5423](https://github.com/napari/napari/pull/5423)) - Maint: Typing and ImportError -> ModuleNotFoundError. ([napari/napari/#5431](https://github.com/napari/napari/pull/5431)) - Fix tox `passenv` setup for `DISPLAY` and `XAUTHORITY` environment variables ([napari/napari/#5441](https://github.com/napari/napari/pull/5441)) @@ -342,7 +341,7 @@ https://napari.org - Second PR that enables more ruff rules. ([napari/napari/#5520](https://github.com/napari/napari/pull/5520)) - Use pytest-pretty for better log readability ([napari/napari/#5525](https://github.com/napari/napari/pull/5525)) - MAINT: Follow Nep29, bump minimum numpy. ([napari/napari/#5532](https://github.com/napari/napari/pull/5532)) -- [pre-commit.ci] pre-commit autoupdate ([napari/napari/#5534](https://github.com/napari/napari/pull/5534)) +- \[pre-commit.ci\] pre-commit autoupdate ([napari/napari/#5534](https://github.com/napari/napari/pull/5534)) - Move layer editable change from slicing to controls ([napari/napari/#5546](https://github.com/napari/napari/pull/5546)) - update conda_menu_config.json for latest fixes in menuinst ([napari/napari/#5564](https://github.com/napari/napari/pull/5564)) - Enable the `COM` and `SIM` rules in `ruff` configuration ([napari/napari/#5566](https://github.com/napari/napari/pull/5566)) @@ -351,9 +350,9 @@ https://napari.org - Remove leftover duplicated code ([napari/napari/#5586](https://github.com/napari/napari/pull/5586)) - Remove napari-hub API access code ([napari/napari/#5587](https://github.com/napari/napari/pull/5587)) - Enable `ruff` rules part 4. ([napari/napari/#5590](https://github.com/napari/napari/pull/5590)) -- [pre-commit.ci] pre-commit autoupdate ([napari/napari/#5592](https://github.com/napari/napari/pull/5592)) +- \[pre-commit.ci\] pre-commit autoupdate ([napari/napari/#5592](https://github.com/napari/napari/pull/5592)) - Maint: ImportError -> ModuleNotFoundError. ([napari/napari/#5628](https://github.com/napari/napari/pull/5628)) -- [pre-commit.ci] pre-commit autoupdate ([napari/napari/#5645](https://github.com/napari/napari/pull/5645)) +- \[pre-commit.ci\] pre-commit autoupdate ([napari/napari/#5645](https://github.com/napari/napari/pull/5645)) - MAINT: Do not use mutable default for dataclass. ([napari/napari/#5647](https://github.com/napari/napari/pull/5647)) - MAINT: Do not use cgi-traceback on 3.11+ (deprecated, marked for removal) ([napari/napari/#5648](https://github.com/napari/napari/pull/5648)) - MAINT: Add explicit level to warn. ([napari/napari/#5649](https://github.com/napari/napari/pull/5649)) @@ -365,25 +364,24 @@ https://napari.org - Don't resize shape after `Shift` release until mouse moves ([napari/napari/#5707](https://github.com/napari/napari/pull/5707)) - Update `test_examples` job dependencies, unskip `surface_timeseries_.py` and update some examples validations ([napari/napari/#5716](https://github.com/napari/napari/pull/5716)) - Add test to check basic interactions with layer controls widgets ([napari/napari/#5757](https://github.com/napari/napari/pull/5757)) -- test: [Automatic] Constraints upgrades: `dask`, `hypothesis`, `imageio`, `npe2`, `numpy`, `pandas`, `psutil`, `pygments`, `pytest`, `rich`, `tensorstore`, `tifffile`, `virtualenv`, `xarray` ([napari/napari/#5776](https://github.com/napari/napari/pull/5776)) -- [MAINT, packaging] Remove support for briefcase installers ([napari/napari/#5804](https://github.com/napari/napari/pull/5804)) +- test: \[Automatic\] Constraints upgrades: `dask`, `hypothesis`, `imageio`, `npe2`, `numpy`, `pandas`, `psutil`, `pygments`, `pytest`, `rich`, `tensorstore`, `tifffile`, `virtualenv`, `xarray` ([napari/napari/#5776](https://github.com/napari/napari/pull/5776)) +- \[MAINT, packaging\] Remove support for briefcase installers ([napari/napari/#5804](https://github.com/napari/napari/pull/5804)) - Update `PIP_CONSTRAINT` value to fix failing comprehensive jobs ([napari/napari/#5809](https://github.com/napari/napari/pull/5809)) -- [pre-commit.ci] pre-commit autoupdate ([napari/napari/#5836](https://github.com/napari/napari/pull/5836)) -- [pre-commit.ci] pre-commit autoupdate ([napari/napari/#5860](https://github.com/napari/napari/pull/5860)) +- \[pre-commit.ci\] pre-commit autoupdate ([napari/napari/#5836](https://github.com/napari/napari/pull/5836)) +- \[pre-commit.ci\] pre-commit autoupdate ([napari/napari/#5860](https://github.com/napari/napari/pull/5860)) - Fix Dev Docker Container ([napari/napari/#5877](https://github.com/napari/napari/pull/5877)) - Make mypy error checking opt-out instead of opt-in ([napari/napari/#5885](https://github.com/napari/napari/pull/5885)) - Update Error description when plugin not installed ([napari/napari/#5899](https://github.com/napari/napari/pull/5899)) - maint: add fixture to disable throttling ([napari/napari/#5908](https://github.com/napari/napari/pull/5908)) - Update upgrade dependecies and test workflows ([napari/napari/#5919](https://github.com/napari/napari/pull/5919)) -- [Maint] Fix comprehensive tests by skipping labels controls test on py311 pyqt6 ([napari/napari/#5922](https://github.com/napari/napari/pull/5922)) +- \[Maint\] Fix comprehensive tests by skipping labels controls test on py311 pyqt6 ([napari/napari/#5922](https://github.com/napari/napari/pull/5922)) - Fix typo in resources/requirements_mypy.in file name ([napari/napari/#5924](https://github.com/napari/napari/pull/5924)) - Add Python 3.11 trove classifier. ([napari/napari/#5937](https://github.com/napari/napari/pull/5937)) - Change license_file to license_files in setup.cfg ([napari/napari/#5948](https://github.com/napari/napari/pull/5948)) -- test: [Automatic] Constraints upgrades: `dask`, `fsspec`, `hypothesis`, `imageio`, `ipython`, `napari-plugin-manager`, `napari-svg`, `numpy`, `psygnal`, `pydantic`, `pyqt6`, `pytest`, `rich`, `scikit-image`, `virtualenv`, `zarr` ([napari/napari/#5963](https://github.com/napari/napari/pull/5963)) +- test: \[Automatic\] Constraints upgrades: `dask`, `fsspec`, `hypothesis`, `imageio`, `ipython`, `napari-plugin-manager`, `napari-svg`, `numpy`, `psygnal`, `pydantic`, `pyqt6`, `pytest`, `rich`, `scikit-image`, `virtualenv`, `zarr` ([napari/napari/#5963](https://github.com/napari/napari/pull/5963)) - Update deprecation information ([napari/napari/#5984](https://github.com/napari/napari/pull/5984)) - Pin napari and pydantic when installing a plugin ([napari/napari/#6022](https://github.com/napari/napari/pull/6022)) - ## 40 authors added to this release (alphabetical) - [Alister Burt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt @@ -427,7 +425,6 @@ https://napari.org - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Wouter-Michiel Vierdag](https://github.com/napari/napari/commits?author=melonora) - @melonora - ## 43 reviewers added to this release (alphabetical) - [Alan Lowe](https://github.com/napari/napari/commits?author=quantumjot) - @quantumjot @@ -474,7 +471,6 @@ https://napari.org - [Wouter-Michiel Vierdag](https://github.com/napari/napari/commits?author=melonora) - @melonora - [Ziyang Liu](https://github.com/napari/napari/commits?author=liu-ziyang) - @liu-ziyang - ## 19 docs authors added to this release (alphabetical) - [Ashley Anderson](https://github.com/napari/docs/commits?author=aganders3) - @aganders3 @@ -497,7 +493,6 @@ https://napari.org - [Sean Martin](https://github.com/napari/docs/commits?author=seankmartin) - @seankmartin - [Wouter-Michiel Vierdag](https://github.com/napari/docs/commits?author=melonora) - @melonora - ## 20 docs reviewers added to this release (alphabetical) - [Alister Burt](https://github.com/napari/docs/commits?author=alisterburt) - @alisterburt diff --git a/docs/release/release_0_4_2.md b/docs/release/release_0_4_2.md index 05fc154ca..e946e112b 100644 --- a/docs/release/release_0_4_2.md +++ b/docs/release/release_0_4_2.md @@ -6,7 +6,6 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari @@ -21,6 +20,7 @@ for our model files, which will result in a dramatic simplification of that part of the codebase. ## Improvements + - async-28: Misc Cleanup (#1900) - async-29: Shared Memory Server (#1909) - Use evented dataclass for axes (#1910) @@ -32,6 +32,7 @@ part of the codebase. - Remove add layers mixin (#1921) ## Bug Fixes + - Fix performance issues 1 - adding layers (#1945) - Fix track labels lookup (#1946) - Update sliders when change scale of layer (#1951) @@ -39,13 +40,16 @@ part of the codebase. - Refine TypedMutableSequence.__getitem__ error type, add magicgui tests (#1962) ## API Changes -- ``Viewer.camera.ndisplay`` has been dropped. Instead, use - ``Viewer.dims.ndisplay``. (#1914) + +- `Viewer.camera.ndisplay` has been dropped. Instead, use + `Viewer.dims.ndisplay`. (#1914) ## Deprecations + - All existing deprecations have been bumped by one release (#1963) ## Build Tools and Docs + - Fix checks for running under cProfile and yappi (#1924) - Fix missing vispy.ext.six (#1930) - Replace calls to layer.shape in tests (#1938) @@ -54,7 +58,6 @@ part of the codebase. - Don't run app bundling in forked repos (#1953) - Add tests for #1895 (#1961) - ## 8 authors added to this release (alphabetical) - [Alan R Lowe](https://github.com/napari/napari/commits?author=quantumjot) - @quantumjot @@ -67,7 +70,6 @@ part of the codebase. - [Philip Winston](https://github.com/napari/napari/commits?author=pwinston) - @pwinston - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - ## 5 reviewers added to this release (alphabetical) - [Grzegorz Bokota](https://github.com/napari/napari/commits?author=Czaki) - @Czaki @@ -75,4 +77,3 @@ part of the codebase. - [Nicholas Sofroniew](https://github.com/napari/napari/commits?author=sofroniewn) - @sofroniewn - [Philip Winston](https://github.com/napari/napari/commits?author=pwinston) - @pwinston - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - diff --git a/docs/release/release_0_4_3.md b/docs/release/release_0_4_3.md index 69eac6979..69e3a373b 100644 --- a/docs/release/release_0_4_3.md +++ b/docs/release/release_0_4_3.md @@ -6,11 +6,11 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari ## Highlights + In this release we've added two new analysis and GUI focused [hook specifications](https://github.com/napari/napari/blob/87961d0554b2bb1574553e23bf2231a9a5117568/docs/source/plugins/hook_specifications.rst) for our plugin developers (#2080). The first one `napari_experimental_provide_function_widget` allows you to provide a function or list of functions that we @@ -27,13 +27,13 @@ We've also made good progress on our `experimental` support for an octree system Finally we've added our [0.4 series roadmap](https://napari.org/roadmaps/0_4.html) and a [retrospective on our 0.3 roadmap](https://napari.org/roadmaps/0_3_retrospective.html)! - ## New Features + - Add support for function widgets (#1856) - Gui hookspecs (#2080) - ## Improvements + - Use evented dataclass for dims (#1917) - Add information about screen resolution (#1957) - Add asdict and update to evented dataclass (#1966) @@ -63,10 +63,10 @@ Finally we've added our [0.4 series roadmap](https://napari.org/roadmaps/0_4.htm - Remove global app logic from Window init (#2065) - async-45: Docs and Cleanup (#2067) - Better bound magicgui viewer (#2100) -- reduce call of _extent_data in layer (#2106) - +- reduce call of \_extent_data in layer (#2106) ## Bug Fixes + - Fix append and remove from layerlist (#1955) - Change label tooltip checkbox (#1978) - Fix cursor size (#1983) @@ -88,25 +88,25 @@ Finally we've added our [0.4 series roadmap](https://napari.org/roadmaps/0_4.htm - Fix overly strict magic kwargs (#2099) - Undo calling pyrcc with python sys.executable (#2102) - ## API Changes -- The ``axis`` parameter is no longer present on the ``current_step``, ``range``, or ``axis_labels`` events. Instead a single event is emitted whenever the tuple changes (#1917) -- The deprecated public layer dims has been removed in 0.4.2 and the private ``layer._dims`` is now a NamedTuple (#1919) -- The deprecated ``layer.shape`` arrtibute has been removed. Instead you should use the ``layer.extent.data`` and ``layer.extent.world attributes`` to get the extent of the data in data or world coordinates (#1990, #2002) -- Keymap handling has been moved off the ``Viewer`` and ``Viewer.keymap_providers`` has been removed. The ``Viewer`` itself -can still provide keymappings, but no longer handles keymappings from other objects like the layers. (#2003) -- Drop scale background color and axes background color. These colors are now determined by defaults or the canvas background color. (#2037) -- ``event.text`` was renamed ``event.value`` for the events emitted when changing ``Viewer.status``, ``Viewer.title``, -``Viewer.help``, and ``event.item`` was renamed ``event.value`` for the event emitted when changing ``Viewer.active_layer`` (#2038) +- The `axis` parameter is no longer present on the `current_step`, `range`, or `axis_labels` events. Instead a single event is emitted whenever the tuple changes (#1917) +- The deprecated public layer dims has been removed in 0.4.2 and the private `layer._dims` is now a NamedTuple (#1919) +- The deprecated `layer.shape` arrtibute has been removed. Instead you should use the `layer.extent.data` and `layer.extent.world attributes` to get the extent of the data in data or world coordinates (#1990, #2002) +- Keymap handling has been moved off the `Viewer` and `Viewer.keymap_providers` has been removed. The `Viewer` itself + can still provide keymappings, but no longer handles keymappings from other objects like the layers. (#2003) +- Drop scale background color and axes background color. These colors are now determined by defaults or the canvas background color. (#2037) +- `event.text` was renamed `event.value` for the events emitted when changing `Viewer.status`, `Viewer.title`, + `Viewer.help`, and `event.item` was renamed `event.value` for the event emitted when changing `Viewer.active_layer` (#2038) ## Deprecations -- The ``Viewer.interactive`` parameter has been deprecated, instead you should use ``Viewer.camera.interactive`` (#2008) -- The ``Viewer.palette`` attribute has been deprecated. To access the palette you can get it using ``napari.utils.theme.register_theme`` dictionary using the ``viewer.theme`` as the key (#2031) -- Annotating a magicgui function with a return type of ``napari.layers.Layer`` is deprecated. To indicate that your function returns a layer data tuple, please use a return annotation of ``napari.types.LayerDataTuple`` or ``List[napari.types.LayerDataTuple]``(#2079) +- The `Viewer.interactive` parameter has been deprecated, instead you should use `Viewer.camera.interactive` (#2008) +- The `Viewer.palette` attribute has been deprecated. To access the palette you can get it using `napari.utils.theme.register_theme` dictionary using the `viewer.theme` as the key (#2031) +- Annotating a magicgui function with a return type of `napari.layers.Layer` is deprecated. To indicate that your function returns a layer data tuple, please use a return annotation of `napari.types.LayerDataTuple` or `List[napari.types.LayerDataTuple]`(#2079) ## Build Tools and Support + - 0.4 roadmap (#1906) - Rename artifact for nightly build releases (#1971) - Update latest tag alone with nightly build (#2001) @@ -129,7 +129,6 @@ can still provide keymappings, but no longer handles keymappings from other obje - Add PR 2106 to 0.4.3 release notes (#2107) - Fix `pytest --pyargs napari` test on pip install. Add CI test (#2109) - ## 9 authors added to this release (alphabetical) - [Alister Burt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt @@ -143,7 +142,6 @@ can still provide keymappings, but no longer handles keymappings from other obje - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 7 reviewers added to this release (alphabetical) - [Genevieve Buckley](https://github.com/napari/napari/commits?author=GenevieveBuckley) - @GenevieveBuckley @@ -153,4 +151,3 @@ can still provide keymappings, but no longer handles keymappings from other obje - [Philip Winston](https://github.com/napari/napari/commits?author=pwinston) - @pwinston - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - diff --git a/docs/release/release_0_4_4.md b/docs/release/release_0_4_4.md index f9163da68..23e2c3421 100644 --- a/docs/release/release_0_4_4.md +++ b/docs/release/release_0_4_4.md @@ -6,11 +6,11 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari ## Highlights + This release is a quick follow on from our `0.4.3` release and contains some nice improvements to the GUI and analysis function hookspecs we experimentally added in that release. We've expanded the API of the @@ -25,8 +25,8 @@ function or list of functions. napari will then take care to generate an appropriate user interface for that function. This will make it even easier to create analysis pipelines in napari (#2158). - ## Improvements + - Add example code to hook documentation (#2112) - move Viewer import into method (#2119) - Support for EventedList.__setitem__ with array-like items (#2120) @@ -39,17 +39,19 @@ create analysis pipelines in napari (#2158). - Add informations on what to do on error in GUI (#2165) ## Documentation + - Better documentation of API changes in 0.4.4 release notes (#2171) - Add new function and dock widget hook specifications to documentation (#2158) ## Bug Fixes + - QtAboutKeyBindings patch (#2132) - Fix too-late registration of napari types in magicgui (#2139) - Fix magicgui.FunctionGui deprecation warning (#2164) - Fix show/ hide of plugin widgets (#2173) - ## API Changes + - `viewer.grid_view()` has been removed, use `viewer.grid.enabled = True` instead (#2144) - `viewer.stack_view()` has been removed, use `viewer.grid.enabled = False` @@ -68,8 +70,8 @@ create analysis pipelines in napari (#2158). `napari_experimental_provide_dock_widget` for more elaborate plugins that require custom widgets. (#2158) - ## Deprecations + - `layer.status` is deprecated, to be removed in 0.4.6. Users should instead use `layer.get_status(position)`. (#1985) - The position argument to `layer.get_value()` is no longer optional, and will @@ -77,8 +79,8 @@ create analysis pipelines in napari (#2158). - `layer.get_message()` is deprecated, to be removed in 0.4.6. Users should use `layer.get_status(position)` instead. (#1985) - ## Build Tools and Support + - Add missed doc string in `import_resources` (#2113) - Delay import of pkg_resources (#2121) - Remove duplicate entry in install_requires (#2122) @@ -88,7 +90,6 @@ create analysis pipelines in napari (#2158). - Provide `make_test_viewer` as a pytest plugin, for external use. (#2131) - Doc: fix syntax = instead of : (#2141) - ## 11 authors added to this release (alphabetical) - [Alister Burt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt @@ -103,7 +104,6 @@ create analysis pipelines in napari (#2158). - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Volker Hilsenstein](https://github.com/napari/napari/commits?author=VolkerH) - @VolkerH - ## 6 reviewers added to this release (alphabetical) - [Juan Nunez-Iglesias](https://github.com/napari/napari/commits?author=jni) - @jni diff --git a/docs/release/release_0_4_5.md b/docs/release/release_0_4_5.md index f8c8ec8b7..e11f3c7fa 100644 --- a/docs/release/release_0_4_5.md +++ b/docs/release/release_0_4_5.md @@ -6,11 +6,11 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari ## Highlights + This release is our first release using Jupyter Book to build our documentation (#2187) which can be seen at https://napari.org/docs/dev/, or https://napari.org/docs/0.4.5/ . We'll be continuing to reorganize the @@ -21,12 +21,12 @@ We've also added exprimental support for the ability to link attribute in layers be useful for synchronizing attribute values across layers, for example to set matching contrast limits for multiple channels (#2226). - ## New Features -- Add experimental link_layers (#2226) +- Add experimental link_layers (#2226) ## Improvements + - Replace evented dataclasses with pydantic evented model on viewer (#2042) - Add pydantic evented model (#2127) - UI: Add keybindings to the Paint and Pick tooltips (#2184) @@ -37,32 +37,32 @@ set matching contrast limits for multiple channels (#2226). - Support equality checking for arrays (or other unusual types) in EventedModel. (#2232) - Make get_stylesheet public (#2241) - ## Bug Fixes + - Fix show/ hide plugin widgets (#2173) - Fix bug with modification of `AVAILABLE_COLORMAPS` when iterating over it (#2193) - Prevent monitor information refering to one of first issues (#2214) - Fix close procedure (#2220) - Update `napari.run`, prevent double-blocking (#2225) - ## Documentation -- Convert docs to use Jupyter Book (#2187) +- Convert docs to use Jupyter Book (#2187) ## API Changes + - Removed github searching for plugin discovery, instead the `Framework :: napari` classifer should be used (#2228) - Removed evented_dataclass, instead the `EventedModel` should be used (#2236) -- The deprecated ``Viewer.interactive`` parameter has been removed, instead you should use ``Viewer.camera.interactive`` (#2198) -- The deprecated ``Viewer.palette`` attribute has been removed. To access the palette you can get it using ``napari.utils.theme.register_theme`` dictionary using the ``viewer.theme`` as the key (#2198) -- The deprecated approach of annotating a magicgui function with a return type of ``napari.layers.Layer`` has been removed. To indicate that your function returns a layer data tuple, please use a return annotation of ``napari.types.LayerDataTuple`` or ``List[napari.types.LayerDataTuple]``(#2198) - +- The deprecated `Viewer.interactive` parameter has been removed, instead you should use `Viewer.camera.interactive` (#2198) +- The deprecated `Viewer.palette` attribute has been removed. To access the palette you can get it using `napari.utils.theme.register_theme` dictionary using the `viewer.theme` as the key (#2198) +- The deprecated approach of annotating a magicgui function with a return type of `napari.layers.Layer` has been removed. To indicate that your function returns a layer data tuple, please use a return annotation of `napari.types.LayerDataTuple` or `List[napari.types.LayerDataTuple]`(#2198) ## Deprecations - - The `asdict` method has been renamed `dict` and is now deprecated on `Axes`, `Camera`, `Cursor`, `Dims, `GridCanvas`, `ScaleBar` (#2197) +- The `asdict` method has been renamed `dict` and is now deprecated on `Axes`, `Camera`, `Cursor`, `Dims, `GridCanvas`, `ScaleBar\` (#2197) ## Build Tools and Support + - Use napari-console package (#2118) - Fix typo in hookspecs docs (#2180) - DOC: autoreformat all the docstrings (#2186) @@ -78,7 +78,6 @@ set matching contrast limits for multiple channels (#2226). - Update napari_plugin_tester docstring (#2251) - Remove pluginmanager fixture in favor of devtools repo (#2252) - ## 9 authors added to this release (alphabetical) - [Gonzalo Peña-Castellanos](https://github.com/napari/napari/commits?author=goanpeca) - @goanpeca @@ -91,7 +90,6 @@ set matching contrast limits for multiple channels (#2226). - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Ziyang Liu](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 10 reviewers added to this release (alphabetical) - [Gonzalo Peña-Castellanos](https://github.com/napari/napari/commits?author=goanpeca) - @goanpeca @@ -104,4 +102,3 @@ set matching contrast limits for multiple channels (#2226). - [Matthias Bussonnier](https://github.com/napari/napari/commits?author=Carreau) - @Carreau - [Nicholas Sofroniew](https://github.com/napari/napari/commits?author=sofroniewn) - @sofroniewn - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - diff --git a/docs/release/release_0_4_6.md b/docs/release/release_0_4_6.md index 3733b2779..2892fb9a2 100644 --- a/docs/release/release_0_4_6.md +++ b/docs/release/release_0_4_6.md @@ -6,11 +6,11 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari ## Highlights + This release is the first that adds support for persistent settings in napari (#2212). Right now we just store the current theme and window geometry but we will be expanding this to include a full set of preferences in future releases. @@ -19,14 +19,14 @@ We've also made plugin installation from our plugin dialog more flexible, includ supporting installation from anything supported by `pip`, such as a package name, url, or local file (#2319). - ## New Features + - Add the ability to show contours of labels (#2168) - Add initial settings management for theme and window geometry (#2212) - More flexible plugin install (from url, name or file) (#2319) - ## Improvements + - Use pydantic for viewer model (#2066) - Use vectorized indexing for xarray arrays in labels (#2191) - Add basic composite tree model (#2217) @@ -46,8 +46,8 @@ package name, url, or local file (#2319). - Don't subclass `Layer` directly from `Image`, (adds `_ImageBase`) (#2307) - Disable run pre release test outside napari main repository (#2331) - ## Bug Fixes + - Fix QtPoll to poll when camera moves (#2227) - Prevent garbage collection of viewer during startup. (#2262) - Fix EventedModel signatures with PySide2 imported (#2265) @@ -55,15 +55,16 @@ package name, url, or local file (#2319). - Fix bug with not display points close to given layer (#2289) - Fix plugin discovery in bundle (use certifi context for urlopen) (#2298) - Call processEvents before nbscreenshot (#2303) -- Add handling of python <= 3.7 for translator (#2305) +- Add handling of python \<= 3.7 for translator (#2305) - Add parent parameter to about dialog to correctly inherit and apply theme (#2308) - Remove unneeded stylesheet on QtAbout dialog (#2312) - Avoid importing xarray (and other array libs) for equality checkers (#2325) ## API Changes + - The ViewerModel is now a Pydantic BaseModel and so subclassing the Viewer might require -reading some of the [Pydantic BaseModel documentation](https://pydantic-docs.helpmanual.io/usage/models/). -Otherwise the API of the ViewerModel is unchanged. + reading some of the [Pydantic BaseModel documentation](https://pydantic-docs.helpmanual.io/usage/models/). + Otherwise the API of the ViewerModel is unchanged. - Because `Labels` no longer inherit from `Image` (#2307), magicgui function parameters annotated as `napari.layers.Image` will no longer include `napari.layers.Labels` layer types in the dropdown menu. (`napari.layers.Layer` @@ -71,17 +72,17 @@ Otherwise the API of the ViewerModel is unchanged. menu.) ## Build Tools and Support + - Add missing release notes 0.4.5 (#2250) - Explicitly document ability to implement multiple of the same hook (#2256) - Update install instructions to work with zsh default Mac shell (#2258) - Fix broken links in README (#2275) - Documentation typos (#2294) - Documentation on napari types in magicgui (#2306) -- Pin pydantic < 1.8.0 (#2323) +- Pin pydantic \< 1.8.0 (#2323) - Fix docs build with code execution (#2324) - Pin pydantic != 1.8.0 (#2333) - ## 12 authors added to this release (alphabetical) - [Draga Doncila Pop](https://github.com/napari/napari/commits?author=DragaDoncila) - @DragaDoncila @@ -97,7 +98,6 @@ Otherwise the API of the ViewerModel is unchanged. - [Philip Winston](https://github.com/napari/napari/commits?author=pwinston) - @pwinston - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - ## 13 reviewers added to this release (alphabetical) - [Draga Doncila Pop](https://github.com/napari/napari/commits?author=DragaDoncila) - @DragaDoncila diff --git a/docs/release/release_0_4_7.md b/docs/release/release_0_4_7.md index 68250a1e9..2545732e4 100644 --- a/docs/release/release_0_4_7.md +++ b/docs/release/release_0_4_7.md @@ -6,7 +6,6 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari @@ -41,11 +40,13 @@ documentation. See below for the full list of changes. ## New Features + - Notification Manager (#2205) - Add preference dialog (#2211) - Make napari strings localizable (#2429) ## Documentation + - Add profiling documentation (#1998) - Add release note about pydantic viewermodel (#2334) - Broken link fixed for Handling Code of Conduct Reports (#2342) @@ -54,6 +55,7 @@ See below for the full list of changes. - Make structural doc changes to better align with sitemap (#2404) ## Improvements + - ColorManager take 2 (w/ pydantic) (#2204) - Add basic selection model (#2293) - Tooltips2 (#2310) @@ -88,6 +90,7 @@ See below for the full list of changes. - Update preferences dialog style to match designs (#2456) ## Bug Fixes + - Fix initialization of an empty Points layer (#2341) - Fix octree clim (#2349) - Fix json encoder inheritance on nested submodels for pydantic>=1.8.0 (#2357) @@ -139,10 +142,10 @@ We have also deprecated the `napari.qt.QtNDisplayButton`. Instead a more general `napari.qt.QtStateButton` is provided. ## Build Tools and Support + - Add environment flag for sparse library (#2396) - re-add plausible (#2433) - ## 14 authors added to this release (alphabetical) - [alisterburt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt @@ -160,7 +163,6 @@ We have also deprecated the `napari.qt.QtNDisplayButton`. Instead a more general - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [ziyangczi](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - ## 12 reviewers added to this release (alphabetical) - [Draga Doncila Pop](https://github.com/napari/napari/commits?author=DragaDoncila) - @DragaDoncila @@ -175,4 +177,3 @@ We have also deprecated the `napari.qt.QtNDisplayButton`. Instead a more general - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - [Thomas A Caswell](https://github.com/napari/napari/commits?author=tacaswell) - @tacaswell - [ziyangczi](https://github.com/napari/napari/commits?author=ziyangczi) - @ziyangczi - diff --git a/docs/release/release_0_4_8.md b/docs/release/release_0_4_8.md index 96f5f6776..2fe94e5e2 100644 --- a/docs/release/release_0_4_8.md +++ b/docs/release/release_0_4_8.md @@ -39,8 +39,7 @@ plugin](https://github.com/napari/napari/blob/v0.4.8/docs/plugins/for_plugin_dev specification](https://github.com/napari/napari/blob/v0.4.8/napari/plugins/hook_specifications.py#L57). The scale bar now has rudimentary support for physical units 📏 (#2617). To use -it, set your scale numerically as before, then use `viewer.scale_bar.unit = -'um'`, for example. +it, set your scale numerically as before, then use `viewer.scale_bar.unit = 'um'`, for example. We have also added a text overlay, which you can use to display arbitrary text over the viewer (#2595). You can use this to display time series time stamps, @@ -58,7 +57,6 @@ bar on the viewer (#2580). You can find usage examples in the repo and [here](https://github.com/napari/napari/blob/fa342dc399b636330afdb1b4cb58f919832651fd/examples/progress_bar_segmentation.py). - ## New Features - Add highlight widget to preferences dialog (#2435) @@ -99,7 +97,7 @@ and - Support varying number of dimensions during labels painting (#2609) - Add units to the ScaleBar visual (#2617) - Return widgets created by `add_plugin_dock_widget` (#2635) -- Add _QtMainWindow.current (#2638) +- Add \_QtMainWindow.current (#2638) - Relax dask test (#2641) - Add table header style (#2645) - QLargeIntSpinbox with QAbstractSpinbox and python model (#2648) @@ -162,18 +160,19 @@ and the previous behaviour with `napari.utils.resize_dask_cache(memory_fraction=0.1)`. You can of course also experiment with other values! + - The default `area` for `add_dock_widget` is now `right`, and no longer `bottom`. + - To avoid oddly spaced sparse widgets, #2154 adds vertical stretch to the bottom of all dock widgets added (via plugins or manually) with an `area` of `left` or `right`, *unless:* - 1) the widget, or any widget in its primary layout, has a vertical - [`QSizePolicy`](https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum) - of `Expanding`, `MinimumExpanding`, or `Ignored` - - 1) `add_vertical_stretch=False` is provided to `add_dock_widget`, - or in the widget options provided with plugin dock widgets. + 1. the widget, or any widget in its primary layout, has a vertical + [`QSizePolicy`](https://doc.qt.io/qt-5/qsizepolicy.html#Policy-enum) + of `Expanding`, `MinimumExpanding`, or `Ignored` + 2. `add_vertical_stretch=False` is provided to `add_dock_widget`, + or in the widget options provided with plugin dock widgets. ## Deprecations @@ -226,7 +225,6 @@ and - Update PULL_REQUEST_TEMPLATE.md (#2497) - Non-dynamic base layer classes (#2624) - ## 19 authors added to this release (alphabetical) - [alisterburt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt @@ -249,7 +247,6 @@ and - [Wilson Adhikari](https://github.com/napari/napari/commits?author=wadhikar) - @wadhikar - [Zac Hatfield-Dodds](https://github.com/napari/napari/commits?author=Zac-HD) - @Zac-HD - ## 20 reviewers added to this release (alphabetical) - [alisterburt](https://github.com/napari/napari/commits?author=alisterburt) - @alisterburt diff --git a/docs/release/release_0_4_9.md b/docs/release/release_0_4_9.md index d00482daf..2bdcdf381 100644 --- a/docs/release/release_0_4_9.md +++ b/docs/release/release_0_4_9.md @@ -6,24 +6,24 @@ It's designed for browsing, annotating, and analyzing large multi-dimensional images. It's built on top of Qt (for the GUI), vispy (for performant GPU-based rendering), and the scientific Python stack (numpy, scipy). - For more information, examples, and documentation, please visit our website: https://github.com/napari/napari - ## Highlights + This release adds a couple nice new features like additional shading modes for our surface layer (#2972) and the ability to copy a screenshot directly to the clipboard (#2721). It also contains a variety of bug fixes and improvements. - ## New Features + - Add tooltip for labels (#2658) - Added copy-to-clipboard functionality (#2721) - Add `make watch` command for hot reload (#2763) - Expose alternative shading modes for surfaces (#2792) ## Improvements + - Global plugin setting (#2565) - Provide interface for progress bars in @thread_workers (#2655) - Delay all imports in `napari.__init__` behind module level `napari.__getattr__` (#2662) @@ -53,8 +53,8 @@ It also contains a variety of bug fixes and improvements. - Followup to #2485 to add opengl context (#2846) - Do not store default values in preference files (#2848) - ## Bug Fixes + - Fix Labels and Points properties set (#2657) - Fixing `add_dock_widget` compatibility with `magicgui v0.2` (#2734) - Shortcuts: Render properly shortcuts with minus and space. (#2735) @@ -84,18 +84,17 @@ It also contains a variety of bug fixes and improvements. - Be more robust for non-existant keybindings in settings (#2861) - trans NameError bugfix (#2865) - ## Tasks + - Add imports linting (#2659) - Pre-commit update (#2728) - Remove linenos option from code-blocks and line references (#2739) -- Remove qt from _qt import linter rule (#2774) +- Remove qt from \_qt import linter rule (#2774) - Add PR labeler and update templates (#2775) - Add pytest-order and move threading tests to the top of the suite (#2779) - Auto assign PR to author (#2794) - Typo in PR template (#2831) - ## 12 authors added to this release (alphabetical) - [Ahmet Can Solak](https://github.com/napari/napari/commits?author=AhmetCanSolak) - @AhmetCanSolak @@ -111,7 +110,6 @@ It also contains a variety of bug fixes and improvements. - [Pam](https://github.com/napari/napari/commits?author=ppwadhwa) - @ppwadhwa - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - ## 10 reviewers added to this release (alphabetical) - [Andy Sweet](https://github.com/napari/napari/commits?author=andy-sweet) - @andy-sweet @@ -124,4 +122,3 @@ It also contains a variety of bug fixes and improvements. - [Nicholas Sofroniew](https://github.com/napari/napari/commits?author=sofroniewn) - @sofroniewn - [Pam](https://github.com/napari/napari/commits?author=ppwadhwa) - @ppwadhwa - [Talley Lambert](https://github.com/napari/napari/commits?author=tlambert03) - @tlambert03 - diff --git a/docs/roadmaps/0_3.md b/docs/roadmaps/0_3.md index d6b18c36e..85eed3edf 100644 --- a/docs/roadmaps/0_3.md +++ b/docs/roadmaps/0_3.md @@ -1,6 +1,6 @@ # Roadmap 0.3 -## For 0.3.* series of releases - April 2020 +## For 0.3.\* series of releases - April 2020 The napari roadmap captures current development priorities within the project and should serve as a guide for napari core developers, to encourage and inspire contributors, and to provide insights to external developers who are interested in building for the napari ecosystem. For more details on what this document is and is not, see the [about this document section](#about-this-document). diff --git a/docs/roadmaps/0_3_retrospective.md b/docs/roadmaps/0_3_retrospective.md index 1bd750821..9a75507a2 100644 --- a/docs/roadmaps/0_3_retrospective.md +++ b/docs/roadmaps/0_3_retrospective.md @@ -4,7 +4,7 @@ Periodically, the napari team produces roadmaps to clarify priorities in the com Below, we examine our 0.3.x roadmap (reproduced in its entirety) and compare it to what we achieved in the corresponding 6 months (in inline quote blocks). -## For 0.3.* series of releases - April 2020-Nov 2020 +## For 0.3.\* series of releases - April 2020-Nov 2020 The napari roadmap captures current development priorities within the project and should serve as a guide for napari core developers, to encourage and inspire contributors, and to provide insights to external developers who are interested in building for the napari ecosystem. For more details on what this document is and is not, see the [about this document section](#about-this-document). @@ -18,72 +18,69 @@ The [mission](our-mission) of napari is to be a foundational multi-dimensional i Once the above goals are met, we will develop napari's capabilities for image processing and analysis. We are prioritizing the robustness and polish of the core viewer before adding advanced features or support for functional or interactive plugins to ensure that plugin development will happen against a solid foundation, and because we have repeatedly encountered biologists and other scientists who have trouble even looking at their data, and annotating it. We are prioritizing the downloadable application with reader / writer plugin management so that we can start getting feedback from non-coding users to better understand their needs. -> Note added Novemember 2020. We have now finished our 0.3 series of releases. Overall we made good progress towards these priorites. We now provide cross-platform support for downloadable app that you can install reader and writer plugins into. We added support for world coordinates, we reduced the number of bugs by ~20% (>80 to <60), and we've begun begun to support asynchronous rendering and improve performance. We also had some highlights that weren't on our roadmap including contributions from the community like the Tracks layer. We've added more documentation and tutorials, but a redesign of our website is still in progress. For a detailed lookback at the work that was done during the `0.3` series of releases you can look at comments under the individual bullet items below. For a look at where things are heading next, please see our [0.4 roadmap](https://napari.org/roadmaps/0_4.html). +> Note added Novemember 2020. We have now finished our 0.3 series of releases. Overall we made good progress towards these priorites. We now provide cross-platform support for downloadable app that you can install reader and writer plugins into. We added support for world coordinates, we reduced the number of bugs by ~20% (>80 to \<60), and we've begun begun to support asynchronous rendering and improve performance. We also had some highlights that weren't on our roadmap including contributions from the community like the Tracks layer. We've added more documentation and tutorials, but a redesign of our website is still in progress. For a detailed lookback at the work that was done during the `0.3` series of releases you can look at comments under the individual bullet items below. For a look at where things are heading next, please see our [0.4 roadmap](https://napari.org/roadmaps/0_4.html). ## Make the data viewing and annotation capabilities bug-free, fast and easy to use - **Better support for viewing big datasets**. Currently, napari is fast when viewing on-disk datasets that can be naturally sliced along one axis (e.g. a time series) *and where loading one slice is fast*. However, when the loading is slow, the napari UI itself becomes slow, sometimes to the point of being unusable. We aim to improve this by making views and interactions non-blocking ([#845](https://github.com/napari/napari/issues/845)), and improving caching ([#718](https://github.com/napari/napari/issues/718)). We will also ensure that napari can be used `headless` without the GUI. - > We've made good progress towards `asynchronous` loading of data in our [async](https://github.com/napari/napari/labels/async) work, but we still have much more to do. The async functionality can be enabled right now in an opt in fashion but is still considered experimental as we develop it further. We're also working on an octree which will enable better multiscale rendering, see this [rendering explanation note](rendering-explanation), which has become available during our initial 0.4 series of releases. - + > We've made good progress towards `asynchronous` loading of data in our [async](https://github.com/napari/napari/labels/async) work, but we still have much more to do. The async functionality can be enabled right now in an opt in fashion but is still considered experimental as we develop it further. We're also working on an octree which will enable better multiscale rendering, see this [rendering explanation note](rendering-explanation), which has become available during our initial 0.4 series of releases. - **Improving the performance of operations on in-memory data**. Even when data is loaded in memory, some operations, such as label and shape painting, slicing along large numbers of points, or adjusting contrast and gamma, can be slow. We will continue developing our [benchmark suite](https://napari.org/stable/developers/benchmarks.html) and work to integrate it into our development process. See the [`performance` label](https://github.com/napari/napari/labels/performance) for a current list of issues related to performance. - > We've improved some perfomance issues, such as adjusting gamma and contrast limits, which now happen on the shader, but still have more to go. Painting in labels remains slow for large datasets and still needs to be improved. We added an [explantory note on perfomance](napari-performance) to detail how we're thinking about perfomance in napari and ensuring it stays high. We've also added some [perfomance monitoring tooling](perfmon) that will help us during this process. We successful used this functionality to improve performance of the Shapes Layer and will continue this work during the 0.4 series of releases. - + > We've improved some perfomance issues, such as adjusting gamma and contrast limits, which now happen on the shader, but still have more to go. Painting in labels remains slow for large datasets and still needs to be improved. We added an [explantory note on perfomance](napari-performance) to detail how we're thinking about perfomance in napari and ensuring it stays high. We've also added some [perfomance monitoring tooling](perfmon) that will help us during this process. We successful used this functionality to improve performance of the Shapes Layer and will continue this work during the 0.4 series of releases. - Add a unified **world coordinate system**. Scientists need to measure data that comes from a real space with physical dimensions. Currently, napari has no concept of the space in which data lives: everything is unitless. Further, it is unclear at various parts in the UI whether a coordinate has been transformed. And finally, some data are acquired with distortions, such as skew in data collected on stage-scanning lightsheet microscopes, and napari should be able to account for those distortions by chaining together transforms - including affine and ultimately deformable transforms. We are tracking progress in this area in the [World Coordinates project board](https://github.com/napari/napari/projects/10). - > We have completed our world coordinate system, including support for affine transforms, a scale bar, and an axes rotation visual. We have more work to do to add support for physical units which will come in the 0.4 series of releases, non-orthogonal slicing, and non-rigid transforms, which will be saved for future roadmaps. + > We have completed our world coordinate system, including support for affine transforms, a scale bar, and an axes rotation visual. We have more work to do to add support for physical units which will come in the 0.4 series of releases, non-orthogonal slicing, and non-rigid transforms, which will be saved for future roadmaps. - Ensure **easy installation with a success rate close to 100%**. We can still struggle with installation of Qt in some cases, and have had problems with dependency conflicts with other Python packages. We have recently added a [conda-forge package](https://github.com/conda-forge/napari-feedstock) and are working towards distributing bundled applications in [#496](https://github.com/napari/napari/pull/496). See the [`installation` label](https://github.com/napari/napari/labels/installation) for a current list of issues related to installation. - > We have made good progress on improving the success rate of our installation process, and now encounter less installation related new issues. In particular, installation in fresh environments is often successful, especially using conda-forge, and our bundled app now exists and works for a large proportion of users. Many platform specific bugs still remain though. Windows is often a problem, partly due to lack of representation in the core team, and macOS Big Sur has raised a whole new set of issues, though again, this works with conda-forge and python 3.9.0 as of the 0.4 series of releases. + > We have made good progress on improving the success rate of our installation process, and now encounter less installation related new issues. In particular, installation in fresh environments is often successful, especially using conda-forge, and our bundled app now exists and works for a large proportion of users. Many platform specific bugs still remain though. Windows is often a problem, partly due to lack of representation in the core team, and macOS Big Sur has raised a whole new set of issues, though again, this works with conda-forge and python 3.9.0 as of the 0.4 series of releases. - **Eliminate all known bugs**. See the [`bug` label](https://github.com/napari/napari/labels/bug) for a current list of known bugs. We also want to make it easier for users to report bugs ([#1090](https://github.com/napari/napari/issues/1090)). Additionally, when bugs are encountered, we want to examine whether an improved software architecture could have prevented them. For example, we are undertaking a refactor to centralize event handling within napari and avoid circular, out of order, or repeated function calls ([#1040](https://github.com/napari/napari/pull/1040)). In the process of eliminating bugs we should also ensure that our continuous integration, i.e. automatic testing of each change on cloud infrastructure, is robust, and that we have increased coverage of our tests, including more GUI tests with screenshots. See the [`tests` label](https://github.com/napari/napari/labels/tests) for more information about our tests. - > We've reduced the number of open [`bug`](https://github.com/napari/napari/labels/bug) labeled issues by more than 20% in the past two months from > 80 to < 60 and added some more screenshot and GUI tests, but test coverage of the GUI can still be improved. + > We've reduced the number of open [`bug`](https://github.com/napari/napari/labels/bug) labeled issues by more than 20% in the past two months from > 80 to \< 60 and added some more screenshot and GUI tests, but test coverage of the GUI can still be improved. - **Improve support for annotation** of points [#858](https://github.com/napari/napari/issues/858), shapes [#177](https://github.com/napari/napari/issues/177), labels, and images [#209](https://github.com/napari/napari/issues/209) including text rendering [#600](https://github.com/napari/napari/pull/600). - > We have added programmatic support for text and other properties in points, shapes, and labels, allowing users to effectively visualize many properties in their data. However, support for editing these properties from the UI remains limited or non-existent, so we will aim to improve this in the next roadmap. + > We have added programmatic support for text and other properties in points, shapes, and labels, allowing users to effectively visualize many properties in their data. However, support for editing these properties from the UI remains limited or non-existent, so we will aim to improve this in the next roadmap. - Add **linked multi-canvas support** ([#760](https://github.com/napari/napari/issues/760)) to allow orthogonal views, or linked views of two datasets, for example to select keypoints for image alignment, or simultaneous 2D slices with 3D rendering. - > We have not done this work yet, and this work will be moved to our 0.4 series of releases. + > We have not done this work yet, and this work will be moved to our 0.4 series of releases. - Add **layer groups** [#970](https://github.com/napari/napari/issues/970), which allow operating on many layers simultaneously making the viewer easier to use for multispectral or multimodal data, or, in the context of multiple canvases, where one wants to assign different groups to different canvases. - > We have not done this work yet, and this work will be moved to our 0.4 series of releases. + > We have not done this work yet, and this work will be moved to our 0.4 series of releases. - Improve the **user interface and design** of the viewer to make it easier to use. A [napari design audit](https://github.com/napari/napari/issues/469) last year dramatically improved the usability of the viewer. We must continue to run through these periodically to ensure the UI is friendly to new users, particularly non-programmers. See the [`design` label](https://github.com/napari/napari/labels/design) for more information. - > We have conducted a product heuristics analysis to [identify design and usability issues](https://github.com/napari/product-heuristics-2020), and will now be working during the 0.4 series of releases to implement them. + > We have conducted a product heuristics analysis to [identify design and usability issues](https://github.com/napari/product-heuristics-2020), and will now be working during the 0.4 series of releases to implement them. ## Make a downloadable application with basic plugin management and persistent settings - Distribute **a bundled application for each major OS** [#496](https://github.com/napari/napari/pull/496) to allow scientists to use napari without requiring a Python development environment, which can be difficult to install and maintain. The bundle should contain the necessary machinery to add plugins [#1074](https://github.com/napari/napari/issues/1074). - > We have succeeded in providing a bundled app that you can install plugins into across all platforms. We have more work to do around plugin management and app updates that will continue in our 0.4 series of releases, but the bundled app is now usable to install napari by people who aren't familiar with the command line. + > We have succeeded in providing a bundled app that you can install plugins into across all platforms. We have more work to do around plugin management and app updates that will continue in our 0.4 series of releases, but the bundled app is now usable to install napari by people who aren't familiar with the command line. - Support for reader plugins [#937](https://github.com/napari/napari/pull/937) and writer plugins [#1068](https://github.com/napari/napari/issues/1068) to allow **viewing of domain-specific data and saving of annotations**. For more details see the [`plugins` label](https://github.com/napari/napari/labels/plugins) on our repository. - > We have a stable specification for adding both [reader and writer plugins](hook-specifications-reference) that is being used in the community. + > We have a stable specification for adding both [reader and writer plugins](hook-specifications-reference) that is being used in the community. - Support for persistent settings [#834](https://github.com/napari/napari/pull/834) to allow **saving of preferences** between launches of the viewer. - > We have not done this work yet, and this work will be moved to our 0.4 series of releases. - + > We have not done this work yet, and this work will be moved to our 0.4 series of releases. ## Provide accessible documentation, tutorials, and demos - Improve our website [napari.org](https://napari.org) to provide easy access to all napari related materials, including the [**four types of documentation**](https://www.divio.com/blog/documentation/): learning-oriented tutorials, goal-oriented how-to-guides or galleries, understanding-oriented explanations (including developer resources), and a comprehensive API reference. See [#764](https://github.com/napari/napari/issues/764) and the [`documentation` label](https://github.com/napari/napari/labels/documentation) on the napari repository for more details. - > We have begun the design work for this but have not begun the implementation yet. This work will be moved to our 0.4 series of releases. + > We have begun the design work for this but have not begun the implementation yet. This work will be moved to our 0.4 series of releases. - Add a **napari human interface guide** for plugin developers, akin to [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/guidelines/overview/). We want such a guide to promote best practices and ensure that plugins provide a consistent user experience. - > We have not done this work yet, and this work will be moved to our 0.4 series of releases. + > We have not done this work yet, and this work will be moved to our 0.4 series of releases. ## Work prioritized for future roadmaps @@ -95,7 +92,7 @@ We’re also planning or working on the following more advanced features, which - Support for generating animations [#780](https://github.com/napari/napari/pull/780). This feature will be supported after we have the ability to serialize the viewer state [#851](https://github.com/napari/napari/pull/851). - > We have moved development of the animation functionality to a standalone plugin. Currently under active development at https://github.com/sofroniewn/napari-animation. + > We have moved development of the animation functionality to a standalone plugin. Currently under active development at https://github.com/sofroniewn/napari-animation. - Draggable and resizable layers [#299](https://github.com/napari/napari/issues/299) and [#989](https://github.com/napari/napari/pull/989). This feature will be supported after we have added support for world coordinates [project board 10](https://github.com/napari/napari/projects/10) and rotations to our transform model. @@ -105,7 +102,6 @@ We’re also planning or working on the following more advanced features, which - Functional or interactive plugins that allow for analysis of data or add elements to the GUI. - ## About this document This document is meant to be a snapshot of tasks and features that the `napari` team is investing resources in during our 0.3 series of releases starting April 2020. This document should be used to guide napari core developers, encourage and inspire contributors, and provide insights to external developers who are interested in building for the napari ecosystem. It is not meant to limit what is being worked on within napari, and in accordance with our [values](our-values) we remain **community-driven**, responding to feature requests and proposals on our [issue tracker](https://github.com/napari/napari/issues) and making decisions that are driven by our users’ requirements, not by the whims of the core team. diff --git a/docs/roadmaps/0_4.md b/docs/roadmaps/0_4.md index 0639bf66f..938281537 100644 --- a/docs/roadmaps/0_4.md +++ b/docs/roadmaps/0_4.md @@ -1,6 +1,6 @@ # Roadmap 0.4 -## For 0.4.* series of releases - November 2020 +## For 0.4.\* series of releases - November 2020 The napari roadmap captures current development priorities within the project and aims to guide for napari core developers, to encourage and inspire contributors, and to provide insights to external developers who are interested in building for the napari ecosystem. For more details on what this document is and is not, see the [about this document section](#about-this-document). @@ -91,7 +91,6 @@ permanent features. For more details, follow the [plugins label on GitHub](https://github.com/napari/napari/labels/plugins). - ## Provide accessible documentation, tutorials, and demos To make the most of napari, users need to find reliable documentation on how to diff --git a/docs/tutorials/annotation/annotate_points.md b/docs/tutorials/annotation/annotate_points.md index 7c31958f3..82d4d55bb 100644 --- a/docs/tutorials/annotation/annotate_points.md +++ b/docs/tutorials/annotation/annotate_points.md @@ -9,8 +9,8 @@ In this tutorial, we will cover creating and interacting with a Points layer wit At the end of this tutorial, we will have created a GUI for annotating points in videos that we can simply call by: ```python -im_path = '/*.png' -point_annotator(im_path, labels=['ear_l', 'ear_r', 'tail']) +im_path = "/*.png" +point_annotator(im_path, labels=["ear_l", "ear_r", "tail"]) ``` The resulting viewer looks like this (images from [Mathis et al., 2018](https://www.nature.com/articles/s41593-018-0209-y), downloaded from [here](https://github.com/DeepLabCut/DeepLabCut/tree/f21321ef8060c537f9df0ce9346189bda07701b5/examples/openfield-Pranav-2018-10-30/labeled-data/m4s1)): @@ -30,16 +30,16 @@ import numpy as np COLOR_CYCLE = [ - '#1f77b4', - '#ff7f0e', - '#2ca02c', - '#d62728', - '#9467bd', - '#8c564b', - '#e377c2', - '#7f7f7f', - '#bcbd22', - '#17becf' + "#1f77b4", + "#ff7f0e", + "#2ca02c", + "#d62728", + "#9467bd", + "#8c564b", + "#e377c2", + "#7f7f7f", + "#bcbd22", + "#17becf", ] @@ -59,12 +59,12 @@ def create_label_menu(points_layer, labels): the magicgui Container with our dropdown menu widget """ # Create the label selection menu - label_menu = ComboBox(label='feature_label', choices=labels) + label_menu = ComboBox(label="feature_label", choices=labels) label_widget = Container(widgets=[label_menu]) def update_label_menu(event): """Update the label menu when the point selection changes""" - new_label = str(points_layer.current_properties['label'][0]) + new_label = str(points_layer.current_properties["label"][0]) if new_label != label_menu.value: label_menu.value = new_label @@ -74,7 +74,7 @@ def create_label_menu(points_layer, labels): """Update the Points layer when the label menu selection changes""" selected_label = event.value current_properties = points_layer.current_properties - current_properties['label'] = np.asarray([selected_label]) + current_properties["label"] = np.asarray([selected_label]) points_layer.current_properties = current_properties label_menu.changed.connect(label_changed) @@ -83,8 +83,8 @@ def create_label_menu(points_layer, labels): def point_annotator( - im_path: str, - labels: List[str], + im_path: str, + labels: List[str], ): """Create a GUI for annotating points in a series of images. @@ -99,54 +99,54 @@ def point_annotator( viewer = napari.view_image(stack) points_layer = viewer.add_points( - properties={'label': labels}, - edge_color='label', + properties={"label": labels}, + edge_color="label", edge_color_cycle=COLOR_CYCLE, - symbol='o', - face_color='transparent', + symbol="o", + face_color="transparent", edge_width=8, size=12, - ndim=3 + ndim=3, ) - points_layer.edge_color_mode = 'cycle' + points_layer.edge_color_mode = "cycle" # add the label menu widget to the viewer label_widget = create_label_menu(points_layer, labels) viewer.window.add_dock_widget(label_widget) - @viewer.bind_key('.') + @viewer.bind_key(".") def next_label(event=None): """Keybinding to advance to the next label with wraparound""" current_properties = points_layer.current_properties - current_label = current_properties['label'][0] + current_label = current_properties["label"][0] ind = list(labels).index(current_label) new_ind = (ind + 1) % len(labels) new_label = labels[new_ind] - current_properties['label'] = np.array([new_label]) + current_properties["label"] = np.array([new_label]) points_layer.current_properties = current_properties def next_on_click(layer, event): """Mouse click binding to advance the label when a point is added""" - if layer.mode == 'add': + if layer.mode == "add": next_label() # by default, napari selects the point that was just added # disable that behavior, as the highlight gets in the way layer.selected_data = {} - points_layer.mode = 'add' + points_layer.mode = "add" points_layer.mouse_drag_callbacks.append(next_on_click) - @viewer.bind_key(',') + @viewer.bind_key(",") def prev_label(event): """Keybinding to decrement to the previous label with wraparound""" current_properties = points_layer.current_properties - current_label = current_properties['label'][0] + current_label = current_properties["label"][0] ind = list(labels).index(current_label) n_labels = len(labels) new_ind = ((ind - 1) + n_labels) % n_labels new_label = labels[new_ind] - current_properties['label'] = np.array([new_label]) + current_properties["label"] = np.array([new_label]) points_layer.current_properties = current_properties ``` @@ -206,16 +206,16 @@ As discussed above, we will be storing which feature of interest each point corr To visualize the feature each point represents, we set the edge color as a color cycle mapped to the `label` property (`edge_color='label'`). ```python -properties = {'label': labels} +properties = {"label": labels} points_layer = viewer.add_points( properties=properties, - edge_color='label', + edge_color="label", edge_color_cycle=COLOR_CYCLE, - symbol='o', - face_color='transparent', + symbol="o", + face_color="transparent", edge_width=8, size=12, - ndim=3 + ndim=3, ) ``` @@ -226,16 +226,16 @@ For example, the [category10 color palette](https://github.com/d3/d3-3.x-api-ref ```python COLOR_CYCLE = [ - '#1f77b4', - '#ff7f0e', - '#2ca02c', - '#d62728', - '#9467bd', - '#8c564b', - '#e377c2', - '#7f7f7f', - '#bcbd22', - '#17becf' + "#1f77b4", + "#ff7f0e", + "#2ca02c", + "#d62728", + "#9467bd", + "#8c564b", + "#e377c2", + "#7f7f7f", + "#bcbd22", + "#17becf", ] ``` @@ -244,7 +244,7 @@ We set the points `ndim` to 3 so that the coordinates for the point annotations Finally, we set the edge color to a color cycle: ```python - points_layer.edge_color_mode = 'cycle' +points_layer.edge_color_mode = "cycle" ``` ## Adding a GUI for selecting points @@ -279,7 +279,7 @@ To make the a dropdown menu populated with the valid point labels, we simply cre ```python # Create the label selection menu -label_menu = ComboBox(label='feature_label', choices=labels) +label_menu = ComboBox(label="feature_label", choices=labels) label_widget = Container(widgets=[label_menu]) ``` @@ -293,10 +293,11 @@ We connect the function we created to the event so that `update_label_menu()` is ```python def update_label_menu(event): """Update the label menu when the point selection changes""" - new_label = str(points_layer.current_properties['label'][0]) + new_label = str(points_layer.current_properties["label"][0]) if new_label != label_menu.value: label_menu.value = new_label + points_layer.events.current_properties.connect(update_label_menu) ``` @@ -309,9 +310,10 @@ def label_changed(event): """Update the Points layer when the label menu selection changes""" selected_label = event.value current_properties = points_layer.current_properties - current_properties['label'] = np.asarray([selected_label]) + current_properties["label"] = np.asarray([selected_label]) points_layer.current_properties = current_properties + label_menu.changed.connect(label_changed) ``` @@ -332,13 +334,13 @@ The decorator requires that we pass the key to bind the function to as a string In this case, we are binding `next_label()` to the `.` key. ```python -@viewer.bind_key('.') +@viewer.bind_key(".") def next_label(event=None): """Keybinding to advance to the next label with wraparound""" # get the currently selected label current_properties = points_layer.current_properties - current_label = current_properties['label'][0] + current_label = current_properties["label"][0] # determine the index of that label in the labels list ind = list(labels).index(current_label) @@ -348,23 +350,23 @@ def next_label(event=None): # get the new label and assign it new_label = labels[new_ind] - current_properties['label'] = np.array([new_label]) + current_properties["label"] = np.array([new_label]) points_layer.current_properties = current_properties ``` We can do the same with another function that instead decrements the label with wraparound. ```python -@viewer.bind_key(',') +@viewer.bind_key(",") def prev_label(event): """Keybinding to decrement to the previous label with wraparound""" current_properties = points_layer.current_properties - current_label = current_properties['label'][0] + current_label = current_properties["label"][0] ind = list(labels).index(current_label) n_labels = len(labels) new_ind = ((ind - 1) + n_labels) % n_labels new_label = labels[new_ind] - current_properties['label'] = np.array([new_label]) + current_properties["label"] = np.array([new_label]) points_layer.current_properties = current_properties ``` @@ -381,7 +383,7 @@ Finally, def next_on_click(layer, event): """Mouse click binding to advance the label when a point is added""" # only do something if we are adding points - if layer.mode == 'add': + if layer.mode == "add": next_label() # by default, napari selects the point that was just added @@ -392,7 +394,7 @@ def next_on_click(layer, event): After creating the function, we then add it to the `points_layer` mouse drag callbacks. In napari, clicking and dragging events are both handled under the `mouse_drag_callbacks`. For more details on how mouse event callbacks work, -see the examples [[1](https://github.com/napari/napari/blob/main/examples/custom_mouse_functions.py), [2](https://github.com/napari/napari/blob/main/examples/mouse_drag_callback.py)]. +see the examples \[[1](https://github.com/napari/napari/blob/main/examples/custom_mouse_functions.py), [2](https://github.com/napari/napari/blob/main/examples/mouse_drag_callback.py)\]. ```python # bind the callback to the mouse drag event @@ -406,9 +408,9 @@ points_layer.mouse_drag_callbacks.append(next_on_click) Now that you've put it all together, you should be ready to test! You can call the function as shown below. ```python -im_path = '/*.png' +im_path = "/*.png" -point_annotator(im_path, labels=['ear_l', 'ear_r', 'tail']) +point_annotator(im_path, labels=["ear_l", "ear_r", "tail"]) ``` ### Saving the annotations @@ -423,5 +425,5 @@ Alternatively, we can use the `points_layer.save()` method to save the coordinat We can enter the command either in the script (e.g., bind a save function to a hot key) or the napari terminal. ```python -points_layer.save('path/to/file.csv') +points_layer.save("path/to/file.csv") ``` diff --git a/docs/tutorials/annotation/index.md b/docs/tutorials/annotation/index.md index 48552da74..be1d45864 100644 --- a/docs/tutorials/annotation/index.md +++ b/docs/tutorials/annotation/index.md @@ -1,6 +1,7 @@ (annotation)= + # Annotation This section contains tutorials showing how to annotate images and videos using the napari points, labels, or shapes layers. -* [Annotating videos with napari](annotate_points) +- [Annotating videos with napari](annotate_points) diff --git a/docs/tutorials/fundamentals/getting_started.md b/docs/tutorials/fundamentals/getting_started.md index e54c5c125..d626e4e5d 100644 --- a/docs/tutorials/fundamentals/getting_started.md +++ b/docs/tutorials/fundamentals/getting_started.md @@ -1,4 +1,5 @@ (getting_started)= + # Getting started Welcome to the getting started with **napari** tutorial! @@ -156,7 +157,7 @@ and where data changed in the GUI will be accessible in the notebook. To learn more about: -* how to use the napari viewer graphical user interface (GUI), +- how to use the napari viewer graphical user interface (GUI), checkout the [viewer tutorial](./viewer) -* how to use the napari viewer with different types of napari layers, see +- how to use the napari viewer with different types of napari layers, see [layers at a glance](../../guides/layers) diff --git a/docs/tutorials/fundamentals/installation.md b/docs/tutorials/fundamentals/installation.md index 46ea8a807..19ab5211e 100644 --- a/docs/tutorials/fundamentals/installation.md +++ b/docs/tutorials/fundamentals/installation.md @@ -1,17 +1,6 @@ ---- -jupyter: - jupytext: - formats: ipynb,md - text_representation: - extension: .md - format_name: markdown - format_version: '1.3' - jupytext_version: 1.13.0 - kernelspec: - display_name: Python 3 (ipykernel) - language: python - name: python3 ---- +______________________________________________________________________ + +## jupyter: jupytext: formats: ipynb,md text_representation: extension: .md format_name: markdown format_version: '1.3' jupytext_version: 1.13.0 kernelspec: display_name: Python 3 (ipykernel) language: python name: python3 (installation)= @@ -30,19 +19,23 @@ If you want to contribute code back into napari, you should follow the [developm Prerequisites differ depending on how you want to install napari. ### Prerequisites for installing napari as a Python package + This installation method allows you to use napari from Python to programmatically interact with the app. It is the best way to install napari and make full use of all its features. It requires: + - [Python {{ python_version_range }}](https://www.python.org/downloads/) - the ability to install python packages via [pip](https://pypi.org/project/pip/) OR [conda-forge](https://conda-forge.org/docs/user/introduction.html) You may also want: + - an environment manager like [conda](https://docs.conda.io/en/latest/miniconda.html) or -[venv](https://docs.python.org/3/library/venv.html) **(Highly recommended)** + [venv](https://docs.python.org/3/library/venv.html) **(Highly recommended)** ### Prerequisites for installing napari as a bundled app + This is the easiest way to install napari if you only wish to use it as a standalone GUI app. This installation method does not have any prerequisites. @@ -53,7 +46,7 @@ for installing the bundled app. Python package distributions of napari can be installed via `pip`, `conda-forge`, or from source. -````{important} +```{important} While not strictly required, it is highly recommended to install napari into a clean virtual environment using an environment manager like [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) or @@ -63,7 +56,7 @@ This should be set up *before* you install napari. For example, setting with up a Python {{ python_version }} environment with `conda`: {{ conda_create_env }} -```` +``` Choose one of the options below to install napari as a Python package. @@ -89,7 +82,6 @@ notation.)* ```` - ````{admonition} **2. From conda-forge** :class: dropdown @@ -141,7 +133,6 @@ mamba install napari ```` - ````{admonition} **3. From the main branch on Github** :class: dropdown @@ -153,6 +144,7 @@ python -m pip install "git+https://github.com/napari/napari.git#egg=napari[all]" ```` + ## Checking it worked After installation you should be able to launch napari from the command line by @@ -164,10 +156,11 @@ napari An empty napari viewer should appear as follows. -````{note} +```{note} You can check the napari version, to ensure it's what you expect, for example the current release {{ napari_version }}, using command: `napari --version` . -```` +``` + ![macOS desktop with a napari viewer window without any image opened in the foreground, and a terminal in the background with the appropriate conda environment activated (if applicable) and the command to open napari entered.](../assets/tutorials/launch_cli_empty.png) ## Choosing a different Qt backend @@ -253,21 +246,20 @@ accessed {{ '[here](https://github.com/napari/napari/releases/tag/vNAPARI_VER)'. To get to the download link, just scroll all the way to bottom of the page and expand the `Assets` section. You can then download the appropriate file for your platform. - ## Next steps - to start learning how to use napari, checkout our [getting -started](./getting_started) tutorial + started](./getting_started) tutorial - if you are interested in -contributing to napari please check our [contributing -guidelines](../../developers/contributing.md) + contributing to napari please check our [contributing + guidelines](../../developers/contributing.md) - if you are running into issues or bugs, please open a new issue on our [issue -tracker](https://github.com/napari/napari/issues) - - include the output of `napari --info` + tracker](https://github.com/napari/napari/issues) + - include the output of `napari --info` (or go to `Help>Info` in the viewer and copy paste the information) - if you want help using napari, we are a community partner on the [imagesc -forum](https://forum.image.sc/tags/napari) and all usage support requests should -be posted on the forum with the tag `napari`. We look forward to interacting -with you there! + forum](https://forum.image.sc/tags/napari) and all usage support requests should + be posted on the forum with the tag `napari`. We look forward to interacting + with you there! diff --git a/docs/tutorials/fundamentals/quick_start.md b/docs/tutorials/fundamentals/quick_start.md index efb30d7c1..5c3e293a6 100644 --- a/docs/tutorials/fundamentals/quick_start.md +++ b/docs/tutorials/fundamentals/quick_start.md @@ -1,17 +1,9 @@ ---- -jupytext: - formats: ipynb,md:myst - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.11.5 -kernelspec: - display_name: Python 3 (ipykernel) - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: formats: ipynb,md:myst text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.11.5 kernelspec: display_name: Python 3 (ipykernel) language: python name: python3 + (napari-quick-start)= + # Quick start +++ @@ -49,6 +41,7 @@ You will also see some examples of plugins. The core napari viewer focuses on do ### Installation - Download the napari {{ napari_version }} bundled app for a simple installation: + - Linux: {{ '[`napari-NAPARI_VER-Linux-x86_64.sh`](https://github.com/napari/napari/releases/download/vNAPARI_VER/napari-NAPARI_VER-Linux-x86_64.sh)'.replace('NAPARI_VER', napari_version) }}. - macOS (Intel): {{ '[`napari-NAPARI_VER-macOS-x86_64.pkg`](https://github.com/napari/napari/releases/download/vNAPARI_VER/napari-NAPARI_VER-macOS-x86_64.pkg)'.replace('NAPARI_VER', napari_version) }}. - macOS (Apple Silicon): {{ '[`napari-NAPARI_VER-macOS-arm64.pkg`](https://github.com/napari/napari/releases/download/vNAPARI_VER/napari-NAPARI_VER-macOS-arm64.pkg)'.replace('NAPARI_VER', napari_version) }}. @@ -57,18 +50,17 @@ You will also see some examples of plugins. The core napari viewer focuses on do - For those familiar with Python: - napari can be installed on most macOS (Intel x86), Linux, and Windows systems with Python {{ python_version_range }} using pip. - - First, create a clean virtual environment: + napari can be installed on most macOS (Intel x86), Linux, and Windows systems with Python {{ python_version_range }} using pip. - {{ conda_create_env }} + First, create a clean virtual environment: - Once in napari-env, + {{ conda_create_env }} - ```python - python -m pip install 'napari[all]' - ``` + Once in napari-env, + ```python + python -m pip install 'napari[all]' + ``` If you run into any issues, please visit the more detailed [installation guide](./installation), or [report an issue on GitHub](https://github.com/napari/napari/issues/new/choose)! @@ -82,15 +74,16 @@ Here we will be mainly focused on the GUI application. - From command line: - Once installed, simply run + Once installed, simply run + ```python napari ``` - If you installed the bundled app: - Click on the app icon to open it.
- *Note: macOS users might need to right click on the app icon and select "Open" to bypass the security check. You can also go to System Settings > Privacy & Security and click on "Open Anyway".* + Click on the app icon to open it.
+ *Note: macOS users might need to right click on the app icon and select "Open" to bypass the security check. You can also go to System Settings > Privacy & Security and click on "Open Anyway".* +++ @@ -115,7 +108,8 @@ For demo purpose, we will use a sample image that comes with napari. ```python from skimage import data -viewer.add_image(data.cell(), name='cell') + +viewer.add_image(data.cell(), name="cell") ``` ```{code-cell} ipython3 @@ -138,7 +132,7 @@ The layer controls panel at the upper left of the viewer allows you to adjust co To change the image display through the [API](../../api/index), in IPython console, type ```python -viewer.layers['cell'].colormap = "yellow" +viewer.layers["cell"].colormap = "yellow" ``` ```{code-cell} ipython3 @@ -154,9 +148,9 @@ To measure the area of the cell, we can use a labels layer and manually "paint" The labels layer allows you to record the segmentation result by assigning background = 0, and assigning each object with an integer. 1. Add a new labels layer -1. Click on "paint" -1. Circle the cell -1. Use "fill" bucket to fill it. +2. Click on "paint" +3. Circle the cell +4. Use "fill" bucket to fill it. ![manual_label](../../images/manual_label.webm) @@ -174,7 +168,8 @@ In IPython console, type ```python from skimage.measure import regionprops -props = regionprops(viewer.layers['Labels'].data) + +props = regionprops(viewer.layers["Labels"].data) print("the cell area is: ", props[0].area) ``` @@ -189,6 +184,7 @@ viewer.close_all() ``` ### Next steps + - napari provides the flexibility to handle multi-dimensional data. Try opening 3D or higher dimensional images, and switch to 3D view. ![ndisplay](../../images/ndisplay.png) diff --git a/docs/tutorials/fundamentals/viewer.md b/docs/tutorials/fundamentals/viewer.md index f63b066e8..8fb00e9d2 100644 --- a/docs/tutorials/fundamentals/viewer.md +++ b/docs/tutorials/fundamentals/viewer.md @@ -1,17 +1,9 @@ ---- -jupytext: - text_representation: - extension: .md - format_name: myst - format_version: 0.13 - jupytext_version: 1.10.3 -kernelspec: - display_name: Python 3 - language: python - name: python3 ---- +______________________________________________________________________ + +## jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.10.3 kernelspec: display_name: Python 3 language: python name: python3 (viewer-tutorial)= + # Viewer tutorial +++ @@ -60,6 +52,7 @@ import napari viewer = napari.Viewer() new_layer = viewer.add_image(data.astronaut(), rgb=True) ``` + {meth}`add_image` accepts the same arguments as {func}`imshow` but only returns an {class}`Image` layer instead of both the {class}`Viewer` and {class}`Image` layer (as you must already have a viewer to use it). After running either of those two commands, you should be able to see the photograph of the astronaut in the **napari** viewer as shown below: @@ -76,7 +69,7 @@ nbscreenshot(viewer, alt_text="photograph of an astronaut in napari viewer") viewer.close() ``` - {func}`imshow` and the {meth}`add_image` methods accept any numpy-array like object as input, including n-dimensional arrays. For more information on adding images to the viewer see the [image layer guide](../../howtos/layers/image). +{func}`imshow` and the {meth}`add_image` methods accept any numpy-array like object as input, including n-dimensional arrays. For more information on adding images to the viewer see the [image layer guide](../../howtos/layers/image). Now we will continue exploring the rest of the viewer. @@ -85,16 +78,17 @@ Now we will continue exploring the rest of the viewer. ## Layout of the viewer The viewer is organized into a few key areas which are explained in the next sections: -* Main Menu (top bar menu) -* Layer Controls -* Layer Buttons -* Layer List -* Viewer Buttons -* Status Bar -* Canvas -* Dimension Sliders -* Scroll Buttons -* Frame Playback + +- Main Menu (top bar menu) +- Layer Controls +- Layer Buttons +- Layer List +- Viewer Buttons +- Status Bar +- Canvas +- Dimension Sliders +- Scroll Buttons +- Frame Playback The image below has the areas of the viewer labeled: @@ -106,20 +100,19 @@ We'll go through each of these in the next sections. The main menu consists of the **File**, **View**, **Window**, **Plugins**, and **Help** options. -* **File** has the options to open files, folders, and samples, save layers and screenshots,copy screenshots to clipboard and, in the Windows version, preferences. - - All the options on the **File** menu are relatively self-explanatory except **Preferences** on the Windows version of napari. **Preferences** allows you to personalize napari to some degree. To learn more about the **Preferences** menu, there is a tutorial designed for developers [here](https://napari.org/stable/guides/preferences.html). +- **File** has the options to open files, folders, and samples, save layers and screenshots,copy screenshots to clipboard and, in the Windows version, preferences. - **Note:** In macOS, **Preferences** is under the napari menu. + All the options on the **File** menu are relatively self-explanatory except **Preferences** on the Windows version of napari. **Preferences** allows you to personalize napari to some degree. To learn more about the **Preferences** menu, there is a tutorial designed for developers [here](https://napari.org/stable/guides/preferences.html). + **Note:** In macOS, **Preferences** is under the napari menu. -* **View** allows you to toggle full screen, the menu bar, play, display axes, the scale bar, tooltips, and the activity dock. +- **View** allows you to toggle full screen, the menu bar, play, display axes, the scale bar, tooltips, and the activity dock. -* **Window** allows you to open the integrated console, display the layer controls and layer list. +- **Window** allows you to open the integrated console, display the layer controls and layer list. -* **Plugins** allows you to install and manage plugins and displays a list of plugins that are currently installed. +- **Plugins** allows you to install and manage plugins and displays a list of plugins that are currently installed. -* **Help** contains the citation and about information. +- **Help** contains the citation and about information. +++ @@ -134,7 +127,9 @@ The **canvas** is in the center of the viewer and contains the visual display of +++ + (layer_list)= + ### Layer list Layers are one of the basic napari objects. There are different layer types for `Image`, `Points`, `Shapes`, and other data types. They can be added to the viewer either programmatically or through the GUI. Once added, they populate the layer list located on the bottom left side of the canvas. @@ -314,12 +309,13 @@ On the left end of the dimension slider is the **frame playback** button. Right ### Viewer buttons Below the **layer list** is a row containing these buttons: -* Console -* 2D/3D -* Roll Dimensions -* Transpose Dimensions -* Grid display -* Home + +- Console +- 2D/3D +- Roll Dimensions +- Transpose Dimensions +- Grid display +- Home ![image: Viewer buttons](../assets/tutorials/viewer-buttons.png) @@ -345,7 +341,6 @@ The console (when available) appears at the bottom of the viewer as shown below: The second button from the left is the 2D/3D button which toggles between `2D` and `3D` renderings of the data. For example, run the following code: - ```{code-cell} python :tags: [remove-output] from skimage import data @@ -366,6 +361,7 @@ the following, to indicate 3D mode: ![image: 3D_button](../assets/tutorials/3D_button.png) This mode can be entered programmatically using: + ```python viewer.dims.ndisplay = 3 ``` @@ -389,6 +385,7 @@ Shift key while dragging with the mouse. Finally, while in 3D mode you can chang 3D view by holding Shift, pressing the right mouse button (on macOS holding Control) and dragging the mouse or by right-clicking (on macOS holding Control and clicking) on the 2D/3D mode button, which will bring up the perspective slider. The camera perspective can also be altered programmatically: + ```python viewer.camera.perspective = 45 ``` @@ -420,29 +417,31 @@ On the left side of the status bar there is a message about the position of the The right side of the status bar contains some helpful tips depending on which layer and tools are currently selected. ## Right-click menu - A context-sensitive menu is available when you right-click on any of the layers. The type of layer determines which options are available. Note that if you have multiple layers selected, the menu actions will affect all of the selected layers. The options that are not available for a layer are greyed out. The following options are available depending on which layer type you have selected: -* **Duplicate Layer** - creates a second copy of the selected layer. Can be used on **Points**, **Shapes**, **Labels**, and **Image** layers. This is useful for testing your analysis on a copy instead of on the original image. -* **Convert to Labels** - converts an **Image** layer to a **Labels** layer. This is useful for converting a binary image segmentation map to a labels layer with each segmented object denoted by its own integer. Can also be used on a **Shapes** layer. -* **Convert to Image** - converts a **Labels** layer into an **Image** layer. -* **Toggle visibility** - hides or shows the selected layer. -* **Convert datatype** - converts an **Image** or **Labels** layer into int8, int16, int32, int64, uint8, uint16, uint32, or uint64 data types. The initial data type is the data type of the data itself. -* **Make Projection** - can be used only on a layer with more than 2 dimensions, also known as a *stack*. It creates a new layer that is a projection of the layer stack with the characteristic the user selects, reducing the number of dimensions by 1. More information about the types of projections is available [here](https://medium.com/@damiandn/an-intoduction-to-biological-image-processing-in-imagej-part-3-stacks-and-stack-projections-942aa789420f). The following projections are available: - * **Max** - maximum intensity projection. At each pixel position, we go through the stacks, find the pixel with the maximum intensity, and that becomes the intensity of that pixel value in the projected image. - * **Min** - minimum intensity projection. Similar to the maximum intensity projection, except that the minimum pixel value is used for the projected image instead of the maximum pixel value. - * **Std** - the standard deviation projection. At each pixel position, the standard deviation of the pixel intensities through the stack is the assigned value of that pixel position. Positions with large differences in the pixel intensities through the stack appear brighter in this projection. - * **Sum** - the sum projection simply adds together all the pixel values in the stack for a given position. In this projection, the image is typically re-scaled to a 16-bit image, as the sum of all the pixel intensity values usually exceeds 255, which would result in a completely white 8-bit image. - * **Mean** - the mean projection is the average intensity projection. It simply averages all the pixel values in the stacks to make the final projected image. - * **Median** - the median projection takes the median pixel intensity for the final projected image. -* **Split RGB** - if the image layer is an RGB image, it will be split into 3 new layers with red, green, and blue values in separate layers. -* **Split Stack** - if an image layer is a stack (has 3 or more dimensions), it is split into a list of layers along the axis. This option takes a little time to execute. Properties will be changed as follows: - * **Colormap:** (magenta, green) for a stack with 2 channels, (CYMRGB) for stacks with more than 2 channels - * **Blending:** additive - * **Contrast_limits:** min and max values of the layer - * All other properties, such as **Scale** and **Translate** will be propagated from the original stack. -* **Merge to Stack** - combines a set of layers to a single-layer stack. The resulting layer stack will contain the layers with their original ordering in the layer list. Layers must be of the same type (e.g. An **Image** layer can be merged only with other **Image** layers.) and must have the same dimensionality. (e.g. a 1024 x 1024 layer can only be merged with another 1024 x 1024 layer.) -* **Link Layers** - links the selected layers. Once layers are linked, any action performed on one layer will be performed on all linked layers at the same time. The layer control panel will show _only_ when a single layer is selected. Changing properties with that layer's control panel will change properties in all of the linked layers. -* **Unlink Layers** - appears when layers are linked. It unlinks the layers so that changes to one of the layer's properties no longer result in the same changes to the previously linked layers. -* **Select Linked Layers** - appears only when layers are linked. Selects all layers linked to a given layer. + +A context-sensitive menu is available when you right-click on any of the layers. The type of layer determines which options are available. Note that if you have multiple layers selected, the menu actions will affect all of the selected layers. The options that are not available for a layer are greyed out. The following options are available depending on which layer type you have selected: + +- **Duplicate Layer** - creates a second copy of the selected layer. Can be used on **Points**, **Shapes**, **Labels**, and **Image** layers. This is useful for testing your analysis on a copy instead of on the original image. +- **Convert to Labels** - converts an **Image** layer to a **Labels** layer. This is useful for converting a binary image segmentation map to a labels layer with each segmented object denoted by its own integer. Can also be used on a **Shapes** layer. +- **Convert to Image** - converts a **Labels** layer into an **Image** layer. +- **Toggle visibility** - hides or shows the selected layer. +- **Convert datatype** - converts an **Image** or **Labels** layer into int8, int16, int32, int64, uint8, uint16, uint32, or uint64 data types. The initial data type is the data type of the data itself. +- **Make Projection** - can be used only on a layer with more than 2 dimensions, also known as a *stack*. It creates a new layer that is a projection of the layer stack with the characteristic the user selects, reducing the number of dimensions by 1. More information about the types of projections is available [here](https://medium.com/@damiandn/an-intoduction-to-biological-image-processing-in-imagej-part-3-stacks-and-stack-projections-942aa789420f). The following projections are available: + - **Max** - maximum intensity projection. At each pixel position, we go through the stacks, find the pixel with the maximum intensity, and that becomes the intensity of that pixel value in the projected image. + - **Min** - minimum intensity projection. Similar to the maximum intensity projection, except that the minimum pixel value is used for the projected image instead of the maximum pixel value. + - **Std** - the standard deviation projection. At each pixel position, the standard deviation of the pixel intensities through the stack is the assigned value of that pixel position. Positions with large differences in the pixel intensities through the stack appear brighter in this projection. + - **Sum** - the sum projection simply adds together all the pixel values in the stack for a given position. In this projection, the image is typically re-scaled to a 16-bit image, as the sum of all the pixel intensity values usually exceeds 255, which would result in a completely white 8-bit image. + - **Mean** - the mean projection is the average intensity projection. It simply averages all the pixel values in the stacks to make the final projected image. + - **Median** - the median projection takes the median pixel intensity for the final projected image. +- **Split RGB** - if the image layer is an RGB image, it will be split into 3 new layers with red, green, and blue values in separate layers. +- **Split Stack** - if an image layer is a stack (has 3 or more dimensions), it is split into a list of layers along the axis. This option takes a little time to execute. Properties will be changed as follows: + - **Colormap:** (magenta, green) for a stack with 2 channels, (CYMRGB) for stacks with more than 2 channels + - **Blending:** additive + - **Contrast_limits:** min and max values of the layer + - All other properties, such as **Scale** and **Translate** will be propagated from the original stack. +- **Merge to Stack** - combines a set of layers to a single-layer stack. The resulting layer stack will contain the layers with their original ordering in the layer list. Layers must be of the same type (e.g. An **Image** layer can be merged only with other **Image** layers.) and must have the same dimensionality. (e.g. a 1024 x 1024 layer can only be merged with another 1024 x 1024 layer.) +- **Link Layers** - links the selected layers. Once layers are linked, any action performed on one layer will be performed on all linked layers at the same time. The layer control panel will show _only_ when a single layer is selected. Changing properties with that layer's control panel will change properties in all of the linked layers. +- **Unlink Layers** - appears when layers are linked. It unlinks the layers so that changes to one of the layer's properties no longer result in the same changes to the previously linked layers. +- **Select Linked Layers** - appears only when layers are linked. Selects all layers linked to a given layer. +++ diff --git a/docs/tutorials/index.md b/docs/tutorials/index.md index 0ef3b8246..a11cafc31 100644 --- a/docs/tutorials/index.md +++ b/docs/tutorials/index.md @@ -1,4 +1,5 @@ (napari-tutorials)= + # napari tutorials These tutorials will help you explore the main usage modes and methods of @@ -44,4 +45,4 @@ As you make your way through the tutorials, if you spot any errors or areas that can be improved, please raise an issue on the repository or make a pull request! We love it when our community helps make them better for everyone! To see how to modify documentation pages, please see our -[documentation contributing guide](../developers/documentation/index). \ No newline at end of file +[documentation contributing guide](../developers/documentation/index). diff --git a/docs/tutorials/processing/dask.md b/docs/tutorials/processing/dask.md index b7616daa5..f1bdbdada 100644 --- a/docs/tutorials/processing/dask.md +++ b/docs/tutorials/processing/dask.md @@ -50,7 +50,7 @@ from skimage.io import imread from dask import delayed lazy_imread = delayed(imread) -reader = lazy_imread('/path/to/file.tif') # doesn't actually read the file +reader = lazy_imread("/path/to/file.tif") # doesn't actually read the file array = reader.compute() # *now* it reads. ``` @@ -89,7 +89,7 @@ stack.shape # (nfiles, nz, ny, nx) stack ``` -![HTML representation of a Dask array as seen in Jupyter notebook. The image is split into two main regions: a table showing the bytes, shape, count and data type attributes of the array and of each chunk, and a visual representation of the shape of the chunks that make up the array (a rectangle of 1200x1) and each individual chunk (a 65*256*256 cube).](../assets/tutorials/dask_repr.png) +![HTML representation of a Dask array as seen in Jupyter notebook. The image is split into two main regions: a table showing the bytes, shape, count and data type attributes of the array and of each chunk, and a visual representation of the shape of the chunks that make up the array (a rectangle of 1200x1) and each individual chunk (a 65256256 cube).](../assets/tutorials/dask_repr.png) *No data has been read from disk yet!* @@ -103,7 +103,7 @@ import napari # specify contrast_limits and multiscale=False with big data # to avoid unnecessary computations -napari.view_image(stack, contrast_limits=[0,2000], multiscale=False) +napari.view_image(stack, contrast_limits=[0, 2000], multiscale=False) ``` *Note: providing the* `contrast_limits` *and* `multiscale` *arguments prevents* `napari` *from trying to calculate the data min/max, which can take an extremely long time with big data. @@ -125,7 +125,7 @@ import napari from dask_image.imread import imread stack = imread("/path/to/experiment/*.tif") -napari.view_image(stack, contrast_limits=[0,2000], multiscale=False) +napari.view_image(stack, contrast_limits=[0, 2000], multiscale=False) ``` ![napari viewer with image loaded as a dask array showing mCherry-H2B showing chromosome separation during mitosis. Collected on a lattice light sheet microscope.](../assets/tutorials/dask1.webm) @@ -141,7 +141,8 @@ For example: ```python from dask_image.imread import imread -stack = imread('/path/to/experiment/*.tif') + +stack = imread("/path/to/experiment/*.tif") stack.shape # -> something like (1200, 64, 256, 280) stack[0].compute() # incurs a single file read @@ -171,7 +172,6 @@ channels = [imread(file_pattern.format(i)) for i in range(nchannels)] stack = da.stack(channels) stack.shape # (2, 600, 64, 256, 280) stack[0, 0].compute() # incurs a single file read - ``` ## Processing data with `dask.array.map_blocks` @@ -206,11 +206,13 @@ psf = io.imread("/path/to/psf.tif") # prepare some functions that accept a numpy array # and return a processed array + def last3dims(f): # this is just a wrapper because the pycudadecon function # expects ndims==3 but our blocks will have ndim==4 def func(array): return f(array[0])[None, ...] + return func @@ -218,6 +220,7 @@ def crop(array): # simple cropping function return array[:, 2:, 10:-20, :500] + # https://docs.python.org/3.8/library/functools.html#functools.partial deskew = last3dims(partial(pycudadecon.deskew_gpu, angle=31.5)) deconv = last3dims(partial(pycudadecon.decon, psf=psf, background=10)) diff --git a/docs/tutorials/processing/index.md b/docs/tutorials/processing/index.md index 50c6b1514..476d86e6f 100644 --- a/docs/tutorials/processing/index.md +++ b/docs/tutorials/processing/index.md @@ -1,6 +1,7 @@ (processing)= + # Processing This section contains tutorials for data processing. -* [Using Dask and napari to process & view large datasets](dask) +- [Using Dask and napari to process & view large datasets](dask) diff --git a/docs/tutorials/segmentation/annotate_segmentation.md b/docs/tutorials/segmentation/annotate_segmentation.md index b19ede869..eac470dbe 100644 --- a/docs/tutorials/segmentation/annotate_segmentation.md +++ b/docs/tutorials/segmentation/annotate_segmentation.md @@ -68,9 +68,7 @@ def make_bbox(bbox_extents): maxr = bbox_extents[2] maxc = bbox_extents[3] - bbox_rect = np.array( - [[minr, minc], [maxr, minc], [maxr, maxc], [minr, maxc]] - ) + bbox_rect = np.array([[minr, minc], [maxr, minc], [maxr, maxc], [minr, maxc]]) bbox_rect = np.moveaxis(bbox_rect, 2, 0) return bbox_rect @@ -91,7 +89,7 @@ def circularity(perimeter, area): circularity : float The circularity of the region as defined by 4*pi*area / perimeter^2 """ - circularity = 4 * np.pi * area / (perimeter ** 2) + circularity = 4 * np.pi * area / (perimeter**2) return circularity @@ -102,43 +100,41 @@ label_image = segment(image) # create the properties dictionary properties = regionprops_table( - label_image, properties=('label', 'bbox', 'perimeter', 'area') -) -properties['circularity'] = circularity( - properties['perimeter'], properties['area'] + label_image, properties=("label", "bbox", "perimeter", "area") ) +properties["circularity"] = circularity(properties["perimeter"], properties["area"]) # create the bounding box rectangles -bbox_rects = make_bbox([properties[f'bbox-{i}'] for i in range(4)]) +bbox_rects = make_bbox([properties[f"bbox-{i}"] for i in range(4)]) # specify the display parameters for the text text_parameters = { - 'string': 'label: {label}\ncirc: {circularity:.2f}', - 'size': 12, - 'color': 'green', - 'anchor': 'upper_left', - 'translation': [-3, 0], + "string": "label: {label}\ncirc: {circularity:.2f}", + "size": 12, + "color": "green", + "anchor": "upper_left", + "translation": [-3, 0], } # initialise viewer with coins image -viewer = napari.view_image(image, name='coins', rgb=False) +viewer = napari.view_image(image, name="coins", rgb=False) # add the labels -label_layer = viewer.add_labels(label_image, name='segmentation') +label_layer = viewer.add_labels(label_image, name="segmentation") shapes_layer = viewer.add_shapes( bbox_rects, - face_color='transparent', - edge_color='green', + face_color="transparent", + edge_color="green", properties=properties, text=text_parameters, - name='bounding box', + name="bounding box", ) napari.run() - ``` ## Segmentation + We start by defining a function to perform segmentation of an image based on intensity. Based on the [skimage segmentation example](https://scikit-image.org/docs/stable/auto_examples/applications/plot_thresholding.html), we determine the threshold intensity that separates the foreground and background pixels using [Otsu's method](https://en.wikipedia.org/wiki/Otsu%27s_method). We then perform some cleanup and generate a label image where each discrete region is given a unique integer index. ```python @@ -178,10 +174,10 @@ image = data.coins()[50:-50, 50:-50] label_image = segment(image) # initialize viewer with coins image -viewer = napari.view_image(image, name='coins', rgb=False) +viewer = napari.view_image(image, name="coins", rgb=False) # add the labels -label_layer = viewer.add_labels(label_image, name='segmentation') +label_layer = viewer.add_labels(label_image, name="segmentation") napari.run() ``` @@ -195,7 +191,7 @@ Next, we use [`regionprops_table`](https://scikit-image.org/docs/dev/api/skimage ```python # create the properties dictionary properties = regionprops_table( - label_image, properties=('label', 'bbox', 'perimeter', 'area') + label_image, properties=("label", "bbox", "perimeter", "area") ) ``` @@ -203,19 +199,24 @@ Conveniently, `regionprops_table()` returns a dictionary in the same format as t ```python { - 'label': array([1, 2, 3, 4, 5, 6, 7, 8]), - 'bbox-0': array([ 46, 55, 57, 60, 120, 122, 125, 129]), - 'bbox-1': array([195, 136, 34, 84, 139, 201, 30, 85]), - 'bbox-2': array([ 94, 94, 95, 95, 166, 166, 167, 167]), - 'bbox-3': array([246, 177, 72, 124, 187, 247, 74, 124]), - 'perimeter': - array( - [ - 165.88225099, 129.05382387, 123.98275606, 121.98275606, - 155.88225099, 149.05382387, 140.46803743, 125.39696962 - ] - ), - 'area': array([1895, 1212, 1124, 1102, 1720, 1519, 1475, 1155]) + "label": array([1, 2, 3, 4, 5, 6, 7, 8]), + "bbox-0": array([46, 55, 57, 60, 120, 122, 125, 129]), + "bbox-1": array([195, 136, 34, 84, 139, 201, 30, 85]), + "bbox-2": array([94, 94, 95, 95, 166, 166, 167, 167]), + "bbox-3": array([246, 177, 72, 124, 187, 247, 74, 124]), + "perimeter": array( + [ + 165.88225099, + 129.05382387, + 123.98275606, + 121.98275606, + 155.88225099, + 149.05382387, + 140.46803743, + 125.39696962, + ] + ), + "area": array([1895, 1212, 1124, 1102, 1720, 1519, 1475, 1155]), } ``` @@ -237,7 +238,7 @@ def circularity(perimeter, area): circularity : float The circularity of the region as defined by 4*pi*area / perimeter^2 """ - circularity = 4 * np.pi * area / (perimeter ** 2) + circularity = 4 * np.pi * area / (perimeter**2) return circularity ``` @@ -245,9 +246,7 @@ def circularity(perimeter, area): We can then calculate the circularity of each region and save it as a property. ```python -properties['circularity'] = circularity( - properties['perimeter'], properties['area'] -) +properties["circularity"] = circularity(properties["perimeter"], properties["area"]) ``` We will use a napari shapes layer to visualize the bounding box of the segmentation. The napari shapes layer requires each shape to be defined by the coordinates of corner. Since regionprops returns the bounding box as a tuple of `(min_row, min_column, max_row, max_column)` we define a function `make_bbox()` to convert the regionprops bounding box to the napari shapes format. @@ -274,9 +273,7 @@ def make_bbox(bbox_extents): maxr = bbox_extents[2] maxc = bbox_extents[3] - bbox_rect = np.array( - [[minr, minc], [maxr, minc], [maxr, maxc], [minr, maxc]] - ) + bbox_rect = np.array([[minr, minc], [maxr, minc], [maxr, maxc], [minr, maxc]]) bbox_rect = np.moveaxis(bbox_rect, 2, 0) return bbox_rect @@ -286,20 +283,21 @@ Finally, we can use an list comprension to pass the bounding box extents to `mak ```python # create the bounding box rectangles -bbox_rects = make_bbox([properties[f'bbox-{i}'] for i in range(4)]) +bbox_rects = make_bbox([properties[f"bbox-{i}"] for i in range(4)]) ``` ## Visualizing the segmentation results + Now that we have performed out analysis, we can visualize the results in napari. To do so, we will utilize 3 napari layer types: (1) Image, (2) Labels, and (3) Shapes. As we saw above in the segmentation section, we can visualize the original image and the resulting label images as follows: ```python # initialise viewer with coins image -viewer = napari.view_image(image, name='coins', rgb=False) +viewer = napari.view_image(image, name="coins", rgb=False) # add the labels -label_layer = viewer.add_labels(label_image, name='segmentation') +label_layer = viewer.add_labels(label_image, name="segmentation") napari.run() ``` @@ -307,12 +305,9 @@ napari.run() Next, we will use the Shapes layer to overlay the bounding boxes for each detected object as well as display the calculated circularity. The code for creating the Shapes layer is listed here and each keyword argument is explained below. ```python - shapes_layer = viewer.add_shapes( - bbox_rects, - face_color='transparent', - edge_color='green', - name='bounding box' - ) +shapes_layer = viewer.add_shapes( + bbox_rects, face_color="transparent", edge_color="green", name="bounding box" +) ``` ![napari viewer showing eight roughly circular shapes, each colored differently. Each shape has a bounding box automatically generated around it.](../assets/tutorials/segmentation_bbox.png) @@ -320,17 +315,18 @@ Next, we will use the Shapes layer to overlay the bounding boxes for each detect The first positional argument (`bbox_rects`) contains the bounding boxes we created above. We specified that the face of each bounding box has no color (`face_color='transparent'`) and the edges of the bounding box are green (`edge_color='green'`). Finally, the name of the layer displayed in the layer list in the napari GUI is `bounding box` (`name='bounding box'`). ## Annotating shapes with text + We can further annotate our analysis by using text to display properties of each segmentation. The code to create a shapes layer with text is pasted here and explained below. ```python - shapes_layer = viewer.add_shapes( - bbox_rects, - face_color='transparent', - edge_color='green', - properties=properties, - text=text_parameters, - name='bounding box' - ) +shapes_layer = viewer.add_shapes( + bbox_rects, + face_color="transparent", + edge_color="green", + properties=properties, + text=text_parameters, + name="bounding box", +) ``` We will use `Shapes.properties` to store the annotations for each bounding box. The properties are definined as a dictionary where each key is the name of the property (i.e., label, circularity) and the values are arrays where each element contains the value for the corresponding shape (i.e., index matched to the Shape data). As a reminder, we created `labels` and `circularity` above and each is a list containing where each element is property value for the corresponding (i.e., index matched) shape. @@ -338,8 +334,8 @@ We will use `Shapes.properties` to store the annotations for each bounding box. ```python # create the properties dictionary properties = { - 'label': labels, - 'circularity': circularity, + "label": labels, + "circularity": circularity, } ``` @@ -347,11 +343,11 @@ Each bounding box can be annotated with text drawn from the layer `properties`. ```python text_parameters = { - 'string': 'label: {label}\ncirc: {circularity:.2f}', - 'size': 12, - 'color': 'green', - 'anchor': 'upper_left', - 'translation': [-3, 0] + "string": "label: {label}\ncirc: {circularity:.2f}", + "size": 12, + "color": "green", + "anchor": "upper_left", + "translation": [-3, 0], } ``` @@ -371,38 +367,39 @@ All together, the visualization code is: ```python # create the properties dictionary properties = { - 'label': labels, - 'circularity': circularity, + "label": labels, + "circularity": circularity, } # specify the display parameters for the text text_kwargs = { - 'string': 'label: {label}\ncirc: {circularity:.2f}', - 'size': 12, - 'color': 'green', - 'anchor': 'upper_left', - 'translation': [-3, 0] + "string": "label: {label}\ncirc: {circularity:.2f}", + "size": 12, + "color": "green", + "anchor": "upper_left", + "translation": [-3, 0], } # initialise viewer with coins image -viewer = napari.view_image(image, name='coins', rgb=False) +viewer = napari.view_image(image, name="coins", rgb=False) # add the labels -label_layer = viewer.add_labels(label_image, name='segmentation') +label_layer = viewer.add_labels(label_image, name="segmentation") shapes_layer = viewer.add_shapes( bbox_rects, - face_color='transparent', - edge_color='green', + face_color="transparent", + edge_color="green", properties=properties, text=text_parameters, - name='bounding box' + name="bounding box", ) napari.run() ``` ## Summary + In this tutorial, we have used napari to view and annotate segmentation results. ![napari viewer showing eight roughly circular shapes. The shapes are classified according to circularity and have bounding boxes automatically generated around them showing a circularity parameter and an integer for a label.](../assets/tutorials/annotated_bbox.png) diff --git a/docs/tutorials/segmentation/index.md b/docs/tutorials/segmentation/index.md index 23d5a5af6..a984762bb 100644 --- a/docs/tutorials/segmentation/index.md +++ b/docs/tutorials/segmentation/index.md @@ -1,6 +1,7 @@ (segmentation)= + # Segmentation This section contains tutorials for segmentation [labeling](../../howtos/layers/labels). -* [Annotating segmentation with text and bounding boxes](annotate_segmentation) +- [Annotating segmentation with text and bounding boxes](annotate_segmentation) diff --git a/docs/tutorials/start_index.md b/docs/tutorials/start_index.md index afddcbdaf..7c5b3524e 100644 --- a/docs/tutorials/start_index.md +++ b/docs/tutorials/start_index.md @@ -1,4 +1,4 @@ - # Getting started +# Getting started The following documents will give you an overview of how to install and use napari. For more detailed use-cases, check out the [napari tutorials](./index) @@ -44,4 +44,4 @@ and how the data within it is organized. An overview of the napari *Layers*, the basic viewable objects that can be displayed on the canvas. ::: -:::: \ No newline at end of file +:::: diff --git a/docs/tutorials/tracking/cell_tracking.md b/docs/tutorials/tracking/cell_tracking.md index 497c83e4d..8712acc8f 100644 --- a/docs/tutorials/tracking/cell_tracking.md +++ b/docs/tutorials/tracking/cell_tracking.md @@ -3,6 +3,7 @@ In this application note, we will use napari (requires version 0.4.0 or greater) to visualize single cell tracking data using the `Tracks` layer. For an overview of the `Tracks` layer, please see the [tracks layer guide](../../howtos/layers/tracks). This application note covers two examples: + 1. Visualization of a cell tracking challenge dataset 2. Single cell tracking using btrack and napari @@ -26,9 +27,10 @@ import pandas as pd from skimage.io import imread from skimage.measure import regionprops_table -PATH = '/path/to/Fluo-N3DH-CE/' +PATH = "/path/to/Fluo-N3DH-CE/" NUM_IMAGES = 195 + def load_image(idx: int): """Load an image from the sequence. @@ -42,9 +44,10 @@ def load_image(idx: int): image : np.ndarray The image specified by the index, idx """ - filename = os.path.join(PATH, '01_GT/TRA', f'man_track{idx:0>3}.tif') + filename = os.path.join(PATH, "01_GT/TRA", f"man_track{idx:0>3}.tif") return imread(filename) + stack = np.asarray([load_image(i) for i in range(NUM_IMAGES)]) ``` @@ -64,19 +67,20 @@ def regionprops_plus_time(idx): data_df : pd.DataFrame The dataframe of track data for one time step (specified by idx). """ - props = regionprops_table(stack[idx, ...], properties=('label', 'centroid')) - props['frame'] = np.full(props['label'].shape, idx) + props = regionprops_table(stack[idx, ...], properties=("label", "centroid")) + props["frame"] = np.full(props["label"].shape, idx) return pd.DataFrame(props) + data_df_raw = pd.concat( [regionprops_plus_time(idx) for idx in range(NUM_IMAGES)] ).reset_index(drop=True) # sort the data lexicographically by track_id and time -data_df = data_df_raw.sort_values(['label', 'frame'], ignore_index=True) +data_df = data_df_raw.sort_values(["label", "frame"], ignore_index=True) # create the final data array: track_id, T, Z, Y, X data = data_df.loc[ - :, ['label', 'frame', 'centroid-0', 'centroid-1', 'centroid-2'] + :, ["label", "frame", "centroid-0", "centroid-1", "centroid-2"] ].to_numpy() ``` @@ -85,7 +89,7 @@ At this point, there is no concept of track *links*, lineages, or tracks splitti These single tracks are sometimes known as tracklets: ```python -napari.view_tracks(data, name='tracklets') +napari.view_tracks(data, name="tracklets") napari.run() ``` @@ -97,15 +101,16 @@ mapping between a `track_id` and the parents of the track. This graph can be use In the cell tracking challenge dataset, cell lineage information is stored in a text file `man_track.txt` in the following format: > A text file representing an acyclic graph for the whole video. Every line corresponds -to a single track that is encoded by four numbers separated by a space: +> to a single track that is encoded by four numbers separated by a space: > L - a unique label of the track (label of markers, 16-bit positive value) > B - a zero-based temporal index of the frame in which the track begins > E - a zero-based temporal index of the frame in which the track ends > P - label of the parent track (0 is used when no parent is defined) To extract the graph, we load the text file and convert it to a Nx4 integer numpy array, where the rows represent individual tracks and the columns represent L, B, E and P: + ```python -lbep = np.loadtxt(os.path.join(PATH, '01_GT/TRA', 'man_track.txt'), dtype=np.uint) +lbep = np.loadtxt(os.path.join(PATH, "01_GT/TRA", "man_track.txt"), dtype=np.uint) ``` We can then create a dictionary representing the graph, where the key is the unique track label (L) and the value is the label of the parent track (P). @@ -115,6 +120,7 @@ full_graph = dict(lbep[:, [0, 3]]) ``` Finally, we remove the root nodes (*i.e.* cells without a parent) for visualization with the `Tracks` layer: + ```python graph = {k: v for k, v in full_graph.items() if v != 0} ``` @@ -142,12 +148,14 @@ def root(node: int): return node return root(full_graph[node]) + roots = {k: root(k) for k in full_graph.keys()} ``` The `Tracks` layer enables the vertices of the tracks to be colored by user specified properties. Here, we will create a property which represents the `root_id` of each tree, so that cells with a common ancestor are colored the same: + ```python -properties = {'root_id': [roots[idx] for idx in data[:, 0]]} +properties = {"root_id": [roots[idx] for idx in data[:, 0]]} ``` ### Visualizing the tracks with napari @@ -156,7 +164,7 @@ Alongside the tracks, we can also visualize the fluorescence imaging data. ```python timelapse = np.asarray( - [imread(os.path.join(PATH, '01', f't{i:0>3}.tif')) for i in range(NUM_IMAGES)] + [imread(os.path.join(PATH, "01", f"t{i:0>3}.tif")) for i in range(NUM_IMAGES)] ) ``` @@ -171,14 +179,14 @@ We can now visualize the full, linked tracks in napari! ```python viewer = napari.Viewer() -viewer.add_image(timelapse, scale=SCALE, name='Fluo-N3DH-CE') -viewer.add_tracks(data, properties=properties, graph=graph, scale=SCALE, name='tracks') +viewer.add_image(timelapse, scale=SCALE, name="Fluo-N3DH-CE") +viewer.add_tracks(data, properties=properties, graph=graph, scale=SCALE, name="tracks") napari.run() ``` ![napari viewer with fluorescence imaging cells image layer and tracks layer loaded. The dimension slider under the canvas is at 0.](../assets/tutorials/tracks_isbi.webm) ---- +______________________________________________________________________ ## 2. Using `btrack` to track cells @@ -191,21 +199,20 @@ import btrack We start by loading a file containing the centroids of all the found cells in each frame of the source movie. Note that this file only contains the locations of cells in the movie, there are no tracks yet. We can use the `btrack` library to load this file as a list of `objects` that contain information about each found cell, including the TZYX position. The example dataset can be downloaded [here](https://github.com/quantumjot/BayesianTracker/blob/0f8bbd937535193bde20e3ebe91a323f6bb915e9/examples/napari_example.csv). ```python -objects = btrack.dataio.import_CSV('napari_example.csv') +objects = btrack.dataio.import_CSV("napari_example.csv") ``` Next, we set up a `btrack.BayesianTracker` instance using a context manager to ensure the library is properly initialized. The `objects` are added to the tracker using the `.append()` method. We also set the imaging volume using the `volume` property. As this is a 2D dataset, the limits of the volume are set to the XY dimensions of the image dataset, while the Z dimension is set to be very large (±1e5). In this case, setting the Z limits of the volume to be very large penalises tracks which initialize or terminate in the centre of the XY plane, unless they can be explained by corresponding cell division or death events. - The tracker performs the process of linking individual cell observations into tracks and the generating the associated track graph: +The tracker performs the process of linking individual cell observations into tracks and the generating the associated track graph: ```python with btrack.BayesianTracker() as tracker: - # configure the tracker using a config file - tracker.configure_from_file('cell_config.json') + tracker.configure_from_file("cell_config.json") tracker.append(objects) - tracker.volume=((0,1600), (0,1200), (-1e5,1e5)) + tracker.volume = ((0, 1600), (0, 1200), (-1e5, 1e5)) # track and optimize tracker.track_interactive(step_size=100) @@ -232,15 +239,18 @@ napari.run() A notebook for this example can be found in the btrack examples directory ([`napari_btrack.ipynb`](https://github.com/quantumjot/BayesianTracker/blob/caa56fa82330e4b16b5d28150f9b60ed963165c7/examples/napari_btrack.ipynb)) ## Summary + In this application note, we have used napari to track and visualize single cells. ## Further reading References for cell tracking challenge: -+ https://www.nature.com/articles/nmeth.1228 -+ http://dx.doi.org/10.1093/bioinformatics/btu080 -+ http://dx.doi.org/10.1038/nmeth.4473 + +- https://www.nature.com/articles/nmeth.1228 +- http://dx.doi.org/10.1093/bioinformatics/btu080 +- http://dx.doi.org/10.1038/nmeth.4473 For a more advanced example of visualizing cell tracking data with napari, please see the Arboretum plugin for napari: -+ [btrack](https://github.com/quantumjot/BayesianTracker) -+ [arboretum](https://github.com/lowe-lab-ucl/arboretum) + +- [btrack](https://github.com/quantumjot/BayesianTracker) +- [arboretum](https://github.com/lowe-lab-ucl/arboretum) diff --git a/docs/tutorials/tracking/index.md b/docs/tutorials/tracking/index.md index 49c793bc4..a7a39a7d2 100644 --- a/docs/tutorials/tracking/index.md +++ b/docs/tutorials/tracking/index.md @@ -1,6 +1,7 @@ (tracking)= + # Tracking This section contains tutorials showing how to work with object tracking data and how to format it for display as a napari tracks layer. -* [Single cell tracking with napari](cell_tracking) +- [Single cell tracking with napari](cell_tracking)