Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into update-dev-container
Browse files Browse the repository at this point in the history
  • Loading branch information
hollandjg committed Sep 27, 2024
2 parents 77dd14a + adcf649 commit ac63f7f
Show file tree
Hide file tree
Showing 16 changed files with 748 additions and 123 deletions.
18 changes: 10 additions & 8 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,23 @@ jobs:
strategy:
fail-fast: false
matrix:
version:
- '1.9'
os:
- ubuntu-latest
arch:
- x64
version: ['1.9', '1.10', '~1.11.0-0']
os: [ubuntu-latest, macos-latest]
arch: [x64, arm64]
exclude:
- os: ubuntu-latest
arch: arm64
- os: macos-latest
arch: x64
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10'
python-version: '3.11'
- name: update pip
run: python -m pip install -U pip
- name: install python deps
run: python -m pip install -U numpy==1.23 scikit-image==0.20.0 pyproj==3.6.0 rasterio==1.3.7 requests==2.31.0 skyfield==1.45.0 pandas==2 jinja2==3.1
run: python -m pip install -U -r requirements.txt
- uses: julia-actions/setup-julia@v2
with:
version: ${{ matrix.version }}
Expand Down
8 changes: 8 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
jinja2==3.1
numpy==1.23.2
pandas==2.1
pyproj==3.6.0
rasterio==1.3.7
scikit-image==0.20.0
requests==2.31.0
skyfield==1.45.0
64 changes: 49 additions & 15 deletions src/IceFloeTracker.jl
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
module IceFloeTracker
using Images
using DelimitedFiles: readdlm, writedlm
using Clustering
using DSP
using DataFrames
using Dates
using DelimitedFiles: readdlm, writedlm
using ImageContrastAdjustment
using ImageSegmentation
using Images
using Interpolations
using OffsetArrays: centered
using Peaks
using Pkg
using PyCall
using Random
using Serialization: deserialize, serialize
using StatsBase
using Interpolations
using DataFrames
using PyCall
using Clustering
using DSP
using StaticArrays
using OffsetArrays: centered
using Serialization: serialize, deserialize
using StatsBase
using TiledIteration
using TOML

export readdlm,
Expand Down Expand Up @@ -66,8 +68,12 @@ include("hbreak.jl")
include("bridge.jl")
include("branch.jl")
include("special_strels.jl")
include("tilingutils.jl")
include("histogram_equalization.jl")


const sk_measure = PyNULL()
const sk_exposure = PyNULL()
const getlatlon = PyNULL()

function get_version_from_toml(pth=dirname(dirname(pathof(IceFloeTracker))))::VersionNumber
Expand All @@ -77,14 +83,42 @@ end

const IFTVERSION = get_version_from_toml()

function parse_requirements(file_path)
requirements = Dict{String,String}()
open(file_path, "r") do f
for line in eachline(f)
if occursin("==", line)
pkg, version = split(line, "==")
elseif occursin("=", line)
pkg, version = split(line, "=")
else
pkg, version = line, ""
end
requirements[pkg] = version
end
end
return requirements
end

function __init__()
pyimport_conda("numpy", "numpy=1.23")
pyimport_conda("pyproj", "pyproj=3.6.0")
pyimport_conda("rasterio", "rasterio=1.3.7")
pyimport_conda("jinja2", "jinja2=3.1.2")
pyimport_conda("pandas", "pandas=2")

deps = parse_requirements(joinpath(dirname(@__DIR__), "requirements.txt"))

for (pkg, version) in deps
if pkg == "scikit-image"
_modules = ["measure", "exposure"]
for _module in _modules
imported_module = pyimport_conda("skimage.$_module", "$(pkg)=$(version)")
reference_module = eval(Symbol("sk_$_module"))
copy!(reference_module, imported_module)
end

else
pyimport_conda(pkg, "$(pkg)=$(version)")
end
end

@pyinclude(joinpath(@__DIR__, "latlon.py"))
copy!(sk_measure, pyimport_conda("skimage.measure", "scikit-image=0.20.0"))
copy!(getlatlon, py"getlatlon")
return nothing
end
Expand Down
44 changes: 18 additions & 26 deletions src/anisotropic_image_diffusion.jl
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
# Anisotropic Image Diffusion ##
# This script is borrowed from https://github.com/Red-Portal/UltrasoundDesignGallery.jl ##
## MIT license with permission to use

