One part of the project I am currently working on, requires me to achieve the following:
I tried using PIL's image.rotate function for rotation and kept expand=True
.
I developed a pixel reversal function which works fine for 90 degree rotation , but on using angles between 0 and 90, it doesn't work properly.
Here's a couple of images showing the rotation and it's results, with the red dot indicating the two pixels that are supposed to be identical. The image on the left is the original, whereas the one on the right is the rotated one
Here's the code I wrote so far.
import math
from PIL import Image, ImageDraw
from icecream import ic
def draw_circle(img, x, y,title):
# Create an ImageDraw object to draw on the image
draw = ImageDraw.Draw(img)
# Set the coordinates for the center of the point
point_x, point_y = x, y
# Set the size (width and height) of the point (making it bigger)
point_size = 50 # You can adjust this value to change the size of the point
# Set the color of the point (red in this example, represented as RGB)
point_color = (255, 0, 0)
# Calculate the bounding box for the ellipse
point_bbox = (
point_x - point_size // 2,
point_y - point_size // 2,
point_x + point_size // 2,
point_y + point_size // 2,
)
# Draw the ellipse (representing the point) on the image
draw.ellipse(point_bbox, fill=point_color, outline=point_color)
img.show(title=title)
# Close the ImageDraw object
del draw
def rotate_pixel(x, y, theta,rotated_img):
"""
Give this function, the X,Y positions from the original image, the angle by which the original image is rotated,
and the rotated image itself.
Returns: The X,Y coordinates of that exact pixel in the rotated image
To reverse the rotation, give the resulting X,Y coordinates, along with -theta, and the original image
"""
rotated_img_width, rotated_img_height = rotated_img.size
# Convert theta to radians
theta_rad = math.radians(theta)
# Calculate the new x and y coordinates after rotation
new_x = round(x * math.cos(theta_rad) - y * math.sin(theta_rad))
new_y = round(x * math.sin(theta_rad) + y * math.cos(theta_rad))
if new_y < 0:
new_y = rotated_img_height + new_y
elif new_x < 0:
new_x = rotated_img_width + new_x
return new_x, new_y
strip_image_path = '../../dumb_data/perfect_strip.png'
angle = 90
img = Image.open(strip_image_path)
rot_img = img.rotate(angle, expand=True)
width,height = img.size
rot_width,rot_height = rot_img.size
original_x, original_y = 500, 100
rotated_x, rotated_y = rotate_pixel(original_x,original_y,-angle,rotated_img=rot_img) # rotation_coordinates(img, +angle, original_x, original_y)
reverse_x, reverse_y = rotate_pixel(rotated_x,rotated_y,angle,rotated_img=img) #rotation_coordinates(img, -angle, rotated_x, rotated_y)
# Define the image dimensions
image_width, image_height = img.size
print()
ic(img.size)
ic(rot_img.size)
ic(original_x, original_y)
ic(rotated_x, rotated_y)
ic(reverse_x, reverse_y)
draw_circle(img, original_x, original_y,title="original")
draw_circle(rot_img, rotated_x, rotated_y,title="rotated")
Any solutions which achieve the general above mentioned goal are welcome
Here is a solution using PIL with type hints and docstrings.
The image is uploaded and rotated, but without cropping, using the method provided by the PIL library itself. Then, it's just about applying a simple rotation on the plane.
If it's not what you are looking for, I will try to make it better.
from pathlib import Path
from typing import Tuple
import numpy as np
from PIL import Image
from matplotlib import pyplot as plt
def main(image_path_: Path, angle: float, pixel_coordinates: Tuple[int, int]) -> None:
"""
Entry point.
Parameters
----------
image_path_ : Path
angle : float
pixel_coordinates : Tuple[int, int]
"""
image = Image.open(image_path_)
rotated_image = rotate_image(image, angle_)
transformed_pixel_coordinates = transform_pixel_coordinates(
pixel_coordinates, angle, image, rotated_image
)
draw_images(image, rotated_image, pixel_coordinates_, transformed_pixel_coordinates)
def rotate_image(image: Image, angle: float) -> Image:
"""
Rotate image.
Parameters
----------
image : Image
angle : float
Returns
-------
Image
"""
return image.rotate(angle, expand=True)
def transform_pixel_coordinates(
pixel_coordinates: Tuple[int, int],
angle: float,
image: Image,
rotated_image: Image,
) -> Tuple[int, int]:
"""
Transform pixel coordinates.
Parameters
----------
pixel_coordinates : Tuple[int, int]
angle : float
image : Image
rotated_image : Image
Returns
-------
Tuple[int, int]
"""
x, y = pixel_coordinates
center = (image.width / 2, image.height / 2)
transformed_center = (rotated_image.width / 2, rotated_image.height / 2)
angle_radians = -np.deg2rad(angle)
x -= center[0]
y -= center[1]
x_transformed = x * np.cos(angle_radians) - y * np.sin(angle_radians)
y_transformed = x * np.sin(angle_radians) + y * np.cos(angle_radians)
x_transformed += transformed_center[0]
y_transformed += transformed_center[1]
return int(x_transformed), int(y_transformed)
def draw_images(
image, rotated_image, pixel_coordinates, transformed_pixel_coordinates
) -> None:
"""
Draw images and pixel.
Parameters
----------
image : Image
rotated_image : Image
pixel_coordinates : Tuple[int, int]
transformed_pixel_coordinates : Tuple[int, int]
"""
fig, axes = plt.subplots(1, 2, figsize=(12, 6))
axes[0].imshow(image)
axes[0].scatter(*pixel_coordinates, color="y", s=50)
axes[0].set_title("Image")
axes[0].axis("off")
axes[1].imshow(rotated_image)
axes[1].scatter(*transformed_pixel_coordinates, color="y", s=50)
axes[1].set_title("Rotated Image")
axes[1].axis("off")
plt.show()
if __name__ == "__main__":
image_path = Path("test.png")
angle_ = 30
pixel_coordinates_ = (100, 200)
main(image_path, angle_, pixel_coordinates_)