Flatten the grating sample

Python

Code that uses K-Means clustering to split the Top and Bottom regions and performs line flattening for each region [dependency] - numpy - scikit-learn - scipy - matplotlib - pspylib

main 1 file
Flatten the grating …..py
flatten_grating_sample.py 7.3 KB
Flatten the grating …..py 7524 bytes
import os
import warnings
import numpy as np
import matplotlib.pyplot as plt
from sklearn.cluster import KMeans
from scipy.interpolate import interp1d
from scipy.integrate import trapz
from scipy import optimize
from pspylib.tiff.reader import TiffReader

def grating_flatten(array_tiff):
    whole_x = one_d_flatten(array_tiff,scope='whole_x')
    whole_x_y = one_d_flatten(whole_x,scope='whole_y')
    top_region, bottom_region = __grating_region(whole_x_y)
    whole_x_y_top = one_d_flatten(whole_x_y,scope='line_x',region=top_region)
    whole_x_y_top_bottom = one_d_flatten(whole_x_y_top,scope='line_x',region=bottom_region)
    return whole_x_y_top_bottom

def one_d_flatten(array_tiff, scope='line_x', region=None):
    assert scope in ['line_x', 'whole_x', 'line_y', 'whole_y']
    if region is not None:
        assert (np.array(array_tiff.shape) == np.array(region.shape)).all() # shape should match
    else:
        region = np.ones_like(array_tiff, dtype=np.bool_)

    image = array_tiff.copy()
    H, W = image.shape
    match scope:
        case 'line_x' | 'whole_x':
            A = np.ones((W, 2))
            A[:, 0] = (np.arange(1, W + 1) * 1.0 / W)
            results = []
            for i in range(H):
                img_row, reg_row = image[i, :], region[i, :]
                valid_n = np.sum(reg_row)
                if valid_n <= 2: # no region selected
                    bias = np.mean(img_row)
                    result_row = img_row - bias
                else:
                    coef, resids, rank, s = np.linalg.lstsq(A[reg_row, :], img_row[reg_row], rcond=None)
                    result_row = img_row - np.dot(A, coef)
                results.append(result_row)
            result_image = np.stack(results, axis=0)
            line_image = image - result_image
            avg_line = np.mean(line_image, axis=0, keepdims=True)
    
            if scope == 'whole_x':
                result_image = image - avg_line

        case 'line_y' | 'whole_y':
            A = np.ones((H, 2))
            A[:, 0] = (np.arange(1, H + 1) * 1.0 / H)
            results = []
            for i in range(W):
                img_column, reg_column = image[:, i], region[:, i]
                valid_n = np.sum(reg_column)
                if valid_n <= 2: # no region selected
                    bias = np.mean(img_column)
                    result_column = img_column - bias
                else:
                    coef, resids, rank, s = np.linalg.lstsq(A[reg_column, :], img_column[reg_column], rcond=None)
                    result_column = img_column - np.dot(A, coef)
                results.append(result_column)
            result_image = np.stack(results, axis=1)
            line_image = image - result_image
            avg_line = np.mean(line_image, axis=1, keepdims=True)
    
            if scope == 'whole_y':
                result_image = image - avg_line

    return result_image

def __grating_region(array_tiff):
    """Region segmentation based on K-Means"""
    split_hists, split_edges = cluster_kmeans(array_tiff)
    min_1 = np.min(split_edges[0])
    min_2 = np.min(split_edges[1])
    max_1 = np.max(split_edges[0])
    max_2 = np.max(split_edges[1])
    
    if max_1 < min_2:
        split_pos = min_2
    elif max_2 < min_1:
        split_pos = min_1
    else:
        print('ERROR')
    region = np.zeros_like(array_tiff, dtype=np.bool_)
    top_region = np.where(array_tiff > split_pos ,True, region)
    bottom_region = np.where(array_tiff <= split_pos,True,region)
    return top_region, bottom_region

def region_histogram(region):
    bins = __optimal_bins(region)
    hist,edges = np.histogram(region,bins)
    return hist, edges

def __optimal_bins(gray_image):
    vmin = np.min(gray_image)
    vmax = np.max(gray_image)
    # cvt Scale (Pico -> Nano)
    bound = int((vmax - vmin)* 1000)
    if bound < 10:
        bound = 128
    count = gray_image.size
    sqrt_count = int(np.sqrt(count))

    if (sqrt_count < bound) & (count < bound * 50):
        bins = sqrt_count                                                  
    else: bins = bound
    if bins > 128:
        bins = 128
    return bins

