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

Is there a way for the user to define a metric by themself? #95

Open
000Justin000 opened this issue Jun 20, 2018 · 8 comments
Open

Is there a way for the user to define a metric by themself? #95

000Justin000 opened this issue Jun 20, 2018 · 8 comments

Comments

@000Justin000
Copy link

No description provided.

@dkarrasch
Copy link
Member

Sure, all metrics in the package are written by someone, they are not magically built in Julia. So you can closely follow the examples written here. Generally, you will need to first create a metric type

struct MyMetric <: Metric end

You will then need to define an instance of evaluate:

function evaluate(dist::MyMetric,a::AbstractArray,b::AbstractArray)
    # how to compute dist(a,b)
end

You then have automatically generic pairwise and colwise functions, which you can specifically optimize if needed, analogously to pairwise(::Euclidean,A,B) etc. It is probably easiest if you start with the whole block of code related to one specific metric that is somewhat close to your required structure, i.e., including convenience functions like, e.g., euclidean(a,b), and modify it according to your own needs. Hope this helps.

@000Justin000
Copy link
Author

000Justin000 commented Jun 20, 2018 via email

@dkarrasch
Copy link
Member

That's possible. You first import Distances or using Distances in your package, and then define the struct and the evaluate instance. In the first case, you will need to add the name of the package where the type is originally defined, say struct MyMetric <: Distances.Metric end, in the second that is not necessary. When you define "your" evaluate function, you overload the other evaluate instances, which are written for the other metric types. That is called multiple dispatch, so depending on the metric that you're asking for, evaluate will know what to do with a and b. You can do all of that in your own package, you don't need to have your own branch of Distances.jl, modify the package's files or anything like that. I have done that in a package that is not yet available, so unfortunately I can't give you a link. But I'm sure that other people here have other metrics defined in other packages. In fact, some of them have been ported to this package because of general interest, see some of the closed issues, or check out the Haversine.jl file. Those pieces of code used to be embedded in other packages before.

@dkarrasch
Copy link
Member

There is one more detail: you will need to explicitly import those methods from Distances.jl that you wish to extend/overload, so import Distances: evaluate for example, but Julia will tell you that anyway when you get there.

@000Justin000
Copy link
Author

Thank you for your information!

@Crown421
Copy link

I am trying to do the same right now, but it appears to be very difficult to do properly. In addition to the various eval_ functions, one needs to define

@eval @inline (dist::CustomMetric)(a::AbstractArray, b::AbstractArray) = _evaluate(dist, a, b)
@eval @inline (dist::CustomMetric)(a::Number, b::Number) = eval_end(dist, eval_op(dist, a, b))

which I believe is needed in the ...wise implementations. This is unclear to me, because from the code above one could dispatch via evaluate in those implementations as well?

After doing this, I run into the problem that _evaluate is explicitly specialized for ::UnionMetrics, and it is unclear to me how I properly extend _evaluate to also take my new metric.

I believe this is related to the discussion in #150. I would be grateful about advice on how to define "proper" custom metric in the most recent version, that takes advantage of the existing and theoretically generic code.

@johnnychen94
Copy link
Contributor

johnnychen94 commented Jan 26, 2020

If you're interested, https://github.com/JuliaImages/ImageDistances.jl/blob/10ebabafc534d0fc7046960558882b7606153adb/src/metrics.jl#L106-L131 is a live demo of defining new "metrics" for image inputs outside of Distances.jl :P

@jlapeyre
Copy link
Contributor

one needs to define

@eval @inline (dist::CustomMetric)(a::AbstractArray, b::AbstractArray) = _evaluate(dist, a, b)

@eval is not necessary here. It is normally used when you need to interpolate code into an expression using $. For example to write several methods programatically

Distances.jl/src/metrics.jl

Lines 212 to 214 in 8d6e093

for dist in weightedmetrics
@eval parameters(d::$dist) = d.weights
end

Your example above is similar to @eval x = 1. It gives the same result as x = 1, but @eval is superfluous.

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

No branches or pull requests

5 participants