Skip to content
This repository has been archived by the owner on Oct 25, 2023. It is now read-only.

Fix template scale in find element by image #307

Merged
merged 17 commits into from
Mar 20, 2019

Conversation

KazuCocoa
Copy link
Member

Fix remaining issue in https://github.com/appium/appium-base-driver/pull/306/files#r261480026 .
In the PR, I fixed a template image had different width/height with device screen, but it can be fixed scaling them up/down upto match to device width/height.

But the ratio issue has also below case.

example:

  • A user has a template image which is scaled down from 750 x 1314 pixels image to 187 x 328 pixels in order to decrease their storage when he store the template image.
  • The user uses the scale downed image as the next test case
    • Then, the scaled image does not match in current find by image logic since Appium cannot determin what ratio should be scaled the template up/down
    • Users must scale them up the image in the user side every time.

To resolve the case, I introduced defaultImageTemplateScale. If the user sets defaultImageTemplateScale: 4 for example, Appium will scale the template image up 4 times by default. Then, users do not need to scale in user side.

It can use with fixImageTemplateScale for example. It can help to handle some cases such as I described as #306

cc @jlipps

@KazuCocoa KazuCocoa requested a review from jlipps March 1, 2019 13:02
@KazuCocoa KazuCocoa changed the title Fix template scale more Fix template scale in find element by image Mar 1, 2019
@@ -183,6 +183,9 @@ commands.findByCustom = async function (selector, multiple) {
* image is merely to check staleness. If so we can bypass a lot of logic
* @property {boolean} [multiple=false] - Whether we are finding one element or
* multiple
* @property {boolean} [ignoreDefaultImageTemplateScale=false] - Whether we
* ignore defaultImageTemplateScale. It can be used when you would noe like to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noe?

* @param {string} b64Template - base64-encoded image used as a template to be
* matched in the screenshot
* @param {ScreenshotScale} scale - scale of screen. Default to `{}`
* @param {ScreenshotScale, ImageTemplateSettings} opts - Image template scale related options
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather keep a single ImageTemplateSettings type here.


const defaultScale = 1.0; // To initialise xScale and yScale
const defaultOpts = {
fixImageTemplateScale: false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can set defaults without _.defaults call:

const {
  defaultImageTemplateScale: DEFAULT_TEMPLATE_IMAGE_SCALE,
  ...
} = opts;

return b64Template;
}

const defaultScale = 1.0; // To initialise xScale and yScale
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constants should be in uppercase

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also it's better to move it out of the func

xScale = xScale * defaultImageTemplateScale;
yScale = yScale * defaultImageTemplateScale;
} else {
// `1.0 *` makes NaN if defaultImageTemplateScale is no number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is not a number

const {xScale, yScale} = scale;
// Calculate xScale and yScale Appium should scale
if (opts.fixImageTemplateScale) {
xScale = xScale * defaultImageTemplateScale;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use xScale *= defaultImageTemplateScale

if (!xScale || !yScale) {
return b64Template;
}

// Return if the scale is deatul (1.0) value
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deatul?

if (!xScale || !yScale) {
return b64Template;
}

// Return if the scale is deatul (1.0) value
if (xScale === defaultScale && yScale === defaultScale) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in general it is not a good idea to compare float values for equality

@@ -25,6 +25,14 @@ const GLOBAL_DEFAULT_SETTINGS = {
// if a user use `width=750 × height=1334` pixels's base template image.
fixImageTemplateScale: false,

// Users might have scaled template image to reduce their storage size.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be nice to have this also described in some doc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


const screenAR = screenWidth / screenHeight;
const shotAR = shotWidth / shotHeight;
if (screenAR === shotAR) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/appium/appium-base-driver/pull/306/files#diff-a7d1893e663367145d766d1aaab4aad4L347 case.
I discussed with @jlipps and figure out the issue.
I left an example of this ratio issue as a comment below.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

float comparison?

// Because if Appium selects the both ratio, the screenshot will be distorted.
// If Appium selects the xScale, height will be bigger than real screenshot size
// which is used to image comparison by OpenCV as a base image.
const _xScale = (1.0 * shotWidth) / screenWidth;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need underscore prefixes?

return b64Template;
}

const {xScale, yScale} = scale;
let {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

xScale *= defaultImageTemplateScale;
yScale *= defaultImageTemplateScale;
} else {
// `1 *` makes NaN if defaultImageTemplateScale is not a number
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think parseFloat would be more readable for such purpose

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

> parseFloat('a')
NaN

ah... 👍

if (!xScale || !yScale) {
return b64Template;
}

// Return if the scale is default (1.0) value
if ((xScale - DEFAULT_FIX_IMAGE_TEMPLATE_SCALE) <= 0 && (yScale - DEFAULT_FIX_IMAGE_TEMPLATE_SCALE) === 0) {
Copy link
Contributor

@mykola-mokhnach mykola-mokhnach Mar 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please avoid comparing float values for equality.
It is mandatory to have some delta epsilon value or use toFixed transformation and compare these as strings

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -12,6 +12,10 @@ const commands = {}, helpers = {}, extensions = {};
const IMAGE_STRATEGY = '-image';
const CUSTOM_STRATEGY = '-custom';

// Used to compar ratio and screen width
// Pixel is basically under 1080 for example. 100K is probably enough fo a while.
const DEFAULT_ROUND_FLOAT = 100000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather rename it to FLOAT_PRECISION



const commands = {}, helpers = {}, extensions = {};

const IMAGE_STRATEGY = '-image';
const CUSTOM_STRATEGY = '-custom';

// Used to compar ratio and screen width
// Pixel is basically under 1080 for example. 100K is probably enough fo a while.
const FLOAT_PERCISION = 100000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PRECISION

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops...

@@ -357,42 +367,119 @@ helpers.getScreenshotForImageFind = async function (screenWidth, screenHeight) {
// of coordinates returned by the image match algorithm, since we match based
// on the screenshot coordinates not the device coordinates themselves. There
// are two potential types of mismatch: aspect ratio mismatch and scale
// mismatch.
// mismatch. we need to detect and fix both
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We

@KazuCocoa
Copy link
Member Author

Will wait for @jlipps to double check the comparison logic

@KazuCocoa
Copy link
Member Author

@jlipps Do you have a chance to have a look?
(Or can I merge and publish it as beta?)

Copy link
Member

@jlipps jlipps left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got some final questions for you, looks mostly good though



const commands = {}, helpers = {}, extensions = {};

const IMAGE_STRATEGY = '-image';
const CUSTOM_STRATEGY = '-custom';

// Used to compar ratio and screen width
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compare

const screenAR = screenWidth / screenHeight;
const shotAR = shotWidth / shotHeight;
if (Math.round(screenAR * FLOAT_PRECISION) === Math.round(shotAR * FLOAT_PRECISION)) {
log.info('Screenshot aspect ratio matched screen aspect ratio');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to do something like 'Screenshot aspect ratio (${shotAR}) matched screen aspect ratio (${screenAR})'

`is ${screenWidth}x${screenHeight} whereas screenshot is ` +
`${shotWidth}x${shotHeight}.`);

// Select smaller ratio
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    // In the case where the x-scale and y-scale are different, we need to decide
    // which one to respect, otherwise the screenshot and template will end up
    // being resized in a way that changes its aspect ratio (distorts it). For example, let's say:
    // this.getScreenshot(shotWidth, shotHeight) is 540x397,
    // this.getDeviceSize(screenWidth, screenHeight) is 1080x1920.
    // The ratio would then be {xScale: 0.5, yScale: 0.2}.
    // In this case, we must should `yScale: 0.2` as scaleFactor, because
    // If we select the xScale, the height will be bigger than real screenshot size
    // which is used to image comparison by OpenCV as a base image.
    // All of this is primarily useful when the screenshot is a horizontal slice taken out of the
    // screen (for example not including top/bottom nav bars)


// Resize based on the screen dimensions only if both width and height are mismatched
// since except for that, it might be a situation which is different window rect and
// screenshot size like `@driver.window_rect #=>x=0, y=0, width=1080, height=1794` and
// `"deviceScreenSize"=>"1080x1920"`
let scale;
if (screenWidth !== shotWidth && screenHeight !== shotHeight) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't we be comparing the new imgObj width and height here? it looks like we're comparing against the old shotWidth/shotHeight, but those could have changed in the previous block, right?

Copy link
Member Author

@KazuCocoa KazuCocoa Mar 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I added b536b10 to update shotWidth and shotHeight if they should have been updated in the ratio comparison

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jlipps Do you have a chance to take a look ^?

// Users might have scaled template image to reduce their storage size.
// This setting allows users to scale a template image they send to Appium server
// so that Appium server compare actual scale users orginaly have.
// e.g. A user have `270 × 32 pixels` image oridinally `1080 × 126 pixels`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

originally

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user has an image of 270 x 32 pixels which was originally 1080 x 126 pixels

@@ -25,6 +25,14 @@ const GLOBAL_DEFAULT_SETTINGS = {
// if a user use `width=750 × height=1334` pixels's base template image.
fixImageTemplateScale: false,

// Users might have scaled template image to reduce their storage size.
// This setting allows users to scale a template image they send to Appium server
// so that Appium server compare actual scale users orginaly have.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so that the Appium server compares the actual scale users originally had

// This setting allows users to scale a template image they send to Appium server
// so that Appium server compare actual scale users orginaly have.
// e.g. A user have `270 × 32 pixels` image oridinally `1080 × 126 pixels`
// The user set {defaultImageTemplateScale: 4.0} to scale the small image
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user can set

@KazuCocoa KazuCocoa requested a review from jlipps March 19, 2019 16:55
@KazuCocoa KazuCocoa merged commit b38ef75 into appium:master Mar 20, 2019
@KazuCocoa KazuCocoa deleted the fix-template-scale-more branch March 20, 2019 00:00
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants