Skip to content

Commit a3a91f2

Browse files
authored
Merge pull request #150 from GeoscienceAustralia/external_tides
Support external tide data, upgrade DEA Tools
2 parents c1e5215 + 7eab3fa commit a3a91f2

File tree

8 files changed

+2805
-146
lines changed

8 files changed

+2805
-146
lines changed

intertidal/elevation.py

Lines changed: 53 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import click
66
import matplotlib.pyplot as plt
77
import numpy as np
8+
import pandas as pd
89
import seaborn as sns
910
import xarray as xr
1011
from dea_tools.dask import create_local_dask_cluster
@@ -744,6 +745,7 @@ def clean_edge_pixels(ds):
744745

745746
def elevation(
746747
satellite_ds,
748+
tide_data=None,
747749
valid_mask=None,
748750
ndwi_thresh=0.1,
749751
min_freq=0.01,
@@ -759,14 +761,18 @@ def elevation(
759761
log=None,
760762
**model_tides_kwargs,
761763
):
762-
"""Generates DEA Intertidal Elevation outputs using satellite imagery
763-
and tidal modeling.
764+
"""Generate DEA Intertidal Elevation outputs using satellite imagery and tide data.
764765
765766
Parameters
766767
----------
767768
satellite_ds : xarray.Dataset
768769
A satellite data time series containing an "ndwi" water index
769770
variable.
771+
tide_data : array, optional
772+
An optional array of tide heights or sea level measurements
773+
matching the timesteps in `satellite_ds`. If this is provided,
774+
these values will be used instead of modelling tides for each
775+
satellite observation timestep.
770776
valid_mask : xr.DataArray, optional
771777
A boolean mask used to optionally constrain the analysis area,
772778
with the same spatial dimensions as `satellite_ds`. For example,
@@ -803,7 +809,8 @@ def elevation(
803809
determine workers.
804810
tide_model : str, optional
805811
The tide model or a list of models used to model tides, as
806-
supported by the `eo-tides` Python package. Options include:
812+
supported by the `eo-tides` Python package; ignored if `tide_data`
813+
is provided. Options include:
807814
- "EOT20" (default)
808815
- "TPXO10-atlas-v2-nc"
809816
- "FES2022"
@@ -816,6 +823,7 @@ def elevation(
816823
The directory containing tide model data files. Defaults to
817824
"/var/share/tide_models"; for more information about the
818825
directory structure, refer to `eo-tides.utils.list_models`.
826+
Ignored if `tide_data` is provided.
819827
run_id : string, optional
820828
An optional string giving the name of the analysis; used to
821829
prefix log entries.
@@ -852,22 +860,49 @@ def elevation(
852860
# Use run ID name for logs if it exists
853861
run_id = "Processing" if run_id is None else run_id
854862

855-
# Model tides into every pixel in the three-dimensional satellite
856-
# dataset (x by y by time). If `model` is "ensemble" this will model
857-
# tides by combining the best local tide models.
858-
log.info(f"{run_id}: Modelling tide heights for each pixel")
859-
tide_m = pixel_tides(
860-
data=satellite_ds,
861-
model=tide_model,
862-
directory=tide_model_dir,
863-
**model_tides_kwargs,
864-
)
863+
# If tide heights are provided, use these directly after validating
864+
# they have the current number of timesteps
865+
if tide_data is not None:
866+
log.info(f"{run_id}: Using provided tide heights")
867+
# Convert to xarray if required
868+
if isinstance(tide_data, pd.Series):
869+
tide_data = tide_data.rename_axis("time").to_xarray()
870+
elif isinstance(tide_data, xr.DataArray):
871+
assert "time" in tide_data.dims, "Provided tide data must include a 'time' dimension."
872+
else:
873+
raise ValueError("Tide data must be provided in `pd.Series` or `xr.DataArray` format.")
874+
875+
# Verify that data includes the expected number of timesteps
876+
if len(tide_data.time) != len(satellite_ds.time):
877+
err_msg = (
878+
"`tide_data` has a different number of timesteps "
879+
f"({len(tide_data.time)}) than `satellite_ds` "
880+
f"({len(satellite_ds.time)}). Ensure `tide_data` includes only a "
881+
"single measurement for each satellite observation in `satellite_ds`."
882+
)
883+
raise ValueError(err_msg)
884+
885+
# Add to dataset
886+
satellite_ds["tide_m"] = tide_data
887+
tide_m = tide_data
888+
889+
# Otherwise, model tides into every pixel in the 3D (i.e. `x` by `y`
890+
# by `time`) satellite dataset . If `model` is "ensemble" this will
891+
# model tides by combining the best local tide models.
892+
else:
893+
log.info(f"{run_id}: Modelling tide heights for each pixel")
894+
tide_m = pixel_tides(
895+
data=satellite_ds,
896+
model=tide_model,
897+
directory=tide_model_dir,
898+
**model_tides_kwargs,
899+
)
865900

866-
# Set tide array pixels to nodata if the satellite data array pixels
867-
# contain nodata. This ensures that we ignore any tide observations
868-
# where we don't have matching satellite imagery
869-
log.info(f"{run_id}: Masking nodata and adding tide heights to satellite data array")
870-
satellite_ds["tide_m"] = tide_m.where(~satellite_ds.to_array().isel(variable=0).isnull().drop_vars("variable"))
901+
# Set tide array pixels to nodata if the satellite data array pixels
902+
# contain nodata. This ensures that we ignore any tide observations
903+
# where we don't have matching satellite imagery
904+
log.info(f"{run_id}: Masking nodata and adding tide heights to satellite data array")
905+
satellite_ds["tide_m"] = tide_m.where(~satellite_ds.to_array().isel(variable=0).isnull().drop_vars("variable"))
871906

872907
# Flatten array from 3D (time, y, x) to 2D (time, z) and drop pixels
873908
# with no correlation with tide. This greatly improves processing

notebooks/Getting_started_with_DEA_Intertidal.ipynb

Lines changed: 9 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"\n",
1515
"<div class=\"alert alert-info\">\n",
1616
" \n",
17-
"**Note:** For more information on setting up tide models for DEA Intertidal using the `eo-tides` Python pacakge, refer to the [guide located here](https://geoscienceaustralia.github.io/eo-tides/setup/).\n",
17+
"**Note:** For more information on setting up tide models for DEA Intertidal using the `eo-tides` Python package, refer to the [guide located here](https://geoscienceaustralia.github.io/eo-tides/setup/).\n",
1818
"\n",
1919
"</div>"
2020
]
@@ -53,7 +53,7 @@
5353
"id": "c586a481-013f-4884-8e53-f3ddb9c438d7",
5454
"metadata": {},
5555
"source": [
56-
"Install latest version of DEA Intertidal:"
56+
"Install latest version of DEA Intertidal if required:"
5757
]
5858
},
5959
{
@@ -73,7 +73,7 @@
7373
}
7474
],
7575
"source": [
76-
"%pip install -e . --quiet"
76+
"# %pip install -e . --quiet"
7777
]
7878
},
7979
{
@@ -1204,21 +1204,13 @@
12041204
"fix, axes = plt.subplots(2, 2, figsize=(10, 8))\n",
12051205
"axes = axes.flatten()\n",
12061206
"\n",
1207-
"ds.elevation.plot.imshow(\n",
1208-
" cmap=\"viridis\", robust=True, ax=axes[0], label=False, add_labels=False\n",
1209-
")\n",
1207+
"ds.elevation.plot.imshow(cmap=\"viridis\", robust=True, ax=axes[0], label=False, add_labels=False)\n",
12101208
"axes[0].set_title(\"Elevation (m)\")\n",
1211-
"ds.elevation_uncertainty.plot.imshow(\n",
1212-
" cmap=\"inferno\", robust=True, ax=axes[1], label=False, add_labels=False\n",
1213-
")\n",
1209+
"ds.elevation_uncertainty.plot.imshow(cmap=\"inferno\", robust=True, ax=axes[1], label=False, add_labels=False)\n",
12141210
"axes[1].set_title(\"Elevation uncertainty (m)\")\n",
1215-
"ds.qa_ndwi_corr.plot.imshow(\n",
1216-
" cmap=\"RdBu\", vmin=-0.7, vmax=0.7, ax=axes[2], label=False, add_labels=False\n",
1217-
")\n",
1211+
"ds.qa_ndwi_corr.plot.imshow(cmap=\"RdBu\", vmin=-0.7, vmax=0.7, ax=axes[2], label=False, add_labels=False)\n",
12181212
"axes[2].set_title(\"Tide correlation\")\n",
1219-
"ds.qa_ndwi_freq.plot.imshow(\n",
1220-
" cmap=\"Blues\", vmin=0, vmax=1, ax=axes[3], label=False, add_labels=False\n",
1221-
")\n",
1213+
"ds.qa_ndwi_freq.plot.imshow(cmap=\"Blues\", vmin=0, vmax=1, ax=axes[3], label=False, add_labels=False)\n",
12221214
"axes[3].set_title(\"Inundation frequency (%)\");"
12231215
]
12241216
},
@@ -1481,9 +1473,7 @@
14811473
}
14821474
],
14831475
"source": [
1484-
"ds.exposure.plot.imshow(\n",
1485-
" cmap=cmocean.cm.matter_r, vmin=0, vmax=100, label=False, add_labels=False\n",
1486-
")\n",
1476+
"ds.exposure.plot.imshow(cmap=cmocean.cm.matter_r, vmin=0, vmax=100, label=False, add_labels=False)\n",
14871477
"plt.gca().set_title(\"Exposure\");"
14881478
]
14891479
},
@@ -1561,105 +1551,7 @@
15611551
},
15621552
"widgets": {
15631553
"application/vnd.jupyter.widget-state+json": {
1564-
"state": {
1565-
"04ff1dc786bd43ae9b40ac9e79054b76": {
1566-
"model_module": "@jupyter-widgets/controls",
1567-
"model_module_version": "2.0.0",
1568-
"model_name": "ProgressStyleModel",
1569-
"state": {
1570-
"description_width": ""
1571-
}
1572-
},
1573-
"14cb61b551c5421d9c6787bb1059c213": {
1574-
"model_module": "@jupyter-widgets/controls",
1575-
"model_module_version": "2.0.0",
1576-
"model_name": "HTMLModel",
1577-
"state": {
1578-
"layout": "IPY_MODEL_f7eb721ebfd84ab793c9ea035b513eee",
1579-
"style": "IPY_MODEL_e0202762aaa64a15bac48e8846e4efcc",
1580-
"value": "100%"
1581-
}
1582-
},
1583-
"76c1f85381964e93b4aa38fde6175eb8": {
1584-
"model_module": "@jupyter-widgets/controls",
1585-
"model_module_version": "2.0.0",
1586-
"model_name": "HTMLModel",
1587-
"state": {
1588-
"layout": "IPY_MODEL_f9754946f72d4baf81112a1835d76a3d",
1589-
"style": "IPY_MODEL_c2532225aa694c5e9efdcbe107336c19",
1590-
"value": " 55/55 [01:04&lt;00:00,  1.16s/it]"
1591-
}
1592-
},
1593-
"84ab042d9b364ae8b79d2aae6f3d036e": {
1594-
"model_module": "@jupyter-widgets/controls",
1595-
"model_module_version": "2.0.0",
1596-
"model_name": "FloatProgressModel",
1597-
"state": {
1598-
"bar_style": "success",
1599-
"layout": "IPY_MODEL_a059d536fe8d4aaf9fc3ce37fb0b3d77",
1600-
"max": 55,
1601-
"style": "IPY_MODEL_04ff1dc786bd43ae9b40ac9e79054b76",
1602-
"value": 55
1603-
}
1604-
},
1605-
"a059d536fe8d4aaf9fc3ce37fb0b3d77": {
1606-
"model_module": "@jupyter-widgets/base",
1607-
"model_module_version": "2.0.0",
1608-
"model_name": "LayoutModel",
1609-
"state": {}
1610-
},
1611-
"bdcdf2be0d65434486dc96907baca417": {
1612-
"model_module": "@jupyter-widgets/controls",
1613-
"model_module_version": "2.0.0",
1614-
"model_name": "HBoxModel",
1615-
"state": {
1616-
"children": [
1617-
"IPY_MODEL_14cb61b551c5421d9c6787bb1059c213",
1618-
"IPY_MODEL_84ab042d9b364ae8b79d2aae6f3d036e",
1619-
"IPY_MODEL_76c1f85381964e93b4aa38fde6175eb8"
1620-
],
1621-
"layout": "IPY_MODEL_f5bf1a0aa1344fafb10502093a0d2e95"
1622-
}
1623-
},
1624-
"c2532225aa694c5e9efdcbe107336c19": {
1625-
"model_module": "@jupyter-widgets/controls",
1626-
"model_module_version": "2.0.0",
1627-
"model_name": "HTMLStyleModel",
1628-
"state": {
1629-
"description_width": "",
1630-
"font_size": null,
1631-
"text_color": null
1632-
}
1633-
},
1634-
"e0202762aaa64a15bac48e8846e4efcc": {
1635-
"model_module": "@jupyter-widgets/controls",
1636-
"model_module_version": "2.0.0",
1637-
"model_name": "HTMLStyleModel",
1638-
"state": {
1639-
"description_width": "",
1640-
"font_size": null,
1641-
"text_color": null
1642-
}
1643-
},
1644-
"f5bf1a0aa1344fafb10502093a0d2e95": {
1645-
"model_module": "@jupyter-widgets/base",
1646-
"model_module_version": "2.0.0",
1647-
"model_name": "LayoutModel",
1648-
"state": {}
1649-
},
1650-
"f7eb721ebfd84ab793c9ea035b513eee": {
1651-
"model_module": "@jupyter-widgets/base",
1652-
"model_module_version": "2.0.0",
1653-
"model_name": "LayoutModel",
1654-
"state": {}
1655-
},
1656-
"f9754946f72d4baf81112a1835d76a3d": {
1657-
"model_module": "@jupyter-widgets/base",
1658-
"model_module_version": "2.0.0",
1659-
"model_name": "LayoutModel",
1660-
"state": {}
1661-
}
1662-
},
1554+
"state": {},
16631555
"version_major": 2,
16641556
"version_minor": 0
16651557
}

0 commit comments

Comments
 (0)