JuliaImages / juliaimages.github.io

Documentation For JuliaImages
https://juliaimages.org
33 stars 56 forks source link

consistent method dispatch #103

Open johnnychen94 opened 5 years ago

johnnychen94 commented 5 years ago

This is not a trivial work that can be done in a short time.

I've added some simple but useful traits to ImageCore v0.8.2, which can be used throughout the JuliaImage repos.

For example:

const Gray2dImage = ImageCore.Gray2dImage

function denoise(img::Gray2dImage)
# generic denoise algorithm for Gray 2d images
end

function denoise(img::Gray2dImage{Normed})
# denoise algorithm for Gray 2d images with base type Normed
end

where

# defined in ImageCore but not exported
const GrayLike{T<:Union{Bool, FixedPoint, AbstractFloat}} = Union{T, AbstractGray{T}}
const Gray2dImage{T} = AbstractArray{<:GrayLike{T}, 2}

I can think of three advantages adapting this seems-trivial notation:

The third becomes possible if we do

const Gray2dImage = Union{ImageCore.Gray2dImage, OtherGray2dImageType}

Multiple dispatches is extremely important for robust image-processing algorithms because the algorithm behavior is totally different when types are different, e.g., dealing with 2d and 3d images, dealing with Gray and RGB images, and dealing with Normed and AbstractFloat base types. IMO, for most of the time, AbstractArray and AbstractArray{<:Colorant} are too generic. (These were easy to be typed down, but now Gray2dImage becomes easier)

The goal of ImageCore is to reduce too delicately manual dispatch for downstream packages as much as possible. In the meantime, ImageCore can't foresee and prepare for all possible types. For example, sometimes RGB images are processed channel-wise, and sometimes three channels are processed in a coherent way. Such kind of decisions must remain to downstream package developers.


In the future we may need similar symbols such as GenericImage, GenericColorImage, GenericRGBImage, Generic2dImage, RGB2dImage, but I haven't considered so far yet.

timholy commented 5 years ago

One thing to keep in mind is the possibility that denoise would not need to be different (why should the algorithm depend on the storage type?), but that some method it calls may need to be specialized. For example, if you're worried about overflow with Normed types, you may want to have

intermediatetype(::Type{T}) where T<:AbstractFloat = T
intermediatetype(::Type{T}) where T<:Normed = FixedPointNumbers.floattype(T)

and make sure that your temporary variables are initialized as z = zero(intermediatetype(T)) or something.

johnnychen94 commented 4 years ago

why should the algorithm depend on the storage type

I've transferred this issue to juliaimages.github.io because I think there's a need to document this; when we start to like multiple dispatches, we tend to dispatch on everything.