Attenuated MIP Refresh

Hello Everyone,

As always, thank you for helping provide such a great collaborative community. I am having a strange issue, and I assume that there is a straightforward fix. I would like to display an image with napari in a loop, and export snapshots of each frame. However, before I got to the snapshot stage, I started having some issues with the attenuation setting. Regardless of what value I place in the python script, the image does not show a change, despite the fact that the GUI slider shows the proper position based upon the python script. The moment I touch the slider in the GUI the image immediately updates, but of course, I would like to perform this process in a loop. I assume that this must have something to do with multithreading, how the calculation is being done, or a refresh of the GUI. My short script is below, and any help is appreciated.

Thank you,
Kevin

##########

import os
import napari
from tifffile import imread

if ‘BINDER_SERVICE_HOST’ in os.environ:
os.environ[‘DISPLAY’] = ‘:1.0’

with napari.gui_qt():
viewer = napari.Viewer(ndisplay=3)
data_location = ‘/path/to/image/1’
for i in range(100):
im_number = str(i)
im_number = im_number.zfill(6)
im_name = ‘1_CH00_’ + im_number + ‘.tif’
membrane = imread(os.path.join(data_location, im_name))
viewer.add_image(data=membrane)
viewer.layers[‘membrane’].rendering = ‘attenuated_mip’
viewer.layers[‘membrane’].contrast_limits = [84, 550]
viewer.layers[‘membrane’].opacity = 1.0
viewer.layers[‘membrane’].attenuation = 0.04

1 Like

each time you add a new layer in your loop with viewer.add_image(data=membrane), that new layer is going to have a unique name (e.g. the first one will be “membrane”, the second will be “membrane [1]”, and so on).

so, with these lines here:

viewer.layers['membrane'].rendering = 'attenuated_mip'
viewer.layers['membrane'].contrast_limits = [84, 550]
viewer.layers['membrane'].opacity = 1.0
viewer.layers['membrane'].attenuation = 0.04

you’re resetting the parameters for the first layer only. If you wanted to access the most recently added layer, you can use viewer.layers[-1]. However, for this application, you’re better off just updating the layer data directly. so, grab a handle to the layer, and update the data:

viewer = napari.Viewer(ndisplay=3)
...
layer = viewer.add_image(data=membrane) # first plane
for i in range(100):
    newdata = ...
    layer.data = newdata

sidenote, you can replace this:

im_number = str(i)
im_number = im_number.zfill(6)
im_name = ‘1_CH00_’ + im_number + ‘.tif’
membrane = imread(os.path.join(data_location, im_name))

with this:

membrane = imread(os.path.join(data_location, f'1_CH00_{i:06}.tif'))
1 Like

Hello Talley,

Thank you for the tips. You are absolutely correct, each loop would add another layer, each with a unique name. For troubleshooting, I was only iterating the loop once, and my intention was to delete the layer at the end of the loop (but I did not show that in my script).

Unfortunately, the problem persists. The attenuation value I am programmatically telling napari to display is reflected by the slider, but not the image.

Best,
Kevin

with napari.gui_qt():
	viewer = napari.Viewer(ndisplay=3)
	membrane = imread(os.path.join(data_location, f'1_CH00_{1:06}.tif'))
	layer = viewer.add_image(data=membrane, rendering='attenuated_mip',
							 contrast_limits=[84, 550], opacity = 1.0, attenuation=0.04)

	for i in range(1):
		membrane = imread(os.path.join(data_location, f'1_CH00_{i:06}.tif'))
		layer.data = membrane
1 Like

hmm, strange. What is it that makes you say it’s not reflected in the image? is it something where you move the slider manually (after programmatically setting it) and then it “finally” updates the image?

If I run this simplified code, the attenuation threshold set when adding the image works fine, and persists when I add new layers.

import napari
import numpy as np

viewer = napari.Viewer(ndisplay=3)
membrane = np.random.rand(256, 256)
layer = viewer.add_image(data=membrane, rendering='attenuated_mip', attenuation=0.01)
layer.data = np.random.rand(256, 256)

maybe it would help if you could show a screenshot/gif of what it is that’s convincing you that the attenuation value on the slider is not reflected in the rendered image?

1 Like

This is what it appears like immediately after I execute the script.

And this is what it appears like after I adjust the slider by hand to the same value.

1 Like

k, unless @sofroniewn has any other ideas, that might be a bug. Can you submit an issue, including those screenshots and the script, and also the output of napari --info (looks like you’re on windows yes?)

