all, I have some graphs in .pdf that I want to digitize with coordinates in .csv. I have the python code that reads the pdf, recognizes the graph markers and fits a b-spline. Then I want to transfer the curve from pixel coordinates to real-world coordinates, but giving the min/max X and Y values. Then comes the problem - the code just extrapolates the curve to align with the X and Y extreme values which gives invalid results. Does anybody know a way to teach the code to read the original graph position with respect to X and Y values (since the original graph never starts from X0,Y0, but the code extrapolates the curve falsely to start from X0,Y0)? Contour detection and B-spline fit to pixels vs. original graph Fitted B-spline to real-world coordinates import fitz # PyMuPDF import cv2 import numpy as np from PIL import Image import matplotlib.pyplot as plt from scipy.interpolate import splrep, splev import csv # Load the PDF pdf_path = "app 1,2 2.pdf" doc = fitz.open(pdf_path) # Process each page for page_num in range(len(doc)): page = doc.load_page(page_num) pix = page.get_pixmap() image = Image.frombytes("RGB", [pix.width, pix.height], pix.samples) image_np = np.array(image) # Convert image to HSV and create a blue color mask hsv = cv2.cvtColor(image_np, cv2.COLOR_RGB2HSV) lower_blue = np.array([85, 30, 30]) upper_blue = np.array([135, 255, 255]) blue_mask = cv2.inRange(hsv, lower_blue, upper_blue) # Find contours and compute centers contours, _ = cv2.findContours(blue_mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) circular_centers = [] for cnt in contours: M = cv2.moments(cnt) if M["m00"] != 0: cx = int(M["m10"] / M["m00"]) cy = int(M["m01"] / M["m00"]) circular_centers.append((cx, cy)) # Proceed only if enough points are detected if len(circular_centers) >= 5: circular_centers = sorted(circular_centers, key=lambda pt: pt[0]) x_pixels = np.array([pt[0] for pt in circular_centers]) y_pixels = np.array([pt[1] for pt in circular_centers]) # Fit B-spline only within the detected pixel range spline_params = splrep(x_pixels, y_pixels, s=0) x_fit_pixels = np.linspace(min(x_pixels), max(x_pixels), len(x_pixels)) y_fit_pixels = splev(x_fit_pixels, spline_params) # Map pixel X to real-world log scale (0.001 to 200 mm) x_min_real, x_max_real = 0.001, 200 x_min_pixel, x_max_pixel = min(x_pixels), max(x_pixels) log_x_real = np.log10(x_min_real) + (x_fit_pixels - x_min_pixel) / (x_max_pixel - x_min_pixel) * ( np.log10(x_max_real) - np.log10(x_min_real)) x_real_values = 10 ** log_x_real # Normalize and invert Y values y_min, y_max = min(y_pixels), max(y_pixels) y_fit_normalized = 100 * (y_fit_pixels - y_min) / (y_max - y_min) y_inverted = 100 - y_fit_normalized Continue reading...