LSST Applications g00274db5b6+edbf708997,g00d0e8bbd7+edbf708997,g199a45376c+5137f08352,g1fd858c14a+1d4b6db739,g262e1987ae+f4d9505c4f,g29ae962dfc+7156fb1a53,g2cef7863aa+73c82f25e4,g35bb328faa+edbf708997,g3e17d7035e+5b3adc59f5,g3fd5ace14f+852fa6fbcb,g47891489e3+6dc8069a4c,g53246c7159+edbf708997,g64539dfbff+9f17e571f4,g67b6fd64d1+6dc8069a4c,g74acd417e5+ae494d68d9,g786e29fd12+af89c03590,g7ae74a0b1c+a25e60b391,g7aefaa3e3d+536efcc10a,g7cc15d900a+d121454f8d,g87389fa792+a4172ec7da,g89139ef638+6dc8069a4c,g8d7436a09f+28c28d8d6d,g8ea07a8fe4+db21c37724,g92c671f44c+9f17e571f4,g98df359435+b2e6376b13,g99af87f6a8+b0f4ad7b8d,gac66b60396+966efe6077,gb88ae4c679+7dec8f19df,gbaa8f7a6c5+38b34f4976,gbf99507273+edbf708997,gc24b5d6ed1+9f17e571f4,gca7fc764a6+6dc8069a4c,gcc769fe2a4+97d0256649,gd7ef33dd92+6dc8069a4c,gdab6d2f7ff+ae494d68d9,gdbb4c4dda9+9f17e571f4,ge410e46f29+6dc8069a4c,geaed405ab2+e194be0d2b,w.2025.47
LSST Data Management Base Package
Loading...
Searching...
No Matches
lsst.pipe.tasks.prettyPictureMaker._localContrast Namespace Reference

Functions

NDArray r (NDArray img, NDArray out, float g, float sigma, float shadows, float highlights, float clarity)
 
Sequence[NDArray] makeGaussianPyramid (NDArray img, list[int] padY, list[int] padX, List[NDArray]|None out)
 
Sequence[NDArray] makeLapPyramid (NDArray img, list[int] padY, list[int] padX, List[NDArray]|None gaussOut, List[NDArray]|None lapOut, List[NDArray]|None upscratch=None)
 
 _calculateOutput (List[NDArray] out, List[NDArray] pyramid, NDArray gamma, List[NDArray] pyramidVectorsBottom, List[NDArray] pyramidVectorsTop)
 
list[int] levelPadder (int numb, int levels)
 
NDArray localContrast (NDArray image, float sigma, float highlights=-0.9, float shadows=0.4, float clarity=0.15, int|None maxLevel=None, int numGamma=20)
 

Function Documentation

◆ _calculateOutput()

lsst.pipe.tasks.prettyPictureMaker._localContrast._calculateOutput ( List[NDArray] out,
List[NDArray] pyramid,
NDArray gamma,
List[NDArray] pyramidVectorsBottom,
List[NDArray] pyramidVectorsTop )
protected
Computes the output by interpolating between basis vectors at each pixel in
a Gaussian pyramid.

The function iterates over each pixel in the Gaussian pyramids
and interpolates between the corresponding basis vectors  from
`pyramidVectorsBottom` and `pyramidVectorsTop`. If a pixel value is outside
the range defined by gamma, it skips interpolation.

Parameters:
-----------
out : `numba.typed.typedlist.List` of `np.ndarray`
    A list of numpy arrays representing the output image pyramids.
pyramid : `numba.typed.typedlist.List` of `np.ndarray`
    A list of numpy arrays representing the Gaussian pyramids.
gamma : `np.ndarray`
    A numpy array containing the range for pixel values to be considered in
    the interpolation.
pyramidVectorsBottom : `numba.typed.typedlist.List` of `np.ndarray`
    A list of numpy arrays representing the basis vectors at the bottom
    level of each pyramid layer.
pyramidVectorsTop : `numba.typed.typedlist.List` of `np.ndarray`
    A list of numpy arrays representing the basis vectors at the top level
    of each pyramid layer.

Definition at line 233 of file _localContrast.py.

