Example #1
0
class MIPick(Pick):
    """This is like ``Pick`` plugin but modified to work with ``MultiImage``
    plugin."""
    def __init__(self, fv, fitsimage):
        # superclass defines some variables for us, like logger
        super(MIPick, self).__init__(fv, fitsimage)

        # Override parent attributes
        self.layertag = 'mipick-canvas'
        self._textlabel = 'MIPick'

        # Additional attributes
        self.multiimage_name = 'MultiImage'
        self.region = None

    def resume(self):
        super(MIPick, self).resume()

        # Setup the region
        if self.region is None:
            self.region = Region()
            self.region.coord = 'wcs'
            self.region.image = self.fitsimage.get_image()

        # See if multiimage is active
        opmon = self.chinfo.opmon
        multiimage = None
        if opmon.is_active(self.multiimage_name):
            try:
                multiimage = opmon.getPlugin(self.multiimage_name)
            except Exception:
                multiimage = None
            else:
                multiimage.region = self.region
        self.multiimage = multiimage

    def redo(self):
        if self.picktag is None:
            return

        serialnum = self.bump_serial()
        self.ev_intr.set()

        fig = self.canvas.get_object_by_tag(self.picktag)
        if fig.kind != 'compound':
            return True
        bbox = fig.objects[0]
        self.region.image = self.fitsimage.get_image()
        self.draw_compound(bbox, self.canvas, *self.region.bbox(coord='data'))
        fig = self.canvas.getObjectByTag(self.picktag)
        bbox = fig.objects[0]

        # set the pick image to have the same cut levels and transforms
        self.fitsimage.copy_attributes(self.pickimage,
                                       ['transforms', 'cutlevels', 'rgbmap'])

        try:
            # Get other parts of the indicator
            point = fig.objects[1]
            text = fig.objects[2]

            # sanity check on region
            width = bbox.x2 - bbox.x1
            height = bbox.y2 - bbox.y1
            if (width > self.max_side) or (height > self.max_side):
                errmsg = "Image area (%dx%d) too large!" % (width, height)
                self.fv.show_error(errmsg)
                raise Exception(errmsg)

            # Cut and show pick image in pick window
            self.logger.debug("bbox %f,%f %f,%f" %
                              (bbox.x1, bbox.y1, bbox.x2, bbox.y2))
            x1, y1, x2, y2, data = self.cutdetail(self.fitsimage,
                                                  self.pickimage, int(bbox.x1),
                                                  int(bbox.y1), int(bbox.x2),
                                                  int(bbox.y2))
            self.logger.debug("cut box %f,%f %f,%f" % (x1, y1, x2, y2))

            # calculate center of pick image
            wd, ht = self.pickimage.get_data_size()
            xc = wd // 2
            yc = ht // 2
            if self.pickcenter is None:
                p_canvas = self.pickimage.get_canvas()
                tag = p_canvas.add(
                    self.dc.Point(xc, yc, 5, linewidth=1, color='red'))
                self.pickcenter = p_canvas.get_object_by_tag(tag)

            self.pick_x1, self.pick_y1 = x1, y1
            self.pick_data = data
            self.wdetail.sample_area.set_text('%dx%d' % (x2 - x1, y2 - y1))

            point.color = 'red'
            text.text = '{0}: calc'.format(self._textlabel)
            self.pickcenter.x = xc
            self.pickcenter.y = yc
            self.pickcenter.color = 'red'

            # clear contour and fwhm plots
            if self.have_mpl:
                self.clear_contours()
                self.clear_fwhm()
                self.clear_radial()

            # If multiimage, redo there also.
            try:
                self.multiimage.redo()
            except Exception:
                """Doesn't matter"""
                pass

            # Delete previous peak marks
            objs = self.canvas.getObjectsByTagpfx('peak')
            self.canvas.delete_objects(objs)

            # Offload this task to another thread so that GUI remains
            # responsive
            self.fv.nongui_do(self.search, serialnum, data, x1, y1, wd, ht,
                              fig)

        except Exception as e:
            self.logger.error("Error calculating quality metrics: %s" %
                              (str(e)))
            return True

    def draw_cb(self, canvas, tag):
        obj = canvas.getObjectByTag(tag)
        self.draw_compound(obj, canvas)
        return self.redo()

    def edit_cb(self, canvas, obj):
        if obj.kind != 'rectangle':
            return True

        # Get the compound object that sits on the canvas.
        # Make sure edited rectangle was our pick rectangle.
        c_obj = self.canvas.get_object_by_tag(self.picktag)
        if ((c_obj.kind != 'compound') or (len(c_obj.objects) < 3)
                or (c_obj.objects[0] != obj)):
            return False

        # determine center of rectangle
        x1, y1, x2, y2 = obj.get_llur()
        x = x1 + (x2 - x1) // 2
        y = y1 + (y2 - y1) // 2

        # reposition other elements to match
        point = c_obj.objects[1]
        point.x, point.y = x, y
        text = c_obj.objects[2]
        text.x, text.y = x1, y2 + 4

        self.regions.set_bbox(x1, y1, x2, y2, coord='data')

        return self.redo()

    def reset_region(self):
        self.dx = region_default_width
        self.dy = region_default_height

        obj = self.canvas.get_object_by_tag(self.picktag)
        if obj.kind != 'compound':
            return True
        bbox = obj.objects[0]

        # calculate center of bbox
        wd = bbox.x2 - bbox.x1
        dw = wd // 2
        ht = bbox.y2 - bbox.y1
        dh = ht // 2
        x, y = bbox.x1 + dw, bbox.y1 + dh

        # calculate new coords
        bbox.x1, bbox.y1, bbox.x2, bbox.y2 = (x - self.dx, y - self.dy,
                                              x + self.dx, y + self.dy)

        self.regions.set_bbox(bbox.x1, bbox.y1, bbox.x2, bbox.y2, coord='data')

        self.redo()

    def draw_compound(self, obj, canvas, *args):
        """Draw the pick info box."""
        if obj.kind != 'rectangle':
            return True
        canvas.deleteObject(obj)

        if self.picktag:
            try:
                canvas.deleteObjectByTag(self.picktag)
            except Exception:
                pass

        # Get rectangle:
        if len(args) == 4:
            x1, y1, x2, y2 = args
        else:
            x1, y1, x2, y2 = obj.get_llur()

        # determine center of rectangle
        x = x1 + (x2 - x1) // 2
        y = y1 + (y2 - y1) // 2

        tag = canvas.add(
            self.dc.CompoundObject(
                self.dc.Rectangle(x1, y1, x2, y2, color=self.pickcolor),
                self.dc.Point(x, y, 10, color='red'),
                self.dc.Text(x1,
                             y2 + 4,
                             '{0}: calc'.format(self._textlabel),
                             color=self.pickcolor)))
        self.picktag = tag
        self.region.set_bbox(x1, y1, x2, y2, coord='data')

    def __str__(self):
        return 'mipick'
