spectralpython / spectral

Python module for hyperspectral image processing
MIT License
571 stars 139 forks source link

Image x, y coordinate of pixel from which spectrum was displayed? #115

Closed pythonic2020 closed 3 years ago

pythonic2020 commented 3 years ago

Hello, is there a way to display the x/y coordinates of a pixel from which a spectrum was just displayed using imshow? When I double-click on a displayed image and a pixel spectrum is displayed in a new pop-up window, it would be great if the x/y coord was displayed so that I could then easily extract that spectrum from the cube using

spectrum1 = image.read_pixel(2225, 3801)

Now I have to use ENVI to get the pixel coordinate, or display the image in the normal backend using %matplotlib widget magic, with which x/y coords and band DN values are streamed when you move the mouse.

Thank you...

tboggs commented 3 years ago

Current mouse position (in pixel coordinates) are displayed in the bottom margin of the Matplotlib display, though that may not happen if you are displaying in a jupyter notebook. So the easiest (i.e., do nothing) way to get the coordinates is to note the coordinate being displayed in the window.

If you want to do a little hacking, you can override the callback that handles spectrum plotting. By default, the plot is created by the ImageViewMouseHandler. You can create a subclass that overrides the handle_event method of that class, then register your new handler for a given display. For example,

from spectral.graphics.spypylab import ImageViewMouseHandler, KeyParser
class MyMouseHandler(ImageViewMouseHandler):
    def handle_event(self, event): 
        '''Callback for click event in the image display.''' 
        if self.show_events: 
            print(event, ', key = %s' % event.key) 
        if event.inaxes is not self.view.axes: 
             return 
        (r, c) = (int(event.ydata + 0.5), int(event.xdata + 0.5)) 
        (nrows, ncols) = self.view._image_shape 
        if r < 0 or r >= nrows or c < 0 or c >= ncols: 
            return 
        kp = KeyParser(event.key) 
        if event.button == 1: 
            if event.dblclick and kp.key is None: 
                if self.view.source is not None: 
                    from spectral import settings 
                    import matplotlib.pyplot as plt 
                    print("YOU CLICKED", (r, c)) 
                    if self.view.spectrum_plot_fig_id is None: 
                         f = plt.figure() 
                         self.view.spectrum_plot_fig_id = f.number 
                    try: 
                        f = plt.figure(self.view.spectrum_plot_fig_id) 
                    except: 
                        f = plt.figure() 
                        self.view.spectrum_plot_fig_id = f.number 
                    s = f.gca() 
                    settings.plotter.plot(self.view.source[r, c], 
                                          self.view.source) 
                    s.xaxis.axes.relim() 
                    s.xaxis.axes.autoscale(True) 
                    f.canvas.draw() 

Note that the only new line above is the "YOU CLICKED" line.

To use the new callback in an image display, do something like this:

In [101]: v = spy.imshow(img[:200,:200], (40, 30, 20))
In [102]: handler = MyMouseHandler(v)
In [103]: handler.connect()

Then, whenever you double-click in the display, you'll see output like this:

YOU CLICKED (75, 74)
YOU CLICKED (123, 77)
YOU CLICKED (78, 110)

Obviously, you can change the handler however you like. So you could, for example, add a legend to the plot that displays the coordinates of the pixel or add them to a list in your mouse handler to save for subsequent processing.

pythonic2020 commented 3 years ago

WOW! Thank you so much. All I want is the quick display of clicked pixel coords, and I didn't see that in the WX-based window generated by imshow. I'll recheck it to see if I missed it. Then I'll try your solution above.

pythonic2020 commented 3 years ago

I found that the pixel coords are displayed at lower right in the WX-based new window, but are mostly obscured. See image. I guess the issue is related to the wxpython and perhaps pyopengl, both of which I had to install to get the WX backend working from a notebook. Setting figsize in imshow doesn't help. Any ideas?

Untitled

The pixel coords display normally in my notebook if a %matplotlib widget (ipympl) is used for interactive display, but then I can't double-click to get the spectral plot!

I also tried your function above, but don't understand how I define "MyMouseHandler" to read the new function.

After adding your function as you wrote, I tried this:

from spectral.graphics.spypylab import imshow 

view = imshow(data=image, bands=(76, 83, 92), origin='upper', stretch=(0.08, 0.98),
              source=image, title='SWIR False Color')

handler = MyMouseHandler(view) # Not defined...
handler.connect()

Could you please show how to define MyMouseHandler?

tboggs commented 3 years ago

Oops. I forgot to include the first line of the class definition in my code example. I just edited it. Take another look.

pythonic2020 commented 3 years ago

Hmm, I see no change. I used this code after invoking the imshow to the view variable:

handler = ImageViewMouseHandler(view)
handler.connect()

Is that correct? Since the pixel coords are mainly blocked from view at the lower right corner of window, will the "YOU CLICKED (78, 110)" even be visible? My wx backend could be the problem. I just installed wxwidgets, but that hasn't helped the obscured text issue.

tboggs commented 3 years ago

I must have forgotten to click the update button. Check again now. Basically, I forgot this line:

class MyMouseHandler(ImageViewMouseHandler):
pythonic2020 commented 3 years ago

It worked!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! YOU CLICKED line displayed in notebook!!!!!!!!!!!! Perfect. Great job. Many thanks.

pythonic2020 commented 3 years ago

Here is sample code to extract spectra from cube using Jupyter Notebook and new mouse handler from above:

import numpy as np
import matplotlib as mpl

# May need to install wxpython and pyopengl
mpl.use('WXAgg') # Must enable for spectral plots and 3-d cube, and call it before importing pyplot
import matplotlib.pyplot as plt

import spectral.io.envi as envi

# Open cube as SpyFile
cube1 = envi.open('envi_cube.hdr', image=envi_cube')

from spectral.graphics.spypylab import ImageViewMouseHandler, KeyParser
from spectral import settings

class MyMouseHandler(ImageViewMouseHandler):
    def handle_event(self, event): 
        '''Callback for click event in the image display. Will write coordinates
of clicked pixel in notebook cell. Written by tboggs.''' 
        if self.show_events: 
            print(event, ', key = %s' % event.key) 
        if event.inaxes is not self.view.axes: 
             return 
        (r, c) = (int(event.ydata + 0.5), int(event.xdata + 0.5)) 
        (nrows, ncols) = self.view._image_shape 
        if r < 0 or r >= nrows or c < 0 or c >= ncols: 
            return 
        kp = KeyParser(event.key) 
        if event.button == 1: 
            if event.dblclick and kp.key is None: 
                if self.view.source is not None:  
                    print("YOU CLICKED", (r, c)) 
                    if self.view.spectrum_plot_fig_id is None: 
                        f = plt.figure() 
                        self.view.spectrum_plot_fig_id = f.number 
                    try: 
                        f = plt.figure(self.view.spectrum_plot_fig_id) 
                    except: 
                        f = plt.figure() 
                        self.view.spectrum_plot_fig_id = f.number 
                    s = f.gca() 
                    settings.plotter.plot(self.view.source[r, c], 
                                          self.view.source) 
                    s.xaxis.axes.relim() 
                    s.xaxis.axes.autoscale(True) 
                    f.canvas.draw()

from spectral.graphics.spypylab import imshow 

# Must use full image to get accurate image coordinates for spectra. Don't view a subset!

view = imshow(data=cube1, bands=(76, 83, 92), origin='upper', stretch=(0.08, 0.98),
              source=cube1, title='Cube - Bands 76-83-92/RGB', figsize=(6, 6))

handler = MyMouseHandler(view)
handler.connect()

# Use magnify tool in pop-up window to zoom to area of interest.  Then double-click in the window to display spectrum.

# Extract spectrum from pixel selected above.  Insert your own coordinates.  Creates 1-d numpy array.
spectrum1 = cube1.read_pixel(2225, 3801) 
SondosBsharat commented 1 year ago

I am not able to select a pixel , there is no popup window

tboggs commented 1 year ago

@SondosBsharat I need more info to help you. Try this before calling spy.imshow and provide any error messages you see when double-clicking in the image:

from spectral.graphics.spypylab import MplCallback
MplCallback.raise_event_exceptions = True

If there are no exceptions generated, close the window, run the following command, then provide any output given after you double-click in the image:

MplCallback.show_events = True