Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: save labeled segmentations rather than binary segmentations #193

Draft
wants to merge 31 commits into
base: jghrefactor/A2f-add-single-file-versions-of-cli-hdf5files
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
69991da
add step to label segmented floes
hollandjg Nov 5, 2024
cc12dfc
simplify test script to move into directory and run simpler commands …
hollandjg Nov 5, 2024
17318bc
add fixed point numbers dependency
hollandjg Nov 6, 2024
caca865
add label to props collected on floes
hollandjg Nov 6, 2024
c9f6f46
load FixedPointNumbers
hollandjg Nov 6, 2024
617dac1
update loading labeled floes
hollandjg Nov 6, 2024
211991f
fix type of Img to default for the dataframe to get around a type err…
hollandjg Nov 6, 2024
1656f56
update example file to use new filename
hollandjg Nov 6, 2024
d815d87
Merge branch 'jghrefactor/A2f-add-single-file-versions-of-cli-hdf5fil…
hollandjg Nov 6, 2024
1597b4a
Update IFTPipeline.jl/src/tracker.jl
hollandjg Nov 6, 2024
3d5f053
Update IFTPipeline.jl/src/preprocess.jl
hollandjg Nov 6, 2024
8bf8a24
add beginnings of functions to save and load labeled segmented images
hollandjg Nov 9, 2024
4d06328
feat: handle saving and loading integer images
hollandjg Nov 11, 2024
9cc594b
remove commented out code
hollandjg Nov 11, 2024
8e24794
add docstrings
hollandjg Nov 11, 2024
d932c5e
use image saving/loading for preprocess, tracker, h5
hollandjg Nov 11, 2024
16d8fa6
recast imgs to Int so they match the type of the labels in the props
hollandjg Nov 11, 2024
5dd478f
add recast to Image type based on Gray subtype
hollandjg Nov 11, 2024
1296d75
add cast to integer type when loading from gray
hollandjg Nov 11, 2024
973a454
convert just the labels rather than the image – more memory efficient
hollandjg Nov 11, 2024
9dadb45
update docs
hollandjg Nov 11, 2024
ce505e8
update extractfeatures_single to use the load_labeled_img function
hollandjg Nov 11, 2024
89bc5fc
remove debug message which ruins the output
hollandjg Nov 11, 2024
9f18d78
remove excess debug messages
hollandjg Nov 11, 2024
1adcbf3
remove label from feature extraction in batch mode
hollandjg Nov 11, 2024
5496c84
add option to extractfeatures to work with bool images
hollandjg Nov 11, 2024
1f9af06
update paths for batch processing test
hollandjg Nov 11, 2024
4ae04f8
don't cd – that breaks the IFT call
hollandjg Nov 11, 2024
753d0fa
simplify test save load definitions
hollandjg Nov 26, 2024
739e294
fix broken function definition
hollandjg Nov 26, 2024
d6b6ca4
Update IFTPipeline.jl/src/feature-extraction.jl
hollandjg Nov 26, 2024
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
2 changes: 2 additions & 0 deletions IFTPipeline.jl/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
CSVFiles = "5d742f6a-9f54-50ce-8119-2520741973ca"
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
FixedPointNumbers = "53c48c17-4a7d-5ca2-90c5-79b7896eea93"
Folds = "41a02a25-b8f0-4f67-bc48-60067656b558"
HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f"
IceFloeTracker = "04643c7a-9ac6-48c5-822f-2704f9e70bd3"
ImageSegmentation = "80713f31-8817-5129-9cf8-209ff8fb23e1"
Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0"
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
LoggingExtras = "e6f89c97-d47a-5376-807f-9c37f3926c36"
Expand Down
6 changes: 6 additions & 0 deletions IFTPipeline.jl/src/IFTPipeline.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ using Pkg
using FileIO
using Images
using CSV
using ImageSegmentation
using FixedPointNumbers