macro swap!(a::Symbol, b::Symbol)
blk = quote
c = $(esc(a))
$(esc(a)) = $(esc(b))
$(esc(b)) = c
end
return blk
end
# Anisotropic Image Diffusion
# Adapted from https://github.com/Red-Portal/UltrasoundDesignGallery.jl
## MIT license with permission to use

function pmad_kernel!(image, output, g, λ)
M = size(image, 1)
N = size(image, 2)
M, N = size(image)

@inbounds for j in 1:N
@simd for i in 1:M
Expand All @@ -38,34 +28,36 @@ function pmad_kernel!(image, output, g, λ)
end
end

function invert_color(color::RGB{Float64})
function invert_color(color::RGB{T}) where {T<:AbstractFloat}
return RGB(1.0 / color.r, 1.0 / color.g, 1.0 / color.b)
end
function invert_color(color::Gray{Float64})

function invert_color(color::Gray{T}) where {T<:AbstractFloat}
return Gray(1.0 / color.val)
end

function diffusion(
image::Matrix{T}, λ::Float64, K::Int, niters::Int
) where {T<:Color{Float64}}
#=
Perona, Pietro, and Jitendra Malik.
"Scale-space and edge detection using anisotropic diffusion."
Perona, Pietro, and Jitendra Malik.
"Scale-space and edge detection using anisotropic diffusion."
IEEE Transactions on Pattern Analysis and Machine Intelligence (PAMI), 1990.
=#
if !(0 <= λ && λ <= 0.25)
error("Lambda must be between zero and 0.25")
end
!(0 <= λ <= 0.25) && throw(ArgumentError("Lambda must be between zero and 0.25"))
!(K > 0) && throw(ArgumentError("K must be greater than zero"))
!(niters > 0) && throw(ArgumentError("Number of iterations must be greater than zero"))

@inline function g(norm∇I)
coef = (norm∇I / K)
denom = (T(1) .+ coef coef)
coef = norm∇I / K
denom = T(1) .+ coef coef
return invert_color(denom)
end

output = deepcopy(image)
image = deepcopy(image)
for i in 1:niters
for _ in 1:niters
pmad_kernel!(image, output, g, λ)
@swap!(image, output)
image, output = output, image
end
return output
end
93 changes: 68 additions & 25 deletions src/cloudmask.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,58 @@
function convert_to_255_matrix(img)::Matrix{Int}
img_clamped = clamp.(img, 0.0, 1.0)
return round.(Int, img_clamped * 255)
end

function _get_masks(
false_color_image::Matrix{RGB{Float64}};
prelim_threshold::Float64=Float64(110 / 255),
band_7_threshold::Float64=Float64(200 / 255),
band_2_threshold::Float64=Float64(190 / 255),
ratio_lower::Float64=0.0,
ratio_upper::Float64=0.75,
use_uint8::Bool=false,
)::Tuple{BitMatrix,BitMatrix}

ref_view = channelview(false_color_image)
false_color_image_b7 = @view ref_view[1, :, :]
if use_uint8
false_color_image_b7 = convert_to_255_matrix(false_color_image_b7)
end

clouds_view = false_color_image_b7 .> prelim_threshold
mask_b7 = false_color_image_b7 .< band_7_threshold
mask_b2 = @view(ref_view[2, :, :])
if use_uint8
mask_b2 = convert_to_255_matrix(mask_b2)
end
mask_b2 = mask_b2 .> band_2_threshold

# First find all the pixels that meet threshold logic in band 7 (channel 1) and band 2 (channel 2)
# Masking clouds and discriminating cloud-ice

mask_b7b2 = mask_b7 .&& mask_b2

# Next find pixels that meet both thresholds and mask them from band 7 (channel 1) and band 2 (channel 2)
b7_masked = mask_b7b2 .* false_color_image_b7

_b2 = @view(ref_view[2, :, :])
b2_masked = use_uint8 ? convert_to_255_matrix(_b2) : _b2
b2_masked = mask_b7b2 .* b2_masked

cloud_ice = Float64.(b7_masked) ./ Float64.(b2_masked)
mask_cloud_ice = @. (cloud_ice >= ratio_lower) && (cloud_ice < ratio_upper)

return mask_cloud_ice, clouds_view
end


