Skip to content
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

updates to ones, zeros, oneunit and zero to keep consistency to Base #175

Closed
wants to merge 4 commits into from
Closed

Conversation

johnnychen94
Copy link
Member

@johnnychen94 johnnychen94 commented Mar 1, 2020

There must be some discussion on these in julialang/julia but I can't find a source...

@codecov
Copy link

codecov bot commented Mar 1, 2020

Codecov Report

Merging #175 into master will increase coverage by 0.11%.
The diff coverage is n/a.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #175      +/-   ##
==========================================
+ Coverage   92.06%   92.18%   +0.11%     
==========================================
  Files           7        7              
  Lines         668      678      +10     
==========================================
+ Hits          615      625      +10     
  Misses         53       53              
Impacted Files Coverage Δ
src/traits.jl 96.37% <0.00%> (-0.17%) ⬇️
src/show.jl 100.00% <0.00%> (ø) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 03b029e...ac7c586. Read the comment docs.

With `one` deprecated in favor of `oneunit`, the fallback implementation of
`ones` would complain about the usage of `one`. This commit specializes on
`ones(Gray, args...)` so that it doesn't throw warnings.
This keep consistency to Base, e.g., `zero(0.8) == 0` and `oneunit(0.8) == 1`
Copy link
Collaborator

@kimikage kimikage left a comment

Choose a reason for hiding this comment

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

Except for UndefVarError: g not defined, I think there is no problem. I think that the return type of ones(Gray) should be decided in terms of image processing.

Edit:
Some of the added methods will be unnecessary when one(::Type{<:Gray}) switch to returning 1. It might be nice to have TODO comments.

test/traits.jl Outdated Show resolved Hide resolved
test/traits.jl Outdated Show resolved Hide resolved
test/traits.jl Outdated Show resolved Hide resolved
test/traits.jl Show resolved Hide resolved
and also fixes some dispatch issues on range type
Copy link
Member Author

@johnnychen94 johnnychen94 left a comment

Choose a reason for hiding this comment

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

Some of the added methods will be unnecessary when one(::Type{<:Gray}) switch to returning 1

🤔 not very sure which method would become unnecessary here. If you could elaborate more I'm glad to add more comments

@test zero(g) == zero(T) == Gray(zero(T))
end

@test_broken one(Gray{Float32}) * g == g * one(Gray{Float32}) == g
Copy link
Member Author

Choose a reason for hiding this comment

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

ColorVectorSpaces seems already fixes this, so it not a valid test anymore.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The test means one(Gray) should return 1. Do not remove it now.

Copy link
Member Author

Choose a reason for hiding this comment

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

