| """ |
| Copyright (c) 2019, OptoFidelity OY |
| |
| Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: |
| |
| 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. |
| 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. |
| 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the OptoFidelity OY. |
| 4. Neither the name of the OptoFidelity OY nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY |
| EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY |
| DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| """ |
| # -*- coding: utf-8 -*- |
| from .measurementdb import * |
| import numpy as np |
| import matplotlib |
| matplotlib.use('Agg') |
| import matplotlib.pyplot as plt |
| import matplotlib.gridspec as gridspec |
| from matplotlib.patches import Ellipse, Rectangle |
| import TPPTAnalysisSW.measurementdb as measurementdb |
| from matplotlib import cm |
| from mpl_toolkits.mplot3d import Axes3D |
| |
| import TPPTAnalysisSW.plotters as plotters |
| from hashlib import md5 |
| import random |
| import TPPTAnalysisSW.analyzers as analyzers |
| from .settings import settings |
| from threading import Semaphore |
| from datetime import datetime |
| from TPPTAnalysisSW.utils import Timer |
| |
| # Used for mutual exclusion. Only one thread can use matplotlib as a time |
| plotSemaphore = Semaphore() |
| |
| # Usage of matplotlib figure numbers |
| # 1 - Preview image (800 x 600) |
| # 2 - Detailed image (2000 x 2000) |
| # 3 - Large details image (800 x 800) |
| # 4 - Wide details image (1000 x 600) |
| # 5 - Narrow details image (600 x 600) |
| # 6 - Large details image (1000 x 800) |
| |
| # Color constants |
| _PASS = '#00FF00' |
| _FAIL = '#FF0000' |
| _DEFAULT = 'r' |
| _STATIONARY_EDGE_PINCH = '#DFDF51' |
| |
| # Scatter plot marker size (in points) |
| _markersize = 40 |
| |
| # Z-orders for different elements |
| _zorder = {'pass': 3, |
| 'fail': 4, |
| 'edges': 1, # Panel borders |
| 'lines': 2, |
| } |
| |
| # Decorator for easy locking |
| def synchronized(f): |
| global plotSemaphore |
| def locker(*args, **kwargs): |
| with plotSemaphore: |
| return f(*args, **kwargs) |
| return locker |
| |
| def waitForPlot(): |
| """ Waits until there is no plot running. Does not hold the lock, so the next drawing may commence after waitForPlot() returns""" |
| plotSemaphore.acquire() |
| plotSemaphore.release() |
| |
| # Template for new image plotting functions: |
| # |
| #@synchronized |
| #def plot_xxx(imagepath, *args, **kwargs): |
| # fig = plt.figure(1) |
| # plt.clf() |
| |
| # drawing_code_here... |
| |
| # # Create the image |
| # fig.savefig(imagepath) |
| |
| @synchronized |
| def plot_dummy_image(imagepath, plotinfo, *args, **kwargs): |
| """ Plots a dummy image for dummy test """ |
| |
| t = Timer(3) |
| |
| fig = plt.figure(1) |
| plt.clf() |
| |
| #plt.axis([-5.0, 5.0, 5.0, -5.0]) |
| x = [p[0] for p in plotinfo['points']] |
| y = [p[1] for p in plotinfo['points']] |
| plt.scatter(x, y, s=_markersize, c=_DEFAULT) |
| t.Time("Plots") |
| |
| # Here we can use parameters |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| plt.xlabel('X [mm]') |
| plt.ylabel('Y [mm]') |
| t.Time("Labels") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| t.Time("Ready") |
| |
| @synchronized |
| def plot_passfail_on_target(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots passed and failed points on target. |
| plotinfo argument must be a dictionary containing lists 'passed_points' and |
| 'failed_points' of points (x,y) on target coordinates """ |
| # Used by: |
| |
| s = Timer(3) |
| fig = plt.figure(1) |
| plt.clf() |
| |
| # Plot panel borders |
| plt.axis('equal') |
| plotters.plot_panel_borders(dutinfo.dimensions[0], dutinfo.dimensions[1],zorder=_zorder['edges']) |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, dutinfo.dimensions[1] + 5.0, -5.0]) |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Passed points |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'], edgecolors='k') |
| # Failed points |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'], edgecolors='k') |
| |
| s.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| s.Time("Save") |
| |
| @synchronized |
| def plot_passfail_labels_on_target(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots a graph with labelled points. Plotinfo contains 'passed_points' and |
| 'failed_points' arrays of tuples (x, y, s) where s is the label for a point """ |
| # Used by: first contact latency, stationary_reporting_rate, repeatability, stationary jitter |
| t = Timer(3) |
| if len(args) > 0 and args[0] == 'detailed': |
| fig = plt.figure(num=2, dpi=100, figsize=(20,20)) |
| else: |
| fig = plt.figure(num=1, dpi=100, figsize=(8,6)) |
| plt.clf() |
| |
| # Plot panel borders |
| plt.axis('equal') |
| plotters.plot_panel_borders(dutinfo.dimensions[0], dutinfo.dimensions[1],zorder=_zorder['edges']) |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, dutinfo.dimensions[1] + 5.0, -5.0]) |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Passed points |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| s = [str(p[2]) for p in plotinfo['passed_points']] |
| plt.scatter(x, y, s=50, c=_PASS, zorder=_zorder['pass']) |
| for (x, y, s) in zip(x, y, s): |
| plt.annotate(s, (x,y), xytext=(5, 5), textcoords='offset points') |
| # Failed points |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| s = [str(p[2]) for p in plotinfo['failed_points']] |
| plt.scatter(x, y, s=50, c=_FAIL, zorder=_zorder['fail']) |
| for (x, y, s) in zip(x, y, s): |
| plt.annotate(s, (x,y), xytext=(5, 5), textcoords='offset points') |
| |
| t.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| t.Time("Save") |
| |
| |
| @synchronized |
| def plot_tapping_passfail_labels_on_target(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots a graph with labelled points. Plotinfo contains 'passed_points' and |
| 'failed_points' arrays of tuples (x, y, s) where s is the label for a point """ |
| # Used by: tapping repeatability |
| t = Timer(3) |
| if len(args) > 0 and args[0] == 'detailed': |
| fig = plt.figure(num=2, dpi=100, figsize=(20, 20)) |
| else: |
| fig = plt.figure(num=1, dpi=100, figsize=(8, 6)) |
| plt.clf() |
| |
| # Plot panel borders |
| plt.axis('equal') |
| plotters.plot_panel_borders(dutinfo.dimensions[0], dutinfo.dimensions[1], zorder=_zorder['edges']) |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, dutinfo.dimensions[1] + 5.0, -5.0]) |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Plot edge area separator line |
| border_width = float(plotinfo['border_width']) |
| plt.plot([dutinfo.dimensions[0], 0], [border_width, border_width], color='blue', |
| linestyle='dashed') |
| plt.plot([border_width, border_width], [0, dutinfo.dimensions[1]], color='blue', |
| linestyle='dashed') |
| plt.plot([0, dutinfo.dimensions[0]], |
| [dutinfo.dimensions[1] - border_width, dutinfo.dimensions[1] - border_width], color='blue', |
| linestyle='dashed') |
| plt.plot([dutinfo.dimensions[0] - border_width, dutinfo.dimensions[0] - border_width], |
| [0, dutinfo.dimensions[1]], color='blue', linestyle='dashed') |
| |
| # Passed points |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| s = [str(p[2]) for p in plotinfo['passed_points']] |
| plt.scatter(x, y, s=50, c=_PASS, zorder=_zorder['pass']) |
| for (x, y, s) in zip(x, y, s): |
| plt.annotate(s, (x, y), xytext=(5, 5), textcoords='offset points') |
| # Failed points |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| s = [str(p[2]) for p in plotinfo['failed_points']] |
| plt.scatter(x, y, s=50, c=_FAIL, zorder=_zorder['fail']) |
| for (x, y, s) in zip(x, y, s): |
| plt.annotate(s, (x, y), xytext=(5, 5), textcoords='offset points') |
| |
| t.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| t.Time("Save") |
| |
| |
| @synchronized |
| def plot_passfail_labels(imagepath, plotinfo, *args, **kwargs): |
| """ Plots a graph with labelled points. Plotinfo contains 'passed_points' and |
| 'failed_points' arrays of tuples (x, y, s) where s is the label for a point. |
| An optional 'center' point in plotinfo gives the center of the graph """ |
| # Used by: stationary_jitter |
| t = Timer(3) |
| fig = plt.figure(1) |
| plt.clf() |
| |
| # Plot panel borders |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Passed points |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| s = [str(p[2]) for p in plotinfo['passed_points']] |
| plt.scatter(x, y, s=50, c=_PASS, zorder=_zorder['pass']) |
| for (x, y, s) in zip(x, y, s): |
| plt.annotate(s, (x,y), xytext=(5, 5), textcoords='offset points') |
| # Failed points |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| s = [str(p[2]) for p in plotinfo['failed_points']] |
| plt.scatter(x, y, s=50, c=_FAIL, zorder=_zorder['fail']) |
| for (x, y, s) in zip(x, y, s): |
| plt.annotate(s, (x,y), xytext=(5, 5), textcoords='offset points') |
| |
| robot_x = plotinfo['robot_point'][0].robot_x |
| robot_y = plotinfo['robot_point'][0].robot_y |
| robot_point = [(robot_x, robot_y),] |
| |
| plt.scatter(robot_x, robot_y, s=50, c="k") |
| plt.annotate("Reference", (robot_x, robot_y), xytext=(30, 10), textcoords='offset points', color='k', |
| arrowprops=dict(arrowstyle="simple", fc="k", ec="none", |
| connectionstyle="arc3,rad=0.3")) |
| # Set axis |
| passed_range = plotters.get_range(plotinfo['passed_points'] + plotinfo['failed_points'] + robot_point) |
| if 'center' in plotinfo: |
| center = plotinfo['center'] |
| x_width = max(center[0] - passed_range[0], passed_range[2] - center[0]) |
| y_width = max(center[1] - passed_range[1], passed_range[3] - center[1]) |
| plt.axis([center[0] - x_width - 0.5, center[0] + x_width + 0.5, center[1] + y_width + 0.5, center[1] - y_width - 0.5]) |
| elif passed_range is None: |
| # Empty lists |
| plt.axis([-5.0, 5.0, 5.0, -5.0]) |
| else: |
| # This might result in non-equal axis... |
| plt.axis([passed_range[0] - 0.5, passed_range[2] + 0.5, passed_range[3] + 0.5, passed_range[1] - 0.5]) |
| |
| t.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| t.Time("Save") |
| |
| @synchronized |
| def plot_repeatability_details(imagepath, plotinfo, *args, **kwargs): |
| """ Plots repeatability diagram: a diagram of points and a circle with center at the |
| average value of point coordinates. Points are in arrays 'passed_points', 'failed_points', |
| 'average_point' and 'robot_point' (the coordinates to which the robot has pressed) """ |
| # Used by: repeatability |
| |
| t = Timer(3) |
| t.Time("START") |
| |
| fig = plt.figure(5, figsize=(6,6), dpi=100) |
| plt.clf() |
| |
| plt.axis('equal') |
| plt.xlabel('X [mm]') |
| plt.ylabel('Y [mm]') |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Plot individual measurements |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| s = [20 if n == 1 else 50 for n in plotinfo['passed_points_count']] |
| plt.scatter(x, y, s=s, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| for (x, y, s) in zip(x, y, plotinfo['passed_points_count']): |
| if s > 1: |
| plt.annotate(str(s), (x,y), xytext=(5, 5), textcoords='offset points') |
| |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| s = [20 if n == 1 else 50 for n in plotinfo['failed_points_count']] |
| plt.scatter(x, y, s=s, c=_FAIL, zorder=_zorder['fail']) |
| for (x, y, s) in zip(x, y, plotinfo['failed_points_count']): |
| if s > 1: |
| plt.annotate(str(s), (x,y), xytext=(5, 5), textcoords='offset points') |
| |
| # Plot robot point |
| robot_x = plotinfo['robot_point'][0] |
| robot_y = plotinfo['robot_point'][1] |
| plt.scatter(robot_x, robot_y, s=50, c="k") |
| plt.annotate("Reference", (robot_x,robot_y), xytext=(30, 10), textcoords='offset points', color='k', |
| arrowprops=dict(arrowstyle="simple", fc="k", ec="none", |
| connectionstyle="arc3,rad=0.3")) |
| |
| # Plot error square |
| if 'reference_point' in plotinfo: |
| rx, ry = plotinfo['reference_point'] |
| d = float(plotinfo['distance']) |
| # Draw a square |
| x = [rx, rx + d, rx + d, rx, rx] |
| y = [ry, ry, ry + d, ry + d, ry] |
| plt.plot(x, y, "k") |
| |
| # Reverse y scale |
| ax = plt.gca() |
| ax.set_ylim(ax.get_ylim()[::-1]) |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| t.Time("END") |
| |
| |
| @synchronized |
| def plot_tapping_repeatability_details(imagepath, plotinfo, *args, **kwargs): |
| """ Plots tapping repeatability diagram: a diagram of points and a circle with center at the |
| average value of point coordinates. Points are in arrays 'passed_points', 'failed_points', |
| 'average_point' and 'robot_point' (the coordinates to which the robot has pressed) """ |
| # Used by: tapping repeatability |
| |
| t = Timer(3) |
| t.Time("START") |
| |
| fig = plt.figure(5, figsize=(6, 6), dpi=100) |
| plt.clf() |
| |
| plt.axis('equal') |
| plt.xlabel('X [mm]') |
| plt.ylabel('Y [mm]') |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Plot individual measurements |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| s = [20 if n == 1 else 50 for n in plotinfo['passed_points_count']] |
| plt.scatter(x, y, s=s, c=_PASS, zorder=_zorder['pass'], edgecolors='k') |
| for (x, y, s) in zip(x, y, plotinfo['passed_points_count']): |
| if s > 1: |
| plt.annotate(str(s), (x, y), xytext=(5, 5), textcoords='offset points') |
| |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| s = [20 if n == 1 else 50 for n in plotinfo['failed_points_count']] |
| plt.scatter(x, y, s=s, c=_FAIL, zorder=_zorder['fail']) |
| for (x, y, s) in zip(x, y, plotinfo['failed_points_count']): |
| if s > 1: |
| plt.annotate(str(s), (x, y), xytext=(5, 5), textcoords='offset points') |
| |
| # Plot reference point |
| reference_x = plotinfo['reference_point'][0] |
| reference_y = plotinfo['reference_point'][1] |
| plt.scatter(reference_x, reference_y, s=50, c="k") |
| plt.annotate("Reference point", (reference_x, reference_y), xytext=(30, 10), textcoords='offset points', color='k', |
| arrowprops=dict(arrowstyle="simple", fc="k", ec="none", |
| connectionstyle="arc3,rad=0.3")) |
| |
| # Plot max error circle |
| d = float(plotinfo['error_radius']) |
| circle = plt.Circle((reference_x, reference_y), d, fill=False, linestyle='--', color='r') |
| |
| ax = plt.gca() |
| # Reverse y scale |
| ax.set_ylim(ax.get_ylim()[::-1]) |
| ax.add_artist(circle) |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| t.Time("END") |
| |
| |
| @synchronized |
| def plot_swipes_on_target_with_labels(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots swipe diagram - 'target_points' of points (x,y) on target coordinates and |
| 'lines' that are tuple of (x,y) coordinates giving swipe start and end coordinates. |
| alternatively, 'target_points' can be replaced by 'passed_points' and 'failed_points' """ |
| # Used by: hover, one finger swipe, non-stationary reporting rate |
| t = Timer(3) |
| |
| if len(args) > 0 and args[0] == 'detailed': |
| fig = plt.figure(num=2, dpi=100, figsize=(20,20)) |
| else: |
| fig = plt.figure(num=1, dpi=100, figsize=(8,6)) |
| plt.clf() |
| |
| # Plot panel borders |
| plt.axis('equal') |
| plotters.plot_panel_borders(dutinfo.dimensions[0], dutinfo.dimensions[1],zorder=_zorder['edges']) |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, dutinfo.dimensions[1] + 5.0, -5.0]) |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Plot arrows in the image |
| for line_id, line in zip(plotinfo['swipe_ids'], plotinfo['lines']): |
| plt.arrow(line[0][0], line[0][1], |
| line[1][0] - line[0][0], |
| line[1][1] - line[0][1], |
| width=0.1, length_includes_head=True) |
| plt.annotate("Line ID " + str(line_id), (line[0][0], line[0][1]), xytext=(25, 5), textcoords='offset points', color='k', |
| arrowprops=dict(arrowstyle="simple", fc="k", ec="none", |
| connectionstyle="arc3,rad=0.3")) |
| |
| t.Time("Arrows") |
| |
| # points |
| if 'target_points' in plotinfo: |
| x = [p[0] for p in plotinfo['target_points']] |
| y = [p[1] for p in plotinfo['target_points']] |
| plt.scatter(x, y, s=_markersize, c=_DEFAULT) |
| else: |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'],edgecolors='k') |
| |
| t.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| t.Time("Save") |
| |
| @synchronized |
| def plot_swipes_on_target(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots swipe diagram - 'target_points' of points (x,y) on target coordinates and |
| 'lines' that are tuple of (x,y) coordinates giving swipe start and end coordinates. |
| alternatively, 'target_points' can be replaced by 'passed_points' and 'failed_points' """ |
| # Used by: hover, one finger swipe, non-stationary reporting rate |
| t = Timer(3) |
| |
| if len(args) > 0 and args[0] == 'detailed': |
| fig = plt.figure(num=2, dpi=100, figsize=(20,20)) |
| else: |
| fig = plt.figure(num=1, dpi=100, figsize=(8,6)) |
| plt.clf() |
| |
| # Plot panel borders |
| plt.axis('equal') |
| plotters.plot_panel_borders(dutinfo.dimensions[0], dutinfo.dimensions[1],zorder=_zorder['edges']) |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, dutinfo.dimensions[1] + 5.0, -5.0]) |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Plot arrows in the image |
| for line in plotinfo['lines']: |
| plt.arrow(line[0][0], line[0][1], |
| line[1][0] - line[0][0], |
| line[1][1] - line[0][1], |
| width=0.1, length_includes_head=True) |
| |
| t.Time("Arrows") |
| |
| # points |
| if 'target_points' in plotinfo: |
| x = [p[0] for p in plotinfo['target_points']] |
| y = [p[1] for p in plotinfo['target_points']] |
| plt.scatter(x, y, s=_markersize, c=_DEFAULT) |
| else: |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'],edgecolors='k') |
| |
| t.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| t.Time("Save") |
| |
| @synchronized |
| def plot_jitter_diagram(imagepath, plotinfo, dutinfo, **kwargs): |
| """ Plots jitter diagram - two subplots with target and swipe information |
| plotinfo argument must be a dictionary containing lists 'target_points' |
| and 'swipe_points' of points (x,y) on target coordinates """ |
| # Used by: hover, one finger swipe |
| |
| s = Timer(3) |
| fig = plt.figure(num=4, dpi=100, figsize=(10,6)) |
| plt.clf() |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Plot panel borders |
| plt.subplot(121) |
| plot_one_finger_swipe_on_target(plotinfo, dutinfo) |
| |
| # Swipe |
| plt.subplot(122) |
| plot_one_finger_swipe_offset_jitter(plotinfo) |
| |
| s.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath, bbox_inches="tight") |
| plt.close('all') |
| s.Time("Save") |
| |
| @synchronized |
| def plot_linearity_details(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ |
| Plots linearity subplots for single line. |
| |
| Requires: |
| swipe_points (tuples of (traveled_distance, offset)) |
| linear_error (linear errors from least squares fit to reported coordinates) |
| offset_error (offset errors from least squares fit to robot line) |
| |
| """ |
| |
| s = Timer(3) |
| fig = plt.figure(num=4, dpi=100, figsize=(12, 8)) |
| plt.clf() |
| |
| x = [p[0] for p in plotinfo['swipe_points']] |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| gs = gridspec.GridSpec(3, 1, height_ratios=[1, 1, 2]) |
| |
| # Plot panel borders |
| ax = plt.subplot(gs[0]) |
| plt.xlabel('Distance traveled [mm]') |
| plt.ylabel('Linearity error [mm]') |
| |
| plt.plot(x, plotinfo['linearity']['linear_error'], color='b') |
| plt.vlines(x, 0.0, plotinfo['linearity']['linear_error'], alpha=0.5) |
| |
| # Swipe |
| plt.subplot(gs[1]) |
| plt.xlabel('Distance traveled [mm]') |
| plt.ylabel('Offset error [mm]') |
| |
| plt.plot(x, plotinfo['linearity']['offset_error'], color='b') |
| plt.vlines(x, 0.0, plotinfo['linearity']['offset_error'], alpha=0.5) |
| |
| plt.subplot(gs[2]) |
| plt.axis('equal') |
| plt.xlabel('X [mm]') |
| plt.ylabel('Y [mm]') |
| |
| plotters.plot_panel_borders(dutinfo.dimensions[0], |
| dutinfo.dimensions[1], |
| zorder=_zorder['edges']) |
| |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, |
| dutinfo.dimensions[1] + 5.0, -5.0]) |
| |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'], edgecolors='black') |
| |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'], edgecolors='black') |
| |
| s.Time("Plots") |
| |
| plt.tight_layout(h_pad=0.3) |
| plt.subplots_adjust(top=0.95) |
| |
| # Create the image |
| fig.savefig(imagepath, bbox_inches="tight") |
| plt.close('all') |
| s.Time("Save") |
| |
| @synchronized |
| def plot_linearity_detailed_histograms(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| |
| if len(args) > 0 and args[0] == 'detailed': |
| fig = plt.figure(num=2, dpi=100, figsize=(15, 13)) |
| else: |
| fig = plt.figure(num=1, dpi=100, figsize=(14, 8)) |
| |
| if 'title' in kwargs: |
| fig.suptitle(kwargs['title']) |
| |
| prefix = '' |
| if 'exclude_edges' in kwargs: |
| if kwargs['exclude_edges']: |
| prefix = 'center_' |
| |
| |
| columns = ( |
| ('horizontal', 'Horizontal'), |
| ('vertical', 'Vertical'), |
| ('diagonal', 'Diagonal'), |
| ('all', 'Overall')) |
| |
| for column, (column_id, column_name) in enumerate(columns): |
| |
| ax = fig.add_subplot(3, 4, column + 1) |
| |
| hist, bins = np.histogram(plotinfo[column_id][prefix + 'linearity_errors'], bins='auto') |
| width = 0.7 * (bins[1] - bins[0]) |
| center = (bins[:-1] + bins[1:]) / 2 |
| |
| ax.bar(center, hist, align='center', width=width) |
| ax.set_ylabel('Count') |
| ax.set_xlabel('{} Linearity [mm]'.format(column_name)) |
| skip = int(math.ceil(len(center) / 8.0)) |
| ax.set_xticks(center[::skip]) |
| plt.setp(ax.get_xticklabels(), rotation=30, horizontalalignment='right') |
| |
| ax = fig.add_subplot(3, 4, column + 5) |
| |
| hist, bins = np.histogram(plotinfo[column_id][prefix + 'jitters'], bins='auto') |
| width = 0.7 * (bins[1] - bins[0]) |
| center = (bins[:-1] + bins[1:]) / 2 |
| |
| ax.bar(center, hist, align='center', width=width) |
| ax.set_ylabel('Count') |
| ax.set_xlabel('{} Jitter [mm]'.format(column_name)) |
| skip = int(math.ceil(len(center) / 8.0)) |
| ax.set_xticks(center[::skip]) |
| plt.setp(ax.get_xticklabels(), rotation=30, horizontalalignment='right') |
| |
| ax = fig.add_subplot(3, 4, column + 9) |
| |
| hist, bins = np.histogram(plotinfo[column_id][prefix + 'offset_errors'], bins='auto') |
| width = 0.7 * (bins[1] - bins[0]) |
| center = (bins[:-1] + bins[1:]) / 2 |
| |
| ax.bar(center, hist, align='center', width=width) |
| ax.set_ylabel('Count') |
| ax.set_xlabel('{} Offset [mm]'.format(column_name)) |
| skip = int(math.ceil(len(center) / 8.0)) |
| ax.set_xticks(center[::skip]) |
| plt.xticks() |
| plt.setp(ax.get_xticklabels(), rotation=30, horizontalalignment='right') |
| |
| fig.tight_layout() |
| fig.subplots_adjust(top=0.9) |
| fig.savefig(imagepath) |
| plt.close(fig) |
| |
| def plot_one_finger_swipe_on_target(plotinfo, dutinfo): |
| """ Plots one swipe on panel. See plot_jitter_diagram() for parameters. |
| This is used in normal one finger swipe and in angle swipe. |
| """ |
| |
| plt.axis('equal') |
| plt.xlabel('X [mm]') |
| plt.ylabel('Y [mm]') |
| |
| plotters.plot_panel_borders(dutinfo.dimensions[0], |
| dutinfo.dimensions[1], |
| zorder=_zorder['edges']) |
| |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, |
| dutinfo.dimensions[1] + 5.0, -5.0]) |
| |
| # Target |
| if 'target_points' in plotinfo: |
| x = [p[0] for p in plotinfo['target_points']] |
| y = [p[1] for p in plotinfo['target_points']] |
| plt.scatter(x, y, s=_markersize, c=_DEFAULT) |
| |
| else: |
| x = [p[0] for p in plotinfo['passed_points']] |
| y = [p[1] for p in plotinfo['passed_points']] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| |
| x = [p[0] for p in plotinfo['failed_points']] |
| y = [p[1] for p in plotinfo['failed_points']] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'],edgecolors='k') |
| |
| if 'line_start' in plotinfo: |
| plt.arrow(plotinfo['line_start'][0], plotinfo['line_start'][1], |
| plotinfo['line_end'][0] - plotinfo['line_start'][0], |
| plotinfo['line_end'][1] - plotinfo['line_start'][1], |
| width=0.1, length_includes_head=True) |
| |
| |
| def plot_one_finger_swipe_offset_jitter(plotinfo): |
| """ Plots single swipe offset/jitter graph. |
| See plot_jitter_diagram() for parameters. |
| This is used in normal one finger swipe and in angle swipe. |
| """ |
| |
| plt.xlabel('distance traveled [mm]') |
| |
| x = [p[0] for p in plotinfo['swipe_points']] |
| y = [p[1] for p in plotinfo['swipe_points']] |
| |
| #plt.plot(x, y, "o-", color='k', mec='k', mfc='r') |
| plt.plot(x, y, color='b') |
| plt.vlines(x, 0.0, y, alpha=0.5) |
| plt.plot(x, plotinfo['jitters'], color=_DEFAULT) |
| plt.legend(("offset", "jitter"), |
| bbox_to_anchor=(1.05, 1), |
| loc=2, |
| borderaxespad=0) |
| |
| @synchronized |
| def plot_one_finger_swipe_with_linear_fit(imagepath, plotinfo, dutinfo, **kwargs): |
| """ Plots linear fit diagram - two subplots with target and swipe information |
| plotinfo argument must be a dictionary containing lists 'target_points' |
| and 'swipe_points' of points (x,y) on target coordinates """ |
| # Used by: one finger swipe |
| |
| s = Timer(3) |
| fig = plt.figure(num=1, dpi=100, figsize=(10,11)) |
| plt.clf() |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Plot points on regression line |
| plt.subplot2grid((4,2), (0,0), colspan=2, rowspan=1).set_title('Points on regression line') |
| plot_one_finger_swipe_with_linear_fit_on_target(plotinfo) |
| |
| # Deviation from linear fit |
| plt.subplot2grid((4,2), (1,0), colspan=2, rowspan=1).set_title('Linear fit error calculated from regression line') |
| plot_one_finger_deviation_from_linear_fit(plotinfo) |
| |
| # Plot panel borders |
| plt.subplot2grid((4,2), (2,0), colspan=1, rowspan=2).set_title('Points on robot drawn line') |
| plot_one_finger_swipe_on_target(plotinfo, dutinfo) |
| |
| # Swipe |
| plt.subplot2grid((4,2), (2,1), colspan=1, rowspan=2).set_title('Jitter with a sliding window') |
| plot_one_finger_swipe_offset_jitter(plotinfo) |
| |
| fig.subplots_adjust(hspace=0.7) |
| |
| s.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath, bbox_inches="tight") |
| plt.close('all') |
| s.Time("Save") |
| |
| def plot_one_finger_swipe_with_linear_fit_on_target(plotinfo): |
| """ Plots one swipe on panel. See plot_jitter_diagram() for parameters. |
| This is used in normal one finger swipe and in angle swipe. |
| """ |
| |
| plt.xlabel('X [mm]') |
| plt.ylabel('Y [mm]') |
| |
| x = [p[0] for p in plotinfo['swipe_points']] |
| y = [p[1] for p in plotinfo['swipe_points']] |
| |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| |
| if len(plotinfo['swipe_points']) > 0: |
| plt.axis([-0.2 + min(x), max(x) + 0.2, |
| max(y) + 0.2, min(y) - 0.2]) |
| fit = np.polyfit(x, y, 1) |
| fit_fn = np.poly1d(fit) |
| plt.plot(x, fit_fn(x), 'r', zorder=10) |
| else: |
| plt.plot([], [], 'r', zorder=10) |
| |
| def plot_one_finger_deviation_from_linear_fit(plotinfo): |
| """ Plots single swipe deviation from linear fit graph. |
| This is used in one finger swipe and in angle swipe. |
| """ |
| |
| plt.xlabel('distance traveled [mm]') |
| |
| x = [p[0] for p in plotinfo['swipe_points']] |
| y = plotinfo['linear_error'] |
| |
| if len(plotinfo['swipe_points']) > 0: |
| plt.axis([-5.0 + min(x), max(x) + 5.0, |
| 0.0, max(y) + max(y) / 4]) |
| |
| linear_error, = plt.plot(x, y, color=_DEFAULT) |
| |
| # Create a blank rectangle for adding additional info to legend |
| blank = Rectangle((0, 0), 1, 1, fc="w", fill=False, edgecolor='none', linewidth=0) |
| max_err = round(plotinfo['lin_error_max'], 2) |
| avg_err = round(plotinfo['lin_error_avg'], 2) |
| rms_err = round(plotinfo['lin_error_rms'], 2) |
| |
| plt.legend([linear_error, blank, blank, blank], |
| ("Linear fit error", "Max: %s" % max_err, "Avg: %s" % avg_err, "Rms: %s" % rms_err), |
| bbox_to_anchor=(1.02, 1), |
| loc=2, |
| borderaxespad=0, |
| fontsize=11) |
| |
| @synchronized |
| def plot_reporting_rate(imagepath, plotinfo, **kwargs): |
| """ Plots reporting rate diagram. plotinfo argument must be a dictionary containing |
| 'delays': list of delays, and 'passed' & 'failed' of tuples (0-based index, delay). |
| If there is max_delay in plotinfo, it is used to scale the axis """ |
| # Used by: non-stationary reporting rate, stationary reporting rate |
| |
| s = Timer(3) |
| fig = plt.figure(num=4, dpi=100, figsize=(10,6)) |
| plt.clf() |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Delays |
| x = range(1, len(plotinfo['delays']) + 1) |
| plt.plot(x,plotinfo['delays'], "-", color='k') |
| |
| x = [(p[0]+1) for p in plotinfo['passed']] |
| y = [p[1] for p in plotinfo['passed']] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| x = [(p[0]+1) for p in plotinfo['failed']] |
| y = [p[1] for p in plotinfo['failed']] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'],edgecolors='k') |
| |
| plt.xlim(0, len(plotinfo['delays']) + 2) |
| if 'max_delay' in plotinfo and plotinfo['max_delay'] is not None: |
| plt.ylim(0, plotinfo['max_delay'] * 1.1) |
| |
| if 'max_allowed_delay' in plotinfo: |
| plt.plot([0, len(plotinfo['delays']) + 2], [plotinfo['max_allowed_delay']] * 2, c=_FAIL) |
| |
| plt.ylabel("Delay [ms]") |
| plt.xlabel("Point index") |
| |
| s.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath, bbox_inches="tight") |
| plt.close('all') |
| s.Time("Save") |
| |
| @synchronized |
| def plot_taptest_on_target(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots one finger tap targets and points. Requires 'target-points', |
| 'passed_points', and 'failed_points', last two are tuples of (x,y) tuples: |
| (target, actual_hit). If args[0] is 'detailed', a |
| more accurate version is plotted""" |
| |
| s = Timer(3) |
| |
| if len(args) > 0 and args[0] == 'detailed': |
| fig = plt.figure(num=2, dpi=100, figsize=(20,20)) |
| else: |
| fig = plt.figure(num=1, dpi=100, figsize=(8,6)) |
| plt.clf() |
| |
| plt.axis('equal') |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, dutinfo.dimensions[1] + 5.0, -5.0]) |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| else: |
| plt.suptitle('Overview plot') |
| plt.xlabel('X [mm]') |
| plt.ylabel('Y [mm]') |
| plotters.plot_panel_borders(dutinfo.dimensions[0], dutinfo.dimensions[1],zorder=_zorder['edges']) |
| s.Time("Borders") |
| |
| # plot targets |
| cx = np.cos(np.linspace(0,np.pi*2, num=32)) |
| cy = np.sin(np.linspace(0,np.pi*2, num=32)) |
| x = [] |
| y = [] |
| for p in plotinfo['hits']: |
| x.extend([p[0][0] + float(p[1])*c for c in cx]) |
| y.extend([p[0][1] + float(p[1])*c for c in cy]) |
| x.append(None) |
| y.append(None) |
| plt.plot(x, y, color="#000000") |
| x = [] |
| y = [] |
| for p in plotinfo['missing']: |
| x.extend([p[0][0] + float(p[1])*c for c in cx]) |
| y.extend([p[0][1] + float(p[1])*c for c in cy]) |
| x.append(None) |
| y.append(None) |
| plt.plot(x, y, color=_FAIL, zorder=_zorder['lines']) |
| s.Time("Targets") |
| |
| # Passed points |
| lines_x = [] |
| lines_y = [] |
| for p in plotinfo['passed_points']: |
| lines_x.extend((p[0][0], p[1][0], None)) |
| lines_y.extend((p[0][1], p[1][1], None)) |
| px = [p[1][0] for p in plotinfo['passed_points']] |
| py = [p[1][1] for p in plotinfo['passed_points']] |
| plt.plot(lines_x, lines_y, color=_PASS, zorder=_zorder['lines']) |
| plt.scatter(px, py, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| s.Time("Plots") |
| |
| # Failed points |
| lines_x = [] |
| lines_y = [] |
| for p in plotinfo['failed_points']: |
| lines_x.extend((p[0][0], p[1][0], None)) |
| lines_y.extend((p[0][1], p[1][1], None)) |
| px = [p[1][0] for p in plotinfo['failed_points']] |
| py = [p[1][1] for p in plotinfo['failed_points']] |
| plt.plot(lines_x, lines_y, color=_FAIL, zorder=_zorder['lines']) |
| plt.scatter(px, py, s=_markersize, c=_FAIL, zorder=_zorder['fail'],edgecolors='k') |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| s.Time("Save") |
| |
| |
| @synchronized |
| def plot_dxdy_graph(imagepath, plotinfo, limited, *args, **kwargs): |
| """ Plots dx-dy graph with two histograms. If args[0] is 'detailed', a |
| more accurate version is plotted. The plotinfo parameter is a dectionary, |
| which contains passed_points (tuple (target_point, hit)), failed points |
| and 'maxposerror'. If edge are plot is to be printed, the 'maxposerror' is |
| replaced by 'edgepositioningerror' """ |
| #used by: one finger tap |
| s = Timer(3) |
| |
| if len(args) > 0 and args[0] == 'detailed': |
| fig = plt.figure(num=2, dpi=100, figsize=(20,20)) |
| else: |
| fig = plt.figure(num=1, dpi=100, figsize=(8,6)) |
| |
| plt.clf() |
| |
| # setup subpolots |
| plt.axis('equal') |
| gs = gridspec.GridSpec(2, 2, width_ratios = [3, 1], height_ratios = [3, 1]) |
| ax = plt.subplot(gs[0]) |
| |
| # Axis labels |
| plt.xlabel('linearity error X [mm]') |
| plt.ylabel('linearity error Y [mm]') |
| ax.grid(True) |
| ax.axhline(color="#000000") |
| ax.axvline(color="#000000") |
| |
| # Draw acceptance circle |
| if 'edge_only' not in kwargs: |
| radius = float(plotinfo['maxposerror']) |
| x = radius*np.cos(np.linspace(0,np.pi*2)) |
| y = radius*np.sin(np.linspace(0,np.pi*2)) |
| plt.plot(x, y, "k") |
| |
| if 'edgepositioningerror' in plotinfo and 'center_only' not in kwargs: |
| # Draw second acceptance limit to the edge areas |
| radius = float(plotinfo['edgepositioningerror']) |
| x = radius*np.cos(np.linspace(0,np.pi*2)) |
| y = radius*np.sin(np.linspace(0,np.pi*2)) |
| plt.plot(x, y, "k") |
| |
| if limited: |
| ax.set_xlim(-radius, radius) |
| ax.set_ylim(-radius, radius) |
| ax.set_aspect('equal') |
| if 'center_only' in kwargs: |
| plt.title('Center input error scatter plot (limited area)') |
| elif 'edge_only' in kwargs: |
| plt.title('Edge input error scatter plot (limited area)') |
| else: |
| plt.title('Input error scatter plot (limited area)') |
| else: |
| ax.set_aspect('equal') |
| plt.title('Input error scatter plot') |
| |
| passed_points_x = [(p[1][0] - p[0][0]) for p in plotinfo['passed_points']] |
| passed_points_y = [(p[1][1] - p[0][1]) for p in plotinfo['passed_points']] |
| failed_points_x = [(p[1][0] - p[0][0]) for p in plotinfo['failed_points']] |
| failed_points_y = [(p[1][1] - p[0][1]) for p in plotinfo['failed_points']] |
| ax.scatter(passed_points_x, passed_points_y, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| ax.scatter(failed_points_x, failed_points_y, s=_markersize, c=_FAIL, zorder=_zorder['fail'],edgecolors='k') |
| |
| # Set yaxis location and tick location |
| ax.yaxis.tick_right() |
| ax.set_ylim(ax.get_ylim()[::-1]) |
| |
| |
| |
| # plot histograms |
| ax1 = plt.subplot(gs[1], sharey=ax) |
| # Set histogram bins according to the ticks in the axis |
| bins = ax1.get_yticks() |
| bins = plotters.split_bins(bins, 3) |
| plt.setp(ax1.get_yticklabels(), visible=False) |
| #ax1.set_xticklabels([]) |
| plt.hist([passed_points_y, failed_points_y], bins=bins, orientation='horizontal', color=[_PASS, _FAIL], stacked=True, edgecolor='k') |
| labels = [int(l) for l in ax1.get_xticks()] |
| for i in range(1, len(labels) - 1): |
| labels[i] = '' |
| ax1.set_xticklabels(labels) |
| |
| ax2 = plt.subplot(gs[2], sharex=ax) |
| #ax2.set_yticklabels([]) |
| #ax2.yaxis.tick_right() |
| plt.setp(ax2.get_xticklabels(), visible=False) |
| bins = ax2.get_xticks() |
| bins = plotters.split_bins(bins, 3) |
| plt.hist([passed_points_x, failed_points_x], bins=bins, orientation='vertical', color=[_PASS, _FAIL], stacked=True, edgecolor='k') |
| ax2.set_ylim(ax2.get_ylim()[::-1]) |
| labels = [int(l) for l in ax2.get_yticks()] |
| for i in range(1, len(labels) - 1): |
| labels[i] = '' |
| ax2.set_yticklabels(labels) |
| |
| if limited: |
| ax.set_xlim(-radius, radius) |
| ax.set_ylim(radius, -radius) |
| ax.set_aspect('equal') |
| |
| # Create the image |
| plt.tight_layout() |
| fig.savefig(imagepath) |
| plt.close('all') |
| s.Time("Save") |
| |
| @synchronized |
| def plot_p2p_err_histogram(imagepath, plotinfo, limited, *args, **kwargs): |
| if len(args) > 0 and args[0] == 'detailed': |
| fig = plt.figure(num=2, dpi=100, figsize=(20,20)) |
| else: |
| fig = plt.figure(num=1, dpi=100, figsize=(8,6)) |
| |
| acc_errors = plotinfo['distances'] |
| |
| num_bins = 30 |
| plt.hist(acc_errors, bins=num_bins, color='gray', stacked=True, edgecolor='k') |
| plt.xlabel('Accuracy [mm]') |
| plt.ylabel('Frequency') |
| plt.title('Input Error Histogram') |
| |
| |
| sorted_acc_errors = np.sort(acc_errors) |
| index_85_0 = round(0.85*len(acc_errors)) - 1 |
| limit_85_0 = sorted_acc_errors[index_85_0] |
| index_99_7 = round(0.997*len(acc_errors)) - 1 |
| limit_99_7 = sorted_acc_errors[index_99_7] |
| |
| line_85 = plt.axvline(x=limit_85_0, linewidth=2, color='g') |
| line_997 = plt.axvline(x=limit_99_7, linewidth=2, color='r') |
| |
| # Create a blank rectangle for adding additional info to legend |
| blank = Rectangle((0, 0), 1, 1, fc="w", fill=False, edgecolor='none', linewidth=0) |
| |
| legend = plt.legend([line_85, line_997, blank, blank], (u"85 % = " + str(limit_85_0) + u" mm", |
| u"99.7 % = " + str(limit_99_7) + u" mm", |
| "Mean: %s mm" % round(np.mean(acc_errors), 2), |
| "Std: %s mm" % round(np.std(acc_errors), 2)), fontsize=11) |
| # Create the image |
| plt.tight_layout() |
| fig.savefig(imagepath) |
| plt.close('all') |
| |
| @synchronized |
| def plot_multifinger_p2p(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots multifinger tap test detailed overview plot. |
| plotinfo argument is the result array from multifinger analysis """ |
| # Used by: |
| |
| s = Timer(3) |
| fig = plt.figure(1) |
| plt.clf() |
| |
| # Plot panel borders |
| plt.axis('equal') |
| plotters.plot_panel_borders(dutinfo.dimensions[0], dutinfo.dimensions[1],zorder=_zorder['edges']) |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, dutinfo.dimensions[1] + 5.0, -5.0]) |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| lines = [] |
| passed_fingers = [] |
| failed_fingers = [] |
| for tap in plotinfo['taps']: |
| for point, verdict in zip(tap['targetpoints'], [f['verdict'] for f in tap['fingers']]): |
| lines.append(point) |
| if verdict: |
| passed_fingers.append(point) |
| else: |
| failed_fingers.append(point) |
| lines.append((None, None)) |
| |
| # Lines connecting points |
| x = [p[0] for p in lines] |
| y = [p[1] for p in lines] |
| plt.plot(x, y, color='k', zorder=_zorder['lines']) |
| # Passed points |
| x = [p[0] for p in passed_fingers] |
| y = [p[1] for p in passed_fingers] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'], edgecolors='k') |
| # Failed points |
| x = [p[0] for p in failed_fingers] |
| y = [p[1] for p in failed_fingers] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'], edgecolors='k') |
| |
| s.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| |
| |
| @synchronized |
| def plot_multifinger_tapdetails(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots multifinger tap detailed plot for single tap. |
| plotinfo argument is the result array from multifinger tap analysis """ |
| # Used by: |
| |
| s = Timer(3) |
| fig = plt.figure(num=3, dpi=100, figsize=(8,8)) |
| plt.clf() |
| |
| cx = np.cos(np.linspace(0,np.pi*2, num=32)) |
| cy = np.sin(np.linspace(0,np.pi*2, num=32)) |
| |
| num_fingers = plotinfo['num_fingers'] |
| |
| gs = gridspec.GridSpec(2, num_fingers, height_ratios = [3, 1]) |
| ax0 = plt.subplot(gs[0, :]) |
| axf = [plt.subplot(gs[1, i]) for i in range(num_fingers)] |
| |
| ax0.axis('equal') |
| if 'title' in kwargs: |
| ax0.set_title(kwargs['title']) |
| |
| error_c = [] |
| passed_points = [] |
| failed_points = [] |
| failed_lines = [] |
| for i, f in enumerate(plotinfo['fingers']): |
| passed_f = [] |
| failed_f = [] |
| failed_linesf = [] |
| for id in f['points'].keys(): |
| passed_f.extend([p for d, p in zip(f['distances'][id], f['points'][id]) if d <= f['maxposerror']]) |
| failed_f.extend([p for d, p in zip(f['distances'][id], f['points'][id]) if d > f['maxposerror']]) |
| for p in failed_f: |
| failed_linesf.extend(zip((f['target'][0], p[0], None), (f['target'][1], p[1], None))) |
| passed_points.extend(passed_f) |
| failed_points.extend(failed_f) |
| failed_lines.extend(failed_linesf) |
| |
| error_cf = zip([f['target'][0] + x * float(f['maxposerror']) for x in cx], |
| [f['target'][1] + y * float(f['maxposerror']) for y in cy]) |
| error_c.extend(error_cf) |
| error_c.append((None,None)) |
| |
| axf[i].axis('equal') |
| axf[i].set_xticklabels([]) |
| axf[i].set_yticklabels([]) |
| axf[i].set_title('Finger %d' % (i + 1)) |
| |
| # Subplot: Passed points |
| x = [p[0] for p in passed_f] |
| y = [p[1] for p in passed_f] |
| axf[i].scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'], edgecolors='k') |
| # Subplot: lines |
| x = [p[0] for p in error_cf] |
| y = [p[1] for p in error_cf] |
| axf[i].plot(x, y, color='k', zorder=_zorder['lines']) |
| # Subplot: Lines to failed points |
| x = [p[0] for p in failed_linesf] |
| y = [p[1] for p in failed_linesf] |
| axf[i].plot(x, y, color=_FAIL, zorder=_zorder['lines']) |
| # Subplot: Failed points |
| x = [p[0] for p in failed_f] |
| y = [p[1] for p in failed_f] |
| axf[i].scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'], edgecolors='k') |
| diagstr = "n=%d, id: %s" % (len(passed_f) + len(failed_f), ','.join(str(k) for k in f['points'].keys())) |
| col = 'k' if len(f['points'].keys()) == 1 else _FAIL |
| axf[i].text(0.5, -0.15,diagstr, color=col, horizontalalignment='center', verticalalignment='center', transform=axf[i].transAxes) |
| axf[i].set_ylim(axf[i].get_ylim()[::-1]) |
| |
| |
| # Annotations |
| x = [f['target'][0] for f in plotinfo['fingers']] |
| y = [f['target'][1] for f in plotinfo['fingers']] |
| d = [float(f['maxposerror']) for f in plotinfo['fingers']] |
| strings = [i + 1 for i in range(len(plotinfo['fingers']))] |
| for (x, y, d, string) in zip(x, y, d, strings): |
| ax0.annotate(string, (x+d,y+d), xytext=(1, 1), textcoords='offset points') |
| |
| # Passed points |
| x = [p[0] for p in passed_points] |
| y = [p[1] for p in passed_points] |
| ax0.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'], edgecolors='k') |
| # Circles |
| x = [p[0] for p in error_c] |
| y = [p[1] for p in error_c] |
| ax0.plot(x, y, color='k', zorder=_zorder['lines']) |
| |
| # Lines to failed points |
| x = [p[0] for p in failed_lines] |
| y = [p[1] for p in failed_lines] |
| ax0.plot(x, y, color=_FAIL, zorder=_zorder['lines']) |
| # Failed points |
| x = [p[0] for p in failed_points] |
| y = [p[1] for p in failed_points] |
| ax0.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'], edgecolors='k') |
| ax0.set_ylim(ax0.get_ylim()[::-1]) |
| |
| s.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| s.Time("Save") |
| |
| @synchronized |
| def plot_separation_results(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots separation general view |
| plotinfo argument is the result array from separation analysis """ |
| # Used by: |
| |
| s = Timer(3) |
| fig = plt.figure(1) |
| plt.clf() |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| passed = [] |
| failed = [] |
| for angle, avalues in plotinfo['angles'].items(): |
| for distance, dvalues in avalues['distances'].items(): |
| if dvalues['verdict']: |
| passed.extend([dvalues['point'], dvalues['point2']]) |
| else: |
| failed.extend([dvalues['point'], dvalues['point2']]) |
| |
| # Passed points |
| x = [p[0] for p in passed] |
| y = [p[1] for p in passed] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| # Failed points |
| x = [p[0] for p in failed] |
| y = [p[1] for p in failed] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'],edgecolors='k') |
| |
| s.Time("Plots") |
| |
| ax = plt.gca() |
| ax.autoscale(tight=True) |
| ax.set_aspect('equal', 'datalim') |
| ax.set_ylim(ax.get_ylim()[::-1]) |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| s.Time("Save") |
| |
| @synchronized |
| def plot_separation_details(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots separation general view |
| plotinfo argument is the result array from separation analysis """ |
| # Used by: |
| |
| s = Timer(3) |
| fig = plt.figure(1) |
| plt.clf() |
| plt.axis('equal') |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| passed = [] |
| failed = [] |
| for angle, avalues in plotinfo['angles'].items(): |
| for distance, dvalues in avalues['distances'].items(): |
| for tapid, taps in dvalues['taps'].items(): |
| if dvalues['verdict']: |
| passed.extend(taps) |
| else: |
| failed.extend(taps) |
| |
| # Passed points |
| x = [p[0] for p in passed] |
| y = [p[1] for p in passed] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| # Failed points |
| x = [p[0] for p in failed] |
| y = [p[1] for p in failed] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'],edgecolors='k') |
| |
| ax = plt.gca() |
| ax.autoscale(tight=True) |
| ax.set_aspect('equal', 'datalim') |
| ax.set_ylim(ax.get_ylim()[::-1]) |
| |
| s.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| s.Time("Save") |
| |
| @synchronized |
| def plot_separation_tapdetails(imagepath, plotinfo, dutinfo, *args, **kwargs): |
| """ Plots separation tap details |
| plotinfo argument is the result array from separation analysis """ |
| # Used by: |
| |
| s = Timer(3) |
| fig = plt.figure(5, figsize=(6,6), dpi=100) |
| plt.clf() |
| plt.axis('equal') |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| radius = plotinfo['point_diameter'] / 2.0 |
| x = plotinfo['point'][0] + radius*np.cos(np.linspace(0,np.pi*2)) |
| y = plotinfo['point'][1] + radius*np.sin(np.linspace(0,np.pi*2)) |
| plt.plot(x, y, "k", zorder=_zorder['lines']) |
| radius = plotinfo['point2_diameter'] / 2.0 |
| x = plotinfo['point2'][0] + radius*np.cos(np.linspace(0,np.pi*2)) |
| y = plotinfo['point2'][1] + radius*np.sin(np.linspace(0,np.pi*2)) |
| plt.plot(x, y, "k", zorder=_zorder['lines']) |
| |
| passed = [] |
| failed = [] |
| lines = [] |
| for tapid, taps in plotinfo['taps'].items(): |
| if plotinfo['verdict']: |
| passed.extend(taps) |
| else: |
| failed.extend(taps) |
| lines.extend(taps) |
| lines.append((None,None)) |
| |
| # Passed points |
| x = [p[0] for p in passed] |
| y = [p[1] for p in passed] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'],edgecolors='k') |
| # Failed points |
| x = [p[0] for p in failed] |
| y = [p[1] for p in failed] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'],edgecolors='k') |
| x = [p[0] for p in lines] |
| y = [p[1] for p in lines] |
| plt.plot(x, y, "k", zorder=_zorder['lines']) |
| |
| ax = plt.gca() |
| ax.set_ylim(ax.get_ylim()[::-1]) |
| |
| s.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath) |
| plt.close('all') |
| s.Time("Save") |
| |
| |
| @synchronized |
| def plot_multifinger_swipedetails(imagepath, plotinfo, dutinfo, **kwargs): |
| """ Plots multifinger swipe details diagram - two subplots with target and swipe information |
| plotinfo argument comes from multifinger swipe analysis """ |
| # Used by: multifinger swipe |
| |
| s = Timer(3) |
| fig = plt.figure(num=6, dpi=100, figsize=(10,8)) |
| plt.clf() |
| |
| if 'title' in kwargs: |
| plt.suptitle(kwargs['title']) |
| |
| # Plot panel borders |
| gs = gridspec.GridSpec(len(plotinfo['fingers']), 2, hspace=0.35) |
| ax = plt.subplot(gs[:, 0]) |
| |
| plt.axis('equal') |
| plt.xlabel('X [mm]') |
| plt.ylabel('Y [mm]') |
| plotters.plot_panel_borders(dutinfo.dimensions[0], dutinfo.dimensions[1],zorder=_zorder['edges']) |
| plt.axis([-5.0, dutinfo.dimensions[0] + 5.0, dutinfo.dimensions[1] + 5.0, -5.0]) |
| |
| passed = [] |
| failed = [] |
| |
| for finger in plotinfo['fingers']: |
| for idpoints in finger['passed_points'].values(): |
| passed.extend(idpoints) |
| for idpoints in finger['failed_points'].values(): |
| failed.extend(idpoints) |
| plt.arrow(finger['swipe_start'][0], finger['swipe_start'][1], |
| finger['swipe_end'][0] - finger['swipe_start'][0], |
| finger['swipe_end'][1] - finger['swipe_start'][1], |
| width=0.1, length_includes_head=True) |
| |
| # Target |
| x = [p[0] for p in passed] |
| y = [p[1] for p in passed] |
| plt.scatter(x, y, s=_markersize, c=_PASS, zorder=_zorder['pass'], edgecolors='k') |
| x = [p[0] for p in failed] |
| y = [p[1] for p in failed] |
| plt.scatter(x, y, s=_markersize, c=_FAIL, zorder=_zorder['fail'], edgecolors='k') |
| |
| # Plot swipes |
| for i, finger in enumerate(plotinfo['fingers']): |
| axf = plt.subplot(gs[i, 1]) |
| if i == len(plotinfo['fingers']) - 1: |
| plt.xlabel('distance traveled [mm]') |
| |
| x = [] |
| y = [] |
| jitters = [] |
| for fid in finger['swipe_points'].keys(): |
| x.extend([p[0] for p in finger['swipe_points'][fid]]) |
| y.extend([p[1] for p in finger['swipe_points'][fid]]) |
| jitters.extend(finger['jitters'][fid]) |
| #plt.plot(x, y, "o-", color='k', mec='k', mfc='r') |
| plt.plot(x, y, color='b') |
| plt.vlines(x, 0.0, y, alpha=0.5) |
| plt.plot(x, jitters, color=_DEFAULT) |
| if len(x) > 0: |
| axf.set_title('Finger %d id: %s, n=%d' % (i + 1, ', '.join([str(id) for id in finger['swipe_points'].keys()]), len(x)), fontsize=8) |
| else: |
| axf.set_title('Finger %d no measurements' % (i + 1), fontsize=8) |
| legend = plt.legend(("offset", "jitter"), bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0, fontsize=8) |
| for tick in axf.xaxis.get_major_ticks(): |
| tick.label.set_fontsize(8) |
| for tick in axf.yaxis.get_major_ticks(): |
| tick.label.set_fontsize(8) |
| |
| s.Time("Plots") |
| |
| # Create the image |
| fig.savefig(imagepath, bbox_inches="tight") |
| plt.close('all') |
| s.Time("Save") |
| |