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(f'Save {str(self)} parameters') 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 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 __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_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 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) 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("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())
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'
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()
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'
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()
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)
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()
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)
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'
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'
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')
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())
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'
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()
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'
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'
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))
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')
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'
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( ibar_num=1, mbar_num=1, ebar_num=1, move_to_open=False, bar_dest=0.0, bar_pos=137.0, ) self.settings.load(onError='silent') self.instrument_hosts = ['vm-mosfire', 'nuu', 'vm-mosfirebld'] self.hostname = socket.gethostname().split('.')[0].lower() self.bars_analysis = None self.state_analysis = None self.bars_file = None self.state_file = None self.bars_header = None self.state_header = None 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()) ## Fit relationship between bar position and pixels tick = dt.now() pixels, physical = self.get_data() self.fit_transforms(pixels, physical) tock = dt.now() elapsed = (tock - tick).total_seconds() # print('Completed fit of 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))