-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Draw API #7628
Add Draw API #7628
Conversation
The tests for this PR is under CPU-backend, instead of Core, because different backend would use different context and has different implementation to validate the image data. |
@axinging Could you help review this PR? Hope the API design make sense for WebGPU backend. |
} | ||
const ctx = canvas.getContext(contextType, | ||
canvasOptions?.contextAttributes || {}) as CanvasRenderingContext2D ; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check whether ctx
is null in case getContext
is called before with a different contextType
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
const value = data[i * depth + d]; | ||
|
||
if (image.dtype === 'float32') { | ||
if (value < 0 || value > 1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we throw an error or just clamp it to the supported range? For webgl/webgpu backend, it's not possible to do this check and throw errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For WebGL and WebGPU, I don't think we need to check values and we could just clamp them. For CPU, it's very convenient to make such checks and throwing an error would be helpful for users, because it's common that users pass values out of 255 and get a pure white canvas with no hit message. So, I think this throw-error feature is good to have.
throw new Error( | ||
`toPixels only supports rank 2 or 3 tensors, got rank ${$img.rank}.`); | ||
} | ||
validateImgTensor($img); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a warning in this API to tell users that this API will be deprecated and suggest them switch to the more powerful draw
API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Thanks!
tfjs-core/src/ops/browser.ts
Outdated
*/ | ||
export function draw( | ||
image: Tensor2D|Tensor3D|TensorLike, canvas: HTMLCanvasElement, | ||
contextOptions?: ContextOptions, drawOptions?: DrawOptions): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it simpler to merge contextOptions
and drawOptions
into CanvasOptions
/CanvasConfiguration
? For example, in webgpu, we have GPUCanvasConfiguration
export interface CanvasOptions {
/**
* Optional. If it is not set, it would be variable based on the current
* backend.
*/
contextType?: string;
/**
* Optional.
*/
contextAttributes?: WebGLContextAttributes;
/**
* Optional. A number in range [0-1]. If the image is a 2D tensor or a 3D
* tensor with 1 or 3 channels, the alpha channels would set as its value;
* otherwise, it would not make effects.
*/
alpha?: number;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GPUCanvasConfiguration has alphaMode, but it is different from the alpha here.
I mean either contextOptions or GPUCanvasConfiguration are describing the setting of canvas or context. However, drawOptions is to customize the kernel computations. Separating them might be more clearer for users?
Also, do you think the name and the definition of contextOptions
makes sense for WebGPU?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's ok to custom alpha
. I don't have good candidate name for drawOptions
. Leave it open if other reviewers have better suggestion or just keep the current name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I mean contextOptions
(updated my comment), does it makes senses for WebGPU? Should I add more optional fields, like GPUCanvasConfiguration? I intend to wrap all canvas and context related settings into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. It will be good to change contextAttributes?: WebGLContextAttributes;
-> contextAttributes?: WebGLContextAttributes|GPUCanvasConfiguration;
. GPUCanvasConfiguration makes it possible that draw the tensor to a different device's canvas since it has the device member.
One issue is that webgpu allows "opaque" alphaMode. It may conflict with alpha
in drawOption. We need to define the priority. Once alphaMode is opaque. alpha will take no effect or alpha will override alphaMode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that merging two options into one can cause some confusion in some overlapping but different purpose params like alpha
. But I feel a little bit concerned when I see your example usage code:
tf.browser.draw(res, canvasElem, undefined, {alpha: 0.5});
From the user's perspective it is hard to tell whether the first one or second one is drawOptions. We may hurt the readability and usability of this API. Just my personal concerns, throw it out for more discussion. Feel free to disagree.
Also, if we choose to release it as two separate options params, consider to make it:
function draw(
image: Tensor2D|Tensor3D|TensorLike, canvas: HTMLCanvasElement, drawOptions?: DrawOptions, contextOptions?: ContextOptions)
if you think customizing drawOptions
is a normal user feature but specifying contextOptions
is more like an feature for advanced users, swapping the order may be more easy to use since most of the users can just do tf.browser.draw(res, canvas, { alpha: 1, ...});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Since we would add
GPUCanvasConfiguration
toCanvasOptions
, I renamedContextOptions
toCanvasOptions
. - For usage of the API with two objects, if users follow the coding style, it would be:
const canvasOptions = {};
const drawOptions = {alpha: 0.5}
tf.browser.draw(res, canvasElem, canvasOptions, drawOptions);
- I am open to suggestions for whether we want to merge the two options as
Options: {CanvasOptions, DrawOptions}
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As collected feedbacks from you and WebML channel, get most votes for unified option object for this. Updated.
Maybe we can easily let each backend support both backend preferred canvas context + 2d canvas. Then we can always use 2d canvas to verify the correctness in core. |
Nice catch, done. Thanks! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
tfjs-core/src/ops/browser.ts
Outdated
*/ | ||
export function draw( | ||
image: Tensor2D|Tensor3D|TensorLike, canvas: HTMLCanvasElement, | ||
contextOptions?: ContextOptions, drawOptions?: DrawOptions): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's ok to custom alpha
. I don't have good candidate name for drawOptions
. Leave it open if other reviewers have better suggestion or just keep the current name.
tfjs-core/src/ops/browser.ts
Outdated
* [0-255]. | ||
* | ||
* @param image A rank-2 tensor with shape `[height, width]`, or a rank-3 tensor | ||
* of shape `[height, width, numChannels]`. If rank-2, draws grayscale. If |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"draws grayscale" -> "draws in grayscale"
I feel like the comment can be written to be more clear. @mattsoulanille please help us with grammar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM except for:
- The doc comment can be improved. @mattsoulanille for native speaker review.
- The API with two options looks okay to me, but we can have more discussion on that if you want online and/or offline.
tfjs-core/src/ops/browser.ts
Outdated
console.warn( | ||
'tf.browser.toPixels is not efficient to draw tensor on canvas. ' + | ||
'Please try tf.browser.draw instead.'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we print this warning only once? Otherwise, we may spam the console if the user is running toPixels in a loop / requestAnimationFrame.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice catch. Added a variable in the browser.ts file for the boolean, since the file already has a variable hasToPixelsWarned
, which share the coding style.
} | ||
} | ||
|
||
describeWithFlags('Draw on 2d context', BROWSER_ENVS, () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: If 'Draw on 2d context' should never be tested in WebGL, then we could use a different set of backends than BROWSER_ENVS
instead of filtering it out in setup_test.ts
. Alternatively, if we intend to implement drawing on a 2d canvas from WebGL in the future, then the current filtering approach looks good.
On the other hand, this might be more complicated than simply filtering the test case out, so feel free to leave it as-is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As Jiajia suggested, we would implement and test 2d context for all backends soon.
tfjs-core/src/ops/browser.ts
Outdated
* @param canvasOptions A object to configure the canvas to draw to. | ||
* @param drawOptions A object of options to customize drawing. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we describe the settings that people can change on these config and draw options here, or is this documented elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is at the definition of drawOptions
and canvasOptions
and tfjs-website would add them to the API. Similar as Options
of this API.
Co-authored-by: Matthew Soulanille <[email protected]>
Co-authored-by: Matthew Soulanille <[email protected]>
Thanks for the nice lib. I also get the warning: |
This PR add Draw op to Core and implemented it in CPU backend.
We could get the following effects with a couple lines of codes:
To see the logs from the Cloud Build CI, please join either our discussion or announcement mailing list.