bauerdavid / napari-bbox

BSD 3-Clause "New" or "Revised" License
5 stars 3 forks source link

[napari 0.5.4, pyqt6] Cannot create annotator #20

Open psobolewskiPhD opened 2 weeks ago

psobolewskiPhD commented 2 weeks ago

If I open an image in napari 0.5.4 and then launch the widget (from Plugins menu) and click Create, I get a traceback:

---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari_bbox/_widget.py:35, in BoundingBoxCreator.__init__.<locals>.<lambda>()
     32 layout.addLayout(ndims_layout)
     34 create_button = QPushButton("Create")
---> 35 create_button.clicked.connect(lambda: viewer.add_bounding_boxes(ndim=ndims_spinbox.value()))
        viewer = Viewer(camera=Camera(center=(0.0, np.float64(127.5), np.float64(127.5)), zoom=np.float64(1.58828125), angles=(0.0, 0.0, 90.0), perspective=0.0, mouse_pan=True, mouse_zoom=True), cursor=Cursor(position=(np.float64(29.0), np.float64(210.608703183833), np.float64(228.8674338492475)), scaled=True, style=<CursorStyle.STANDARD: 'standard'>, size=1.0), dims=Dims(ndim=3, ndisplay=2, order=(0, 1, 2), axis_labels=('0', '1', '2'), rollable=(True, True, True), range=(RangeTuple(start=np.float64(0.0), stop=np.float64(59.0), step=np.float64(1.0)), RangeTuple(start=np.float64(0.0), stop=np.float64(255.0), step=np.float64(1.0)), RangeTuple(start=np.float64(0.0), stop=np.float64(255.0), step=np.float64(1.0))), margin_left=(0.0, 0.0, 0.0), margin_right=(0.0, 0.0, 0.0), point=(np.float64(29.0), np.float64(127.0), np.float64(127.0)), last_used=0), grid=GridCanvas(stride=1, shape=(-1, -1), enabled=False), layers=[<Image layer 'membrane' at 0x1367596d0>, <Image layer 'nuclei' at 0x16b949070>, <BoundingBoxLayer layer 'BoundingBoxLayer' at 0x3022ccfb0>, <BoundingBoxLayer layer 'BoundingBoxLayer [1]' at 0x3039df380>, <BoundingBoxLayer layer 'BoundingBoxLayer [2]' at 0x30273c950>, <BoundingBoxLayer layer 'BoundingBoxLayer [3]' at 0x31cc89e80>, <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>], help='use <2> for transform', status={'layer_name': 'membrane', 'layer_base': 'membrane', 'source_type': 'sample', 'plugin': 'napari builtins', 'coordinates': ' [29 211 229]: 1723'}, tooltip=Tooltip(visible=False, text=''), theme='dark', title='napari', mouse_over_canvas=True, mouse_move_callbacks=[], mouse_drag_callbacks=[], mouse_double_click_callbacks=[], mouse_wheel_callbacks=[<function dims_scroll at 0x130bdaac0>], _persisted_mouse_event={}, _mouse_drag_gen={}, _mouse_wheel_gen={}, _keymap={})
        ndims_spinbox = <PyQt6.QtWidgets.QSpinBox object at 0x131fe8ff0>
     36 layout.addWidget(create_button)
     37 separator = QFrame()

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari_bbox/__init__.py:46, in add_bounding_boxes(self=Viewer(camera=Camera(center=(0.0, np.float64(127...use_drag_gen={}, _mouse_wheel_gen={}, _keymap={}), *args=(), **kwargs={'ndim': 3})
     44     self.layers.events.removed.disconnect(disconnect_all)
     45 self.layers.events.removed.connect(disconnect_all)
---> 46 self.layers.append(layer)
        layer = <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>
        self = Viewer(camera=Camera(center=(0.0, np.float64(127.5), np.float64(127.5)), zoom=np.float64(1.58828125), angles=(0.0, 0.0, 90.0), perspective=0.0, mouse_pan=True, mouse_zoom=True), cursor=Cursor(position=(np.float64(29.0), np.float64(210.608703183833), np.float64(228.8674338492475)), scaled=True, style=<CursorStyle.STANDARD: 'standard'>, size=1.0), dims=Dims(ndim=3, ndisplay=2, order=(0, 1, 2), axis_labels=('0', '1', '2'), rollable=(True, True, True), range=(RangeTuple(start=np.float64(0.0), stop=np.float64(59.0), step=np.float64(1.0)), RangeTuple(start=np.float64(0.0), stop=np.float64(255.0), step=np.float64(1.0)), RangeTuple(start=np.float64(0.0), stop=np.float64(255.0), step=np.float64(1.0))), margin_left=(0.0, 0.0, 0.0), margin_right=(0.0, 0.0, 0.0), point=(np.float64(29.0), np.float64(127.0), np.float64(127.0)), last_used=0), grid=GridCanvas(stride=1, shape=(-1, -1), enabled=False), layers=[<Image layer 'membrane' at 0x1367596d0>, <Image layer 'nuclei' at 0x16b949070>, <BoundingBoxLayer layer 'BoundingBoxLayer' at 0x3022ccfb0>, <BoundingBoxLayer layer 'BoundingBoxLayer [1]' at 0x3039df380>, <BoundingBoxLayer layer 'BoundingBoxLayer [2]' at 0x30273c950>, <BoundingBoxLayer layer 'BoundingBoxLayer [3]' at 0x31cc89e80>, <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>], help='use <2> for transform', status={'layer_name': 'membrane', 'layer_base': 'membrane', 'source_type': 'sample', 'plugin': 'napari builtins', 'coordinates': ' [29 211 229]: 1723'}, tooltip=Tooltip(visible=False, text=''), theme='dark', title='napari', mouse_over_canvas=True, mouse_move_callbacks=[], mouse_drag_callbacks=[], mouse_double_click_callbacks=[], mouse_wheel_callbacks=[<function dims_scroll at 0x130bdaac0>], _persisted_mouse_event={}, _mouse_drag_gen={}, _mouse_wheel_gen={}, _keymap={})
        self.layers = [<Image layer 'membrane' at 0x1367596d0>, <Image layer 'nuclei' at 0x16b949070>, <BoundingBoxLayer layer 'BoundingBoxLayer' at 0x3022ccfb0>, <BoundingBoxLayer layer 'BoundingBoxLayer [1]' at 0x3039df380>, <BoundingBoxLayer layer 'BoundingBoxLayer [2]' at 0x30273c950>, <BoundingBoxLayer layer 'BoundingBoxLayer [3]' at 0x31cc89e80>, <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>]
     47 return layer

File <frozen _collections_abc>:1130, in append(self=[<Image layer 'membrane' at 0x1367596d0>, <Image...yer layer 'BoundingBoxLayer [4]' at 0x31c91e690>], value=<BoundingBoxLayer layer 'BoundingBoxLayer [4]'>)

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari/components/layerlist.py:188, in LayerList.insert(self=[<Image layer 'membrane' at 0x1367596d0>, <Image...yer layer 'BoundingBoxLayer [4]' at 0x31c91e690>], index=6, value=<BoundingBoxLayer layer 'BoundingBoxLayer [4]'>)
    186 new_layer.events.extent.connect(self._clean_cache)
    187 new_layer.events._extent_augmented.connect(self._clean_cache)
--> 188 super().insert(index, new_layer)
        new_layer = <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>
        index = 6

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari/utils/events/containers/_selectable_list.py:68, in SelectableEventedList.insert(self=[<Image layer 'membrane' at 0x1367596d0>, <Image...yer layer 'BoundingBoxLayer [4]' at 0x31c91e690>], index=6, value=<BoundingBoxLayer layer 'BoundingBoxLayer [4]'>)
     67 def insert(self, index: int, value: _T) -> None:
---> 68     super().insert(index, value)
        index = 6
        value = <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>
     69     if self._activate_on_insert:
     70         # Make layer selected and unselect all others
     71         self.selection.active = value

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari/utils/events/containers/_evented_list.py:202, in EventedList.insert(self=[<Image layer 'membrane' at 0x1367596d0>, <Image...yer layer 'BoundingBoxLayer [4]' at 0x31c91e690>], index=6, value=<BoundingBoxLayer layer 'BoundingBoxLayer [4]'>)
    200 self.events.inserting(index=index)
    201 super().insert(index, value)
--> 202 self.events.inserted(index=index, value=value)
        index = 6
        value = <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>
        self.events.inserted = <napari.utils.events.event.EventEmitter object at 0x131f3d520>
        self.events = <napari.utils.events.event.EmitterGroup object at 0x131f3d3a0>
        self = [<Image layer 'membrane' at 0x1367596d0>, <Image layer 'nuclei' at 0x16b949070>, <BoundingBoxLayer layer 'BoundingBoxLayer' at 0x3022ccfb0>, <BoundingBoxLayer layer 'BoundingBoxLayer [1]' at 0x3039df380>, <BoundingBoxLayer layer 'BoundingBoxLayer [2]' at 0x30273c950>, <BoundingBoxLayer layer 'BoundingBoxLayer [3]' at 0x31cc89e80>, <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>]
    203 self._connect_child_emitters(value)

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari/utils/events/event.py:764, in EventEmitter.__call__(self=<napari.utils.events.event.EventEmitter object>, *args=(), **kwargs={'index': 6, 'value': <BoundingBoxLayer layer 'BoundingBoxLayer [4]'>})
    761     self._block_counter.update([cb])
    762     continue
--> 764 self._invoke_callback(cb, event if pass_event else None)
        event = <Event blocked=False handled=False native=None source=None sources=[] type='inserted'>
        self = <napari.utils.events.event.EventEmitter object at 0x131f3d520>
        cb = <bound method QtLayerControlsContainer._add of <napari._qt.layer_controls.qt_layer_controls_container.QtLayerControlsContainer object at 0x1690e6210>>
        pass_event = True
    765 if event.blocked:
    766     break

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari/utils/events/event.py:802, in EventEmitter._invoke_callback(self=<napari.utils.events.event.EventEmitter object>, cb=<bound method QtLayerControlsContainer._add of <...trols_container.QtLayerControlsContainer object>>, event=<Event blocked=False handled=False native=None source=None sources=[] type='inserted'>)
    800     self.disconnect(cb)
    801     return
--> 802 _handle_exception(
        self = <napari.utils.events.event.EventEmitter object at 0x131f3d520>
        event = <Event blocked=False handled=False native=None source=None sources=[] type='inserted'>
        cb = <bound method QtLayerControlsContainer._add of <napari._qt.layer_controls.qt_layer_controls_container.QtLayerControlsContainer object at 0x1690e6210>>
        (cb, event) = (<bound method QtLayerControlsContainer._add of <napari._qt.layer_controls.qt_layer_controls_container.QtLayerControlsContainer object at 0x1690e6210>>, <Event blocked=False handled=False native=None source=None sources=[] type='inserted'>)
    803     self.ignore_callback_errors,
    804     self.print_callback_errors,
    805     self,
    806     cb_event=(cb, event),
    807 )

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari/utils/events/event.py:789, in EventEmitter._invoke_callback(self=<napari.utils.events.event.EventEmitter object>, cb=<bound method QtLayerControlsContainer._add of <...trols_container.QtLayerControlsContainer object>>, event=<Event blocked=False handled=False native=None source=None sources=[] type='inserted'>)
    787 try:
    788     if event is not None:
--> 789         cb(event)
        event = <Event blocked=False handled=False native=None source=None sources=[] type='inserted'>
        cb = <bound method QtLayerControlsContainer._add of <napari._qt.layer_controls.qt_layer_controls_container.QtLayerControlsContainer object at 0x1690e6210>>
    790     else:
    791         cb()

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari/_qt/layer_controls/qt_layer_controls_container.py:142, in QtLayerControlsContainer._add(self=<napari._qt.layer_controls.qt_layer_controls_container.QtLayerControlsContainer object>, event=<Event blocked=False handled=False native=None source=None sources=[] type='inserted'>)
    134 """Add the controls target layer to the list of control widgets.
    135 
    136 Parameters
   (...)
    139     Event with the target layer at `event.value`.
    140 """
    141 layer = event.value
--> 142 controls = create_qt_layer_controls(layer)
        layer = <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>
    143 controls.ndisplay = self.viewer.dims.ndisplay
    144 self.addWidget(controls)

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari/_qt/layer_controls/qt_layer_controls_container.py:67, in create_qt_layer_controls(layer=<BoundingBoxLayer layer 'BoundingBoxLayer [4]'>)
     65 candidates.sort(key=lambda layer_type: layer_cls.mro().index(layer_type))
     66 controls = layer_to_controls[candidates[0]]
---> 67 return controls(layer)
        controls = <class 'napari_bbox.boundingbox.napari_0_4_18.qt_bounding_box_control.QtBoundingBoxControls'>
        layer = <BoundingBoxLayer layer 'BoundingBoxLayer [4]' at 0x31c91e690>

File ~/micromamba/envs/napari-054/lib/python3.12/site-packages/napari_bbox/boundingbox/napari_0_4_18/qt_bounding_box_control.py:215, in QtBoundingBoxControls.__init__(self=<napari_bbox.boundingbox.napari_0_4_18.qt_bounding_box_control.QtBoundingBoxControls object>, layer=<BoundingBoxLayer layer 'BoundingBoxLayer [4]'>)
    213 bb_size_mode_combobox.addItem("average")
    214 bb_size_mode_combobox.addItem("constant")
--> 215 bb_size_mode_combobox.activated[str].connect(self.changeSizeMode)
        bb_size_mode_combobox = <PyQt6.QtWidgets.QComboBox object at 0x31e360eb0>
        self = <napari_bbox.boundingbox.napari_0_4_18.qt_bounding_box_control.QtBoundingBoxControls object at 0x31d7356d0>
    216 self.bb_size_mode_combobox = bb_size_mode_combobox
    218 bb_size_mult_slider = QLabeledDoubleSlider(Qt.Horizontal, parent=self)

KeyError: 'there is no matching overloaded signal'

It's referring to napari_bbox.boundingbox.napari_0_4_18 so I suspect it's somehow not properly detecting napari 0.5.4?

❯ napari --info 
napari: 0.5.4
Platform: macOS-14.7-arm64-arm-64bit
System: MacOS 14.7
Python: 3.12.7 | packaged by conda-forge | (main, Oct  4 2024, 15:57:01) [Clang 17.0.6 ]
Qt: 6.7.1
PyQt6: 
NumPy: 2.1.3
SciPy: 1.14.1
Dask: 2024.10.0
VisPy: 0.14.3
magicgui: 0.9.1
superqt: 0.6.7
in-n-out: 0.2.1
app-model: 0.3.0
psygnal: 0.11.1
npe2: 0.7.7
pydantic: 2.9.2

OpenGL:
  - GL version:  2.1 Metal - 88.1
  - MAX_TEXTURE_SIZE: 16384
  - GL_MAX_3D_TEXTURE_SIZE: 2048

Screens:
  - screen 1: resolution 2056x1329, scale 2.0
  - screen 2: resolution 2560x1440, scale 1.0

Optional:
  - numba not installed
  - triangle not installed
  - napari-plugin-manager not installed

Settings path:
  - /Users/sobolp/Library/Application Support/napari/napari-054_a5653443d955661cca1fa68178d4ba2893ce56ef/settings.yaml
Plugins:
  - napari: 0.5.4 (81 contributions)
  - napari-bbox: 0.0.8 (4 contributions)
  - napari-console: 0.1.0 (0 contributions)
  - napari-svg: 0.2.0 (2 contributions)
bauerdavid commented 1 week ago

Wow, that's an interesting one...

It's referring to napari_bbox.boundingbox.napari_0_4_18 so I suspect it's somehow not properly detecting napari 0.5.4?

When there's a bigger change in the inner logic of napari, I have to adapt my code to accomodate these into the bounding box layer. The last such was with 0.5.0, so here it imports from napari_bbox.boundingbox.napari_0_5_0.qt_bounding_box_control, but as in this part of the code there were no changes in napari, it will simply import from napari_bbox.boundingbox.napari_0_4_18.qt_bounding_box_control. At a first glance, I think that the plugin is not compatible with Qt6, only Qt5, but I'll take a look at it later.

psobolewskiPhD commented 1 week ago

Ah! Works perfectly with a new env with napari 0.5.4 and pyqt5.

psobolewskiPhD commented 1 week ago

With a little help from microsoft copilot, I think I have a fix. Will test locally and then open PR.