Image x, y coordinate of pixel from which spectrum was displayed?
pythonic2020 opened this issue ยท 10 comments
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...
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.
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.
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?
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?
Oops. I forgot to include the first line of the class definition in my code example. I just edited it. Take another look.
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.
I must have forgotten to click the update button. Check again now. Basically, I forgot this line:
class MyMouseHandler(ImageViewMouseHandler):
It worked!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! YOU CLICKED line displayed in notebook!!!!!!!!!!!! Perfect. Great job. Many thanks.
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)
I am not able to select a pixel ,
there is no popup window
@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