Example #2
0
class MIPick(Pick):
    """This is like ``Pick`` plugin but modified to work with ``MultiImage``
    plugin."""
    def __init__(self, fv, fitsimage):
        # superclass defines some variables for us, like logger
        super(MIPick, self).__init__(fv, fitsimage)

        # Override parent attributes
        self.layertag = 'mipick-canvas'
        self._textlabel = 'MIPick'

        # Additional attributes
        self.multiimage_name = 'MultiImage'
        self.region = None

    def resume(self):
        super(MIPick, self).resume()

        # Setup the region
        if self.region is None:
            self.region = Region()
            self.region.coord = 'wcs'
            self.region.image = self.fitsimage.get_image()

        # See if multiimage is active
        opmon = self.chinfo.opmon
        multiimage = None
        if opmon.is_active(self.multiimage_name):
            try:
                multiimage = opmon.getPlugin(self.multiimage_name)
            except:
                multiimage = None
            else:
                multiimage.region = self.region
        self.multiimage = multiimage

    def redo(self):
        if self.picktag is None:
            return

        serialnum = self.bump_serial()
        self.ev_intr.set()

        fig = self.canvas.get_object_by_tag(self.picktag)
        if fig.kind != 'compound':
            return True
        bbox = fig.objects[0]
        self.region.image = self.fitsimage.get_image()
        self.draw_compound(bbox, self.canvas, *self.region.bbox(coord='data'))
        fig = self.canvas.getObjectByTag(self.picktag)
        bbox = fig.objects[0]

        # set the pick image to have the same cut levels and transforms
        self.fitsimage.copy_attributes(self.pickimage,
                                       ['transforms', 'cutlevels', 'rgbmap'])

        try:
            # Get other parts of the indicator
            point = fig.objects[1]
            text = fig.objects[2]

            # sanity check on region
            width = bbox.x2 - bbox.x1
            height = bbox.y2 - bbox.y1
            if (width > self.max_side) or (height > self.max_side):
                errmsg = "Image area (%dx%d) too large!" % (width, height)
                self.fv.show_error(errmsg)
                raise Exception(errmsg)

            # Cut and show pick image in pick window
            self.logger.debug("bbox %f,%f %f,%f" % (bbox.x1, bbox.y1,
                                                    bbox.x2, bbox.y2))
            x1, y1, x2, y2, data = self.cutdetail(self.fitsimage,
                                                  self.pickimage,
                                                  int(bbox.x1), int(bbox.y1),
                                                  int(bbox.x2), int(bbox.y2))
            self.logger.debug("cut box %f,%f %f,%f" % (x1, y1, x2, y2))

            # calculate center of pick image
            wd, ht = self.pickimage.get_data_size()
            xc = wd // 2
            yc = ht // 2
            if self.pickcenter is None:
                p_canvas = self.pickimage.get_canvas()
                tag = p_canvas.add(self.dc.Point(xc, yc, 5,
                                                 linewidth=1, color='red'))
                self.pickcenter = p_canvas.get_object_by_tag(tag)

            self.pick_x1, self.pick_y1 = x1, y1
            self.pick_data = data
            self.wdetail.sample_area.set_text('%dx%d' % (x2 - x1, y2 - y1))

            point.color = 'red'
            text.text = '{0}: calc'.format(self._textlabel)
            self.pickcenter.x = xc
            self.pickcenter.y = yc
            self.pickcenter.color = 'red'

            # clear contour and fwhm plots
            if self.have_mpl:
                self.clear_contours()
                self.clear_fwhm()
                self.clear_radial()

            # If multiimage, redo there also.
            try:
                self.multiimage.redo()
            except:
                """Doesn't matter"""
                pass

            # Delete previous peak marks
            objs = self.canvas.getObjectsByTagpfx('peak')
            self.canvas.delete_objects(objs)

            # Offload this task to another thread so that GUI remains
            # responsive
            self.fv.nongui_do(self.search, serialnum, data,
                              x1, y1, wd, ht, fig)

        except Exception as e:
            self.logger.error("Error calculating quality metrics: %s" % (
                str(e)))
            return True

    def draw_cb(self, canvas, tag):
        obj = canvas.getObjectByTag(tag)
        self.draw_compound(obj, canvas)
        return self.redo()

    def edit_cb(self, canvas, obj):
        if obj.kind != 'rectangle':
            return True

        # Get the compound object that sits on the canvas.
        # Make sure edited rectangle was our pick rectangle.
        c_obj = self.canvas.get_object_by_tag(self.picktag)
        if ((c_obj.kind != 'compound') or (len(c_obj.objects) < 3) or
                (c_obj.objects[0] != obj)):
            return False

        # determine center of rectangle
        x1, y1, x2, y2 = obj.get_llur()
        x = x1 + (x2 - x1) // 2
        y = y1 + (y2 - y1) // 2

        # reposition other elements to match
        point = c_obj.objects[1]
        point.x, point.y = x, y
        text = c_obj.objects[2]
        text.x, text.y = x1, y2 + 4

        self.regions.set_bbox(x1, y1, x2, y2, coord='data')

        return self.redo()

    def reset_region(self):
        self.dx = region_default_width
        self.dy = region_default_height

        obj = self.canvas.get_object_by_tag(self.picktag)
        if obj.kind != 'compound':
            return True
        bbox = obj.objects[0]

        # calculate center of bbox
        wd = bbox.x2 - bbox.x1
        dw = wd // 2
        ht = bbox.y2 - bbox.y1
        dh = ht // 2
        x, y = bbox.x1 + dw, bbox.y1 + dh

        # calculate new coords
        bbox.x1, bbox.y1, bbox.x2, bbox.y2 = (x - self.dx, y - self.dy,
                                              x + self.dx, y + self.dy)

        self.regions.set_bbox(bbox.x1, bbox.y1,
                              bbox.x2, bbox.y2, coord='data')

        self.redo()

    def draw_compound(self, obj, canvas, *args):
        """Draw the pick info box."""
        if obj.kind != 'rectangle':
            return True
        canvas.deleteObject(obj)

        if self.picktag:
            try:
                canvas.deleteObjectByTag(self.picktag)
            except:
                pass

        # Get rectangle:
        if len(args) == 4:
            x1, y1, x2, y2 = args
        else:
            x1, y1, x2, y2 = obj.get_llur()

        # determine center of rectangle
        x = x1 + (x2 - x1) // 2
        y = y1 + (y2 - y1) // 2

        tag = canvas.add(self.dc.CompoundObject(
            self.dc.Rectangle(x1, y1, x2, y2, color=self.pickcolor),
            self.dc.Point(x, y, 10, color='red'),
            self.dc.Text(x1, y2 + 4, '{0}: calc'.format(self._textlabel),
                         color=self.pickcolor)))
        self.picktag = tag
        self.region.set_bbox(x1, y1, x2, y2, coord='data')

    def __str__(self):
        return 'mipick'
