The pseudocode for the isotopy is below. The swirl is more intense near the center, and decays exponentially as you approach the border.
Code
img = load_imagex,y = pixel_coordinates(img)r,t = convert_to_polar(x,y)radius = r/rmaxswirl =2pi/ exp(radius) # more intense near centernear_border = where(r >0.6*rmax) # threshold for "near the border"# swirl less near the borderswirl[near_border] *= exp(-40*(radius -0.6)) # Decay factor for near the border# linear homotopy b/w swirl and minor swirlminor_swirl =0.0000001swirl_strength = (1-radius)*swirl + radius*minor_swirltheta += swirl_strength # add swirl to theta# convert back to Cartesian coordinatesmap_x = r * np.cos(theta) + center_xmap_y = r * np.sin(theta) + center_yshow(img, map_x, map_y)
Code
import cv2import numpy as npfrom PIL import Image# load image and convert to float32 numpy arrayimg = np.array(Image.open('uv_checkerboard.png').convert('RGB'))rows, cols = img.shape[:2]# create the coordinate grids# These MUST be float32 for cv2.remapx_grid, y_grid = np.meshgrid(np.arange(cols), np.arange(rows))map_x = x_grid.astype(np.float32)map_y = y_grid.astype(np.float32)# convert map_x, map_y to polar coordinates# center the coordinates around the middle of the imagecenter_x = cols /2center_y = rows /2# shift the grid to be centered around (0, 0)shifted_x = map_x - center_xshifted_y = map_y - center_y# convert to polar coordinatesr = np.sqrt(shifted_x**2+ shifted_y**2)theta = np.arctan2(shifted_y, shifted_x)# swirl the image by adding a function of r to theta# create a swirl effect that increases with distance from the centerrmax = r.max()img_id ='1'main_swirl =2*np.pi / np.exp(r/rmax) # more intense near centernear_border = np.where(r >0.6*rmax) # Define a threshold for "near the border"# gradually reduce the swirl strength as you approach the bordermain_swirl[near_border] *= np.exp(-40*(r[near_border]/rmax -0.6)) # Decay factor for near the border# swirl that decays exponentially as you approach the border, creating a more natural swirl effectminor_swirl =0.0000001swirl_strength = (1-r/rmax)*main_swirl + r/rmax*minor_swirltheta += swirl_strength# convert back to Cartesian coordinatesmap_x = r * np.cos(theta) + center_xmap_y = r * np.sin(theta) + center_y# remap call# using BORDER_WRAP makes the pixels that fall off the left side# appear on the right side result = cv2.remap( img, map_x, map_y, interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_WRAP)# show itImage.fromarray(result).show()# save the imageImage.fromarray(result).save(f'swirled_checkerboard_{img_id}.png')