Skip to content

Commit 025c3d5

Browse files
committed
Remove image detection and related deps to image_detection lib
1 parent 0796807 commit 025c3d5

13 files changed

Lines changed: 590 additions & 1440 deletions

File tree

CHANGELOG.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,74 @@
11
# Changelog
22

3+
## Image 0.67.0
4+
5+
### Breaking Changes
6+
7+
* **`Image.Classification` and `Image.Generation` have moved to a new sibling package, [`:image_detection`](https://hex.pm/packages/image_detection).** The two modules keep their fully-qualified names (`Image.Classification` and `Image.Generation`) so that existing call sites continue to work — they just live in a different OTP application now. To restore the previous functionality, add `:image_detection` to your `mix.exs`:
8+
9+
def deps do
10+
[
11+
{:image, "~> 0.67"},
12+
{:image_detection, "~> 0.1"},
13+
# plus your preferred Nx backend, e.g.
14+
{:exla, "~> 0.10"}
15+
]
16+
end
17+
18+
* **`:bumblebee` is no longer a dependency of `:image`.** It is brought in transitively only when you add `:image_detection`. The `:nx`, `:nx_image`, `:scholar`, `:exla`, and `:rustler` optional deps stay in `:image` because `Image.to_nx/2`, `Image.from_nx/1`, `Image.k_means/2`, and a few internal pipelines still use them.
19+
20+
* **`Image.bumblebee_configured?/0` has been removed.** It is replaced by `ImageDetection.bumblebee_configured?/0` which lives in the new package.
21+
22+
* **`Image.Application` no longer autostarts the classification or generation services.** That responsibility moved to `ImageDetection.Application`, which manages its own `ImageDetection.Supervisor`. Both services default to `autostart: false` in the new package — you opt in via `config :image_detection, :classifier, autostart: true` (or `:generator`).
23+
24+
* **`config :image, :classifier` and `config :image, :generator` no longer have any effect.** Move them to `config :image_detection, :classifier` / `:generator` in your `runtime.exs` (or equivalent).
25+
26+
* The `doctest Image.Classification` line has been removed from `test/image_test.exs` since the module is no longer part of `:image`.
27+
28+
### Enhancements
29+
30+
* `:image` no longer pulls `:bumblebee` (or its transitive deps) into the dependency graph. For users who don't need ML-backed classification/generation, the install footprint shrinks substantially. The Hex tarball is unchanged in size, but the dependency tree is much lighter.
31+
32+
* The `image_detection` package's `bumblebee_configured?/0` predicate only checks for `Nx` and `Bumblebee` at compile time; the Nx backend (e.g. `:exla`) is now a runtime-only requirement. Users who want to compile against `image_detection` no longer have to install EXLA on their build machine.
33+
34+
## Image 0.66.0
35+
36+
This release replaces the eVision-backed video frame extraction with an FFmpeg-backed implementation via `:xav`.
37+
38+
### Breaking Changes
39+
40+
* **`Image.Video` is now backed by [Xav](https://hex.pm/packages/xav)** (a thin Elixir wrapper around FFmpeg) instead of `:evision` / OpenCV. The public API surface is largely unchanged but the underlying type, options, and a few semantic details have moved:
41+
42+
* The video struct is now `%Image.Video{}` (with fields `:reader`, `:source`, `:fps`, `:duration_seconds`, `:frame_count`, `:width`, `:height`) rather than `%Evision.VideoCapture{}`. Pattern-match on the new struct module if your code does so.
43+
44+
* `Image.Video.open/2`'s `:backend` option has been removed. FFmpeg picks the demuxer automatically and there is no concept of pluggable backends in Xav.
45+
46+
* `Image.Video.known_backends/0`, `available_backends/0`, `known_backend?/1`, `known_backend_values/0`, and `available_backend?/1` have been removed for the same reason. `Image.Options.Video` (the module that owned the backend table) has been deleted.
47+
48+
* Camera input is now opened via a platform-specific device path. `:default_camera` resolves to `/dev/video0` on Linux, `"0"` (AVFoundation device 0) on macOS, and `"video=0"` on Windows. An integer camera index is mapped to the corresponding `/dev/videoN` (or platform equivalent). For non-default cameras you can also pass an explicit FFmpeg device string.
49+
50+
* Frame-based seeking (`Image.Video.seek/2` with `frame: n`, and `Image.Video.image_from_video/2` with `frame: n`) is now implemented as a time-based seek to `n / fps` followed by zero or more `next_frame` calls. For most files this lands on the requested frame; for very inter-frame-compressed files FFmpeg may snap to the nearest preceding keyframe.
51+
52+
* `Image.Video.close/1` is now a no-op that returns `{:ok, %Image.Video{reader: nil}}`. Xav garbage-collects the underlying FFmpeg context, so explicit close is no longer necessary. The function is retained for source compatibility; subsequent operations on the closed struct return `{:error, %Image.Error{reason: :video_closed}}`.
53+
54+
* Image and audio frames are decoded by FFmpeg + libswscale rather than by OpenCV's videoio backend. Pixel-exact comparisons against fixtures generated by the previous version will not match; the test fixture `test/support/validate/video/video_sample_frame_0.png` has been regenerated.
55+
56+
* **`:xav` is now an optional dependency.** Add it to your `mix.exs` if you use `Image.Video`:
57+
58+
{:xav, "~> 0.10", optional: true}
59+
60+
Xav requires FFmpeg ≥ 6.0 on the system.
61+
62+
* **`:evision` is no longer needed for `Image.Video`.** It is still required for `Image.QRcode` and for the `Image.to_evision/2` / `Image.from_evision/1` interop helpers, which are unchanged. The README's optional-dependency table reflects the new split.
63+
64+
### Enhancements
65+
66+
* `Image.Video` now supports HTTP/HTTPS/RTSP/RTMP URLs as video sources for free, since FFmpeg supports them natively.
67+
68+
* `Image.xav_configured?/0` is the new compile-time predicate that gates the `Image.Video` module (analogous to `Image.evision_configured?/0` and `Image.bumblebee_configured?/0`).
69+
70+
* The `Image.Video.frame_to_image/1` helper exposes the raw `Xav.Frame``Vix.Vips.Image.t()` conversion (used internally by `image_from_video/2` and `stream!/2`). Useful if you have a frame from elsewhere in the Xav ecosystem and want to bring it into `Image`.
71+
372
## Image 0.65.0
473

574
This release prepares the library for a 1.0 tag. It is dominated by API hygiene rather than new features.

README.md

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ Elixir. It is a high-level wrapper around
66
[libvips](https://www.libvips.org) C library, and provides an
77
idiomatic functional API for image manipulation, drawing, text
88
rendering, EXIF/XMP metadata, classification (via Bumblebee),
9-
object detection (via eVision), QR code encoding and decoding,
9+
video frame extraction (via Xav/FFmpeg), QR code encoding and decoding (via eVision),
1010
video frame extraction, blurhash, perceptual hashing, and many
1111
other image-related operations.
1212

@@ -66,13 +66,20 @@ Documentation can be found at <https://hexdocs.pm/image>.
6666
Plug `Conn`s, in-memory binaries, and S3 sources.
6767

6868
* **Optional ML integrations**
69-
* `Image.Classification` and `Image.Generation` via
70-
[Bumblebee](https://hex.pm/packages/bumblebee).
71-
* `Image.QRcode` (encode + decode) and `Image.Video` (frame
72-
extraction, seek, webcam) via
69+
* `Image.Video` (frame extraction, seek, webcam) via
70+
[Xav](https://hex.pm/packages/xav), an Elixir wrapper around
71+
FFmpeg. Requires FFmpeg ≥ 6.0 on the system.
72+
* `Image.QRcode` (encode + decode) via
7373
[eVision](https://hex.pm/packages/evision).
7474
* `Image.k_means` via [Scholar](https://hex.pm/packages/scholar).
7575
* `Image.to_nx/2` / `Image.from_nx/1` via [Nx](https://hex.pm/packages/nx).
76+
* **Object detection, image classification, and image
77+
generation** live in the separate
78+
[`:image_detection`](https://hex.pm/packages/image_detection)
79+
package. Add it alongside `:image` in your `mix.exs` to get
80+
`Image.Detection`, `Image.Classification`, and
81+
`Image.Generation` (which depend on `:axon_onnx` and
82+
[Bumblebee](https://hex.pm/packages/bumblebee) respectively).
7683

7784
* **Hashing** — perceptual difference hash (`Image.dhash/2`),
7885
blurhash encode/decode (`Image.Blurhash`), Hamming distance.
@@ -256,9 +263,10 @@ optional dependencies enable specific features:
256263
| Dependency | Enables |
257264
|---|---|
258265
| `:nx` | `Image.to_nx/2`, `Image.from_nx/1`, tensor interop |
259-
| `:bumblebee` | `Image.Classification`, `Image.Generation` |
260266
| `:scholar` | `Image.k_means/2` |
261-
| `:evision` | `Image.QRcode`, `Image.Video` |
267+
| `:xav` | `Image.Video` (FFmpeg-backed frame extraction) |
268+
| `:evision` | `Image.QRcode`, `Image.to_evision/2`, `Image.from_evision/1` |
269+
| `:image_detection` | `Image.Detection`, `Image.Classification`, `Image.Generation` (object detection, classification, image generation — pulls Bumblebee, Nx, Axon as transitive deps) |
262270
| `:plug` | streaming via `Plug.Conn` |
263271
| `:req` | streaming over HTTP |
264272
| `:kino` | `Image.Kino` (Livebook integration) |
@@ -286,6 +294,37 @@ You can also set the concurrency programmatically with
286294
`Image.put_concurrency/1` and read it back with
287295
`Image.get_concurrency/0`.
288296

297+
## FFmpeg / Xav log noise
298+
299+
If you use `Image.Video` (which is backed by
300+
[Xav](https://hex.pm/packages/xav) / FFmpeg) you may see lines like
301+
302+
[swscaler @ 0x1490a0000] No accelerated colorspace conversion found from yuv420p to rgb24.
303+
304+
written to `stderr` during frame decoding. These are
305+
**informational notices** from FFmpeg's `libswscale`, **not
306+
errors**. They mean that `libswscale` does not have a
307+
hand-optimised SIMD path for that particular pixel-format
308+
conversion on your CPU, so it is using its generic C fallback.
309+
Decoded frames are bit-for-bit correct either way.
310+
311+
The messages come from FFmpeg writing directly to `stderr` at its
312+
default log level (`AV_LOG_INFO`). Xav does not currently expose
313+
`av_log_set_level/1`, so the only way to silence them from
314+
application code is to install an FFmpeg build that has the
315+
SIMD path for your architecture (typically an FFmpeg compiled
316+
with `--enable-runtime-cpudetect` and any of `--enable-asm`,
317+
`--enable-x86asm`, or platform ASM flags — most distribution
318+
packages already do this). On Apple Silicon the arm64 optimised
319+
path for `yuv420p → rgb24` is not in FFmpeg's `swscale` as of
320+
FFmpeg 7.x, which is why macOS users on M-series machines see
321+
the notice most often.
322+
323+
If the noise is disruptive during tests or automation, you can
324+
redirect stderr for the command in question, e.g.
325+
`mix test 2> /dev/null`. Do not do this for production —
326+
suppressing stderr will also hide real FFmpeg errors.
327+
289328
## Security considerations
290329

291330
* `libvips` and the underlying loaders are written in C; a malicious

config/dev.exs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ config :nx, :default_defn_options, compiler: EXLA
2424
config :logger,
2525
level: :warning
2626

27-
config :image, :classifier,
28-
model: {:hf, "Falconsai/nsfw_image_detection"},
29-
featurizer: {:hf, "Falconsai/nsfw_image_detection"},
30-
featurizer_options: [module: Bumblebee.Vision.VitFeaturizer],
31-
name: Image.Classification.Server,
32-
autostart: true
33-
34-
config :image, :generator, autostart: false
27+
# Image classification and generation moved to the
28+
# `:image_detection` package. Configure them there:
29+
#
30+
# config :image_detection, :classifier,
31+
# model: {:hf, "Falconsai/nsfw_image_detection"},
32+
# featurizer: {:hf, "Falconsai/nsfw_image_detection"},
33+
# autostart: true
34+
#
35+
# config :image_detection, :generator, autostart: false

lib/application.ex

Lines changed: 4 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,24 @@
11
defmodule Image.Application do
22
@moduledoc false
33

4-
# This env var, if set, will prevent `libvips` from loading
5-
# untrusted loaders. We set this to true if it is not otherwise set.
6-
# See https://github.com/kipcole9/image/issues/9
4+
# This env var, if set, will prevent `libvips` from loading untrusted
5+
# loaders. We set it to "TRUE" unless the user has explicitly set
6+
# something else. See https://github.com/kipcole9/image/issues/9
77
@untrusted_env_var "VIPS_BLOCK_UNTRUSTED"
88

99
use Application
10-
require Logger
1110

1211
@doc false
1312
def start(_type, _args) do
1413
set_safe_loader()
1514

1615
Supervisor.start_link(
17-
children(Code.ensure_loaded?(Bumblebee)),
16+
[],
1817
strategy: :one_for_one,
1918
name: Image.Supervisor
2019
)
2120
end
2221

23-
# When Bumblebee is available
24-
if Image.bumblebee_configured?() do
25-
@services [
26-
{{Image.Classification, :classifier, []}, true},
27-
{{Image.Generation, :generator, []}, false}
28-
]
29-
30-
defp children(true) do
31-
Enum.reduce(@services, [], fn {{module, function, args}, start?}, acc ->
32-
if autostart?(function, start?) do
33-
case apply(module, function, args) do
34-
{:error, reason} ->
35-
Logger.warning("Cannot autostart #{inspect(function)}. Error: #{inspect(reason)}")
36-
acc
37-
38-
server ->
39-
[server | acc]
40-
end
41-
else
42-
acc
43-
end
44-
end)
45-
end
46-
end
47-
48-
# When bumblebee is not available
49-
defp children(_) do
50-
[]
51-
end
52-
53-
@doc false
54-
def autostart?(service, start?) do
55-
:image
56-
|> Application.get_env(service, autostart: start?)
57-
|> Keyword.get(:autostart)
58-
end
59-
6022
# Sets `VIPS_BLOCK_UNTRUSTED=TRUE` in the environment unless the user
6123
# has already set it themselves. Prevents libvips from loading
6224
# untrusted format loaders, which is the secure default.

lib/image.ex

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11893,17 +11893,12 @@ defmodule Image do
1189311893
end
1189411894

1189511895
@doc false
11896-
def bumblebee_configured? do
11897-
Enum.reduce_while([Nx, EXLA, Bumblebee], true, fn mod, flag ->
11898-
case Code.ensure_compiled(mod) do
11899-
{:module, _module} -> {:cont, flag}
11900-
_other -> {:halt, false}
11901-
end
11902-
end)
11896+
def evision_configured? do
11897+
match?({:module, _module}, Code.ensure_compiled(Evision))
1190311898
end
1190411899

1190511900
@doc false
11906-
def evision_configured? do
11907-
match?({:module, _module}, Code.ensure_compiled(Evision))
11901+
def xav_configured? do
11902+
match?({:module, _module}, Code.ensure_compiled(Xav.Reader))
1190811903
end
1190911904
end

0 commit comments

Comments
 (0)