1 Like

(and do you get the same effect with my simple script? on my system, the image is the same before and after manually moving the slider… so i’m not sure I’ve yet been able to repeat it)

Your script worked. Now when I put my data in there, it doesn’t. It almost seems like the attenuation value is rounding down from 0.02 to 0.

And I am on a Linux OS. I’m pretty confident that napari is displaying the image with an attenuation value of 0 regardless of what value I put in there.

import napari
import numpy as np
from tifffile import imread
import os

data_location = '/archive/bioinformatics/Danuser_lab/Fiolka/MicroscopeDevelopment/OPM/Drosophila/GAP43-mcherry/200703/Cell1'
membrane = imread(os.path.join(data_location, f'1_CH00_{1:06}.tif'))
membrane = np.array(membrane)

with napari.gui_qt():
    viewer = napari.Viewer(ndisplay=3)
    layer = viewer.add_image(data=membrane, rendering='attenuated_mip', attenuation=1)

Immediately after executing the script. Notice the slider bar position which reads a value of 1.

Once I manually toggle the slider bar to ~1.1 then back to 1.

Then, just to confirm the hypothesis, when I move the slider bar to 0.

I’m about to step out but can look at this soon, it might be related to https://github.com/napari/napari/issues/1398 which @jni filed last night.

Can @k-dean confirm if this is on the latest release or on the master version of napari (i.e. installed directly from GitHub) as we changed how attenuation gets set a couple days ago on master and this could be a bug that has crept in.

The output of napari --info or the napari info dialog in the help menu would tell us

1 Like

just looking quickly, it could be a discretization issue related to rounding errors forced on us by our Qt Slider and some strange loops going through our event system. Will look in more detail in ~1 hr

Sounds promising… Just checked the info. I was running off of the pip version of napari (napari 0.2.12
), but will update in the meantime from the GitHub repository

Definitely not in a rush. Enjoy your lunch!

napari: 0.3.4
Platform: Linux-3.10.0-957.el7.x86_64-x86_64-with-redhat-7.6-Maipo
Python: 3.6.10 | packaged by conda-forge | (default, Apr 24 2020, 16:44:11) [GCC 7.3.0]
Qt: 5.14.2
PyQt5: 5.14.2
NumPy: 1.18.4
SciPy: 1.4.1
Dask: 2.17.2
VisPy: 0.6.4

GL version: 2.1 Mesa 18.3.4
MAX_TEXTURE_SIZE: 8192

Plugins:

  • napari-plugin-engine: 0.1.5
  • napari_volume
  • svg: 0.1.3
1 Like

Latest update… Behavior remains the same.

napari: 0.3.6.dev11+ge43f730
Platform: Linux-3.10.0-957.el7.x86_64-x86_64-with-redhat-7.6-Maipo
Python: 3.6.10 | packaged by conda-forge | (default, Apr 24 2020, 16:44:11) [GCC 7.3.0]
Qt: 5.14.2
PyQt5: 5.14.2
NumPy: 1.18.4
SciPy: 1.4.1
Dask: 2.17.2
VisPy: 0.6.4

GL version: 2.1 Mesa 18.3.4
MAX_TEXTURE_SIZE: 8192

Plugins:

  • napari-plugin-engine: 0.1.5
  • napari_volume
  • svg: 0.1.3
1 Like

Ok, I’m back - I can reproduce, definitely a bug. Will look into fix now

1 Like

Ok, I’ve identified the problem, it comes from this line in vispy https://github.com/vispy/vispy/blob/caed4f0326dc9c0804b254339c4d89f66e6a01b6/vispy/visuals/volume.py#L598 and the fact that on startup the u_threshold is not in the shared_program. It seems to get added once things have appeared on screen, hence why things only start working when you move a slider.

I need to think about the best fix. It would be nice to fix on the vispy side, though presumably they had that line there for a reason. If we bypass that line by doing

self.node._threshold = float(attenuation)
self.node.shared_program['u_threshold'] = self.node._threshold
self.node.update()

ourselves inside here https://github.com/napari/napari/blob/e43f730170aab1e011c42060c65ba44133f6f18f/napari/_vispy/vispy_image_layer.py#L225 then it works fine. Curious what @talley thinks we should do. We can probably move this convo to github as it’s a pretty clear bug at this point.

Thanks for letting us know about this one @k-dean!!

2 Likes

Awesome, thanks for the quick look! I went ahead and summarized some of the findings here as a GitHub issue (#1399).

2 Likes