include("cli.jl")
include("soit-parser.jl")
Expand All @@ -37,6 +39,10 @@ export cache_vector, sharpen,
load_truecolor_imgs,
load_falsecolor_imgs,
load_cloudmask,
load_labeled_img,
save_labeled_img,
convert_gray_from_uint,
convert_uint_from_gray,
disc_ice_water,
landmask,
landmask_single,
Expand Down
2 changes: 1 addition & 1 deletion IFTPipeline.jl/src/cli.jl
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ function mkcliextract_single!(settings)
help = """Features to extract. Format: "feature1 feature2". For an extensive list of extractable features see https://scikit-image.org/docs/stable/api/skimage.measure.html#skimage.measure.regionprops:~:text=The%20following%20properties%20can%20be%20accessed%20as%20attributes%20or%20keys"""
nargs = '+'
arg_type = String
default = ["centroid", "area", "major_axis_length", "minor_axis_length", "convex_area", "bbox", "orientation", "perimeter"]
default = ["label", "centroid", "area", "major_axis_length", "minor_axis_length", "convex_area", "bbox", "orientation", "perimeter"]
end
return nothing
end
Expand Down
86 changes: 80 additions & 6 deletions IFTPipeline.jl/src/feature-extraction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,16 +48,18 @@ julia> IFTPipeline.extractfeatures(bw_img; minarea=minarea, maxarea=maxarea, fea
```
"""
function extractfeatures(
bw::T;
floes::AbstractArray{<:Union{Integer,Bool}};
minarea::Int64=350,
maxarea::Int64=90000,
features::Union{Vector{Symbol},Vector{<:AbstractString}}
)::DataFrame where {T<:AbstractArray{Bool}}
)::DataFrame
# assert the first area threshold is less than the second
minarea >= maxarea &&
throw(ArgumentError("The minimum area must be less than the maximum area."))

props = regionprops_table(label_components(bw, trues(3, 3)); properties=features)
floes = isa(eltype(floes), Bool) ? label_components(floes, trues(3, 3)) : floes
props = regionprops_table(floes; properties=features)
@debug "loaded $props"

# filter by area using the area thresholds
return props[minarea.<=props.area.<=maxarea, :]
Expand Down Expand Up @@ -94,11 +96,11 @@ end
function extractfeatures_single(;
input::String, output::String, minarea::Int64, maxarea::Int64, features::Array{String}
)
@info "Loading segmented floes as a binarized image from $input"
segmented_floes = BitMatrix(FileIO.load(input))
@info "Loading segmented floes from $input"
labeled_floes = Int.(load_labeled_img(input))

@info "Extracting features from each floe: $features"
props = IFTPipeline.extractfeatures(segmented_floes; minarea=minarea, maxarea=maxarea, features=features)
props = IFTPipeline.extractfeatures(labeled_floes; minarea=minarea, maxarea=maxarea, features=features)

@info "Extracted properties:"
@info props
Expand All @@ -107,3 +109,75 @@ function extractfeatures_single(;
FileIO.save(output, props)
return nothing
end

"""
load_labeled_img(path)

Load an unsigned integer image from a file.

See also: save_labeled_img
"""
function load_labeled_img(path::AbstractString)
image = FileIO.load(path)
image_reinterpreted = convert_uint_from_gray(image)
return image_reinterpreted
end

"""
save_labeled_img(image, path)

Save an unsigned integer image to an image file.

See also: load_labeled_img
"""
function save_labeled_img(image::AbstractArray{T} where {T <: Union{UInt8, UInt16, UInt32, UInt64}}, path::AbstractString)
image_reinterpreted = convert_gray_from_uint(image)
FileIO.save(path, image_reinterpreted)
return path
end

"""
convert_gray_from_uint(image)

Convert an image from an unsigned integer format into a fixed-point Gray format.

See also: convert_uint_from_gray
"""

function convert_gray_from_uint(image::AbstractArray{T} where {T <: Union{UInt8, UInt16, UInt32, UInt64}})
if eltype(image) === UInt8
target_type = N0f8
elseif eltype(image) === UInt16
target_type = N0f16
elseif eltype(image) === UInt32
target_type = N0f32
elseif eltype(image) === UInt64
target_type = N0f64
end
image_reinterpreted = Gray.(reinterpret.(target_type, image))
return image_reinterpreted
end
hollandjg marked this conversation as resolved.
Show resolved Hide resolved

"""
convert_uint_from_gray(image)

Convert an image from a fixed-point Gray format into unsigned integers.

See also: convert_gray_from_uint
"""
function convert_uint_from_gray(image)
image_reinterpreted = rawview(channelview(image))
element_type = eltype(image)
@info "$element_type"
if eltype(image) === Gray{N0f8}
target_type = UInt8
elseif eltype(image) === Gray{N0f16}
target_type = UInt16
elseif eltype(image) === Gray{N0f32}
target_type = UInt32
elseif eltype(image) === Gray{N0f64}
target_type = UInt64
end
image_recast = target_type.(image_reinterpreted)
return image_recast
end
2 changes: 1 addition & 1 deletion IFTPipeline.jl/src/h5.jl
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ end
function makeh5files_single(; passtime::DateTime, iftversion::Union{String, Nothing}=nothing, truecolor::String, falsecolor::String, labeled::String, props::String, output::String)
latlondata = getlatlon(truecolor)
ptsunix = Int64(Dates.datetime2unix(passtime))
labeled_ = Integer.(rawview(channelview((FileIO.load(labeled)))))
labeled_ = load_labeled_img(labeled)

if isnothing(iftversion)
iftversion = string(IceFloeTracker.IFTVERSION)
Expand Down
10 changes: 7 additions & 3 deletions IFTPipeline.jl/src/preprocess.jl
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,16 @@ function preprocess_single(; truecolor::T, falsecolor::T, landmask::T, landmask_
rgb_truecolor_img = RGB.(truecolor_img)
rgb_falsecolor_img = RGB.(falsecolor_img)

@info "Segmenting floes started"
@info "Segmenting floes"
segmented_floes = preprocess(rgb_truecolor_img, rgb_falsecolor_img, landmask)
@info "Segmenting floes complete"

@info "Labeling floes"
labeled_floes = label_components(segmented_floes)
_dtype = choose_dtype(maximum(labeled_floes))
labeled_floes_cast = convert(Array{_dtype}, labeled_floes)

@info "Writing segmented floes to $output"
FileIO.save(output, segmented_floes)
save_labeled_img(labeled_floes_cast, output)

return nothing
end
11 changes: 9 additions & 2 deletions IFTPipeline.jl/src/tracker.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,17 @@ function track_single(;

# Load the files – can we drop the memory requirements by doing two at once?
@info "Loading $imgs"
imgs_ = [Integer.(rawview(channelview((FileIO.load(img))))) for img in imgs]

imgs_ = [load_labeled_img(img) for img in imgs]
@info "Loading $props"
props_ = [DataFrame(CSV.File(prop)) for prop in props]
# go through each of the props_ dataframes and convert each
# into the element type from the corresponding image.
for (img_, prop_) in zip(imgs_, props_)
label_type = eltype(img_)
@debug "converting labels to $label_type"
prop_[!,:label] = convert.(label_type, prop_[!,:label])
end
Comment on lines +92 to +102
Copy link
Member

@cpaniaguam cpaniaguam Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This suggests you might not be using a formatter. We've been using blue for Julia files.

# .JuliaFormatter.toml
style = "blue"
indent = 4

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added this to #197

@info "Loaded: $props_"

@info "Using passtimes=$passtimes"
Expand Down
41 changes: 41 additions & 0 deletions IFTPipeline.jl/test/test-feature-extract.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Images
using IFTPipeline: load_labeled_img, save_labeled_img

function test_save_load(image::AbstractArray{T} where T <: Union{UInt8, UInt16, UInt32, UInt64}; extension::AbstractString=".tiff")

filename = tempname() * extension

saved_image = save_labeled_img(image, filename)
loaded_image = load_labeled_img(saved_image)

@test isequal(image, loaded_image)
end


function test_cast_uncast(image::AbstractArray{T} where T <: Union{UInt8, UInt16, UInt32, UInt64})

casted_image = convert_gray_from_uint(image)
uncasted_image = convert_uint_from_gray(casted_image)

@test isequal(image, uncasted_image)
@test isequal(eltype(image), eltype(uncasted_image))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@test isequal(eltype(image), eltype(uncasted_image))
@test isequal(T, eltype(uncasted_image))

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that change helps the reader understand the meaning of the test. The test as it is right now says "the eltypes of the images should be the same" whereas you're suggesting something which I'd read as "the eltype of the uncasted image should be the same as the element type of the argument to the function we're in" – which although being essentially the same thing, makes you have to think one level more abstractly when parsing the test.

end


@testset "feature-extraction.jl" begin
image_size = (8, 8)

# Can cast and uncast all sizes of integer
_test_cast_uncast(t) = test_cast_uncast(rand(t, image_size))
_test_cast_uncast(UInt8)
_test_cast_uncast(UInt16)
_test_cast_uncast(UInt32)
_test_cast_uncast(UInt64)

# Can load all sizes of fixed point integer:
_test_save_load(t) = test_save_load(rand(t, image_size))
_test_save_load(UInt8)
_test_save_load(UInt16)
_test_save_load(UInt32)
_test_save_load(UInt64)
end
52 changes: 37 additions & 15 deletions test/test-IFTPipeline.jl-cli.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
# Refs:
# - https://stackoverflow.com/a/28085062/24937841 and
# - https://unix.stackexchange.com/a/31712
: "${IFT:=julia --project=../IFTPipeline.jl ../IFTPipeline.jl/src/cli.jl}"
: "${IFT:=julia --project=`pwd`/../IFTPipeline.jl `pwd`/../IFTPipeline.jl/src/cli.jl}"
echo "IFT=${IFT}"

# Data target
Expand All @@ -21,7 +21,9 @@ echo "DATA_TARGET=${DATA_TARGET}"
echo "DATA_SOURCE=${DATA_SOURCE}"

cp -r ${DATA_SOURCE}/* ${DATA_TARGET}/
SAMPLEIMG=${DATA_TARGET}/20220914.terra.truecolor.250m.tiff
echo "in $(pwd)"

SAMPLEIMG=20220914.terra.truecolor.250m.tiff

# Set up debug messages
export JULIA_DEBUG="Main,IFTPipeline,IceFloeTracker"
Expand All @@ -30,28 +32,48 @@ export JULIA_DEBUG="Main,IFTPipeline,IceFloeTracker"
LANDMASK=${DATA_TARGET}/landmask.tiff
LANDMASK_NON_DILATED=${DATA_TARGET}/landmask.non-dilated.tiff
LANDMASK_DILATED=${DATA_TARGET}/landmask.dilated.tiff

${IFT} landmask_single -i ${LANDMASK} -o ${LANDMASK_NON_DILATED} -d ${LANDMASK_DILATED}

for satellite in "aqua" "terra"
do
TRUECOLOR=${DATA_TARGET}/20220914.${satellite}.truecolor.250m.tiff
FALSECOLOR=${DATA_TARGET}/20220914.${satellite}.falsecolor.250m.tiff
SEGMENTED=${DATA_TARGET}/20220914.${satellite}.segmented.250m.tiff
FLOEPROPERTIES=${DATA_TARGET}/20220914.${satellite}.segmented.250m.props.csv
LABELED=${DATA_TARGET}/20220914.${satellite}.labeled.250m.tiff
FLOEPROPERTIES=${DATA_TARGET}/20220914.${satellite}.labeled.250m.props.csv
HDF5FILE=${DATA_TARGET}/20220914.${satellite}.h5
${IFT} preprocess_single --truecolor ${TRUECOLOR} --falsecolor ${FALSECOLOR} --landmask ${LANDMASK_NON_DILATED} --landmask-dilated ${LANDMASK_DILATED} --output ${SEGMENTED}
${IFT} extractfeatures_single --input ${SEGMENTED} --output ${FLOEPROPERTIES}
${IFT} makeh5files_single --passtime "2022-09-14T12:00:00" --truecolor ${TRUECOLOR} --falsecolor ${FALSECOLOR} --labeled ${SEGMENTED} --props ${FLOEPROPERTIES} --output ${HDF5FILE}

${IFT} preprocess_single \
--truecolor ${TRUECOLOR} \
--falsecolor ${FALSECOLOR} \
--landmask ${LANDMASK_NON_DILATED} \
--landmask-dilated ${LANDMASK_DILATED} \
--output ${LABELED}

${IFT} extractfeatures_single \
--input ${LABELED} \
--output ${FLOEPROPERTIES}

${IFT} makeh5files_single \
--passtime "2022-09-14T12:00:00" \
--truecolor ${TRUECOLOR} \
--falsecolor ${FALSECOLOR} \
--labeled ${LABELED} \
--props ${FLOEPROPERTIES} \
--output ${HDF5FILE}
done

${IFT} track_single --imgs ${DATA_TARGET}/20220914.{aqua,terra}.segmented.250m.tiff --props ${DATA_TARGET}/20220914.{aqua,terra}.segmented.250m.props.csv --latlon ${TRUECOLOR} --passtimes "2022-09-14T12:00:00" "2022-09-15T12:00:00" --output ${DATA_TARGET}/paired-floes.csv


${IFT} track_single \
--imgs ${DATA_TARGET}/20220914.{aqua,terra}.labeled.250m.tiff \
--props ${DATA_TARGET}/20220914.{aqua,terra}.labeled.250m.props.csv \
--latlon ${TRUECOLOR} \
--passtimes "2022-09-14T12:00:00" "2022-09-15T12:00:00" \
--output ${DATA_TARGET}/paired-floes.csv

# Run the processing (batch)
${IFT} landmask ${DATA_TARGET} ${DATA_TARGET}
${IFT} preprocess -t ${DATA_TARGET} -r ${DATA_TARGET} -l ${DATA_TARGET} -p ${DATA_TARGET} -o ${DATA_TARGET}
${IFT} extractfeatures -i ${DATA_TARGET} -o ${DATA_TARGET}
${IFT} track --imgs ${DATA_TARGET} --props ${DATA_TARGET} --passtimes ${DATA_TARGET} --latlon ${SAMPLEIMG} -o ${DATA_TARGET}
${IFT} makeh5files --pathtosampleimg ${SAMPLEIMG} --resdir ${DATA_TARGET}
${IFT} landmask . .
${IFT} preprocess -t . -r . -l . -p . -o .
${IFT} extractfeatures -i . -o .
${IFT} track --imgs . --props . --passtimes . --latlon ${SAMPLEIMG} -o .
${IFT} makeh5files --pathtosampleimg ${SAMPLEIMG} --resdir .

Loading