Open jhennawi opened 4 years ago
I can only advise from the plugin side. You might be able to pull it off by writing something like PixTable
and make it display values from another extension. Maybe my implementation over at stginga to read values from another extension can inspire you?
I can display DQ straight on active image using DQInspect, so I don't see why you cannot similarly display values from another extension on yours.
@jhennawi, can you clarify just a little? Do you have to construct an auxiliary image or is that simply for the convenience of making the wavelength values conveniently accessible? Is the original data a cube?
Hi Eric,
Thanks for the quick reply. We have two images say, a science image and wavelength image. At the moment these are images. In the future for IFUs these may be cubes, and then we would want something like ds9's cube functionality, but were not there yet.
In reality we have like 4 different science diagnostic images (raw data, sky-subtracted, sky-subtracted noise normalized, etc.) and then we have a common wavelength image for them all. What we want to be able to do is use ginga in WCS registered mode and toggle around all these images (that currently works). We then want to be able to see the wavelength value at the bottom of the screen based on the pixel we are hovering over, as derived from the wavelength image.
As I mentioned, we hacked this in with the WCS, but then that breaks WCS registration. I'll add finally that the WCS registration is just based on aligning the pixels, i.e. we are not actually using a full WCS, since these are spectra.
Joe
On Tue, May 26, 2020 at 5:04 PM ejeschke notifications@github.com wrote:
@jhennawi https://github.com/jhennawi, can you clarify just a little? Do you have to construct an auxiliary image or is that simply for the convenience of making the wavelength values conveniently accessible? Is the original data a cube?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ejeschke/ginga/issues/842#issuecomment-634344177, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACC6HI7APGYXUHPDA2B65E3RTRKKDANCNFSM4NKEOG2A .
I'll add finally that the WCS registration is just based on aligning the pixels, i.e. we are not actually using a full WCS, since these are spectra.
So the wavelength value for a given pixel at (X, Y) (data coords) is just the value of the wavelength array at (X, Y)?
that is right.
On Tue, May 26, 2020 at 5:31 PM ejeschke notifications@github.com wrote:
I'll add finally that the WCS registration is just based on aligning the pixels, i.e. we are not actually using a full WCS, since these are spectra.
So the wavelength value for a given pixel at (X, Y) (data coords) is just the value of the wavelength array at (X, Y)?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ejeschke/ginga/issues/842#issuecomment-634352518, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACC6HI4WAYRHD4FQUWLVGFLRTRNPDANCNFSM4NKEOG2A .
@jhennawi, do you want both the Info
(to the left) panel and the Cursor
panel (on the bottom) to read the same way? Will you be mixing viewing of spectra and camera images in the same viewer?
Hi Eric,
It would be convenient to have a Wavelength or Wave entry on both the left (Info) and the bottom (Cursor) panels. We will not be mixing camera and spectra in the same viewer for the present, so having these ready consistently is better I think. If we have multiple images next to each other in channels now they would all be spectra. I can imagine a future application with IFU pseudo images and Camera images next to each other in different channels where it would be nice to know the wavelength of the IFU pseudo image in the image cube, but we are nowhere near there yet.
Joe
On Wed, May 27, 2020 at 3:27 PM ejeschke notifications@github.com wrote:
@jhennawi https://github.com/jhennawi, do you want both the Info (to the left) panel and the Cursor panel (on the bottom) to read the same way? Will you be mixing viewing of spectra and camera images in the same viewer?
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ejeschke/ginga/issues/842#issuecomment-634976665, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACC6HIYBH2B4TOMESVVGHSDRTWHVBANCNFSM4NKEOG2A .
@jhennawi, this sounds pretty simple, I think. I've long wanted to refactor the way that the cursor readout is handled, because I think this won't be the last time we'll want to customize it. I am now thinking about the best way to go about that. From the PR I'll link back to this issue.
All good Eric, many thanks!
On Mon, Jun 1, 2020 at 4:10 PM ejeschke notifications@github.com wrote:
@jhennawi https://github.com/jhennawi, this sounds pretty simple, I think. I've long wanted to refactor the way that the cursor readout is handled, because I think this won't be the last time we'll want to customize it. I am now thinking about the best way to go about that. From the PR I'll link back to this issue.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ejeschke/ginga/issues/842#issuecomment-637173016, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACC6HI5HD6ADPRGM3I3JGG3RUQYN7ANCNFSM4NKEOG2A .
Hi Joe. So after thinking about this request for a little bit I think it can be pretty easily handled via the existing logic if you are willing to launch Ginga with a custom plugin.
Here is a custom plugin called "Pypelt":
import numpy as np
from ginga import GingaPlugin
from ginga.AstroImage import AstroImage
class PypeltImage(AstroImage):
"""
Custom image type for Pypelt
"""
def __init__(self, wav_np=None, **kwargs):
AstroImage.__init__(self, **kwargs)
self.wav_np = wav_np
def info_xy(self, data_x, data_y, settings):
info = super(PypeltImage, self).info_xy(data_x, data_y, settings)
try:
# We report the value across the pixel, even though the coords
# change halfway across the pixel
_d_x, _d_y = (int(np.floor(data_x + 0.5)),
int(np.floor(data_y + 0.5)))
_ht, _wd = self.wav_np.shape
if 0 <= _d_y < _ht and 0 <= _d_x < _wd:
# spectral wavelength is stored in auxillary array
wavelength = self.wav_np[_d_y, _d_x]
# choose your best formatting here...
wav_s = "{:<14.6g}".format(wavelength)
else:
wav_s = ''
info.update(dict(ra_lbl="\u03bb", ra_txt=wav_s,
dec_lbl='', dec_txt=''))
except Exception as e:
self.logger.error("Error getting wavelength value: {}".format(e),
exc_info=True)
return info
class Pypelt(GingaPlugin.GlobalPlugin):
def __init__(self, fv):
super(Pypelt, self).__init__(fv)
def load_buffer(self, imname, chname, img_buf, dims, dtype,
header, wav_buf, wav_dtype, metadata):
"""Display a FITS image buffer.
Parameters
----------
imname : string
a name to use for the image in Ginga
chname : string
channel in which to load the image
img_buf : bytes
the image data, as a buffer
dims : tuple
image dimensions in pixels (usually (height, width))
dtype : string
numpy data type of encoding (e.g. 'float32')
header : dict
fits file header as a dictionary
wav_buf : bytes
the wavelength data, as a buffer
wav_dtype : string
numpy data type of wav_buf array encoding (e.g. 'float32')
metadata : dict
other metadata about image to attach to image
Returns
-------
0
Notes
-----
* Get array dims: data.shape
* Get array dtype: str(data.dtype)
* Make a string from a numpy array: buf = grc.Blob(data.tobytes())
"""
self.logger.info("received image data len=%d" % (len(img_buf)))
# Unpack the data
try:
# dtype string works for most instances
if dtype == '':
dtype = np.float
byteswap = metadata.get('byteswap', False)
# unpack the auxillary wavelength file
data = np.fromstring(wav_buf, dtype=wav_dtype)
if byteswap:
data.byteswap(True)
wav_np = data.reshape(dims)
# Create image container
image = PypeltImage(logger=self.logger, wav_np=wav_np)
image.load_buffer(img_buf, dims, dtype, byteswap=byteswap,
metadata=metadata)
image.update_keywords(header)
image.set(name=imname, path=None)
except Exception as e:
# Some kind of error unpacking the data
errmsg = "Error creating image data for '%s': %s" % (
imname, str(e))
self.logger.error(errmsg)
raise GingaPlugin.PluginError(errmsg)
# Display the image
channel = self.fv.gui_call(self.fv.get_channel_on_demand, chname)
# Note: this little hack needed to let window resize in time for
# file to auto-size properly
self.fv.gui_do(self.fv.change_channel, channel.name)
self.fv.gui_do(self.fv.add_image, imname, image,
chname=channel.name)
return 0
def __str__(self):
return 'pypelt'
If you save this as a file in $HOME/.ginga/plugins/Pypelt.py
, and invoke ginga by:
$ ginga --loglevel=20 --stderr --modules=Pypelt,RC
you can then load your file like so from Python:
sh = viewer.shell()
# image name "foo", channel "Image", data is ndarray of float, aux is wavelength data of same
# dimensions (also float), d is a dictionary of FITS header keys and values
args = ["foo", "Image", grc.Blob(data.tobytes()), data.shape, 'float', d, grc.Blob(aux.tobytes()), 'float', {}]
sh.call_global_plugin_method('Pypelt', 'load_buffer', args, {})
The good thing about this solution is that you will have established a "beachhead" in Ginga with your own plugin. You can then begin to add more methods or even a GUI, a plugin settings configuration, etc.
Great! We will give this a try and get back to you.
Thanks, Joe
On Mon, Jun 22, 2020 at 4:23 PM ejeschke notifications@github.com wrote:
Hi Joe. So after thinking about this request for a little bit I think it can be pretty easily handled via the existing logic if you are willing to launch Ginga with a custom plugin.
Here is a custom plugin called "Pypelt":
import numpy as np from ginga import GingaPluginfrom ginga.AstroImage import AstroImage
class PypeltImage(AstroImage): """ Custom image type for Pypelt """ def init(self, wav_np=None, **kwargs):
AstroImage.__init__(self, **kwargs) self.wav_np = wav_np def info_xy(self, data_x, data_y, settings): info = super(PypeltImage, self).info_xy(data_x, data_y, settings) try: # We report the value across the pixel, even though the coords # change halfway across the pixel _d_x, _d_y = (int(np.floor(data_x + 0.5)), int(np.floor(data_y + 0.5))) _ht, _wd = self.wav_np.shape if 0 <= _d_y < _ht and 0 <= _d_x < _wd: # spectral wavelength is stored in auxillary array wavelength = self.wav_np[_d_y, _d_x] # choose your best formatting here... wav_s = "{:<14.6g}".format(wavelength) else: wav_s = '' info.update(dict(ra_lbl="\u03bb", ra_txt=wav_s, dec_lbl='', dec_txt='')) except Exception as e: self.logger.error("Error getting wavelength value: {}".format(e), exc_info=True) return info
class Pypelt(GingaPlugin.GlobalPlugin):
def __init__(self, fv): super(Pypelt, self).__init__(fv) def load_buffer(self, imname, chname, img_buf, dims, dtype, header, wav_buf, wav_dtype, metadata): """Display a FITS image buffer. Parameters ---------- imname : string a name to use for the image in Ginga chname : string channel in which to load the image img_buf : bytes the image data, as a buffer dims : tuple image dimensions in pixels (usually (height, width)) dtype : string numpy data type of encoding (e.g. 'float32') header : dict fits file header as a dictionary wav_buf : bytes the wavelength data, as a buffer wav_dtype : string numpy data type of wav_buf array encoding (e.g. 'float32') metadata : dict other metadata about image to attach to image Returns ------- 0 Notes ----- * Get array dims: data.shape * Get array dtype: str(data.dtype) * Make a string from a numpy array: buf = grc.Blob(data.tobytes()) """ self.logger.info("received image data len=%d" % (len(img_buf))) # Unpack the data try: # dtype string works for most instances if dtype == '': dtype = np.float byteswap = metadata.get('byteswap', False) # unpack the auxillary wavelength file data = np.fromstring(wav_buf, dtype=wav_dtype) if byteswap: data.byteswap(True) wav_np = data.reshape(dims) # Create image container image = PypeltImage(logger=self.logger, wav_np=wav_np) image.load_buffer(img_buf, dims, dtype, byteswap=byteswap, metadata=metadata) image.update_keywords(header) image.set(name=imname, path=None) except Exception as e: # Some kind of error unpacking the data errmsg = "Error creating image data for '%s': %s" % ( imname, str(e)) self.logger.error(errmsg) raise GingaPlugin.PluginError(errmsg) # Display the image channel = self.fv.gui_call(self.fv.get_channel_on_demand, chname) # Note: this little hack needed to let window resize in time for # file to auto-size properly self.fv.gui_do(self.fv.change_channel, channel.name) self.fv.gui_do(self.fv.add_image, imname, image, chname=channel.name) return 0 def __str__(self): return 'pypelt'
If you save this as a file in $HOME/.ginga/plugins/Pypelt.py, and invoke ginga by:
$ ginga --loglevel=20 --stderr --modules=Pypelt,RC
you can then load your file like so from Python:
sh = viewer.shell()# image name "foo", channel "Image", data is ndarray of float, aux is wavelength data of same# dimensions (also float), d is a dictionary of FITS header keys and valuesargs = ["foo", "Image", grc.Blob(data.tobytes()), data.shape, 'float', d, grc.Blob(aux.tobytes()), 'float', {}]sh.call_global_plugin_method('Pypelt', 'load_buffer', args, {})
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/ejeschke/ginga/issues/842#issuecomment-647817706, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACC6HIZ3RHQAOYEN7OTK65TRX7RXPANCNFSM4NKEOG2A .
Dear @ejeschke,
I finally had a chance to implement this. It works beautifully -- many thanks for your help. The one question I have is whether it is possible for the plugin to live somewhere else besides $HOME/.ginga/plugins/Pypelt.py, and if we can somehow tell ginga to look elsewhere on launch.
The issue is that PypeIt is pip installable etc. and we don't think it is appropriate (may also not be possible) to automatically install a file in someone's home directory. We would prefer the plugin file to reside in the PypeIt code directory, and tell ginga to look in a different place via launch. I understand this may not be possible, but I thought I would just ask.
Thanks! Joe Hennawi
@jhennawi, the plugin module simply needs to be in the import path. Are you launching the reference viewer from your application, or is it expected to be launched by the user independently? If you are launching it, simply set the PYTHONPATH
to include the directory where the plugin lives--and that can be in your PyPelt
install area (or wherever you decide to put the plugin).
If you'd prefer the user to be able to launch the reference viewer and find your custom plugin, I'd recommend using the custom plugin template. The instructions specify how to make a standalone install package, but you could adapt them to doing the install as part of the PyPelt install without having a separate package, etc.
Hi @ejeschke,
We usually have the user launch ginga themselves, but if a script that needs ginga is run and ginga is not yet launched, then we launch if for them. I'll take a look at the custom plugin template, and get back to you. Many thanks for your help with this.
Dear Ginga Developers,
We have been developing the PypeIt spectroscopic data reduction package (see https://arxiv.org/abs/2005.06505) and we use ginga as our data visualization viewer. We are anticipating widespread adoption by spectroscopists. Four viewing spectra, it would be very valuable if we could display the wavelengths as a second set of values as we move the cursor around the image, analogous to the alpha and delta WCS coordinates. Currently in order to get this to work we have to use an unpleasant hack, i.e in RC mode:
ch.load_np(chname, img, 'fits', header, wcs_image='wavelengths.fits')
Which then allows us to display wavelengths instead of the WCS. Besides being clunky, i.e. we have to write out the wavlengths.fits file, this also had the unwanted side-effect of then breaking the WCSMatch feature, when we want to display multiple registered images (i.e. raw data and sky-subtracted spectra) next to each other.
We would greatly appreciate an option like to simply pass in an image
ch.load_np(chname, img, 'fits', header, waveimg = waveimg)
That would allow us to display the wavelength image directly with "Wavelength" being currently displayed as alpha and delta are.
Would it be possible to add such functionality? We realize this could probably be accommodated with a plugin, but we have thus far not been able to venture that deeply into the ginga source to figure out how to do so.
Many Thanks, Joe Hennawi on behalf of the PypeIt team