Skip to content
Open
36 changes: 24 additions & 12 deletions src/DataWrangling/Copernicus/Copernicus.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import ClimaOcean.DataWrangling:
metadata_filename,
inpainted_metadata_path,
reversed_vertical_axis,
available_variables
available_variables,
is_three_dimensional

using Scratch

Expand Down Expand Up @@ -129,20 +130,31 @@ end

inpainted_metadata_path(metadata::CopernicusMetadata) = joinpath(metadata.dir, inpainted_metadata_filename(metadata))

location(::CopernicusMetadata) = (Center, Center, Center)
location(metadata::CopernicusMetadata) = is_three_dimensional(metadata) ? (Center, Center, Center) : (Center, Center, Nothing)
longitude_interfaces(::CopernicusMetadata) = (0, 360)
latitude_interfaces(::CopernicusMetadata) = (-80, 90)

is_three_dimensional(metadata::CopernicusMetadata) =
metadata.name == :temperature ||
metadata.name == :salinity ||
metadata.name == :u_velocity ||
metadata.name == :v_velocity
Comment on lines +137 to +141
Copy link
Member

@glwagner glwagner Oct 3, 2025

Choose a reason for hiding this comment

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

I think you should blacklist the 2D variables rather than whitelisting 3D variables, since most variables are 3D

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Here I was trying to follow the design pattern here:

if is_three_dimensional(metadata)

Copy link
Collaborator

Choose a reason for hiding this comment

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

You can achieve this by using a ! conditional because is_three_dimensional == !is_two_dimensional

Copy link
Collaborator

Choose a reason for hiding this comment

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

for example:

is_three_dimensional(metadata::CopernicusMetadata) =
    !(metadata.name == :ice_concentration ||
      metadata.name == :ice_thickness)


function z_interfaces(metadata::CopernicusMetadata)
path = metadata_path(metadata)
ds = Dataset(path)
zc = - reverse(ds["depth"][:])
close(ds)
dz = zc[2] - zc[1]
zf = zc[1:end-1] .+ zc[2:end]
push!(zf, 0)
pushfirst!(zf, zf[1] - dz)
return zf
# Check if this is a 2D surface variable
if !is_three_dimensional(metadata)
return (0, 1)
Comment on lines +145 to +146
Copy link
Member

Choose a reason for hiding this comment

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

Would it be better to build a Flat grid for 2D variables? Why do the z_interfaces matter?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

this line seems to want to take something for z, I tried returning nothing but I guess it didn't work

