Skip to content

Commit 2c1c47d

Browse files
authored
Update graph func to use centroid observations (#133)
* Update graph func to use centroid observations * Comment * Automatically update integration test validation results * Use pragmatic filter on clear low and high tide obs * Automatically update integration test validation results --------- Co-authored-by: robbibt <robbibt@users.noreply.github.com>
1 parent fc4c6ea commit 2c1c47d

File tree

5 files changed

+76
-60
lines changed

5 files changed

+76
-60
lines changed

intertidal/composites.py

Lines changed: 16 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,14 @@ def tidal_thresholds(
4242
# Calculate per-pixel integer rankings for each tide height
4343
rank_n = tides_highres.rank(dim="time")
4444

45-
# Calculate low and high ranking thresholds from total rankings.
46-
# Low threshold needs to be rounded up ("ceil"), and high tide
47-
# rounded down ("floor") to ensure we capture all matching values.
48-
rank_max = rank_n.max(dim="time")
45+
# Calculate pixel-based low and high ranking thresholds from
46+
# max ranking. Max ranking needs to be rounded up to the nearest
47+
# integer using "ceil" as xarray will give multiple observation
48+
# an average rank (e.g. 50.5) value if they are both identical.
49+
# Additionally: to ensure we capture all matching values, Low
50+
# threshold needs to be rounded up ("ceil"), and high tide
51+
# rounded down ("floor").
52+
rank_max = np.ceil(rank_n.max(dim="time"))
4953
rank_thresh_low = np.ceil(rank_max * threshold_lowtide)
5054
rank_thresh_high = np.floor(rank_max * threshold_hightide)
5155

@@ -169,13 +173,6 @@ def tidal_composites(
169173
log.info(
170174
f"{run_id}: Calculating low and high tide thresholds with minimum {min_obs} observations"
171175
)
172-
# threshold_ds = xr_quantile(
173-
# src=tides_highres.to_dataset(),
174-
# quantiles=[threshold_lowtide, threshold_hightide],
175-
# nodata=np.nan,
176-
# )
177-
# low_threshold = threshold_ds.isel(quantile=0).tide_height.drop("quantile")
178-
# high_threshold = threshold_ds.isel(quantile=-1).tide_height.drop("quantile")
179176
low_threshold, high_threshold = tidal_thresholds(
180177
tides_highres=tides_highres,
181178
threshold_lowtide=threshold_lowtide,
@@ -188,9 +185,9 @@ def tidal_composites(
188185
low_mask = tides_highres <= low_threshold
189186
high_mask = tides_highres >= high_threshold
190187

191-
# Keep only scenes with at least some valid data to speed up geomedian
192-
low_keep = low_mask.any(dim=["x", "y"])
193-
high_keep = high_mask.any(dim=["x", "y"])
188+
# Keep only scenes with at least 1% valid data to speed up geomedian
189+
low_keep = low_mask.mean(dim=["x", "y"]) >= 0.01
190+
high_keep = high_mask.mean(dim=["x", "y"]) >= 0.01
194191
ds_low = satellite_ds.sel(time=low_keep)
195192
ds_high = satellite_ds.sel(time=high_keep)
196193

@@ -236,7 +233,7 @@ def tidal_composites(
236233
ds_lowtide["qa_low_threshold"] = low_threshold
237234
ds_hightide["qa_high_threshold"] = high_threshold
238235

239-
return ds_lowtide, ds_hightide, low_keep, high_keep
236+
return ds_lowtide, ds_hightide
240237

241238

242239
@click.command()
@@ -346,8 +343,7 @@ def tidal_composites(
346343
"--include_coastal_aerosol/--no-include_coastal_aerosol",
347344
type=bool,
348345
default=True,
349-
help="Whether to include the coastal aerosol band. "
350-
"Defaults to True",
346+
help="Whether to include the coastal aerosol band. Defaults to True",
351347
)
352348
@click.option(
353349
"--eps",
@@ -493,7 +489,7 @@ def tidal_composites_cli(
493489

494490
# Calculate high and low tide geomedian composites
495491
log.info(f"{run_id}: Running DEA Tidal Composites workflow")
496-
ds_lowtide, ds_hightide, low_keep, high_keep = tidal_composites(
492+
ds_lowtide, ds_hightide = tidal_composites(
497493
satellite_ds=satellite_ds,
498494
threshold_lowtide=threshold_lowtide,
499495
threshold_hightide=threshold_hightide,
@@ -554,20 +550,12 @@ def tidal_composites_cli(
554550
log=log,
555551
)
556552

557-
# Add new array to data to label high or low tide
558-
# images selected for geomedian analysis.
559-
label = xr.where(
560-
high_keep | low_keep,
561-
"Clear low and high tide images",
562-
"All satellite observations",
563-
)
564-
satellite_ds = satellite_ds.assign(label=label)
565-
566553
# Calculate additional tile-level tidal metadata and graph.
567554
metadata_dict, tide_graph_fig = tidal_metadata(
568555
product_family="tidal_composites",
556+
threshold_lowtide=threshold_lowtide,
557+
threshold_hightide=threshold_hightide,
569558
data=satellite_ds,
570-
plot_var="label",
571559
modelled_freq="30min",
572560
model=tide_model,
573561
directory=tide_model_dir,

intertidal/io.py

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -785,15 +785,27 @@ def _write_stac(
785785
return stac
786786

787787

788-
def tidal_metadata(product_family, **tide_stats_kwargs):
788+
def tidal_metadata(
789+
product_family,
790+
threshold_lowtide=0.15,
791+
threshold_hightide=0.85,
792+
**tide_stats_kwargs,
793+
):
789794
"""
790795
Generate tidal statistics and tide bias plot for a given input tile.
791796
Tidal statistics are calculated based on the centroid of the tile.
792797
798+
For `product_family=="tidal_composites"`, observations matching the
799+
low and high tide thresholds will be plotted in white.
800+
793801
Parameters
794802
----------
795803
product_family : string
796804
Either "intertidal" or "tidal_composites".
805+
threshold_lowtide : float, optional
806+
Quantile used to identify low tide observations, by default 0.15.
807+
threshold_hightide : float, optional
808+
Quantile used to identify high tide observations, by default 0.85.
797809
**tide_stats_kwargs :
798810
Any required parameters to pass to `eo_tides.stats.tide_stats`,
799811
e.g. `data`, `model`, `directory` etc.
@@ -831,36 +843,50 @@ def tidal_metadata(product_family, **tide_stats_kwargs):
831843
)
832844

833845
# Update figure line and point colours
834-
if product_family == "intertidal":
835-
fig.axes[0].get_lines()[0].set_color("#90b7d8")
836-
fig.axes[0].get_lines()[0].set_alpha(1.0)
837-
fig.axes[0].get_lines()[1].set_color("black")
838-
fig.axes[0].get_lines()[1].set_markersize(4)
839-
fig.axes[0].get_lines()[1].set_markeredgecolor("none")
840-
841-
# HAT/LOT lines
842-
fig.axes[0].get_lines()[2].set_color("none")
843-
fig.axes[0].get_lines()[3].set_color("none")
844-
fig.axes[0].get_lines()[4].set_color("none")
845-
fig.axes[0].get_lines()[5].set_color("none")
846-
847-
elif product_family == "tidal_composites":
848-
fig.axes[0].get_lines()[0].set_color("#90b7d8")
849-
fig.axes[0].get_lines()[0].set_alpha(1.0)
850-
fig.axes[0].get_lines()[1].set_markeredgecolor("none")
851-
fig.axes[0].get_lines()[2].set_markeredgecolor("#343c47")
852-
fig.axes[0].get_lines()[1].set_marker("o")
853-
fig.axes[0].get_lines()[2].set_marker("o")
854-
fig.axes[0].get_lines()[1].set_markersize(4)
855-
fig.axes[0].get_lines()[2].set_markersize(5)
856-
fig.axes[0].get_lines()[1].set_color("black")
857-
fig.axes[0].get_lines()[2].set_color("white")
858-
859-
# HAT/LOT lines
860-
fig.axes[0].get_lines()[3].set_color("none")
861-
fig.axes[0].get_lines()[4].set_color("none")
862-
fig.axes[0].get_lines()[5].set_color("none")
863-
fig.axes[0].get_lines()[6].set_color("none")
846+
modelled = fig.axes[0].get_lines()[0]
847+
observed = fig.axes[0].get_lines()[1]
848+
hat = fig.axes[0].get_lines()[2]
849+
hot = fig.axes[0].get_lines()[3]
850+
lot = fig.axes[0].get_lines()[4]
851+
lat = fig.axes[0].get_lines()[5]
852+
853+
# Set styling
854+
modelled.set_color("#90b7d8")
855+
modelled.set_alpha(1.0)
856+
observed.set_color("black")
857+
observed.set_markersize(4)
858+
observed.set_markeredgecolor("none")
859+
860+
# Remove HAT/LOT lines
861+
hat.set_color("none")
862+
hot.set_color("none")
863+
lot.set_color("none")
864+
lat.set_color("none")
865+
866+
# For Tidal Composites, manually set low and high
867+
# tide observations to white
868+
if product_family == "tidal_composites":
869+
870+
# Extract observed data from plot
871+
xdata = observed.get_xdata()
872+
ydata = observed.get_ydata()
873+
874+
# Calculate thresholds and plot subset of points in white
875+
min_thresh, max_thresh = np.quantile(
876+
ydata, [threshold_lowtide, threshold_hightide]
877+
)
878+
mask = (ydata <= min_thresh) | (ydata >= max_thresh)
879+
fig.axes[0].plot(
880+
xdata[mask],
881+
ydata[mask],
882+
marker="o",
883+
linestyle="None",
884+
color="white",
885+
markersize=5,
886+
markeredgecolor="#343c47",
887+
markeredgewidth=0.8,
888+
label="Low and high tide images",
889+
)
864890

865891
# Set background to transparent
866892
fig.patch.set_facecolor("#5d646c00")

tests/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Integration tests
1010
1111
This directory contains tests that are run to verify that DEA Intertidal code runs correctly. The ``test_intertidal.py`` file runs a small-scale full workflow analysis over an intertidal flat in the Gulf of Carpentaria using the DEA Intertidal [Command Line Interface (CLI) tools](../notebooks/Intertidal_CLI.ipynb), and compares these results against a LiDAR validation DEM to produce some simple accuracy metrics.
1212

13-
The latest integration test completed at **2025-05-02 11:43**. Compared to the previous run, it had an:
13+
The latest integration test completed at **2025-05-02 17:14**. Compared to the previous run, it had an:
1414
- RMSE accuracy of **0.14 m ( :heavy_minus_sign: no change)**
1515
- MAE accuracy of **0.12 m ( :heavy_minus_sign: no change)**
1616
- Bias of **0.12 m ( :heavy_minus_sign: no change)**

tests/validation.csv

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,5 @@ time,Correlation,RMSE,MAE,R-squared,Bias,Regression slope
108108
2025-04-28 02:09:27.190062+00:00,0.975,0.145,0.123,0.95,0.117,1.119
109109
2025-04-28 06:23:57.332550+00:00,0.975,0.145,0.123,0.95,0.117,1.119
110110
2025-05-02 01:43:13.002005+00:00,0.975,0.145,0.123,0.95,0.117,1.119
111+
2025-05-02 06:32:51.594612+00:00,0.975,0.145,0.123,0.95,0.117,1.119
112+
2025-05-02 07:14:19.583638+00:00,0.975,0.145,0.123,0.95,0.117,1.119

tests/validation.jpg

-17 Bytes
Loading

0 commit comments

Comments
 (0)