diff --git a/backend/aim/metrics/m26/__init__.py b/backend/aim/metrics/m26/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/aim/metrics/m26/m26_mirror_symmetry_contour_pixel.py b/backend/aim/metrics/m26/m26_mirror_symmetry_contour_pixel.py new file mode 100644 index 0000000..37d886c --- /dev/null +++ b/backend/aim/metrics/m26/m26_mirror_symmetry_contour_pixel.py @@ -0,0 +1,290 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Metric: + Mirror symmetry - contour pixel + + +Description: + The similarity of an object reflection across a straight axis. The measure + considered contour pixels of image. In this metric contour symmetry + was measured by looking for a match for each contour pixel across the central + vertical axis. If a contour pixel had a counterpart across the main vertical + axis, it was counted as a symmetrical pixel. The metric compute ratio of + symmetrical pixels to all edge pixels and normalized it by edge density. + + Category: Organization of Information > Symmetry. + + +Funding information and contact: + This work was funded by Technology Industries of Finland in a three-year + project grant on self-optimizing web services. The principal investigator + is Antti Oulasvirta (antti.oulasvirta@aalto.fi) of Aalto University. + + +References: + 1. Miniukovich, A. and De Angeli, A. (2014). Quantification of Interface + Visual Complexity. In Proceedings of the 2014 International Working + Conference on Advanced Visual Interfaces (AVI '14), pp. 153-160. ACM. + doi: https://doi.org/10.1145/2598153.2598173 + + 2. Miniukovich, A. and De Angeli, A. (2014). Visual Impressions of Mobile + App Interfaces. In Proceedings of the 8th Nordic Conference on + Human-Computer Interaction (NordiCHI '14), pp. 31-40. ACM. + doi: https://doi.org/10.1145/2639189.2641219 + + +Change log: + v2.0 (2022-12-24) + * Revised implementation + + v1.0 (2017-05-29) + * Initial implementation +""" + + +# ---------------------------------------------------------------------------- +# Imports +# ---------------------------------------------------------------------------- + +# Standard library modules +import base64 +from io import BytesIO +from typing import Any, Dict, List, Optional, Tuple, Union + +# Third-party modules +import cv2 +import numpy as np +from PIL import Image +from pydantic import HttpUrl + +# First-party modules +from aim.common.constants import GUI_TYPE_DESKTOP, GUI_TYPE_MOBILE +from aim.metrics.interfaces import AIMMetricInterface + +# ---------------------------------------------------------------------------- +# Metadata +# ---------------------------------------------------------------------------- + +__author__ = "Amir Hossein Kargaran, Markku Laine, Thomas Langerak" +__date__ = "2022-12-24" +__email__ = "markku.laine@aalto.fi" +__version__ = "2.0" + + +# ---------------------------------------------------------------------------- +# Metric +# ---------------------------------------------------------------------------- + + +class Metric(AIMMetricInterface): + """ + Metric: Mirror Symmetry (Contour Pixel). + """ + + # Private constants + _CANNY_EDGE_DETECTION_MATLAB_LOW_THRESHOLD_DESKTOP: float = 0.11 + _CANNY_EDGE_DETECTION_MATLAB_HIGH_THRESHOLD_DESKTOP: float = 0.27 + _CANNY_EDGE_DETECTION_PYTHON_LOW_THRESHOLD_MOBILE: int = 50 + _CANNY_EDGE_DETECTION_PYTHON_HIGH_THRESHOLD_MOBILE: int = 50 + _CANNY_EDGE_DETECTION_PYTHON_MIN_THRESHOLD: int = 0 + _CANNY_EDGE_DETECTION_PYTHON_MAX_THRESHOLD: int = 255 + _GAUSSIAN_KERNEL_SIZE: Tuple[int, int] = (0, 0) + _GAUSSIAN_KERNEL_STANDARD_DEVIATION: int = 2 + _KEY_RADIUS: int = 3 + _SYMMETRY_RADIUS: int = 4 + + @staticmethod + def _get_pixels_in_radius( + x: int, y: int, width: int, height: int, radius: int + ) -> List[List[int]]: + # Get x border + if x < radius: + rad_x_left: int = -x + rad_x_right: int = radius + elif width - x < radius: + rad_x_right = 1 * (width - x) + rad_x_left = -radius + else: + rad_x_left = -radius + rad_x_right = radius + + # Get y borders + if y < radius: + rad_y_top: int = -y + rad_y_bottom: int = radius + elif height - y < radius: + rad_y_bottom = 1 * (height - y) + rad_y_top = -radius + else: + rad_y_top = -radius + rad_y_bottom = radius + + pixels: List[List[int]] = [] + for m in range(rad_x_left, rad_x_right): + for n in range(rad_y_top, rad_y_bottom): + if m != 0 or n != 0: + pixel: List[int] = [x + m, y + n] + pixels.append(pixel) + + return pixels + + # Public methods + @classmethod + def execute_metric( + cls, + gui_image: str, + gui_type: int = GUI_TYPE_DESKTOP, + gui_segments: Optional[Dict[str, Any]] = None, + gui_url: Optional[HttpUrl] = None, + ) -> Optional[List[Union[int, float, str]]]: + """ + Execute the metric. + + Args: + gui_image: GUI image (PNG) encoded in Base64 + + Kwargs: + gui_type: GUI type, desktop = 0 (default), mobile = 1 + gui_segments: GUI segments (defaults to None) + gui_url: GUI URL (defaults to None) + + Returns: + Results (list of measures) + - Normalized pixel symmetry (float, [0, +inf]) + """ + # Create PIL image + img: Image.Image = Image.open(BytesIO(base64.b64decode(gui_image))) + + # Convert image from ??? (should be RGBA) to L (grayscale) color space + img_l: Image.Image = img.convert("L") + + # Get NumPy array + img_l_nparray: np.ndarray = np.array(img_l) + + # Gaussian filter parameters + ksize: Tuple[int, int] = cls._GAUSSIAN_KERNEL_SIZE + sigma: int = cls._GAUSSIAN_KERNEL_STANDARD_DEVIATION + # Note 1: ksize.width and ksize.height can differ but they both must + # be positive and odd. Or, they can be zero's and then they are + # computed from sigma. For details, see + # https://docs.opencv.org/4.4.0/d4/d86/group__imgproc__filter.html#gaabe8c836e97159a9193fb0b11ac52cf1 + # Note 2: According to the following link + # (https://dsp.stackexchange.com/questions/4716/differences-between-opencv-canny-and-matlab-canny), + # OpenCV's GaussianBlur() function + # (https://docs.opencv.org/4.4.0/d4/d86/group__imgproc__filter.html#gaabe8c836e97159a9193fb0b11ac52cf1) + # with sigma=2 mimics the default sigma (sqrt(2)) in MATLAB Canny. + + # Smooth image + img_blurred_nparray: np.ndarray = cv2.GaussianBlur( + src=img_l_nparray, ksize=ksize, sigmaX=sigma, sigmaY=sigma + ) + + # Canny edge detection parameters + low_threshold: float = ( + cls._CANNY_EDGE_DETECTION_PYTHON_LOW_THRESHOLD_MOBILE + if gui_type == GUI_TYPE_MOBILE + else round( + cls._CANNY_EDGE_DETECTION_PYTHON_MAX_THRESHOLD + * cls._CANNY_EDGE_DETECTION_MATLAB_LOW_THRESHOLD_DESKTOP, + 2, + ) + ) # 50 or 28.05 [0, 255] for mobile and desktop GUIs, respectively + high_threshold: float = ( + cls._CANNY_EDGE_DETECTION_PYTHON_HIGH_THRESHOLD_MOBILE + if gui_type == GUI_TYPE_MOBILE + else round( + cls._CANNY_EDGE_DETECTION_PYTHON_MAX_THRESHOLD + * cls._CANNY_EDGE_DETECTION_MATLAB_HIGH_THRESHOLD_DESKTOP, + 2, + ) + ) # 50 or 68.85 [0, 255] for mobile and desktop GUIs, respectively + # Note 1: According to [3], low and high thresholds for desktop GUIs + # were set to 0.11 and 0.27 (MATLAB), respectively. However, MATLAB's + # edge() function (https://www.mathworks.com/help/images/ref/edge.html) + # with the 'Canny' method expects thresholds to be in the range of + # [0, 1], whereas OpenCV's Canny() function + # (https://docs.opencv.org/4.4.0/dd/d1a/group__imgproc__feature.html#ga04723e007ed888ddf11d9ba04e2232de) + # does not specify the scale. According to the following link + # (https://www.pyimagesearch.com/2015/04/06/zero-parameter-automatic-canny-edge-detection-with-python-and-opencv/), + # OpenCV's Canny() function expects thresholds to be in the range of + # [0, 255], though. + # Note 2: According to a personal discussion with Miniukovich, both + # low and high thresholds for mobile GUIs were set to 50 on the 0-255 + # scale. This information is not present in [2]. + + # Detect edges + img_contours_nparray: np.ndarray = cv2.Canny( + image=img_blurred_nparray, + threshold1=low_threshold, + threshold2=high_threshold, + ) + + # Calculate image shape and number of all pixels + img_shape: Tuple[int, ...] = img_contours_nparray.shape + height: int = img_shape[0] + width: int = img_shape[1] + n_all_pixels: int = height * width # height * width + + # Set all pixels in radius of an edge pixel to 0 + n_all_keys: int = 0 + for y in range(height): + for x in range(width): + if img_contours_nparray[y][x] != 0: + n_all_keys += 1 + pixels_in_radius: List[ + List[int] + ] = cls._get_pixels_in_radius( + x, y, width, height, cls._KEY_RADIUS + ) + for pixel in pixels_in_radius: + img_contours_nparray[pixel[1], pixel[0]] = 0 + + # Check vertical symmetry + n_all_sym: int = 0 + for y in range(height): + for x in range(int(width / 2)): + if img_contours_nparray[y][x] != 0: + vertical_pixels: List[ + List[int] + ] = cls._get_pixels_in_radius( + width - x, y, width, height, cls._SYMMETRY_RADIUS + ) + horizontal_pixels: List[ + List[int] + ] = cls._get_pixels_in_radius( + x, height - y, width, height, cls._SYMMETRY_RADIUS + ) + + for pixel in vertical_pixels: + if ( + img_contours_nparray[int(pixel[1]), int(pixel[0])] + != 0 + ): + n_all_sym += 1 + break + + for pixel in horizontal_pixels: + if ( + img_contours_nparray[int(pixel[1]), int(pixel[0])] + != 0 + ): + n_all_sym += 1 + break + + # Compute normalized pixel symmetry + try: + sym_normalized: float = (float(n_all_sym) / float(n_all_keys)) * ( + ( + float((n_all_keys - 1) * cls._SYMMETRY_RADIUS) + / float(n_all_pixels) + ) + ** -1 + ) + except ZeroDivisionError: + sym_normalized = 0.0 + + return [ + sym_normalized, + ] diff --git a/backend/data/evaluations/ALEXA_500/m26_0_histogram.png b/backend/data/evaluations/ALEXA_500/m26_0_histogram.png new file mode 100644 index 0000000..33ce92b Binary files /dev/null and b/backend/data/evaluations/ALEXA_500/m26_0_histogram.png differ diff --git a/backend/data/tests/input_values/duckduckgo.com.png b/backend/data/tests/input_values/duckduckgo.com.png new file mode 100644 index 0000000..8cf6cf1 Binary files /dev/null and b/backend/data/tests/input_values/duckduckgo.com.png differ diff --git a/backend/tests/metrics/test_m26.py b/backend/tests/metrics/test_m26.py new file mode 100644 index 0000000..e20513c --- /dev/null +++ b/backend/tests/metrics/test_m26.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +Tests for the 'Mirror symmetry - contour pixel' metric (m26). +""" + + +# ---------------------------------------------------------------------------- +# Imports +# ---------------------------------------------------------------------------- + +# Standard library modules +import pathlib +from typing import Any, List, Optional, Union + +# Third-party modules +import pytest + +# First-party modules +from aim.common import image_utils +from aim.metrics.m26.m26_mirror_symmetry_contour_pixel import Metric +from tests.common.constants import DATA_TESTS_INPUT_VALUES_DIR + +# ---------------------------------------------------------------------------- +# Metadata +# ---------------------------------------------------------------------------- + +__author__ = "Amir Hossein Kargaran, Markku Laine" +__date__ = "2021-12-24" +__email__ = "markku.laine@aalto.fi" +__version__ = "1.0" + + +# ---------------------------------------------------------------------------- +# Tests +# ---------------------------------------------------------------------------- + + +@pytest.mark.parametrize( + ["input_value", "expected_results"], + [ + ("wikipedia.org_website.png", [1.72452]), + ("duckduckgo.com.png", [4.07753]), + ("aalto.fi_website.png", [0.45583]), + ("transparent.png", [0.0]), # transparent -> white pixels + ("white.png", [0.0]), + ("black.png", [0.0]), + ("gray.png", [0.0]), + ("red.png", [0.0]), + ("green.png", [0.0]), + ("blue.png", [0.0]), + ( + "white_50_transparent_50.png", + [0.0], + ), # transparent -> white pixels + ( + "black_50_transparent_50.png", + [3.60452], + ), # transparent -> white pixels + ("white_50_black_50.png", [3.60452]), + ("red_50_green_50.png", [3.60452]), + ("green_50_blue_50.png", [3.60452]), + ("blue_50_red_50.png", [3.60452]), + ("4_high-contrast_shades_of_gray.png", [0.79900]), + ("4_low-contrast_shades_of_gray.png", [0.0]), + ], +) +def test_mirror_symmetry_contour_pixel_desktop( + input_value: str, expected_results: List[Any] +) -> None: + """ + Test mirror symmetry - contour pixel (desktop GUIs). + + Args: + input_value: GUI image file name + expected_results: Expected results (list of measures) + """ + # Build GUI image file path + gui_image_filepath: pathlib.Path = ( + pathlib.Path(DATA_TESTS_INPUT_VALUES_DIR) / input_value + ) + + # Read GUI image (PNG) + gui_image_png_base64: str = image_utils.read_image(gui_image_filepath) + + # Execute metric + result: Optional[List[Union[int, float, str]]] = Metric.execute_metric( + gui_image_png_base64 + ) + + # Test result + if result is not None and isinstance(result[0], float): + assert round(result[0], 5) == expected_results[0] diff --git a/frontend/src/assets/results/m26_results.json b/frontend/src/assets/results/m26_results.json new file mode 100644 index 0000000..a6f5bf5 --- /dev/null +++ b/frontend/src/assets/results/m26_results.json @@ -0,0 +1,1283 @@ +[ + { + "m26_0":0.7544 + }, + { + "m26_0":0.9789 + }, + { + "m26_0":0.8353 + }, + { + "m26_0":0.7491 + }, + { + "m26_0":0.5236 + }, + { + "m26_0":0.5643 + }, + { + "m26_0":1.0348 + }, + { + "m26_0":0.4838 + }, + { + "m26_0":0.5462 + }, + { + "m26_0":0.331 + }, + { + "m26_0":0.7325 + }, + { + "m26_0":0.4272 + }, + { + "m26_0":1.0808 + }, + { + "m26_0":0.8214 + }, + { + "m26_0":1.5312 + }, + { + "m26_0":1.4307 + }, + { + "m26_0":0.397 + }, + { + "m26_0":0.6842 + }, + { + "m26_0":0.3903 + }, + { + "m26_0":1.0954 + }, + { + "m26_0":0.5637 + }, + { + "m26_0":1.5646 + }, + { + "m26_0":0.807 + }, + { + "m26_0":0.6801 + }, + { + "m26_0":0.7086 + }, + { + "m26_0":0.4404 + }, + { + "m26_0":0.4294 + }, + { + "m26_0":0.8096 + }, + { + "m26_0":0.6579 + }, + { + "m26_0":0.6868 + }, + { + "m26_0":0.4711 + }, + { + "m26_0":0.2891 + }, + { + "m26_0":0.8354 + }, + { + "m26_0":0.7484 + }, + { + "m26_0":1.3007 + }, + { + "m26_0":0.7217 + }, + { + "m26_0":0.6525 + }, + { + "m26_0":0.3348 + }, + { + "m26_0":1.1295 + }, + { + "m26_0":0.5484 + }, + { + "m26_0":0.8531 + }, + { + "m26_0":0.6131 + }, + { + "m26_0":2.9378 + }, + { + "m26_0":0.5264 + }, + { + "m26_0":0.5275 + }, + { + "m26_0":0.7649 + }, + { + "m26_0":0.7058 + }, + { + "m26_0":0.927 + }, + { + "m26_0":0.4679 + }, + { + "m26_0":0.7533 + }, + { + "m26_0":1.1651 + }, + { + "m26_0":0.7957 + }, + { + "m26_0":0.4774 + }, + { + "m26_0":0.6851 + }, + { + "m26_0":0.8579 + }, + { + "m26_0":0.5121 + }, + { + "m26_0":0.6403 + }, + { + "m26_0":0.7225 + }, + { + "m26_0":0.9819 + }, + { + "m26_0":1.0707 + }, + { + "m26_0":0.3858 + }, + { + "m26_0":1.0223 + }, + { + "m26_0":0.9584 + }, + { + "m26_0":0.3523 + }, + { + "m26_0":0.8553 + }, + { + "m26_0":0.6798 + }, + { + "m26_0":0.808 + }, + { + "m26_0":0.6927 + }, + { + "m26_0":0.6803 + }, + { + "m26_0":0.4153 + }, + { + "m26_0":0.7541 + }, + { + "m26_0":0.6428 + }, + { + "m26_0":0.4713 + }, + { + "m26_0":0.6395 + }, + { + "m26_0":0.1865 + }, + { + "m26_0":0.8629 + }, + { + "m26_0":0.7147 + }, + { + "m26_0":1.0209 + }, + { + "m26_0":1.1171 + }, + { + "m26_0":0.5854 + }, + { + "m26_0":0.6767 + }, + { + "m26_0":0.8738 + }, + { + "m26_0":1.3047 + }, + { + "m26_0":1.4002 + }, + { + "m26_0":0.402 + }, + { + "m26_0":0.4505 + }, + { + "m26_0":0.8786 + }, + { + "m26_0":0.7842 + }, + { + "m26_0":1.868 + }, + { + "m26_0":0.836 + }, + { + "m26_0":0.6198 + }, + { + "m26_0":0.8554 + }, + { + "m26_0":0.5255 + }, + { + "m26_0":0.8125 + }, + { + "m26_0":1.0405 + }, + { + "m26_0":0.5737 + }, + { + "m26_0":0.4921 + }, + { + "m26_0":2.094 + }, + { + "m26_0":0.4219 + }, + { + "m26_0":0.8954 + }, + { + "m26_0":0.8461 + }, + { + "m26_0":0.4996 + }, + { + "m26_0":1.2678 + }, + { + "m26_0":0.2487 + }, + { + "m26_0":0.9281 + }, + { + "m26_0":0.523 + }, + { + "m26_0":0.5232 + }, + { + "m26_0":0.7616 + }, + { + "m26_0":0.4748 + }, + { + "m26_0":4.0775 + }, + { + "m26_0":0.5517 + }, + { + "m26_0":0.6495 + }, + { + "m26_0":0.3303 + }, + { + "m26_0":0.5487 + }, + { + "m26_0":0.4679 + }, + { + "m26_0":0.4144 + }, + { + "m26_0":0.5413 + }, + { + "m26_0":0.7059 + }, + { + "m26_0":0.612 + }, + { + "m26_0":0.8456 + }, + { + "m26_0":0.8762 + }, + { + "m26_0":0.4712 + }, + { + "m26_0":0.7776 + }, + { + "m26_0":1.34 + }, + { + "m26_0":0.6399 + }, + { + "m26_0":2.197 + }, + { + "m26_0":0.6949 + }, + { + "m26_0":1.5448 + }, + { + "m26_0":1.1098 + }, + { + "m26_0":0.6078 + }, + { + "m26_0":0.5418 + }, + { + "m26_0":0.5241 + }, + { + "m26_0":0.463 + }, + { + "m26_0":0.6232 + }, + { + "m26_0":0.9358 + }, + { + "m26_0":0.96 + }, + { + "m26_0":0.6898 + }, + { + "m26_0":0.5461 + }, + { + "m26_0":0.8045 + }, + { + "m26_0":1.375 + }, + { + "m26_0":0.4032 + }, + { + "m26_0":2.036 + }, + { + "m26_0":0.4623 + }, + { + "m26_0":0.6364 + }, + { + "m26_0":1.1978 + }, + { + "m26_0":0.4653 + }, + { + "m26_0":0.6002 + }, + { + "m26_0":0.6971 + }, + { + "m26_0":0.9696 + }, + { + "m26_0":0.4851 + }, + { + "m26_0":0.6039 + }, + { + "m26_0":5.6125 + }, + { + "m26_0":1.4665 + }, + { + "m26_0":0.748 + }, + { + "m26_0":1.8962 + }, + { + "m26_0":0.754 + }, + { + "m26_0":0.4669 + }, + { + "m26_0":0.9121 + }, + { + "m26_0":3.5715 + }, + { + "m26_0":0.4647 + }, + { + "m26_0":0.9862 + }, + { + "m26_0":0.7786 + }, + { + "m26_0":0.9547 + }, + { + "m26_0":0.4155 + }, + { + "m26_0":1.1283 + }, + { + "m26_0":0.8317 + }, + { + "m26_0":0.3525 + }, + { + "m26_0":0.9487 + }, + { + "m26_0":0.7104 + }, + { + "m26_0":0.5378 + }, + { + "m26_0":0.5181 + }, + { + "m26_0":1.2016 + }, + { + "m26_0":0.2264 + }, + { + "m26_0":1.3595 + }, + { + "m26_0":0.4626 + }, + { + "m26_0":0.6542 + }, + { + "m26_0":0.4621 + }, + { + "m26_0":1.0547 + }, + { + "m26_0":3.9814 + }, + { + "m26_0":0.8266 + }, + { + "m26_0":1.33 + }, + { + "m26_0":0.7238 + }, + { + "m26_0":0.4901 + }, + { + "m26_0":0.56 + }, + { + "m26_0":0.6759 + }, + { + "m26_0":0.6808 + }, + { + "m26_0":0.6598 + }, + { + "m26_0":0.6064 + }, + { + "m26_0":0.8353 + }, + { + "m26_0":0.4724 + }, + { + "m26_0":1.0945 + }, + { + "m26_0":0.3818 + }, + { + "m26_0":0.7258 + }, + { + "m26_0":1.0306 + }, + { + "m26_0":0.8363 + }, + { + "m26_0":0.7615 + }, + { + "m26_0":0.4366 + }, + { + "m26_0":0.9684 + }, + { + "m26_0":0.5763 + }, + { + "m26_0":0.6567 + }, + { + "m26_0":0.8045 + }, + { + "m26_0":2.5041 + }, + { + "m26_0":0.6575 + }, + { + "m26_0":0.5776 + }, + { + "m26_0":0.4364 + }, + { + "m26_0":0.6654 + }, + { + "m26_0":0.3243 + }, + { + "m26_0":0.6149 + }, + { + "m26_0":0.5054 + }, + { + "m26_0":0.9486 + }, + { + "m26_0":0.4114 + }, + { + "m26_0":0.531 + }, + { + "m26_0":0.1687 + }, + { + "m26_0":1.7354 + }, + { + "m26_0":0.8021 + }, + { + "m26_0":0.9775 + }, + { + "m26_0":0.4841 + }, + { + "m26_0":0.4741 + }, + { + "m26_0":0.6279 + }, + { + "m26_0":0.3699 + }, + { + "m26_0":0.9037 + }, + { + "m26_0":0.9355 + }, + { + "m26_0":0.6805 + }, + { + "m26_0":0.7989 + }, + { + "m26_0":0.975 + }, + { + "m26_0":2.0881 + }, + { + "m26_0":0.9256 + }, + { + "m26_0":1.7451 + }, + { + "m26_0":0.5227 + }, + { + "m26_0":0.5872 + }, + { + "m26_0":0.572 + }, + { + "m26_0":0.7326 + }, + { + "m26_0":0.6015 + }, + { + "m26_0":0.3898 + }, + { + "m26_0":0.9413 + }, + { + "m26_0":0.4622 + }, + { + "m26_0":0.4196 + }, + { + "m26_0":0.9825 + }, + { + "m26_0":0.1992 + }, + { + "m26_0":0.5363 + }, + { + "m26_0":0.3792 + }, + { + "m26_0":0.6406 + }, + { + "m26_0":0.799 + }, + { + "m26_0":0.9221 + }, + { + "m26_0":0.6892 + }, + { + "m26_0":0.5888 + }, + { + "m26_0":0.729 + }, + { + "m26_0":0.5619 + }, + { + "m26_0":0.5593 + }, + { + "m26_0":0.7603 + }, + { + "m26_0":0.6752 + }, + { + "m26_0":1.3384 + }, + { + "m26_0":0.5582 + }, + { + "m26_0":0.7068 + }, + { + "m26_0":0.5693 + }, + { + "m26_0":1.2796 + }, + { + "m26_0":1.0733 + }, + { + "m26_0":0.621 + }, + { + "m26_0":0.3701 + }, + { + "m26_0":0.7227 + }, + { + "m26_0":0.9331 + }, + { + "m26_0":1.1469 + }, + { + "m26_0":1.0175 + }, + { + "m26_0":0.467 + }, + { + "m26_0":1.393 + }, + { + "m26_0":0.6757 + }, + { + "m26_0":0.2896 + }, + { + "m26_0":0.4569 + }, + { + "m26_0":0.7256 + }, + { + "m26_0":0.5366 + }, + { + "m26_0":1.0128 + }, + { + "m26_0":1.0955 + }, + { + "m26_0":0.5152 + }, + { + "m26_0":0.681 + }, + { + "m26_0":1.2097 + }, + { + "m26_0":0.6897 + }, + { + "m26_0":0.8288 + }, + { + "m26_0":0.599 + }, + { + "m26_0":0.5018 + }, + { + "m26_0":1.0745 + }, + { + "m26_0":0.3227 + }, + { + "m26_0":0.6281 + }, + { + "m26_0":0.5935 + }, + { + "m26_0":0.8311 + }, + { + "m26_0":0.3608 + }, + { + "m26_0":0.4687 + }, + { + "m26_0":0.8857 + }, + { + "m26_0":0.663 + }, + { + "m26_0":0.7509 + }, + { + "m26_0":0.7595 + }, + { + "m26_0":0.9361 + }, + { + "m26_0":0.7712 + }, + { + "m26_0":0.692 + }, + { + "m26_0":0.5951 + }, + { + "m26_0":0.3258 + }, + { + "m26_0":1.5543 + }, + { + "m26_0":0.5602 + }, + { + "m26_0":1.337 + }, + { + "m26_0":0.5851 + }, + { + "m26_0":0.517 + }, + { + "m26_0":0.5325 + }, + { + "m26_0":1.0919 + }, + { + "m26_0":2.6874 + }, + { + "m26_0":0.8117 + }, + { + "m26_0":0.2312 + }, + { + "m26_0":1.0119 + }, + { + "m26_0":0.729 + }, + { + "m26_0":0.5208 + }, + { + "m26_0":0.8182 + }, + { + "m26_0":0.3531 + }, + { + "m26_0":0.6851 + }, + { + "m26_0":0.497 + }, + { + "m26_0":0.4306 + }, + { + "m26_0":0.4318 + }, + { + "m26_0":0.6495 + }, + { + "m26_0":0.8295 + }, + { + "m26_0":0.4989 + }, + { + "m26_0":0.5201 + }, + { + "m26_0":0.4779 + }, + { + "m26_0":1.667 + }, + { + "m26_0":2.0572 + }, + { + "m26_0":0.5174 + }, + { + "m26_0":4.2681 + }, + { + "m26_0":0.7707 + }, + { + "m26_0":0.5969 + }, + { + "m26_0":12.3923 + }, + { + "m26_0":0.7209 + }, + { + "m26_0":0.7654 + }, + { + "m26_0":0.6356 + }, + { + "m26_0":0.494 + }, + { + "m26_0":0.9339 + }, + { + "m26_0":0.9353 + }, + { + "m26_0":0.4016 + }, + { + "m26_0":1.7312 + }, + { + "m26_0":1.0938 + }, + { + "m26_0":1.0131 + }, + { + "m26_0":0.4814 + }, + { + "m26_0":5.4341 + }, + { + "m26_0":0.6304 + }, + { + "m26_0":0.4986 + }, + { + "m26_0":0.2764 + }, + { + "m26_0":0.7741 + }, + { + "m26_0":0.9398 + }, + { + "m26_0":0.6017 + }, + { + "m26_0":0.4922 + }, + { + "m26_0":0.5261 + }, + { + "m26_0":1.2777 + }, + { + "m26_0":4.392 + }, + { + "m26_0":0.6442 + }, + { + "m26_0":1.0798 + }, + { + "m26_0":1.4054 + }, + { + "m26_0":1.4359 + }, + { + "m26_0":0.324 + }, + { + "m26_0":0.6162 + }, + { + "m26_0":0.4028 + }, + { + "m26_0":1.1662 + }, + { + "m26_0":1.5664 + }, + { + "m26_0":0.6 + }, + { + "m26_0":0.5425 + }, + { + "m26_0":0.3717 + }, + { + "m26_0":0.5857 + }, + { + "m26_0":0.6462 + }, + { + "m26_0":1.1673 + }, + { + "m26_0":1.1952 + }, + { + "m26_0":0.603 + }, + { + "m26_0":0.5841 + }, + { + "m26_0":0.6196 + }, + { + "m26_0":0.7875 + }, + { + "m26_0":0.8218 + }, + { + "m26_0":0.5851 + }, + { + "m26_0":2.1737 + }, + { + "m26_0":0.898 + }, + { + "m26_0":1.4945 + }, + { + "m26_0":0.5472 + }, + { + "m26_0":0.8498 + }, + { + "m26_0":0.6748 + }, + { + "m26_0":0.6593 + }, + { + "m26_0":0.6553 + }, + { + "m26_0":1.734 + }, + { + "m26_0":1.2631 + }, + { + "m26_0":0.8239 + }, + { + "m26_0":1.3816 + }, + { + "m26_0":0.7134 + }, + { + "m26_0":0.6324 + }, + { + "m26_0":0.4234 + }, + { + "m26_0":1.0015 + }, + { + "m26_0":0.5435 + }, + { + "m26_0":0.5456 + }, + { + "m26_0":0.7244 + }, + { + "m26_0":2.5057 + }, + { + "m26_0":0.6558 + }, + { + "m26_0":0.7737 + }, + { + "m26_0":0.667 + }, + { + "m26_0":0.9857 + }, + { + "m26_0":0.2373 + }, + { + "m26_0":0.5209 + }, + { + "m26_0":0.5932 + }, + { + "m26_0":1.3509 + }, + { + "m26_0":1.289 + }, + { + "m26_0":0.4254 + }, + { + "m26_0":0.8943 + }, + { + "m26_0":0.9511 + }, + { + "m26_0":1.4766 + }, + { + "m26_0":0.6018 + }, + { + "m26_0":0.7122 + }, + { + "m26_0":0.6238 + }, + { + "m26_0":0.7356 + }, + { + "m26_0":0.6524 + }, + { + "m26_0":0.6223 + }, + { + "m26_0":0.5405 + }, + { + "m26_0":1.8425 + }, + { + "m26_0":0.6833 + }, + { + "m26_0":0.4984 + }, + { + "m26_0":1.2665 + }, + { + "m26_0":0.9656 + }, + { + "m26_0":0.5721 + }, + { + "m26_0":1.0082 + }, + { + "m26_0":0.4646 + }, + { + "m26_0":0.4171 + }, + { + "m26_0":0.5838 + }, + { + "m26_0":2.7687 + }, + { + "m26_0":0.638 + }, + { + "m26_0":0.7095 + }, + { + "m26_0":1.0746 + }, + { + "m26_0":1.2306 + }, + { + "m26_0":0.9877 + }, + { + "m26_0":0.5791 + } +] \ No newline at end of file diff --git a/metrics.json b/metrics.json index 82f9ddf..2e0aec1 100644 --- a/metrics.json +++ b/metrics.json @@ -18,7 +18,7 @@ { "name": "Perceptual Fluency", "icon": "brain", - "metrics": ["m2", "m4", "m5", "m6", "m7", "m8", "m9", "m21", "m22", "m24", "m25"] + "metrics": ["m2", "m4", "m5", "m6", "m7", "m8", "m9", "m21", "m22", "m24", "m25", "m26"] } ], "metrics": { @@ -1924,6 +1924,57 @@ "description": false } ] + }, + "m26": { + "id": "m26", + "name": "Mirror symmetry - contour pixel", + "description": "The similarity of an object reflection across a straight axis. The measure considered contour pixels of image.", + "evidence": 4, + "relevance": 2, + "speed": 2, + "segmentation_required": false, + "references": [ + { + "title": "Miniukovich, A. and De Angeli, A. (2014). Quantification of Interface Visual Complexity. In Proceedings of the 2014 International Working Conference on Advanced Visual Interfaces (AVI '14), pp. 153-160. ACM. doi: https://doi.org/10.1145/2598153.2598173", + "url": "https://doi.org/10.1145/2598153.2598173" + }, + { + "title": "Miniukovich, A. and De Angeli, A. (2014). Visual Impressions of Mobile App Interfaces. In Proceedings of the 8th Nordic Conference on Human-Computer Interaction (NordiCHI '14), pp. 31-40. ACM. doi: https://doi.org/10.1145/2639189.2641219", + "url": "https://doi.org/10.1145/2639189.2641219" + } + ], + "results": [ + { + "id": "m26_0", + "index": 0, + "type": "float", + "name": "Normalized pixel symmetry", + "description": false, + "scores": [ + { + "id": "r1", + "range": [0.0, 0.5344], + "description": "Non-Symmetric", + "icon": ["fas", "exclamation-triangle"], + "judgment": "bad" + }, + { + "id": "r2", + "range": [0.5345, 0.9498], + "description": "Fair", + "icon": [null, null], + "judgment": "normal" + }, + { + "id": "r3", + "range": [0.9499, null], + "description": "Symmetric", + "icon": ["far", "check-circle"], + "judgment": "good" + } + ] + } + ] } } }