grid = LatitudeLongitudeGrid(arch, FT; halo, longitude, latitude, z,

Copy link
Collaborator

Choose a reason for hiding this comment

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

yeah, the size and the halo must be 2-tuples to use nothing in z.

else
path = metadata_path(metadata)
ds = Dataset(path)
zc = - reverse(ds["depth"][:])
close(ds)
dz = zc[2] - zc[1]
zf = zc[1:end-1] .+ zc[2:end]
push!(zf, 0)
pushfirst!(zf, zf[1] - dz)
return zf
end
end

end # module Copernicus
end # module Copernicus
2 changes: 1 addition & 1 deletion src/DataWrangling/DataWrangling.jl
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ function default_inpainting(metadata)
if metadata.name in (:temperature, :salinity)
return NearestNeighborInpainting(Inf)
elseif metadata.name in (:sea_ice_thickness, :sea_ice_concentration)
return nothing
return ValueInpainting(0)
else
return NearestNeighborInpainting(5)
end
Expand Down
66 changes: 62 additions & 4 deletions src/DataWrangling/inpainting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,40 @@ struct NearestNeighborInpainting{M}
maxiter :: M
end

"""
ValueInpainting{V}

A structure representing a simple value inpainting algorithm, where all missing values are
substituted with a specified constant value.

# Fields
- `value :: V`: The constant value to use for inpainting missing data.
- `maxiter`: For cache compatibility (not used in the algorithm itself). Defaults to 0.

# Example
```julia
inpainting = ValueInpainting(0.0) # Fill missing values with 0
inpaint_mask!(field, mask; inpainting)
```
"""
struct ValueInpainting{V, M}
value :: V
maxiter :: M
end

# Convenience constructor that sets maxiter to a default value
ValueInpainting(value) = ValueInpainting(value, 0)

propagate_horizontally!(field, ::Nothing, substituting_field=deepcopy(field); kw...) = field

function propagating(field, mask, iter, inpainting::NearestNeighborInpainting)
nans = sum(isnan, field; condition=interior(mask))
return nans > 0 && iter < inpainting.maxiter
end

# ValueInpainting doesn't need iteration
propagating(field, mask, iter, inpainting::ValueInpainting) = false

"""
propagate_horizontally!(inpainting, field, mask [, substituting_field=deepcopy(field)])

Expand Down Expand Up @@ -57,6 +84,17 @@ function propagate_horizontally!(inpainting::NearestNeighborInpainting, field, m
return field
end

function propagate_horizontally!(inpainting::ValueInpainting, field, mask,
substituting_field=deepcopy(field))
grid = field.grid
arch = architecture(grid)

launch!(arch, grid, size(field), _fill_with_number!, field, mask, inpainting.value)
fill_halo_regions!(field)

return field
end

# Maybe we can remove this propagate field in lieu of a diffusion,
# Still we'll need to do this a couple of steps on the original grid
@kernel function _propagate_field!(substituting_field, ::NearestNeighborInpainting, field)
Expand Down Expand Up @@ -102,6 +140,12 @@ end
@inbounds field[i, j, k] *= !isnan(field[i, j, k])
end

@kernel function _fill_with_number!(field, mask, value)
i, j, k = @index(Global, NTuple)
FT_value = convert(eltype(field), value)
@inbounds field[i, j, k] = ifelse(mask[i, j, k], FT_value, field[i, j, k])
end

"""
inpaint_mask!(field, mask; inpainting=NearestNeighborInpainting(Inf))

Expand All @@ -117,10 +161,24 @@ Arguments
- `mask`: Boolean-valued `Field`, values where
`mask[i, j, k] == true` are inpainted.

- `inpainting`: The inpainting algorithm to use. The only option is
`NearestNeighborInpainting(maxiter)`, where an average
of the valid surrounding values is used `maxiter` times.
Default: `NearestNeighborInpainting(Inf)`.
- `inpainting`: The inpainting algorithm to use. Options include:
* `NearestNeighborInpainting(maxiter)`: Uses an average
of the valid surrounding values, repeated `maxiter` times.
Default: `NearestNeighborInpainting(Inf)`.
* `ValueInpainting(value)`: Fills all missing values with
the specified constant `value`.

# Examples
```julia
# Use nearest neighbor inpainting with default settings
inpaint_mask!(field, mask)

# Fill missing values with zero
inpaint_mask!(field, mask; inpainting=ValueInpainting(0))

# Fill missing values with a specific temperature
inpaint_mask!(field, mask; inpainting=ValueInpainting(273.15))
```
"""
function inpaint_mask!(field, mask; inpainting=NearestNeighborInpainting(Inf))

Expand Down
4 changes: 4 additions & 0 deletions src/DataWrangling/metadata_field.jl
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ function set_metadata_field!(field, data, metadatum)
return nothing
end

# TODO: sort out elegant way to handle NaNs for sea ice data in GLORYS
@kernel function _set_2d_metadata_field!(field, data, mangling, temp_units, conc_units)
i, j = @index(Global, NTuple)
d = mangle(i, j, data, mangling)
Expand All @@ -228,6 +229,8 @@ end
elseif !isnothing(conc_units)
d = convert_concentration(d, conc_units)
end

d = convert_temperature(d, temp_units)
@inbounds field[i, j, 1] = d
end

Expand All @@ -246,6 +249,7 @@ end
elseif !isnothing(conc_units)
d = convert_concentration(d, conc_units)
end

@inbounds field[i, j, k] = d
end

Expand Down