Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

RuntimeError: wrapped C/C++ object of type QWidget has been deleted #4377

Closed
wahabk opened this issue Apr 11, 2022 · 6 comments
Closed

RuntimeError: wrapped C/C++ object of type QWidget has been deleted #4377

wahabk opened this issue Apr 11, 2022 · 6 comments
Labels
bug Something isn't working

Comments

@wahabk
Copy link

wahabk commented Apr 11, 2022

馃悰 Bug

Hello, thank you everyone napari is amazing. I have scoured the docs / previous issues but the most similar one seemed to be https://github.com/napari/magicgui/issues/28 which didnt fix my issue

I hope I am posting this in the right place. This seems to be an issue with qt deleting my previous widget in a for loop and me not being able to use add_dock_widget again

To Reproduce

This is my widget

@magicgui(
	# call_button='Detect',
	auto_call=True,
	dp={"widget_type": "FloatSlider", 'min' : 100, 'max' : 200},
	pad={"widget_type": "Slider", 'min' : 0, 'max' : 20},
	layout='vertical',)
def tubeDetector(layer:Layer, dp:float, pad:int) -> Layer:
	if layer is not None and finished==False:
		assert isinstance(layer.data, np.ndarray)  # it will be!

		array = layer.metadata['og'] # get original scan
		_slice = int(layer.position[0]) # get the slice youre looking at

		circle_dict = find_tubes(array, dp=dp/100, slice_to_detect=_slice, pad=pad)
		if circle_dict: 
			labelled = circle_dict['labelled_stack']
			layer.metadata['circle_dict'] = circle_dict
			layer.data = labelled
		
	return

This is how I launch it

	def detectTubes(self, scan):

		scan = self.to8bit(scan)
		scan = np.array([cv2.cvtColor(s, cv2.COLOR_GRAY2RGB) for s in scan])
		m = {'og': scan}

		from .GUI import tubeDetector
		import pdb; pdb.set_trace()

		viewer = napari.Viewer(title='tubeDetector')
		layer = viewer.add_image(scan, metadata=m)

		viewer.window.add_dock_widget(tubeDetector, name="tubeDetector")
		viewer.layers.events.changed.connect(tubeDetector.reset_choices)

		napari.run()
		metadata = layer.metadata
		viewer, layer, tubeDetector = None, None, None
		gc.collect()
		
		# QTimer().singleShot(500, app.quit)

		return metadata['circle_dict']

Steps to reproduce the behavior:

  1. This works absolutely fine, except when i put it in a for loop e.g.
for n in loop:
    circle_dict = lump.detectTubes(array)

it gives me this error: RuntimeError: wrapped C/C++ object of type QWidget has been deleted

  1. I understand that I shouldn't be using layer.metadata after closing napari but this is the only way I can get my info out of the widget
  2. I have tried reimporting my widget (lazily) when I am adding it again but it doesnt work
  3. I have tried viewer.window.remove_dock_widget but this doesn't work because the window has been closed
  4. I have also tried setting the viewer and layer and widget to None and forcing garbage collection , but this is an issue with QT C++ I assume so this didnt help

Expected behavior

I expect napari to close all my widgets and their references when the main viewer window is closed, this was supposed to be fixed by #2036

The Qt mainwindow only weakly references the widgets so previous widget instances should be deleted if I understand correctly

Environment

I am using napari 0.4.14

@wahabk wahabk added the bug Something isn't working label Apr 11, 2022
@Czaki
Copy link
Collaborator

Czaki commented Apr 11, 2022

Did the error happens also in napari 0.4.15?

@wahabk
Copy link
Author

wahabk commented Apr 12, 2022

Hi thank you for your reply, I just tested this and unfortunately this error still occurs in 0.4.15

@Czaki
Copy link
Collaborator

Czaki commented Apr 12, 2022

In general there is a problem with removing and then creating a new viewer. The core of the problem are pending Qt events. Because you put only exception without stacktrace I'm not sure if I correctly understand the source of the problem. But if I good understand your problem I see three solutions:

  1. Reuse napari. viewer could be stored in a global variable and reused, Even if the viewer is closed then show will bring it up.
  2. Try to force process all pending events using QCoreApplication.processEvents and QThread.msleep
  3. convert your code to a widget. Data collection could be done in a separate thread.

If you need more details for any of my suggestions feel free to ask. I'm not sure which one is best/most intuitive for you.

@wahabk
Copy link
Author

wahabk commented Apr 12, 2022

Sorry for not including it in the start, here is the full stack trace

Traceback (most recent call last):
  File "/home/ak18001/code/ctfishpy/scripts/cleaning/1_recrop.py", line 53, in <module>
    angle, center = lump.napari_spin(cropped)
  File "/home/ak18001/miniconda3/envs/fish/lib/python3.8/site-packages/ctfishpy/Lumpfish.py", line 423, in napari_spin
    create_spinner(viewer, layer)
  File "/home/ak18001/miniconda3/envs/fish/lib/python3.8/site-packages/ctfishpy/GUI.py", line 255, in create_spinner
    viewer.window.add_dock_widget(widget, name="spinner")
  File "/home/ak18001/miniconda3/envs/fish/lib/python3.8/site-packages/napari/_qt/qt_main_window.py", line 791, in add_dock_widget
    dock_widget = QtViewerDockWidget(
  File "/home/ak18001/miniconda3/envs/fish/lib/python3.8/site-packages/napari/_qt/widgets/qt_viewer_dock_widget.py", line 139, in __init__
    self.setWidget(widget)
  File "/home/ak18001/miniconda3/envs/fish/lib/python3.8/site-packages/napari/_qt/widgets/qt_viewer_dock_widget.py", line 278, in setWidget
    super().setWidget(widget)
RuntimeError: wrapped C/C++ object of type QWidget has been deleted

I tried all of your suggestions:

  1. Reusing the viewer worked! Thank you so much. Just to document the solution: I started a viewer with napari.Viewer(show=False), passed it to detectTubes as an argument and then used viewer.show() and it fixed my error
  2. I tried to use QCoreApplication.processEvents before and after adding the widget but that didnt work
  3. Just to improve my understanding - what do you mean by convert to a widget? Do you mean creating a class based on QWidget similar to example? How would I collect the Data in a seperate thread?

Sorry for lots of questions, if making the widget as a class is the proper way to do it I will convert my code to that for robustness.

@wahabk wahabk closed this as completed Apr 12, 2022
@Czaki
Copy link
Collaborator

Czaki commented Apr 12, 2022

I tried to use QCoreApplication.processEvents before and after adding the widget but that didnt work

In the napari test, we observe that processEvents is not enough. We need to add qtbot.wait(50) if we do not know on which event we need to wait.

But as you publish full stacktrace this may not work. I think that in your case problem is magicgui widget that was cleaned after the first close of the viewer. I'm not familiar with using magicgui this way. Maybe @tlambert03 have better knowledge in this area.

Just to improve my understanding - what do you mean by convert to a widget? Do you mean creating a class based on QWidget similar to example? How would I collect the Data in a seperate thread?

Based on QWidget or magicgui.widgets.Container. I mean the control process of new data collection from napari viewer without closing and reopening. I suggest a separate thread because this approach allow to not freeze the interface.
If you would like to try this approach I suggest reading https://napari.org/guides/threading.html first (because my try of introduction to this topic will be worse).

@wahabk
Copy link
Author

wahabk commented Oct 24, 2022

I think a much more elegant fix is this one 5243

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants