22
22
load_data ,
23
23
load_topobathy_mask ,
24
24
load_aclum_mask ,
25
+ load_ocean_mask ,
25
26
prepare_for_export ,
26
27
tidal_metadata ,
27
28
export_dataset_metadata ,
31
32
round_date_strings ,
32
33
)
33
34
from intertidal .tide_modelling import pixel_tides_ensemble
34
- from intertidal .extents import extents
35
+ from intertidal .extents import extents , ocean_connection
35
36
from intertidal .exposure import exposure
36
37
from intertidal .tidal_bias_offset import bias_offset
37
38
@@ -82,7 +83,7 @@ def ds_to_flat(
82
83
If True, remove any seasonal signal from the tide height data
83
84
by subtracting monthly mean tide height from each value. This
84
85
can reduce false tide correlations in regions where tide heights
85
- correlate with seasonal changes in surface water. Note that
86
+ correlate with seasonal changes in surface water. Note that
86
87
seasonally corrected tides are only used to identify potentially
87
88
tide influenced pixels - not for elevation modelling itself.
88
89
valid_mask : xr.DataArray, optional
@@ -131,15 +132,15 @@ def ds_to_flat(
131
132
# correlation. This prevents small changes in NDWI beneath the water
132
133
# surface from producing correlations with tide height.
133
134
wet_dry = flat_ds [index ] > ndwi_thresh
134
-
135
+
135
136
# Use either tides directly or correct to remove seasonal signal
136
137
if correct_seasonality :
137
138
print ("Removing seasonal signal before calculating tide correlations" )
138
- gb = flat_ds .tide_m .groupby (' time.month' )
139
- tide_array = ( gb - gb .mean () )
139
+ gb = flat_ds .tide_m .groupby (" time.month" )
140
+ tide_array = gb - gb .mean ()
140
141
else :
141
- tide_array = flat_ds .tide_m
142
-
142
+ tide_array = flat_ds .tide_m
143
+
143
144
if corr_method == "pearson" :
144
145
corr = xr .corr (wet_dry , tide_array , dim = "time" ).rename ("qa_ndwi_corr" )
145
146
elif corr_method == "spearman" :
@@ -558,10 +559,11 @@ def pixel_uncertainty(
558
559
max_q = 0.75 ,
559
560
):
560
561
"""
561
- Calculate uncertainty bounds around a modelled elevation based on
562
- observations that were misclassified by a given NDWI threshold.
562
+ Calculate one-sided uncertainty bounds around a modelled elevation
563
+ based on observations that were misclassified by a given NDWI
564
+ threshold.
563
565
564
- The function identifies observations that were misclassified by the
566
+ Uncertainty is based observations that were misclassified by the
565
567
modelled elevation, i.e., wet observations (NDWI > threshold) at
566
568
lower tide heights than the modelled elevation, or dry observations
567
569
(NDWI < threshold) at higher tide heights than the modelled
@@ -603,7 +605,8 @@ def pixel_uncertainty(
603
605
-------
604
606
dem_flat_low, dem_flat_high, dem_flat_uncertainty : xarray.DataArray
605
607
The lower and upper uncertainty bounds around the modelled
606
- elevation, and the summary uncertainty range between them.
608
+ elevation, and the summary uncertainty range between them
609
+ (expressed as one-sided uncertainty).
607
610
misclassified_sum : xarray.DataArray
608
611
The sum of individual satellite observations misclassified by
609
612
the modelled elevation and NDWI threshold.
@@ -666,8 +669,9 @@ def pixel_uncertainty(
666
669
dem_flat_low = np .minimum (uncertainty_low , flat_dem .elevation )
667
670
dem_flat_high = np .maximum (uncertainty_high , flat_dem .elevation )
668
671
669
- # Subtract low from high DEM to summarise uncertainy range
670
- dem_flat_uncertainty = dem_flat_high - dem_flat_low
672
+ # Subtract low from high DEM to summarise uncertainty range
673
+ # (and divide by two to give one-sided uncertainty)
674
+ dem_flat_uncertainty = (dem_flat_high - dem_flat_low ) / 2.0
671
675
672
676
return (
673
677
dem_flat_low ,
@@ -763,6 +767,7 @@ def clean_edge_pixels(ds):
763
767
def elevation (
764
768
satellite_ds ,
765
769
valid_mask = None ,
770
+ ocean_mask = None ,
766
771
ndwi_thresh = 0.1 ,
767
772
min_freq = 0.01 ,
768
773
max_freq = 0.99 ,
@@ -791,6 +796,12 @@ def elevation(
791
796
this could be a mask generated from a topo-bathy DEM, used to
792
797
limit the analysis to likely intertidal pixels. Default is None,
793
798
which will not apply a mask.
799
+ ocean_mask : xr.DataArray, optional
800
+ An optional mask identifying ocean pixels within the analysis
801
+ area, with the same spatial dimensions as `satellite_ds`.
802
+ If provided, this will be used to restrict the analysis to pixels
803
+ that are directly connected to ocean waters. Defaults is None,
804
+ which will not apply a mask.
794
805
ndwi_thresh : float, optional
795
806
A threshold value for the normalized difference water index
796
807
(NDWI) above which pixels are considered water, by default 0.1.
@@ -950,6 +961,16 @@ def elevation(
950
961
elevation_bands = [d for d in ds .data_vars if "elevation" in d ]
951
962
ds [elevation_bands ] = clean_edge_pixels (ds [elevation_bands ])
952
963
964
+ # Mask out any non-ocean connected elevation pixels.
965
+ # `~(ds.qa_ndwi_freq < min_freq)` ensures that nodata pixels are
966
+ # treated as wet
967
+ if ocean_mask is not None :
968
+ log .info (f"{ run_id } : Restricting outputs to ocean-connected waters" )
969
+ ocean_connected_mask = ocean_connection (
970
+ ~ (ds .qa_ndwi_freq < min_freq ), ocean_mask
971
+ )
972
+ ds [elevation_bands ] = ds [elevation_bands ].where (ocean_connected_mask )
973
+
953
974
# Return output data and tide height array
954
975
log .info (f"{ run_id } : Successfully completed intertidal elevation modelling" )
955
976
return ds , tide_m
@@ -1067,6 +1088,18 @@ def elevation(
1067
1088
help = "Proportion of the tide range to use for each window radius "
1068
1089
"in the per-pixel rolling median calculation, by default 0.15." ,
1069
1090
)
1091
+ @click .option (
1092
+ "--correct_seasonality/--no-correct_seasonality" ,
1093
+ is_flag = True ,
1094
+ default = False ,
1095
+ help = "If True, remove any seasonal signal from the tide height data "
1096
+ "by subtracting monthly mean tide height from each value prior to "
1097
+ "correlation calculations. This can reduce false tide correlations "
1098
+ "in regions where tide heights correlate with seasonal changes in "
1099
+ "surface water. Note that seasonally corrected tides are only used "
1100
+ "to identify potentially tide influenced pixels - not for elevation "
1101
+ "modelling itself." ,
1102
+ )
1070
1103
@click .option (
1071
1104
"--tide_model" ,
1072
1105
type = str ,
@@ -1126,6 +1159,7 @@ def intertidal_cli(
1126
1159
min_correlation ,
1127
1160
windows_n ,
1128
1161
window_prop_tide ,
1162
+ correct_seasonality ,
1129
1163
tide_model ,
1130
1164
tide_model_dir ,
1131
1165
modelled_freq ,
@@ -1175,11 +1209,12 @@ def intertidal_cli(
1175
1209
)
1176
1210
satellite_ds .load ()
1177
1211
1178
- # Load topobathy mask from GA's AusBathyTopo 250m 2023 Grid
1212
+ # Load topobathy mask from GA's AusBathyTopo 250m 2023 Grid,
1213
+ # urban land use class mask from ABARES CLUM, and ocean mask
1214
+ # from geodata_coast_100k
1179
1215
topobathy_mask = load_topobathy_mask (dc , satellite_ds .odc .geobox .compat )
1180
-
1181
- # Load urban land use class mask from ABARES CLUM
1182
1216
reclassified_aclum = load_aclum_mask (dc , satellite_ds .odc .geobox .compat )
1217
+ ocean_mask = load_ocean_mask (dc , satellite_ds .odc .geobox .compat )
1183
1218
1184
1219
# Also load ancillary dataset IDs to use in metadata
1185
1220
# (both layers are continental continental products with only
@@ -1193,30 +1228,31 @@ def intertidal_cli(
1193
1228
ds , tide_m = elevation (
1194
1229
satellite_ds ,
1195
1230
valid_mask = topobathy_mask ,
1231
+ ocean_mask = ocean_mask ,
1196
1232
ndwi_thresh = ndwi_thresh ,
1197
1233
min_freq = min_freq ,
1198
1234
max_freq = max_freq ,
1199
1235
min_correlation = min_correlation ,
1200
1236
windows_n = windows_n ,
1201
1237
window_prop_tide = window_prop_tide ,
1202
- correct_seasonality = True ,
1238
+ correct_seasonality = correct_seasonality ,
1203
1239
tide_model = tide_model ,
1204
1240
tide_model_dir = tide_model_dir ,
1205
1241
run_id = run_id ,
1206
1242
log = log ,
1207
1243
)
1208
1244
1209
- # Calculate extents
1210
- log .info (f"{ run_id } : Calculating Intertidal Extents" )
1211
- ds ["extents" ] = extents (
1212
- dem = ds .elevation ,
1213
- freq = ds .qa_ndwi_freq ,
1214
- corr = ds .qa_ndwi_corr ,
1215
- reclassified_aclum = reclassified_aclum ,
1216
- min_freq = min_freq ,
1217
- max_freq = max_freq ,
1218
- min_correlation = min_correlation ,
1219
- )
1245
+ # # Calculate extents (to be included in next version)
1246
+ # log.info(f"{run_id}: Calculating Intertidal Extents")
1247
+ # ds["extents"] = extents(
1248
+ # dem=ds.elevation,
1249
+ # freq=ds.qa_ndwi_freq,
1250
+ # corr=ds.qa_ndwi_corr,
1251
+ # reclassified_aclum=reclassified_aclum,
1252
+ # min_freq=min_freq,
1253
+ # max_freq=max_freq,
1254
+ # min_correlation=min_correlation,
1255
+ # )
1220
1256
1221
1257
if exposure_offsets :
1222
1258
log .info (f"{ run_id } : Calculating Intertidal Exposure" )
@@ -1251,7 +1287,6 @@ def intertidal_cli(
1251
1287
) = bias_offset (
1252
1288
tide_m = tide_m ,
1253
1289
tide_cq = tide_cq ,
1254
- extents = ds .extents ,
1255
1290
lot_hot = True ,
1256
1291
lat_hat = True ,
1257
1292
)
0 commit comments