|
@@ -0,0 +1,357 @@
|
|
|
+# !/usr/bin/env python
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+from __future__ import print_function, division
|
|
|
+
|
|
|
+import numpy as np
|
|
|
+import scipy.signal as sp
|
|
|
+import random
|
|
|
+
|
|
|
+import nixio as nix
|
|
|
+import matplotlib.pyplot as plt
|
|
|
+
|
|
|
+COLORS_BLUE_AND_RED = (
|
|
|
+ 'dodgerblue', 'red'
|
|
|
+)
|
|
|
+
|
|
|
+COLORS_BLUE_GRADIENT = (
|
|
|
+ "#034980", "#055DA1", "#1B70E0", "#3786ED", "#4A95F7",
|
|
|
+ "#0C3663", "#1B4775", "#205082", "#33608F", "#51779E",
|
|
|
+ "#23B0DB", "#29CDFF", "#57D8FF", "#8FE5FF"
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+class Plotter(object):
|
|
|
+ """
|
|
|
+ Plotter class for nix data arrays.
|
|
|
+ """
|
|
|
+
|
|
|
+ def __init__(self, width=800, height=600, dpi=90, lines=1, cols=1, facecolor="white",
|
|
|
+ defaultcolors=COLORS_BLUE_GRADIENT):
|
|
|
+ """
|
|
|
+
|
|
|
+
|
|
|
+ :param width: Width of the image in pixels
|
|
|
+ :param height: Height of the image in pixels
|
|
|
+ :param dpi: DPI of the image (default 90)
|
|
|
+ :param lines: Number of vertical subplots
|
|
|
+ :param cols: Number of horizontal subplots
|
|
|
+ :param facecolor: The background color of the plot
|
|
|
+ :param defaultcolors: Defaultcolors that are assigned to lines in each subplot.
|
|
|
+ """
|
|
|
+
|
|
|
+ self.__width = width
|
|
|
+ self.__height = height
|
|
|
+ self.__dpi = dpi
|
|
|
+ self.__lines = lines
|
|
|
+ self.__cols = cols
|
|
|
+ self.__facecolor = facecolor
|
|
|
+ self.__defaultcolors = defaultcolors
|
|
|
+
|
|
|
+ self.__subplot_data = tuple()
|
|
|
+ for i in range(self.subplot_count):
|
|
|
+ self.__subplot_data += ([], )
|
|
|
+
|
|
|
+ self.__last_figure = None
|
|
|
+
|
|
|
+ # properties
|
|
|
+
|
|
|
+ @property
|
|
|
+ def subplot_count(self):
|
|
|
+ return self.__cols * self.__lines
|
|
|
+
|
|
|
+ @property
|
|
|
+ def subplot_data(self):
|
|
|
+ return self.__subplot_data
|
|
|
+
|
|
|
+ @property
|
|
|
+ def defaultcolors(self):
|
|
|
+ return self.__defaultcolors
|
|
|
+
|
|
|
+ @property
|
|
|
+ def last_figure(self):
|
|
|
+ assert self.__last_figure is not None, "No figure available (method plot has to be called at least once)"
|
|
|
+ return self.__last_figure
|
|
|
+
|
|
|
+ # methods
|
|
|
+
|
|
|
+ def save(self, name):
|
|
|
+ """
|
|
|
+ Saves the last figure to the specified location.
|
|
|
+
|
|
|
+ :param name: The name of the figure file
|
|
|
+ """
|
|
|
+ self.last_figure.savefig(name)
|
|
|
+
|
|
|
+ def add(self, array, subplot=0, color=None, xlim=None, downsample=None, labels=None):
|
|
|
+ """
|
|
|
+ Add a new data array to the plot
|
|
|
+
|
|
|
+ :param array: The data array to plot
|
|
|
+ :param subplot: The index of the subplot where the array should be added (starting with 0)
|
|
|
+ :param color: The color of the array to plot (if None the next default colors will be assigned)
|
|
|
+ :param xlim: Start and end of the x-axis limits.
|
|
|
+ :param downsample: True if the array should be sampled down
|
|
|
+ :param labels: Data array with labels that should be added to each data point of the array to plot
|
|
|
+ """
|
|
|
+ color = self.__mk_color(color, subplot)
|
|
|
+ pdata = PlottingData(array, color, subplot, xlim, downsample, labels)
|
|
|
+ self.subplot_data[subplot].append(pdata)
|
|
|
+
|
|
|
+ def plot(self, width=None, height=None, dpi=None, lines=None, cols=None, facecolor=None):
|
|
|
+ """
|
|
|
+ Plots all data arrays added to the plotter.
|
|
|
+
|
|
|
+ :param width: Width of the image in pixels
|
|
|
+ :param height: Height of the image in pixels
|
|
|
+ :param dpi: DPI of the image (default 90)
|
|
|
+ :param lines: Number of vertical subplots
|
|
|
+ :param cols: Number of horizontal subplots
|
|
|
+ :param facecolor: The background color of the plot
|
|
|
+ """
|
|
|
+ # defaults
|
|
|
+ width = width or self.__width
|
|
|
+ height = height or self.__height
|
|
|
+ dpi = dpi or self.__dpi
|
|
|
+ lines = lines or self.__lines
|
|
|
+ cols = cols or self.__cols
|
|
|
+ facecolor = facecolor or self.__facecolor
|
|
|
+
|
|
|
+ # plot
|
|
|
+ figure, axis_all = plot_make_figure(width, height, dpi, cols, lines, facecolor)
|
|
|
+
|
|
|
+ for subplot, pdata_list in enumerate(self.subplot_data):
|
|
|
+ axis = axis_all[subplot]
|
|
|
+ pdata_list.sort()
|
|
|
+
|
|
|
+ event_like = Plotter.__count_event_like(pdata_list)
|
|
|
+ signal_like = Plotter.__count_signal_like(pdata_list)
|
|
|
+
|
|
|
+ for i, pdata in enumerate(pdata_list):
|
|
|
+ d1type = pdata.array.dimensions[0].dimension_type
|
|
|
+ shape = pdata.array.shape
|
|
|
+ nd = len(shape)
|
|
|
+
|
|
|
+ if nd == 1:
|
|
|
+ if d1type == nix.DimensionType.Set:
|
|
|
+ second_y = signal_like > 0
|
|
|
+ hint = (i + 1.0) / (event_like + 1.0) if event_like > 0 else None
|
|
|
+ plot_array_1d_set(pdata.array, axis, color=pdata.color, xlim=pdata.xlim, labels=pdata.labels,
|
|
|
+ second_y=second_y, hint=hint)
|
|
|
+ else:
|
|
|
+ plot_array_1d(pdata.array, axis, color=pdata.color, xlim=pdata.xlim,
|
|
|
+ downsample=pdata.downsample)
|
|
|
+ elif nd == 2:
|
|
|
+ if d1type == nix.DimensionType.Set:
|
|
|
+ plot_array_2d_set(pdata.array, axis, color=pdata.color, xlim=pdata.xlim,
|
|
|
+ downsample=pdata.downsample)
|
|
|
+ else:
|
|
|
+ plot_array_2d(pdata.array, axis, color=pdata.color, xlim=pdata.xlim,
|
|
|
+ downsample=pdata.downsample)
|
|
|
+ else:
|
|
|
+ raise Exception('Unsupported data')
|
|
|
+
|
|
|
+ axis.legend()
|
|
|
+
|
|
|
+ self.__last_figure = figure
|
|
|
+
|
|
|
+ # private methods
|
|
|
+
|
|
|
+ def __mk_color(self, color, subplot):
|
|
|
+ """
|
|
|
+ If color is None, select one from the defaults or create a random color.
|
|
|
+ """
|
|
|
+ if color is None:
|
|
|
+ color_count = len(self.defaultcolors)
|
|
|
+ count = len(self.subplot_data[subplot])
|
|
|
+ color = self.defaultcolors[count if count < color_count else color_count - 1]
|
|
|
+
|
|
|
+ if color == "random":
|
|
|
+ color = "#%02x%02x%02x" % (random.randint(50, 255), random.randint(50, 255), random.randint(50, 255))
|
|
|
+
|
|
|
+ return color
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def __count_signal_like(pdata_list):
|
|
|
+ sig_types = (nix.DimensionType.Range, nix.DimensionType.Sample)
|
|
|
+ count = 0
|
|
|
+
|
|
|
+ for pdata in pdata_list:
|
|
|
+ dims = pdata.array.dimensions
|
|
|
+ nd = len(dims)
|
|
|
+
|
|
|
+ if nd == 1 and dims[0].dimension_type in sig_types:
|
|
|
+ count += 1
|
|
|
+ elif nd == 2 and dims[0].dimension_type == nix.DimensionType.Set and dims[1].dimension_type in sig_types:
|
|
|
+ count += 1
|
|
|
+
|
|
|
+ return count
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def __count_image_like(pdata_list):
|
|
|
+ sig_types = (nix.DimensionType.Range, nix.DimensionType.Sample)
|
|
|
+ count = 0
|
|
|
+
|
|
|
+ for pdata in pdata_list:
|
|
|
+ dims = pdata.array.dimensions
|
|
|
+ nd = len(dims)
|
|
|
+
|
|
|
+ if nd == 2 and dims[0].dimension_type in sig_types and dims[1].dimension_type in sig_types:
|
|
|
+ count += 1
|
|
|
+
|
|
|
+ return count
|
|
|
+
|
|
|
+ @staticmethod
|
|
|
+ def __count_event_like(pdata_list):
|
|
|
+ count = 0
|
|
|
+
|
|
|
+ for pdata in pdata_list:
|
|
|
+ dims = pdata.array.dimensions
|
|
|
+ nd = len(dims)
|
|
|
+
|
|
|
+ if dims[0].dimension_type == nix.DimensionType.Set:
|
|
|
+ count += 1
|
|
|
+
|
|
|
+ return count
|
|
|
+
|
|
|
+
|
|
|
+class PlottingData(object):
|
|
|
+
|
|
|
+ def __init__(self, array, color, subplot=0, xlim=None, downsample=False, labels=None):
|
|
|
+ self.array = array
|
|
|
+ self.dimensions = array.dimensions
|
|
|
+ self.shape = array.shape
|
|
|
+ self.rank = len(array.shape)
|
|
|
+ self.color = color
|
|
|
+ self.subplot = subplot
|
|
|
+ self.xlim = xlim
|
|
|
+ self.downsample = downsample
|
|
|
+ self.labels = labels
|
|
|
+
|
|
|
+ def __cmp__(self, other):
|
|
|
+ weights = lambda dims: [(1 if d.dimension_type == nix.DimensionType.Sample else 0) for d in dims]
|
|
|
+ return cmp(weights(self.array.dimensions), weights(other.array.dimensions))
|
|
|
+
|
|
|
+ def __lt__(self, other):
|
|
|
+ return self.__cmp__(other) < 0
|
|
|
+
|
|
|
+
|
|
|
+def plot_make_figure(width, height, dpi, cols, lines, facecolor):
|
|
|
+ axis_all = []
|
|
|
+ figure = plt.figure(facecolor=facecolor, figsize=(width / dpi, height / dpi), dpi=90)
|
|
|
+ figure.subplots_adjust(wspace=0.3, hspace=0.3, left=0.1, right=0.9, bottom=0.05, top=0.95)
|
|
|
+
|
|
|
+ for subplot in range(cols * lines):
|
|
|
+
|
|
|
+ axis = figure.add_subplot(lines, cols, subplot+1)
|
|
|
+ axis.tick_params(direction='out')
|
|
|
+ axis.spines['top'].set_color('none')
|
|
|
+ axis.spines['right'].set_color('none')
|
|
|
+ axis.xaxis.set_ticks_position('bottom')
|
|
|
+ axis.yaxis.set_ticks_position('left')
|
|
|
+
|
|
|
+ axis_all.append(axis)
|
|
|
+
|
|
|
+ return figure, axis_all
|
|
|
+
|
|
|
+
|
|
|
+def plot_array_1d(array, axis, color=None, xlim=None, downsample=None, hint=None, labels=None):
|
|
|
+ dim = array.dimensions[0]
|
|
|
+
|
|
|
+ assert dim.dimension_type in (nix.DimensionType.Sample, nix.DimensionType.Range), "Unsupported data"
|
|
|
+
|
|
|
+ y = array[:]
|
|
|
+ if dim.dimension_type == nix.DimensionType.Sample:
|
|
|
+ x_start = dim.offset or 0
|
|
|
+ x = np.arange(0, array.shape[0]) * dim.sampling_interval + x_start
|
|
|
+ else:
|
|
|
+ x = np.array(dim.ticks)
|
|
|
+
|
|
|
+ if downsample is not None:
|
|
|
+ x = sp.decimate(x, downsample)
|
|
|
+ y = sp.decimate(y, downsample)
|
|
|
+ if xlim is not None:
|
|
|
+ y = y[(x >= xlim[0]) & (x <= xlim[1])]
|
|
|
+ x = x[(x >= xlim[0]) & (x <= xlim[1])]
|
|
|
+
|
|
|
+ axis.plot(x, y, color, label=array.name)
|
|
|
+ axis.set_xlabel('%s [%s]' % (dim.label, dim.unit))
|
|
|
+ axis.set_ylabel('%s [%s]' % (array.label, array.unit))
|
|
|
+ axis.set_xlim([np.min(x), np.max(x)])
|
|
|
+
|
|
|
+
|
|
|
+def plot_array_1d_set(array, axis, color=None, xlim=None, hint=None, labels=None, second_y=False):
|
|
|
+ dim = array.dimensions[0]
|
|
|
+
|
|
|
+ assert dim.dimension_type == nix.DimensionType.Set, "Unsupported data"
|
|
|
+
|
|
|
+ x = array[:]
|
|
|
+ z = np.ones_like(x) * 0.8 * (hint or 0.5) + 0.1
|
|
|
+ if second_y:
|
|
|
+ ax2 = axis.twinx()
|
|
|
+ ax2.set_ylim([0, 1])
|
|
|
+ ax2.scatter(x, z, 50, color, linewidths=2, label=array.name, marker="|")
|
|
|
+ ax2.set_yticks([])
|
|
|
+
|
|
|
+ if labels is not None:
|
|
|
+ for i, v in enumerate(labels[:]):
|
|
|
+ ax2.annotate(str(v), (x[i], z[i]))
|
|
|
+
|
|
|
+ else:
|
|
|
+ #x = array[xlim or Ellipsis]
|
|
|
+ axis.set_ylim([0, 1])
|
|
|
+ axis.scatter(x, z, 50, color, linewidths=2, label=array.name, marker="|")
|
|
|
+ axis.set_xlabel('%s [%s]' % (array.label, array.unit))
|
|
|
+ axis.set_ylabel(array.name)
|
|
|
+ axis.set_yticks([])
|
|
|
+
|
|
|
+ if labels is not None:
|
|
|
+ for i, v in enumerate(labels[:]):
|
|
|
+ axis.annotate(str(v), (x[i], z[i]))
|
|
|
+
|
|
|
+
|
|
|
+def plot_array_2d(array, axis, color=None, xlim=None, downsample=None, hint=None, labels=None):
|
|
|
+ d1 = array.dimensions[0]
|
|
|
+ d2 = array.dimensions[1]
|
|
|
+
|
|
|
+ d1_type = d1.dimension_type
|
|
|
+ d2_type = d2.dimension_type
|
|
|
+
|
|
|
+ assert d1_type == nix.DimensionType.Sample, "Unsupported data"
|
|
|
+ assert d2_type == nix.DimensionType.Sample, "Unsupported data"
|
|
|
+
|
|
|
+ z = array[:]
|
|
|
+ x_start = d1.offset or 0
|
|
|
+ y_start = d2.offset or 0
|
|
|
+ x_end = x_start + array.shape[0] * d1.sampling_interval
|
|
|
+ y_end = y_start + array.shape[1] * d2.sampling_interval
|
|
|
+
|
|
|
+ axis.imshow(z, origin='lower', extent=[x_start, x_end, y_start, y_end])
|
|
|
+ axis.set_xlabel('%s [%s]' % (d1.label, d1.unit))
|
|
|
+ axis.set_ylabel('%s [%s]' % (d2.label, d2.unit))
|
|
|
+ axis.set_title(array.name)
|
|
|
+ bar = plt.colorbar()
|
|
|
+ bar.label('%s [%s]' % (array.label, array.unit))
|
|
|
+
|
|
|
+
|
|
|
+def plot_array_2d_set(array, axis, color=None, xlim=None, downsample=None, hint=None, labels=None):
|
|
|
+ d1 = array.dimensions[0]
|
|
|
+ d2 = array.dimensions[1]
|
|
|
+
|
|
|
+ d1_type = d1.dimension_type
|
|
|
+ d2_type = d2.dimension_type
|
|
|
+
|
|
|
+ assert d1_type == nix.DimensionType.Set, "Unsupported data"
|
|
|
+ assert d2_type == nix.DimensionType.Sample, "Unsupported data"
|
|
|
+
|
|
|
+ x_start = d2.offset or 0
|
|
|
+ x_one = x_start + np.arange(0, array.shape[1]) * d2.sampling_interval
|
|
|
+ x = np.tile(x_one.reshape(array.shape[1], 1), array.shape[0])
|
|
|
+ y = array[:]
|
|
|
+ axis.plot(x, y.T, color=color)
|
|
|
+ axis.set_title(array.name)
|
|
|
+ axis.set_xlabel('%s [%s]' % (d2.label, d2.unit))
|
|
|
+ axis.set_ylabel('%s [%s]' % (array.label, array.unit))
|
|
|
+
|
|
|
+ if d1.labels is not None:
|
|
|
+ axis.legend(d1.labels)
|
|
|
+
|