|
7 | 7 | from numpy.fft import fft2, ifft2 |
8 | 8 | from scipy.signal import gaussian |
9 | 9 |
|
10 | | -def transform(image): |
| 10 | + |
| 11 | +def transform(image: np.ndarray) -> np.ndarray: |
| 12 | + """ |
| 13 | + Normalize and scale an image to 8-bit range (0-255). |
| 14 | +
|
| 15 | + Args: |
| 16 | + image (np.ndarray): Input image array. |
| 17 | +
|
| 18 | + Returns: |
| 19 | + np.ndarray: Normalized and scaled 8-bit image. |
| 20 | + """ |
11 | 21 | i_min = image.min() |
12 | 22 | i_max = image.max() |
13 | | - |
14 | | - image = ((image - i_min)/(i_max - i_min)) * 255 |
| 23 | + image = ((image - i_min) / (i_max - i_min)) * 255 |
15 | 24 | return image.astype(np.uint8) |
16 | 25 |
|
17 | 26 |
|
18 | | -def standard_scaler(image): |
| 27 | +def standard_scaler(image: np.ndarray) -> np.ndarray: |
| 28 | + """ |
| 29 | + Apply Gaussian blur and standardize the image to have zero mean and unit variance. |
| 30 | +
|
| 31 | + Args: |
| 32 | + image (np.ndarray): Input image array. |
| 33 | +
|
| 34 | + Returns: |
| 35 | + np.ndarray: Scaled and transformed image. |
| 36 | + """ |
19 | 37 | kernel_size = 9 |
20 | 38 | image = cv2.GaussianBlur(image, (kernel_size, kernel_size), 0) |
21 | 39 | mu = np.mean(image) |
22 | 40 | sigma = np.std(image) |
23 | | - image = (image - mu)/sigma |
24 | | - image = transform(image).astype(np.uint8) |
25 | | - return image |
| 41 | + image = (image - mu) / sigma |
| 42 | + return transform(image).astype(np.uint8) |
26 | 43 |
|
27 | | -def contrast_enhancement(image): |
28 | | - enhanced_image = cv2.fastNlMeansDenoising(image, None, h=10, templateWindowSize=7, searchWindowSize=21) |
29 | | - |
30 | | - return enhanced_image |
31 | 44 |
|
| 45 | +def contrast_enhancement(image: np.ndarray) -> np.ndarray: |
| 46 | + """ |
| 47 | + Enhance the contrast of the image using Non-Local Means denoising. |
32 | 48 |
|
33 | | -def gaussian_kernel(kernel_size = 3): |
| 49 | + Args: |
| 50 | + image (np.ndarray): Input image array. |
| 51 | +
|
| 52 | + Returns: |
| 53 | + np.ndarray: Contrast-enhanced image. |
| 54 | + """ |
| 55 | + return cv2.fastNlMeansDenoising(image, None, h=10, templateWindowSize=7, searchWindowSize=21) |
| 56 | + |
| 57 | + |
| 58 | +def gaussian_kernel(kernel_size: int = 3) -> np.ndarray: |
| 59 | + """ |
| 60 | + Generate a 2D Gaussian kernel. |
| 61 | +
|
| 62 | + Args: |
| 63 | + kernel_size (int, optional): Size of the kernel. Defaults to 3. |
| 64 | +
|
| 65 | + Returns: |
| 66 | + np.ndarray: Gaussian kernel. |
| 67 | + """ |
34 | 68 | h = gaussian(kernel_size, kernel_size / 3).reshape(kernel_size, 1) |
35 | 69 | h = np.dot(h, h.transpose()) |
36 | 70 | h /= np.sum(h) |
37 | 71 | return h |
38 | 72 |
|
39 | | -def wiener_filter(img, kernel, K): |
| 73 | + |
| 74 | +def wiener_filter(img: np.ndarray, kernel: np.ndarray, K: float) -> np.ndarray: |
| 75 | + """ |
| 76 | + Apply Wiener filtering to the image. |
| 77 | +
|
| 78 | + Args: |
| 79 | + img (np.ndarray): Input image array. |
| 80 | + kernel (np.ndarray): Convolution kernel. |
| 81 | + K (float): Noise-to-signal power ratio. |
| 82 | +
|
| 83 | + Returns: |
| 84 | + np.ndarray: Filtered image. |
| 85 | + """ |
40 | 86 | kernel /= np.sum(kernel) |
41 | | - dummy = np.copy(img) |
42 | | - dummy = fft2(dummy) |
43 | | - kernel = fft2(kernel, s = img.shape) |
| 87 | + dummy = fft2(img) |
| 88 | + kernel = fft2(kernel, s=img.shape) |
44 | 89 | kernel = np.conj(kernel) / (np.abs(kernel) ** 2 + K) |
45 | | - dummy = dummy * kernel |
46 | | - dummy = np.abs(ifft2(dummy)) |
47 | | - return dummy |
| 90 | + dummy *= kernel |
| 91 | + return np.abs(ifft2(dummy)) |
| 92 | + |
| 93 | + |
| 94 | +def clahe(image: np.ndarray) -> np.ndarray: |
| 95 | + """ |
| 96 | + Apply Contrast Limited Adaptive Histogram Equalization (CLAHE) to the image. |
| 97 | +
|
| 98 | + Args: |
| 99 | + image (np.ndarray): Input image array. |
| 100 | +
|
| 101 | + Returns: |
| 102 | + np.ndarray: CLAHE-processed image. |
| 103 | + """ |
| 104 | + clahe_instance = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(16, 16)) |
| 105 | + return clahe_instance.apply(transform(image)) |
48 | 106 |
|
49 | | -def clahe(image): |
50 | | - clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(16,16)) |
51 | 107 |
|
52 | | - # Apply CLAHE to the image |
53 | | - img_equalized = clahe.apply(transform(image)) |
54 | | - return img_equalized |
| 108 | +def guided_filter(input_image: np.ndarray, guidance_image: np.ndarray, radius: int = 20, epsilon: float = 0.1) -> np.ndarray: |
| 109 | + """ |
| 110 | + Apply guided filtering to refine the image using a guidance image. |
55 | 111 |
|
56 | | -def guided_filter(input_image, guidance_image, radius=20, epsilon=0.1): |
57 | | - # Convert images to float32 |
| 112 | + Args: |
| 113 | + input_image (np.ndarray): Input image array. |
| 114 | + guidance_image (np.ndarray): Guidance image array. |
| 115 | + radius (int, optional): Radius of the filter. Defaults to 20. |
| 116 | + epsilon (float, optional): Regularization parameter. Defaults to 0.1. |
| 117 | +
|
| 118 | + Returns: |
| 119 | + np.ndarray: Filtered image. |
| 120 | + """ |
58 | 121 | input_image = input_image.astype(np.float32) / 255.0 |
59 | 122 | guidance_image = guidance_image.astype(np.float32) / 255.0 |
60 | 123 |
|
61 | | - # Compute mean values of the guidance image and input image |
62 | 124 | mean_guidance = cv2.boxFilter(guidance_image, -1, (radius, radius)) |
63 | 125 | mean_input = cv2.boxFilter(input_image, -1, (radius, radius)) |
64 | | - |
65 | | - # Compute correlation and covariance of the guidance and input images |
66 | 126 | mean_guidance_input = cv2.boxFilter(guidance_image * input_image, -1, (radius, radius)) |
67 | 127 | covariance_guidance_input = mean_guidance_input - mean_guidance * mean_input |
68 | 128 |
|
69 | | - # Compute squared mean of the guidance image |
70 | 129 | mean_guidance_sq = cv2.boxFilter(guidance_image * guidance_image, -1, (radius, radius)) |
71 | 130 | variance_guidance = mean_guidance_sq - mean_guidance * mean_guidance |
72 | 131 |
|
73 | | - # Compute weights and mean of the weights |
74 | 132 | a = covariance_guidance_input / (variance_guidance + epsilon) |
75 | 133 | b = mean_input - a * mean_guidance |
76 | 134 | mean_a = cv2.boxFilter(a, -1, (radius, radius)) |
77 | 135 | mean_b = cv2.boxFilter(b, -1, (radius, radius)) |
78 | 136 |
|
79 | | - # Compute the filtered image |
80 | 137 | output_image = mean_a * guidance_image + mean_b |
81 | | - |
82 | 138 | return transform(output_image) |
83 | 139 |
|
84 | 140 |
|
85 | | -def denoise(image_path): |
86 | | - kernel = gaussian_kernel(kernel_size = 9) |
| 141 | +def denoise(image_path: str) -> np.ndarray: |
| 142 | + """ |
| 143 | + Apply a sequence of denoising operations to an input image. |
| 144 | +
|
| 145 | + Args: |
| 146 | + image_path (str): Path to the input .mrc image file. |
| 147 | +
|
| 148 | + Returns: |
| 149 | + np.ndarray: Fully denoised image. |
| 150 | + """ |
| 151 | + kernel = gaussian_kernel(kernel_size=9) |
87 | 152 | image = mrcfile.read(image_path) |
88 | 153 | image = image.T |
89 | 154 | image = np.rot90(image) |
90 | 155 | normalized_image = standard_scaler(np.array(image)) |
91 | 156 | contrast_enhanced_image = contrast_enhancement(normalized_image) |
92 | | - weiner_filtered_image = wiener_filter(contrast_enhanced_image, kernel, K = 30) |
| 157 | + weiner_filtered_image = wiener_filter(contrast_enhanced_image, kernel, K=30) |
93 | 158 | clahe_image = clahe(weiner_filtered_image) |
94 | 159 | guided_filter_image = guided_filter(clahe_image, weiner_filtered_image) |
95 | | - |
96 | 160 | return guided_filter_image |
97 | | - |
|
0 commit comments