"""
create_cloudmask(falsecolor_image; prelim_threshold, band_7_threshold, band_2_threshold, ratio_lower, ratio_upper)
create_cloudmask(false_color_image; prelim_threshold, band_7_threshold, band_2_threshold, ratio_lower, ratio_upper)
Convert a 3-channel false color reflectance image to a 1-channel binary matrix; clouds = 0, else = 1. Default thresholds are defined in the published Ice Floe Tracker article: Remote Sensing of the Environment 234 (2019) 111406.
# Arguments
- `falsecolor_image`: corrected reflectance false color image - bands [7,2,1]
- `false_color_image`: corrected reflectance false color image - bands [7,2,1]
- `prelim_threshold`: threshold value used to identify clouds in band 7, N0f8(RGB intensity/255)
- `band_7_threshold`: threshold value used to identify cloud-ice in band 7, N0f8(RGB intensity/255)
- `band_2_threshold`: threshold value used to identify cloud-ice in band 2, N0f8(RGB intensity/255)
Expand All @@ -13,47 +61,41 @@ Convert a 3-channel false color reflectance image to a 1-channel binary matrix;
"""
function create_cloudmask(
ref_image::Matrix{RGB{Float64}};
false_color_image::Matrix{RGB{Float64}};
prelim_threshold::Float64=Float64(110 / 255),
band_7_threshold::Float64=Float64(200 / 255),
band_2_threshold::Float64=Float64(190 / 255),
ratio_lower::Float64=0.0,
ratio_upper::Float64=0.75,
)::BitMatrix
# Setting thresholds
ref_view = channelview(ref_image)
ref_image_b7 = @view ref_view[1, :, :]
clouds_view = ref_image_b7 .> prelim_threshold
mask_b7 = ref_image_b7 .< band_7_threshold
mask_b2 = @view(ref_view[2, :, :]) .> band_2_threshold
# First find all the pixels that meet threshold logic in band 7 (channel 1) and band 2 (channel 2)
# Masking clouds and discriminating cloud-ice
mask_cloud_ice, clouds_view = _get_masks(
false_color_image,
prelim_threshold=prelim_threshold,
band_7_threshold=band_7_threshold,
band_2_threshold=band_2_threshold,
ratio_lower=ratio_lower,
ratio_upper=ratio_upper,
)

mask_b7b2 = mask_b7 .&& mask_b2
# Next find pixels that meet both thresholds and mask them from band 7 (channel 1) and band 2 (channel 2)
b7_masked = mask_b7b2 .* ref_image_b7
b2_masked = mask_b7b2 .* @view(ref_view[2, :, :])
cloud_ice = Float64.(b7_masked) ./ Float64.(b2_masked)
mask_cloud_ice = @. cloud_ice >= ratio_lower && cloud_ice < ratio_upper
# Creating final cloudmask
cloudmask = mask_cloud_ice .|| .!clouds_view
return cloudmask
end

"""
apply_cloudmask(ref_image, cloudmask)
apply_cloudmask(false_color_image, cloudmask)
Zero out pixels containing clouds where clouds and ice are not discernable. Arguments should be of the same size.
# Arguments
- `ref_image`: reference image, e.g. corrected reflectance false color image bands [7,2,1] or grayscale
- `false_color_image`: reference image, e.g. corrected reflectance false color image bands [7,2,1] or grayscale
- `cloudmask`: binary cloudmask with clouds = 0, else = 1
"""
function apply_cloudmask(
ref_image::Matrix{RGB{Float64}}, cloudmask::AbstractArray{Bool}
false_color_image::Matrix{RGB{Float64}}, cloudmask::AbstractArray{Bool}
)::Matrix{RGB{Float64}}
masked_image = cloudmask .* ref_image
masked_image = cloudmask .* false_color_image
image_view = channelview(masked_image)
cloudmasked_view = StackedView(
zeroarray, @view(image_view[2, :, :]), @view(image_view[3, :, :])
Expand All @@ -63,13 +105,14 @@ function apply_cloudmask(
end

function apply_cloudmask(
ref_image::Matrix{Gray{Float64}}, cloudmask::AbstractArray{Bool}
false_color_image::Matrix{Gray{Float64}}, cloudmask::AbstractArray{Bool}
)::Matrix{Gray{Float64}}
return Gray.(cloudmask .* ref_image)
return Gray.(cloudmask .* false_color_image)
end

function create_clouds_channel(
cloudmask::AbstractArray{Bool}, ref_image::Matrix{RGB{Float64}}
cloudmask::AbstractArray{Bool}, false_color_image::Matrix{RGB{Float64}}
)::Matrix{Gray{Float64}}
return Gray.(@view(channelview(cloudmask .* ref_image)[1, :, :]))
return Gray.(@view(channelview(cloudmask .* false_color_image)[1, :, :]))
end

Loading

0 comments on commit ac63f7f

Please sign in to comment.