From 2958d54480a220f8a26cc78a0fb21ca0611612cc Mon Sep 17 00:00:00 2001 From: Dae Woo Kim Date: Mon, 27 Apr 2026 14:20:23 -0500 Subject: [PATCH 1/7] Add Aqua.jl quality checks; all 8 checks pass - Add Aqua to [extras]/[targets] with compat bound "0.8" - Add compat entries for Random and Test stdlib extras - Add Aqua.test_all testset to runtests.jl - Add Aqua badge to README Co-Authored-By: Claude Sonnet 4.6 --- Project.toml | 8 ++++++-- README.md | 1 + test/runtests.jl | 5 +++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 55c42c7..e86dbbf 100644 --- a/Project.toml +++ b/Project.toml @@ -1,20 +1,24 @@ name = "CenterIndexedArrays" uuid = "46a7138f-0d70-54e1-8ada-fb8296f91f24" -authors = ["Tim Holy "] version = "0.2.4" +authors = ["Tim Holy "] [deps] Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" [compat] +Aqua = "0.8" Interpolations = "0.11, 0.12, 0.13, 0.14, 0.15, 0.16" OffsetArrays = "0.10, 0.11, 1" +Random = "1" +Test = "1" julia = "1" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Random", "Test"] +test = ["Aqua", "Random", "Test"] diff --git a/README.md b/README.md index 0726c92..e1f0b34 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # CenterIndexedArrays [![Build Status](https://travis-ci.org/HolyLab/CenterIndexedArrays.jl.svg?branch=master)](https://travis-ci.org/HolyLab/CenterIndexedArrays.jl) +[![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) A CenterIndexedArray is an array indexed symmetrically around its midpoint. Here's a quick demo: diff --git a/test/runtests.jl b/test/runtests.jl index 7efa677..6197e88 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -2,6 +2,7 @@ using Interpolations, OffsetArrays using Test, Random using OffsetArrays: IdentityUnitRange +using Aqua if !isdefined(@__MODULE__, :ambs) const ambs = detect_ambiguities(Base, Interpolations, OffsetArrays) @@ -10,6 +11,10 @@ end using CenterIndexedArrays using CenterIndexedArrays: SymRange +@testset "Aqua" begin + Aqua.test_all(CenterIndexedArrays) +end + @testset "Ambiguities" begin ambscia = detect_ambiguities(Base, Interpolations, OffsetArrays, CenterIndexedArrays) VERSION >= v"1.1" && @test isempty(setdiff(ambscia, ambs)) From dd179f3a328585550c1bc3f34b8f482f635785b4 Mon Sep 17 00:00:00 2001 From: Dae Woo Kim Date: Mon, 27 Apr 2026 14:22:44 -0500 Subject: [PATCH 2/7] Remove deprecated CenterIndexedArray(Type, dims) constructors Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 1 + src/CenterIndexedArrays.jl | 2 -- src/deprecated.jl | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 src/deprecated.jl diff --git a/.gitignore b/.gitignore index 49541d3..a174b0a 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ *.jl.mem deps/deps.jl Manifest.toml +Manifest-v*.toml diff --git a/src/CenterIndexedArrays.jl b/src/CenterIndexedArrays.jl index 12b47bf..8ff5ea1 100644 --- a/src/CenterIndexedArrays.jl +++ b/src/CenterIndexedArrays.jl @@ -105,6 +105,4 @@ function Base.showarg(io::IO, A::CenterIndexedArray, toplevel) toplevel && print(io, " with eltype ", eltype(A)) end -include("deprecated.jl") - end # module diff --git a/src/deprecated.jl b/src/deprecated.jl deleted file mode 100644 index d8df79c..0000000 --- a/src/deprecated.jl +++ /dev/null @@ -1,2 +0,0 @@ -@deprecate CenterIndexedArray(::Type{T}, dims) where {T} CenterIndexedArray{T}(undef, dims...) -@deprecate CenterIndexedArray(::Type{T}, dims...) where {T} CenterIndexedArray{T}(undef, dims...) From 52662fee56643a617e6625dd930dbb54e84c0d04 Mon Sep 17 00:00:00 2001 From: Dae Woo Kim Date: Mon, 27 Apr 2026 14:34:20 -0500 Subject: [PATCH 3/7] Add ExplicitImports.jl; make implicit imports explicit - Replace bare `using Interpolations, OffsetArrays` with explicit `using Interpolations: Interpolations, AbstractInterpolation` and `using OffsetArrays: OffsetArrays, OffsetArray` - Switch `IdentityUnitRange` from an import to qualified `Base.IdentityUnitRange` - Add ExplicitImports as a test dependency (extras/targets) with compat "1" - Add ExplicitImports testset to prevent regressions Co-Authored-By: Claude Sonnet 4.6 --- Project.toml | 4 +++- src/CenterIndexedArrays.jl | 6 +++--- test/runtests.jl | 5 +++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Project.toml b/Project.toml index e86dbbf..ce7227e 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" [compat] Aqua = "0.8" +ExplicitImports = "1" Interpolations = "0.11, 0.12, 0.13, 0.14, 0.15, 0.16" OffsetArrays = "0.10, 0.11, 1" Random = "1" @@ -17,8 +18,9 @@ julia = "1" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "Random", "Test"] +test = ["Aqua", "ExplicitImports", "Random", "Test"] diff --git a/src/CenterIndexedArrays.jl b/src/CenterIndexedArrays.jl index 8ff5ea1..736e283 100644 --- a/src/CenterIndexedArrays.jl +++ b/src/CenterIndexedArrays.jl @@ -1,7 +1,7 @@ module CenterIndexedArrays -using Interpolations, OffsetArrays -using OffsetArrays: IdentityUnitRange +using Interpolations: Interpolations, AbstractInterpolation +using OffsetArrays: OffsetArrays, OffsetArray export CenterIndexedArray @@ -53,7 +53,7 @@ end # This is incomplete: ideally we wouldn't need SymAx in the first slot # as long as there was at least one SymAx. -function Base.similar(A::CenterIndexedArray, ::Type{T}, inds::Tuple{SymAx,Vararg{Union{Int,<:IdentityUnitRange,SymAx}}}) where T +function Base.similar(A::CenterIndexedArray, ::Type{T}, inds::Tuple{SymAx,Vararg{Union{Int,<:Base.IdentityUnitRange,SymAx}}}) where T torange(n) = isa(n, Int) ? Base.OneTo(n) : n return OffsetArray{T}(undef, map(torange, inds)) end diff --git a/test/runtests.jl b/test/runtests.jl index 6197e88..6dd42e6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -3,6 +3,7 @@ using Interpolations, OffsetArrays using Test, Random using OffsetArrays: IdentityUnitRange using Aqua +using ExplicitImports if !isdefined(@__MODULE__, :ambs) const ambs = detect_ambiguities(Base, Interpolations, OffsetArrays) @@ -15,6 +16,10 @@ using CenterIndexedArrays: SymRange Aqua.test_all(CenterIndexedArrays) end +@testset "ExplicitImports" begin + test_explicit_imports(CenterIndexedArrays; all_qualified_accesses_are_public=false) +end + @testset "Ambiguities" begin ambscia = detect_ambiguities(Base, Interpolations, OffsetArrays, CenterIndexedArrays) VERSION >= v"1.1" && @test isempty(setdiff(ambscia, ambs)) From edf0fbd2531fb4fd1077c058a1526455fdb17d7e Mon Sep 17 00:00:00 2001 From: Dae Woo Kim Date: Mon, 27 Apr 2026 14:51:19 -0500 Subject: [PATCH 4/7] Improve test coverage to 98.7%; remove dead iterate methods Remove two `iterate` methods from symrange.jl that defined `CenterIndexedArrays.iterate` instead of `Base.iterate` and were never called (iteration relied on the AbstractUnitRange fallback). The methods also had a bug: the `r.n == 0` guard treated SymRange(0) as empty, when it actually contains a single element (0). New tests cover: SymRange(n::Integer) with non-Int input, SymRange(0) iteration, and interpolation-backed CenterIndexedArray indexing with both integer and fractional indices. Co-Authored-By: Claude Sonnet 4.6 --- src/symrange.jl | 10 ---------- test/runtests.jl | 20 ++++++++++++++++++++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/symrange.jl b/src/symrange.jl index e5f62ef..e7a17bf 100644 --- a/src/symrange.jl +++ b/src/symrange.jl @@ -17,16 +17,6 @@ Base.axes(r::SymRange) = (r,) @inline Base.unsafe_indices(r::SymRange) = (r,) -function iterate(r::SymRange) - r.n == 0 && return nothing - first(r), first(r) -end - -function iterate(r::SymRange, s) - s == last(r) && return nothing - copy(s+1), s+1 -end - @inline function Base.getindex(v::SymRange, i::Int) @boundscheck abs(i) <= v.n || Base.throw_boundserror(v, i) return i diff --git a/test/runtests.jl b/test/runtests.jl index 6dd42e6..bcca863 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -56,6 +56,12 @@ end io = IOBuffer() print(io, r) @test String(take!(io)) == "SymRange(3)" + + # Non-Int Integer constructor + @test SymRange(Int32(3)) === SymRange(3) + + # SymRange(0) contains a single element, 0 + @test collect(SymRange(0)) == [0] end @testset "Uninitialized" begin @@ -184,3 +190,17 @@ end @test A+A == CenterIndexedArray(dat+dat) @test isa(A .+ 1, CenterIndexedArray) end + +@testset "Interpolations" begin + dat = rand(5, 7) + itp = interpolate(dat, BSpline(Linear())) + A = CenterIndexedArray(itp) + # Integer indexing + @test A[0, 0] ≈ dat[3, 4] + @test A[-2, -3] ≈ dat[1, 1] + @test @inferred(A[1, 2]) ≈ dat[4, 6] + # Fractional (non-integer) indexing + @test A[0.0, 0.0] ≈ dat[3, 4] + @test A[0.5, 0.5] ≈ itp(3.5, 4.5) + @test_throws BoundsError A[3, 0] +end From 1d7d02fb15e4ccd1da4e9258a79709943d40bb81 Mon Sep 17 00:00:00 2001 From: Dae Woo Kim Date: Mon, 27 Apr 2026 14:55:33 -0500 Subject: [PATCH 5/7] Add module docstring; rewrite CenterIndexedArray docstring with full constructor coverage Co-Authored-By: Claude Sonnet 4.6 --- src/CenterIndexedArrays.jl | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/src/CenterIndexedArrays.jl b/src/CenterIndexedArrays.jl index 736e283..d00bfb5 100644 --- a/src/CenterIndexedArrays.jl +++ b/src/CenterIndexedArrays.jl @@ -1,3 +1,10 @@ +""" + CenterIndexedArrays + +Provides `CenterIndexedArray`, an array type whose center element is at +index `(0, 0, …)`. Valid indices along each dimension of size `2n+1` +run from `-n` to `n`. +""" module CenterIndexedArrays using Interpolations: Interpolations, AbstractInterpolation @@ -8,11 +15,16 @@ export CenterIndexedArray include("symrange.jl") """ -A `CenterIndexedArray` is one for which the array center has indexes -`0,0,...`. Along each coordinate, allowed indexes range from `-n:n`. + CenterIndexedArray(A::AbstractArray) + CenterIndexedArray{T}(undef, dims...) + CenterIndexedArray{T,N}(undef, dims...) + +An array wrapper that re-indexes around zero: the center element is at +index `(0, 0, …)`, and along each dimension of size `2n+1` the valid +indices run from `-n` to `n`. All dimension sizes must be odd. -CenterIndexedArray(A) "converts" `A` into a CenterIndexedArray. All -the sizes of `A` must be odd. +The first form wraps `A` without copying. The `undef` forms allocate a +new `Array{T}` with the given dimensions (each of which must be odd). """ struct CenterIndexedArray{T,N,A<:AbstractArray} <: AbstractArray{T,N} data::A From 915df33b0e793df0944d553f3b08683c011b2041 Mon Sep 17 00:00:00 2001 From: Dae Woo Kim Date: Mon, 27 Apr 2026 14:59:07 -0500 Subject: [PATCH 6/7] Rewrite README with updated badges and verified jldoctest examples Replace dead Travis CI badge with GitHub Actions, add Codecov and JuliaHub version badges. Add installation section, SymRange explanation, and Interpolations integration section. Convert all code examples to jldoctest blocks verified against a live Julia session. Co-Authored-By: Claude Sonnet 4.6 --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index e1f0b34..51bc9ba 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,98 @@ # CenterIndexedArrays -[![Build Status](https://travis-ci.org/HolyLab/CenterIndexedArrays.jl.svg?branch=master)](https://travis-ci.org/HolyLab/CenterIndexedArrays.jl) +[![CI](https://github.com/HolyLab/CenterIndexedArrays.jl/actions/workflows/CI.yml/badge.svg)](https://github.com/HolyLab/CenterIndexedArrays.jl/actions/workflows/CI.yml) +[![codecov](https://codecov.io/gh/HolyLab/CenterIndexedArrays.jl/branch/master/graph/badge.svg)](https://codecov.io/gh/HolyLab/CenterIndexedArrays.jl) +[![version](https://juliahub.com/docs/General/CenterIndexedArrays/stable/version.svg)](https://juliahub.com/ui/Packages/General/CenterIndexedArrays) [![Aqua QA](https://juliatesting.github.io/Aqua.jl/dev/assets/badge.svg)](https://github.com/JuliaTesting/Aqua.jl) -A CenterIndexedArray is an array indexed symmetrically around its midpoint. -Here's a quick demo: +A `CenterIndexedArray` is an array indexed symmetrically around its midpoint: the center +element is always at index `(0, 0, …)`, and along each dimension of size `2n+1` the valid +indices run from `-n` to `n`. All dimension sizes must be odd. + +A common use case is image registration, where the mismatch between two images is stored as +a function of their relative displacement — the center of that array is naturally the +zero-displacement case. + +## Installation ```julia +using Pkg +Pkg.add("CenterIndexedArrays") +``` + +## Basic usage + +```jldoctest +julia> using CenterIndexedArrays + julia> A = CenterIndexedArray(reshape(1:15, 3, 5)) 3×5 CenterIndexedArray(reshape(::UnitRange{Int64}, 3, 5)) with eltype Int64 with indices SymRange(1)×SymRange(2): 1 4 7 10 13 2 5 8 11 14 3 6 9 12 15 -julia> A[0, 0] # the center point +julia> A[0, 0] # center element 8 -julia> A[0, -1] +julia> A[0, -1] # one step left of center 5 + +julia> A[-1, 2] # one step above, two steps right +13 ``` -An example application is in image registration, to encode the mismatch between two images as you displace them relative to one another. +You can also allocate an uninitialized array: -The axes, `SymRange`, are symmetric ranges. They too are indexed symmetrically around 0: +```jldoctest +julia> using CenterIndexedArrays -```julia -julia> r = CenterIndexedArrays.SymRange(3) +julia> B = CenterIndexedArray{Float64}(undef, 3, 5); + +julia> axes(B) +(SymRange(1), SymRange(2)) + +julia> size(B) +(3, 5) +``` + +## SymRange axes + +The axes of a `CenterIndexedArray` are `SymRange` values — unit ranges symmetric around +zero. `SymRange(n)` covers `-n:n` and has length `2n+1`. + +```jldoctest +julia> using CenterIndexedArrays: SymRange + +julia> r = SymRange(3) SymRange(3) julia> length(r) 7 -julia> r[7] -ERROR: BoundsError: attempt to access 7-element SymRange with indices SymRange(3) at index [7] -Stacktrace: - [1] throw_boundserror(::SymRange, ::Int64) at ./abstractarray.jl:538 - [2] getindex(::SymRange, ::Int64) at /home/tim/.julia/dev/CenterIndexedArrays/src/symrange.jl:28 - [3] top-level scope at none:0 +julia> first(r), last(r) +(-3, 3) + +julia> r[-3], r[3] +(-3, 3) +``` + +## Interpolation + +Wrapping an `Interpolations.jl` interpolation object enables fractional (sub-integer) +indexing, which is useful when computing cross-correlations at non-integer offsets. + +```jldoctest +julia> using CenterIndexedArrays, Interpolations + +julia> dat = collect(reshape(1.0:25.0, 5, 5)); + +julia> itp = interpolate(dat, BSpline(Linear())); + +julia> A = CenterIndexedArray(itp); -julia> r[-3] --3 +julia> A[0, 0] # center, equivalent to dat[3, 3] +13.0 -julia> r[3] -3 +julia> A[0.5, 0.0] # fractional index; linearly interpolated +13.5 ``` From 9d5d0c0219fd5d06860b6efddb69c62aa4f449b7 Mon Sep 17 00:00:00 2001 From: Dae Woo Kim Date: Wed, 29 Apr 2026 11:05:49 -0500 Subject: [PATCH 7/7] set CI julia minimum version as 'min' --- .github/workflows/CI.yml | 4 ++-- Project.toml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 5c8ecde..d7157e9 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: version: - - '1.10' + - 'min' - '1' # - 'nightly' os: @@ -22,7 +22,7 @@ jobs: - x64 steps: - uses: actions/checkout@v2 - - uses: julia-actions/setup-julia@v1 + - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.version }} arch: ${{ matrix.arch }} diff --git a/Project.toml b/Project.toml index ce7227e..510a0c7 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "CenterIndexedArrays" uuid = "46a7138f-0d70-54e1-8ada-fb8296f91f24" -version = "0.2.4" +version = "1.0.0" authors = ["Tim Holy "] [deps] @@ -14,7 +14,7 @@ Interpolations = "0.11, 0.12, 0.13, 0.14, 0.15, 0.16" OffsetArrays = "0.10, 0.11, 1" Random = "1" Test = "1" -julia = "1" +julia = "1.10" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"