Skip to content

Commit c204b2b

Browse files
committed
Add dialyzer, and to CI
1 parent 947e027 commit c204b2b

7 files changed

Lines changed: 85 additions & 3 deletions

File tree

.dialyzer_ignore_warnings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[
2+
]

.github/workflows/ci.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,19 @@ jobs:
8585
- name: Run tests
8686
run: mix test
8787

88+
- name: Cache Dialyzer PLT
89+
if: ${{ matrix.lint }}
90+
uses: actions/cache@v5
91+
with:
92+
path: priv/plts
93+
key: ${{ runner.os }}-plt-${{ matrix.otp }}-${{ matrix.elixir }}-${{ hashFiles('**/mix.lock') }}
94+
restore-keys: |
95+
${{ runner.os }}-plt-${{ matrix.otp }}-${{ matrix.elixir }}-
96+
97+
- name: Run Dialyzer
98+
run: mix dialyzer --format github
99+
if: ${{ matrix.lint }}
100+
88101
- name: Build hex package (verify release readiness)
89102
run: MIX_ENV=prod mix hex.build
90103
if: ${{ matrix.lint }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,6 @@ CLAUDE.md
6262
.claude
6363

6464
image_lens_correction-*.tar
65+
66+
# Dialyzer PLTs
67+
/priv/plts/

lensfun/lensfun.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ defmodule Image.LensFun do
1414
@db_save_file "priv/lensfun/lensfun.etf"
1515

1616
@doc "Absolute path to the bundled lensfun ETF database."
17+
@doc subject: "Database", since: "0.1.0"
1718
def db_path do
1819
Application.app_dir(@app_name, @db_save_file)
1920
end
@@ -22,6 +23,7 @@ defmodule Image.LensFun do
2223
Load and decode the bundled lensfun database. Cached in the persistent
2324
term cache so repeated calls are cheap.
2425
"""
26+
@doc subject: "Database", since: "0.1.0"
2527
def db do
2628
case :persistent_term.get({__MODULE__, :db}, :missing) do
2729
:missing ->
@@ -63,6 +65,7 @@ defmodule Image.LensFun do
6365
* `{:error, :not_found}`.
6466
6567
"""
68+
@doc subject: "Database", since: "0.1.0"
6669
def find_lens(maker, lens_model, options \\ []) do
6770
crop_factor = Keyword.get(options, :crop_factor)
6871

@@ -167,6 +170,7 @@ defmodule Image.LensFun do
167170
168171
Returns `{:ok, %{model:, mount:, crop_factor:}}` or `{:error, :not_found}`.
169172
"""
173+
@doc subject: "Database", since: "0.1.0"
170174
def find_camera(maker, model) do
171175
list = Map.get(db().cameras, canonical_maker(maker), [])
172176

@@ -186,6 +190,7 @@ defmodule Image.LensFun do
186190
endpoints handled by clamping. Returns `{:ok, distortion}` or
187191
`{:error, :no_calibration}`.
188192
"""
193+
@doc subject: "Database", since: "0.1.0"
189194
def interpolate_distortion(%{distortion: []}, _focal), do: {:error, :no_calibration}
190195

191196
def interpolate_distortion(%{distortion: distortions}, focal) when is_number(focal) do
@@ -206,6 +211,7 @@ defmodule Image.LensFun do
206211
207212
Returns `{:ok, tca}` or `{:error, :no_calibration}`.
208213
"""
214+
@doc subject: "Database", since: "0.1.0"
209215
def interpolate_tca(lens, focal)
210216

211217
def interpolate_tca(%{tca: []}, _focal), do: {:error, :no_calibration}
@@ -226,6 +232,7 @@ defmodule Image.LensFun do
226232
aperture and distance using inverse-distance weighting (the same
227233
approach as lensfun's `InterpolateVignetting`).
228234
"""
235+
@doc subject: "Database", since: "0.1.0"
229236
def interpolate_vignetting(%{vignetting: []}, _focal, _aperture, _distance) do
230237
{:error, :no_calibration}
231238
end
@@ -309,6 +316,7 @@ defmodule Image.LensFun do
309316
map with keys `:make`, `:model`, `:lens_make`, `:lens_model`,
310317
`:focal_length`, `:aperture`, `:distance`, `:crop_factor`.
311318
"""
319+
@doc subject: "Database", since: "0.1.0"
312320
def metrics_from_exif_and_options(%Vimage{} = image, options \\ []) do
313321
exif =
314322
case Image.exif(image) do

mix.exs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ defmodule ImageLensCorrection.MixProject do
1717
source_url: @source_url,
1818
build_embedded: Mix.env() == :prod,
1919
start_permanent: Mix.env() == :prod,
20-
elixirc_paths: elixirc_paths(Mix.env())
20+
elixirc_paths: elixirc_paths(Mix.env()),
21+
dialyzer: [
22+
ignore_warnings: ".dialyzer_ignore_warnings",
23+
plt_add_apps: ~w(mix ex_unit)a,
24+
plt_local_path: "priv/plts",
25+
plt_core_path: "priv/plts"
26+
]
2127
]
2228
end
2329

@@ -42,7 +48,8 @@ defmodule ImageLensCorrection.MixProject do
4248
# database; pulled transitively by `:image` for XMP metadata so
4349
# cannot be scoped to `:dev` here.
4450
{:sweet_xml, "~> 0.7"},
45-
{:ex_doc, "~> 0.34", only: [:dev], runtime: false}
51+
{:ex_doc, "~> 0.34", only: [:dev], runtime: false},
52+
{:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}
4653
] ++ maybe_json_polyfill()
4754
end
4855

@@ -92,7 +99,7 @@ defmodule ImageLensCorrection.MixProject do
9299
"CHANGELOG.md",
93100
"LICENSE.md"
94101
],
95-
formatters: ["html"],
102+
formatters: ["html", "markdown"],
96103
groups_for_docs: [
97104
Distortion: &(&1[:subject] == "Distortion"),
98105
Database: &(&1[:subject] == "Database")

mix.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
%{
22
"cc_precompiler": {:hex, :cc_precompiler, "0.1.11", "8c844d0b9fb98a3edea067f94f616b3f6b29b959b6b3bf25fee94ffe34364768", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "3427232caf0835f94680e5bcf082408a70b48ad68a5f5c0b02a3bea9f3a075b9"},
33
"color": {:hex, :color, "0.12.1", "a1c3aa9aa5ad99b3a217d49ab6743c082399af57002c83d9c8160f7f8b7a3019", [:mix], [{:bandit, "~> 1.5", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b4241a97b21af453aeb72aa56e5297821003eb93ce29b2893b344bf2166a1740"},
4+
"dialyxir": {:hex, :dialyxir, "1.4.7", "dda948fcee52962e4b6c5b4b16b2d8fa7d50d8645bbae8b8685c3f9ecb7f5f4d", [:mix], [{:erlex, ">= 0.2.8", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b34527202e6eb8cee198efec110996c25c5898f43a4094df157f8d28f27d9efe"},
45
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
56
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
7+
"erlex": {:hex, :erlex, "0.2.8", "cd8116f20f3c0afe376d1e8d1f0ae2452337729f68be016ea544a72f767d9c12", [:mix], [], "hexpm", "9d66ff9fedf69e49dc3fd12831e12a8a37b76f8651dd21cd45fcf5561a8a7590"},
68
"ex_doc": {:hex, :ex_doc, "0.40.1", "67542e4b6dde74811cfd580e2c0149b78010fd13001fda7cfeb2b2c2ffb1344d", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "bcef0e2d360d93ac19f01a85d58f91752d930c0a30e2681145feea6bd3516e00"},
79
"image": {:hex, :image, "0.66.0", "8abf53bd01c7e988146b61a1a26e98a308c052d182ec630ce983fa6d72704f98", [:mix], [{:color, "~> 0.4", [hex: :color, repo: "hexpm", optional: false]}, {:evision, "~> 0.1.33 or ~> 0.2", [hex: :evision, repo: "hexpm", optional: true]}, {:exla, "~> 0.10", [hex: :exla, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:kino, "~> 0.13", [hex: :kino, repo: "hexpm", optional: true]}, {:nx, "~> 0.10", [hex: :nx, repo: "hexpm", optional: true]}, {:nx_image, "~> 0.1", [hex: :nx_image, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.1 or ~> 3.2 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: true]}, {:req, "~> 0.4", [hex: :req, repo: "hexpm", optional: true]}, {:rustler, "> 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:scholar, "~> 0.3", [hex: :scholar, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}, {:vix, "~> 0.33", [hex: :vix, repo: "hexpm", optional: false]}, {:xav, "~> 0.10", [hex: :xav, repo: "hexpm", optional: true]}], "hexpm", "3cd641cf715cb3fa9e03a4af09a72c8ef18a93131e74be1acc8af89306e2182f"},
810
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},

test/lensfun_correct_test.exs

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,52 @@ defmodule Image.LensFun.Correct.Test do
5757

5858
assert diff < 0.5
5959
end
60+
61+
test ":projection routes through Geometry for a fisheye lens" do
62+
# Samyang 7.5mm fisheye is in the bundled DB with type: :fisheye.
63+
image = Image.new!(200, 200, color: :red, bands: 3)
64+
65+
assert {:ok, corrected} =
66+
Correct.correct(image,
67+
lens_make: "Samyang",
68+
lens_model: "Samyang 7.5mm f/3.5 UMC Fish-eye MFT",
69+
focal_length: 7.5,
70+
aperture: 4.0,
71+
crop_factor: 2.0,
72+
corrections: [:projection],
73+
target_projection: :rectilinear
74+
)
75+
76+
# Projection conversion preserves dimensions but should change pixel
77+
# values (the image is geometrically reprojected). Compare against
78+
# the input — if projection actually ran, the corner moves.
79+
assert Image.width(corrected) == Image.width(image)
80+
assert Image.height(corrected) == Image.height(image)
81+
end
82+
83+
test ":projection is a no-op when source equals target" do
84+
# Most lenses default to :rectilinear; selecting :rectilinear as the
85+
# target is a no-op even when projection is enabled.
86+
image = Image.new!(100, 100, color: [128, 64, 200], bands: 3)
87+
88+
assert {:ok, corrected} =
89+
Correct.correct(image,
90+
lens_make: "Canon",
91+
lens_model: "Canon EF 100mm f/2.8 Macro USM",
92+
focal_length: 100.0,
93+
aperture: 5.6,
94+
crop_factor: 1.0,
95+
corrections: [:projection],
96+
target_projection: :rectilinear
97+
)
98+
99+
diff =
100+
image
101+
|> Image.Math.subtract!(corrected)
102+
|> Image.Math.pow!(2)
103+
|> Vix.Vips.Operation.avg!()
104+
105+
assert diff < 0.5
106+
end
60107
end
61108
end

0 commit comments

Comments
 (0)