cocoa-xu / evision

Evision: An OpenCV-Erlang/Elixir binding
https://evision.app
Apache License 2.0
322 stars 22 forks source link

feat: allow users to pass named arguments #256

Closed cocoa-xu closed 2 weeks ago

cocoa-xu commented 2 weeks ago

This should fix a category of issues where we cannot distinguish user intentions. For example, the first argument of Evision.applyColormap/2 is always expected to be an Evision.Mat.maybe_mat_in(), and the second argument can be either

However, as we allow numbers to be implicitly converted to a cv::Mat, we would always match the first overload function in the generated code, and the second argument will be passed as userColor instead of colormap.

def applyColorMap(src, userColor, opts) when (is_struct(src, Evision.Mat) or is_struct(src, Nx.Tensor) or is_number(src) or is_tuple(src)) and (is_struct(userColor, Evision.Mat) or is_struct(userColor, Nx.Tensor) or is_number(userColor) or is_tuple(userColor)) and (opts == nil or (is_list(opts) and is_tuple(hd(opts))))
do
  positional = [
    src: Evision.Internal.Structurise.from_struct(src),
    userColor: Evision.Internal.Structurise.from_struct(userColor)
  ]
  :evision_nif.applyColorMap(positional ++ Evision.Internal.Structurise.from_struct(opts || []))
    |> to_struct()
end
@spec applyColorMap(Evision.Mat.maybe_mat_in(), integer(), [{atom(), term()},...] | nil) :: Evision.Mat.t() | {:error, String.t()}
def applyColorMap(src, colormap, opts) when (is_struct(src, Evision.Mat) or is_struct(src, Nx.Tensor) or is_number(src) or is_tuple(src)) and is_integer(colormap) and (opts == nil or (is_list(opts) and is_tuple(hd(opts))))
do
  positional = [
    src: Evision.Internal.Structurise.from_struct(src),
    colormap: Evision.Internal.Structurise.from_struct(colormap)
  ]
  :evision_nif.applyColorMap(positional ++ Evision.Internal.Structurise.from_struct(opts || []))
    |> to_struct()
end

With that said, this is also the expected behaviour in cases like doing Evision.add(1, 2) where it's expected to convert numbers implicitly (which also follows the conventions in opencv-python).

Therefore, it's necessary to add support for named arguments for all functions so the users can make it clearer which overloaded C++ function they're trying to invoke. The only downside is, we currently cannot pass some positional arguments as positional ones while passing some of these as named one.

iex> img = Evision.imread("test/testdata/dog.jpg")
%Evision.Mat{
  channels: 3,
  dims: 2,
  type: {:u, 8},
  raw_type: 16,
  shape: {576, 768, 3},
  ref: #Reference<0.3798908985.2397700162.237747>
}
iex> Evision.applyColorMap(src: img, colormap: Evision.Constant.cv_COLORMAP_AUTUMN)
%Evision.Mat{
  channels: 3,
  dims: 2,
  type: {:u, 8},
  raw_type: 16,
  shape: {576, 768, 3},
  ref: #Reference<0.3798908985.2397700154.237107>
}

Related issue: #255