Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
#from tests_plot.dgilib_averages import HoldTimes
data_gpio = self.data gpio_start_index = self.index
return [] # We can't identify intervals with only one value return [] # We're being asked to do an index that does not exist yet, so just skip
#last_toggle_timestamp = data_gpio.timestamps[start_index]
#print("New data, starting on pin " + str(pin) + " at timestamp " + str(data_gpio.timestamps[start_index]) + " of value " + str(last_toggle_value) + ". Index is: " + str(start_index))
#print("Detected toggle on pin " + str(pin) + " at timestamp " + str(data_gpio.timestamps[i]) + " of value " + str(data_gpio.values[i][pin]) + ". Index is: " + str(i))
#last_toggle_timestamp = data_gpio.timestamps[i]
# A smart printing for debugging this function # Either leave 'debug = False' or comment it, but don't lose it for (t, v) in data_gpio: # print(str((t,v))) if t in toggle_times: print("\t" + str(t) + "\t\t" + str(v) + "\t <-- toggled") else: print("\t" + str(t) + "\t\t" + str(v))
# , last_toggle_index
data_gpio = self.data return [] # We can't identify intervals with only one value return [] # We're being asked to do an index that does not exist yet, so just skip
pin, data_gpio, self.index)
#print("T2F: " + str(true_to_false_times)) #print("F2T: " + str(false_to_true_times))
# A fix elif (pin_value == False): # A fix if true_to_false_times[0] > false_to_true_times[0]: false_to_true_times.pop(0) hold_times = zip(true_to_false_times, false_to_true_times)
# A smart printing for debugging this function # Either leave 'debug = False' or comment it, but don't lose it ht_zip = list(zip(*hold_times)) for (t, v) in data_gpio: # print(str((t,v))) if t in ht_zip[0]: print("\t" + str(t) + "\t\t" + str(v) + "\t <-- start") elif t in ht_zip[1]: print("\t" + str(t) + "\t\t" + str(v) + "\t <-- stop") else: print("\t" + str(t) + "\t\t" + str(v))
hold_times_list[-1][-1]) + 1 except IndexError: # If you remove this, you get an error pass
"""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. """
# Maybe the user wants to put the power figure along with # other figures he wants
# Set window title if supplied, if not set a default "Plot of current (in amperes) and" + "gpio pins")
# We need the Line2D object as well, to update it else: self.ln = self.ax.lines[0] self.ln.set_xdata([]) self.ln.set_ydata([])
# Initialize xmax, ymax of plot initially else:
else: self.plot_yauto = False
#self.total_average = [0,0,0,0]
#self.refresh_plot_pause_secs = kwargs.get("refresh_plot_pause_secs", 0.00000001)
ln_pin, bool)] + [self.ln]) # Should actually check if it is a lines instance
# Hardwiring these values to 0
# We need these values from the user (or from the superior class), # hence no default values # TODO: Are they really needed though? # 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 # Show grid
# Slider color # Title format # Should use this https://matplotlib.org/gallery/recipes/placing_text_boxes.html
# Hold times for pins list, we're going to collect them
#TODO: Change to pixel sizes
self.plot_xstep = float(text)
#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)
#if ((self.spos.val - self.plot_xstep) >= (self.plot_xmin)): update_pos(self.spos.val)
#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)
if ((val + 0.05) < (self.swidth.val - 0.000001)): xstep_submit(self.xsteptb.text)
if ((val - 0.05) >= (0.05)): xstep_submit(self.xsteptb.text)
# I'm not sure how to detach these without them forgetting their parents (sliders)
#self.set_axis(pos, pos + width, self.plot_ymin, self.plot_ymax, "update_pos function")
#self.set_axis(pos, pos + width, self.plot_ymin, self.plot_ymax, "update_width function")
# TODO: This 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()
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()
# Auto-change the sliders/buttons when using plot tools 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()
print(self.ax.get_ylim())
#self.ax.callbacks.connect('ylim_changed', on_ylims_change)
data = self.dgilib_extra.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 verbose: print("dgilib_plot.update_plot: Expected 'data' containing gpio data. Got 'data' with interfaces but no gpio timestamp & value pairs.") return
plt.show() else:
#TODO: ln might have an update_callback and then it can listen to the data being updated instead of updating data here
automove = False
self.spos.set_val(pos + width)
""" 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()
"""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 ...) #plot_pins_method = self.plot_pins_method or "highlight"
# Here we do checks and stop drawing pins if something is unset
# 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.
# This should be in update_plot() f"Logging. Collected {len(data.power)} power samples and {len(data.gpio)} gpio samples.")
data.gpio.timestamps + extend_gpio * [data.power.timestamps[-1]]) data.gpio.get_select_in_value(pin) + extend_gpio * [data.gpio.values[-1][pin]]) else: raise ValueError(f"Unrecognized plot_pins_method: {self.plot_pins_method}")
"""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)
"""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. """
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`. """
"""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)
# Obsolete # Give it an index to continue from, so it does not go through all the data # def identify_hold_times(data, start_index, true_false, pin, correction_forward=0.00, shrink=0.00): # if len(data.gpio.timestamps) <= 1: return [] # We can't identify intervals with only one value
# hold_times = [] # start = data.gpio.timestamps[0] # end = data.gpio.timestamps[0] # in_hold = true_false # not_in_hold = not true_false # search = not true_false
# #interval_sizes = []
# #print("Start index: " + str(start_index))
# for i in range(start_index, len(data.gpio.timestamps)): # if search == not_in_hold: # Searching for start of hold time # if data.gpio.values[i][pin] == search: # start = data.gpio.timestamps[i] # else: # end = data.gpio.timestamps[i] # search = not search
# if search == in_hold: # Searching for end of hold time # if data.gpio.values[i][pin] == search: # end = data.gpio.timestamps[i] # else: # search = not search # to_add = (start + correction_forward + shrink, end + correction_forward - shrink) # if ((to_add[0] != to_add[1]) and (to_add[0] < to_add[1])): # hold_times.append(to_add) # #interval_sizes.append(to_add[1] - to_add[0]) # start = data.gpio.timestamps[i]
# # should_add_last_interval = True # # for ht in hold_times: # # if (ht[0] == start): should_add_last_interval = False
# # if should_add_last_interval:
# # invented_end_time = data.power.timestamps[-1] + correction_forward - shrink
# # # This function ASSUMES that the intervals are about the same in size. # # # ... If the last interval that should be highlighted on the graph is # # # ... abnormally higher than the maximum of the ones that already happened # # # ... correctly then cancel highlighting with the help of 'invented_end_time' # # # ... and highlight using the minimum from the 'interval_sizes' list, to get # # # ... an average that is most likely unaffected by stuff happening at the end # # # ... of the interval, which the power interface from the board failed to # # # ... communicate to us. # # # if ((invented_end_time - start) > max(interval_sizes)): # # # invented_end_time = start + min(interval_sizes)
# # to_add = (start + correction_forward + shrink, invented_end_time)
# # if ((to_add[0] != to_add[1]) and (to_add[0] < to_add[1])): # # hold_times.append(to_add)
# # A smart printing for debugging this function # # Either leave 'debug = False' or comment it, but don't lose it # debug = False # if debug: # ht_zip = list(zip(*hold_times)) # for (t, v) in data.gpio: # #print(str((t,v))) # if t in ht_zip[0]: # print("\t" + str(t) + "\t\t" + str(v) + "\t <-- start") # elif t in ht_zip[1]: # print("\t" + str(t) + "\t\t" + str(v) + "\t <-- stop") # else: # print("\t" + str(t) + "\t\t" + str(v))
# return hold_times
# Obsolete # def shift_data(data, shift_by): # new_data = copy.deepcopy(data)
# for i in range(len(data[INTERFACE_POWER][0])): # new_data[INTERFACE_POWER][0][i] = shift_by + data[INTERFACE_POWER][0][i]
# for i in range(len(data[INTERFACE_GPIO][0])): # new_data[INTERFACE_GPIO][0][i] = shift_by + data[INTERFACE_GPIO][0][i]
# return new_data |