View Source Changelog

v0-1-21-2022-11-25

v0.1.21 (2022-11-25)

Browse the Repository | Released Assets

fixed

Fixed

  • [py_src] fixed functions in dnn that return *this.

    For this part, this original code (as in python-opencv) would case a new object to be allocated in C++ like

    TextDetectionModel_DB retval;
    retval = self.setSomeValue(...)
    return pyopencv_from(retval);

    Noticing the address of the object has changed (because it's a new one) after calling m.setBinaryThreshold.

    >>> import cv2'
    >>> m = cv2.dnn_TextDetectionModel_DB("DB_IC15_resnet18.onnx")
    >>> m
    < cv2.dnn.TextDetectionModel_DB 0x1064cf210>
    >>> m.setBinaryThreshold(0.5)
    < cv2.dnn.TextDetectionModel_DB 0x11ecda7f0>

v0-1-20-2022-11-24

v0.1.20 (2022-11-24)

Browse the Repository | Released Assets

fixed-1

Fixed

  • [Precompiled] fixed incorrect checksum for x86_64-linux-gnu.

v0-1-19-2022-11-14

v0.1.19 (2022-11-14)

Browse the Repository | Released Assets

changed

Changed

  • [py_src/c_src] Added has_default field to ArgInfo.

v0-1-18-2022-11-12

v0.1.18 (2022-11-12)

Browse the Repository | Released Assets

fixes

Fixes

  • [precompile] Fixed Mix.Tasks.Compile.EvisionPrecompiled.read_checksum_map/1
  • [py_src] Fixed code generation for derived classes in namespace cv::dnn
  • [test] added test for Evision.DNN.DetectionModel.

v0-1-17-2022-11-11

v0.1.17 (2022-11-11)

Browse the Repository | Released Assets

fixes-1

Fixes

  • [py_src] Fixed a code generation bug when all the input arguments of a function are optional.

changed-1

Changed

  • [example] Req.get! should only raise on 4xx and 5xx. Thanks @wojtekmach

added

Added

  • [example] Added two examples:

    • find and draw contours in an image.
    • extracting sudoku puzzle from an image.
  • [erlang] Structurised/recordized all #references that have their own Erlang module.

  • [erlang] Download precompiled binaries using evision_precompiled.erl.

  • [erlang] Generate typespecs.

v0-1-16-2022-10-30

v0.1.16 (2022-10-30)

Browse the Repository | Released Assets

fixes-2

Fixes

  • [deps] :kino will be an optional dependency, if we use if before defmodule. This reverts the changes in in v0.1.15.

    Thanks @josevalim for helping me figuring out why using if before defmodule would solve the problem. More details can be found here.

changes

Changes

  • [config.exs] Added configurable parameters related to rendering Evision.Mat in Kino. (They are optional and can also be adjusted in runtime)
    • config :evision, kino_render_image_encoding: :png
    • config :evision, kino_render_image_max_size: {8192, 8192}
    • config :evision, kino_render_tab_order: [:image, :raw, :numerical]

added-1

Added

v0-1-15-2022-10-26

v0.1.15 (2022-10-26)

Browse the Repository | Released Assets

changes-1

Changes

  • [mix compile] Suppress logs if evision.so is already presented when compiling from source.
  • [Precompile] Added precompile target aarch64-windows-msvc.

fixes-3

Fixes

  • [deps] :kino should be a required dependency

v0-1-14-2022-10-22

v0.1.14 (2022-10-22)

Browse the Repository | Released Assets

breaking-changes

Breaking Changes

  • [Precompile] Linux: remove GTK support in precompiled binaries. (This change only affects users on Linux.)

    This means functions in the Evision.HighGui module will return error if you are using precompiled binaries. This follows the convention in opencv-python.

    Workarounds for this:

    1. compile evision from source so that OpenCV will try to use the GUI backends they support on your system.
    2. use Evision.Wx. still in development, but basic functions like imshow/2 are available. However, it requires Erlang to be compiled with wxWidgets.
    3. use Livebook with :kino >= 0.7. evision has built-in support for Kino.Render which can automatically give a visualised result in Livebook. This requires :kino >= 0.7.
  • [Evision.Nx] Module Evision.Nx is now removed. Functions in Evision.Nx were moved to Evision.Mat in v0.1.13. Many thanks to @zacky1972 and @josevalim for their contributions to this module in very early days of the development.

    OldNew
    Evision.Nx.to_mat/{1,2}Evision.Mat.from_nx/{1,2}
    Evision.Nx.to_mat/5Evision.Mat.from_binary/5
    Evision.Nx.to_mat_2d/1Evision.Mat.from_nx_2d/1
    Evision.Nx.to_nx/1Evision.Mat.to_nx/1

added-2

Added

  • [Evision.Wx] implemented imshow/2, destroyWindow/1 and destroyAllWindows/0.

  • [SmartCell] Added SmartCells. They are optional and :kino >= 0.7 will be required to use them.

    If you'd like to use smartcells, please add :kino to deps in the mix.exs file.

    defp deps do
      [
        # ...
        {:kino, "~> 0.7"},
        # ...
      ]
    end

    And then please register smartcells to :kino by invoking Evision.SmartCell.register_smartcells().

    Evision.SmartCell.available_smartcells/0 will return all available smartcells.

    (Optional step) It's also possible to add only some of these smartcells, for example,

    Evision.SmartCell.register_smartcells([
      Evision.SmartCell.ML.TrainData,
      Evision.SmartCell.ML.SVM
    ])

v0-1-13-2022-10-19

v0.1.13 (2022-10-19)

Browse the Repository | Released Assets

fixes-4

Fixes

  • [csrc] Specialised function `evision_to [with Tp=cv::UMat]`.
  • [Evision.Backend] ensure that an Evision.Mat is returned from reject_error/1.

changed-2

Changed

  • [c_src] parseSequence will only handle tuples.

  • [Evision.Mat] Evision.Mat.quicklook will use alternative escaping sequence to avoid having a dedicate function in NIF. Thanks to @akash-akya and @kipcole9 (vix#68).

    ST means either BEL (hex code 0x07) or ESC \\.
  • [nx-integration] Functions in Evision.Nx are now moved to Evision.Mat.

    OldNew
    Evision.Nx.to_mat/{1,2}Evision.Mat.from_nx/{1,2}
    Evision.Nx.to_mat/5Evision.Mat.from_binary/5
    Evision.Nx.to_mat_2d/1Evision.Mat.from_nx_2d/1
    Evision.Nx.to_nx/1Evision.Mat.to_nx/1

As of v0.1.13, calls to these old functions will be forwarded to the corresponding new ones.

In the next release (v0.1.14), Evision.Nx will be removed.

  • [Evision.Mat] Evision.Mat.tranpose will use cv::transposeND if possible.
  • [Precompile] Try to compile OpenCV with gtk3 support.

added-3

Added

  • [test] Added a test for Evision.warpPerspective.
  • [example] Added an example for Evision.warpPerspective.
  • [example] Added some examples for Evision.warpPolar.
  • [example] Added QRCode encoding and decoding example.
  • [docs] Added a cheatsheet.

v0-1-12-2022-10-15

v0.1.12 (2022-10-15)

Browse the Repository | Released Assets

breaking-changes-1

Breaking Changes

fixes-5

Fixes

  • Function guard should also allow Nx.Tensor when the corresponding input argument is Evision.Mat.maybe_mat_in().
  • [Evision.Mat] Evision.Mat.quicklook/1 should also check the number of channels is one of [1, 3, 4] when dims == 2.
  • [c_src] evision_cv_mat_broadcast_to should call enif_free((void *)dst_data); if void * tmp_data = (void *)enif_alloc(elem_size * count_new_elem); failed.
  • [py_src] Fixed the template of simple call constructor.

changed-3

Changed

  • [Docs] Example Livebooks is now included in docs as extras.

  • [Evision.Mat] Evision.Mat.roi/{2,3} now supports Elixir Range.

  • [Evision.Mat] Implemented Access behaviour.

    • Access.fetch/2 examples:

      iex> img = Evision.imread("test/qr_detector_test.png")
      %Evision.Mat{
        channels: 3,
        dims: 2,
        type: {:u, 8},
        raw_type: 16,
        shape: {300, 300, 3},
        ref: #Reference<0.809884129.802291734.78316>
      }
      
      # Same behaviour as Nx. 
      # Also, img[0] gives the same result as img[[0]]
      # For this example, they are both equvilent of img[[0, :all, :all]]
      iex> img[[0]]
      %Evision.Mat{
        channels: 3,
        dims: 2,
        type: {:u, 8},
        raw_type: 16,
        shape: {1, 300, 3},
        ref: #Reference<0.809884129.802291731.77296>
      }
      
      # same as img[[0..100, 50..200, :all]]
      # however, currently we only support ranges with step size 1
      #
      # **IMPORTANT NOTE**
      #
      # also, please note that we are using Elixir.Range here
      # and Elixir.Range is **inclusive**, i.e, [start, end] 
      # while cv::Range `{integer(), integer()}` is `[start, end)`
      # the difference can be observed in the `shape` field
      iex> img[[0..100, 50..200]]
      %Evision.Mat{
        channels: 3,
        dims: 2,
        type: {:u, 8},
        raw_type: 16,
        shape: {101, 151, 3},
        ref: #Reference<0.809884129.802291731.77297>
      }
      iex> img[[{0, 100}, {50, 200}]]
      %Evision.Mat{
        channels: 3,
        dims: 2,
        type: {:u, 8},
        raw_type: 16,
        shape: {100, 150, 3},
        ref: #Reference<0.809884129.802291731.77297>
      }
      
      # for this example, the result is the same as `Evision.extractChannel(img, 0)`
      iex> img[[:all, :all, 0]]
      %Evision.Mat{
        channels: 1,
        dims: 2,
        type: {:u, 8},
        raw_type: 0,
        shape: {300, 300},
        ref: #Reference<0.809884129.802291731.77298>
      }
      iex> img[[:all, :all, 0..1]]
      %Evision.Mat{
        channels: 2,
        dims: 2,
        type: {:u, 8},
        raw_type: 8,
        shape: {300, 300, 2},
        ref: #Reference<0.809884129.802291731.77299>
      }
      
      # when index is out of bounds
      iex> img[[:all, :all, 42]]
      {:error, "index 42 is out of bounds for axis 2 with size 3"}
      
      # it works the same way for any dimensional Evision.Mat
      iex> mat = Evision.Mat.ones({10, 10, 10, 10, 10}, :u8)
      iex> mat[[1..7, :all, 2..6, 3..9, :all]]
      %Evision.Mat{
        channels: 1,
        dims: 5,
        type: {:u, 8},
        raw_type: 0,
        shape: {7, 10, 5, 7, 10},
        ref: #Reference<0.3015448455.3766878228.259075>
      }
    • Access.get_and_update/3 examples:

      iex> mat = Evision.Mat.zeros({5, 5}, :u8)
      iex> Evision.Nx.to_nx(mat)
      #Nx.Tensor<
        u8[5][5]
        Evision.Backend
        [
          [0, 0, 0, 0, 0],
          [0, 0, 0, 0, 0],
          [0, 0, 0, 0, 0],
          [0, 0, 0, 0, 0],
          [0, 0, 0, 0, 0]
        ]
      >
      iex> {old, new} = Evision.Mat.get_and_update(mat, [1..3, 1..3], fn roi ->
          {roi, Nx.broadcast(Nx.tensor(255, type: roi.type), roi.shape)}
      end)
      iex> Evision.Nx.to_nx(new)
      #Nx.Tensor<
        u8[5][5]
        Evision.Backend
        [
          [0, 0, 0, 0, 0],
          [0, 255, 255, 255, 0],
          [0, 255, 255, 255, 0],
          [0, 255, 255, 255, 0],
          [0, 0, 0, 0, 0]
        ]
      >

v0-1-11-2022-10-13

v0.1.11 (2022-10-13)

Browse the Repository | Released Assets

important-note

Important Note

In v0.1.10, an invalid checksum file was pushed to hex.pm, please read the changelog, especially the breaking changes in v0.1.10. Changelog for v0.1.10.

fixed-2

Fixed

v0-1-10-2022-10-13

v0.1.10 (2022-10-13)

Browse the Repository | Released Assets

important-note-1

Important Note

Invalid checksum file was pushed to hex.pm, please use v0.1.11 instead.

breaking-changes-2

Breaking Changes

  • Say goodbye to the bang(!) version functions!

    Thanks to @josevalim who wrote me this Errorize module back in Feb 2022, and in v0.1.10 this module will be removed. There are two main reasons for this:

    • I've managed to structurise all #references that have their own modules in #101.
    • After generating function specs, dialyzer seems to be really upset about these bang(!) version functions, and would emit a few thousand warnings.
  • [Precompile] Include NIF version in precompiled tarball filename.

    "evision-nif_#{nif_version}-#{target}-#{version}"
  • Return value changed if the first return type of the function is bool

    • If the function only returns a bool, the updated return value will simple be true or false.

      # before
      iex> :ok = Evision.imwrite("/path/to/image.png", img)
      iex> :error = Evision.imwrite("/path/to/image.png", invalid_img)
      # after
      iex> true = Evision.imwrite("/path/to/image.png", img)
      iex> false = Evision.imwrite("/path/to/image.png", invalid_img)
    • If the first return type is bool, and there is another value to return:

      # before
      iex> frame = Evision.VideoCapture.read(capture) # has a frame available
      iex> :error = Evision.VideoCapture.read(capture) # cannot read / no more available frames
      # after
      iex> frame = Evision.VideoCapture.read(capture) # has a frame available
      iex> false = Evision.VideoCapture.read(capture) # cannot read / no more available frames
    • If the first return type is bool, and there are more than one value to return:

      # before
      iex> {val1, val2} = Evision.SomeModule.some_function(arg1) # when succeeded
      iex> :error = Evision.SomeModule.some_function(capture) # when failed
      # after
      iex> {val1, val2} = Evision.SomeModule.some_function(arg1) # when succeeded
      iex> false = Evision.SomeModule.some_function(capture) # when failed
  • std::string and cv::String will be wrapped in a binary term instead of a list.

    For example,

    # before
    iex> {'detected text', _, _} = Evision.QRCodeDetector.detectAndDecode(qr, img)
    # after
    iex> {"detected text", _, _} = Evision.QRCodeDetector.detectAndDecode(qr, img)
  • Structurised all #references that have their own module.

    A list of modules that are now wrapped in structs can be found in the appendix section.

  • [Evision.DNN] As it's not possible to distinguish std::vector<uchar> and String in Elixir, Evision.DNN::readNet* functions that load a model from in-memoy buffer will be renamed to Evision.DNN::readNet*Buffer.

    For example,

    @spec readNetFromONNX(binary()) :: Evision.DNN.Net.t() | {:error, String.t()}
    def readNetFromONNX(onnxFile)
    
    @spec readNetFromONNXBuffer(binary()) :: Evision.DNN.Net.t() | {:error, String.t()}
    def readNetFromONNXBuffer(buffer)

changed-4

Changed

  • [Evision.Backend] raise a better error message for callbacks that haven't been implemented in Evision.Backend. Thanks to @josevalim

    An example of the updated error message:

    iex> Evision.Backend.slice(1,2,3,4,5)
    ** (RuntimeError) operation slice is not yet supported on Evision.Backend.
    Please use another backend like Nx.BinaryBackend or Torchx.Backend.
      To use Torchx.Backend, :torchx should be added to your app's deps.
      Please see https://github.com/elixir-nx/nx/tree/main/torchx for more information on how to install and use it.
    To convert the tensor to another backend, please use Evision.Nx.to_nx(tensor, Backend.ModuleName)
      for example, Evision.Nx.to_nx(tensor, Nx.BinaryBackend) or Evision.Nx.to_nx(tensor, Torchx.Backend).
    Pull request would be more than welcomed if you'd like to implmenent this function and make contributions.
        (evision 0.1.10-dev) lib/evision/backend.ex:815: Evision.Backend.slice/5
        iex:1: (file)
  • [Docs] Improved cross reference in inline docs. For example,

    Before

    @doc """
    ...
    @see setCVFolds
    ...
    """
    def getCVFolds(self) do

    After

    @doc """
    ...
    @see `setCVFolds/2`
    ...
    """
    def getCVFolds(self) do

    In this way, you can navigate to the referenced function in the generated html docs.

fixed-3

Fixed

  • Docs: included retval and self in the Return section.

added-4

Added

  • [Spec] Function spec for all Elixir functions, including generated ones.

  • [Evision.Mat] Added Evision.Mat.roi/{2,3}.

    iex> img = Evision.imread("test/qr_detector_test.png")
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {300, 300, 3},
      ref: #Reference<0.3957900973.802816029.173984>
    }
    
    # Mat operator()( const Rect& roi ) const;
    iex> sub_img = Evision.Mat.roi(img, {10, 10, 100, 200})
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {200, 100, 3},
      ref: #Reference<0.3957900973.802816020.173569>
    }
    
    # Mat operator()( Range rowRange, Range colRange ) const;
    iex> sub_img = Evision.Mat.roi(img, {10, 100}, {20, 200}) 
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {90, 180, 3},
      ref: #Reference<0.3957900973.802816020.173570>
    }
    iex> sub_img = Evision.Mat.roi(img, :all, {20, 200}) 
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {300, 180, 3},
      ref: #Reference<0.3957900973.802816020.173571>
    }
    
    # Mat operator()(const std::vector<Range>& ranges) const;
    iex> sub_img = Evision.Mat.roi(img, [{10, 100}, {10, 100}])
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {90, 90, 3},
      ref: #Reference<0.3957900973.802816020.173567>
    }
    iex> sub_img = Evision.Mat.roi(img, [{10, 100}, :all])
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {90, 300, 3},
      ref: #Reference<0.3957900973.802816020.173568>
    }
  • [Evision.Mat] Added Evision.Mat.quicklook/1.

    This function will check the value of :display_inline_image_iterm2 in the application config. If is true, then it will detect if current session is running in iTerm2 (by checking the environment variable LC_TERMINAL).

    If both are true, we next check if the image is a 2D image, also if its size is within the limits. The maximum size can be set in the application config, for example,

    config :evision, display_inline_image_iterm2: true
    config :evision, display_inline_image_max_size: {8192, 8192}

    If it passes all the checks, then it will be displayed as an inline image in iTerm2.

