blob: 40d5c62cd049177b76cb16d8e199e8d75b276abe [file] [log] [blame]
"""
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.
"""
import cherrypy
from genshi.template import MarkupTemplate
from sqlalchemy.orm import joinedload
import numpy as np
import numpy.linalg
from math import sqrt
from sqlalchemy import *
from sqlalchemy.dialects.mysql import LONGTEXT
from TPPTAnalysisSW.sqluploader import Base
from TPPTAnalysisSW.testbase import TestBase, testclasscreator, timestr_to_datetime
from TPPTAnalysisSW.imagefactory import ImageFactory
from TPPTAnalysisSW.utils import Timer
import TPPTAnalysisSW.measurementdb as db
from TPPTAnalysisSW.settings import settings
from TPPTAnalysisSW.info.version import Version
import TPPTAnalysisSW.plot_factory as plot_factory
import TPPTAnalysisSW.plotinfo as plotinfo
import TPPTAnalysisSW.analyzers as analyzers
import datetime
class StationaryJitterStaticNoiseSummarySQL(Base):
__tablename__ = 'touch_stationary_jitter_static_noise_summary'
meta_id = Column(Integer, primary_key=True)
time_test_start = Column(DATETIME)
test_id = Column(DECIMAL(10, 0))
time_sequence_start = Column(DATETIME)
time_sequence_end = Column(DATETIME)
border_width = Column(DECIMAL(8, 2))
finger_name = Column(VARCHAR(40))
finger_type = Column(VARCHAR(40))
finger_size = Column(DECIMAL(8, 2))
step_size = Column(DECIMAL(8, 2))
lift_off_distance = Column(DECIMAL(8, 2))
ground_status = Column(VARCHAR(40))
display_background = Column(VARCHAR(40))
noise_status = Column(VARCHAR(40))
touch_area = Column(VARCHAR(40))
contact_duration = Column(DECIMAL(8, 2))
number_of_fingers_on_screen = Column(DECIMAL(6, 2))
log = Column(LONGTEXT)
test_type = Column(VARCHAR(40))
stationary_jitter_avg_of_error_avgs = Column(DECIMAL(16, 3))
stationary_jitter_total_stdev_error = Column(DECIMAL(16, 3))
stationary_jitter_avg_of_max_errors = Column(DECIMAL(16, 3))
stationary_jitter_max_of_max_errors = Column(DECIMAL(16, 3))
total_number_of_missing_points = Column(Integer)
# The time of database entry creation set by the database server
created = Column(TIMESTAMP(), server_default=func.current_timestamp())
class StationaryJitterStaticNoiseTest(TestBase):
"""
Stationary jitter static noise test measures how much reported touch locations vary
from actual robot position under different conditions.
"""
# This is the generator function for the class - it must exist in all derived classes
# Just update the id (dummy=99) and class name
@staticmethod
@testclasscreator(16)
def create_testclass(*args, **kwargs):
return StationaryJitterStaticNoiseTest(*args, **kwargs)
# Init function: make necessary initializations.
# Parent function initializes: self.test_id, self.test_item (dictionary, contains test_type_name) and self.testsession (dictionary)
def __init__(self, ddtest_row, *args, **kwargs):
""" Initializes a new test case class """
super(StationaryJitterStaticNoiseTest, self).__init__(ddtest_row, *args, **kwargs)
self.sql_summary_class = StationaryJitterStaticNoiseSummarySQL
# Override to make necessary analysis for test session success
def runanalysis(self, *args, **kwargs):
""" Runs the analysis, return a string containing the test result """
results = self.read_test_results()
return results['verdict']
# Override to make necessary operations for clearing test results
# Clearing the test result from the results table is done elsewhere
def clearanalysis(self, *args, **kwargs):
""" Clears analysis results """
ImageFactory.delete_images(self.test_id)
# Create the test report. Return the created HTML, or raise cherrypy.HTTPError
def createreport(self, *args, **kwargs):
t = Timer(1)
self.clearanalysis()
# Create common template parameters (including test_item dictionary, testsession dictionary, test_id, test_type_name etc)
template_params = super(StationaryJitterStaticNoiseTest, self).create_common_templateparams(**kwargs)
results = self.read_test_results()
t.Time("Results")
# Add the image name and parameters to the report
template_params['results'] = results
template_params['figure'] = ImageFactory.create_image_name(self.test_id, 'stjitt')
template_params['detailed_figure'] = ImageFactory.create_image_name(self.test_id, 'stjitt', 'detailed')
template_params['test_page'] = 'test_stationary_jitter_static_noise.html'
template_params['test_script'] = 'test_page_subplots.js'
template_params['version'] = Version
template_params['test_parameters'] = (('Border width [mm]', results['border_width']),
('Finger name', results['finger_name']),
('Finger type', results['finger_type']),
('Finger size [mm]', results['finger_size']),
('Finger 2 name', results['finger2_name']),
('Separation [mm]', results['separation']),
('Step size [mm]', results['step_size']),
('Lift off distance [mm]', results['lift_off_distance']),
('Ground status', results['ground_status']),
('Touch area', results['touch_area']),
('Contact duration [ms]', results['contact_duration']),
('Noise status', results['noise_status']),
('Display background', results['display_background']),
('Number of fingers on screen', results['number_of_fingers_on_screen']),
)
self.disable_upload_button_if_already_uploaded(template_params)
template = MarkupTemplate(open("templates/test_configured_body.html"))
stream = template.generate(**(template_params))
t.Time("Markup")
return stream.render('xhtml'), results['verdict']
# Create images for the report. If the function returns a value, it is used as the new image name (without image path)
def createimage(self, imagepath, image_name, *args, **kwargs):
# Dummy test has only one image: dummyimage.
if image_name == 'stjitt':
dbsession = db.get_database().session()
dutinfo = plotinfo.TestDUTInfo(testdut_id=self.dut['id'], dbsession=dbsession)
results = self.read_test_results(dutinfo, dbsession)
title = 'Preview: Stationary Jitter Static Noise ' + self.dut['program']
plot_factory.plot_passfail_labels_on_target(imagepath, results, dutinfo, *args, title=title, **kwargs)
elif image_name == 'stjittdtls':
dbsession = db.get_database().session()
dutinfo = plotinfo.TestDUTInfo(testdut_id=self.dut['id'], dbsession=dbsession)
results = self.read_point_details(args[0], dutinfo, dbsession)
title = 'Preview: Stationary Jitter Static Noise details ' + self.dut['program']
plot_factory.plot_passfail_labels(imagepath, results, title=title, **kwargs)
else:
raise cherrypy.HTTPError(message = "No such image in the report")
return None
def read_test_results(self, dutinfo = None, dbsession = None):
if dbsession is None:
dbsession = db.get_database().session()
if dutinfo is None:
dutinfo = plotinfo.TestDUTInfo(testdut_id=self.dut['id'], dbsession=dbsession)
pts = dbsession.query(db.StationaryJitterStaticNoiseTest).filter(db.StationaryJitterStaticNoiseTest.test_id==self.test_id).\
options(joinedload(db.StationaryJitterStaticNoiseTest.stationary_jitter_static_noise_results)).\
order_by(db.StationaryJitterStaticNoiseTest.id)
num_pts = len(list(pts))
passed = []
failed = []
points = []
all_deviations = []
avg_max_jitter = 0
avg_mean_jitter = 0
total_stdev_jitter = 0
max_max_jitter = None
verdict = "N/A"
point_id = 1
noise_status = pts[0].noise_status if num_pts > 0 else ""
test_type = pts[0].test_type if num_pts > 0 else ""
# The pass/fail limit depends on the test case
if noise_status == '':
jitter_limit = settings['max_stat_jitter_no_noise']
else:
jitter_limit = settings['max_stat_jitter_noise']
if test_type == 'noise_test':
jitter_limit = settings['max_noise_test_jitter']
num_missing_points = 0
num_valid_points = 0
# For each touch location, compute maximum and standard deviation jitter.
for point in pts:
# Get panel points that result from the first finger (ID 0).
panel_points = []
for p in point.stationary_jitter_static_noise_results:
if p.finger_id == 0:
panel_points.append((p.panel_x, p.panel_y))
target_points = analyzers.panel_to_target(panel_points, dutinfo)
robot_point = analyzers.robot_to_target((point.robot_x, point.robot_y), dutinfo)
if len(target_points) == 0:
# No measurements for point
failed.append(list(robot_point) + [str(point_id)])
points.append((point_id, None, "N/A", None))
point_id += 1
num_missing_points += 1
else:
num_valid_points += 1
points_np = np.array([np.array(p) for p in target_points])
min_point = np.min(points_np, axis=0)
max_point = np.max(points_np, axis=0)
point_max_jitter = np.linalg.norm(max_point - min_point)
point_max_jitter_rounded = analyzers.float_for_db(point_max_jitter)
mean_point = np.mean(points_np, axis=0)
mean_points = np.tile(mean_point, (len(target_points), 1))
deviations = np.linalg.norm(mean_points - points_np, axis=1)
all_deviations = np.concatenate((all_deviations, deviations))
point_avg_jitter = np.mean(deviations)
avg_mean_jitter += point_avg_jitter
point_verdict = "Fail" if point_max_jitter > jitter_limit else "Pass"
points.append((point_id, point_max_jitter_rounded, point_verdict, ImageFactory.create_image_name(self.test_id, 'stjittdtls', str(point.id))))
if max_max_jitter is None or point_max_jitter > max_max_jitter:
max_max_jitter = point_max_jitter
avg_max_jitter += point_max_jitter
if point_verdict == "Pass":
passed.append(list(target_points[0]) + [point_id])
else:
failed.append(list(target_points[0]) + [point_id])
point_id += 1
if num_valid_points > 0:
avg_mean_jitter = analyzers.float_for_db(avg_mean_jitter / num_valid_points)
avg_max_jitter = analyzers.float_for_db(avg_max_jitter / num_valid_points)
max_max_jitter = analyzers.float_for_db(max_max_jitter)
verdict = "Fail" if avg_max_jitter > jitter_limit else "Pass"
# Compute standard deviation of all deviations.
total_stdev_jitter = analyzers.float_for_db(sqrt(np.mean(all_deviations ** 2)))
else:
# None values show up as "N/A" in the result page.
avg_mean_jitter = None
avg_max_jitter = None
max_max_jitter = None
total_stdev_jitter = None
if num_pts > 0:
missing_points_percentage = 100*num_missing_points / num_pts
else:
missing_points_percentage = 100
if missing_points_percentage <= settings['jitter_missing_points']:
missing_inputs_verdict = 'Pass'
else:
missing_inputs_verdict = 'Fail'
results = {'passed_points': passed,
'failed_points': failed,
'total_points': num_pts,
'missing_points_percentage': missing_points_percentage,
'missing_inputs_verdict': missing_inputs_verdict,
'avg_mean_jitter': avg_mean_jitter,
'avg_max_jitter': avg_max_jitter,
'max_max_jitter': max_max_jitter,
'total_stdev_jitter': total_stdev_jitter,
'verdict': verdict,
'points': points,
'border_width': pts[0].border_width if num_pts > 0 else 0,
'finger_name': pts[0].finger_name if num_pts > 0 else "",
'finger_type': pts[0].finger_type if num_pts > 0 else "",
'finger2_name': pts[0].finger2_name if num_pts > 0 else "",
'separation': pts[0].separation if num_pts > 0 else 0,
'step_size': pts[0].step_size if num_pts > 0 else 0,
'finger_size': pts[0].finger_size if num_pts > 0 else 0,
'lift_off_distance': pts[0].lift_off_distance if num_pts > 0 else 0,
'noise_status': pts[0].noise_status if num_pts > 0 else "",
'ground_status': pts[0].ground_status if num_pts > 0 else "",
'display_background': pts[0].display_background if num_pts > 0 else "",
'touch_area': pts[0].touch_area if num_pts > 0 else "",
'contact_duration': pts[0].contact_duration if num_pts > 0 else 0,
'number_of_fingers_on_screen': pts[0].number_of_fingers_on_screen if num_pts > 0 else 0,
'missing_inputs': num_missing_points,
'test_type': test_type
}
return results
def read_point_details(self, point_id, dutinfo = None, dbsession = None):
if dbsession is None:
dbsession = db.get_database().session()
if dutinfo is None:
dutinfo = plotinfo.TestDUTInfo(testdut_id=self.dut['id'], dbsession=dbsession)
points = dbsession.query(db.StationaryJitterStaticNoiseResults).filter(db.StationaryJitterStaticNoiseResults.point_id==point_id).\
order_by(db.StationaryJitterStaticNoiseResults.id)
robot_point = dbsession.query(db.StationaryJitterStaticNoiseTest).filter(db.StationaryJitterStaticNoiseTest.id==point_id).\
order_by(db.StationaryJitterStaticNoiseTest.id)
# Get panel points that result from the first finger (ID 0).
panel_points = []
for p in points:
if p.finger_id == 0:
panel_points.append((p.panel_x, p.panel_y))
target_points = analyzers.panel_to_target(panel_points, dutinfo)
points = []
point_count = []
for point in target_points:
if point in points:
point_count[points.index(point)] += 1
else:
points.append(point)
point_count.append(1)
result = None
# When using max error metric it is not possible to separate passed and failed points.
# Just put all points in passed list.
if len(points) > 1:
orig = np.array(points[0])
distances = np.array([np.linalg.norm(np.array(p) - orig) for p in points])
passed = []
failed = []
for point, distance, count in zip(points, distances, point_count):
passed.append((point[0], point[1], str(count) if count > 1 else ''))
result = {'passed_points': passed, 'failed_points': failed, 'robot_point': robot_point}
elif len(points) == 1:
# Only one point in lists...
points = [(points[0][0], points[0][1], str(point_count[0]) if point_count[0] > 1 else '')]
result = {'passed_points': points, 'failed_points': [], 'robot_point': robot_point}
if len(points) == 0:
# No points
result = {'passed_points': [], 'failed_points': [], 'robot_point': robot_point}
return result
def upload_sql_data(self, session):
test_results = self.read_test_results()
test_item = self.get_test_item()
test_session = self.get_test_session()
summary = StationaryJitterStaticNoiseSummarySQL()
summary.test_id = self.test_id
summary.time_test_start = timestr_to_datetime(test_item.starttime)
summary.time_sequence_start = timestr_to_datetime(test_session.starttime)
# End time is None if sequence was not completed.
if test_session.endtime is not None:
summary.time_sequence_end = timestr_to_datetime(test_session.endtime)
summary.border_width = test_results["border_width"]
summary.finger_name = test_results["finger_name"]
summary.finger_type = test_results["finger_type"]
summary.finger_size = test_results["finger_size"]
summary.step_size = test_results["step_size"]
summary.lift_off_distance = test_results["lift_off_distance"]
summary.ground_status = test_results["ground_status"]
summary.display_background = test_results["display_background"]
summary.touch_area = test_results["touch_area"]
summary.noise_status = test_results["noise_status"]
summary.number_of_fingers_on_screen = test_results["number_of_fingers_on_screen"]
summary.contact_duration = test_results["contact_duration"]
summary.log = self.test_item['kmsg_log'] # The test_item comes form Testbase
summary.test_type = test_results['test_type']
summary.stationary_jitter_avg_of_error_avgs = test_results["avg_mean_jitter"]
summary.stationary_jitter_total_stdev_error = test_results["total_stdev_jitter"]
summary.stationary_jitter_avg_of_max_errors = test_results["avg_max_jitter"]
summary.stationary_jitter_max_of_max_errors = test_results["max_max_jitter"]
summary.total_number_of_missing_points = test_results["missing_inputs"]
session.add(summary)
session.commit()