mkitti / Napari.jl

Julia package for the Napari multi-dimensional image viewer
BSD 3-Clause "New" or "Revised" License
10 stars 3 forks source link

NaN crashes IJulia Kernel #13

Open roflmaostc opened 3 years ago

roflmaostc commented 3 years ago

Hey,

having NaNs in an image, crashes my IJulia Kernel. Below a REPL example showing the same errors:

julia> using Napari
┌ Info: napari version
└   version = "0.4.4"
[ Info: /home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari

julia> x = NaN * rand(2,2)
2×2 Matrix{Float64}:
 NaN  NaN
 NaN  NaN

julia> napari.view_image(x)
ERROR: PyError ($(Expr(:escape, :(ccall(#= /home/fxw/.julia/packages/PyCall/tqyST/src/pyfncall.jl:43 =# @pysym(:PyObject_Call), PyPtr, (PyPtr, PyPtr, PyPtr), o, pyargsptr, kw))))) <class 'ValueError'>
ValueError('cannot convert float NaN to integer')
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/view_layers.py", line 9, in view_image
    (where <layer_type> is replaced with one of the layer types):
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/components/viewer_model.py", line 738, in add_image
    self.layers.append(layer)
  File "/usr/lib/python3.8/_collections_abc.py", line 962, in append
    self.insert(len(self), value)
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/components/layerlist.py", line 70, in insert
    super().insert(index, new_layer)
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/utils/events/containers/_evented_list.py", line 159, in insert
    self.events.inserted(index=index, value=value)
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/utils/events/event.py", line 510, in __call__
    self._invoke_callback(cb, event)
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/utils/events/event.py", line 527, in _invoke_callback
    _handle_exception(
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/utils/events/event.py", line 525, in _invoke_callback
    cb(event)
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/_qt/layer_controls/qt_layer_controls_container.py", line 113, in _add
    controls = create_qt_layer_controls(layer)
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/_qt/layer_controls/qt_layer_controls_container.py", line 48, in create_qt_layer_controls
    return controls(layer)
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/_qt/layer_controls/qt_image_controls.py", line 45, in __init__
    super().__init__(layer)
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/_qt/layer_controls/qt_image_controls_base.py", line 61, in __init__
    self.contrastLimitsSlider = QHRangeSlider(
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/_qt/widgets/qt_range_slider.py", line 78, in __init__
    self.setValues((20, 80) if initial_values is None else initial_values)
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/_qt/widgets/qt_range_slider.py", line 116, in setValues
    self.setSliderValues([self._data_to_slider_value(v) for v in values])
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/_qt/widgets/qt_range_slider.py", line 139, in setSliderValues
    self.updateDisplayPositions()
  File "/home/fxw/julia/PythonEnvs/python38/lib/python3.8/site-packages/napari/_qt/widgets/qt_range_slider.py", line 257, in updateDisplayPositions
    range_min = int(size * self.value_min)

Stacktrace:
  [1] pyerr_check
    @ ~/.julia/packages/PyCall/tqyST/src/exception.jl:62 [inlined]
  [2] pyerr_check
    @ ~/.julia/packages/PyCall/tqyST/src/exception.jl:66 [inlined]
  [3] _handle_error(msg::String)
    @ PyCall ~/.julia/packages/PyCall/tqyST/src/exception.jl:83
  [4] macro expansion
    @ ~/.julia/packages/PyCall/tqyST/src/exception.jl:97 [inlined]
  [5] #109
    @ ~/.julia/packages/PyCall/tqyST/src/pyfncall.jl:43 [inlined]
  [6] disable_sigint
    @ ./c.jl:458 [inlined]
  [7] __pycall!
    @ ~/.julia/packages/PyCall/tqyST/src/pyfncall.jl:42 [inlined]
  [8] _pycall!(ret::PyCall.PyObject, o::PyCall.PyObject, args::Tuple{Matrix{Float64}}, nargs::Int64, kw::Ptr{Nothing})
    @ PyCall ~/.julia/packages/PyCall/tqyST/src/pyfncall.jl:29
  [9] _pycall!
    @ ~/.julia/packages/PyCall/tqyST/src/pyfncall.jl:11 [inlined]
 [10] #_#116
    @ ~/.julia/packages/PyCall/tqyST/src/pyfncall.jl:86 [inlined]
 [11] (::PyCall.PyObject)(args::Matrix{Float64})
    @ PyCall ~/.julia/packages/PyCall/tqyST/src/pyfncall.jl:86
 [12] top-level scope
    @ REPL[7]:1

I was wondering at which part in the code it would be the best to throw a Julia error instead.

Thanks,

Felix

mkitti commented 3 years ago

The problem is upstream and probably in VisPy https://github.com/napari/napari/issues/1310

roflmaostc commented 3 years ago

Yeah, but in the meantime we could catch such errors, couldn't we?

If we don't catch this Python error it kills completely my Pluto/Jupyter frequently which is quite annoying (especially in Julia loading CUDA, Zyogte, Flux, ...)

mkitti commented 3 years ago

You wouldn't be able to intercept the error via napari.view_image. That's purely PyCall.

You could try to intercept the error via Napari.view_image: https://github.com/mkitti/Napari.jl/blob/main/src/layers.jl#L10-L11

You could also intercept the error via the macro interface. For example, call_with_name_keyword or call_viewer_with_name_keyword https://github.com/mkitti/Napari.jl/blob/ba7a837aa206c372492d93bc4e268440ab1cf8b8/src/layer_name_macros.jl#L23 https://github.com/mkitti/Napari.jl/blob/ba7a837aa206c372492d93bc4e268440ab1cf8b8/src/layer_name_macros.jl#L37

roflmaostc commented 3 years ago

Yes, I was referring to intercept the error at a Julia level checking like this

julia> valid_data(x) = all(isfinite.(x))
valid_data (generic function with 1 method)

julia> valid_data([1, 2])
true

julia> valid_data([1, Inf])
false

julia> valid_data([1, NaN])
false

Is data always a pure array which could be checked with valid_data?

mkitti commented 3 years ago

What happens if you throw a try catch around the call? Does IJulia still crash?

roflmaostc commented 3 years ago

Right now the Jupyter doesn't die with the minimal example posted above despite the error is the same :( Maybe it was due some CUDA interaction where the NaN originated from. But I can remember that I had these issues already a few month ago without CUDA.