Exemplo n.º 1
0
class TVMask(LocalPlugin):

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

        self.layertag = 'tvmask-canvas'
        self.masktag = None
        self.maskhltag = None

        self._color_options = self._short_color_list()

        # User preferences. Some are just default values and can also be
        # changed by GUI.
        prefs = self.fv.get_preferences()
        self.settings = prefs.create_category('plugin_TVMask')
        self.settings.add_defaults(maskcolor='green', maskalpha=0.5,
                                   hlcolor='white', hlalpha=1.0)
        self.settings.load(onError='silent')
        self.maskcolor = self.settings.get('maskcolor', 'green')
        self.maskalpha = self.settings.get('maskalpha', 0.5)
        self.hlcolor = self.settings.get('hlcolor', 'white')
        self.hlalpha = self.settings.get('hlalpha', 1.0)

        # Display coords info table
        self.treeview = None
        self.tree_dict = Bunch.caselessDict()
        self.columns = [('No.', 'ID'), ('Filename', 'MASKFILE')]

        # Store results
        self._seqno = 1
        self._maskobjs = []
        self._treepaths = []

        self.dc = self.fv.get_draw_classes()

        canvas = self.dc.DrawingCanvas()
        canvas.enable_draw(True)
        canvas.enable_edit(False)
        canvas.set_callback('draw-event', self.hl_canvas2table_box)
        #canvas.set_callback('cursor-down', self.hl_canvas2table)
        canvas.register_for_cursor_drawing(self.fitsimage)
        canvas.set_surface(self.fitsimage)
        canvas.set_drawtype('rectangle', color='green', linestyle='dash')
        self.canvas = canvas

        fv.add_callback('remove-image', lambda *args: self.redo())

        self.gui_up = False

    # If user complains about lack of choices (!!!), we can remove this.
    def _short_color_list(self):
        """Color list is too long. Discard variations with numbers."""
        return [c for c in colors.get_colors() if not re.search(r'\d', c)]

    def build_gui(self, container):
        vbox, sw, self.orientation = Widgets.get_oriented_box(container)

        captions = (('Color:', 'label', 'mask color', 'combobox'),
                    ('Alpha:', 'label', 'mask alpha', 'entry'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        combobox = b.mask_color
        for name in self._color_options:
            combobox.append_text(name)
        b.mask_color.set_index(self._color_options.index(self.maskcolor))
        b.mask_color.add_callback('activated', self.set_maskcolor_cb)

        b.mask_alpha.set_tooltip('Mask alpha (transparency)')
        b.mask_alpha.set_text(str(self.maskalpha))
        b.mask_alpha.add_callback('activated', lambda w: self.set_maskalpha())

        container.add_widget(w, stretch=0)

        treeview = Widgets.TreeView(auto_expand=True,
                                    sortable=True,
                                    selection='multiple',
                                    use_alt_row_color=True)
        self.treeview = treeview
        treeview.setup_table(self.columns, 2, 'ID')
        treeview.add_callback('selected', self.hl_table2canvas)
        container.add_widget(treeview, stretch=1)

        captions = (('Load Mask', 'button'),
                    ('Show', 'button', 'Hide', 'button', 'Forget', 'button'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        b.load_mask.set_tooltip('Load mask image')
        b.load_mask.add_callback('activated', lambda w: self.load_mask_cb())

        b.show.set_tooltip('Show masks')
        b.show.add_callback('activated', lambda w: self.redo())

        b.hide.set_tooltip('Hide masks')
        b.hide.add_callback('activated', lambda w: self.clear_mask())

        b.forget.set_tooltip('Forget masks')
        b.forget.add_callback('activated', lambda w: self.forget_masks())

        container.add_widget(w, stretch=0)

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

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

        container.add_widget(btns, stretch=0)

        self.gui_up = True

        # Initialize mask file selection dialog
        self.mfilesel = FileSelection(self.fv.w.root.get_widget(),
                                      all_at_once=True)

        # Populate table
        self.redo()

    def redo(self):
        """Image or masks have changed. Clear and redraw."""
        if not self.gui_up:
            return

        self.clear_mask()

        image = self.fitsimage.get_image()
        if image is None:
            return

        n_obj = len(self._maskobjs)
        self.logger.debug('Displaying {0} masks'.format(n_obj))
        if n_obj == 0:
            return

        # Display info table
        self.recreate_toc()

        # Draw on canvas
        self.masktag = self.canvas.add(self.dc.CompoundObject(*self._maskobjs))
        self.fitsimage.redraw()  # Force immediate redraw

    def clear_mask(self):
        """Clear mask from image.
        This does not clear loaded masks from memory."""
        if self.masktag:
            try:
                self.canvas.delete_object_by_tag(self.masktag, redraw=False)
            except Exception:
                pass

        if self.maskhltag:
            try:
                self.canvas.delete_object_by_tag(self.maskhltag, redraw=False)
            except Exception:
                pass

        self.treeview.clear()  # Clear table too
        self.fitsimage.redraw()  # Force immediate redraw

    def forget_masks(self):
        """Forget all loaded coordinates."""
        self._seqno = 1
        self._maskobjs = []
        self._treepaths = []
        self.tree_dict = Bunch.caselessDict()
        self.redo()

    # TODO: Support more formats?
    def load_file(self, filename):
        """Load mask image.

        Results are appended to previously loaded masks.
        This can be used to load mask per color.

        """
        if not os.path.isfile(filename):
            return

        self.logger.info('Loading mask image from {0}'.format(filename))

        try:
            # 0=False, everything else True
            dat = fits.getdata(filename).astype(np.bool)
        except Exception as e:
            self.logger.error('{0}: {1}'.format(e.__class__.__name__, str(e)))
            return

        key = '{0},{1}'.format(self.maskcolor, self.maskalpha)

        if key in self.tree_dict:
            sub_dict = self.tree_dict[key]
        else:
            sub_dict = {}
            self.tree_dict[key] = sub_dict

        # Add to listing
        seqstr = '{0:04d}'.format(self._seqno)  # Prepend 0s for proper sort
        sub_dict[seqstr] = Bunch.Bunch(ID=seqstr,
                                       MASKFILE=os.path.basename(filename))
        self._treepaths.append((key, seqstr))
        self._seqno += 1

        # Create mask layer
        obj = self.dc.Image(0, 0, masktorgb(
            dat, color=self.maskcolor, alpha=self.maskalpha))
        self._maskobjs.append(obj)

        self.redo()

    def load_files(self, filenames):
        """Load mask images.

        Results are appended to previously loaded masks.
        This can be used to load mask per color.

        """
        for filename in filenames:
            self.load_file(filename)

    def load_mask_cb(self):
        """Activate file dialog to select mask image."""
        self.mfilesel.popup('Load mask image', self.load_files,
                            initialdir='.', filename='FITS files (*.fits)')

    def recreate_toc(self):
        self.logger.debug('Recreating table of contents...')
        self.treeview.set_tree(self.tree_dict)

    def _rgbtomask(self, obj):
        """Convert RGB arrays from mask canvas object back to boolean mask."""
        dat = obj.get_image().get_data()  # RGB arrays
        return dat.sum(axis=2).astype(np.bool)  # Convert to 2D mask

    def hl_table2canvas(self, w, res_dict):
        """Highlight mask on canvas when user click on table."""
        objlist = []

        # Remove existing highlight
        if self.maskhltag:
            try:
                self.canvas.delete_object_by_tag(self.maskhltag, redraw=False)
            except Exception:
                pass

        for sub_dict in res_dict.values():
            for seqno in sub_dict:
                mobj = self._maskobjs[int(seqno) - 1]
                dat = self._rgbtomask(mobj)
                obj = self.dc.Image(0, 0, masktorgb(
                    dat, color=self.hlcolor, alpha=self.hlalpha))
                objlist.append(obj)

        # Draw on canvas
        if len(objlist) > 0:
            self.maskhltag = self.canvas.add(self.dc.CompoundObject(*objlist))

        self.fitsimage.redraw()  # Force immediate redraw

    def hl_canvas2table_box(self, canvas, tag):
        """Highlight all masks inside user drawn box on table."""
        self.treeview.clear_selection()

        # Remove existing box
        cobj = canvas.get_object_by_tag(tag)
        if cobj.kind != 'rectangle':
            return
        canvas.delete_object_by_tag(tag, redraw=False)

        # Remove existing highlight
        if self.maskhltag:
            try:
                canvas.delete_object_by_tag(self.maskhltag, redraw=True)
            except Exception:
                pass

        # Nothing to do if no masks are displayed
        try:
            obj = canvas.get_object_by_tag(self.masktag)
        except Exception:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if len(self._maskobjs) == 0:
            return

        # Find masks that intersect the rectangle
        for i, mobj in enumerate(self._maskobjs):
            # The actual mask
            mask1 = self._rgbtomask(mobj)

            # The selected area
            rgbimage = mobj.get_image()
            mask2 = rgbimage.get_shape_mask(cobj)

            # Highlight mask with intersect
            if np.any(mask1 & mask2):
                self._highlight_path(self._treepaths[i])

    # NOTE: This does not work anymore when left click is used to draw box.
    def hl_canvas2table(self, canvas, button, data_x, data_y):
        """Highlight mask on table when user click on canvas."""
        self.treeview.clear_selection()

        # Remove existing highlight
        if self.maskhltag:
            try:
                canvas.delete_object_by_tag(self.maskhltag, redraw=True)
            except Exception:
                pass

        # Nothing to do if no masks are displayed
        try:
            obj = canvas.get_object_by_tag(self.masktag)
        except Exception:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if len(self._maskobjs) == 0:
            return

        for i, mobj in enumerate(self._maskobjs):
            mask1 = self._rgbtomask(mobj)

            # Highlight mask covering selected cursor position
            if mask1[int(data_y), int(data_x)]:
                self._highlight_path(self._treepaths[i])

    def _highlight_path(self, hlpath):
        """Highlight an entry in the table and associated mask."""
        self.logger.debug('Highlighting {0}'.format(hlpath))
        self.treeview.select_path(hlpath)

        # TODO: Does not work in Qt. This is known issue in Ginga.
        self.treeview.scroll_to_path(hlpath)

    def set_maskcolor_cb(self, w, index):
        """Set color of mask."""
        self.maskcolor = self._color_options[index]

    def set_maskalpha(self):
        """Set alpha (transparency) of mask."""
        try:
            a = float(self.w.mask_alpha.get_text())
        except ValueError:
            self.logger.error('Cannot set mask alpha')
            self.w.mask_alpha.set_text(str(self.maskalpha))
            return

        if a < 0 or a > 1:
            self.logger.error('Alpha must be between 0 and 1, inclusive')
            self.w.mask_alpha.set_text(str(self.maskalpha))
            return

        self.maskalpha = a

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

    def start(self):
        # insert canvas, if not already
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.get_object_by_tag(self.layertag)
        except KeyError:
            # Add drawing layer
            p_canvas.add(self.canvas, tag=self.layertag)

        self.resume()

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

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

        self.canvas.ui_set_active(True)
        self.fv.show_status('Press "Help" for instructions')

    def stop(self):
        # remove canvas from image
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.delete_object_by_tag(self.layertag)
        except Exception:
            pass

        self.canvas.update_canvas(whence=0)  # Force redraw
        self.gui_up = False
        self.fv.show_status('')

    def __str__(self):
        """
        This method should be provided and should return the lower case
        name of the plugin.
        """
        return 'tvmask'
Exemplo n.º 2
0
class TVMark(LocalPlugin):

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

        self.layertag = 'tvmark-canvas'
        self.marktag = None
        self.markhltag = None

        self._mark_options = ['box', 'circle', 'cross', 'plus', 'point']
        self._color_options = self._short_color_list()
        self._dwidth = 2  # Additional width to highlight selection

        # User preferences. Some are just default values and can also be
        # changed by GUI.
        prefs = self.fv.get_preferences()
        self.settings = prefs.create_category('plugin_TVMark')
        self.settings.add_defaults(marktype='circle', markcolor='green',
                                   marksize=5, markwidth=1, pixelstart=1,
                                   use_radec=True,
                                   ra_colname='ra', dec_colname='dec',
                                   x_colname='x', y_colname='y',
                                   extra_columns=[])
        self.settings.load(onError='silent')
        self.marktype = self.settings.get('marktype', 'circle')
        self.markcolor = self.settings.get('markcolor', 'green')
        self.marksize = self.settings.get('marksize', 5)
        self.markwidth = self.settings.get('markwidth', 1)
        self.pixelstart = self.settings.get('pixelstart', 1)
        self.use_radec = self.settings.get('use_radec', True)
        self.extra_columns = self.settings.get('extra_columns', [])

        # Display coords info table
        self.treeview = None
        self.treeviewsel = None
        self.treeviewbad = None
        self.tree_dict = Bunch.caselessDict()
        self.columns = [('No.', 'MARKID'), ('RA', 'RA'), ('DEC', 'DEC'),
                        ('X', 'X'), ('Y', 'Y')]

        # Append extra columns to table header
        self.columns += [(colname, colname) for colname in self.extra_columns]

        # Store results
        self.coords_dict = defaultdict(list)
        self._xarr = []
        self._yarr = []
        self._treepaths = []

        self.dc = self.fv.get_draw_classes()

        canvas = self.dc.DrawingCanvas()
        canvas.enable_draw(True)
        canvas.enable_edit(False)
        canvas.set_callback('draw-event', self.hl_canvas2table_box)
        #canvas.set_callback('cursor-down', self.hl_canvas2table)
        canvas.register_for_cursor_drawing(self.fitsimage)
        canvas.set_surface(self.fitsimage)
        canvas.set_drawtype('rectangle', color='green', linestyle='dash')
        self.canvas = canvas

        fv.add_callback('remove-image', lambda *args: self.redo())

        self.gui_up = False

    # If user complains about lack of choices (!!!), we can remove this.
    def _short_color_list(self):
        """Color list is too long. Discard variations with numbers."""
        return [c for c in colors.get_colors() if not re.search(r'\d', c)]

    def build_gui(self, container):
        vbox, sw, self.orientation = Widgets.get_oriented_box(container)

        captions = (('Mark:', 'label', 'mark type', 'combobox'),
                    ('Color:', 'label', 'mark color', 'combobox'),
                    ('Size:', 'label', 'mark size', 'entry'),
                    ('Width:', 'label', 'mark width', 'entry'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        combobox = b.mark_type
        for name in self._mark_options:
            combobox.append_text(name)
        b.mark_type.set_index(self._mark_options.index(self.marktype))
        b.mark_type.add_callback('activated', self.set_marktype_cb)

        combobox = b.mark_color
        for name in self._color_options:
            combobox.append_text(name)
        b.mark_color.set_index(self._color_options.index(self.markcolor))
        b.mark_color.add_callback('activated', self.set_markcolor_cb)

        b.mark_size.set_tooltip('Size/radius of the marking')
        b.mark_size.set_text(str(self.marksize))
        b.mark_size.add_callback('activated', lambda w: self.set_marksize())

        b.mark_width.set_tooltip('Line width of the marking')
        b.mark_width.set_text(str(self.markwidth))
        b.mark_width.add_callback('activated', lambda w: self.set_markwidth())

        container.add_widget(w, stretch=0)

        nb = Widgets.TabWidget()
        self.w.nb1 = nb
        container.add_widget(nb, stretch=1)

        treeview = Widgets.TreeView(auto_expand=True,
                                    sortable=True,
                                    selection='multiple',
                                    use_alt_row_color=True)
        self.treeview = treeview
        treeview.setup_table(self.columns, 2, 'MARKID')
        treeview.add_callback('selected', self.hl_table2canvas)
        nb.add_widget(treeview, title='Shown')

        treeview2 = Widgets.TreeView(auto_expand=True,
                                     sortable=True,
                                     use_alt_row_color=True)
        self.treeviewsel = treeview2
        treeview2.setup_table(self.columns, 2, 'MARKID')
        nb.add_widget(treeview2, title='Selected')

        treeview3 = Widgets.TreeView(auto_expand=True,
                                     sortable=True,
                                     use_alt_row_color=True)
        self.treeviewbad = treeview3
        treeview3.setup_table(self.columns, 2, 'MARKID')
        nb.add_widget(treeview3, title='Outliers')

        captions = (('Loaded:', 'llabel', 'ntotal', 'llabel',
                     'Shown:', 'llabel', 'nshown', 'llabel',
                     'Selected:', 'llabel', 'nselected', 'llabel'), )
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        b.ntotal.set_tooltip('Number of objects read from tables')
        b.ntotal.set_text('0')

        b.nshown.set_tooltip('Number of objects shown on image')
        b.nshown.set_text('0')

        b.nselected.set_tooltip('Number of objects selected')
        b.nselected.set_text('0')

        container.add_widget(w, stretch=0)

        captions = (('Load Coords', 'button', 'Use RADEC', 'checkbutton'),
                    ('Show', 'button', 'Hide', 'button', 'Forget', 'button'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        b.load_coords.set_tooltip('Load coordinates file')
        b.load_coords.add_callback('activated', lambda w: self.load_coords_cb())

        b.use_radec.set_tooltip('Use RA/DEC as coordinates instead of X/Y')
        b.use_radec.set_state(self.use_radec)
        b.use_radec.add_callback('activated', self.set_coordtype_cb)

        b.show.set_tooltip('Show markings')
        b.show.add_callback('activated', lambda w: self.redo())

        b.hide.set_tooltip('Hide markings')
        b.hide.add_callback('activated', lambda w: self.clear_marking())

        b.forget.set_tooltip('Forget markings')
        b.forget.add_callback('activated', lambda w: self.forget_coords())

        container.add_widget(w, stretch=0)

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

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

        container.add_widget(btns, stretch=0)

        self.gui_up = True

        # Initialize coordinates file selection dialog
        self.cfilesel = FileSelection(self.fv.w.root.get_widget())

        # Populate table
        self.redo()

    def redo(self):
        """Image or coordinates have changed. Clear and redraw."""
        if not self.gui_up:
            return

        self.clear_marking()
        self.tree_dict = Bunch.caselessDict()
        self.treeviewbad.clear()
        bad_tree_dict = Bunch.caselessDict()
        nbad = 0
        self._xarr = []
        self._yarr = []
        self._treepaths = []

        image = self.fitsimage.get_image()
        if image is None:
            return

        if not hasattr(image, 'radectopix'):
            self.logger.error(
                'Image as no radectopix() method for coordinates conversion')
            return

        objlist = []
        seqno = 1
        max_x = image.width - 1
        max_y = image.height - 1

        for key, coords in self.coords_dict.items():
            if len(coords) == 0:
                continue

            marktype, marksize, markcolor = key
            kstr = ','.join(map(str, key))
            sub_dict = {}
            bad_sub_dict = {}
            self.tree_dict[kstr] = sub_dict
            bad_tree_dict[kstr] = bad_sub_dict

            for args in coords:
                ra, dec, x, y = args[:4]

                # Use X and Y positions directly. Convert to RA and DEC (deg).
                if ra is None or dec is None:
                    ra, dec = image.pixtoradec(x, y)

                # RA and DEC already in degrees. Convert to pixel X and Y.
                else:
                    x, y = image.radectopix(ra, dec)

                # Display original X/Y (can be 0- or 1-indexed) using
                # our internal 0-indexed values.
                xdisp = x + self.pixelstart
                ydisp = y + self.pixelstart

                seqstr = '{0:04d}'.format(seqno)  # Prepend 0s for proper sort
                bnch = Bunch.Bunch(zip(self.extra_columns, args[4:]))  # Extra
                bnch.update(Bunch.Bunch(MARKID=seqstr, RA=ra, DEC=dec,
                                        X=xdisp, Y=ydisp))

                # Do not draw out of bounds
                if (not np.isfinite(x) or x < 0 or x > max_x or
                        not np.isfinite(y) or y < 0 or y > max_y):
                    self.logger.debug('Ignoring RA={0}, DEC={1} '
                                      '(x={2}, y={3})'.format(ra, dec, x, y))
                    bad_sub_dict[seqstr] = bnch
                    nbad += 1

                # Display point
                else:
                    obj = self._get_markobj(
                        x, y, marktype, marksize, markcolor, self.markwidth)
                    objlist.append(obj)

                    sub_dict[seqstr] = bnch
                    self._xarr.append(x)
                    self._yarr.append(y)
                    self._treepaths.append((kstr, seqstr))

                seqno += 1

        n_obj = len(objlist)
        self.logger.debug('Displaying {0} markings'.format(n_obj))

        if nbad > 0:
            self.treeviewbad.set_tree(bad_tree_dict)

        if n_obj == 0:
            return

        # Convert to Numpy arrays to avoid looping later
        self._xarr = np.array(self._xarr)
        self._yarr = np.array(self._yarr)
        self._treepaths = np.array(self._treepaths)

        # Display info table
        self.recreate_toc()

        # Draw on canvas
        self.marktag = self.canvas.add(self.dc.CompoundObject(*objlist))
        self.fitsimage.redraw()  # Force immediate redraw

    def _get_markobj(self, x, y, marktype, marksize, markcolor, markwidth):
        """Generate canvas object for given mark parameters."""
        if marktype == 'circle':
            obj = self.dc.Circle(
                x=x, y=y, radius=marksize, color=markcolor, linewidth=markwidth)
        elif marktype in ('cross', 'plus'):
            obj = self.dc.Point(
                x=x, y=y, radius=marksize, color=markcolor, linewidth=markwidth,
                style=marktype)
        elif marktype == 'box':
            obj = self.dc.Box(
                x=x, y=y, xradius=marksize, yradius=marksize, color=markcolor,
                linewidth=markwidth)
        else:  # point, marksize
            obj = self.dc.Box(
                x=x, y=y, xradius=1, yradius=1, color=markcolor,
                linewidth=markwidth, fill=True, fillcolor=markcolor)

        return obj

    def clear_marking(self):
        """Clear marking from image.
        This does not clear loaded coordinates from memory."""
        if self.marktag:
            try:
                self.canvas.delete_object_by_tag(self.marktag, redraw=False)
            except Exception:
                pass

        if self.markhltag:
            try:
                self.canvas.delete_object_by_tag(self.markhltag, redraw=False)
            except Exception:
                pass

        self.treeview.clear()  # Clear table too
        self.w.nshown.set_text('0')
        self.fitsimage.redraw()  # Force immediate redraw

    def forget_coords(self):
        """Forget all loaded coordinates."""
        self.w.ntotal.set_text('0')
        self.coords_dict.clear()
        self.redo()

    # TODO: Support more formats?
    def load_file(self, filename):
        """Load coordinates file.

        Results are appended to previously loaded coordinates.
        This can be used to load one file per color.

        """
        if not os.path.isfile(filename):
            return

        self.logger.info('Loading coordinates from {0}'.format(filename))

        if filename.endswith('.fits'):
            fmt = 'fits'
        else:  # Assume ASCII
            fmt = 'ascii'

        try:
            tab = Table.read(filename, format=fmt)
        except Exception as e:
            self.logger.error('{0}: {1}'.format(e.__class__.__name__, str(e)))
            return

        if self.use_radec:
            colname0 = self.settings.get('ra_colname', 'ra')
            colname1 = self.settings.get('dec_colname', 'dec')
        else:
            colname0 = self.settings.get('x_colname', 'x')
            colname1 = self.settings.get('y_colname', 'y')

        try:
            col_0 = tab[colname0]
            col_1 = tab[colname1]
        except Exception as e:
            self.logger.error('{0}: {1}'.format(e.__class__.__name__, str(e)))
            return

        nrows = len(col_0)
        dummy_col = [None] * nrows

        try:
            oldrows = int(self.w.ntotal.get_text())
        except ValueError:
            oldrows = 0

        self.w.ntotal.set_text(str(oldrows + nrows))

        if self.use_radec:
            ra = self._convert_radec(col_0)
            dec = self._convert_radec(col_1)
            x = y = dummy_col
        else:
            ra = dec = dummy_col

            # X and Y always 0-indexed internally
            x = col_0.data - self.pixelstart
            y = col_1.data - self.pixelstart

        args = [ra, dec, x, y]

        # Load extra columns
        for colname in self.extra_columns:
            try:
                col = tab[colname].data
            except Exception as e:
                self.logger.error(
                    '{0}: {1}'.format(e.__class__.__name__, str(e)))
                col = dummy_col

            args.append(col)

        # Use list to preserve order. Does not handle duplicates.
        key = (self.marktype, self.marksize, self.markcolor)
        self.coords_dict[key] += list(zip(*args))

        self.redo()

    def _convert_radec(self, val):
        """Convert RA or DEC table column to degrees and extract data.
        Assume already in degrees if cannot convert.

        """
        try:
            ans = val.to('deg')
        except Exception as e:
            self.logger.error('Cannot convert, assume already in degrees')
            ans = val.data
        else:
            ans = ans.value

        return ans

    # TODO: Support more extensions?
    def load_coords_cb(self):
        """Activate file dialog to select coordinates file."""
        self.cfilesel.popup('Load coordinates file', self.load_file,
                            initialdir='.',
                            filename='Table files (*.txt *.dat *.fits)')

    def set_coordtype_cb(self, w, val):
        """Toggle between RA/DEC or X/Y coordinates."""
        self.use_radec = val

    def recreate_toc(self):
        self.logger.debug('Recreating table of contents...')
        self.treeview.set_tree(self.tree_dict)
        n = 0

        for sub_dict in self.tree_dict.values():
            n += len(sub_dict)

        self.w.nshown.set_text(str(n))

    def hl_table2canvas(self, w, res_dict):
        """Highlight marking on canvas when user click on table."""
        objlist = []
        width = self.markwidth + self._dwidth

        # Remove existing highlight
        if self.markhltag:
            try:
                self.canvas.delete_object_by_tag(self.markhltag, redraw=False)
            except Exception:
                pass

        # Display highlighted entries only in second table
        self.treeviewsel.set_tree(res_dict)

        for kstr, sub_dict in res_dict.items():
            s = kstr.split(',')
            marktype = s[0]
            marksize = float(s[1])
            markcolor = s[2]

            for bnch in sub_dict.values():
                obj = self._get_markobj(bnch.X - self.pixelstart,
                                        bnch.Y - self.pixelstart,
                                        marktype, marksize, markcolor, width)
                objlist.append(obj)

        nsel = len(objlist)
        self.w.nselected.set_text(str(nsel))

        # Draw on canvas
        if nsel > 0:
            self.markhltag = self.canvas.add(self.dc.CompoundObject(*objlist))

        self.fitsimage.redraw()  # Force immediate redraw

    def hl_canvas2table_box(self, canvas, tag):
        """Highlight all markings inside user drawn box on table."""
        self.treeview.clear_selection()

        # Remove existing box
        cobj = canvas.get_object_by_tag(tag)
        if cobj.kind != 'rectangle':
            return
        canvas.delete_object_by_tag(tag, redraw=False)

        # Remove existing highlight
        if self.markhltag:
            try:
                canvas.delete_object_by_tag(self.markhltag, redraw=True)
            except Exception:
                pass

        # Nothing to do if no markings are displayed
        try:
            obj = canvas.get_object_by_tag(self.marktag)
        except Exception:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if (len(self._xarr) == 0 or len(self._yarr) == 0 or
                len(self._treepaths) == 0):
            return

        # Find markings inside box
        mask = cobj.contains_arr(self._xarr, self._yarr)

        for hlpath in self._treepaths[mask]:
            self._highlight_path(hlpath)

    # NOTE: This does not work anymore when left click is used to draw box.
    def hl_canvas2table(self, canvas, button, data_x, data_y):
        """Highlight marking on table when user click on canvas."""
        self.treeview.clear_selection()

        # Remove existing highlight
        if self.markhltag:
            try:
                canvas.delete_object_by_tag(self.markhltag, redraw=True)
            except Exception:
                pass

        # Nothing to do if no markings are displayed
        try:
            obj = canvas.get_object_by_tag(self.marktag)
        except Exception:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if (len(self._xarr) == 0 or len(self._yarr) == 0 or
                len(self._treepaths) == 0):
            return

        sr = 10  # self.settings.get('searchradius', 10)
        dx = data_x - self._xarr
        dy = data_y - self._yarr
        dr = np.sqrt(dx * dx + dy * dy)
        mask = dr <= sr

        for hlpath in self._treepaths[mask]:
            self._highlight_path(hlpath)

    def _highlight_path(self, hlpath):
        """Highlight an entry in the table and associated marking."""
        self.logger.debug('Highlighting {0}'.format(hlpath))
        self.treeview.select_path(hlpath)

        # TODO: Does not work in Qt. This is known issue in Ginga.
        self.treeview.scroll_to_path(hlpath)

    def set_marktype_cb(self, w, index):
        """Set type of marking."""
        self.marktype = self._mark_options[index]

        # Mark size is not used for point
        if self.marktype != 'point':
            self.w.mark_size.set_enabled(True)
        else:
            self.w.mark_size.set_enabled(False)

    def set_markcolor_cb(self, w, index):
        """Set color of marking."""
        self.markcolor = self._color_options[index]

    def set_marksize(self):
        """Set size/radius of marking."""
        try:
            sz = float(self.w.mark_size.get_text())
        except ValueError:
            self.logger.error('Cannot set mark size')
            self.w.mark_size.set_text(str(self.marksize))
        else:
            self.marksize = sz

    def set_markwidth(self):
        """Set width of marking."""
        try:
            sz = int(self.w.mark_width.get_text())
        except ValueError:
            self.logger.error('Cannot set mark width')
            self.w.mark_width.set_text(str(self.markwidth))
        else:
            self.markwidth = sz

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

    def start(self):
        # insert canvas, if not already
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.get_object_by_tag(self.layertag)
        except KeyError:
            # Add drawing layer
            p_canvas.add(self.canvas, tag=self.layertag)

        self.resume()

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

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

        self.canvas.ui_set_active(True)
        self.fv.show_status('Press "Help" for instructions')

    def stop(self):
        # remove canvas from image
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.delete_object_by_tag(self.layertag)
        except Exception:
            pass

        # Free some memory, maybe
        self.tree_dict = Bunch.caselessDict()
        self._xarr = []
        self._yarr = []
        self._treepaths = []

        self.gui_up = False
        self.fv.show_status('')

    def __str__(self):
        """
        This method should be provided and should return the lower case
        name of the plugin.
        """
        return 'tvmark'
Exemplo n.º 3
0
class CSU_initializer(GingaPlugin.LocalPlugin):
    def __init__(self, fv, fitsimage):
        """
        This method is called when the plugin is loaded for the  first
        time.  ``fv`` is a reference to the Ginga (reference viewer) shell
        and ``fitsimage`` is a reference to the specific ImageViewCanvas
        object associated with the channel on which the plugin is being
        invoked.
        You need to call the superclass initializer and then do any local
        initialization.
        """
        super(CSU_initializer, self).__init__(fv, fitsimage)

        # Load plugin preferences
        prefs = self.fv.get_preferences()
        self.settings = prefs.createCategory('plugin_CSU_initializer')
        self.settings.setDefaults(
            bar_num=1,
            move_to_open=False,
            overlay_bar_positions_from_csu_bar_state_file=False,
            overlay_bar_positions_from_fits_header=False,
            overlay_bar_positions_from_analyzed_image=False,
            bar_dist=0.0,
        )
        self.settings.load(onError='silent')

        self.layertag = 'bars-canvas'
        self.dc = fv.get_draw_classes()
        canvas = self.dc.DrawingCanvas()
        canvas.enable_draw(False)
        canvas.set_surface(self.fitsimage)
        self.canvas = canvas

        self.colornames = colors.get_colors()
        self.canvas_img = None

        self.mfilesel = FileSelection(self.fv.w.root.get_widget())

        ## Define dimensions and angles relative to the pixels of the image
        #         self.slit_angle = (4.00-0.22) * np.pi/180.
        #         pixels = np.array([ (721, 2022), # pixels
        #                             (1068, 1934),
        #                             (984, 1804),
        #                             (1112, 40),
        #                           ])
        #         physical = np.array([ (179.155, self.bar_to_slit(2)), # mm, slit number
        #                               (133.901, self.bar_to_slit(6)),
        #                               (144.962, self.bar_to_slit(12)),
        #                               (129.033, self.bar_to_slit(92))
        #                             ])

        pixels = np.array([
            (1026.6847023205248, 31.815757489924671),
            (1031.1293065907989, 31.815757489924671),
            (1100.0527926274958, 76.568051304306408),
            (1104.4723170387663, 76.568051304306408),
            (869.79921202733158, 119.71402079180322),
            (874.17468615739256, 119.71402079180322),
            (790.04504261037619, 163.97941699869187),
            (794.38269316256697, 163.97941699869187),
            (844.76764696920873, 208.45498973235158),
            (849.06840834451555, 208.45498973235158),
            (918.16119587182891, 253.46863795483193),
            (922.57167115281891, 253.46863795483193),
            (667.1708458173706, 296.83477802171569),
            (671.58750566149126, 296.83477802171569),
            (1210.6743343816352, 342.85304935109269),
            (1215.1047501727178, 342.85304935109269),
            (1037.1504738673596, 386.56200191364559),
            (1041.5376839155629, 386.56200191364559),
            (1380.9733624348846, 431.75478066748974),
            (1385.3923546613969, 431.75478066748974),
            (1392.3137244788115, 476.40898670973735),
            (1396.5838727543558, 476.40898670973735),
            (701.99737614209846, 518.12290417047029),
            (706.31972548163674, 518.12290417047029),
            (775.43118955263321, 562.76481942553085),
            (779.76336695630744, 562.76481942553085),
            (695.39446696825667, 606.9386852721824),
            (699.68592870194686, 606.9386852721824),
            (1225.8966927438423, 652.79237015375304),
            (1230.2681865131638, 652.79237015375304),
            (1299.3047613957535, 697.52305237026349),
            (1303.6542557465727, 697.52305237026349),
            (953.60567493512144, 740.39597570556316),
            (957.91890612112604, 740.39597570556316),
            (1027.0080928255736, 784.70486151318767),
            (1031.3650789520013, 784.70486151318767),
            (1241.625753053888, 830.10892664282756),
            (1245.9181149708163, 830.10892664282756),
            (1266.796600696397, 874.17188807394371),
            (1271.1082253968038, 874.17188807394371),
            (1404.8881828516335, 919.85774261912377),
            (1409.9449171925908, 919.85774261912377),
            (1325.0207484270156, 963.32163630950686),
            (1329.3681702175545, 963.32163630950686),
            (1185.9570564396361, 1007.0164717446025),
            (1190.2368155733498, 1007.0164717446025),
            (1306.6628878384579, 1051.9073888851103),
            (1310.9679069215179, 1051.9073888851103),
            (1151.3860791138529, 1095.4860726831637),
            (1155.7367238283309, 1095.4860726831637),
            (1224.7162502034391, 1140.436681012593),
            (1229.0598756552718, 1140.436681012593),
            (904.70409145100268, 1183.267412335555),
            (908.99297982589781, 1183.267412335555),
            (978.00762214758913, 1227.9731804278615),
            (982.41054057239705, 1227.9731804278615),
            (869.65543493075677, 1271.3564678397893),
            (873.95299108698168, 1271.3564678397893),
            (942.99396243198464, 1316.2391922602001),
            (947.36667894787513, 1316.2391922602001),
            (1256.7806430753744, 1361.195495916817),
            (1261.0847133245632, 1361.195495916817),
            (1330.1305637595844, 1406.3795550431571),
            (1334.3960288420271, 1406.3795550431571),
            (1060.9423305503171, 1449.3586376395574),
            (1065.3182032594575, 1449.3586376395574),
            (1108.6465868246237, 1493.9756362677167),
            (1112.9382994207679, 1493.9756362677167),
            (662.84522896384874, 1536.9734554153649),
            (667.12956877347722, 1536.9734554153649),
            (712.5287834914659, 1581.2712766110319),
            (716.80585127180609, 1581.2712766110319),
            (956.48762939159371, 1626.1728182002655),
            (960.9581522740466, 1626.1728182002655),
            (723.23974640617337, 1670.0165354200499),
            (727.67208274341931, 1670.0165354200499),
            (1172.3594885486252, 1715.8650599984883),
            (1176.8341929555718, 1715.8650599984883),
            (1015.7329598422145, 1759.5446833817025),
            (1020.1920698607528, 1759.5446833817025),
            (935.82358262678224, 1803.5644982617907),
            (940.3126440130676, 1803.5644982617907),
            (989.98752991018682, 1847.9507718487364),
            (994.40511955530712, 1847.9507718487364),
            (1278.2218422583971, 1892.8072028048214),
            (1282.7070969966558, 1892.8072028048214),
            (1351.5377751257745, 1938.5923374638328),
            (1355.9221844080257, 1938.5923374638328),
            (1171.5812780061251, 1981.4914424153424),
            (1176.0817255338613, 1981.4914424153424),
        ])

        physical = np.array([
            (139.917, self.bar_to_slit(92)),
            (139.41, self.bar_to_slit(91)),
            (130.322, self.bar_to_slit(90)),
            (129.815, self.bar_to_slit(89)),
            (160.334, self.bar_to_slit(88)),
            (159.827, self.bar_to_slit(87)),
            (170.738, self.bar_to_slit(86)),
            (170.231, self.bar_to_slit(85)),
            (163.579, self.bar_to_slit(84)),
            (163.072, self.bar_to_slit(83)),
            (153.983, self.bar_to_slit(82)),
            (153.476, self.bar_to_slit(81)),
            (186.718, self.bar_to_slit(80)),
            (186.211, self.bar_to_slit(79)),
            (115.773, self.bar_to_slit(78)),
            (115.266, self.bar_to_slit(77)),
            (138.413, self.bar_to_slit(76)),
            (137.906, self.bar_to_slit(75)),
            (93.508, self.bar_to_slit(74)),
            (93.001, self.bar_to_slit(73)),
            (92.021, self.bar_to_slit(72)),
            (91.514, self.bar_to_slit(71)),
            (182.097, self.bar_to_slit(70)),
            (181.59, self.bar_to_slit(69)),
            (172.502, self.bar_to_slit(68)),
            (171.995, self.bar_to_slit(67)),
            (182.905, self.bar_to_slit(66)),
            (182.398, self.bar_to_slit(65)),
            (113.665, self.bar_to_slit(64)),
            (113.158, self.bar_to_slit(63)),
            (104.069, self.bar_to_slit(62)),
            (103.562, self.bar_to_slit(61)),
            (149.161, self.bar_to_slit(60)),
            (148.654, self.bar_to_slit(59)),
            (139.566, self.bar_to_slit(58)),
            (139.059, self.bar_to_slit(57)),
            (111.528, self.bar_to_slit(56)),
            (111.021, self.bar_to_slit(55)),
            (108.22, self.bar_to_slit(54)),
            (107.713, self.bar_to_slit(53)),
            (90.189, self.bar_to_slit(52)),
            (89.681, self.bar_to_slit(51)),
            (100.593, self.bar_to_slit(50)),
            (100.086, self.bar_to_slit(49)),
            (118.731, self.bar_to_slit(48)),
            (118.223, self.bar_to_slit(47)),
            (102.94, self.bar_to_slit(46)),
            (102.432, self.bar_to_slit(45)),
            (123.212, self.bar_to_slit(44)),
            (122.704, self.bar_to_slit(43)),
            (113.615, self.bar_to_slit(42)),
            (113.108, self.bar_to_slit(41)),
            (155.354, self.bar_to_slit(40)),
            (154.847, self.bar_to_slit(39)),
            (145.759, self.bar_to_slit(38)),
            (145.251, self.bar_to_slit(37)),
            (159.887, self.bar_to_slit(36)),
            (159.38, self.bar_to_slit(35)),
            (150.292, self.bar_to_slit(34)),
            (149.785, self.bar_to_slit(33)),
            (109.338, self.bar_to_slit(32)),
            (108.83, self.bar_to_slit(31)),
            (99.742, self.bar_to_slit(30)),
            (99.235, self.bar_to_slit(29)),
            (134.842, self.bar_to_slit(28)),
            (134.335, self.bar_to_slit(27)),
            (128.616, self.bar_to_slit(26)),
            (128.109, self.bar_to_slit(25)),
            (186.778, self.bar_to_slit(24)),
            (186.271, self.bar_to_slit(23)),
            (180.272, self.bar_to_slit(22)),
            (179.765, self.bar_to_slit(21)),
            (148.417, self.bar_to_slit(20)),
            (147.91, self.bar_to_slit(19)),
            (178.822, self.bar_to_slit(18)),
            (178.314, self.bar_to_slit(17)),
            (120.197, self.bar_to_slit(16)),
            (119.689, self.bar_to_slit(15)),
            (140.601, self.bar_to_slit(14)),
            (140.094, self.bar_to_slit(13)),
            (151.005, self.bar_to_slit(12)),
            (150.498, self.bar_to_slit(11)),
            (143.947, self.bar_to_slit(10)),
            (143.44, self.bar_to_slit(9)),
            (106.313, self.bar_to_slit(8)),
            (105.806, self.bar_to_slit(7)),
            (96.717, self.bar_to_slit(6)),
            (96.21, self.bar_to_slit(5)),
            (120.202, self.bar_to_slit(4)),
            (119.695, self.bar_to_slit(3)),
        ])

        tick = dt.now()
        self.fit_transforms(pixels, physical)
        tock = dt.now()
        elapsed = (tock - tick).total_seconds()
        print('  Fitted transforms in {:.3f} s'.format(elapsed))

        ## Determine slit angle and bar center to center distance in pixels
        ## from the transformation and the known longslit positions
        ##   in longslit, bar 02 is at 145.472
        ##   in longslit, bar 92 is at 129.480
        physical = [[145.472, self.bar_to_slit(2)],
                    [129.480, self.bar_to_slit(92)]]
        pixels = self.physical_to_pixel(physical)
        dx = pixels[1][0] - pixels[0][0]
        dy = pixels[0][1] - pixels[1][1]
        self.slit_angle_pix = np.arctan(dx / dy)
        print("Slit Angle on CCD = {:.3f} deg".format(self.slit_angle_pix *
                                                      180. / np.pi))
        self.slit_height_pix = dy / (self.bar_to_slit(92) -
                                     self.bar_to_slit(2))
        print("Slit Height on CCD = {:.3f} pix".format(self.slit_height_pix))

    def build_gui(self, container):
        """
        This method is called when the plugin is invoked.  It builds the
        GUI used by the plugin into the widget layout passed as
        ``container``.
        This method may be called many times as the plugin is opened and
        closed for modal operations.  The method may be omitted if there
        is no GUI for the plugin.
        This specific example uses the GUI widget set agnostic wrappers
        to build the GUI, but you can also just as easily use explicit
        toolkit calls here if you only want to support one widget set.
        """
        top = Widgets.VBox()
        top.set_border_width(4)

        # this is a little trick for making plugins that work either in
        # a vertical or horizontal orientation.  It returns a box container,
        # a scroll widget and an orientation ('vertical', 'horizontal')
        vbox, sw, orientation = Widgets.get_oriented_box(container)
        vbox.set_border_width(4)
        vbox.set_spacing(2)

        self.msg_font = self.fv.get_font("sansFont", 12)

        ## -----------------------------------------------------
        ## Acquire or Load Image
        ## -----------------------------------------------------
        fr1 = Widgets.Frame("Image the CSU Mask")
        vbox.add_widget(fr1, stretch=0)

        # A button box that is always visible at the top
        btns1 = Widgets.HBox()
        btns1.set_spacing(3)

        # Add mask image buttons
        btn_acq_im = Widgets.Button("Acquire Mask Image")
        btn_acq_im.add_callback('activated', lambda w: self.acq_mask_image())
        btns1.add_widget(btn_acq_im, stretch=0)
        btns1.add_widget(Widgets.Label(''), stretch=1)

        btn_load_im = Widgets.Button("Load Mask Image")
        btn_load_im.add_callback('activated', lambda w: self.load_mask_image())
        btns1.add_widget(btn_load_im, stretch=0)
        btns1.add_widget(Widgets.Label(''), stretch=1)

        vbox.add_widget(btns1, stretch=0)

        ## -----------------------------------------------------
        ## Analyze Image
        ## -----------------------------------------------------
        #         tw_analyze = Widgets.TextArea(wrap=True, editable=False)
        #         tw_analyze.set_font(self.msg_font)
        #         self.tw_analyze = tw_analyze

        fr2 = Widgets.Frame("Analyze CSU Mask Image")
        #         fr2.set_widget(tw_analyze)
        vbox.add_widget(fr2, stretch=0)

        btns2 = Widgets.HBox()
        btns2.set_spacing(3)

        btn_analyze = Widgets.Button("Analyze Mask Image")
        btn_analyze.add_callback('activated',
                                 lambda w: self.analyze_mask_image())
        btns2.add_widget(btn_analyze, stretch=0)
        btns2.add_widget(Widgets.Label(''), stretch=1)

        vbox.add_widget(btns2, stretch=0)

        ## -----------------------------------------------------
        ## Full Mask Initialization
        ## -----------------------------------------------------
        #         fr3 = Widgets.Frame("Full Mask Initialization")
        #
        #         captions = [
        #             ("Fast Init from Keywords", 'button'),
        #             ("Fast Init from Header", 'button'),
        #             ("Fast Init from Image Analysis", 'button'),
        #             ("Full Init", 'button'),
        #             ]
        #
        #         w, b = Widgets.build_info(captions, orientation=orientation)
        #         self.w.update(b)
        #
        #         b.fast_init_from_keywords.add_callback('activated', lambda w: self.fast_init_from_keywords_cb())
        #         b.fast_init_from_header.add_callback('activated', lambda w: self.fast_init_from_header_cb())
        #         b.fast_init_from_image_analysis.add_callback('activated', lambda w: self.fast_init_from_image_analysis_cb())
        #         b.full_init.add_callback('activated', lambda w: self.full_init_cb())
        #
        #
        #         fr3.set_widget(w)
        #         vbox.add_widget(fr3, stretch=0)

        ## -----------------------------------------------------
        ## Bar Control
        ## -----------------------------------------------------
        #         tw_bar_control = Widgets.TextArea(wrap=True, editable=False)
        #         tw_bar_control.set_font(self.msg_font)
        #         self.tw_bar_control = tw_bar_control

        # Frame for instructions and add the text widget with another
        # blank widget to stretch as needed to fill emp
        fr4 = Widgets.Frame("CSU Bar Control")
        #         fr1.set_widget(tw_bar_control)

        captions = [
            ("CSU Bar: ", 'label', 'bar_num', 'llabel', 'set_bar_num',
             'entry'),
            ("Distance: ", 'label', 'bar_dist', 'llabel', 'set_bar_dist',
             'entry'),
            ("Initialize Bar", 'button', "Move to open", 'checkbutton'),
            ("Move Bar", 'button'),
        ]

        w, b = Widgets.build_info(captions, orientation=orientation)
        self.w.update(b)

        bar_num = self.settings.get('bar_num', 1)
        b.bar_num.set_text('{:2d}'.format(bar_num))
        b.set_bar_num.set_text(str(bar_num))
        b.set_bar_num.add_callback('activated', self.set_bar_num_cb)
        b.set_bar_num.set_tooltip("Set bar number")

        bar_dist = self.settings.get('bar_dist', 0.0)
        b.bar_dist.set_text('{:+.1f}'.format(bar_dist))
        b.set_bar_dist.set_text(str(bar_dist))
        b.set_bar_dist.add_callback('activated', self.set_bar_dist_cb)
        b.set_bar_dist.set_tooltip("Set distance to move bar")

        b.move_to_open.set_tooltip(
            "Move bar to open position before initialization")
        move_to_open = self.settings.get('move_to_open', False)
        b.move_to_open.set_state(move_to_open)
        b.move_to_open.add_callback('activated', self.move_to_open_cb)
        b.initialize_bar.add_callback('activated',
                                      lambda w: self.initialize_bar_cb())

        b.move_bar.add_callback('activated', lambda w: self.move_bar_cb())

        fr4.set_widget(w)
        vbox.add_widget(fr4, stretch=0)

        ## -----------------------------------------------------
        ## Bar Overlay
        ## -----------------------------------------------------
        fr5 = Widgets.Frame("Bar Overlay")

        #captions = (('Overlay bar positions from csu_bar_state file', 'button'),
        #            ('Overlay bar positions from FITS header', 'button'),
        #            ('Clear', 'button'))

        captions = (('Overlay bar positions from csu_bar_state file',
                     'checkbutton'), ('Overlay bar positions from FITS header',
                                      'checkbutton'),
                    ('Overlay bar positions from analyzed image',
                     'checkbutton'), ('Clear', 'button'))

        w, b = Widgets.build_info(captions, orientation=orientation)
        self.w.update(b)

        overlay_bar_positions_from_csu_bar_state_file = self.settings.get(
            'overlay_bar_positions_from_csu_bar_state_file', False)
        b.overlay_bar_positions_from_csu_bar_state_file.set_state(
            overlay_bar_positions_from_csu_bar_state_file)
        b.overlay_bar_positions_from_csu_bar_state_file.add_callback(
            'activated', self.overlaybars_from_file_cb)

        overlay_bar_positions_from_fits_header = self.settings.get(
            'overlay_bar_positions_from_fits_header', False)
        b.overlay_bar_positions_from_fits_header.set_state(
            overlay_bar_positions_from_fits_header)
        b.overlay_bar_positions_from_fits_header.add_callback(
            'activated', self.overlaybars_from_header_cb)

        overlay_bar_positions_from_analyzed_image = self.settings.get(
            'overlay_bar_positions_from_analyzed_image', False)
        b.overlay_bar_positions_from_analyzed_image.set_state(
            overlay_bar_positions_from_analyzed_image)
        b.overlay_bar_positions_from_fits_header.add_callback(
            'activated', self.overlaybars_from_image_cb)

        b.clear.add_callback('activated', lambda w: self.clear_canvas())
        fr5.set_widget(w)
        vbox.add_widget(fr5, stretch=0)

        ## -----------------------------------------------------
        ## Spacer
        ## -----------------------------------------------------

        # Add a spacer to stretch the rest of the way to the end of the
        # plugin space
        spacer = Widgets.Label('')
        vbox.add_widget(spacer, stretch=1)

        # scroll bars will allow lots of content to be accessed
        top.add_widget(sw, stretch=1)

        ## -----------------------------------------------------
        ## Bottom
        ## -----------------------------------------------------

        # A button box that is always visible at the bottom
        btns_close = Widgets.HBox()
        btns_close.set_spacing(3)

        # Add a close button for the convenience of the user
        btn = Widgets.Button("Close")
        btn.add_callback('activated', lambda w: self.close())
        btns_close.add_widget(btn, stretch=0)

        btns_close.add_widget(Widgets.Label(''), stretch=1)
        top.add_widget(btns_close, stretch=0)

        # Add our GUI to the container
        container.add_widget(top, stretch=1)
        # NOTE: if you are building a GUI using a specific widget toolkit
        # (e.g. Qt) GUI calls, you need to extract the widget or layout
        # from the non-toolkit specific container wrapper and call on that
        # to pack your widget, e.g.:
        #cw = container.get_widget()
        #cw.addWidget(widget, stretch=1)

    def close(self):
        """
        Example close method.  You can use this method and attach it as a
        callback to a button that you place in your GUI to close the plugin
        as a convenience to the user.
        """
        self.fv.stop_local_plugin(self.chname, str(self))
        return True

    def start(self):
        """
        This method is called just after ``build_gui()`` when the plugin
        is invoked.  This method may be called many times as the plugin is
        opened and closed for modal operations.  This method may be omitted
        in many cases.
        """
        # start ruler drawing operation
        p_canvas = self.fitsimage.get_canvas()
        try:
            obj = p_canvas.get_object_by_tag(self.layertag)

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

        self.resume()

    def pause(self):
        """
        This method is called when the plugin loses focus.
        It should take any actions necessary to stop handling user
        interaction events that were initiated in ``start()`` or
        ``resume()``.
        This method may be called many times as the plugin is focused
        or defocused.  It may be omitted if there is no user event handling
        to disable.
        """
        pass

    def resume(self):
        """
        This method is called when the plugin gets focus.
        It should take any actions necessary to start handling user
        interaction events for the operations that it does.
        This method may be called many times as the plugin is focused or
        defocused.  The method may be omitted if there is no user event
        handling to enable.
        """
        pass

    def stop(self):
        """
        This method is called when the plugin is stopped.
        It should perform any special clean up necessary to terminate
        the operation.  The GUI will be destroyed by the plugin manager
        so there is no need for the stop method to do that.
        This method may be called many  times as the plugin is opened and
        closed for modal operations, and may be omitted if there is no
        special cleanup required when stopping.
        """
        pass

    def redo(self):
        """
        This method is called when the plugin is active and a new
        image is loaded into the associated channel.  It can optionally
        redo the current operation on the new image.  This method may be
        called many times as new images are loaded while the plugin is
        active.  This method may be omitted.
        """
        pass

    def __str__(self):
        """
        This method should be provided and should return the lower case
        name of the plugin.
        """
        return 'CSU Initializer Plugin'

    ## ------------------------------------------------------------------
    ##  Coordinate Transformation Utilities
    ## ------------------------------------------------------------------
    def slit_to_bars(self, slit):
        return (slit * 2 - 1, slit * 2)

    def bar_to_slit(self, bar):
        return int((bar + 1) / 2)

    def pad(self, x):
        return np.hstack([x, np.ones((x.shape[0], 1))])

    def unpad(self, x):
        return x[:, :-1]

    def fit_transforms(self, pixels, physical):
        assert pixels.shape[1] == 2
        assert physical.shape[1] == 2
        assert pixels.shape[0] == physical.shape[0]

        # Pad the data with ones, so that our transformation can do translations too
        n = pixels.shape[0]
        pad = lambda x: np.hstack([x, np.ones((x.shape[0], 1))])
        unpad = lambda x: x[:, :-1]
        X = pad(pixels)
        Y = pad(physical)

        # Solve the least squares problem X * A = Y
        # to find our transformation matrix A
        A, res, rank, s = np.linalg.lstsq(X, Y)
        Ainv, res, rank, s = np.linalg.lstsq(Y, X)
        A[np.abs(A) < 1e-10] = 0
        Ainv[np.abs(A) < 1e-10] = 0
        self.Apixel_to_physical = A
        self.Aphysical_to_pixel = Ainv

    def pixel_to_physical(self, x):
        x = np.array(x)
        result = self.unpad(np.dot(self.pad(x), self.Apixel_to_physical))
        return result

    def physical_to_pixel(self, x):
        x = np.array(x)
        result = self.unpad(np.dot(self.pad(x), self.Aphysical_to_pixel))
        return result

    ## ------------------------------------------------------------------
    ##  Read Bar Positions and Overlay
    ## ------------------------------------------------------------------
    def read_csu_bar_state(self, filename):
        with open(filename, 'r') as FO:
            lines = FO.readlines()
        bars = {}
        state = {}
        state_trans = {0: 'OK', 1: 'SETUP', 2: 'MOVING', -3: 'ERROR'}
        for line in lines:
            barno, pos, statestr = line.strip('\n').split(',')
            bars[int(barno)] = float(pos)
            state[int(barno)] = state_trans[int(statestr)]
        return bars, state

    def read_bars_from_header(self, header):
        bars = {}
        for i in range(1, 93):
            bars[i] = float(header['B{:02d}POS'.format(i)])
        return bars

    def overlaybars(self, bars, state=None):
        colormap = {'OK': 'green', 'ERROR': 'red'}
        draw_height = 0.45
        for j in range(1, 47):
            b1, b2 = self.slit_to_bars(j)

            physical1 = [[8.0, j - draw_height], [8.0, j + draw_height],
                         [bars[b1], j + draw_height],
                         [bars[b1], j - draw_height]]
            physical1 = np.array(physical1)
            pixels1 = self.physical_to_pixel(physical1)
            pixels1[2][0] += draw_height * self.slit_height_pix * np.sin(
                self.slit_angle_pix)
            pixels1[3][0] -= draw_height * self.slit_height_pix * np.sin(
                self.slit_angle_pix)

            physical2 = [[270.4 + 2.0, j - draw_height],
                         [270.4 + 2.0, j + draw_height],
                         [bars[b2], j + draw_height],
                         [bars[b2], j - draw_height]]
            physical2 = np.array(physical2)
            pixels2 = self.physical_to_pixel(physical2)
            pixels2[2][0] += draw_height * self.slit_height_pix * np.sin(
                self.slit_angle_pix)
            pixels2[3][0] -= draw_height * self.slit_height_pix * np.sin(
                self.slit_angle_pix)

            try:
                b1color = colormap[state[b1]]
            except:
                b1color = 'blue'
            try:
                b2color = colormap[state[b2]]
            except:
                b2color = 'blue'

            self.canvas.add(self.dc.Polygon(pixels1, color=b1color))
            self.canvas.add(self.dc.Polygon(pixels2, color=b2color))
            x1, y1 = self.physical_to_pixel([[14.0, j + 0.3]])[0]
            self.canvas.add(
                self.dc.Text(x1,
                             y1,
                             '{:d}'.format(b1),
                             fontsize=10,
                             color='white'))
            x2, y2 = self.physical_to_pixel([[270.4 - 2.0, j + 0.3]])[0]
            self.canvas.add(
                self.dc.Text(x2,
                             y2,
                             '{:d}'.format(b2),
                             fontsize=10,
                             color='white'))

    def overlaybars_from_file(self):
        bars, state = self.read_csu_bar_state(
            '/Users/jwalawender/MOSFIRE_Test_Data/20170414/csu_bar_state')
        self.overlaybars(bars, state=state)

    def clear_canvas(self):
        self.canvas.delete_all_objects()

    ## ------------------------------------------------------------------
    ##  Button Callbacks
    ## ------------------------------------------------------------------
    def set_bar_num_cb(self, w):
        bar_num = int(w.get_text())
        self.settings.set(bar_num=bar_num)
        self.w.bar_num.set_text('{:2d}'.format(bar_num))

    def initialize_bar_cb(self):
        if self.settings.get('move_to_open'):
            pass
        else:
            pass

    def move_to_open_cb(self, widget, tf):
        self.settings.set(move_to_open_cb=tf)

    def set_bar_dist_cb(self, w):
        bar_dist = float(w.get_text())
        self.settings.set(bar_dist=bar_dist)
        self.w.bar_dist.set_text('{:+.1f}'.format(bar_dist))

    def move_bar_cb(self):
        pass

    def load_cb(self):
        self.mfilesel.popup('Load bar file',
                            self.overlaybars,
                            initialdir='.',
                            filename='txt files (*.txt)')

    def overlaybars_from_file_cb(self, widget, tf):
        self.settings.set(overlaybars_from_file=tf)
        overlay_bar_positions_from_csu_bar_state_file = self.settings.get(
            'overlay_bar_positions_from_csu_bar_state_file', False)
        if overlay_bar_positions_from_csu_bar_state_file:
            bars, state = self.read_csu_bar_state(
                '/Users/jwalawender/MOSFIRE_Test_Data/20170414/csu_bar_state')
            self.overlaybars(bars, state=state)
        else:
            self.canvas.delete_all_objects()

    def overlaybars_from_header_cb(self, widget, tf):
        self.settings.set(overlay_bar_positions_from_fits_header=tf)
        overlay_bar_positions_from_fits_header = self.settings.get(
            'overlay_bar_positions_from_fits_header', False)
        if overlay_bar_positions_from_fits_header:
            channel = self.fv.get_channel(self.chname)
            image = channel.get_current_image()
            header = image.get_header()
            bars = self.read_bars_from_header(header)
            picture = self.overlaybars(bars)
        else:
            self.canvas.delete_all_objects()

    def overlaybars_from_image_cb(self, widget, tf):
        self.settings.set(overlay_bar_positions_from_analyzed_image=tf)
Exemplo n.º 4
0
class TVMark(LocalPlugin):

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

        self.layertag = 'tvmark-canvas'
        self.marktag = None
        self.markhltag = None

        self._mark_options = ['box', 'circle', 'cross', 'plus', 'point']
        self._color_options = self._short_color_list()
        self._dwidth = 2  # Additional width to highlight selection

        # User preferences. Some are just default values and can also be
        # changed by GUI.
        prefs = self.fv.get_preferences()
        self.settings = prefs.create_category('plugin_TVMark')
        self.settings.add_defaults(marktype='circle', markcolor='green',
                                   marksize=5, markwidth=1, pixelstart=1,
                                   use_radec=True,
                                   ra_colname='ra', dec_colname='dec',
                                   x_colname='x', y_colname='y',
                                   extra_columns=[])
        self.settings.load(onError='silent')
        self.marktype = self.settings.get('marktype', 'circle')
        self.markcolor = self.settings.get('markcolor', 'green')
        self.marksize = self.settings.get('marksize', 5)
        self.markwidth = self.settings.get('markwidth', 1)
        self.pixelstart = self.settings.get('pixelstart', 1)
        self.use_radec = self.settings.get('use_radec', True)
        self.extra_columns = self.settings.get('extra_columns', [])

        # Display coords info table
        self.treeview = None
        self.treeviewsel = None
        self.treeviewbad = None
        self.tree_dict = Bunch.caselessDict()
        self.columns = [('No.', 'MARKID'), ('RA', 'RA'), ('DEC', 'DEC'),
                        ('X', 'X'), ('Y', 'Y')]

        # Append extra columns to table header
        self.columns += [(colname, colname) for colname in self.extra_columns]

        # Store results
        self.coords_dict = defaultdict(list)
        self._xarr = []
        self._yarr = []
        self._treepaths = []

        self.dc = self.fv.get_draw_classes()

        canvas = self.dc.DrawingCanvas()
        canvas.enable_draw(True)
        canvas.enable_edit(False)
        canvas.set_callback('draw-event', self.hl_canvas2table_box)
        #canvas.set_callback('cursor-down', self.hl_canvas2table)
        canvas.register_for_cursor_drawing(self.fitsimage)
        canvas.set_surface(self.fitsimage)
        canvas.set_drawtype('rectangle', color='green', linestyle='dash')
        self.canvas = canvas

        fv.add_callback('remove-image', lambda *args: self.redo())

        self.gui_up = False

    # If user complains about lack of choices (!!!), we can remove this.
    def _short_color_list(self):
        """Color list is too long. Discard variations with numbers."""
        return [c for c in colors.get_colors() if not re.search(r'\d', c)]

    def build_gui(self, container):
        vbox, sw, self.orientation = Widgets.get_oriented_box(container)

        captions = (('Mark:', 'label', 'mark type', 'combobox'),
                    ('Color:', 'label', 'mark color', 'combobox'),
                    ('Size:', 'label', 'mark size', 'entry'),
                    ('Width:', 'label', 'mark width', 'entry'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        combobox = b.mark_type
        for name in self._mark_options:
            combobox.append_text(name)
        b.mark_type.set_index(self._mark_options.index(self.marktype))
        b.mark_type.add_callback('activated', self.set_marktype_cb)

        combobox = b.mark_color
        for name in self._color_options:
            combobox.append_text(name)
        b.mark_color.set_index(self._color_options.index(self.markcolor))
        b.mark_color.add_callback('activated', self.set_markcolor_cb)

        b.mark_size.set_tooltip('Size/radius of the marking')
        b.mark_size.set_text(str(self.marksize))
        b.mark_size.add_callback('activated', lambda w: self.set_marksize())

        b.mark_width.set_tooltip('Line width of the marking')
        b.mark_width.set_text(str(self.markwidth))
        b.mark_width.add_callback('activated', lambda w: self.set_markwidth())

        container.add_widget(w, stretch=0)

        nb = Widgets.TabWidget()
        self.w.nb1 = nb
        container.add_widget(nb, stretch=1)

        treeview = Widgets.TreeView(auto_expand=True,
                                    sortable=True,
                                    selection='multiple',
                                    use_alt_row_color=True)
        self.treeview = treeview
        treeview.setup_table(self.columns, 2, 'MARKID')
        treeview.add_callback('selected', self.hl_table2canvas)
        nb.add_widget(treeview, title='Shown')

        treeview2 = Widgets.TreeView(auto_expand=True,
                                     sortable=True,
                                     use_alt_row_color=True)
        self.treeviewsel = treeview2
        treeview2.setup_table(self.columns, 2, 'MARKID')
        nb.add_widget(treeview2, title='Selected')

        treeview3 = Widgets.TreeView(auto_expand=True,
                                     sortable=True,
                                     use_alt_row_color=True)
        self.treeviewbad = treeview3
        treeview3.setup_table(self.columns, 2, 'MARKID')
        nb.add_widget(treeview3, title='Outliers')

        captions = (('Loaded:', 'llabel', 'ntotal', 'llabel',
                     'Shown:', 'llabel', 'nshown', 'llabel',
                     'Selected:', 'llabel', 'nselected', 'llabel'), )
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        b.ntotal.set_tooltip('Number of objects read from tables')
        b.ntotal.set_text('0')

        b.nshown.set_tooltip('Number of objects shown on image')
        b.nshown.set_text('0')

        b.nselected.set_tooltip('Number of objects selected')
        b.nselected.set_text('0')

        container.add_widget(w, stretch=0)

        captions = (('Load Coords', 'button', 'Use RADEC', 'checkbutton'),
                    ('Show', 'button', 'Hide', 'button', 'Forget', 'button'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        b.load_coords.set_tooltip('Load coordinates file')
        b.load_coords.add_callback('activated', lambda w: self.load_coords_cb())

        b.use_radec.set_tooltip('Use RA/DEC as coordinates instead of X/Y')
        b.use_radec.set_state(self.use_radec)
        b.use_radec.add_callback('activated', self.set_coordtype_cb)

        b.show.set_tooltip('Show markings')
        b.show.add_callback('activated', lambda w: self.redo())

        b.hide.set_tooltip('Hide markings')
        b.hide.add_callback('activated', lambda w: self.clear_marking())

        b.forget.set_tooltip('Forget markings')
        b.forget.add_callback('activated', lambda w: self.forget_coords())

        container.add_widget(w, stretch=0)

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

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

        container.add_widget(btns, stretch=0)

        self.gui_up = True

        # Initialize coordinates file selection dialog
        self.cfilesel = FileSelection(self.fv.w.root.get_widget(),
                                      all_at_once=True)

        # Populate table
        self.redo()

    def redo(self):
        """Image or coordinates have changed. Clear and redraw."""
        if not self.gui_up:
            return

        self.clear_marking()
        self.tree_dict = Bunch.caselessDict()
        self.treeviewbad.clear()
        bad_tree_dict = Bunch.caselessDict()
        nbad = 0
        self._xarr = []
        self._yarr = []
        self._treepaths = []

        image = self.fitsimage.get_image()
        if image is None:
            return

        if not hasattr(image, 'radectopix'):
            self.logger.error(
                'Image as no radectopix() method for coordinates conversion')
            return

        objlist = []
        seqno = 1
        max_x = image.width - 1
        max_y = image.height - 1

        for key, coords in self.coords_dict.items():
            if len(coords) == 0:
                continue

            marktype, marksize, markcolor = key
            kstr = ','.join(map(str, key))
            sub_dict = {}
            bad_sub_dict = {}
            self.tree_dict[kstr] = sub_dict
            bad_tree_dict[kstr] = bad_sub_dict

            for args in coords:
                ra, dec, x, y = args[:4]

                # Use X and Y positions directly. Convert to RA and DEC (deg).
                if ra is None or dec is None:
                    ra, dec = image.pixtoradec(x, y)

                # RA and DEC already in degrees. Convert to pixel X and Y.
                else:
                    x, y = image.radectopix(ra, dec)

                # Display original X/Y (can be 0- or 1-indexed) using
                # our internal 0-indexed values.
                xdisp = x + self.pixelstart
                ydisp = y + self.pixelstart

                seqstr = '{0:04d}'.format(seqno)  # Prepend 0s for proper sort
                bnch = Bunch.Bunch(zip(self.extra_columns, args[4:]))  # Extra
                bnch.update(Bunch.Bunch(MARKID=seqstr, RA=ra, DEC=dec,
                                        X=xdisp, Y=ydisp))

                # Do not draw out of bounds
                if (not np.isfinite(x) or x < 0 or x > max_x or
                        not np.isfinite(y) or y < 0 or y > max_y):
                    self.logger.debug('Ignoring RA={0}, DEC={1} '
                                      '(x={2}, y={3})'.format(ra, dec, x, y))
                    bad_sub_dict[seqstr] = bnch
                    nbad += 1

                # Display point
                else:
                    obj = self._get_markobj(
                        x, y, marktype, marksize, markcolor, self.markwidth)
                    objlist.append(obj)

                    sub_dict[seqstr] = bnch
                    self._xarr.append(x)
                    self._yarr.append(y)
                    self._treepaths.append((kstr, seqstr))

                seqno += 1

        n_obj = len(objlist)
        self.logger.debug('Displaying {0} markings'.format(n_obj))

        if nbad > 0:
            self.treeviewbad.set_tree(bad_tree_dict)

        if n_obj == 0:
            return

        # Convert to Numpy arrays to avoid looping later
        self._xarr = np.array(self._xarr)
        self._yarr = np.array(self._yarr)
        self._treepaths = np.array(self._treepaths)

        # Display info table
        self.recreate_toc()

        # Draw on canvas
        self.marktag = self.canvas.add(self.dc.CompoundObject(*objlist))
        self.fitsimage.redraw()  # Force immediate redraw

    def _get_markobj(self, x, y, marktype, marksize, markcolor, markwidth):
        """Generate canvas object for given mark parameters."""
        if marktype == 'circle':
            obj = self.dc.Circle(
                x=x, y=y, radius=marksize, color=markcolor, linewidth=markwidth)
        elif marktype in ('cross', 'plus'):
            obj = self.dc.Point(
                x=x, y=y, radius=marksize, color=markcolor, linewidth=markwidth,
                style=marktype)
        elif marktype == 'box':
            obj = self.dc.Box(
                x=x, y=y, xradius=marksize, yradius=marksize, color=markcolor,
                linewidth=markwidth)
        else:  # point, marksize
            obj = self.dc.Box(
                x=x, y=y, xradius=1, yradius=1, color=markcolor,
                linewidth=markwidth, fill=True, fillcolor=markcolor)

        return obj

    def clear_marking(self):
        """Clear marking from image.
        This does not clear loaded coordinates from memory."""
        if self.marktag:
            try:
                self.canvas.delete_object_by_tag(self.marktag, redraw=False)
            except Exception:
                pass

        if self.markhltag:
            try:
                self.canvas.delete_object_by_tag(self.markhltag, redraw=False)
            except Exception:
                pass

        self.treeview.clear()  # Clear table too
        self.w.nshown.set_text('0')
        self.fitsimage.redraw()  # Force immediate redraw

    def forget_coords(self):
        """Forget all loaded coordinates."""
        self.w.ntotal.set_text('0')
        self.coords_dict.clear()
        self.redo()

    # TODO: Support more formats?
    def load_file(self, filename):
        """Load coordinates file.

        Results are appended to previously loaded coordinates.
        This can be used to load one file per color.

        """
        if not os.path.isfile(filename):
            return

        self.logger.info('Loading coordinates from {0}'.format(filename))

        if filename.endswith('.fits'):
            fmt = 'fits'
        else:  # Assume ASCII
            fmt = 'ascii'

        try:
            tab = Table.read(filename, format=fmt)
        except Exception as e:
            self.logger.error('{0}: {1}'.format(e.__class__.__name__, str(e)))
            return

        if self.use_radec:
            colname0 = self.settings.get('ra_colname', 'ra')
            colname1 = self.settings.get('dec_colname', 'dec')
        else:
            colname0 = self.settings.get('x_colname', 'x')
            colname1 = self.settings.get('y_colname', 'y')

        try:
            col_0 = tab[colname0]
            col_1 = tab[colname1]
        except Exception as e:
            self.logger.error('{0}: {1}'.format(e.__class__.__name__, str(e)))
            return

        nrows = len(col_0)
        dummy_col = [None] * nrows

        try:
            oldrows = int(self.w.ntotal.get_text())
        except ValueError:
            oldrows = 0

        self.w.ntotal.set_text(str(oldrows + nrows))

        if self.use_radec:
            ra = self._convert_radec(col_0)
            dec = self._convert_radec(col_1)
            x = y = dummy_col
        else:
            ra = dec = dummy_col

            # X and Y always 0-indexed internally
            x = col_0.data - self.pixelstart
            y = col_1.data - self.pixelstart

        args = [ra, dec, x, y]

        # Load extra columns
        for colname in self.extra_columns:
            try:
                col = tab[colname].data
            except Exception as e:
                self.logger.error(
                    '{0}: {1}'.format(e.__class__.__name__, str(e)))
                col = dummy_col

            args.append(col)

        # Use list to preserve order. Does not handle duplicates.
        key = (self.marktype, self.marksize, self.markcolor)
        self.coords_dict[key] += list(zip(*args))

        self.redo()

    def load_files(self, filenames):
        """Load coordinates files.

        Results are appended to previously loaded coordinates.
        This can be used to load one file per color.

        """
        for filename in filenames:
            self.load_file(filename)

    def _convert_radec(self, val):
        """Convert RA or DEC table column to degrees and extract data.
        Assume already in degrees if cannot convert.

        """
        try:
            ans = val.to('deg')
        except Exception as e:
            self.logger.error('Cannot convert, assume already in degrees')
            ans = val.data
        else:
            ans = ans.value

        return ans

    # TODO: Support more extensions?
    def load_coords_cb(self):
        """Activate file dialog to select coordinates file."""
        self.cfilesel.popup('Load coordinates file', self.load_files,
                            initialdir='.',
                            filename='Table files (*.txt *.dat *.fits)')

    def set_coordtype_cb(self, w, val):
        """Toggle between RA/DEC or X/Y coordinates."""
        self.use_radec = val

    def recreate_toc(self):
        self.logger.debug('Recreating table of contents...')
        self.treeview.set_tree(self.tree_dict)
        n = 0

        for sub_dict in self.tree_dict.values():
            n += len(sub_dict)

        self.w.nshown.set_text(str(n))

    def hl_table2canvas(self, w, res_dict):
        """Highlight marking on canvas when user click on table."""
        objlist = []
        width = self.markwidth + self._dwidth

        # Remove existing highlight
        if self.markhltag:
            try:
                self.canvas.delete_object_by_tag(self.markhltag, redraw=False)
            except Exception:
                pass

        # Display highlighted entries only in second table
        self.treeviewsel.set_tree(res_dict)

        for kstr, sub_dict in res_dict.items():
            s = kstr.split(',')
            marktype = s[0]
            marksize = float(s[1])
            markcolor = s[2]

            for bnch in sub_dict.values():
                obj = self._get_markobj(bnch.X - self.pixelstart,
                                        bnch.Y - self.pixelstart,
                                        marktype, marksize, markcolor, width)
                objlist.append(obj)

        nsel = len(objlist)
        self.w.nselected.set_text(str(nsel))

        # Draw on canvas
        if nsel > 0:
            self.markhltag = self.canvas.add(self.dc.CompoundObject(*objlist))

        self.fitsimage.redraw()  # Force immediate redraw

    def hl_canvas2table_box(self, canvas, tag):
        """Highlight all markings inside user drawn box on table."""
        self.treeview.clear_selection()

        # Remove existing box
        cobj = canvas.get_object_by_tag(tag)
        if cobj.kind != 'rectangle':
            return
        canvas.delete_object_by_tag(tag, redraw=False)

        # Remove existing highlight
        if self.markhltag:
            try:
                canvas.delete_object_by_tag(self.markhltag, redraw=True)
            except Exception:
                pass

        # Nothing to do if no markings are displayed
        try:
            obj = canvas.get_object_by_tag(self.marktag)
        except Exception:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if (len(self._xarr) == 0 or len(self._yarr) == 0 or
                len(self._treepaths) == 0):
            return

        # Find markings inside box
        mask = cobj.contains_arr(self._xarr, self._yarr)

        for hlpath in self._treepaths[mask]:
            self._highlight_path(hlpath)

    # NOTE: This does not work anymore when left click is used to draw box.
    def hl_canvas2table(self, canvas, button, data_x, data_y):
        """Highlight marking on table when user click on canvas."""
        self.treeview.clear_selection()

        # Remove existing highlight
        if self.markhltag:
            try:
                canvas.delete_object_by_tag(self.markhltag, redraw=True)
            except Exception:
                pass

        # Nothing to do if no markings are displayed
        try:
            obj = canvas.get_object_by_tag(self.marktag)
        except Exception:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if (len(self._xarr) == 0 or len(self._yarr) == 0 or
                len(self._treepaths) == 0):
            return

        sr = 10  # self.settings.get('searchradius', 10)
        dx = data_x - self._xarr
        dy = data_y - self._yarr
        dr = np.sqrt(dx * dx + dy * dy)
        mask = dr <= sr

        for hlpath in self._treepaths[mask]:
            self._highlight_path(hlpath)

    def _highlight_path(self, hlpath):
        """Highlight an entry in the table and associated marking."""
        self.logger.debug('Highlighting {0}'.format(hlpath))
        self.treeview.select_path(hlpath)

        # TODO: Does not work in Qt. This is known issue in Ginga.
        self.treeview.scroll_to_path(hlpath)

    def set_marktype_cb(self, w, index):
        """Set type of marking."""
        self.marktype = self._mark_options[index]

        # Mark size is not used for point
        if self.marktype != 'point':
            self.w.mark_size.set_enabled(True)
        else:
            self.w.mark_size.set_enabled(False)

    def set_markcolor_cb(self, w, index):
        """Set color of marking."""
        self.markcolor = self._color_options[index]

    def set_marksize(self):
        """Set size/radius of marking."""
        try:
            sz = float(self.w.mark_size.get_text())
        except ValueError:
            self.logger.error('Cannot set mark size')
            self.w.mark_size.set_text(str(self.marksize))
        else:
            self.marksize = sz

    def set_markwidth(self):
        """Set width of marking."""
        try:
            sz = int(self.w.mark_width.get_text())
        except ValueError:
            self.logger.error('Cannot set mark width')
            self.w.mark_width.set_text(str(self.markwidth))
        else:
            self.markwidth = sz

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

    def start(self):
        # insert canvas, if not already
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.get_object_by_tag(self.layertag)
        except KeyError:
            # Add drawing layer
            p_canvas.add(self.canvas, tag=self.layertag)

        self.resume()

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

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

        self.canvas.ui_set_active(True)
        self.fv.show_status('Press "Help" for instructions')

    def stop(self):
        # remove canvas from image
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.delete_object_by_tag(self.layertag)
        except Exception:
            pass

        # Free some memory, maybe
        self.tree_dict = Bunch.caselessDict()
        self._xarr = []
        self._yarr = []
        self._treepaths = []

        self.gui_up = False
        self.fv.show_status('')

    def __str__(self):
        """
        This method should be provided and should return the lower case
        name of the plugin.
        """
        return 'tvmark'
Exemplo n.º 5
0
class ProgramsTab(QueueFileTab.QueueFileTab):
    def __init__(self, controller):
        super(ProgramsTab, self).__init__(controller)

        # Register a callback function for when the QueueModel loads
        # the programs file
        self.model.add_callback('programs-file-loaded', self.populate_cb)
        # Register a callback function for when the user updates the
        # Programs. The callback will enable the "Save" item so that
        # the user can save the programs to the output file.
        self.model.add_callback('programs-updated', self.enable_save_item_cb)

    def build_gui(self, container):
        super(ProgramsTab, self).build_gui(container)

        self.tableview.doubleClicked.connect(self.doubleClicked)

        load_plan = self.filemenu.add_name('Load Plan')
        load_plan.set_enabled(True)
        load_plan.add_callback('activated', self.load_plan_cb)

        save_plan = self.filemenu.add_name('Save As Plan')
        save_plan.set_enabled(True)
        save_plan.add_callback('activated', self.save_plan_cb)

        self.file_sel = FileSelection(container.get_widget())

    def build_table(self):
        super(ProgramsTab, self).build_table('ProgramsTab', 'TableModel')

    def doubleClicked(self, index):
        # When a user double-clicks on a program in the first column,
        # send that information to the QueueModel so the observing
        # blocks from that program can be displayed in the ObsBlock
        # tab.
        row, col = index.row(), index.column()
        if self.columnNames[col].lower() == 'proposal':
            proposal = self.dataForTableModel[row][col]
            if proposal not in self.model.ob_qf_dict:
                # We haven't read in this program file. Get the
                # ControlPanel plugin so that we can call the
                # load_program method to read in the program file.
                control_panel_plugin = self.view.get_plugin('cp')
                control_panel_plugin.load_program(proposal)

            # Set the QueueModel.proposalForPropTab attribute so
            # that the ProposalTab object can get that value and
            # know which proposal it should display.
            self.model.setProposalForPropTab(proposal)
            self.view.gui_do(self.createTab)

    def createTab(self):
        # If we have already created (and possibly closed) this
        # proposal tab before, just reload it. Otherwise, we have to
        # create it from scratch.
        proposal = self.model.proposalForPropTab
        self.logger.info('Creating tab for proposal %s' % proposal)
        if self.view.gpmon.has_plugin(proposal):
            self.view.reload_plugin(proposal)
        else:
            spec = Bunch(module='ProposalTab',
                         klass='ProposalTab',
                         ws='report',
                         tab=proposal,
                         name=proposal,
                         start=False,
                         ptype='global',
                         hidden=True)
            self.view.load_plugin(proposal, spec)

        self.view.start_plugin(proposal)

        # Raise the tab we just created
        self.view.ds.raise_tab(proposal)

    def save_plan_cb(self, w):
        try:
            if self.inputData is None:
                raise ValueError("No table data defined yet")

            w = Widgets.SaveDialog(title="Save Plan As",
                                   selectedfilter="*.yml")
            plan_file = w.get_path()
            if plan_file is None:
                # user cancelled dialog
                return

            # prepare a dict of the plan
            plan_dct = {
                d['proposal']: dict(qc_priority=d['qcp'], skip=d['skip'])
                for d in self.inputData.rows
            }
            plan_dct = dict(programs=plan_dct)

            with open(plan_file, 'w') as out_f:
                out_f.write(yaml.dump(plan_dct))

            self.logger.info(f"wrote plan {plan_file}")

        except Exception as e:
            errmsg = f"error writing plan file: {e}"
            self.logger.error(errmsg, exc_info=True)
            self.view.gui_do(self.view.show_error, errmsg, raisetab=True)

    def load_plan_cb(self, w):
        if self.inputData is None:
            self.logger.error("No table data defined yet")

        self.file_sel.popup("Load Plan", self.load_plan, filename="*.yml")

    def load_plan(self, plan_file):
        try:
            if self.inputData is None:
                raise ValueError("No table data defined yet")

            self.model.load_qc_plan(plan_file)

        except Exception as e:
            errmsg = f"error reading QC plan file: {e}"
            self.logger.error(errmsg, exc_info=True)
            self.view.gui_do(self.view.show_error, errmsg, raisetab=True)
Exemplo n.º 6
0
class ParamMixin(object):
    """Mixin class for Ginga local plugin that enables the feature to
    save/load parameters.

    """
    def build_param_gui(self, container):
        """Call this in ``build_gui()`` to create 'Load Param' and 'Save Param'
        buttons.

        Parameters
        ----------
        container : widget
            The widget to contain these buttons.

        """
        captions = (('Load Param', 'button', 'Save Param', 'button'), )
        w, b = Widgets.build_info(captions, orientation=self.orientation)
        self.w.update(b)

        b.load_param.set_tooltip('Load previously saved parameters')
        b.load_param.add_callback('activated', lambda w: self.load_params_cb())

        b.save_param.set_tooltip('Save {0} parameters'.format(str(self)))
        b.save_param.add_callback('activated', lambda w: self.save_params())

        container.add_widget(w, stretch=0)

        # Initialize file save dialog
        self.filesel = FileSelection(self.fv.w.root.get_widget())

    def params_dict(self):
        """Return current parameters as a dictionary."""
        raise NotImplementedError('To be implemented by Ginga local plugin')

    def save_params(self):
        """Save parameters to a JSON file."""
        pardict = self.params_dict()
        fname = Widgets.SaveDialog(title='Save parameters',
                                   selectedfilter='*.json').get_path()
        if fname is None:  # Cancel
            return
        if os.path.exists(fname):
            self.logger.warn('{0} will be overwritten'.format(fname))
        with open(fname, 'w') as fout:
            json.dump(pardict,
                      fout,
                      indent=4,
                      sort_keys=True,
                      cls=JsonCustomEncoder)
        self.logger.info('Parameters saved as {0}'.format(fname))

    def load_params_cb(self):
        """Allow user to select JSON file to load."""
        self.filesel.popup('Load JSON file',
                           self.load_params,
                           initialdir='.',
                           filename='JSON files (*.json)')

    def load_params(self, filename):
        """Load previously saved parameters from a JSON file."""
        if not os.path.isfile(filename):
            return True

        with open(filename) as fin:
            self.logger.info('{0} parameters loaded from {1}'.format(
                str(self), filename))
            pardict = json.load(fin)

        self.ingest_params(pardict)

    def ingest_params(self, pardict):
        """Ingest dictionary containing plugin parameters into plugin
        GUI and internal variables."""
        raise NotImplementedError('To be implemented by Ginga local plugin')
Exemplo n.º 7
0
class KeckMosaic(GingaPlugin.LocalPlugin):
    """
    MultiBars
    =========
    Plugin Type: Local
    ------------------
    Pending.

    Usage
    -----
    Pending.
    """
    def __init__(self, fv, fitsimage):
        super(KeckMosaic, self).__init__(fv, fitsimage)

    def build_gui(self, container):
        top = Widgets.VBox()
        top.set_border_width(4)

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

        fr = Widgets.Frame("Keck Mosaic")

        captions = (('Load a multi-extension FITS file, create a mosaic',
                     'label'), ('Load', 'button'))

        w, b = Widgets.build_info(captions, orientation=orientation)
        self.w.update(b)

        b.load.add_callback('activated', lambda w: self.load_cb())

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

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

        top.add_widget(sw, stretch=1)

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

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

        container.add_widget(top, stretch=1)

        self.mfilesel = FileSelection(self.fv.w.root.get_widget())

    def help(self):
        name = str(self).capitalize()
        self.fv.help_text(name, self.__doc__, text_kind='rst', trim_pfx=4)

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

    def start(self):
        pass

    def pause(self):
        pass

    def resume(self):
        pass

    def stop(self):
        pass

    def redo(self):
        pass

###BEGIN MAIN FUNCTION###

    def multimos(self, filename):
        hdulist = fits.open(filename)
        files = np.sum([
            1 for hdu in hdulist if
            type(hdu) in [fits.hdu.image.PrimaryHDU, fits.hdu.image.ImageHDU]
            and hdu.data is not None
        ])
        k = 0
        j = 0

        ra_deg = 0.5
        dec_deg = 0.5
        fov_deg = 0.2
        header = hdulist[1].header
        (rot_deg, cdelt1,
         cdelt2) = wcs.get_rotation_and_scale(header, skew_threshold=1)
        px_scale = math.fabs(cdelt1)
        cdbase = [np.sign(cdelt1), np.sign(cdelt2)]

        ###Sorting Images###
        imagesort = [[0 for x in range(4)] for y in range(files)]
        for i in range(1, files + 1):
            imagesort[i - 1][0] = hdulist[i]
            df = hdulist[i].header['DETSEC']
            cpos = df.index(':')
            epos = df.index(',')
            dflow = ''.join(df[1:cpos])
            dflow = int(dflow)
            dfhigh = ''.join(df[cpos + 1:epos])
            dfhigh = int(dfhigh)
            imagesort[i - 1][1] = dflow + dfhigh
            ncpos = df.index(':', cpos + 1)
            nepos = df.index(']')
            yflow = ''.join(df[epos + 1:ncpos])
            yflow = int(yflow)
            yhigh = ''.join(df[ncpos + 1:nepos])
            yhigh = int(yhigh)
            imagesort[i - 1][2] = yflow + yhigh
            ccd = hdulist[i].header['CCDNAME']
            imagesort[i - 1][3] = ccd

        sortedim = sorted(imagesort, key=lambda x: x[1])

        ###Putting images together###

        #2D#

        if sortedim[0][1] == sortedim[1][1]:
            for i in range(1, files + 1):
                dr = sortedim[i - 1][0].header['DATASEC']
                colpos = dr.index(':')
                endpos = dr.index(',')
                drlow = ''.join(dr[1:colpos])
                drlow = int(drlow)
                drhigh = ''.join(dr[colpos + 1:endpos])
                drhigh = int(drhigh)
                imdata = sortedim[i - 1][0].data[:, drlow:drhigh]
                gapxdim = np.size(imdata, 0)
                gapx = np.zeros((gapxdim, 30))
                gapx[:, :] = np.nan
                gapydim = np.size(imdata, 1) + 30
                gapy = np.zeros((20, gapydim))
                gapy[:, :] = np.nan
                fgapy = np.zeros((20, gapydim - 30))
                fgapy[:, :] = np.nan
                #image flips#
                df = sortedim[i - 1][0].header['DETSEC']
                cpos = df.index(':')
                epos = df.index(',')
                dflow = ''.join(df[1:cpos])
                dflow = int(dflow)
                dfhigh = ''.join(df[cpos + 1:epos])
                dfhigh = int(dfhigh)
                ncpos = df.index(':', cpos + 1)
                nepos = df.index(']')
                yflow = ''.join(df[epos + 1:ncpos])
                yflow = int(yflow)
                yhigh = ''.join(df[ncpos + 1:nepos])
                yhigh = int(yhigh)
                if dflow > dfhigh:
                    findatx = np.fliplr(imdata)
                else:
                    findatx = imdata

                if yflow > yhigh:
                    findat = np.flipud(findatx)
                else:
                    findat = findatx
                fm = files - 1
                if i != files:
                    if i != fm:
                        if sortedim[i - 1][3] != sortedim[i][3]:
                            findat = np.column_stack((findat, gapx))

                if i == 1:
                    vert = findat

                elif sortedim[i - 1][1] == sortedim[i - 2][1]:
                    if sortedim[i - 1][2] > sortedim[i - 2][2]:
                        if i == files:
                            vert = np.vstack((vert, fgapy))
                            vert = np.vstack((vert, findat))
                            if i == files:
                                final = np.column_stack((final, vert))
                        else:
                            vert = np.vstack((vert, gapy))
                            vert = np.vstack((vert, findat))
                            if i == files:
                                final = np.column_stack((final, vert))
                    else:
                        findat = np.vstack((findat, gapy))
                        vert = np.vstack((findat, vert))
                        if i == files:
                            final = np.column_stack((final, vert))

                else:
                    k = k + 1
                    if k == 1:
                        final = vert
                        vert = findat
                    else:
                        final = np.column_stack((final, vert))
                        vert = findat

        else:
            #1d#
            for i in range(1, files + 1):
                #data ranges#
                dr = sortedim[i - 1][0].header['DATASEC']
                colpos = dr.index(':')
                endpos = dr.index(',')
                drlow = ''.join(dr[1:colpos])
                drlow = int(drlow)
                drhigh = ''.join(dr[colpos + 1:endpos])
                drhigh = int(drhigh)
                imdata = sortedim[i - 1][0].data[:, drlow:drhigh]
                gapxdim = np.size(imdata, 0)
                gapx = np.zeros((gapxdim, 30))
                gapx[:, :] = np.nan
                #image flips#
                df = sortedim[i - 1][0].header['DETSEC']
                cpos = df.index(':')
                epos = df.index(',')
                dflow = ''.join(df[1:cpos])
                dflow = int(dflow)
                dfhigh = ''.join(df[cpos + 1:epos])
                dfhigh = int(dfhigh)
                ncpos = df.index(':', cpos + 1)
                nepos = df.index(']')
                yflow = ''.join(df[epos + 1:ncpos])
                yflow = int(yflow)
                yhigh = ''.join(df[ncpos + 1:nepos])
                yhigh = int(yhigh)

                if dflow > dfhigh:
                    findatx = np.fliplr(imdata)
                else:
                    findatx = imdata

                if yflow > yhigh:
                    findat = np.flipud(findatx)
                else:
                    findat = findatx
                #final image
                if i == 1:
                    final = findat
                else:
                    final = np.append(final, findat, axis=1)

                if i != files and sortedim[i - 1][3] != sortedim[i][3]:
                    final = np.append(final, gapx, axis=1)

        #finalimage#
        #plt.imshow(final)
        #plt.show()
        #finalimage = fits.ImageHDU()
        #finalimage.data = final
        oldim = dp.create_blank_image(ra_deg,
                                      dec_deg,
                                      fov_deg,
                                      px_scale,
                                      rot_deg,
                                      cdbase=cdbase,
                                      logger=None)
        finalimage = dp.make_image(final, oldim, {}, pfx='mosaic')
        name = dp.get_image_name(finalimage)
        self.fv.add_image(name, finalimage, chname='Mosaic')
        #finalimage.writeto('mosaic.fits', clobber=True)

###END MAIN FUNCTION###

#####LOADING FILE#######

    def load_cb(self):
        self.mfilesel.popup('Load fits file',
                            self.multimos,
                            initialdir='.',
                            filename='FITS files (*.fits)')

    def __str__(self):
        return 'keckmosaic'
Exemplo n.º 8
0
class TVMask(LocalPlugin):

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

        self.layertag = 'tvmask-canvas'
        self.masktag = None
        self.maskhltag = None

        self._color_options = self._short_color_list()

        # User preferences. Some are just default values and can also be
        # changed by GUI.
        prefs = self.fv.get_preferences()
        self.settings = prefs.create_category('plugin_TVMask')
        self.settings.add_defaults(maskcolor='green', maskalpha=0.5,
                                   hlcolor='white', hlalpha=1.0)
        self.settings.load(onError='silent')
        self.maskcolor = self.settings.get('maskcolor', 'green')
        self.maskalpha = self.settings.get('maskalpha', 0.5)
        self.hlcolor = self.settings.get('hlcolor', 'white')
        self.hlalpha = self.settings.get('hlalpha', 1.0)

        # Display coords info table
        self.treeview = None
        self.tree_dict = Bunch.caselessDict()
        self.columns = [('No.', 'ID'), ('Filename', 'MASKFILE')]

        # Store results
        self._seqno = 1
        self._maskobjs = []
        self._treepaths = []

        self.dc = self.fv.get_draw_classes()

        canvas = self.dc.DrawingCanvas()
        canvas.enable_draw(True)
        canvas.enable_edit(False)
        canvas.set_callback('draw-event', self.hl_canvas2table_box)
        #canvas.set_callback('cursor-down', self.hl_canvas2table)
        canvas.register_for_cursor_drawing(self.fitsimage)
        canvas.set_surface(self.fitsimage)
        canvas.set_drawtype('rectangle', color='green', linestyle='dash')
        self.canvas = canvas

        fv.add_callback('remove-image', lambda *args: self.redo())

        self.gui_up = False

    # If user complains about lack of choices (!!!), we can remove this.
    def _short_color_list(self):
        """Color list is too long. Discard variations with numbers."""
        return [c for c in colors.get_colors() if not re.search(r'\d', c)]

    def build_gui(self, container):
        vbox, sw, self.orientation = Widgets.get_oriented_box(container)

        captions = (('Color:', 'label', 'mask color', 'combobox'),
                    ('Alpha:', 'label', 'mask alpha', 'entry'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        combobox = b.mask_color
        for name in self._color_options:
            combobox.append_text(name)
        b.mask_color.set_index(self._color_options.index(self.maskcolor))
        b.mask_color.add_callback('activated', self.set_maskcolor_cb)

        b.mask_alpha.set_tooltip('Mask alpha (transparency)')
        b.mask_alpha.set_text(str(self.maskalpha))
        b.mask_alpha.add_callback('activated', lambda w: self.set_maskalpha())

        container.add_widget(w, stretch=0)

        treeview = Widgets.TreeView(auto_expand=True,
                                    sortable=True,
                                    selection='multiple',
                                    use_alt_row_color=True)
        self.treeview = treeview
        treeview.setup_table(self.columns, 2, 'ID')
        treeview.add_callback('selected', self.hl_table2canvas)
        container.add_widget(treeview, stretch=1)

        captions = (('Load Mask', 'button'),
                    ('Show', 'button', 'Hide', 'button', 'Forget', 'button'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        b.load_mask.set_tooltip('Load mask image')
        b.load_mask.add_callback('activated', lambda w: self.load_mask_cb())

        b.show.set_tooltip('Show masks')
        b.show.add_callback('activated', lambda w: self.redo())

        b.hide.set_tooltip('Hide masks')
        b.hide.add_callback('activated', lambda w: self.clear_mask())

        b.forget.set_tooltip('Forget masks')
        b.forget.add_callback('activated', lambda w: self.forget_masks())

        container.add_widget(w, stretch=0)

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

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

        container.add_widget(btns, stretch=0)

        self.gui_up = True

        # Initialize mask file selection dialog
        self.mfilesel = FileSelection(self.fv.w.root.get_widget())

        # Populate table
        self.redo()

    def redo(self):
        """Image or masks have changed. Clear and redraw."""
        if not self.gui_up:
            return

        self.clear_mask()

        image = self.fitsimage.get_image()
        if image is None:
            return

        n_obj = len(self._maskobjs)
        self.logger.debug('Displaying {0} masks'.format(n_obj))
        if n_obj == 0:
            return

        # Display info table
        self.recreate_toc()

        # Draw on canvas
        self.masktag = self.canvas.add(self.dc.CompoundObject(*self._maskobjs))
        self.fitsimage.redraw()  # Force immediate redraw

    def clear_mask(self):
        """Clear mask from image.
        This does not clear loaded masks from memory."""
        if self.masktag:
            try:
                self.canvas.delete_object_by_tag(self.masktag, redraw=False)
            except Exception:
                pass

        if self.maskhltag:
            try:
                self.canvas.delete_object_by_tag(self.maskhltag, redraw=False)
            except Exception:
                pass

        self.treeview.clear()  # Clear table too
        self.fitsimage.redraw()  # Force immediate redraw

    def forget_masks(self):
        """Forget all loaded coordinates."""
        self._seqno = 1
        self._maskobjs = []
        self._treepaths = []
        self.tree_dict = Bunch.caselessDict()
        self.redo()

    # TODO: Support more formats?
    def load_file(self, filename):
        """Load mask image.

        Results are appended to previously loaded masks.
        This can be used to load mask per color.

        """
        if not os.path.isfile(filename):
            return

        self.logger.info('Loading mask image from {0}'.format(filename))

        try:
            # 0=False, everything else True
            dat = fits.getdata(filename).astype(np.bool)
        except Exception as e:
            self.logger.error('{0}: {1}'.format(e.__class__.__name__, str(e)))
            return

        key = '{0},{1}'.format(self.maskcolor, self.maskalpha)

        if key in self.tree_dict:
            sub_dict = self.tree_dict[key]
        else:
            sub_dict = {}
            self.tree_dict[key] = sub_dict

        # Add to listing
        seqstr = '{0:04d}'.format(self._seqno)  # Prepend 0s for proper sort
        sub_dict[seqstr] = Bunch.Bunch(ID=seqstr,
                                       MASKFILE=os.path.basename(filename))
        self._treepaths.append((key, seqstr))
        self._seqno += 1

        # Create mask layer
        obj = self.dc.Image(0, 0, masktorgb(
            dat, color=self.maskcolor, alpha=self.maskalpha))
        self._maskobjs.append(obj)

        self.redo()

    def load_mask_cb(self):
        """Activate file dialog to select mask image."""
        self.mfilesel.popup('Load mask image', self.load_file,
                            initialdir='.', filename='FITS files (*.fits)')

    def recreate_toc(self):
        self.logger.debug('Recreating table of contents...')
        self.treeview.set_tree(self.tree_dict)

    def _rgbtomask(self, obj):
        """Convert RGB arrays from mask canvas object back to boolean mask."""
        dat = obj.get_image().get_data()  # RGB arrays
        return dat.sum(axis=2).astype(np.bool)  # Convert to 2D mask

    def hl_table2canvas(self, w, res_dict):
        """Highlight mask on canvas when user click on table."""
        objlist = []

        # Remove existing highlight
        if self.maskhltag:
            try:
                self.canvas.delete_object_by_tag(self.maskhltag, redraw=False)
            except Exception:
                pass

        for sub_dict in res_dict.values():
            for seqno in sub_dict:
                mobj = self._maskobjs[int(seqno) - 1]
                dat = self._rgbtomask(mobj)
                obj = self.dc.Image(0, 0, masktorgb(
                    dat, color=self.hlcolor, alpha=self.hlalpha))
                objlist.append(obj)

        # Draw on canvas
        if len(objlist) > 0:
            self.maskhltag = self.canvas.add(self.dc.CompoundObject(*objlist))

        self.fitsimage.redraw()  # Force immediate redraw

    def hl_canvas2table_box(self, canvas, tag):
        """Highlight all masks inside user drawn box on table."""
        self.treeview.clear_selection()

        # Remove existing box
        cobj = canvas.get_object_by_tag(tag)
        if cobj.kind != 'rectangle':
            return
        canvas.delete_object_by_tag(tag, redraw=False)

        # Remove existing highlight
        if self.maskhltag:
            try:
                canvas.delete_object_by_tag(self.maskhltag, redraw=True)
            except Exception:
                pass

        # Nothing to do if no masks are displayed
        try:
            obj = canvas.get_object_by_tag(self.masktag)
        except Exception:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if len(self._maskobjs) == 0:
            return

        # Find masks that intersect the rectangle
        for i, mobj in enumerate(self._maskobjs):
            # The actual mask
            mask1 = self._rgbtomask(mobj)

            # The selected area
            rgbimage = mobj.get_image()
            mask2 = rgbimage.get_shape_mask(cobj)

            # Highlight mask with intersect
            if np.any(mask1 & mask2):
                self._highlight_path(self._treepaths[i])

    # NOTE: This does not work anymore when left click is used to draw box.
    def hl_canvas2table(self, canvas, button, data_x, data_y):
        """Highlight mask on table when user click on canvas."""
        self.treeview.clear_selection()

        # Remove existing highlight
        if self.maskhltag:
            try:
                canvas.delete_object_by_tag(self.maskhltag, redraw=True)
            except Exception:
                pass

        # Nothing to do if no masks are displayed
        try:
            obj = canvas.get_object_by_tag(self.masktag)
        except Exception:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if len(self._maskobjs) == 0:
            return

        for i, mobj in enumerate(self._maskobjs):
            mask1 = self._rgbtomask(mobj)

            # Highlight mask covering selected cursor position
            if mask1[int(data_y), int(data_x)]:
                self._highlight_path(self._treepaths[i])

    def _highlight_path(self, hlpath):
        """Highlight an entry in the table and associated mask."""
        self.logger.debug('Highlighting {0}'.format(hlpath))
        self.treeview.select_path(hlpath)

        # TODO: Does not work in Qt. This is known issue in Ginga.
        self.treeview.scroll_to_path(hlpath)

    def set_maskcolor_cb(self, w, index):
        """Set color of mask."""
        self.maskcolor = self._color_options[index]

    def set_maskalpha(self):
        """Set alpha (transparency) of mask."""
        try:
            a = float(self.w.mask_alpha.get_text())
        except ValueError:
            self.logger.error('Cannot set mask alpha')
            self.w.mask_alpha.set_text(str(self.maskalpha))
            return

        if a < 0 or a > 1:
            self.logger.error('Alpha must be between 0 and 1, inclusive')
            self.w.mask_alpha.set_text(str(self.maskalpha))
            return

        self.maskalpha = a

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

    def start(self):
        # insert canvas, if not already
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.get_object_by_tag(self.layertag)
        except KeyError:
            # Add drawing layer
            p_canvas.add(self.canvas, tag=self.layertag)

        self.resume()

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

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

        self.canvas.ui_set_active(True)
        self.fv.show_status('Press "Help" for instructions')

    def stop(self):
        # remove canvas from image
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.delete_object_by_tag(self.layertag)
        except Exception:
            pass

        self.canvas.update_canvas(whence=0)  # Force redraw
        self.gui_up = False
        self.fv.show_status('')

    def __str__(self):
        """
        This method should be provided and should return the lower case
        name of the plugin.
        """
        return 'tvmask'
Exemplo n.º 9
0
class TVMask(LocalPlugin):
    """
    TVMask
    ======
    Display masks from file (non-interative mode) on an image.

    Plugin Type: Local
    ------------------
    TVMask is a local plugin, which means it is associated with a
    channel.  An instance can be opened for each channel.

    Usage
    -----
    This plugin allows non-interactive display of mask by reading in a FITS
    file, where non-zero is assumed to be masked data.

    To display different masks (e.g., some masked as green and some as pink, as
    shown above):

    1. Select green from the drop-down menu. Alternately, enter desired alpha value.
    2. Using "Load Mask" button, load the relevant FITS file.
    3. Repeat Step 1 but now select pink from the drop-down menu.
    4. Repeat Step 2 but choose another FITS file.
    5. To display a third mask as pink too, repeat Step 4 without changing the
       drop-down menu.

    Selecting an entry (or multiple entries) from the table listing will
    highlight the mask(s) on the image. The highlight uses a pre-defined color and
    alpha (customizable below). Clicking on a masked pixel will highlight the
    mask(s) both on the image and the table listing.

    You can also highlight all the masks within a region both on the image
    and the table listing by drawing a rectangle on the image using the right mouse
    button while this plugin is active.

    Pressing the "Hide" button will hide the masks but does not clear the
    plugin's memory; That is, when you press "Show", the same masks will
    reappear on the same image. However, pressing "Forget" will clear the masks
    both from display and memory; That is, you will need to reload your file(s) to
    recreate the masks.

    To redraw the same masks with different color or alpha, press "Forget"
    and repeat the steps above, as necessary.

    If images of very different pointings/dimensions are displayed in the same
    channel, masks that belong to one image but fall outside another will not
    appear in the latter.

    To create a mask that this plugin can read, one can use results from
    the `Drawing` plugin (press "Create Mask" after drawing and save the
    mask using `SaveImage`), in addition to creating a FITS
    file by hand using `astropy.io.fits`, etc.

    Used together with `TVMark`, you can overlay both point sources and
    masked regions in Ginga.
    """
    def __init__(self, fv, fitsimage):
        # superclass defines some variables for us, like logger
        super(TVMask, self).__init__(fv, fitsimage)

        self.layertag = 'tvmask-canvas'
        self.masktag = None
        self.maskhltag = None

        self._color_options = self._short_color_list()

        # User preferences. Some are just default values and can also be
        # changed by GUI.
        prefs = self.fv.get_preferences()
        self.settings = prefs.create_category('plugin_TVMask')
        self.settings.load(onError='silent')
        self.maskcolor = self.settings.get('maskcolor', 'green')
        self.maskalpha = self.settings.get('maskalpha', 0.5)
        self.hlcolor = self.settings.get('hlcolor', 'white')
        self.hlalpha = self.settings.get('hlalpha', 1.0)

        # Display coords info table
        self.treeview = None
        self.tree_dict = Bunch.caselessDict()
        self.columns = [('No.', 'ID'), ('Filename', 'MASKFILE')]

        # Store results
        self._seqno = 1
        self._maskobjs = []
        self._treepaths = []

        self.dc = self.fv.get_draw_classes()

        canvas = self.dc.DrawingCanvas()
        canvas.enable_draw(True)
        canvas.enable_edit(False)
        canvas.set_callback('draw-event', self.hl_canvas2table_box)
        canvas.set_callback('cursor-down', self.hl_canvas2table)
        canvas.set_surface(self.fitsimage)
        canvas.set_drawtype('rectangle', color='green', linestyle='dash')
        self.canvas = canvas

        fv.add_callback('remove-image', lambda *args: self.redo())

        self.gui_up = False

    # If user complains about lack of choices (!!!), we can remove this.
    def _short_color_list(self):
        """Color list is too long. Discard variations with numbers."""
        return [c for c in colors.get_colors() if not re.search(r'\d', c)]

    def build_gui(self, container):
        vbox, sw, self.orientation = Widgets.get_oriented_box(container)

        captions = (('Color:', 'label', 'mask color', 'combobox'),
                    ('Alpha:', 'label', 'mask alpha', 'entry'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        combobox = b.mask_color
        for name in self._color_options:
            combobox.append_text(name)
        b.mask_color.set_index(self._color_options.index(self.maskcolor))
        b.mask_color.add_callback('activated', self.set_maskcolor_cb)

        b.mask_alpha.set_tooltip('Mask alpha (transparency)')
        b.mask_alpha.set_text(str(self.maskalpha))
        b.mask_alpha.add_callback('activated', lambda w: self.set_maskalpha())

        container.add_widget(w, stretch=0)

        treeview = Widgets.TreeView(auto_expand=True,
                                    sortable=True,
                                    selection='multiple',
                                    use_alt_row_color=True)
        self.treeview = treeview
        treeview.setup_table(self.columns, 2, 'ID')
        treeview.add_callback('selected', self.hl_table2canvas)
        container.add_widget(treeview, stretch=1)

        captions = (('Load Mask', 'button'),
                    ('Show', 'button', 'Hide', 'button', 'Forget', 'button'))
        w, b = Widgets.build_info(captions)
        self.w.update(b)

        b.load_mask.set_tooltip('Load mask image')
        b.load_mask.add_callback('activated', lambda w: self.load_mask_cb())

        b.show.set_tooltip('Show masks')
        b.show.add_callback('activated', lambda w: self.redo())

        b.hide.set_tooltip('Hide masks')
        b.hide.add_callback('activated', lambda w: self.clear_mask())

        b.forget.set_tooltip('Forget masks')
        b.forget.add_callback('activated', lambda w: self.forget_masks())

        container.add_widget(w, stretch=0)

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

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

        container.add_widget(btns, stretch=0)

        self.gui_up = True

        # Initialize mask file selection dialog
        self.mfilesel = FileSelection(self.fv.w.root.get_widget())

        # Populate table
        self.redo()

    def redo(self):
        """Image or masks have changed. Clear and redraw."""
        if not self.gui_up:
            return

        self.clear_mask()

        image = self.fitsimage.get_image()
        if image is None:
            return

        n_obj = len(self._maskobjs)
        self.logger.debug('Displaying {0} masks'.format(n_obj))
        if n_obj == 0:
            return

        # Display info table
        self.recreate_toc()

        # Draw on canvas
        self.masktag = self.canvas.add(self.dc.CompoundObject(*self._maskobjs))
        self.fitsimage.redraw()  # Force immediate redraw

    def clear_mask(self):
        """Clear mask from image.
        This does not clear loaded masks from memory."""
        if self.masktag:
            try:
                self.canvas.delete_object_by_tag(self.masktag, redraw=False)
            except:
                pass

        if self.maskhltag:
            try:
                self.canvas.delete_object_by_tag(self.maskhltag, redraw=False)
            except:
                pass

        self.treeview.clear()  # Clear table too
        self.fitsimage.redraw()  # Force immediate redraw

    def forget_masks(self):
        """Forget all loaded coordinates."""
        self._seqno = 1
        self._maskobjs = []
        self._treepaths = []
        self.tree_dict = Bunch.caselessDict()
        self.redo()

    # TODO: Support more formats?
    def load_file(self, filename):
        """Load mask image.

        Results are appended to previously loaded masks.
        This can be used to load mask per color.

        """
        if not os.path.isfile(filename):
            return

        self.logger.info('Loading mask image from {0}'.format(filename))

        try:
            # 0=False, everything else True
            dat = fits.getdata(filename).astype(np.bool)
        except Exception as e:
            self.logger.error('{0}: {1}'.format(e.__class__.__name__, str(e)))
            return

        key = '{0},{1}'.format(self.maskcolor, self.maskalpha)

        if key in self.tree_dict:
            sub_dict = self.tree_dict[key]
        else:
            sub_dict = {}
            self.tree_dict[key] = sub_dict

        # Add to listing
        seqstr = '{0:04d}'.format(self._seqno)  # Prepend 0s for proper sort
        sub_dict[seqstr] = Bunch.Bunch(ID=seqstr,
                                       MASKFILE=os.path.basename(filename))
        self._treepaths.append((key, seqstr))
        self._seqno += 1

        # Create mask layer
        obj = self.dc.Image(0, 0, masktorgb(
            dat, color=self.maskcolor, alpha=self.maskalpha))
        self._maskobjs.append(obj)

        self.redo()

    def load_mask_cb(self):
        """Activate file dialog to select mask image."""
        self.mfilesel.popup('Load mask image', self.load_file,
                            initialdir='.', filename='FITS files (*.fits)')

    def recreate_toc(self):
        self.logger.debug('Recreating table of contents...')
        self.treeview.set_tree(self.tree_dict)

    def _rgbtomask(self, obj):
        """Convert RGB arrays from mask canvas object back to boolean mask."""
        dat = obj.get_image().get_data()  # RGB arrays
        return dat.sum(axis=2).astype(np.bool)  # Convert to 2D mask

    def hl_table2canvas(self, w, res_dict):
        """Highlight mask on canvas when user click on table."""
        objlist = []

        # Remove existing highlight
        if self.maskhltag:
            try:
                self.canvas.delete_object_by_tag(self.maskhltag, redraw=False)
            except:
                pass

        for sub_dict in itervalues(res_dict):
            for seqno in sub_dict:
                mobj = self._maskobjs[int(seqno) - 1]
                dat = self._rgbtomask(mobj)
                obj = self.dc.Image(0, 0, masktorgb(
                    dat, color=self.hlcolor, alpha=self.hlalpha))
                objlist.append(obj)

        # Draw on canvas
        if len(objlist) > 0:
            self.maskhltag = self.canvas.add(self.dc.CompoundObject(*objlist))

        self.fitsimage.redraw()  # Force immediate redraw

    def hl_canvas2table_box(self, canvas, tag):
        """Highlight all masks inside user drawn box on table."""
        self.treeview.clear_selection()

        # Remove existing box
        cobj = canvas.get_object_by_tag(tag)
        if cobj.kind != 'rectangle':
            return
        canvas.delete_object_by_tag(tag, redraw=False)

        # Remove existing highlight
        if self.maskhltag:
            try:
                canvas.delete_object_by_tag(self.maskhltag, redraw=True)
            except:
                pass

        # Nothing to do if no masks are displayed
        try:
            obj = canvas.get_object_by_tag(self.masktag)
        except:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if len(self._maskobjs) == 0:
            return

        # Find masks that intersect the rectangle
        for i, mobj in enumerate(self._maskobjs):
            # The actual mask
            mask1 = self._rgbtomask(mobj)

            # The selected area
            rgbimage = mobj.get_image()
            mask2 = rgbimage.get_shape_mask(cobj)

            # Highlight mask with intersect
            if np.any(mask1 & mask2):
                self._highlight_path(self._treepaths[i])

    def hl_canvas2table(self, canvas, button, data_x, data_y):
        """Highlight mask on table when user click on canvas."""
        self.treeview.clear_selection()

        # Remove existing highlight
        if self.maskhltag:
            try:
                canvas.delete_object_by_tag(self.maskhltag, redraw=True)
            except:
                pass

        # Nothing to do if no masks are displayed
        try:
            obj = canvas.get_object_by_tag(self.masktag)
        except:
            return

        if obj.kind != 'compound':
            return

        # Nothing to do if table has no data
        if len(self._maskobjs) == 0:
            return

        for i, mobj in enumerate(self._maskobjs):
            mask1 = self._rgbtomask(mobj)

            # Highlight mask covering selected cursor position
            if mask1[data_y, data_x]:
                self._highlight_path(self._treepaths[i])

    def _highlight_path(self, hlpath):
        """Highlight an entry in the table and associated mask."""
        self.logger.debug('Highlighting {0}'.format(hlpath))
        self.treeview.select_path(hlpath)

        # TODO: Does not work in Qt. This is known issue in Ginga.
        self.treeview.scroll_to_path(hlpath)

    def set_maskcolor_cb(self, w, index):
        """Set color of mask."""
        self.maskcolor = self._color_options[index]

    def set_maskalpha(self):
        """Set alpha (transparency) of mask."""
        try:
            a = float(self.w.mask_alpha.get_text())
        except ValueError:
            self.logger.error('Cannot set mask alpha')
            self.w.mask_alpha.set_text(str(self.maskalpha))
            return

        if a < 0 or a > 1:
            self.logger.error('Alpha must be between 0 and 1, inclusive')
            self.w.mask_alpha.set_text(str(self.maskalpha))
            return

        self.maskalpha = a

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

    def help(self):
        name = str(self).capitalize()
        self.fv.help_text(name, self.__doc__, text_kind='rst', trim_pfx=4)

    def start(self):
        # insert canvas, if not already
        p_canvas = self.fitsimage.get_canvas()
        try:
            obj = p_canvas.get_object_by_tag(self.layertag)
        except KeyError:
            # Add drawing layer
            p_canvas.add(self.canvas, tag=self.layertag)

        self.resume()

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

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

        self.canvas.ui_set_active(True)
        self.fv.show_status('Press "Help" for instructions')

    def stop(self):
        # remove canvas from image
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.delete_object_by_tag(self.layertag)
        except:
            pass

        self.gui_up = False
        self.fv.show_status('')

    def __str__(self):
        """
        This method should be provided and should return the lower case
        name of the plugin.
        """
        return 'tvmask'
Exemplo n.º 10
0
class MultiBars(GingaPlugin.LocalPlugin):
    """
    MultiBars
    =========
    Plugin Type: Local
    ------------------
    TVMask is a local plugin, which means it is associated with a
    channel.  An instance can be opened for each channel.

    Usage
    -----
    This plugin adds cartoon bars with expected positions over 
    and image of the MOSFIRE CSU.  Click overlay to load your
    position file and have it overlay your image.    
    """

    def __init__(self, fv, fitsimage):
        super(MultiBars, self).__init__(fv, fitsimage)

        self.layertag = 'bars-canvas'

        self.dc = fv.get_draw_classes()
        canvas = self.dc.DrawingCanvas()
        canvas.enable_draw(False)
        canvas.set_surface(self.fitsimage)
        self.canvas = canvas

        self.colornames = colors.get_colors()
        self.canvas_img = None
        
        self.mfilesel = FileSelection(self.fv.w.root.get_widget())

    def build_gui(self, container):
        top = Widgets.VBox()
        top.set_border_width(4)

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

        fr = Widgets.Frame("Bar Input")

        captions = (('Overlay', 'button'),
            ('Clear', 'button'))
    
        w, b = Widgets.build_info(captions, orientation=orientation)
        self.w.update(b)

        b.overlay.add_callback('activated',
                   lambda w: self.load_cb())

        b.clear.add_callback('activated', lambda w: self.clear_canvas())
        fr.set_widget(w)
        vbox.add_widget(fr, stretch=0)

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

        top.add_widget(sw, stretch=1)

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

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

        container.add_widget(top, stretch=1)
    
    
    
    def help(self):
        name = str(self).capitalize()
        self.fv.help_text(name, self.__doc__, text_kind='rst', trim_pfx=4)

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

    def start(self):
        # start ruler drawing operation
        p_canvas = self.fitsimage.get_canvas()
        try:
            obj = p_canvas.get_object_by_tag(self.layertag)

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

        self.resume()

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

    def resume(self):
        self.canvas.ui_setActive(True)
        self.fv.show_status("Enter a value for bar length")

    def stop(self):
        self.arrsize = None
        self.rgbobj.set_data(self.rgbarr)

        # remove the canvas from the image
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.delete_object_by_tag(self.layertag)
        except:
            pass
        #self.canvas.ui_setActive(False)
        self.fv.show_status("")

    def redo(self):
        pass

####Main function#####
    def overlaybars(self, filename):
        bf = open(filename)
        lines = bf.readlines()
        for j in range(0, 46):
            start = 12
            height = (2044-8)/46
            y1 = start + height*j + 0.11/0.1798
            y2 = start + height*(j+1) - 0.11/0.1798
            cols1 = lines[(6*j)].split()
            cols2 = lines[(6*j)+3].split()
            x1 = (float(cols2[2])-8.34)/0.124
            x2 = (float(cols1[2])-8.34)/0.124
            self.canvas.add(self.dc.Rectangle(2044, int(np.floor(y2)), x1, int(np.ceil(y1))))
            self.canvas.add(self.dc.Rectangle(0, int(np.floor(y2)), x2, int(np.ceil(y1))))    
   
#####For loading file with popup#########
    def load_cb(self):
        self.mfilesel.popup('Load bar file', self.overlaybars,
                            initialdir='.', filename='txt files (*.txt)')

    def clear_canvas(self):
        self.canvas.delete_all_objects()

    def __str__(self):
        return 'MultiBars'
Exemplo n.º 11
0
class ParamMixin(object):
    """Mixin class for Ginga local plugin that enables the feature to
    save/load parameters.

    """
    def build_param_gui(self, container):
        """Call this in ``build_gui()`` to create 'Load Param' and 'Save Param'
        buttons.

        Parameters
        ----------
        container : widget
            The widget to contain these buttons.

        """
        captions = (('Load Param', 'button', 'Save Param', 'button'), )
        w, b = Widgets.build_info(captions, orientation=self.orientation)
        self.w.update(b)

        b.load_param.set_tooltip('Load previously saved parameters')
        b.load_param.add_callback(
            'activated', lambda w: self.load_params_cb())

        b.save_param.set_tooltip('Save {0} parameters'.format(str(self)))
        b.save_param.add_callback(
            'activated', lambda w: self.save_params())

        container.add_widget(w, stretch=0)

        # Initialize file save dialog
        self.filesel = FileSelection(self.fv.w.root.get_widget())

    def params_dict(self):
        """Return current parameters as a dictionary."""
        raise NotImplementedError('To be implemented by Ginga local plugin')

    def save_params(self):
        """Save parameters to a JSON file."""
        pardict = self.params_dict()
        fname = Widgets.SaveDialog(
            title='Save parameters', selectedfilter='*.json').get_path()
        if not fname:  # Cancel
            return
        if os.path.exists(fname):
            self.logger.warn('{0} will be overwritten'.format(fname))
        with open(fname, 'w') as fout:
            json.dump(pardict, fout, indent=4, sort_keys=True,
                      cls=JsonCustomEncoder)
        self.logger.info('Parameters saved as {0}'.format(fname))

    def load_params_cb(self):
        """Allow user to select JSON file to load."""
        self.filesel.popup('Load JSON file', self.load_params, initialdir='.',
                           filename='JSON files (*.json)')

    def load_params(self, filename):
        """Load previously saved parameters from a JSON file."""
        if not os.path.isfile(filename):
            return True

        with open(filename) as fin:
            self.logger.info('{0} parameters loaded from {1}'.format(
                str(self), filename))
            pardict = json.load(fin)

        self.ingest_params(pardict)

    def ingest_params(self, pardict):
        """Ingest dictionary containing plugin parameters into plugin
        GUI and internal variables."""
        raise NotImplementedError('To be implemented by Ginga local plugin')
Exemplo n.º 12
0
class NiresTest(GingaPlugin.LocalPlugin):
    def __init__(self, fv, fitsimage):
        # superclass defines some variables for us, like logger
        super(NiresTest, self).__init__(fv, fitsimage)

        self.niimage = None
        self.nisky = None
        self.sqcolor = 'green'
        self.layertag = 'nires-canvas'
        self.fwhm_plot = None
        self.iqcalc = iqcalc.IQCalc(self.logger)
        self.xclick = 0
        self.yclick = 0
        self.bside = 50
        self.dc = fv.get_draw_classes()
        canvas = self.dc.DrawingCanvas()
        canvas.name = 'nires-canvas'
        canvas.add_callback('cursor-down', self.btndown)
        canvas.set_surface(self.fitsimage)
        self.canvas = canvas

        self.sqbx = self.dc.Rectangle(0,
                                      0,
                                      self.bside,
                                      self.bside,
                                      color=self.sqcolor)

        self.canvas.add(self.sqbx, redraw=False)

    def build_gui(self, container):
        top = Widgets.VBox()
        top.set_border_width(4)

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

        paned = Widgets.Splitter(orientation=orientation)

        self.fwhm_plot = plots.FWHMPlot(logger=self.logger,
                                        width=400,
                                        height=400)

        if plots.MPL_GE_2_0:
            kwargs = {'facecolor': 'white'}
        else:
            kwargs = {'axisbg': 'white'}
        ax = self.fwhm_plot.add_axis(**kwargs)
        ax.grid(True)
        w = Plot.PlotWidget(self.fwhm_plot)
        w.resize(400, 400)
        paned.add_widget(Widgets.hadjust(w, orientation))

        captions = (("Load a Nires Image", 'label', "Load", 'button'),
                    ("Load an image and sky", 'label', "Load with sky",
                     'button'), ('Object_X', 'label', 'Object_X', 'llabel'),
                    ('Object_Y', 'label', 'Object_Y',
                     'llabel'), ('Box Size (50): ', 'label', 'Box Size',
                                 'entry', "Resize", 'button'))

        w, b = Widgets.build_info(captions, orientation=orientation)
        self.w.update(b)

        self.wdetail = b

        b.load.add_callback('activated', lambda w: self.load_cb())

        b.load_with_sky.add_callback('activated',
                                     lambda w: self.load_with_sky_cb())

        b.box_size.add_callback('activated', lambda w: self.boxsize_cb())

        b.resize.add_callback('activated', lambda w: self.resize_cb())

        fr = Widgets.Frame("Pick Target Star")
        fr.set_widget(w)
        box.add_widget(fr, stretch=0)
        paned.add_widget(sw)

        paned.set_sizes([400, 500])

        top.add_widget(paned, stretch=5)

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

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

        container.add_widget(top, stretch=1)
        self.mfilesel = FileSelection(self.fv.w.root.get_widget())

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

    def start(self):
        # start crosshair operation
        p_canvas = self.fitsimage.get_canvas()
        if not p_canvas.has_object(self.canvas):
            p_canvas.add(self.canvas, tag=self.layertag)

        self.resume()

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

    def resume(self):
        self.canvas.ui_set_active(True)
        self.fv.show_status("Click to print")

    def stop(self):
        #remove the canvas from the image
        p_canvas = self.fitsimage.get_canvas()
        try:
            p_canvas.delete_object_by_tag(self.layertag)
        except Exception:
            pass
        self.canvas.ui_set_active(False)
        self.fv.show_status("")

    def redo(self):
        pass

    def cutdetail(self, image, shape_obj):
        view, mask = image.get_shape_view(shape_obj)

        data = image._slice(view)

        y1, y2 = view[0].start, view[0].stop
        x1, x2 = view[1].start, view[1].stop

        # mask non-containing members
        mdata = np.ma.array(data, mask=np.logical_not(mask))
        return (x1, y1, x2, y2, mdata)

    def findstar(self):
        image = self.fitsimage.get_image()
        obj = self.sqbx
        shape = obj
        x1, y1, x2, y2, data = self.cutdetail(image, shape)
        ht, wd = data.shape[:2]
        xc, yc = wd // 2, ht // 2
        radius = min(xc, yc)
        peaks = [(xc, yc)]
        peaks = self.iqcalc.find_bright_peaks(data,
                                              threshold=None,
                                              radius=radius)

        xc, yc = peaks[0]
        xc += 1
        yc += 1
        return (xc, yc, radius, data)

    def movebox(self, viewer):
        self.sqbx.move_to(self.xclick, self.yclick)
        self.canvas.update_canvas(whence=3)
        image = self.fitsimage.get_image()
        x1, y1, x2, y2 = self.sqbx.get_llur()
        xc, yc, radius, boxdata = self.findstar()
        xc += x1
        yc += y1

        self.wdetail.object_x.set_text('%.3f' % (xc))
        self.wdetail.object_y.set_text('%.3f' % (yc))

        self.fwhm_plot.plot_fwhm(xc,
                                 yc,
                                 radius,
                                 image,
                                 cutout_data=boxdata,
                                 iqcalc=self.iqcalc,
                                 fwhm_method='gaussian')

    def btndown(self, canvas, event, data_x, data_y):
        self.xclick = data_x
        self.yclick = data_y
        self.movebox(self.fitsimage)

    def skysub(self):
        firstdat = self.niimage.get_data()
        seconddat = self.nisky.get_data()
        finaldat = firstdat - seconddat
        final_image = dp.make_image(finaldat,
                                    self.niimage, {},
                                    pfx='subtracted')
        name = dp.get_image_name(final_image)
        self.fv.add_image(name, final_image, chname='Image')

    def load_cb(self):
        self.mfilesel.popup('Image',
                            self.loadimage,
                            initialdir='.',
                            filename='fits files (*.fits)')

    def load_with_sky_cb(self):
        self.mfilesel.popup('Image',
                            self.loadimage,
                            initialdir='.',
                            filename='fits files (*.fits)')

        self.mfilesel.popup('Sky',
                            self.loadsky,
                            initialdir='.',
                            filename='fits files (*.fits)')

    def boxsize_cb(self):
        self.canvas.deleteAllObjects()
        self.bside = float(self.w.box_size.get_text())
        x1 = self.xclick - (self.bside / 2)
        x2 = self.xclick + (self.bside / 2)
        y1 = self.yclick - (self.bside / 2)
        y2 = self.yclick + (self.bside / 2)

        self.sqbx = self.dc.Rectangle(x1, y1, x2, y2, color=self.sqcolor)
        self.canvas.add(self.sqbx, redraw=True)
        self.canvas.update_canvas(whence=3)

    def resize_cb(self):
        self.boxsize_cb()

    def loadimage(self, filename):
        self.niimage = self.fv.load_file(filename, chname='image')
        print("loading...")
        return True

    def loadsky(self, filename):
        self.nisky = self.fv.load_file(filename, chname='Sky')
        self.skysub()
        return True

    def __str__(self):
        return 'nirestest'