239):
240 """
241 Computes the output by interpolating between basis vectors at each pixel in
242 a Gaussian pyramid.
243
244 The function iterates over each pixel in the Gaussian pyramids
245 and interpolates between the corresponding basis vectors from
246 `pyramidVectorsBottom` and `pyramidVectorsTop`. If a pixel value is outside
247 the range defined by gamma, it skips interpolation.
248
249 Parameters:
250 -----------
251 out : `numba.typed.typedlist.List` of `np.ndarray`
252 A list of numpy arrays representing the output image pyramids.
253 pyramid : `numba.typed.typedlist.List` of `np.ndarray`
254 A list of numpy arrays representing the Gaussian pyramids.
255 gamma : `np.ndarray`
256 A numpy array containing the range for pixel values to be considered in
257 the interpolation.
258 pyramidVectorsBottom : `numba.typed.typedlist.List` of `np.ndarray`
259 A list of numpy arrays representing the basis vectors at the bottom
260 level of each pyramid layer.
261 pyramidVectorsTop : `numba.typed.typedlist.List` of `np.ndarray`
262 A list of numpy arrays representing the basis vectors at the top level
263 of each pyramid layer.
264
265 """
266 # loop over each pixel in the gaussian pyramid
267 # gammaDiff = gamma[1] - gamma[0]
268 for level in prange(0, len(pyramid) - 1):
269 yshape = pyramid[level].shape[0]
270 xshape = pyramid[level].shape[1]
271 plevel = pyramid[level]
272 outlevel = out[level]
273 basisBottom = pyramidVectorsBottom[level]
274 basisTop = pyramidVectorsTop[level]
275 for y in prange(yshape):
276 plevelY = plevel[y]
277 outLevelY = outlevel[y]
278 basisBottomY = basisBottom[y]
279 basisTopY = basisTop[y]
280 for x in prange(xshape):
281 val = plevelY[x]
282 if not (val >= gamma[0] and val <= gamma[1]):
283 continue
284 a = (plevelY[x] - gamma[0]) / (gamma[1] - gamma[0])
285 outLevelY[x] = (1 - a) * basisBottomY[x] + a * basisTopY[x]
286
287

◆ levelPadder()

list[int] lsst.pipe.tasks.prettyPictureMaker._localContrast.levelPadder ( int numb,
int levels )
Determine if each level of transform will need to be padded by
one to make the level divisible by two.

Parameters
----------
numb : int
    The size of the input dimension
levels : int
    The number of times the dimensions will be reduced by a factor of two

Returns
-------
padds : list of int
    A list where the entries are either zero or one depending on if the
    size will need padded to be a power of two.

Definition at line 288 of file _localContrast.py.

288def levelPadder(numb: int, levels: int) -> list[int]:
289 """Determine if each level of transform will need to be padded by
290 one to make the level divisible by two.
291
292 Parameters
293 ----------
294 numb : int
295 The size of the input dimension
296 levels : int
297 The number of times the dimensions will be reduced by a factor of two
298
299 Returns
300 -------
301 padds : list of int
302 A list where the entries are either zero or one depending on if the
303 size will need padded to be a power of two.
304
305 """
306 pads = []
307 if numb % 2 != 0:
308 pads.append(1)
309 numb += 1
310 else:
311 pads.append(0)
312 for _ in range(levels):
313 numb /= 2
314 if numb % 2 != 0:
315 pads.append(1)
316 numb += 1
317 else:
318 pads.append(0)
319 return pads
320
321

◆ localContrast()

NDArray lsst.pipe.tasks.prettyPictureMaker._localContrast.localContrast ( NDArray image,
float sigma,
float highlights = -0.9,
float shadows = 0.4,
float clarity = 0.15,
int | None maxLevel = None,
int numGamma = 20 )
Enhance the local contrast of an input image.

Parameters
----------
image : `NDArray`
    Two dimensional numpy array representing the image to have contrast
    increased.
sigma : `float`
    The scale over which edges are considered real and not noise.
highlights : `float`
    A parameter that controls how highlights are enhansed or reduced,
    contrary to intuition, negative values increase highlights.
shadows : `float`
    A parameter that controls how shadows are deepened.
clarity : `float`
    A parameter that relates to the contrast between highlights and
    shadow.
maxLevel : `int` or `None`
    The maximum number of image pyramid levels to enhanse the contrast over.
    Each level has a spatial scale of roughly 2^(level) pixles.
numGamma : `int`
    This is an optimization parameter. This algorithm divides up contrast
    space into a certain numbers over which the expensive computation
    is done. Contrast values in the image which fall between two of these
    values are interpolated to get the outcome. The higher the numGamma,
    the smoother the image is post contrast enhancement, though above
    some number there is no decerable difference.

Returns
-------
image : `NDArray`
    Two dimensional numpy array of the input image with increased local
    contrast.

