Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 2 additions & 11 deletions .github/workflows/Documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,11 @@ jobs:
os: [ubuntu-latest]
steps:
- uses: actions/checkout@v4
- uses: julia-actions/setup-julia@latest
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.julia-version }}
- name: Cache artifacts
uses: actions/cache@v4
env:
cache-name: cache-artifacts
with:
path: ~/.julia/artifacts
key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }}
restore-keys: |
${{ runner.os }}-test-${{ env.cache-name }}-
${{ runner.os }}-test-
${{ runner.os }}-
uses: julia-actions/cache@v2
- name: Install dependencies
run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()'
- name: Build and deploy
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/UnitTest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ jobs:
uses: julia-actions/cache@v2

- name: "Unit Test"
uses: julia-actions/julia-runtest@master
uses: julia-actions/julia-runtest@v1
env:
JULIA_NUM_THREADS: 2

- uses: julia-actions/julia-processcoverage@v1
- uses: codecov/codecov-action@v5
Expand Down
4 changes: 3 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
RealFFTs = "3645faec-0534-4e91-afef-021db0981eec"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Expand All @@ -31,10 +32,11 @@ ImageCore = "0.10"
OffsetArrays = "1.9"
PrecompileTools = "1"
Reexport = "1.1"
RealFFTs = "1"
StaticArrays = "0.10, 0.11, 0.12, 1.0"
Statistics = "1"
TiledIteration = "0.2, 0.3, 0.4, 0.5"
julia = "1.6"
julia = "1.10"

[extras]
AxisArrays = "39de3d68-74b9-583c-8d2d-e117c070f3a9"
Expand Down
22 changes: 20 additions & 2 deletions docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ processing.

The main functions provided by this package are:

| Function | Action |
| Function | Action |
|:-------------------------|:---------------|
|[`imfilter`](@ref) | Filter a one, two or multidimensional array img with a kernel by computing their correlation |
|[`imfilter!`](@ref) | Filter an array img with kernel kernel by computing their correlation, storing the result in imgfilt |
Expand All @@ -25,7 +25,7 @@ The main functions provided by this package are:
|[`findlocalminima`](@ref) | Returns the coordinates of elements whose value is smaller than all of their immediate neighbors |
|[`findlocalmaxima`](@ref) | Returns the coordinates of elements whose value is larger than all of their immediate neighbors |

Common kernels (filters) are organized in the `Kernel` and `KernelFactors` modules.
Common kernels (filters) are organized in the `Kernel` and `KernelFactors` modules.

A common task in image processing and computer vision is computing
image *gradients* (derivatives), for which there is the dedicated
Expand Down Expand Up @@ -80,6 +80,24 @@ transform (FFT). By default, this choice is made based on kernel
size. You can manually specify the algorithm using [`Algorithm.FFT()`](@ref)
or [`Algorithm.FIR()`](@ref).

#### Reusing FFT plans

It is possible to reuse FFT plans if the operation is going to be done on the
same array type and dimensions i.e. on each image of an image stack

```julia
using ImageFiltering, ComputationalResources
imgstack = rand(Float64, 200, 100, 10)
imgstack_filtered = similar(imgstack)

kernel = ImageFiltering.factorkernel(Kernel.LoG(1))
fft_planned = CPU1(ImageFiltering.planned_fft(imgstack_filtered[:,:,1], kernel))

for i in axes(imgstack, 3)
imfilter!(fft_planned, imgstack_filtered[:,:,i], imgstack[:,:,i], kernel)
end
```

### Feature: Multithreading

If you launch Julia with `JULIA_NUM_THREADS=n` (where `n > 1`), then
Expand Down
24 changes: 22 additions & 2 deletions src/ImageFiltering.jl
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
module ImageFiltering

using FFTW
using RealFFTs
using ImageCore, FFTViews, OffsetArrays, StaticArrays, ComputationalResources, TiledIteration
# Where possible we avoid a direct dependency to reduce the number of [compat] bounds
# using FixedPointNumbers: Normed, N0f8 # reexported by ImageCore
using ImageCore.MappedArrays
using Statistics, LinearAlgebra
using Base: Indices, tail, fill_to_length, @pure, depwarn, @propagate_inbounds
import Base: copy!
using OffsetArrays: IdentityUnitRange # using the one in OffsetArrays makes this work with multiple Julia versions
using SparseArrays # only needed to fix an ambiguity in borderarray
using Reexport
Expand All @@ -30,7 +32,8 @@ export Kernel, KernelFactors,
imgradients, padarray, centered, kernelfactors, reflect,
freqkernel, spacekernel,
findlocalminima, findlocalmaxima,
blob_LoG, BlobLoG
blob_LoG, BlobLoG,
planned_fft

const FixedColorant{T<:Normed} = Colorant{T}
const StaticOffsetArray{T,N,A<:StaticArray} = OffsetArray{T,N,A}
Expand All @@ -50,16 +53,33 @@ function Base.transpose(A::StaticOffsetArray{T,2}) where T
end

module Algorithm
import FFTW
# deliberately don't export these, but it's expected that they
# will be used as Algorithm.FFT(), etc.
abstract type Alg end
"Filter using the Fast Fourier Transform" struct FFT <: Alg end
"Filter using the Fast Fourier Transform" struct FFT <: Alg
plan1::Union{Function,Nothing}
plan2::Union{Function,Nothing}
plan3::Union{Function,Nothing}
end
FFT() = FFT(nothing, nothing, nothing)
function Base.show(io::IO, alg::FFT)
if alg.plan1 === nothing && alg.plan2 === nothing && alg.plan3 === nothing
print(io, "FFT()")
else
print(io, "FFT(planned)")
end
end
"Filter using a direct algorithm" struct FIR <: Alg end
Base.show(io::IO, alg::FIR) = print(io, "FIR()")
"Cache-efficient filtering using tiles" struct FIRTiled{N} <: Alg
tilesize::Dims{N}
end
Base.show(io::IO, ::FIRTiled{N}) where N = print(io, "FIRTiled{$N}()")
"Filter with an Infinite Impulse Response filter" struct IIR <: Alg end
Base.show(io::IO, alg::IIR) = print(io, "IIR()")
"Filter with a cascade of mixed types (IIR, FIR)" struct Mixed <: Alg end
Base.show(io::IO, alg::Mixed) = print(io, "Mixed()")

FIRTiled() = FIRTiled(())
end
Expand Down
Loading