-
Notifications
You must be signed in to change notification settings - Fork 21
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
Added findaxis
, finddim
, and hasdim
#89
Conversation
These are zero allocation methods that should allow easy development of axis name based methods.
Codecov Report
@@ Coverage Diff @@
## master #89 +/- ##
==========================================
+ Coverage 67.6% 68.55% +0.94%
==========================================
Files 9 9
Lines 426 442 +16
==========================================
+ Hits 288 303 +15
- Misses 138 139 +1
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## master #89 +/- ##
==========================================
+ Coverage 67.6% 69.26% +1.65%
==========================================
Files 9 9
Lines 426 449 +23
==========================================
+ Hits 288 311 +23
Misses 138 138
Continue to review full report at Codecov.
|
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 generally approving but you'll see there are some design issues to think about.
src/traits.jl
Outdated
if hasdim_noerror(syms, name) | ||
return getfield(nt, name) | ||
else | ||
return 0:0 |
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.
0:0
is not empty (it has one item). I think you mean 0:-1
.
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.
That's is what I was going for but if there's something more rational to fallback on I'm all ears.
src/traits.jl
Outdated
Returns the axis (as in `axes(img, dim)`) that corresponds to the `name`. If no | ||
matching name is found any empy axis (i.e. `0:0`) is returned. | ||
""" | ||
@inline findaxis(A::T, name::Symbol) where {T} = _findaxis(namedaxes(A), name) |
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.
::T where T
is extraneous here.
src/traits.jl
Outdated
_hasdim(::HasDimNames{true}, x::T, name::Symbol) where {T} = hasdim_noerror(names(x), name) | ||
_hasdim(::HasDimNames{false}, x::T, name::Symbol) where {T} = false | ||
|
||
Base.@pure function hasdim_noerror(dimnames::Tuple{Vararg{Symbol, N}}, name::Symbol) where 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.
The name here is slightly odd. You just want it to be different from hasdim
I guess?
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 adapted the method from what I found in NamedDims
and kind of stuck with the naming. My goal was to isolate it so it didn't have any other generic methods. I'm never sure if I'm going to mess up use of @pure
because I've read very different explanations of how it should be used from experienced users. I'd be happy to make it consistent with the other methods if it's not a problem.
src/traits.jl
Outdated
""" | ||
hasdim(x::T, name::Symbol) where {T} = _hasdim(HasDimNames(T), x, name) | ||
_hasdim(::HasDimNames{true}, x::T, name::Symbol) where {T} = hasdim_noerror(names(x), name) | ||
_hasdim(::HasDimNames{false}, x::T, name::Symbol) where {T} = 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.
finddim(A, :dim_1)
could work even if !hasdim(A, :dim_1)
. I worry a bit about API confusion/consistency in such cases. How important is it to have default names? Should we eliminate them and support operations only on explicitly-named axes?
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.
Good point. That needs to be more consistent. It seems kind of pointless to have it act on a default name that has no significant meaning. This makes me think that if there are default names they should probably be more meaningful, like those in AxisArrays. It may also help users if there's consistency with how AxisArrays names worked. I don't have a strong opinion either way, as long as I don't end being the guy that breaks JuliaImages.
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 I'm a fan of :row
, :col
, and :page
either. Those don't sound like image properties. At best they are linear algebra properties; just because we use 2d arrays for both doesn't mean image == matrix. (How often do you multiply an image times a vector??)
Can we just drop the default names altogether, and have these only operate on arrays with explicitly-assigned names?
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.
vote for :dim_1
, :dim_2
, and :dim_3
* New (old AxisArray) default names * More logical internal names (e.g., `_hasdim`)
1. The internal names now follow the general rule that the first internal layer of a method is "_methodname" and the second is "__methodname". This makes the internal vs public api more specific. 2. Added tests for each default name. Hopefully this gives 100% coverage for the trait interfaces methods.
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 it's possible to just get rid of the default names, I think we can merge this. If it's necessary to keep them, please elaborate on an actual use-case for which this is a make-or-break deal.
function namedaxes(::HasDimNames{false}, img::AbstractArray{T,N}) where {T,N} | ||
NamedTuple{default_names(Val(N))}(axes(img)) | ||
Returns the axis (as in `axes(img, dim)`) that corresponds to the `name`. If no | ||
matching name is found any empy axis (i.e. `0:0`) is returned. |
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.
matching name is found any empy axis (i.e. `0:0`) is returned. | |
matching name is found any empty axis (i.e. `0:-1`) is returned. |
I almost wonder if axisrange
is a better name for this operation?
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.
In this case would you be thinking of the axis
as the names and range
as the tuple of axes
?
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 just trying to ask myself, "what would I expect findaxis
to do?" And I am not sure. findmin
returns both the value and location of the minimum. So maybe findaxis
should return the value (i.e., range) and dimension index (like finddim
)? Or maybe axisvalue
, which is again back to the names chosen by AxisArrays.
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 guess I'm trying to imitate the behavior of axes(img, i)
where i
would be a symbol. The trick is that whatever type img
is may not wrap axes that are also directly linked to their names (e.g., one array type stores names wraps another type that stores axes). So if we can access the axes and names independently then we can use namedaxes
to connect these otherwise independent features.
I think the rationale of findaxis
returning something similar to findmin
makes a lot of sense though. In this case maybe findaxis
would return something like NamedTuple{(:dim_1)}((1:10,))
and axisvalue
would return the individual axis/range.
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 could go with that latter proposal.
I know that One suggestion: back when JuliaImages was mostly a one-person show, I was writing the algorithms at the same time as deciding about traits, and the efforts of implementing the algorithms forced me to think clearly about what I actually needed. (Not that I always got the design right, mind you!) You may be doing the same thing, so when you encounter an example, don't hesitate to post it in a comment or gist. It would help make sure we are thinking about the same thing. |
The downside to not using colordim(img) = finddim(img, :color)
coloraxis(img) = findaxis(img, :color)
timedim(img) = finddim(img, :time)
timeaxis(img) = findaxis(img, :time) Without defaults it would either throw an error or the default behavior would @inline finddim(A::T, name::Symbol) where {T} = _finddim(HasDimNames(T), A, name)
_finddim(::HasDimNames{false}, A::T, name::Symbol) where {T} = nothing
_finddim(::HasDimNames{true}, A::T, name::Symbol) where {T} = __finddim(names(A), name)
function timedim(img)
d = finddim(img)
if isnothing(d)
return 0
else
return d
end
end I don't think there's anything wrong with this approach, it's just more verbose. |
I kind of like the verbosity. And why couldn't |
The main reason was so that it always returned something of the same type, but that's only an issue if d = (NamedDimsArray, Array)
timedim(d[1]) |
Actually, returning |
It looks like the main methods this PR was for ( |
Unless there are any objections, I'm going to close this and probably open a more disruptive but complete PR using more recent developments for axes and named dimensions. |
These are zero allocation methods that should allow easy development of methods based on the axis names. For example, defining
timedim
with this implementation would only requiretimedim(x) = finddim(x, :time)
.