Raises
------
ValueError
    Raised if the max level to enhance to is greater than the image
    supports.

Notes
-----
This function, and it's supporting functions, spiritually implement the
algorithm outlined at
https://people.csail.mit.edu/sparis/publi/2011/siggraph/
titled "Local Laplacian Filters: Edge-aware Image Processing with Laplacian
Pyramid". This is not a 1:1 implementation, it's optimized for the
python language and runtime performance. Most notably it transforms only
certain levels and linearly interpolates to find other values. This
implementation is inspired by the ony done in the darktable image editor:
https://www.darktable.org/2017/11/local-laplacian-pyramids/. None of the
code is in common, nor is the implementation 1:1, but reading the original
paper and the darktable implementation gives more info about this function.
Specifically some variable names follow the paper/other implementation,
and may be confusing when viewed without that context.

Definition at line 322 of file _localContrast.py.

330) -> NDArray:
331 """Enhance the local contrast of an input image.
332
333 Parameters
334 ----------
335 image : `NDArray`
336 Two dimensional numpy array representing the image to have contrast
337 increased.
338 sigma : `float`
339 The scale over which edges are considered real and not noise.
340 highlights : `float`
341 A parameter that controls how highlights are enhansed or reduced,
342 contrary to intuition, negative values increase highlights.
343 shadows : `float`
344 A parameter that controls how shadows are deepened.
345 clarity : `float`
346 A parameter that relates to the contrast between highlights and
347 shadow.
348 maxLevel : `int` or `None`
349 The maximum number of image pyramid levels to enhanse the contrast over.
350 Each level has a spatial scale of roughly 2^(level) pixles.
351 numGamma : `int`
352 This is an optimization parameter. This algorithm divides up contrast
353 space into a certain numbers over which the expensive computation
354 is done. Contrast values in the image which fall between two of these
355 values are interpolated to get the outcome. The higher the numGamma,
356 the smoother the image is post contrast enhancement, though above
357 some number there is no decerable difference.
358
359 Returns
360 -------
361 image : `NDArray`
362 Two dimensional numpy array of the input image with increased local
363 contrast.
364
365 Raises
366 ------
367 ValueError
368 Raised if the max level to enhance to is greater than the image
369 supports.
370
371 Notes
372 -----
373 This function, and it's supporting functions, spiritually implement the
374 algorithm outlined at
375 https://people.csail.mit.edu/sparis/publi/2011/siggraph/
376 titled "Local Laplacian Filters: Edge-aware Image Processing with Laplacian
377 Pyramid". This is not a 1:1 implementation, it's optimized for the
378 python language and runtime performance. Most notably it transforms only
379 certain levels and linearly interpolates to find other values. This
380 implementation is inspired by the ony done in the darktable image editor:
381 https://www.darktable.org/2017/11/local-laplacian-pyramids/. None of the
382 code is in common, nor is the implementation 1:1, but reading the original
383 paper and the darktable implementation gives more info about this function.
384 Specifically some variable names follow the paper/other implementation,
385 and may be confusing when viewed without that context.
386
387 """
388 # ensure the supplied values are floats, and not ints
389 highlights = float(highlights)
390 shadows = float(shadows)
391 clarity = float(clarity)
392
393 # Determine the maximum level over which the image will be inhanced
394 # and the amount of padding that will be needed to be added to the
395 # image.
396 maxImageLevel = int(np.min(np.log2(image.shape)))
397 if maxLevel is None:
398 maxLevel = maxImageLevel
399 if maxImageLevel < maxLevel:
400 raise ValueError(
401 f"The supplied max level {maxLevel} is is greater than the max of the image: {maxImageLevel}"
402 )
403 support = 1 << (maxLevel - 1)
404 padY_amounts = levelPadder(image.shape[0] + support, maxLevel)
405 padX_amounts = levelPadder(image.shape[1] + support, maxLevel)
406 imagePadded = cv2.copyMakeBorder(
407 image, *(0, support), *(0, support), cv2.BORDER_REPLICATE, None, None
408 ).astype(image.dtype)
409
410 # build a list of intensities
411 gamma = np.linspace(image.min(), image.max(), numGamma)
412
413 # make gaussian pyramid
414 pyramid = makeGaussianPyramid(imagePadded, padY_amounts, padX_amounts, None)
415
416 finalPyramid = List()
417 for sample in pyramid[:-1]:
418 finalPyramid.append(np.zeros_like(sample))
419 finalPyramid.append(pyramid[-1])
420
421 # make a working array for gaussian pyramid in Lap
422 # make two working arrays for laplace as the true value is interpolated
423 # between the endpoints.
424 # This prevents needing re-allocations which can be time consuming.
425 tmpGauss = List()
426 tmpLap1 = List()
427 tmpLap2 = List()
428 upscratch = List()
429 for i, sample in enumerate(pyramid):
430 tmpGauss.append(np.empty_like(sample))
431 tmpLap1.append(np.empty_like(sample))
432 tmpLap2.append(np.empty_like(sample))
433 if i == 0:
434 upscratch.append(np.empty((0, 0), dtype=image.dtype))
435 continue
436 upscratch.append(np.empty((sample.shape[0] * 2, sample.shape[1] * 2), dtype=image.dtype))
437 # cycle between the endpoints, because there is no reason to recalculate both
438 # endpoints as only one changes for each bin.
439 cycler = iter(cycle((tmpLap1, tmpLap2)))
440 # allocate temporary arrays to use for each bin
441 outCycle = iter(cycle((np.copy(imagePadded), np.copy(imagePadded))))
442 prevImg = r(
443 imagePadded, next(outCycle), gamma[0], sigma, shadows=shadows, highlights=highlights, clarity=clarity
444 )
445 prevLapPyr = makeLapPyramid(
446 prevImg, padY_amounts, padX_amounts, tmpGauss, next(cycler), upscratch=upscratch
447 )
448
449 for value in range(1, len(gamma) - 1):
450 pyramidVectors = List()
451 pyramidVectors.append(prevLapPyr)
452 newImg = r(
453 imagePadded,
454 next(outCycle),
455 gamma[value],
456 sigma,
457 shadows=shadows,
458 highlights=highlights,
459 clarity=clarity,
460 )
461 prevLapPyr = makeLapPyramid(
462 newImg, padY_amounts, padX_amounts, tmpGauss, next(cycler), upscratch=upscratch
463 )
464 pyramidVectors.append(prevLapPyr)
465
466 _calculateOutput(
467 finalPyramid,
468 pyramid,
469 np.array((gamma[value - 1], gamma[value])),
470 pyramidVectors[0],
471 pyramidVectors[1],
472 )
473 del pyramidVectors
474
475 # time to reconstruct
476 output = finalPyramid[-1]
477 for i in range(-2, -1 * len(finalPyramid) - 1, -1):
478 upsampled = cv2.pyrUp(output)
479 upsampled = upsampled[
480 : upsampled.shape[0] - 2 * padY_amounts[i + 1], : upsampled.shape[1] - 2 * padX_amounts[i + 1]
481 ]
482 output = finalPyramid[i] + upsampled
483 return output[:-support, :-support]

