Giter VIP home page Giter VIP logo

napari-plot-profile's Introduction

napari-plot-profile (npp)

License PyPI Python Version tests codecov Development Status napari hub

Plot a Line Profile

Plot intensities along a line in napari.

img.png

  • Open some images in napari.

  • Add a shapes layer.

img.png

  • Activate the line drawing tool or the path tool and draw a line.

img.png

  • After drawing a line, click on the menu Plugins > Measurements (Plot Profile)
  • If you modify the line, you may want to click the "Refresh" button to redraw the profile.

img.png

To see how these steps can be done programmatically from python, check out the demo notebook

Create a Topographical View

Create a 3D view of a 2D image by warping pixel intensities to heights using the menu Tools > Visualization > Topographical view (npp). It can be displayed as a 3D image layer, a points cloud layer or a surface layer.

This napari plugin was generated with Cookiecutter using with @napari's cookiecutter-napari-plugin template.


Installation

You can install napari-plot-profile via pip:

pip install napari-plot-profile

Contributing

Contributions are very welcome. Tests can be run with tox, please ensure the coverage at least stays the same before you submit a pull request.

License

Distributed under the terms of the BSD-3 license, "napari-plot-profile" is free and open source software

Issues

If you encounter any problems, please create a thread on image.sc along with a detailed description and tag @haesleinhuepf.

napari-plot-profile's People

Contributors

haesleinhuepf avatar zoccoler avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

napari-plot-profile's Issues

Is there a more elegant way to send the profile table to the Python environement?

First, thanks you, that's a very nice tool!

Otherwise, I wanted to calculated the area under my peaks in a profile (with Python) but I could not find a way to export the profile in my environement.
Maybe I missed something obvious...
But in the mean time I did something pretty dirty:
I added a new function to your code but I'm not that experienced so there is some part of your code I don't get entirely. At the end this solution took me more time than saving as csv and reopening it...

def _list_out(self):
        table = {}
        for my_profile in self.data:
            positions = np.asarray(my_profile['positions'])
            for i, x in enumerate(positions[0]):
                table[my_profile['name'] + '_pos' + str(i)] = positions[:, i]

            table[my_profile['name'] + '_intensity'] = my_profile['intensities']
            table[my_profile['name'] + '_distance'] = my_profile['distances']

        return table

It works nice when I plot from the console like:

from napari_plot_profile import PlotProfile
profiler = PlotProfile(viewer)
my_profile=profiler._list_out()

So I would like your opinion on the matter.

EDIT: Sorry, nevermind, found it

my_profile=PlotProfile(viewer).data

Plot not (always) what I expect, plus UI Comments

Awesome plugin—I'd put together something similar using some napari examples and help from napari.zulip, but this is much more polished.
Main comment:

  1. Sometimes the plots are not what I expect? See example below:
    image
    I think I have the line draw fairly symmetrical and mousing over points returns values that are in agreement with the matplotlib graph my widget generates. But your plugin graph is odd, very asymmetrical and just one peak. Is it not actually doing line profiles but something different? Is it due to the angle, so pixels are not neighboring correctly?
    For reference, I'm using measure.profile_line from skimage for my plot—again based on napari/examples (my code in gist at bottom, with some comments)

Minor comments:

  1. It's cool that the plot lines use the img LUTs, but initially I thought the plugin just didn't work, because my preferred LUT is inverted, so I get black on black. (You can see the same issue with my matplotlib below: black on black for the case of the text. derp.)
    image
    Once I figured out it was from the Image LUT, I was again confused because changing the image LUT didn't change the lines color. Eventually I figured out you change the image LUT and then have to go back to the Shapes layer for it to trigger.
    So in the end I think I would prefer the option to use the Shapes line color, since it's more intuitive?
  2. On a related note, switching to the image layer makes the graph disappear. This is somewhat odd, because I need to be on the image layer to so the cursor returns values in the status bar.
  3. When I draw lines, e.g. below, the plot is reversed. I drew the line from top to bottom so I'd expect the graph to go from start to finish of the line, as in the plot below the image.
    image
  4. Sometimes the refresh & live-refresh ceases to work? I think it's with z-slices when I draw on one slice then on another and then go back. I assume the last line is "live" on a different slice, so the earlier one doesn't work. Drawing a new line on the old slice makes it work again...

