4
4
5
5
import cv2
6
6
import numpy as np
7
+ import skimage
8
+ import skimage .io
9
+ import skimage .measure
7
10
from PIL import Image , ImageEnhance
11
+ from skimage import draw , exposure
12
+ from skimage .util import img_as_ubyte
8
13
9
14
10
- def is_image_too_dark (image : Image , pixel_brightness_threshold : float = 10.0 ) -> bool :
15
+ def is_image_too_dark (
16
+ image : Image .Image , pixel_brightness_threshold : float = 10.0
17
+ ) -> bool :
11
18
"""
12
19
Check if the image is too dark based on the mean brightness.
13
20
By "too dark" we mean not as visible to the human eye.
@@ -32,7 +39,7 @@ def is_image_too_dark(image: Image, pixel_brightness_threshold: float = 10.0) ->
32
39
return mean_brightness < pixel_brightness_threshold
33
40
34
41
35
- def adjust_image_brightness (image : Image ) -> Image :
42
+ def adjust_image_brightness (image : Image . Image ) -> Image . Image :
36
43
"""
37
44
Adjust the brightness of an image using histogram equalization.
38
45
@@ -64,3 +71,197 @@ def adjust_image_brightness(image: Image) -> Image:
64
71
reduced_brightness_image = enhancer .enhance (0.7 )
65
72
66
73
return reduced_brightness_image
74
+
75
+
76
+ def draw_outline_on_image_from_outline (
77
+ orig_image : np .ndarray , outline_image_path : str
78
+ ) -> np .ndarray :
79
+ """
80
+ Draws green outlines on an image based on a provided outline image and returns
81
+ the combined result.
82
+
83
+ Args:
84
+ orig_image (np.ndarray):
85
+ The original image on which the outlines will be drawn.
86
+ It must be a grayscale or RGB image with shape `(H, W)` for
87
+ grayscale or `(H, W, 3)` for RGB.
88
+ outline_image_path (str):
89
+ The file path to the outline image. This image will be used
90
+ to determine the areas where the outlines will be drawn.
91
+ It can be grayscale or RGB.
92
+
93
+ Returns:
94
+ np.ndarray:
95
+ The original image with green outlines drawn on the non-black areas from
96
+ the outline image. The result is returned as an RGB image with shape
97
+ `(H, W, 3)`.
98
+ """
99
+
100
+ # Load the outline image
101
+ outline_image = skimage .io .imread (outline_image_path )
102
+
103
+ # Resize if necessary
104
+ if outline_image .shape [:2 ] != orig_image .shape [:2 ]:
105
+ outline_image = skimage .transform .resize (
106
+ outline_image ,
107
+ orig_image .shape [:2 ],
108
+ preserve_range = True ,
109
+ anti_aliasing = True ,
110
+ ).astype (orig_image .dtype )
111
+
112
+ # Create a mask for non-black areas (with threshold)
113
+ threshold = 10 # Adjust as needed
114
+ # Grayscale
115
+ if outline_image .ndim == 2 : # noqa: PLR2004
116
+ non_black_mask = outline_image > threshold
117
+ else : # RGB/RGBA
118
+ non_black_mask = np .any (outline_image [..., :3 ] > threshold , axis = - 1 )
119
+
120
+ # Ensure the original image is RGB
121
+ if orig_image .ndim == 2 : # noqa: PLR2004
122
+ orig_image = np .stack ([orig_image ] * 3 , axis = - 1 )
123
+ elif orig_image .shape [- 1 ] != 3 : # noqa: PLR2004
124
+ raise ValueError ("Original image must have 3 channels (RGB)." )
125
+
126
+ # Ensure uint8 data type
127
+ if orig_image .dtype != np .uint8 :
128
+ orig_image = (orig_image * 255 ).astype (np .uint8 )
129
+
130
+ # Apply the green outline
131
+ combined_image = orig_image .copy ()
132
+ combined_image [non_black_mask ] = [0 , 255 , 0 ] # Green in uint8
133
+
134
+ return combined_image
135
+
136
+
137
+ def draw_outline_on_image_from_mask (
138
+ orig_image : np .ndarray , mask_image_path : str
139
+ ) -> np .ndarray :
140
+ """
141
+ Draws green outlines on an image based on a binary mask and returns
142
+ the combined result.
143
+
144
+ Please note: masks are inherently challenging to use when working with
145
+ multi-compartment datasets and may result in outlines that do not
146
+ pertain to the precise compartment. For example, if an object mask
147
+ overlaps with one or many other object masks the outlines may not
148
+ differentiate between objects.
149
+
150
+ Args:
151
+ orig_image (np.ndarray):
152
+ Image which a mask will be applied to. Must be a NumPy array.
153
+ mask_image_path (str):
154
+ Path to the binary mask image file.
155
+
156
+ Returns:
157
+ np.ndarray:
158
+ The resulting image with the green outline applied.
159
+ """
160
+ # Load the binary mask image
161
+ mask_image = skimage .io .imread (mask_image_path )
162
+
163
+ # Ensure the original image is RGB
164
+ # Grayscale input
165
+ if orig_image .ndim == 2 : # noqa: PLR2004
166
+ orig_image = np .stack ([orig_image ] * 3 , axis = - 1 )
167
+ # Unsupported input
168
+ elif orig_image .shape [- 1 ] != 3 : # noqa: PLR2004
169
+ raise ValueError ("Original image must have 3 channels (RGB)." )
170
+
171
+ # Ensure the mask is 2D (binary)
172
+ if mask_image .ndim > 2 : # noqa: PLR2004
173
+ mask_image = mask_image [..., 0 ] # Take the first channel if multi-channel
174
+
175
+ # Detect contours from the mask
176
+ contours = skimage .measure .find_contours (mask_image , level = 0.5 )
177
+
178
+ # Create an outline image with the same shape as the original image
179
+ outline_image = np .zeros_like (orig_image )
180
+
181
+ # Draw contours as green lines
182
+ for contour in contours :
183
+ rr , cc = draw .polygon_perimeter (
184
+ np .round (contour [:, 0 ]).astype (int ),
185
+ np .round (contour [:, 1 ]).astype (int ),
186
+ shape = orig_image .shape [:2 ],
187
+ )
188
+ # Assign green color to the outline in all three channels
189
+ outline_image [rr , cc , :] = [0 , 255 , 0 ]
190
+
191
+ # Combine the original image with the green outline
192
+ combined_image = orig_image .copy ()
193
+ mask = np .any (outline_image > 0 , axis = - 1 ) # Non-zero pixels in the outline
194
+ combined_image [mask ] = outline_image [mask ]
195
+
196
+ return combined_image
197
+
198
+
199
+ def adjust_with_adaptive_histogram_equalization (image : np .ndarray ) -> np .ndarray :
200
+ """
201
+ Adaptive histogram equalization with additional smoothing to reduce graininess.
202
+
203
+ Parameters:
204
+ image (np.ndarray):
205
+ The input image to be processed.
206
+
207
+ Returns:
208
+ np.ndarray:
209
+ The processed image with enhanced contrast.
210
+ """
211
+ # Adjust parameters dynamically
212
+ kernel_size = (
213
+ max (image .shape [0 ] // 10 , 1 ), # Ensure the kernel size is at least 1
214
+ max (image .shape [1 ] // 10 , 1 ), # Ensure the kernel size is at least 1
215
+ )
216
+ clip_limit = 0.02 # Lower clip limit to suppress over-enhancement
217
+ nbins = 512 # Increase bins for finer histogram granularity
218
+
219
+ # Check if the image has an alpha channel (RGBA)
220
+ # RGBA image
221
+ if image .shape [- 1 ] == 4 : # noqa: PLR2004
222
+ rgb_np = image [:, :, :3 ]
223
+ alpha_np = image [:, :, 3 ]
224
+
225
+ equalized_rgb_np = np .zeros_like (rgb_np , dtype = np .float32 )
226
+
227
+ for channel in range (3 ):
228
+ equalized_rgb_np [:, :, channel ] = exposure .equalize_adapthist (
229
+ rgb_np [:, :, channel ],
230
+ kernel_size = kernel_size ,
231
+ clip_limit = clip_limit ,
232
+ nbins = nbins ,
233
+ )
234
+
235
+ equalized_rgb_np = img_as_ubyte (equalized_rgb_np )
236
+ final_image_np = np .dstack ([equalized_rgb_np , alpha_np ])
237
+
238
+ # Grayscale image
239
+ elif len (image .shape ) == 2 : # noqa: PLR2004
240
+ final_image_np = exposure .equalize_adapthist (
241
+ image ,
242
+ kernel_size = kernel_size ,
243
+ clip_limit = clip_limit ,
244
+ nbins = nbins ,
245
+ )
246
+ final_image_np = img_as_ubyte (final_image_np )
247
+
248
+ # RGB image
249
+ elif image .shape [- 1 ] == 3 : # noqa: PLR2004
250
+ equalized_rgb_np = np .zeros_like (image , dtype = np .float32 )
251
+
252
+ for channel in range (3 ):
253
+ equalized_rgb_np [:, :, channel ] = exposure .equalize_adapthist (
254
+ image [:, :, channel ],
255
+ kernel_size = kernel_size ,
256
+ clip_limit = clip_limit ,
257
+ nbins = nbins ,
258
+ )
259
+
260
+ final_image_np = img_as_ubyte (equalized_rgb_np )
261
+
262
+ else :
263
+ raise ValueError (
264
+ "Unsupported image format. Ensure the image is grayscale, RGB, or RGBA."
265
+ )
266
+
267
+ return final_image_np
0 commit comments