◆ makeGaussianPyramid()

Sequence[NDArray] lsst.pipe.tasks.prettyPictureMaker._localContrast.makeGaussianPyramid ( NDArray img,
list[int] padY,
list[int] padX,
List[NDArray] | None out )
Create a Gaussian Pyramid from an input image.

Parameters:
    img : `NDArray`
        The input image, which will be processed to create the pyramid.
    padY : `list` of `int`
        List containing padding sizes along the Y-axis for each level of the pyramid.
    padX : `list` of `int`
        List containing padding sizes along the X-axis for each level of the pyramid.
    out  `numba.typed.typedlist.List` of `NDarray` or `None`
        Optional list to store the output images of the pyramid levels.
        If None, a new list is created.

Returns:
    pyramid : `Sequence` of `NDArray`
        A sequence of images representing the Gaussian Pyramid.

Notes:
    - The function creates a padded version of the input image and then
      reduces its size using `cv2.pyrDown` to generate each level of the
      pyramid.
    - If 'out' is provided, it will be used to store the pyramid levels;
      otherwise, a new list is dynamically created.
    - Padding is applied only if specified by non-zero values in `padY` and
     `padX`.

Definition at line 102 of file _localContrast.py.

104) -> Sequence[NDArray]:
105 """
106 Create a Gaussian Pyramid from an input image.
107
108 Parameters:
109 img : `NDArray`
110 The input image, which will be processed to create the pyramid.
111 padY : `list` of `int`
112 List containing padding sizes along the Y-axis for each level of the pyramid.
113 padX : `list` of `int`
114 List containing padding sizes along the X-axis for each level of the pyramid.
115 out `numba.typed.typedlist.List` of `NDarray` or `None`
116 Optional list to store the output images of the pyramid levels.
117 If None, a new list is created.
118
119 Returns:
120 pyramid : `Sequence` of `NDArray`
121 A sequence of images representing the Gaussian Pyramid.
122
123 Notes:
124 - The function creates a padded version of the input image and then
125 reduces its size using `cv2.pyrDown` to generate each level of the
126 pyramid.
127 - If 'out' is provided, it will be used to store the pyramid levels;
128 otherwise, a new list is dynamically created.
129 - Padding is applied only if specified by non-zero values in `padY` and
130 `padX`.
131 """
132 # Initialize the output pyramid list if not provided
133 if out is None:
134 pyramid = List()
135 else:
136 pyramid = out
137
138 # Apply padding only if needed, ensuring the type matches the input image
139 if padY[0] or padX[0]:
140 paddedImage = cv2.copyMakeBorder(
141 img, *(0, padY[0]), *(0, padX[0]), cv2.BORDER_REPLICATE, None if out is None else pyramid[0], None
142 ).astype(img.dtype)
143 else:
144 paddedImage = img
145
146 # Store the first level of the pyramid (padded image)
147 if out is None:
148 pyramid.append(paddedImage)
149 else:
150 # This might not be sound all the time, copy might be needed!
151 # Update the first level in the provided list
152 pyramid[0] = paddedImage
153
154 # Generate each subsequent level of the Gaussian Pyramid
155 for i in range(1, len(padY)):
156 if padY[i] or padX[i]:
157 paddedImage = cv2.copyMakeBorder(
158 paddedImage, *(0, padY[i]), *(0, padX[i]), cv2.BORDER_REPLICATE, None, None
159 ).astype(img.dtype)
160 # Downsample the image
161 paddedImage = cv2.pyrDown(paddedImage, None if out is None else pyramid[i])
162
163 # Append to the list if not provided externally
164 if out is None:
165 pyramid.append(paddedImage)
166 return pyramid
167
168

