Source code for crappy.blocks.drawing

# coding: utf-8

from datetime import timedelta
from time import time

from .block import Block
from .._global import OptionalModule

try:
  import matplotlib.pyplot as plt
  from matplotlib import cm
except (ModuleNotFoundError, ImportError):
  plt = OptionalModule("matplotlib")
  cm = OptionalModule("matplotlib")


# ======= Visual objects =========
# These classes represent all that can be drawn on the canvas
# Common arg:
#  drawing: The drawing itself. It is used to access its attributes when needed
# Common kwargs:
#   coord: The coordinates of the object on the Canvas
#
# Update method will be called frequently by the Drawing block, you can
# define here what it will do on each update
# It gives the argument "data" containing all the latest data received
# by the block


[docs]class Text: """A simple text line."""
[docs] def __init__(self, *_, **kwargs) -> None: """Sets the args. Args: *_: Contains the :class:`Drawing` object, unused. **kwargs: Contains the coordinates, the text and the label to be displayed. Also contains the type of drawing, but unused. """ self.coord = kwargs['coord'] self.text = kwargs['text'] self.label = kwargs['label'] self.txt = plt.text(self.coord[0], self.coord[1], self.text)
def update(self, data: dict) -> None: self.txt.set_text(self.text % data[self.label])
[docs]class Dot_text: """Like :class:`Text`, but with a colored dot to visualize a numerical value. """
[docs] def __init__(self, drawing, **kwargs) -> None: """Sets the args. Args: drawing: The :class:`Drawing` object. **kwargs: Contains the coordinates, the text and the label to be displayed. Also contains the type of drawing, but unused. Important: The value received in label must be a numeric value. It will be normalized on the ``crange`` of the block and the dot will change color from blue to red depending on this value. """ self.coord = kwargs['coord'] self.text = kwargs['text'] self.label = kwargs['label'] self.txt = plt.text(self.coord[0] + 40, self.coord[1] + 20, self.text, size=16) self.dot = plt.Circle(self.coord, 20) drawing.ax.add_artist(self.dot) low, high = drawing.crange self.amp = high-low self.low = low
def update(self, data: dict) -> None: self.txt.set_text(self.text % data[self.label]) self.dot.set_color(cm.coolwarm((data[self.label] - self.low) / self.amp))
[docs]class Time: """To print the time of the experiment. It will print the time since the `t0` of the block. """ def __init__(self, drawing, **kwargs) -> None: self.coord = kwargs['coord'] self.txt = plt.text(self.coord[0], self.coord[1], "00:00", size=38) self.block = drawing def update(self, *_) -> None: self.txt.set_text(str(timedelta(seconds=int(time()-self.block.t0))))
elements = {'text': Text, 'dot_text': Dot_text, 'time': Time} # ========== The block itself ==========
[docs]class Drawing(Block): """Block to make a visual representation of data."""
[docs] def __init__(self, image, draw: dict = None, crange: list = None, title: str = "Drawing", window_size: tuple = (7, 5), freq: float = 2, backend: str = "TkAgg") -> None: """Sets the args and initializes the parent block. Args: image: This image will be the background for the Canvas. draw (:obj:`dict`, optional): A :obj:`list` of :obj:`dict` defining what to draw. See below for more details. crange: title: window_size: freq: backend: Note: - ``draw`` keys: - ``type`` (:obj:`str`): Mandatory, the type of drawing to display. It can be either `'Text'`, `'Dot_text'` or `''Time`. - ``coord`` (:obj:`list`): Mandatory, a :obj:`list` containing the `x` and `y` coordinates where the drawing should be displayed. - ``text``: Mandatory for :class:`Text` and :class:`Dot_text` only, the left part of the displayed string. - ``label`` (:obj:`str`): Mandatory for :class:`Text` and :class:`Dot_text` only, the label of the data to display. It will be append to the ``text``. """ Block.__init__(self) if draw is None: draw = [] self.freq = freq self.image = image self.draw = draw self.crange = [20, 300] if crange is None else crange self.title = title self.window_size = window_size self.backend = backend
[docs] def prepare(self) -> None: plt.switch_backend(self.backend) self.fig, self.ax = plt.subplots(figsize=self.window_size) image = self.ax.imshow(plt.imread(self.image), cmap=cm.coolwarm) image.set_clim(-0.5, 1) cbar = self.fig.colorbar(image, ticks=[-0.5, 1], fraction=0.061, orientation='horizontal', pad=0.04) cbar.set_label('Temperatures(C)') cbar.ax.set_xticklabels(self.crange) self.ax.set_title(self.title) self.ax.set_axis_off() self.elements = [] for d in self.draw: self.elements.append(elements[d['type']](self, **d))
def loop(self) -> None: data = self.get_last() for elt in self.elements: elt.update(data) self.fig.canvas.draw() plt.pause(0.001)
[docs] def finish(self) -> None: plt.close()