//  [python]
def random_image_rotation(image, masks, boxes, keypoints, max_angle=45, probability=0.9):
    """
    What this function does:
    1. It takes a random box and rotates everything around its center.
    2. Then it rescales the image so that the box not too small or not too big.
    3. Then it translates the image's center to be at the box's center.

    All coordinates are absolute:
    1. Boxes have coordinates in ranges [0, height] and [0, width].
    2. Keypoints have coordinates in ranges [0, height - 1] and [0, width - 1].

    Arguments:
        image: a float tensor with shape [height, width, 3].
        masks: a float tensor with shape [height / DOWNSAMPLE, width / DOWNSAMPLE, 2].
        boxes: a float tensor with shape [num_persons, 4].
        keypoints: an int tensor with shape [num_persons, 17, 3].
        max_angle: an integer.
        probability: a float number.
    Returns:
        image: a float tensor with shape [height, width, 3].
        masks: a float tensor with shape [height / DOWNSAMPLE, width / DOWNSAMPLE, 2].
        boxes: a float tensor with shape [num_remaining_boxes, 4],
            where num_remaining_boxes <= num_persons.
        keypoints: an int tensor with shape [num_remaining_boxes, 17, 3].
    """
    def rotate(image, masks, boxes, keypoints):

        # get the center of the image
        image_shape = tf.cast(tf.shape(image), tf.float32)
        image_height = image_shape[0]
        image_width = image_shape[1]
        image_center = 0.5 * tf.stack([image_height, image_width])
        image_center = tf.reshape(image_center, [1, 2])

        box_center, box_width = get_random_box_center(boxes, image_height, image_width)
        rotation = get_random_rotation(max_angle, box_center, image_width)
        scaler = get_random_scaling(box_center, box_width, image_width)

        rotation *= scaler
        translation = image_center - tf.matmul(box_center, rotation)

        """
        Assume tensor `points` has shape [n, 2].
        1. points = points - box_center (translate center of the coordinate system to the box center)
        2. points = points * rotation (rotate and scale relative to the new center)
        3. points = points + box_center (translate back)
        4. points = points - center_translation (translate image center to the box center)

        So full transformation is:
        (points - box_center) * rotation + box_center - center_translation =
        = points * rotation + translation, where translation = image_center - rotation * box_center.
        """

        boxes = transform_boxes(boxes, rotation, translation)
        keypoints = transform_keypoints(keypoints, rotation, translation)
        # after this some boxes and keypoints could be out of the image

        boxes, keypoints = correct(boxes, keypoints, image_height, image_width)
        # now all boxes and keypoints are inside the image

        transform = get_inverse_transform(rotation, translation)
        image = contrib.image.transform(image, transform, interpolation='BILINEAR')

        # masks are smaller than the image
        scaler = tf.stack([1, 1, DOWNSAMPLE, 1, 1, DOWNSAMPLE, 1, 1])
        masks_transform = transform / tf.cast(scaler, tf.float32)

        masks = contrib.image.transform(masks, masks_transform, interpolation='NEAREST')
        # masks are binary so we use the nearest neighbor interpolation

        return image, masks, boxes, keypoints

    do_it = tf.less(tf.random_uniform([]), probability)
    image, masks, boxes, keypoints = tf.cond(
        do_it,
        lambda: rotate(image, masks, boxes, keypoints),
        lambda: (image, masks, boxes, keypoints)
    )
    return image, masks, boxes, keypoints