Add in a squareness test This CL adds a "squareness" test to the firmware test suite. Essentially, we've seen a few firmwares for touch devices come in that don't have a squared resolution. eg: movement is compressed in x or y. When this happens if you draw a 45 degree line on the pad, the height and width will not be exactly the same, and it will have a distorted angle.. This test has the robot/operator draw a 45 deg line on the pad, then looks at the angle formed by these points. It should be right about 45 if the resolution is correct. BUG=none TEST=manually tested. Change-Id: Id7536c7371418d622684b3b8f67d1d43376d5554 Signed-off-by: Charlie Mooney <charliemooney@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/326470
diff --git a/gesture_interpreter.py b/gesture_interpreter.py index 76f9711..6fbd3f0 100644 --- a/gesture_interpreter.py +++ b/gesture_interpreter.py
@@ -222,6 +222,9 @@ elif test.name == tests.DRAG_THUMB_EDGE: fn = lambda: _PerformThumbEdgeTest(variation, robot, device_spec) + elif test.name == tests.SQUARE_RESOLUTION: + fn = lambda: _PerformSquareResolutionTest(variation, robot, device_spec) + if fn is None: print color.Fore.RED + 'Robot unable to perform gesture! Skipping...' return None @@ -336,3 +339,8 @@ robot.fingertips[STANDARD_SECONDARY_FINGERTIP]] location = LOCATION_COORDINATES[tests.GV.CENTER] robot.Drumroll(device_spec, fingertips, location) + +def _PerformSquareResolutionTest(variation, robot, device_spec): + fingertip = robot.fingertips[STANDARD_FINGERTIP] + location = LOCATION_COORDINATES[tests.GV.CENTER] + robot.Draw45DegreeLineSegment(device_spec, [fingertip], location)
diff --git a/tests/test_configurations.py b/tests/test_configurations.py index a0c791c..5a46f9e 100644 --- a/tests/test_configurations.py +++ b/tests/test_configurations.py
@@ -45,6 +45,7 @@ FIRST_FINGER_TRACKING_AND_SECOND_FINGER_TAPS = \ 'first_finger_tracking_and_second_finger_taps' DRUMROLL = 'drumroll' +SQUARE_RESOLUTION = 'square_resolution' REPEATED_TAPS = 'repeated_taps_20' ONE_FINGER_TRACKING_FROM_CENTER = 'one_finger_tracking_from_center' @@ -54,7 +55,7 @@ PINCH_TO_ZOOM, ONE_FINGER_TAP, TWO_FINGER_TAP, THREE_FINGER_TAP, FOUR_FINGER_TAP, FIVE_FINGER_TAP, ONE_FINGER_PHYSICAL_CLICK, TWO_FINGER_PHYSICAL_CLICK, STATIONARY_FINGER_NOT_AFFECTED_BY_2ND_FINGER_TAPS, - FAT_FINGER_MOVE_WITH_RESTING_FINGER, DRAG_THUMB_EDGE, + FAT_FINGER_MOVE_WITH_RESTING_FINGER, DRAG_THUMB_EDGE, SQUARE_RESOLUTION, TWO_CLOSE_FINGERS_TRACKING, RESTING_FINGER_PLUS_2ND_FINGER_MOVE, TWO_FAT_FINGERS_TRACKING, FIRST_FINGER_TRACKING_AND_SECOND_FINGER_TAPS, DRUMROLL, REPEATED_TAPS, ONE_FINGER_TRACKING_FROM_CENTER @@ -78,12 +79,31 @@ PINCH_CRITERIA_MM = '>= 15, ~ -5' RELAXED_LINEARITY_CRITERIA_MM = '<= 1.5, ~ +3.0' STATIONARY_FINGER_CRITERIA_MM = '<= 1.0' +SQUARENESS_CRITERIA_RATIO = '~= 45.0, ~ +4' RELAXED_STATIONARY_FINGER_CRITERIA_MM = '<= 3.0' TAP_ACCURACY_CRITERIA_MM = '<= 0.5, ~ +0.5' # Definitions for the test specifics tests_dict = { + SQUARE_RESOLUTION: + Test( + name=SQUARE_RESOLUTION, + variations=(GV.CENTER,), + prompt='Use one finger and a jig to draw ~2cm 45 degree line segment in ' + 'the center of the pad. Make sure it is exactly 45 degrees away ' + 'from the bottom edge of the sensor and keep all lines in the ' + 'center of the pad -- lines drawn near/off edges can cause false ' + 'negatives for this test.', + subprompt={ + GV.CENTER: ('center',), + }, + validators=( + FingerCountValidator('== 1'), + ReportRateValidator(REPORT_RATE_CRITERIA_HTZ), + LineAngleValidator(SQUARENESS_CRITERIA_RATIO), + ), + ), NOISE_STATIONARY: Test( name=NOISE_STATIONARY,
diff --git a/tests/validator/fuzzy.py b/tests/validator/fuzzy.py index e0b99b0..9d4c66d 100644 --- a/tests/validator/fuzzy.py +++ b/tests/validator/fuzzy.py
@@ -90,6 +90,34 @@ return (0.5 + 0.5 * math.cos((x - self.middle) / self.width_left * math.pi)) +class FuzzyPiMemberFunction(FuzzyMemberFunctions): + """A class provides fuzzy Pi Membership Function. + This is implemented as a generalized bell membership function with + parameters to make the top of the bell flattened, producing the "pi" + shape we're looking for. + + Pi Membership Function: + parameters: (target, acceptable_deviation) + grade(x) = 1 for x very close to the target + = 0 for x farther than acceptable_deviation away from target + E.g., FuzzyPiMemberFunction((1.0, 0.2)) + Usage: when we want the x == 1, but between 0.8 and 1.2 is okay + grade = 1.0, when x == 1.0 + > 0.0, when 0.8 <= x <= 1.2 + ~= 0.0, when x < 0.8 or x > 1.2 + """ + + def __init__(self, para): + super(FuzzyPiMemberFunction, self).__init__(para) + self.target, self.acceptable_deviation = self.para_values + + def grade(self, x): + """The grading method of the fuzzy membership function.""" + def gbellmf(x, width, flatness_on_top, center): + return 1.0 / (1.0 + (abs((x - center) / width) ** (2 * flatness_on_top))) + + PI_MF_FLATNESS = 4 + return gbellmf(x, self.acceptable_deviation, PI_MF_FLATNESS, self.target) class FuzzySMemberFunction(FuzzyMemberFunctions): """A class provides fuzzy S Membership Function. @@ -163,8 +191,8 @@ # Mapping from membership functions to the fuzzy member function classes. MF_DICT = { - # TODO(josephsih): PI, TRAPEZ, and TRIANGLE functions are to be implemented. - # PI_FUNCTION: FuzzyPiMemberFunction, + # TODO(josephsih): TRAPEZ, and TRIANGLE functions are to be implemented. + PI_FUNCTION: FuzzyPiMemberFunction, SINGLETON_FUNCTION: FuzzySingletonMemberFunction, S_FUNCTION: FuzzySMemberFunction, # TRAPEZ_FUNCTION: FuzzyTrapezMemberFunction, @@ -203,8 +231,7 @@ op_value = None for c in criteria_list: op, value = c.split() - # TODO(josephsih): should support and '~=' later. - if op in ['<=', '<', '>=', '>', '==']: + if op in ['<=', '<', '>=', '>', '==', '~=']: primary_op = op self.default_mf_name = DEFAULT_MEMBERSHIP_FUNCTION[op] op_value = float(value) @@ -227,6 +254,8 @@ if primary_op in ['<=', '<', '>=', '>']: self.value_range = (range_min, range_max) + elif primary_op == '~=': + self.value_range = (op_value, tolerable_delta[0]) elif primary_op == '==': self.value_range = (range_min, op_value, range_max) else:
diff --git a/tests/validator/validators.py b/tests/validator/validators.py index 3494012..76f7e56 100644 --- a/tests/validator/validators.py +++ b/tests/validator/validators.py
@@ -148,6 +148,53 @@ return result +class LineAngleValidator(BaseValidator): + """ Validator to check the angle formed between a line drawn on the + pad, and the bottom edge of the sensor. This is intended to detect + when the resolution is compressed or stretched in one direction. In + such a situation, the angle will be distorted. + + Example: + To verify that the angle is close to 45 degrees: + FingerCountValidator('~= 45.0, ~ 5') + """ + def __init__(self, criteria_str, mf=None): + name = self.__class__.__name__ + desc = ("What angle does the reported line form with the bottom edge " + "of the sensor? If a device fails this test, it means that " + "movement appears to be compressed along x or y, resulting " + "in a distorted angle.") + + super(LineAngleValidator, self).__init__(criteria_str, mf, name, desc) + + def Validate(self, snapshots): + # Sort the paths by length, to find the main one + paths = sorted(self._SeparatePaths(snapshots), key=lambda x: len(x)) + angle = float('inf') + if paths: + longest_path = paths[-1] + # Segment the path to find the straightest parts (in the middle) + start, middle, end = self._SegmentPath(longest_path) + xs = [finger.x for finger in middle] + ys = [finger.y for finger in middle] + x_delta = max(xs) - min(xs) + y_delta = max(ys) - min(ys) + if x_delta or y_delta: + angle = math.degrees(np.arctan2(y_delta, x_delta)) + + result = Result() + result.name = self.name + result.description = self.description + result.units = 'degrees' + result.criteria = self.criteria_str + result.observed = angle + result.score = self.fc.mf.grade(result.observed) + if not paths: + result.error = 'Cannot measure unless at least one moving finger is seen.' + + return result + + class ReportRateValidator(BaseValidator): """ Validator to check the report rate.
diff --git a/touchbot/touchbot.py b/touchbot/touchbot.py index e0008a6..ffb66bf 100644 --- a/touchbot/touchbot.py +++ b/touchbot/touchbot.py
@@ -1010,3 +1010,42 @@ # Lift the fingers both up and return the robot to its original speed self._MoveBothFingersAbsolute(0, 0) self.PopSpeed() + + def Draw45DegreeLineSegment(self, device_spec, fingertips, location): + """ Draw a short line segment 45 degrees off from the x axis of + the touch device. This gesture is slightly different than the others, + in that it uses real measurements instead of reletive to the dimensions + of the sensor. This is to allow us to test and see if the resolution + of the touch device is configured squarely. ie: if the robot draws + what we know is a 45 degree angle, and the touchpad reports it as + 45, then we know the resolution is configured correctly, otherwise + motion is being compressed on one of the axes. + """ + LINE_LENGTH_MM = 10 + # Prepare for the gesture, by getting the required fingertips + self._RequireFingertips(device_spec, fingertips) + + # Compute the positions of the corners of the square + center = device_spec.RelativePosToAbsolutePos(location) + center.ax_5 = self._SuggestSpacing(fingertips) + center = self._CenterForFingertip(fingertips[0], center) + + start = copy.copy(center) + start.x -= LINE_LENGTH_MM // 2 + start.y -= LINE_LENGTH_MM // 2 + + end = copy.copy(center) + end.x += LINE_LENGTH_MM // 2 + end.y += LINE_LENGTH_MM // 2 + + # Get the robot into position, touching the starting position + self.PushSpeed(self.SPEED_MEDIUM) + self._SetCartesian(start, finger_distance=start.ax_5) + self._ExtendFingerUntilContact(1) + self.PopSpeed() + + # Have the robot trace slowly between the computer points + self.PushSpeed(self.SPEED_SLOW) + self._SetCartesian(end, finger_distance=end.ax_5) + self.PopSpeed() + self._MoveBothFingersAbsolute(0, 0)