appendix

Appendix

List of modules that are now wrapped in structs.

v0-1-9-2022-10-09

v0.1.9 (2022-10-09)

Browse the Repository | Released Assets

bug-fixes

Bug Fixes

  • Mix.Tasks.Compile.EvisionPrecompiled: using File.cp_r/2 instead of calling cp -a via System.cmd/3.

  • Fixed TLS warnings when downloading precompiled tarball file. Thanks to @kipcole9!

  • Only include evision_custom_headers/evision_ml.hpp if the HAVE_OPENCV_ML macro is defined.

  • Support parsing RefWrapper<T> (&value)[N] from list or tuple. (#99)

    See the function in c_src/evision.cpp.

    bool parseSequence(ErlNifEnv *env, ERL_NIF_TERM obj, RefWrapper<T> (&value)[N], const ArgInfo& info)
    # `RotatedRect` has to be a tuple, {centre, size, angle}
    Evision.boxPoints!({{224.0, 262.5}, {343.0, 344.0}, 90.0})
    
    # while `Point`/`Size` can be either a list, `[x, y]`, or a tuple, `{x, y}`
    Evision.boxPoints!({[224.0, 262.5], [343.0, 344.0], 90.0})
  • Fixed the mapping from a type to the corresponding function guard in py_src/helper.py. (#99)

changed-5

Changed

  • Display RotatedRect type as {centre={x, y}, size={s1, s2}, angle} in docs.

v0-1-8-2022-10-08

v0.1.8 (2022-10-08)

Browse the Repository | Released Assets

changed-6

Changed

  • CMake and make (nmake if on Windows) will not be used to download and deploy precompiled binaries for Elixir users.

    This means that evision can be downloaded and deployed once Erlang and Elixir are properly installed on the system.

v0-1-7-2022-10-07

v0.1.7 (2022-10-07)

Browse the Repository | Released Assets

breaking-changes-3

Breaking Changes

  • EVISION_PREFER_PRECOMPILED is set to true by default.

    :evision will try to use precompiled binaries if available. Otherwise, it will fallback to building from source.

  • Precompiled binary filename changed:

    arm64-apple-darwin => aarch64-apple-darwin
    amd64-windows-msvc => x86_64-windows-msvc

changed-7

Changed

  • cv::VideoCapture will be wrapped in struct. For example:

    iex> cap = Evision.VideoCapture.videoCapture!("test/videocapture_test.mp4")
    %Evision.VideoCapture{
      fps: 43.2,
      frame_count: 18.0,
      frame_width: 1920.0,
      frame_height: 1080.0,
      isOpened: true,
      ref: #Reference<0.3650318819.3952214034.37793>
    }
    iex> frame = Evision.VideoCapture.read!(cap)
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {1080, 1920, 3},
      ref: #Reference<0.3650318819.3952214042.38343>
    }
  • Evision.Mat.empty/0 will also return an Evision.Mat struct (was returning #Reference<some random numbers>).

    iex> Evision.Mat.empty!()
    %Evision.Mat{
      channels: 1,
      dims: 0,
      type: {:u, 8},
      raw_type: 0,
      shape: {},
      ref: #Reference<0.2351084001.2568618002.207930>
    }
  • raise RuntimeError for all unimplemented :nx callbacks.

    raise RuntimeError, "not implemented yet"
  • Elixir functions that have the same name and arity will be grouped together now.

    This should massively reduce the number of warnings emitted by the elixir compiler.

  • Only generate corresponding binding code.

    • Only generate binding code for Elixir when compiling :evision using mix;
    • Only generate binding code for erlang when compiling :evision using rebar;

    It's possible to generate erlang and Elixir at the same time. However, currently it's only possible to do so when compiling evision using mix.

    # default value is `elixir` when compiling evision using `mix`
    # default value is `erlang` when compiling evision using `rebar`
    #
    # expected format is a comma-separated string
    export EVISION_GENERATE_LANG="erlang,elixir"
    
  • Better inline docs.

    • Inline docs will have a section for Positional Arguments and a section for Keyword Arguments. For example,

      @doc """
      ### Positional Arguments
      - **bboxes**: vector_Rect2d. 
      - **scores**: vector_float. 
      - **score_threshold**: float. 
      - **nms_threshold**: float. 
      
      ### Keyword Arguments
      - **eta**: float.
      - **top_k**: int.
      
      Performs non maximum suppression given boxes and corresponding scores.
      
      Python prototype (for reference): 
      ```
      NMSBoxes(bboxes, scores, score_threshold, nms_threshold[, eta[, top_k]]) -> indices
      ```
      """
      @doc namespace: :"cv.dnn"
      def nmsBoxes(bboxes, scores, score_threshold, nms_threshold, opts)
    • If a function (same name and arity) has multiple variants, the inline docs will show each of them in section ## Variant VAR_INDEX. For example,

      @doc """
      #### Variant 1:
      
      ##### Positional Arguments
      - **dx**: UMat. 
      - **dy**: UMat. 
      - **threshold1**: double. 
      - **threshold2**: double. 
      
      ##### Keyword Arguments
      - **edges**: UMat.
      - **l2gradient**: bool.
      
        \\overload
        Finds edges in an image using the Canny algorithm with custom image gradient.
          \\f$=\\sqrt{(dI/dx)^2 + (dI/dy)^2}\\f$ should be used to calculate the image gradient magnitude (
          L2gradient=true ), or whether the default \\f$L\\_1\\f$ norm \\f$=|dI/dx|+|dI/dy|\\f$ is enough (
          L2gradient=false ).
      
      Python prototype (for reference): 
      ```
      Canny(dx, dy, threshold1, threshold2[, edges[, L2gradient]]) -> edges
      ```
      #### Variant 2:
      
      ##### Positional Arguments
      - **image**: UMat. 
      - **threshold1**: double. 
      - **threshold2**: double. 
      
      ##### Keyword Arguments
      - **edges**: UMat.
      - **apertureSize**: int.
      - **l2gradient**: bool.
      
      Finds edges in an image using the Canny algorithm @cite Canny86 .
        The function finds edges in the input image and marks them in the output map edges using the
        Canny algorithm. The smallest value between threshold1 and threshold2 is used for edge linking. The
        largest value is used to find initial segments of strong edges. See
        <http://en.wikipedia.org/wiki/Canny_edge_detector>
          \\f$=\\sqrt{(dI/dx)^2 + (dI/dy)^2}\\f$ should be used to calculate the image gradient magnitude (
          L2gradient=true ), or whether the default \\f$L\\_1\\f$ norm \\f$=|dI/dx|+|dI/dy|\\f$ is enough (
          L2gradient=false ).
      
      Python prototype (for reference): 
      ```
      Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) -> edges
      ```
      
      """
      @doc namespace: :cv
      def canny(image, threshold1, threshold2, opts)
      when (is_reference(image) or is_struct(image)) and is_number(threshold1) and is_number(threshold2) and is_list(opts) and (opts == [] or is_tuple(hd(opts))), do: # variant 2
      
      def canny(dx, dy, threshold1, threshold2)
      when (is_reference(dx) or is_struct(dx)) and (is_reference(dy) or is_struct(dy)) and is_number(threshold1) and is_number(threshold2), do: # variant 1
  • Better integration with :nx.

    iex> t = Nx.tensor([[[0,0,0], [255, 255, 255]]], type: :u8)
    #Nx.Tensor<
      u8[1][2][3]
      [
        [
          [0, 0, 0],
          [255, 255, 255]
        ]
      ]
    >
    iex> mat = Evision.imread!("test.png")
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {1, 2, 3},
      ref: #Reference<0.2067356221.74055707.218654>
    }
    iex> mat = Evision.Mat.channel_as_last_dim!(mat)
    %Evision.Mat{
      channels: 1,
      dims: 3,
      type: {:u, 8},
      raw_type: 0,
      shape: {1, 2, 3},
      ref: #Reference<0.2067356221.74055698.218182>
    }
    iex> result = Evision.Mat.add!(t, mat)
    %Evision.Mat{
      channels: 1,
      dims: 3,
      type: {:u, 8},
      raw_type: 0,
      shape: {1, 2, 3},
      ref: #Reference<0.2067356221.74055698.218184>
    }
    iex> Evision.Nx.to_nx!(result)
    #Nx.Tensor<
      u8[1][2][3]
      Evision.Backend
      [
        [
          [255, 255, 255],
          [255, 255, 255]
        ]
      ]
    >
  • Implemented property setter for cv::Ptr<> wrapped types. For example,

    iex> k = Evision.KalmanFilter.kalmanFilter!(1, 1)
    #Reference<0.382162378.457572372.189094>
    iex> Evision.KalmanFilter.get_gain!(k) |> Evision.Nx.to_nx!
    #Nx.Tensor<
      f32[1][1]
      Evision.Backend
      [
        [0.0]
      ]
    >
    iex> Evision.KalmanFilter.set_gain!(k, Evision.Mat.literal!([1.0], :f32))
    #Reference<0.382162378.457572372.189094>
    iex> Evision.KalmanFilter.get_gain!(k) |> Evision.Nx.to_nx!
    #Nx.Tensor<
      f32[1][1]
      Evision.Backend
      [
        [1.0]
      ]
    >
  • More detailed error message for property getter/setter. For example,

    • When setting a property that is type A and value passed to the setter is type B, and there is no known conversion from B to A, then it will return an error-tuple

      iex> k = Evision.KalmanFilter.kalmanFilter!(1, 1)
      iex> Evision.KalmanFilter.set_gain(k, :p)
      {:error, "cannot assign new value, mismatched type?"}
      iex> Evision.KalmanFilter.set_gain(k, :p)
      ** (RuntimeError) cannot assign new value, mismatched type?
          (evision 0.1.7) lib/generated/evision_kalmanfilter.ex:175: Evision.KalmanFilter.set_gain!/2
          iex:7: (file)
    • For property getter/setter, if the self passed in is a different type than what is expected, an error-tuple will be returned

      iex> mat = Evision.Mat.literal!([1.0], :f32)
      %Evision.Mat{
        channels: 1,
        dims: 2,
        type: {:f, 32},
        raw_type: 5,
        shape: {1, 1},
        ref: #Reference<0.1499445684.3682467860.58544>
      }
      iex> Evision.KalmanFilter.set_gain(mat, mat) 
      {:error,
      "cannot get `Ptr<cv::KalmanFilter>` from `self`: mismatched type or invalid resource?"}
      iex> Evision.KalmanFilter.set_gain!(mat, mat)
      ** (RuntimeError) cannot get `Ptr<cv::KalmanFilter>` from `self`: mismatched type or invalid resource?
          (evision 0.1.7) lib/generated/evision_kalmanfilter.ex:175: Evision.KalmanFilter.set_gain!/2
          iex:2: (file)
  • evision_##NAME##_getp (in c_src/erlcompat.hpp) should just return true or false.

    Returning a ERL_NIF_TERM (enif_make_badarg) in the macro (when enif_get_resource fails) will prevent the caller from returning an error-tuple with detailed error message.

  • Improved the quality of generated inline docs.

    Also displays what variable(s) will be returned (when applicable) in the ##### Return section.

added-5

Added

  • Added Evision.Mat.literal/{1,2,3} to create Evision.Mat from list literals.

    Creating Evision.Mat from empty list literal ([]) is the same as calling Evision.Mat.empty().

    iex> Evision.Mat.literal!([])
    %Evision.Mat{
      channels: 1,
      dims: 0,
      type: {:u, 8},
      raw_type: 0,
      shape: {},
      ref: #Reference<0.1204050731.2031747092.46781>
    }

    By default, the shape of the Mat will stay as is.

    iex> Evision.Mat.literal!([[[1,1,1],[2,2,2],[3,3,3]]], :u8)
    %Evision.Mat{
      channels: 1,
      dims: 3,
      type: {:u, 8},
      raw_type: 0,
      shape: {1, 3, 3},
      ref: #Reference<0.512519210.691404819.106300>
    }

    Evision.Mat.literal/3 will return a valid 2D image if the keyword argument, as_2d, is set to true and if the list literal can be represented as a 2D image.

    iex> Evision.Mat.literal!([[[1,1,1],[2,2,2],[3,3,3]]], :u8, as_2d: true)
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {1, 3, 3},
      ref: #Reference<0.512519210.691404820.106293>
    }
  • Added Evision.Mat.channel_as_last_dim/1.

    This function does the opposite as to Evision.Mat.last_dim_as_channel/1.

    If the number of channels of the input Evision.Mat is greater than 1, then this function would convert the input Evision.Mat with dims dims=list(int()) to a 1-channel Evision.Mat with dims [dims | channels].

    If the number of channels of the input Evision.Mat is equal to 1,

    • if dims == shape, then nothing happens
    • otherwise, a new Evision.Mat that has dims=[dims | channels] will be returned

    For example,

    iex> mat = Evision.imread!("test.png")
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {1, 2, 3},
      ref: #Reference<0.2067356221.74055707.218654>
    }
    iex> mat = Evision.Mat.channel_as_last_dim!(mat)
    %Evision.Mat{
      channels: 1,
      dims: 3,
      type: {:u, 8},
      raw_type: 0,
      shape: {1, 2, 3},
      ref: #Reference<0.2067356221.74055698.218182>
    }
  • Automatically displays a tabbed output in Livebook if the type of evaluated result is Evision.Mat.

    This is an optional feature. To enable it, :kino should be added to deps, e.g.,

    defp deps do
      [
        # ...
        {:kino, "~> 0.7"},
        # ...
      ]
    end

    Now, with :kino >= v0.7 available, a tabbed output will shown in Livebook if the evaluated result is an Evision.Mat.

    A Raw tab will always be the first one, e.g.,

    %Evision.Mat{
      channels: 1,
      dims: 3,
      type: {:u, 8},
      raw_type: 0,
      shape: {1, 2, 3},
      ref: #Reference<0.3310236255.1057357843.168932>
    }

    For 2D images (dims == 2), the second tab will be Image, which displays the image.

    For all Evision.Mat, the last tab will be Numerical, which shows the numbers behind the scene. Of course, for large size Evision.Mat, only part of the data will be shown. A example output in this tab:

    #Nx.Tensor<
      u8[1][2][3]
      Evision.Backend
      [
        [
          [1, 2, 3],
          [1, 2, 3]
        ]
      ]
    >

v0-1-6-2022-09-29

v0.1.6 (2022-09-29)

Browse the Repository | Released Assets

breaking-changes-4

Breaking Changes

  • Evision.imencode/{2,3} will now return encoded image as binary instead of a list.

  • cv::Mat will be wrapped in struct. For example:

    iex> Evision.imread!("path/to/image.png")
    %Evision.Mat{
      channels: 3,
      dims: 2,
      type: {:u, 8},
      raw_type: 16,
      shape: {512, 512, 3},
      ref: #Reference<0.2992585850.4173463580.172624>
    }

    This should close #76.

v0-1-5-2022-09-27

v0.1.5 (2022-09-27)

Browse the Repository | Released Assets

changed-8

Changed

  • Always use Evision.Mat.from_binary_by_shape/3 for Evision.Nx.to_mat.
  • Check cv::Mat::Mat.type() when fetching the shape of a Mat. The number of channels will be included as the last dim of the shape if and only if cv::Mat::Mat.type() did not encode any channel information.

bug-fixes-1

Bug Fixes

  • Fixed Evision.Mat.transpose: should call shape! instead of shape. Thanks to @kipcole9 ! #77

added-6

Added

v0-1-4-2022-09-10

v0.1.4 (2022-09-10)

Browse the Repository | Released Assets

