blob: 7a3c6e6d50236c93663bd736bca08ae43782b932 [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.
"""
from math import sqrt, isnan
import cherrypy
import numpy as np
from genshi.template import MarkupTemplate
from sqlalchemy import Column, DECIMAL, INTEGER, VARCHAR, ForeignKey, DATETIME, TIMESTAMP, func
from sqlalchemy.dialects.mysql import LONGTEXT
from sqlalchemy.orm import joinedload
import TPPTAnalysisSW.analyzers as analyzers
import TPPTAnalysisSW.measurementdb as db
import TPPTAnalysisSW.plot_factory as plot_factory
import TPPTAnalysisSW.plotinfo as plotinfo
from TPPTAnalysisSW.sqluploader import Base
from TPPTAnalysisSW.imagefactory import ImageFactory
from TPPTAnalysisSW.info.version import Version
from TPPTAnalysisSW.measurementdb import get_database, OneFingerTappingRepeatabilityTest
from TPPTAnalysisSW.settings import settings
from TPPTAnalysisSW.testbase import TestBase, testclasscreator, timestr_to_datetime
from TPPTAnalysisSW.utils import Timer
class TappingRepeatabilitySummarySQL(Base):
__tablename__ = 'touch_tapping_repeatability_summary'
meta_id = Column(INTEGER, primary_key=True)
time_test_start = Column(DATETIME)
test_id = Column(DECIMAL(10, 3))
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))
display_background = Column(VARCHAR(40))
number_of_random_locations_center = Column(DECIMAL(8, 2))
number_of_random_locations_edge = Column(DECIMAL(8, 2))
number_of_random_locations_corner = Column(DECIMAL(8, 2))
number_of_taps_at_each_location = Column(DECIMAL(8, 2))
lift_off_distance = Column(DECIMAL(8, 2))
ground_status = Column(VARCHAR(40))
noise_status = Column(VARCHAR(40))
touch_area = Column(VARCHAR(40))
log = Column(LONGTEXT)
touch_direction = Column(VARCHAR(40))
tapping_repeatability_avg_of_error_avgs = Column(DECIMAL(16, 3))
tapping_repeatability_total_stdev_error = Column(DECIMAL(16, 3))
tapping_repeatability_avg_of_max_errors = Column(DECIMAL(16, 3))
tapping_repeatability_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 TappingRepeatabilityResultsSQL(Base):
__tablename__ = 'touch_tapping_repeatability_results'
meta_id = Column(INTEGER, primary_key=True)
test_meta_id = Column(INTEGER, ForeignKey('touch_tapping_repeatability_summary.meta_id', ondelete='CASCADE'),
nullable=False)
point_id = Column(INTEGER)
touch_direction = Column(VARCHAR(40))
avg_tapping_repeatability = Column(DECIMAL(10, 3))
stdev_tapping_repeatability = Column(DECIMAL(10, 3))
max_tapping_repeatability = Column(DECIMAL(10, 3))
number_of_missing_points = Column(INTEGER)
class TappingRepeatabilityTest(TestBase):
""" A dummy test class for use as a template in creating new test classes """
# 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(17)
def create_testclass(*args, **kwargs):
return TappingRepeatabilityTest(*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 TappingRepeatabilityTest class """
super(TappingRepeatabilityTest, self).__init__(ddtest_row, *args, **kwargs)
# Even though one could collect all this data from many different sources
# for clarity it is better to put everything in one dict (so that nothing breaks)
self.point_db_data = {}
self.point_errors_down = []
self.point_errors_up = []
self.results = None
self.dut_info = None
self.sql_summary_class = TappingRepeatabilitySummarySQL
# Override to make necessary analysis for test session success
def runanalysis(self, *args, **kwargs):
""" Runs the analysis, return a string containing the test result """
verdict = "Fail"
self.results = self.read_test_results()
max_allowed_error_pen_down, max_allowed_error_pen_lift = get_maximum_errors(self.results['touch_area'])
up_verdict = True if not isnan(self.results['avg_of_max_up_repeatability_error']) and \
self.results['avg_of_max_up_repeatability_error'] <= max_allowed_error_pen_lift else False
down_verdict = True if not isnan(self.results['avg_of_max_down_repeatability_error']) and \
self.results['avg_of_max_down_repeatability_error'] <= max_allowed_error_pen_down else False
if up_verdict and down_verdict:
verdict = "Pass"
return 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(TappingRepeatabilityTest, self).create_common_templateparams(**kwargs)
verdict = "Pass"
self.results = self.read_test_results()
template_params['touch_area'] = self.results['touch_area']
test_parameters = self.results['test_parameters']
# We need to use here embedded list (instead of dict) to be able to decide the order of appearance
template_params['test_parameters'] = (('Border Width [mm]', test_parameters['border_width']),
('Finger Name', test_parameters['finger_name']),
('Display Background', test_parameters['display_background']),
('Number of taps at each location', test_parameters[
'number_of_taps_at_each_location']),
('Lift off distance [mm]', test_parameters['lift_off_distance']),
('Ground status', test_parameters['ground_status']),
('Touch area', self.results['touch_area']),
('Noise status', test_parameters['noise_status']))
if self.results['touch_area'] == 'center_area':
template_params['test_parameters'] = template_params['test_parameters'] + \
(('Number of random locations',
test_parameters['number_of_random_locations_center']),)
elif self.results['touch_area'] == 'edge_area':
template_params['test_parameters'] = template_params['test_parameters'] + \
(('Number of random locations, edge',
test_parameters['number_of_random_locations_edge']),)
elif self.results['touch_area'] == 'corner_area':
template_params['test_parameters'] = template_params['test_parameters'] + \
(('Number of random locations, corner',
test_parameters['number_of_random_locations_corner']),)
else:
raise Exception('Invalid touch area "' + self.results['touch_area'] + '"')
template_params['max_allowed_error_pen_down'], template_params[
'max_allowed_error_pen_lift'] = get_maximum_errors(self.results['touch_area'])
t.Time("Results")
# Add the image name and parameters to the report
template_params['figure'] = ImageFactory.create_image_name(self.test_id, 'rept')
template_params['detailed_figure'] = ImageFactory.create_image_name(self.test_id, 'rept', 'detailed')
template_params['max_down_repeatability_error'] = self.results['max_down_repeatability_error']
template_params['avg_of_max_down_repeatability_error'] = self.results['avg_of_max_down_repeatability_error']
template_params['avg_down_repeatability_error'] = self.results['avg_down_repeatability_error']
template_params['std_dev_down_repeatability_error'] = self.results['stdev_down_repeatability_error']
template_params['verdict_down'] = 'Pass' if not isnan(self.results['avg_of_max_down_repeatability_error']) and \
self.results['avg_of_max_down_repeatability_error'] <= template_params[
'max_allowed_error_pen_down'] else 'Fail'
template_params['max_lift_repeatability_error'] = self.results['max_up_repeatability_error']
template_params['avg_of_max_lift_repeatability_error'] = self.results['avg_of_max_up_repeatability_error']
template_params['avg_lift_repeatability_error'] = self.results['avg_up_repeatability_error']
template_params['std_dev_lift_repeatability_error'] = self.results['stdev_up_repeatability_error']
template_params['verdict_lift'] = 'Pass' if not isnan(self.results['avg_of_max_up_repeatability_error']) and \
self.results['avg_of_max_up_repeatability_error'] <= template_params[
'max_allowed_error_pen_lift'] else 'Fail'
template_params['num_missing_total'] = self.results['num_missing_total']
template_params['num_points_total'] = self.results['num_points_total']
template_params['missing_point_percentage'] = self.results['missing_point_percentage']
template_params['missing_point_verdict'] = self.results['missing_point_verdict']
template_params['maxmissing_in_percentage'] = settings['repeatability_missing_points']
template_params['test_page'] = 'test_tapping_repeatability.html'
template_params['test_script'] = 'test_page_subplots.js'
template_params['version'] = Version
template_params['repeatability_errors_down'] = self.results['repeatability_errors_down']
subfigures = [None] # Use one-based indexing
for point_errors in self.results['repeatability_errors_down']:
subfigures.append(ImageFactory.create_image_name(self.test_id, "repinfo_down", str(point_errors[4])))
template_params['pointPlots_down'] = subfigures
template_params['repeatability_errors_lift'] = self.results['repeatability_errors_up']
subfigures = [None] # Use one-based indexing
for point_errors in self.results['repeatability_errors_up']:
subfigures.append(ImageFactory.create_image_name(self.test_id, "repinfo_lift", str(point_errors[4])))
template_params['pointPlots_lift'] = subfigures
self.disable_upload_button_if_already_uploaded(template_params)
t.Time("Params")
template = MarkupTemplate(open("templates/test_configured_body.html"))
stream = template.generate(**template_params)
t.Time("Markup")
if template_params['verdict_down'] == 'Fail' or template_params['verdict_lift'] == 'Fail':
verdict = "Fail"
return stream.render('xhtml'), 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):
if image_name == 'rept':
# Overview image
t = Timer(1)
results = self.results
if self.dut_info is None:
self.dut_info = plotinfo.TestDUTInfo(testdut_id=self.dut['id'])
t.Time("DB")
title = 'Preview: One Finger Tapping Repeatability ' + self.dut['program']
plot_factory.plot_tapping_passfail_labels_on_target(imagepath, results, self.dut_info, *args, title=title,
**kwargs)
t.Time("Image")
elif image_name == 'repinfo':
# Individual point image
t = Timer(1)
point_id = int(args[1])
# Check if doing pen down or pen lift-off details
down = args[0] == 'down'
results = self.read_point_details(point_id, pen_down=down)
t.Time("DB")
plot_factory.plot_tapping_repeatability_details(imagepath, results, *args, **kwargs)
t.Time("Image")
else:
raise cherrypy.HTTPError(message="No such image in the report")
return None
def read_test_results(self, dutinfo=None):
dbsession = get_database().session()
if dutinfo is None:
self.dut_info = plotinfo.TestDUTInfo(testdut_id=self.dut['id'])
else:
self.dut_info = dutinfo
repeatability_test_points = dbsession.query(db.OneFingerTappingRepeatabilityTest) \
.filter(db.OneFingerTappingRepeatabilityTest.test_id == self.test_id) \
.options(joinedload(db.OneFingerTappingRepeatabilityTest.one_finger_tapping_repeatability_results)) \
.order_by(db.OneFingerTappingRepeatabilityTest.id) \
.all()
num_test_points = len(list(repeatability_test_points))
number_of_taps_at_each_location = repeatability_test_points[0].number_of_taps_at_each_location if num_test_points > 0 else 0
# Some information just needs to be passed through the analyses
test_parameters = {'border_width': repeatability_test_points[0].border_width if num_test_points > 0 else 0,
'finger_name': repeatability_test_points[0].finger_name if num_test_points > 0 else "",
'finger_type': repeatability_test_points[0].finger_type if num_test_points > 0 else "",
'finger_size': repeatability_test_points[0].finger_size if num_test_points > 0 else 0,
'display_background': repeatability_test_points[0].display_background if num_test_points > 0 else "",
'number_of_random_locations_center': repeatability_test_points[0].number_of_random_locations_center if num_test_points > 0 else 0,
'number_of_random_locations_edge': repeatability_test_points[0].number_of_random_locations_edge if num_test_points > 0 else 0,
'number_of_random_locations_corner': repeatability_test_points[0].number_of_random_locations_corner if num_test_points > 0 else 0,
'number_of_taps_at_each_location': number_of_taps_at_each_location,
'lift_off_distance': repeatability_test_points[0].lift_off_distance if num_test_points > 0 else 0,
'ground_status': repeatability_test_points[0].ground_status if num_test_points > 0 else "",
'noise_status': repeatability_test_points[0].noise_status if num_test_points > 0 else ""
}
# Group the results by the point_id for easier handling
# At the same time add a running point id as the first member of the array
results = {}
point_counter = 0
num_missing_touch_down_total = 0
num_missing_touch_up_total = 0
for point in repeatability_test_points:
# Collecting all the data per tap in dictionary (since there are multiple taps per point)
point_counter += 1
point_number = point.point_number
results_per_tap = {}
# Find touch down or touch up events from recorded event stream.
filtered_points = analyzers.filter_points(point.one_finger_tapping_repeatability_results)
for touch_data in filtered_points:
if touch_data.tap_id not in results_per_tap:
results_per_tap[touch_data.tap_id] = [touch_data]
else:
results_per_tap[touch_data.tap_id].append(touch_data)
results[point_number] = {'robot_x': point.robot_x,
'robot_y': point.robot_y,
'taps': {}}
for tap_id, touch_data in results_per_tap.items():
touch_down = None
touch_up = None
# Make sure first touch event is "touch down".
if len(touch_data) > 0 and touch_data[0].event == 0:
touch_down = (touch_data[0].panel_x, touch_data[0].panel_y)
# Make sure last touch event is "touch up".
if len(touch_data) > 0 and touch_data[-1].event == 1:
touch_up = (touch_data[-1].panel_x, touch_data[-1].panel_y)
down_up_per_tap = {'touch_down': touch_down,
'touch_up': touch_up}
results[point_number]['taps'][tap_id] = down_up_per_tap
verdicts_down = [None] * point_counter
verdicts_up = [None] * point_counter
point_numbers = [None] * point_counter
passed_points = []
failed_points = []
data_for_drawings = {}
down_accuracy_errors = [0.0] * point_counter
up_accuracy_errors = [0.0] * point_counter
for point_number, data in results.items():
x_coordinates_down = []
y_coordinates_down = []
x_coordinates_up = []
y_coordinates_up = []
point_id = point_number - 1 # point_number starts from 1
# Initially set the reference point to be the robot point
reference_x_down = data['robot_x']
reference_y_down = data['robot_y']
reference_x_up = data['robot_x']
reference_y_up = data['robot_y']
data_for_drawings[str(point_number)] = {'reference_point_down': (reference_x_down, reference_y_down),
'reference_point_up': (reference_x_up, reference_y_up),
'touch_down': [],
'touch_up': []}
# Number of taps reported by device for current location.
num_touch_down = 0
num_touch_up = 0
for tap, coords in data['taps'].items():
touch_down = coords['touch_down']
touch_up = coords['touch_up']
if touch_down is not None:
point_down = analyzers.panel_to_target((touch_down[0], touch_down[1]), self.dut_info)
x_coordinates_down.append(point_down[0])
y_coordinates_down.append(point_down[1])
data_for_drawings[str(point_number)]['touch_down'].append(point_down)
num_touch_down += 1
if touch_up is not None:
point_up = analyzers.panel_to_target((touch_up[0], touch_up[1]), self.dut_info)
x_coordinates_up.append(point_up[0])
y_coordinates_up.append(point_up[1])
data_for_drawings[str(point_number)]['touch_up'].append(point_up)
num_touch_up += 1
number_of_missing_touch_down = number_of_taps_at_each_location - num_touch_down
number_of_missing_touch_up = number_of_taps_at_each_location - num_touch_up
num_missing_touch_down_total += number_of_missing_touch_down
num_missing_touch_up_total += number_of_missing_touch_up
# Overwrite drawing reference point and calculate errors based on the average location of all the points
if x_coordinates_down:
reference_x_down = np.average(x_coordinates_down)
if y_coordinates_down:
reference_y_down = np.average(y_coordinates_down)
data_for_drawings[str(point_number)]['reference_point_down'] = (reference_x_down, reference_y_down)
down_accuracy_errors[point_id] = calculate_errors(reference_x_down, reference_y_down,
x_coordinates_down,
y_coordinates_down)
self.point_errors_down += down_accuracy_errors[point_id]
if x_coordinates_up:
reference_x_up = np.average(x_coordinates_up)
if y_coordinates_up:
reference_y_up = np.average(y_coordinates_up)
data_for_drawings[str(point_number)]['reference_point_up'] = (reference_x_up, reference_y_up)
up_accuracy_errors[point_id] = calculate_errors(reference_x_up, reference_y_up,
x_coordinates_up,
y_coordinates_up)
self.point_errors_up += up_accuracy_errors[point_id]
# Calculating the single point values
if down_accuracy_errors[point_id]:
down_accuracy_errors_array = np.array(down_accuracy_errors[point_id])
down_err_avg = np.average(down_accuracy_errors_array)
down_err_stdev = sqrt(np.mean(down_accuracy_errors_array ** 2))
down_err_max = max(down_accuracy_errors_array)
else:
down_err_avg = None
down_err_stdev = None
down_err_max = None
if up_accuracy_errors[point_id]:
up_accuracy_errors_array = np.array(up_accuracy_errors[point_id])
up_err_avg = np.average(up_accuracy_errors_array)
up_err_stdev = sqrt(np.mean(up_accuracy_errors_array ** 2))
up_err_max = max(up_accuracy_errors_array)
else:
up_err_avg = None
up_err_stdev = None
up_err_max = None
# Storing the single point values, typecasting in string just in case
self.point_db_data[str(point_number)] = {'touch_down': {'avg': analyzers.float_for_db(down_err_avg),
'stdev': analyzers.float_for_db(down_err_stdev),
'max': analyzers.float_for_db(down_err_max),
'num_missing': number_of_missing_touch_down},
'touch_up': {'avg': analyzers.float_for_db(up_err_avg),
'stdev': analyzers.float_for_db(up_err_stdev),
'max': analyzers.float_for_db(up_err_max),
'num_missing': number_of_missing_touch_up}
}
point_numbers[point_id] = point_number
max_pen_down_error, max_pen_up_error = get_maximum_errors(repeatability_test_points[0].touch_area)
if down_accuracy_errors[point_id]:
# If there are any values in the list, check the maximum against the set limit
down_error_exceeds_limit = max(down_accuracy_errors[point_id]) > analyzers.float_for_db(max_pen_down_error)
else:
# No values, consider it failed
down_error_exceeds_limit = True
# Same handling as above
if up_accuracy_errors[point_id]:
up_error_exceeds_limit = max(up_accuracy_errors[point_id]) > analyzers.float_for_db(max_pen_up_error)
else:
up_error_exceeds_limit = True
if down_error_exceeds_limit or up_error_exceeds_limit:
failed_points.append((data['robot_x'], data['robot_y'], point_id + 1))
else:
passed_points.append((data['robot_x'], data['robot_y'], point_id + 1))
verdicts_down[point_id] = "Fail" if down_error_exceeds_limit else "Pass"
verdicts_up[point_id] = "Fail" if up_error_exceeds_limit else "Pass"
try:
down_max = analyzers.float_for_db(np.max([np.max(errors) for errors in down_accuracy_errors if errors]))
down_avg_of_max = analyzers.float_for_db(np.average([np.max(errors) for errors in down_accuracy_errors if errors]))
down_avg = analyzers.float_for_db(np.average([np.average(errors) for errors in down_accuracy_errors if errors]))
except ValueError:
down_max = np.NaN
down_avg_of_max = np.NaN
down_avg = np.NaN
down_sd = analyzers.float_for_db(sqrt(np.mean(np.array(self.point_errors_down) ** 2))) if self.point_errors_down else np.NaN
try:
up_max = analyzers.float_for_db(np.max([np.max(errors) for errors in up_accuracy_errors if errors]))
up_avg_of_max = analyzers.float_for_db(np.average([np.max(errors) for errors in up_accuracy_errors if errors]))
up_avg = analyzers.float_for_db(np.average([np.average(errors) for errors in up_accuracy_errors if errors]))
except ValueError:
up_max = np.NaN
up_avg_of_max = np.NaN
up_avg = np.NaN
up_sd = analyzers.float_for_db(sqrt(np.mean(np.array(self.point_errors_up) ** 2))) if self.point_errors_up else np.NaN
# Total number of points including both "touch up" and "touch down".
num_points_total = len(list(repeatability_test_points)) * number_of_taps_at_each_location * 2
num_missing_total = num_missing_touch_down_total + num_missing_touch_up_total
# Missing points do not affect the total verdict
if num_points_total > 0:
missing_point_percentage = 100 * num_missing_total / num_points_total
if missing_point_percentage <= settings['repeatability_missing_points']:
missing_point_verdict = 'Pass'
else:
missing_point_verdict = 'Fail'
else:
missing_point_percentage = 100
missing_point_verdict = 'Fail'
results = {'border_width': repeatability_test_points[0].border_width if num_test_points > 0 else 0,
'max_down_repeatability_error': down_max,
'avg_of_max_down_repeatability_error': down_avg_of_max,
'avg_down_repeatability_error': down_avg,
'stdev_down_repeatability_error': down_sd,
'max_up_repeatability_error': up_max,
'avg_of_max_up_repeatability_error': up_avg_of_max,
'avg_up_repeatability_error': up_avg,
'stdev_up_repeatability_error': up_sd,
'repeatability_errors_down': format_error_list(down_accuracy_errors, verdicts_down, point_numbers),
'repeatability_errors_up': format_error_list(up_accuracy_errors, verdicts_up, point_numbers),
'passed_points': passed_points,
'failed_points': failed_points,
'touch_area': repeatability_test_points[0].touch_area if num_test_points > 0 else "",
'test_parameters': test_parameters,
'data_for_drawings': data_for_drawings,
'num_missing_touch_down_total': num_missing_touch_down_total,
'num_missing_touch_up_total': num_missing_touch_up_total,
'num_missing_total': num_missing_total,
'num_points_total': num_points_total,
'missing_point_percentage': missing_point_percentage,
'missing_point_verdict': missing_point_verdict}
dbsession.close()
return results
def read_point_details(self, point_id, pen_down):
"""
Runs read_test_results and collects data from there in the form that the picture drawing function
requires it
:param point_id:
:param pen_down:
:param dutinfo:
:return:
"""
all_results = self.results
point_results = all_results['data_for_drawings'][str(point_id)]
error_settings = get_maximum_errors(all_results['touch_area'])
max_error = error_settings[0] if pen_down else error_settings[1]
reference_point = point_results['reference_point_down'] if pen_down else point_results['reference_point_up']
points_arr = []
results = {}
if pen_down:
for point in point_results['touch_down']:
points_arr.append(point)
else: # pen_up
for point in point_results['touch_up']:
points_arr.append(point)
failed, failed_count, passed, passed_count = analyze_points(max_error, points_arr, reference_point)
results['reference_point'] = reference_point
results['error_radius'] = max_error
results['passed_points'] = passed
results['passed_points_count'] = passed_count
results['failed_points'] = failed
results['failed_points_count'] = failed_count
return results
def upload_sql_data(self, session):
# Add test summary to database
if self.results is not None:
test_results = self.results
else:
self.results = self.read_test_results()
test_results = self.results
test_item = self.get_test_item()
test_session = self.get_test_session()
for touch_dir in ['touch_down', 'touch_up']:
summary = TappingRepeatabilitySummarySQL()
summary.test_id = self.test_id
test_parameters = test_results['test_parameters']
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_parameters['border_width']
summary.finger_name = test_parameters['finger_name']
summary.finger_type = test_parameters['finger_type']
summary.finger_size = test_parameters['finger_size']
summary.display_background = test_parameters['display_background']
summary.number_of_random_locations_center = test_parameters['number_of_random_locations_center']
summary.number_of_random_locations_edge = test_parameters['number_of_random_locations_edge']
summary.number_of_random_locations_corner = test_parameters['number_of_random_locations_corner']
summary.number_of_taps_at_each_location = test_parameters['number_of_taps_at_each_location']
summary.lift_off_distance = test_parameters['lift_off_distance']
summary.ground_status = test_parameters['ground_status']
summary.noise_status = test_parameters['noise_status']
summary.touch_area = test_results['touch_area']
summary.log = self.test_item['kmsg_log'] # The test_item comes form Testbase
summary.touch_direction = touch_dir
# Separate results for different touch directions
if touch_dir == 'touch_down':
summary.tapping_repeatability_avg_of_error_avgs = test_results['avg_down_repeatability_error']
summary.tapping_repeatability_total_stdev_error = test_results['stdev_down_repeatability_error']
summary.tapping_repeatability_avg_of_max_errors = test_results['avg_of_max_down_repeatability_error']
summary.tapping_repeatability_max_of_max_errors = test_results['max_down_repeatability_error']
summary.total_number_of_missing_points = test_results['num_missing_touch_down_total']
else: # touch up
summary.tapping_repeatability_avg_of_error_avgs = test_results['avg_up_repeatability_error']
summary.tapping_repeatability_total_stdev_error = test_results['stdev_up_repeatability_error']
summary.tapping_repeatability_avg_of_max_errors = test_results['avg_of_max_up_repeatability_error']
summary.tapping_repeatability_max_of_max_errors = test_results['max_up_repeatability_error']
summary.total_number_of_missing_points = test_results['num_missing_touch_up_total']
session.add(summary)
session.commit()
# Add individual points to database (NOTE: test meta_id is only created
# when the summary is added to database)
test_meta_id = summary.meta_id
# Write results for individual points
if test_results['touch_area'] == 'center_area':
num_of_points = test_parameters['number_of_random_locations_center']
elif test_results['touch_area'] == 'edge_area':
num_of_points = test_parameters['number_of_random_locations_edge'] * 4 # there are 4 edges
elif test_results['touch_area'] == 'corner_area':
num_of_points = test_parameters['number_of_random_locations_corner'] * 4 # there are 4 corners
else:
raise Exception('Invalid touch area')
for point in range(num_of_points):
point_id = point + 1
results = TappingRepeatabilityResultsSQL()
# Writing touch specific data to the database
results.touch_direction = touch_dir
results.test_meta_id = test_meta_id
results.point_id = point_id
# NOTE: we might not have results for all the points!
try:
touch_data = self.point_db_data[str(point_id)][touch_dir]
results.avg_tapping_repeatability = touch_data['avg']
results.stdev_tapping_repeatability = touch_data['stdev']
results.max_tapping_repeatability = touch_data['max']
results.number_of_missing_points = touch_data['num_missing']
except:
results.avg_tapping_repeatability = None
results.stdev_tapping_repeatability = None
results.max_tapping_repeatability = None
pass
session.add(results)
session.commit()
##############################################
# Auxiliary functions
##############################################
def analyze_points(max_error, points_arr, reference_point):
failed = []
failed_count = []
passed = []
passed_count = []
if len(points_arr) > 0:
for point in points_arr:
point_error = sqrt(
(reference_point[0] - point[0]) ** 2 + (reference_point[1] - point[1]) ** 2)
if point_error > max_error:
if point in failed:
failed_count[failed.index(point)] += 1
else:
failed.append(point)
failed_count.append(1)
else:
if point in passed:
passed_count[passed.index(point)] += 1
else:
passed.append(point)
passed_count.append(1)
return failed, failed_count, passed, passed_count
def format_error_list(accuracy_errors, verdicts, points):
max_errors = []
avg_errors = []
stdev_errors = []
for error_list in accuracy_errors:
try:
max_errors.append(max(error_list))
avg_errors.append(np.average(error_list))
stdev_errors.append(np.std(error_list))
except ValueError:
max_errors.append(np.NaN)
avg_errors.append(np.NaN)
stdev_errors.append(np.NaN)
return list(
zip(analyzers.float_for_db_array(max_errors),
analyzers.float_for_db_array(avg_errors),
analyzers.float_for_db_array(stdev_errors),
verdicts,
points))
def calculate_errors(reference_x, reference_y, x_coordinates, y_coordinates):
differences = [(x - reference_x, y - reference_y) for (x, y) in zip(x_coordinates, y_coordinates)]
return [sqrt(x ** 2 + y ** 2) for (x, y) in differences]
def get_maximum_errors(touch_area):
max_pen_down_error = 0.0
max_pen_up_error = 0.0
if touch_area == 'edge_area':
max_pen_down_error = settings['max_error_edge_down']
max_pen_up_error = settings['max_error_edge_up']
elif touch_area == 'center_area':
max_pen_down_error = settings['max_error_center_down']
max_pen_up_error = settings['max_error_center_up']
elif touch_area == 'corner_area':
max_pen_down_error = settings['max_error_corner_down']
max_pen_up_error = settings['max_error_corner_up']
return max_pen_down_error, max_pen_up_error