Skip to content

Commit eb11f43

Browse files
committed
Fixes numerical issues with N0f8 types in CLAHE
Another issue that arose is that we try to simultaneously support canonical JuliaImages image types, as well as the ability to pass in a matrix of raw numbers. Using the function `gray` on a raw number throws an error. I've refactored some key lines so that gray is only utilised on JuliaImages types.
1 parent 7204c50 commit eb11f43

8 files changed

Lines changed: 75 additions & 15 deletions

File tree

Project.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "ImageContrastAdjustment"
22
uuid = "f332f351-ec65-5f6a-b3d1-319c6670881a"
3+
version = "0.3.13"
34
authors = ["Dr. Zygmunt L. Szpak <zygmunt.szpak@gmail.com>"]
4-
version = "0.3.12"
55

66
[deps]
77
ImageBase = "c817782e-172a-44cc-b673-b171935fbb9e"
@@ -10,19 +10,27 @@ ImageTransformations = "02fcd773-0e25-5acc-982a-7f6622650795"
1010
Parameters = "d96e819e-fc66-5662-9728-84c9c7592b0a"
1111

1212
[compat]
13+
Aqua = "0.8"
1314
ImageBase = "0.1"
1415
ImageCore = "0.9.3, 0.10"
16+
ImageFiltering = "0.7"
17+
ImageMagick = "1"
1518
ImageTransformations = "0.8.1, 0.9, 0.10"
19+
LinearAlgebra = "1"
1620
Parameters = "0.12"
21+
StableRNGs = "1.0.4"
22+
Test = "1"
23+
TestImages = "1"
1724
julia = "1"
1825

1926
[extras]
2027
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
2128
ImageFiltering = "6a3955dd-da59-5b1f-98d4-e7296123deb5"
2229
ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1"
2330
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
31+
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
2432
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
2533
TestImages = "5e47fb64-e119-507b-a336-dd2b206d9990"
2634

2735
[targets]
28-
test = ["Test", "Aqua", "ImageFiltering", "TestImages", "ImageMagick", "LinearAlgebra"]
36+
test = ["Test", "Aqua", "ImageFiltering", "TestImages", "ImageMagick", "LinearAlgebra", "StableRNGs"]

src/ImageContrastAdjustment.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ using Parameters: @with_kw # Same as Base.@kwdef but works on Julia 1.0
1010
# TODO Relax this to all image color types
1111
const GenericGrayImage = AbstractArray{<:Union{Number, AbstractGray}}
1212

13+
@inline intensity(x::AbstractGray) = float(gray(x))
14+
@inline intensity(x::Real) = float(x)
15+
1316
# TODO: port HistogramAdjustmentAPI to ImagesAPI
1417
include("HistogramAdjustmentAPI/HistogramAdjustmentAPI.jl")
1518
import .HistogramAdjustmentAPI: AbstractHistogramAdjustmentAlgorithm,

src/algorithms/adaptive_equalization.jl

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -417,8 +417,9 @@ function perform_iterative_redistribution!(histogram::AbstractArray, limit::Numb
417417
end
418418

419419

420+
420421
function apply_cdf_transform(val::Union{Real,AbstractGray}, minval::Union{Real,AbstractGray}, maxval::Union{Real,AbstractGray}, edges::AbstractArray, cdf::AbstractArray)
421-
val, minval, maxval = gray(val), gray(minval), gray(maxval)
422+
val, minval, maxval = intensity(val), intensity(minval), intensity(maxval)
422423

423424
first_edge = first(edges)
424425
inv_step_size = 1 / step(edges)
@@ -494,13 +495,25 @@ function transform_image!(out, img, block_centroid_r, block_centroid_c, block_wi
494495
intensity_range, block_cdf)
495496
end
496497

497-
function transform_interior!(out, img, bounds, block_centroids, block_dimensions, intensity_range, block_cdf)
498+
@inline integer_store(::Type{T}, x, lo, hi) where {T} = convert(T, clamp(ceil(x), lo, hi))
499+
@inline clamp_store(::Type{T}, x, lo, hi) where {T} = convert(T, clamp(x, lo, hi))
500+
501+
storefn(::Type{T}) where {T<:Integer} = integer_store
502+
storefn(::Type{T}) where {T} = clamp_store
503+
504+
function transform_interior!(out::AbstractArray{T}, img, bounds, block_centroids, block_dimensions, intensity_range, block_cdf) where {T}
498505
rows, cols = bounds
499506
block_centroid_r, block_centroid_c = block_centroids
500507
block_width, block_height = block_dimensions
501508
minval, maxval = intensity_range
509+
510+
lo = intensity(minval)
511+
hi = intensity(maxval)
512+
store = storefn(T)
513+
502514
inv_block_height = 1 / block_height
503515
inv_block_width = 1 / block_width
516+
504517
for r in rows
505518
for c in cols
506519
rᵢ = round(Int, r * inv_block_height)
@@ -512,53 +525,70 @@ function transform_interior!(out, img, bounds, block_centroids, block_dimensions
512525
T₃ = apply_cdf_transform(img[r,c], minval, maxval, block_cdf[rᵢ + 1, cᵢ + 1]...)
513526
T₄ = apply_cdf_transform(img[r,c], minval, maxval, block_cdf[rᵢ, cᵢ + 1]...)
514527
interpolated_val = (1 - t)*(1 - u)*T₁ + t*(1 - u)*T₂ + t*u*T₃ + (1 - t)*u*T₄
515-
out[r,c] = eltype(img) <: Integer ? ceil(interpolated_val) : interpolated_val
528+
out[r,c] = store(T, interpolated_val, lo, hi)
516529
end
517530
end
518531
end
519532

520533

521-
function transform_vertical_strip!(out, img, bounds, block_centroid_r, cᵢ, block_height, intensity_range, block_cdf)
534+
function transform_vertical_strip!(out::AbstractArray{T}, img, bounds, block_centroid_r, cᵢ, block_height, intensity_range, block_cdf) where {T}
522535
rows, cols = bounds
523536
minval, maxval = intensity_range
537+
538+
lo = intensity(minval)
539+
hi = intensity(maxval)
540+
store = storefn(T)
541+
524542
inv_block_height = 1 / block_height
543+
525544
for r in rows
526545
for c in cols
527546
rᵢ = round(Int, r * inv_block_height)
528547
t = (r - block_centroid_r[rᵢ]) / block_height
529548
T₁ = apply_cdf_transform(img[r,c], minval, maxval, block_cdf[rᵢ, cᵢ]...)
530549
T₂ = apply_cdf_transform(img[r,c], minval, maxval, block_cdf[rᵢ + 1, cᵢ]...)
531550
interpolated_val = (1 - t)*T₁ + t*T₂
532-
out[r,c] = eltype(img) <: Integer ? ceil(interpolated_val) : interpolated_val
551+
out[r,c] = store(T, interpolated_val, lo, hi)
533552
end
534553
end
535554
end
536555

537556

538-
function transform_horizontal_strip!(out, img, bounds, block_centroid_c, rᵢ, block_width, intensity_range, block_cdf)
557+
function transform_horizontal_strip!(out::AbstractArray{T}, img, bounds, block_centroid_c, rᵢ, block_width, intensity_range, block_cdf) where {T}
539558
rows, cols = bounds
540559
minval, maxval = intensity_range
560+
561+
lo = intensity(minval)
562+
hi = intensity(maxval)
563+
store = storefn(T)
564+
541565
inv_block_width = 1 / block_width
566+
542567
for r in rows
543568
for c in cols
544569
cᵢ = round(Int, c * inv_block_width)
545570
u = (c - block_centroid_c[cᵢ]) / block_width
546571
T₁ = apply_cdf_transform(img[r,c], minval, maxval, block_cdf[rᵢ, cᵢ]...)
547572
T₂ = apply_cdf_transform(img[r,c], minval, maxval, block_cdf[rᵢ, cᵢ + 1]...)
548573
interpolated_val = (1 - u)*T₁ + u*T₂
549-
out[r,c] = eltype(img) <: Integer ? ceil(interpolated_val) : interpolated_val
574+
out[r,c] = store(T, interpolated_val, lo, hi)
550575
end
551576
end
552577
end
553578

554579

555-
function transform_corner!(out, img, bounds, rᵢ, cᵢ, intensity_range, block_cdf)
580+
function transform_corner!(out::AbstractArray{T}, img, bounds, rᵢ, cᵢ, intensity_range, block_cdf) where {T}
556581
rows, cols = bounds
557582
minval, maxval = intensity_range
583+
584+
lo = intensity(minval)
585+
hi = intensity(maxval)
586+
store = storefn(T)
587+
558588
for r in rows
559589
for c in cols
560590
val = apply_cdf_transform(img[r,c], minval, maxval, block_cdf[rᵢ, cᵢ]...)
561-
out[r,c] = eltype(img) <: Integer ? ceil(val) : val
591+
out[r,c] = store(T, val, lo, hi)
562592
end
563593
end
564594
end

src/algorithms/common.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ function transform_density!(out::GenericGrayImage, img::GenericGrayImage, edges:
33
first_newval, last_newval = first(newvals), last(newvals)
44
inv_step_size = 1/step(edges)
55
function transform(val)
6-
val = gray(val)
6+
val = intensity(val)
77
if val >= last_edge
88
return last_newval
99
elseif val < first_edge

src/algorithms/matching.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ function match_pdf!(img::GenericGrayImage, edges::AbstractArray, pdf::AbstractAr
144144
first_edge = first(edges)
145145
last_edge = last(edges)
146146
map!(img, img) do val
147-
val = gray(val)
147+
val = intensity(val)
148148
if isnan(val)
149149
return val
150150
else

src/build_histogram.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ function build_histogram(img::GenericGrayImage, edges::AbstractRange)
200200
elseif val < first_edge
201201
counts[lb] += 1
202202
else
203-
index = floor(Int, gray((val-first_edge)*inv_step_size)) + 1
203+
index = floor(Int, intensity((val-first_edge)*inv_step_size)) + 1
204204
counts[index] += 1
205205
end
206206
end

test/adaptive_equalization.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,22 @@
122122
imgeq₂ = adjust_histogram(imgg₂, algo)
123123
@test norm(imgeq₁ .- imgeq₂) 0.0
124124
end
125+
126+
@testset "CLAHE regression: Avoid failure on Gray{N0f8} images " begin
127+
# https://github.com/JuliaImages/ImageContrastAdjustment.jl/issues/64
128+
rng = StableRNG(123)
129+
img = Gray{N0f8}.([only(rand(rng,1)) for r = 1:600, c = 1:600])
130+
algo = AdaptiveEqualization( nbins = 256, minval = 0, maxval = 1, rblocks = 4, cblocks = 4, clip = 0.2)
131+
imgeq = adjust_histogram(img, algo)
132+
@test minimum(imgeq) >= Gray{N0f8}(0)
133+
@test maximum(imgeq) <= Gray{N0f8}(1)
134+
end
135+
136+
@testset "CLAHE supports raw UInt8 arrays without conversion failure" begin
137+
img = UInt8.([round(Int, 240 + 15 * sin(r / 8)) for r = 1:64, c = 1:64])
138+
algo = AdaptiveEqualization(nbins = 256, minval = 0, maxval = 255, rblocks = 8, cblocks = 8, clip = 0.2)
139+
imgeq = adjust_histogram(img, algo)
140+
@test eltype(imgeq) == UInt8
141+
@test minimum(imgeq) >= 0
142+
@test maximum(imgeq) <= 255
143+
end

test/runtests.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using ImageContrastAdjustment
2-
using Test, ImageCore, ImageFiltering, TestImages, LinearAlgebra
2+
using Test, ImageCore, ImageFiltering, TestImages, LinearAlgebra, StableRNGs
33
using Aqua
44

55
if Base.VERSION >= v"1.6"

0 commit comments

Comments
 (0)