Current section

Files

Jump to
evision mix.exs
Raw

mix.exs

defmodule Evision.MixProject.Metadata do
@moduledoc false
def app, do: :evision
def version, do: "0.2.17"
def github_url, do: "https://github.com/cocoa-xu/evision"
def opencv_version, do: "4.13.0"
# only means compatible. need to write more tests
def compatible_opencv_versions,
do: [
"4.5.3",
"4.5.4",
"4.5.5",
"4.6.0",
"4.7.0",
"4.8.0",
"4.9.0",
"4.10.0",
"4.11.0",
"4.13.0"
]
def default_cuda_version, do: {"12", "9"}
def all_cuda_version(target) do
cuda11 = [{"11", "8"}, {"11", "9"}]
cuda12 = [{"12", "8"}, {"12", "9"}]
if String.contains?("msvc", target) do
cuda12
else
cuda11 ++ cuda12
end
end
end
defmodule Mix.Tasks.Compile.EvisionPrecompiled do
@moduledoc false
use Mix.Task
require Logger
alias Evision.MixProject.Metadata
@precompiled_lock_stale_seconds 3600
@available_targets [
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-linux-gnu",
"aarch64-linux-musl",
"x86_64-linux-gnu",
"x86_64-linux-musl",
"armv7l-linux-gnueabihf",
"armv6-linux-gnueabihf",
"s390x-linux-gnu",
"riscv64-linux-gnu",
"riscv64-linux-musl",
"i686-linux-gnu",
"x86_64-unknown-freebsd",
"x86_64-windows-msvc"
]
@compile_nif_version "2.16"
def get_available_nif_versions do
["2.16", "2.17"]
end
def available_targets do
opencv_version = System.get_env("OPENCV_VER", Metadata.opencv_version())
available_targets = @available_targets
if Enum.member?(["4.11.0"], opencv_version) do
available_targets -- ["ppc64le-linux-gnu", "aarch64-windows-msvc"]
else
available_targets
end
end
def available_nif_urls(_host_nif_version, version \\ Metadata.version()) do
nif_version = get_compile_nif_version()
Enum.reduce(available_targets(), [], fn target, acc ->
no_contrib = get_download_url(target, version, nif_version, false, false, "", "")
with_contrib = get_download_url(target, version, nif_version, true, false, "", "")
with_cuda =
if target in ["x86_64-linux-gnu", "aarch64-linux-gnu"] do
Enum.map(Metadata.all_cuda_version(target), fn {cuda_ver, cudnn_version} ->
get_download_url(target, version, nif_version, true, true, cuda_ver, cudnn_version)
end)
else
[]
end
[no_contrib, with_contrib] ++ with_cuda ++ acc
end)
end
def current_target_nif_url(_host_nif_version, version \\ Metadata.version()) do
{target, _} = get_target()
nif_version = get_compile_nif_version()
enable_contrib = System.get_env("EVISION_ENABLE_CONTRIB", "true") == "true"
if enable_contrib do
System.put_env("EVISION_ENABLE_CONTRIB", "true")
else
System.put_env("EVISION_ENABLE_CONTRIB", "false")
end
enable_cuda = System.get_env("EVISION_ENABLE_CUDA", "false") == "true"
{cuda_version, cudnn_version} =
if enable_cuda do
{defalt_cuda, default_cudnn} = Metadata.default_cuda_version()
System.put_env("EVISION_ENABLE_CUDA", "true")
{
System.get_env("EVISION_CUDA_VERSION", defalt_cuda),
System.get_env("EVISION_CUDNN_VERSION", default_cudnn)
}
else
System.put_env("EVISION_ENABLE_CUDA", "false")
{"", ""}
end
get_download_url(
target,
version,
nif_version,
enable_contrib,
enable_cuda,
cuda_version,
cudnn_version
)
end
def checksum_file(app \\ Mix.Project.config()[:app]) when is_atom(app) do
# Saves the file in the project root.
{
Path.join(File.cwd!(), "checksum.exs"),
Path.join([File.cwd!(), "checksum.erl"])
}
end
defp read_checksum_map(app \\ Mix.Project.config()[:app]) when is_atom(app) do
{file, _} = checksum_file(app)
with {:ok, contents} <- File.read(file),
{%{} = contents, _} <- Code.eval_string(contents) do
contents
else
_ -> %{}
end
end
def get_compile_nif_version do
@compile_nif_version
end
def get_nif_version do
to_string(:erlang.system_info(:nif_version))
end
def get_target do
case System.get_env("MIX_TARGET") do
"ios" ->
{"aarch64-apple-darwin-ios", ["aarch64", "apple", "darwin"]}
"xros" ->
{"aarch64-apple-darwin-xros", ["aarch64", "apple", "darwin"]}
_ ->
target = String.split(to_string(:erlang.system_info(:system_architecture)), "-")
[arch, os, abi] =
case Enum.count(target) do
3 ->
target
4 ->
[arch, _vendor, os, abi] = target
[arch, os, abi]
1 ->
with ["win32"] <- target do
["x86_64", "windows", "msvc"]
else
[unknown_target] ->
[unknown_target, "unknown", nil]
end
end
abi =
case abi do
"darwin" <> _ ->
"darwin"
"freebsd" <> _ ->
"freebsd"
"win32" ->
{compiler_id, _} = :erlang.system_info(:c_compiler_used)
case compiler_id do
:msc -> "msvc"
_ -> to_string(compiler_id)
end
_ ->
abi
end
arch =
if os == "windows" do
case String.downcase(System.get_env("PROCESSOR_ARCHITECTURE")) do
"arm64" ->
"aarch64"
arch when arch in ["x64", "x86_64", "amd64"] ->
"x86_64"
arch ->
arch
end
else
arch
end
# FreeBSD's system_architecture is `arch-vendor-osversion` (e.g. amd64-portbld-freebsd13.5),
# but the 3-tuple parser above puts the vendor in the `os` slot. Canonical precompiled
# names use `unknown` as the vendor and normalize arch to LLVM-style.
{arch, os} =
if abi == "freebsd" do
{if(arch == "amd64", do: "x86_64", else: arch), "unknown"}
else
{arch, os}
end
abi = System.get_env("TARGET_ABI", abi)
os = System.get_env("TARGET_OS", os)
arch =
if String.match?(System.get_env("TARGET_CPU", ""), ~r/arm11[357]6/) do
"armv6"
else
System.get_env("TARGET_ARCH", arch)
end
{Enum.join([arch, os, abi], "-"), [arch, os, abi]}
end
end
def use_precompiled?(log? \\ false) do
prefer_precompiled_default =
if "dev" in Version.parse!(Metadata.version()).pre do
"false"
else
"true"
end
prefer_precompiled = System.get_env("EVISION_PREFER_PRECOMPILED", prefer_precompiled_default)
use_precompiled? = prefer_precompiled == "true"
if log? do
if use_precompiled? do
Logger.info(
"EVISION_PREFER_PRECOMPILED: true; try to download and use the precompiled library."
)
else
Logger.info(
"EVISION_PREFER_PRECOMPILED: #{prefer_precompiled}; will not use precompiled binaries."
)
end
end
use_precompiled?
end
def available_for_target?(target, log? \\ false) do
available_for_target? = Enum.member?(available_targets(), target)
if log? do
if available_for_target? do
Logger.info("Current target `#{target}` has precompiled binaries.")
else
Logger.warning("Current target `#{target}` does not have precompiled binaries.")
end
end
available_for_target?
end
def available_for_nif_version?(nif_version, log? \\ false) do
host_nif_version =
case Enum.count(String.split(nif_version, ".")) do
2 ->
Version.parse!("#{nif_version}.0")
3 ->
Version.parse!(nif_version)
_ ->
raise RuntimeError, "unknown nif_version: #{nif_version}"
end
compile_nif_version = get_compile_nif_version()
available_for_nif_version? =
Version.compare(Version.parse!("#{compile_nif_version}.0"), host_nif_version) != :gt
if log? do
if available_for_nif_version? do
Logger.info(
"Current host NIF version is `#{nif_version}`, will use precompiled binaries with NIF version #{compile_nif_version}."
)
else
Logger.warning(
"Current host NIF version `#{nif_version}` does not have precompiled binaries."
)
end
end
available_for_nif_version?
end
# https_opts and related code are taken from
# https://github.com/elixir-cldr/cldr_utils/blob/master/lib/cldr/http/http.ex
@certificate_locations [
# Configured cacertfile
System.get_env("ELIXIR_MAKE_CACERT"),
# A little hack to use cacerts.pem in CAStore/certfi
## when `:evision` is a dependency in other application
## => Mix.ProjectStack.project_file()
## when `:evision` is the top application
## => __ENV__.file
Path.join([
Path.dirname(Mix.ProjectStack.project_file() || __ENV__.file),
"deps/castore/priv/cacerts.pem"
]),
Path.join([
Path.dirname(Mix.ProjectStack.project_file() || __ENV__.file),
"deps/certfi/priv/cacerts.pem"
]),
# Debian/Ubuntu/Gentoo etc.
"/etc/ssl/certs/ca-certificates.crt",
# Fedora/RHEL 6
"/etc/pki/tls/certs/ca-bundle.crt",
# OpenSUSE
"/etc/ssl/ca-bundle.pem",
# OpenELEC
"/etc/pki/tls/cacert.pem",
# CentOS/RHEL 7
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
# Open SSL on MacOS
"/usr/local/etc/openssl/cert.pem",
# MacOS & Alpine Linux
"/etc/ssl/cert.pem"
]
|> Enum.reject(&is_nil/1)
@doc false
def certificate_store do
@certificate_locations
|> Enum.find(&File.exists?/1)
|> warning_if_no_cacertfile!
|> :erlang.binary_to_list()
end
defp warning_if_no_cacertfile!(nil) do
Logger.warning("""
No certificate trust store was found.
Tried looking for: #{inspect(@certificate_locations)}
A certificate trust store is required in
order to download locales for your configuration.
Since elixir_make could not detect a system
installed certificate trust store one of the
following actions may be taken:
1. Install the hex package `castore`. It will
be automatically detected after recompilation.
2. Install the hex package `certifi`. It will
be automatically detected after recomilation.
3. Specify the location of a certificate trust store
by configuring it in environment variable:
export ELIXIR_MAKE_CACERT="/path/to/cacerts.pem"
""")
""
end
defp warning_if_no_cacertfile!(file) do
file
end
defp https_opts(hostname) do
cert_file = certificate_store()
if secure_ssl?() and cert_file != [] do
[
ssl: [
verify: :verify_peer,
cacertfile: certificate_store(),
depth: 4,
ciphers: preferred_ciphers(),
versions: protocol_versions(),
eccs: preferred_eccs(),
reuse_sessions: true,
server_name_indication: hostname,
secure_renegotiate: true,
customize_hostname_check: [
match_fun: :public_key.pkix_verify_hostname_match_fun(:https)
]
]
]
else
[
ssl: [
verify: :verify_none,
server_name_indication: hostname,
secure_renegotiate: true,
reuse_sessions: true,
versions: protocol_versions(),
ciphers: preferred_ciphers()
]
]
end
end
def preferred_ciphers do
preferred_ciphers = [
# Cipher suites (TLS 1.3): TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256
%{cipher: :aes_128_gcm, key_exchange: :any, mac: :aead, prf: :sha256},
%{cipher: :aes_256_gcm, key_exchange: :any, mac: :aead, prf: :sha384},
%{cipher: :chacha20_poly1305, key_exchange: :any, mac: :aead, prf: :sha256},
# Cipher suites (TLS 1.2): ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:
# ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:
# ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
%{cipher: :aes_128_gcm, key_exchange: :ecdhe_ecdsa, mac: :aead, prf: :sha256},
%{cipher: :aes_128_gcm, key_exchange: :ecdhe_rsa, mac: :aead, prf: :sha256},
%{cipher: :aes_256_gcm, key_exchange: :ecdh_ecdsa, mac: :aead, prf: :sha384},
%{cipher: :aes_256_gcm, key_exchange: :ecdh_rsa, mac: :aead, prf: :sha384},
%{cipher: :chacha20_poly1305, key_exchange: :ecdhe_ecdsa, mac: :aead, prf: :sha256},
%{cipher: :chacha20_poly1305, key_exchange: :ecdhe_rsa, mac: :aead, prf: :sha256},
%{cipher: :aes_128_gcm, key_exchange: :dhe_rsa, mac: :aead, prf: :sha256},
%{cipher: :aes_256_gcm, key_exchange: :dhe_rsa, mac: :aead, prf: :sha384}
]
:ssl.filter_cipher_suites(preferred_ciphers, [])
end
def protocol_versions do
if otp_version() < 25 do
[:"tlsv1.2"]
else
[:"tlsv1.2", :"tlsv1.3"]
end
end
def otp_version do
:erlang.system_info(:otp_release) |> List.to_integer()
end
def preferred_eccs do
# TLS curves: X25519, prime256v1, secp384r1
preferred_eccs = [:secp256r1, :secp384r1]
:ssl.eccs() -- (:ssl.eccs() -- preferred_eccs)
end
def secure_ssl? do
case System.get_env("ELIXIR_MAKE_UNSAFE_HTTPS") do
nil -> true
"FALSE" -> false
"false" -> false
"nil" -> false
"NIL" -> false
_other -> true
end
end
def download!(url, save_as, overwrite \\ false)
def download!(url, save_as, false) do
unless File.exists?(save_as) do
download!(url, save_as, true)
end
checksum(save_as)
end
def download!(url, save_as, true) do
with {:ok, body} <- download!(url) do
File.write!(save_as, body)
checksum(save_as)
end
end
def download!(url) do
opts = [body_format: :binary]
arg = {url, []}
# https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/inets
# TODO: This may no longer be necessary from Erlang/OTP 26.0 or later.
http_options = https_opts(String.to_charlist(URI.parse(url).host))
if proxy = System.get_env("HTTP_PROXY") || System.get_env("http_proxy") do
Mix.shell().info("Using HTTP_PROXY: #{proxy}")
%{host: host, port: port} = URI.parse(proxy)
:httpc.set_options([{:proxy, {{String.to_charlist(host), port}, []}}])
end
if proxy = System.get_env("HTTPS_PROXY") || System.get_env("https_proxy") do
Mix.shell().info("Using HTTPS_PROXY: #{proxy}")
%{host: host, port: port} = URI.parse(proxy)
:httpc.set_options([{:https_proxy, {{String.to_charlist(host), port}, []}}])
end
Mix.shell().info("Downloading precompiled tarball from: #{url}")
case :httpc.request(:get, arg, http_options, opts) do
{:ok, {{_, 200, _}, _, body}} ->
{:ok, body}
{:ok, {{_, status_code, _}, _, _body}} ->
raise RuntimeError, "Cannot download file from #{url}: #{status_code}."
{:error, reason} ->
raise RuntimeError, "Cannot download file from #{url}: #{reason}."
end
end
def filename(
target,
version,
nif_version,
enable_contrib,
enable_cuda,
cuda_version,
cudnn_version,
with_ext \\ ""
)
def filename(
target,
version,
nif_version,
_enable_contrib = false,
_enable_cuda,
_cuda_version,
_cudnn_version,
with_ext
) do
"evision-nif_#{nif_version}-#{target}-#{version}#{with_ext}"
end
def filename(
target,
version,
nif_version,
_enable_contrib = true,
_enable_cuda = false,
_cuda_version,
_cudnn_version,
with_ext
) do
"evision-nif_#{nif_version}-#{target}-contrib-#{version}#{with_ext}"
end
def filename(
target,
version,
nif_version,
_enable_contrib = true,
_enable_cuda = true,
cuda_version,
cudnn_version,
with_ext
) do
"evision-nif_#{nif_version}-#{target}-contrib-cuda#{cuda_version}-cudnn#{cudnn_version}-#{version}#{with_ext}"
end
def get_download_url(
target,
version,
nif_version,
enable_contrib,
enable_cuda,
cuda_version,
cudnn_version
) do
tar_file =
filename(
target,
version,
nif_version,
enable_contrib,
enable_cuda,
cuda_version,
cudnn_version,
".tar.gz"
)
"#{Metadata.github_url()}/releases/download/v#{version}/#{tar_file}"
end
def cache_dir do
cache_opts = if System.get_env("MIX_XDG"), do: %{os: :linux}, else: %{}
cache_dir = :filename.basedir(:user_cache, "", cache_opts)
cache_dir = System.get_env("ELIXIR_MAKE_CACHE_DIR", cache_dir)
File.mkdir_p!(cache_dir)
cache_dir
end
defp precompiled_lock_dir(cache_dir) do
Path.join(cache_dir, "evision-precompiled.lock")
end
defp with_precompiled_lock(lock_dir, fun) do
case File.mkdir(lock_dir) do
:ok ->
try do
fun.()
after
File.rm_rf(lock_dir)
end
{:error, :eexist} ->
if stale_precompiled_lock?(lock_dir) do
File.rm_rf(lock_dir)
else
Process.sleep(1000)
end
with_precompiled_lock(lock_dir, fun)
{:error, reason} ->
raise RuntimeError,
"Failed to acquire precompiled install lock #{lock_dir}: #{inspect(reason)}"
end
end
defp stale_precompiled_lock?(lock_dir) do
with {:ok, %File.Stat{mtime: mtime}} <- File.stat(lock_dir) do
now = :calendar.datetime_to_gregorian_seconds(:calendar.local_time())
modified = :calendar.datetime_to_gregorian_seconds(mtime)
now - modified > @precompiled_lock_stale_seconds
else
_ -> false
end
end
@checksum_algo :sha256
def checksum_algo, do: @checksum_algo
def checksum(file_path, algo \\ @checksum_algo) do
case File.read(file_path) do
{:ok, content} ->
file_hash =
algo
|> :crypto.hash(content)
|> Base.encode16(case: :lower)
{:ok, "#{algo}", "#{file_hash}"}
{:error, reason} ->
{:error,
"cannot read the file for checksum comparison: #{inspect(file_path)}. " <>
"Reason: #{inspect(reason)}"}
end
end
def app_priv(app \\ Metadata.app()) when is_atom(app) do
build_path = Mix.Project.build_path()
Path.join([build_path, "lib", "#{app}", "priv"])
end
def prepare(
target,
os,
version,
nif_version,
enable_contrib,
enable_cuda,
cuda_version,
cudnn_version
) do
name =
filename(
target,
version,
nif_version,
enable_contrib,
enable_cuda,
cuda_version,
cudnn_version
)
filename =
filename(
target,
version,
nif_version,
enable_contrib,
enable_cuda,
cuda_version,
cudnn_version,
".tar.gz"
)
cache_dir = cache_dir()
cache_file = Path.join([cache_dir, filename])
unarchive_dest_dir = Path.join([cache_dir, name])
cached_priv_dir = Path.join([cache_dir, name, "priv"])
cached_elixir_dir = Path.join([cache_dir, name, "elixir_generated"])
evision_so_file =
if os == "windows" do
"evision.dll"
else
"evision.so"
end
windows_fix_so_file =
if os == "windows" do
"windows_fix.dll"
else
"windows_fix.so"
end
evision_so_file = Path.join([app_priv(), evision_so_file])
windows_fix_so_file = Path.join([app_priv(), windows_fix_so_file])
with_precompiled_lock(precompiled_lock_dir(cache_dir), fn ->
# first we check if we already have the NIF file
# if not, then we defintely need to copy it (and other files) from somewhere
needs_copy = !File.exists?(evision_so_file) or !File.exists?(windows_fix_so_file)
if needs_copy do
# to copy the nif file and other files
# we first check if they are cached in the cache dir
if !File.dir?(unarchive_dest_dir) do
# if `unarchive_dest_dir` does not exists
# then perhaps we need to extract these files from the precompiled binary tarball
#
# to extract files from the tarball
# we check if the tarball is cached in the cache dir
{needs_download, needs_unarchive} =
if File.exists?(cache_file) do
# and it has to be a regular file
if File.regular?(cache_file) do
# of course we have to compute its checksum
{:ok, algo, cache_file_checksum} = checksum(cache_file)
Logger.info(
"Precompiled binary tarball cached at #{cache_file}, #{algo}=#{cache_file_checksum}"
)
# and verify the checksum in the map
{checksum_matched?, algo_in_map, checksum_in_map} =
verify_checksum(cache_file, algo, cache_file_checksum)
if checksum_matched? do
# if these checksum matches
# we can extract files from the tarball
{false, true}
else
Logger.error(
"Checksum mismatched: #{cache_file}[#{algo}=#{cache_file_checksum}], expected:[#{algo_in_map}=#{checksum_in_map}]"
)
Logger.warning("Will delete cached tarball #{cache_file} and re-download")
# otherwise we delete the cached file and download it again
{true, true}
end
else
# not a regular file
# remove it and download again
File.rm_rf!(cache_file)
Logger.warning(
"Cached file at #{cache_file} is not a regular file, will delete it and re-download"
)
{true, true}
end
else
{true, true}
end
if needs_download do
download_url =
get_download_url(
target,
version,
nif_version,
enable_contrib,
enable_cuda,
cuda_version,
cudnn_version
)
{:ok, _} = Application.ensure_all_started(:inets)
{:ok, _} = Application.ensure_all_started(:ssl)
# of course we have to verify its checksum too
{:ok, algo, checksum} = download!(download_url, cache_file, true)
Logger.info(
"Precompiled binary tarball downloaded and saved to #{cache_file}, #{algo}=#{checksum}"
)
{checksum_matched?, algo_in_map, checksum_in_map} =
verify_checksum(cache_file, algo, checksum)
if !checksum_matched? do
msg =
"Checksum mismatched: downloaded file: #{cache_file}[#{algo}=#{checksum}], expected:[#{algo_in_map}=#{checksum_in_map}]"
Logger.error(msg)
raise RuntimeError, msg
end
end
if needs_unarchive do
File.rm_rf!(unarchive_dest_dir)
unarchive!(cache_file, cache_dir)
end
end
# if `unarchive_dest_dir` exists and is a directory
# then we can simply copy they to the expected locations
deploy_from_dir!(cached_priv_dir, cached_elixir_dir)
else
:ok
end
end)
end
def verify_checksum(cache_file, algo, cache_file_checksum) do
checksum_map = read_checksum_map()
basename = Path.basename(cache_file)
case Map.fetch(checksum_map, basename) do
{:ok, algo_with_hash} ->
[algo_in_map, hash] = String.split(algo_with_hash, ":")
{cache_file_checksum == hash and algo_in_map == algo, algo_in_map, hash}
:error ->
{:error,
"the precompiled NIF file does not exist in the checksum file. " <>
"Please consider run: `EVISION_FETCH_PRECOMPILED=true mix evision.fetch --all` to generate the checksum file."}
end
end
def deploy_from_dir!(cached_priv_dir, cached_elixir_dir) do
app_priv = app_priv()
generated_elixir_dir = Path.join([File.cwd!(), "lib", "generated"])
if File.exists?(app_priv) do
File.rm_rf!(app_priv)
end
if File.exists?(generated_elixir_dir) do
File.rm_rf!(generated_elixir_dir)
end
Logger.info("Copying priv directory: #{cached_priv_dir} => #{app_priv}")
with {:ok, _} <- File.cp_r(cached_priv_dir, app_priv) do
:ok
else
error ->
msg = "Failed to copy priv directory, `File.cp_r`: #{inspect(error)}"
Logger.error(msg)
raise RuntimeError, msg
end
Logger.info(
"Copying generated Elixir binding files: #{cached_elixir_dir} => #{generated_elixir_dir}"
)
with {:ok, _} <- File.cp_r(cached_elixir_dir, generated_elixir_dir) do
:ok
else
error ->
msg = "Failed to copy generated Elixir binding files, `File.cp_r`: #{inspect(error)}"
Logger.error(msg)
raise RuntimeError, msg
end
end
def unarchive!(filepath, to_directory) do
File.mkdir_p!(to_directory)
with :ok <- :erl_tar.extract(filepath, [:compressed, {:cwd, to_directory}]) do
:ok
else
err ->
msg = "Failed to unarchive tarball file: #{filepath}, error: #{inspect(err)}"
Logger.error(msg)
raise RuntimeError, msg
end
end
def deploy_type(log? \\ false) do
{target, [_arch, _os, abi]} = get_target()
{:ok, %Version{pre: pre}} = Version.parse(Metadata.version())
nif_version = get_nif_version()
if use_precompiled?(log?) and pre != ["dev"] and
available_for_target?(target, log?) and available_for_nif_version?(nif_version, log?) do
{:precompiled, abi}
else
{:build_from_source, abi}
end
end
@impl true
def run(_args) do
{target, [_arch, os, _abi]} = get_target()
evision_so_file =
if os == "windows" do
"evision.dll"
else
"evision.so"
end
windows_fix_so_file =
if os == "windows" do
"windows_fix.dll"
else
"windows_fix.so"
end
evision_so_file = Path.join([app_priv(), evision_so_file])
windows_fix_so_file = Path.join([app_priv(), windows_fix_so_file])
if !File.exists?(evision_so_file) or !File.exists?(windows_fix_so_file) do
with {:precompiled, _} <- deploy_type(true) do
version = Metadata.version()
nif_version = get_compile_nif_version()
enable_contrib = System.get_env("EVISION_ENABLE_CONTRIB", "true") == "true"
enable_cuda = System.get_env("EVISION_ENABLE_CUDA", "false") == "true"
{default_cuda_version, default_cudnn_version} = Metadata.default_cuda_version()
cuda_version = System.get_env("EVISION_CUDA_VERSION", default_cuda_version)
cudnn_version = System.get_env("EVISION_CUDNN_VERSION", default_cudnn_version)
prepare(
target,
os,
version,
nif_version,
enable_contrib,
enable_cuda,
cuda_version,
cudnn_version
)
else
_ ->
raise RuntimeError, "Cannot use precompiled binaries."
end
else
:ok
end
end
end
defmodule Evision.MixProject do
use Mix.Project
require Logger
alias Evision.MixProject.Metadata
alias Mix.Tasks.Compile.EvisionPrecompiled
@source_url "#{Metadata.github_url()}/tree/v#{Metadata.version()}"
def project do
{compilers, make_env} =
if System.get_env("EVISION_FETCH_PRECOMPILED") != "true" do
{deploy_type, target_abi} = EvisionPrecompiled.deploy_type(false)
if deploy_type == :build_from_source do
{cmake_options, enabled_modules} = generate_cmake_options()
make_env = %{
"HAVE_NINJA" => "#{System.find_executable("ninja") != nil}",
"OPENCV_VER" =>
opencv_versions(System.get_env("OPENCV_VER", Metadata.opencv_version())),
"MAKE_BUILD_FLAGS" =>
System.get_env("MAKE_BUILD_FLAGS", "-j#{System.schedulers_online()}"),
"CMAKE_OPTIONS" => cmake_options,
"ENABLED_CV_MODULES" => enabled_modules,
"TARGET_ABI" => System.get_env("TARGET_ABI", target_abi),
"EVISION_GENERATE_LANG" => System.get_env("EVISION_GENERATE_LANG", "elixir"),
"EVISION_PREFER_PRECOMPILED" => "false"
}
make_env =
case :os.type() do
{:win32, _} ->
Map.put_new(
make_env,
"PYTHON3_EXECUTABLE",
System.find_executable("python3") || System.find_executable("python")
)
_ ->
make_env
end
{[:elixir_make] ++ Mix.compilers(), make_env}
else
{[:evision_precompiled] ++ Mix.compilers(), %{}}
end
else
{Mix.compilers(), %{}}
end
[
app: Metadata.app(),
name: "Evision",
version: Metadata.version(),
elixir: "~> 1.11",
deps: deps(),
docs: docs(),
aliases: aliases(),
elixirc_paths: elixirc_paths(),
erlc_paths: erlc_paths(),
compilers: compilers,
source_url: Metadata.github_url(),
description: description(),
package: package(),
make_executable: make_executable(),
make_makefile: make_makefile(),
make_env: make_env,
xref: [
exclude: [
:wx,
:wxBitmap,
:wxBoxSizer,
:wxDC,
:wxFrame,
:wxImage,
:wxMemoryDC,
:wxPanel,
:wxSizer,
:wxStaticBoxSizer,
:wxWindowDC,
:wx_object
]
]
]
end
defp aliases do
[
compile_opencv: &compile_opencv/1
]
end
defp compile_opencv(_) do
config = Mix.Project.config()
Mix.shell().print_app()
priv? = File.dir?("priv")
Mix.Project.ensure_structure()
config = Keyword.put_new(config, :make_targets, ["opencv"])
compiler_file =
Path.join([
Path.dirname(Mix.ProjectStack.project_file() || __ENV__.file),
"deps/elixir_make/lib/elixir_make/compiler.ex"
])
with [{ElixirMake.Compiler, _}] <- Code.require_file(compiler_file) do
ElixirMake.Compiler.make(config, [])
# IF there was no priv before and now there is one, we assume
# the user wants to copy it. If priv already existed and was
# written to it, then it won't be copied if build_embedded is
# set to true.
if not priv? and File.dir?("priv") do
Mix.Project.build_structure()
end
{:ok, []}
else
_ ->
Mix.shell().error("Cannot compile OpenCV only: failed to require file #{compiler_file}")
end
end
def make_executable() do
case :os.type() do
{:win32, _} -> "nmake"
{:unix, :freebsd} -> "gmake"
_ -> "make"
end
end
def make_makefile() do
case :os.type() do
{:win32, _} -> "Makefile.win"
_ -> "Makefile"
end
end
def opencv_versions(version) do
if Enum.member?(Metadata.compatible_opencv_versions(), version) do
version
else
Logger.warning(
"OpenCV version #{version} is not in the compatible list, you may encounter compile errors"
)
Logger.warning(
"Compatible OpenCV versions: " <>
(Metadata.compatible_opencv_versions() |> Enum.join(", "))
)
version
end
end
def application do
[
extra_applications: [:logger, :inets, :ssl],
mod: {Evision.Application, []}
]
end
@module_configuration %{
# opencv/opencv_contrib
opencv: [
# module name: is_enabled
# note that these true values here only mean that we requested
# these module to be compiled
# some of them can be disabled due to dependency issues
calib3d: true,
core: true,
dnn: true,
features2d: true,
flann: true,
highgui: true,
imgcodecs: true,
imgproc: true,
ml: true,
photo: true,
stitching: true,
ts: true,
video: true,
videoio: true,
gapi: false,
world: false,
python2: false,
python3: false,
java: false
],
opencv_contrib: [
aruco: true,
barcode: true,
bgsegm: true,
bioinspired: true,
dnn_superres: true,
face: true,
hfs: true,
img_hash: true,
line_descriptor: true,
mcc: true,
plot: true,
quality: true,
rapid: true,
reg: true,
rgbd: true,
saliency: true,
shape: true,
stereo: true,
structured_light: true,
surface_matching: true,
text: true,
tracking: true,
wechat_qrcode:
System.get_env("MIX_TARGET") != "ios" or System.get_env("MIX_TARGET") != "xros",
xfeatures2d: true,
ximgproc: true,
xphoto: true,
# no bindings yet
datasets: false,
dnn_objdetect: false,
dpm: false,
optflow: false,
sfm: false,
videostab: false,
xobjdetect: false
],
cuda: [
cudaarithm: true,
cudabgsegm: true,
cudacodec: true,
cudafeatures2d: true,
cudafilters: true,
cudaimgproc: true,
cudalegacy: true,
cudaobjdetect: true,
cudaoptflow: true,
cudastereo: true,
cudawarping: true,
cudev: true
]
}
defp module_configuration, do: @module_configuration
@enabled_img_codecs (if System.get_env("MIX_TARGET") == "ios" or
System.get_env("MIX_TARGET") == "xros" do
[]
else
[:png, :jpeg, :tiff, :webp, :openjpeg, :jasper, :openexr]
end)
# To make things easier, you can set `compile_mode` to `only_enabled_modules` so
# that only modules specified in `enabled_modules` will be compiled. Like-wise,
# set `except_disabled_modules` to only exclude modules in `disabled_modules`.
# By default, the value of `compile_mode` is `auto`, which means to leave unspecified
# modules to CMake to decide.
@compile_mode :auto
defp generate_cmake_options() do
mc = module_configuration()
enable_cuda = System.get_env("EVISION_ENABLE_CUDA", "false")
enable_opencv_cuda = enable_cuda == "true"
if enable_opencv_cuda do
System.put_env("EVISION_ENABLE_CUDA", "true")
else
System.put_env("EVISION_ENABLE_CUDA", "false")
end
enable_contrib = System.get_env("EVISION_ENABLE_CONTRIB", "true")
enable_opencv_contrib = enable_contrib == "true"
if enable_opencv_cuda and !enable_opencv_contrib do
Logger.warning(
"EVISION_ENABLE_CUDA is set to true, while EVISION_ENABLE_CONTRIB is set to false. CUDA support will NOT be available."
)
end
if enable_opencv_contrib do
System.put_env("EVISION_ENABLE_CONTRIB", "true")
else
System.put_env("EVISION_ENABLE_CONTRIB", "false")
end
all_modules =
Enum.map(mc.opencv, fn {m, _} -> m end) ++ Enum.map(mc.opencv_contrib, fn {m, _} -> m end)
enabled_modules =
Enum.filter(mc.opencv, fn {_, e} -> e end) ++
if enable_opencv_contrib do
Enum.filter(mc.opencv_contrib, fn {_, e} -> e end)
else
[]
end ++
if enable_opencv_cuda do
Enum.filter(mc.cuda, fn {_, e} -> e end)
else
[]
end
disabled_modules =
Enum.filter(mc.opencv, fn {_, e} -> !e end) ++
if enable_opencv_contrib do
Enum.filter(mc.opencv_contrib, fn {_, e} -> !e end)
else
[]
end
enabled_modules = Keyword.keys(enabled_modules)
disabled_modules = Keyword.keys(disabled_modules)
enabled_img_codecs = Application.get_env(:evision, :enabled_img_codecs, @enabled_img_codecs)
compile_mode = Application.get_env(:evision, :compile_mode, @compile_mode)
{cmake_options, enabled_modules} =
case compile_mode do
:auto ->
cmake_options =
(enabled_modules
|> Enum.map(&"-D BUILD_opencv_#{Atom.to_string(&1)}=ON")
|> Enum.join(" ")) <>
" " <>
(disabled_modules
|> Enum.map(&"-D BUILD_opencv_#{&1}=OFF")
|> Enum.join(" "))
{cmake_options, enabled_modules}
:only_enabled_modules ->
cmake_options =
"-D BUILD_LIST=" <>
(enabled_modules
|> Enum.map(&Atom.to_string(&1))
|> Enum.join(","))
{cmake_options, enabled_modules}
:except_disabled_modules ->
enabled_modules = all_modules -- disabled_modules
cmake_options =
"-D BUILD_LIST=" <>
(enabled_modules
|> Enum.map(&Atom.to_string(&1))
|> Enum.join(","))
{cmake_options, enabled_modules}
unrecognised_mode ->
Mix.raise("unrecognised compile_mode for evision: #{inspect(unrecognised_mode)}")
end
options =
cmake_options <>
" " <>
(enabled_img_codecs
|> Enum.map(&"-D BUILD_#{Atom.to_string(&1) |> String.upcase()}=ON")
|> Enum.join(" ")) <>
" "
options =
if enable_opencv_cuda and enable_opencv_contrib do
"#{options} -D WITH_CUDA=ON"
else
options
end
{options, enabled_modules |> Enum.map(&Atom.to_string(&1)) |> Enum.join(",")}
end
defp deps do
[
# compilation
{:elixir_make, "~> 0.7", runtime: false},
{:castore, "~> 0.1 or ~> 1.0"},
# runtime
{:nx, "~> 0.6"},
# optional
## kino: for smart cells and better output in livebook
{:kino, "~> 0.11", optional: true},
## progress_bar: for the model zoo smart cell
{:progress_bar, "~> 2.0 or ~> 3.0", optional: true},
# docs
{:ex_doc, "~> 0.34.0", only: :docs, runtime: false},
# test
{:scidata, "~> 0.1", only: :test}
]
end
defp docs do
[
main: "Evision",
source_ref: "v#{Metadata.version()}",
source_url: @source_url,
extras: extras(),
before_closing_body_tag: &before_closing_body_tag/1,
groups_for_extras: groups_for_extras(),
groups_for_modules: groups_for_modules(),
nest_modules_by_prefix: nest_modules_by_prefix(),
groups_for_docs: groups_for_docs()
]
end
# Categories that ExDoc applies to functions inside a module. The generator
# emits `@doc group: "<OpenCV @defgroup title>"` for cv:: namespace
# functions (the ~1400 that land in the root `Evision` module) so that the
# function list mirrors OpenCV's docs taxonomy rather than appearing as one
# flat brick. Entries here pull in everything OpenCV's headers tag plus the
# binding-only `external`/`Enumerator` buckets.
defp groups_for_docs do
[
external: &(&1[:namespace] == :external),
Enumerator: &(&1[:enum] == true)
] ++ for title <- opencv_doc_group_titles(), do: {String.to_atom(title), &doc_in_group?(&1, title)}
end
defp doc_in_group?(metadata, title), do: metadata[:group] == title
defp opencv_doc_group_titles do
[
"Operations on arrays",
"Basic structures",
"Asynchronous API",
"XML/YAML/JSON Persistence",
"Clustering",
"Utility and system functions and macros",
"Logging facilities",
"OpenGL interoperability",
"Optimization Algorithms",
"OpenCL support",
"Hardware Acceleration Layer",
"Parallel Processing",
"Parallel backends API",
"Quaternion",
"Image Filtering",
"Geometric Image Transformations",
"Miscellaneous Image Transformations",
"Drawing Functions",
"Color Space Conversions",
"ColorMaps in OpenCV",
"Planar Subdivision",
"Histograms",
"Structural Analysis and Shape Descriptors",
"Motion Analysis and Object Tracking",
"Feature Detection",
"Object Detection",
"Image Segmentation",
"Image file reading and writing",
"High-level GUI",
"Qt New Functions",
"Video I/O",
"Query I/O API backends registry",
"Camera Calibration",
"Fisheye camera model",
"3D vision functionality",
"Stereo Correspondence",
"Drawing Function of Keypoints and Matches",
"Object Tracking",
"Legacy Tracking API",
"Optical Flow Algorithms",
"Deep Neural Network module",
"HDR imaging",
"Inpainting",
"Denoising",
"Seamless Cloning",
"Non-Photorealistic Rendering",
"Contrast Preserving Decolorization",
"Images stitching",
"Features Finding and Images Matching",
"Rotation Estimation",
"Autocalibration",
"Image Blenders",
"Additional photo processing algorithms",
"Extended Image Processing",
"Fourier descriptors",
"Structured forests for fast edge detection",
"Superpixels",
"EdgeDrawing",
"Fast line detector",
"Experimental 2D Features Matching Algorithm",
"Binary descriptors for lines extracted from an image",
"ArUco markers and boards detection for robust camera pose estimation",
"Improved Background-Foreground Segmentation Methods",
"Face Analysis",
"Image segmentation",
"Scene Text Detection",
"Scene Text Recognition",
"Shape Distance and Matching",
"Surface Matching",
"silhouette based 3D object tracking",
"Alpha Matting",
"Custom Calibration Pattern for 3D reconstruction",
"RGB-Depth Processing",
"Module-wrapper for FastCV hardware accelerated functions",
"Signal Processing",
"Math with F0-transform support",
"Math with F1-transform support",
"Fuzzy image processing",
"The module brings implementations of different image hashing algorithms.",
"The module brings implementations of intensity transformation algorithms to adjust image contrast.",
"Common functions and classes",
"Hierarchical Data Format version 5",
"Drawing UTF-8 strings with freetype/harfbuzz"
]
end
defp extras do
[
"README.md",
"CHANGELOG.md",
"Cheatsheet.cheatmd",
"examples/getting_started.livemd",
"examples/qrcode.livemd",
"examples/warp_perspective.livemd",
"examples/warp_polar.livemd",
"examples/find_and_draw_contours.livemd",
"examples/stitching.livemd",
"examples/pca.livemd",
"examples/photo-hdr.livemd",
"examples/sudoku.livemd",
"examples/cifar10.livemd",
"examples/dnn-googlenet.livemd",
"examples/dnn-detection-model.livemd",
"examples/densenet121_benchmark.livemd",
"examples/ml-svm.livemd",
"examples/ml-decision_tree_and_random_forest.livemd"
]
end
defp groups_for_extras do
[
Introduction: ["Cheatsheet.cheatmd", "CHANGELOG.md"],
"Examples: Getting Started": ["examples/getting_started.livemd"],
"Examples: Image Processing": [
"examples/qrcode.livemd",
"examples/warp_perspective.livemd",
"examples/warp_polar.livemd",
"examples/find_and_draw_contours.livemd",
"examples/stitching.livemd",
"examples/pca.livemd",
"examples/photo-hdr.livemd",
"examples/sudoku.livemd"
],
"Examples: Deep Neural Networks": [
"examples/cifar10.livemd",
"examples/dnn-googlenet.livemd",
"examples/dnn-detection-model.livemd",
"examples/densenet121_benchmark.livemd"
],
"Examples: Machine Learning": [
"examples/ml-svm.livemd",
"examples/ml-decision_tree_and_random_forest.livemd"
]
]
end
# OpenCV's high-level module taxonomy mapped onto the Elixir module names
# emitted by py_src. Order matters: ExDoc walks the list top-down and a
# module ends up in the first group whose pattern it matches, so put more
# specific patterns above broader ones.
defp groups_for_modules do
[
"Core Functionality": [
Evision.Constant,
Evision.Backend,
~r/^Evision\.(Mat|SparseMat|UMat|UMatData|UMatUsageFlags)(\..*)?$/,
~r/^Evision\.(AsyncArray|Algorithm|AlgorithmHint|RotatedRect|KeyPoint|DMatch|RNG|TickMeter|Subdiv2D|IStreamReader)$/,
~r/^Evision\.(TermCriteria|PCA|SVD|FileNode|FileStorage|Param|Parallel|Samples|Animation)(\..*)?$/,
~r/^Evision\.(Error|Instr|QuatAssumeType|QuatEnum)(\..*)?$/,
~r/^Evision\.(Utils|UtilsFS|Utilslogging|Ipp|Signal|AccessFlag)(\..*)?$/,
~r/^Evision\.Formatter\..+$/
],
"Image Processing": [
~r/^Evision\.(CLAHE|GeneralizedHough|GeneralizedHoughBallard|GeneralizedHoughGuil|LineSegmentDetector|IntelligentScissorsMB)$/
],
"Image File I/O": [
~r/^Evision\.(Imread|Imwrite)[A-Z].*$/,
Evision.ImageMetadataType
],
"Video I/O": [
~r/^Evision\.Video(Capture|Writer|IORegistry|Acceleration)[A-Z]*.*$/
],
"High-level GUI": [
Evision.HighGui,
Evision.Wx,
~r/^Evision\.(Window|MouseEvent|Qt)[A-Z].*$/,
~r/^Evision\.(MarkerTypes|HersheyFonts|LineTypes|DrawMatchesFlags)$/
],
"Video Analysis": [
~r/^Evision\.(KalmanFilter|BackgroundSubtractor.*|VariationalRefinement)$/,
~r/^Evision\.(DenseOpticalFlow|DISOpticalFlow|FarnebackOpticalFlow|SparseOpticalFlow|SparsePyrLKOpticalFlow)$/
],
"Camera Calibration & 3D Reconstruction": [
~r/^Evision\.(FishEye|Omnidir|StereoBM|StereoSGBM|StereoMatcher)$/,
~r/^Evision\.(CirclesGridFinderParameters|UsacParams|SolveLPResult|SolvePnPMethod|HandEyeCalibrationMethod|RobotWorldHandEyeCalibrationMethod|LocalOptimMethod|SamplingMethod|NeighborSearchMethod|ScoreMethod|PolishingMethod)(\..*)?$/
],
"Stereo Correspondence": [
~r/^Evision\.Stereo(\..*)?$/
],
"2D Features Framework": [
~r/^Evision\.(AKAZE|AffineFeature|AgastFeatureDetector|AffineTransformer|BRISK|BFMatcher|FastFeatureDetector|Feature2D|FlannBasedMatcher|GFTTDetector|KAZE|MSER|ORB|SIFT|SimpleBlobDetector)(\..*)?$/,
~r/^Evision\.BOW[A-Z].*$/,
~r/^Evision\.DescriptorMatcher(\..*)?$/
],
"Object Detection": [
~r/^Evision\.(CascadeClassifier|BaseCascadeClassifier|HOGDescriptor|FaceDetectorYN|GraphicalCodeDetector)(\..*)?$/,
~r/^Evision\.(QRCodeDetector|QRCodeDetectorAruco|QRCodeEncoder|Barcode)(\..*)?$/
],
"Computational Photography (HDR)": [
~r/^Evision\.(Tonemap|TonemapDrago|TonemapMantiuk|TonemapReinhard)$/,
~r/^Evision\.(CalibrateCRF|CalibrateDebevec|CalibrateRobertson)$/,
~r/^Evision\.(MergeDebevec|MergeExposures|MergeMertens|MergeRobertson)$/,
~r/^Evision\.(AlignExposures|AlignMTB|SeamlessCloneFlags|Alphamat)$/
],
"Shape Matching": [
~r/^Evision\.(ShapeContextDistanceExtractor|ShapeDistanceExtractor|ShapeTransformer|ShapeMatchModes|ThinPlateSplineShapeTransformer|HausdorffDistanceExtractor)$/,
~r/^Evision\.(ChiHistogramCostExtractor|NormHistogramCostExtractor|EMDHistogramCostExtractor|EMDL1HistogramCostExtractor|HistogramCostExtractor)$/
],
"Image Stitching": [
~r/^Evision\.(Stitcher|PyRotationWarper|WarperCreator)(\..*)?$/,
~r/^Evision\.Detail(\..*)?$/
],
"Machine Learning (ML)": [
~r/^Evision\.ML(\..*)?$/
],
"Deep Neural Networks (DNN)": [
~r/^Evision\.DNN(\..*)?$/
],
"DNN Super-Resolution": [
~r/^Evision\.DNNSuperRes(\..*)?$/
],
"Clustering & Search (FLANN)": [
~r/^Evision\.Flann(\..*)?$/
],
"CUDA Acceleration": [
~r/^Evision\.CUDA(\..*)?$/
],
"OpenCL Acceleration": [
~r/^Evision\.OCL(\..*)?$/
],
"OpenGL Interop": [
~r/^Evision\.OGL(\..*)?$/
],
"Object Tracking": [
~r/^Evision\.(Tracker|TrackerCSRT|TrackerDaSiamRPN|TrackerGOTURN|TrackerKCF|TrackerMIL|TrackerNano|TrackerVit)(\..*)?$/,
~r/^Evision\.Legacy(\..*)?$/
],
"3D Volumetric Reconstruction (KinFu)": [
~r/^Evision\.(KinFu|LargeKinfu|ColoredKinFu|DynaFu)(\..*)?$/
],
"ArUco Markers": [
~r/^Evision\.ArUco(\..*)?$/
],
"Background Segmentation (bgsegm)": [
~r/^Evision\.BgSegm(\..*)?$/
],
"Bio-Inspired Vision": [
~r/^Evision\.Bioinspired(\..*)?$/
],
"Color Correction Model (CCM)": [
~r/^Evision\.CCM(\..*)?$/
],
"Face Analysis": [
~r/^Evision\.(Face|FaceRecognizerSF)(\..*)?$/
],
"Fuzzy Image Processing": [
~r/^Evision\.Ft(\..*)?$/
],
"Hierarchical Feature Selection (HFS)": [
~r/^Evision\.HFS(\..*)?$/
],
"Image Hashing": [
~r/^Evision\.ImgHash(\..*)?$/
],
"Intensity Transformations": [
~r/^Evision\.Intensitytransform(\..*)?$/
],
"Line Descriptors": [
~r/^Evision\.LineDescriptor(\..*)?$/
],
LineMod: [
~r/^Evision\.LineMod(\..*)?$/
],
"Macbeth Chart (MCC)": [
~r/^Evision\.MCC(\..*)?$/
],
"Multi-Camera Calibration": [
~r/^Evision\.MultiCalib(\..*)?$/
],
"Phase Unwrapping": [
~r/^Evision\.PhaseUnwrapping(\..*)?$/
],
"2D Plotting": [
~r/^Evision\.Plot(\..*)?$/
],
"Surface Matching (PPF Match 3D)": [
~r/^Evision\.PPFMatch3D(\..*)?$/
],
"Image Quality Analysis": [
~r/^Evision\.Quality(\..*)?$/
],
"RAPID 3D Tracking": [
~r/^Evision\.Rapid(\..*)?$/
],
"Image Registration": [
~r/^Evision\.Reg(\..*)?$/
],
"RGB-Depth Processing": [
~r/^Evision\.RGBD(\..*)?$/
],
Saliency: [
~r/^Evision\.Saliency(\..*)?$/
],
"Image Segmentation": [
~r/^Evision\.Segmentation(\..*)?$/
],
"Structured Light": [
~r/^Evision\.StructuredLight(\..*)?$/
],
"Text Detection & Recognition": [
~r/^Evision\.Text(\..*)?$/
],
"WeChat QR Code": [
~r/^Evision\.WeChatQRCode(\..*)?$/
],
"Extra 2D Features (xfeatures2d)": [
~r/^Evision\.XFeatures2D(\..*)?$/
],
"Extended Image Processing (ximgproc)": [
~r/^Evision\.XImgProc(\..*)?$/
],
"Extra Photo (xphoto)": [
~r/^Evision\.XPhoto(\..*)?$/
],
"Model Zoo": [
~r/^Evision\.Zoo(\..*)?$/
],
"Livebook Smart Cells": [
~r/^Evision\.SmartCell(\..*)?$/
],
"IPC Handles": [
~r/^Evision\.IPCHandle(\..*)?$/
],
"Enums & Flags": [
~r/^Evision\..*(Flags|Types|Modes|Codes|Masks|Methods|Classes|Shapes|Mode|Filter)$/
]
]
end
# Fold deeply-nested generated namespaces under a single prefix in the
# sidebar. `Evision.DNN.Net` shows as `Net` under the `Evision.DNN` heading,
# etc.
defp nest_modules_by_prefix do
[
Evision.ArUco,
Evision.BgSegm,
Evision.Bioinspired,
Evision.CCM,
Evision.ColoredKinFu,
Evision.CUDA,
Evision.Detail,
Evision.DNN,
Evision.DNNSuperRes,
Evision.DynaFu,
Evision.Face,
Evision.Flann,
Evision.HFS,
Evision.ImgHash,
Evision.IPCHandle,
Evision.KinFu,
Evision.LargeKinfu,
Evision.Legacy,
Evision.LineDescriptor,
Evision.LineMod,
Evision.MCC,
Evision.ML,
Evision.MultiCalib,
Evision.OCL,
Evision.OGL,
Evision.PhaseUnwrapping,
Evision.Plot,
Evision.PPFMatch3D,
Evision.Quality,
Evision.Rapid,
Evision.Reg,
Evision.RGBD,
Evision.Saliency,
Evision.Segmentation,
Evision.SmartCell,
Evision.Stereo,
Evision.Stitcher,
Evision.StructuredLight,
Evision.Text,
Evision.Utils,
Evision.WeChatQRCode,
Evision.XFeatures2D,
Evision.XImgProc,
Evision.XPhoto,
Evision.Zoo
]
end
defp before_closing_body_tag(:html) do
"""
<script>
window.MathJax = {
tex: {
inlineMath: [["\\\\f$", "\\\\f$"]],
displayMath: [["\\\\f[", "\\\\f]"]],
processEnvironments: false,
macros: {
matTT: ["\\\\[ \\\\left|\\\\begin{array}{ccc} #1 & #2 & #3\\\\\\\\ #4 & #5 & #6\\\\\\\\ #7 & #8 & #9 \\\\end{array}\\\\right| \\\\]", 9],
fork: ["\\\\left\\\\{ \\\\begin{array}{l l} #1 & \\\\mbox{#2}\\\\\\\\ #3 & \\\\mbox{#4}\\\\\\\\ \\\\end{array} \\\\right.", 4],
forkthree: ["\\\\left\\\\{ \\\\begin{array}{l l} #1 & \\\\mbox{#2}\\\\\\\\ #3 & \\\\mbox{#4}\\\\\\\\ #5 & \\\\mbox{#6}\\\\\\\\ \\\\end{array} \\\\right.", 6],
forkfour: ["\\\\left\\\\{ \\\\begin{array}{l l} #1 & \\\\mbox{#2}\\\\\\\\ #3 & \\\\mbox{#4}\\\\\\\\ #5 & \\\\mbox{#6}\\\\\\\\ #7 & \\\\mbox{#8}\\\\\\\\ \\\\end{array} \\\\right.", 8],
vecthree: ["\\\\begin{bmatrix} #1\\\\\\\\ #2\\\\\\\\ #3 \\\\end{bmatrix}", 3],
vecthreethree: ["\\\\begin{bmatrix} #1 & #2 & #3\\\\\\\\ #4 & #5 & #6\\\\\\\\ #7 & #8 & #9 \\\\end{bmatrix}", 9],
cameramatrix: ["#1 = \\\\begin{bmatrix} f_x & 0 & c_x\\\\\\\\ 0 & f_y & c_y\\\\\\\\ 0 & 0 & 1 \\\\end{bmatrix}", 1],
distcoeffs: ["(k_1, k_2, p_1, p_2[, k_3[, k_4, k_5, k_6 [, s_1, s_2, s_3, s_4[, \\\\tau_x, \\\\tau_y]]]]) \\\\text{ of 4, 5, 8, 12 or 14 elements}"],
distcoeffsfisheye: ["(k_1, k_2, k_3, k_4)"],
hdotsfor: ["\\\\dots", 1],
mathbbm: ["\\\\mathbb{#1}", 1],
bordermatrix: ["\\\\matrix{#1}", 1]
}
},
options: {
skipHtmlTags: ["script", "noscript", "style", "textarea", "pre", "code"],
ignoreHtmlClass: "tex2jax_ignore",
processHtmlClass: "tex2jax_process"
},
svg: { fontCache: "global" },
startup: { typeset: true }
};
</script>
<script defer src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"></script>
"""
end
defp before_closing_body_tag(_), do: ""
defp description() do
"OpenCV-Erlang/Elixir binding."
end
def links do
%{
"GitHub" => Metadata.github_url(),
"Readme" => "#{Metadata.github_url()}/blob/v#{Metadata.version()}/README.md",
"Changelog" => "#{Metadata.github_url()}/blob/v#{Metadata.version()}/CHANGELOG.md"
}
end
defp elixirc_paths, do: ["lib"]
defp erlc_paths, do: []
defp package() do
[
name: "evision",
# These are the default files included in the package
files: ~w(
c_src/evision_custom_headers
c_src/modules
c_src/windows_fix
c_src/ArgInfo.hpp
c_src/erlcompat.hpp
c_src/evision_consts.h
c_src/evision_custom_headers.h
c_src/evision.cpp
c_src/nif_utils.hpp
cc_toolchain
cmake
lib/assets
lib/evision
lib/mix
lib/smartcell
lib/zoo
lib/*.ex
patches
py_src/*.py
scripts
src/evision_mat.erl
src/evision_highgui.erl
src/evision_internal_structurise.erl
src/evision.app.src
checksum.exs
checksum.erl
CMakeLists.txt
evision_precompiled.erl
Makefile
Makefile.win
.formatter.exs
mix.exs
rebar.config
README*
LICENSE*
CHANGELOG*),
licenses: ["Apache-2.0"],
links: links()
]
end
end