◆ makeLapPyramid()

Sequence[NDArray] lsst.pipe.tasks.prettyPictureMaker._localContrast.makeLapPyramid ( NDArray img,
list[int] padY,
list[int] padX,
List[NDArray] | None gaussOut,
List[NDArray] | None lapOut,
List[NDArray] | None upscratch = None )
Create a Laplacian pyramid from the input image.

This function constructs a Laplacian pyramid from the input image. It first
generates a Gaussian pyramid and then, for each level (except the last),
subtracts the upsampled version of the next lower level from the current
level to obtain the Laplacian levels. If `lapOut` is None, it creates a
new list to store the Laplacian pyramid; otherwise, it uses the provided
`lapOut`.

Parameters
----------
img : `NDArray`
    The input image as a numpy array.
padY : `list` of `int`
    List of padding sizes for rows (vertical padding).
padX : `list` of `int`
    List of padding sizes for columns (horizontal padding).
gaussOut : `numba.typed.typedlist.List` of `NDArray` or None
    Preallocated storage for the output of the Gaussian pyramid function.
    If `None` new storage is allocated.
lapOut : `numba.typed.typedlist.List` of `NDArray` or None
    Preallocated for the output Laplacian pyramid. If None, a new
    `numba.typed.typedlist.List` is created.
upscratch : `numba.typed.typedlist.List` of `NDarray`, optional
    List to store intermediate results of pyramids (default is None).

Returns
-------
results : `Sequence` of `NDArray`
    The Laplacian pyramid as a sequence of numpy arrays.

Definition at line 169 of file _localContrast.py.