Here's a link to my hacked together widget, which should run:
(FYI The examples above were with a different approach where I get the shape layer and slice.)
https://gist.github.com/psobolewskiPhD/53eec7440eda501d9edf08a99659f6b0
Ironically, this example doesn't work with your plugin. Traceback:

Traceback (most recent call last): File "/Users/piotrsobolewski/Dev/miniforge3/envs/TF-StarDist-napari/lib/python3.9/site-packages/napari_plot_profile/_dock_widget.py", line 101, in _on_selection self.redraw(force_redraw=True) File "/Users/piotrsobolewski/Dev/miniforge3/envs/TF-StarDist-napari/lib/python3.9/site-packages/napari_plot_profile/_dock_widget.py", line 158, in redraw my_profile = profile(layer, line, num_points=num_bins) File "/Users/piotrsobolewski/Dev/miniforge3/envs/TF-StarDist-napari/lib/python3.9/site-packages/napari_plot_profile/_dock_widget.py", line 219, in profile start = line[min(current_line, len(line) - 1)] / layer.scale ValueError: operands could not be broadcast together with shapes (2,) (3,)
This blocks all interaction with the shapes layer and breaks the plugin. Is there a way to *switch off* a plugin? the [x] just closes it, but it's still listed in the Window menu.

I think because I take the route of making a 2D generic shapes layer that appears on all slices, since for a "tool" it was more intuitive to me to see a line profile for the same physical line in z or t. It helped get around the issue of having to re-draw a line on a different slice if I didn't delete the previous one. I still want advancing slices to trigger the replotting too...Edit: got it working! updated gist.
Just passing this to napari triggers the traceback:

line = np.array([[11, 13], [111, 113]])
line_prof_layer = viewer.add_shapes(
    line, shape_type="line", edge_color="red", name="Line Profile"
)

Interpolation

It would be cool to have some interpolation for the measurements. It might also make sense to take the thickness of the annotated line into account and average over a small region. For that, we should figure out, how exactly this is done in other tools such as ImageJ

Issue with points on multiple Z-slices

Reported by @psobolewskiPhD in #3

  1. Sometimes the refresh & live-refresh ceases to work? I think it's with z-slices when I draw on one slice then on another and then go back. I assume the last line is "live" on a different slice, so the earlier one doesn't work. Drawing a new line on the old slice makes it work again...

Plot invisible if working with a LUT that has black as maximum color

Reported by @psobolewskiPhD in #3

  1. It's cool that the plot lines use the img LUTs, but initially I thought the plugin just didn't work, because my preferred LUT is inverted, so I get black on black. (You can see the same issue with my matplotlib below: black on black for the case of the text. derp.)
    image
    Once I figured out it was from the Image LUT, I was again confused because changing the image LUT didn't change the lines color. Eventually I figured out you change the image LUT and then have to go back to the Shapes layer for it to trigger.
    So in the end I think I would prefer the option to use the Shapes line color, since it's more intuitive?

Plot intesnity profile along time-series

Is there a way to plot the intensity profile for each time point in a time series using the same line? and then have the csv files with the raw values?

And secondly is there a way to just export the intensity plots directly from napari?

Thank you.

Scaling issue

Reported by @psobolewskiPhD in #3:

  1. Sometimes the plots are not what I expect? See example below:
    image
    I think I have the line draw fairly symmetrical and mousing over points returns values that are in agreement with the matplotlib graph my widget generates. But your plugin graph is odd, very asymmetrical and just one peak. Is it not actually doing line profiles but something different? Is it due to the angle, so pixels are not neighboring correctly?
    For reference, I'm using measure.profile_line from skimage for my plot—again based on napari/examples (my code in gist at bottom, with some comments)

Here's a link to my hacked together widget, which should run:
(FYI The examples above were with a different approach where I get the shape layer and slice.)
https://gist.github.com/psobolewskiPhD/53eec7440eda501d9edf08a99659f6b0
Ironically, this example doesn't work with your plugin. Traceback:

