-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create a Complement type #189
Conversation
I will probably move this into its file that is included near the top of the file. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #189 +/- ##
===========================================
- Coverage 91.04% 77.63% -13.41%
===========================================
Files 3 3
Lines 268 313 +45
===========================================
- Hits 244 243 -1
- Misses 24 70 +46 ☔ View full report in Codecov by Sentry. |
I don't have a ton of experience with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like introducing Complement
but I worry the interpretation here isn't consistent. Perhaps you can explain more about why you think it should work this way?
src/ColorVectorSpace.jl
Outdated
`convert` to `C` at which point `complement` will be applied. Like `complement` | ||
the alpha channel is not modified. | ||
|
||
The main interpretation of this type is to invert the *interpretation* of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two "interpretation"s on one line. Do you mean "main application"?
src/ColorVectorSpace.jl
Outdated
Mathematical operations on `Complement` will affect the underlying | ||
`Colorant` rather than changing the value of the complement. That is |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm worried about this behavior---generally, we value the interpretation above the representation. What should Complement(0.1) + 0.1
return? I'd say 1.0 and not 0.2. Complement(0.0)
is white, but with this logic, Complement(0.0) == 1.0
should return false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps Complement(0.1) + 0.1
should be Complement(0.0)
then. That is Complement(x) + y
is Complement(x-y)
.
I'm tempted to drop the math part from line 315 down |
Bump, this would be nice to have merged for tlnagy/TiffImages.jl#134 |
@tlnagy what are the minimum features that you need? |
I think it would be nice for julia> res = Gray{N0f8}.(Matrix(I, 5, 5));
julia> Complement.(res) # doesn't work
TypeError: in typeassert, expected Complement{N0f8, 1, Gray{N0f8}}, got a value of type Gray{N0f8}
Stacktrace:
[1] setindex!
@ ./array.jl:971 [inlined]
[2] setindex!
@ ./multidimensional.jl:670 [inlined]
[3] macro expansion
@ ./broadcast.jl:974 [inlined]
[4] macro expansion
@ ./simdloop.jl:77 [inlined]
[5] copyto!
@ ./broadcast.jl:973 [inlined]
[6] copyto!
@ ./broadcast.jl:926 [inlined]
[7] copy
@ ./broadcast.jl:898 [inlined]
[8] materialize(bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{2}, Nothing, Type{Complement}, Tuple{Matrix{Gray{N0f8}}}})
@ Base.Broadcast ./broadcast.jl:873
[9] top-level scope
@ In[12]:1
julia> convert.(Gray{N0f8}, Complement.(res)) # does work, but clunky For |
Wait... why doesn't julia> using ColorVectorSpace, Colors, FixedPointNumbers, LinearAlgebra
julia> res = Gray{N0f8}.(Matrix(I, 5, 5));
julia> Complement.(res)
5×5 Array{Complement{N0f8},2} with eltype Complement{N0f8, 1, Gray{N0f8}}:
Complement(Gray{N0f8}(1.0)) Complement(Gray{N0f8}(0.0)) … Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(0.0))
Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(1.0)) Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(0.0))
Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(0.0))
Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(1.0)) Complement(Gray{N0f8}(0.0))
Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(0.0)) Complement(Gray{N0f8}(1.0)) I see that it should display, but it should "work". |
I had IMO the lazy complement should be realized on display. |
I think the problem is that |
I made |
Would it be possible to write a display for Complement that just punts to the correct display based on the wrapped type? i.e. |
I believe so. My understanding is that this is implemented in Colors.jl: https://github.com/JuliaGraphics/Colors.jl/blob/master/src%2Fdisplay.jl It looks like we just need to overload |
I have an implementation of display now. julia> using ColorVectorSpace, Colors, FixedPointNumbers, LinearAlgebra
julia> res = rand(RGB{Float64}, 10, 10)
julia> c_res = reinterpret(Complement, res) # less allocations
juila> using TestImages
julia> cameraman = testimage("cameraman.tif")
juila> c_cameraman = reinterpret(Complement, cameraman) # no allocation complement
julia> ca_cameraman = ComplementArray(c_cameraman) # no allocation, now of element type Gray{N0f8}
julia> [cameraman ca_cameraman] |
I'm getting tempted to implement this in its own package. It seems slightly out of place here and we might be able to iterate on it faster if it were independent. |
Looks great! There's a slight issue though with display for larger images: using ColorVectorSpace
using Colors
using FixedPointNumbers
using ImageShow
using LinearAlgebra
res = Gray{N0f8}.(Matrix(I, 500, 500)) # no warning
#snip
@time reinterpret(Complement, res) # works but with warning
#snip
┌ Warning: Output swatches are reduced due to the large size (500×500).
│ Load the ImageShow package for large images.
└ @ Colors ~/.julia/packages/Colors/mIuXl/src/display.jl:15 This happens despite ImageShow being loaded and this warning doesn't happen for non-Complemented images. EDIT: wrapping with ComplementArray fixes this: ComplementArray(reinterpret(Complement, res)) # no warning |
Should we push to get this merged now or should I break out into a package to iterate on this independently? |
Given that |
Sorry, I'll try to get this a review by next Monday. I got started and remembered getting worried about something fundamental, but that was long enough ago that I've forgotten. |
There's been considerable changes since you last took a look, Tim.
|
Implementation of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm on board with the general idea. Needs tests.
Base.parent(a::ComplementArray) = a.parent | ||
Base.size(a::ComplementArray) = size(a.parent) | ||
function Base.getindex(a::ComplementArray{T}, I::Vararg{Int,N})::T where {T,N} | ||
getindex(a.parent, I...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confused, shouldn't this be complement(getindex(a.parent, I...))
? Basically this works like mappedarray(complement, A)
, right?
Which begs the question, do we actually need ComplementArray
? But perhaps I misunderstand the intention.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still a TODO? Given that I don't know what this is about, I'm unsure.
src/ColorVectorSpace.jl
Outdated
getindex(a.parent, I...) | ||
end | ||
function Base.setindex!(a::ComplementArray{T}, v, I::Vararg{Int, N}) where {T,N} | ||
setindex!(a.parent, convert(eltype(a.parent), v)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The convert
will happen automatically, no need to do it manually.
function Base.setindex!(a::ComplementArray{T}, v, I::Vararg{Int, N}) where {T,N} | ||
setindex!(a.parent, convert(eltype(a.parent), v)) | ||
end | ||
Base.IndexStyle(a::ComplementArray) = IndexStyle(a.parent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you do this you should also implement getindex
for a single Int
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given your Vararg{Int, N}
for any N
I doubt that's really needed.
Base.IndexStyle(a::ComplementArray) = IndexStyle(a.parent) | ||
|
||
function Base.show(io::IO, mime::MIME"image/svg+xml", c::Complement{C}) where C | ||
show(io, mime, C(c)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens without this method? I would think that if you have all the abstractions correct, this Just Works without needing a new method. Similar thoughts about the other methods below.
Bump @mkitti, this would be great to have! |
I agree. I just have to find some time to get back around to it. Feel free to beat me to it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commit suggestions from Tim Holy
Co-authored-by: Tim Holy <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for adding the tests and other fixes! It's looking good, but I've left a few other notes about things that seem likely to need fixing.
In cases where you're unsure how something should behave, I tend to expect that mappedarray(complement, a)
should be a reasonable guide for how ComplementArray
should behave. IF, that is, I understand why it's here at all.
Base.parent(a::ComplementArray) = a.parent | ||
Base.size(a::ComplementArray) = size(a.parent) | ||
function Base.getindex(a::ComplementArray{T}, I::Vararg{Int,N})::T where {T,N} | ||
getindex(a.parent, I...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still a TODO? Given that I don't know what this is about, I'm unsure.
@test red(comp) ≈ red(comp2) | ||
@test green(comp) ≈ green(comp2) | ||
@test blue(comp) ≈ blue(comp2) | ||
@test alpha(comp) == alpha(comp2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@test alpha(comp) == alpha(comp2) | |
@test alpha(comp) == alpha(comp2) == alpha(_color) |
@test red(comp) ≈ red(comp2) | ||
@test green(comp) ≈ green(comp2) | ||
@test blue(comp) ≈ blue(comp2) | ||
@test alpha(comp) == alpha(comp2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@test alpha(comp) == alpha(comp2) | |
@test alpha(comp) == alpha(comp2) == 1 |
@test color_type(comp) == Complement{RGB{Float64}, Float64, 4} | ||
@test base_color_type(comp) == Complement{RGB, <:Any, 4} | ||
@test base_colorant_type(comp) == Complement{RGBA, <:Any, 4} | ||
@test red(comp) ≈ red(comp2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason these shouldn't be ==
instead of ≈
?
@test base_color_type(comp) == Complement{Gray, <:Any, 1} | ||
@test base_colorant_type(comp) == Complement{Gray, <:Any, 1} | ||
@test gray(comp) ≈ gray(comp2) | ||
@test alpha(comp) == alpha(comp2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@test alpha(comp) == alpha(comp2) | |
@test alpha(comp) == alpha(comp2) == 1 |
struct ComplementArray{T,N,A<:AbstractArray{<:Complement{T},N}} <: AbstractArray{T,N} | ||
parent::A | ||
ComplementArray(parent::A) where {T,N,A <: AbstractArray{<:Complement{T},N}} = new{T,N,A}(parent) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fundamentally I think I don't understand what this is for. I imagine this is the same as ca = mappedarray(complement, a)
but without relying on MappedArrays. Is that true, or is it something else?
ColorTypes.color(c::Complement) = Complement(color(parent(c))) | ||
ColorTypes.color_type(::Type{Complement{C, T, N}}) where {C,T,N} = Complement{color_type(C), T, N} | ||
ColorTypes.base_color_type(::Type{<: Complement{C, <: Any, N}}) where {N,C} = Complement{base_color_type(C), <: Any, N} | ||
ColorTypes.base_colorant_type(::Type{<: Complement{C, <: Any, N}}) where {N,C} = Complement{base_colorant_type(C), <: Any, N} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we always want the <:Any
on the RHS. Can we also specialize ColorTypes.base_color_type(::Type{Complement{C, T, N}}) where {C,T,N}
?
Moreover, since base_color_type(complement(c)) === base_color_type(c)
, I suspect you want the same property for Complement
. I think of Complement
as something that represents a lazy computation but it doesn't fundamentally change the underlying colorspace.
@test reinterpret(typeof(comp), _color) == comp | ||
@test reinterpret(Complement, _color) == comp | ||
end | ||
@test all(reinterpret(typeof(comp), [_color]) .≈ [comp]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing:
julia> complement(complement(c)) == c
true
julia> Complement(Complement(c)) == c
false
The latter should fail for ===
but pass for ==
.
function Base.setindex!(a::ComplementArray{T}, v, I::Vararg{Int, N}) where {T,N} | ||
setindex!(a.parent, convert(eltype(a.parent), v)) | ||
end | ||
Base.IndexStyle(a::ComplementArray) = IndexStyle(a.parent) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given your Vararg{Int, N}
for any N
I doubt that's really needed.
comp2 = complement(_color) | ||
@test comp isa Complement{RGBA{Float64}, Float64, 4} | ||
@test color_type(comp) == Complement{RGB{Float64}, Float64, 4} | ||
@test base_color_type(comp) == Complement{RGB, <:Any, 4} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you're testing types I recommend ===
(it's nicer on the compiler, for one, and should let the tests run more quickly). Not sure I agree with the choice for the RHS though.
|
or more symmetrically, |
My main concern is operation and support in the downstream packages. julia> Complement(RGB(1,1,0)) isa AbstractRGB
false It is not very pleasant to have to wrap One idea is to push such an ugly hack into a separate package. Edit: |
For reference, |
The other option is to convert |
@tlnagy The outstanding question here is if To clearly indicate that I am not intending further action here until a downstream application arises, I am closing this pull request. I would be willing to reopen if a need arises. |
This adds a
Complement{C,T,N}
type.The complement is to be interpreted as a lazy
complement
.Math onComplement
affects its underlyingColorant
.