class MoveBoxRoutine(AniRoutine): def __init__(self, figure, plot_cood): super().__init__(figure, plot_cood) self.move_box = TextBox(self.ax, 'Move #: ', 0) def update(self, index): self.move_box.set_val(index)
def view(): # fig, ax = plt.subplots() fig, ax = plt.subplots() # axes style ax.set_title('B-Spline-Curves') ax.set_xlim(1, 10) ax.set_ylim(1, 24) box = ax.get_position() ax.set_position([box.x0, box.y0, box.width * 0.7, box.height]) # fig layout plt.text(10.3, 18, "User Tips:", fontfamily='fantasy', fontstyle='italic', fontsize='large', fontweight='roman', color='r', backgroundcolor='#87CEEB', # skyblue bbox=dict(boxstyle="round", fc='#87CEEB'), rasterized='True' ) plt.text(10.5, 11, "Add point: $Mouse-Right$\n" "Drag point: $Mouse-Left$\n" "Hide Line: h / H\nExit: q / Q\n", fontfamily='serif', fontstyle='oblique', fontsize='medium', color='#000080' ) # add new line button, input textbox and radio button addlinebtn = fig.add_axes([0.75, 0.22, 0.2, 0.075]) inputbox = fig.add_axes([0.85, 0.12, 0.1, 0.07]) rax = fig.add_axes([0.75, 0.32, 0.2, 0.12]) # button click btncallback = CreatLine(ax) binsert = Button(addlinebtn, 'New Line', color='#00BFFF') binsert.on_clicked(btncallback.createnewline) # input submit input_text = TextBox(inputbox, "Degree : ") input_text.on_submit(change_degree) input_text.set_val("3") # radio callback radio = RadioButtons(rax, ("Uniform", "Clamped")) radio.on_clicked(change_type) # add first line CreatLine(ax).createnewline(event=None) ax.legend(loc='upper left', bbox_to_anchor=(1, 1.02), fontsize='medium', shadow='True') plt.show()
class numeric_field: def __init__(self, inp_ax, label, regexp, maxlen, decpoint=None): self.val = '' self.curpos = 0 self.pattern = re.compile(regexp) self.maxlen = maxlen self.decpoint = decpoint self.tb = TextBox(inp_ax, label) self.tb.on_text_change(self.tc_func) def tc_func(self, inp): if (len(inp) > self.maxlen): self.tb.cursor_index = self.curpos self.tb.set_val(self.val) return if (self.decpoint and inp.find('.') < 0 and len(inp) > self.maxlen - 1): self.tb.cursor_index = self.curpos self.tb.set_val(self.val) return if (not self.pattern.match(inp)): self.tb.cursor_index = self.curpos self.tb.set_val(self.val) return self.val = inp self.curpos = self.tb.cursor_index
class DGILibPlot(object): """DGILibPlot The `DGILibPlot` class is responsible with plotting the electrical current (Amperes) data and gpio state data (values of `1`/`0`) obtained from an Atmel board. The X axis represents time in seconds, while the Y axis represents the electrical current in Amperes. There are two ways that the gpio pins state can be shown along with the electrical current. One is the `line` method and one is the `highlight` method. The `line` method shows a square waveform, a typical byproduct of the digital signal that gpio pins usually have. The `highlight` method highlights only particular parts of the plot with semi-transparent coloured areas, where the pins have a value of interest (set using the ``plot_pins_values`` argument of the class). Below are shown some examples of `DGILibPlot` and the two methods of drawing the gpio pins (`line`/`highlight`). **Example plots using "line" method:** .. figure:: images/plot_line_1.png :scale: 60% Figure 1: Example of plot with the 'line' method chosen for the drawing of pins. All of the pins are being plotted, so you can always see their `True`/`False` values. .. figure:: images/plot_line_2.png :scale: 60% Figure 2: The same plot with the same data as figure 1, only zoomed in. .. figure:: images/plot_line_3.png :scale: 60% Figure 3: The same plot with the same data as figure 1 and 2, only even more zoomed in. Here we can clearly see that gpio pins 0, 2 and 3 have defaulted on a single value all along the program's execution on the board. We can however clearly see the toggling of pin 1, represented in orange. **Example plots using "highlight" method:** .. figure:: images/plot_highlight_1.png :scale: 60% Figure 4: Example of plot with the 'highlight' method chosen for the drawing of pins. The time the pins are holding the value of interest (in this case, `True` value) is small every time. This is why we can see the highlighted areas looking more like vertical lines when zoomed out. The only pin being toggled by the program on the board is pin 1, hence it's why we only see one color of highlighted areas. .. figure:: images/plot_highlight_2.png :scale: 60% Figure 5: The same plot with the same data as figure 1, only zoomed in. .. figure:: images/plot_highlight_3.png :scale: 60% Figure 6: The same plot with the same data as figure 1 and 2, only even more zoomed in. Now we can see one of the the highlight area in its proper form. **Parameters** The parameters listed in the section below can be passed as arguments when initializing the `DGILibPlot` object, or as arguments to a `DGILibExtra` object. They can be included in a configuration dictionary (:py:class:`dict` object) and then unwrapped in the initialization function call of either the `DGILibPlot` object or the `DGILibExtra` object (by doing: ``dgilib_plot = DGILibPlot(**config_dict)`` or ``with DGILibExtra(**config_dict) as dgilib: ...``). Parameters ---------- dgilib_extra : DGILibExtra A `DGILibExtra` object can be specified, from where the plot can obtain the electrical current and gpio state data. If a `DGILibExtra` object is not desired to be specified however, then it should be set to `None`. (the default is `None`, meaning that measurements data (in the form of a :class:`DGILibData` should be manually called as function updates)) fig : matplotlib.pyplot.figure, optional If it is wanted so that the data is to be plotted on an already existing `matplotlib` figure, then the object representing the already instantiated figure can be specified for this parameter. For example, the electrical current data and gpio state data can be plotted in a subplot of a figure window that holds other plots as well. (the default is `None`, meaning that a new figure object will be created internally) ax : matplotlib.pyplot.axes, optional If it is wanted so that the data is to be plotted on an already existing `matplotlib` axes, then the object representing the already instantiated axes can be specified for this parameter. (the default is `None`, meaning that a new axes object will be created internally) ln : matplotlib.pyplot.lines2d, optional If it is wanted so that the data is to be plotted on an already existing `matplotlib` `Lines2D` object, then the object representing the already instantiated `Lines2D` object can be specified for this parameter. (the default is `None`, meaning that a new `Lines2D` object will be created internally) window_title : str, optional If another window title than the default is desired to be used, it can be specified here. (the default is ``Plot of current (in amperes) and gpio pins``) plot_xmax : int, optional This *initializes* the figure view to a maximum of `plot_xmax` on the X axis, where the data to be plotted. Later, the user can change the figure view using the bottom sliders of the plot figure. (the default is an arbitrary `10`) plot_ymax : int, optional This *initializes* the figure view to a maximum of `plot_xmax` on the Y axis, where the data to be plotted. Later, the user can change the figure view using the bottom sliders of the plot figure. (the default is `0.005`, meaning 5 mA, so that something as energy consuming as a blinking LED can be shown by a `DGILibPlot` with default settings) plot_pins : list(bool, bool, bool, bool), optional Set the pins to be drawn in the plot, out of the 4 GPIO available pins that the Atmel board gives data about to be sent through the computer through the Atmel Embedded Debugger (EDBG) Data Gateway Interface (DGI). (the default is `[True, True, True, True]`, meaning all pins are drawn) plot_pins_method : str, optional Set the *method* of drawing the pin. The values can be either ``"line"`` or ``"highlight"``. Refer to the above figures to see the differences. (the default is `"highlight"`) plot_pins_colors : list(str, str, str, str), optional Set the colors of the semi-transparent highlight areas drawn when using the `highlight` method, or the lines when using the `lines` method of drawing pins. (the default is `["red", "orange", "blue", "green"]`, meaning that pin 0 will have a `red` semi-transparent highlight area or line, pin 1 will have `orange` ones, and so on) automove_method : str, optional When the plot is receiving data live from the measurements taken in real-time from the board (as opposed to receiving all the data to be plotted at once, say, when reading the data from saved csv files), and `plot_xmax` is smaller than the expected size of the data in the end, then at some point the data will update past the figure view. `DGILibPlot` automatically moves the figure view to the last timestamp of the latest data received, and it can do so in two ways, depending on the value of ``automove_method``. The `page` method changes the figure view in increments of ``plot_ymax``, when the data updates past the figure view, as if the plot is turning one "page" at a time. The `latest_data` method makes the figure view always have the latest data in view, meaning that it moves in small increments so that it always keeps the latest data point `0.15` seconds away from the right edge of the figure view. The `0.15` seconds value is an arbitrary hard-coded value, chosen after some experiments. (the default is 'latest_data', meaning the plot's figure view will follow the latest data in smaller increments, keeping the latest data always on the right side of the plot) verbose : int Specifies verbosity: - 0: Silent - 1: Currently prints if data is missing, either as an object or as \ values, when :func:`update_plot` is being called. (the default is `0`) Attributes ---------- axvspans : list(4 x list(matplotlib.pyplot.axvspan)) The way the semi-transparent coloured areas for the gpio pins are drawn on the plot is by using ``matplotlib.pyplot.axvspan`` objects. The `axvspan` objects are collected in a list for potential use for later. (e.g.: such as to delete them, using the :func:`clear_pins` method). (the default is 4 empty lists, meaning no highlighting of areas of interest has occured yet) annotations : list(4 x list(str)) As we have seen in figures 4, 5, 6, for the `highlight` method of drawing pins, the counting or iteration of the highlighted areas are also showed on the plot. There are 4 pins, so therefore 4 lists of the counting/iteration stored as strings are saved by the `DGILibPlot` for later use by developers (e.g.: to replace from numbers to actual descriptions and then call the redrawing of pins). (the default is 4 empty lists, meaning no annotations for the highlighted areas of interest were placed yet) preprocessed_averages_data : list(4 x list(tuple(int, \ tuple(float, float), int, float))) As the `highlight` method draws the pins on the plot with the help of the :class:`HoldTimes` class, that means the plot knows afterwards the time intervals in which the pins have values of interest. This can be valuable for a subsequent averaging function that wants to calculate faster the average current or energy consumption of the the board activity only where it was highlighted on the plot. As such, `DGILibPlot` prepares a list of 4 lists of tuples, each for every pin, the tuple containing the iteration index of the highlighted area of interest the pins kept the same value consecutively (called a `hold` time), another tuple containing the timestamps with respect to the electrical current data, which says the beginning and end times of that `hold` interval, an index in the list of the electrical current data points where the respective hold time starts and, lastly, a `None` value, which then should be replaced with a :py:class:`float` value for the the average current or charge during that hold time. (the default is 4 empty lists, meaning no gathered preprocessed averages data yet) iterations : list(4 x int) As the plot is being updated live, the `iterations` list holds the number of highlighed areas that have been drawn already for for each pin. As the whole measurement data gets plotted, the iterations list practically holds the number of iterations the of all the areas of interest for each pin (which can be, for example, the number of `for` loops that took place in the program itself running on the board). (the default are 4 ``0`` values, meaning no areas of interest on the gpio data has been identified) last_xpos : float As the plot is being updated live, the figure view moves along with the latest data to be shown on the plot. If the user desires to stop this automatic movement and focus on some specific area of the plot, while the data is still being updated in real-time, the figure needs to detect that it should stop following the data to not disturb the user. It does so by comparing the current x-axis position value of the x axis shown, with the last x-axis position value saved, before doing any automatic movement of the figure view. If they are different, no automatic movement is being done from now on. xylim_mutex : Lock As there are many ways to move, zoom and otherwise manipulate the figure view to different sections of the plot, using the `matplotlib`'s implicit movement and zoom controls, or the freshly built `DGILibPlot` sliders appearing at the bottom (See figures), or the automatic following of the latest data feature, there is a multi-threading aspect involved and therefore a mutex should be involved, in the form of a `Lock` object, to prevent anomalies. hold_times_obj: HoldTimes Only instantiated when `highlight` method is being used for drawing pins information, the :class:`HoldTimes` class holds the algorithm that works on the live data to obtain the timestamps of areas of interest of the gpio data, in order to be highlighted on the plot. As the algorithm works on live updating data, the areas of interst can be cut off between updates of data. As such, the :class:`HoldTimes` keeps information for the algorithm to work with, in order to advert this. """ def __init__(self, dgilib_extra=None, *args, **kwargs): self.dgilib_extra = dgilib_extra # Maybe the user wants to put the power figure along with # other figures he wants self.fig = kwargs.get("fig") if self.fig is None: self.fig = plt.figure(figsize=(8, 6)) # Set window title if supplied, if not set a default self.window_title = kwargs.get( "window_title", "Plot of current (in amperes) and" + "gpio pins") self.fig.canvas.set_window_title(self.window_title) self.ax = kwargs.get("ax") if self.ax is None: self.ax = self.fig.add_subplot(1, 1, 1) self.ax.set_xlabel('Time [s]') self.ax.set_ylabel('Current [A]') # We need the Line2D object as well, to update it if (len(self.ax.lines) == 0): self.ln, = self.ax.plot([], [], 'r-', label="Power") else: self.ln = self.ax.lines[0] self.ln.set_xdata([]) self.ln.set_ydata([]) # Initialize xmax, ymax of plot initially self.plot_xmax = kwargs.get("plot_xmax", None) if self.plot_xmax is None: self.plot_xauto = True self.plot_xmax = 10 else: self.plot_xauto = False self.plot_ymax = kwargs.get("plot_ymax", None) if self.plot_ymax is None: self.plot_yauto = True self.plot_ymax = 0.005 else: self.plot_yauto = False self.plot_pins = kwargs.get("plot_pins", [True, True, True, True]) self.plot_pins_values = kwargs.get("plot_pins_values", [True, True, True, True]) self.plot_pins_method = kwargs.get("plot_pins_method", "highlight") # or "line" self.plot_pins_colors = kwargs.get("plot_pins_colors", ["red", "orange", "blue", "green"]) self.automove_method = kwargs.get("automove_method", "latest_data") # or "page" self.axvspans = [[], [], [], []] self.annotations = [[], [], [], []] self.preprocessed_averages_data = [[], [], [], []] #self.total_average = [0,0,0,0] self.iterations = [0, 0, 0, 0] self.last_xpos = 0.0 self.xylim_mutex = Lock() #self.refresh_plot_pause_secs = kwargs.get("refresh_plot_pause_secs", 0.00000001) if self.plot_pins_method == "highlight": self.hold_times_obj = HoldTimes() if self.plot_pins_method == "line": self.ax_pins = self.ax.twinx() self.ax_pins.set_ylabel('Pin Value') self.ax_pins.set_ylim([-0.1, 1.1]) self.ax_pins.set_yticks([0, 1]) self.ax_pins.set_yticklabels(["Low", "High"]) self.ln_pins = list( self.plot_pins) # Instantiate as copy of plot_pins for pin, plot_pin in enumerate(self.plot_pins): if plot_pin: self.ln_pins[pin] = self.ax_pins.plot( [], [], label=f"gpio{pin}")[0] self.ax_pins.legend(handles=[ ln_pin for ln_pin in self.ln_pins if not isinstance(ln_pin, bool) ] + [self.ln]) # Should actually check if it is a lines instance # Hardwiring these values to 0 self.plot_xmin = 0 self.plot_ymin = 0 # We need these values from the user (or from the superior class), # hence no default values # TODO: Are they really needed though? self.plot_xdiv = kwargs.get("plot_xdiv", min(5, self.plot_xmax)) self.plot_xstep = kwargs.get("plot_xstep", 0.5) self.plot_xstep_default = self.plot_xstep # self.duration = kwargs.get("duration", max(self.plot_xmax, 5)) # We'll have some sliders to zoom in and out of the plot, as well as a cursor to move around when zoomed in # Leave space for sliders at the bottom plt.subplots_adjust(bottom=0.3) # Show grid self.ax.grid() # Slider color self.axcolor = 'lightgoldenrodyellow' # Title format # Should use this https://matplotlib.org/gallery/recipes/placing_text_boxes.html self.title_avg = "Total avg curr: %1" self.title_vis = "Visible avg curr: %1" self.title_pin = "Avg curr in %1: %2" # "calculate_average(data[INTERFACE_POWER])*1e3:.4} mA" self.title_time = "Time spent in %1: %2" self.ax.set_title("Waiting for data...") # Hold times for pins list, we're going to collect them self.hold_times_sum = 0.00 self.hold_times_next_index = 0 # Will be incremented to 0 later self.hold_times_already_drawn = [] self.verbose = kwargs.get("verbose", 0) self.initialize_sliders() def initialize_sliders(self): self.axpos = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=self.axcolor) self.axwidth = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=self.axcolor) self.resetax = plt.axes([0.8, 0.025, 0.1, 0.04]) self.spos = Slider(self.axpos, 'x', 0, self.plot_xmax, valinit=0, valstep=self.plot_xstep) self.swidth = Slider(self.axwidth, 'xmax', 0, self.plot_xmax, valinit=self.plot_xdiv, valstep=self.plot_xstep) self.resetbtn = Button(self.resetax, 'Reset', color=self.axcolor, hovercolor='0.975') #TODO: Change to pixel sizes self.xleftax = plt.axes([0.4, 0.025, 0.095, 0.04]) # x_pos, y_pos, width, height self.xrightax = plt.axes([0.5, 0.025, 0.095, 0.04]) self.xmaxleftax = plt.axes([0.6, 0.025, 0.095, 0.04]) self.xmaxrightax = plt.axes([0.7, 0.025, 0.095, 0.04]) self.xstepupax = plt.axes([0.3, 0.025, 0.095, 0.04]) self.xstepdownax = plt.axes([0.2, 0.025, 0.095, 0.04]) self.xleftbtn = Button(self.xleftax, '<x', color=self.axcolor, hovercolor='0.975') self.xrightbtn = Button(self.xrightax, 'x>', color=self.axcolor, hovercolor='0.975') self.xmaxleftbtn = Button(self.xmaxleftax, 'xmax-', color=self.axcolor, hovercolor='0.975') self.xmaxrightbtn = Button(self.xmaxrightax, 'xmax+', color=self.axcolor, hovercolor='0.975') self.xstepupbtn = Button(self.xstepupax, 'xstep+', color=self.axcolor, hovercolor='0.975') self.xstepdownbtn = Button(self.xstepdownax, 'xstep-', color=self.axcolor, hovercolor='0.975') self.xstepax = plt.axes([0.1, 0.025, 0.095, 0.04]) self.xsteptb = TextBox(self.xstepax, 'xstep', initial=str(self.plot_xstep)) def xstep_submit(text): self.plot_xstep = float(text) self.xsteptb.on_submit(xstep_submit) def increase_x(event): #if ((self.spos.val + self.plot_xstep) <= (self.plot_xmax - self.swidth.val)): self.spos.set_val(self.spos.val + self.plot_xstep) update_pos(self.spos.val) def decrease_x(event): #if ((self.spos.val - self.plot_xstep) >= (self.plot_xmin)): self.spos.set_val(self.spos.val - self.plot_xstep) update_pos(self.spos.val) def increase_xmax(event): #if ((self.swidth.val + self.plot_xstep) <= self.plot_xmax): self.swidth.set_val(self.swidth.val + self.plot_xstep) update_width(self.swidth.val) def decrease_xmax(event): if ((self.swidth.val - self.plot_xstep) >= (self.plot_xmin + 0.000001)): self.swidth.set_val(self.swidth.val - self.plot_xstep) update_width(self.swidth.val) def increase_xstep(event): val = float(self.xsteptb.text) if ((val + 0.05) < (self.swidth.val - 0.000001)): self.xsteptb.set_val("{0:.2f}".format(val + 0.05)) xstep_submit(self.xsteptb.text) def decrease_xstep(event): val = float(self.xsteptb.text) if ((val - 0.05) >= (0.05)): self.xsteptb.set_val("{0:.2f}".format(val - 0.05)) xstep_submit(self.xsteptb.text) self.xleftbtn.on_clicked(decrease_x) self.xrightbtn.on_clicked(increase_x) self.xmaxleftbtn.on_clicked(decrease_xmax) self.xmaxrightbtn.on_clicked(increase_xmax) self.xstepupbtn.on_clicked(increase_xstep) self.xstepdownbtn.on_clicked(decrease_xstep) # I'm not sure how to detach these without them forgetting their parents (sliders) def update_pos(val): if self.xylim_mutex.acquire(False): pos = self.spos.val width = self.swidth.val #self.set_axis(pos, pos + width, self.plot_ymin, self.plot_ymax, "update_pos function") self.ax.axis( [pos, pos + width, self.plot_ymin, self.plot_ymax]) self.xylim_mutex.release() def update_width(val): if self.xylim_mutex.acquire(False): pos = self.spos.val width = self.swidth.val self.axpos.clear() self.spos.__init__(self.axpos, 'x', 0, width, valinit=pos, valstep=self.plot_xstep) self.spos.on_changed(update_pos) self.spos.set_val(pos) #self.set_axis(pos, pos + width, self.plot_ymin, self.plot_ymax, "update_width function") self.ax.axis( [pos, pos + width, self.plot_ymin, self.plot_ymax]) self.xylim_mutex.release() self.spos.on_changed(update_pos) self.swidth.on_changed(update_width) # TODO: This def see_all(event): if self.xylim_mutex.acquire(False): #self.set_axis(0, self.last_timestamp, self.plot_ymin, self.plot_ymax, "see_all function") self.ax.axis( [0, self.last_timestamp, self.plot_ymin, self.plot_ymax]) self.last_xpos = -1 self.xylim_mutex.release() def reset(event): if self.xylim_mutex.acquire(False): self.swidth.set_val(self.plot_xmax) self.axpos.clear() self.spos.__init__(self.axpos, 'x', 0, self.plot_xmax, valinit=0, valstep=self.plot_xstep_default) self.spos.on_changed(update_pos) self.xsteptb.set_val(str(self.plot_xstep_default)) #self.set_axis(self.plot_xmin, self.plot_xmax, self.plot_ymin, self.plot_ymax, "reset function") self.ax.axis([ self.plot_xmin, self.plot_xmax, self.plot_ymin, self.plot_ymax ]) self.last_xpos = -1 self.xylim_mutex.release() self.resetbtn.on_clicked(reset) self.spos.set_val(0) self.swidth.set_val(self.plot_xmax) # Auto-change the sliders/buttons when using plot tools def on_xlims_change(axes): if self.xylim_mutex.acquire(False): # Non-blocking xlim_left = self.ax.get_xlim()[0] xlim_right = self.ax.get_xlim()[1] pos = xlim_left width = xlim_right - xlim_left self.spos.set_val(pos) self.swidth.set_val(width) self.last_xpos = -1 self.xylim_mutex.release() def on_ylims_change(axes): print(self.ax.get_ylim()) self.ax.callbacks.connect('xlim_changed', on_xlims_change) #self.ax.callbacks.connect('ylim_changed', on_ylims_change) def update_plot(self, data): verbose = self.verbose if data is None: data = self.dgilib_extra.data if (not data): if verbose: print( "dgilib_plot.update_plot: Expected 'data' containing interfaces. Got 'data' with no interfaces. Returning from call with no work done." ) return if (not data.power): if verbose: print( "dgilib_plot.update_plot: Expected 'data' containing power data. Got 'data' with interfaces but no power timestamp & value pairs. Returning from call with no work done." ) return if (not data.gpio): if verbose: print( "dgilib_plot.update_plot: Expected 'data' containing gpio data. Got 'data' with interfaces but no gpio timestamp & value pairs." ) return if not plt.fignum_exists(self.fig.number): plt.show() else: plt.draw() self.refresh_plot() #TODO: ln might have an update_callback and then it can listen to the data being updated instead of updating data here self.ln.set_xdata(data.power.timestamps) self.ln.set_ydata(data.power.values) automove = True current_xpos = self.ax.get_xlim()[0] if self.last_xpos != current_xpos: automove = False if automove and self.xylim_mutex.acquire(False): last_timestamp = data.power.timestamps[-1] pos = self.spos.val width = self.swidth.val if (last_timestamp > (pos + width)): if self.automove_method == "page": self.spos.set_val(pos + width) elif self.automove_method == "latest_data": arbitrary_amount = 0.15 if last_timestamp > width: self.spos.set_val(last_timestamp + arbitrary_amount - width) pos = self.spos.val width = self.swidth.val self.ax.axis([pos, pos + width, self.plot_ymin, self.plot_ymax]) self.last_xpos = pos self.xylim_mutex.release() self.refresh_plot() self.draw_pins(data) def clear_pins(self): """ Clears the highlighted areas on the plot that represent the state of the gpio pins (as seen in figures 4, 5, 6). Using this method only makes sense if the `highlight` method of drawing pins was used. """ if self.axvspans is None: return for axvsp in self.axvspans: axvsp.remove() def draw_pins(self, data): """draw_pins [summary] Raises ------ ValueError Raised when `plot_pins_method` member of class has another string value than the ones available (`highlight`, `line`) Parameters ---------- data : DGILibData :class:`DGILibData` object that contains the GPIO data to be drawn on the plot. """ # Here we set defaults (with 'or' keyword ...) ax = self.ax plot_pins = self.plot_pins plot_pins_values = self.plot_pins_values #plot_pins_method = self.plot_pins_method or "highlight" plot_pins_colors = self.plot_pins_colors # Here we do checks and stop drawing pins if something is unset if ax is None: return if plot_pins is None: return verbose = self.verbose no_of_pins = len(self.plot_pins) if self.plot_pins_method == "highlight": for pin_idx in range(no_of_pins): # For every pin number (0,1,2,3) if plot_pins[pin_idx] == True: # If we want them plotted hold_times = self.hold_times_obj.identify_hold_times( pin_idx, plot_pins_values[pin_idx], data.gpio) if hold_times is not None: for ht in hold_times: axvsp = ax.axvspan(ht[0], ht[1], color=plot_pins_colors[pin_idx], alpha=0.25) self.axvspans[pin_idx].append(axvsp) x_halfway = (ht[1] - ht[0]) / 4 + ht[0] y_halfway = (self.plot_ymax - self.plot_ymin) / 2 + self.plot_ymin annon = ax.annotate(str(self.iterations[pin_idx] + 1), xy=(x_halfway, y_halfway)) self.annotations[pin_idx].append(annon) self.iterations[pin_idx] += 1 # TODO: The start and stop indexes of the data points that are area of interest # might be more useful for an averaging function, but currently the plot uses # the coordinates of the X axis(the start/stop timestamps) in order to highlight # the areas of interest. self.preprocessed_averages_data[pin_idx].append( (self.iterations[pin_idx], ht, 0, None)) # This should be in update_plot() self.ax.set_title( f"Logging. Collected {len(data.power)} power samples and {len(data.gpio)} gpio samples." ) elif self.plot_pins_method == "line": extend_gpio = data.gpio.timestamps[-1] < data.power.timestamps[-1] for pin, plot_pin in enumerate(self.plot_pins): if plot_pin: self.ln_pins[pin].set_xdata(data.gpio.timestamps + extend_gpio * [data.power.timestamps[-1]]) self.ln_pins[pin].set_ydata( data.gpio.get_select_in_value(pin) + extend_gpio * [data.gpio.values[-1][pin]]) self.ax.set_title( f"Logging. Collected {len(data.power)} power samples and {len(data.gpio)} gpio samples." ) self.fig.show() else: raise ValueError( f"Unrecognized plot_pins_method: {self.plot_pins_method}") def plot_still_exists(self): """plot_still_exists Can be used in a boolean (e.g.: `if`, `while`) clause to check if the plot is still shown inside a window (e.g.: to unpause program functionality if the plot is closed). Also used in the :func:`keep_plot_alive` member function of the class. Returns ------- bool Returns `True` if the plot still exists and `False` otherwise. """ return plt.fignum_exists(self.fig.number) def refresh_plot(self): """refresh_plot Makes a few `matplotlib` specific calls to refresh and redraw the plot (especially when new data is to be drawn). Is used in :func:`update_plot` member function of the class. """ self.ax.relim() # recompute the data limits self.ax.autoscale_view() # automatic axis scaling self.fig.canvas.flush_events() def keep_plot_alive(self): while self.plot_still_exists(): self.refresh_plot() """keep_plot_alive Pauses program functionality until the plot is closed. Does so by refreshing the plot using :func:`refresh_plot`. """ def pause(self, time=0.000001): """pause Calls `matplotlib's` `pause` function for an amount of seconds. Parameters ---------- time : float The number of seconds the plot should have time to refresh itself and pause program functionality. """ plt.pause(time)
class Polygon: def __init__(self,imgs_raw,path,fnum): self.imgs_raw=imgs_raw self.path=path self.fnum=fnum self.data_fname=fname_format(path,fnum) fig=plt.figure(str(fnum)) self.sfigs=[] self.sfigs+=[plt.subplot(1,2,1)] plt.imshow(imgs_raw[0]) self.sfigs+=[plt.subplot(1,2,2)] plt.imshow(imgs_raw[1]) self.verts_list=[None]*len(imgs_raw) self.lasssos = [ LassoSelector(ax, self.on_select, button=3 ) for ax in self.sfigs ] axsave = plt.axes([0.8, 0.05, 0.1, 0.075]) bsave = Button(axsave, 'Save') bsave.on_clicked(self.save) axadd = plt.axes([0.7, 0.05, 0.1, 0.075]) badd = Button(axadd, 'Add') badd.on_clicked(self.add) axdel = plt.axes([0.6, 0.05, 0.1, 0.075]) bdel = Button(axdel, 'Del') bdel.on_clicked(self.delete) cid = fig.canvas.mpl_connect('button_press_event', self.onclick) axbox = plt.axes([0.1, 0.05, 0.45, 0.075]) self.text_box = TextBox(axbox, '', initial='?') #text_box.on_submit(submit) try: self.object_list=pickle.load(open(self.data_fname,'rb')) except: print('no data file for fnum=',fnum) self.object_list=[] self.objects_hdls=None self.draw_objs() self.selected_obj_ind=-1 plt.show() def on_select(self,verts): print('selecing in',self.last_ind_ax) verts.append(verts[0]) #verts=Path(verts,closed=True).cleaned().vertices self.verts_list[self.last_ind_ax]=verts def draw_objs(self,selected=-1): if self.objects_hdls is not None: for h in self.objects_hdls: #print('----',h) h[0].remove() self.objects_hdls=[] h=self.objects_hdls for ind,obj in enumerate(self.object_list): for find,subp in enumerate(self.sfigs): data = obj['pts'][find] if data is not None: xs,ys=np.array(data).T h.append(subp.plot(xs,ys,'y')) h.append(subp.plot(xs[0],ys[0],'or' if selected==ind else 'oy',alpha=0.5)) def get_closest(self,x,y,ax_ind): max_ind=-1 max_val=30 #minmal distance to accept click for ind,obj in enumerate(self.object_list): vrts= obj['pts'][ax_ind] if vrts is not None: d = abs(vrts[0][0]-x) + abs(vrts[0][1]-y) if d< max_val: max_val=d max_ind=ind return max_ind def delete(self,event): if self.selected_obj_ind != -1: self.object_list.pop(self.selected_obj_ind) self.selected_obj_ind=-1 self.verts_list=[None]*len(self.verts_list) self.draw_objs() plt.draw() def save(self,event): with open(self.data_fname,'wb') as fd: pickle.dump(self.object_list,fd) def add(self,event): #import pdb;pdb.set_trace() if any(self.verts_list): obj = {} obj['pts'] = self.verts_list.copy() obj['desc'] = self.text_box.text self.object_list.append(obj) print('len of object_list',len(self.object_list)) self.draw_objs() plt.draw() self.verts_list=[None]*len(self.verts_list) def onclick(self,event): #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)) ind=self.sfigs.index(event.inaxes) if event.inaxes in self.sfigs else -1 print('inaxes',ind) x,y=event.xdata,event.ydata #self.sfigs[ind].plot(x,y,'or') if ind >= 0: self.last_ind_ax=ind cind=self.get_closest(x,y,ind) self.selected_obj_ind=cind if cind>-1: self.draw_objs(cind) self.text_box.set_val(self.object_list[cind]['desc']) plt.draw()
class TTFVisualizer: index = 0 node_colors = [] node_labels = {} node_positions = [] interactions = [] G = nx.MultiGraph() ax = [] run_btn = {} next_trans_btn = {} next_btn = {} def __init__(self, ttfObject): self.index = 0 self.ttfObject = ttfObject #set the graph self.G.add_nodes_from(range(0, self.ttfObject.numberOfAgents)) self.node_positions = nx.spring_layout(self.G) def run_ttf(self): #turn on pyplot interactive mode fig, axes = plt.subplots(nrows=2, ncols=1, gridspec_kw={'height_ratios': [20, 1]}) self.ax = axes.flatten() self.ax[1].set_axis_off() plt.tight_layout() #set the node colors self.set_node_colors() self.set_node_labels() nx.draw(self.G, ax=self.ax[0], with_labels=True, node_color=self.node_colors, pos=self.node_positions, labels=self.node_labels, linewidths=4, font_size=12, node_size=500, width=2) for i in range(self.ttfObject.numberOfAgents): for j in range(self.ttfObject.numberOfAgents): if (self.ttfObject.probabilityMatrix[i][j] != 0): source, target = i, j arrowprops = dict( arrowstyle="<|-", color="black", shrinkA=15, shrinkB=15, patchA=None, patchB=None, connectionstyle="arc3,rad=0.1", ) self.ax[0].annotate("", xy=self.node_positions[source], xytext=self.node_positions[target], arrowprops=arrowprops) #run button axcut = plt.axes([0.01, 0.002, 0.1, 0.075]) self.run_btn = Button(axcut, 'Run') self.run_btn.on_clicked(self.run) #next button axcut = plt.axes([0.12, 0.002, 0.1, 0.075]) self.next_btn = Button(axcut, 'Next') self.next_btn.on_clicked(self.next) # #next data transfer button # axcut = plt.axes([0.23, 0.002, 0.2, 0.075]) # self.next_trans_btn = Button(axcut, 'Next Transfer') # self.next_trans_btn.on_clicked(self.run) #add text with time and energy consumed axcut = plt.axes([0.63, 0.002, 0.1, 0.075]) self.text_box1 = TextBox(axcut, "Time: ") self.text_box1.set_val(str(self.index)) axcut = plt.axes([0.83, 0.002, 0.1, 0.075]) self.text_box2 = TextBox(axcut, "Energy: ") self.text_box2.set_val(str(self.ttfObject.numberOfDataTransitions)) plt.show() def run(self, event): while self.ttfObject.is_termination_configuration() != True: self.ax[0].clear() self.interactions.append(self.ttfObject.next_interaction()) print('Current interaction: ', self.interactions[self.index]) print('Number of interactions: ', self.index + 1) #set the node colors self.set_node_colors() self.set_node_labels() self.set_sender_reciver_colors(self.index) self.set_interaction_edge_color(self.index) nx.draw(self.G, ax=self.ax[0], with_labels=True, node_color=self.node_colors, pos=self.node_positions, labels=self.node_labels, linewidths=4, font_size=12, node_size=500, width=2) if not self.ttfObject.is_termination_configuration(): plt.pause(0.2) self.text_box2.set_val( str(self.ttfObject.numberOfDataTransitions)) # #increase the interactions number self.index += 1 self.text_box1.set_val(str(self.index)) plt.show() else: print("Done") self.index += 1 self.text_box1.set_val(str(self.index)) self.text_box2.set_val( str(self.ttfObject.numberOfDataTransitions)) plt.show() f = open("test_ttf.txt", "a+") f.write("Time = " + str(self.index) + " Energy = " + str(self.ttfObject.numberOfDataTransitions) + '\n') f.close() plt.show(block=True) def next(self, event): if not self.ttfObject.is_termination_configuration(): self.ax[0].clear() self.interactions.append(self.ttfObject.next_interaction()) print('Current interaction: ', self.interactions[self.index]) print('Number of interactions: ', self.index + 1) print('Number of transitions: ', self.ttfObject.numberOfDataTransitions) #set the node colors self.set_node_colors() self.set_node_labels() self.set_sender_reciver_colors(self.index) self.set_interaction_edge_color(self.index) nx.draw(self.G, ax=self.ax[0], with_labels=True, node_color=self.node_colors, pos=self.node_positions, labels=self.node_labels, linewidths=4, font_size=12, node_size=500, width=2) self.text_box2.set_val(str(self.ttfObject.numberOfDataTransitions)) self.index += 1 self.text_box1.set_val(str(self.index)) plt.show() def set_node_colors(self): #set the node colors and positions self.node_colors = [] self.node_colors = [ 'blue' for x in range(self.ttfObject.numberOfAgents) ] for i in range(len(self.ttfObject.tokenList)): if self.ttfObject.tokenList[i] == 0: self.node_colors[i] = 'gray' self.node_colors[0] = 'red' def set_sender_reciver_colors(self, interactionIndex): self.node_colors[self.interactions[interactionIndex][0][1]] = 'yellow' self.node_colors[self.interactions[interactionIndex][0][0]] = 'orange' if self.ttfObject.is_termination_configuration(): self.node_colors[0] = 'green' def set_node_labels(self): #set the node lables k = 0 self.node_labels = {} for el in self.ttfObject.tokenList: self.node_labels[k] = chr(ord('a') + k) + '(' + str(el) + ')' k += 1 def set_interaction_edge_color(self, interactionIndex): for i in range(self.ttfObject.numberOfAgents): for j in range(self.ttfObject.numberOfAgents): if (self.ttfObject.probabilityMatrix[i][j] != 0): source, target = i, j color = 'black' if source == self.interactions[ self.index][0][1] and target == self.interactions[ self.index][0][0]: if self.interactions[self.index][1]: color = 'green' else: color = 'red' arrowprops = dict( arrowstyle="<|-", color=color, shrinkA=15, shrinkB=15, patchA=None, patchB=None, connectionstyle="arc3,rad=0.1", ) self.ax[0].annotate('', xy=self.node_positions[source], xytext=self.node_positions[target], arrowprops=arrowprops)
def _multi_fig(fig, num_cams, gain_min, gain_max, gain_default, exposure_min, exposure_max, exposure_default, fps_min, fps_max, fps_default): """ Creates multi cam GUI figure """ # Position params padding = 0.01 row_height = 0.02 num_top_rows = 1 num_bottom_rows = 4 num_cams_width = (1 - 3 * padding) / 2 cam_plot_height_offset = num_bottom_rows * row_height + num_bottom_rows * padding cam_plot_width = (1 - (num_cams + 1) * padding) / num_cams + 2 * padding cam_plot_height = 1 - cam_plot_height_offset - (num_top_rows * row_height + num_top_rows * padding) name_format_width = (1 - 3 * padding) / 2 save_width = (0.5 - (num_cams + 1.5) * padding) / (num_cams + 1) num_images_width = (((1 - 3 * padding) / 2) - 3 * padding) / 8 delay_width = (((1 - 3 * padding) / 2) - 3 * padding) / 8 num_bursts_width = (((1 - 3 * padding) / 2) - 3 * padding) / 8 counter_width = (((1 - 3 * padding) / 2) - 3 * padding) / 8 # num cams button num_cams_button_pos = [ padding, 1 - row_height - padding, num_cams_width, row_height ] num_cams_button_axes = fig.add_axes(num_cams_button_pos) num_cams_button = Button(num_cams_button_axes, 'Set # Cams') num_cams_button.label.set_fontsize(7) # num cams text num_cams_text_pos = [ num_cams_button_pos[0] + num_cams_button_pos[2] + padding, num_cams_button_pos[1], num_cams_width, row_height ] num_cams_text_axes = fig.add_axes(num_cams_text_pos) num_cams_text = TextBox(num_cams_text_axes, '') num_cams_text.set_val(str(num_cams)) # cam plots cam_plot_dicts = [] for i in range(num_cams): # Set camera string; note that first camera is considered "primary" cam_str = 'Cam ' + str(i + 1) if i == 0: cam_str = cam_str + " (primary)" else: cam_str = cam_str + " (secondary)" cam_plot_pos = [ i * (cam_plot_width - padding), cam_plot_height_offset, cam_plot_width, cam_plot_height ] cam_plot_dict = _cam_plot(fig, cam_plot_pos, cam_str, row_height, gain_min, gain_max, gain_default, padding) # Append cam_plot_dicts.append(cam_plot_dict) # exposure slider exposure_pos = [0, cam_plot_height_offset - row_height, 1, row_height] exposure_slider, exposure_text = _slider_with_text( fig, exposure_pos, 'Exposure', exposure_min, exposure_max, exposure_default, padding) # FPS slider fps_pos = [ exposure_pos[0], exposure_pos[1] - row_height - padding, 1, row_height ] fps_slider, fps_text = _slider_with_text(fig, fps_pos, 'FPS', fps_min, fps_max, fps_default, padding) # name format name_format_pos = [ name_format_width + 2 * padding, fps_pos[1] - row_height - padding, name_format_width, row_height ] name_format_axes = fig.add_axes(name_format_pos) name_format_text = TextBox(name_format_axes, 'Name format') name_format_text.label.set_fontsize(7) name_format_text.set_val('{serial}_{datetime}_{cam}_{frameid}_{counter}') # save cam buttons save_cam_buttons = [] for i in range(num_cams): # save button save_cam_button_pos = [ i * save_width + (i + 1) * padding, padding, save_width, row_height ] save_cam_button_axes = fig.add_axes(save_cam_button_pos) save_cam_button = Button(save_cam_button_axes, 'Save Cam "' + str(i + 1) + '"') save_cam_button.label.set_fontsize(7) # Append save_cam_buttons.append(save_cam_button) # multi save button save_multi_button_pos = [ num_cams * save_width + (num_cams + 1) * padding, padding, save_width, row_height ] save_multi_button_axes = fig.add_axes(save_multi_button_pos) save_multi_button = Button(save_multi_button_axes, 'Save Multi') save_multi_button.label.set_fontsize(7) # num images text num_images_text_pos = [ save_multi_button_pos[0] + save_multi_button_pos[2] + num_images_width + padding, save_multi_button_pos[1], num_images_width, row_height ] num_images_text_axes = fig.add_axes(num_images_text_pos) num_images_text = TextBox(num_images_text_axes, '# Images') num_images_text.label.set_fontsize(7) num_images_text.set_val(1) # delay delay_pos = [ num_images_text_pos[0] + num_images_text_pos[2] + delay_width + padding, num_images_text_pos[1], delay_width, row_height ] delay_axes = fig.add_axes(delay_pos) delay_text = TextBox(delay_axes, 'Delay') delay_text.label.set_fontsize(7) delay_text.set_val(1) # num burst num_bursts_pos = [ delay_pos[0] + delay_pos[2] + num_bursts_width + padding, delay_pos[1], num_bursts_width, row_height ] num_bursts_axes = fig.add_axes(num_bursts_pos) num_bursts_text = TextBox(num_bursts_axes, '# Burst') num_bursts_text.label.set_fontsize(7) num_bursts_text.set_val(1) # counter counter_pos = [ num_bursts_pos[0] + num_bursts_pos[2] + counter_width + padding, num_bursts_pos[1], counter_width, row_height ] counter_axes = fig.add_axes(counter_pos) counter_text = TextBox(counter_axes, 'Counter') counter_text.label.set_fontsize(7) counter_text.set_val(1) return { 'num_cams_button': num_cams_button, 'num_cams_text': num_cams_text, 'cam_plot_dicts': cam_plot_dicts, 'exposure_slider': exposure_slider, 'exposure_text': exposure_text, 'fps_slider': fps_slider, 'fps_text': fps_text, 'name_format_text': name_format_text, 'save_cam_buttons': save_cam_buttons, 'save_multi_button': save_multi_button, 'num_images_text': num_images_text, 'delay_text': delay_text, 'num_bursts_text': num_bursts_text, 'counter_text': counter_text }
class PointBrowser(object): """ See "Event Handling" example from matplotlib documentation: https://matplotlib.org/examples/event_handling/data_browser.html Click on a point to select and highlight it -- the data that generated the point will be shown in the lower axes. Use the 'a' and 's' keys to browse through the next and previous points along x-axis (ordered by RdTools estimate). """ def __init__(self, data, xlim=None, ylim=None, prcntl=95): logging.info('NEW SESSION') warnings.filterwarnings("ignore") self.scsf_cache_dir = './local_cache/' if not os.path.exists(self.scsf_cache_dir): os.makedirs(self.scsf_cache_dir) ordering = np.argsort(data['rd']).values self.data = data.iloc[ordering] self.xs = self.data['rd'].values self.ys = self.data['deg'].values gs = GridSpec(4, 3) fig = plt.figure('DataViewer', figsize=(8, 16)) ax = [plt.subplot(gs[0, :2])] # Main scatter plot with sns.axes_style('white'): ax.append(plt.subplot(gs[0, -1])) # Record viewing panel ax[-1].set_axis_off() ax.append(plt.subplot(gs[1, :])) # Timeseries heatmap view ax.append(plt.subplot(gs[2, :])) # ClearSky heatmap view ax.append(plt.subplot(gs[3, :])) # Daily Energy view self.fig = fig self.ax = ax self.ax[0].set_title('click on point to view record') self.ax[0].set_xlabel('RdTools Estimate YoY deg (%)') self.ax[0].set_ylabel('SCSF Estimate YoY deg (%)') self.ax[2].set_title('Measured power') self.ax[2].set_xlabel('Day number') self.ax[2].set_yticks([]) self.ax[2].set_ylabel('(sunset) Time of day (sunrise)') self.line, = self.ax[0].plot(self.xs, self.ys, '.', picker=5) # 5 points tolerance m = np.logical_and( np.logical_and( self.data['res-median'] < np.percentile(self.data['res-median'], prcntl), self.data['res-var'] < np.percentile(self.data['res-var'], prcntl) ), self.data['res-L0norm'] < np.percentile(self.data['res-L0norm'], prcntl) ) m = np.logical_not(m.values) self.ax[0].plot(self.xs[m], self.ys[m], '.') if xlim is None: xlim = self.ax[0].get_xlim() if ylim is None: ylim = self.ax[0].get_ylim() pts = ( min(xlim[0], ylim[0]), max(xlim[1], ylim[1]) ) self.ax[0].plot(pts, pts, ls='--', color='red') self.ax[0].set_xlim(xlim) self.ax[0].set_ylim(ylim) self.text = self.ax[0].text(0.05, 0.95, 'system ID: none', transform=self.ax[0].transAxes, va='top') self.selected, = self.ax[0].plot([self.xs[0]], [self.ys[0]], 'o', ms=6, alpha=0.4, color='yellow', visible=False) with sns.axes_style('white'): ax.append(plt.axes([.77, .5 * (1 + .57), .2, .05 / 2])) # Text box entry ax.append(plt.axes([.82, .5 * (1 + .5), .1, .05 / 2])) # run SCSF button self.text_box = TextBox(self.ax[-2], 'ID Number') self.button = Button(self.ax[-1], 'run SCSF', color='red') self.lastind = None self.D = None self.ics = None self.cb = None self.cb2 = None self.local_cash = {} self.prcntl = prcntl plt.tight_layout() self.fig.canvas.mpl_connect('pick_event', self.onpick) self.fig.canvas.mpl_connect('key_press_event', self.onpress) self.text_box.on_submit(self.submit) self.button.on_clicked(self.clicked) plt.show() def submit(self, text): logging.info('submit: ' + str(text)) asrt = np.argsort(np.abs(self.data.index - float(text))) sysid = self.data.index[asrt[0]] bool_list = self.data.index == sysid # bool_list = self.data.index == int(text) index_lookup = np.arange(self.data.shape[0]) self.lastind = int(index_lookup[bool_list]) logging.info('selected index: ' + str(self.lastind)) self.update() def clicked(self, event): if self.lastind is None: logging.info('button click: nothing selected!') return sysid = self.data.iloc[self.lastind].name logging.info('button click: current ID: {}'.format(sysid)) self.ax[3].cla() self.ax[3].text(0.05, 0.95, 'initializing algorithm...', transform=self.ax[3].transAxes, va='top', fontname='monospace') self.ax[3].set_xlabel('Day number') self.ax[3].set_yticks([]) self.ax[3].set_ylabel('(sunset) Time of day (sunrise)') plt.tight_layout() self.fig.canvas.draw() self.ax[4].cla() D = self.D cached_files = os.listdir(self.scsf_cache_dir) fn = 'pvo_' + str(sysid) + '.scsf' if fn in cached_files: ics = IterativeClearSky() ics.load_instance(self.scsf_cache_dir + fn) self.ics = ics self.ax[4].plot(np.sum(ics.D, axis=0) * 24 / ics.D.shape[0], linewidth=1, label='raw data') use_day = ics.weights > 1e-1 days = np.arange(ics.D.shape[1]) self.ax[4].scatter(days[use_day], np.sum(ics.D, axis=0)[use_day] * 24 / ics.D.shape[0], color='orange', alpha=0.7, label='days selected') self.ax[4].legend() self.ax[4].set_title('Daily Energy') self.ax[4].set_xlabel('Day Number') self.ax[4].set_ylabel('kWh') self.ax[3].cla() self.ax[3].text(0.05, 0.95, 'loading cached results...', transform=self.ax[3].transAxes, va='top', fontname='monospace') self.ax[3].set_xlabel('Day number') self.ax[3].set_yticks([]) self.ax[3].set_ylabel('(sunset) Time of day (sunrise)') self.show_ticks(self.ax[2]) plt.tight_layout() self.fig.canvas.draw() else: ics = IterativeClearSky(D) self.ics = ics self.ax[4].plot(np.sum(ics.D, axis=0) * 24 / ics.D.shape[0], linewidth=1, label='raw data') use_day = ics.weights > 1e-1 days = np.arange(ics.D.shape[1]) self.ax[4].scatter(days[use_day], np.sum(ics.D, axis=0)[use_day] * 24 / ics.D.shape[0], color='orange', alpha=0.7, label='days selected') self.ax[4].legend() self.ax[4].set_title('Daily Energy') self.ax[4].set_xlabel('Day Number') self.ax[4].set_ylabel('kWh') self.ax[3].cla() self.ax[3].text(0.05, 0.95, 'running algorithm...', transform=self.ax[3].transAxes, va='top', fontname='monospace') self.ax[3].set_xlabel('Day number') self.ax[3].set_yticks([]) self.ax[3].set_ylabel('(sunset) Time of day (sunrise)') self.show_ticks(self.ax[2]) plt.tight_layout() self.fig.canvas.draw() logging.info('starting algorithm') config_l = CONFIG1.copy() config_l['max_iter'] = 1 obj_vals = ics.calc_objective(False) old_obj = np.sum(obj_vals) ti = time.time() for cntr in range(CONFIG1['max_iter']): ics.minimize_objective(**config_l) logging.info('min iteration {} complete'.format(cntr + 1)) obj_vals = ics.calc_objective(False) new_obj = np.sum(obj_vals) improvement = (old_obj - new_obj) * 1. / old_obj self.ax[3].cla() self.ax[3].set_xlabel('Day number') self.ax[3].set_yticks([]) self.ax[3].set_ylabel('(sunset) Time of day (sunrise)') s1 = 'Iteration {} complete: obj = {:.2f}, f1 = {:.2f}'.format(cntr + 1, new_obj, obj_vals[0]) s2 = 'Improvement: {:.2f}%'.format(100 * improvement) tf = time.time() s3 = 'Time elapsed: {:.2f} minutes'.format((tf - ti) / 60.) textout = '\n'.join([s1, s2, s3]) logging.info(textout) self.ax[3].text(0.05, 0.95, textout, transform=self.ax[3].transAxes, va='top', fontname='monospace') plt.tight_layout() self.fig.canvas.draw() old_obj = new_obj if improvement <= CONFIG1['eps']: break ics.save_instance(self.scsf_cache_dir + fn) logging.info('algorithm complete') self.ax[4].plot((ics.R_cs.value[0] * np.sum(ics.L_cs.value[:, 0])) * 24 / ics.D.shape[0], linewidth=1, label='clear sky estimate') self.ax[4].legend() logging.info('first plot complete') with sns.axes_style('white'): self.ax[3].cla() bar = self.ax[3].imshow(ics.L_cs.value.dot(ics.R_cs.value), cmap='hot', vmin=0, vmax=np.max(ics.D), interpolation='none', aspect='auto') if self.cb2 is not None: self.cb2.remove() self.cb2 = plt.colorbar(bar, ax=self.ax[3], label='kW') self.show_ticks(self.ax[3]) self.ax[3].set_title('Estimated clear sky power') self.ax[3].set_xlabel('Day number') self.ax[3].set_yticks([]) self.ax[3].set_ylabel('(sunset) Time of day (sunrise)') logging.info('second plot complete') plt.tight_layout() self.fig.canvas.draw() return def show_ticks(self, ax): xlim = ax.get_xlim() ylim = ax.get_ylim() use_day = self.ics.weights > 1e-1 days = np.arange(self.ics.D.shape[1]) y1 = np.ones_like(days[use_day]) * self.D.shape[0] * .99 ax.scatter(days[use_day], y1, marker='|', color='yellow', s=2) ax.scatter(days[use_day], .995 * y1, marker='|', color='yellow', s=2) ax.set_xlim(*xlim) ax.set_ylim(*ylim) return def onpress(self, event): if self.lastind is None: return logging.info('press event: ' + str(event.key)) if event.key == 'a': inc = -1 self.lastind += inc self.lastind = np.clip(self.lastind, 0, len(self.xs) - 1) elif event.key == 's': inc = 1 self.lastind += inc self.lastind = np.clip(self.lastind, 0, len(self.xs) - 1) else: return self.update() def onpick(self, event): if event.artist != self.line: return True N = len(event.ind) if not N: return True # the click locations x = event.mouseevent.xdata y = event.mouseevent.ydata logging.info('pick: ' + str(x) + ', ' + str(y)) distances = np.hypot(x - self.xs[event.ind], y - self.ys[event.ind]) indmin = distances.argmin() dataind = event.ind[indmin] self.lastind = dataind self.update() def update(self): if self.lastind is None: return dataind = self.lastind prcntl = self.prcntl logging.info('updating, ID = {}'.format(self.data.iloc[dataind].name)) self.selected.set_visible(True) self.selected.set_data(self.xs[dataind], self.ys[dataind]) out1 = 'system ID: {:d}'.format(self.data.iloc[dataind].name) out2 = str(self.data.iloc[dataind]) # self.text_box.set_val('') idxs = np.arange(len(self.data.columns)) if self.data.iloc[dataind]['res-median'] > np.percentile(self.data['res-median'], prcntl): l1 = out2.split('\n') i = idxs[self.data.columns == 'res-median'][0] l1[i] = '*' + l1[i][:-2] + '*' out2 = '\n'.join(l1) if self.data.iloc[dataind]['res-var'] > np.percentile(self.data['res-var'], prcntl): l1 = out2.split('\n') i = idxs[self.data.columns == 'res-var'][0] l1[i] = '*' + l1[i][:-2] + '*' out2 = '\n'.join(l1) if self.data.iloc[dataind]['res-L0norm'] > np.percentile(self.data['res-L0norm'], prcntl): l1 = out2.split('\n') i = idxs[self.data.columns == 'res-L0norm'][0] l1[i] = '*' + l1[i][:-2] + '*' out2 = '\n'.join(l1) self.text.set_text(out1) self.ax[1].cla() self.ax[1].text(0.00, 0.95, out2, transform=self.ax[1].transAxes, va='top', fontname='monospace') self.ax[1].set_axis_off() self.ax[2].cla() self.ax[2].text(0.05, 0.95, 'data loading...', transform=self.ax[2].transAxes, va='top', fontname='monospace') self.ax[2].set_xlabel('Day number') self.ax[2].set_yticks([]) self.ax[2].set_ylabel('(sunset) Time of day (sunrise)') self.ax[3].cla() self.ax[4].cla() self.ics = None plt.tight_layout() self.fig.canvas.draw() with sns.axes_style('white'): idnum = self.data.iloc[dataind].name if idnum in self.local_cash.keys(): df = self.local_cash[idnum] else: df = load_sys(idnum=idnum, local=False) self.local_cash[idnum] = df days = df.resample('D').max().index[1:-1] start = days[0] end = days[-1] D = df.loc[start:end].iloc[:-1].values.reshape(288, -1, order='F') self.D = D self.ax[2].cla() foo = self.ax[2].imshow(D, cmap='hot', interpolation='none', aspect='auto') if self.cb is not None: self.cb.remove() self.cb = plt.colorbar(foo, ax=self.ax[2], label='kW') self.ax[2].set_xlabel('Day number') self.ax[2].set_yticks([]) self.ax[2].set_ylabel('(sunset) Time of day (sunrise)') self.ax[2].set_title('Measured power') self.text_box.set_val('') self.fig.canvas.draw()
class LoaderUI(object): def __init__(self): self.full_sensor_data = None self.selected_data = None self.spe_file = None self.selector = None self.pol_angle = None self.success = False dir = os.path.dirname(__file__) filename = os.path.join(dir, 'style', 'custom-wa.mplstyle') plt.style.use(filename) fig = plt.figure() fig.set_size_inches(20, 12, forward=True) fig.canvas.set_window_title('Load Data') grid_shape = (16, 28) # Make open, load, draw buttons self.full_sensor_ax = plt.subplot2grid(grid_shape, (0, 0), colspan=13, rowspan=13) self.selected_ax = plt.subplot2grid(grid_shape, (0, 15), colspan=13, rowspan=4) axopen = plt.subplot2grid(grid_shape, (14, 4), colspan=4) axload = plt.subplot2grid(grid_shape, (14, 20), colspan=4) axfull_lambda = plt.subplot2grid(grid_shape, (11, 16), colspan=4) axpix_min = plt.subplot2grid(grid_shape, (7, 16), colspan=4) axpix_max = plt.subplot2grid(grid_shape, (7, 23), colspan=4) axrefresh = plt.subplot2grid(grid_shape, (11, 23), colspan=4) axpol_angle = plt.subplot2grid(grid_shape, (9, 23), colspan=4) bload = Button(axload, 'Load Selected', color='0.25', hovercolor='0.3') bload.on_clicked(self._load_callback) bopen = Button(axopen, 'Open File', color='0.25', hovercolor='0.3') bopen.on_clicked(self._open_callback) self.chk_full_lambda = CheckButtons(axfull_lambda, ['Full Lambda'], [True]) self.chk_full_lambda.on_clicked(self._full_lambda_callback) self.ypix_min = TextBox(axpix_min, 'Sel. Lower \nbound ', '0', color='0.25', hovercolor='0.3') self.ypix_max = TextBox(axpix_max, 'Sel. Upper \nbound ', '0', color='0.25', hovercolor='0.3') self.refresh_selection = Button(axrefresh, 'Refresh Selection', color='0.25', hovercolor='0.3') self.refresh_selection.on_clicked(self._refresh_selection_callback) self.txt_pol_angle = TextBox(axpol_angle, 'Pol. Angle \n (deg) ', '0', color='0.25', hovercolor='0.3') self._full_lambda_callback(None) plt.show(block=True) def _rect_select_callback(self, eclick, erelease): x1, y1 = eclick.xdata, eclick.ydata x2, y2 = erelease.xdata, erelease.ydata def _span_select_callback(self, ymin, ymax): ymin = int(np.floor(ymin)) ymax = int(np.ceil(ymax)) self.selected_data = self.full_sensor_data[ymin:ymax + 1, :] self.ypix_min.set_val(str(ymin)) self.ypix_max.set_val(str(ymax)) self._image_selected_data(ymin, ymax) def _load_callback(self, event): if self.spe_file is not None and self.selected_data is not None: self.pol_angle = float(self.txt_pol_angle.text) self.success = True plt.close() mpl.rcParams.update(mpl.rcParamsDefault) else: print( 'Invalid loader state: must have loaded file and selected data' ) def _open_callback(self, event): # calls general observation load function root = tk.Tk() root.withdraw() filename = filedialog.askopenfilename() if filename is not '': self.spe_file = spe_loader.load_from_files([filename]) self.full_sensor_data = self.spe_file.data[0][0] self._image_full_sensor_data() else: return def _full_lambda_callback(self, event): if self.selector is not None: self.selector.set_visible(False) chk_status = self.chk_full_lambda.get_status() if chk_status[0]: self.selector = SpanSelector(self.full_sensor_ax, self._span_select_callback, direction='vertical', minspan=5, useblit=True, span_stays=False, rectprops=dict(facecolor='red', alpha=0.2)) else: self.selector = RectangleSelector( self.full_sensor_ax, self._rect_select_callback, drawtype='box', useblit=True, button=[1, 3], # don't use middle button minspanx=1, minspany=0.1, spancoords='data', interactive=True) def _refresh_selection_callback(self, event): ymin = int(self.ypix_min.text) ymax = int(self.ypix_max.text) self._span_select_callback(ymin, ymax) def _image_full_sensor_data(self): self.full_sensor_ax.clear() self._full_lambda_callback(None) self.full_sensor_ax.imshow(self.full_sensor_data, aspect='auto') self.full_sensor_ax.set_title('Source Data') def _image_selected_data(self, ymin, ymax): self.selected_ax.clear() self.selected_ax.imshow(self.selected_data, aspect='auto') title = 'Selected: {0} rows ({1} -> {2})\n' \ '{3} wavelengths ({4:.3f} -> {5:.3f})'.format(self.selected_data.shape[0], ymin, ymax, self.selected_data.shape[1], self.spe_file.wavelength[0], self.spe_file.wavelength[-1]) self.selected_ax.set_title(title)
def __spincam_gui(): # Get figure fig = plt.figure(1) fig2 = plt.figure(2) # Set position params padding = 0.01 options_height = 0.02 num_options = 6 cam_plot_height_offset = num_options * options_height + num_options * padding cam_plot_width = 0.75 cam_plot_height = 1 - cam_plot_height_offset # cid = fig.canvas.mpl_connect('button_press_event', onclick) # Display Camera Image display_pos = [0, cam_plot_height_offset, cam_plot_width, cam_plot_height] display_dict = __cam_plot(fig, display_pos, 'Camera', options_height, padding) # Set initial values # Set callbacks display_dict['find_and_init_button'].on_clicked(__test) ''' # Start stream start_stream_button_pos = [padding, display_pos[1] - options_height, 0.5 - 2 * padding, options_height] start_stream_button_axes = fig.add_axes(start_stream_button_pos) start_stream_button = Button(start_stream_button_axes, 'Start Stream') start_stream_button.label.set_fontsize(7) # Set callback start_stream_button.on_clicked(__start_stream) # Stop stream stop_stream_button_pos = [start_stream_button_pos[0] + start_stream_button_pos[2] + 2 * padding, display_pos[1] - options_height, 0.5 - 2 * padding, options_height] stop_stream_button_axes = fig.add_axes(stop_stream_button_pos) stop_stream_button = Button(stop_stream_button_axes, 'Stop Stream') stop_stream_button.label.set_fontsize(7) # Set callback stop_stream_button.on_clicked(__stop_stream) ''' start_stream_button_pos = [ padding, display_pos[1] - options_height, 0.5 - 2 * padding, options_height ] start_stream_button_axes = fig.add_axes(start_stream_button_pos) start_stream_button = Button(start_stream_button_axes, 'Open Preview') start_stream_button.label.set_fontsize(7) # Set callback start_stream_button.on_clicked(__open_preview) # Stop stream stop_stream_button_pos = [ start_stream_button_pos[0] + start_stream_button_pos[2] + 2 * padding, display_pos[1] - options_height, 0.5 - 2 * padding, options_height ] stop_stream_button_axes = fig.add_axes(stop_stream_button_pos) stop_stream_button = Button(stop_stream_button_axes, 'Plot Histogram') stop_stream_button.label.set_fontsize(7) # Set callback stop_stream_button.on_clicked(__plot_hist) # fps fps_pos = [ 0, start_stream_button_pos[1] - options_height - padding, 1, options_height ] (fps_slider, fps_text) = __slider_with_text(fig, fps_pos, 'FPS', __FPS_MIN, __FPS_MAX, __FPS_MIN, padding) # Set callbacks fps_slider.on_changed(__fps_slider) fps_text.on_submit(__fps_text) # Exposure exposure_pos = [ 0, fps_pos[1] - options_height - padding, 1, options_height ] (exposure_slider, exposure_text) = __slider_with_text(fig, exposure_pos, 'Exposure', __EXPOSURE_MIN, __EXPOSURE_MAX, __EXPOSURE_MIN, padding) # Set callbacks exposure_slider.on_changed(__exposure_slider) exposure_text.on_submit(__exposure_text) # Set default directory to current directory_pos = [0.13, exposure_pos[1], 0.66, options_height] directory_axes = fig2.add_axes(directory_pos) directory_text = TextBox(directory_axes, 'Directory') directory_text.label.set_fontsize(7) directory_text.set_val('') directory_button_pos = [ directory_pos[0] + directory_pos[2] + padding, directory_pos[1], 0.16, options_height ] directory_button_axes = fig2.add_axes(directory_button_pos) directory_button = Button(directory_button_axes, 'Choose Directory') directory_button.label.set_fontsize(7) directory_button.on_clicked(__choose_directory) # Set name format name_format_pos = [ directory_pos[0], directory_pos[1] - options_height - padding, 0.5, options_height ] name_format_axes = fig2.add_axes(name_format_pos) name_format_text = TextBox(name_format_axes, 'Name format') name_format_text.label.set_fontsize(7) name_format_text.set_val('test_{date}') # Set save primary button save_button_pos = [ directory_pos[1], name_format_pos[1] - options_height - padding, 0.2, options_height ] save_button_axes = fig2.add_axes(save_button_pos) save_button = Button(save_button_axes, 'Start Acquisition(no z-scan)') save_button.label.set_fontsize(7) # Set callback save_button.on_clicked(__acquire_no_z) # Set num images text num_images_text_pos = [ cam_plot_width - 20 * padding, padding, 0.1 - 2 * padding, options_height ] #save_button.on_clicked(__save_fourcolor) # Set num images text avg_images_text_pos = [ save_button_pos[0] + save_button_pos[2] + padding + (0.5 - 2 * padding) * 0.1875 + 2 * padding, name_format_pos[1] - options_height - padding, (0.2 - 2 * padding) * 0.8125 - padding, options_height ] avg_images_text_axes = fig2.add_axes(avg_images_text_pos) avg_images_text = TextBox(avg_images_text_axes, '# to Avg') avg_images_text.label.set_fontsize(7) avg_images_text.set_val(10) # Set counter counter_pos = [ name_format_pos[0] + name_format_pos[2] + 10 * padding, directory_pos[1] - options_height - padding, 0.2, options_height ] counter_axes = fig2.add_axes(counter_pos) counter_text = TextBox(counter_axes, 'Counter') counter_text.label.set_fontsize(7) counter_text.set_val(1) # Set num images text num_images_text_pos = [ avg_images_text_pos[0] + avg_images_text_pos[2] + 12 * padding, name_format_pos[1] - options_height - padding, 0.1 - 2 * padding, options_height ] num_images_text_axes = fig2.add_axes(num_images_text_pos) num_images_text = TextBox(num_images_text_axes, '# to Acquire') num_images_text.label.set_fontsize(7) num_images_text.set_val(1) stage_dict = __stage_gui(fig2) return { 'fig': fig, 'display_dict': display_dict, 'stage_dict': stage_dict, 'start_stream_button': start_stream_button, 'stop_stream_button': stop_stream_button, 'save_button': save_button, 'name_format_text': name_format_text, 'counter_text': counter_text, 'num_images_text': num_images_text, 'avg_images_text': avg_images_text, 'fps_slider': fps_slider, 'fps_text': fps_text, 'exposure_slider': exposure_slider, 'exposure_text': exposure_text, 'directory_text': directory_text, 'directory_button': directory_button }
class ViewImageStack(object): # interactive image viewer for a stack of images def __init__(self, chunk_size_current, img_data, trainid_data, pulseid_data, cellid_data, is_good_data, view_cmap, view_clip, view_figsz, dir_save): self.fg = plt.figure(figsize=view_figsz) self.cmap = view_cmap self.clmin = view_clip[0] self.clmax = view_clip[1] self.dirsave = dir_save self.imtot = chunk_size_current # total number of images in the file self.X = img_data self.trainid_data = trainid_data self.pulseid_data = pulseid_data self.cellid_data = cellid_data self.imtype = is_good_data self.ind = 0 #initial image to show, e.g, 0 or self.imtot//2 self.mindef = self.clmin # default values used for error handling self.maxdef = self.clmax clrinact = 'lightgray' # 'papayawhip' 'lightgray' clrhover = 'darkgray' # 'salmon' 'darkgray' # axes for all visual components self.ax = self.fg.add_subplot(111) self.cbax = self.fg.add_axes( [0.85, 0.1, 0.03, 0.77]) # for colorbar [left, bottom, width, height] self.resax = self.fg.add_axes([0.05, 0.83, 0.1, 0.05]) # for 'Reset' button self.rax = self.fg.add_axes([0.05, 0.72, 0.1, 0.1], facecolor=clrinact) # for radiobuttons self.minax = self.fg.add_axes([0.04, 0.65, 0.12, 0.05 ]) # min and max values for datarange self.maxax = self.fg.add_axes([0.04, 0.59, 0.12, 0.05]) self.imnax = self.fg.add_axes([0.07, 0.23, 0.1, 0.04], facecolor=clrinact) # chose image number self.savebut = self.fg.add_axes([0.05, 0.30, 0.1, 0.05]) # for 'Save image' button self.exitax = self.fg.add_axes([0.05, 0.93, 0.1, 0.05]) # for 'Exit' button # visual components self.im = self.ax.imshow(self.X[self.ind, :, :], cmap=self.cmap, clim=(self.clmin, self.clmax)) self.cls = plt.colorbar(self.im, cax=self.cbax) self.averbtn = Button(self.savebut, 'Save png', color=clrinact) self.resbt = Button(self.resax, 'Reset view', color=clrinact) self.exitbt = Button(self.exitax, 'Exit', color=clrinact) self.tbmin = TextBox(self.minax, 'min:', initial=str(self.clmin), color=clrinact) self.tbmax = TextBox(self.maxax, 'max:', initial=str(self.clmax), color=clrinact) self.rb = RadioButtons(self.rax, ('lin', 'log'), active=0, activecolor='red') self.imnum = TextBox(self.imnax, 'jump to \n image #', initial=str(self.ind + 1), color=clrinact) strID = "Image information:\nTrainID: " + str( self.trainid_data[self.ind]) + "\nPulseID: " + str( self.pulseid_data[self.ind]) + "\nCellID: " + str( self.cellid_data[self.ind]) + "\nImage type: " + str( self.imtype[self.ind]) self.textID = self.fg.text(0.03, 0.085, strID) self.fg.canvas.mpl_connect('scroll_event', self.on_scroll) self.fg.canvas.mpl_connect('button_press_event', self.on_press) self.fg.canvas.mpl_connect('key_press_event', self.on_keypress) self.update() plt.show(block=True) def on_press(self, event): if event.inaxes == self.rax.axes: self.update() if event.inaxes == self.resax.axes: # 'reset view' button : default datarange, linear scale, first image self.clmin = self.mindef self.clmax = self.maxdef self.tbmin.set_val(self.clmin) self.tbmax.set_val(self.clmax) self.rb.set_active(0) self.ind = 0 print('updated data range is [%s , %s ]' % (self.clmin, self.clmax)) self.update() if event.inaxes == self.exitax: sys.exit(0) # exit program if event.inaxes == self.savebut.axes: # 'save image' button items = [ self.ax, self.cbax, self.cbax.get_yaxis().get_label(), self.ax.get_xaxis().get_label(), self.ax.get_yaxis().get_label() ] bbox = Bbox.union([item.get_window_extent() for item in items]) extent = bbox.transformed(self.fg.dpi_scale_trans.inverted()) strim = 'img_trID_{:d}_plID_{:d}_ceID_{:d}_type_{:d}.png'.format( self.trainid_data[self.ind], self.pulseid_data[self.ind], self.cellid_data[self.ind], self.imtype[self.ind]) self.fg.savefig(self.dirsave + strim, bbox_inches=extent) def on_keypress(self, event): #if (event.inaxes == self.minax.axes) | (event.inaxes == self.maxax.axes): if event.key == 'enter': #one may face bad script behaviour here if some specific keyboard button like 'tab' has been pressed try: self.clmin = float(self.tbmin.text) self.clmax = float(self.tbmax.text) #print('updated data range is [%s , %s ]' %(self.clmin, self.clmax)) except: print('inappropriate values for data range specified') self.tbmin.set_val(self.clmin) self.tbmax.set_val(self.clmax) self.update() if (event.inaxes == self.imnax): if event.key == 'enter': #one may face bad script behaviour here if some specific keyboard button like 'tab' has been pressed try: val = int(self.imnum.text) if (val >= 1) & (val <= self.imtot): self.ind = val - 1 else: print('inappropriate image number specified') self.imnum.set_val(str(self.ind + 1)) except: print('inappropriate image number specified') self.imnum.set_val(str(self.ind + 1)) self.update() def on_scroll(self, event): if event.button == 'up': self.ind = (self.ind + 1) % self.imtot else: self.ind = (self.ind - 1) % self.imtot self.update() def update(self): self.ax.set_title( 'image # %s out of %s \n (use scroll wheel to navigate through images)' % (self.ind + 1, self.imtot)) # choose linear or log scale if self.rb.value_selected == 'lin': self.im.set_data(self.X[self.ind, :, :]) else: with np.errstate(divide='ignore', invalid='ignore'): self.im.set_data(np.log10(self.X[self.ind, :, :])) #self.im.set_clim(np.log10(self.clmin), np.log10(self.clmax)) #adjust scaling # update display representation self.im.set_clim(self.clmin, self.clmax) strID = "Image information:\nTrainID: " + str( self.trainid_data[self.ind]) + "\nPulseID: " + str( self.pulseid_data[self.ind]) + "\nCellID: " + str( self.cellid_data[self.ind]) + "\nImage type: " + str( self.imtype[self.ind]) self.textID.set_text(strID) self.cls.draw_all() self.im.axes.figure.canvas.draw() def __del__(self): self.fg.canvas.mpl_disconnect(self.on_scroll) self.fg.canvas.mpl_disconnect(self.on_press) self.fg.canvas.mpl_disconnect(self.on_keypress)
class MultiRoi: def __init__(self, img=None, roi_types=(), color_cycle=('b', 'g', 'r', 'c', 'm', 'y', 'k'), finish_callback=None): self.color_cycle = color_cycle self.mask_fig = plt.figure(figsize=(5, 3)) self.mask_fig_idx = 1 self.mask_axes = self.mask_fig.gca() self.mask_axes.set_title("Mask") self.mask_axes.set_axis_off() self.img_fig = plt.figure(figsize=(10, 6)) self.img_fig_idx = 2 self.img_axes = self.img_fig.gca() self.img_axes.set_axis_off() self.__image = None self.image = img # __mask is for visualization, mask is for labling self.__mask = None if img is None else np.zeros(img.shape) self.mask = None if img is None else np.zeros(img.shape[:2]) self.rois = [] self.roi_values = {} self.roi_value_colors = {} self.current_roi_value = None self.finish_callback = finish_callback self.textbox = None self.buttons = [] for roi_name in roi_types: self.__add_roi_type(roi_name) self.make_widgets() self.update_plot() @property def image(self): return self.__image @image.setter def image(self, img): self.__image = img self.__mask = None if img is None else np.zeros(img.shape) self.mask = None if img is None else np.zeros(img.shape[:2], dtype=np.uint8) if self.__image is not None: self.update_plt_mask() self.update_plt_img() self.rois = [] # empty rois @staticmethod def update_plot(): if sys.flags.interactive: plt.show(block=False) else: plt.show(block=True) # plt.show(block=True) def make_widgets(self): self.textbox = TextBox(ax=plt.axes([0.2, 0.07, 0.1, 0.04]), label="ROI Name: ", initial='[roi_name]') btn_finish = Button(plt.axes([0.8, 0.07, 0.1, 0.04]), 'Finish') btn_finish.on_clicked(self.finish) btn_clear = Button(plt.axes([0.7, 0.07, 0.1, 0.04]), 'Clear') btn_clear.on_clicked(self.clear_rois) btn_add = Button(plt.axes([0.6, 0.07, 0.1, 0.04]), 'New ROI') btn_add.on_clicked(self.new_roi) btn_new = Button(plt.axes([0.5, 0.07, 0.1, 0.04]), 'New ROI Type') btn_new.on_clicked(self.new_roi_type) self.buttons.extend([btn_new, btn_add, btn_clear, btn_finish]) # plt.show(block=True) def completed(self): if len(self.rois) > 0: return all(r.completed for r in self.rois) else: return True def __add_roi_type(self, roi_name): if roi_name in self.roi_values.values(): return roi_value = len(self.roi_values.keys()) + 1 self.roi_values[roi_value] = roi_name self.roi_value_colors[roi_value] = np.random.rand(3) btn = Button(plt.axes([0.1 * roi_value, 0.02, 0.1, 0.04]), roi_name) btn.on_clicked(lambda x: self.select_roi_type(roi_value, x)) self.buttons.append(btn) return roi_value def select_roi_type(self, idx, event): self.textbox.set_val(self.roi_values[idx]) self.current_roi_value = idx def new_roi_type(self, event): roi_name = self.textbox.text if roi_name in self.roi_values.values(): msg = "{} ROI already exists.".format(roi_name) self.log(logging.WARNING, msg) return # roi_value begins from 1 roi_value = self.__add_roi_type(roi_name) self.roi_values[roi_value] = roi_name # generate a color randomly for this type of ROI self.roi_value_colors[roi_value] = np.random.rand(3) if self.completed(): self.current_roi_value = roi_value msg = "Add New ROI Type: {}".format(roi_name) self.log(logging.INFO, msg) self.update_plot() def new_roi(self, event): if self.__image is None: self.log(logging.WARNING, "No image loaded yet.") return if not self.completed(): self.log(logging.INFO, "Some roi(s) haven't been completed.") return rois_cnt = len(self.rois) try: roi_name = self.roi_values[self.current_roi_value] except KeyError: return msg = "Creating new ROI {}, type {}".format(rois_cnt, roi_name) self.log(logging.INFO, msg) plt.draw() roi_color = self.color_cycle[rois_cnt % len(self.color_cycle)] roi = RoiPoly(roi_val=self.current_roi_value, color=roi_color, fig=self.img_fig, ax=self.img_axes, close_fig=False, show_fig=False, completed_callback=self.roi_completed) self.rois.append(roi) def roi_completed(self, roi: RoiPoly): msg = "ROI {} completed".format(roi.value) self.log(logging.INFO, msg) mask = roi.get_mask(self.__image.shape[:2]) self.mask[mask] = roi.value self.__mask[mask] = self.roi_value_colors[roi.value] self.update_plt_mask() def clear_rois(self, event): self.image = np.array(self.__image) self.update_plot() def finish(self, event): if not self.completed(): self.log(logging.INFO, "Some roi(s) haven't been completed") return if self.finish_callback: if self.finish_callback(self): logging.log(logging.INFO, "Finishing signal from callback.") plt.close('all') else: pass else: self.log(logging.WARNING, "No callback after finishing a ROI.") plt.close('all') def update_img_title(self, title): plt.figure(self.img_fig_idx) self.img_axes.set_title(title) plt.draw() def update_mask_title(self, title): plt.figure(self.mask_fig_idx) plt.title(title) plt.draw() plt.figure(self.img_fig_idx) def update_plt_img(self): plt.figure(self.img_fig_idx) self.img_axes.clear() self.img_axes.set_axis_off() self.img_axes.imshow(self.__image) plt.draw() def update_plt_mask(self): plt.figure(self.mask_fig_idx) self.mask_axes.imshow(self.__mask) plt.draw() plt.figure(self.img_fig_idx) def log(self, level, msg): logging.log(level, msg) self.update_img_title(msg)
class Oscillo: def __init__( self, channel_list, sampling=5e3, volt_range=10.0, trigger_level=None, trigsource=None, backend="daqmx", ): if backend not in Backends: raise ValueError(f"Backend {backend:} is not available.") self.backend = backend self.channel_list = channel_list self.sampling = sampling self.volt_range = volt_range self.ignore_voltrange_submit = False self.trigger_level = trigger_level self.trigger_source = trigsource self.N = 1024 plt.ion() self.fig = plt.figure() # left, bottom, width, height self.ax = self.fig.add_axes([0.1, 0.1, 0.7, 0.8]) self.running = False self.last_trigged = 0 self.ask_pause_acqui = False self.paused = False self.freq = None self.Pxx = None self.N_spectra = 0 self.fig_spectrum = None self.hanning = True self.ac = True self.power_spectrum = True self.spectrum_unit = 1.0 self.spectrum_unit_str = "V^2/Hz" self.system = None self.saved_spectra = list() self.fig_stats = None # Configure widgets left, bottom = 0.825, 0.825 width, height = 0.15, 0.075 vpad = 0.02 ax_sampling = self.fig.add_axes([left, bottom, width, height]) bottom -= height * 1.25 + 2 * vpad self.textbox_sampling = TextBox(ax_sampling, label="", initial=f"{sampling:.1e}") self.textbox_sampling.on_submit(self.ask_sampling_change) _ = ax_sampling.text(0, 1.25, "Sampling") ax_enable_trigger = self.fig.add_axes([left, bottom, width, height]) bottom -= height * 1.25 + 2 * vpad self.textbox_triggersource = TextBox(ax_enable_trigger, label="", initial="None") self.textbox_triggersource.on_submit(self.ask_trigger_change) _ = ax_enable_trigger.text(0, 1.25, "Trigger") ax_triggerlevel = self.fig.add_axes([left, bottom, width, height]) bottom -= height * 1.25 + 2 * vpad if trigger_level is not None: initial_value = f"{trigger_level:.2f}" else: initial_value = "1.0" self.textbox_triggerlevel = TextBox(ax_triggerlevel, label="", initial=initial_value) self.textbox_triggerlevel.on_submit(self.ask_trigger_change) _ = ax_triggerlevel.text(0, 1.25, "Level") ax_winsize = self.fig.add_axes([left, bottom, width, height]) bottom -= height * 1.25 + 2 * vpad self.textbox_winsize = TextBox(ax_winsize, label="", initial=f"{self.N:d}") self.textbox_winsize.on_submit(self.ask_winsize_change) _ = ax_winsize.text(0, 1.25, "Win size") ax_voltrange = self.fig.add_axes([left, bottom, width, height]) bottom -= height * 1.25 + 2 * vpad self.textbox_voltrange = TextBox(ax_voltrange, label="", initial=f"{self.volt_range:.1f}") self.textbox_voltrange.on_submit(self.ask_voltrange_change) _ = ax_voltrange.text(0, 1.25, "Range") ax_start_stats = self.fig.add_axes([left, bottom, width, height]) bottom -= height + vpad self.btn_start_stats = Button(ax_start_stats, label="Stats") self.btn_start_stats.on_clicked(self.start_stats) ax_start_spectrum = self.fig.add_axes([left, bottom, width, height]) bottom -= height + vpad self.btn_start_spectrum = Button(ax_start_spectrum, label="FFT") self.btn_start_spectrum.on_clicked(self.start_spectrum) def clean_spectrum(self, *args): self.freq = None self.Pxx = None self.N_spectra = 0 def start_stats(self, event): if self.fig_stats is None: self.fig_stats = dict() self.box_mean = dict() self.box_std = dict() self.box_min = dict() self.box_max = dict() self.box_freq = dict() nbox = 5 height = 1.0 / (nbox + 1) padding = height / 4 for chan in self.channel_list: if chan not in self.fig_stats: self.fig_stats[chan] = plt.figure(figsize=(2, 4)) self.fig_stats[chan].canvas.set_window_title(chan) ax_mean = self.fig_stats[chan].add_axes( [0.25, 9 * height / 2, 0.7, height - padding]) self.box_mean[chan] = TextBox(ax_mean, label="Mean", initial="") ax_std = self.fig_stats[chan].add_axes( [0.25, 7 * height / 2, 0.7, height - padding]) self.box_std[chan] = TextBox(ax_std, label="Std", initial="") ax_min = self.fig_stats[chan].add_axes( [0.25, 5 * height / 2, 0.7, height - padding]) self.box_min[chan] = TextBox(ax_min, label="Min", initial="") ax_max = self.fig_stats[chan].add_axes( [0.25, 3 * height / 2, 0.7, height - padding]) self.box_max[chan] = TextBox(ax_max, label="Max", initial="") ax_freq = self.fig_stats[chan].add_axes( [0.25, height / 2, 0.7, height - padding]) self.box_freq[chan] = TextBox(ax_freq, label="Freq", initial="") def start_spectrum(self, *args, **kwargs): if self.fig_spectrum is None: self.fig_spectrum = plt.figure() self.ax_spectrum = self.fig_spectrum.add_axes([0.1, 0.1, 0.7, 0.8]) # Widgets ax_hanning = self.fig_spectrum.add_axes([0.825, 0.75, 0.15, 0.15]) self.checkbox_hanning = CheckButtons(ax_hanning, ["Hanning", "AC"], [self.hanning, self.ac]) self.checkbox_hanning.on_clicked(self.ask_hanning_change) ax_spectrum_unit = self.fig_spectrum.add_axes( [0.825, 0.51, 0.15, 0.25]) self.radio_units = RadioButtons( ax_spectrum_unit, ["V^2/Hz", "V/sq(Hz)", "mV/sq(Hz)", "µV/sq(Hz)", "nV/sq(Hz)"], ) self.radio_units.on_clicked(self.ask_spectrum_units_change) ax_restart = self.fig_spectrum.add_axes([0.825, 0.35, 0.15, 0.075]) self.btn_restart = Button(ax_restart, label="Restart") self.btn_restart.on_clicked(self.clean_spectrum) ax_save_hold = self.fig_spectrum.add_axes( [0.825, 0.25, 0.15, 0.075]) self.btn_save_hold = Button(ax_save_hold, label="Hold&Save") self.btn_save_hold.on_clicked(self.save_hold) self.clean_spectrum() def save_hold(self, event): if self.N_spectra > 0: dt = datetime.now() filename = (f"pymanip_oscillo_{dt.year:}-{dt.month}-{dt.day}" f"_{dt.hour}-{dt.minute}-{dt.second}.hdf5") bb = self.freq > 0 with h5py.File(filename) as f: f.attrs["ts"] = dt.timestamp() f.attrs["N_spectra"] = self.N_spectra f.attrs["sampling"] = self.sampling f.attrs["volt_range"] = self.volt_range f.attrs["N"] = self.N f.create_dataset("freq", data=self.freq[bb]) f.create_dataset("Pxx", data=self.Pxx[bb] / self.N_spectra) self.saved_spectra.append({ "freq": self.freq[bb], "Pxx": self.Pxx[bb] / self.N_spectra }) def ask_spectrum_units_change(self, event): power_spectrum_dict = { "V^2/Hz": True, "V/sq(Hz)": False, "mV/sq(Hz)": False, "µV/sq(Hz)": False, "nV/sq(Hz)": False, } spectrum_unit_dict = { "V^2/Hz": 1.0, "V/sq(Hz)": 1.0, "mV/sq(Hz)": 1e3, "µV/sq(Hz)": 1e6, "nV/sq(Hz)": 1e9, } if event in power_spectrum_dict: self.spectrum_unit_str = event self.power_spectrum = power_spectrum_dict[event] self.spectrum_unit = spectrum_unit_dict[event] async def winsize_change(self, new_N): await self.pause_acqui() old_N = self.N self.N = new_N self.clean_spectrum() self.figure_t_axis() try: self.system.configure_clock(self.sampling, self.N) except Exception as e: print(e) self.N = old_N await self.restart_acqui() def ask_winsize_change(self, label): try: new_N = int(label) except ValueError: print("winsize must be an integer") return loop = asyncio.get_event_loop() asyncio.ensure_future(self.winsize_change(new_N), loop=loop) def ask_trigger_change(self, label): changed = False trigger_source = self.textbox_triggersource.text possible_trigger_source = self.system.possible_trigger_channels() if trigger_source not in possible_trigger_source: print(f"{trigger_source:} is not an acceptable trigger source") print("Possible values:", possible_trigger_source) trigger_source = None self.textbox_triggersource.set_val("None") if trigger_source == "None": trigger_source = None if self.trigger_source != trigger_source: self.trigger_source = trigger_source changed = True if self.trigger_source is not None: try: trigger_level = float(self.textbox_triggerlevel.text) except ValueError: trigger_level = self.trigger_level val_str = f"{trigger_level:.2f}" self.textbox_triggerlevel.set_val(val_str) if trigger_level != self.trigger_level: self.trigger_level = trigger_level changed = True if changed: loop = asyncio.get_event_loop() asyncio.ensure_future(self.trigger_change(), loop=loop) async def pause_acqui(self): self.ask_pause_acqui = True await self.system.stop() while not self.paused: await asyncio.sleep(0.5) async def restart_acqui(self): self.ask_pause_acqui = False while self.paused: await asyncio.sleep(0.5) async def trigger_change(self): print("Awaiting pause") await self.pause_acqui() print("Acquisition paused") if self.trigger_level is not None: trigger_source = self.trigger_source trigger_level = self.trigger_level self.system.configure_trigger(trigger_source, trigger_level) else: self.system.configure_trigger(None) self.clean_spectrum() await self.restart_acqui() async def sampling_change(self): await self.pause_acqui() try: self.system.configure_clock(self.sampling, self.N) except Exception: print("Invalid sampling frequency") self.ask_sampling_change(self.system.samp_clk_max_rate) return if self.system.sample_rate != self.sampling: self.ask_sampling_change(self.system.sample_rate) return self.figure_t_axis() self.clean_spectrum() await self.restart_acqui() def ask_sampling_change(self, sampling): try: self.sampling = float(sampling) changed = True except ValueError: print("Wrong value:", sampling) changed = False self.textbox_sampling.set_val(f"{self.sampling:.1e}") if changed: loop = asyncio.get_event_loop() asyncio.ensure_future(self.sampling_change(), loop=loop) def ask_hanning_change(self, label): self.hanning, self.ac = self.checkbox_hanning.get_status() self.clean_spectrum() def figure_t_axis(self): self.t = np.arange(self.N) / self.sampling if self.t[-1] < 1: self.t *= 1000 self.unit = "[ms]" else: self.unit = "[s]" async def run_gui(self): while self.running: if time.monotonic() - self.last_trigged > self.N / self.sampling: self.ax.set_title("Waiting for trigger") self.fig.canvas.start_event_loop(0.5) await asyncio.sleep(0.05) if not plt.fignum_exists(self.fig.number): self.running = False if self.fig_spectrum and not plt.fignum_exists( self.fig_spectrum.number): self.fig_spectrum = None self.freq = None self.Pxx = None self.N_spectra = 0 if self.fig_stats is not None: for chan in list(self.fig_stats.keys()): if not plt.fignum_exists(self.fig_stats[chan].number): self.fig_stats.pop(chan) self.box_mean.pop(chan) self.box_std.pop(chan) self.box_min.pop(chan) self.box_max.pop(chan) self.box_freq.pop(chan) if not self.fig_stats: self.fig_stats = None def ask_voltrange_change(self, new_range): if not self.ignore_voltrange_submit: try: new_range = float(new_range) except ValueError: print("Volt range must be a float") return loop = asyncio.get_event_loop() asyncio.ensure_future(self.voltrange_change(new_range), loop=loop) async def voltrange_change(self, new_range): await self.pause_acqui() self.volt_range = new_range self.clean_spectrum() self.create_system() await self.restart_acqui() actual_range = self.system.actual_ranges[0] print("actual_range =", actual_range) self.ignore_voltrange_submit = True self.textbox_voltrange.set_val(f"{actual_range:.1f}") self.ignore_voltrange_submit = False def create_system(self): if self.system is not None: self.system.close() self.system = Backends[self.backend]() self.ai_channels = list() for chan in self.channel_list: ai_chan = self.system.add_channel(chan, terminal_config=TC.Diff, voltage_range=self.volt_range) self.ai_channels.append(ai_chan) self.system.configure_clock(self.sampling, self.N) self.textbox_sampling.set_val(f"{self.system.sample_rate:.1e}") if self.trigger_level is not None: trigger_source = self.channel_list[self.trigger_source] self.system.configure_trigger(trigger_source, trigger_level=self.trigger_level) else: self.system.configure_trigger(None) self.figure_t_axis() async def run_acqui(self): self.create_system() try: while self.running: while self.ask_pause_acqui and self.running: self.paused = True await asyncio.sleep(0.5) self.paused = False if not self.running: break self.system.start() data = await self.system.read() await self.system.stop() if data is None: continue self.last_trigged = self.system.last_read self.ax.cla() if len(self.channel_list) == 1: self.ax.plot(self.t, data, "-") elif len(self.channel_list) > 1: for d in data: self.ax.plot(self.t, d, "-") if self.trigger_source not in (None, "Ext"): self.ax.plot([self.t[0], self.t[-1]], [self.trigger_level] * 2, "g--") self.ax.set_xlim([self.t[0], self.t[-1]]) self.ax.set_title("Trigged!") self.ax.set_xlabel("t " + self.unit) if self.fig_spectrum: self.ax_spectrum.cla() if self.saved_spectra: for spectra in self.saved_spectra: self.ax_spectrum.loglog(spectra["freq"], spectra["Pxx"], "-") if self.N_spectra == 0: self.freq = np.fft.fftfreq(self.N, 1.0 / self.sampling) bb = self.freq > 0 norm = math.pi * math.sqrt(self.N / self.sampling) if self.hanning: window = np.hanning(self.N) else: window = np.ones((self.N, )) if len(self.channel_list) == 1: if self.ac: m = np.mean(data) else: m = 0.0 self.Pxx = (np.abs( np.fft.fft((data - m) * window) / norm)**2) else: if self.ac: ms = [np.mean(d) for d in data] else: ms = [0.0 for d in data] self.Pxx = [ np.abs(np.fft.fft((d - m) * window) / norm)**2 for d, m in zip(data, ms) ] self.N_spectra = 1 else: if len(self.channel_list) == 1: if self.ac: m = np.mean(data) else: m = 0.0 self.Pxx += (np.abs( np.fft.fft((data - m) * window) / norm)**2) else: if self.ac: ms = [np.mean(d) for d in data] else: ms = [0.0 for d in data] for p, d, m in zip(self.Pxx, data, ms): p += np.abs( np.fft.fft((d - m) * window) / norm)**2 self.N_spectra += 1 if self.power_spectrum: def process_spec(s): return self.spectrum_unit * s else: def process_spec(s): return self.spectrum_unit * np.sqrt(s) if len(self.channel_list) == 1: self.ax_spectrum.loglog( self.freq[bb], process_spec(self.Pxx[bb] / self.N_spectra), "-", ) else: for p in self.Pxx: self.ax_spectrum.loglog( self.freq[bb], process_spec(p[bb] / self.N_spectra), "-") self.ax_spectrum.set_xlabel("f [Hz]") self.ax_spectrum.set_ylabel(self.spectrum_unit_str) self.ax_spectrum.set_title(f"N = {self.N_spectra:d}") if self.fig_stats: if len(self.channel_list) == 1: list_data = [data] else: list_data = data for chan, d in zip(self.channel_list, list_data): if chan in self.fig_stats: self.box_mean[chan].set_val("{:.5f}".format( np.mean(d))) self.box_std[chan].set_val("{:.5f}".format( np.std(d))) self.box_min[chan].set_val("{:.5f}".format( np.min(d))) self.box_max[chan].set_val("{:.5f}".format( np.max(d))) ff = np.fft.fftfreq(self.N, 1.0 / self.sampling) pp = np.abs(np.fft.fft(d - np.mean(d))) ii = np.argmax(pp) self.box_freq[chan].set_val("{:.5f}".format( ff[ii])) finally: self.system.close() def ask_exit(self, *args, **kwargs): self.running = False def run(self): loop = asyncio.get_event_loop() self.running = True if sys.platform == "win32": signal.signal(signal.SIGINT, self.ask_exit) else: for signame in ("SIGINT", "SIGTERM"): loop.add_signal_handler(getattr(signal, signame), self.ask_exit) loop.run_until_complete( asyncio.gather(self.run_gui(), self.run_acqui()))
class Monster(): ''' class for instantiating a Monster Object, which contains some basic information about a monster (AC and HP, + a name) and which interfaces with an Encounter to show a dynamic bar graph of your monsters' HP in battle. ''' def __init__(self, monster_name, HP, AC): ''' Initialize Monster ------------------ Params: monster_name (string): name to label the bar/textbox for each monster. Shorter is better... HP (int/float): value of the monster's initial HP AC (int/float): value for the monster's AC (static, but displayed for ease of viewing) Returns: None ''' self.monster_name = monster_name self.HP = HP self.init_HP = HP self.AC = AC def update_barnum(self, barnum): ''' Tells each monster which mpl bar it is. ------------------- Params: barnum (int): bar number in overall mpl figure corresponding to this monster. ''' self.barnum = barnum def create_textbox(self, axes): ''' Create textbox entry space and label for each monster, using input axes calculated from the full monster list supplied to Encounter. -------------------------- Params: axes (array_like): list containing mpl axes argument, e.g., [0.1,0.1,0.8,0.8] Returns: None ''' self.textboxaxes = plt.axes(axes) self.textboxaxes.text(0.5, 1.2, self.monster_name, horizontalalignment='center', verticalalignment='center', transform=self.textboxaxes.transAxes) self.textbox = TextBox(self.textboxaxes, label='', initial='') self.textbox.on_submit(self.update_damage) def update_damage(self, damage): ''' Method to update the bar on the plot for this monster's HP based on input damage. Negative damage corresponds to gaining HP. --------------------------- Params: damage (int/float): amount of damage to subtract from the current HP. Returns: None, but on_submit will run this on <enter> or clicking out of the textbox. Updates the bar width. ''' try: sub_value = eval(damage) except: self.textbox.set_val('') return self.HP -= sub_value bars[self.barnum].set_width(self.HP) if self.HP < 0.2 * self.init_HP: bars[self.barnum].set_color('r') elif self.HP > 0.2 * self.init_HP: bars[self.barnum].set_color('C0') self.textbox.set_val('')
class interceptor(): sol_range = (1, 15) # range of possible solutions tv_range = (1, 30) # range of coorinates of the target velocity vector mv = np.array([0,4]) # velocity vector of the missile mp0 = np.array([0,0]) # initial position of the missile font = 'monospace' figsize = (6,6) launched = False # flag inticating if the missile launch time has been entered by the user t0 = 0 # missile launch time def __init__(self, q=None): # select the velocity and the initial position of the target self.tp0 = np.array([0, 0]) # position of the target self.tv0 = np.array([0, 0]) # velocity vector of the target # select target velocity and problem solution at random # check if these choices are reasonable; if not try again while self.tp0[1] <= 0 or self.tv0[0] == 0: self.tv0 = self.choose_tv() # self.sol is the solution vector # self.sol[1] is the correct launch time of the missile # self.sol[0] is the duration of the flight is the missile to the inteception point self.sol = self.choose_sol() self.tp0 = self.sol[0]*self.mv - np.sum(self.sol)*self.tv0 # initial position of the target # limits of plot axes self.xmax = abs(self.tp0[0]) + 10 self.xmin = (-1)*abs(self.tp0[0]) -10 self.ymax = max(abs(self.tp0[1]), self.sol[0]*self.mv[1]) +15 self.ymin = -1 self.start_game(q) def start_game(self, q=None): #if q='show' the missile launch time will be displayed # in the time entry box # reset initial values self.tp = self.tp0.copy() self.tv = self.tv0.copy() self.mp = self.mp0.copy() # distance to target self.dist = np.linalg.norm(self.tp - self.mp) self.launched = False self.t0 = 0 # set up plot self.fig = plt.figure(figsize = self.figsize) #main axes object self.ax = axbox = plt.axes([0.1, 0.15, 0.8, 0.8]) #textbox axes object self.t_ax= plt.axes([0.8, 0.05, 0.1, 0.05]) self.ax.set_facecolor('k') self.ax.set_xlim(self.xmin, self.xmax) self.ax.set_ylim(self.ymin, self.ymax) self.ax.grid(alpha = 0.2, color='g') # text box for entering the missile launch time istr = ('' if q != 'show' else str(self.sol[1])) self.text_box = TextBox(self.t_ax, 'Select missile launch time $t_0=$ ', initial=istr) self.text_box.on_submit(self.missile_launch) #text with problem data text1 = 'target position = [{:4}, {:4}]'.format(self.tp[0], self.tp[1]) text2 = 'target velocity vector = [{:4}, {:4}]'.format(self.tv[0], self.tv[1]) text3 = '---------------------------------------' text4 = 'missile position = [{:4}, {:4}]'.format(self.mp[0], self.mp[1]) text5 = 'missile velocity vector = [{:4}, {:4}]'.format(self.mv[0], self.mv[1]) # text for the final screen summary = [] summary.append('TARGET INTERCEPTED') summary.append('MISSION SUMMARY:') summary.append('----------------------------------------') summary.append('initial target position = [{:4}, {:4}]'.format(self.tp0[0], self.tp0[1])) summary.append('target velocity vector = [{:4}, {:4}]'.format(self.tv0[0], self.tv0[1])) summary.append('----------------------------------------') summary.append('initial missile position = [{:4}, {:4}]'.format(self.mp0[0], self.mp0[1])) summary.append('missile velocity vector = [{:4}, {:4}]'.format(self.mv[0], self.mv[1])) summary.append('----------------------------------------') summary.append('missile launch time = {:>5.2f}'.format(self.sol[1])) summary.append('target interception time = {:>5.2f}'.format(np.sum(self.sol))) summary.append('----------------------------------------') summary.append('THE END') self.summary_text = '\n'.join(summary) # self.text_all is the string displyed on the plot self.text_all = '\n'.join([text1, text2, text3, text4, text5]) self.data_text = self.ax.text(0.05, 0.95, self.text_all, transform=self.ax.transAxes, color='g', family = self.font, verticalalignment = 'top', ) #target position self.tp_plot, = self.ax.plot(*list(self.tp), 'g+', ms=7) #target velocity vector self.tv_plot, = self.ax.plot(*zip(list(self.tp), list(self.tp+self.tv)), 'g') #missile position self.mp_plot, = self.ax.plot(*list(self.mp), 'g^') #missile velocity vector self.mv_plot, = self.ax.plot(*zip(list(self.mp), list(self.mp+self.mv)), 'g') #animation self.ani = FuncAnimation(self.fig, func = self.update_plot, init_func= self.init_plot, frames=self.frames, interval=20, blit=True, repeat=False ) plt.show() def missile_launch(self, t0): ''' function linked to the text box for entering missile launch time ''' try: # check if a numerical value has been entered self.t0 = float(t0) self.launched = True except: self.text_box.set_val("") def choose_tv(self): ''' selects a random target velocity vector ''' return np.random.randint(*self.tv_range, 2) def choose_sol(self): ''' selects a random problem solution ''' return np.random.randint(*self.sol_range, 2) def init_plot(self): ''' initialized animation ''' self.tv_plot.set_visible(False) self.mv_plot.set_visible(False) return self.data_text, self.tp_plot, self.mp_plot, self.tv_plot, self.mv_plot def update_plot(self, t): ''' updates plot on each animation frame ''' self.tv_plot.set_visible(True) self.mv_plot.set_visible(True) self.data_text.set_text(self.text_all) if t >= 0: self.tv_plot.set_visible(False) self.mv_plot.set_visible(False) self.tp_plot.set_data(*list(self.tp.T)) self.mp_plot.set_data(*list(self.mp)) return self.data_text, self.tp_plot, self.mp_plot, self.tv_plot, self.mv_plot def frames(self): ''' generating fuction which computes data for each animation frame ''' incr = 0.04 # time increment on each animation frame fired = False # flag indicating if the missile has been fired hit = False # flag indicating if the target has been hit N = 50 # number of animated particles after missile-target collision spread = np.array([0.02*self.xmax, 0.02*self.ymax], dtype=float) # controls the spread of particles fall_rate = np.array([0, -0.003*self.ymax]) # controls the fall rate of particles brightness = 0.95 # controls decrease in the brightness of the particles alpha = 1 # initial transparency of the target marker #do not animate until missile launch time has been entered while not self.launched: yield -1 # start time count t = 0 while True: yield t if not fired: self.text_all = 'time: {:.2f}\ntarget distance: {:.2f}\nmissile launch in {:.2f}'.format( t, self.dist, self.t0 - t, ) else: self.text_all = 'time: {:.2f}\ntarget distance: {:.2f}\nmissile en route'.format(t, self.dist) t += incr if not hit: # increment target position self.tp = self.tp + incr*self.tv if t > self.t0: if not fired: # just for aesthetic: a small missile back jump just before it is launched self.mp = self.mp - np.array([0, 0.5]) fired = True else: # increment missile position self.mp = self.mp + incr*self.mv # if missile is above target or target crossed the missile axis then the missile will not hit if self.tp[0] > 2 or self.mp[1] > self.tp[1] + 2: self.text_all = 'time: {:.2f}\ntarget lost'.format(t) self.dist = np.linalg.norm(self.tp - self.mp) # missile hit if self.t0 == self.sol[1] and t >= np.sum(self.sol): self.tp = np.zeros((N,2), dtype=float) + self.tp - np.array([0, -0.2]) self.mp = self.mp + np.array([0, 0.5]) hit = True else: #after the hit # remove the missile plot self.mp_plot.set_visible(False) # replace the target by a particle cloud self.tp = self.tp + (np.random.rand(N, 2)-0.5)*spread + fall_rate self.tp_plot.set_markersize(1.5) alpha = min(alpha*brightness + 0.05*np.random.rand(1)[0], 1) self.tp_plot.set_alpha(alpha) text_end = int((t - np.sum(self.sol))/incr) self.text_all = '\n'+ self.summary_text[:text_end]
class SpectraViewer: def __init__(self, spectrometer): """ A matplotlib-based window to display and manage a spectrometer to replace the insanely inept OceanView software from OceanInsight or the dude-wtf?1 software from Stellarnet. If anybody reads this from Ocean Insight, you can direct people to this Python script. It is simpler to call it directly from the spectrometer object with its own display function that will instantiate a SpectraViewer and call its display function with itself as a paramater. Parameters ---------- spectrometer: Spectrometer A spectrometer from Ocean Insight or Stellarnet """ self.spectrometer = spectrometer self.lastSpectrum = [] self.whiteReference = None self.darkReference = None self.figure = None self.axes = None self.quitFlag = False self.saveBtn = None self.quitBtn = None self.lightBtn = None self.darkBtn = None self.integrationTimeBox = None self.animation = None def display(self): """ Display the spectrum in free-running mode, with simple autoscale, save and quit buttons as well as a text entry for the integration time. This is the only user-facing function that is needed. """ self.figure, self.axes = self.createFigure() self.setupLayout() self.quitFlag = False self.animation = animation.FuncAnimation(self.figure, self.animate, interval=100) plt.show() def createFigure(self): """ Create a matplotlib figure with decent properties. """ SMALL_SIZE = 14 MEDIUM_SIZE = 18 BIGGER_SIZE = 36 plt.rc('font', size=SMALL_SIZE) # controls default text sizes plt.rc('axes', titlesize=SMALL_SIZE) # fontsize of the axes title plt.rc('axes', labelsize=MEDIUM_SIZE) # fontsize of the x and y labels plt.rc('xtick', labelsize=MEDIUM_SIZE) # fontsize of the tick labels plt.rc('ytick', labelsize=MEDIUM_SIZE) # fontsize of the tick labels plt.rc('legend', fontsize=SMALL_SIZE) # legend fontsize plt.rc('figure', titlesize=BIGGER_SIZE) # fontsize of the figure title fig, axes = plt.subplots() fig.set_size_inches(10, 6, forward=True) serialNumber = self.spectrometer.getSerialNumber() model = self.spectrometer.model fig.canvas.manager.set_window_title( 'Spectrometer [serial # {0}, model {1}]'.format( serialNumber, model)) axes.set_xlabel("Wavelength [nm]") axes.set_ylabel("Intensity [arb.u]") return fig, axes def setupLayout(self): axScale = plt.axes([0.125, 0.90, 0.13, 0.075]) axLight = plt.axes([0.25, 0.90, 0.08, 0.075]) axDark = plt.axes([0.30, 0.90, 0.08, 0.075]) axTime = plt.axes([0.59, 0.90, 0.08, 0.075]) axSave = plt.axes([0.73, 0.90, 0.08, 0.075]) axQuit = plt.axes([0.82, 0.90, 0.08, 0.075]) self.saveBtn = Button(axSave, 'Save') self.saveBtn.on_clicked(self.clickSave) self.quitBtn = Button(axQuit, 'Quit') self.quitBtn.on_clicked(self.clickQuit) self.autoscaleBtn = Button(axScale, 'Autoscale') self.autoscaleBtn.on_clicked(self.clickAutoscale) rootDir = os.path.dirname(os.path.abspath(__file__)) try: axLight.imshow(plt.imread("{0}/lightbulb.png".format(rootDir))) axLight.set_xticks([]) axLight.set_yticks([]) self.lightBtn = Button(axLight, '') except: self.lightBtn = Button(axLight, 'W') self.lightBtn.on_clicked(self.clickWhiteReference) try: axDark.imshow(plt.imread("{0}/darkbulb.png".format(rootDir))) axDark.set_xticks([]) axDark.set_yticks([]) self.darkBtn = Button(axDark, '') except: self.darkBtn = Button(axDark, 'D') self.darkBtn.on_clicked(self.clickDarkReference) currentIntegrationTime = self.spectrometer.getIntegrationTime() self.integrationTimeBox = TextBox( axTime, 'Integration time [ms]', initial="{0}".format(currentIntegrationTime), label_pad=0.1) self.integrationTimeBox.on_submit(self.submitTime) self.figure.canvas.mpl_connect('key_press_event', self.keyPress) def plotSpectrum(self, spectrum=None): """ Plot a spectrum into the figure or request a new spectrum. This is called repeatedly when the display function is called.""" if spectrum is None: spectrum = self.spectrometer.getSpectrum() if len(self.axes.lines) == 0: self.axes.plot(self.spectrometer.wavelength, spectrum, 'k') self.axes.set_xlabel("Wavelength [nm]") self.axes.set_ylabel("Intensity [arb.u]") else: self.axes.lines[0].set_data(self.spectrometer.wavelength, spectrum) # set plot data self.axes.relim() def animate(self, i): """ Internal function that is called repeatedly to manage the update of the spectrum plot. It is better to use the `animation` strategy instead of a loop with plt.pause() because plt.pause() will always bring the window to the foreground. This function is also responsible for determining if the user asked to quit. """ try: self.lastSpectrum = self.spectrometer.getSpectrum() if self.darkReference is not None: self.lastSpectrum -= self.darkReference if self.whiteReference is not None: np.seterr(divide='ignore', invalid='ignore') if self.darkReference is not None: self.lastSpectrum = self.lastSpectrum / ( self.whiteReference - self.darkReference) else: self.lastSpectrum = self.lastSpectrum / self.whiteReference self.plotSpectrum(spectrum=self.lastSpectrum) except usb.core.USBError as err: print("The spectrometer was disconnected. Quitting.") self.quitFlag = True if self.quitFlag: self.animation.event_source.stop() self.animation = None plt.close() def keyPress(self, event): """ Event-handling function for keypress: if the user clicks command-Q on macOS, it will nicely quit.""" if event.key == 'cmd+q': self.clickQuit(event) if event.key == 'backspace': self.clickClearReferences(event) def submitTime(self, event): """ Event-handling function for when the user hits return/enter in the integration time text field. The new integration time is set in the spectrometer. We must autoscale the plot because the intensities could be very different. However, it takes a small amount of time for the spectrometer to react. We wait 0.3 seconds, which is small enough to not be annoying and seems to work fine. Anything incorrect will bring the integration time to 3 milliseconds. """ try: time = float(self.integrationTimeBox.text) if time == 0: raise ValueError('Requested integration time is invalid: \ the text "{0}" converts to 0.') self.spectrometer.setIntegrationTime(time) plt.pause(0.3) self.axes.autoscale_view() except Exception as err: print("Error when setting integration time: ", err) self.integrationTimeBox.set_val("3") def clickAutoscale(self, event): """ Event-handling function to autoscale the plot """ self.axes.autoscale_view() def clickClearReferences(self, event): """ Event-handling function to acquire a white reference """ self.whiteReference = None self.lightBtn.color = '0.85' self.darkReference = None self.darkBtn.color = '0.85' plt.pause(0.3) self.axes.autoscale_view() def clickWhiteReference(self, event): """ Event-handling function to acquire a white reference """ if self.whiteReference is None: self.whiteReference = self.spectrometer.getSpectrum() self.lightBtn.color = '0.99' else: self.whiteReference = None self.lightBtn.color = '0.85' plt.pause(0.3) self.axes.autoscale_view() def clickDarkReference(self, event): """ Event-handling function to acquire a dark reference """ if self.darkReference is None: self.darkReference = self.spectrometer.getSpectrum() self.darkBtn.color = '0.99' else: self.darkReference = None self.darkBtn.color = '0.85' plt.pause(0.3) self.axes.autoscale_view() def clickSave(self, event): """ Event-handling function to save the file. We stop the animation to avoid acquiring more spectra. The last spectrum acquired (i.e. the one displayed) after we have requested the filename. The data is saved as a CSV file, and the animation is restarted. Technical note: To request the filename, we use different strategies on different platforms. On macOS, we can use a function from the backend. On Windows and others, we fall back on Tk, which is usually installed with python. """ self.animation.event_source.stop() filepath = "spectrum.csv" try: filepath = backends.backend_macosx._macosx.choose_save_file( 'Save the data', filepath) except: import tkinter as tk from tkinter import filedialog root = tk.Tk() root.withdraw() filepath = filedialog.asksaveasfilename() if filepath is not None: self.spectrometer.saveSpectrum(filepath, spectrum=self.lastSpectrum, whiteReference=self.whiteReference, darkReference=self.darkReference) self.animation.event_source.start() def clickQuit(self, event): """ Event-handling function to quit nicely.""" self.quitFlag = True
class FlightAnim: def __init__(self): self.pd = PlanetData('GEO') self.simulator = Simulator(self.pd) self.tf = 1.0e8 # max time for simulation self.delta_t_mars = 0.0 self.Xs = [] self.Ts = [] self.difficulty = 1 # 1 = just hit space around mars in distance of 0.1 AE # 2 = 0.01 AE # 3 = orbit max = 3.0 * self.pd.AE self.box_lbx = -max self.box_ubx = max self.box_lby = -max self.box_uby = max self.fig = plt.figure() gs = gridspec.GridSpec(10, 10) self.ax = plt.subplot(gs[1:7, 0:6]) self.ax.grid(True) command_input_fields = 5 self.ax_command_input = [] self.command_text_boxes = [] self.commands = {0: np.array([0.0, 600.0, 0.0, 5.0])} for i in range(1, command_input_fields): self.commands[i] = None # set up axes for i in range(0, len(self.commands.keys())): self.ax_command_input.append(plt.subplot(gs[i + 1, 7:])) # define command input field for i in range(0, len(self.commands.keys())): if i == 0: initial = str(self.commands[i][0]) + " " + str(self.commands[i][1]) + " " + str(self.commands[i][2]) \ + " " + str(self.commands[i][3]) else: initial = "" self.command_text_boxes.append( TextBox(self.ax_command_input[i], 'Befehl {} '.format(i + 1), initial=initial)) f = functools.partial(self.submit_command, i) self.command_text_boxes[i].on_submit(f) self.ax_mars_delta_t = plt.subplot(gs[command_input_fields + 1, 7:]) self.delta_t_mars_text_box = TextBox(self.ax_mars_delta_t, 'Delta t Mars ', initial='0.0') self.delta_t_mars_text_box.on_submit(self.submit_delta_t_mars) # outputs self.ax_time = plt.subplot(gs[0, 4:6]) self.ax_time.get_xaxis().set_visible(False) self.ax_time.get_yaxis().set_visible(False) self.text_time = self.ax_time.text(0.1, 0.5, 't = 0.0') self.ax_comdescript = plt.subplot(gs[0, 6:]) self.ax_comdescript.get_xaxis().set_visible(False) self.ax_comdescript.get_yaxis().set_visible(False) self.text_comdescript = self.ax_comdescript.text( 0.1, 0.1, 'Zeit, Dauer, Winkel, Schub') self.text_comvar = self.ax_comdescript.text( 0.1, 0.6, 't, delta t, w, u') self.ax_level = plt.subplot(gs[0, 0:2]) self.ax_level.get_xaxis().set_visible(False) self.ax_level.get_yaxis().set_visible(False) self.text_level = self.ax_level.text(0.1, 0.5, 'Level 1') self.ax_info = plt.subplot(gs[7:10, 0:5]) self.ax_info.get_xaxis().set_visible(False) self.ax_info.get_yaxis().set_visible(False) self.text_vel_rocket = self.ax_info.text(0.1, 0.25, 'v_{R} = (0.0, 0.0)') self.ax_info.get_xaxis().set_visible(False) self.ax_info.get_yaxis().set_visible(False) self.text_vel_mars = self.ax_info.text(0.1, 0.75, 'v_{M} = (0.0, 0.0)') self.ax_infocontrol = plt.subplot(gs[8:10, 5:9]) self.ax_infocontrol.get_xaxis().set_visible(False) self.ax_infocontrol.get_yaxis().set_visible(False) self.text_setfaster = self.ax_infocontrol.text( 0.1, 0.1, 'Sim-Geschw. erhoehen : +') self.ax_infocontrol.get_xaxis().set_visible(False) self.ax_infocontrol.get_yaxis().set_visible(False) self.text_setslower = self.ax_infocontrol.text( 0.1, 0.35, 'Sim-Geschw. verringern : -') self.ax_infocontrol.get_xaxis().set_visible(False) self.ax_infocontrol.get_yaxis().set_visible(False) self.text_setpause = self.ax_infocontrol.text( 0.1, 0.75, 'Pause : Pfeiltaste runter') self.victory_text = self.ax_info.text(0.1, 0.25, 'Victory!') self.ax_button = plt.subplot(gs[9, 9]) self.ax.set_ylim([self.box_lby, self.box_uby]) self.ax.set_xlim([self.box_lbx, self.box_ubx]) self.ax.figure.canvas.draw() self.rocket_x = [] self.rocket_y = [] self.line_rocket, = self.ax.plot(self.rocket_x, self.rocket_y, 'k', animated=False) self.point_rocket, = self.ax.plot([], [], color='k', marker='o', markersize=2, animated=False) self.earth_x = [] self.earth_y = [] self.line_earth, = self.ax.plot(self.earth_x, self.earth_y, color='b', linestyle='--', animated=False) self.earth = Ellipse((1 * self.pd.AE, 0), 0.1 * self.pd.AE, 0.1 * self.pd.AE, animated=False, color='blue') self.mars_x = [] self.mars_y = [] self.line_mars, = self.ax.plot(self.mars_x, self.mars_y, color='r', linestyle='--', animated=False) self.mars = Ellipse((0, 1.5173 * self.pd.AE), 0.1 * self.pd.AE, 0.1 * self.pd.AE, animated=False, color='red') self.sun_x = [] self.sun_y = [] self.line_sun, = self.ax.plot(self.sun_x, self.sun_y, color='y', linestyle='--') self.sun = Ellipse((0, 0), 0.2 * self.pd.AE, 0.2 * self.pd.AE, animated=False, color='yellow') self.text_rocket = self.ax.text(0, 0, 'Rakete', verticalalignment='bottom') self.text_earth = self.ax.text(0, 0, 'Erde', verticalalignment='top') self.text_mars = self.ax.text(0, 0, 'Mars', verticalalignment='top') self.text_sun = self.ax.text(0, 0, 'Sonne', verticalalignment='top') self.ax.add_artist(self.earth) self.ax.add_artist(self.mars) self.ax.add_artist(self.sun) self.ax.add_artist(self.text_rocket) self.ax.add_artist(self.text_earth) self.ax.add_artist(self.text_mars) self.ax.add_artist(self.text_sun) self.ax_time.add_artist(self.text_time) self.button_run = Button(self.ax_button, 'Start', color='g', hovercolor='g') self.button_run.on_clicked(self.callback_button_run) #self.fig.canvas.mpl_connect('motion_notify_event', self.axes_enter) #self.fig.canvas.mpl_connect('resize_event', self.axes_enter) self.fig.canvas.mpl_connect('key_press_event', self.handleKeys) self.ax.set_aspect('equal') self.speed = 10 self.myframe = 0 self.anim_running = False self.anim = None self.redraw = False def init_func(self): print("init function") # initialize plot data self.line_rocket.set_data([], []) self.line_earth.set_data([], []) self.line_mars.set_data([], []) self.point_rocket.set_data([], []) self.earth.center = (0, 0) self.mars.center = (0, 0) self.sun.center = (0, 0) self.text_rocket.set_text('') self.text_earth.set_text('') self.text_mars.set_text('') self.text_sun.set_text('') self.text_time.set_text('') self.text_vel_rocket.set_text('') self.text_vel_mars.set_text('') self.victory_text.set_text('') self.text_level.set_text('') return self.line_rocket, self.line_earth, self.line_mars, self.point_rocket, self.earth, self.mars, self.sun, \ self.text_rocket, self.text_earth, self.text_mars, self.text_sun, self.text_time, self.text_vel_rocket, \ self.text_vel_mars, self.victory_text, self.text_level def update_func(self, frame): if self.anim_running: self.myframe = min(self.myframe, len(self.Ts) - 1) self.text_rocket.set_text('Rakete') self.text_earth.set_text('Erde') self.text_mars.set_text('Mars') self.text_sun.set_text('Sonne') # update plot data for rocket pos_rocket = self.Xs[self.myframe, 0:2] self.rocket_x.append(pos_rocket[0]) self.rocket_y.append(pos_rocket[1]) self.line_rocket.set_data(self.rocket_x, self.rocket_y) self.point_rocket.set_data(pos_rocket[0], pos_rocket[1]) self.text_rocket.set_position(pos_rocket) # update plot data for earth pos_earth = self.Xs[self.myframe, 4:6] self.earth_x.append(pos_earth[0]) self.earth_y.append(pos_earth[1]) self.line_earth.set_data(self.earth_x, self.earth_y) self.earth.center = pos_earth self.text_earth.set_position(pos_earth) # update plot data for mars pos_mars = self.Xs[self.myframe, 8:10] self.mars_x.append(pos_mars[0]) self.mars_y.append(pos_mars[1]) self.line_mars.set_data(self.mars_x, self.mars_y) self.mars.center = pos_mars self.text_mars.set_position(pos_mars) # update displayed texts t = self.Ts[self.myframe] vel_rocket = self.Xs[self.myframe][2:4] vel_mars = self.Xs[self.myframe][10:12] self.text_time.set_text('t = {:6.0f} s'.format(t)) self.text_vel_rocket.set_text('v_R = ({:6.0f}, {:6.0f})'.format( vel_rocket[0], vel_rocket[1])) self.text_vel_mars.set_text('v_M = ({:6.0f}, {:6.0f})'.format( vel_mars[0], vel_mars[1])) self.text_level.set_text('Level {}'.format(self.difficulty)) # check victory conditions dist_rocket_mars = np.linalg.norm( np.array(pos_rocket) - np.array(pos_mars)) vel_dist_rocket_mars = np.linalg.norm( np.array(vel_rocket) - np.array(vel_mars)) self.check_victory(dist_rocket_mars, vel_dist_rocket_mars) self.myframe += max(1, self.speed * self.speed) else: # do nothing pass return self.line_rocket, self.line_earth, self.line_mars, self.point_rocket, self.earth, self.mars, self.sun, \ self.text_rocket, self.text_earth, self.text_mars, self.text_sun, self.text_time, self.text_vel_rocket, \ self.text_vel_mars, self.victory_text, self.text_level def check_victory(self, dist, vel_dist): victory = False if self.difficulty == 1: if dist < 0.05 * self.pd.AE: victory = True elif self.difficulty == 2: if dist < 0.005 * self.pd.AE: victory = True elif self.difficulty == 3: if dist <= 3.0e9 and vel_dist <= 1.0e3: victory = True print("Orbit achieved!") if victory == True: # display information # time, energy, dist, delta_v self.text_vel_mars.set_text('') self.text_vel_rocket.set_text('') days = self.Ts[self.myframe] / (60.0 * 60.0 * 24.0) dist = dist / 1000.0 energy = 0.0 for k in self.commands.keys(): if self.commands[k] is not None: energy += self.commands[k][1] * self.commands[k][3]**2 text = 'Mars erreicht!\n\n Zeit (d): {:6.2f} \n Abstand (km): {:6.2f} \n Delta v (m/s): {:6.2f} \n ' \ 'Treibstoffverbrauch: {:6.2f}'.format(days, dist, vel_dist, energy) self.victory_text.set_text(text) self.anim_running = False #self.redraw = True #self.anim.event_source.stop() else: self.victory_text.set_text('') def submit_command(self, i, text): # TODO check text text = text.replace(',', '.') data = np.fromstring(text, sep=' ') if self.check_data(data, i) == True: print("good data") print("data = {}".format(data)) self.commands[i] = data else: print("invalid data") self.commands[i] = None def check_data(self, data, i): if len(data) != 4: return False if data[0] < 0 or data[0] > self.tf or data[ 1] <= 0.0 or data[0] + data[1] > self.tf: return False # check if time point is too early for j in range(0, i): if self.commands[j] is not None: if self.commands[j][0] + self.commands[j][1] > data[0]: return False # check if duration is too long for j in range(i + 1, len(self.commands.keys())): if self.commands[j] is not None: if data[0] + data[1] > self.commands[j][0]: return False return True def submit_delta_t_mars(self, text): # TODO check text text = text.replace(',', '.') self.delta_t_mars = float(text) def callback_button_run(self, event): self.reset() print("Button pressed -> rerun simulation") def onClick(self, event): print("Click Event!") def handleKeys(self, event): if event.key == 'down': if self.anim_running: self.anim_running = False else: self.anim_running = True difficulty_old = self.difficulty # control simulation speed if event.key == '-': self.speed -= 1 self.speed = max(self.speed, 1) print("speed = {}".format(self.speed)) elif event.key == '+': self.speed += 1 print("speed = {}".format(self.speed)) elif event.key == 'ctrl+l': print("autosolve") self.auto_solve() elif event.key == 'ctrl+1': self.difficulty = 1 elif event.key == 'ctrl+2': self.difficulty = 2 elif event.key == 'ctrl+3': self.difficulty = 3 difficulty_changed = (self.difficulty != difficulty_old) if difficulty_changed: if self.difficulty == 1: self.mars.width = 0.1 * self.pd.AE self.mars.height = 0.1 * self.pd.AE self.earth.width = 0.1 * self.pd.AE self.earth.height = 0.1 * self.pd.AE elif self.difficulty == 2: self.mars.width = 0.01 * self.pd.AE self.mars.height = 0.01 * self.pd.AE self.earth.width = 0.01 * self.pd.AE self.earth.height = 0.01 * self.pd.AE elif self.difficulty > 2: self.mars.width = 6800.0e3 self.mars.height = 6800.0e3 self.earth.width = 12742.0e3 self.earth.height = 12742.0e3 def auto_solve(self): # reset to get a clean state if self.commands[0] is not None: for i in range(1, len(self.commands.keys())): self.commands[i] = None self.delta_t_mars = 0.0 self.reset() # TODO set correct bounds samples_r = self.Xs[2000:-1:5000, 0:2] samples_m = self.Xs[2000:-1:5000, 8:10] scaling = np.max([np.abs(samples_r), np.abs(samples_m)]) samples_r_scaled = samples_r / scaling samples_m_scaled = samples_m / scaling # ellipse equation: a x^2 + b xy + c y^2 + d x + e y + f = 0 er_params_scaled, er_center_scaled, er_angle_scaled, er_axis_scaled = ellipse_helpers.compute_ellipse_params( samples_r_scaled) em_params_scaled, em_center_scaled, em_angle_scaled, em_axis_scaled = ellipse_helpers.compute_ellipse_params( samples_m_scaled) sol_scaled = ellipse_helpers.ellipse_intersection( er_params_scaled, em_params_scaled, self.Xs[0, 0:2] / scaling) sol = sol_scaled * scaling # find out intersection time of rocket with point T_intersect_rocket_index = np.argmin( np.linalg.norm(self.Xs[:, 0:2] - sol, axis=1)) T_intersect_rocket = self.Ts[T_intersect_rocket_index] # find out intersection time of rocket with point T_intersect_mars_index = np.argmin( np.linalg.norm(self.Xs[:, 8:10] - sol, axis=1)) T_intersect_mars = self.Ts[T_intersect_mars_index] self.delta_t_mars = T_intersect_mars - T_intersect_rocket vel_rocket = self.Xs[T_intersect_rocket_index, 2:4] vel_mars = self.Xs[T_intersect_mars_index, 10:12] delta_v = vel_mars - vel_rocket delta_v_norm = delta_v / np.linalg.norm(delta_v) e1 = np.array([1.0, 0.0]) w_des = np.arccos(np.dot(delta_v_norm, e1)) if delta_v_norm[1] < 0.0: # if vector points down in the y-component we have to adjust the angle such that it is mathematically positive w_des = 2.0 * np.pi - w_des a_des = delta_v / 1200.0 u_des = a_des[0] / np.cos(w_des) w_des = w_des * 360.0 / (2.0 * np.pi) self.commands[1] = np.array([T_intersect_rocket, 1200.0, w_des, u_des]) new_text = "{:6.0f} {:6.2f} {:6.2f} {:6.2f}".format( self.commands[1][0], self.commands[1][1], self.commands[1][2], self.commands[1][3]) self.command_text_boxes[1].set_val(new_text) new_time_mars = "{:6.0f} ".format(self.delta_t_mars) self.delta_t_mars_text_box.set_val(new_time_mars) def reset(self): if self.anim_running == True: self.anim.event_source.stop() self.rocket_x = [] self.rocket_y = [] self.mars_x = [] self.mars_y = [] self.earth_x = [] self.earth_y = [] if self.delta_t_mars != 0.0: x_temp = self.simulator.run_simulation_planets(self.delta_t_mars) self.simulator.set_mars_position(x_temp[4:8]) else: self.simulator.reset_mars_position() commands_clean = self.clean_commands() self.Ts, self.Xs = self.simulator.run_simulation( commands_clean, self.tf) self.myframe = 0 self.anim_running = True self.anim.event_source.start() def clean_commands(self): commands_clean = None for i in range(0, len(self.commands.keys())): if self.commands[i] is not None: c = np.copy(self.commands[i]) c[2] = c[2] / 360.0 * 2.0 * np.pi # convert to rad if commands_clean is None: commands_clean = np.array([c]) else: commands_clean = np.vstack((commands_clean, c)) return commands_clean def main_loop(self): commands_clean = self.clean_commands() self.Ts, self.Xs = self.simulator.run_simulation( commands_clean, self.tf) self.anim_running = True self.anim = animation.FuncAnimation(self.fig, self.update_func, frames=100000000000000, interval=10, blit=True, init_func=self.init_func) plt.show()
def __spincam_gui(): # Get figure fig = plt.figure() # Set position params padding = 0.01 options_height = 0.02 num_options = 6 cam_plot_height_offset = num_options * options_height + num_options * padding cam_plot_width = 0.75 cam_plot_height = 1 - cam_plot_height_offset #cid = fig.canvas.mpl_connect('button_press_event', onclick) # Display Camera Image display_pos = [0, cam_plot_height_offset, cam_plot_width, cam_plot_height] display_dict = __cam_plot(fig, display_pos, 'Camera', options_height, padding) # Set initial values # Set callbacks display_dict['find_and_init_button'].on_clicked(__find_and_init_cam) stage_pos = [cam_plot_width, cam_plot_height_offset, 0.25, cam_plot_height] # Creates stage controls but_width = options_height * 2 but_height = options_height * 3 red_but_pos = [ display_pos[0] + display_pos[3] - 4 * padding, display_pos[1] + display_pos[3] - 2 * padding - but_height, but_width, options_height * 2 ] red_but_axes = fig.add_axes(red_but_pos) red_but = Button(red_but_axes, 'R') red_but.label.set_fontsize(7) yellow_but_pos = [ red_but_pos[0] + padding + but_width, red_but_pos[1], but_width, options_height * 2 ] yellow_but_axes = fig.add_axes(yellow_but_pos) yellow_but = Button(yellow_but_axes, 'Y') yellow_but.label.set_fontsize(7) green_but_pos = [ yellow_but_pos[0] + padding + but_width, yellow_but_pos[1], but_width, options_height * 2 ] green_but_axes = fig.add_axes(green_but_pos) green_but = Button(green_but_axes, 'G') green_but.label.set_fontsize(7) blue_but_pos = [ green_but_pos[0] + padding + but_width, green_but_pos[1], but_width, options_height * 2 ] blue_but_axes = fig.add_axes(blue_but_pos) blue_but = Button(blue_but_axes, 'B') blue_but.label.set_fontsize(7) b_text_pos = [ red_but_pos[0], display_pos[3] - padding - but_height, 0.1, options_height * 2 ] b_text_axes = fig.add_axes(b_text_pos) b_text = TextBox(b_text_axes, 'bval') b_text.label.set_fontsize(7) b_text.set_val(1) g_text_pos = [ red_but_pos[0], display_pos[3] - 10 * padding - but_height, 0.1, options_height * 2 ] g_text_axes = fig.add_axes(g_text_pos) g_text = TextBox(g_text_axes, 'gval') g_text.label.set_fontsize(7) g_text.set_val(1) red_but.on_clicked(__ledr) yellow_but.on_clicked(__ledy) blue_but.on_clicked(__ledb) green_but.on_clicked(__ledg) b_text.on_submit(__b_text) g_text.on_submit(__g_text) # Start stream start_stream_button_pos = [ padding, display_pos[1] - options_height, 0.5 - 2 * padding, options_height ] start_stream_button_axes = fig.add_axes(start_stream_button_pos) start_stream_button = Button(start_stream_button_axes, 'Start Stream') start_stream_button.label.set_fontsize(7) # Set callback start_stream_button.on_clicked(__start_stream) # Stop stream stop_stream_button_pos = [ start_stream_button_pos[0] + start_stream_button_pos[2] + 2 * padding, display_pos[1] - options_height, 0.5 - 2 * padding, options_height ] stop_stream_button_axes = fig.add_axes(stop_stream_button_pos) stop_stream_button = Button(stop_stream_button_axes, 'Stop Stream') stop_stream_button.label.set_fontsize(7) # Set callback stop_stream_button.on_clicked(__stop_stream) # fps fps_pos = [ 0, start_stream_button_pos[1] - options_height - padding, 1, options_height ] (fps_slider, fps_text) = __slider_with_text(fig, fps_pos, 'FPS', __FPS_MIN, __FPS_MAX, __FPS_MIN, padding) # Set callbacks fps_slider.on_changed(__fps_slider) fps_text.on_submit(__fps_text) # Exposure exposure_pos = [ 0, fps_pos[1] - options_height - padding, 1, options_height ] (exposure_slider, exposure_text) = __slider_with_text(fig, exposure_pos, 'Exposure', __EXPOSURE_MIN, __EXPOSURE_MAX, __EXPOSURE_MIN, padding) # Set callbacks exposure_slider.on_changed(__exposure_slider) exposure_text.on_submit(__exposure_text) #Set default directory to current directory_pos = [ padding, exposure_pos[1] - options_height - padding, 0.75 - 2 * padding, options_height ] directory_axes = fig.add_axes(directory_pos) directory_text = TextBox(directory_axes, 'Directory') directory_text.label.set_fontsize(7) directory_text.set_val('') directory_button_pos = [ directory_pos[0] + directory_pos[2] + padding, directory_pos[1], 0.25 - 2 * padding, options_height ] directory_button_axes = fig.add_axes(directory_button_pos) directory_button = Button(directory_button_axes, 'Choose Directory') directory_button.label.set_fontsize(7) directory_button.on_clicked(__choose_directory) # Set name format name_format_pos = [(0.5 - 2 * padding) * 0.1875 + 2 * padding, directory_pos[1] - options_height - padding, (0.5 - 2 * padding) * 0.8125 - padding, options_height] name_format_axes = fig.add_axes(name_format_pos) name_format_text = TextBox(name_format_axes, 'Name format') name_format_text.label.set_fontsize(7) name_format_text.set_val('test_{date}') # Set save primary button save_button_pos = [ padding, name_format_pos[1] - options_height - padding, (0.5 - 4 * padding) / 3, options_height ] save_button_axes = fig.add_axes(save_button_pos) save_button = Button(save_button_axes, 'Start Acquisition') save_button.label.set_fontsize(7) # Set callback save_button.on_clicked(__save_images) # Set num images text num_images_text_pos = [ cam_plot_width - 20 * padding, padding, 0.1 - 2 * padding, options_height ] num_images_text_axes = fig.add_axes(num_images_text_pos) num_images_text = TextBox(num_images_text_axes, '# to Acquire') num_images_text.label.set_fontsize(7) num_images_text.set_val(1) # Set num images text time_images_text_pos = [ 0.75, directory_pos[1] - options_height - padding, 0.125, options_height ] time_images_text_axes = fig.add_axes(time_images_text_pos) time_images_text = TextBox(time_images_text_axes, 'Time between frames (s)') time_images_text.label.set_fontsize(7) time_images_text.set_val(0) # Set num images text avg_images_text_pos = [ save_button_pos[0] + save_button_pos[2] + padding + (0.5 - 2 * padding) * 0.1875 + 2 * padding, name_format_pos[1] - options_height - padding, (0.2 - 2 * padding) * 0.8125 - padding, options_height ] avg_images_text_axes = fig.add_axes(avg_images_text_pos) avg_images_text = TextBox(avg_images_text_axes, '# to Avg') avg_images_text.label.set_fontsize(7) avg_images_text.set_val(10) # Set save primary button bulk_button_pos = [ num_images_text_pos[0] + num_images_text_pos[2] + padding, name_format_pos[1] - options_height - padding, (0.2 - 2 * padding) * 0.8125 - padding, options_height ] bulk_button_axes = fig.add_axes(bulk_button_pos) bulk_button = Button(bulk_button_axes, 'Zero Bulk') bulk_button.label.set_fontsize(7) # Set callback bulk_button.on_clicked(__bulk_save) return { 'fig': fig, 'display_dict': display_dict, 'start_stream_button': start_stream_button, 'stop_stream_button': stop_stream_button, 'save_button': save_button, 'name_format_text': name_format_text, 'num_images_text': num_images_text, 'avg_images_text': avg_images_text, 'fps_slider': fps_slider, 'fps_text': fps_text, 'exposure_slider': exposure_slider, 'exposure_text': exposure_text, 'time_images_text': time_images_text, 'directory_text': directory_text, 'directory_button': directory_button, 'bulk_button': bulk_button, 'red_but': red_but, 'yellow_but': yellow_but, 'green_but': green_but, 'blue_but': blue_but, 'b_text': b_text, 'g_text': g_text }
def __stage_gui(fig2): # Creates stage controls options_height = 0.02 padding = 0.01 but_width = options_height * 2 but_height = options_height * 3 pos = [0, 0, 1, 1] up_button_pos1 = [ pos[0] + 0.125 - options_height, pos[1] + pos[3] - 2 * padding - but_height, but_width, but_height ] up_button_axes1 = fig2.add_axes(up_button_pos1) up_button1 = Button(up_button_axes1, '++y') up_button1.label.set_fontsize(7) up_button1.on_clicked(__go_up_plus) up_button_pos2 = [ pos[0] + 0.125 - options_height, up_button_pos1[1] - but_height, but_width, but_height ] up_button_axes2 = fig2.add_axes(up_button_pos2) up_button2 = Button(up_button_axes2, '+y') up_button2.label.set_fontsize(7) up_button2.on_clicked(__go_up) down_button_pos1 = [ pos[0] + 0.125 - options_height, up_button_pos2[1] - 2 * but_height, but_width, but_height ] down_button_axes1 = fig2.add_axes(down_button_pos1) down_button1 = Button(down_button_axes1, '-y') down_button1.label.set_fontsize(7) down_button1.on_clicked(__go_down) down_button_pos2 = [ pos[0] + 0.125 - options_height, down_button_pos1[1] - but_height, but_width, but_height ] down_button_axes2 = fig2.add_axes(down_button_pos2) down_button2 = Button(down_button_axes2, '--y') down_button2.label.set_fontsize(7) down_button2.on_clicked(__go_down_plus) left_button_pos1 = [ pos[0] + 0.125 - options_height - but_width, down_button_pos1[1] + but_height, but_width, but_height ] left_button_axes1 = fig2.add_axes(left_button_pos1) left_button1 = Button(left_button_axes1, '-x') left_button1.label.set_fontsize(7) left_button1.on_clicked(__go_left) left_button_pos2 = [ pos[0] + 0.125 - options_height - 2 * but_width, down_button_pos1[1] + but_height, but_width, but_height ] left_button_axes2 = fig2.add_axes(left_button_pos2) left_button2 = Button(left_button_axes2, '--x') left_button2.label.set_fontsize(7) left_button2.on_clicked(__go_left_plus) right_button_pos1 = [ pos[0] + 0.125 - options_height + but_width, left_button_pos1[1], but_width, but_height ] right_button_axes1 = fig2.add_axes(right_button_pos1) right_button1 = Button(right_button_axes1, '+x') right_button1.label.set_fontsize(7) right_button1.on_clicked(__go_right) right_button_pos2 = [ pos[0] + 0.125 - options_height + 2 * but_width, left_button_pos1[1], but_width, but_height ] right_button_axes2 = fig2.add_axes(right_button_pos2) right_button2 = Button(right_button_axes2, '++x') right_button2.label.set_fontsize(7) right_button2.on_clicked(__go_right_plus) # current position x_pos_but_pos = [ up_button_pos1[0] + 6 * padding, down_button_pos2[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] x_pos_but_axes = fig2.add_axes(x_pos_but_pos) x_pos_but = TextBox(x_pos_but_axes, 'current x pos.') x_pos_but.label.set_fontsize(7) x_pos_but.set_val(1) x_pos_but.on_submit(__update_pos_x) # current position y_pos_but_pos = [ up_button_pos1[0] + 6 * padding, x_pos_but_pos[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] y_pos_but_axes = fig2.add_axes(y_pos_but_pos) y_pos_but = TextBox(y_pos_but_axes, 'current y pos.') y_pos_but.label.set_fontsize(7) y_pos_but.set_val(1) y_pos_but.on_submit(__update_pos_y) # Set x-y step size xy_step_text_pos1 = [ up_button_pos1[0] + 6 * padding, y_pos_but_pos[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] xy_step_text_axes1 = fig2.add_axes(xy_step_text_pos1) xy_step_text1 = TextBox(xy_step_text_axes1, '+ xy step size (um)') xy_step_text1.label.set_fontsize(7) xy_step_text1.set_val(__XY_STEP) xy_step_text1.on_submit(__xy_step) # Set x-y step size xy_step_text_pos2 = [ up_button_pos1[0] + 6 * padding, xy_step_text_pos1[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] xy_step_text_axes2 = fig2.add_axes(xy_step_text_pos2) xy_step_text2 = TextBox(xy_step_text_axes2, '++ xy step size (um)') xy_step_text2.label.set_fontsize(7) xy_step_text2.set_val(__XY_STEP_PLUS) xy_step_text2.on_submit(__xy_step_plus) z_up_button_pos1 = [ up_button_pos1[0] + 8 * but_width, up_button_pos1[1], but_width, but_height ] z_up_button_axes1 = fig2.add_axes(z_up_button_pos1) z_up_button1 = Button(z_up_button_axes1, '++z') z_up_button1.label.set_fontsize(7) z_up_button1.on_clicked(__go_defocus_up_plus) z_up_button_pos2 = [ z_up_button_pos1[0], up_button_pos2[1], but_width, but_height ] z_up_button_axes2 = fig2.add_axes(z_up_button_pos2) z_up_button2 = Button(z_up_button_axes2, '+ z') z_up_button2.label.set_fontsize(7) z_up_button2.on_clicked(__go_defocus_up) z_down_button_pos1 = [ z_up_button_pos2[0], z_up_button_pos2[1] - 2 * but_height, but_width, but_height ] z_down_button_axes1 = fig2.add_axes(z_down_button_pos1) z_down_button1 = Button(z_down_button_axes1, '- z') z_down_button1.label.set_fontsize(7) z_down_button1.on_clicked(__go_defocus_down) z_down_button_pos2 = [ z_up_button_pos2[0], z_down_button_pos1[1] - but_height, but_width, but_height ] z_down_button_axes2 = fig2.add_axes(z_down_button_pos2) z_down_button2 = Button(z_down_button_axes2, '-- z') z_down_button2.label.set_fontsize(7) z_down_button2.on_clicked(__go_defocus_down_plus) # current position z_pos_but_pos = [ z_down_button_pos2[0] + but_width * 2, z_down_button_pos2[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] z_pos_but_axes = fig2.add_axes(z_pos_but_pos) z_pos_but = TextBox(z_pos_but_axes, 'current z pos.') z_pos_but.label.set_fontsize(7) z_pos_but.set_val(1) z_pos_but.on_submit(__update_pos_z) # Set z step size z_step_text_pos1 = [ z_pos_but_pos[0], z_pos_but_pos[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] z_step_text_axes1 = fig2.add_axes(z_step_text_pos1) z_step_text1 = TextBox(z_step_text_axes1, '+ z step size (um)') z_step_text1.label.set_fontsize(7) z_step_text1.set_val(__Z_STEP) z_step_text1.on_submit(__z_step) # Set z step size z_step_text_pos2 = [ z_pos_but_pos[0], z_step_text_pos1[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] z_step_text_axes2 = fig2.add_axes(z_step_text_pos2) z_step_text2 = TextBox(z_step_text_axes2, '++ z step size (um)') z_step_text2.label.set_fontsize(7) z_step_text2.set_val(__Z_STEP_PLUS) z_step_text2.on_submit(__z_step_plus) # num of steps step_num_text_pos = [ z_pos_but_pos[0], z_step_text_pos2[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] step_num_text_axes = fig2.add_axes(step_num_text_pos) step_num_text = TextBox(step_num_text_axes, '# of steps (radius)') step_num_text.label.set_fontsize(7) step_num_text.set_val(1) step_num_text.on_submit(__number_defocus) # z interval z_interval_text_pos = [ z_pos_but_pos[0], step_num_text_pos[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] z_interval_text_axes = fig2.add_axes(z_interval_text_pos) z_interval_text = TextBox(z_interval_text_axes, 'defocus interval (um)') z_interval_text.label.set_fontsize(7) z_interval_text.set_val(1) z_interval_text.on_submit(__test) # time btwn z stacks time_btwn_z_text_pos = [ z_pos_but_pos[0], z_interval_text_pos[1] - 2 * padding - options_height * 2, 0.1 - 2 * padding, options_height * 2 ] time_btwn_z_text_axes = fig2.add_axes(time_btwn_z_text_pos) time_btwn_z_text = TextBox(time_btwn_z_text_axes, 'time between z stacks') time_btwn_z_text.label.set_fontsize(7) time_btwn_z_text.set_val(30) time_btwn_z_text.on_submit(__time_int_def) z_acquire_but_pos = [ pos[0] + 3 * padding, time_btwn_z_text_pos[1] - 2 * padding - options_height * 2, 0.55, options_height * 2 ] z_acquire_but_axes = fig2.add_axes(z_acquire_but_pos) z_acquire_but = Button(z_acquire_but_axes, 'Start defocus acquisition(s)') z_acquire_but.label.set_fontsize(7) z_acquire_but.on_clicked(__defocus_acquisition) led_but_width = but_width * 2 red_but_pos = [ pos[0] + 4 * padding, z_acquire_but_pos[1] - 2 * padding - options_height * 2, led_but_width, options_height * 2 ] red_but_axes = fig2.add_axes(red_but_pos) red_but = Button(red_but_axes, 'R') red_but.label.set_fontsize(7) red_but.on_clicked(__ledr) yellow_but_pos = [ red_but_pos[0] + 7 * padding + but_width, red_but_pos[1], led_but_width, options_height * 2 ] yellow_but_axes = fig2.add_axes(yellow_but_pos) yellow_but = Button(yellow_but_axes, 'Y') yellow_but.label.set_fontsize(7) yellow_but.on_clicked(__ledy) green_but_pos = [ yellow_but_pos[0] + 7 * padding + but_width, yellow_but_pos[1], led_but_width, options_height * 2 ] green_but_axes = fig2.add_axes(green_but_pos) green_but = Button(green_but_axes, 'G') green_but.label.set_fontsize(7) green_but.on_clicked(__ledg) blue_but_pos = [ green_but_pos[0] + 7 * padding + but_width, green_but_pos[1], led_but_width, options_height * 2 ] blue_but_axes = fig2.add_axes(blue_but_pos) blue_but = Button(blue_but_axes, 'B') blue_but.label.set_fontsize(7) blue_but.on_clicked(__ledb) off_but_pos = [ blue_but_pos[0] + 7 * padding + but_width, blue_but_pos[1], led_but_width, options_height * 2 ] off_but_axes = fig2.add_axes(off_but_pos) off_but = Button(off_but_axes, 'OFF') off_but.label.set_fontsize(7) off_but.on_clicked(__ledc) return { 'fig2': fig2, 'up_button1': up_button1, 'down_button1': down_button1, 'left_button1': left_button1, 'right_button1': right_button1, 'up_button2': up_button2, 'down_button2': down_button2, 'left_button2': left_button2, 'right_button2': right_button2, 'z_up_button1': z_up_button1, 'z_down_button1': z_down_button1, 'z_up_button2': z_up_button2, 'z_down_button2': z_down_button2, 'x_pos_but': x_pos_but, 'y_pos_but': y_pos_but, 'z_pos_but': z_pos_but, 'xy_step_text1': xy_step_text1, 'xy_step_text2': xy_step_text2, 'z_step_text1': z_step_text1, 'z_step_text2': z_step_text2, 'step_num_text': step_num_text, 'z_interval_text': z_interval_text, 'time_btwn_z_text': time_btwn_z_text, 'z_acquire_but': z_acquire_but, 'red_but': red_but, 'yellow_but': yellow_but, 'green_but': green_but, 'blue_but': blue_but, 'off_but': off_but }
class GUI(): def __init__(self): self.axcolor = 'lightgrey' self.fig = plt.figure(figsize=(8, 6)) self.updating_status = False self.main() def main(self): self.calc_infos() self.init_figure() self.init_control_zone() self.init_result_zone() self.add_events() plt.show() def calc_infos(self, algorithm=0): self.t_r2, self.s, self.Q, self.x_bias, self.y_bias, self.RMSE, self.W_p, self.rac_u_p, self.s_p, self.t_r2_p, self.T, self.S = run_AutoFit( algorithm) def init_figure(self): ######## Plot Zone ######## ax = self.fig.add_subplot(1, 1, 1) plt.subplots_adjust(bottom=0.42, left=0.18, right=0.65) rec_u, W_std = W_u_std(1e-8, 10, 0.01) self.l, = plt.plot(self.t_r2 + self.x_bias, self.s + self.y_bias, 's', color='maroon', markersize=5, label='measurement data') plt.plot(rec_u, W_std, color='k', linewidth=0.85, label='theis type-curve') plt.xlabel(r'lg$(\frac{1}{u})$ ', fontsize=10) plt.ylabel(r'lg$(W)$', fontsize=10) plt.xlim( np.min(self.t_r2 + self.x_bias) - 1, np.max(self.t_r2 + self.x_bias) + 1) plt.ylim( np.min(self.s + self.y_bias) - 1, np.max(self.s + self.y_bias) + 0.5) plt.grid(True, ls='--', lw=.5, c='k', alpha=0.6) plt.legend(fontsize=8) plt.suptitle("Theis Type-curve Fitting") def init_control_zone(self): ######## Control Zone ######## contrl_ax = plt.axes([0.05, 0.05, 0.90, 0.27]) plt.text(0.035, 0.85, 'Control Zone') contrl_ax.set_xticks([]) contrl_ax.set_yticks([]) # slider ax_xbias = plt.axes([0.14, 0.21, 0.35, 0.03], facecolor=self.axcolor) ax_ybias = plt.axes([0.14, 0.16, 0.35, 0.03], facecolor=self.axcolor) self.s_xbias = Slider(ax_xbias, 'x_bias', 0.0, 5, valinit=self.x_bias) self.s_ybias = Slider(ax_ybias, 'y_bias', 0.0, 5, valinit=self.y_bias) # buttons aftax = plt.axes([0.8, 0.06, 0.1, 0.04]) self.reset_button = Button(aftax, 'RESET', color=self.axcolor, hovercolor='mistyrose') x_bias_dec_tax = plt.axes([0.57, 0.21, 0.03, 0.03]) self.x_bias_dec_button = Button(x_bias_dec_tax, '-', color=self.axcolor, hovercolor='mistyrose') x_bias_inc_tax = plt.axes([0.72, 0.21, 0.03, 0.03]) self.x_bias_inc_button = Button(x_bias_inc_tax, '+', color=self.axcolor, hovercolor='mistyrose') y_bias_dec_tax = plt.axes([0.57, 0.16, 0.03, 0.03]) self.y_bias_dec_button = Button(y_bias_dec_tax, '-', color=self.axcolor, hovercolor='mistyrose') y_bias_inc_tax = plt.axes([0.72, 0.16, 0.03, 0.03]) self.y_bias_inc_button = Button(y_bias_inc_tax, '+', color=self.axcolor, hovercolor='mistyrose') # textbox xtextax = plt.axes([0.61, 0.21, 0.1, 0.03]) ytextax = plt.axes([0.61, 0.16, 0.1, 0.03]) self.t_xbias = TextBox(xtextax, ' ', str(float('%.2f' % self.x_bias))) self.t_ybias = TextBox(ytextax, ' ', str(float('%.2f' % self.y_bias))) # radio button rax = plt.axes([0.78, 0.12, 0.15, 0.17], facecolor=self.axcolor) plt.title('AutoFit Algorithms', fontsize=8) self.algos = np.array(['Traverse', 'Mass Center', 'Slope']) self.radio = RadioButtons(rax, self.algos, active=0) def init_result_zone(self, ): ######## Result Zone ######## re_ax = plt.axes([0.70, 0.37, 0.20, 0.50]) plt.text(0.035, 0.95, 'Results Zone') re_ax.set_xticks([]) re_ax.set_yticks([]) self.W_l = plt.text(0.2, 0.85, r'$W: %.2f $' % (self.W_p)) self.u_l = plt.text(0.2, 0.75, r'$1/u: %.2f $' % (self.rac_u_p)) self.s_l = plt.text(0.2, 0.65, r'$s: %.2f $' % (self.s_p)) self.tr2_l = plt.text(0.2, 0.55, r'$t/r^2: %.4f $' % (self.t_r2_p)) self.T_l = plt.text(0.2, 0.45, r'$T: %.2f m^3/d$' % (self.T)) self.S_l = plt.text(0.2, 0.35, r'$S: %.6f $' % (self.S)) plt.text(0.035, 0.2, 'Statistics of Fitting') self.RMSE_l = plt.text(0.2, 0.1, r'$RMSE: %.4f$' % (self.RMSE)) def add_events(self): self.s_xbias.on_changed(self.update_x) self.s_ybias.on_changed(self.update_y) self.reset_button.on_clicked(self.reset) self.x_bias_dec_button.on_clicked(self.dec_x_bias) self.x_bias_inc_button.on_clicked(self.inc_x_bias) self.y_bias_dec_button.on_clicked(self.dec_y_bias) self.y_bias_inc_button.on_clicked(self.inc_y_bias) self.t_xbias.on_submit(self.submit_x) self.t_ybias.on_submit(self.submit_y) self.radio.on_clicked(self.chg_algorithm) def chg_result(self, t_r2, s, x_bias, y_bias): """ change the results in the results zone """ self.W_p, self.rac_u_p, self.s_p, self.t_r2_p, self.T, self.S = calResult( self.t_r2, self.s, self.Q, self.x_bias, self.y_bias, num=2) self.W_bias = (W(1 / (10**(t_r2 + float('%2f' % x_bias))))) self.RMSE = np.sqrt( np.mean((self.W_bias - 10**(s + float('%.2f' % y_bias)))**2)) self.W_l.set_text(r'$W: %.2f $' % (self.W_p)) self.u_l.set_text(r'$1/u: %.2f $' % (self.rac_u_p)) self.s_l.set_text(r'$s: %.2f $' % (self.s_p)) self.tr2_l.set_text(r'$t/r^2: %.4f $' % (self.t_r2_p)) self.T_l.set_text(r'$T: %.2f m^3/d$' % (self.T)) self.S_l.set_text(r'$S: %.6f $' % (self.S)) self.RMSE_l.set_text(r'$RMSE: %.4f$' % (self.RMSE)) def update_x(self, x_bias): """update all widgets with new x_bias""" # 防抖 if self.updating_status: return x_bias = float('%.2f' % x_bias) if x_bias == self.x_bias: return self.updating_status = True self.x_bias = x_bias self.s_xbias.set_val(float('%.2f' % self.x_bias)) self.t_xbias.set_val(float('%.2f' % self.x_bias)) self.l.set_xdata(self.t_r2 + self.x_bias) self.fig.canvas.draw_idle() self.chg_result(self.t_r2, self.s, self.x_bias, self.y_bias) self.updating_status = False def update_y(self, y_bias): """update all widgets with new y_bias""" # 防抖 if self.updating_status: return y_bias = float('%.2f' % y_bias) if y_bias == self.y_bias: return self.updating_status = True self.y_bias = y_bias self.s_ybias.set_val(float('%.2f' % self.y_bias)) self.t_ybias.set_val(float('%.2f' % self.y_bias)) self.l.set_ydata(self.s + self.y_bias) self.fig.canvas.draw_idle() self.chg_result(self.t_r2, self.s, self.x_bias, self.y_bias) self.updating_status = False def reset(self, event): """ reset the figure """ self.radio.set_active(0) def dec_x_bias(self, event): """ decrease the x_bias using '-' button """ self.update_x(self.x_bias - 0.01) def inc_x_bias(self, event): """ increase the x_bias using '+' button """ self.update_x(self.x_bias + 0.01) def dec_y_bias(self, event): """ decrease the y_bias using '-' button """ self.update_y(self.y_bias - 0.01) def inc_y_bias(self, event): """ increase the y_bias using '+' button """ self.update_y(self.y_bias + 0.01) def submit_x(self, text): """ updates the figure using textbox (x_bias)""" self.update_x(float(text)) def submit_y(self, text): """ updates the figure using textbox (y_bias)""" self.update_y(float(text)) def chg_algorithm(self, label): algorithm = np.argwhere(self.algos == label)[0][0] # t_r2, s, Q, x_bias, y_bias, RMSE, W_p, rac_u_p, s_p, t_r2_p, T, S = run_AutoFit(algorithm) self.calc_infos(algorithm) self.update_x(self.x_bias) self.update_y(self.y_bias)
class Lantern_Cscan_vizualiser(object): def __init__(self, fig1, Cscan_LP01, Cscan_LP11): self.fig1 = fig1 self.dCscan_LP01 = np.array(10 * np.log(Cscan_LP01)) self.dCscan_LP11 = np.array(10 * np.log(Cscan_LP11)) def update_intensity(self, event): self.l_LP11.set_clim(vmin=self.SVmin.val, vmax=self.SVmax.val) self.l_LP01.set_clim(vmin=self.SVmin.val, vmax=self.SVmax.val) self.fig.canvas.draw_idle() def submit(self, text): self.N_plot = eval(text) self.dBscan = self.dCscan_LP01[:, :, self.N_plot].T self.l_LP01.set_data(self.dBscan) self.l_LP11.set_data(self.dBscan) self.normalize_image() self.textbox.set_val(self.N_plot) self.fig.canvas.draw_idle() def next(self, event): self.N_plot += 1 self.dBscan = self.dCscan_LP01[:, :, self.N_plot].T self.normalize_image() self.l_LP01.set_data(self.dBscan - np.mean(self.dBscan)) self.l_LP11.set_data(self.dBscan - np.mean(self.dBscan)) self.textbox.set_val(self.N_plot) self.fig.canvas.draw_idle() def normalize_image(self): self.dBscan -= np.mean(self.dBscan) - 255 / 2 self.ax3.cla() self.ax3.hist(self.dBscan.ravel(), 255) range = max(np.abs(np.max(self.dBscan) - 255 / 2), np.abs(np.min(self.dBscan) - 255 / 2)) std_range = 3 * np.std(self.dBscan) range = 5 * std_range print(std_range) self.ax3.set_xlim(255 / 2 - range, 255 / 2 + range) self.SVmin.valmin = 255 / 2 - range self.SVmin.valmax = 255 / 2 + range self.SVmax.valmin = 255 / 2 - range self.SVmax.valmax = 255 / 2 + range self.SVmin.ax.set_xlim(self.SVmin.valmin, self.SVmin.valmax) self.SVmax.ax.set_xlim(self.SVmax.valmin, self.SVmax.valmax) self.SVmin.val = 255 / 2 - std_range self.SVmax.val = 255 / 2 + std_range self.SVmax.set_value = 1 self.l_LP01.set_clim(vmin=255 / 2 - std_range, vmax=255 / 2 + std_range) self.l_LP11.set_clim(vmin=255 / 2 - std_range, vmax=255 / 2 + std_range) def previous(self, event): self.N_plot -= 1 self.dBscan = self.dCscan_LP01[:, :, self.N_plot].T self.normalize_image() self.l_LP01.set_data(self.dBscan) self.l_LP11.set_data(self.dBscan) self.textbox.set_val(self.N_plot) self.fig.canvas.draw_idle() def save_LP11(self, event): save_dir = "results/" extent = self.ax2.get_window_extent().transformed( self.fig.dpi_scale_trans.inverted()) plt.savefig(save_dir + "figure_" + 'LP11', bbox_inches=extent) def save_LP01(self, event): save_dir = "results/" extent = self.ax1.get_window_extent().transformed( self.fig.dpi_scale_trans.inverted()) plt.savefig(save_dir + "figure_" + 'LP01', bbox_inches=extent) def Bscan_lanterne_plots(self): self.fig = plt.figure(figsize=(16, 10)) gs = gridspec.GridSpec(5, 5) self.N_plot = 0 self.dBscan = self.dCscan_LP11[:, :, self.N_plot].T self.ax3 = self.fig.add_subplot(gs[4, 0:5]) self.l_hist = self.ax3.hist(self.dBscan.ravel(), 256) axVmin_intensity = plt.axes([0.1, 0.01, 0.8, 0.03]) axVmax_intensity = plt.axes([0.1, 0.05, 0.8, 0.03]) axsave_LP11 = plt.axes([0.8, 0.79, 0.1, 0.075]) axsave_LP01 = plt.axes([0.8, 0.9, 0.1, 0.075]) axnext = plt.axes([0.8, 0.3, 0.1, 0.075]) axprevious = plt.axes([0.8, 0.41, 0.1, 0.075]) Min_LP11, Max_LP11 = np.min(self.dBscan) * 1, np.max(self.dBscan) * 1. Min_LP01, Max_LP01 = np.min(self.dBscan) * 1, np.max(self.dBscan) * 1. Nstep_LP11 = (Max_LP11 - Min_LP11) / 200 Nstep_LP01 = (Max_LP01 - Min_LP01) / 200 self.SVmin = Slider(axVmin_intensity, 'Vmin', 0, 255, valinit=Min_LP11, valstep=Nstep_LP11) self.SVmax = Slider(axVmax_intensity, 'Vmax', 0, 255, valinit=Max_LP11, valstep=Nstep_LP11) temp_descr = 'wow' axLabel = plt.axes([0.5, 0.9, 0.11, 0.075]) self.textbox = TextBox(axLabel, '# Scan: ', temp_descr) self.textbox.set_val(self.N_plot) bsave_LP11 = Button(axsave_LP11, 'Save Bscan') bsave_LP01 = Button(axsave_LP01, 'Save Bscan') self.Bnext = Button(axnext, 'Next') self.Bprevious = Button(axprevious, 'Previous') self.ax1 = self.fig.add_subplot(gs[1:4, 0:2]) print(np.shape(self.dBscan)) self.l_LP01 = self.ax1.imshow(self.dBscan[:, :], cmap="gray", vmin=None, vmax=None) self.ax1.invert_yaxis() self.ax1.set_title("Processed Bscan LP01") self.ax2 = self.fig.add_subplot(gs[1:4, 2:4]) self.l_LP11 = self.ax2.imshow(self.dBscan[:, :], cmap="gray", vmin=None, vmax=None) self.ax2.invert_yaxis() self.ax2.set_title("Processed Bscan LP11") self.SVmin.on_changed(self.update_intensity) self.SVmax.on_changed(self.update_intensity) self.textbox.on_submit(self.submit) bsave_LP11.on_clicked(self.save_LP11) bsave_LP01.on_clicked(self.save_LP01) self.Bnext.on_clicked(self.next) self.Bprevious.on_clicked(self.previous) plt.show()
class GUIDisplay(object): """Display 2d arrays with interactive parameters: - Contrast - Colormap - Axis label - Legend - Calibration - Scalebar - Line profile - Export""" def __init__(self, data_to_display, cal=None): # 2D array to display with calibration cal in nm/pixel self.image_data = data_to_display # Window for image display + matplotlib parameters self.fig_image = plt.figure(figsize=(10, 7), dpi=100) # Layout figure self.gs_fig_image = gridspec.GridSpec(8, 8) # Make buttons and assign function calls buttons = (ButtonParams('Refresh', 0, 0, self.test), ButtonParams('Set\nColourmap', 1, 0, self.colourmap_button), ButtonParams('Calibration', 2, 0, self.test), ButtonParams('Scale bar', 3, 0, self.update_scalebar), ButtonParams('Line profile', 4, 0, self.line_profile), ButtonParams('Rot 90 CCW', 5, 0, self.rotate_90), ButtonParams('Num 6', 6, 0, self.test), ButtonParams('Export', 7, 0, self.export_data)) self.fig_image_parameter = [] # Assign button to subplot in figure for ii in buttons: button = Button(plt.subplot(self.gs_fig_image[ii.x, ii.y]), ii.text) button.on_clicked(ii.functioncall) self.fig_image_parameter.append(button) # Define image axis self.ax_image = plt.subplot(self.gs_fig_image[1:-1, 1:6]) self.ax_image.set_axis_off() self.cmap = 'bwr' self.image = self.ax_image.imshow(self.image_data, cmap=self.cmap, vmin=-0.02, vmax=0.02) # Contrast histogram display and span selector self.ax_contrast = plt.subplot(self.gs_fig_image[0, 1:6]) self.contrastbins = 256 self.cmin = -0.02 self.cmax = 0.02 self.imhist, self.imbins = np.histogram(self.image_data, bins=self.contrastbins) self.ax_contrast_span = None self.plot_contrast_histogram() # Colormaps self.maps = sorted([m for m in plt.cm.datad if not m.endswith("_r")]) self.cmapfig = None self.cmapaxes = None # (https://scipy.github.io/old-wiki/pages/Cookbook/Matplotlib/Show_colormaps) # Colourbar self.ax_colourbar = plt.subplot(self.gs_fig_image[1:-1, 7]) self.colourbar = Colorbar(self.ax_colourbar, self.image) # Textbox for colormap self.ax_cmin = plt.axes([0.8, 0.85, 0.1, 0.05]) self.ax_cmax = plt.axes([0.8, 0.8, 0.1, 0.05]) self.text_cmin = TextBox(self.ax_cmin, label='min', initial="%.4f" % self.cmin, label_pad=0.25) self.text_cmax = TextBox(self.ax_cmax, label='max', initial="%.4f" % self.cmax, label_pad=0.25) self.text_cmin.on_submit(self.update_cmin) self.text_cmax.on_submit(self.update_cmax) # Calibration textbox self.cal = cal self.ax_cal = plt.axes([0.5, 0.1, 0.1, 0.05]) if self.cal is None: self.text_cal = TextBox(self.ax_cal, label='Calibration (nm/pixel)', initial='', label_pad=0.25) else: self.text_cal = TextBox(self.ax_cal, label='Calibration (nm/pixel)', initial=self.cal, label_pad=0.25) self.text_cal.on_submit(self.update_calibration) # Scalebar self.state_scalebar = 0 self.scalebar = None # Line profile self.line_prof = None self.line_prof_edit = 0 self.fig_line_prof = None self.ax_fig_line_prof = None self.profile = None # Show the display window plt.show() @staticmethod def test(event): print(event) # Button to open colourmap selection window def colourmap_button(self, event): if event.inaxes == self.fig_image_parameter[1].ax: nummaps = len(self.maps) self.cmapfig = plt.figure('Colourmap options, pick one!', figsize=(5, 2 * nummaps)) self.cmapaxes = {} gradient = np.linspace(0, 1, 100) * np.ones((3, 100)) for mm in range(nummaps): corners = [0., mm / float(nummaps), 0.75, 1. / nummaps] self.cmapaxes[mm] = plt.axes(corners) self.cmapaxes[mm].annotate(self.maps[mm], xy=(0.77, (mm + 0.2) / float(nummaps)), xycoords='figure fraction', fontsize=11) self.cmapaxes[mm].set_axis_off() self.cmapaxes[mm].imshow(gradient, cmap=plt.get_cmap(self.maps[mm])) self.cmapfig.canvas.mpl_connect('button_press_event', self.colourmap_axis_select) plt.show() # Set colourmap based on clicking on an axis in the colourmap window def colourmap_axis_select(self, event): for aa in self.cmapaxes: if event.inaxes == self.cmapaxes[aa]: self.cmap = self.maps[aa] self.image.set_cmap(plt.get_cmap(self.cmap)) self.update_colourmap() self.fig_image.canvas.draw() def line_profile(self, event): if event.inaxes == self.fig_image_parameter[4].ax: if self.line_prof_edit == 0: if self.line_prof is None: print('create line') self.line_prof_edit = 1 self.line_prof = guilinemanager.LineDraw(self.ax_image) self.line_prof.ConnectDraw() else: print('edit line') self.line_prof_edit = 1 self.line_prof.ConnectMove() elif self.line_prof_edit == 1: print('disconect') self.line_prof_edit = 0 self.line_prof.DisconnectDraw() self.line_prof.DisconnectMove() self.fig_line_prof = plt.figure() self.ax_fig_line_prof = self.fig_line_prof.add_subplot(1, 1, 1) print(self.line_prof.WidthData) first_postion = (self.line_prof.LineCoords[0][1], self.line_prof.LineCoords[0][0]) second_postion = (self.line_prof.LineCoords[1][1], self.line_prof.LineCoords[1][0]) self.profile = skimage.measure.profile_line( self.image_data, first_postion, second_postion, linewidth=int(self.line_prof.WidthData)) self.ax_fig_line_prof.plot(self.profile) plt.show() else: return # Function to update image after changing it def update_image(self): self.image.set_clim(vmin=self.cmin, vmax=self.cmax) def update_colourmap(self): self.colourbar.update_bruteforce(self.image) def update_cm_textbox(self): self.text_cmin.set_val("%.4f" % self.cmin) self.text_cmax.set_val("%.4f" % self.cmax) def update_cmin(self, event): self.cmin = float(event) self.contrast_span(self.cmin, self.cmax) def update_cmax(self, event): self.cmax = float(event) self.contrast_span(self.cmin, self.cmax) # Calculates and plots image histogram and connects interactive spanselector def plot_contrast_histogram(self): self.ax_contrast.cla() self.ax_contrast.plot(self.imbins[:-1], self.imhist, color='k') self.ax_contrast.set_axis_off() self.ax_contrast_span = SpanSelector(self.ax_contrast, self.contrast_span, 'horizontal', span_stays=True, rectprops=dict(alpha=0.5, facecolor='green')) # Function for interactive spanselector for contrast histogram def contrast_span(self, cmin, cmax): self.cmin = cmin self.cmax = cmax self.update_image() self.update_colourmap() self.update_cm_textbox() def update_calibration(self, event): self.cal = float(event) def update_scalebar(self, event): if event.inaxes == self.fig_image_parameter[3].ax: if self.state_scalebar == 0: if self.cal is not None: self.state_scalebar = 1 self.scalebar = self.ax_image.add_artist( ScaleBar(self.cal * 10**-9)) self.fig_image.canvas.draw() elif self.state_scalebar == 1: if self.cal is not None: self.state_scalebar = 0 self.scalebar.remove() self.fig_image.canvas.draw() else: raise Exception("Invalid parameter for scalebar") def rotate_90(self, event): if event.inaxes == self.fig_image_parameter[5].ax: self.image_data = np.rot90(self.image_data) self.image.set_array(self.image_data) self.fig_image.canvas.draw() def export_data(self, event): if event.inaxes == self.fig_image_parameter[7].ax: print('export') #'''Save image respecting the number of pixels of the origin image''' #imsave('image_array.png', self.image_data) #'''Save image without respecting the number of pixels of the origin image''' plt.ioff() fig_export = plt.figure(figsize=(10, 7), dpi=100) ax_fig_export = fig_export.add_subplot(1, 1, 1) image_fig_export = ax_fig_export.imshow(self.image_data, cmap=self.cmap, vmin=self.cmin, vmax=self.cmax) ax_fig_export.set_axis_off() if self.state_scalebar == 1: ax_fig_export.add_artist(ScaleBar(self.cal * 10**-9)) fig_export.canvas.draw() if self.line_prof is not None: np.savetxt('line_profile', self.profile) fig_export.colorbar(image_fig_export) fig_export.savefig('image.png') print('Image saved') status_export_raw = 1 if status_export_raw == 1: np.savetxt('image_raw', self.image_data, delimiter=',') print('Raw data extracted') plt.close(fig_export)
def submit(expression): """ Update the plotted function to the new math *expression*. *expression* is a string using "t" as its independent variable, e.g. "t ** 3". """ ydata = eval(expression) l.set_ydata(ydata) ax.relim() ax.autoscale_view() plt.draw() axbox = fig.add_axes([0.1, 0.05, 0.8, 0.075]) text_box = TextBox(axbox, "Evaluate") text_box.on_submit(submit) text_box.set_val("t ** 2") # Trigger `submit` with the initial string. plt.show() ############################################################################# # # .. admonition:: References # # The use of the following functions, methods, classes and modules is shown # in this example: # # - `matplotlib.widgets.TextBox`
class EventSelector(): def __init__(self, parent=None): self.parent = parent self.fig = plt.figure() self.run_ax = plt.axes([0.3, 0.9, 0.5, 0.1]) self.run_selection = TextBox(self.run_ax, 'Run', initial=str(self.parent.run), color='white', hovercolor='lightgrey') self.run_selection.on_submit(self.submit_run) self.entry_ax = plt.axes([0.3, 0.8, 0.5, 0.1]) self.entry_selection = TextBox(self.entry_ax, 'Entry', initial=str(self.parent.entry), color='white', hovercolor='lightgrey') self.entry_selection.on_submit(self.submit_entry) self.event_ax = plt.axes([0.3, 0.7, 0.5, 0.1]) self.event_selection = TextBox( self.event_ax, 'Event', initial=str(self.parent.event_analyzer.header.event_number), color='white', hovercolor='lightgrey') #self.event_selection.on_submit(self.parent.set_event) self.event_selection.on_submit(self.submit_event) self.set_values() plt.ion() self.fig.show() def submit_event(self, event): try: self.parent.set_event(event) self.set_values() except: self.event_selection.color = 'red' pass def submit_entry(self, entry): try: self.parent.set_entry(entry) self.set_values() except: self.entry_selection.color = 'red' def submit_run(self, run): try: self.parent.set_run(run) self.set_values() except: self.run_selection.color = 'red' def set_values(self): self.run_selection.set_val(str(self.parent.run)) self.entry_selection.set_val(str(self.parent.entry)) self.event_selection.set_val( str(self.parent.event_analyzer.header.event_number)) for selection in [ self.run_selection, self.entry_selection, self.event_selection ]: selection.color = 'white'
class Visualization: """Visualize embedding with its infrastructure and overlay""" def __init__(self, embedding: PartialEmbedding): self.embedding = embedding shape = (2, 3) self.infra_ax = plt.subplot2grid(shape=shape, loc=(0, 0)) self.overlay_ax = plt.subplot2grid(shape=shape, loc=(1, 0)) self.embedding_ax = plt.subplot2grid(shape=shape, loc=(0, 1), rowspan=2, colspan=2) plt.subplots_adjust(bottom=0.2) input_text_ax = plt.axes([0.1, 0.05, 0.6, 0.075] # left # bottom # width # height ) input_btn_ax = plt.axes([0.7, 0.05, 0.2, 0.075] # left # bottom # width # height ) self.update_infra() self.update_overlay() self.update_embedding() pa = mpatches.Patch plt.gcf().legend(handles=[ pa(color=COLORS["sources_color"], label="source"), pa(color=COLORS["sink_color"], label="sink"), pa(color=COLORS["intermediates_color"], label="intermediate"), ]) random = get_random_action(self.embedding, rand=np.random) self.text_box_val = str(random) self.text_box = TextBox(input_text_ax, "Action", initial=self.text_box_val) def _update_textbox_val(new_val): self.text_box_val = new_val self.text_box.on_text_change(_update_textbox_val) self.submit_btn = Button(input_btn_ax, "Take action") def _on_clicked(_): self._take_action(self._parse_textbox()) self.submit_btn.on_clicked(_on_clicked) def _parse_textbox(self): action = self.text_box_val possibilities = self.embedding.possibilities() for possibility in possibilities: if str(possibility) == action: return possibility return None def _update_textbox(self): next_random = get_random_action(self.embedding, rand=np.random) self.text_box.set_val( str(next_random) if next_random is not None else "") def _take_action(self, action): if action is None: print("Action could not be parsed") return print(f"Taking action: {action}") success = self.embedding.take_action(*action) if not success: print("Action is not valid. The possibilities are:") self.update_embedding() self._update_textbox() plt.draw() def update_infra(self): """Redraws the infrastructure""" plt.sca(self.infra_ax) plt.cla() self.infra_ax.set_title("Infrastructure") draw_infra(self.embedding.infra, **COLORS) def update_overlay(self): """Redraws the overlay""" plt.sca(self.overlay_ax) plt.cla() self.overlay_ax.set_title("Overlay") draw_overlay(self.embedding.overlay, **COLORS) def update_embedding(self): """Redraws the embedding""" plt.sca(self.embedding_ax) plt.cla() self.embedding_ax.set_title("Embedding") draw_embedding(self.embedding, **COLORS)
def main(): global gc_args, g_curve_x, g_curve_y, gc_fig, gc_ax gc_args = retrieve_args() g_curve_x = np.linspace(g_left_x, g_right_x, gc_sl_n) g_curve_y = np.array([g_sl_valinit] * gc_sl_n) gc_fig = plt.figure(figsize=(12, 12)) gc_ax = gc_fig.add_subplot(111) axis_color, hover_color = 'lightgoldenrodyellow', '0.975' sl_hstep, sl_x, sl_y, sl_w, sl_h = 0.163, 0.23, 0.05, 0.02, 0.4 btn_h, btn_top, btn_vstep = 0.04, 0.62, 0.06 lp_x, lp_w = 0.025, 0.15 txt_top, txt_w, txt_h, txt_hstep = 0.91, 0.279, 0.029, 0.298 txt_label_color = 'brown' # * * Create widgets * * # Adjust the subplots region to leave some space for the sliders and buttons gc_fig.subplots_adjust(left=sl_x, bottom=0.50) for i, slider in enumerate(gc_sl): sl_ax = gc_fig.add_axes([sl_x + i * sl_hstep, sl_y, sl_w, sl_h], facecolor=axis_color) gc_sl[i] = i, VertSlider(sl_ax, f"S{i}:", g_bottom_y, g_top_y, valinit=g_sl_valinit) # Draw the initial plot plot_curve() for slider in gc_sl: i, s = slider s.on_changed(sliders_on_changed) tp = TextBox(gc_fig.add_axes([lp_x, txt_top + 0.035, 0.875, txt_h]), '', initial=Path.cwd()) # Current directory. tp.label.set_color(txt_label_color) ti = TextBox(gc_fig.add_axes([lp_x, txt_top, txt_w, txt_h]), 'I', initial=g_inc) # Include target directory. ti.on_text_change(inc_textbox_on_text_change) ti.label.set_color(txt_label_color) ts = TextBox(gc_fig.add_axes([lp_x + txt_hstep, txt_top, txt_w, txt_h]), 'S', initial=g_src) # Source target directory. ts.on_text_change(src_textbox_on_text_change) ts.label.set_color(txt_label_color) tn = TextBox(gc_fig.add_axes([lp_x + txt_hstep * 2, txt_top, txt_w, txt_h]), 'N', initial=g_name) # File and function name stem. tn.on_text_change(name_textbox_on_text_change) tn.label.set_color(txt_label_color) tn.text_disp.set_color('black') if gc_args.function_name: tn.set_val(Path(gc_args.function_name).resolve().stem) tr = TextBox(gc_fig.add_axes([lp_x, 0.852, lp_w, txt_h]), 'R', initial=str(g_range)) # Function range. tr.on_text_change(range_textbox_on_text_change) tr.label.set_color(txt_label_color) br = Button(gc_fig.add_axes([lp_x, btn_top - btn_vstep * 2, lp_w, btn_h]), 'Reset', color=axis_color, hovercolor=hover_color) br.on_clicked(reset_button_on_clicked) be = Button(gc_fig.add_axes([lp_x, btn_top, lp_w, btn_h]), 'Export', color=axis_color, hovercolor=hover_color) be.on_clicked(export_button_on_clicked) bi = Button(gc_fig.add_axes([lp_x, btn_top - btn_vstep, lp_w, btn_h]), 'Import', color=axis_color, hovercolor=hover_color) bi.on_clicked(import_button_on_clicked) rbd = RadioButtons(gc_fig.add_axes([lp_x, 0.68, lp_w, 0.15], facecolor=axis_color), ('128', '256', '512', '1024'), active=0) rbd.on_clicked(domain_radios_on_clicked) # * * Run the show * * plt.show()
class AtlasEditor(plot_support.ImageSyncMixin): """Graphical interface to view an atlas in multiple orthogonal dimensions and edit atlas labels. :attr:`plot_eds` are dictionaries of keys specified by one of :const:`magmap.config.PLANE` plane orientations to Plot Editors. Attributes: image5d: Numpy image array in t,z,y,x,[c] format. labels_img: Numpy image array in z,y,x format. channel: Channel of the image to display. offset: Index of plane at which to start viewing in x,y,z (user) order. fn_close_listener: Handle figure close events. borders_img: Numpy image array in z,y,x,[c] format to show label borders, such as that generated during label smoothing. Defaults to None. If this image has a different number of labels than that of ``labels_img``, a new colormap will be generated. fn_show_label_3d: Function to call to show a label in a 3D viewer. Defaults to None. title (str): Window title; defaults to None. fn_refresh_atlas_eds (func): Callback for refreshing other Atlas Editors to synchronize them; defaults to None. Typically takes one argument, this ``AtlasEditor`` object to refreshing it. Defaults to None. alpha_slider: Matplotlib alpha slider control. alpha_reset_btn: Maplotlib button for resetting alpha transparency. alpha_last: Float specifying the previous alpha value. interp_planes: Current :class:`InterpolatePlanes` object. interp_btn: Matplotlib button to initiate plane interpolation. save_btn: Matplotlib button to save the atlas. fn_status_bar (func): Function to call during status bar updates in :class:`pixel_display.PixelDisplay`; defaults to None. fn_update_coords (func): Handler for coordinate updates, which takes coordinates in z-plane orientation; defaults to None. """ _EDIT_BTN_LBLS = ("Edit", "Editing") def __init__(self, image5d, labels_img, channel, offset, fn_close_listener, borders_img=None, fn_show_label_3d=None, title=None, fn_refresh_atlas_eds=None, fig=None, fn_status_bar=None): """Plot ROI as sequence of z-planes containing only the ROI itself.""" super().__init__() self.image5d = image5d self.labels_img = labels_img self.channel = channel self.offset = offset self.fn_close_listener = fn_close_listener self.borders_img = borders_img self.fn_show_label_3d = fn_show_label_3d self.title = title self.fn_refresh_atlas_eds = fn_refresh_atlas_eds self.fig = fig self.fn_status_bar = fn_status_bar self.alpha_slider = None self.alpha_reset_btn = None self.alpha_last = None self.interp_planes = None self.interp_btn = None self.save_btn = None self.edit_btn = None self.color_picker_box = None self.fn_update_coords = None self._labels_img_sitk = None # for saving labels image def show_atlas(self): """Set up the atlas display with multiple orthogonal views.""" # set up the figure if self.fig is None: fig = figure.Figure(self.title) self.fig = fig else: fig = self.fig fig.clear() gs = gridspec.GridSpec(2, 1, wspace=0.1, hspace=0.1, height_ratios=(20, 1), figure=fig, left=0.06, right=0.94, bottom=0.02, top=0.98) gs_viewers = gridspec.GridSpecFromSubplotSpec(2, 2, subplot_spec=gs[0, 0]) # set up a colormap for the borders image if present cmap_borders = colormaps.get_borders_colormap(self.borders_img, self.labels_img, config.cmap_labels) coord = list(self.offset[::-1]) # editor controls, split into a slider sub-spec to allow greater # spacing for labels on either side and a separate sub-spec for # buttons and other fields gs_controls = gridspec.GridSpecFromSubplotSpec(1, 2, subplot_spec=gs[1, 0], width_ratios=(1, 1), wspace=0.15) self.alpha_slider = Slider( fig.add_subplot(gs_controls[0, 0]), "Opacity", 0.0, 1.0, valinit=plot_editor.PlotEditor.ALPHA_DEFAULT) gs_controls_btns = gridspec.GridSpecFromSubplotSpec( 1, 5, subplot_spec=gs_controls[0, 1], wspace=0.1) self.alpha_reset_btn = Button(fig.add_subplot(gs_controls_btns[0, 0]), "Reset") self.interp_btn = Button(fig.add_subplot(gs_controls_btns[0, 1]), "Fill Label") self.interp_planes = InterpolatePlanes(self.interp_btn) self.interp_planes.update_btn() self.save_btn = Button(fig.add_subplot(gs_controls_btns[0, 2]), "Save") self.edit_btn = Button(fig.add_subplot(gs_controls_btns[0, 3]), "Edit") self.color_picker_box = TextBox( fig.add_subplot(gs_controls_btns[0, 4]), None) # adjust button colors based on theme and enabled status; note # that colors do not appear to refresh until fig mouseover for btn in (self.alpha_reset_btn, self.edit_btn): enable_btn(btn) enable_btn(self.save_btn, False) enable_btn(self.color_picker_box, color=config.widget_color + 0.1) def setup_plot_ed(axis, gs_spec): # set up a PlotEditor for the given axis # subplot grid, with larger height preference for plot for # each increased row to make sliders of approx equal size and # align top borders of top images rows_cols = gs_spec.get_rows_columns() extra_rows = rows_cols[3] - rows_cols[2] gs_plot = gridspec.GridSpecFromSubplotSpec( 2, 1, subplot_spec=gs_spec, height_ratios=(1, 10 + 14 * extra_rows), hspace=0.1 / (extra_rows * 1.4 + 1)) # transform arrays to the given orthogonal direction ax = fig.add_subplot(gs_plot[1, 0]) plot_support.hide_axes(ax) plane = config.PLANE[axis] arrs_3d, aspect, origin, scaling = \ plot_support.setup_images_for_plane( plane, (self.image5d[0], self.labels_img, self.borders_img)) img3d_tr, labels_img_tr, borders_img_tr = arrs_3d # slider through image planes ax_scroll = fig.add_subplot(gs_plot[0, 0]) plane_slider = Slider(ax_scroll, plot_support.get_plane_axis(plane), 0, len(img3d_tr) - 1, valfmt="%d", valinit=0, valstep=1) # plot editor max_size = max_sizes[axis] if max_sizes else None plot_ed = plot_editor.PlotEditor( ax, img3d_tr, labels_img_tr, config.cmap_labels, plane, aspect, origin, self.update_coords, self.refresh_images, scaling, plane_slider, img3d_borders=borders_img_tr, cmap_borders=cmap_borders, fn_show_label_3d=self.fn_show_label_3d, interp_planes=self.interp_planes, fn_update_intensity=self.update_color_picker, max_size=max_size, fn_status_bar=self.fn_status_bar) return plot_ed # setup plot editors for all 3 orthogonal directions max_sizes = plot_support.get_downsample_max_sizes() for i, gs_viewer in enumerate( (gs_viewers[:2, 0], gs_viewers[0, 1], gs_viewers[1, 1])): self.plot_eds[config.PLANE[i]] = setup_plot_ed(i, gs_viewer) self.set_show_crosslines(True) # attach listeners fig.canvas.mpl_connect("scroll_event", self.scroll_overview) fig.canvas.mpl_connect("key_press_event", self.on_key_press) fig.canvas.mpl_connect("close_event", self._close) fig.canvas.mpl_connect("axes_leave_event", self.axes_exit) self.alpha_slider.on_changed(self.alpha_update) self.alpha_reset_btn.on_clicked(self.alpha_reset) self.interp_btn.on_clicked(self.interpolate) self.save_btn.on_clicked(self.save_atlas) self.edit_btn.on_clicked(self.toggle_edit_mode) self.color_picker_box.on_text_change(self.color_picker_changed) # initialize and show planes in all plot editors if self._max_intens_proj is not None: self.update_max_intens_proj(self._max_intens_proj) self.update_coords(coord, config.PLANE[0]) plt.ion() # avoid the need for draw calls def _close(self, evt): """Handle figure close events by calling :attr:`fn_close_listener` with this object. Args: evt (:obj:`matplotlib.backend_bases.CloseEvent`): Close event. """ self.fn_close_listener(evt, self) def on_key_press(self, event): """Respond to key press events. """ if event.key == "a": # toggle between current and 0 opacity if self.alpha_slider.val == 0: # return to saved alpha if available and reset if self.alpha_last is not None: self.alpha_slider.set_val(self.alpha_last) self.alpha_last = None else: # make translucent, saving alpha if not already saved # during a halve-opacity event if self.alpha_last is None: self.alpha_last = self.alpha_slider.val self.alpha_slider.set_val(0) elif event.key == "A": # halve opacity, only saving alpha on first halving to allow # further halving or manual movements while still returning to # originally saved alpha if self.alpha_last is None: self.alpha_last = self.alpha_slider.val self.alpha_slider.set_val(self.alpha_slider.val / 2) elif event.key == "up" or event.key == "down": # up/down arrow for scrolling planes self.scroll_overview(event) elif event.key == "w": # shortcut to toggle editing mode self.toggle_edit_mode(event) elif event.key == "ctrl+s" or event.key == "cmd+s": # support default save shortcuts on multiple platforms; # ctrl-s will bring up save dialog from fig, but cmd/win-S # will bypass self.save_fig(self.get_save_path()) def update_coords(self, coord, plane_src=config.PLANE[0]): """Update all plot editors with given coordinates. Args: coord: Coordinate at which to center images, in z,y,x order. plane_src: One of :const:`magmap.config.PLANE` to specify the orientation from which the coordinates were given; defaults to the first element of :const:`magmap.config.PLANE`. """ coord_rev = libmag.transpose_1d_rev(list(coord), plane_src) for i, plane in enumerate(config.PLANE): coord_transposed = libmag.transpose_1d(list(coord_rev), plane) if i == 0: self.offset = coord_transposed[::-1] if self.fn_update_coords: # update offset based on xy plane, without centering # planes are centered on the offset as-is self.fn_update_coords(coord_transposed, False) self.plot_eds[plane].update_coord(coord_transposed) def view_subimg(self, offset, shape): """Zoom all Plot Editors to the given sub-image. Args: offset: Sub-image coordinates in ``z,y,x`` order. shape: Sub-image shape in ``z,y,x`` order. """ for i, plane in enumerate(config.PLANE): offset_tr = libmag.transpose_1d(list(offset), plane) shape_tr = libmag.transpose_1d(list(shape), plane) self.plot_eds[plane].view_subimg(offset_tr[1:], shape_tr[1:]) self.fig.canvas.draw_idle() def refresh_images(self, plot_ed=None, update_atlas_eds=False): """Refresh images in a plot editor, such as after editing one editor and updating the displayed image in the other editors. Args: plot_ed (:obj:`magmap.plot_editor.PlotEditor`): Editor that does not need updating, typically the editor that originally changed. Defaults to None. update_atlas_eds (bool): True to update other ``AtlasEditor``s; defaults to False. """ for key in self.plot_eds: ed = self.plot_eds[key] if ed != plot_ed: ed.refresh_img3d_labels() if ed.edited: # display save button as enabled if any editor has been edited enable_btn(self.save_btn) if update_atlas_eds and self.fn_refresh_atlas_eds is not None: # callback to synchronize other Atlas Editors self.fn_refresh_atlas_eds(self) def scroll_overview(self, event): """Scroll images and crosshairs in all plot editors Args: event: Scroll event. """ for key in self.plot_eds: self.plot_eds[key].scroll_overview(event) def alpha_update(self, event): """Update the alpha transparency in all plot editors. Args: event: Slider event. """ for key in self.plot_eds: self.plot_eds[key].alpha_updater(event) def alpha_reset(self, event): """Reset the alpha transparency in all plot editors. Args: event: Button event, currently ignored. """ self.alpha_slider.reset() def axes_exit(self, event): """Trigger axes exit for all plot editors. Args: event: Axes exit event. """ for key in self.plot_eds: self.plot_eds[key].on_axes_exit(event) def interpolate(self, event): """Interpolate planes using :attr:`interp_planes`. Args: event: Button event, currently ignored. """ try: self.interp_planes.interpolate(self.labels_img) # flag Plot Editors as edited so labels can be saved for ed in self.plot_eds.values(): ed.edited = True self.refresh_images(None, True) except ValueError as e: print(e) def save_atlas(self, event): """Save atlas labels using the registered image suffix given by :attr:`config.reg_suffixes[config.RegSuffixes.ANNOTATION]`. Args: event: Button event, currently not used. """ # only save if at least one editor has been edited if not any([ed.edited for ed in self.plot_eds.values()]): return # save to the labels reg suffix; use sitk Image if loaded and store # any Image loaded during saving reg_name = config.reg_suffixes[config.RegSuffixes.ANNOTATION] if self._labels_img_sitk is None: self._labels_img_sitk = config.labels_img_sitk self._labels_img_sitk = sitk_io.write_registered_image( self.labels_img, config.filename, reg_name, self._labels_img_sitk, overwrite=True) # reset edited flag in all editors and show save button as disabled for ed in self.plot_eds.values(): ed.edited = False enable_btn(self.save_btn, False) print("Saved labels image at {}".format(datetime.datetime.now())) def get_save_path(self): """Get figure save path based on filename, ROI, and overview plane shown. Returns: str: Figure save path. """ ext = config.savefig if config.savefig else config.DEFAULT_SAVEFIG return "{}.{}".format( naming.get_roi_path(os.path.basename(self.title), self.offset), ext) def toggle_edit_mode(self, event): """Toggle editing mode, determining the current state from the first :class:`magmap.plot_editor.PlotEditor` and switching to the opposite value for all plot editors. Args: event: Button event, currently not used. """ edit_mode = False for i, ed in enumerate(self.plot_eds.values()): if i == 0: # change edit mode based on current mode in first plot editor edit_mode = not ed.edit_mode toggle_btn(self.edit_btn, edit_mode, text=self._EDIT_BTN_LBLS) ed.edit_mode = edit_mode if not edit_mode: # reset the color picker text box when turning off editing self.color_picker_box.set_val("") def update_color_picker(self, val): """Update the color picker :class:`TextBox` with the given value. Args: val (str): Color value. If None, only :meth:`color_picker_changed` will be triggered. """ if val is None: # updated picked color directly self.color_picker_changed(val) else: # update text box, which triggers color_picker_changed self.color_picker_box.set_val(val) def color_picker_changed(self, text): """Respond to color picker :class:`TextBox` changes by updating the specified intensity value in all plot editors. Args: text (str): String of text box value. Converted to an int if non-empty. """ intensity = text if text: if not libmag.is_number(intensity): return intensity = int(intensity) print("updating specified color to", intensity) for i, ed in enumerate(self.plot_eds.values()): ed.intensity_spec = intensity
an.append(expression) func(a0[len(a0) - 1], expression, bn[len(bn) - 1]) ax.relim def submit2(expression): bn.append(expression) func(a0[len(a0) - 1], an[len(an) - 1], expression) ax.relim def submit3(expression): a0.append(expression) func(expression, an[len(an) - 1], bn[len(bn) - 1]) ax.relim axbox3 = fig.add_axes([0.1, 0.4, 0.2, 0.055], xlabel='D (m)') text_box3 = TextBox(axbox3, "") text_box3.on_submit(submit3) text_box3.set_val('17.4') axbox = fig.add_axes([0.4, 0.4, 0.2, 0.055], xlabel='$\lambda$ (nm)') text_box = TextBox(axbox, "") text_box.on_submit(submit) text_box.set_val('584') axbox2 = fig.add_axes([0.7, 0.4, 0.2, 0.055], xlabel='d (m)') text_box2 = TextBox(axbox2, "") text_box2.on_submit(submit2) text_box2.set_val("9.6e-4")
class Viewer: def __init__(self, gui_params, params): self.curation_data = utils.load_manual_curation( params['curation_csvpath']) self.males_list = utils.load_male_list(params['male_listpath']) self.induction_dates = utils.load_induction_data( params['induction_csvpath']) self.season_data = utils.load_pond_season_data( params['pond_season_csvpath']) self.early_release = utils.load_release_data( params['early_release_csvpath']) self.late_release = utils.load_release_data( params['late_release_csvpath']) self.duplicate_data = utils.load_duplicate_data( params['duplicate_csvpath']) self.experimenter_data, self.inducer_data = utils.load_experimenter_data( params['experimenter_csvpath']) self.gui_params = gui_params self.params = params self.params.update(self.gui_params) self.auto_save_count = 0 file_list = [] metadata_list = [] print "Reading in image file list" with open(self.params["image_list_filepath"], "rb") as f: line = f.readline().strip() while line: file_list.append(line) line = f.readline().strip() print "Reading in analysis file" self.data = utils.csv_to_df(self.params["input_analysis_file"]) try: saved_data = utils.csv_to_df(self.params["output_analysis_file"]) for row in saved_data.iterrows(): i = np.where(self.data['filebase'] == row[1]['filebase']) for k, v in row[1].iteritems(): self.data.loc[i[0], k] = v except IOError: pass if not hasattr(self.data, 'accepted'): self.data['accepted'] = np.zeros(len(self.data)) self.shape_data = utils.read_shape_long( self.params["input_shape_file"]) try: saved_shape_data = utils.read_shape_long( self.params["output_shape_file"]) fbs = np.unique(saved_shape_data['filebase']) self.shape_data = self.shape_data.drop( self.shape_data[self.shape_data['filebase'].isin(fbs)].index) self.shape_data = self.shape_data.append(saved_shape_data) except IOError: pass try: self.masked_regions = utils.read_masked_regions_long( self.params["masked_regions_output"]) except IOError: self.masked_regions = {} all_clone_list = [] for f in file_list: try: fileparts = f.split("/") clone = utils.dfrow_to_clone( self.data, np.where(self.data.filebase == fileparts[-1])[0][0], self.params) clone.filepath = f all_clone_list.append(clone) except IndexError: print "No entry found in datafile for " + str(f) for i in xrange(len(all_clone_list)): index = (self.shape_data.filebase == all_clone_list[i].filebase) all_clone_list[i].dorsal_edge = np.transpose( np.vstack((np.transpose(self.shape_data.loc[index].x), np.transpose(self.shape_data.loc[index].y)))) all_clone_list[i].q = np.array(self.shape_data.loc[index].q) all_clone_list[i].qi = np.array(self.shape_data.loc[index].qi) idx = self.shape_data.loc[index].checkpoint == 1 all_clone_list[i].checkpoints = all_clone_list[i].dorsal_edge[ idx, :] clone_list = [] for clone in all_clone_list: if len(clone.dorsal_edge) > 0: if not (int(self.params['skip_accepted']) and clone.accepted): clone_list.append(clone) else: print "No shape data found for " + clone.filebase if len(clone_list) == 0: print "Either image list is empty or they have all been 'accepted'" return self.all_clone_list = all_clone_list self.clone_list = clone_list self.curr_idx = 0 self.clone = self.clone_list[self.curr_idx] self.saving = 0 self.fig = plt.figure(figsize=(15, 10)) self.fig.patch.set_facecolor("lightgrey") self.display = self.fig.add_subplot(111) self.display.axis('off') try: self.obj = PointFixer(self.clone, self.display) try: self.obj.masked_regions = self.masked_regions[ self.clone.filebase] self.mask_all_regions() except (AttributeError, KeyError): pass except IOError: try: self.clone.modification_notes += " Image file not found." except TypeError: self.clone.modification_notes = "Image file not found." if self.curr_idx < len(self.clone_list) - 1: self.next_button_press(1) self.obj.clone.modifier = self.gui_params["default_modifier"] self.populate_figure() self.add_checkpoint = False return def populate_figure(self): self.display.clear() button_color = [0.792156862745098, 0.8823529411764706, 1.0] if self.saving == 1: self.saving_text = self.fig.text(0.875, 0.879, 'Saving', fontsize=10, fontweight='bold', color=[0.933, 0.463, 0]) else: try: self.saving_text.remove() except (ValueError, AttributeError): pass self.notes_text = self.fig.text(0.852, 0.575, 'Notes', fontsize=10, color="black") axmodnotes = plt.axes([0.852, 0.45, 0.125, 0.12]) initial_modnotes = str(self.obj.clone.modification_notes) if initial_modnotes == "nan": initial_modnotes = "" self.notestextbox = TextBox(axmodnotes, '', initial=initial_modnotes) self.notestextbox.on_submit(self.set_notes) axmodifier = plt.axes([0.875, 0.37, 0.1, 0.035]) self.modifiertextbox = TextBox(axmodifier, 'Initials', initial=str( self.params['default_modifier'])) self.modifiertextbox.on_submit(self.change_default_modifier) axflip = plt.axes([0.50, 0.01, 0.1, 0.075]) self.flipbutton = Button(axflip, 'Flip', color=button_color, hovercolor=button_color) self.flipbutton.on_clicked(self.obj.flip_button_press) axedges = plt.axes([0.60, 0.01, 0.1, 0.075]) self.edgebutton = Button(axedges, 'Toggle Edges', color=button_color, hovercolor=button_color) self.edgebutton.on_clicked(self.obj.edge_button_press) axtogdorsal = plt.axes([0.70, 0.01, 0.1, 0.075]) self.togdorsalbutton = Button(axtogdorsal, 'Toggle Dorsal Fit', color=button_color, hovercolor=button_color) self.togdorsalbutton.on_clicked(self.obj.toggle_dorsal_button_press) axdel = plt.axes([0.025, 0.01, 0.1, 0.075]) self.delcheckbutton = Button(axdel, 'Delete Checkpoint', color=button_color, hovercolor=button_color) self.delcheckbutton.on_clicked(self.obj.delete_selected_checkpoint) axsave = plt.axes([0.875, 0.8, 0.1, 0.075]) self.savebutton = Button(axsave, 'Save', color=button_color, hovercolor=button_color) self.savebutton.on_clicked(self.save) axblur = plt.axes([0.25, 0.01, 0.1, 0.035]) self.blurtextbox = TextBox(axblur, 'Gaussian Blur StDev', initial=str(self.obj.edge_blur)) self.blurtextbox.on_submit(self.set_edge_blur) axreset = plt.axes([0.40, 0.01, 0.1, 0.075]) self.resetbutton = Button(axreset, 'Reset', color=button_color, hovercolor=button_color) self.resetbutton.on_clicked(self.reset_button_press) if self.curr_idx + 1 < len(self.clone_list): axnext = plt.axes([0.875, 0.01, 0.1, 0.075]) self.nextbutton = Button(axnext, 'Next', color=button_color, hovercolor=button_color) self.nextbutton.on_clicked(self.next_button_press) if self.curr_idx > 0: axprev = plt.axes([0.875, 0.085, 0.1, 0.075]) self.prevbutton = Button(axprev, 'Previous', color=button_color, hovercolor=button_color) self.prevbutton.on_clicked(self.prev_button_press) self.obj.draw() def set_notes(self, text): try: self.obj.clone.modification_notes = str(text) self.obj.draw() except ValueError: print "Invalid value for notes" def set_edge_blur(self, text): try: self.obj.edge_blur = float(text) except ValueError: print "Invalid value for gaussian blur sigma" self.obj.clone.initialize_dorsal_edge( self.obj.original_image, dorsal_edge_blur=self.obj.edge_blur) self.obj.clone.fit_dorsal_edge(self.obj.original_image, dorsal_edge_blur=self.obj.edge_blur) self.obj.de = self.obj.clone.interpolate(self.obj.clone.dorsal_edge) self.obj.edge_image = self.obj.clone.edges self.mask_all_regions() self.obj.checkpoints = self.obj.clone.checkpoints self.obj.edges = False self.obj.edge_button_press(1) self.obj.clone.dorsal_edge_blur = self.obj.edge_blur self.obj.draw() def mask_all_regions(self): try: for region_id in xrange(len(self.obj.masked_regions.keys())): region = self.obj.masked_regions[region_id] if region[0] == "u": self.obj.lasso_then_unmask(region[1], add_to_metadata=False) elif region[0] == "m": self.obj.lasso_then_mask(region[1], add_to_metadata=False) except AttributeError: pass def change_default_modifier(self, text): self.gui_params['default_modifier'] = str(text) with open("gui_params.txt", "w+") as f: for k, v in gui_params.items(): f.write(str(k) + ", " + str(v) + "\n") self.obj.clone.modifier = self.gui_params["default_modifier"] def prev_button_press(self, event): self.clone_list[self.curr_idx] = self.obj.clone self.masked_regions[self.obj.clone.filebase] = self.obj.masked_regions self.curr_idx -= 1 self.clone = self.clone_list[self.curr_idx] self.fig.clear() self.display = self.fig.add_subplot(111) self.display.axis('off') self.obj = PointFixer(self.clone, self.display) self.obj.clone.modifier = self.gui_params["default_modifier"] try: self.obj.masked_regions = self.masked_regions[ self.obj.clone.filebase] self.mask_all_regions() except KeyError: pass self.populate_figure() return def next_button_press(self, event): try: self.clone_list[self.curr_idx] = self.obj.clone self.masked_regions[ self.obj.clone.filebase] = self.obj.masked_regions except AttributeError: self.clone_list[self.curr_idx] = self.clone self.auto_save_count += 1 if self.gui_params["auto_save"]: if self.auto_save_count == self.gui_params["auto_save_number"]: self.save(1) self.auto_save_count = 0 self.curr_idx += 1 try: self.clone = self.clone_list[self.curr_idx] if os.path.isfile(self.clone.filepath): self.fig.clear() self.display = self.fig.add_subplot(111) self.display.axis('off') self.obj.clone.modifier = self.gui_params["default_modifier"] self.obj = PointFixer(self.clone, self.display) try: self.obj.masked_regions = self.masked_regions[ self.obj.clone.filebase] self.mask_all_regions() except KeyError: pass else: print "Image " + self.clone.filepath + " not found. Check your image filepath." raise IOError except IOError: try: self.clone.modification_notes += " Image file not found." except TypeError: self.clone.modification_notes = "Image file not found." if self.curr_idx < len(self.clone_list) - 1: self.next_button_press(event) else: self.curr_idx -= 1 self.clone = self.clone_list[self.curr_idx] self.populate_figure() def reset_button_press(self, event): if self.obj.clone.flip: self.obj.clone.flip = not self.obj.clone.flip self.obj.clone.find_features(self.obj.original_image, **self.obj.params) self.obj.clone.get_orientation_vectors() self.obj.clone.find_head(self.obj.original_image, **self.obj.params) self.obj.edge_blur = 1.0 self.blurtextbox.set_val('1.0') self.obj.clone.dorsal_blur = 1.0 self.obj.edge_image = self.obj.original_edge_image.copy() if self.obj.edges: self.obj.image = self.obj.edge_image self.obj.clone.initialize_dorsal_edge(self.obj.original_image, edges=self.obj.edge_image, **self.obj.params) self.obj.clone.fit_dorsal_edge(self.obj.original_image, **self.obj.params) self.obj.clone.find_tail(self.obj.original_image) self.obj.masked_regions = {} self.obj.clone.remove_tail_spine() self.obj.de = self.obj.clone.interpolate(self.obj.clone.dorsal_edge) self.obj.selected = None self.obj.checkpoints = self.obj.clone.checkpoints self.obj.draw() def save(self, event): print "Saving..." self.clone_list[self.curr_idx] = self.obj.clone self.masked_regions[self.obj.clone.filebase] = self.obj.masked_regions for all_c in xrange(len(self.all_clone_list)): for c in xrange(len(self.clone_list)): if self.all_clone_list[all_c].filebase == self.clone_list[ c].filebase: self.all_clone_list[all_c] = self.clone_list[c] self.saving = 1 self.populate_figure() with open(self.params["input_analysis_file"], "rb") as analysis_file_in, open( self.params["output_analysis_file"], "wb") as analysis_file_out: # read/write header and save column names line = analysis_file_in.readline() analysis_file_out.write(line) line = line.strip() DATA_COLS = line.split("\t") line = analysis_file_in.readline() while line: written = False for clone in self.all_clone_list: if (line.split("\t")[0] == clone.filebase) and clone.accepted: analysis_file_out.write( utils.clone_to_line(clone, DATA_COLS) + "\n") written = True if (not written) and (not self.params['truncate_output']): analysis_file_out.write(line) line = analysis_file_in.readline() with open(self.params["output_shape_file"], "wb") as shape_file_out: # read/write header line = "\t".join( ["filebase", "i", "x", "y", "qi", "q", "checkpoint"]) + "\n" shape_file_out.write(line) clone = None for c in self.all_clone_list: if c.accepted: clone = c if clone is not None: for i in np.arange(len(clone.dorsal_edge)): if len( np.where((clone.checkpoints == clone.dorsal_edge[i, :]).all( axis=1))[0]) > 0: checkpoint = 1 else: checkpoint = 0 shape_file_out.write('\t'.join([ clone.filebase, str(i), str(clone.dorsal_edge[i, 0]), str(clone.dorsal_edge[i, 1]), str(clone.qi[i]), str(clone.q[i]), str(checkpoint) ]) + "\n") clone = None self.saving = 0 self.populate_figure() with open(self.params["input_analysis_metadata_file"], "rb") as analysis_file_in, open( self.params["output_analysis_metadata_file"], "wb") as analysis_file_out: line = analysis_file_in.readline() analysis_file_out.write(line) line = analysis_file_in.readline() while line: written = False for clone in self.all_clone_list: if (line.split("\t")[0] == clone.filebase) and clone.accepted: metadata = [clone.filebase] + [ str(getattr(clone, mf)) for mf in ANALYSIS_METADATA_FIELDS ] analysis_file_out.write("\t".join(metadata + ["\n"])) written = True if (not written) and (not self.params['truncate_output']): analysis_file_out.write(line) line = analysis_file_in.readline() with open(self.params["masked_regions_output"], "wb") as file_out: file_out.write("\t".join( ["filebase", "i", "x", "y", "masking_or_unmasking"]) + "\n") for clone in self.all_clone_list: if clone.accepted: try: masked_regions = self.masked_regions[clone.filebase] for i in xrange(len(masked_regions.keys())): m_or_u, region = masked_regions[i] for j in xrange(region.shape[0]): file_out.write("\t".join([ clone.filebase, str(i), str(region[j][0]), str(region[j][1]), m_or_u ]) + "\n") except KeyError: continue print "Saving done."
ax = fig.add_subplot(111) p, = ax.plot(x, y) d, = ax.plot(listx, listy, "o") def submit_fn_x0(value): update_function("x0", float(value)) plt.draw() axbox_x0 = fig.add_axes([0.75, 0.93, 0.1, 0.05]) text_box_x0 = TextBox(axbox_x0, "x1 ") text_box_x0.on_submit(submit_fn_x0) text_box_x0.set_val(listx[0]) def submit_fn_y0(value): update_function("y0", float(value)) plt.draw() axbox_y0 = fig.add_axes([0.89, 0.93, 0.1, 0.05]) text_box_y0 = TextBox(axbox_y0, "y1 ") text_box_y0.on_submit(submit_fn_y0) text_box_y0.set_val(listy[0]) def submit_fn_x1(value): update_function("x1", float(value))