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

Support labeled segmented images #458

Merged
merged 39 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d456e19
use julia via feature rather than devcontainer
hollandjg Jul 24, 2024
19071aa
remove nonexistent post-create script
hollandjg Sep 5, 2024
a2ae16d
make building the conda environment work by unpinning python deps
hollandjg Sep 5, 2024
2c539d7
add notes on test_results
hollandjg Sep 5, 2024
4f90d1e
remove extra newlines
hollandjg Sep 5, 2024
bdadf25
Add xsucces note
hollandjg Sep 5, 2024
fadb3e5
Merge remote-tracking branch 'upstream/main' into devcontainer-unpin-…
hollandjg Sep 11, 2024
4b258b2
add additional cropfloes functions to handle labeled images
hollandjg Sep 20, 2024
f277cd4
update types for pairfloes to pass tests
hollandjg Sep 20, 2024
647c787
fix sort function - wasn't working before
hollandjg Sep 20, 2024
201b390
fix image types for cropfloes
hollandjg Sep 20, 2024
9ee4833
add support for float centroids in convertcentroid
hollandjg Sep 24, 2024
52efada
add support for unsigned integer image types in boundary trace
hollandjg Sep 24, 2024
84dba7d
remove unnecessary casting
hollandjg Sep 24, 2024
5dc27cf
update image type
hollandjg Sep 24, 2024
a912c08
chore: remove unexpected change
hollandjg Oct 23, 2024
8500c12
fix extra spaces
hollandjg Oct 23, 2024
03570f7
remove unneeded change to readme
hollandjg Oct 23, 2024
fd7a4e7
remove unnecessary file
hollandjg Oct 23, 2024
fa933bc
rename FloeLabelsIntOrBool from floesimgvectortype
hollandjg Oct 23, 2024
cc98ca8
add missing filename
hollandjg Oct 23, 2024
ef4134b
simplify type description for FloeLabelsIntOrBool
hollandjg Oct 23, 2024
3c85362
make types more specific
hollandjg Oct 23, 2024
14135bc
rename FloeLabelsImage from FloeLabelsIntOrBool
hollandjg Oct 23, 2024
789bf1e
Merge branch 'main' into support-labeled-segmented-images
hollandjg Oct 23, 2024
cf3c52e
reorder functions to minimize the size of the diff
hollandjg Oct 23, 2024
f563e44
simplify type for segmented_imgs
hollandjg Oct 23, 2024
1aec770
remove info print
hollandjg Oct 23, 2024
e816831
fix weird change in find_reflectance_peaks
hollandjg Oct 23, 2024
48ac134
remove extra debugging call
hollandjg Oct 23, 2024
939866c
revert changes to devcontainer
hollandjg Oct 23, 2024
884a1e8
Update test/test-regionprops-labeled.jl
hollandjg Oct 23, 2024
8441473
update type of ints
hollandjg Oct 23, 2024
d8600da
simplify cropfloe nested ifs
hollandjg Oct 24, 2024
63fa9cd
deduplicate label definitions by using lists of variables
hollandjg Oct 24, 2024
0ca3c59
simplify and make more robust using Sets
hollandjg Oct 24, 2024
8dc5597
remove repetition in testing code
hollandjg Oct 24, 2024
aaef1e0
merge cases list into constructor
hollandjg Oct 28, 2024
180c8bf
remove ability to crop floes without bounding boxes
hollandjg Nov 4, 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
1 change: 1 addition & 0 deletions src/IceFloeTracker.jl
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export readdlm,
imsharpen,
label_components,
regionprops_table,
cropfloe,
loadimg,
matchcorr,
centered,
Expand Down
2 changes: 1 addition & 1 deletion src/bwtraceboundary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ julia> boundary[3]
```
"""
function bwtraceboundary(
image::Union{Matrix{Int64},Matrix{Float64},T};
image::Union{Matrix{UInt8},Matrix{Int64},Matrix{Float64},T};
P0::Union{Tuple{Int,Int},CartesianIndex{2},Nothing}=nothing,
closed::Bool=true,
) where {T<:AbstractArray{Bool,2}}
cpaniaguam marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
80 changes: 76 additions & 4 deletions src/regionprops.jl
Original file line number Diff line number Diff line change
Expand Up @@ -180,16 +180,65 @@ function getbboxcolumns(props::DataFrame)
return filter(col -> occursin(r"^bbox-\d$", col), names(props))
end


FloeLabelsImage = Union{BitMatrix, Matrix{<:Bool}, Matrix{<:Integer}}

"""
cropfloe(floesimg, props, i)

Crops the floe delimited by the bounding box data in `props` at index `i` from the floe image `floesimg`.

If the dataframe has bounding box data `min_row`, `min_col`, `max_row`, `max_col`, but no `label`, then returns the largest contiguous component.

If the dataframe has bounding box data `min_row`, `min_col`, `max_row`, `max_col`, and a `label`, then returns the component with the label. In this case, `floesimg` must be an Array{Int}.

If the dataframe has only a `label` and no bounding box data, then returns the component with the label, padded by one cell of zeroes on all sides. In this case, `floesimg` must be an Array{Int}.


"""
function cropfloe(floesimg::FloeLabelsImage, props::DataFrame, i::Integer)
props_row = props[i, :]
colnames = Set(names(props_row))
bbox_column_names = Set(["min_row", "min_col", "max_row", "max_col"])
label_column_names = Set(["label"])
bbox_label_column_names = union(bbox_column_names, label_column_names)

if issubset(bbox_label_column_names, colnames)
return cropfloe(
floesimg,
props_row.min_row,
props_row.min_col,
props_row.max_row,
props_row.max_col,
props_row.label
)

elseif issubset(bbox_column_names, colnames)
floesimg_bitmatrix = floesimg .> 0
return cropfloe(
floesimg_bitmatrix,
props_row.min_row,
props_row.min_col,
props_row.max_row,
props_row.max_col
)

elseif issubset(label_column_names, colnames)
return cropfloe(floesimg, props_row.label)

end
end

"""
cropfloe(floesimg, min_row, min_col, max_row, max_col)

Crops the floe delimited by `min_row`, `min_col`, `max_row`, `max_col`, from the floe image `floesimg`.
"""
function cropfloe(floesimg::BitMatrix, props::DataFrame, i::Int64)
function cropfloe(floesimg::BitMatrix, min_row::I, min_col::I, max_row::I, max_col::I) where {I<:Integer}
#=
Crop the floe using bounding box data in props.
Note: Using a view of the cropped floe was considered but if there were multiple components in the cropped floe, the source array with the floes would be modified. =#
prefloe = floesimg[props.min_row[i]:props.max_row[i], props.min_col[i]:props.max_col[i]]
prefloe = floesimg[min_row:max_row, min_col:max_col]

#= Check if more than one component is present in the cropped image.
If so, keep only the largest component by removing all on pixels not in the largest component =#
Expand All @@ -202,12 +251,35 @@ function cropfloe(floesimg::BitMatrix, props::DataFrame, i::Int64)
return prefloe
end

"""
cropfloe(floesimg, min_row, min_col, max_row, max_col, label)

Crops the floe from `floesimg` with the label `label`, returning the region bounded by `min_row`, `min_col`, `max_row`, `max_col`, and converting to a BitMatrix.
"""
function cropfloe(floesimg::Matrix{I}, min_row::J, min_col::J, max_row::J, max_col::J, label::I) where {I<:Integer, J<:Integer}
cpaniaguam marked this conversation as resolved.
Show resolved Hide resolved
#=
Crop the floe using bounding box data in props.
Note: Using a view of the cropped floe was considered but if there were multiple components in the cropped floe, the source array with the floes would be modified. =#
prefloe = floesimg[min_row:max_row, min_col:max_col]
@debug "prefloe: $prefloe"

#= Remove any pixels not corresponding to that numbered floe
(each segment has a different integer) =#
floe_area = prefloe .== label
@debug "mask: $floe_area"
hollandjg marked this conversation as resolved.
Show resolved Hide resolved

return floe_area
end




"""
addfloearrays(props::DataFrame, floeimg::BitMatrix)

Add a column to `props` called `floearray` containing the cropped floe masks from `floeimg`.
"""
function addfloemasks!(props::DataFrame, floeimg::BitMatrix)
function addfloemasks!(props::DataFrame, floeimg::FloeLabelsImage)
props.mask = getfloemasks(props, floeimg)
return nothing
end
Expand All @@ -217,7 +289,7 @@ end

Return a vector of cropped floe masks from `floeimg` using the bounding box data in `props`.
"""
function getfloemasks(props::DataFrame, floeimg::BitMatrix)
function getfloemasks(props::DataFrame, floeimg::FloeLabelsImage)
return map(i -> cropfloe(floeimg, props, i), 1:nrow(props))
end

Expand Down
6 changes: 3 additions & 3 deletions src/tracker/tracker-funcs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ function addψs!(props::Vector{DataFrame})
return nothing
end

function addfloemasks!(props, imgs)
function addfloemasks!(props::Vector{DataFrame}, imgs::Vector{<:FloeLabelsImage})
for (img, prop) in zip(imgs, props)
IceFloeTracker.addfloemasks!(prop, img)
end
Expand All @@ -507,13 +507,13 @@ Convert the centroid coordinates from row and column to latitude and longitude d
"""
function convertcentroid!(propdf, latlondata, colstodrop)
latitude, longitude = [
[latlondata[c][x, y] for
[latlondata[c][Int(round(x)), Int(round(y))] for
(x, y) in zip(propdf.row_centroid, propdf.col_centroid)] for
c in ["latitude", "longitude"]
]

x, y = [
[latlondata[c][z] for z in V] for
[latlondata[c][Int(round(z))] for z in V] for
(c, V) in zip(["Y", "X"], [propdf.row_centroid, propdf.col_centroid])
]

Expand Down
4 changes: 2 additions & 2 deletions src/tracker/tracker.jl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function sort_floes_by_area!(props)
end

function _pairfloes(
segmented_imgs::Vector{BitMatrix},
segmented_imgs::Vector{<:FloeLabelsImage},
props::Vector{DataFrame},
passtimes::Vector{DateTime},
condition_thresholds,
Expand Down Expand Up @@ -224,7 +224,7 @@ Returns a dataframe containing the following columns:
- `corr`: psi-s shape correlation between the two floes in row_i and row_i+1
"""
function pairfloes(
segmented_imgs::Vector{BitMatrix},
segmented_imgs::Vector{<:FloeLabelsImage},
props::Vector{DataFrame},
passtimes::Vector{DateTime},
latlonrefimage::AbstractString,
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ to_test = alltests # uncomment this line to run all tests or add individual file
# "test-bwtraceboundary.jl",
# "test-resample-boundary.jl",
# "test-regionprops.jl",
# "test-regionprops-labeled.jl",
# "test-psi-s.jl",
# "test-crosscorr.jl"
# "test-bwperim.jl",
Expand Down
170 changes: 170 additions & 0 deletions test/test-regionprops-labeled.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
@testset "regionprops-labeled.jl" begin
println("------------------------------------------------------------")
println("-------------- regionprops (labeled) Tests ----------------")

img1 = Int[
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 # row 1
0 1 1 0 2 2 2 0 3 3 3 3 0 0 0 0 # 2
0 1 1 0 2 2 2 0 3 3 3 3 0 0 0 0 # 3
0 0 0 0 0 2 2 0 3 3 3 3 0 0 0 0 # 4
0 0 0 0 0 0 0 0 3 3 3 0 0 0 0 0 # 5
0 4 4 4 4 5 0 0 0 0 0 0 0 0 0 0 # 6
0 4 5 5 5 5 0 0 0 0 0 0 0 0 0 0 # 7
0 4 5 5 0 0 0 0 0 0 0 0 0 0 0 0 # 8
0 4 4 4 4 4 0 0 0 0 0 0 0 0 0 0 # 9
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 # 10
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 # 11
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 # 12
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 # 13
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 # 14
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 # 15
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 # 16
#= col
1 2 3 4 5 6 7 8 9 a b c d e f g
=#

]

@info "testing props with label"
props_with_label = DataFrame([
1 1 1 4 4
2 1 4 5 8
3 1 8 6 13
4 5 1 10 7
5 5 1 10 7
], [:label, :min_row, :min_col, :max_row, :max_col])


@test cropfloe(img1, props_with_label, 1) == [
0 0 0 0
0 1 1 0
0 1 1 0
0 0 0 0
]

@test cropfloe(img1, props_with_label, 2) == [
0 0 0 0 0
0 1 1 1 0
0 1 1 1 0
0 0 1 1 0
0 0 0 0 0
]

@test cropfloe(img1, props_with_label, 3) == [
0 0 0 0 0 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 1 0 0
0 0 0 0 0 0
]

@test cropfloe(img1, props_with_label, 4) == [
0 0 0 0 0 0 0
0 1 1 1 1 0 0
0 1 0 0 0 0 0
0 1 0 0 0 0 0
0 1 1 1 1 1 0
0 0 0 0 0 0 0
]

@test cropfloe(img1, props_with_label, 5) == [
0 0 0 0 0 0 0
0 0 0 0 0 1 0
0 0 1 1 1 1 0
0 0 1 1 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
]

@info "testing props without label"
props_without_label = DataFrame([
1 1 4 4
1 4 5 8
1 8 6 13
5 1 10 7
5 1 10 7
], [:min_row, :min_col, :max_row, :max_col])

@test cropfloe(img1, props_without_label, 1) == [
0 0 0 0
0 1 1 0
0 1 1 0
0 0 0 0
]

@test cropfloe(img1, props_without_label, 2) == [
0 0 0 0 0
0 1 1 1 0
0 1 1 1 0
0 0 1 1 0
0 0 0 0 0
]

@test cropfloe(img1, props_without_label, 3) == [
0 0 0 0 0 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 1 0 0
0 0 0 0 0 0
]

# This can't distinguish between the two interlocking regions, so it merges them
@test cropfloe(img1, props_without_label, 4) == [
0 0 0 0 0 0 0
0 1 1 1 1 1 0
0 1 1 1 1 1 0
0 1 1 1 0 0 0
0 1 1 1 1 1 0
0 0 0 0 0 0 0
]

@test cropfloe(img1, props_without_label, 4) == cropfloe(img1, props_without_label, 5)


@info "testing values with label"
# cropfloe
@test cropfloe(img1, 1, 1, 4, 4, 1) == [
0 0 0 0
0 1 1 0
0 1 1 0
0 0 0 0
]

@test cropfloe(img1, 1, 4, 5, 8, 2) == [
0 0 0 0 0
0 1 1 1 0
0 1 1 1 0
0 0 1 1 0
0 0 0 0 0
]

@test cropfloe(img1, 1, 8, 6, 13, 3) == [
0 0 0 0 0 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 1 1 0
0 1 1 1 0 0
0 0 0 0 0 0
]

@test cropfloe(img1, 5, 1, 10, 7, 4) == [
0 0 0 0 0 0 0
0 1 1 1 1 0 0
0 1 0 0 0 0 0
0 1 0 0 0 0 0
0 1 1 1 1 1 0
0 0 0 0 0 0 0
]

@test cropfloe(img1, 5, 1, 10, 7, 5) == [
0 0 0 0 0 0 0
0 0 0 0 0 1 0
0 0 1 1 1 1 0
0 0 1 1 0 0 0
0 0 0 0 0 0 0
0 0 0 0 0 0 0
]

end
Loading