diff --git a/src/IceFloeTracker.jl b/src/IceFloeTracker.jl index 6912e905..59465b50 100644 --- a/src/IceFloeTracker.jl +++ b/src/IceFloeTracker.jl @@ -38,6 +38,7 @@ export readdlm, imsharpen, label_components, regionprops_table, + cropfloe, loadimg, matchcorr, centered, diff --git a/src/bwtraceboundary.jl b/src/bwtraceboundary.jl index e4388e47..9ee455a3 100644 --- a/src/bwtraceboundary.jl +++ b/src/bwtraceboundary.jl @@ -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}} diff --git a/src/regionprops.jl b/src/regionprops.jl index cffb40a6..d5259e2e 100644 --- a/src/regionprops.jl +++ b/src/regionprops.jl @@ -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 =# @@ -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} + #= + 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" + + 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 @@ -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 diff --git a/src/tracker/tracker-funcs.jl b/src/tracker/tracker-funcs.jl index 29744e1d..fe9e168b 100644 --- a/src/tracker/tracker-funcs.jl +++ b/src/tracker/tracker-funcs.jl @@ -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 @@ -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]) ] diff --git a/src/tracker/tracker.jl b/src/tracker/tracker.jl index 1bb5bfb6..3b7fe6a0 100644 --- a/src/tracker/tracker.jl +++ b/src/tracker/tracker.jl @@ -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, @@ -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, diff --git a/test/runtests.jl b/test/runtests.jl index 5a77ed59..f6018fc8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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", diff --git a/test/test-regionprops-labeled.jl b/test/test-regionprops-labeled.jl new file mode 100644 index 00000000..aefff915 --- /dev/null +++ b/test/test-regionprops-labeled.jl @@ -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