Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This is a temporary module to validate `AbstractImageFilter` idea
# proposed in https://github.com/JuliaImages/ImagesAPI.jl/pull/3
module HistogramAdjustmentAPI
module ContrastAdjustmentAPI

using ImageCore # ColorTypes is sufficient

Expand All @@ -22,8 +22,9 @@ Filters are image algorithms whose input and output are both images
abstract type AbstractImageFilter <: AbstractImageAlgorithm end

include("histogram_adjustment.jl")
include("intensity_adjustment.jl")

# we do not export any symbols since we don't require
# package developers to implemente all the APIs

end # module HistogramAdjustmentAPI
end # module ContrastAdjustmentAPI
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
# usage example for package developer:
#
# import HistogramAdjustmentAPI: AbstractHistogramAdjustmentAlgorithm,
# import ContrastAdjustmentAPI: AbstractHistogramAdjustmentAlgorithm,
# adjust_histogram, adjust_histogram!

"""
AbstractHistogramAdjustmentAlgorithm <: AbstractImageFilter

The root type for `ImageContrastAdjustment` package.
A root type for `ImageContrastAdjustment` package that relates to algorithms
that manipulate contrast by operating on intensity histograms.

Any concrete histogram adjustment algorithm shall subtype it to support
[`adjust_histogram`](@ref) and [`adjust_histogram!`](@ref) APIs.
Expand All @@ -18,7 +19,7 @@ following pattern:

```julia
# first generate an algorithm instance
f = LinearStretching()
f = Equalization()

# then pass the algorithm to `adjust_histogram`
img_adjusted = adjust_histogram(img, f)
Expand Down
177 changes: 177 additions & 0 deletions src/ContrastAdjustmentAPI/intensity_adjustment.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# usage example for package developer:
#
# import ContrastAdjustmentAPI: AbstractIntensityAdjustmentAlgorithm,
# adjust_intensity, adjust_intensity!

"""
AbstractIntensityAdjustmentAlgorithm <: AbstractImageFilter

A root type for `ImageContrastAdjustment` package that relates to algorithms
that manipulate contrast without operating on intensity histograms.

Any concrete intensity adjustment algorithm shall subtype it to support
[`adjust_intensity`](@ref) and [`adjust_intensity!`](@ref) APIs.

# Examples

All intensity adjustment algorithms in ImageContrastAdjustment are called in the
following pattern:

```julia
# first generate an algorithm instance
f = LinearStretching()

# then pass the algorithm to `adjust_intensity`
img_adjusted = adjust_intensity(img, f)

# or use in-place version `adjust_intensity!`
img_adjusted = similar(img)
adjust_intensity!(img_adjusted, img, f)
```

Some algorithms also receive additional information as an argument,
e.g., `gamma` of `GammaCorrection`.

```julia
# you can explicit specify the parameters
f = GammaCorrection(gamma = 1.4)
```

For more examples, please check [`adjust_intensity`](@ref),
[`adjust_intensity!`](@ref) and concrete algorithms.
"""
abstract type AbstractIntensityAdjustmentAlgorithm <: AbstractImageFilter end
Copy link
Member

@johnnychen94 johnnychen94 Sep 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another choice is to make it

abstract type AbstractIntensityAdjustmentAlgorithm <: AbstractHistogramAdjustmentAlgorithm end

and so that we don't need to deprecate the old usages. We can just incrementally add new adjust_intensity methods with default alg to be LinearStretching. This means that both adjust_histogram and adjust_intensity would work for AbstractIntensityAdjustmentAlgorithm types.

How do you think?

Copy link
Member Author

@zygmuntszpak zygmuntszpak Sep 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that making

abstract type AbstractIntensityAdjustmentAlgorithm <: AbstractHistogramAdjustmentAlgorithm end

goes against the spirit of the issue #32 which seems to be specifically about the fact that operations that don't manipulate the histogram ought not to be conceptualised as histogram adjustment algorithms.

I reckon let's just go ahead and make the deprecations before we embed this all in the Images ecosystem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

goes against the spirit of the issue #32 which seems to be specifically about the fact that operations that don't manipulate the histogram ought

My argument is that not all histogram adjustment algorithm needs to explicitly construct a histogram. Directly adjust intensity can be considered as an implicit way to scale the x scale of the intensity histogram.

My personal understanding of #32 is that we just need a default algorithm for easier and more convenient usage to close #32. For generic histogram adjustment, there isn't a consensus default algorithm, but for intensity adjustment, we can default to linear stretching.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My argument is that not all histogram adjustment algorithm needs to explicitly construct a histogram. Directly adjust intensity can be considered as an implicit way to scale the x scale of the intensity histogram.

That was also my argument and the reason why I implemented it the way I did in the first place :D, but @stillyslalom seems to disagree with our view here.

We can just incrementally add new adjust_intensity methods with default alg to be LinearStretching. This means that both adjust_histogram and adjust_intensity would work for AbstractIntensityAdjustmentAlgorithm types.

I'm torn here. If we go this route, then what's the benefit of introducing the adjust_intensity function? Is it effectively not going to just be an alias for adjust_histogram?

I guess one distinction is that AbstractIntensityAdjustmentAlgorithm is typically embarrassingly parallelisable, whereas some of the histogram-based algorithms may not be.

My personal understanding of #32 is that we just need a default algorithm for easier and more convenient usage to close #32. For generic histogram adjustment, there isn't a consensus default algorithm, but for intensity adjustment, we can default to linear stretching.

I was planning to introduce some convenience functions such as span_contrast to address issues such as #27

Copy link
Member

@johnnychen94 johnnychen94 Sep 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all we need here is to introduce a convenient function span_contrast backed by LinearStretching to save some typings and easier to remember without checking the documentation. Changing the type hierarchy doesn't make things easier.

In the meantime, we can generalize LinearStretching and solves #33

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My argument is that not all histogram adjustment algorithm needs to explicitly construct a histogram. Directly adjust intensity can be considered as an implicit way to scale the x scale of the intensity histogram.

I don't know if I agree with this argument. We don't call fill! adjust_variance even though it does that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for sharing your thoughts. What is your suggestion regarding the type hierarchy? Should AbstractIntensityAdjustmentAlgorithn subtype the AbstractHistogramAdjustmentAlgorithm type?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vice versa, I'd say. The second sounds way more specific, the first is much more vague.


adjust_intensity!(out::Union{GenericGrayImage, AbstractArray{<:Color3}},
img,
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...) =

Check warning on line 48 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L48

Added line #L48 was not covered by tests
f(out, img, args...; kwargs...)

# TODO: Relax this to all color types
function adjust_intensity!(img::Union{GenericGrayImage, AbstractArray{<:Color3}},

Check warning on line 52 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L52

Added line #L52 was not covered by tests
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...)
tmp = copy(img)
f(img, tmp, args...; kwargs...)
return img

Check warning on line 57 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L55-L57

Added lines #L55 - L57 were not covered by tests
end

function adjust_intensity(::Type{T},

Check warning on line 60 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L60

Added line #L60 was not covered by tests
img,
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...) where T
out = similar(Array{T}, axes(img))
adjust_intensity!(out, img, f, args...; kwargs...)
return out

Check warning on line 66 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L64-L66

Added lines #L64 - L66 were not covered by tests
end

adjust_intensity(img::AbstractArray{T},
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...) where T <: Colorant =

Check warning on line 71 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L71

Added line #L71 was not covered by tests
adjust_intensity(T, img, f, args...; kwargs...)

# Do not promote Number to Gray{<:Number}
adjust_intensity(img::AbstractArray{T},
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...) where T <: Number =

Check warning on line 77 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L77

Added line #L77 was not covered by tests
adjust_intensity(T, img, f, args...; kwargs...)


# Handle instance where the input is a sequence of images.
adjust_intensity!(out_sequence::Vector{T},
img_sequence,
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...) where T <: Union{GenericGrayImage, AbstractArray{<:Color3}} =

Check warning on line 85 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L85

Added line #L85 was not covered by tests
f(out_sequence, img_sequence, args...; kwargs...)

# TODO: Relax this to all color types
function adjust_intensity!(img_sequence::Vector{T},

Check warning on line 89 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L89

Added line #L89 was not covered by tests
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...) where T <: Union{GenericGrayImage, AbstractArray{<:Color3}}
tmp = copy(img_sequence)
f(img_sequence, tmp, args...; kwargs...)
return img_sequence

Check warning on line 94 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L92-L94

Added lines #L92 - L94 were not covered by tests
end

function adjust_intensity(::Type{T},

Check warning on line 97 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L97

Added line #L97 was not covered by tests
img_sequence::Vector{<:AbstractArray},
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...) where T
N = length(img_sequence)
out_sequence = [similar(Array{T}, axes(img_sequence[n])) for n = 1:N]
adjust_intensity!(out_sequence, img_sequence, f, args...; kwargs...)
return out_sequence

Check warning on line 104 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L101-L104

Added lines #L101 - L104 were not covered by tests
end

adjust_intensity(img_sequence::Vector{<:AbstractArray{T}},
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...) where T <: Colorant =

Check warning on line 109 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L109

Added line #L109 was not covered by tests
adjust_intensity(T, img_sequence, f, args...; kwargs...)

# Do not promote Number to Gray{<:Number}
adjust_intensity(img_sequence::Vector{<:AbstractArray{T}},
f::AbstractIntensityAdjustmentAlgorithm,
args...; kwargs...) where T <: Number =

Check warning on line 115 in src/ContrastAdjustmentAPI/intensity_adjustment.jl

View check run for this annotation

Codecov / codecov/patch

src/ContrastAdjustmentAPI/intensity_adjustment.jl#L115

Added line #L115 was not covered by tests
adjust_intensity(T, img_sequence, f, args...; kwargs...)

### Docstrings

"""
adjust_intensity!([out,] img, f::AbstractIntensityAdjustmentAlgorithm, args...; kwargs...)

Adjust intensity of `img` using algorithm `f`.

# Output

If `out` is specified, it will be changed in place. Otherwise `img` will be changed in place.

# Examples

Just simply pass an algorithm to `adjust_intensity!`:

```julia
img_adjusted = similar(img)
adjust_intensity!(img_adjusted, img, f)
```

For cases you just want to change `img` in place, you don't necessarily need to manually
allocate `img_adjusted`; just use the convenient method:

```julia
adjust_intensity!(img, f)
```

See also: [`adjust_intensity`](@ref)
"""
adjust_intensity!

"""
adjust_intensity([T::Type,] img, f::AbstractIntensityAdjustmentAlgorithm, args...; kwargs...)

Adjust intensity of `img` using algorithm `f`.

# Output

The return image `img_adjusted` is an `Array{T}`.

If `T` is not specified, then it's inferred.
# Examples

Just simply pass the input image and algorithm to `adjust_intensity`

```julia
img_adjusted = adjust_intensity(img, f)
```

This reads as "`adjust_intensity` of image `img` using algorithm `f`".

You can also explicitly specify the return type:

```julia
img_adjusted_float32 = adjust_intensity(Gray{Float32}, img, f)
```

See also [`adjust_intensity!`](@ref) for in-place intensity adjustment.
"""
adjust_intensity
12 changes: 8 additions & 4 deletions src/ImageContrastAdjustment.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ using ImageCore.MappedArrays
using Parameters: @with_kw # Same as Base.@kwdef but works on Julia 1.0

# TODO: port HistogramAdjustmentAPI to ImagesAPI
include("HistogramAdjustmentAPI/HistogramAdjustmentAPI.jl")
import .HistogramAdjustmentAPI: AbstractHistogramAdjustmentAlgorithm,
adjust_histogram, adjust_histogram!
include("ContrastAdjustmentAPI/ContrastAdjustmentAPI.jl")
import .ContrastAdjustmentAPI: AbstractHistogramAdjustmentAlgorithm,
adjust_histogram, adjust_histogram!,
AbstractIntensityAdjustmentAlgorithm,
adjust_intensity, adjust_intensity!

# TODO Relax this to all image color types
const GenericGrayImage = AbstractArray{<:Union{Number, AbstractGray}}
Expand Down Expand Up @@ -37,6 +39,8 @@ export
ContrastStretching,
build_histogram,
adjust_histogram,
adjust_histogram!
adjust_histogram!,
adjust_intensity,
adjust_intensity!

end # module