class PlotHandler(gui.handlers.BaseHandlers): def canvas_click(self, event): builder = self.state.builder toggle = builder.get_object('pick_location_toggle') if toggle.get_active(): print('%s click: button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % ('double' if event.dblclick else 'single', event.button, event.x, event.y, event.xdata, event.ydata)) lat = event.ydata lon = event.xdata lat_box = builder.get_object('lat_entry') lon_box = builder.get_object('lon_entry') lat_box.set_text(str(lat)) lon_box.set_text(str(lon)) toggle.set_active(False) def __init__(self, *args, **kwargs): super(PlotHandler, self).__init__(*args, **kwargs) with open('osmnx_pune_1km', 'rb') as f: self.graph = pickle.load(f) self.graph = osmnx.core.add_edge_lengths(self.graph) builder = self.state.builder sw = builder.get_object('GraphArea') nav_box_holder = builder.get_object('plot_box') self.fig, self.ax = osmnx.plot_graph(self.graph, show=False) self.ax.plot() self.canvas = FigureCanvas(self.fig) self.canvas.mpl_connect('button_release_event', self.canvas_click) window = builder.get_object('AppWin') toolbar = NavigationToolbar(self.canvas, window) nav_box_holder.pack_start(toolbar, False, True, 1) sw.add_with_viewport(self.canvas)
class GraphBase(object): """ A basic graph provider for using :py:mod:`matplotlib` to create graph representations of campaign data. This class is meant to be subclassed by real providers. """ name = 'Unknown' """The name of the graph provider.""" name_human = 'Unknown' """The human readable name of the graph provider used for UI identification.""" graph_title = 'Unknown' """The title that will be given to the graph.""" is_available = True def __init__(self, application, size_request=None, style_context=None): """ :param tuple size_request: The size to set for the canvas. """ self.application = application self.style_context = style_context self.config = application.config """A reference to the King Phisher client configuration.""" self.figure, _ = pyplot.subplots() self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE)) self.axes = self.figure.get_axes() self.canvas = FigureCanvas(self.figure) self.manager = None self.minimum_size = (380, 200) """An absolute minimum size for the canvas.""" if size_request is not None: self.resize(*size_request) self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed) self.canvas.show() self.navigation_toolbar = NavigationToolbar(self.canvas, self.application.get_active_window()) self.popup_menu = managers.MenuManager() self.popup_menu.append('Export', self.signal_activate_popup_menu_export) self.popup_menu.append('Refresh', self.signal_activate_popup_refresh) menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar') menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar) self._menu_item_show_toolbar = menu_item self.popup_menu.append_item(menu_item) self.navigation_toolbar.hide() self._legend = None @property def rpc(self): return self.application.rpc @staticmethod def _ax_hide_ticks(ax): for tick in ax.yaxis.get_major_ticks(): tick.tick1On = False tick.tick2On = False @staticmethod def _ax_set_spine_color(ax, spine_color): for pos in ('top', 'right', 'bottom', 'left'): ax.spines[pos].set_color(spine_color) def add_legend_patch(self, legend_rows, fontsize=None): if self._legend is not None: self._legend.remove() self._legend = None fontsize = fontsize or self.fontsize_scale legend_bbox = self.figure.legend( tuple(patches.Patch(color=patch_color) for patch_color, _ in legend_rows), tuple(label for _, label in legend_rows), borderaxespad=1.25, fontsize=fontsize, frameon=True, handlelength=1.5, handletextpad=0.75, labelspacing=0.3, loc='lower right' ) legend_bbox.legendPatch.set_linewidth(0) self._legend = legend_bbox def get_color(self, color_name, default): """ Get a color by its style name such as 'fg' for foreground. If the specified color does not exist, default will be returned. The underlying logic for this function is provided by :py:func:`~.gui_utilities.gtk_style_context_get_color`. :param str color_name: The style name of the color. :param default: The default color to return if the specified one was not found. :return: The desired color if it was found. :rtype: tuple """ color_name = 'theme_color_graph_' + color_name sc_color = gui_utilities.gtk_style_context_get_color(self.style_context, color_name, default) return (sc_color.red, sc_color.green, sc_color.blue) def make_window(self): """ Create a window from the figure manager. :return: The graph in a new, dedicated window. :rtype: :py:class:`Gtk.Window` """ if self.manager is None: self.manager = FigureManager(self.canvas, 0) self.navigation_toolbar.destroy() self.navigation_toolbar = self.manager.toolbar self._menu_item_show_toolbar.set_active(True) window = self.manager.window window.set_transient_for(self.application.get_active_window()) window.set_title(self.graph_title) return window @property def fontsize_scale(self): scale = self.markersize_scale if scale < 5: fontsize = 'xx-small' elif scale < 7: fontsize = 'x-small' elif scale < 9: fontsize = 'small' else: fontsize = 'medium' return fontsize @property def markersize_scale(self): bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted()) return bbox.width * self.figure.dpi * 0.01 def mpl_signal_canvas_button_pressed(self, event): if event.button != 3: return self.popup_menu.menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time()) return True def signal_activate_popup_menu_export(self, action): dialog = extras.FileChooserDialog('Export Graph', self.application.get_active_window()) file_name = self.config['campaign_name'] + '.png' response = dialog.run_quick_save(file_name) dialog.destroy() if not response: return destination_file = response['target_path'] self.figure.savefig(destination_file, dpi=200, facecolor=self.figure.get_facecolor(), format='png') def signal_activate_popup_refresh(self, event): self.refresh() def signal_toggled_popup_menu_show_toolbar(self, widget): if widget.get_property('active'): self.navigation_toolbar.show() else: self.navigation_toolbar.hide() def resize(self, width=0, height=0): """ Attempt to resize the canvas. Regardless of the parameters the canvas will never be resized to be smaller than :py:attr:`.minimum_size`. :param int width: The desired width of the canvas. :param int height: The desired height of the canvas. """ min_width, min_height = self.minimum_size width = max(width, min_width) height = max(height, min_height) self.canvas.set_size_request(width, height)
class Profile(Gtk.Window): def __init__(self): Gtk.Window.__init__(self, title="Profile") #f = Figure(figsize=(5,4), dpi=100) f = Figure() self.axes = f.add_subplot(111) self.axes.set_xlabel('Time (sec.)') self.axes.set_ylabel('Temperature (C)') self.axes.set_xlim(0, 5*60) self.axes.set_ylim(20, 300) self.axes.grid() self.axes.xaxis.set_major_formatter(FuncFormatter(self.format_xaxis)) self.x = [] self.y = [] self.plot, = self.axes.plot(self.x, self.y, 'o-', picker=5) self.minutes = False self.file_name = None self.canvas = FigureCanvas(f) self.canvas.set_size_request(800,600) self.canvas.mpl_connect('button_press_event', self.onclick) self.canvas.mpl_connect('button_release_event', self.onrelease) self.canvas.mpl_connect('pick_event', self.onpick) self.canvas.mpl_connect('motion_notify_event', self.onmotion) self.picking = None self.store = Gtk.ListStore(str, str) self.tree = Gtk.TreeView(self.store) renderer = Gtk.CellRendererText() renderer.set_property("editable", True) renderer.connect('edited', self.on_time_edited) column = Gtk.TreeViewColumn("Time", renderer, text=0) self.tree.append_column(column) renderer = Gtk.CellRendererText() renderer.set_property("editable", True) renderer.connect('edited', self.on_temp_edited) column = Gtk.TreeViewColumn("Temperature", renderer, text=1) self.tree.append_column(column) self.box = Gtk.Box() self.box.pack_start(self.canvas, False, False, 0) self.box.pack_start(self.tree, True, True, 0) action_group = Gtk.ActionGroup("profile_actions") action_group.add_actions([ ("FileMenu", None, "File", None, None, None), ("FileNew", Gtk.STOCK_NEW, "_New", "<control>N", None, self.on_file_new), ("FileOpen", Gtk.STOCK_OPEN, "_Open", "<control>O", None, self.on_file_open), ("FileSave", Gtk.STOCK_SAVE, "_Save", "<control>S", None, self.on_file_save), ("FileSaveAs", Gtk.STOCK_SAVE_AS, "Save _As…", "<shift><control>S", None, self.on_file_save_as), ("FileQuit", Gtk.STOCK_QUIT, "_Quit", "<control>Q", None, Gtk.main_quit) ]) uimanager = Gtk.UIManager() uimanager.add_ui_from_string(UI_INFO) accelgroup = uimanager.get_accel_group() self.add_accel_group(accelgroup) uimanager.insert_action_group(action_group) menubar = uimanager.get_widget("/MenuBar") self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.vbox.pack_start(menubar, False, False, 0) self.vbox.pack_start(self.box, True, True, 0) self.statusbar = Gtk.Statusbar() self.status_pos = self.statusbar.get_context_id("position") self.vbox.pack_start(self.statusbar, False, False, 0) self.add(self.vbox) def open_file(self, name): reader = csv.reader(open(name, 'rb')) x, y = zip(*reader) x = [ float(i) for i in x] y = [ float(i) for i in y] self.x, self.y = x, y self.file_name = name self.set_title('Profile - ' + name) self.update_data() self.update_scale() self.canvas.draw() def save_file(self, name): writer = csv.writer(open(name, 'wd')) writer.writerows(zip(self.x, self.y)) self.file_name = name self.set_title('Profile - ' + name) def on_file_new(self, widget): self.file_name = None self.set_title('Profile') self.x = [] self.y = [] self.store.clear() self.update_data() self.update_scale() self.canvas.draw() def on_file_open(self, widget): dialog = Gtk.FileChooserDialog("", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_current_folder_uri(Gio.file_new_for_path(os.curdir).get_uri()) response = dialog.run() if response == Gtk.ResponseType.OK: try: self.open_file(dialog.get_filename()) except: pass dialog.destroy() def on_file_save(self, widget): if self.file_name is None: self.on_file_save_as(widget) else: self.save_file(self.file_name) def on_file_save_as(self, widget): dialog = Gtk.FileChooserDialog("", self, Gtk.FileChooserAction.SAVE, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_SAVE_AS, Gtk.ResponseType.OK)) dialog.set_current_folder_uri(Gio.file_new_for_path(os.curdir).get_uri()) response = dialog.run() if response == Gtk.ResponseType.OK: self.save_file(dialog.get_filename()) dialog.destroy() def format_xaxis(self, x, pos): return self.format_x(x) def format_x(self, val): m, s = 0, val if self.minutes: m = int(val / 60) s = val % 60 if m: return '%dm%.1fs'%(m,s) else: return '%.1fs'%s def format_y(self, val): return '%.1f°C'%val def on_time_edited(self, widget, path, text): treeiter = self.store.get_iter(path) try: m, s = "0", "0" if 'm' in text: m, text = text.split('m') if 's' in text: s, text = text.split('s') else: text = "" assert len(text) == 0 val = int(m) * 60.0 + round(float(s), 1) except: return at = max(0, int(path)) if at != 0: val = max(val, self.x[at-1]) if at != len(self.x)-1: val = min(val, self.x[at+1]) self.store.set(treeiter, 0, self.format_x(val)) self.x[int(path)] = val self.update_data() self.update_scale() self.canvas.draw() def on_temp_edited(self, widget, path, text): treeiter = self.store.get_iter(path) try: if '°' in text: text, _ = text.split('°') if 'c' in text.lower(): text, _ = text.lower().split('c') val = round(float(text), 1) except: return val = min(300.0, val) val = max(20.0, val) self.store.set(treeiter, 1, self.format_y(val)) self.y[int(path)] = val self.update_data() self.canvas.draw() def update_data(self): self.plot.set_data(self.x, self.y) def update_scale(self): if len(self.x) and self.x[-1] + 30 > 5 * 60: minutes = int((self.x[-1] + 90) / 60) else: minutes = 5 self.axes.set_xlim(0, minutes*60) def onclick(self, event): if self.picking is not None or event.button != 1: return xdata = round(event.xdata, 1) ydata = round(event.ydata, 1) at = bisect.bisect(self.x, xdata) self.x.insert(at, xdata) self.y.insert(at, ydata) self.store.insert(at, [self.format_x(xdata), self.format_y(ydata)]) self.update_data() self.update_scale() self.canvas.draw() def onrelease(self, event): if self.picking is None: return self.update_scale() self.canvas.draw() self.picking = None def onpick(self, event): on = event.artist ind = event.ind[0] if event.mouseevent.button == 1: self.picking = ind elif event.mouseevent.button == 3: self.x.pop(ind) self.y.pop(ind) self.store.remove(self.store.get_iter(Gtk.TreePath(ind))) self.update_data() self.update_scale() self.canvas.draw() def onmotion(self, event): self.statusbar.remove_all(self.status_pos) if event.xdata is None or event.ydata is None: return self.statusbar.push(self.status_pos, self.format_x(event.xdata) + ', ' + self.format_y(event.ydata)) if self.picking is None: return xdata = max(0, round(event.xdata, 1)) ydata = round(event.ydata, 1) ydata = min(300.0, ydata) ydata = max(20.0, ydata) if self.picking != 0: xdata = max(xdata, self.x[self.picking-1]) if self.picking != len(self.x)-1: xdata = min(xdata, self.x[self.picking+1]) self.x[self.picking] = xdata self.y[self.picking] = ydata treeiter = self.store.get_iter(Gtk.TreePath(self.picking)) self.store.set(treeiter, 0, self.format_x(xdata), 1, self.format_y(ydata)) self.update_data() self.canvas.draw()
class GraphBase(object): """ A basic graph provider for using :py:mod:`matplotlib` to create graph representations of campaign data. This class is meant to be subclassed by real providers. """ name = 'Unknown' """The name of the graph provider.""" name_human = 'Unknown' """The human readable name of the graph provider used for UI identification.""" graph_title = 'Unknown' """The title that will be given to the graph.""" table_subscriptions = [] """A list of tables from which information is needed to produce the graph.""" is_available = True def __init__(self, application, size_request=None, style_context=None): """ :param tuple size_request: The size to set for the canvas. """ self.application = application self.style_context = style_context self.config = application.config """A reference to the King Phisher client configuration.""" self.figure, _ = pyplot.subplots() self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE)) self.axes = self.figure.get_axes() self.canvas = FigureCanvas(self.figure) self.manager = None self.minimum_size = (380, 200) """An absolute minimum size for the canvas.""" if size_request is not None: self.resize(*size_request) self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed) self.canvas.show() self.navigation_toolbar = NavigationToolbar( self.canvas, self.application.get_active_window()) self.popup_menu = Gtk.Menu.new() menu_item = Gtk.MenuItem.new_with_label('Export') menu_item.connect('activate', self.signal_activate_popup_menu_export) self.popup_menu.append(menu_item) menu_item = Gtk.MenuItem.new_with_label('Refresh') menu_item.connect('activate', self.signal_activate_popup_refresh) self.popup_menu.append(menu_item) menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar') menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar) self._menu_item_show_toolbar = menu_item self.popup_menu.append(menu_item) self.popup_menu.show_all() self.navigation_toolbar.hide() self._legend = None @property def rpc(self): return self.application.rpc @staticmethod def _ax_hide_ticks(ax): for tick in ax.yaxis.get_major_ticks(): tick.tick1On = False tick.tick2On = False @staticmethod def _ax_set_spine_color(ax, spine_color): for pos in ('top', 'right', 'bottom', 'left'): ax.spines[pos].set_color(spine_color) def add_legend_patch(self, legend_rows, fontsize=None): if self._legend is not None: self._legend.remove() self._legend = None fontsize = fontsize or self.fontsize_scale legend_bbox = self.figure.legend(tuple( patches.Patch(color=patch_color) for patch_color, _ in legend_rows), tuple(label for _, label in legend_rows), borderaxespad=1.25, fontsize=fontsize, frameon=True, handlelength=1.5, handletextpad=0.75, labelspacing=0.3, loc='lower right') legend_bbox.legendPatch.set_linewidth(0) self._legend = legend_bbox def get_color(self, color_name, default): """ Get a color by its style name such as 'fg' for foreground. If the specified color does not exist, default will be returned. The underlying logic for this function is provided by :py:func:`~.gui_utilities.gtk_style_context_get_color`. :param str color_name: The style name of the color. :param default: The default color to return if the specified one was not found. :return: The desired color if it was found. :rtype: tuple """ color_name = 'theme_color_graph_' + color_name sc_color = gui_utilities.gtk_style_context_get_color( self.style_context, color_name, default) return (sc_color.red, sc_color.green, sc_color.blue) def make_window(self): """ Create a window from the figure manager. :return: The graph in a new, dedicated window. :rtype: :py:class:`Gtk.Window` """ if self.manager is None: self.manager = FigureManager(self.canvas, 0) self.navigation_toolbar.destroy() self.navigation_toolbar = self.manager.toolbar self._menu_item_show_toolbar.set_active(True) window = self.manager.window window.set_transient_for(self.application.get_active_window()) window.set_title(self.graph_title) return window @property def fontsize_scale(self): scale = self.markersize_scale if scale < 5: fontsize = 'xx-small' elif scale < 7: fontsize = 'x-small' elif scale < 9: fontsize = 'small' else: fontsize = 'medium' return fontsize @property def markersize_scale(self): bbox = self.axes[0].get_window_extent().transformed( self.figure.dpi_scale_trans.inverted()) return bbox.width * self.figure.dpi * 0.01 def mpl_signal_canvas_button_pressed(self, event): if event.button != 3: return self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time()) return True def signal_activate_popup_menu_export(self, action): dialog = extras.FileChooserDialog('Export Graph', self.application.get_active_window()) file_name = self.config['campaign_name'] + '.png' response = dialog.run_quick_save(file_name) dialog.destroy() if not response: return destination_file = response['target_path'] self.figure.savefig(destination_file, dpi=200, facecolor=self.figure.get_facecolor(), format='png') def signal_activate_popup_refresh(self, event): self.refresh() def signal_toggled_popup_menu_show_toolbar(self, widget): if widget.get_property('active'): self.navigation_toolbar.show() else: self.navigation_toolbar.hide() def resize(self, width=0, height=0): """ Attempt to resize the canvas. Regardless of the parameters the canvas will never be resized to be smaller than :py:attr:`.minimum_size`. :param int width: The desired width of the canvas. :param int height: The desired height of the canvas. """ min_width, min_height = self.minimum_size width = max(width, min_width) height = max(height, min_height) self.canvas.set_size_request(width, height)
class CampaignGraph(object): """ A basic graph provider for using :py:mod:`matplotlib` to create graph representations of campaign data. This class is meant to be subclassed by real providers. """ name = 'Unknown' """The name of the graph provider.""" name_human = 'Unknown' """The human readable name of the graph provider used for UI identification.""" graph_title = 'Unknown' """The title that will be given to the graph.""" table_subscriptions = [] """A list of tables from which information is needed to produce the graph.""" is_available = True def __init__(self, config, parent, size_request=None): """ :param dict config: The King Phisher client configuration. :param parent: The parent window for this object. :type parent: :py:class:`Gtk.Window` :param tuple size_request: The size to set for the canvas. """ self.config = config """A reference to the King Phisher client configuration.""" self.parent = parent """The parent :py:class:`Gtk.Window` instance.""" self.figure, _ = pyplot.subplots() self.axes = self.figure.get_axes() self.canvas = FigureCanvas(self.figure) self.manager = None if size_request: self.canvas.set_size_request(*size_request) self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed) self.canvas.show() self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent) self.popup_menu = Gtk.Menu.new() menu_item = Gtk.MenuItem.new_with_label('Export') menu_item.connect('activate', self.signal_activate_popup_menu_export) self.popup_menu.append(menu_item) menu_item = Gtk.MenuItem.new_with_label('Refresh') menu_item.connect('activate', lambda action: self.refresh()) self.popup_menu.append(menu_item) menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar') menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar) self._menu_item_show_toolbar = menu_item self.popup_menu.append(menu_item) self.popup_menu.show_all() self.navigation_toolbar.hide() def _load_graph(self, info_cache): raise NotImplementedError() def _graph_bar_set_yparams(self, top_lim): min_value = top_lim + (top_lim * 0.075) if min_value <= 25: scale = 5 else: scale = scale = 10 ** (len(str(int(min_value))) - 1) inc_scale = scale while scale <= min_value: scale += inc_scale top_lim = scale ax = self.axes[0] yticks = set((round(top_lim * 0.5), top_lim)) ax.set_yticks(tuple(yticks)) ax.set_ylim(top=top_lim) return def _graph_null_pie(self, title): ax = self.axes[0] ax.pie((100,), labels=(title,), colors=(MPL_COLOR_NULL,), autopct='%1.0f%%', shadow=True, startangle=90) ax.axis('equal') return def add_legend_patch(self, legend_rows, fontsize=None): handles = [] if not fontsize: scale = self.markersize_scale if scale < 5: fontsize = 'xx-small' elif scale < 7: fontsize = 'x-small' elif scale < 9: fontsize = 'small' else: fontsize = 'medium' for row in legend_rows: handles.append(patches.Patch(color=row[0], label=row[1])) self.axes[0].legend(handles=handles, fontsize=fontsize, loc='lower right') def graph_bar(self, bars, color=None, xticklabels=None, ylabel=None): """ Create a standard bar graph with better defaults for the standard use cases. :param list bars: The values of the bars to graph. :param color: The color of the bars on the graph. :type color: list, str :param list xticklabels: The labels to use on the x-axis. :param str ylabel: The label to give to the y-axis. :return: The bars created using :py:mod:`matplotlib` :rtype: `matplotlib.container.BarContainer` """ color = color or MPL_COLOR_NULL width = 0.25 ax = self.axes[0] self._graph_bar_set_yparams(max(bars) if bars else 0) bars = ax.bar(range(len(bars)), bars, width, color=color) ax.set_xticks([float(x) + (width / 2) for x in range(len(bars))]) if xticklabels: ax.set_xticklabels(xticklabels, rotation=30) for col in bars: height = col.get_height() ax.text(col.get_x() + col.get_width() / 2.0, height, "{0:,}".format(height), ha='center', va='bottom') if ylabel: ax.set_ylabel(ylabel) self.figure.subplots_adjust(bottom=0.25) return bars def make_window(self): """ Create a window from the figure manager. :return: The graph in a new, dedicated window. :rtype: :py:class:`Gtk.Window` """ if self.manager == None: self.manager = FigureManager(self.canvas, 0) self.navigation_toolbar.destroy() self.navigation_toolbar = self.manager.toolbar self._menu_item_show_toolbar.set_active(True) window = self.manager.window window.set_transient_for(self.parent) window.set_title(self.graph_title) return window @property def markersize_scale(self): bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted()) return max(bbox.width, bbox.width) * self.figure.dpi * 0.01 def mpl_signal_canvas_button_pressed(self, event): if event.button != 3: return self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time()) return True def signal_activate_popup_menu_export(self, action): dialog = gui_utilities.UtilityFileChooser('Export Graph', self.parent) file_name = self.config['campaign_name'] + '.png' response = dialog.run_quick_save(file_name) dialog.destroy() if not response: return destination_file = response['target_path'] self.figure.savefig(destination_file, format='png') def signal_toggled_popup_menu_show_toolbar(self, widget): if widget.get_property('active'): self.navigation_toolbar.show() else: self.navigation_toolbar.hide() def load_graph(self): """Load the graph information via :py:meth:`.refresh`.""" self.refresh() def refresh(self, info_cache=None, stop_event=None): """ Refresh the graph data by retrieving the information from the remote server. :param dict info_cache: An optional cache of data tables. :param stop_event: An optional object indicating that the operation should stop. :type stop_event: :py:class:`threading.Event` :return: A dictionary of cached tables from the server. :rtype: dict """ info_cache = (info_cache or {}) if not self.parent.rpc: return info_cache for table in self.table_subscriptions: if stop_event and stop_event.is_set(): return info_cache if not table in info_cache: info_cache[table] = tuple(self.parent.rpc.remote_table('campaign/' + table, self.config['campaign_id'])) for ax in self.axes: ax.clear() self._load_graph(info_cache) self.axes[0].set_title(self.graph_title, y=1.03) self.canvas.draw() return info_cache
class CampaignGraph(object): """ A basic graph provider for using :py:mod:`matplotlib` to create graph representations of campaign data. This class is meant to be subclassed by real providers. """ name = 'Unknown' """The name of the graph provider.""" name_human = 'Unknown' """The human readable name of the graph provider used for UI identification.""" graph_title = 'Unknown' """The title that will be given to the graph.""" table_subscriptions = [] """A list of tables from which information is needed to produce the graph.""" is_available = True def __init__(self, application, size_request=None, style_context=None): """ :param tuple size_request: The size to set for the canvas. """ self.application = application self.style_context = style_context self.config = application.config """A reference to the King Phisher client configuration.""" self.figure, _ = pyplot.subplots() self.figure.set_facecolor(self.get_color('bg', ColorHexCode.WHITE)) self.axes = self.figure.get_axes() self.canvas = FigureCanvas(self.figure) self.manager = None if size_request: self.canvas.set_size_request(*size_request) self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed) self.canvas.show() self.navigation_toolbar = NavigationToolbar(self.canvas, self.application.get_active_window()) self.popup_menu = Gtk.Menu.new() menu_item = Gtk.MenuItem.new_with_label('Export') menu_item.connect('activate', self.signal_activate_popup_menu_export) self.popup_menu.append(menu_item) menu_item = Gtk.MenuItem.new_with_label('Refresh') menu_item.connect('activate', lambda action: self.refresh()) self.popup_menu.append(menu_item) menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar') menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar) self._menu_item_show_toolbar = menu_item self.popup_menu.append(menu_item) self.popup_menu.show_all() self.navigation_toolbar.hide() self._legend = None @property def rpc(self): return self.application.rpc @staticmethod def _ax_hide_ticks(ax): for tick in ax.yaxis.get_major_ticks(): tick.tick1On = False tick.tick2On = False @staticmethod def _ax_set_spine_color(ax, spine_color): for pos in ('top', 'right', 'bottom', 'left'): ax.spines[pos].set_color(spine_color) def _load_graph(self, info_cache): raise NotImplementedError() def add_legend_patch(self, legend_rows, fontsize=None): if self._legend is not None: self._legend.remove() self._legend = None if fontsize is None: scale = self.markersize_scale if scale < 5: fontsize = 'xx-small' elif scale < 7: fontsize = 'x-small' elif scale < 9: fontsize = 'small' else: fontsize = 'medium' legend_bbox = self.figure.legend( tuple(patches.Patch(color=patch_color) for patch_color, _ in legend_rows), tuple(label for _, label in legend_rows), borderaxespad=1.25, fontsize=fontsize, frameon=True, handlelength=1.5, handletextpad=0.75, labelspacing=0.3, loc='lower right' ) legend_bbox.legendPatch.set_linewidth(0) self._legend = legend_bbox def get_color(self, color_name, default): """ Get a color by its style name such as 'fg' for foreground. If the specified color does not exist, default will be returned. The underlying logic for this function is provided by :py:func:`~.gui_utilities.gtk_style_context_get_color`. :param str color_name: The style name of the color. :param default: The default color to return if the specified one was not found. :return: The desired color if it was found. :rtype: tuple """ color_name = 'theme_color_graph_' + color_name sc_color = gui_utilities.gtk_style_context_get_color(self.style_context, color_name, default) return (sc_color.red, sc_color.green, sc_color.blue) def make_window(self): """ Create a window from the figure manager. :return: The graph in a new, dedicated window. :rtype: :py:class:`Gtk.Window` """ if self.manager is None: self.manager = FigureManager(self.canvas, 0) self.navigation_toolbar.destroy() self.navigation_toolbar = self.manager.toolbar self._menu_item_show_toolbar.set_active(True) window = self.manager.window window.set_transient_for(self.application.get_active_window()) window.set_title(self.graph_title) return window @property def markersize_scale(self): bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted()) return bbox.width * self.figure.dpi * 0.01 def mpl_signal_canvas_button_pressed(self, event): if event.button != 3: return self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time()) return True def signal_activate_popup_menu_export(self, action): dialog = gui_utilities.FileChooser('Export Graph', self.application.get_active_window()) file_name = self.config['campaign_name'] + '.png' response = dialog.run_quick_save(file_name) dialog.destroy() if not response: return destination_file = response['target_path'] self.figure.savefig(destination_file, format='png') def signal_toggled_popup_menu_show_toolbar(self, widget): if widget.get_property('active'): self.navigation_toolbar.show() else: self.navigation_toolbar.hide() def load_graph(self): """Load the graph information via :py:meth:`.refresh`.""" self.refresh() def refresh(self, info_cache=None, stop_event=None): """ Refresh the graph data by retrieving the information from the remote server. :param dict info_cache: An optional cache of data tables. :param stop_event: An optional object indicating that the operation should stop. :type stop_event: :py:class:`threading.Event` :return: A dictionary of cached tables from the server. :rtype: dict """ info_cache = (info_cache or {}) if not self.rpc: return info_cache for table in self.table_subscriptions: if stop_event and stop_event.is_set(): return info_cache if not table in info_cache: info_cache[table] = tuple(self.rpc.remote_table(table, query_filter={'campaign_id': self.config['campaign_id']})) for ax in self.axes: ax.clear() if self._legend is not None: self._legend.remove() self._legend = None self._load_graph(info_cache) self.figure.suptitle( self.graph_title, color=self.get_color('fg', ColorHexCode.BLACK), size=14, weight='bold', y=0.97 ) self.canvas.draw() return info_cache
class CampaignGraph(object): """ A basic graph provider for using :py:mod:`matplotlib` to create graph representations of campaign data. This class is meant to be subclassed by real providers. """ name = 'Unknown' """The name of the graph provider.""" name_human = 'Unknown' """The human readable name of the graph provider used for UI identification.""" graph_title = 'Unknown' """The title that will be given to the graph.""" table_subscriptions = [] """A list of tables from which information is needed to produce the graph.""" is_available = True def __init__(self, config, parent, size_request=None): """ :param dict config: The King Phisher client configuration. :param parent: The parent window for this object. :type parent: :py:class:`Gtk.Window` :param tuple size_request: The size to set for the canvas. """ self.config = config """A reference to the King Phisher client configuration.""" self.parent = parent """The parent :py:class:`Gtk.Window` instance.""" self.figure, _ = pyplot.subplots() self.axes = self.figure.get_axes() self.canvas = FigureCanvas(self.figure) self.manager = None if size_request: self.canvas.set_size_request(*size_request) self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed) self.canvas.show() self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent) self.popup_menu = Gtk.Menu.new() menu_item = Gtk.MenuItem.new_with_label('Export') menu_item.connect('activate', self.signal_activate_popup_menu_export) self.popup_menu.append(menu_item) menu_item = Gtk.MenuItem.new_with_label('Refresh') menu_item.connect('activate', lambda action: self.refresh()) self.popup_menu.append(menu_item) menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar') menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar) self._menu_item_show_toolbar = menu_item self.popup_menu.append(menu_item) self.popup_menu.show_all() self.navigation_toolbar.hide() def _load_graph(self, info_cache): raise NotImplementedError() def _graph_bar_set_yparams(self, top_lim): min_value = top_lim + (top_lim * 0.075) if min_value <= 25: scale = 5 else: scale = scale = 10 ** (len(str(int(min_value))) - 1) inc_scale = scale while scale <= min_value: scale += inc_scale top_lim = scale ax = self.axes[0] yticks = set((round(top_lim * 0.5), top_lim)) ax.set_yticks(tuple(yticks)) ax.set_ylim(top=top_lim) return def _graph_null_pie(self, title): ax = self.axes[0] ax.pie((100,), labels=(title,), colors=(MPL_COLOR_NULL,), autopct='%1.0f%%', shadow=True, startangle=90) ax.axis('equal') return def add_legend_patch(self, legend_rows, fontsize=None): handles = [] if not fontsize: scale = self.markersize_scale if scale < 5: fontsize = 'xx-small' elif scale < 7: fontsize = 'x-small' elif scale < 9: fontsize = 'small' else: fontsize = 'medium' for row in legend_rows: handles.append(patches.Patch(color=row[0], label=row[1])) self.axes[0].legend(handles=handles, fontsize=fontsize, loc='lower right') def graph_bar(self, bars, color=None, xticklabels=None, ylabel=None): """ Create a standard bar graph with better defaults for the standard use cases. :param list bars: The values of the bars to graph. :param color: The color of the bars on the graph. :type color: list, str :param list xticklabels: The labels to use on the x-axis. :param str ylabel: The label to give to the y-axis. :return: The bars created using :py:mod:`matplotlib` :rtype: `matplotlib.container.BarContainer` """ color = color or MPL_COLOR_NULL width = 0.25 ax = self.axes[0] self._graph_bar_set_yparams(max(bars) if bars else 0) bars = ax.bar(range(len(bars)), bars, width, color=color) ax.set_xticks([float(x) + (width / 2) for x in range(len(bars))]) if xticklabels: ax.set_xticklabels(xticklabels, rotation=30) for col in bars: height = col.get_height() ax.text(col.get_x() + col.get_width() / 2.0, height, "{0:,}".format(height), ha='center', va='bottom') if ylabel: ax.set_ylabel(ylabel) self.figure.subplots_adjust(bottom=0.25) return bars def make_window(self): """ Create a window from the figure manager. :return: The graph in a new, dedicated window. :rtype: :py:class:`Gtk.Window` """ if self.manager == None: self.manager = FigureManager(self.canvas, 0) self.navigation_toolbar.destroy() self.navigation_toolbar = self.manager.toolbar self._menu_item_show_toolbar.set_active(True) window = self.manager.window window.set_transient_for(self.parent) window.set_title(self.graph_title) return window @property def markersize_scale(self): bbox = self.axes[0].get_window_extent().transformed(self.figure.dpi_scale_trans.inverted()) return max(bbox.width, bbox.width) * self.figure.dpi * 0.01 def mpl_signal_canvas_button_pressed(self, event): if event.button != 3: return self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time()) return True def signal_activate_popup_menu_export(self, action): dialog = gui_utilities.FileChooser('Export Graph', self.parent) file_name = self.config['campaign_name'] + '.png' response = dialog.run_quick_save(file_name) dialog.destroy() if not response: return destination_file = response['target_path'] self.figure.savefig(destination_file, format='png') def signal_toggled_popup_menu_show_toolbar(self, widget): if widget.get_property('active'): self.navigation_toolbar.show() else: self.navigation_toolbar.hide() def load_graph(self): """Load the graph information via :py:meth:`.refresh`.""" self.refresh() def refresh(self, info_cache=None, stop_event=None): """ Refresh the graph data by retrieving the information from the remote server. :param dict info_cache: An optional cache of data tables. :param stop_event: An optional object indicating that the operation should stop. :type stop_event: :py:class:`threading.Event` :return: A dictionary of cached tables from the server. :rtype: dict """ info_cache = (info_cache or {}) if not self.parent.rpc: return info_cache for table in self.table_subscriptions: if stop_event and stop_event.is_set(): return info_cache if not table in info_cache: info_cache[table] = tuple(self.parent.rpc.remote_table('campaign/' + table, self.config['campaign_id'])) for ax in self.axes: ax.clear() self._load_graph(info_cache) self.axes[0].set_title(self.graph_title, y=1.03) self.canvas.draw() return info_cache
class Plotter(Gtk.Alignment): def __init__(self, loop=False, buffer_size=2500, xformat='%g', dpi=80): super().__init__() self.set(0.5, 0.5, 1, 1) self.format_x = FormatStrFormatter(xformat) self.ring_buffer = loop self.buffer_size = buffer_size self.colormap = cm.get_cmap('Dark2') self.axis_space = 0.92 self.cursor_line = None self.cursor_points = {} self.plot_scales = {} self.lines = {} self.axis = {} self.data_type = {} self.values = None self.grid_mode = False self.grid_specs = {} self.grid_image = None self.grid_norm = Normalize() self.grid_snake = False self.fig = Figure(figsize=(10, 6), dpi=dpi) self.clear() self.canvas = FigureCanvas(self.fig) # a Gtk.DrawingArea self.canvas.mpl_connect('motion_notify_event', self.on_mouse_motion) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.toolbar = PlotterToolbar(self.canvas, dialogs.MAIN_WINDOW) box.pack_start(self.canvas, True, True, 0) box.pack_start(self.toolbar, False, False, 0) self.add(box) self.show_all() def clear(self, specs=None): """ Clear the plot and configure it for the given specifications. :param specs: dictionary containing configuration parameters """ self.fig.clear() self.fig.subplots_adjust(bottom=0.1, left=0.05, top=0.90, right=self.axis_space) specs = {} if specs is None else specs self.grid_mode = 'grid' in specs.get('scan_type', '') self.data_type = specs.get('data_type') self.values = misc.RecordArray(self.data_type, size=self.buffer_size, loop=self.ring_buffer) self.cursor_line = None self.lines = {} self.grid_snake = specs.get('grid_snake', False) self.grid_specs = {} self.grid_image = None self.grid_norm = Normalize() ax = self.fig.add_subplot(111) ax.yaxis.tick_right() ax.yaxis.set_major_formatter(ScalarFormatter()) self.axis = {'default': ax} if specs: names = self.data_type['names'][1:] scales = specs.get('data_scale') if scales: self.plot_scales = { ('default' if i == 0 else 'axis-{}'.format(i)): scale for i, scale in enumerate(scales) } else: self.plot_scales = { ('default' if i == 0 else 'axis-{}'.format(i)): (name, ) for i, name in enumerate(names) } def get_axis_for(self, name): """ Return the axis for the named line :param name: line name :return: an axis object """ return self.lines[name].axes def add_axis(self, name=None, label=""): """ Add a named axis to the plot with the :param name: axis name :param label: axis label :return: matplotlib axis object """ name = 'axis-{}'.format(len(self.axis)) if not name else name default = self.axis.get('default') index = len(self.axis) + 1 axis_position = 1 / (self.axis_space**(index - 1)) self.fig.subplots_adjust(right=self.axis_space**index) ax = self.fig.add_axes(default.get_position(), sharex=default, frameon=False) ax.spines['right'].set_position(('axes', axis_position)) ax.yaxis.set_major_formatter(ScalarFormatter()) ax.set_frame_on(True) ax.patch.set_visible(False) ax.yaxis.tick_right() ax.yaxis.set_label_position('right') ax.set_ylabel(label) for label in ax.get_xticklabels(): label.set_visible(False) self.axis[name] = ax self.plot_scales[name] = () return ax def add_line(self, xpoints, ypoints, style='-', name='', lw=1, axis="default", alpha=1.0, color=None, redraw=True, markevery=[]): """ Add a named line to the plot :param xpoints: initial x axis values :param ypoints: initial y axis values :param style: matplotlib line style string :param name: line name, optional :param lw: line width :param axis: optional name of axis of add line to :param alpha: line transparency :param color: line color :param redraw: whether to redraw the line or note :param markevery: matplotlit 'markevery' parameter, set to None to show markers at every point """ assert (len(xpoints) == len(ypoints)) if axis not in self.axis: self.add_axis(axis) name = 'line-{}'.format(len(self.lines)) if not name else name color = self.colormap(len(self.lines)) if not color else color self.axis[axis].autoscale(False) xmin_current, xmax_current = self.axis[axis].get_xlim() ymin_current, ymax_current = self.axis[axis].get_ylim() line, = self.axis[axis].plot(xpoints, ypoints, '.', ls=style, lw=lw, markersize=8, label=name, alpha=alpha, markevery=markevery, color=color) # adjust axes limits as necessary xmin, xmax = misc.get_min_max(xpoints, ldev=0, rdev=0) ymin, ymax = misc.get_min_max(ypoints, ldev=1, rdev=1) xmin, xmax = min(xmin, xmin_current), max(xmax, xmax_current) ymin, ymax = min(ymin, ymin_current), max(ymax, ymax_current) line.axes.set_xlim(xmin, xmax) line.axes.set_ylim(ymin, ymax) self.lines[name] = line if name not in self.plot_scales[axis]: self.plot_scales[axis] += (name, ) if len(xpoints) > 1: self.values.add_func(name, xpoints, ypoints) if redraw: self.redraw() def add_point(self, row, redraw=True): """ Add a row of scan points to the data table :param row: sequence of values to add :param redraw: Whether to redraw the plot """ if numpy.nan in row: return self.values.append(row) x_name = self.data_type['names'][0] if self.grid_mode: # no lines for grid mode self.update_grid_data() elif not self.lines: count = 0 for axis, lines in self.plot_scales.items(): if axis != 'default': self.add_axis(name=axis) for line in lines: self.add_line(self.values.data[x_name], self.values.data[line], color=self.colormap(count), name=line, axis=axis, markevery=[-1]) count += 1 else: xmin, xmax = misc.get_min_max(self.values.data[x_name], ldev=0, rdev=0) for axis, lines in self.plot_scales.items(): ymin = ymax = None ax = None for name in lines: line = self.lines[name] line.set_data(self.values.data[x_name], self.values.data[name]) ax = line.axes ylo, yhi = misc.get_min_max(self.values.data[name], ldev=0.5, rdev=0.5) if ymin is None: ymin, ymax = ylo, yhi else: ymin, ymax = min(ymin, ylo), max(ymax, yhi) ymin, ymax = ymin, ymax # adjust axes limits as necessary if ax is not None: offset = (ymax - ymin) * .1 ax.set_ylim(ymin - offset, ymax + offset) ax.set_xlim(xmin, xmax) if len(self.lines) > 1: default = self.axis.get('default') xmin_current, xmax_current = default.get_xlim() default.set_xlim(min(xmin, xmin_current), max(xmax, xmax_current)) if redraw: self.redraw() def new_row(self, index): """ Prepare for A new row of data :param index: row index for next row """ if self.grid_mode and index > 1: # for slew grid scans, data needs to be padded/truncated y_name = self.data_type['names'][1] yo = self.values.data[y_name] x_size = (yo == yo[0]).sum() y_size = index pad = x_size * y_size - yo.shape[0] if pad == 0: return elif pad > 0: for i in range(pad): self.values.append(self.values.data[-1]) # padding elif pad < 0: self.values.length = x_size * y_size self.update_grid_data() def update_grid_data(self): """ Update the grid image values """ x_name, y_name, counts_name = self.data_type['names'][:3] xo = self.values.data[x_name] yo = self.values.data[y_name] counts = self.values.data[counts_name] x_min, x_max = xo.min(), xo.max() y_min, y_max = yo.min(), yo.max() self.grid_norm.autoscale(counts) xsize = (yo == yo[0]).sum() ysize = int(numpy.ceil(yo.shape[0] / xsize)) # pad unfilled values with nan blanks = xsize * ysize - counts.shape[0] if blanks: counts = numpy.pad(counts, (0, blanks), 'constant', constant_values=(numpy.nan, numpy.nan)) count_data = numpy.resize(counts, (ysize, xsize)) # flip alternate rows if self.grid_snake: count_data[1::2, :] = count_data[1::2, ::-1] self.grid_specs.update({ 'x': xo, 'y': yo, 'counts': count_data, }) extent = [ x_min, x_max, y_min, y_max, ] if self.grid_image is None: default = self.axis.get('default') self.grid_image = default.imshow( self.grid_specs['counts'], cmap=cm.get_cmap(GRID_COLORMAP), origin='lower', norm=self.grid_norm, extent=extent, aspect='auto', interpolation=GRID_INTERPOLATION, ) else: self.grid_image.set_data(self.grid_specs['counts']) self.grid_image.set_extent(extent) # set axis limits self.grid_image.axes.set_xlim(extent[:2]) self.grid_image.axes.set_ylim(extent[-2:]) self.redraw() def get_records(self): """ Return the data array manager for the plot """ return self.values def set_labels(self, title="", x_label="", y1_label=""): default = self.axis.get('default') default.set_xlabel(x_label, ha='right', va='top') default.set_ylabel(y1_label) default.xaxis.set_label_coords(1.0, -0.075) def set_time_labels(self, labels, fmt, maj_int, min_int): default = self.axis.get('default') default.xaxis.set_major_locator(MinuteLocator(interval=maj_int)) default.xaxis.set_minor_locator(SecondLocator(interval=min_int)) if len(default.xaxis.get_major_ticks()) < len(labels): labels.pop(0) default.set_xticklabels( [d != ' ' and d.strftime(fmt) or '' for d in labels]) def redraw(self): if not self.grid_mode: lines = list(self.lines.values()) labels = list(self.lines.keys()) self.axis['default'].legend(lines, labels, loc='upper left', bbox_to_anchor=(0, 1.075), ncol=8, fancybox=False, framealpha=0.0, edgecolor='inherit', borderaxespad=0, fontsize=9) self.canvas.draw_idle() def on_mouse_motion(self, event): default = self.axis.get('default') if event.inaxes and self.lines and not self.grid_mode: x, y = event.xdata, event.ydata if self.cursor_line is None: self.cursor_line = default.axvline(x, lw=1, color='#3a7ca8', antialiased=None) for axis, lines in self.plot_scales.items(): for name in lines: y_value = self.values(name, x) ax = self.axis[axis] if name in self.lines: line = self.lines[name] trans = transforms.blended_transform_factory( ax.get_yticklabels()[0].get_transform(), ax.transData) self.cursor_points[name] = ax.text( 1, y_value, "< {}".format(name), color=line.get_color(), transform=trans, ha="left", va="center") else: self.cursor_line.set_xdata(x) for axis, lines in self.plot_scales.items(): for name in lines: if name in self.lines: y_value = self.values(name, x) if name in self.cursor_points: self.cursor_points[name].set_position( (1, y_value)) self.canvas.draw_idle() else: if self.cursor_line: self.cursor_line.remove() self.cursor_line = None for name in list(self.cursor_points.keys()): mark = self.cursor_points.pop(name) mark.remove() self.canvas.draw_idle()
class App(Gtk.Application): def __init__(self): ''' Build GUI ''' # build GUI from glade file self.builder = Gtk.Builder() self.glade_file = os.path.join(curr_dir, 'kfit.glade') self.builder.add_from_file(self.glade_file) # get the necessary ui objects self.main_window = self.builder.get_object('main_window') self.graph_box = self.builder.get_object('graph_box') self.gau_sw = self.builder.get_object('param_scroll_gau') self.lor_sw = self.builder.get_object('param_scroll_lor') self.voi_sw = self.builder.get_object('param_scroll_voi') self.lin_sw = self.builder.get_object('param_scroll_lin') self.param_viewport_gau = self.builder.get_object('param_viewport_gau') self.param_viewport_lor = self.builder.get_object('param_viewport_lor') self.param_viewport_voi = self.builder.get_object('param_viewport_voi') self.param_viewport_lin = self.builder.get_object('param_viewport_lin') self.statusbar_viewport = self.builder.get_object('statusbar_viewport') self.data_treeview = self.builder.get_object('data_treeview') self.column_entry_x = self.builder.get_object('column_entry_x') self.column_entry_y = self.builder.get_object('column_entry_y') self.graph_box = self.builder.get_object('graph_box') self.fname_textview = self.builder.get_object('fname_textview') self.fit_button = self.builder.get_object('fit_button') self.reset_button = self.builder.get_object('reset_button') self.settings_button = self.builder.get_object('settings_button') self.import_button = self.builder.get_object('import_button') self.export_button = self.builder.get_object('export_button') self.help_button = self.builder.get_object('help_button') self.output_textview = self.builder.get_object('output_textview') self.settings_dialog = self.builder.get_object('settings_dialog') self.sep_entry = self.builder.get_object('sep_entry') self.header_entry = self.builder.get_object('header_entry') self.skiprows_entry = self.builder.get_object('skiprows_entry') self.dtype_entry = self.builder.get_object('dtype_entry') self.encoding_entry = self.builder.get_object('encoding_entry') self.fit_method_entry = self.builder.get_object('fit_method_entry') self.add_gau = self.builder.get_object('add_gau') self.rem_gau = self.builder.get_object('rem_gau') self.add_lor = self.builder.get_object('add_lor') self.rem_lor = self.builder.get_object('rem_lor') self.add_voi = self.builder.get_object('add_voi') self.rem_voi = self.builder.get_object('rem_voi') self.add_lin = self.builder.get_object('add_lin') self.rem_lin = self.builder.get_object('rem_lin') # define other class attributes self.title = 'kfit' self.file_name = '' self.xcol_idx = 0 self.ycol_idx = 1 self.cmode_state = 0 self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) # configure help button self.help_button.set_label(' Help') help_image = Gtk.Image() help_image.set_from_file( os.path.join(curr_dir, '../images/dialog-question-symbolic.svg') ) self.help_button.set_image(help_image) self.help_button.set_always_show_image(True) # for graph... x = np.linspace(0, 10, 500) y = models.gauss(x, 0.5, 4, 0.4) + \ models.gauss(x, 0.8, 5, 0.2) + \ models.gauss(x, 0.4, 6, 0.3) + 0.2 self.data = pd.DataFrame([x, y]).T self.data.columns = ['x', 'y'] self.x = self.data['x'].values self.y = self.data['y'].values self.xmin = self.data['x'].min() self.xmax = self.data['x'].max() plt.style.use(os.path.join(curr_dir, 'kfit.mplstyle')) self.figure = Figure(figsize=(10, 4), dpi=60) self.canvas = FigureCanvas(self.figure) self.canvas.set_size_request(900, 400) self.toolbar = NavigationToolbar(self.canvas, self.main_window) self.graph_box.pack_start(self.toolbar, True, True, 0) self.graph_box.pack_start(self.canvas, True, True, 0) self.cmode_toolitem = Gtk.ToolItem() self.cmode_box = Gtk.Box() self.cmode_box.set_margin_start(24) self.cmode_box.set_margin_end(36) self.cmode_radio_off = Gtk.RadioButton.new_with_label_from_widget( None, label='off' ) self.cmode_radio_off.connect('toggled', self.toggle_copy_mode) self.cmode_radio_x = Gtk.RadioButton.new_from_widget( self.cmode_radio_off ) self.cmode_radio_x.set_label('x') self.cmode_radio_x.connect('toggled', self.toggle_copy_mode) self.cmode_radio_y = Gtk.RadioButton.new_from_widget( self.cmode_radio_off ) self.cmode_radio_y.set_label('y') self.cmode_radio_y.connect('toggled', self.toggle_copy_mode) self.cmode_box.pack_start( Gtk.Label(label='Copy mode:'), False, False, 0 ) self.cmode_box.pack_start(self.cmode_radio_off, False, False, 0) self.cmode_box.pack_start(self.cmode_radio_x, False, False, 0) self.cmode_box.pack_start(self.cmode_radio_y, False, False, 0) self.cmode_toolitem.add(self.cmode_box) self.toolbar.insert(self.cmode_toolitem, -1) # for fit... self.fit_method = 'least_squares' self.model = None self.result = None self.yfit = None self.ngau = 0 self.nlor = 0 self.nvoi = 0 self.nlin = 1 self.curves_df = None self.params_df = None self.params = Parameters() self.guesses = { 'value': {}, 'min': {}, 'max': {} } self.usr_vals = { 'value': {}, 'min': {}, 'max': {} } self.usr_entry_widgets = {} self.cid = None # for data view... self.fname_buffer = Gtk.TextBuffer() self.display_data() # for output... self.output_buffer = Gtk.TextBuffer() self.output_textview.set_buffer(self.output_buffer) # file import settings self.sep = ',' self.header = 'infer' self.index_col = None self.skiprows = None self.dtype = None self.encoding = None # show initial plot self.plot() # add statusbar self.statusbar = Gtk.Statusbar() self.statusbar.set_margin_top(0) self.statusbar.set_margin_bottom(0) self.statusbar.set_margin_start(0) self.statusbar.set_margin_end(0) self.statusbar_viewport.add(self.statusbar) # connect signals events = { 'on_fit_button_clicked': self.fit, 'on_import_button_clicked': self.get_data, 'on_settings_button_clicked': self.run_settings_dialog, 'on_reset_button_clicked': self.hard_reset, 'on_add_gau_clicked': self.on_add_gau_clicked, 'on_rem_gau_clicked': self.on_rem_gau_clicked, 'on_add_lor_clicked': self.on_add_lor_clicked, 'on_rem_lor_clicked': self.on_rem_lor_clicked, 'on_add_voi_clicked': self.on_add_voi_clicked, 'on_rem_voi_clicked': self.on_rem_voi_clicked, 'on_add_lin_clicked': self.on_add_lin_clicked, 'on_rem_lin_clicked': self.on_rem_lin_clicked, 'on_column_entry_changed': self.get_column_index, 'on_export_button_clicked': self.export_data, } self.builder.connect_signals(events) # add accelerators / keyboard shortcuts self.accelerators = Gtk.AccelGroup() self.main_window.add_accel_group(self.accelerators) self.add_accelerator(self.fit_button, '<Control>f') self.add_accelerator(self.reset_button, '<Control>r') self.add_accelerator(self.settings_button, '<Control>p') self.add_accelerator(self.import_button, '<Control>o') self.add_accelerator(self.export_button, '<Control>s') self.add_accelerator(self.add_gau, 'g') self.add_accelerator(self.rem_gau, '<Shift>g') self.add_accelerator(self.add_lor, 'l') self.add_accelerator(self.rem_lor, '<Shift>l') self.add_accelerator(self.add_voi, 'v') self.add_accelerator(self.rem_voi, '<Shift>v') self.add_accelerator(self.add_lin, 'n') self.add_accelerator(self.rem_lin, '<Shift>n') # configure interface self.main_window.connect('destroy', Gtk.main_quit) self.init_param_widgets() # show the app window self.main_window.show_all() def plot(self): self.figure.clear() self.axis = self.figure.add_subplot(111) self.set_xlims() if len(self.x) >= 1000: self.axis.plot( self.x, self.y, c='#af87ff', linewidth=12, label='data' ) else: self.axis.scatter( self.x, self.y, s=200, c='#af87ff', edgecolors='black', linewidth=1, label='data' ) if self.result is not None: self.yfit = self.result.best_fit self.axis.plot(self.x, self.yfit, c='r', linewidth=2.5) cmap = cm.get_cmap('gnuplot') components = self.result.eval_components() for i, comp in enumerate(components): self.axis.plot( self.x, components[comp], linewidth=2.5, linestyle='--', c=cmap(i/len(components)), label=comp[:comp.find('_')] ) self.axis.set_xlabel(self.data.columns[self.xcol_idx]) self.axis.set_ylabel(self.data.columns[self.ycol_idx]) self.axis.legend(loc='best') self.axis.set_xlim([self.xmin, self.xmax]) self.canvas.draw() def fit(self, source=None, event=None): self.cmode_radio_off.set_active(True) self.toggle_copy_mode(self.cmode_radio_off) self.set_xrange_to_zoom() self.filter_nan() self.set_params() self.result = self.model.fit( data=self.y, params=self.params, x=self.x, method=self.fit_method ) self.output_buffer.set_text(self.result.fit_report()) self.plot() # overwrite widgets to clear input (not ideal method..) self.init_param_widgets() def init_model(self): # note: increment() ensures nlin >= 1 self.model = models.line_mod(self.nlin) if self.ngau != 0: self.model += models.gauss_mod(self.ngau) if self.nlor != 0: self.model += models.lor_mod(self.nlor) if self.nvoi != 0: self.model += models.voigt_mod(self.nvoi) self.statusbar.push( self.statusbar.get_context_id('info'), "Model updated: " + str([self.ngau, self.nlor, self.nvoi, self.nlin]) ) def init_param_widgets(self): self.init_model() self.usr_entry_widgets = { 'value': {}, 'min': {}, 'max': {} } labels = {} rnd = 3 # decimals to round to in placeholder text self.clear_param_viewports() # main boxes to hold user entry widgets self.vbox_gau = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.vbox_lor = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.vbox_voi = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.vbox_lin = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) for param_name in self.model.param_names: # set param label text labels[param_name] = Gtk.Label() labels[param_name].set_text(param_name) # make user entry widgets for key in self.usr_entry_widgets: self.usr_entry_widgets[key][param_name] = Gtk.Entry() if param_name in self.usr_vals[key]: self.usr_entry_widgets[key][param_name]\ .set_placeholder_text( str(round(self.usr_vals[key][param_name], rnd)) ) else: self.usr_entry_widgets[key][param_name]\ .set_placeholder_text(key) # set up connections self.usr_entry_widgets[key][param_name].connect( 'changed', self.update_usr_vals, self.usr_entry_widgets ) # add widgets to respective layouts vbox_sub = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) for key in self.usr_entry_widgets: vbox_sub.pack_start( self.usr_entry_widgets[key][param_name], False, False, pad ) if param_name.find('gau') != -1: self.vbox_gau.pack_start(labels[param_name], False, False, pad) self.vbox_gau.pack_start(vbox_sub, False, False, pad) self.vbox_gau.set_halign(Gtk.Align.CENTER) if param_name.find('lor') != -1: self.vbox_lor.pack_start(labels[param_name], False, False, pad) self.vbox_lor.pack_start(vbox_sub, False, False, pad) self.vbox_lor.set_halign(Gtk.Align.CENTER) if param_name.find('voi') != -1: self.vbox_voi.pack_start(labels[param_name], False, False, pad) self.vbox_voi.pack_start(vbox_sub, False, False, pad) self.vbox_voi.set_halign(Gtk.Align.CENTER) if param_name.find('lin') != -1: self.vbox_lin.pack_start(labels[param_name], False, False, pad) self.vbox_lin.pack_start(vbox_sub, False, False, pad) self.vbox_lin.set_halign(Gtk.Align.CENTER) # Resize all of the entry widgets for key in self.usr_entry_widgets: for param, widget in self.usr_entry_widgets[key].items(): widget.set_width_chars(7) # add/replace box in viewport self.param_viewport_gau.add(self.vbox_gau) self.param_viewport_lor.add(self.vbox_lor) self.param_viewport_voi.add(self.vbox_voi) self.param_viewport_lin.add(self.vbox_lin) for viewport in [self.param_viewport_gau, self.param_viewport_lor, self.param_viewport_voi, self.param_viewport_lin]: viewport.show_all() if self.result is not None: self.set_params() self.update_param_widgets() def update_usr_vals(self, widget, entry_widget_dict): # get text input from each usr_entry_widget for val_type, param_dict in entry_widget_dict.items(): for param, param_widget in param_dict.items(): try: self.usr_vals[val_type][param] = \ float(param_widget.get_text()) except Exception: pass def update_param_widgets(self): rnd = 3 # the 'value' placeholder text is the result for that param # taken from self.result # the 'min' and 'max' text is from either the self.guesses # or from self.usr_vals for param in self.params: if param in self.result.best_values: self.usr_entry_widgets['value'][param].set_placeholder_text( str(round(self.result.best_values[param], rnd)) ) self.usr_entry_widgets['min'][param].set_placeholder_text( str(round(self.params[param].min, rnd)) ) self.usr_entry_widgets['max'][param].set_placeholder_text( str(round(self.params[param].max, rnd)) ) def guess_params(self, source=None, event=None): for comp in self.model.components: if comp.prefix.find('gau') != -1 or \ comp.prefix.find('lor') != -1 or \ comp.prefix.find('voi') != -1: # need to define explicitly to make proper guesses c = comp.prefix + 'center' a = comp.prefix + 'amplitude' s = comp.prefix + 'sigma' f = comp.prefix + 'fraction' self.guesses['value'][c] = \ self.data.iloc[:, self.xcol_idx].mean() self.guesses['value'][a] = \ self.data.iloc[:, self.ycol_idx].mean() self.guesses['value'][s] = \ self.data.iloc[:, self.xcol_idx].std() self.guesses['min'][c] = None self.guesses['min'][a] = 0 self.guesses['min'][s] = 0 self.guesses['max'][c] = None self.guesses['max'][a] = None self.guesses['max'][s] = None if comp.prefix.find('voi') != -1: self.guesses['value'][f] = 0.5 self.guesses['min'][f] = 0 self.guesses['max'][f] = 1 else: slope = comp.prefix + 'slope' intc = comp.prefix + 'intercept' for p in [slope, intc]: self.guesses['value'][p] = \ self.data.iloc[:, self.ycol_idx].mean() self.guesses['min'][p] = None self.guesses['max'][p] = None def set_params(self, source=None, event=None): self.params = Parameters() self.guess_params() self.update_usr_vals(None, self.usr_entry_widgets) vals = {} # fill params with any user-entered values # fill in blanks with guesses for param_name in self.model.param_names: for val_type in ['value', 'min', 'max']: if param_name in self.usr_vals[val_type]: vals[val_type] = self.usr_vals[val_type][param_name] else: vals[val_type] = self.guesses[val_type][param_name] self.params.add( name=param_name, value=vals['value'], vary=True, min=vals['min'], max=vals['max'] ) def set_xlims(self, source=None, event=None): self.xmin = np.min(self.x) - 0.02*(np.max(self.x) - np.min(self.x)) self.xmax = np.max(self.x) + 0.02*(np.max(self.x) - np.min(self.x)) def set_xrange_to_zoom(self): self.xmin, self.xmax = self.axis.get_xlim() range_bool = (self.x >= self.xmin) & (self.x <= self.xmax) self.x = self.x[range_bool] self.y = self.y[range_bool] def filter_nan(self): if True in np.isnan(self.x) or True in np.isnan(self.y): nanbool = (~np.isnan(self.x) & ~np.isnan(self.y)) self.x = self.x[nanbool] self.y = self.y[nanbool] def increment(self, val, add): if add: if val == 'gau': self.ngau += 1 if val == 'lor': self.nlor += 1 if val == 'voi': self.nvoi += 1 if val == 'lin': self.nlin += 1 if not add: if val == 'gau': self.ngau -= 1 if val == 'lor': self.nlor -= 1 if val == 'voi': self.nvoi -= 1 if val == 'lin': self.nlin -= 1 # make sure value doesn't go below zero if self.ngau < 0: self.ngau = 0 if self.nlor < 0: self.nlor = 0 if self.nvoi < 0: self.nvoi = 0 if self.nlin < 1: self.nlin = 1 def clear_param_viewports(self): # clear any existing widgets from viewports for viewport in [self.param_viewport_gau, self.param_viewport_lin, self.param_viewport_lor, self.param_viewport_voi]: if viewport.get_child(): viewport.remove(viewport.get_child()) def hard_reset(self, source=None, event=None): self.clear_param_viewports() self.ngau = 0 self.nlor = 0 self.nvoi = 0 self.nlin = 1 self.init_model() self.params = Parameters() self.result = None self.params_df = None self.curves_df = None self.guesses = { 'value': {}, 'min': {}, 'max': {} } self.usr_vals = { 'value': {}, 'min': {}, 'max': {} } self.output_buffer.set_text('') self.init_param_widgets() self.get_column_index() self.plot() self.cmode_radio_off.set_active(True) self.toggle_copy_mode(self.cmode_radio_off) def on_add_gau_clicked(self, source=None, event=None): self.increment('gau', True) self.init_param_widgets() def on_rem_gau_clicked(self, source=None, event=None): self.increment('gau', False) self.init_param_widgets() def on_add_lor_clicked(self, source=None, event=None): self.increment('lor', True) self.init_param_widgets() def on_rem_lor_clicked(self, source=None, event=None): self.increment('lor', False) self.init_param_widgets() def on_add_voi_clicked(self, source=None, event=None): self.increment('voi', True) self.init_param_widgets() def on_rem_voi_clicked(self, source=None, event=None): self.increment('voi', False) self.init_param_widgets() def on_add_lin_clicked(self, source=None, event=None): self.increment('lin', True) self.init_param_widgets() def on_rem_lin_clicked(self, source=None, event=None): self.increment('lin', False) self.init_param_widgets() def get_data(self, source=None, event=None): self.cmode_radio_off.set_active(True) self.toggle_copy_mode(self.cmode_radio_off) # reset column indices self.xcol_idx = 0 self.column_entry_x.set_text(str(self.xcol_idx)) self.ycol_idx = 1 self.column_entry_y.set_text(str(self.ycol_idx)) # open file dialog self.dialog = Gtk.FileChooserDialog( title='Import data file...', parent=self.main_window, action=Gtk.FileChooserAction.OPEN, ) self.dialog.add_buttons( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK ) filter_csv = Gtk.FileFilter() filter_csv.set_name('.csv files') filter_csv.add_mime_type('text/csv') self.dialog.add_filter(filter_csv) filter_any = Gtk.FileFilter() filter_any.set_name('All files') filter_any.add_pattern("*") self.dialog.add_filter(filter_any) self.dialog.set_default_size(800, 400) response = self.dialog.run() if response == Gtk.ResponseType.OK: self.file_name = self.dialog.get_filename() try: df = tools.to_df( self.file_name, sep=self.sep, header=self.header, index_col=self.index_col, skiprows=self.skiprows, dtype=self.dtype, encoding=self.encoding ) df.iloc[:, self.xcol_idx] df.iloc[:, self.ycol_idx] except Exception: self.statusbar.push( self.statusbar.get_context_id('import_error'), file_import_error_msg ) self.dialog.destroy() return else: self.file_name = None self.statusbar.push( self.statusbar.get_context_id('import_canceled'), 'Import canceled.' ) self.dialog.destroy() return self.dialog.destroy() self.data = df self.display_data() self.result = None # reset x, y, and xlim self.x = self.data.iloc[:, self.xcol_idx].values self.y = self.data.iloc[:, self.ycol_idx].values self.filter_nan() self.set_xlims() self.plot() self.statusbar.push( self.statusbar.get_context_id('import_finished'), 'Imported {}'.format(self.file_name) ) def display_data(self): # remove any pre-existing columns from treeview for col in self.data_treeview.get_columns(): self.data_treeview.remove_column(col) # create model # TODO: allow for other types, and infer from data col_types = [float for col in self.data.columns] list_store = Gtk.ListStore(*col_types) # fill model with data for row in self.data.itertuples(): list_store.append( [row[i+1] for i, col in enumerate(self.data.columns)] ) # set it as TreeView model self.data_treeview.set_model(list_store) # Create and append columns for i, col in enumerate(self.data.columns): renderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn(col, renderer, text=i) self.data_treeview.append_column(column) self.fname_buffer.set_text('Source: {}'.format(self.file_name)) self.fname_textview.set_buffer(self.fname_buffer) def export_data(self, source=None, event=None): ''' Export fit data and parameters to .csv ''' self.file_export_dialog = Gtk.FileChooserDialog( title='Export results file...', parent=self.main_window, action=Gtk.FileChooserAction.SAVE, ) self.file_export_dialog.add_buttons( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK ) filter_csv = Gtk.FileFilter() filter_csv.set_name('.csv files') filter_csv.add_mime_type('text/csv') self.file_export_dialog.add_filter(filter_csv) response = self.file_export_dialog.run() if response == Gtk.ResponseType.OK: export_filename = self.file_export_dialog.get_filename() if export_filename.find('.csv') == -1: export_filename += '.csv' self.process_results() self.curves_df.to_csv(export_filename) self.params_df.to_csv( '{}.params.csv'.format( export_filename[:export_filename.find('.csv')] ) ) self.statusbar.push( self.statusbar.get_context_id('export_results'), 'Exported: {}'.format(export_filename) ) else: self.statusbar.push( self.statusbar.get_context_id('export_canceled'), 'Export canceled.' ) self.file_export_dialog.hide() def process_results(self): if self.result is not None: self.params_df = pd.DataFrame.from_dict( self.result.best_values, orient='index' ) self.params_df.index.name = 'parameter' self.params_df.columns = ['value'] curves_dict = { 'data': self.y, 'total_fit': self.result.best_fit, } components = self.result.eval_components() for i, comp in enumerate(components): curves_dict[comp[:comp.find('_')]] = components[comp] self.curves_df = pd.DataFrame.from_dict(curves_dict) self.curves_df.index = self.x self.curves_df.index.name = self.data.columns[self.xcol_idx] else: self.statusbar.push( self.statusbar.get_context_id('no_fit_results'), 'No fit results to export!' ) self.file_export_dialog.hide() def get_column_index(self, source=None, event=None): # make sure user enters index that can be converted to int try: idx_x = int(self.column_entry_x.get_text()) except ValueError: self.statusbar.push( self.statusbar.get_context_id('idx_type_error'), idx_type_error_msg ) self.column_entry_x.set_text('') return try: idx_y = int(self.column_entry_y.get_text()) except ValueError: self.statusbar.push( self.statusbar.get_context_id('idx_type_error'), idx_type_error_msg ) self.column_entry_y.set_text('') return self.xcol_idx = idx_x self.ycol_idx = idx_y self.result = None # make sure user enters an index that's in the data range try: self.x = self.data.iloc[:, self.xcol_idx] except IndexError: self.statusbar.push( self.statusbar.get_context_id('idx_range_error'), idx_range_error_msg ) self.column_entry_x.set_text(None) return try: self.y = self.data.iloc[:, self.ycol_idx] except IndexError: self.statusbar.push( self.statusbar.get_context_id('idx_range_error'), idx_range_error_msg ) self.column_entry_y.set_text(None) return self.xmin = np.min(self.x) self.xmax = np.max(self.x) self.statusbar.push( self.statusbar.get_context_id('new_idx_success'), 'Column Index (X) = ' + str(self.xcol_idx) + ', ' + 'Column Index (Y) = ' + str(self.ycol_idx) ) self.plot() def run_settings_dialog(self, source=None, event=None): ''' Opens the settings dialog window and controls its behavior. ''' # set label text for help button # couldn't do this in glade for some reason... import_help_button = self.builder.get_object('import_help_button') import_help_button.set_label('help') fit_help_button = self.builder.get_object('fit_help_button') fit_help_button.set_label('help') # run the dialog response = self.settings_dialog.run() if response == Gtk.ResponseType.APPLY: self.sep = self.sep_entry.get_text() if self.header_entry.get_text() != 'infer': self.header = int(self.header_entry.get_text()) else: self.header = 'infer' if self.skiprows_entry.get_text() == 'None': self.skiprows = None else: self.skiprows = int(self.skiprows_entry.get_text()) if self.dtype_entry.get_text() == 'None': self.dtype = None else: self.dtype = self.dtype_entry.get_text() if self.encoding_entry.get_text() == 'None': self.encoding = None else: self.encoding = self.encoding_entry.get_text() if self.fit_method_entry.get_text() != 'least_squares': self.fit_method = self.fit_method_entry.get_text() else: self.fit_method = 'least_squares' else: self.settings_dialog.hide() self.settings_dialog.hide() def toggle_copy_mode(self, button): state_messages = { 0: 'Copy mode off', 1: 'Copy mode on | x-value', 2: 'Copy mode on | y-value', } if button.get_active(): if button.get_label() == 'x': self.cmode_state = 1 self.mpl_cursor = Cursor( self.axis, lw=1, c='red', linestyle='--' ) self.cid = self.canvas.mpl_connect( 'button_press_event', self.get_coord_click ) elif button.get_label() == 'y': self.cmode_state = 2 self.mpl_cursor = Cursor( self.axis, lw=1, c='red', linestyle='--' ) self.cid = self.canvas.mpl_connect( 'button_press_event', self.get_coord_click ) else: # copy mode off self.cmode_state = 0 self.mpl_cursor = None self.canvas.mpl_disconnect(self.cid) self.statusbar.push( self.statusbar.get_context_id('cmode_state'), state_messages[self.cmode_state] ) def get_coord_click(self, event): x_copy, y_copy = str(round(event.xdata, 3)), str(round(event.ydata, 3)) if self.cmode_state == 1: self.clipboard.set_text(x_copy, -1) self.statusbar.push( self.statusbar.get_context_id('copied_x'), 'Copied X=' + str(x_copy) + ' to clipboard!' ) if self.cmode_state == 2: self.clipboard.set_text(y_copy, -1) self.statusbar.push( self.statusbar.get_context_id('copied_y'), 'Copied Y=' + str(y_copy) + ' to clipboard!' ) def add_accelerator(self, widget, accelerator, signal="activate"): ''' Adds keyboard shortcuts ''' if accelerator is not None: key, mod = Gtk.accelerator_parse(accelerator) widget.add_accelerator( signal, self.accelerators, key, mod, Gtk.AccelFlags.VISIBLE )
class AppCanvas(): def __init__(self): self.curvesCounter = 0 self.activeCurve = None self.pointOfRotation = None self.getScaleWidget = None self.getAngleWidget = None fig = plt.figure( figsize=[9, 6], dpi=100, ) self.ax = fig.add_subplot() #plt.axis([0,300,0,200],'scaled') plt.axis([0, 300, 0, 200]) self.canvas = FigureCanvas(fig) def get_canvas(self): return self.canvas def get_ax(self): return self.ax def set_activeCurve(self, activeCurve): self.activeCurve = activeCurve def set_getScaleWidget(self, getScaleWidget): self.getScaleWidget = getScaleWidget def set_getAngleWidget(self, getAngleWidget): self.getAngleWidget = getAngleWidget def pick_curve(self, event): lineName = event.artist.get_label() if lineName == self.activeCurve.linePlot.get_label(): self.drag_curve_active = self.canvas.mpl_connect( 'motion_notify_event', self.drag_curve) self.drop_curve_active = self.canvas.mpl_connect( 'button_release_event', self.drop_curve) self.mouseX = event.mouseevent.xdata self.mouseY = event.mouseevent.ydata #self.activeCurve.set_working_accurancy() def drop_curve(self, event): if self.drag_curve_active != None: if event.inaxes != None: self.activeCurve.move_curve(event.xdata - self.mouseX, event.ydata - self.mouseY) self.canvas.mpl_disconnect(self.drag_curve_active) self.canvas.mpl_disconnect(self.drop_curve_active) #self.activeCurve.set_normal_accurancy() self.canvas.draw_idle() self.drag_curve_active = None self.drop_curve_active = None def drag_curve(self, event): if event.inaxes != None: self.activeCurve.move_curve(event.xdata - self.mouseX, event.ydata - self.mouseY) self.mouseX = event.xdata self.mouseY = event.ydata self.canvas.draw_idle() def resize_curve(self, event): scale = self.getScaleWidget.get_scale_value() self.getScaleWidget.reset_scale_value() if self.activeCurve != None: self.activeCurve.resize_curve(scale / 100.0) self.canvas.draw_idle() def change_line_width(self, event): scale = self.getScaleWidget.get_scale_value() self.getScaleWidget.reset_scale_value() if self.activeCurve != None: self.activeCurve.change_line_width(scale / 100.0) self.canvas.draw_idle() def change_curve(self, event): scale = self.getScaleWidget.get_scale_value() self.getScaleWidget.reset_scale_value() if self.activeCurve != None: self.activeCurve.resize_curve(scale / 100.0) self.canvas.draw_idle() def rotate_curve(self, event): angle = self.getAngleWidget.get_entry_text() if self.pointOfRotation != None: s = self.pointOfRotation[0].get_xdata()[0] t = self.pointOfRotation[0].get_ydata()[0] else: s, t = 0, 0 try: if self.activeCurve != None and int(angle) < 360 and int( angle) > -360: angle = int(angle) if angle < 0: angle += 360 self.activeCurve.rotate_curve(angle, s, t) self.canvas.draw_idle() except: pass def add_point_of_rotation(self, event): self.delete_point_of_rotation() self.pointOfRotation = self.ax.plot([event.xdata], [event.ydata], 'ko') self.canvas.draw_idle() def delete_point_of_rotation(self): if self.pointOfRotation != None: self.pointOfRotation[0].remove() del self.pointOfRotation self.pointOfRotation = None self.canvas.draw_idle() def select_point(self, event): lineName = event.artist.get_label() if lineName == self.activeCurve.pointsPlot.get_label(): self.activeCurve.activate_point(event.mouseevent.xdata, event.mouseevent.ydata) self.canvas.draw_idle() def delete_point(self, event): lineName = event.artist.get_label() if lineName == self.activeCurve.pointsPlot.get_label(): self.activeCurve.delete_point(event.mouseevent.xdata, event.mouseevent.ydata) self.canvas.draw_idle() def add_point(self, event): if self.activeCurve != None and event.inaxes != None: self.activeCurve.add_point(event.xdata, event.ydata) self.canvas.draw_idle() def pick_point(self, event): self.drag_point_active = self.canvas.mpl_connect( 'motion_notify_event', self.drag_point) self.activeCurve.set_working_accurancy() def drop_point(self, event): self.canvas.mpl_disconnect(self.drag_point_active) if self.activeCurve != None: self.activeCurve.disactivate_point() self.activeCurve.set_normal_accurancy() self.canvas.draw_idle() def drag_point(self, event): if event.inaxes != None: self.activeCurve.move_point(event.xdata, event.ydata) self.canvas.draw_idle()
class MyWindow(Gtk.Window): @timing def __init__(self): Gtk.Window.__init__(self, title="Echelle Reduction GUI") self.set_default_size(1000, 800) self.figure = Figure(figsize=(5,7), dpi=100) self.plot_1D = self.figure.add_subplot(212) self.plot_2D = self.figure.add_subplot(232) self.plot_PHD = self.figure.add_subplot(231) self.plot_orders = self.figure.add_subplot(233) self.plot_orders.tick_params(axis='both', labelsize=6) self.plot_orders.set_title("Orders") self.plot_2D.tick_params(axis='both', labelsize=7) self.plot_2D.set_title("2D Raw Data") self.plot_1D.set_title("1D Extracted Data") self.plot_1D.set_xlabel('pixels') self.plot_1D.set_ylabel('intensity') self.plot_1D.tick_params(axis='both', labelsize=7) self.plot_PHD.set_title('Pulse Height Data') self.plot_PHD.tick_params(axis='both', labelsize=7) self.canvas = FigureCanvas(self.figure) menubar = Gtk.MenuBar() menubar_file = Gtk.MenuItem("File") filemenu = Gtk.Menu() menubar_file.set_submenu(filemenu) menubar.append(menubar_file) filemenu_open = Gtk.MenuItem("Open") filemenu_open.connect('activate', self.open_file_dialog) filemenu.append(filemenu_open) filemenu.append(Gtk.SeparatorMenuItem()) filemenu_save = Gtk.MenuItem("Save Orders") filemenu_save.connect('activate', self.on_filemenu_save_clicked) filemenu.append(filemenu_save) filemenu_load = Gtk.MenuItem("Load Orders") filemenu_load.connect('activate', self.on_filemenu_load_clicked) filemenu.append(filemenu_load) filemenu.append(Gtk.SeparatorMenuItem()) filemenu_exit = Gtk.MenuItem('Exit') filemenu_exit.connect('activate', Gtk.main_quit) filemenu.append(filemenu_exit) menubox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) menubox.pack_start(menubar, False, False, 0) toolbar = NavigationToolbar(self.canvas, self) main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.add(main_box) self.statusbar = Gtk.Statusbar() self.context_id = self.statusbar.get_context_id("stat bar example") self.statusbar.push(0, 'Please Open 2D Fits Data File') hbutton_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self.count_rate_button = Gtk.ToggleButton(label='Raw count rate') self.count_rate_button.connect("toggled", self.on_count_rate_button_clicked, self.context_id) self.filter_phd_button = Gtk.Button('Filter PHD') self.filter_phd_button.connect("clicked", self.on_filter_phd_button_clicked, self.context_id) self.gauss_fit_button = Gtk.ToggleButton(label='Fit 1D Gauss') self.gauss_fit_button.set_active(False) self.gauss_fit_button.connect("toggled", self.on_gauss_fit_button_clicked, self.context_id) self.remove_orders_button = Gtk.ToggleButton(label='Remove Orders') self.remove_orders_button.connect("toggled", self.on_remove_orders_button_clicked, self.context_id) self.add_orders_button = Gtk.ToggleButton(label='Add Orders') self.add_orders_button.connect("toggled", self.on_add_orders_button_clicked, self.context_id) self.buttonbg = Gtk.Button('Airglow') self.buttonbg.connect('clicked', self.buttonbg_clicked, self.context_id) self.recalculate_button = Gtk.Button('Recalculate') self.recalculate_button.connect('clicked', self.on_recalculate_button_clicked, self.context_id) hbutton_box.pack_start(self.count_rate_button, True, True, 0) hbutton_box.pack_start(self.filter_phd_button, True, True, 0) hbutton_box.pack_start(self.gauss_fit_button, True, True, 0) hbutton_box.pack_start(self.remove_orders_button, True, True, 0) hbutton_box.pack_start(self.add_orders_button, True, True, 0) hbutton_box.pack_start(self.buttonbg, True, True, 0) hbutton_box.pack_start(self.recalculate_button, True, True, 0) main_box.pack_start(menubox, False, False, 0) main_box.pack_start(toolbar, False, False, 0) main_box.pack_start(self.canvas, True, True, 0) main_box.pack_start(hbutton_box, False, False, 0) main_box.pack_start(self.statusbar, False, False, 0) #~ self.filename = './2014-03-14-185937.fits' #~ self.open_file(self.filename) @timing def open_file_dialog(self, widget): dialog = Gtk.FileChooserDialog( "Please Choose a File", self, Gtk.FileChooserAction.OPEN, ( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK ) ) dialog.set_default_response(Gtk.ResponseType.OK) filter = Gtk.FileFilter() filter.set_name('fits Files') filter.add_mime_type('fits') filter.add_pattern('*.fits') dialog.add_filter(filter) response = dialog.run() if response == Gtk.ResponseType.OK : self.filename = dialog.get_filename() self.statusbar.push(0, 'Opened File: ' + self.filename) dialog.destroy() del dialog self.open_file(self.filename) elif response == Gtk.ResponseType.CANCEL: dialog.destroy() del dialog @timing def open_file(self, filename): hdulist = fits.open(filename) self.photon_list = hdulist[1].data hdulist.close() # Init for new file self.orders = [] self.science(self.photon_list) @timing def science(self, photon_list, min_phd=0, max_phd=255, orders=[]): self.recalculate_button.set_sensitive(False) self.min_phd = min_phd self.max_phd = max_phd PHD = np.array(photon_list['PHD']) photon_list_filtered = photon_list[(PHD >= min_phd) & (PHD <= max_phd)] image, xedges, yedges = np.histogram2d(photon_list_filtered['X'], photon_list_filtered['Y'], bins=2048, range=[[0,8192],[0,8192]]) self.update_2D_plot(image) # Collapse 2D data into 1D to view orders as peaks peaks = [] for i in range(0, len(image[0])): # sums ith row peaks.append(np.sum(image[i,:])) # Smooth peaks by convolving with a boxcar boxcar = np.zeros(300) boxcar[125:175] = 1.0 smooth = np.convolve(peaks, boxcar, mode='same') #~ self.peaks_smoothed = peaks / smooth self.peaks_smoothed = (peaks/max(peaks)) - (smooth/max(smooth)/2) # Only find orders if there are no orders yet, eg. first run if not self.orders and not orders: print 'Finding peaks...' # Requires scipy version .11.0 or greater self.orders = signal.find_peaks_cwt(self.peaks_smoothed, np.arange(10, 20)) elif orders: print 'Using premade peaks list...' self.orders = orders # Plot 1D data with lines through the centers of the # orders to double check how the peak finder did self.update_orders_plot(self.peaks_smoothed, self.orders) # self.update_PHD_plot(PHD, min_phd, max_phd) self.dragbox = [] ### extraction of orders ### # find the widths of the orders (difference between peaks) peak_widths = [] for i in range(1, len(self.orders)): peak_widths.append(self.orders[i] - self.orders[i - 1]) # Double last entry to make peak_widths the right length peak_widths.append(peak_widths[-1]) # Add up spectrum lines from 2D plot where y coord is an order +/-FWHM # Remember, peak_widths[0] is orders[1]-orders[0]. FWHM = 0.63 # full width half max spectrum = [] for i in range(len(self.orders)): peak = int(self.orders[i]) #~ width = int(peak_widths[i]*FWHM) width = 1 for j in range(0, width): # Find chord length of circular image r = len(image[0])/2 start = int(r - (peak * (2*r - peak))**0.5) end = int(2 * r - start) for k in range(start, end): spectrum.append(image[peak-width/2+j][k]) self.update_1D_plot(spectrum) self.statusbar.push(0, 'Done! ' + self.filename) @timing def update_2D_plot(self, image): self.plot_2D.cla() self.plot_2D.tick_params(axis='both', labelsize=7) self.plot_2D.set_title("2D Raw Data") max = np.amax(image) self.plot_2D.imshow(image, norm=LogNorm(vmin=0.1, vmax=max/2), origin='lower') self.canvas.draw() @timing def update_orders_plot(self, peaks, orders): self.plot_orders.cla() self.plot_orders_line, = self.plot_orders.plot([],[]) self.plot_orders.tick_params(axis='both', labelsize=6) self.plot_orders.set_title("Orders") self.plot_orders_line.set_xdata(peaks) self.plot_orders_line.set_ydata(np.arange(len(peaks))) self.plot_orders.hlines(orders, min(peaks), max(peaks), color='purple') self.plot_orders.relim() self.plot_orders.autoscale_view(True, True, True) self.canvas.draw() # Seems a max of 18980 x values are supported by Cairo. Since # we have more than the max we have to reduce the spectrum # resolution to fit. # Maybe do this dynamically based on xlim()? # xmin, xmax = xlim() # return the current xlim @timing def update_1D_plot(self, spectrum): self.plot_1D.cla() self.plot_1D_line, = self.plot_1D.plot([],[]) self.plot_1D.set_title("1D Extracted Data") self.plot_1D.set_xlabel('pixels') self.plot_1D.set_ylabel('intensity') self.plot_1D.tick_params(axis='both', labelsize=7) MAX = 18980 #16384 # 2^14 scale = int(len(spectrum)/MAX) + 1 print len(spectrum), scale, len(spectrum)/scale # When we get wavelength calibration, change this line to #~ x = np.linspace(minWL, maxWL, num = len(spectrum)/scale) x = np.arange(0, len(spectrum), scale) spectrum = [np.sum(spectrum[i:i+scale]) for i in x] self.plot_1D_line.set_xdata(x) self.plot_1D_line.set_ydata(spectrum) self.plot_1D.relim() self.plot_1D.autoscale_view(True, True, True) self.canvas.draw() @timing def update_PHD_plot(self, PHD, min_phd, max_phd): self.plot_PHD.cla() self.plot_PHD.set_title('Pulse Height Data') self.plot_PHD.tick_params(axis='both', labelsize=7) self.plot_PHD.axvline(x=min_phd, color='purple') self.plot_PHD.axvline(x=max_phd, color='purple') self.plot_PHD.hist(PHD, bins=256, range=[0,255], histtype='stepfilled') self.plot_PHD.relim() self.plot_PHD.autoscale_view(True, True, True) self.canvas.draw() ## airglow button ## def buttonbg_clicked(self, widget, data): dialog = Gtk.FileChooserDialog("Please Choose Airglow File", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_default_response(Gtk.ResponseType.OK) filter = Gtk.FileFilter() filter.set_name('fits Files') filter.add_mime_type('fits') filter.add_pattern('*.fits') dialog.add_filter(filter) response = dialog.run() if response == Gtk.ResponseType.OK: self.airglow_filename = dialog.get_filename() #~ global _AirglowFile #~ _AirglowFile = fname #print "open file" + fname #self.statusbar.push(0,'Opened File:' + fname) dialog.destroy() self.open_airglow_file(self.airglow_filename) elif response == Gtk.ResponseType.CANCEL: dialog.destroy() # opening aiglow fits file def open_airglow_file(self, airglow_filename): #this opens up the fits file hdulist = fits.open(airglow_filename) ## this line will need to be used for real data targname = hdulist[0].header['targname'] #targname = self.targname ## but for now #~ targname = 'HD128627J' # this picks out the actual data from fits file, and turns it into numpy array self.glowdata = hdulist[0].data hdulist.close() # simple subtraction of aurglow image from science image scidata = self.scidata - self.glowdata self.science(scidata, targname) ## gauss fitting button def on_gauss_fit_button_clicked(self, widget, data): self.statusbar.push(data,'Ready to fit. Click on both sides of the emission feature you wish to fit') self.xdata = [] def onclick(event): if self.gauss_fit_button.get_active(): self.xdata.append(event.xdata) self.statusbar.push(data, 'one more click...') if len(self.xdata) == 2: self.statusbar.push(data, 'Ready to fit. Click on both sides of the emission feature you wish to fit') xdata = self.xdata self.gauss_fit(xdata) # mouse click event on 1d cid = self.canvas.mpl_connect('button_press_event', onclick) if [self.gauss_fit_button.get_active()] == [False]: self.statusbar.push(0, 'Opened File:' + File) ### gauss fitting ### def gauss_fit(self, xdata): x = list(self.x) xg = [] xg.append(int(xdata[0])) xg.append(int(xdata[1])) xg1 = min(xg) xg2 = max(xg) xgauss = x[xg1:xg2] ygauss = self.odo[xg1:xg2] right = ygauss[len(xgauss)-4:len(xgauss)] left = ygauss[0:4] # background subtraction averight = sum(right) / len(right) aveleft = sum(left) / len(left) bg_y = [averight, aveleft] rightx = xgauss[len(xgauss)-4:len(xgauss)] leftx = xgauss[0:4] averightx = sum(rightx) / len(rightx) aveleftx = sum(leftx) / len(leftx) bg_x = [averightx, aveleftx] m,b = np.polyfit(bg_x, bg_y, 1) slopex = [i * m for i in xgauss] bg_fit = slopex + b # making a model gauss def gauss(xgauss, MAX, mu, sigma): return MAX * np.exp(-(xgauss - mu)**2 / (2.0 * sigma**2)) avex = sum(xgauss) / len(xgauss) guess = [1.0, avex, 1.0] # plugging in model to matplotlibs curve_fit() coeff, var_matrix = curve_fit(gauss, xgauss, ygauss-bg_fit, p0=guess) fit = gauss(xgauss, *coeff) sigma = coeff[2] FWHM = sigma * 2 * np.sqrt(2 * np.log(2)) FWHM = round(FWHM, 2) fitplot = plt.plot(xgauss, ygauss, color='k') plt.plot(xgauss, fit + bg_fit, color='b', linewidth=1.5) xpos = xgauss[0] + 0.01 * (coeff[1] - xgauss[0]) strFWHM = str(FWHM) plt.text(xpos, 0.9 * max(ygauss), 'FWHM = ' + strFWHM + '', color='purple', fontweight='bold') center = str(round(coeff[1], 2)) plt.text(xpos, 0.95 * max(ygauss), 'Center = ' + center + '', color='green', fontweight='bold') plt.plot(xgauss, bg_fit, 'r--') plt.show() self.xdata = [] ### count rate button def on_count_rate_button_clicked(self, widget, data): if self.count_rate_button.get_active(): self.statusbar.push(data,'Use zoom feature in navigation bar to select count rate region') else: self.statusbar.push(0, 'Opened File:' + self.filename) dragbox = [] def onclick(event): if self.count_rate_button.get_active(): dragbox.append(event.xdata) dragbox.append(event.ydata) def offclick(event): if self.count_rate_button.get_active(): dragbox.append(event.xdata) dragbox.append(event.ydata) self.count_rate(dragbox, data) self.canvas.mpl_connect('button_press_event', onclick) self.canvas.mpl_connect('button_release_event', offclick) def count_rate(self, dragbox, data): # fake exposure time in seconds datafake = './chesstest.fits' hdu = fits.open(datafake) exptime = hdu[0].header['EXPOSURE'] dragbox = [int(x) for x in dragbox] cntbox = self.scidata[ dragbox[0]:dragbox[2], 1024-dragbox[1]:1024-dragbox[3] ] totpix = np.size(cntbox) cntrate = np.sum(cntbox) / exptime totpix = str(totpix) cntrate = str(cntrate) self.statusbar.push(data, 'count rate in box = ' + cntrate + ' cnt/sec, pixels in box = ' + totpix + '') return cntrate @timing def on_filter_phd_button_clicked(self, widget, data): self.phd_window = Gtk.MessageDialog(image = None) self.phd_window.set_size_request(500, 100) self.phd_window.move(400, 300) #self.phd_window.connect("delet_event",lambda w,e:) #~ self.phd_window.connect("delete-event", self.phd_window.destroy) mainbox = self.phd_window.get_content_area() thebox = Gtk.HBox(False, 0) label = Gtk.Label("Keep PHD between") label2 = Gtk.Label('and') label.show() label2.show() self.ok_button = Gtk.Button('Okay') self.ok_button.connect('clicked', self.phd_entry_button_clicked) self.min_phd_entry = Gtk.Entry() self.min_phd_entry.set_activates_default(True) self.max_phd_entry = Gtk.Entry() #~ self.max_phd_entry.set_activates_default(True) self.min_phd_entry.show() self.max_phd_entry.show() self.ok_button.show() thebox.pack_start(label,False,False,0) thebox.pack_start(self.min_phd_entry,False,False,0) thebox.pack_start(label2,False,False,0) thebox.pack_start(self.max_phd_entry,False,False,0) mainbox.pack_start(thebox,True,True,0) mainbox.pack_start(self.ok_button,True,False,0) mainbox.show() thebox.show() self.phd_window.show() @timing def phd_entry_button_clicked(self, widget): min_phd = int(self.min_phd_entry.get_text()) max_phd = int(self.max_phd_entry.get_text()) self.phd_window.destroy() self.statusbar.push(0, 'Filtering by: ' + str(min_phd) + ' < PHD < ' + str(max_phd)) self.orders = [] self.science(self.photon_list, min_phd, max_phd) @timing def on_add_orders_button_clicked(self, widget, data): if [self.add_orders_button.get_active()] == [True]: self.remove_orders_button.set_sensitive(False) self.statusbar.push(data, 'Click where to add an order.') def onclick_peak(event): if self.add_orders_button.get_active(): self.add_order(event.ydata) self.cid_add = self.canvas.mpl_connect('button_press_event', onclick_peak) else: self.canvas.mpl_disconnect(self.cid_add) del self.cid_add self.remove_orders_button.set_sensitive(True) self.statusbar.push(0, 'Opened File:' + self.filename) @timing def add_order(self, ydata): order = ydata print 'Number of original orders', len(self.orders) self.orders.append(order) self.orders.sort() self.update_orders_plot(self.peaks_smoothed, self.orders) self.recalculate_button.set_sensitive(True) print 'Corrected number of orders', len(self.orders) @timing def on_remove_orders_button_clicked(self, widget, data): if [self.remove_orders_button.get_active()] == [True]: self.add_orders_button.set_sensitive(False) self.statusbar.push(data, 'Click on the order to remove.') def onclick_order(event): if self.remove_orders_button.get_active(): self.remove_order(event.ydata) self.cid_remove = self.canvas.mpl_connect('button_press_event', onclick_order) else: self.canvas.mpl_disconnect(self.cid_remove) del self.cid_remove self.add_orders_button.set_sensitive(True) self.statusbar.push(0, 'Opened File:' + self.filename) @timing def remove_order(self, ydata): order = min(self.orders, key=lambda x:abs(x - ydata)) print 'Number of original orders', len(self.orders) self.orders.remove(order) self.update_orders_plot(self.peaks_smoothed, self.orders) self.recalculate_button.set_sensitive(True) print 'Corrected number of orders', len(self.orders) def on_recalculate_button_clicked(self, widget, data): self.science(self.photon_list, self.min_phd, self.max_phd) def on_filemenu_save_clicked(self, widget): now = datetime.datetime.now() date = now.strftime("%Y_%m_%d_%H_%M_%S") temp = self.orders, self.min_phd, self.max_phd pickle.dump(temp, open(self.filename[:-5] + '_1D_' + date + '.orders', 'wb')) self.statusbar.push(0, 'Saved Orders: ' + self.filename[:-5] + '_1D_' + date + '.orders') def on_filemenu_load_clicked(self, widget): dialog = Gtk.FileChooserDialog( "Please Choose a File", self, Gtk.FileChooserAction.OPEN, ( Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK ) ) dialog.set_default_response(Gtk.ResponseType.OK) filter = Gtk.FileFilter() filter.set_name('orders Files') filter.add_mime_type('orders') filter.add_pattern('*.orders') dialog.add_filter(filter) response = dialog.run() if response == Gtk.ResponseType.OK : filename = dialog.get_filename() self.statusbar.push(0, 'Loaded Orders: ' + filename) dialog.destroy() del dialog orders, min_phd, max_phd = pickle.load(open(filename, 'rb')) self.science(self.photon_list, min_phd, max_phd, orders) elif response == Gtk.ResponseType.CANCEL: dialog.destroy() del dialog
def __init__(self, sigs={}, fig=None, title='', uidir=''): oscopy.Figure.__init__(self, None, fig) self.to_figure = [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, self.TARGET_TYPE_SIGNAL)] self.hadjpreval = None self.vadjpreval = None self.current_axes = None # The canvas for the Figure canvas = FigureCanvas(self) canvas.supports_blit = False canvas.mpl_connect('button_press_event', self.button_press) canvas.mpl_connect('scroll_event', self.mouse_scroll) canvas.mpl_connect('axes_enter_event', self.axes_enter) canvas.mpl_connect('axes_leave_event', self.axes_leave) canvas.mpl_connect('figure_enter_event', self.figure_enter) canvas.mpl_connect('figure_leave_event', self.figure_leave) canvas.mpl_connect('key_press_event', self.key_press) canvas.mpl_connect('motion_notify_event', self.show_coords) self.canvas = canvas self.draw_hid = canvas.mpl_connect('draw_event', self.update_scrollbars) # The GtkBuilder builder = Gtk.Builder() builder.add_from_file('/'.join((uidir, IOSCOPY_GTK_FIGURE_UI))) self.builder = builder self.uidir = uidir # The window w = builder.get_object('w') w.set_title(title) w.drag_dest_set(Gtk.DestDefaults.ALL, self.to_figure, Gdk.DragAction.COPY) # Init the store for the combo box store = builder.get_object('store') iter = store.append([_('All Graphs'), False, True, False, Gtk.Adjustment(), Gtk.Adjustment()]) for i in range(4): iter = store.append([_('Graph %d') % (i + 1), False, True if i < len(self.graphs) else False, False, Gtk.Adjustment(), Gtk.Adjustment()]) self.cbx_store = store # The Graph Combobox graphs_cbx = builder.get_object('graphs_cbx') graphs_cbx.set_active(0) # Add remaining widgets builder.get_object('box').pack_start(canvas, True, True, 0) # Expose widgets needed elsewhere self.window = w self.hbar = builder.get_object('hbar') self.vbar = builder.get_object('vbar') self.coords_lbl1 = builder.get_object('coord_lbl1') self.coords_lbl2 = builder.get_object('coord_lbl2') self.graphs_cbx = graphs_cbx self.store = store self.mpsel_get_act = [builder.get_object('rb%d' % (b + 1)).get_active for b in range(4)] self.mpsel_set_act = [builder.get_object('rb%d' % (b + 1)).set_active for b in range(4)] self.mpsel_set_sens = [builder.get_object('rb%d' % (b + 1)).set_sensitive for b in range(4)] self.window.show_all() # Actions a = Gio.SimpleAction.new('set_range', GLib.VariantType.new('t')) a.connect('activate', self.set_range_activated) self.window.add_action(a) a = Gio.SimpleAction.new('set_units', GLib.VariantType.new('t')) a.connect('activate', self.set_units_activated) self.window.add_action(a) a = Gio.SimpleAction.new_stateful('set_scale', GLib.VariantType.new('(ts)'), GLib.Variant.new_string('lin')) a.connect('activate', self.set_scale_activated) self.window.add_action(a) a = Gio.SimpleAction.new('remove_signal', GLib.VariantType.new('(ts)')) a.connect('activate', self.remove_signal_activated) self.window.add_action(a) # Connect additional GTK signals cbmap = {'span_toggle_btn_toggled': self.span_toggle_btn_toggled, 'x10_toggle_btn_toggled': self.x10_toggle_btn_toggled, 'hadj_pressed': self.hadj_pressed, 'hadj_released': self.hadj_released, 'hscroll_change_value': self.hscroll_change_value, 'vadj_pressed': self.vadj_pressed, 'vadj_released': self.vadj_released, 'vscroll_change_value': self.vscroll_change_value, 'disable_adj_update_on_draw': self.disable_adj_update_on_draw, 'enable_adj_update_on_draw': self.enable_adj_update_on_draw, 'update_scrollbars': self.update_scrollbars, 'save_fig_btn_clicked': self.save_fig_btn_clicked, 'delete_event_cb': lambda w, e: w.hide() or True, } builder.connect_signals(cbmap) graphs_cbx.connect('changed', self.graphs_cbx_changed, (builder.get_object('x10_toggle_btn'), builder.get_object('span_toggle_btn'), store)) # Add signals if sigs: self.add(sigs) # # Update canvas for SpanSelector of Graphs for gr in self.graphs: if hasattr(gr, 'span'): gr.span.new_axes(gr)
class CampaignGraph(object): """ A basic graph provider for using :py:mod:`matplotlib` to create graph representations of campaign data. This class is meant to be subclassed by real providers. """ title = 'Unknown' """The title of the graph.""" _graph_id = None table_subscriptions = [] """A list of tables from which information is needed to produce the graph.""" def __init__(self, config, parent, size_request=None): """ :param dict config: The King Phisher client configuration. :param parent: The parent window for this object. :type parent: :py:class:`Gtk.Window` :param tuple size_request: The size to set for the canvas. """ self.config = config """A reference to the King Phisher client configuration.""" self.parent = parent """The parent :py:class:`Gtk.Window` instance.""" self.figure, ax = pyplot.subplots() self.axes = self.figure.get_axes() self.canvas = FigureCanvas(self.figure) self.manager = None if size_request: self.canvas.set_size_request(*size_request) self.canvas.mpl_connect('button_press_event', self.mpl_signal_canvas_button_pressed) self.canvas.show() self.navigation_toolbar = NavigationToolbar(self.canvas, self.parent) self.navigation_toolbar.hide() self.popup_menu = Gtk.Menu.new() menu_item = Gtk.MenuItem.new_with_label('Export') menu_item.connect('activate', self.signal_activate_popup_menu_export) self.popup_menu.append(menu_item) menu_item = Gtk.MenuItem.new_with_label('Refresh') menu_item.connect('activate', lambda action: self.refresh()) self.popup_menu.append(menu_item) menu_item = Gtk.CheckMenuItem.new_with_label('Show Toolbar') menu_item.connect('toggled', self.signal_toggled_popup_menu_show_toolbar) self.popup_menu.append(menu_item) self.popup_menu.show_all() @classmethod def get_graph_id(klass): """ The graph id of an exported :py:class:`.CampaignGraph`. :param klass: The class to return the graph id of. :type klass: :py:class:`.CampaignGraph` :return: The id of the graph. :rtype: int """ return klass._graph_id def make_window(self): """ Create a window from the figure manager. :return: The graph in a new, dedicated window. :rtype: :py:class:`Gtk.Window` """ if self.manager == None: self.manager = FigureManager(self.canvas, 0) window = self.manager.window window.set_transient_for(self.parent) window.set_title(self.title) return window def mpl_signal_canvas_button_pressed(self, event): if event.button != 3: return self.popup_menu.popup(None, None, None, None, event.button, Gtk.get_current_event_time()) return True def signal_activate_popup_menu_export(self, action): dialog = gui_utilities.UtilityFileChooser('Export Graph', self.parent) file_name = self.config['campaign_name'] + '.png' response = dialog.run_quick_save(file_name) dialog.destroy() if not response: return destination_file = response['target_path'] self.figure.savefig(destination_file, format='png') def signal_toggled_popup_menu_show_toolbar(self, widget): if widget.get_property('active'): self.navigation_toolbar.show() else: self.navigation_toolbar.hide() def load_graph(self): """Load the graph information via :py:meth:`.refresh`.""" self.refresh() def refresh(self, info_cache=None, stop_event=None): """ Refresh the graph data by retrieving the information from the remote server. :param dict info_cache: An optional cache of data tables. :param stop_event: An optional object indicating that the operation should stop. :type stop_event: :py:class:`threading.Event` :return: A dictionary of cached tables from the server. :rtype: dict """ info_cache = (info_cache or {}) if not self.parent.rpc: return info_cache for table in self.table_subscriptions: if stop_event and stop_event.is_set(): return info_cache if not table in info_cache: info_cache[table] = list( self.parent.rpc.remote_table('campaign/' + table, self.config['campaign_id'])) map(lambda ax: ax.clear(), self.axes) self._load_graph(info_cache) self.canvas.draw() return info_cache
class MyWindow(Gtk.Window): def __init__(self): Gtk.Window.__init__(self,title="Echelle Reduction GUI") ## setting up canvase for plotting ### #self.box = Gtk.EventBox() self.set_default_size(1000,700) self.f = Figure(figsize=(5,7), dpi=100) self.b = self.f.add_subplot(212) # 1D self.c = self.f.add_subplot(231) # PHD self.e = self.f.add_subplot(233) # orders self.a = self.f.add_subplot(232) # raw self.e.tick_params(axis='both',labelsize=6) self.e.set_title("orders") self.a.tick_params(axis='both', labelsize=7) self.a.set_title("2D raw data") self.b.set_title("1D extracted data") self.b.set_xlabel('pixels') self.b.set_ylabel('intensity') self.b.tick_params(axis='both', labelsize=7) self.c.set_title('PHD') self.c.tick_params(axis='both', labelsize=7) self.canvas = FigureCanvas(self.f) # menue bar menubar = Gtk.MenuBar() filem = Gtk.MenuItem("File") filemenu = Gtk.Menu() filem.set_submenu(filemenu) open = Gtk.MenuItem("Open") open.connect('activate',self.on_file_clicked) filemenu.append(open) menubar.append(filem) #savesub = Gtk.Menu() #savesub.append() exit = Gtk.MenuItem('Exit') exit.connect('activate',Gtk.main_quit) filemenu.append(exit) menubox = Gtk.Box( orientation = Gtk.Orientation.VERTICAL) menubox.pack_start(menubar,False,False,0) #open.connect("open", self.on_file_clicked) # Navigtion toolbar stuff toolbar = NavigationToolbar(self.canvas, self) main_box = Gtk.Box( orientation = Gtk.Orientation.VERTICAL) self.add(main_box) # status bar self.statusbar = Gtk.Statusbar() context_id=self.statusbar.get_context_id("stat bar example") self.statusbar.push(0,'Please Open 2D Fits Data File') # button box vbutton_box = Gtk.Box(orientation = Gtk.Orientation.HORIZONTAL) self.button1 = Gtk.ToggleButton(label='Raw count rate') self.button1.connect("toggled", self.on_button1_clicked, context_id) self.button2 = Gtk.Button('Filter PHD') self.button2.connect("clicked",self.on_button2_clicked,context_id) self.button3 = Gtk.ToggleButton(label='Fit 1D Gauss') self.button3.set_active(False) self.button3.connect("toggled", self.on_button3_clicked, context_id) self.orderbutton = Gtk.ToggleButton(label='Remove Orders') self.orderbutton.connect("toggled",self.orderbutton_clicked,context_id) self.buttonbg = Gtk.Button('Airglow') self.buttonbg.connect('clicked', self.buttonbg_clicked, context_id) vbutton_box.pack_start(self.button1,True,True, 0) vbutton_box.pack_start(self.button2,True, True, 0) vbutton_box.pack_start(self.button3,True, True, 0) vbutton_box.pack_start(self.orderbutton,True,True,0) vbutton_box.pack_start(self.buttonbg,True,True,0) # packing in main_box main_box.pack_start(self.statusbar, False,False,0) main_box.pack_start(self.canvas, True, True, 0) main_box.pack_start(vbutton_box,False,False,0) main_box.pack_start(toolbar, False, False, 0) main_box.pack_start(menubox,False,False,0) # ### file selector window ### def on_file_clicked(self, widget): dialog = Gtk.FileChooserDialog("Please Choose a File", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_default_response(Gtk.ResponseType.OK) filter = Gtk.FileFilter() filter.set_name('fits Files') filter.add_mime_type('fits') filter.add_pattern('*.fits') dialog.add_filter(filter) response = dialog.run() if response == Gtk.ResponseType.OK : fname = dialog.get_filename() global _File _File = fname #print "open file" + fname self.statusbar.push(0,'Opened File:' + fname) dialog.destroy() self.open_file(_File) elif response == Gtk.ResponseType.CANCEL: dialog.destroy() # opening fits file def open_file(self,_File): #this opens up the fits file hdulist = fits.open(_File) self.targname = hdulist[0].header['targname'] #this picks out the actual data from fits file, and turns it into numpy array #self.scidata = hdulist['sci',2].data self.scidata = hdulist['sci',1].data hdulist.close() scidata = self.scidata targname = self.targname self.science(scidata,targname) def science(self,scidata,targname): # sends 2d data to my gui plotting funtion self.update_plot(scidata) #self.scidata = scidata ### smashing 2D data into 1D to view orders as peaks #### # filling in y with the sums of the rows of scidata y=[] for i in range(0,len(scidata[0])): t = np.sum(scidata[i,:]) y.append(t) # making an x axis with same dementions as y x = np.linspace(0,len(scidata[0]), num = len(scidata[0])) self.x = x #reversing x for the sake of the visualization xrev = x[::-1] # the orders seem to blend around lyman alpha so i have to select # the biggest chunk I could use to get a delta y between the # orders. i wil have to make this selectable on the GUI some how chunk = [0,500] minv = chunk[0] maxv = chunk[1] #drawing box around my chunk to check makesure I have a good portion #plt.hself.lines(chunk,[-1000],[5000],color='r') #plt.vself.lines([-1000,5000],minv,maxv,color='r') #plt.show() #cutting out the chunk of data that i selected xchunk = x[max(x)-chunk[1]:max(x)] index1 = int(max(x)-chunk[1]) index2 = int(max(x)) ychunk = y[index1:index2] #reversing x for the sake of the plot xrevchunk = xchunk[::-1] #plt.figure(figsize=(7.5,8.4)) # # using scipy.signal.find_peaks_cwt() to find centers of orders. this required scipy version .11.0 or greater peakind=signal.find_peaks_cwt(ychunk,np.arange(3,15)) # plotting chunk of 1D data with self.lines through the centers of the orders to double check how the peak finder did self.lines=xchunk[peakind] revlines=self.lines[::-1] lines = self.lines self.xchunk = xchunk self.ychunk = ychunk self.update_ordersplot(ychunk,xchunk,self.lines) ### fake PDH stuff ### (fake data for now) PHDfake = '/home/rachel/codes/chesstest.fits' hdu = fits.open(PHDfake) PHD = hdu[1].data['PHD'] self.update_PHDplot(PHD) hdu.close() self.dragbox=[] ### extraction of orders ### # find w, the widths of the orders (difference between peaks) w=[] for i in range(1,len(peakind)): t=peakind[i]-peakind[i-1] w.append(t) # i have to add an extra w at the end of the array to make it the right size i (hopefully this is kosher) maxw=max(w)-4 w.append(maxw) self.w = w ### making arrays of 1s ans 0s and extracting the 1d orders by matrix multiplication #def extraction(self, peakind,x,scidata): zeros=np.zeros((len(x),1)) index = range(0,len(self.w)) reindex = index[::-1] global oneDorders oneDorders = {} for i in reindex: zeros1=np.copy(zeros) zeros1[ len(scidata[0]) - (np.sum(w[(i):18])) : len(scidata[0]) - np.sum(w[(i+1):18]) ] = 1 twoD = scidata*zeros1 # making 2d orders in to 1d orders Y=[] for j in range(0,len(scidata[0])): t = np.sum(twoD[:,j]) Y.append(t) # placing 1d orders in dictionary called oneDorders oneDorders[str(i)]=Y # sending plotting info to update_1dplot for gui (for now using just on order until cross coralation is added to script self.x = np.linspace(0,len(scidata[0]), num = len(scidata[0])) self.odo=oneDorders['16'] odo = self.odo[:] self.update_1dplot(odo,x) self.save_pickle(oneDorders) def update_plot(self, scidata): #self.a.cla() #cbar.self.a.cla() self.plt= self.a.imshow(scidata, vmin = 0, vmax = 255,origin = 'lower') cbar=self.f.colorbar(self.plt,shrink=.84,pad=0.01) self.canvas.draw() def update_ordersplot(self,ychunk,xchunk,lines): ## if you dont want to new airglow subtracted data to over plot but to replot, uncomment this next line self.e.cla() self.e.set_title("orders") self.plt=self.e.plot(ychunk,xchunk) self.e.hlines(lines,0,2000,color='purple',label='centers') self.canvas.draw() def update_1dplot(self,odo,x): ## if you dont want to new airglow subtracted data to over plot but to replot, uncomment this next line self.b.cla() self.plt=self.b.plot(x,self.odo) self.canvas.draw() def update_PHDplot(self,PHD): ## if you dont want to new airglow subtracted data to over plot but to replot, uncomment this next line self.c.cla() self.c.set_title('PHD') self.plt=self.c.hist(PHD,bins=80,histtype='stepfilled') self.canvas.draw() ## airglow button ## def buttonbg_clicked(self, widget, data): dialog = Gtk.FileChooserDialog("Please Choose Airglow File", self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK)) dialog.set_default_response(Gtk.ResponseType.OK) filter = Gtk.FileFilter() filter.set_name('fits Files') filter.add_mime_type('fits') filter.add_pattern('*.fits') dialog.add_filter(filter) response = dialog.run() if response == Gtk.ResponseType.OK : fname = dialog.get_filename() global _AirglowFile _AirglowFile = fname #print "open file" + fname #self.statusbar.push(0,'Opened File:' + fname) dialog.destroy() self.open_Airglowfile(_AirglowFile) elif response == Gtk.ResponseType.CANCEL: dialog.destroy() # opening aiglow fits file def open_Airglowfile(self,_AirglowFile): #this opens up the fits file hdulist = fits.open(_AirglowFile) ## this line will need to be used for real data #self.targname = hdulist[0].header['targname'] #targname = self.targname ## but for now targname = 'HD128627J' # this picks out the actual data from fits file, and turns it into numpy array self.glowdata = hdulist[0].data hdulist.close() # simple subtraction of aurglow image from science image scidata = self.scidata - self.glowdata self.science(scidata,targname) ## gauss fitting button def on_button3_clicked(self, widget, data): self.statusbar.push(data,'Ready to fit. Click on both sides of the emission feature you wish to fit') self.xdata = [] def onclick(event): if self.button3.get_active(): self.xdata.append(event.xdata) self.statusbar.push(data,'one more click...') if len(self.xdata) == 2: self.statusbar.push(data,'Ready to fit. Click on both sides of the emission feature you wish to fit') xdata=self.xdata self.gauss_fit(xdata) # mouse click event on 1d cid = self.canvas.mpl_connect('button_press_event', onclick) if [self.button3.get_active()] == [False]: self.statusbar.push(0,'Opened File:' + _File) ### guass fitting ### def gauss_fit(self,xdata): x = list(self.x) xg=[] xg.append(int(xdata[0])) xg.append(int(xdata[1])) xg1 = min(xg) xg2 = max(xg) xgauss = x[xg1:xg2] ygauss = self.odo[xg1:xg2] right = ygauss[len(xgauss)-4:len(xgauss)] left = ygauss[0:4] # background subtraction averight = sum(right)/len(right) aveleft = sum(left)/len(left) bg_y = [averight,aveleft] rightx = xgauss[len(xgauss)-4:len(xgauss)] leftx = xgauss[0:4] averightx = sum(rightx)/len(rightx) aveleftx = sum(leftx)/len(leftx) bg_x = [averightx,aveleftx] m,b = np.polyfit(bg_x,bg_y,1) slopex = [i * m for i in xgauss] bg_fit = slopex+b # makeing a model gauss def gauss(xgauss,MAX,mu,sigma): return MAX*np.exp(-(xgauss-mu)**2/(2.*sigma**2)) avex = sum(xgauss)/len(xgauss) guess = [1.,avex,1.] # plugging in model to matplotlibs curve_fit() coeff, var_matrix = curve_fit(gauss,xgauss,ygauss-bg_fit,p0=guess) fit = gauss(xgauss, *coeff) sigma = coeff[2] FWHM = sigma*2*np.sqrt(2*np.log(2)) FWHM = round(FWHM,2) fitplot = plt.plot(xgauss,ygauss,color='k') plt.plot(xgauss,fit+bg_fit,color = 'b',linewidth = 1.5) xpos = xgauss[0]+.01*(coeff[1]-xgauss[0]) strFWHM = str(FWHM) plt.text(xpos,.9*max(ygauss),'FWHM = '+strFWHM+'',color = 'purple',fontweight = 'bold') center = str(round(coeff[1],2)) plt.text(xpos,.95*max(ygauss),'Center = '+center+'',color = 'green',fontweight = 'bold') plt.plot(xgauss,bg_fit,'r--') plt.show() self.xdata = [] ### count rate button def on_button1_clicked(self, widget,data): if self.button1.get_active(): self.statusbar.push(data,'Use zoom feature in navigation bar to select count rate region') else: self.statusbar.push(0,'Opened File:' + _File) def onclick2(event): if self.button1.get_active(): self.dragbox = [] #print event.xdata, event.ydata self.dragbox.append(event.xdata) self.dragbox.append(event.ydata) def offclick2(event): # print event.xdata, event.ydata if self.button1.get_active(): self.dragbox.append(event.xdata) self.dragbox.append(event.ydata) dragbox = self.dragbox self.cnt_rate(dragbox,data) cid2 = self.canvas.mpl_connect('button_press_event',onclick2 ) cid3 = self.canvas.mpl_connect('button_release_event',offclick2 ) ### count rate ##### def cnt_rate(self,dragbox,data): # fake exposure time in seconds datafake = '/home/rachel/codes/chesstest.fits' hdu = fits.open(datafake) exptime = hdu[0].header['EXPOSURE'] dragbox = [int(x) for x in dragbox] cntbox = self.scidata[dragbox[0]:dragbox[2],1024-dragbox[1]:1024-dragbox[3]] totpix = np.size(cntbox) cntrate = np.sum(cntbox)/exptime totpix = str(totpix) cntrate = str(cntrate) self.statusbar.push(data,'count rate in box = '+cntrate+' cnt/sec, pixels in box = '+totpix+'') return cntrate #### phd filter button ## def on_button2_clicked(self,widget,data): self.phd_window = Gtk.MessageDialog(image = None) self.phd_window.set_size_request(500,100) self.phd_window.move(400, 300) #self.phd_window.connect("delet_event",lambda w,e:) mainbox = self.phd_window.get_content_area() self.phd_window.add(mainbox) thebox = Gtk.HBox(False, 0) label = Gtk.Label("Discard PHD between") label2 = Gtk.Label('and') label.show() label2.show() self.okbutton = Gtk.Button('Okay') self.okbutton.connect('clicked',self.phd_entry_button) self.entry = Gtk.Entry() self.entry.set_activates_default(True) self.entry2 = Gtk.Entry() self.entry2.set_activates_default(True) self.entry.show() self.entry2.show() self.okbutton.show() thebox.pack_start(label,False,False,0) thebox.pack_start(self.entry,False,False,0) thebox.pack_start(label2,False,False,0) thebox.pack_start(self.entry2,False,False,0) mainbox.pack_start(thebox,True,True,0) mainbox.pack_start(self.okbutton,True,False,0) mainbox.show() thebox.show() self.phd_window.show() def phd_entry_button(self,widget): minphd = self.entry.get_text() maxphd = self.entry2.get_text() phdfilt = [minphd,maxphd] self.phd_window.destroy() self.filter_phd(phdfilt) # ### phd filter function ## def filter_phd(self, phdfilt): phdfilt = [int(x) for x in phdfilt] fakedata = '/home/rachel/codes/chesstest.fits' hdu = fits.open(fakedata) PHD = hdu[1].data['PHD'] PHD = np.array(PHD) data = hdu[1].data newdata = data[(PHD > phdfilt[0]) & (PHD < phdfilt[1])] plt.subplot(221) oldplot = plt.plot(data['X'],data['Y'],linestyle = '',marker = '.') plt.subplot(222) newplot = plt.plot(newdata['X'],newdata['Y'],linestyle = '',marker = '.') plt.show() ### mouse click on remove orders ### def orderbutton_clicked(self, widget, data): self.statusbar.push(data,'click on the order that you want to exclude.') self.remove = [] lines = self.lines def onclick_order(event): if self.orderbutton.get_active(): self.remove.append(event.ydata) remove = self.remove self.remove_orders(remove,lines) cid4 = self.canvas.mpl_connect('button_press_event',onclick_order) if [self.orderbutton.get_active()] == [False]: self.statusbar.push(0,'Opened File:' + _File) ### removing orders def remove_orders(self,remove,lines): bad_orders = [] bad_orders_index = [] for i in range(0,len(remove)): bad = min(lines, key=lambda x:abs(x-remove[i])) bad_orders.append(bad) bad_orders_index.append( np.where(lines == bad) ) newlines = filter(lambda lines: lines not in bad_orders,lines) xchunk = self.xchunk ychunk = self.ychunk self.e.cla() lines = newlines self.update_ordersplot(ychunk,xchunk,lines) #for key in oneDorders.keys(): print key print 'number or original orders',len(oneDorders) self.new_dictionary(bad_orders_index) def new_dictionary(self,bad_orders_index): maxint = len(oneDorders) for i in range(0,len(bad_orders_index)): num = int(bad_orders_index[i][0]) if oneDorders.get(str(num),None): oneDorders.pop(str(num)) for key in oneDorders.keys(): k = int(key) if k > num: oneDorders[str(k-1)] = oneDorders.get(key) if k == maxint-1: oneDorders.pop(key) # print 'corrected number of orders',len(oneDorders) self.save_pickle(oneDorders) #for key in oneDorders.keys(): print key def save_pickle(self, ondDorders): order_dict = oneDorders now = datetime.datetime.now() date = now.strftime("%m_%d_%Y") targname = str(self.targname) pickle.dump(order_dict,open(''+targname+'_1D_'+date+'.p','wb'))