changed-9

Changed

bug-fixes-2

Bug Fixes

  • Fixed class inheritance issue in py_src/class_info.py.
  • Fixed missing comma in example livebooks' Mix.install. Thanks to @dbii.

added-7

Added

  • Added decision tree and random forest example.

v0-1-3-2022-09-01

v0.1.3 (2022-09-01)

Browse the Repository | Released Assets

bug-fixes-3

Bug Fixes

  • Fixed issues in restoring files from precompiled package for macOS and Linux.
    • Paths are now quoted.
    • using cp -RPf on macOS while cp -a on Linux.
  • Fixed destroyAllWindows in NIF. It was generated as 'erlang:destroyAllWindows/1' but it should be 'erlang:destroyAllWindows/0'.

v0-1-2-2022-08-26

v0.1.2 (2022-08-26)

Browse the Repository | Released Assets

bug-fixes-4

Bug Fixes

  • Fixed transpose.

added-8

Added

  • Added x86_64 musl compilation CI test.
  • Added a few precompilation musl targets:
    • x86_64-linux-musl
    • aarch64-linux-musl
    • armv7l-linux-musleabihf
    • riscv64-linux-musl

v0-1-1-2022-08-25

v0.1.1 (2022-08-25)

Browse the Repository | Released Assets

changed-10

Changed

  • Use OpenCV 4.6.0 by default.

  • Deprecated the use of the EVISION_PRECOMPILED_VERSION environment variable. The version information will be implied by the tag:

      def deps do
      [
          {:evision, "~> 0.1.1", github: "cocoa-xu/evision", tag: "v0.1.1"}
      ]
      end

    The value of the environment variable EVISION_PREFER_PRECOMPILED decides whether the precompiled artefacts will be used or not.

    From the next version (>=0.1.2), evision will set EVISION_PREFER_PRECOMPILED to true by default.

added-9

Added

  • Implemented a few nx callbacks (remaining ones will be implemented in the next release).

v0-1-0-2022-07-23

v0.1.0 (2022-07-23)

First release.