176) -> Sequence[NDArray]:
177 """
178 Create a Laplacian pyramid from the input image.
179
180 This function constructs a Laplacian pyramid from the input image. It first
181 generates a Gaussian pyramid and then, for each level (except the last),
182 subtracts the upsampled version of the next lower level from the current
183 level to obtain the Laplacian levels. If `lapOut` is None, it creates a
184 new list to store the Laplacian pyramid; otherwise, it uses the provided
185 `lapOut`.
186
187 Parameters
188 ----------
189 img : `NDArray`
190 The input image as a numpy array.
191 padY : `list` of `int`
192 List of padding sizes for rows (vertical padding).
193 padX : `list` of `int`
194 List of padding sizes for columns (horizontal padding).
195 gaussOut : `numba.typed.typedlist.List` of `NDArray` or None
196 Preallocated storage for the output of the Gaussian pyramid function.
197 If `None` new storage is allocated.
198 lapOut : `numba.typed.typedlist.List` of `NDArray` or None
199 Preallocated for the output Laplacian pyramid. If None, a new
200 `numba.typed.typedlist.List` is created.
201 upscratch : `numba.typed.typedlist.List` of `NDarray`, optional
202 List to store intermediate results of pyramids (default is None).
203
204 Returns
205 -------
206 results : `Sequence` of `NDArray`
207 The Laplacian pyramid as a sequence of numpy arrays.
208
209 """
210 pyramid = makeGaussianPyramid(img, padY, padX, gaussOut)
211 if lapOut is None:
212 lapPyramid = List()
213 else:
214 lapPyramid = lapOut
215 for i in range(len(pyramid) - 1):
216 upsampled = cv2.pyrUp(pyramid[i + 1], None if upscratch is None else upscratch[i + 1])
217 if padY[i + 1] or padX[i + 1]:
218 upsampled = upsampled[
219 : upsampled.shape[0] - 2 * padY[i + 1], : upsampled.shape[1] - 2 * padX[i + 1]
220 ]
221 if lapOut is None:
222 lapPyramid.append(pyramid[i] - upsampled)
223 else:
224 cv2.subtract(pyramid[i], upsampled, dst=lapPyramid[i])
225 if lapOut is None:
226 lapPyramid.append(pyramid[-1])
227 else:
228 lapPyramid[-1][:, :] = pyramid[-1]
229 return lapPyramid
230
231
232@njit(fastmath=True, parallel=True, error_model="numpy", nogil=True)

◆ r()

NDArray lsst.pipe.tasks.prettyPictureMaker._localContrast.r ( NDArray img,
NDArray out,
float g,
float sigma,
float shadows,
float highlights,
float clarity )
Apply a post-processing effect to an image using the specified parameters.

Parameters:
    img : `NDArray`
        The input image array of shape (n_images, height, width).
    out : `NDArray`
        The output image array where the result will be stored. Should have the same shape as `img`.
    g : `float`
        A parameter for gamma correction.
    sigma : `float`
        Parameter that defines the scale at which a change should be considered an edge.
    shadows : `float`
        Shadow adjustment factor.
    highlights `float`
        Highlight adjustment factor. Negative values INCREASE highlights.
    clarity : `float`
        Clarity adjustment factor.

Returns:
    result : `NDArray`
        The processed image array with the same shape as `out`.

Definition at line 36 of file _localContrast.py.

38) -> NDArray:
39 """
40 Apply a post-processing effect to an image using the specified parameters.
41
42 Parameters:
43 img : `NDArray`
44 The input image array of shape (n_images, height, width).
45 out : `NDArray`
46 The output image array where the result will be stored. Should have the same shape as `img`.
47 g : `float`
48 A parameter for gamma correction.
49 sigma : `float`
50 Parameter that defines the scale at which a change should be considered an edge.
51 shadows : `float`
52 Shadow adjustment factor.
53 highlights `float`
54 Highlight adjustment factor. Negative values INCREASE highlights.
55 clarity : `float`
56 Clarity adjustment factor.
57
58 Returns:
59 result : `NDArray`
60 The processed image array with the same shape as `out`.
61 """
62
63 h_s = (highlights, shadows)
64
65 # Iterate over each pixel in the image
66 for i in prange(out.shape[0]):
67 # Get the current image slice
68 imgI = img[i]
69 # Get the corresponding output slice
70 outI = out[i]
71
72 # Iterate over each pixel in the image
73 for j in prange(out.shape[1]):
74 # Calculate the contrast adjusted by gamma correction
75 c = imgI[j] - g
76 # Determine the sign of the contrast adjustment
77 s = np.sign(c)
78
79 # Compute the transformation term t based on the signed contrast
80 t = s * c / (2.0 * sigma)
81 # Clamp t to be within [0, 1]
82 t = max(0, min(t, 1))
83
84 t2 = t * t
85 # Complement of t
86 mt = 1.0 - t
87
88 # Determine the index based on the sign of c (either 0 or 1)
89 index = np.uint8(np.bool_(1 + s))
90
91 # Compute the final pixel value using the transformation and
92 # additional terms for shadows/highlights and clarity
93 val = g + s * sigma * 2 * mt * t + t2 * (s * sigma + s * sigma * h_s[index])
94 val = val + clarity * c * np.exp(-(c * c) / (2.0 * sigma * sigma / 3.0))
95
96 # Assign the computed value to the output image
97 outI[j] = val
98
99 return out
100
101