Example #3
0
class MIPick(GingaPlugin.LocalPlugin):

    def __init__(self, fv, fitsimage):
        # superclass defines some variables for us, like logger
        super(MIPick, self).__init__(fv, fitsimage)

        self.layertag = 'pick-canvas'
        self.pickimage = None
        self.pickcenter = None
        self.pick_qs = None
        self.picktag = None
        self.region = None

        # get Pick preferences
        prefs = self.fv.get_preferences()
        self.settings = prefs.createCategory('plugin_Pick')
        self.settings.load(onError='silent')

        self.sync_preferences()

        self.pick_x1 = 0
        self.pick_y1 = 0
        self.pick_data = None
        self.pick_log = None
        self.dx = region_default_width
        self.dy = region_default_height
        # For offloading intensive calculation from graphics thread
        self.serialnum = 0
        self.lock = threading.RLock()
        self.lock2 = threading.RLock()
        self.ev_intr = threading.Event()

        self.last_rpt = {}
        self.iqcalc = iqcalc.IQCalc(self.logger)

        self.dc = self.fv.getDrawClasses()

        canvas = self.dc.DrawingCanvas()
        canvas.enable_draw(True)
        canvas.enable_edit(True)
        canvas.set_drawtype('rectangle', color='cyan', linestyle='dash',
                            drawdims=True)
        canvas.set_callback('draw-event', self.draw_cb)
        canvas.set_callback('edit-event', self.edit_cb)
        canvas.add_draw_mode('move', down=self.btndown,
                             move=self.drag, up=self.update)
        canvas.register_for_cursor_drawing(self.fitsimage)
        canvas.setSurface(self.fitsimage)
        canvas.set_draw_mode('move')
        self.canvas = canvas

        self.have_mpl = have_mpl

    def sync_preferences(self):
        # Load various preferences
        self.pickcolor = self.settings.get('color_pick', 'green')
        self.candidate_color = self.settings.get('color_candidate', 'purple')

        # Peak finding parameters and selection criteria
        self.max_side = self.settings.get('max_side', 1024)
        self.radius = self.settings.get('radius', 10)
        self.threshold = self.settings.get('threshold', None)
        self.min_fwhm = self.settings.get('min_fwhm', 2.0)
        self.max_fwhm = self.settings.get('max_fwhm', 50.0)
        self.min_ellipse = self.settings.get('min_ellipse', 0.5)
        self.edgew = self.settings.get('edge_width', 0.01)
        self.show_candidates = self.settings.get('show_candidates', False)
        # Report in 0- or 1-based coordinates
        coord_offset = self.fv.settings.get('pixel_coords_offset', 0.0)
        self.pixel_coords_offset = self.settings.get('pixel_coords_offset',
                                                     coord_offset)

        # For controls
        self.delta_sky = self.settings.get('delta_sky', 0.0)
        self.delta_bright = self.settings.get('delta_bright', 0.0)

        # Formatting for reports
        self.do_record = self.settings.get('record_picks', False)
        self.rpt_header = self.settings.get('report_header',
                                            "# ra, dec, eq, x, y, fwhm, fwhm_x, fwhm_y, starsize, ellip, bg, sky, bright, time_local, time_ut")
        self.rpt_format = self.settings.get('report_format',
                                            "%(ra_deg)f, %(dec_deg)f, %(equinox)6.1f, %(x)f, %(y)f, %(fwhm)f, %(fwhm_x)f, %(fwhm_y)f, %(starsize)f, %(ellipse)f, %(background)f, %(skylevel)f, %(brightness)f, %(time_local)s, %(time_ut)s")

        self.do_report_log = self.settings.get('report_to_log', False)
        report_log = self.settings.get('report_log_path', None)
        if report_log is None:
            report_log = "pick_log.txt"
        self.report_log = report_log

        # For contour plot
        self.num_contours = self.settings.get('num_contours', 8)
        self.contour_size_limit = self.settings.get('contour_size_limit', 70)

    def build_gui(self, container):
        assert iqcalc.have_scipy == True, \
               Exception("Please install python-scipy to use this plugin")

        self.pickcenter = None

        vtop = Widgets.VBox()
        vtop.set_border_width(4)

        vbox, sw, orientation = Widgets.get_oriented_box(container)
        vbox.set_border_width(4)
        vbox.set_spacing(2)

        self.msgFont = self.fv.getFont("sansFont", 12)
        tw = Widgets.TextArea(wrap=True, editable=False)
        tw.set_font(self.msgFont)
        self.tw = tw

        fr = Widgets.Expander("Instructions")
        fr.set_widget(tw)
        vbox.add_widget(fr, stretch=0)

        vpaned = Widgets.Splitter(orientation=orientation)

        nb = Widgets.TabWidget(tabpos='bottom')
        self.w.nb1 = nb
        vpaned.add_widget(nb)

        cm, im = self.fv.cm, self.fv.im

        di = Viewers.ImageViewCanvas(logger=self.logger)
        width, height = 200, 200
        #di.set_desired_size(width, height)
        di.configure_window(width, height)
        di.enable_autozoom('off')
        di.enable_autocuts('off')
        di.zoom_to(3)
        settings = di.get_settings()
        settings.getSetting('zoomlevel').add_callback('set',
                               self.zoomset, di)
        di.set_cmap(cm)
        di.set_imap(im)
        di.set_callback('none-move', self.detailxy)
        di.set_bg(0.4, 0.4, 0.4)
        # for debugging
        di.set_name('pickimage')
        self.pickimage = di

        bd = di.get_bindings()
        bd.enable_pan(True)
        bd.enable_zoom(True)
        bd.enable_cuts(True)

        iw = Viewers.GingaViewerWidget(viewer=di)
        nb.add_widget(iw, title="Image")
        di.configure(width, height)

        if have_mpl:
            # Contour plot
            self.contour_plot = plots.ContourPlot(logger=self.logger)
            self.contour_plot.add_axis(axisbg='black')
            pw = Plot.PlotWidget(self.contour_plot)
            pw.resize(400, 300)
            nb.add_widget(pw, title="Contour")

            # FWHM gaussians plot
            self.fwhm_plot = plots.FWHMPlot(logger=self.logger)
            self.fwhm_plot.add_axis(axisbg='white')
            pw = Plot.PlotWidget(self.fwhm_plot)
            pw.resize(400, 300)
            nb.add_widget(pw, title="FWHM")

            # Radial profile plot
            self.radial_plot = plots.RadialPlot(logger=self.logger)
            self.radial_plot.add_axis(axisbg='white')
            pw = Plot.PlotWidget(self.radial_plot)
            pw.resize(400, 300)
            nb.add_widget(pw, title="Radial")

        vpaned.add_widget(Widgets.Label(''))
        vbox.add_widget(vpaned, stretch=1)
        #vbox.add_widget(nb, stretch=1)

        fr = Widgets.Frame("Pick")

        nb = Widgets.TabWidget(tabpos='bottom')
        self.w.nb2 = nb

        # Build report panel
        captions = (('Zoom:', 'label', 'Zoom', 'llabel',
                     'Contour Zoom:', 'label', 'Contour Zoom', 'llabel'),
                    ('Object_X', 'label', 'Object_X', 'llabel',
                     'Object_Y', 'label', 'Object_Y', 'llabel'),
                    ('RA:', 'label', 'RA', 'llabel',
                     'DEC:', 'label', 'DEC', 'llabel'),
                    ('Equinox:', 'label', 'Equinox', 'llabel',
                     'Background:', 'label', 'Background', 'llabel'),
                    ('Sky Level:', 'label', 'Sky Level', 'llabel',
                     'Brightness:', 'label', 'Brightness', 'llabel'),
                    ('FWHM X:', 'label', 'FWHM X', 'llabel',
                     'FWHM Y:', 'label', 'FWHM Y', 'llabel'),
                    ('FWHM:', 'label', 'FWHM', 'llabel',
                     'Star Size:', 'label', 'Star Size', 'llabel'),
                    ('Sample Area:', 'label', 'Sample Area', 'llabel',
                     'Default Region', 'button'),
                    ('Pan to pick', 'button'),
                    )

        w, b = Widgets.build_info(captions, orientation=orientation)
        self.w.update(b)
        b.zoom.set_text(self.fv.scale2text(di.get_scale()))
        self.wdetail = b
        b.default_region.add_callback('activated',
                                      lambda w: self.reset_region())
        b.default_region.set_tooltip("Reset region size to default")
        b.pan_to_pick.add_callback('activated',
                                   lambda w: self.pan_to_pick_cb())
        b.pan_to_pick.set_tooltip("Pan image to pick center")

        vbox1 = Widgets.VBox()
        vbox1.add_widget(w, stretch=0)

        # spacer
        vbox1.add_widget(Widgets.Label(''), stretch=0)

        # Pick field evaluation status
        hbox = Widgets.HBox()
        hbox.set_spacing(4)
        hbox.set_border_width(4)
        label = Widgets.Label()
        #label.set_alignment(0.05, 0.5)
        self.w.eval_status = label
        hbox.add_widget(self.w.eval_status, stretch=0)
        hbox.add_widget(Widgets.Label(''), stretch=1)
        vbox1.add_widget(hbox, stretch=0)

        # Pick field evaluation progress bar and stop button
        hbox = Widgets.HBox()
        hbox.set_spacing(4)
        hbox.set_border_width(4)
        btn = Widgets.Button("Stop")
        btn.add_callback('activated', lambda w: self.eval_intr())
        btn.set_enabled(False)
        self.w.btn_intr_eval = btn
        hbox.add_widget(btn, stretch=0)

        self.w.eval_pgs = Widgets.ProgressBar()
        hbox.add_widget(self.w.eval_pgs, stretch=1)
        vbox1.add_widget(hbox, stretch=0)

        nb.add_widget(vbox1, title="Readout")

        # Build settings panel
        captions = (('Show Candidates', 'checkbutton'),
                    ('Radius:', 'label', 'xlbl_radius', 'label',
                     'Radius', 'spinbutton'),
                    ('Threshold:', 'label', 'xlbl_threshold', 'label',
                     'Threshold', 'entry'),
                    ('Min FWHM:', 'label', 'xlbl_min_fwhm', 'label',
                     'Min FWHM', 'spinbutton'),
                    ('Max FWHM:', 'label', 'xlbl_max_fwhm', 'label',
                     'Max FWHM', 'spinbutton'),
                    ('Ellipticity:', 'label', 'xlbl_ellipticity', 'label',
                     'Ellipticity', 'entry'),
                    ('Edge:', 'label', 'xlbl_edge', 'label',
                     'Edge', 'entry'),
                    ('Max side:', 'label', 'xlbl_max_side', 'label',
                     'Max side', 'spinbutton'),
                    ('Coordinate Base:', 'label',
                     'xlbl_coordinate_base', 'label',
                     'Coordinate Base', 'entry'),
                    ('Redo Pick', 'button'),
                    )

        w, b = Widgets.build_info(captions, orientation=orientation)
        self.w.update(b)
        b.radius.set_tooltip("Radius for peak detection")
        b.threshold.set_tooltip("Threshold for peak detection (blank=default)")
        b.min_fwhm.set_tooltip("Minimum FWHM for selection")
        b.max_fwhm.set_tooltip("Maximum FWHM for selection")
        b.ellipticity.set_tooltip("Minimum ellipticity for selection")
        b.edge.set_tooltip("Minimum edge distance for selection")
        b.show_candidates.set_tooltip("Show all peak candidates")
        b.coordinate_base.set_tooltip("Base of pixel coordinate system")
        # radius control
        #b.radius.set_digits(2)
        #b.radius.set_numeric(True)
        b.radius.set_limits(5.0, 200.0, incr_value=1.0)

        def chg_radius(w, val):
            self.radius = float(val)
            self.w.xlbl_radius.set_text(str(self.radius))
            return True
        b.xlbl_radius.set_text(str(self.radius))
        b.radius.add_callback('value-changed', chg_radius)

        # threshold control
        def chg_threshold(w):
            threshold = None
            ths = w.get_text().strip()
            if len(ths) > 0:
                threshold = float(ths)
            self.threshold = threshold
            self.w.xlbl_threshold.set_text(str(self.threshold))
            return True
        b.xlbl_threshold.set_text(str(self.threshold))
        b.threshold.add_callback('activated', chg_threshold)

        # min fwhm
        #b.min_fwhm.set_digits(2)
        #b.min_fwhm.set_numeric(True)
        b.min_fwhm.set_limits(0.1, 200.0, incr_value=0.1)
        b.min_fwhm.set_value(self.min_fwhm)
        def chg_min(w, val):
            self.min_fwhm = float(val)
            self.w.xlbl_min_fwhm.set_text(str(self.min_fwhm))
            return True
        b.xlbl_min_fwhm.set_text(str(self.min_fwhm))
        b.min_fwhm.add_callback('value-changed', chg_min)

        # max fwhm
        #b.max_fwhm.set_digits(2)
        #b.max_fwhm.set_numeric(True)
        b.max_fwhm.set_limits(0.1, 200.0, incr_value=0.1)
        b.max_fwhm.set_value(self.max_fwhm)
        def chg_max(w, val):
            self.max_fwhm = float(val)
            self.w.xlbl_max_fwhm.set_text(str(self.max_fwhm))
            return True
        b.xlbl_max_fwhm.set_text(str(self.max_fwhm))
        b.max_fwhm.add_callback('value-changed', chg_max)

        # Ellipticity control
        def chg_ellipticity(w):
            minellipse = None
            val = w.get_text().strip()
            if len(val) > 0:
                minellipse = float(val)
            self.min_ellipse = minellipse
            self.w.xlbl_ellipticity.set_text(str(self.min_ellipse))
            return True
        b.xlbl_ellipticity.set_text(str(self.min_ellipse))
        b.ellipticity.add_callback('activated', chg_ellipticity)

        # Edge control
        def chg_edgew(w):
            edgew = None
            val = w.get_text().strip()
            if len(val) > 0:
                edgew = float(val)
            self.edgew = edgew
            self.w.xlbl_edge.set_text(str(self.edgew))
            return True
        b.xlbl_edge.set_text(str(self.edgew))
        b.edge.add_callback('activated', chg_edgew)

        #b.max_side.set_digits(0)
        #b.max_side.set_numeric(True)
        b.max_side.set_limits(5, 10000, incr_value=10)
        b.max_side.set_value(self.max_side)
        def chg_max_side(w, val):
            self.max_side = int(val)
            self.w.xlbl_max_side.set_text(str(self.max_side))
            return True
        b.xlbl_max_side.set_text(str(self.max_side))
        b.max_side.add_callback('value-changed', chg_max_side)

        b.redo_pick.add_callback('activated', lambda w: self.redo())
        b.show_candidates.set_state(self.show_candidates)
        b.show_candidates.add_callback('activated', self.show_candidates_cb)
        self.w.xlbl_coordinate_base.set_text(str(self.pixel_coords_offset))
        b.coordinate_base.set_text(str(self.pixel_coords_offset))
        b.coordinate_base.add_callback('activated', self.coordinate_base_cb)

        nb.add_widget(w, title="Settings")

        # Build controls panel
        vbox3 = Widgets.VBox()
        captions = (
            ('Sky cut', 'button', 'Delta sky:', 'label',
             'xlbl_delta_sky', 'label', 'Delta sky', 'entry'),
            ('Bright cut', 'button', 'Delta bright:', 'label',
             'xlbl_delta_bright', 'label', 'Delta bright', 'entry'),
            )

        w, b = Widgets.build_info(captions, orientation=orientation)
        self.w.update(b)
        b.sky_cut.set_tooltip("Set image low cut to Sky Level")
        b.delta_sky.set_tooltip("Delta to apply to low cut")
        b.bright_cut.set_tooltip("Set image high cut to Sky Level+Brightness")
        b.delta_bright.set_tooltip("Delta to apply to high cut")

        b.sky_cut.set_enabled(False)
        self.w.btn_sky_cut = b.sky_cut
        self.w.btn_sky_cut.add_callback('activated', lambda w: self.sky_cut())
        self.w.sky_cut_delta = b.delta_sky
        b.xlbl_delta_sky.set_text(str(self.delta_sky))
        b.delta_sky.set_text(str(self.delta_sky))
        def chg_delta_sky(w):
            delta_sky = 0.0
            val = w.get_text().strip()
            if len(val) > 0:
                delta_sky = float(val)
            self.delta_sky = delta_sky
            self.w.xlbl_delta_sky.set_text(str(self.delta_sky))
            return True
        b.delta_sky.add_callback('activated', chg_delta_sky)

        b.bright_cut.set_enabled(False)
        self.w.btn_bright_cut = b.bright_cut
        self.w.btn_bright_cut.add_callback('activated',
                                           lambda w: self.bright_cut())
        self.w.bright_cut_delta = b.delta_bright
        b.xlbl_delta_bright.set_text(str(self.delta_bright))
        b.delta_bright.set_text(str(self.delta_bright))
        def chg_delta_bright(w):
            delta_bright = 0.0
            val = w.get_text().strip()
            if len(val) > 0:
                delta_bright = float(val)
            self.delta_bright = delta_bright
            self.w.xlbl_delta_bright.set_text(str(self.delta_bright))
            return True
        b.delta_bright.add_callback('activated', chg_delta_bright)

        vbox3.add_widget(w, stretch=0)
        vbox3.add_widget(Widgets.Label(''), stretch=1)
        nb.add_widget(vbox3, title="Controls")

        vbox3 = Widgets.VBox()
        msgFont = self.fv.getFont("fixedFont", 10)
        tw = Widgets.TextArea(wrap=False, editable=True)
        tw.set_font(msgFont)
        self.w.report = tw
        sw1 = Widgets.ScrollArea()
        sw1.set_widget(tw)
        vbox3.add_widget(sw1, stretch=1)
        tw.append_text(self._make_report_header())

        btns = Widgets.HBox()
        btns.set_spacing(4)
        btn = Widgets.Button("Add Pick")
        btn.add_callback('activated', lambda w: self.add_pick_cb())
        btns.add_widget(btn)
        btn = Widgets.CheckBox("Record Picks automatically")
        btn.set_state(self.do_record)
        btn.add_callback('activated', self.record_cb)
        btns.add_widget(btn)
        btns.add_widget(Widgets.Label(''), stretch=1)
        vbox3.add_widget(btns, stretch=0)

        btns = Widgets.HBox()
        btns.set_spacing(4)
        btn = Widgets.CheckBox("Log Records")
        btn.set_state(self.do_report_log)
        btn.add_callback('activated', self.do_report_log_cb)
        btns.add_widget(btn)
        btns.add_widget(Widgets.Label("File:"))
        ent = Widgets.TextEntry()
        ent.set_text(self.report_log)
        ent.add_callback('activated', self.set_report_log_cb)
        btns.add_widget(ent, stretch=1)
        vbox3.add_widget(btns, stretch=0)

        nb.add_widget(vbox3, title="Report")

        fr.set_widget(nb)
        vbox.add_widget(fr, stretch=0)

        mode = self.canvas.get_draw_mode()
        hbox = Widgets.HBox()
        btn1 = Widgets.RadioButton("Move")
        btn1.set_state(mode == 'move')
        btn1.add_callback('activated', lambda w, val: self.set_mode_cb('move', val))
        btn1.set_tooltip("Choose this to position pick")
        self.w.btn_move = btn1
        hbox.add_widget(btn1)

        btn2 = Widgets.RadioButton("Draw", group=btn1)
        btn2.set_state(mode == 'draw')
        btn2.add_callback('activated', lambda w, val: self.set_mode_cb('draw', val))
        btn2.set_tooltip("Choose this to draw a replacement pick")
        self.w.btn_draw = btn2
        hbox.add_widget(btn2)

        btn3 = Widgets.RadioButton("Edit", group=btn1)
        btn3.set_state(mode == 'edit')
        btn3.add_callback('activated', lambda w, val: self.set_mode_cb('edit', val))
        btn3.set_tooltip("Choose this to edit a pick")
        self.w.btn_edit = btn3
        hbox.add_widget(btn3)

        hbox.add_widget(Widgets.Label(''), stretch=1)
        vbox.add_widget(hbox, stretch=0)

        vtop.add_widget(sw, stretch=1)

        ## spacer = Widgets.Label('')
        ## vtop.add_widget(spacer, stretch=0)

        btns = Widgets.HBox()
        btns.set_spacing(4)

        btn = Widgets.Button("Close")
        btn.add_callback('activated', lambda w: self.close())
        btns.add_widget(btn)
        btns.add_widget(Widgets.Label(''), stretch=1)
        vtop.add_widget(btns, stretch=0)

        container.add_widget(vtop, stretch=1)

    def copyText(self, w):
        text = w.get_text()
        # TODO: put it in the clipboard

    def record_cb(self, w, tf):
        self.do_record = tf
        return True

    def do_report_log_cb(self, w, tf):
        self.do_report_log = tf
        if tf and (self.pick_log is None):
            self.open_report_log()
        return True

    def set_report_log_cb(self, w):
        self.close_report_log()

        report_log = w.get_text().strip()
        if len(report_log) == 0:
            report_log = "pick_log.txt"
            w.set_text(report_log)
        self.report_log = report_log

        self.open_report_log()
        return True

    def instructions(self):
        self.tw.set_text("""Left-click to place region.  Left-drag to position region.  Redraw region with the right mouse button.""")
        self.tw.set_font(self.msgFont)

    def update_status(self, text):
        self.fv.gui_do(self.w.eval_status.set_text, text)

    def init_progress(self):
        self.w.btn_intr_eval.set_enabled(True)
        self.w.eval_pgs.set_value(0.0)

    def update_progress(self, pct):
        self.w.eval_pgs.set_value(pct)

    def show_candidates_cb(self, w, state):
        self.show_candidates = state
        if not self.show_candidates:
            # Delete previous peak marks
            objs = self.canvas.getObjectsByTagpfx('peak')
            self.canvas.delete_objects(objs)

    def coordinate_base_cb(self, w):
        self.pixel_coords_offset = float(w.get_text())
        self.w.xlbl_coordinate_base.set_text(str(self.pixel_coords_offset))

    def bump_serial(self):
        with self.lock:
            self.serialnum += 1
            return self.serialnum

    def get_serial(self):
        with self.lock:
            return self.serialnum

    def plot_contours(self, image):
        # Make a contour plot

        ht, wd = self.pick_data.shape

        # If size of pick region is too large, carve out a subset around
        # the picked object coordinates for plotting contours
        maxsize = max(ht, wd)
        if maxsize > self.contour_size_limit:
            radius = int(self.contour_size_limit // 2)
            x, y = self.pick_qs.x, self.pick_qs.y
            data, x1, y1, x2, y2 = image.cutout_radius(x, y, radius)
            x, y = x - x1, y - y1
            ht, wd = data.shape
        else:
            data = self.pick_data
            x, y = self.pickcenter.x, self.pickcenter.y

        try:
            # TODO: fix
            self.contour_plot.num_contours = self.num_contours

            self.contour_plot.plot_contours(x, y, data)

        except Exception as e:
            self.logger.error("Error making contour plot: %s" % (
                str(e)))

    def clear_contours(self):
        self.contour_plot.clear()

    def plot_fwhm(self, qs, image):
        # Make a FWHM plot
        x, y, radius = qs.x, qs.y, qs.fwhm_radius

        try:
            self.fwhm_plot.plot_fwhm(x, y, radius, self.pick_data, image,
                                     iqcalc=self.iqcalc)

        except Exception as e:
            self.logger.error("Error making fwhm plot: %s" % (
                str(e)))

    def clear_fwhm(self):
        self.fwhm_plot.clear()

    def plot_radial(self, qs, image):
        # Make a radial plot
        x, y, radius = qs.x, qs.y, qs.fwhm_radius

        try:
            self.radial_plot.plot_radial(x, y, radius, image)

        except Exception as e:
            self.logger.error("Error making radial plot: %s" % (
                str(e)))

    def clear_radial(self):
        self.radial_plot.clear()

    def open_report_log(self):
        # Open report log if user specified one
        if self.do_report_log and (self.report_log is not None) and \
               (self.pick_log is None):
            try:
                file_exists = os.path.exists(self.report_log)
                self.pick_log = open(self.report_log, 'a')
                if not file_exists:
                    self.pick_log.write(self.rpt_header + '\n')
                self.logger.info("Opened Pick log '%s'" % (self.report_log))

            except IOError as e:
                self.logger.error("Error opening Pick log (%s): %s" % (
                    self.report_log, str(e)))

    def close_report_log(self):
        if self.pick_log is not None:
            try:
                self.pick_log.close()
                self.logger.info("Closed Pick log '%s'" % (self.report_log))
            except IOError as e:
                self.logger.error("Error closing Pick log (%s): %s" % (
                    self.report_log, str(e)))
            finally:
                self.pick_log = None

    def close(self):
        chname = self.fv.get_channelName(self.fitsimage)
        self.fv.stop_local_plugin(chname, str(self))
        return True

    def start(self):
        self.instructions()
        self.open_report_log()

        # insert layer if it is not already
        p_canvas = self.fitsimage.get_canvas()
        try:
            obj = p_canvas.get_object_by_tag(self.layertag)

        except KeyError:
            # Add canvas layer
            p_canvas.add(self.canvas, tag=self.layertag)

        self.resume()

    def pause(self):
        self.canvas.ui_setActive(False)

    def resume(self):
        # turn off any mode user may be in
        self.modes_off()

        self.canvas.ui_setActive(True)
        self.fv.showStatus("Draw a rectangle with the right mouse button")

        # Setup the region
        if self.region is None:
            self.region = Region()
            self.region.coord = 'wcs'
            self.region.image = self.fitsimage.get_image()

        # See if multiimage is active
        chname = self.fv.get_channelName(self.fitsimage)
        chinfo = self.fv.get_channelInfo(chname)
        opmon = chinfo.opmon
        multiimage = None
        if opmon.is_active('MultiImage'):
            try:
                multiimage = opmon.getPlugin('MultiImage')
                multiimage.region = self.region
            except:
                multiimage = None
        self.multiimage = multiimage

    def stop(self):
        # Delete previous peak marks
        objs = self.canvas.getObjectsByTagpfx('peak')
        self.canvas.delete_objects(objs)

        # close pick log, if any
        self.close_report_log()

        # deactivate the canvas
        self.canvas.ui_setActive(False)
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.delete_object_by_tag(self.layertag)
        except:
            pass
        self.fv.showStatus("")

    def redo(self):
        if self.picktag is None:
            return

        serialnum = self.bump_serial()
        self.ev_intr.set()

        fig = self.canvas.get_object_by_tag(self.picktag)
        if fig.kind != 'compound':
            return True
        bbox = fig.objects[0]
        self.region.image = self.fitsimage.get_image()
        self.draw_compound(bbox, self.canvas,
                           *self.region.bbox(coord='data'))
        fig = self.canvas.getObjectByTag(self.picktag)
        bbox = fig.objects[0]

        # set the pick image to have the same cut levels and transforms
        self.fitsimage.copy_attributes(self.pickimage,
                                       ['transforms', 'cutlevels',
                                        'rgbmap'])

        try:
            # Get other parts of the indicator
            point = fig.objects[1]
            text = fig.objects[2]

            # sanity check on region
            width = bbox.x2 - bbox.x1
            height = bbox.y2 - bbox.y1
            if (width > self.max_side) or (height > self.max_side):
                errmsg = "Image area (%dx%d) too large!" % (
                    width, height)
                self.fv.show_error(errmsg)
                raise Exception(errmsg)

            # Cut and show pick image in pick window
            self.logger.debug("bbox %f,%f %f,%f" % (bbox.x1, bbox.y1,
                                                    bbox.x2, bbox.y2))
            x1, y1, x2, y2, data = self.cutdetail(self.fitsimage,
                                                  self.pickimage,
                                                  int(bbox.x1), int(bbox.y1),
                                                  int(bbox.x2), int(bbox.y2))
            self.logger.debug("cut box %f,%f %f,%f" % (x1, y1, x2, y2))

            # calculate center of pick image
            wd, ht = self.pickimage.get_data_size()
            xc = wd // 2
            yc = ht // 2
            if self.pickcenter is None:
                p_canvas = self.pickimage.get_canvas()
                tag = p_canvas.add(self.dc.Point(xc, yc, 5,
                                                 linewidth=1, color='red'))
                self.pickcenter = p_canvas.get_object_by_tag(tag)

            self.pick_x1, self.pick_y1 = x1, y1
            self.pick_data = data
            self.wdetail.sample_area.set_text('%dx%d' % (x2-x1, y2-y1))

            point.color = 'red'
            text.text = 'Pick: calc'
            self.pickcenter.x = xc
            self.pickcenter.y = yc
            self.pickcenter.color = 'red'

            # clear contour and fwhm plots
            if self.have_mpl:
                self.clear_contours()
                self.clear_fwhm()
                self.clear_radial()

            # If multiimage, redo there also.
            try:
                self.multiimage.redo()
            except:
                """Doesn't matter"""
                pass

            # Delete previous peak marks
            objs = self.canvas.getObjectsByTagpfx('peak')
            self.canvas.delete_objects(objs)

            # Offload this task to another thread so that GUI remains
            # responsive
            self.fv.nongui_do(self.search, serialnum, data,
                              x1, y1, wd, ht, fig)

        except Exception as e:
            self.logger.error("Error calculating quality metrics: %s" % (
                str(e)))
            return True

    def search(self, serialnum, data, x1, y1, wd, ht, fig):
        if serialnum != self.get_serial():
            return
        with self.lock2:
            self.pgs_cnt = 0
            self.ev_intr.clear()
            self.fv.gui_call(self.init_progress)

            msg, results, qs = None, None, None
            try:
                self.update_status("Finding bright peaks...")
                # Find bright peaks in the cutout
                peaks = self.iqcalc.find_bright_peaks(data,
                                                      threshold=self.threshold,
                                                      radius=self.radius)
                num_peaks = len(peaks)
                if num_peaks == 0:
                    raise Exception("Cannot find bright peaks")

                def cb_fn(obj):
                    self.pgs_cnt += 1
                    pct = float(self.pgs_cnt) / num_peaks
                    self.fv.gui_do(self.update_progress, pct)

                # Evaluate those peaks
                self.update_status("Evaluating %d bright peaks..." % (
                    num_peaks))
                objlist = self.iqcalc.evaluate_peaks(peaks, data,
                                                     fwhm_radius=self.radius,
                                                     cb_fn=cb_fn,
                                                     ev_intr=self.ev_intr)

                num_candidates = len(objlist)
                if num_candidates == 0:
                    raise Exception("Error evaluating bright peaks: no candidates found")

                self.update_status("Selecting from %d candidates..." % (
                    num_candidates))
                height, width = data.shape
                results = self.iqcalc.objlist_select(objlist, width, height,
                                                     minfwhm=self.min_fwhm,
                                                     maxfwhm=self.max_fwhm,
                                                     minelipse=self.min_ellipse,
                                                     edgew=self.edgew)
                if len(results) == 0:
                    raise Exception("No object matches selection criteria")
                qs = results[0]

            except Exception as e:
                msg = str(e)
                self.update_status(msg)

            if serialnum == self.get_serial():
                self.fv.gui_do(self.update_pick, serialnum, results, qs,
                               x1, y1, wd, ht, fig, msg)

    def _make_report_header(self):
        return self.rpt_header + '\n'

    def _make_report(self, image, qs):
        d = Bunch.Bunch()
        try:
            x, y = qs.objx, qs.objy
            equinox = float(image.get_keyword('EQUINOX', 2000.0))

            try:
                ra_deg, dec_deg = image.pixtoradec(x, y, coords='data')
                ra_txt, dec_txt = wcs.deg2fmt(ra_deg, dec_deg, 'str')

            except Exception as e:
                self.logger.warn("Couldn't calculate sky coordinates: %s" % (str(e)))
                ra_deg, dec_deg = 0.0, 0.0
                ra_txt = dec_txt = 'BAD WCS'

            # Calculate star size from pixel pitch
            try:
                header = image.get_header()
                ((xrot, yrot),
                 (cdelt1, cdelt2)) = wcs.get_xy_rotation_and_scale(header)

                starsize = self.iqcalc.starsize(qs.fwhm_x, cdelt1,
                                                qs.fwhm_y, cdelt2)
            except Exception as e:
                self.logger.warn("Couldn't calculate star size: %s" % (str(e)))
                starsize = 0.0

            rpt_x = x + self.pixel_coords_offset
            rpt_y = y + self.pixel_coords_offset

            # make a report in the form of a dictionary
            d.setvals(x = rpt_x, y = rpt_y,
                      ra_deg = ra_deg, dec_deg = dec_deg,
                      ra_txt = ra_txt, dec_txt = dec_txt,
                      equinox = equinox,
                      fwhm = qs.fwhm,
                      fwhm_x = qs.fwhm_x, fwhm_y = qs.fwhm_y,
                      ellipse = qs.elipse, background = qs.background,
                      skylevel = qs.skylevel, brightness = qs.brightness,
                      starsize = starsize,
                      time_local = time.strftime("%Y-%m-%d %H:%M:%S",
                                                 time.localtime()),
                      time_ut = time.strftime("%Y-%m-%d %H:%M:%S",
                                              time.gmtime()),
                      )
        except Exception as e:
            self.logger.error("Error making report: %s" % (str(e)))

        return d

    def update_pick(self, serialnum, objlist, qs, x1, y1, wd, ht, fig, msg):
        if serialnum != self.get_serial():
            return

        try:
            image = self.fitsimage.get_image()
            point = fig.objects[1]
            text = fig.objects[2]
            text.text = "Pick"

            if msg is not None:
                raise Exception(msg)

            # Mark new peaks, if desired
            if self.show_candidates:
                for obj in objlist:
                    tag = self.canvas.add(self.dc.Point(x1+obj.objx,
                                                        y1+obj.objy,
                                                        5,
                                                        linewidth=1,
                                                        color=self.candidate_color),
                                          tagpfx='peak')

            # Add back in offsets into image to get correct values with respect
            # to the entire image
            qs.x += x1
            qs.y += y1
            qs.objx += x1
            qs.objy += y1

            # Calculate X/Y of center of star
            obj_x = qs.objx
            obj_y = qs.objy
            fwhm = qs.fwhm
            fwhm_x, fwhm_y = qs.fwhm_x, qs.fwhm_y
            point.x, point.y = obj_x, obj_y
            text.color = 'cyan'

            # Make report
            self.last_rpt = self._make_report(image, qs)
            d = self.last_rpt
            if self.do_record:
                self.add_pick_cb()

            self.wdetail.fwhm_x.set_text('%.3f' % fwhm_x)
            self.wdetail.fwhm_y.set_text('%.3f' % fwhm_y)
            self.wdetail.fwhm.set_text('%.3f' % fwhm)
            self.wdetail.object_x.set_text('%.3f' % (d.x))
            self.wdetail.object_y.set_text('%.3f' % (d.y))
            self.wdetail.sky_level.set_text('%.3f' % qs.skylevel)
            self.wdetail.background.set_text('%.3f' % qs.background)
            self.wdetail.brightness.set_text('%.3f' % qs.brightness)
            self.wdetail.ra.set_text(d.ra_txt)
            self.wdetail.dec.set_text(d.dec_txt)
            self.wdetail.equinox.set_text(str(d.equinox))
            self.wdetail.star_size.set_text('%.3f' % d.starsize)

            self.w.btn_sky_cut.set_enabled(True)
            self.w.btn_bright_cut.set_enabled(True)

            # Mark center of object on pick image
            i1 = point.x - x1
            j1 = point.y - y1
            self.pickcenter.x = i1
            self.pickcenter.y = j1
            self.pickcenter.color = 'cyan'
            self.pick_qs = qs
            self.pickimage.panset_xy(i1, j1)

            # Mark object center on image
            point.color = 'cyan'
            #self.fitsimage.panset_xy(obj_x, obj_y)

            self.update_status("Done")
            self.plot_panx = float(i1) / wd
            self.plot_pany = float(j1) / ht
            if self.have_mpl:
                self.plot_contours(image)
                self.plot_fwhm(qs, image)
                self.plot_radial(qs, image)

        except Exception as e:
            errmsg = "Error calculating quality metrics: %s" % (
                str(e))
            self.logger.error(errmsg)
            self.fv.show_error(errmsg, raisetab=False)
            #self.update_status("Error")
            for key in ('sky_level', 'background', 'brightness',
                        'star_size', 'fwhm_x', 'fwhm_y'):
                self.wdetail[key].set_text('')
            self.wdetail.fwhm.set_text('Failed')
            self.w.btn_sky_cut.set_enabled(False)
            self.w.btn_bright_cut.set_enabled(False)
            self.pick_qs = None
            text.color = 'red'

            self.plot_panx = self.plot_pany = 0.5
            #self.plot_contours(image)
            # TODO: could calc background based on numpy calc

        self.w.btn_intr_eval.set_enabled(False)
        self.pickimage.redraw(whence=3)
        self.canvas.redraw(whence=3)

        self.fv.showStatus("Click left mouse button to reposition pick")
        return True

    def eval_intr(self):
        self.ev_intr.set()

    def btndown(self, canvas, event, data_x, data_y, viewer):
        try:
            obj = self.canvas.get_object_by_tag(self.picktag)
            if obj.kind == 'rectangle':
                bbox = obj
            else:
                bbox  = obj.objects[0]
                point = obj.objects[1]
            self.dx = (bbox.x2 - bbox.x1) // 2
            self.dy = (bbox.y2 - bbox.y1) // 2
        except Exception as e:
            pass

        dx = self.dx
        dy = self.dy

        # Mark center of object and region on main image
        try:
            self.canvas.delete_object_by_tag(self.picktag)
        except:
            pass

        x1, y1 = data_x - dx, data_y - dy
        x2, y2 = data_x + dx, data_y + dy

        tag = self.canvas.add(self.dc.Rectangle(x1, y1, x2, y2,
                                                color='cyan',
                                                linestyle='dash'))
        self.picktag = tag

        #self.draw_cb(self.canvas, tag)
        return True

    def update(self, canvas, event, data_x, data_y, viewer):
        try:
            obj = self.canvas.get_object_by_tag(self.picktag)
            if obj.kind == 'rectangle':
                bbox = obj
            else:
                bbox  = obj.objects[0]
                point = obj.objects[1]
            self.dx = (bbox.x2 - bbox.x1) // 2
            self.dy = (bbox.y2 - bbox.y1) // 2
        except Exception as e:
            obj = None
            pass

        dx = self.dx
        dy = self.dy

        x1, y1 = data_x - dx, data_y - dy
        x2, y2 = data_x + dx, data_y + dy

        if (not obj) or (obj.kind == 'compound'):
            # Replace compound image with rectangle
            try:
                self.canvas.delete_object_by_tag(self.picktag)
            except:
                pass

            tag = self.canvas.add(self.dc.Rectangle(x1, y1, x2, y2,
                                                    color='cyan',
                                                    linestyle='dash'))
        else:
            # Update current rectangle with new coords
            bbox.x1, bbox.y1, bbox.x2, bbox.y2 = x1, y1, x2, y2
            tag = self.picktag

        self.draw_cb(self.canvas, tag)
        return True


    def drag(self, canvas, event, data_x, data_y, viewer):

        obj = self.canvas.get_object_by_tag(self.picktag)
        if obj.kind == 'compound':
            bbox = obj.objects[0]
        elif obj.kind == 'rectangle':
            bbox = obj
        else:
            return True

        # calculate center of bbox
        wd = bbox.x2 - bbox.x1
        dw = wd // 2
        ht = bbox.y2 - bbox.y1
        dh = ht // 2
        x, y = bbox.x1 + dw, bbox.y1 + dh

        # calculate offsets of move
        dx = (data_x - x)
        dy = (data_y - y)

        # calculate new coords
        x1, y1, x2, y2 = bbox.x1+dx, bbox.y1+dy, bbox.x2+dx, bbox.y2+dy

        if (not obj) or (obj.kind == 'compound'):
            # Replace compound image with rectangle
            try:
                self.canvas.delete_object_by_tag(self.picktag)
            except:
                pass

            self.picktag = self.canvas.add(self.dc.Rectangle(x1, y1, x2, y2,
                                                             color='cyan',
                                                             linestyle='dash'))
        else:
            # Update current rectangle with new coords and redraw
            bbox.x1, bbox.y1, bbox.x2, bbox.y2 = x1, y1, x2, y2
            self.canvas.redraw(whence=3)

        return True

    def draw_cb(self, canvas, tag):
        obj = canvas.getObjectByTag(tag)
        self.draw_compound(obj, canvas)
        return self.redo()

    def edit_cb(self, canvas, obj):
        if obj.kind != 'rectangle':
            return True

        # Get the compound object that sits on the canvas.
        # Make sure edited rectangle was our pick rectangle.
        c_obj = self.canvas.get_object_by_tag(self.picktag)
        if (c_obj.kind != 'compound') or (len(c_obj.objects) < 3) or \
               (c_obj.objects[0] != obj):
            return False

        # determine center of rectangle
        x1, y1, x2, y2 = obj.get_llur()
        x = x1 + (x2 - x1) // 2
        y = y1 + (y2 - y1) // 2

        # reposition other elements to match
        point = c_obj.objects[1]
        point.x, point.y = x, y
        text = c_obj.objects[2]
        text.x, text.y = x1, y2 + 4

        self.regions.set_bbox(x1, y1, x2, y2, coord='data')

        return self.redo()

    def reset_region(self):
        self.dx = region_default_width
        self.dy = region_default_height

        obj = self.canvas.get_object_by_tag(self.picktag)
        if obj.kind != 'compound':
            return True
        bbox = obj.objects[0]

        # calculate center of bbox
        wd = bbox.x2 - bbox.x1
        dw = wd // 2
        ht = bbox.y2 - bbox.y1
        dh = ht // 2
        x, y = bbox.x1 + dw, bbox.y1 + dh

        # calculate new coords
        bbox.x1, bbox.y1, bbox.x2, bbox.y2 = (x-self.dx, y-self.dy,
                                              x+self.dx, y+self.dy)

        self.regions.set_bbox(bbox.x1, bbox.y1,
                              bbox.x2, bbox.y2, coord='data')

        self.redo()

    def pan_to_pick_cb(self):
        if not self.pick_qs:
            self.fv.showStatus("Please pick an object to set the sky level!")
            return
        pan_x, pan_y = self.pick_qs.objx, self.pick_qs.objy

        # TODO: convert to WCS coords based on user preference
        self.fitsimage.set_pan(pan_x, pan_y, coord='data')
        return True

    def sky_cut(self):
        if not self.pick_qs:
            self.fv.showStatus("Please pick an object to set the sky level!")
            return
        loval = self.pick_qs.skylevel
        oldlo, hival = self.fitsimage.get_cut_levels()
        try:
            loval += self.delta_sky
            self.fitsimage.cut_levels(loval, hival)

        except Exception as e:
            self.fv.showStatus("No valid sky level: '%s'" % (loval))

    def bright_cut(self):
        if not self.pick_qs:
            self.fv.showStatus("Please pick an object to set the brightness!")
            return
        skyval = self.pick_qs.skylevel
        hival = self.pick_qs.brightness
        loval, oldhi = self.fitsimage.get_cut_levels()
        try:
            # brightness is measured ABOVE sky level
            hival = skyval + hival + self.delta_bright
            self.fitsimage.cut_levels(loval, hival)

        except Exception as e:
            self.fv.showStatus("No valid brightness level: '%s'" % (hival))

    def zoomset(self, setting, zoomlevel, fitsimage):
        scalefactor = fitsimage.get_scale()
        self.logger.debug("scalefactor = %.2f" % (scalefactor))
        text = self.fv.scale2text(scalefactor)
        self.wdetail.zoom.set_text(text)

    def detailxy(self, canvas, button, data_x, data_y):
        """Motion event in the pick fits window.  Show the pointing
        information under the cursor.
        """
        if button == 0:
            # TODO: we could track the focus changes to make this check
            # more efficient
            fitsimage = self.fv.getfocus_fitsimage()
            # Don't update global information if our fitsimage isn't focused
            if fitsimage != self.fitsimage:
                return True

            # Add offsets from cutout
            data_x = data_x + self.pick_x1
            data_y = data_y + self.pick_y1

            return self.fv.showxy(self.fitsimage, data_x, data_y)

    def cutdetail(self, srcimage, dstimage, x1, y1, x2, y2):
        image = srcimage.get_image()
        data, x1, y1, x2, y2 = image.cutout_adjust(x1, y1, x2, y2)

        dstimage.set_data(data)

        return (x1, y1, x2, y2, data)

    def pan_plot(self, xdelta, ydelta):
        x1, x2 = self.w.ax.get_xlim()
        y1, y2 = self.w.ax.get_ylim()

        self.w.ax.set_xlim(x1+xdelta, x2+xdelta)
        self.w.ax.set_ylim(y1+ydelta, y2+ydelta)
        self.w.canvas.draw()

    def write_pick_log(self, rpt):
        if self.pick_log is not None:
            self.pick_log.write(rpt)
            self.pick_log.flush()

    def add_pick_cb(self):
        if self.last_rpt is not None:
            rpt = (self.rpt_format % self.last_rpt) + '\n'
            self.w.report.append_text(rpt)
            ## if self.pick_log:
            ##     self.fv.nongui_do(self.write_pick_log, rpt)
            self.write_pick_log(rpt)

    def edit_select_pick(self):
        if self.picktag is not None:
            obj = self.canvas.get_object_by_tag(self.picktag)
            if obj.kind != 'compound':
                return True
            # drill down to reference shape
            bbox = obj.objects[0]
            self.canvas.edit_select(bbox)
        else:
            self.canvas.clear_selected()
        self.canvas.update_canvas()

    def set_mode_cb(self, mode, tf):
        if tf:
            self.canvas.set_draw_mode(mode)
            if mode == 'edit':
                self.edit_select_pick()
        return True

    def __str__(self):
        return 'mipick'

    def draw_compound(self, obj, canvas, *args):
        """Draw the pick info box"""
        if obj.kind != 'rectangle':
            return True
        canvas.deleteObject(obj)

        if self.picktag:
            try:
                canvas.deleteObjectByTag(self.picktag)
            except:
                pass

        # Get rectangle:
        if len(args) == 4:
            x1, y1, x2, y2 = args
        else:
            x1, y1, x2, y2 = obj.get_llur()

        # determine center of rectangle
        x = x1 + (x2 - x1) // 2
        y = y1 + (y2 - y1) // 2

        tag = canvas.add(self.dc.CompoundObject(
            self.dc.Rectangle(x1, y1, x2, y2,
                              color=self.pickcolor),
            self.dc.Point(x, y, 10, color='red'),
            self.dc.Text(x1, y2+4, "Pick: calc",
                         color=self.pickcolor)))
        self.picktag = tag
        self.region.set_bbox(x1, y1, x2, y2, coord='data')