Neither Gray{N0f8}(1) * Gray{N0f8}(0.8) nor 1 * Gray{N0f8}(0.8 are valid without ColorVectorSpaces...

And Gray{N0f8} == 1 is true

The test means one(Gray) should return 1

I'll add one more test for this.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Neither Gray{N0f8}(1) * Gray{N0f8}(0.8) nor 1 * Gray{N0f8}(0.8 are valid without ColorVectorSpaces...

You are right. However, I think the broken test makes sense to indicate that "this is broken" and "one is an identity element".

@johnnychen94
Copy link
Member Author

The latest commit also fixes JuliaGraphics/Colors.jl#369

@johnnychen94 johnnychen94 changed the title updates to ones, oneunit and zero to keep consistency to Base updates to ones, zeros, oneunit and zero to keep consistency to Base Mar 1, 2020
@kimikage
Copy link
Collaborator

kimikage commented Mar 1, 2020

not very sure which method would become unnecessary here.

I had assumed that eltype(ones(Gray)) === Gray. We will probably need almost all of the added methods. 😄

and add missing test for `one`
Comment on lines +529 to +532
# specialize ones and zeros for Gray type:
# * Base implementation uses `one` instead of `oneunit`; however, `one(Gray)` will return `1`
# * Base implementation use `Array{T}` to initialize the array, however, if we pass
# `Gray`, it gives an abstract eltype `Array{Gray}`, which is inconsistent to `zero(Gray)`
Copy link
Collaborator

@kimikage kimikage Mar 1, 2020

Choose a reason for hiding this comment

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

To be honest, I don't understand what this comment says. I feel the reason is the opposite.
Even though one(Gray) should return 1, not so. Thus, one shows the warning. Isn't this PR a workaround to avoid that warning? If one(Gray) returns 1, ones(Gray) returns an array of Gray(1).

Regarding the latter:

julia> zero(AbstractFloat)
0.0

julia> zeros(AbstractFloat)
0-dimensional Array{AbstractFloat,0}:
0.0

Gray{N0f8} is just a matter of practicality, i.e. speed or memory efficiency.

Copy link
Member Author

@johnnychen94 johnnychen94 Mar 1, 2020

Choose a reason for hiding this comment

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

I meant to make ones(Gray) still return an array of Gray(1) regardless of the fact that one(Gray) === 1 in the future. Did I misunderstand the original reason for the shift?

I think I need to raise zeros(AbstractFloat) issue at julialang/julia.. My idea is to make the array eltype equals(===) to its initializer, which makes more sense to me.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think I need to raise zeros(AbstractFloat) issue at julialang/julia..

Umm... What is the problem?

Copy link
Member Author

@johnnychen94 johnnychen94 Mar 1, 2020

Choose a reason for hiding this comment

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

That eltype(ones(AbstractFloat)) === typeof(one(AbstractFloat)) should be true (and for zeros)

Copy link
Collaborator

@kimikage kimikage Mar 1, 2020

Choose a reason for hiding this comment

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

Yes. I understand the claim, but I just want to know the reason.

Edit:
ones should return ones instead of oneunits as the name suggests.
If we follow your claim, ones should throw an error because of typeof(one(Gray{T}))!== Gray{T} (in the near future).

Copy link
Member Author

@johnnychen94 johnnychen94 Mar 1, 2020

Choose a reason for hiding this comment

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

Just opened a PR for this JuliaLang/julia#34948

wrt whether ones(Gray{N0f8}, 4, 4) should return array of N0f8 or Gray{N0f8}, I'd like to get @timholy involved. My consideration is that ones is used so widely that we might want to abuse the meaning for image processing purpose.

Copy link
Collaborator

@kimikage kimikage Mar 1, 2020

Choose a reason for hiding this comment

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

Definitely, ones(Gray{N0f8}, 4, 4) should not return an array of N0f8. Where did the choice come from?

ones(Gray{N0f8}, 2, 2)
--> Gray{N0f8}[? ?
               ? ?]
--> Gray{N0f8}[one(Gray{N0f8}) one(Gray{N0f8})
               one(Gray{N0f8}) one(Gray{N0f8})]
--> Gray{N0f8}[1 1
               1 1]
--> Gray{N0f8}[Gray{N0f8}(1) Gray{N0f8}(1) 
               Gray{N0f8}(1) Gray{N0f8}(1)]
ones(Gray, 2, 2)
--> Gray[? ?
         ? ?]
--> Gray[one(Gray) one(Gray)
         one(Gray) one(Gray)]
--> Gray[1 1
         1 1]
--> Gray[convert(Gray, 1) convert(Gray, 1)
         convert(Gray, 1) convert(Gray, 1)]
--> Gray[Gray{N0f8}(1) Gray{N0f8}(1) 
         Gray{N0f8}(1) Gray{N0f8}(1)]

As you know, the array with non-concrete element type has the performance issue. I think your suggestions are good for application specific types. But in general that is a "premature optimization".

@timholy
Copy link
Member

timholy commented Mar 1, 2020

zeros should be fine but that already seems to work properly.

ones is more problematic in that I don't think ones should return anything different from an array of one(T) but the distinction between one and oneunit causes trouble. xref JuliaGraphics/Colors.jl#369 (comment) and JuliaLang/julia#34948 (comment).

@johnnychen94
Copy link
Member Author

Since we have different rules for Gray and Number, wouldn't that be more surprising when ones returns an array of Number while its siblings zeros returns an array of Gray?

Also, what benefits would one(Gray) be by saying it returns a multiplicative identity for Gray when we disallow the multiplication...except that users don't get MethodError when they call ones?

I can understand the general rules here but it looks like I'm still keeping confused when using these in the wild; it's very hard to not thinking zeros/ones as the counterpart of that in Matlab and NumPy worlds.

Said this, I would feel a lot better if one returns the same type as zero, whatever it might be. (For Base consistency or for general image processing convenience)

@johnnychen94
Copy link
Member Author

Let's not include this PR in v0.10 and leave more time to think about it.

@timholy
Copy link
Member

timholy commented Mar 2, 2020

The point is that if one is supposed to be the multiplicative identity, it needs to at least be that. So we have to decide whether Gray(1)*Gray(1) == Gray(1). Currently it's not unless you load ColorVectorSpace. And for RGB the situation is even more confusing.

Really, ones should just die and be replaced by fill.

@johnnychen94
Copy link
Member Author

johnnychen94 commented Mar 2, 2020

I believe it's harmless to support 0.8 * Gray(1.) and Gray(1.) * 0.8 here, which can be read as "multiply the grayscale by 0.8"? (but not Gray(1) * Gray(1)). This makes 1 a valid multiplicative identity for Gray.

Really, ones should just die and be replaced by fill.

This becomes the most unambiguous solution for me now.

@timholy
Copy link
Member

timholy commented Mar 2, 2020

This makes 1 a valid multiplicative identity for Gray

If we support multiplication here at all. From a standpoint of pure colorimetry I'm not certain that multiplying RGB by anything other than 1 makes sense. (Of course we have to support it in image processing...)

@kimikage
Copy link
Collaborator

kimikage commented Mar 3, 2020

I don't think ones should return anything different from an array of one(T)

Originally posted by @timholy in #175 (comment)

I think ones(T, ...) should return an array of the first argument T, because ones (and zeros) should be for the "array constructor".
If one(T) should not be converted to T (e.g. Dates.Day), ones should throw an error. However,
both a Gray value and 1 can be converted to Gray.
There is disagreement about whether that is appropriate, but at least, the current implementation agrees with that (cf. #175 (comment)).

julia> ones(AbstractFloat,2,2)
2×2 Array{AbstractFloat,2}:
 1.0  1.0
 1.0  1.0

Of course, one(AbstractFloat) == 1.0, so this is not a good example, though. I mean:

julia> Base.one(::Type{AbstractFloat}) = 1 # `1` should be a multiplicative identity for `AbstractFloat`, too.

julia> one(AbstractFloat)
1

julia> ones(AbstractFloat, 2,2)
2×2 Array{AbstractFloat,2}:
 1.0  1.0
 1.0  1.0

julia> Base.one(::Type{AbstractFloat}) = true # I think this is somewhat strange, but ok.

julia> oneunit(AbstractFloat)
1.0

julia> ones(AbstractFloat, 2,2)
2×2 Array{AbstractFloat,2}:
 1.0  1.0
 1.0  1.0

We can get the same results for concrete types (e.g. Float64), since AbstractFloat is a supertype of all floating-point types.

Whether ones(Gray, ...) should return an array of Gray{N0f8} is another matter. I don't care about it. (I think throwing error is also reasonable.)

@kimikage kimikage mentioned this pull request Mar 8, 2020
@timholy
Copy link
Member

timholy commented Mar 12, 2020

I'm converging on the following strategy:

  1. delete one(Gray{T}) from this package altogether (one(Gray{T}) will throw an error if you've only loaded ColorTypes)
  2. In ColorVectorSpace, define one(Gray{T}) as Gray{T}(1). That's the opposite of what our depwarn here indicates, but we'd be using the longstanding existence of the depwarn as an excuse for step 1 above.
  3. Move this PR to ColorVectorSpace

@kimikage
Copy link
Collaborator

It looks reasonable to me. I'm sure some people will wonder why ColorTypes doesn't define one, though. 😄

@timholy
Copy link
Member

timholy commented Mar 12, 2020

I am beginning to think we need to extend Julia's printing of MethodErrors to allow packages to push custom hints. Similar to JuliaLang/julia#34642 and many similar changes, but package-extensible.

EDIT: JuliaLang/julia#35094

@kimikage
Copy link
Collaborator

The issues related to this PR have been settled by PR #243.

Now, ones(Gray, ...), zeros(RGB, ...) etc. are not recommended for the performance reason.

julia> ones(Gray{N0f8}, 4, 4)
4×4 Matrix{Gray{N0f8}}:
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0
 1.0  1.0  1.0  1.0

julia> ones(Gray, 4, 4)
4×4 Matrix{Gray}:
 Gray{N0f8}(1.0)  Gray{N0f8}(1.0)  Gray{N0f8}(1.0)  Gray{N0f8}(1.0)
 Gray{N0f8}(1.0)  Gray{N0f8}(1.0)  Gray{N0f8}(1.0)  Gray{N0f8}(1.0)
 Gray{N0f8}(1.0)  Gray{N0f8}(1.0)  Gray{N0f8}(1.0)  Gray{N0f8}(1.0)
 Gray{N0f8}(1.0)  Gray{N0f8}(1.0)  Gray{N0f8}(1.0)  Gray{N0f8}(1.0)

@kimikage kimikage closed this May 14, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

support ones(::Type{<:AbstractGray}, sz) promote_rule for Type{Any}
3 participants