Question: I've been aligning over 200 images of the sun taken in sequence in order to analyse them at a later point. I thought I had managed, but when playing back a video of all the images in sequence I noticed that the sun drifts downwards. I believe this is because in some images the full disc is not present, and hence my aligning them "by the centroid" is wrong in some way. Here are some examples of what I mean by drift:   I believe this drift is due to how I find my centroid for each disk - as the full disk is not present in every FITS image, the centroid slowly moves to a different position and the alignment skews. My code for this is here: def get_centroid(data): #data is extracted from a FITS file #getting data centroid y_index, x_index = np.where(data >= 1e4) #threshold is set to only include bright circle (sun) and not background #calculate the centroid centroid_y = np.mean(y_index) centroid_x = np.mean(x_index) return(centroid_y, centroid_x)  I've realised this only works if the full circle is present - in some of my images, parts of the sun disk are cut off. I'm struggling to edit my function so that if one of the x or y axes is shorter (i.e. if part of the circle is cut off) I could adjust the indices to make it as if the full circle was there - so that my centroid is truly the centre of the circle and I could continue alignment that way? Thanks in advance

Answer: Hope this helps you. I used the ideia presentend OpenCV: Fitting a single circle to an image (in Python). Also sorry for the huge answer. First lets create a larger canvas so we can centralize the sun image later. import cv2 as cv import matplotlib.pyplot as plt import numpy as np img1 = cv.imread('6cTxW.png') img2 = cv.imread('CVxDC.png') def image_inside_larger_canvas(img,size): # Define the size of the larger canvas larger_canvas_size = size # Change the dimensions as needed # Create a larger canvas of the specified size larger_canvas = np.zeros((larger_canvas_size[1], larger_canvas_size[0], 3), dtype=np.uint8) # Calculate the position to place the image in the center of the larger canvas x_offset = (larger_canvas_size[0] - img.shape[1]) // 2 y_offset = (larger_canvas_size[1] - img.shape[0]) // 2 # Paste the image onto the larger canvas larger_canvas[y_offset:y_offset + img.shape[0], x_offset:x_offset + img.shape[1]] = img return larger_canvas  Now is the image processing part, the explanation is inside the code. #create a larger canvas img1_larger = image_inside_larger_canvas(img2,(1200,1200)) #convert to gray img1_gray = cv.cvtColor(img1_larger, cv.COLOR_BGR2GRAY) #binarization so we can fit the countours th_val,binarized1 = cv.threshold(img1_gray,1,255,cv.THRESH_OTSU) # this part will get you the outer shape of the sun, in # other words, the perimeter (also called morph gradient) kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE,(5,5)) binarized1_eroded = cv.erode(binarized1,kernel) gradient = binarized1 - binarized1_eroded #finding countours and finding the biggest area contours,_ = cv.findContours(gradient,cv.RETR_TREE,cv.CHAIN_APPROX_SIMPLE) areas = [cv.contourArea(c) for c in contours] sorted_areas = np.sort(areas) #choosing the one with the biggest area cnt = contours[areas.index(sorted_areas[-1])] # fit circle (x,y),radius = cv.minEnclosingCircle(cnt) center = (int(x),int(y)) radius = int(radius) # fit ellipse (blue) ellipse = cv.fitEllipse(cnt) (ellipse_center, axes, angle) = ellipse # Get ellipse center for later use x_c,y_c = int(ellipse_center[0]), int(ellipse_center[1]) #change to RGB so we can plot the fitted ellipses and circles img1_gray = cv.cvtColor(img1_gray, cv.COLOR_GRAY2RGB) # draw ellipse and circle to see the process # you can remove this later. cv.ellipse(img1_gray,ellipse,(255,0,0),2) cv.circle(img1_gray,center,radius,(0,255,0),2) # Draw a dot also _ = cv.circle(img1_gray, (x_c,y_c), 10, (255, 0, 0), -1)  Now you just need to translate the sun to the center of the canvas. Wich is done by using warpAffine with the T matrix that uses the distance of the ellipse center from the middle of the canvas. height, width = img1_gray.shape[:2] #get the middle of the canvas middle_h, middle_w = height // 2, width // 2 # the matrix tarfomation T = np.float32([[1, 0, middle_w-x_c], [0, 1, middle_h-y_c]]) # use warpAffine to transform the image using the matrix, T img_translation = cv.warpAffine(img1_gray, T, (width, height)) # Draw a horizontal line on the canvas to check if it is in the middle. line = img_translation.shape[1] // 2 cv.line(img_translation, (0, line), (img_translation.shape[0], line), (0, 255, 0), 2) cv.line(img_translation, (line, 0), (line, img_translation.shape[0]), (0, 255, 0), 2) # Draw a dot on the canvas cv.circle(img_translation, (np.abs(x_c), np.abs(y_c)), 10, (0, 255, 0), -1) plt.figure(figsize=(20,20)) plt.imshow(img_translation) plt.axis('off') plt.savefig('dale1.png') plt.show()  The green dot is the old sun center, the red circle is the ellipse, the green circle is the circle and, the green lines are the image center lines. Results: