Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support for previewing image in Livebook with Kino #25

Merged
merged 12 commits into from
Nov 8, 2022
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ endif
.DEFAULT_GLOBAL := build

build: $(STB_IMAGE_NIF_SO)
@ echo > /dev/null

$(STB_IMAGE_NIF_SO):
@ mkdir -p $(PRIV_DIR)
Expand Down
99 changes: 99 additions & 0 deletions lib/stb_image.ex
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,105 @@ defmodule StbImage do
"unsupported tensor shape: #{inspect(shape)} (expected height-width-channel)"
)

if Code.ensure_loaded?(Kino.Render) do
defimpl Kino.Render do
defp within_maximum_size(image) do
max_size = Application.fetch_env!(:stb_image, :kino_render_max_size)

case max_size do
{max_height, max_width} when is_integer(max_height) and is_integer(max_width) ->
case image.shape do
{h, w} ->
h <= max_height and w <= max_width
cocoa-xu marked this conversation as resolved.
Show resolved Hide resolved

{h, w, _c} ->
h <= max_height and w <= max_width

_ ->
false
end
cocoa-xu marked this conversation as resolved.
Show resolved Hide resolved

_ ->
raise """
Invalid configuration for max size, expecting a 2-tuple, {height, width},
where height and width are both integers. However, got `#{inspect(max_size)}`
josevalim marked this conversation as resolved.
Show resolved Hide resolved
"""
end
end

@spec to_livebook(StbImage.t()) :: Kino.Output.t()
def to_livebook(image) when is_struct(image, StbImage) do
render_types = Application.fetch_env!(:stb_image, :kino_render_tab_order)

Enum.map(render_types, fn
:raw ->
{"Raw", Kino.Inspect.new(image)}

:numerical ->
if Code.ensure_loaded?(Nx) do
{"Numerical", Kino.Inspect.new(StbImage.to_nx(image))}
else
{"Numerical",
Kino.Markdown.new("""
The `Numerical` tab requires application `:nx`, please add `{:nx, "~> 0.4"}` to the dependency list.
""")}
end

:image ->
render_encoding = Application.fetch_env!(:stb_image, :kino_render_encoding)

if render_encoding not in [:png, :jpg, :jpeg] do
raise "Invalid value for :kino_render_encoding. Expected one of :png, :jpg, :jpeg, got #{inspect(render_encoding)}"
josevalim marked this conversation as resolved.
Show resolved Hide resolved
end

{stb_format, kino_format} =
case render_encoding do
:jpg ->
{:jpg, :jpeg}

:jpeg ->
{:jpg, :jpeg}

:png ->
{:png, :png}
end

with true <- within_maximum_size(image),
encoded <- StbImage.to_binary(image, stb_format),
true <- is_binary(encoded) do
{"Image", Kino.Image.new(encoded, kino_format)}
else
_ ->
nil
end

type ->
raise """
Unknown type `#{inspect(type)}`. The set of supported types is `[:image, :raw, :numerical]`.
You can specify any valid subset of it. However, note that `:numerical` one depends on `:nx`.
josevalim marked this conversation as resolved.
Show resolved Hide resolved
"""
end)
|> Enum.reject(fn a -> a == nil end)
josevalim marked this conversation as resolved.
Show resolved Hide resolved
|> to_livebook_tabs(render_types, image)
end

defp to_livebook_tabs([], [:image], image) do
Kino.Layout.tabs([{"Raw", Kino.Inspect.new(image)}])
|> Kino.Render.to_livebook()
end

defp to_livebook_tabs(_tabs, [], image) do
Kino.Inspect.new(image)
|> Kino.Render.to_livebook()
end

defp to_livebook_tabs(tabs, _types, _mat) do
Kino.Layout.tabs(tabs)
|> Kino.Render.to_livebook()
end
end
end

@doc """
Reads image from file at `path`.

Expand Down
6 changes: 6 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ defmodule StbImage.MixProject do

def application do
[
env: [
kino_render_encoding: :png,
kino_render_max_size: {8192, 8192},
kino_render_tab_order: [:image, :raw]
],
extra_applications: [:logger]
]
end
Expand All @@ -30,6 +35,7 @@ defmodule StbImage.MixProject do
[
{:elixir_make, "~> 0.6"},
{:nx, "~> 0.1", optional: true},
{:kino, "~> 0.7", optional: true},
{:ex_doc, "~> 0.23", only: :docs, runtime: false}
]
end
Expand Down
2 changes: 2 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"earmark_parser": {:hex, :earmark_parser, "1.4.25", "2024618731c55ebfcc5439d756852ec4e85978a39d0d58593763924d9a15916f", [:mix], [], "hexpm", "56749c5e1c59447f7b7a23ddb235e4b3defe276afc220a6227237f3efe83f51e"},
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
"ex_doc": {:hex, :ex_doc, "0.28.4", "001a0ea6beac2f810f1abc3dbf4b123e9593eaa5f00dd13ded024eae7c523298", [: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", "bf85d003dd34911d89c8ddb8bda1a958af3471a274a4c2150a9c01c78ac3f8ed"},
"kino": {:hex, :kino, "0.7.0", "98d2623bebf8d1c4d761c5ed9741c6aae2f1909706baec501f2fdad347cb9d92", [:mix], [{:table, "~> 0.1.2", [hex: :table, repo: "hexpm", optional: false]}], "hexpm", "3dd1ee761c241caa30364333e3102a954d68188414a46e867f95c858d745f3fd"},
"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"},
"nx": {:hex, :nx, "0.2.0", "ae550cd72bca2e56f90fc63d59f2f11ac17ed3c4a9cd0968397f4ef9b795b3d6", [:mix], [{:complex, "~> 0.4.0", [hex: :complex, repo: "hexpm", optional: false]}], "hexpm", "31d4936d2cf78a0b147830f0e6fca45bd3ed4b1e592e2bcd61a85f81e892617c"},
"table": {:hex, :table, "0.1.2", "87ad1125f5b70c5dea0307aa633194083eb5182ec537efc94e96af08937e14a8", [:mix], [], "hexpm", "7e99bc7efef806315c7e65640724bf165c3061cdc5d854060f74468367065029"},
}