# This is open-source software licensed under a BSD license.
# Please see the file LICENSE.txt for details.
"""
The ``Info`` plugin provides a pane of commonly useful metadata about the
focused channel image. Common information includes some metadata
header values, coordinates, dimensions of the image, minimum and
maximum values, etc. As the cursor is moved around the image, the X, Y,
Value, RA, and DEC values are updated to reflect the value under the cursor.
**Plugin Type: Global**
``Info`` is a global plugin. Only one instance can be opened.
**Usage**
At the bottom of the ``Info`` interface are the color distribution
and cut levels controls. The selector above the cut levels boxes lets
you chose from several distribution algorithms that map the values in
the image to the color map. Choices are "linear", "log", "power", "sqrt",
"squared", "asinh", "sinh", and "histeq" (histogram equalization).
Below this, the low and high cut levels are shown and can be adjusted.
Pressing the "Auto Levels" button will recalculate cut levels based on
the current auto cut levels algorithm and parameters defined in the channel
preferences.
Below the "Auto Levels" button, the status of the settings for
"Cut New", "Zoom New", and "Center New" are shown for the currently active
channel. These indicate how new images that are added to the channel
will be affected by auto cut levels, fitting to the window and panning
to the center of the image.
The "Follow New" checkbox controls whether the viewer will automatically
display new images added to the channel. The "Raise New" checkbox controls
whether an image viewer window is raised when a new image is added. These
two controls can be useful, for example, if an external program is adding
images to the viewer, and you wish to prevent interruption of your work
examining a particular image.
As a global plugin, ``Info`` responds to a change of focus to a new channel
by displaying the metadata from the new channel.
It typically appears under the "Synopsis" tab in the user interface.
This plugin is not usually configured to be closeable, but the user can
make it so by setting the "closeable" setting to True in the configuration
file--then Close and Help buttons will be added to the bottom of the UI.
"""
import time
import numpy as np
from ginga.gw import Widgets
from ginga import GingaPlugin, ColorDist
from ginga.rv.plugins.Toolbar import Toolbar
from ginga.table.AstroTable import AstroTable
from ginga.plot.Plotable import Plotable
from ginga.canvas.CanvasObject import get_canvas_types
__all__ = ['Info', 'Info_Ginga_Image',
'Info_Ginga_Plot', 'Info_Ginga_Table']
class Info(Toolbar):
def __init__(self, fv):
# superclass defines some variables for us, like logger
super().__init__(fv)
self.opname_prefix = 'Info_'
def __str__(self):
return 'info'
class Info_Common(GingaPlugin.LocalPlugin):
def __init__(self, fv, chviewer):
# superclass defines some variables for us, like logger
super().__init__(fv, chviewer)
# truncate names after this size
self.maxstr = 60
def trunc(self, s):
if len(s) > self.maxstr:
return s[:self.maxstr - 3] + '...'
else:
return s
[docs]
class Info_Ginga_Image(Info_Common):
"""Info sidebar for the Ginga Image viewer.
"""
def __init__(self, fv, chviewer):
# superclass defines some variables for us, like logger
super().__init__(fv, chviewer)
self.autozoom_options = ['on', 'override', 'once', 'off']
self.autocut_options = self.autozoom_options
self.autocenter_options = self.autozoom_options
self.fv.add_callback('field-info', self.field_info_cb)
self.gui_up = False
[docs]
def build_gui(self, container):
sw = Widgets.ScrollArea()
vbox = Widgets.VBox()
sw.set_widget(vbox)
captions = (('Channel:', 'label', 'Channel', 'llabel'),
('Name:', 'label', 'Name', 'llabel'),
('Object:', 'label', 'Object', 'llabel'),
('X:', 'label', 'X', 'llabel'),
('Y:', 'label', 'Y', 'llabel'),
('Image-X:', 'label', 'Image_X', 'llabel'),
('Image-Y:', 'label', 'Image_Y', 'llabel'),
('Value:', 'label', 'Value', 'llabel'),
('RA:', 'label', 'RA', 'llabel'),
('DEC:', 'label', 'DEC', 'llabel'),
('Equinox:', 'label', 'Equinox', 'llabel'),
('Dimensions:', 'label', 'Dimensions', 'llabel'),
('Min:', 'label', 'Min', 'llabel'),
('Max:', 'label', 'Max', 'llabel'),
)
w, b = Widgets.build_info(captions)
self.w = b
col = Widgets.VBox()
row = Widgets.HBox()
row.set_spacing(0)
row.set_border_width(0)
row.add_widget(w, stretch=0)
row.add_widget(Widgets.Label(''), stretch=1)
col.add_widget(row, stretch=0)
col.add_widget(Widgets.Label(''), stretch=1)
sw2 = Widgets.ScrollArea()
sw2.set_widget(col)
vbox.add_widget(sw2, stretch=1)
captions = (('Zoom:', 'label', 'Zoom', 'llabel',
'Color Dist', 'combobox'),
('Cut Low:', 'label', 'Cut Low Value', 'llabel',
'Cut Low', 'entry'),
('Cut High:', 'label', 'Cut High Value', 'llabel',
'Cut High', 'entry'),
('Auto Levels', 'button', 'spacer_1', 'spacer',
'Cut Levels', 'button'),
('Cut New:', 'label', 'Cut New', 'combobox'),
('Zoom New:', 'label', 'Zoom New', 'combobox',
'Follow New', 'checkbutton'),
('Center New:', 'label', 'Center New', 'combobox',
'Raise New', 'checkbutton'),
)
w, b = Widgets.build_info(captions)
self.w.update(b)
b.cut_levels.set_tooltip("Set cut levels manually")
loval, hival = self.fitsimage.get_cut_levels()
b.auto_levels.set_tooltip("Set cut levels by algorithm")
b.cut_low.set_tooltip("Set low cut level (press Enter)")
b.cut_low.set_length(9)
b.cut_low.set_text(str(loval))
b.cut_low_value.set_text(str(loval))
b.cut_high.set_tooltip("Set high cut level (press Enter)")
b.cut_high.set_length(9)
b.cut_high.set_text(str(hival))
b.cut_high_value.set_text(str(hival))
combobox = b.color_dist
for name in ColorDist.get_dist_names():
combobox.append_text(name)
b.color_dist.set_tooltip("Set distribution (stretching) algorithm")
combobox = b.cut_new
for name in self.autocut_options:
combobox.append_text(name)
b.cut_new.set_tooltip("Automatically set cut levels when switching images")
combobox = b.zoom_new
for name in self.autozoom_options:
combobox.append_text(name)
b.zoom_new.set_tooltip("Automatically fit image to window when switching images")
combobox = b.center_new
for name in self.autocenter_options:
combobox.append_text(name)
b.center_new.set_tooltip("Automatically center image in window when switching images")
b.follow_new.set_tooltip("Automatically switch to new images in channel")
b.raise_new.set_tooltip("Automatically raise channel viewer for new images")
row = Widgets.HBox()
row.set_spacing(0)
row.set_border_width(0)
row.add_widget(w, stretch=0)
row.add_widget(Widgets.Label(''), stretch=1)
vbox.add_widget(row, stretch=0)
self.w.channel.set_text(self.channel.name)
self.w.color_dist.add_callback('activated', self.set_color_dist)
self.w.cut_low.add_callback('activated', self.cut_levels)
self.w.cut_high.add_callback('activated', self.cut_levels)
self.w.cut_levels.add_callback('activated', self.cut_levels)
self.w.auto_levels.add_callback('activated', self.auto_levels)
self.w.cut_new.add_callback('activated', self.set_autocuts_cb)
self.w.zoom_new.add_callback('activated', self.set_autozoom_cb)
self.w.center_new.add_callback('activated', self.set_autocenter_cb)
self.w.follow_new.add_callback('activated', self.set_follow_cb)
self.w.raise_new.add_callback('activated', self.set_raise_cb)
fitssettings = self.fitsimage.get_settings()
for name in ['cuts']:
fitssettings.get_setting(name).add_callback('set',
self.cutset_cb)
for name in ['scale']:
fitssettings.get_setting(name).add_callback('set',
self.zoomset_cb)
fitssettings.get_setting('color_algorithm').add_callback('set',
self.cdistset_cb)
fitssettings.get_setting('autocuts').add_callback('set',
self.autocuts_cb)
fitssettings.get_setting('autozoom').add_callback('set',
self.autozoom_cb)
fitssettings.get_setting('autocenter').add_callback('set',
self.autocenter_cb)
fitssettings.get_setting('switchnew').add_callback('set',
self.follow_cb)
fitssettings.get_setting('raisenew').add_callback('set',
self.raise_cb)
container.add_widget(sw, stretch=1)
self.gui_up = True
[docs]
def start(self):
self.redo()
[docs]
def stop(self):
self.gui_up = False
[docs]
def close(self):
# NOTE: this shouldn't be called under normal usage
self.fv.stop_local_plugin(self.chname, str(self))
[docs]
def redo(self):
self.set_info()
[docs]
def zoomset_cb(self, setting, value):
"""This callback is called when the main window is zoomed.
"""
if not self.gui_up:
return
#scale_x, scale_y = self.fitsimage.get_scale_xy()
scale_x, scale_y = value
# Set text showing zoom factor (1X, 2X, etc.)
if scale_x == scale_y:
text = self.fv.scale2text(scale_x)
else:
textx = self.fv.scale2text(scale_x)
texty = self.fv.scale2text(scale_y)
text = "X: %s Y: %s" % (textx, texty)
self.w.zoom.set_text(text)
[docs]
def cutset_cb(self, setting, value):
if not self.gui_up:
return
loval, hival = value
#self.w.cut_low.set_text('%.4g' % (loval))
self.w.cut_low_value.set_text('%.4g' % (loval))
#self.w.cut_high.set_text('%.4g' % (hival))
self.w.cut_high_value.set_text('%.4g' % (hival))
[docs]
def cdistset_cb(self, setting, value):
if not self.gui_up:
return
name = value
self.w.color_dist.set_text(name)
[docs]
def autocuts_cb(self, setting, option):
if not self.gui_up:
return
self.logger.debug("autocuts changed to %s" % option)
index = self.autocut_options.index(option)
self.w.cut_new.set_index(index)
[docs]
def autozoom_cb(self, setting, option):
if not self.gui_up:
return
index = self.autozoom_options.index(option)
self.w.zoom_new.set_index(index)
[docs]
def autocenter_cb(self, setting, option):
if not self.gui_up:
return
# Hack to convert old values that used to be T/F
if isinstance(option, bool):
choice = {True: 'on', False: 'off'}
option = choice[option]
index = self.autocenter_options.index(option)
self.w.center_new.set_index(index)
[docs]
def follow_cb(self, setting, option):
if not self.gui_up:
return
self.w.follow_new.set_state(option)
[docs]
def raise_cb(self, setting, option):
if not self.gui_up:
return
self.w.raise_new.set_state(option)
[docs]
def set_autocuts_cb(self, w, index):
if not self.gui_up:
return
option = self.autocut_options[index]
self.fitsimage.enable_autocuts(option)
[docs]
def set_autozoom_cb(self, w, index):
if not self.gui_up:
return
option = self.autozoom_options[index]
self.fitsimage.enable_autozoom(option)
[docs]
def set_autocenter_cb(self, w, index):
if not self.gui_up:
return
option = self.autocenter_options[index]
self.fitsimage.enable_autocenter(option)
[docs]
def set_follow_cb(self, w, tf):
if not self.gui_up:
return
self.fitsimage.get_settings().set(switchnew=tf)
[docs]
def set_raise_cb(self, w, tf):
if not self.gui_up:
return
self.fitsimage.get_settings().set(raisenew=tf)
# LOGIC
[docs]
def set_info(self):
if not self.gui_up:
return
# Show cut levels
loval, hival = self.fitsimage.get_cut_levels()
#self.w.cut_low.set_text('%.4g' % (loval))
self.w.cut_low_value.set_text('%.4g' % (loval))
#self.w.cut_high.set_text('%.4g' % (hival))
self.w.cut_high_value.set_text('%.4g' % (hival))
# update zoom indicator
scalefactor = self.fitsimage.get_scale()
text = self.fv.scale2text(scalefactor)
self.w.zoom.set_text(text)
# update cut new/zoom new indicators
t_ = self.fitsimage.get_settings()
index = self.autocut_options.index(t_['autocuts'])
self.w.cut_new.set_index(index)
index = self.autozoom_options.index(t_['autozoom'])
self.w.zoom_new.set_index(index)
option = t_['autocenter']
# Hack to convert old values that used to be T/F
if isinstance(option, bool):
choice = {True: 'on', False: 'off'}
option = choice[option]
index = self.autocenter_options.index(option)
self.w.center_new.set_index(index)
self.w.follow_new.set_state(t_['switchnew'])
self.w.raise_new.set_state(t_['raisenew'])
# Set color distribution indicator
name = t_['color_algorithm']
self.w.color_dist.set_text(name)
image = self.fitsimage.get_image()
if image is None:
return
header = image.get_header()
# Update info panel
name = self.trunc(image.get('name', 'Noname'))
self.w.name.set_text(name)
objtext = self.trunc(header.get('OBJECT', 'UNKNOWN'))
self.w.object.set_text(objtext)
equinox = header.get('EQUINOX', '')
self.w.equinox.set_text(str(equinox))
# Show min, max values
width, height = self.fitsimage.get_data_size()
minval, maxval = image.get_minmax(noinf=False)
self.w.max.set_text(str(maxval))
self.w.min.set_text(str(minval))
# Show dimensions
dim_txt = "%dx%d" % (width, height)
self.w.dimensions.set_text(dim_txt)
[docs]
def field_info_cb(self, fv, channel, info):
if not self.gui_up:
return
self.w.x.set_text("%.3f" % info.x)
self.w.y.set_text("%.3f" % info.y)
if 'image_x' in info:
self.w.image_x.set_text("%.3f" % info.image_x)
else:
self.w.image_x.set_text("")
if 'image_y' in info:
self.w.image_y.set_text("%.3f" % info.image_y)
else:
self.w.image_y.set_text("")
self.w.value.set_text(str(info.value))
if 'ra_txt' in info:
self.w.ra.set_text(info.ra_txt)
self.w.dec.set_text(info.dec_txt)
if 'ra_lbl' in info:
self.w.lbl_ra.set_text(info.ra_lbl + ':')
self.w.lbl_dec.set_text(info.dec_lbl + ':')
[docs]
def cut_levels(self, w):
loval, hival = self.fitsimage.get_cut_levels()
try:
lostr = self.w.cut_low.get_text().strip()
if lostr != '':
loval = float(lostr)
histr = self.w.cut_high.get_text().strip()
if histr != '':
hival = float(histr)
self.logger.debug("locut=%f hicut=%f" % (loval, hival))
return self.fitsimage.cut_levels(loval, hival)
except Exception as e:
self.fv.show_error("Error cutting levels: %s" % (str(e)))
return True
[docs]
def auto_levels(self, w):
self.fitsimage.auto_levels()
[docs]
def set_color_dist(self, w, idx):
name = w.get_text()
self.fitsimage.set_color_algorithm(name)
def __str__(self):
return 'info_ginga_image'
[docs]
class Info_Ginga_Plot(Info_Common):
"""Info sidebar for the Ginga Plot viewer.
"""
def __init__(self, fv, fitsimage):
# superclass defines some variables for us, like logger
super().__init__(fv, fitsimage)
# Plugin preferences
prefs = self.fv.get_preferences()
self.settings = prefs.create_category('info_Ginga_Plot')
self.settings.add_defaults(linewidth=1,
linestyle='-',
linecolor='blue',
markersize=6,
markerwidth=0.5,
markerstyle='o',
markercolor='red',
show_marker=False,
file_suffix='.png')
self.settings.load(onError='silent')
viewer = self.channel.get_viewer('Ginga Plot')
self.plot_viewer = viewer
viewer.add_callback('range-set', self.range_set_cb)
viewer.add_callback('motion', self.motion_cb)
self.gui_up = False
[docs]
def build_gui(self, container):
# if not have_mpl:
# raise ImportError('Install matplotlib to use this plugin')
t_ = self.plot_viewer.get_settings()
sw = Widgets.ScrollArea()
vbox = Widgets.VBox()
sw.set_widget(vbox)
captions = (('Channel:', 'label', 'channel', 'llabel'),
('Name:', 'label', 'name', 'llabel'),
('X:', 'label', 'x_val', 'llabel'),
('Y:', 'label', 'y_val', 'llabel'),
)
w, b = Widgets.build_info(captions)
self.w = b
self.w.channel.set_text(self.channel.name)
col = Widgets.VBox()
row = Widgets.HBox()
row.set_spacing(0)
row.set_border_width(0)
row.add_widget(w, stretch=0)
row.add_widget(Widgets.Label(''), stretch=1)
col.add_widget(row, stretch=0)
col.add_widget(Widgets.Label(''), stretch=1)
sw2 = Widgets.ScrollArea()
sw2.set_widget(col)
vbox.add_widget(sw2, stretch=1)
captions = (('spacer_1', 'spacer', 'X', 'llabel', 'Y', 'llabel'),
('Dist:', 'label', 'x_dist', 'combobox',
'y_dist', 'combobox'),
('Low:', 'label', 'x_lo', 'entry', 'y_lo', 'entry'),
('High:', 'label', 'x_hi', 'entry', 'y_hi', 'entry'),
('spacer_3', 'spacer', 'Show marker', 'checkbox',
'Save Plot', 'button'),
)
w, b = Widgets.build_info(captions)
self.w.update(b)
# Controls for X-axis scaling
combobox = b.x_dist
for name in ['linear', 'log']:
combobox.append_text(name)
combobox.set_tooltip('Select a mapping to plot on X-axis')
combobox.set_text(t_['plot_dist_axis'][0])
combobox.add_callback('activated', self.x_dist_cb)
# Controls for Y-axis column listing
combobox = b.y_dist
for name in ['linear', 'log']:
combobox.append_text(name)
combobox.set_tooltip('Select a mapping to plot on Y-axis')
combobox.set_text(t_['plot_dist_axis'][1])
combobox.add_callback('activated', self.y_dist_cb)
self.set_xlimits_widgets()
self.set_ylimits_widgets()
b.x_lo.add_callback('activated', lambda w: self.set_xlim_lo_cb())
b.x_lo.set_tooltip('Set X lower limit')
b.x_hi.add_callback('activated', lambda w: self.set_xlim_hi_cb())
b.x_hi.set_tooltip('Set X upper limit')
b.y_lo.add_callback('activated', lambda w: self.set_ylim_lo_cb())
b.y_lo.set_tooltip('Set Y lower limit')
b.y_hi.add_callback('activated', lambda w: self.set_ylim_hi_cb())
b.y_hi.set_tooltip('Set Y upper limit')
b.show_marker.set_state(t_.get('plot_show_marker', False))
b.show_marker.add_callback('activated', self.set_marker_cb)
b.show_marker.set_tooltip('Mark data points')
# Button to save plot
b.save_plot.set_tooltip("Save plot to file")
b.save_plot.add_callback('activated', lambda w: self.save_cb())
b.save_plot.set_enabled(True)
row = Widgets.HBox()
row.set_spacing(0)
row.set_border_width(0)
row.add_widget(w, stretch=0)
row.add_widget(Widgets.Label(''), stretch=1)
vbox.add_widget(row, stretch=0)
container.add_widget(sw, stretch=1)
self.gui_up = True
[docs]
def redo(self):
"""This is called when a new image arrives or the data in the
existing image changes.
"""
if not self.gui_up:
return
dataobj = self.plot_viewer.get_dataobj()
name = self.trunc(dataobj.get('name', 'Noname'))
self.w.name.set_text(name)
[docs]
def x_dist_cb(self, w, index):
_dist = w.get_text().lower()
self.plot_viewer.set_dist_axis(x_axis=_dist)
[docs]
def y_dist_cb(self, w, index):
_dist = w.get_text().lower()
self.plot_viewer.set_dist_axis(y_axis=_dist)
[docs]
def set_xlim_lo_cb(self, redraw=True):
"""Set plot limit based on user values."""
ranges = self.plot_viewer.get_ranges()
(x_lo, x_hi), (y_lo, y_hi) = ranges
try:
x_min = float(self.w.x_lo.get_text())
except Exception as e:
self.fv.show_error(f"error setting X low limit: {e}")
return
self.plot_viewer.set_ranges(x_range=(x_min, x_hi))
[docs]
def set_xlim_hi_cb(self, redraw=True):
"""Set plot limit based on user values."""
ranges = self.plot_viewer.get_ranges()
(x_lo, x_hi), (y_lo, y_hi) = ranges
try:
x_max = float(self.w.x_hi.get_text())
except Exception as e:
self.fv.show_error(f"error setting X high limit: {e}")
return
self.plot_viewer.set_ranges(x_range=(x_lo, x_max))
[docs]
def set_ylim_lo_cb(self, redraw=True):
"""Set plot limit based on user values."""
ranges = self.plot_viewer.get_ranges()
(x_lo, x_hi), (y_lo, y_hi) = ranges
try:
y_min = float(self.w.y_lo.get_text())
except Exception as e:
self.fv.show_error(f"error setting Y low limit: {e}")
return
self.plot_viewer.set_ranges(y_range=(y_min, y_hi))
[docs]
def set_ylim_hi_cb(self, redraw=True):
"""Set plot limit based on user values."""
ranges = self.plot_viewer.get_ranges()
(x_lo, x_hi), (y_lo, y_hi) = ranges
try:
y_max = float(self.w.y_hi.get_text())
except Exception as e:
self.fv.show_error(f"error setting Y high limit: {e}")
return
self.plot_viewer.set_ranges(y_range=(y_lo, y_max))
[docs]
def range_set_cb(self, viewer, ranges):
(xmin, xmax), (ymin, ymax) = ranges
if self.gui_up:
self.set_xlimits_widgets()
self.set_ylimits_widgets()
[docs]
def motion_cb(self, viewer, event):
if not self.gui_up:
return
data_x, data_y = event.data_x, event.data_y
x_str = str(data_x)
y_str = str(data_y)
self.w.x_val.set_text(x_str)
self.w.y_val.set_text(y_str)
[docs]
def set_marker_cb(self, w, tf):
"""Toggle show/hide data point markers."""
settings = self.plot_viewer.get_settings()
settings.set(plot_show_marker=tf)
[docs]
def save_cb(self):
"""Save plot to file."""
# This just defines the basename.
# Extension has to be explicitly defined or things can get messy.
w = Widgets.SaveDialog(title='Save plot')
target = w.get_path()
if target is None:
# Save canceled
return
plot_ext = self.settings.get('file_suffix', '.png')
try:
self.plot_viewer.save_rgb_image_as_file(target, format='png')
except Exception as e:
self.logger.error(str(e))
else:
self.logger.info('Table plot saved as {0}'.format(target))
[docs]
def start(self):
self.redo()
[docs]
def stop(self):
self.gui_up = False
[docs]
def close(self):
# NOTE: this shouldn't be called under normal usage
self.fv.stop_local_plugin(self.chname, str(self))
def __str__(self):
return 'info_ginga_plot'
[docs]
class Info_Ginga_Table(Info_Common):
"""Info sidebar for the Ginga Table viewer.
"""
def __init__(self, fv, chviewer):
# superclass defines some variables for us, like logger
super().__init__(fv, chviewer)
viewer = self.channel.get_viewer('Ginga Table')
self.table_viewer = viewer
# To store all active table info
self.tab = None
self.cols = []
self._idx = []
self._idxname = '_idx'
# To store selected columns names of active table
self.x_col = ''
self.y_col = ''
self.gui_up = False
[docs]
def build_gui(self, container):
sw = Widgets.ScrollArea()
vbox = Widgets.VBox()
sw.set_widget(vbox)
captions = (('Channel:', 'label', 'channel', 'llabel'),
('Name:', 'label', 'name', 'llabel'),
)
w, b = Widgets.build_info(captions)
self.w = b
self.w.channel.set_text(self.channel.name)
col = Widgets.VBox()
row = Widgets.HBox()
row.set_spacing(0)
row.set_border_width(0)
row.add_widget(w, stretch=0)
row.add_widget(Widgets.Label(''), stretch=1)
col.add_widget(row, stretch=0)
col.add_widget(Widgets.Label(''), stretch=1)
sw2 = Widgets.ScrollArea()
sw2.set_widget(col)
vbox.add_widget(sw2, stretch=1)
channel = self.channel
self.w.channel.set_text(channel.name)
captions = (('spacer_1', 'spacer', 'X', 'llabel', 'Y', 'llabel'),
('Col:', 'label', 'x_col', 'combobox', 'y_col', 'combobox'),
('spacer_3', 'spacer', 'spacer_4', 'spacer',
'Make Plot', 'button'),
)
w, b = Widgets.build_info(captions)
self.w.update(b)
# Controls for X-axis column listing
combobox = b.x_col
combobox.set_enabled(False)
for name in self.cols:
combobox.append_text(name)
if self.x_col in self.cols:
combobox.set_text(self.x_col)
combobox.add_callback('activated', self.x_select_cb)
combobox.set_tooltip('Select a column to plot on X-axis')
# Controls for Y-axis column listing
combobox = b.y_col
combobox.set_enabled(False)
for name in self.cols:
combobox.append_text(name)
if self.y_col in self.cols:
combobox.set_text(self.y_col)
combobox.add_callback('activated', self.y_select_cb)
combobox.set_tooltip('Select a column to plot on Y-axis')
# Button to save plot
b.make_plot.set_tooltip("Plot selected columns")
b.make_plot.add_callback('activated', self.make_plot_cb)
b.make_plot.set_enabled(False)
fr = Widgets.Frame("Plot Maker")
fr.set_widget(w)
row = Widgets.HBox()
row.set_spacing(0)
row.set_border_width(0)
row.add_widget(fr, stretch=0)
row.add_widget(Widgets.Label(''), stretch=1)
vbox.add_widget(row, stretch=0)
container.add_widget(sw, stretch=1)
self.gui_up = True
[docs]
def start(self):
self.redo()
[docs]
def stop(self):
self.tab = None
self.gui_up = False
[docs]
def close(self):
# NOTE: this shouldn't be called under normal usage
self.fv.stop_local_plugin(self.chname, str(self))
def _set_combobox(self, combobox, vals, default=0):
"""Populate combobox with given list."""
combobox.clear()
for val in vals:
combobox.append_text(val)
if default > len(vals):
default = 0
val = vals[default]
combobox.show_text(val)
return val
[docs]
def setup_table(self, dataobj):
# Generate column indices
self.w.x_col.set_enabled(True)
self.w.y_col.set_enabled(True)
self.w.make_plot.set_enabled(True)
# Generate column indices
self.tab = dataobj.get_data()
self._idx = np.arange(len(self.tab))
# Populate combobox with table column names
cols = [self._idxname] + self.tab.colnames
if cols != self.cols:
self.cols = cols
self.x_col = self._set_combobox(self.w.x_col, self.cols, default=1)
self.y_col = self._set_combobox(self.w.y_col, self.cols, default=2)
[docs]
def clear(self):
self.tab = None
self.cols = []
self._idx = []
self._idxname = '_idx'
self.x_col = ''
self.y_col = ''
[docs]
def redo(self):
"""This is called when a new image arrives or the data in the
existing image changes.
"""
if not self.gui_up:
return
dataobj = self.table_viewer.get_dataobj()
if isinstance(dataobj, AstroTable):
self.setup_table(dataobj)
else:
self.logger.info("not able to process this object")
name = self.trunc(dataobj.get('name', 'Noname'))
self.w.name.set_text(name)
# LOGIC
def _get_label(self, axis):
"""Return plot label for column for the given axis."""
if axis == 'x':
colname = self.x_col
else: # y
colname = self.y_col
if colname == self._idxname:
label = 'Index'
else:
col = self.tab[colname]
if col.unit:
label = '{0} ({1})'.format(col.name, col.unit)
else:
label = col.name
return label
[docs]
def x_select_cb(self, w, index):
"""Callback to set X-axis column."""
try:
self.x_col = self.cols[index]
except IndexError as e:
self.logger.error(str(e))
[docs]
def y_select_cb(self, w, index):
"""Callback to set Y-axis column."""
try:
self.y_col = self.cols[index]
except IndexError as e:
self.logger.error(str(e))
[docs]
def make_plot_cb(self, w):
if self.x_col == self._idxname:
x_data = self._idx
else:
x_data = self.tab[self.x_col].data
if self.y_col == self._idxname:
y_data = self._idx
else:
y_data = self.tab[self.y_col].data
x_label = self._get_label('x')
y_label = self._get_label('y')
dataobj = self.table_viewer.get_dataobj()
name = "plot_{}".format(time.time())
#title = self.trunc(dataobj.get('name', 'NoName'))
title = name
if self.tab.masked:
if self.x_col == self._idxname:
x_mask = np.ones_like(self._idx, dtype=bool)
else:
x_mask = ~self.tab[self.x_col].mask
if self.y_col == self._idxname:
y_mask = np.ones_like(self._idx, dtype=bool)
else:
y_mask = ~self.tab[self.y_col].mask
mask = x_mask & y_mask
x_data = x_data[mask]
y_data = y_data[mask]
if len(x_data) > 1:
i = np.argsort(x_data) # Sort X-axis to avoid messy line plot
x_data = x_data[i]
y_data = y_data[i]
plot = Plotable(logger=self.logger)
plot.set_titles(x_axis=x_label, y_axis=y_label, title=title)
plot.set_grid(True)
plot.set(name=name, path=None, nothumb=False)
canvas = plot.get_canvas()
dc = get_canvas_types()
points = np.array((x_data, y_data)).T
canvas.add(dc.Path(points, color='black', linewidth=1,
alpha=1.0),
redraw=False)
self.channel.add_image(plot)
def __str__(self):
return 'info_ginga_table'