def cluster_kmeans(gray,n_cluster:int=2):
    data  = np.reshape(gray.copy(), (-1,1))
    kmeans = KMeans(n_clusters=n_cluster,n_init=10 ,tol=1e-6,random_state=0)
    #kmeans = KMeans(n_clusters=n_cluster,n_init='auto',tol=1e-4)
    kmeans.fit_predict(data)
    cluster_id = kmeans.fit_predict(data)
    _ = kmeans.cluster_centers_
    split_data_hist = []
    split_data_edges = []
    for id in np.unique(cluster_id):
        subset = data[cluster_id==id]
        hist, edges = region_histogram(subset)
        split_data_hist.append(hist)
        split_data_edges.append(edges)
    return split_data_hist, split_data_edges

def one_peak_gaussian_fit(x,y):
    y, denominator = __norm_gaussian(x,y)
    popt_gauss = __one_peak_gaussian_fit(x, y)
    fit_data = __one_peak_gaussian(x, *popt_gauss)
    return fit_data*denominator, popt_gauss

def __one_peak_gaussian_fit(x_ori,y_ori):
    """Do not use pico & Nano scale
        fit_data = __one_peak_gaussian(x_axis,popt_gauss[0],popt_gauss[1],popt_gauss[2])
        fit_data = __one_peak_gaussian(x_axis,*popt_gauss)
    """
    x = x_ori.copy()
    y = y_ori.copy() 
    x, y = __interpolate(x, y)
    x,y = __clip_negative(x, y)

    amp = y.max()
    cen = x[np.where(y == amp)][0]
    sigma = np.std(y)
    warnings.filterwarnings("error", category=UserWarning)
    try:
        popt_gauss, _ = optimize.curve_fit(__one_peak_gaussian, x, y, p0=[amp, cen, sigma])
    except UserWarning as e:
        popt_gauss = (amp,cen,sigma)
    except RuntimeError:
        popt_gauss = (amp,cen,sigma)
    return popt_gauss

def __norm_gaussian(x,y):
    # integral = 1
    y = np.abs(y)
    eps = 7/3. - 4/3. -1 
    denominator = np.abs(trapz(x,y)) + eps
    return y / denominator, denominator

def __one_peak_gaussian(x, amp,cen,sigma):
    eps = 7/.3 - 4/.3 - 1
    return amp *( 1 / (eps + sigma * (np.sqrt(2 * np.pi)))) * (np.exp((-1.0 / 2.0) * (((x-cen) / (sigma + eps))**2)))

def __clip_negative(x,y):
    clip_index = np.where(y > 0)
    return x[clip_index], y[clip_index]

def __interpolate(x,y, num:int=10000):
    fun = interp1d(x,y,kind='cubic')
    x_new = np.linspace(x.min(), x.max(),num=num,endpoint=True)
    y_new = fun(x_new)
    return x_new, y_new

def __one_peak_gaussian(x, amp,cen,sigma):
    eps = 7/.3 - 4/.3 - 1
    return amp *( 1 / (eps + sigma * (np.sqrt(2 * np.pi)))) * (np.exp((-1.0 / 2.0) * (((x-cen) / (sigma + eps))**2)))

if __name__ == "__main__":
    samples_path = r"C:\Park Systems\SmartScan\samples"
    tiff_path = os.path.join(samples_path, "Image", "Cheese.tiff")
    tiff = TiffReader(tiff_path)
    Zdata = tiff.data.scanData.ZData
    header = tiff.data.scanHeader.scanHeader
    dshape = (int(header['height'][0]), int(header['width'][0]))
    tiff_image = np.reshape(Zdata,dshape)
    tiff_image = np.flipud(tiff_image)

    flattened_image = grating_flatten(tiff_image)

    plt.subplot(1, 2, 1)
    plt.title('Original Image')
    plt.imshow(tiff_image, cmap='gray')
    plt.axis('off')

    plt.subplot(1, 2, 2)
    plt.title('1D Flattened Image')
    plt.imshow(flattened_image, cmap='gray')
    plt.axis('off')
    plt.tight_layout()
    plt.show()
Comments (0)

No comments yet. Be the first to comment!

Snippet Information
Author: jungyu.lee
Language: Python
Created: Oct 23, 2025
Updated: 0 minutes ago
Views: 52
Stars: 1

Links & Files

Additional Files (1):