Skip to content

Commit 949d89d

Browse files
authored
Add skewness filter (#18)
1 parent 84d62ab commit 949d89d

File tree

15 files changed

+167
-143
lines changed

15 files changed

+167
-143
lines changed

.github/workflows/CI.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ jobs:
99
fail-fast: false
1010
matrix:
1111
version:
12-
- '1.6'
13-
- 'nightly'
12+
- "1.6"
13+
- "1"
14+
- "nightly"
1415
os:
1516
- ubuntu-latest
1617
- macOS-latest

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,7 @@
44
.DS_Store
55
Manifest.toml
66
docs/build/
7+
*.tif
8+
*.gif
9+
test/benchmark.jl
10+
.vscode

Project.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "GeoArrayOps"
22
uuid = "e6005643-8f00-58df-85c5-7d93a3520d70"
33
authors = ["Maarten Pronk <[email protected]>", "Deltares"]
4-
version = "0.3.0"
4+
version = "0.3.1"
55

66
[deps]
77
DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8"
@@ -11,21 +11,21 @@ ImageCore = "a09fc81d-aa75-5fe9-8630-4744c3626534"
1111
ImageFiltering = "6a3955dd-da59-5b1f-98d4-e7296123deb5"
1212
OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881"
1313
PaddedViews = "5432bcbf-9aad-5242-b902-cca2824c8663"
14-
ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca"
1514
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
1615
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
16+
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
1717

1818
[compat]
1919
DataStructures = "0.18"
2020
Distances = "0.10"
2121
FillArrays = "0.12, 0.13"
2222
ImageCore = "0.9"
23-
ImageFiltering = "0.7"
23+
ImageFiltering = "0.6, 0.7"
2424
OffsetArrays = "1.10"
25-
ProgressMeter = "1.7"
2625
PaddedViews = "0.5"
2726
StaticArrays = "1"
28-
julia = "1.5, 1.6, 1.7"
27+
StatsBase = "0.33"
28+
julia = "1.5, 1.6, 1.7, 1.8"
2929

3030
[extras]
3131
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

README.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
# GeoArrayOps
55
Geospatial operations, cost and filtering algorithms as used in for elevation rasters.
66

7-
*This is a work in progress*
8-
97
## Functionality
10-
- Terrain filters, such as Progressive Morphological Filters (PMF, SMF)
8+
- Terrain filters, such as Progressive Morphological Filters (PMF, SMF) and Skewness balancing
119
- Geospatial cost (friction) operations that mimic PCRaster. These functions should however be more Julian, extensible and scale better.
1210
- Visualization, such as Perceptually Shaded Slope Map (PSSM)
1311
- Terrain analysis functions, such as roughness, Topographic Position Index (TPI), Terrain Ruggedness Index (TRI).
@@ -17,5 +15,5 @@ The package can be installed with the Julia package manager.
1715
From the Julia REPL, type `]` to enter the Pkg REPL mode and run:
1816

1917
```
20-
pkg> add https://github.com/Deltares/GeoArrayOps.jl
18+
pkg> add GeoArrayOps
2119
```

docs/src/index.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@
44
# GeoArrayOps
55
Geospatial operations, cost and filtering algorithms as used in for elevation rasters.
66

7-
*This is a work in progress*
8-
97
## Functionality
10-
- Terrain filters, such as Progressive Morphological Filters (PMF, SMF)
8+
- Terrain filters, such as Progressive Morphological Filters (PMF, SMF) and Skewness balancing
119
- Geospatial cost (friction) operations that mimic PCRaster. These functions should however be more Julian, extensible and scale better.
1210
- Visualization, such as Perceptually Shaded Slope Map (PSSM)
1311
- Terrain analysis functions, such as roughness, Topographic Position Index (TPI), Terrain Ruggedness Index (TRI).
@@ -17,7 +15,7 @@ The package can be installed with the Julia package manager.
1715
From the Julia REPL, type `]` to enter the Pkg REPL mode and run:
1816

1917
```
20-
pkg> add https://github.com/Deltares/GeoArrayOps.jl
18+
pkg> add GeoArrayOps
2119
```
2220

2321
## Index

src/GeoArrayOps.jl

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
1-
__precompile__()
21
module GeoArrayOps
3-
using ProgressMeter
2+
using StatsBase: skewness
3+
using OffsetArrays: OffsetMatrix
4+
using ImageFiltering: mapwindow, sobel, imgradients
5+
using Distances: Euclidean, euclidean, evaluate
6+
using PaddedViews: PaddedView
7+
using FillArrays: fill
8+
using StaticArrays: @SMatrix, @MMatrix, SMatrix, MMatrix
9+
using DataStructures: Deque
10+
using Statistics: median, mean
11+
using ImageCore: scaleminmax, Gray
412

513
include("utils.jl")
614
include("pmf.jl")
@@ -9,36 +17,13 @@ include("psf.jl")
917
include("plot.jl")
1018
include("spread.jl")
1119
include("terrain.jl")
20+
include("skew.jl")
1221

1322
export pmf, smf, psf
1423
export pssm
1524
export pitremoval
1625
export spread, spread2
1726
export roughness, TRI, TPI
18-
19-
# function __init__()
20-
# A = rand(Float64, 3, 3)
21-
# pssm(A)
22-
# spread(A, A, A)
23-
# spread2(A, A, A)
24-
# spread(A, A, 1.)
25-
# spread(A, 1., 1.)
26-
# roughness(A)
27-
# TRI(A)
28-
# TPI(A)
29-
# end
30-
31-
precompile(pmf, (Matrix{Float64},))
32-
precompile(psf, (Matrix{Float64},))
33-
precompile(pitremoval, (Matrix{Float64},))
34-
precompile(smf, (Matrix{Float64},))
35-
precompile(pssm, (Matrix{Float64},))
36-
precompile(spread, (Matrix{Float64}, Matrix{Float64}, Matrix{Float64}))
37-
precompile(spread2, (Matrix{Float64}, Matrix{Float64}, Matrix{Float64}))
38-
precompile(spread, (Matrix{Float64}, Matrix{Float64}, Float64))
39-
precompile(spread, (Matrix{Float64}, Float64, Float64))
40-
precompile(roughness, (Matrix{Float64},))
41-
precompile(TRI, (Matrix{Float64},))
42-
precompile(TPI, (Matrix{Float64},))
27+
export skb
4328

4429
end # module

src/plot.jl

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
11
# using RecipesBase
2-
using ImageFiltering
3-
using ImageCore
4-
52
"""
63
```
74
image = pssm(A; exaggeration, resolution)
@@ -18,13 +15,16 @@ Perceptually Shaded Slope Map by *Pingel, Clarke. 2014* [^pingel2014].
1815
1916
[^pingel2014]: Pingel, Thomas, and Clarke, Keith. 2014. ‘Perceptually Shaded Slope Maps for the Visualization of Digital Surface Models’. Cartographica: The International Journal for Geographic Information and Geovisualization 49 (4): 225–40. <https://doi.org/10/ggnthv>.
2017
"""
21-
function pssm(A::AbstractMatrix{<:Real}; exaggeration=2.3, resolution=1.)
22-
x, y = imgradients(A * exaggeration, ImageFiltering.sobel)
23-
G = sqrt.(x.^2 .+ y.^2)
24-
25-
G /= resolution # account for horizontal resolution
26-
18+
function pssm(A::AbstractMatrix{<:Real}; exaggeration=2.3, resolution=1.0)
19+
G = slope(A, exaggeration, resolution)
2720
f = scaleminmax(0, 1)
2821
clamped = f.(G)
2922
Gray.(1 .- clamped)
3023
end
24+
25+
function slope(A, exaggeration=1.0, resolution=1.0)
26+
x, y = imgradients(A .* exaggeration, sobel)
27+
G = sqrt.(x .^ 2 .+ y .^ 2)
28+
G /= resolution # account for horizontal resolution
29+
G
30+
end

src/pmf.jl

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ Afterwards, one can retrieve the resulting mask for `A` by `A .<= B` or `flags .
2121
[^zhang2003]: Zhang, Keqi, Shu-Ching Chen, Dean Whitman, Mei-Ling Shyu, Jianhua Yan, and Chengcui Zhang. “A Progressive Morphological Filter for Removing Nonground Measurements from Airborne LIDAR Data.” IEEE Transactions on Geoscience and Remote Sensing 41, no. 4 (2003): 872–82. <https://doi.org/10.1109/TGRS.2003.810682>.
2222
"""
2323
function pmf(A::AbstractMatrix{T};
24-
ωₘ::Float64 = 20.0,
25-
slope::Float64 = 0.01,
26-
dhₘ::Float64 = 2.5,
27-
dh₀::Float64 = 0.2,
28-
cellsize::Float64 = 1.0,
29-
circular = false) where {T<:Real}
24+
ωₘ::Float64=20.0,
25+
slope::Float64=0.01,
26+
dhₘ::Float64=2.5,
27+
dh₀::Float64=0.2,
28+
cellsize::Float64=1.0,
29+
circular=false) where {T<:Real}
3030

3131
# Compute windowsizes and thresholds
3232
ωₘ = round(Int, ωₘ / cellsize)
@@ -37,7 +37,7 @@ function pmf(A::AbstractMatrix{T};
3737
dwindows = vcat(windowsizes[1], windowsizes) # prepend first element so we get 0 as diff
3838
window_diffs = [dwindows[i] - dwindows[i-1] for i = 2:length(dwindows)]
3939
height_tresholds = [min(dhₘ, slope * window_diff * cellsize + dh₀) for window_diff in window_diffs]
40-
@info "Using the following thresholds: $height_tresholds for the following windows: $windowsizes"
40+
# @info "Using the following thresholds: $height_tresholds for the following windows: $windowsizes"
4141

4242
# Set up arrays
4343
Af = copy(A) # array to be morphed

src/psf.jl

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
"""
32
```
43
B, flags = psf(A; ωₘ, slope, dhₘ, dh₀, cellsize)
@@ -19,13 +18,13 @@ Afterwards, one can retrieve the resulting mask for `A` by `A .<= B` or `flags .
1918
- `dh₀::Float64=0.2` Initial elevation threshold [m]
2019
- `cellsize::Float64=1.` Cellsize in [m]
2120
"""
22-
function psf(A::AbstractMatrix{T};
23-
ωₘ::Float64 = 20.0,
24-
slope::Float64 = 0.01,
25-
dhₘ::Float64 = 2.5,
26-
dh₀::Float64 = 0.2,
27-
cellsize::Float64 = 1.0,
28-
circular = false) where {T<:Real}
21+
function apsf(A::AbstractMatrix{T};
22+
ωₘ::Float64=20.0,
23+
slope::Matrix{Float64},
24+
dhₘ::Float64=2.5,
25+
dh₀::Float64=0.2,
26+
cellsize::Float64=1.0,
27+
circular=false) where {T<:Real}
2928

3029
# Compute windowsizes and thresholds
3130
ωₘ = round(Int, ωₘ / cellsize)
@@ -35,8 +34,8 @@ function psf(A::AbstractMatrix{T};
3534
# Compute tresholds
3635
dwindows = vcat(windowsizes[1], windowsizes) # prepend first element so we get 0 as diff
3736
window_diffs = [dwindows[i] - dwindows[i-1] for i = 2:length(dwindows)]
38-
height_tresholds = [min(dhₘ, slope * window_diff * cellsize + dh₀) for window_diff in window_diffs]
39-
@info "Using the following thresholds: $height_tresholds for the following windows: $windowsizes"
37+
# height_tresholds = [min(dhₘ, slope[1] * window_diff * cellsize + dh₀) for window_diff in window_diffs]
38+
# @debug "Using the following thresholds: $height_tresholds for the following windows: $windowsizes"
4039

4140
# Set up arrays
4241
Af = copy(A) # array to be morphed
@@ -49,15 +48,15 @@ function psf(A::AbstractMatrix{T};
4948
flags[nan_mask] .= NaN
5049

5150
mask = falses(size(A))
51+
dhₜ = min.(dhₘ, slope * window_diffs[1] * cellsize .+ dh₀)
5252

5353
# Iterate over window sizes and height tresholds
54-
p = Progress(sum(windowsizes .^ 2))
55-
progress = 0
56-
for (ωₖ, dhₜ) in zip(windowsizes, height_tresholds)
54+
for (i, ωₖ) in enumerate(windowsizes)
55+
dhₜ = min.(dhₘ, slope * window_diffs[i] * cellsize .+ dh₀)
5756
if circular
58-
mapwindowcirc!(minimum_mask, A, ωₖ, Af, Inf)
57+
mapwindowcirc_approx!(minimum_mask, A, ωₖ, Af, Inf)
5958
else
60-
mapwindow!(minimum, A, ωₖ, Af)
59+
mapwindow_stack!(minimum, A, ωₖ, Af)
6160
end
6261
mask .= (A .- Af) .> dhₜ
6362
for I in eachindex(flags)
@@ -66,9 +65,25 @@ function psf(A::AbstractMatrix{T};
6665
end
6766
end
6867
B .= min.(B, Af .+ dhₜ)
69-
progress += ωₖ^2
70-
ProgressMeter.update!(p, progress)
7168
end
7269

73-
B, flags
70+
B, flags, dhₜ
71+
end
72+
73+
74+
75+
function psf(A::AbstractMatrix{T};
76+
ωₘ::Float64=20.0,
77+
slope::Float64=0.01,
78+
dhₘ::Float64=2.5,
79+
dh₀::Float64=0.2,
80+
cellsize::Float64=1.0,
81+
circular=false) where {T<:Real}
82+
return apsf(A;
83+
ωₘ=ωₘ,
84+
slope=fill(slope, size(A)),
85+
dhₘ=dhₘ,
86+
dh₀=dh₀,
87+
cellsize=cellsize,
88+
circular=circular)
7489
end

src/skew.jl

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Skewness balancing as by Bartels (2006)
2+
"""
3+
```
4+
mask = skb(A, mean)
5+
mask = skb(A)
6+
```
7+
Applies skewness balancing by *Bartels e.a (2006)* [^bartels2006] to `A`.
8+
9+
# Output
10+
- `mask::BitMatrix` Maximum allowable values
11+
12+
Afterwards, one can retrieve the resulting mask for `A` by `A .<= B` or `flags .== 0.`.
13+
14+
[^bartels2006]: Bartels, M., Hong Wei, and D.C. Mason. 2006. “DTM Generation from LIDAR Data Using Skewness Balancing.” In 18th International Conference on Pattern Recognition (ICPR’06), 1:566–69. https://doi.org/10/cwk4v2.
15+
"""
16+
function skb(A::AbstractArray{T}, mean::T) where {T<:Real}
17+
I = sortperm(vec(A))
18+
AA = A[I]
19+
s = 1
20+
for i length(A):-1:1
21+
X = skewness(AA[begin:i], mean)
22+
if X <= 0
23+
s = i + 1
24+
break
25+
end
26+
end
27+
m = trues(size(A))
28+
m[I[s+1:end]] .= false
29+
return m
30+
end
31+
32+
function skb(A::AbstractArray{T}) where {T<:Real}
33+
return skb(A, mean(A))
34+
end

0 commit comments

Comments
 (0)