From 52326b8cfea8f3f0fd0c28b20c502d2bb784619e Mon Sep 17 00:00:00 2001 From: jaeyson Date: Thu, 5 Jan 2023 16:31:01 +0800 Subject: [PATCH] implement image analysis --- .gitignore | 2 ++ CHANGELOG.md | 5 +++ LICENSE | 21 +++++++++++ README.md | 30 +++++++++++++++- lib/ex_azure_vision.ex | 16 ++------- lib/ex_azure_vision/http_client.ex | 50 +++++++++++++++++++++++++++ lib/ex_azure_vision/image_analysis.ex | 34 ++++++++++++++++++ mix.exs | 40 +++++++++++++++++++-- mix.lock | 12 +++++++ test/ex_azure_vision_test.exs | 6 ++-- 10 files changed, 197 insertions(+), 19 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 lib/ex_azure_vision/http_client.ex create mode 100644 lib/ex_azure_vision/image_analysis.ex create mode 100644 mix.lock diff --git a/.gitignore b/.gitignore index da1542a..46885df 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ ex_azure_vision-*.tar # Temporary files, for example, from tests. /tmp/ + +.DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a58727b --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.1.0 (2023.01.05) + +* Initial release diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9736a81 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Jaeyson Anthony Y. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 93e6dc8..1d8613e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ExAzureVision -**TODO: Add description** +Simple REST wrapper for using Azure's [Computer vision](https://learn.microsoft.com/en-us/azure/cognitive-services/computer-vision/) ## Installation @@ -15,6 +15,34 @@ def deps do end ``` +## Config + +```elixir +# config/runtime.exs +config :ex_azure_vision, + header_name: System.get_env("AZURE_OCP_APIM_HEADER_NAME"), + subscription_key: System.get_env("AZURE_OCP_APIM_SUBSCRIPTION_KEY"), + base_url: System.get_env("AZURE_COGNITIVE_VISION_BASE_URI"), + scheme: "https", + path: "/vision/v3.2" +``` + +## Analyze image + +```elixir +iex> image_url = "https://images.unsplash.com/photo-1672676831425-207e5d4a0c41?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=687&q=80" + +iex> query_params = %{ + "visualFeatures" => "Categories,Adult,Tags,Description,Faces,Objects", + "details" => "Landmarks", + "language" => "en", + "model-version" => "latest" + } + +iex> ExAzureVision.analyze(image_url, query_params) +{:ok, ...} +``` + Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) and published on [HexDocs](https://hexdocs.pm). Once published, the docs can be found at . diff --git a/lib/ex_azure_vision.ex b/lib/ex_azure_vision.ex index d9aea91..df1eb7b 100644 --- a/lib/ex_azure_vision.ex +++ b/lib/ex_azure_vision.ex @@ -1,18 +1,8 @@ defmodule ExAzureVision do + @moduledoc since: "0.1.0" @moduledoc """ - Documentation for `ExAzureVision`. + Public API functions to interact with Azure Computer Vision. """ - @doc """ - Hello world. - - ## Examples - - iex> ExAzureVision.hello() - :world - - """ - def hello do - :world - end + defdelegate analyze(image_url, query_params), to: ExAzureVision.ImageAnalysis end diff --git a/lib/ex_azure_vision/http_client.ex b/lib/ex_azure_vision/http_client.ex new file mode 100644 index 0000000..9650a08 --- /dev/null +++ b/lib/ex_azure_vision/http_client.ex @@ -0,0 +1,50 @@ +defmodule ExAzureVision.HttpClient do + @moduledoc false + + def header_name, do: Application.get_env(:ex_azure_vision, :header_name) + def subscription_key, do: Application.get_env(:ex_azure_vision, :subscription_key) + def base_url, do: Application.get_env(:ex_azure_vision, :base_url) + def scheme, do: Application.get_env(:ex_azure_vision, :scheme) + def path, do: Application.get_env(:ex_azure_vision, :path) + + @doc false + @spec request(String.t(), map(), String.t()) :: + {:ok, response :: map()} | {:error, reason :: atom()} + def request(image_url, query_params, service_path \\ "analyze") do + path = [path(), service_path] |> Path.join() + + url = + %URI{ + host: base_url(), + scheme: scheme(), + path: path, + query: URI.encode_query(query_params) + } + |> URI.to_string() + + headers = [ + { + String.to_charlist(header_name()), + String.to_charlist(subscription_key()) + } + ] + + content_type = 'application/json;' + payload = Jason.encode!(%{url: image_url}) + + request = { + url, + headers, + content_type, + payload + } + + case :httpc.request(:post, request, [], []) do + {:ok, {_status_code, _headers, response}} -> + {:ok, Jason.decode!(response)} + + {:error, reason} -> + {:error, reason} + end + end +end diff --git a/lib/ex_azure_vision/image_analysis.ex b/lib/ex_azure_vision/image_analysis.ex new file mode 100644 index 0000000..951127e --- /dev/null +++ b/lib/ex_azure_vision/image_analysis.ex @@ -0,0 +1,34 @@ +defmodule ExAzureVision.ImageAnalysis do + @moduledoc since: "0.1.0" + @moduledoc """ + Module for analyzing image. + """ + + alias ExAzureVision.HttpClient + + @doc """ + Analyzes the image using url. Returns Jason decoded value or error message. + + ## Examples + image_url = "https://example.gif/sample.jpg" + + query_params = + %{ + "visualFeatures" => "Categories,Adult,Tags,Description,Faces,Objects", + "details" => "Landmarks", + "language" => "en", + "model-version" => "latest" + } + + iex> annotate_image(image_url, query_params) + %{ + "categories" => [...], + ... + } + """ + @doc since: "0.1.0" + @spec analyze(String.t(), map()) :: {:ok, response :: map()} | {:error, reason :: atom()} + def analyze(image_url, query_params) do + HttpClient.request(image_url, query_params) + end +end diff --git a/mix.exs b/mix.exs index b6e96a6..d04ed08 100644 --- a/mix.exs +++ b/mix.exs @@ -1,13 +1,21 @@ defmodule ExAzureVision.MixProject do use Mix.Project + @source_url "https://github.com/jaeyson/ex_azure_vision" + @version "0.1.0" + def project do [ app: :ex_azure_vision, - version: "0.1.0", + version: @version, elixir: "~> 1.14", start_permanent: Mix.env() == :prod, - deps: deps() + deps: deps(), + description: "Simple REST wrapper for using Azure's Computer vision", + docs: docs(), + package: package(), + name: "ExAzureVision", + source_url: @source_url ] end @@ -23,6 +31,34 @@ defmodule ExAzureVision.MixProject do [ # {:dep_from_hexpm, "~> 0.3.0"}, # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + {:jason, "~> 1.4"}, + {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, + {:ex_doc, "~> 0.29.1", only: :dev, runtime: false} + ] + end + + defp docs do + [ + api_reference: false, + main: "readme", + source_ref: "v#{@version}", + source_url: @source_url, + canonical: "http://hexdocs.pm/ex_azure_vision", + extras: [ + "README.md", + "CHANGELOG.md", + "LICENSE" + ] + ] + end + + defp package do + [ + maintainers: ["Jaeyson Anthony Y."], + licenses: ["MIT"], + links: %{ + "Github" => @source_url + } ] end end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..871dd40 --- /dev/null +++ b/mix.lock @@ -0,0 +1,12 @@ +%{ + "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, + "credo": {:hex, :credo, "1.6.7", "323f5734350fd23a456f2688b9430e7d517afb313fbd38671b8a4449798a7854", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "41e110bfb007f7eda7f897c10bf019ceab9a0b269ce79f015d54b0dcf4fc7dd3"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.29", "149d50dcb3a93d9f3d6f3ecf18c918fb5a2d3c001b5d3305c926cddfbd33355b", [:mix], [], "hexpm", "4902af1b3eb139016aed210888748db8070b8125c2342ce3dcae4f38dcc63503"}, + "ex_doc": {:hex, :ex_doc, "0.29.1", "b1c652fa5f92ee9cf15c75271168027f92039b3877094290a75abcaac82a9f77", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "b7745fa6374a36daf484e2a2012274950e084815b936b1319aeebcf7809574f6"}, + "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, + "jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"}, + "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, +} diff --git a/test/ex_azure_vision_test.exs b/test/ex_azure_vision_test.exs index f99e5f7..d61fa4d 100644 --- a/test/ex_azure_vision_test.exs +++ b/test/ex_azure_vision_test.exs @@ -2,7 +2,7 @@ defmodule ExAzureVisionTest do use ExUnit.Case doctest ExAzureVision - test "greets the world" do - assert ExAzureVision.hello() == :world - end + # test "greets the world" do + # assert ExAzureVision.hello() == :world + # end end