juglab / napari-n2v

A self-supervised denoising algorithm.
https://juglab.github.io/napari-n2v/
BSD 3-Clause "New" or "Revised" License
24 stars 3 forks source link

Installation instructions are missing crucial detail: tensorflow #30

Closed psobolewskiPhD closed 1 year ago

psobolewskiPhD commented 1 year ago

So my first go with this plugin was eventful: my napari got downgraded which broke some things (need to make a napari issue to prevent that) and then the demo widget didn't work (this is solved!). So I made a fresh env and installed there using pip following the instructions and that didn't work, traceback below. I guess the plugin doesn't specify tensorflow as a requirement and none the other requirements do either? 🤔 Either those requirements should be fixed—then the napari GUI installer can work—or the docs should be explicit about how to install. FYI: on macOS arm64 it works perfectly in a tensorflow env set up following Apple instructions: https://developer.apple.com/metal/tensorflow-plugin/ with a ~2x speedup using tensorflow-metal vs CPU only (tensorflow-macos).

ModuleNotFoundError                       Traceback (most recent call last)
File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/npe2/_command_registry.py:33, in CommandHandler.resolve(self=CommandHandler(id='napari-n2v.make_n2v_demo_pred...name='napari_n2v._predict_widget:DemoPrediction'))
     32 try:
---> 33     self.function = utils.import_python_name(self.python_name)
        self.function = None
        self.python_name = 'napari_n2v._predict_widget:DemoPrediction'
        self = CommandHandler(id='napari-n2v.make_n2v_demo_prediction', function=None, python_name='napari_n2v._predict_widget:DemoPrediction')
        utils = <module 'npe2.manifest.utils' from '/Users/piotrsobolewski/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/npe2/manifest/utils.py'>
     34 except Exception as e:

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/npe2/manifest/utils.py:240, in import_python_name(python_name='napari_n2v._predict_widget:DemoPrediction')
    238 module_name, funcname = match.groups()  # type: ignore [union-attr]
--> 240 mod = import_module(module_name)
        module_name = 'napari_n2v._predict_widget'
    241 return getattr(mod, funcname)

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/importlib/__init__.py:127, in import_module(name='napari_n2v._predict_widget', package=None)
    126         level += 1
--> 127 return _bootstrap._gcd_import(name[level:], package, level)
        level = 0
        name = 'napari_n2v._predict_widget'
        name[level:] = 'napari_n2v._predict_widget'
        package = None
        _bootstrap = <module 'importlib._bootstrap' (frozen)>

File <frozen importlib._bootstrap>:1030, in _gcd_import(name='napari_n2v._predict_widget', package=None, level=0)

File <frozen importlib._bootstrap>:1007, in _find_and_load(name='napari_n2v._predict_widget', import_=<function _gcd_import>)

File <frozen importlib._bootstrap>:972, in _find_and_load_unlocked(name='napari_n2v._predict_widget', import_=<function _gcd_import>)

File <frozen importlib._bootstrap>:228, in _call_with_frames_removed(f=<function _gcd_import>, *args=('napari_n2v',), **kwds={})

File <frozen importlib._bootstrap>:1030, in _gcd_import(name='napari_n2v', package=None, level=0)

File <frozen importlib._bootstrap>:1007, in _find_and_load(name='napari_n2v', import_=<function _gcd_import>)

File <frozen importlib._bootstrap>:986, in _find_and_load_unlocked(name='napari_n2v', import_=<function _gcd_import>)

File <frozen importlib._bootstrap>:680, in _load_unlocked(spec=ModuleSpec(name='napari_n2v', loader=<_frozen_im...pari-39/lib/python3.9/site-packages/napari_n2v']))

File <frozen importlib._bootstrap_external>:850, in exec_module(self=<_frozen_importlib_external.SourceFileLoader object>, module=<module 'napari_n2v' from '/Users/piotrsobolewsk.../python3.9/site-packages/napari_n2v/__init__.py'>)

File <frozen importlib._bootstrap>:228, in _call_with_frames_removed(f=<built-in function exec>, *args=(<code object <module> at 0x17115e190, file "/Use....9/site-packages/napari_n2v/__init__.py", line 1>, {'__builtins__': {'ArithmeticError': <class 'ArithmeticError'>, 'AssertionError': <class 'AssertionError'>, 'AttributeError': <class 'AttributeError'>, 'BaseException': <class 'BaseException'>, 'BlockingIOError': <class 'BlockingIOError'>, 'BrokenPipeError': <class 'BrokenPipeError'>, 'BufferError': <class 'BufferError'>, 'BytesWarning': <class 'BytesWarning'>, 'ChildProcessError': <class 'ChildProcessError'>, 'ConnectionAbortedError': <class 'ConnectionAbortedError'>, ...}, '__cached__': '/Users/piotrsobolewski/Dev/miniforge3/envs/napar...es/napari_n2v/__pycache__/__init__.cpython-39.pyc', '__doc__': None, '__file__': '/Users/piotrsobolewski/Dev/miniforge3/envs/napar...ib/python3.9/site-packages/napari_n2v/__init__.py', '__loader__': <_frozen_importlib_external.SourceFileLoader object>, '__name__': 'napari_n2v', '__package__': 'napari_n2v', '__path__': ['/Users/piotrsobolewski/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/napari_n2v'], '__spec__': ModuleSpec(name='napari_n2v', loader=<_frozen_im...pari-39/lib/python3.9/site-packages/napari_n2v']), '__version__': '0.0.1'}), **kwds={})

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/napari_n2v/__init__.py:3
      1 __version__ = "0.0.1"
----> 3 from ._sample_data import n2v_2D_data, n2v_3D_data, n2v_rgb_data, n2v_sem_data, demo_files
      4 from ._train_widget import TrainingWidgetWrapper

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/napari_n2v/_sample_data.py:11
      9 from napari.utils import notifications as ntf
---> 11 from napari_n2v.utils import cwd, get_default_path
     13 # todo the logic is the same for all functions, possibility to refactor

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/napari_n2v/utils/__init__.py:2
----> 2 from .n2v_utils import (
      3     create_model,
      4     filter_dimensions,
      5     are_axes_valid,
      6     build_modelzoo,
      7     reshape_data,
      8     get_size_from_shape,
      9     get_images_count,
     10     reshape_napari,
     11     create_config,
     12     get_napari_shapes,
     13     get_shape_order,
     14     get_default_path
     15 )
     16 from .load_images_utils import (
     17     load_and_reshape,
     18     load_from_disk,
     19     lazy_load_generator,
     20 )

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/napari_n2v/utils/n2v_utils.py:15
     13 from napari.utils import notifications as ntf
---> 15 from n2v.models import N2V, N2VConfig
     17 from napari_n2v.resources import DOC_BIOIMAGE

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/n2v/models/__init__.py:4
      3 # imports
----> 4 from .n2v_config import N2VConfig
      5 from .n2v_standard import N2V

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/n2v/models/n2v_config.py:3
      1 import argparse
----> 3 import tensorflow.keras.backend as K
      5 from csbdeep.utils import _raise, axes_check_and_normalize, axes_dict, backend_channels_last

ModuleNotFoundError: No module named 'tensorflow'

The above exception was the direct cause of the following exception:

RuntimeError                              Traceback (most recent call last)
File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/napari/_qt/menus/plugins_menu.py:97, in PluginsMenu._add_plugin_actions.<locals>._add_toggle_widget(key=('napari-n2v', 'N2V Demo prediction'), hook_type='dock')
     94     return
     96 if hook_type == 'dock':
---> 97     dock_widget, _w = self._win.add_plugin_dock_widget(*key)
        key = ('napari-n2v', 'N2V Demo prediction')
        self._win = <napari._qt.qt_main_window.Window object at 0x174f17ca0>
        self = <napari._qt.menus.plugins_menu.PluginsMenu object at 0x17114cf70>
     98 else:
     99     dock_widget = self._win._add_plugin_function_widget(*key)

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/napari/_qt/qt_main_window.py:664, in Window.add_plugin_dock_widget(self=<napari._qt.qt_main_window.Window object>, plugin_name='napari-n2v', widget_name='N2V Demo prediction')
    661 Widget = None
    662 dock_kwargs = {}
--> 664 result = _npe2.get_widget_contribution(plugin_name, widget_name)
        plugin_name = 'napari-n2v'
        widget_name = 'N2V Demo prediction'
        _npe2 = <module 'napari.plugins._npe2' from '/Users/piotrsobolewski/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/napari/plugins/_npe2.py'>
    665 if result:
    666     Widget, widget_name = result

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/napari/plugins/_npe2.py:108, in get_widget_contribution(plugin_name='napari-n2v', widget_name='N2V Demo prediction')
    106     if contrib.plugin_name == plugin_name:
    107         if not widget_name or contrib.display_name == widget_name:
--> 108             return contrib.get_callable(), contrib.display_name
        contrib = WidgetContribution(command='napari-n2v.make_n2v_demo_prediction', display_name='N2V Demo prediction', autogenerate=False)
        contrib.display_name = 'N2V Demo prediction'
    109         widgets_seen.add(contrib.display_name)
    110 if widget_name and widgets_seen:

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/npe2/manifest/contributions/_widgets.py:50, in WidgetContribution.get_callable(self=WidgetContribution(command='napari-n2v.make_n2v_...y_name='N2V Demo prediction', autogenerate=False), _registry=None)
     47 def get_callable(
     48     self, _registry: Optional[CommandRegistry] = None
     49 ) -> Callable[..., Widget]:
---> 50     func = super().get_callable()
     51     if self.autogenerate:
     52         try:

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/npe2/manifest/utils.py:75, in Executable.get_callable(self=WidgetContribution(command='napari-n2v.make_n2v_...y_name='N2V Demo prediction', autogenerate=False), _registry=<npe2._command_registry.CommandRegistry object>)
     72     from .._plugin_manager import PluginManager
     74     _registry = PluginManager.instance().commands
---> 75 return _registry.get(self.command)
        _registry = <npe2._command_registry.CommandRegistry object at 0x127055250>
        self.command = 'napari-n2v.make_n2v_demo_prediction'
        self = WidgetContribution(command='napari-n2v.make_n2v_demo_prediction', display_name='N2V Demo prediction', autogenerate=False)

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/npe2/_command_registry.py:132, in CommandRegistry.get(self=<npe2._command_registry.CommandRegistry object>, id='napari-n2v.make_n2v_demo_prediction')
    130     if id not in self._commands:  # sourcery skip
    131         raise KeyError(f"command {id!r} not registered")
--> 132 return self._commands[id].resolve()
        id = 'napari-n2v.make_n2v_demo_prediction'
        self._commands = {'napari-n2v.make_n2v_trainwidget': CommandHandler(id='napari-n2v.make_n2v_trainwidget', function=None, python_name='napari_n2v._train_widget:TrainingWidgetWrapper'), 'napari-n2v.make_n2v_predictwidget': CommandHandler(id='napari-n2v.make_n2v_predictwidget', function=None, python_name='napari_n2v._predict_widget:PredictWidgetWrapper'), 'napari-n2v.make_n2v_demo_prediction': CommandHandler(id='napari-n2v.make_n2v_demo_prediction', function=None, python_name='napari_n2v._predict_widget:DemoPrediction'), 'napari-n2v.data_2D': CommandHandler(id='napari-n2v.data_2D', function=None, python_name='napari_n2v._sample_data:n2v_2D_data'), 'napari-n2v.data_3D': CommandHandler(id='napari-n2v.data_3D', function=None, python_name='napari_n2v._sample_data:n2v_3D_data'), 'napari-n2v.data_RGB': CommandHandler(id='napari-n2v.data_RGB', function=None, python_name='napari_n2v._sample_data:n2v_rgb_data'), 'napari-n2v.data_SEM': CommandHandler(id='napari-n2v.data_SEM', function=None, python_name='napari_n2v._sample_data:n2v_sem_data')}
        self = <npe2._command_registry.CommandRegistry object at 0x127055250>
        self._commands[id] = CommandHandler(id='napari-n2v.make_n2v_demo_prediction', function=None, python_name='napari_n2v._predict_widget:DemoPrediction')

File ~/Dev/miniforge3/envs/napari-39/lib/python3.9/site-packages/npe2/_command_registry.py:35, in CommandHandler.resolve(self=CommandHandler(id='napari-n2v.make_n2v_demo_pred...name='napari_n2v._predict_widget:DemoPrediction'))
     33     self.function = utils.import_python_name(self.python_name)
     34 except Exception as e:
---> 35     raise RuntimeError(
     36         f"Failed to import command at {self.python_name!r}: {e}"
     37     ) from e
     39 return self.function

RuntimeError: Failed to import command at 'napari_n2v._predict_widget:DemoPrediction': No module named 'tensorflow'
jdeschamps commented 1 year ago

Thanks for so much @psobolewskiPhD for going through the pain. I didn't realise that I forgot mention TF in the Readme. It is in the installation instructions, which is what I intended people to follow.

The problem with tensorflow is that the installation, especially with the GPU, can go easily wrong depending on the platform and it is better to follow the official TF tensorflow.

I will remove the pip install from the readme and insist on the installation instructions.

On another note: we are moving away from TF and will have a napari-n2v that is PyTorch based next year.

jdeschamps commented 1 year ago

@psobolewskiPhD Could you try a pip install -e . on your mac with the current main branch? Does it install tensorflow-metal? (install napari[all]==0.4.15 beforehand)

The napari pinned dependency sucks indeed, it is due to the fact that we wanted compatibility with napari-assistant. I am also not a fan of pinning this one down, so we will try to find another solution in the near future to maintain the compatibility and keep up with later versions.

psobolewskiPhD commented 1 year ago

Couple notes: tensorflow-macos and tensorflow-metal arn't arm64 specific, so you could just use darwin flags. See: https://pypi.org/project/tensorflow-macos/#files https://developer.apple.com/metal/tensorflow-plugin/ Also, just FYI pip install napari[all] doesn't work on arm64 because pyqt5 doesn't have a pip wheel. Anyhow, in an conda-forge env with pyqt5 and imagecodecs everything works with: pip install git+https://github.com/juglab/napari-n2v.git Marginally slower than using Apple TF dependencies following the link above (~160 ms vs ~125 ms), so you may still want to mention that in the instructions.

jdeschamps commented 1 year ago

Good to know, thanks again for keeping up with the project and sharing your macOS magic! I must admit that I am totally oblivious to how people do DL/napari on macOS and I didn't see anything on the napari website regarding pyqt5/macOS (probably my fault).

Also, just FYI pip install napari[all] doesn't work on arm64 because pyqt5 doesn't have a pip wheel. Anyhow, in an conda-forge env with pyqt5 and imagecodecs everything works with: pip install git+https://github.com/juglab/napari-n2v.git

I am not sure I follow. Do you do something along these lines ? :

  1. Set up env with napari and pyqt5
    conda create -n napari-n2v -c conda-forge python=3.9 napari pyqt5 imagecodecs
  2. Install tensorflow following your link
  3. Install napari-n2v (currently pip install git+[...], later with pip install napari-n2v)

And would this also be valid for x86? Or macOS x86 users should rather install napari through pip?

Marginally slower than using Apple TF dependencies following the link above (~160 ms vs ~125 ms), so you may still want to mention that in the instructions.

What are you timing there ?

psobolewskiPhD commented 1 year ago

Sorry I wasn't clear. The two things that are missing arm64 from pypi are Qt5 (pyqt or pyside) and imagecodecs. In my experience everything else works. (Qt6 is on pypi, but still some issues in napari/vispy) So I normally make conda envs with those two from conda-forge. Making an env with napari from conda-forge works too, it should pull pyqt5. But yes, this should probably be better documented on napari.org—officially I think arm64 isn't supported.

For tensorflow, I normally setup the env using Apple instructions and then install napari (typically from conda-forge via mamba cause it's just faster), etc. But as noted above it's not needed to make the env using apple's channel. All of the instructions should work the same x86 or arm64.

Regarding the timing, when I run predict I get this in the terminal:

2022-10-28 18:56:58.631369: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:114] Plugin optimizer for device_type GPU is enabled.
1/1 [==============================] - 0s 138ms/step
jdeschamps commented 1 year ago

Great, thanks a lot for the help!

psobolewskiPhD commented 1 year ago

One thing I forgot! the apple tensorflow-metal bit is only officially supported for macOS 12 and higher. It works on 11.x, in my experience, but some things are flakey.

jdeschamps commented 1 year ago

I updated the installation instructions: https://juglab.github.io/napari-n2v/installation.html

jdeschamps commented 1 year ago

I added the arm64 condition back in the setup.cfg, otherwise the tox tests fail during the CI with Github Actions. Once I will have time, I will try to figure out what happens.

psobolewskiPhD commented 1 year ago

Might be due to not-macOS 12? shrug

psobolewskiPhD commented 1 year ago

@jdeschamps I made a PR to fix conda-forge instructions. Plus add back tensorflow-metal. Tested locally and it all works, even without the apple env.