async generateExample(numCircles, numLines, triangleProbability = 0.5) {
    if (triangleProbability == null) {
      triangleProbability = 0.5;
    }
    tf.util.assert(
        triangleProbability >= 0 && triangleProbability <= 1,
        `triangleProbability must be a number >= 0 and <= 1, but got ` +
            `${triangleProbability}`);

    const ctx = this.canvas.getContext('2d');
    ctx.clearRect(0, 0, this.w, this.h);  // Clear canvas.

    // Draw circles (1st half).
    for (let i = 0; i < numCircles / 2; ++i) {
      this.drawCircle(ctx);
    }

    // Draw lines segments (1st half).
    for (let i = 0; i < numLines / 2; ++i) {
      this.drawLineSegment(ctx);
    }

    // Draw the target object: a rectangle or an equilateral triangle.
    // Determine whether the target is a rectangle or a triangle.
    const isRectangle = Math.random() > triangleProbability;

    let boundingBox;
    ctx.fillStyle = generateRandomColorStyle();
    ctx.beginPath();
    if (isRectangle) {
      // Draw a rectangle.
      // Both side lengths of the rectangle are random and independent of
      // each other.
      const rectangleW =
          Math.random() * (this.RECTANGLE_SIDE_MAX - this.RECTANGLE_SIDE_MIN) +
          this.RECTANGLE_SIDE_MIN;
      const rectangleH =
          Math.random() * (this.RECTANGLE_SIDE_MAX - this.RECTANGLE_SIDE_MIN) +
          this.RECTANGLE_SIDE_MIN;
      const centerX = (this.w - rectangleW) * Math.random() + (rectangleW / 2);
      const centerY = (this.h - rectangleH) * Math.random() + (rectangleH / 2);
      boundingBox =
          this.drawRectangle(ctx, centerX, centerY, rectangleH, rectangleW);
    } else {
      // Draw an equilateral triangle, rotated by a random angle.
      // The distance from the center of the triangle to any of the three
      // vertices.
      const side = this.TRIANGLE_SIDE_MIN +
          (this.TRIANGLE_SIDE_MAX - this.TRIANGLE_SIDE_MIN) * Math.random();
      const centerX = (this.w - side) * Math.random() + (side / 2);
      const centerY = (this.h - side) * Math.random() + (side / 2);
      // Rotate the equilateral triangle by a random angle uniformly
      // distributed between 0 and degrees.
      const angle = Math.PI / 3 * 2 * Math.random();  // 0 - 120 degrees.
      boundingBox = this.drawTriangle(ctx, centerX, centerY, side, angle);
    }
    ctx.fill();

    // Draw circles (2nd half).
    for (let i = numCircles / 2; i < numCircles; ++i) {
      this.drawCircle(ctx);
    }

    // Draw lines segments (2nd half).
    for (let i = numLines / 2; i < numLines; ++i) {
      this.drawLineSegment(ctx);
    }

    return tf.tidy(() => {
      const imageTensor = tf.browser.fromPixels(this.canvas);
      const shapeClassIndicator = isRectangle ? 1 : 0;
      const targetTensor =
          tf.tensor1d([shapeClassIndicator].concat(boundingBox));
      return {image: imageTensor, target: targetTensor};
    });
  }
Generate a single image example.

@param {number} numCircles Number of circles (background object type)
  to include.
@param {number} numLines Number of line segments (backgrond object
  type) to include
@param {number} triangleProbability The probability of the target
  object being a triangle (instead of a rectangle). Must be a number
  >= 0 and <= 1. Default: 0.5.
@returns {Object} An object with the following fields:
  - image: A [w, h, 3]-shaped tensor for the pixel content of the image.
    w and h are the width and height of the canvas, respectively.
  - target: A [5]-shaped tensor. The first element is a 0-1 indicator
    for whether the target is a triangle (0) or a rectangle (1).
    The remaning four elements are the bounding box of the shape:
    [left, right, top, bottom], in the unit of pixels.