Traceback (most recent call last): File "/Users/piotrsobolewski/Dev/miniforge3/envs/TF-StarDist-napari/lib/python3.9/site-packages/napari_plot_profile/_dock_widget.py", line 101, in _on_selection self.redraw(force_redraw=True) File "/Users/piotrsobolewski/Dev/miniforge3/envs/TF-StarDist-napari/lib/python3.9/site-packages/napari_plot_profile/_dock_widget.py", line 158, in redraw my_profile = profile(layer, line, num_points=num_bins) File "/Users/piotrsobolewski/Dev/miniforge3/envs/TF-StarDist-napari/lib/python3.9/site-packages/napari_plot_profile/_dock_widget.py", line 219, in profile start = line[min(current_line, len(line) - 1)] / layer.scale ValueError: operands could not be broadcast together with shapes (2,) (3,)
This blocks all interaction with the shapes layer and breaks the plugin. Is there a way to *switch off* a plugin? the [x] just closes it, but it's still listed in the Window menu.

I think because I take the route of making a 2D generic shapes layer that appears on all slices, since for a "tool" it was more intuitive to me to see a line profile for the same physical line in z or t. It helped get around the issue of having to re-draw a line on a different slice if I didn't delete the previous one. I still want advancing slices to trigger the replotting too...Edit: got it working! updated gist.
Just passing this to napari triggers the traceback:

line = np.array([[11, 13], [111, 113]])
line_prof_layer = viewer.add_shapes(
    line, shape_type="line", edge_color="red", name="Line Profile"
)

Plot along a line in 3D

It would be cool if a user clicked in 3D, that we see a plot-profile along the ray the user is viewing at the moment.

Profile on scaled images not working

Hi @haesleinhuepf ,
not sure if this is expected, but I am observing a weird behavior when trying to make a plot profile after adding this image to the viewer:

from napari.viewer import Viewer
from aicsimageio import AICSImage
from aicsimageio.readers import BioformatsReader

fname = 'path-to-tif-file'
image = AICSImage(fname, reader=BioformatsReader)

napari_viewer = Viewer()
napari_viewer.add_image( image.data, name='image', colormap='blue')
napari_viewer.add_image( image.data,  
                         name='image_scaled', 
                         scale=(
                            image.physical_pixel_sizes.Z/image.physical_pixel_sizes.X,
                            1,
                            1
                         )
                         colormap='green'
                       )

All I did in the second image, is scale the Z dimension to account for the anisotropy.

test_napari_plot_profile

As you can see, the blue and green profiles are different. I had a look at this solved issue but could not find a straightforward solution.

On a similar note, when adding the image to the viewer to take into account the XYZ voxel size:

from napari.viewer import Viewer
from aicsimageio import AICSImage
from aicsimageio.readers import BioformatsReader

fname = 'path-to-tif-file'
image = AICSImage(fname, reader=BioformatsReader)

napari_viewer = Viewer()
napari_viewer.add_image( image.data,  
                         name='image_scaled', 
                         scale=image.physical_pixel_sizes,
                         colormap='blue'
                       )

I do not obtain any plot profile:

test_napari_plot_profile_2

Thanks so much for any help/suggestion!


EDIT:
I realize the first example is not ideal because, in the same viewer, the planes visualized are not the same due to the different Z scaling. So, here is a better example:

from napari.viewer import Viewer
from aicsimageio import AICSImage
from aicsimageio.readers import BioformatsReader

fname = 'path-to-tif-file'
image = AICSImage(fname, reader=BioformatsReader)

napari_viewer1 = Viewer()
napari_viewer1.add_image( image.data, name='image', colormap='blue')
napari_viewer2 = Viewer()
napari_viewer2.add_image( image.data,  
                         name='image_scaled', 
                         scale=(
                            image.physical_pixel_sizes.Z/image.physical_pixel_sizes.X,
                            1,
                            1
                         )
                         colormap='green'
                       )

Which produces those two different plot profiles (now looking at the same Z plane).

test_napari_plot_profile_not_scaled
test_napari_plot_profile_scaled

ValueError: operands could not be broadcast together with shapes (3,) (2,)

Hi,

This is excellent functionality (I'm evaluating if I can switch from FIJI:)

However when I open an image, draw a line and run the plugin, I get a
ValueError: operands could not be broadcast together with shapes (3,) (2,) exception
image

Env info:

  • napari: 0.4.18

  • Platform: Windows-10-10.0.19045-SP0

  • Python: 3.10.6 | packaged by conda-forge | (main, Aug 22 2022, 20:29:51) [MSC v.1929 64 bit (AMD64)]

  • Qt: 5.15.2

  • PyQt5: 5.15.4

  • NumPy: 1.25.2

  • SciPy: 1.9.3

  • Dask: 2022.11.0

  • VisPy: 0.12.2

  • magicgui: 0.6.0

  • superqt: 0.6.1

  • in-n-out: 0.1.6

  • app-model: 0.1.1

  • npe2: 0.6.1

Full traceback is here:

ValueError Traceback (most recent call last)
File C:\bin\python\anaconda64\envs\py10image\lib\site-packages\napari_qt\menus\plugins_menu.py:105, in PluginsMenu._add_plugin_actions.._add_toggle_widget(key=('napari-plot-profile', 'Plot Profile'), hook_type='dock')
102 return
104 if hook_type == 'dock':
--> 105 self._win.add_plugin_dock_widget(*key)
key = ('napari-plot-profile', 'Plot Profile')
self._win = <napari._qt.qt_main_window.Window object at 0x000001FD51760580>
self = <napari._qt.menus.plugins_menu.PluginsMenu object at 0x000001FD61182170>
106 else:
107 self._win._add_plugin_function_widget(*key)

File C:\bin\python\anaconda64\envs\py10image\lib\site-packages\napari_qt\qt_main_window.py:811, in Window.add_plugin_dock_widget(self=<napari._qt.qt_main_window.Window object>, plugin_name='napari-plot-profile', widget_name='Plot Profile', tabify=False)
808 wdg = wdg._magic_widget
809 return dock_widget, wdg
--> 811 wdg = _instantiate_dock_widget(
Widget = <class 'napari_plot_profile._dock_widget.PlotProfile'>
self = <napari._qt.qt_main_window.Window object at 0x000001FD51760580>
812 Widget, cast('Viewer', self._qt_viewer.viewer)
813 )
815 # Add dock widget
816 dock_kwargs.pop('name', None)

File C:\bin\python\anaconda64\envs\py10image\lib\site-packages\napari_qt\qt_main_window.py:1465, in _instantiate_dock_widget(wdg_cls=<class 'napari_plot_profile._dock_widget.PlotProfile'>, viewer=Viewer(camera=Camera(center=(0.0, 265.5, 270.5),...ouse_drag_gen={}, _mouse_wheel_gen={}, keymap={}))
1460 break
1461 # cannot look for param.kind == param.VAR_KEYWORD because
1462 # QWidget allows **kwargs but errs on unknown keyword arguments
1463
1464 # instantiate the widget
-> 1465 return wdg_cls(**kwargs)
kwargs = {'napari_viewer': Viewer(camera=Camera(center=(0.0, 265.5, 270.5), zoom=1.707950148809524, angles=(0.0, 0.0, 90.0), perspective=0.0, mouse_pan=False, mouse_zoom=True), cursor=Cursor(position=(4.075508866833751, -126.7598387528613), scaled=True, size=1, style=<CursorStyle.CROSS: 'cross'>), dims=Dims(ndim=2, ndisplay=2, last_used=0, range=((0.0, 532.0, 1.0), (0.0, 542.0, 1.0)), current_step=(265, 270), order=(0, 1), axis_labels=('0', '1')), grid=GridCanvas(stride=1, shape=(-1, -1), enabled=False), layers=[<Image layer '20231115-172120_WT' at 0x1fd6120f9d0>, <Shapes layer 'Shapes' at 0x1fd54355780>], help='use <6> for pan/zoom, use
<2> for transform, use for add rectangles, use for add ellipses, use for add path, use

for add polygons, use for add polygons lasso, use <4> for select vertices, use <5> for select shapes, use <2> for insert vertex, use <1> for remove vertex', status='', tooltip=Tooltip(visible=False, text=''), theme='dark', title='napari', mouse_over_canvas=False, mouse_move_callbacks=[], mouse_drag_callbacks=[], mouse_double_click_callbacks=[], mouse_wheel_callbacks=[<function dims_scroll at 0x000001FD50C0BE20>], _persisted_mouse_event={}, _mouse_drag_gen={}, _mouse_wheel_gen={}, keymap={})}
wdg_cls = <class 'napari_plot_profile._dock_widget.PlotProfile'>

File C:\bin\python\anaconda64\envs\py10image\lib\site-packages\napari_plot_profile_dock_widget.py:121, in PlotProfile.init(self=<napari_plot_profile._dock_widget.PlotProfile object>, napari_viewer=Viewer(camera=Camera(center=(0.0, 265.5, 270.5),...ouse_drag_gen={}, _mouse_wheel_gen={}, keymap={}))
117 self._timer.stop()
119 self._timer.start()
--> 121 self.redraw()
self = <napari_plot_profile._dock_widget.PlotProfile object at 0x000001FD547FF9A0>

File C:\bin\python\anaconda64\envs\py10image\lib\site-packages\napari_plot_profile_dock_widget.py:196, in PlotProfile.redraw(self=<napari_plot_profile._dock_widget.PlotProfile object>, force_redraw=False)
193 self._data = []
194 for i, layer in enumerate(self.selected_image_layers()):
195 # plot profile
--> 196 my_profile = profile(layer, line, num_points=num_bins)
line = <class 'numpy.ndarray'> (2, 2) float64
num_bins = 100
layer = <Image layer '20231115-172120_WT' at 0x1fd6120f9d0>
197 my_profile['name'] = layer.name
198 self._data.append(my_profile)

File C:\bin\python\anaconda64\envs\py10image\lib\site-packages\napari_plot_profile_dock_widget.py:272, in profile(layer=<Image layer '20231115-172120_WT'>, line=<class 'numpy.ndarray'> (2, 2) float64, num_points=100)
270 # check if point still within image
271 position_clipped = np.maximum(position, np.zeros(position.shape))
--> 272 position_clipped = np.minimum(position_clipped, layer.data.shape - np.ones(position.shape))
position = <class 'numpy.ndarray'> (2,) float64
position_clipped = <class 'numpy.ndarray'> (2,) float64
layer = <Image layer '20231115-172120_WT' at 0x1fd6120f9d0>
np.minimum = <ufunc 'minimum'>
np = <module 'numpy' from 'C:\bin\python\anaconda64\envs\py10image\lib\site-packages\numpy\init.py'>
273 if np.array_equal(position, position_clipped):
274 position = position.astype(int)

ValueError: operands could not be broadcast together with shapes (3,) (2,)

Number of points should be capped at line length

At first I didn't 'get' what number of points did. But now I see it, it's the plotting points.
However, if the line length is less than the number of points, default 100, then artifacts are produced:
image
y-values are just duplicated to 'fill' the empty points, so those flat sections aren't real.

Here's the actual data with Num points = to actual number of pixels measured.
image
Careful inspection of the image layer confirms that this is the correct plot, not the one above for 100 points.
Number of points should thus cap at the number of pixels of the line, the number of actual data points.
I can understand downsampling the plot in the case of long lines and many points, such that less points are plotted than measured, but the other direction is definitely not correct.
BTW Perhaps downsampling is a better way of conveying what 'Number of points' is supposed to do?

User-interface unresponsive when loading images via aicsimageio

When napari-acisimageio is installed, TIF image are loaded in a different way. The live-update doesn't work with those images efficiently. Napari freezed then. It appears the images are then no numpy-arrays (but dask arrays?) and thus, pixel-wise access is slower.

Workaround: Convert the images to numpy arrays first, for example using Plugins > clEsperanto > Convert to numpy. You may have to install napari-pyclesperanto-assistant to get access to this menu.

Long-term solution: Make a numpy array internally and cache it. This may cause isssues with large image data.

CC @JacksonMaxfield: Do you have a better idea how to deal with this? Thanks!

Plot inversed (left-right)

Reported by @psobolewskiPhD in #3

  1. When I draw lines, e.g. below, the plot is reversed. I drew the line from top to bottom so I'd expect the graph to go from start to finish of the line, as in the plot below the image.
    image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.