How to rotate relative points in degrees? I have 4 points in range [0.0, 1.0] representing the top-left and bottom-right corners of a bounding box. 
For example: [0.25 0.33 0.71 0.73]
In other words, the first pair (in (y, x) format) means that the point is 25% down the top of the image, and 33% from the left. The second pair means that the bottom right point is located 71% from the top of the image and 73% from the left.
Question
If I now rotate the image by N degrees, how do I compute where those 4 points should be? To be more specific, I really only care to rotate the image 90, 180, 270 degrees.

Left: original image, not rotated. Right: image rotated 90 degrees.
 A: The transformation you require  is:
$$ y’ = y \cos \theta  + x \sin \theta $$ 
$$x’ = -y \sin \theta+ x \cos \theta$$
Also don’t forget that when programming $\ cos \theta$ and $ \sin \theta$  needs to be in radians and not degrees. To convert from degrees to radians multiply by $\pi/180$.
You will note that the above formula is greatly simplified if the rotation angles are multiples of $90 \deg$.
For 0 degrees,  $y’ = y, \; x’ =  x $
For 90 degrees, $ y’ = x, \;  x’ = -y $
For 180 degrees,$ y’ = -y, \;x’ = - x $
For 270 degrees, $y’ = -x, \;x’ =  y $
If the final coordinates all have to be positive, as is the case for many image processing contexts, this should be modified to 
For 0 degrees,  $y’ = y, \;  x’ =  x $
For 90 degrees, $ y’ = x, \;x’ =1 -y $
For 180 degrees,$ y’ = 1-y, \;x’ =1 - x $
For 270 degrees, $y’ =1 -x, \;  x’ =  y$
Finally, I note that the order of $x$ and $y$ coordinates that you use (although used in image processing  fairly often) are reversed to the standard math convention so be careful if you are searching for other  solutions on the internet that you are reading the correctly!
Hope that helps!
A: Based off Martin Roberts answer, here's my complete solution:
  // values in absolute pixels
  box = [y_min, x_min, y_max, x_max];

  // Make points relative to image
  pct = [box[0] / height, box[1] / width, box[2] / height, box[3] / width];
  //^
  //|
  //+--------+
  //         |
  //         v
  rot90 = [pct[1], 1 - pct[2], pct[3], 1 - pct[0]];
  // ^
  // |
  // +--------+
  //          |
  //          v
  rot180 = [rot90[1], 1 - rot90[2], rot90[3], 1 - rot90[0]];
  // ^
  // |
  // +--------+
  //          |
  //          v

  rot270 = [rot180[1], 1 - rot180[2], rot180[3], 1 - rot180[0]];




*

*As a minor implementation detail, although not all images in my dataset were squares, they were deliberately reshaped to be squares because of the neural network model where they'll be used.

*The reason for the reversed (x, y) points, that's because TensorFlow's tf.image.draw_bounding_boxes expects the points in that order.

