content: # This is open-source software licensed under a BSD license.
  # Please see the file LICENSE.txt for details.
  """
  Make changes to channel settings graphically in the UI.
  
  **Plugin Type: Local**
  
  ``Preferences`` is a local plugin, which means it is associated with a
  channel.  An instance can be opened for each channel.
  
  **Usage**
  
  The ``Preferences`` plugin sets the preferences on a per-channel basis.
  The preferences for a given channel are inherited from the "Image"
  channel until they are explicitly set and saved using this plugin.
  
  If "Save Settings" is pressed, it will save the settings to the user's
  home Ginga folder so that when a channel with the same name is created
  in future Ginga sessions it will obtain the same settings.
  
  **Color Distribution Preferences**
  
  .. figure:: figures/cdist-prefs.png
     :align: center
     :alt: Color Distribution preferences
  
     "Color Distribution" preferences.
  
  The "Color Distribution" preferences control the preferences used for the
  data value to color index conversion that occurs after cut levels are
  applied and just before final color mapping is performed.  It concerns
  how the values between the low and high cut levels are distributed to
  the color and intensity mapping phase.
  
  The "Algorithm" control is used to set the algorithm used for the
  mapping.  Click the control to show the list, or simply scroll the mouse
  wheel while hovering the cursor over the control.  There are eight
  algorithms available: linear, log, power, sqrt, squared, asinh, sinh,
  and histeq.  The name of each algorithm is indicative of how
  the data is mapped to the colors in the color map.  "linear" is the
  default.
  
  **Color Mapping Preferences**
  
  .. figure:: figures/cmap-prefs.png
     :align: center
     :alt: Color Mapping preferences
  
     "Color Mapping" preferences.
  
  The "Color Mapping" preferences control the preferences used for the
  color map and intensity map, used during the final phase of the color
  mapping process. Together with the "Color Distribution" preferences, these
  control the mapping of data values into a 24-bpp RGB visual representation.
  
  The "Colormap" control selects which color map should be loaded and
  used.  Click the control to show the list, or simply scroll the mouse
  wheel while hovering the cursor over the control.
  
  The "Intensity" control selects which intensity map should be used
  with the color map.  The intensity map is applied just before the color
  map, and can be used to change the standard linear scale of values into
  an inverted scale, logarithmic, etc.
  
  Ginga comes with a good selection of color maps, but should you want
  more, you can add custom ones or, if ``matplotlib`` is installed, you
  can load all the ones that it has.
  See "Customizing Ginga" for details.
  
  **Zoom Preferences**
  
  .. figure:: figures/zoom-prefs.png
     :align: center
     :alt: Zoom preferences
  
     "Zoom" preferences.
  
  The "Zoom" preferences control Ginga's zooming/scaling behavior.
  Ginga supports two zoom algorithms, chosen using the "Zoom Alg" control:
  
  * The "step" algorithm zooms the image inwards in discrete
    steps of 1X, 2X, 3X, etc. or outwards in steps of 1/2X, 1/3X, 1/4X,
    etc.  This algorithm results in the least artifacts visually, but is a
    bit slower to zoom over wide ranges when using a scrolling motion
    because more "throw" is required to achieve a large zoom change
    (this is not the case if one uses of the shortcut zoom keys, such as
    the digit keys).
  
  * The "rate" algorithm zooms the image by advancing the scaling at
    a rate defined by the value in the "Zoom Rate" box.  This rate defaults
    to the square root of 2.  Larger numbers cause larger changes in scale
    between zoom levels.  If you like to zoom your images rapidly, at a
    small cost in image quality, you would likely want to choose this
    option.
  
  Note that regardless of which method is chosen for the zoom algorithm,
  the zoom can be controlled by holding down ``Ctrl`` (coarse) or ``Shift``
  (fine) while scrolling to constrain the zoom rate (assuming the default
  mouse bindings).
  
  The "Stretch XY" control can be used to stretch one of the axes (X or
  Y) relative to the other.  Select an axis with this control and roll the
  scroll wheel while hovering over the "Stretch Factor" control to
  stretch the pixels in the selected axis.
  
  The "Scale X" and "Scale Y" controls offer direct access to the
  underlying scaling, bypassing the discrete zoom steps.  Here, exact
  values can be typed to scale the image.  Conversely, you will see these
  values change as the image is zoomed.
  
  The "Scale Min" and "Scale Max" controls can be used to place a
  limit on how much the image can be scaled.
  
  The "Zoom Defaults" button will restore the controls to the Ginga
  default values.
  
  **Pan Preferences**
  
  .. figure:: figures/pan-prefs.png
     :align: center
     :alt: Pan Preferences
  
     "Pan" preferences.
  
  The "Pan" preferences control Ginga's panning behavior.
  
  The "Pan X" and "Pan Y" controls offer direct access to set the pan
  position in the image (the part of the image located at the center of
  the window) -- you can see them change as you pan around the image.
  
  The "Center Image" button sets the pan position to the center of the
  image, as calculated by halving the dimensions in X and Y.
  
  The "Mark Center" check box, when checked, will cause Ginga to draw a
  small reticle in the center of the image.  This is useful for knowing
  the pan position and for debugging.
  
  **Transform Preferences**
  
  .. figure:: figures/transform-prefs.png
     :align: center
     :alt: Transform Preferences
  
     "Transform" preferences.
  
  The "Transform" preferences provide for transforming the view of the image
  by flipping the view in X or Y, swapping the X and Y axes, or rotating
  the image in arbitrary amounts.
  
  The "Flip X" and "Flip Y" checkboxes cause the image view to be
  flipped in the corresponding axis.
  
  The "Swap XY" checkbox causes the image view to be altered by swapping
  the X and Y axes.  This can be combined with "Flip X" and "Flip Y" to rotate
  the image in 90 degree increments.  These views will render more quickly
  than arbitrary rotations using the "Rotate" control.
  
  The "Rotate" control will rotate the image view the specified amount.
  The value should be specified in degrees.  "Rotate" can be specified in
  conjunction with flipping and swapping.
  
  The "Restore" button will restore the view to the default view, which
  is unflipped, unswapped, and unrotated.
  
  **Auto Cuts Preferences**
  
  .. figure:: figures/autocuts-prefs.png
     :align: center
     :alt: Auto Cuts Preferences
  
     "Auto Cuts" preferences.
  
  The "Auto Cuts" preferences control the calculation of cut levels for
  the view when the auto cut levels button or key is pressed, or when
  loading a new image with auto cuts enabled.  You can also set the cut
  levels manually from here.
  
  The "Cut Low" and "Cut High" fields can be used to manually specify lower
  and upper cut levels.  Pressing "Cut Levels" will set the levels to these
  values manually. If a value is missing, it is assumed to default to the
  whatever the current value is.
  
  Pressing "Auto Levels" will calculate the levels according to an algorithm.
  The "Auto Method" control is used to choose which auto cuts algorithm
  used: "minmax" (minimum maximum values), "median" (based on median
  filtering), "histogram" (based on an image histogram), "stddev" (based on
  the standard deviation of pixel values), or "zscale" (based on the ZSCALE
  algorithm popularized by IRAF).
  As the algorithm is changed, the boxes under it may also change to
  allow changes to parameters particular to each algorithm.
  
  **WCS Preferences**
  
  .. figure:: figures/wcs-prefs.png
     :align: center
     :alt: WCS Preferences
  
     "WCS" preferences.
  
  The "WCS" preferences control the display preferences for the World
  Coordinate System (WCS) calculations used to report the cursor position in the
  image.
  
  The "WCS Coords" control is used to select the coordinate system in
  which to display the result.
  
  The "WCS Display" control is used to select a sexagesimal (``H:M:S``)
  readout or a decimal degrees readout.
  
  **New Image Preferences**
  
  .. figure:: figures/newimages-prefs.png
     :align: center
     :alt: New Image Preferences
  
     "New Image" preferences.
  
  The "New Images" preferences determine how Ginga reacts when a new image
  is loaded into the channel.  This includes when an older image is
  revisited by clicking on its thumbnail in the ``Thumbs`` plugin pane.
  
  The "Cut New" setting controls whether an automatic cut-level
  calculation should be performed on the new image, or whether the
  currently set cut levels should be applied.  The possible settings are:
  
  * "on": calculate a new cut levels always;
  * "override": calculate a new cut levels until the user overrides
    it by manually setting a cut levels, then turn "off"; or
  * "off": always use the currently set cut levels.
  
  .. tip:: The "override" setting is provided for the convenience of
           having automatic cut levels, while preventing a manually set
           cuts from being overridden when a new image is ingested.  When
           typed in the image window, the semicolon key can be used to
           toggle the mode back to override (from "off"), while colon will
           set the preference to "on".  The ``Info`` panel shows
           the state of this setting.
  
  The "Zoom New" setting controls whether a newly visited image should
  be zoomed to fit the window.  There are three possible values: on,
  override, and off:
  
  * "on": the new image is always zoomed to fit;
  * "override": images are automatically fitted until the zoom level is
    changed manually, then the mode automatically changes to "off", or
  * "off": always use the currently set zoom levels.
  
  .. tip:: The "override" setting is provided for the convenience of
           having an automatic zoom, while preventing a manually set zoom
           level from being overridden when a new image is ingested.  When
           typed in the image window,  the apostrophe (a.k.a. "single quote")
           key can be used to toggle the mode back to "override" (from
           "off"), while quote (a.k.a. double quote) will set the preference
           to "on".  The global plugin ``Info`` panel shows the state of this
           setting.
  
  The "Center New" box, if checked, will cause newly visited images to
  always have the pan position reset to the center of the image.  If
  unchecked, the pan position is unchanged from the previous image.
  
  The "Follow New" setting is used to control whether Ginga will change
  the display if a new image is loaded into the channel.  If unchecked,
  the image is loaded (as seen, for example, by its appearance in the
  ``Thumbs`` tab), but the display will not change to the new image.  This
  setting is useful in cases where new images are being loaded by some
  automated means into a channel and the user wishes to study the current
  image without being interrupted.
  
  The "Raise New" setting controls whether Ginga will raise the tab of a
  channel when an image is loaded into that channel.  If unchecked, then
  Ginga will not raise the tab when an image is loaded into that
  particular channel.
  
  The "Create Thumbnail" setting controls whether Ginga will create a
  thumbnail for images loaded into that channel.  In cases where many
  images are being loaded into a channel frequently (e.g., a low frequency
  video feed), it may be undesirable to create thumbnails for all of them.
  
  **General Preferences**
  
  The "Num Images" setting specifies how many images can be retained in
  buffers in this channel before being ejected.  A value of zero (0) means
  unlimited--images will never be ejected.  If an image was loaded from
  some accessible storage and it is ejected, it will automatically be
  reloaded if the image is revisited by navigating the channel.
  
  The "Sort Order" setting determines whether images are sorted in the
  channel alphabetically by name or by the time when they were loaded.
  This principally affects the order in which images are cycled when using
  the up/down "arrow" keys or buttons, and not necessarily how they are
  displayed in plugins like "Contents" or "Thumbs" (which generally have
  their own setting preference for ordering).
  
  The "Use scrollbars" check box controls whether the channel viewer will
  show scroll bars around the edge of the viewer frame.
  
  **Remember Preferences**
  
  When an image is loaded, a profile is created and attached to the image
  metadata in the channel.  These profiles are continuously updated with
  viewer state as the image is manipulated.  The "Remember" preferences
  control which parts of these profiles are restored to the viewer state
  when the image is navigated to in the channel:
  
  * "Restore Scale" will restore the zoom (scale) level
  * "Restore Pan" will restore the pan position
  * "Restore Transform" will restore any flip or swap axes transforms
  * "Restore Rotation" will restore any rotation of the image
  * "Restore Cuts" will restore any cut levels for the image
  * "Restore Scale" will restore any coloring adjustments made (including
    color map, color distribution, contrast/stretch, etc.)
  
  """
  import math
  
  from ginga.gw import Widgets
  from ginga.misc import ParamSet, Bunch
  
  from ginga import cmap, imap, trcalc
  from ginga import GingaPlugin
  from ginga import AutoCuts, ColorDist
  from ginga.util import wcs, wcsmod, rgb_cms
  
  __all_ = ['Preferences']
  
  
  class Preferences(GingaPlugin.LocalPlugin):
  
      def __init__(self, fv, fitsimage):
          # superclass defines some variables for us, like logger
          super(Preferences, self).__init__(fv, fitsimage)
  
          self.cmap_names = cmap.get_names()
          self.imap_names = imap.get_names()
          self.zoomalg_names = ('step', 'rate')
  
          # get Preferences preferences
          prefs = self.fv.get_preferences()
          self.settings = prefs.create_category('plugin_Preferences')
          self.settings.add_defaults(orientation=None)
          self.settings.load(onError='silent')
  
          self.t_ = self.fitsimage.get_settings()
          self.autocuts_cache = {}
          self.gui_up = False
  
          self.calg_names = ColorDist.get_dist_names()
          self.autozoom_options = self.fitsimage.get_autozoom_options()
          self.autocut_options = self.fitsimage.get_autocuts_options()
          self.autocut_methods = self.fitsimage.get_autocut_methods()
          self.autocenter_options = self.fitsimage.get_autocenter_options()
          self.pancoord_options = ('data', 'wcs')
          self.sort_options = ('loadtime', 'alpha')
  
          for key in ['color_map', 'intensity_map',
                      'color_algorithm', 'color_hashsize']:
              self.t_.get_setting(key).add_callback(
                  'set', self.rgbmap_changed_ext_cb)
  
          self.t_.get_setting('autozoom').add_callback(
              'set', self.autozoom_changed_ext_cb)
          self.t_.get_setting('autocenter').add_callback(
              'set', self.autocenter_changed_ext_cb)
          self.t_.get_setting('autocuts').add_callback(
              'set', self.autocuts_changed_ext_cb)
          for key in ['switchnew', 'raisenew', 'genthumb']:
              self.t_.get_setting(key).add_callback(
                  'set', self.set_chprefs_ext_cb)
  
          for key in ['pan']:
              self.t_.get_setting(key).add_callback(
                  'set', self.pan_changed_ext_cb)
          for key in ['scale']:
              self.t_.get_setting(key).add_callback(
                  'set', self.scale_changed_ext_cb)
  
          self.t_.get_setting('zoom_algorithm').add_callback(
              'set', self.set_zoomalg_ext_cb)
          self.t_.get_setting('zoom_rate').add_callback(
              'set', self.set_zoomrate_ext_cb)
          for key in ['scale_x_base', 'scale_y_base']:
              self.t_.get_setting(key).add_callback(
                  'set', self.scalebase_changed_ext_cb)
          self.t_.get_setting('rot_deg').add_callback(
              'set', self.set_rotate_ext_cb)
          for name in ('flip_x', 'flip_y', 'swap_xy'):
              self.t_.get_setting(name).add_callback(
                  'set', self.set_transform_ext_cb)
  
          self.t_.get_setting('autocut_method').add_callback('set',
                                                             self.set_autocut_method_ext_cb)
          self.t_.get_setting('autocut_params').add_callback('set',
                                                             self.set_autocut_params_ext_cb)
          self.t_.get_setting('cuts').add_callback(
              'set', self.cutset_cb)
  
          self.t_.setdefault('wcs_coords', 'icrs')
          self.t_.setdefault('wcs_display', 'sexagesimal')
  
          # buffer len (number of images in memory)
          self.t_.add_defaults(numImages=4)
          self.t_.get_setting('numImages').add_callback('set', self.set_buflen_ext_cb)
  
          # preload images
          self.t_.add_defaults(preload_images=False)
  
          self.icc_profiles = list(rgb_cms.get_profiles())
          self.icc_profiles.insert(0, None)
          self.icc_intents = rgb_cms.get_intents()
  
      def build_gui(self, container):
          top = Widgets.VBox()
          top.set_border_width(4)
  
          vbox, sw, orientation = Widgets.get_oriented_box(container,
                                                           orientation=self.settings.get('orientation', None))
          self.orientation = orientation
          #vbox.set_border_width(4)
          vbox.set_spacing(2)
  
          # COLOR DISTRIBUTION OPTIONS
          fr = Widgets.Frame("Color Distribution")
  
          captions = (('Algorithm:', 'label', 'Algorithm', 'combobox'),
                      #('Table Size:', 'label', 'Table Size', 'entryset'),
                      ('Dist Defaults', 'button'))
  
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
          self.w.calg_choice = b.algorithm
          #self.w.table_size = b.table_size
          b.algorithm.set_tooltip("Choose a color distribution algorithm")
          #b.table_size.set_tooltip("Set size of the distribution hash table")
          b.dist_defaults.set_tooltip("Restore color distribution defaults")
          b.dist_defaults.add_callback('activated',
                                       lambda w: self.set_default_distmaps())
  
          combobox = b.algorithm
          options = []
          index = 0
          for name in self.calg_names:
              options.append(name)
              combobox.append_text(name)
              index += 1
          try:
              index = self.calg_names.index(self.t_.get('color_algorithm',
                                                        "linear"))
              combobox.set_index(index)
          except Exception:
              pass
          combobox.add_callback('activated', self.set_calg_cb)
  
          ## entry = b.table_size
          ## entry.set_text(str(self.t_.get('color_hashsize', 65535)))
          ## entry.add_callback('activated', self.set_tablesize_cb)
  
          fr.set_widget(w)
          vbox.add_widget(fr)
  
          # COLOR MAPPING OPTIONS
          fr = Widgets.Frame("Color Mapping")
  
          captions = (('Colormap:', 'label', 'Colormap', 'combobox'),
                      ('Intensity:', 'label', 'Intensity', 'combobox'),
                      ('Color Defaults', 'button'))
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
          self.w.cmap_choice = b.colormap
          self.w.imap_choice = b.intensity
          b.color_defaults.add_callback('activated',
                                        lambda w: self.set_default_cmaps())
          b.colormap.set_tooltip("Choose a color map for this image")
          b.intensity.set_tooltip("Choose an intensity map for this image")
          b.color_defaults.set_tooltip("Restore default color and intensity maps")
          fr.set_widget(w)
          vbox.add_widget(fr)
  
          combobox = b.colormap
          options = []
          index = 0
          for name in self.cmap_names:
              options.append(name)
              combobox.append_text(name)
              index += 1
          cmap_name = self.t_.get('color_map', "gray")
          try:
              index = self.cmap_names.index(cmap_name)
          except Exception:
              index = self.cmap_names.index('gray')
          combobox.set_index(index)
          combobox.add_callback('activated', self.set_cmap_cb)
  
          combobox = b.intensity
          options = []
          index = 0
          for name in self.imap_names:
              options.append(name)
              combobox.append_text(name)
              index += 1
          imap_name = self.t_.get('intensity_map', "ramp")
          try:
              index = self.imap_names.index(imap_name)
          except Exception:
              index = self.imap_names.index('ramp')
          combobox.set_index(index)
          combobox.add_callback('activated', self.set_imap_cb)
  
          # AUTOCUTS OPTIONS
          fr = Widgets.Frame("Auto Cuts")
          vbox2 = Widgets.VBox()
          fr.set_widget(vbox2)
  
          captions = (('Cut Low:', 'label', 'Cut Low Value', 'llabel',
                       'Cut Low', 'entry'),
                      ('Cut High:', 'label', 'Cut High Value', 'llabel',
                       'Cut High', 'entry'),
                      ('spacer_1', 'spacer', 'spacer_2', 'spacer',
                       'Cut Levels', 'button'),
                      ('Auto Method:', 'label', 'Auto Method', 'combobox',
                       'Auto Levels', 'button'),)
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
  
          loval, hival = self.t_['cuts']
          b.cut_levels.set_tooltip("Set cut levels manually")
          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_value.set_text('%.4g' % (loval))
          b.cut_high.set_tooltip("Set high cut level (press Enter)")
          b.cut_high.set_length(9)
          b.cut_high_value.set_text('%.4g' % (hival))
  
          b.cut_low.add_callback('activated', self.cut_levels)
          b.cut_high.add_callback('activated', self.cut_levels)
          b.cut_levels.add_callback('activated', self.cut_levels)
          b.auto_levels.add_callback('activated', self.auto_levels)
  
          # Setup auto cuts method choice
          combobox = b.auto_method
          index = 0
          method = self.t_.get('autocut_method', "histogram")
          for name in self.autocut_methods:
              combobox.append_text(name)
              index += 1
          try:
              index = self.autocut_methods.index(method)
              combobox.set_index(index)
          except Exception:
              pass
          combobox.add_callback('activated', self.set_autocut_method_cb)
          b.auto_method.set_tooltip("Choose algorithm for auto levels")
          vbox2.add_widget(w, stretch=0)
  
          self.w.acvbox = Widgets.VBox()
          vbox2.add_widget(self.w.acvbox, stretch=1)
  
          vbox.add_widget(fr, stretch=0)
  
          # TRANSFORM OPTIONS
          fr = Widgets.Frame("Transform")
  
          captions = (('Flip X', 'checkbutton', 'Flip Y', 'checkbutton',
                      'Swap XY', 'checkbutton'),
                      ('Rotate:', 'label', 'Rotate', 'spinfloat'),
                      ('Restore', 'button'),)
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
  
          for name in ('flip_x', 'flip_y', 'swap_xy'):
              btn = b[name]
              btn.set_state(self.t_.get(name, False))
              btn.add_callback('activated', self.set_transforms_cb)
          b.flip_x.set_tooltip("Flip the image around the X axis")
          b.flip_y.set_tooltip("Flip the image around the Y axis")
          b.swap_xy.set_tooltip("Swap the X and Y axes in the image")
          b.rotate.set_tooltip("Rotate the image around the pan position")
          b.restore.set_tooltip("Clear any transforms and center image")
          b.restore.add_callback('activated', self.restore_cb)
  
          b.rotate.set_limits(0.00, 359.99999999, incr_value=10.0)
          b.rotate.set_value(0.00)
          b.rotate.set_decimals(8)
          b.rotate.add_callback('value-changed', self.rotate_cb)
  
          fr.set_widget(w)
          vbox.add_widget(fr, stretch=0)
  
          # WCS OPTIONS
          fr = Widgets.Frame("WCS")
  
          captions = (('WCS Coords:', 'label', 'WCS Coords', 'combobox'),
                      ('WCS Display:', 'label', 'WCS Display', 'combobox'),
                      )
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
  
          b.wcs_coords.set_tooltip("Set WCS coordinate system")
          b.wcs_display.set_tooltip("Set WCS display format")
  
          # Setup WCS coords method choice
          combobox = b.wcs_coords
          index = 0
          for name in wcsmod.coord_types:
              combobox.append_text(name)
              index += 1
          method = self.t_.get('wcs_coords', "")
          try:
              index = wcsmod.coord_types.index(method)
              combobox.set_index(index)
          except ValueError:
              pass
          combobox.add_callback('activated', self.set_wcs_params_cb)
  
          # Setup WCS display format method choice
          combobox = b.wcs_display
          index = 0
          for name in wcsmod.display_types:
              combobox.append_text(name)
              index += 1
          method = self.t_.get('wcs_display', "sexagesimal")
          try:
              index = wcsmod.display_types.index(method)
              combobox.set_index(index)
          except ValueError:
              pass
          combobox.add_callback('activated', self.set_wcs_params_cb)
  
          fr.set_widget(w)
          vbox.add_widget(fr, stretch=0)
  
          # ZOOM OPTIONS
          fr = Widgets.Frame("Zoom")
  
          captions = (('Zoom Alg:', 'label', 'Zoom Alg', 'combobox'),
                      ('Zoom Rate:', 'label', 'Zoom Rate', 'spinfloat'),
                      ('Stretch XY:', 'label', 'Stretch XY', 'combobox'),
                      ('Stretch Factor:', 'label', 'Stretch Factor', 'spinfloat'),
                      ('Scale X:', 'label', 'Scale X', 'entryset'),
                      ('Scale Y:', 'label', 'Scale Y', 'entryset'),
                      ('Scale Min:', 'label', 'Scale Min', 'entryset'),
                      ('Scale Max:', 'label', 'Scale Max', 'entryset'),
                      ('Interpolation:', 'label', 'Interpolation', 'combobox'),
                      ('Zoom Defaults', 'button'))
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
  
          index = 0
          for name in self.zoomalg_names:
              b.zoom_alg.append_text(name.capitalize())
              index += 1
          zoomalg = self.t_.get('zoom_algorithm', "step")
          try:
              index = self.zoomalg_names.index(zoomalg)
              b.zoom_alg.set_index(index)
          except Exception:
              pass
          b.zoom_alg.set_tooltip("Choose Zoom algorithm")
          b.zoom_alg.add_callback('activated', self.set_zoomalg_cb)
  
          index = 0
          for name in ('X', 'Y'):
              b.stretch_xy.append_text(name)
              index += 1
          b.stretch_xy.set_index(0)
          b.stretch_xy.set_tooltip("Stretch pixels in X or Y")
          b.stretch_xy.add_callback('activated', self.set_stretch_cb)
  
          b.stretch_factor.set_limits(1.0, 10.0, incr_value=0.10)
          b.stretch_factor.set_value(1.0)
          b.stretch_factor.set_decimals(8)
          b.stretch_factor.add_callback('value-changed', self.set_stretch_cb)
          b.stretch_factor.set_tooltip("Length of pixel relative to 1 on other side")
          b.stretch_factor.set_enabled(zoomalg != 'step')
  
          zoomrate = self.t_.get('zoom_rate', math.sqrt(2.0))
          b.zoom_rate.set_limits(1.01, 10.0, incr_value=0.1)
          b.zoom_rate.set_value(zoomrate)
          b.zoom_rate.set_decimals(8)
          b.zoom_rate.set_enabled(zoomalg != 'step')
          b.zoom_rate.set_tooltip("Step rate of increase/decrease per zoom level")
          b.zoom_rate.add_callback('value-changed', self.set_zoomrate_cb)
  
          b.zoom_defaults.add_callback('activated', self.set_zoom_defaults_cb)
  
          scale_x, scale_y = self.fitsimage.get_scale_xy()
          b.scale_x.set_tooltip("Set the scale in X axis")
          b.scale_x.set_text(str(scale_x))
          b.scale_x.add_callback('activated', self.set_scale_cb)
          b.scale_y.set_tooltip("Set the scale in Y axis")
          b.scale_y.set_text(str(scale_y))
          b.scale_y.add_callback('activated', self.set_scale_cb)
  
          scale_min, scale_max = self.t_['scale_min'], self.t_['scale_max']
          b.scale_min.set_text(str(scale_min))
          b.scale_min.add_callback('activated', self.set_scale_limit_cb)
          b.scale_min.set_tooltip("Set the minimum allowed scale in any axis")
  
          b.scale_max.set_text(str(scale_max))
          b.scale_max.add_callback('activated', self.set_scale_limit_cb)
          b.scale_min.set_tooltip("Set the maximum allowed scale in any axis")
  
          index = 0
          for name in trcalc.interpolation_methods:
              b.interpolation.append_text(name)
              index += 1
          interp = self.t_.get('interpolation', "basic")
          try:
              index = trcalc.interpolation_methods.index(interp)
          except ValueError:
              # previous choice might not be available if preferences
              # were saved when opencv was being used--if so, default
              # to "basic"
              index = trcalc.interpolation_methods.index('basic')
          b.interpolation.set_index(index)
          b.interpolation.set_tooltip("Choose interpolation method")
          b.interpolation.add_callback('activated', self.set_interp_cb)
  
          fr.set_widget(w)
          vbox.add_widget(fr, stretch=0)
  
          # PAN OPTIONS
          fr = Widgets.Frame("Panning")
  
          captions = (('Pan X:', 'label', 'Pan X', 'entry',
                       'WCS sexagesimal', 'checkbutton'),
                      ('Pan Y:', 'label', 'Pan Y', 'entry',
                       'Apply Pan', 'button'),
                      ('Pan Coord:', 'label', 'Pan Coord', 'combobox'),
                      ('Center Image', 'button', 'Mark Center', 'checkbutton'),
                      )
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
  
          pan_x, pan_y = self.fitsimage.get_pan()
          coord_offset = self.fv.settings.get('pixel_coords_offset', 0.0)
          pan_coord = self.t_.get('pan_coord', "data")
          if pan_coord == 'data':
              pan_x, pan_y = pan_x + coord_offset, pan_y + coord_offset
          b.pan_x.set_tooltip("Coordinate for the pan position in X axis")
          b.pan_x.set_text(str(pan_x))
          #b.pan_x.add_callback('activated', self.set_pan_cb)
          b.pan_y.set_tooltip("Coordinate for the pan position in Y axis")
          b.pan_y.set_text(str(pan_y))
          #b.pan_y.add_callback('activated', self.set_pan_cb)
          b.apply_pan.add_callback('activated', self.set_pan_cb)
          b.apply_pan.set_tooltip("Set the pan position")
          b.wcs_sexagesimal.set_tooltip("Display pan position in sexagesimal")
          b.wcs_sexagesimal.add_callback('activated',
                                         lambda w, tf: self._update_pan_coords())
  
          index = 0
          for name in self.pancoord_options:
              b.pan_coord.append_text(name)
              index += 1
          index = self.pancoord_options.index(pan_coord)
          b.pan_coord.set_index(index)
          b.pan_coord.set_tooltip("Pan coordinates type")
          b.pan_coord.add_callback('activated', self.set_pan_coord_cb)
  
          b.center_image.set_tooltip("Set the pan position to center of the image")
          b.center_image.add_callback('activated', self.center_image_cb)
          b.mark_center.set_tooltip("Mark the center (pan locator)")
          b.mark_center.add_callback('activated', self.set_misc_cb)
  
          fr.set_widget(w)
          vbox.add_widget(fr, stretch=0)
  
          fr = Widgets.Frame("New Images")
  
          captions = (('Cut New:', 'label', 'Cut New', 'combobox'),
                      ('Zoom New:', 'label', 'Zoom New', 'combobox'),
                      ('Center New:', 'label', 'Center New', 'combobox'),
                      ('Follow New', 'checkbutton', 'Raise New', 'checkbutton'),
                      ('Create thumbnail', 'checkbutton'),
                      )
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
  
          combobox = b.cut_new
          index = 0
          for name in self.autocut_options:
              combobox.append_text(name)
              index += 1
          option = self.t_.get('autocuts', "off")
          index = self.autocut_options.index(option)
          combobox.set_index(index)
          combobox.add_callback('activated', self.set_autocuts_cb)
          b.cut_new.set_tooltip("Automatically set cut levels for new images")
  
          combobox = b.zoom_new
          index = 0
          for name in self.autozoom_options:
              combobox.append_text(name)
              index += 1
          option = self.t_.get('autozoom', "off")
          index = self.autozoom_options.index(option)
          combobox.set_index(index)
          combobox.add_callback('activated', self.set_autozoom_cb)
          b.zoom_new.set_tooltip("Automatically fit new images to window")
  
          combobox = b.center_new
          index = 0
          for name in self.autocenter_options:
              combobox.append_text(name)
              index += 1
          option = self.t_.get('autocenter', "off")
          # 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)
          combobox.set_index(index)
          combobox.add_callback('activated', self.set_autocenter_cb)
          b.center_new.set_tooltip("Automatically center new images in window")
  
          b.follow_new.set_tooltip("View new images as they arrive")
          b.raise_new.set_tooltip("Raise and focus tab for new images")
          b.create_thumbnail.set_tooltip("Create thumbnail for new images")
  
          self.w.follow_new.set_state(True)
          self.w.follow_new.add_callback('activated', self.set_chprefs_cb)
          self.w.raise_new.set_state(True)
          self.w.raise_new.add_callback('activated', self.set_chprefs_cb)
          self.w.create_thumbnail.set_state(True)
          self.w.create_thumbnail.add_callback('activated', self.set_chprefs_cb)
  
          fr.set_widget(w)
          vbox.add_widget(fr, stretch=0)
  
          exp = Widgets.Expander("General")
  
          captions = (('Num Images:', 'label', 'Num Images', 'entryset'),
                      ('Sort Order:', 'label', 'Sort Order', 'combobox'),
                      ('Use scrollbars', 'checkbutton',
                       'Preload Images', 'checkbutton'),
                      )
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
  
          b.num_images.set_tooltip(
              "Maximum number of in memory images in channel (0==unlimited)")
          num_images = self.t_.get('numImages', 0)
          self.w.num_images.set_text(str(num_images))
          self.w.num_images.add_callback('activated', self.set_buffer_cb)
  
          combobox = b.sort_order
          index = 0
          for name in self.sort_options:
              combobox.append_text(name)
              index += 1
          option = self.t_.get('sort_order', 'loadtime')
          index = self.sort_options.index(option)
          combobox.set_index(index)
          combobox.add_callback('activated', self.set_sort_cb)
          b.sort_order.set_tooltip("Sort order for images in channel")
  
          scrollbars = self.t_.get('scrollbars', 'off')
          self.w.use_scrollbars.set_state(scrollbars in ['on', 'auto'])
          self.w.use_scrollbars.add_callback('activated', self.set_scrollbars_cb)
          b.use_scrollbars.set_tooltip("Use scrollbars around viewer")
  
          preload_images = self.t_.get('preload_images', False)
          self.w.preload_images.set_state(preload_images)
          self.w.preload_images.add_callback('activated', self.set_preload_cb)
          b.preload_images.set_tooltip(
              "Preload adjacent images to speed up access")
  
          fr = Widgets.Frame()
          fr.set_widget(w)
          exp.set_widget(fr)
          vbox.add_widget(exp, stretch=0)
  
          exp = Widgets.Expander("Remember")
  
          captions = (('Restore Scale', 'checkbutton',
                       'Restore Pan', 'checkbutton'),
                      ('Restore Transform', 'checkbutton',
                      'Restore Rotation', 'checkbutton'),
                      ('Restore Cuts', 'checkbutton',
                       'Restore Color Map', 'checkbutton'),
                      )
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
  
          self.w.restore_scale.set_state(self.t_.get('profile_use_scale', False))
          self.w.restore_scale.add_callback('activated', self.set_profile_cb)
          self.w.restore_scale.set_tooltip("Remember scale with image")
          self.w.restore_pan.set_state(self.t_.get('profile_use_pan', False))
          self.w.restore_pan.add_callback('activated', self.set_profile_cb)
          self.w.restore_pan.set_tooltip("Remember pan position with image")
          self.w.restore_transform.set_state(
              self.t_.get('profile_use_transform', False))
          self.w.restore_transform.add_callback('activated', self.set_profile_cb)
          self.w.restore_transform.set_tooltip("Remember transform with image")
          self.w.restore_rotation.set_state(
              self.t_.get('profile_use_rotation', False))
          self.w.restore_rotation.add_callback('activated', self.set_profile_cb)
          self.w.restore_rotation.set_tooltip("Remember rotation with image")
          self.w.restore_cuts.set_state(self.t_.get('profile_use_cuts', False))
          self.w.restore_cuts.add_callback('activated', self.set_profile_cb)
          self.w.restore_cuts.set_tooltip("Remember cut levels with image")
          self.w.restore_color_map.set_state(
              self.t_.get('profile_use_color_map', False))
          self.w.restore_color_map.add_callback('activated', self.set_profile_cb)
          self.w.restore_color_map.set_tooltip("Remember color map with image")
  
          fr = Widgets.Frame()
          fr.set_widget(w)
          exp.set_widget(fr)
          vbox.add_widget(exp, stretch=0)
  
          exp = Widgets.Expander("ICC Profiles")
  
          captions = (('Output ICC profile:', 'label', 'Output ICC profile',
                       'combobox'),
                      ('Rendering intent:', 'label', 'Rendering intent',
                       'combobox'),
                      ('Proof ICC profile:', 'label', 'Proof ICC profile',
                       'combobox'),
                      ('Proof intent:', 'label', 'Proof intent', 'combobox'),
                      ('__x', 'spacer', 'Black point compensation', 'checkbutton'),
                      )
          w, b = Widgets.build_info(captions, orientation=orientation)
          self.w.update(b)
  
          value = self.t_.get('icc_output_profile', None)
          combobox = b.output_icc_profile
          index = 0
          for name in self.icc_profiles:
              combobox.append_text(str(name))
              index += 1
          try:
              index = self.icc_profiles.index(value)
              combobox.set_index(index)
          except Exception:
              pass
          combobox.add_callback('activated', self.set_icc_profile_cb)
          combobox.set_tooltip("ICC profile for the viewer display")
  
          value = self.t_.get('icc_output_intent', 'perceptual')
          combobox = b.rendering_intent
          index = 0
          for name in self.icc_intents:
              combobox.append_text(name)
              index += 1
          try:
              index = self.icc_intents.index(value)
              combobox.set_index(index)
          except Exception:
              pass
          combobox.add_callback('activated', self.set_icc_profile_cb)
          combobox.set_tooltip("Rendering intent for the viewer display")
  
          value = self.t_.get('icc_proof_profile', None)
          combobox = b.proof_icc_profile
          index = 0
          for name in self.icc_profiles:
              combobox.append_text(str(name))
              index += 1
          try:
              index = self.icc_profiles.index(value)
              combobox.set_index(index)
          except Exception:
              pass
          combobox.add_callback('activated', self.set_icc_profile_cb)
          combobox.set_tooltip("ICC profile for soft proofing")
  
          value = self.t_.get('icc_proof_intent', None)
          combobox = b.proof_intent
          index = 0
          for name in self.icc_intents:
              combobox.append_text(name)
              index += 1
          try:
              index = self.icc_intents.index(value)
              combobox.set_index(index)
          except Exception:
              pass
          combobox.add_callback('activated', self.set_icc_profile_cb)
          combobox.set_tooltip("Rendering intent for soft proofing")
  
          value = self.t_.get('icc_black_point_compensation', False)
          b.black_point_compensation.set_state(value)
          b.black_point_compensation.add_callback(
              'activated', self.set_icc_profile_cb)
          b.black_point_compensation.set_tooltip("Use black point compensation")
  
          fr = Widgets.Frame()
          fr.set_widget(w)
          exp.set_widget(fr)
          vbox.add_widget(exp, stretch=0)
  
          top.add_widget(sw, stretch=1)
  
          btns = Widgets.HBox()
          btns.set_spacing(4)
          btns.set_border_width(4)
  
          btn = Widgets.Button("Close")
          btn.add_callback('activated', lambda w: self.close())
          btns.add_widget(btn)
          btn = Widgets.Button("Help")
          btn.add_callback('activated', lambda w: self.help())
          btns.add_widget(btn, stretch=0)
          btn = Widgets.Button("Save Settings")
          btn.add_callback('activated', lambda w: self.save_preferences())
          btns.add_widget(btn)
          btns.add_widget(Widgets.Label(''), stretch=1)
          top.add_widget(btns, stretch=0)
  
          container.add_widget(top, stretch=1)
  
          self.gui_up = True
  
      def set_cmap_cb(self, w, index):
          """This callback is invoked when the user selects a new color
          map from the preferences pane."""
          name = cmap.get_names()[index]
          self.t_.set(color_map=name)
  
      def set_imap_cb(self, w, index):
          """This callback is invoked when the user selects a new intensity
          map from the preferences pane."""
          name = imap.get_names()[index]
          self.t_.set(intensity_map=name)
  
      def set_calg_cb(self, w, index):
          """This callback is invoked when the user selects a new color
          hashing algorithm from the preferences pane."""
          #index = w.get_index()
          name = self.calg_names[index]
          self.t_.set(color_algorithm=name)
  
      def set_tablesize_cb(self, w):
          value = int(w.get_text())
          self.t_.set(color_hashsize=value)
  
      def set_default_cmaps(self):
          cmap_name = "gray"
          imap_name = "ramp"
          index = self.cmap_names.index(cmap_name)
          self.w.cmap_choice.set_index(index)
          index = self.imap_names.index(imap_name)
          self.w.imap_choice.set_index(index)
          self.t_.set(color_map=cmap_name, intensity_map=imap_name)
  
      def set_default_distmaps(self):
          name = 'linear'
          index = self.calg_names.index(name)
          self.w.calg_choice.set_index(index)
          hashsize = 65535
          ## self.w.table_size.set_text(str(hashsize))
          self.t_.set(color_algorithm=name, color_hashsize=hashsize)
  
      def set_zoomrate_cb(self, w, rate):
          self.t_.set(zoom_rate=rate)
  
      def set_zoomrate_ext_cb(self, setting, value):
          if not self.gui_up:
              return
          self.w.zoom_rate.set_value(value)
  
      def set_zoomalg_cb(self, w, idx):
          self.t_.set(zoom_algorithm=self.zoomalg_names[idx])
  
      def set_zoomalg_ext_cb(self, setting, value):
          if not self.gui_up:
              return
          if value == 'step':
              self.w.zoom_alg.set_index(0)
              self.w.zoom_rate.set_enabled(False)
              self.w.stretch_factor.set_enabled(False)
          else:
              self.w.zoom_alg.set_index(1)
              self.w.zoom_rate.set_enabled(True)
              self.w.stretch_factor.set_enabled(True)
  
      def set_interp_cb(self, w, idx):
          self.t_.set(interpolation=trcalc.interpolation_methods[idx])
  
      def scalebase_changed_ext_cb(self, setting, value):
          if not self.gui_up:
              return
          scale_x_base, scale_y_base = self.fitsimage.get_scale_base_xy()
  
          ratio = float(scale_x_base) / float(scale_y_base)
          if ratio < 1.0:
              # Y is stretched
              idx = 1
              ratio = 1.0 / ratio
          elif ratio > 1.0:
              # X is stretched
              idx = 0
          else:
              idx = self.w.stretch_xy.get_index()
  
          # Update stretch controls to reflect actual scale
          self.w.stretch_xy.set_index(idx)
          self.w.stretch_factor.set_value(ratio)
  
      def set_zoom_defaults_cb(self, w):
          rate = math.sqrt(2.0)
          self.w.stretch_factor.set_value(1.0)
          self.t_.set(zoom_algorithm='step', zoom_rate=rate,
                      scale_x_base=1.0, scale_y_base=1.0)
  
      def set_stretch_cb(self, *args):
          axis = self.w.stretch_xy.get_index()
          value = self.w.stretch_factor.get_value()
          if axis == 0:
              self.t_.set(scale_x_base=value, scale_y_base=1.0)
          else:
              self.t_.set(scale_x_base=1.0, scale_y_base=value)
  
      def set_autocenter_cb(self, w, idx):
          option = self.autocenter_options[idx]
          self.fitsimage.set_autocenter(option)
          self.t_.set(autocenter=option)
  
      def autocenter_changed_ext_cb(self, setting, option):
          if not self.gui_up:
              return
          index = self.autocenter_options.index(option)
          self.w.center_new.set_index(index)
  
      def set_scale_cb(self, w, val):
          scale_x = float(self.w.scale_x.get_text())
          scale_y = float(self.w.scale_y.get_text())
          self.fitsimage.scale_to(scale_x, scale_y)
  
      def scale_changed_ext_cb(self, setting, value):
          if not self.gui_up:
              return
          scale_x, scale_y = value
          self.w.scale_x.set_text(str(scale_x))
          self.w.scale_y.set_text(str(scale_y))
  
      def set_scale_limit_cb(self, *args):
          scale_min = self.w.scale_min.get_text().lower()
          if scale_min == 'none':
              scale_min = None
          else:
              scale_min = float(scale_min)
          scale_max = self.w.scale_max.get_text().lower()
          if scale_max == 'none':
              scale_max = None
          else:
              scale_max = float(scale_max)
          self.t_.set(scale_min=scale_min, scale_max=scale_max)
  
      def set_autozoom_cb(self, w, idx):
          option = self.autozoom_options[idx]
          self.fitsimage.enable_autozoom(option)
          self.t_.set(autozoom=option)
  
      def autozoom_changed_ext_cb(self, setting, option):
          if not self.gui_up:
              return
          index = self.autozoom_options.index(option)
          self.w.zoom_new.set_index(index)
  
      def cut_levels(self, w):
          fitsimage = self.fitsimage
          loval, hival = 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 fitsimage.cut_levels(loval, hival)
          except Exception as e:
              self.fv.show_error("Error cutting levels: %s" % (str(e)))
  
          return True
  
      def auto_levels(self, w):
          self.fitsimage.auto_levels()
  
      def cutset_cb(self, setting, value):
          if not self.gui_up:
              return
          loval, hival = value
          self.w.cut_low_value.set_text('%.4g' % (loval))
          self.w.cut_high_value.set_text('%.4g' % (hival))
  
      def config_autocut_params(self, method):
          try:
              index = self.autocut_methods.index(method)
              self.w.auto_method.set_index(index)
          except Exception:
              pass
  
          # remove old params
          self.w.acvbox.remove_all()
  
          # Create new autocuts object of the right kind
          ac_class = AutoCuts.get_autocuts(method)
  
          # Build up a set of control widgets for the autocuts
          # algorithm tweakable parameters
          paramlst = ac_class.get_params_metadata()
  
          # Get the canonical version of this object stored in our cache
          # and make a ParamSet from it
          params = self.autocuts_cache.setdefault(method, Bunch.Bunch())
          self.ac_params = ParamSet.ParamSet(self.logger, params)
  
          # Build widgets for the parameter/attribute list
          w = self.ac_params.build_params(paramlst,
                                          orientation=self.orientation)
          self.ac_params.add_callback('changed', self.autocut_params_changed_cb)
  
          # Add this set of widgets to the pane
          self.w.acvbox.add_widget(w, stretch=1)
  
      def set_autocut_method_ext_cb(self, setting, value):
          if not self.gui_up:
              return
  
          autocut_method = self.t_['autocut_method']
          self.fv.gui_do(self.config_autocut_params, autocut_method)
  
      def set_autocut_params_ext_cb(self, setting, value):
          if not self.gui_up:
              return
  
          params = self.t_['autocut_params']
          params_d = dict(params)   # noqa
          self.ac_params.update_params(params_d)
          #self.fv.gui_do(self.ac_params.params_to_widgets)
  
      def set_autocut_method_cb(self, w, idx):
          method = self.autocut_methods[idx]
  
          self.config_autocut_params(method)
  
          args, kwdargs = self.ac_params.get_params()
          params = list(kwdargs.items())
          self.t_.set(autocut_method=method, autocut_params=params)
  
      def autocut_params_changed_cb(self, paramObj, ac_obj):
          """This callback is called when the user changes the attributes of
          an object via the paramSet.
          """
          args, kwdargs = paramObj.get_params()
          params = list(kwdargs.items())
          self.t_.set(autocut_params=params)
  
      def set_autocuts_cb(self, w, index):
          option = self.autocut_options[index]
          self.fitsimage.enable_autocuts(option)
          self.t_.set(autocuts=option)
  
      def autocuts_changed_ext_cb(self, setting, option):
          self.logger.debug("autocuts changed to %s" % option)
          index = self.autocut_options.index(option)
          if self.gui_up:
              self.w.cut_new.set_index(index)
  
      def set_transforms_cb(self, *args):
          flip_x = self.w.flip_x.get_state()
          flip_y = self.w.flip_y.get_state()
          swap_xy = self.w.swap_xy.get_state()
          self.t_.set(flip_x=flip_x, flip_y=flip_y, swap_xy=swap_xy)
          return True
  
      def set_transform_ext_cb(self, setting, value):
          if not self.gui_up:
              return
          flip_x, flip_y, swap_xy = (
              self.t_['flip_x'], self.t_['flip_y'], self.t_['swap_xy'])
          self.w.flip_x.set_state(flip_x)
          self.w.flip_y.set_state(flip_y)
          self.w.swap_xy.set_state(swap_xy)
  
      def rgbmap_changed_ext_cb(self, setting, value):
          if not self.gui_up:
              return
  
          calg_name = self.t_['color_algorithm']
          try:
              idx = self.calg_names.index(calg_name)
          except IndexError:
              idx = 0
          self.w.algorithm.set_index(idx)
  
          cmap_name = self.t_['color_map']
          try:
              idx = self.cmap_names.index(cmap_name)
          except IndexError:
              idx = 0
          self.w.colormap.set_index(idx)
  
          imap_name = self.t_['intensity_map']
          try:
              idx = self.imap_names.index(imap_name)
          except IndexError:
              idx = 0
          self.w.intensity.set_index(idx)
  
      def set_buflen_ext_cb(self, setting, value):
          num_images = self.t_['numImages']
  
          # update the datasrc length
          chinfo = self.channel
          chinfo.datasrc.set_bufsize(num_images)
          self.logger.debug("num images was set to {0}".format(num_images))
  
          if not self.gui_up:
              return
          self.w.num_images.set_text(str(num_images))
  
      def set_sort_cb(self, w, index):
          """This callback is invoked when the user selects a new sort order
          from the preferences pane."""
          name = self.sort_options[index]
          self.t_.set(sort_order=name)
  
      def set_preload_cb(self, w, tf):
          """This callback is invoked when the user checks the preload images
          box in the preferences pane."""
          self.t_.set(preload_images=tf)
  
      def set_scrollbars_cb(self, w, tf):
          """This callback is invoked when the user checks the 'Use Scrollbars'
          box in the preferences pane."""
          scrollbars = 'on' if tf else 'off'
          self.t_.set(scrollbars=scrollbars)
  
      def set_icc_profile_cb(self, setting, idx):
          idx = self.w.output_icc_profile.get_index()
          output_profile_name = self.icc_profiles[idx]
          idx = self.w.rendering_intent.get_index()
          intent_name = self.icc_intents[idx]
  
          idx = self.w.proof_icc_profile.get_index()
          proof_profile_name = self.icc_profiles[idx]
          idx = self.w.proof_intent.get_index()
          proof_intent = self.icc_intents[idx]
  
          bpc = self.w.black_point_compensation.get_state()
  
          self.t_.set(icc_output_profile=output_profile_name,
                      icc_output_intent=intent_name,
                      icc_proof_profile=proof_profile_name,
                      icc_proof_intent=proof_intent,
                      icc_black_point_compensation=bpc)
          return True
  
      def rotate_cb(self, w, deg):
          #deg = self.w.rotate.get_value()
          self.t_.set(rot_deg=deg)
          return True
  
      def set_rotate_ext_cb(self, setting, value):
          if not self.gui_up:
              return
          self.w.rotate.set_value(value)
          return True
  
      def center_image_cb(self, *args):
          self.fitsimage.center_image()
          return True
  
      def pan_changed_ext_cb(self, setting, value):
          if not self.gui_up:
              return
          self._update_pan_coords()
  
      def set_pan_cb(self, *args):
          idx = self.w.pan_coord.get_index()
          pan_coord = self.pancoord_options[idx]
          pan_xs = self.w.pan_x.get_text().strip()
          pan_ys = self.w.pan_y.get_text().strip()
          # TODO: use current value for other coord if only one coord supplied
          if (':' in pan_xs) or (':' in pan_ys):
              # TODO: get maximal precision
              pan_x = wcs.hmsStrToDeg(pan_xs)
              pan_y = wcs.dmsStrToDeg(pan_ys)
              pan_coord = 'wcs'
          elif pan_coord == 'wcs':
              pan_x = float(pan_xs)
              pan_y = float(pan_ys)
          else:
              coord_offset = self.fv.settings.get('pixel_coords_offset', 0.0)
              pan_x = float(pan_xs) - coord_offset
              pan_y = float(pan_ys) - coord_offset
  
          self.fitsimage.set_pan(pan_x, pan_y, coord=pan_coord)
          return True
  
      def _update_pan_coords(self):
          pan_coord = self.t_.get('pan_coord', 'data')
          pan_x, pan_y = self.fitsimage.get_pan(coord=pan_coord)
          #self.logger.debug("updating pan coords (%s) %f %f" % (pan_coord, pan_x, pan_y))
          if pan_coord == 'wcs':
              use_sex = self.w.wcs_sexagesimal.get_state()
              if use_sex:
                  pan_x = wcs.raDegToString(pan_x, format='%02d:%02d:%010.7f')
                  pan_y = wcs.decDegToString(pan_y, format='%s%02d:%02d:%09.7f')
          else:
              coord_offset = self.fv.settings.get('pixel_coords_offset', 0.0)
              pan_x += coord_offset
              pan_y += coord_offset
  
          self.w.pan_x.set_text(str(pan_x))
          self.w.pan_y.set_text(str(pan_y))
  
          index = self.pancoord_options.index(pan_coord)
          self.w.pan_coord.set_index(index)
  
      def set_pan_coord_cb(self, w, idx):
          pan_coord = self.pancoord_options[idx]
          pan_x, pan_y = self.fitsimage.get_pan(coord=pan_coord)
          self.t_.set(pan=(pan_x, pan_y), pan_coord=pan_coord)
          #self._update_pan_coords()
          return True
  
      def restore_cb(self, *args):
          self.t_.set(flip_x=False, flip_y=False, swap_xy=False,
                      rot_deg=0.0)
          self.fitsimage.center_image()
          return True
  
      def set_misc_cb(self, *args):
          markc = (self.w.mark_center.get_state() != 0)
          self.t_.set(show_pan_position=markc)
          self.fitsimage.show_pan_mark(markc)
          return True
  
      def set_chprefs_cb(self, *args):
          switchnew = (self.w.follow_new.get_state() != 0)
          raisenew = (self.w.raise_new.get_state() != 0)
          genthumb = (self.w.create_thumbnail.get_state() != 0)
          self.t_.set(switchnew=switchnew, raisenew=raisenew,
                      genthumb=genthumb)
  
      def set_chprefs_ext_cb(self, *args):
          if self.gui_up:
              self.w.follow_new.set_state(self.t_['switchnew'])
              self.w.raise_new.set_state(self.t_['raisenew'])
              self.w.create_thumbnail.set_state(self.t_['genthumb'])
  
      def set_profile_cb(self, *args):
          restore_scale = (self.w.restore_scale.get_state() != 0)
          restore_pan = (self.w.restore_pan.get_state() != 0)
          restore_cuts = (self.w.restore_cuts.get_state() != 0)
          restore_transform = (self.w.restore_transform.get_state() != 0)
          restore_rotation = (self.w.restore_rotation.get_state() != 0)
          restore_color_map = (self.w.restore_color_map.get_state() != 0)
          self.t_.set(profile_use_scale=restore_scale, profile_use_pan=restore_pan,
                      profile_use_cuts=restore_cuts,
                      profile_use_transform=restore_transform,
                      profile_use_rotation=restore_rotation,
                      profile_use_color_map=restore_color_map)
  
      def set_buffer_cb(self, *args):
          num_images = int(self.w.num_images.get_text())
          self.logger.debug("setting num images {0}".format(num_images))
          self.t_.set(numImages=num_images)
  
      def set_wcs_params_cb(self, *args):
          idx = self.w.wcs_coords.get_index()
          try:
              ctype = wcsmod.coord_types[idx]
          except IndexError:
              ctype = 'icrs'
          idx = self.w.wcs_display.get_index()
          dtype = wcsmod.display_types[idx]
          self.t_.set(wcs_coords=ctype, wcs_display=dtype)
  
      def preferences_to_controls(self):
          prefs = self.t_
  
          # color map
          rgbmap = self.fitsimage.get_rgbmap()
          cm = rgbmap.get_cmap()
          try:
              index = self.cmap_names.index(cm.name)
          except ValueError:
              # may be a custom color map installed
              index = 0
          self.w.cmap_choice.set_index(index)
  
          # color dist algorithm
          calg = rgbmap.get_hash_algorithm()
          index = self.calg_names.index(calg)
          self.w.calg_choice.set_index(index)
  
          ## size = rgbmap.get_hash_size()
          ## self.w.table_size.set_text(str(size))
  
          # intensity map
          im = rgbmap.get_imap()
          try:
              index = self.imap_names.index(im.name)
          except ValueError:
              # may be a custom intensity map installed
              index = 0
          self.w.imap_choice.set_index(index)
  
          # TODO: this is a HACK to get around Qt's callbacks
          # on setting widget values--need a way to disable callbacks
          # for direct setting
          auto_zoom = prefs.get('autozoom', 'off')
  
          # zoom settings
          zoomalg = prefs.get('zoom_algorithm', "step")
          index = self.zoomalg_names.index(zoomalg)
          self.w.zoom_alg.set_index(index)
  
          zoomrate = self.t_.get('zoom_rate', math.sqrt(2.0))
          self.w.zoom_rate.set_value(zoomrate)
          self.w.zoom_rate.set_enabled(zoomalg != 'step')
          self.w.stretch_factor.set_enabled(zoomalg != 'step')
  
          self.scalebase_changed_ext_cb(prefs, None)
  
          scale_x, scale_y = self.fitsimage.get_scale_xy()
          self.w.scale_x.set_text(str(scale_x))
          self.w.scale_y.set_text(str(scale_y))
  
          scale_min = prefs.get('scale_min', None)
          self.w.scale_min.set_text(str(scale_min))
          scale_max = prefs.get('scale_max', None)
          self.w.scale_max.set_text(str(scale_max))
  
          # panning settings
          self._update_pan_coords()
          self.w.mark_center.set_state(prefs.get('show_pan_position', False))
  
          # transform settings
          self.w.flip_x.set_state(prefs.get('flip_x', False))
          self.w.flip_y.set_state(prefs.get('flip_y', False))
          self.w.swap_xy.set_state(prefs.get('swap_xy', False))
          self.w.rotate.set_value(prefs.get('rot_deg', 0.00))
  
          # auto cuts settings
          autocuts = prefs.get('autocuts', 'off')
          index = self.autocut_options.index(autocuts)
          self.w.cut_new.set_index(index)
  
          autocut_method = prefs.get('autocut_method', None)
          if autocut_method is None:
              autocut_method = 'histogram'
          else:
              ## params = prefs.get('autocut_params', {})
              ## p = self.autocuts_cache.setdefault(autocut_method, {})
              ## p.update(params)
              pass
          self.config_autocut_params(autocut_method)
  
          # auto zoom settings
          auto_zoom = prefs.get('autozoom', 'off')
          index = self.autozoom_options.index(auto_zoom)
          self.w.zoom_new.set_index(index)
  
          # wcs settings
          method = prefs.get('wcs_coords', "icrs")
          try:
              index = wcsmod.coord_types.index(method)
              self.w.wcs_coords.set_index(index)
          except ValueError:
              pass
  
          method = prefs.get('wcs_display', "sexagesimal")
          try:
              index = wcsmod.display_types.index(method)
              self.w.wcs_display.set_index(index)
          except ValueError:
              pass
  
          # misc settings
          prefs.setdefault('switchnew', True)
          self.w.follow_new.set_state(prefs['switchnew'])
          prefs.setdefault('raisenew', True)
          self.w.raise_new.set_state(prefs['raisenew'])
          prefs.setdefault('genthumb', True)
          self.w.create_thumbnail.set_state(prefs['genthumb'])
  
          num_images = prefs.get('numImages', 0)
          self.w.num_images.set_text(str(num_images))
          prefs.setdefault('preload_images', False)
          self.w.preload_images.set_state(prefs['preload_images'])
  
          # profile settings
          prefs.setdefault('profile_use_scale', False)
          self.w.restore_scale.set_state(prefs['profile_use_scale'])
          prefs.setdefault('profile_use_pan', False)
          self.w.restore_pan.set_state(prefs['profile_use_pan'])
          prefs.setdefault('profile_use_cuts', False)
          self.w.restore_cuts.set_state(prefs['profile_use_cuts'])
          prefs.setdefault('profile_use_transform', False)
          self.w.restore_transform.set_state(prefs['profile_use_transform'])
          prefs.setdefault('profile_use_rotation', False)
          self.w.restore_rotation.set_state(prefs['profile_use_rotation'])
          prefs.setdefault('profile_use_color_map', False)
          self.w.restore_color_map.set_state(prefs['profile_use_color_map'])
  
      def save_preferences(self):
          self.t_.save()
  
      def close(self):
          self.fv.stop_local_plugin(self.chname, str(self))
          return True
  
      def start(self):
          self.preferences_to_controls()
  
      def pause(self):
          pass
  
      def resume(self):
          pass
  
      def stop(self):
          self.gui_up = False
  
      def redo(self):
          pass
  
      def __str__(self):
          return 'preferences'
  
  # END
  