marEx 2.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- marex-2.0.0/.gitignore +9 -0
- marex-2.0.0/PKG-INFO +247 -0
- marex-2.0.0/README.md +205 -0
- marex-2.0.0/examples/gridded data/01_preprocess_extremes.ipynb +912 -0
- marex-2.0.0/examples/gridded data/02_id_track_events.ipynb +2453 -0
- marex-2.0.0/examples/gridded data/03_visualise_events.ipynb +2253 -0
- marex-2.0.0/examples/unstructured data/01_preprocess_extremes.ipynb +1082 -0
- marex-2.0.0/examples/unstructured data/02_id_track_events.ipynb +302 -0
- marex-2.0.0/examples/unstructured data/03_visualise_events.ipynb +415 -0
- marex-2.0.0/marEx/__init__.py +78 -0
- marex-2.0.0/marEx/detect.py +490 -0
- marex-2.0.0/marEx/helper.py +251 -0
- marex-2.0.0/marEx/plotX/__init__.py +83 -0
- marex-2.0.0/marEx/plotX/base.py +387 -0
- marex-2.0.0/marEx/plotX/gridded.py +55 -0
- marex-2.0.0/marEx/plotX/unstructured.py +147 -0
- marex-2.0.0/marEx/track.py +3646 -0
- marex-2.0.0/marEx.egg-info/PKG-INFO +247 -0
- marex-2.0.0/marEx.egg-info/SOURCES.txt +29 -0
- marex-2.0.0/marEx.egg-info/dependency_links.txt +1 -0
- marex-2.0.0/marEx.egg-info/requires.txt +29 -0
- marex-2.0.0/marEx.egg-info/top_level.txt +1 -0
- marex-2.0.0/media/logo.png +0 -0
- marex-2.0.0/media/logo_trans.png +0 -0
- marex-2.0.0/media/movie_ID_field_basic_gridded.mp4 +0 -0
- marex-2.0.0/media/movie_ID_field_gridded.mp4 +0 -0
- marex-2.0.0/media/old/movie_ID_field_centroid.mp4 +0 -0
- marex-2.0.0/media/old/movie_ID_field_nn.mp4 +0 -0
- marex-2.0.0/media/old/movie_ID_field_original.mp4 +0 -0
- marex-2.0.0/pyproject.toml +70 -0
- marex-2.0.0/setup.cfg +4 -0
marex-2.0.0/.gitignore
ADDED
marex-2.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: marEx
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Marine Extremes Detection and Tracking
|
|
5
|
+
Author-email: Aaron Wienkers <aaron@wienkers.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/wienkers/marEx
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/wienkers/marEx/issues
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Requires-Python: >=3.6
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: xarray
|
|
19
|
+
Requires-Dist: dask
|
|
20
|
+
Requires-Dist: scipy
|
|
21
|
+
Requires-Dist: scikit-image
|
|
22
|
+
Requires-Dist: dask_image
|
|
23
|
+
Requires-Dist: pandas
|
|
24
|
+
Requires-Dist: pillow
|
|
25
|
+
Requires-Dist: xhistogram
|
|
26
|
+
Requires-Dist: flox
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest; extra == "dev"
|
|
29
|
+
Requires-Dist: black; extra == "dev"
|
|
30
|
+
Requires-Dist: flake8; extra == "dev"
|
|
31
|
+
Provides-Extra: viz
|
|
32
|
+
Requires-Dist: matplotlib; extra == "viz"
|
|
33
|
+
Requires-Dist: cartopy; extra == "viz"
|
|
34
|
+
Provides-Extra: jax
|
|
35
|
+
Requires-Dist: jax; extra == "jax"
|
|
36
|
+
Requires-Dist: jaxlib; extra == "jax"
|
|
37
|
+
Provides-Extra: full
|
|
38
|
+
Requires-Dist: matplotlib; extra == "full"
|
|
39
|
+
Requires-Dist: cartopy; extra == "full"
|
|
40
|
+
Requires-Dist: jax; extra == "full"
|
|
41
|
+
Requires-Dist: jaxlib; extra == "full"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+

|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
Marine Extremes Python Package
|
|
48
|
+
==============================
|
|
49
|
+
|
|
50
|
+
Efficient & scalable Marine Extremes detection, identification, & tracking for Exascale Climate Data.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
### Data Pre-processing
|
|
55
|
+
**Detrending & Anomaly Detection**:
|
|
56
|
+
- Removes trend and seasonal cycle using a 6+ coefficient model (mean, annual & semi-annual harmonics, and arbitrary polynomial trends).
|
|
57
|
+
- (Optional) Normalises anomalies using a 30-day rolling standard deviation.
|
|
58
|
+
- Identifies extreme events based on a global-in-time percentile threshold.
|
|
59
|
+
- Utilises `dask` for efficient parallel computation and scaling to very large spatio-temporal datasets.
|
|
60
|
+
- Performance/Scaling Test: 100 years of daily 0.25° resolution data with 128 cores...
|
|
61
|
+
- Takes ~5 wall-minutes per _century_
|
|
62
|
+
- Requires only 1 Gb memory per core (when `dask`-backed data has chunks of 25 days)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### Object Detection & Tracking
|
|
66
|
+
**Object Detection**:
|
|
67
|
+
- Implements efficient algorithms for object detection in 2D geographical data.
|
|
68
|
+
- Fully-parallelised workflow built on `dask` for extremely fast & larger-than-memory computation.
|
|
69
|
+
- Uses morphological opening & closing to fill small holes and gaps in binary features.
|
|
70
|
+
- Filters out small objects based on area thresholds.
|
|
71
|
+
- Identifies and labels connected regions in binary data representing arbitrary events (e.g. SST or SSS extrema, tracer presence, eddies, etc...).
|
|
72
|
+
- Performance/Scaling Test: 100 years of daily 0.25° resolution binary data with 64 cores...
|
|
73
|
+
- Takes ~5 wall-minutes per _century_
|
|
74
|
+
- Requires only 1 Gb memory per core (with `dask` chunks of 25 days)
|
|
75
|
+
|
|
76
|
+
**Object Tracking**:
|
|
77
|
+
- Implements strict event tracking conditions to avoid very few, very large objects.
|
|
78
|
+
- Permits temporal gaps (of `T_fill` days) between objects, to allow more continuous event tracking.
|
|
79
|
+
- Requires objects to overlap by at least `overlap_threshold` fraction of the smaller objects's area to be considered the same event and continue tracking with the same ID.
|
|
80
|
+
- Accounts for & keeps a history of object splitting & merging events, ensuring objects are more coherent and retain their previous identities & histories.
|
|
81
|
+
- Improves upon the splitting & merging logic of [Sun et al. (2023)](https://doi.org/10.1038/s41561-023-01325-w):
|
|
82
|
+
- _In this New Version_: Partition the child object based on the parent of the _nearest-neighbour_ cell (_not_ the nearest parent centroid).
|
|
83
|
+
- Provides much more accessible and usable tracking outputs:
|
|
84
|
+
- Tracked object properties (such as area, centroid, and any other user-defined properties) are mapped into `ID`-`time` space
|
|
85
|
+
- Details & Properties of all Merging/Splitting events are recorded.
|
|
86
|
+
- Provides other useful information that may be difficult to extract from the large `object ID field`, such as:
|
|
87
|
+
- Event presence in time
|
|
88
|
+
- Event start/end times and duration
|
|
89
|
+
- etc...
|
|
90
|
+
- Performance/Scaling Test: 100 years of daily 0.25° resolution binary data with 64 cores...
|
|
91
|
+
- Takes ~8 wall-minutes per decade (cf. _Old_ Method, i.e. _without_ merge-split-tracking, time-gap filling, overlap-thresholding, et al., but here updated to leverage `dask`, now takes 1 wall-minute per decade!)
|
|
92
|
+
- Requires only ~2 Gb memory per core (wwith `dask` chunks of 25 days)
|
|
93
|
+
|
|
94
|
+
### Visualisation
|
|
95
|
+
**Plotting**:
|
|
96
|
+
- Provides a few helper functions to create pretty plots, wrapped subplots, and animations (e.g. below).
|
|
97
|
+
|
|
98
|
+
cf. Old Method vs. New Tracking Algorithms (Centroidal & Nearest-Neighbour Partitioning):
|
|
99
|
+
|
|
100
|
+
https://github.com/user-attachments/assets/9936fb43-f7a3-461e-aa82-9beb2826619c
|
|
101
|
+
|
|
102
|
+
https://github.com/user-attachments/assets/462acafd-b2af-467c-b2c5-efec28ed24f1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
## Usage
|
|
107
|
+
|
|
108
|
+
### 1. Pre-process SST Data: cf. `01_preprocess_extremes.ipynb`
|
|
109
|
+
```python
|
|
110
|
+
import xarray as xr
|
|
111
|
+
import dask
|
|
112
|
+
import marEx
|
|
113
|
+
|
|
114
|
+
# Load SST data & rechunk for optimal processing
|
|
115
|
+
file_name = 'path/to/sst/data'
|
|
116
|
+
sst = xr.open_dataset(file_name, chunks={'time':500}).sst
|
|
117
|
+
|
|
118
|
+
# Process Data
|
|
119
|
+
extremes_ds = hot.preprocess_data(sst, threshold_percentile=95)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The resulting xarray dataset `extremes_ds` will have the following structure & entries:
|
|
123
|
+
```
|
|
124
|
+
xarray.Dataset
|
|
125
|
+
Dimensions: (lat, lon, time)
|
|
126
|
+
Coordinates:
|
|
127
|
+
lat (lat)
|
|
128
|
+
lon (lon)
|
|
129
|
+
time (time)
|
|
130
|
+
Data variables:
|
|
131
|
+
dat_detrend (time, lat, lon) float64 dask.array
|
|
132
|
+
mask (lat, lon) bool dask.array
|
|
133
|
+
extreme_events (time, lat, lon) bool dask.array
|
|
134
|
+
```
|
|
135
|
+
where `dat_detrend` is the detrended SST data, `mask` is the deduced land-sea mask, and `extreme_events` is the binary field locating extreme events. Additionally, the STD-renormalised anomalies, `extreme_events_stn`, will be output if `normalise=True` is set in `preprocess_data()`.
|
|
136
|
+
|
|
137
|
+
Optional arguments for `marEx.preprocess_data()` include:
|
|
138
|
+
- `std_normalise`: Whether to normalise the anomalies using a 30-day rolling standard deviation. Default is `False`.
|
|
139
|
+
- `threshold_percentile`: The percentile threshold for extreme event detection. Default is `95`.
|
|
140
|
+
- `detrend_orders`: List of polynomial orders for detrending. Default is `[1]`, i.e. 1st order (linear) detrend. `[1,2]` e.g. would use a linear+quadratic detrending.
|
|
141
|
+
- `dimensions`: The names of the time, latitude, and longitude dimensions in the data array. Default is `('time', 'lat', 'lon')`.
|
|
142
|
+
- `dask_chunks`: The chunk size for the output dataset. Default is `{'time': 25}`.
|
|
143
|
+
|
|
144
|
+
See, e.g. `./examples/unstructured data/01_preprocess_extremes.ipynb` for a detailed example of pre-processing on an _unstructured_ grid.
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
### 2. Identify & Track Marine Heatwaves: cf. `02_id_track_events.ipynb`
|
|
148
|
+
```python
|
|
149
|
+
import xarray as xr
|
|
150
|
+
import dask
|
|
151
|
+
import marEx
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Load Pre-processed Data
|
|
155
|
+
file_name = 'path/to/binary/extreme/data'
|
|
156
|
+
chunk_size = {'time': 25, 'lat': -1, 'lon': -1}
|
|
157
|
+
ds_hot = xr.open_dataset(file_name, chunks=chunk_size)
|
|
158
|
+
|
|
159
|
+
# Extract Extreme Binary Features and Modify Mask
|
|
160
|
+
extreme_bin = ds_hot.dat_stn
|
|
161
|
+
mask = ds_hot.mask.where((ds_hot.lat < 85) & (ds_hot.lat > -90), other=False)
|
|
162
|
+
|
|
163
|
+
# ID, Track, & Merge
|
|
164
|
+
tracker = marEx.tracker(extreme_bin, mask, area_filter_quartile=0.5, R_fill=8, T_fill=2, allow_merging=True, overlap_threshold=0.5, nn_partitioning = True)
|
|
165
|
+
extreme_events_ds, merges_ds = tracker.run(return_merges=True)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The resulting xarray dataset `extreme_events_ds` will have the following structure & entries:
|
|
169
|
+
```
|
|
170
|
+
xarray.Dataset
|
|
171
|
+
Dimensions: (lat, lon, time, ID, component, sibling_ID)
|
|
172
|
+
Coordinates:
|
|
173
|
+
lat (lat)
|
|
174
|
+
lon (lon)
|
|
175
|
+
time (time)
|
|
176
|
+
ID (ID)
|
|
177
|
+
Data variables:
|
|
178
|
+
ID_field (time, lat, lon) int32 dask.array
|
|
179
|
+
global_ID (time, ID) int32 ndarray
|
|
180
|
+
area (time, ID) float32 ndarray
|
|
181
|
+
centroid (component, time, ID) float32 ndarray
|
|
182
|
+
presence (time, ID) bool ndarray
|
|
183
|
+
time_start (ID) datetime64 ndarray
|
|
184
|
+
time_end (ID) datetime64 ndarray
|
|
185
|
+
merge_ledger (time, ID, sibling_ID) int32 ndarray
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
where
|
|
189
|
+
- `ID_field` is the binary field of tracked events,
|
|
190
|
+
- `global_ID` is the unique ID of each object. `global_ID.sel(ID=10)` tells you the corresponding mapped `original_id` of event ID 10 at every time,
|
|
191
|
+
- `area` is the area of each event as a function of time,
|
|
192
|
+
- `centroid` is the (x,y) centroid of each event as a function of time,
|
|
193
|
+
- `presence` indicates the presence of each event at each time (anywhere in space),
|
|
194
|
+
- `time_start` and `time_end` are the start and end times of each event,
|
|
195
|
+
- `merge_ledger` gives the sibling IDs (matching `ID_field`) of each merging event. Values of `-1` indicate no merging event occurred.
|
|
196
|
+
|
|
197
|
+
Additionally, if running with `return_merges=True`, the resulting xarray dataset `merges_ds` will have the following structure & entries:
|
|
198
|
+
```
|
|
199
|
+
xarray.Dataset
|
|
200
|
+
Dimensions: (merge_ID, parent_idx, child_idx)
|
|
201
|
+
Data variables:
|
|
202
|
+
parent_IDs (merge_ID, parent_idx) int32 ndarray
|
|
203
|
+
child_IDs (merge_ID, child_idx) int32 ndarray
|
|
204
|
+
overlap_areas (merge_ID, parent_idx) int32 ndarray
|
|
205
|
+
merge_time (merge_ID) datetime64 ndarray
|
|
206
|
+
n_parents (merge_ID) int8 ndarray
|
|
207
|
+
n_children (merge_ID) int8 ndarray
|
|
208
|
+
```
|
|
209
|
+
where
|
|
210
|
+
- `parent_IDs` and `child_IDs` are the _original_ parent and child IDs of each merging event,
|
|
211
|
+
- `overlap_areas` is the area of overlap between the parent and child objects in each merging event,
|
|
212
|
+
- `merge_time` is the time of each merging event,
|
|
213
|
+
- `n_parents` and `n_children` are the number of parent and child objects participating in each merging event.
|
|
214
|
+
|
|
215
|
+
Arguments for `marEx.tracker()` include:
|
|
216
|
+
- `data_bin`: The binary field of events to group & label. _Must represent an underlying `dask` array_.
|
|
217
|
+
- `mask`: The land-sea mask to apply to the binary field, indicating points to keep.
|
|
218
|
+
- `area_filter_quartile`: The fraction of the smallest objects to discard, i.e. the quantile defining the smallest area object retained.
|
|
219
|
+
- `R_fill`: The size of the structuring element used in morphological opening & closing, relating to the largest hole that can be filled. In units of pixels.
|
|
220
|
+
- `T_fill`: The permissible temporal gap between objects for tracking continuity to be maintained. Default is `2` days.
|
|
221
|
+
- `allow_merging`:
|
|
222
|
+
- `True`: (Default) Apply splitting & merging criteria, track merge events, and maintain original identities of merged objects across time.
|
|
223
|
+
- `False`: Classical `ndmeasure.label` with simple time connectivity, i.e. Scannell et al.
|
|
224
|
+
- `nn_partitioning`:
|
|
225
|
+
- `True`: (Default) Implement a better partitioning of merged child objects _based on closest parent cell_.
|
|
226
|
+
- `False`: Use the _parent centroids_ to determine partitioning between new child objects, i.e. Di Sun & Bohai Zhang 2023. N.B.: This has major problems with small merging objects suddenly obtaining unrealistically-large (and often disjoint) fractions of the larger object.
|
|
227
|
+
- `overlap_threshold`: The fraction of the smaller object's area that must overlap with the larger object's area to be considered the same event and continue tracking with the same ID. Default is `0.5`.
|
|
228
|
+
- `timedim`, `xdim`, `ydim`: The names of the time, latitude, and longitude dimensions in the data array. Default is `('time', 'lat', 'lon')`.
|
|
229
|
+
|
|
230
|
+
See, e.g. `./examples/unstructured data/02_id_track_events.ipynb` for a detailed example of identification, tracking, & merging on an _unstructured_ grid.
|
|
231
|
+
|
|
232
|
+
## Installation
|
|
233
|
+
|
|
234
|
+
**PyPI**
|
|
235
|
+
|
|
236
|
+
To install the full package, run: `pip install marEx[full]`
|
|
237
|
+
Note: JAX may be difficult to install on some systems. If you encounter issues, you can install without JAX support by running: `pip install marEx`
|
|
238
|
+
|
|
239
|
+
**GitHub**
|
|
240
|
+
|
|
241
|
+
1. Clone `marEx`: `git clone https://github.com/wienkers/marEx.git`
|
|
242
|
+
2. Change to the parent directory of `marEx`
|
|
243
|
+
3. Install `marEx` with `pip install -e ./`marEx`.
|
|
244
|
+
This will allow changes you make locally, to be reflected when you import the package in Python
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
Please contact [Aaron Wienkers](mailto:aaron.wienkers@gmail.com) with any questions, comments, issues, or bugs.
|
marex-2.0.0/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
Marine Extremes Python Package
|
|
6
|
+
==============================
|
|
7
|
+
|
|
8
|
+
Efficient & scalable Marine Extremes detection, identification, & tracking for Exascale Climate Data.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
### Data Pre-processing
|
|
13
|
+
**Detrending & Anomaly Detection**:
|
|
14
|
+
- Removes trend and seasonal cycle using a 6+ coefficient model (mean, annual & semi-annual harmonics, and arbitrary polynomial trends).
|
|
15
|
+
- (Optional) Normalises anomalies using a 30-day rolling standard deviation.
|
|
16
|
+
- Identifies extreme events based on a global-in-time percentile threshold.
|
|
17
|
+
- Utilises `dask` for efficient parallel computation and scaling to very large spatio-temporal datasets.
|
|
18
|
+
- Performance/Scaling Test: 100 years of daily 0.25° resolution data with 128 cores...
|
|
19
|
+
- Takes ~5 wall-minutes per _century_
|
|
20
|
+
- Requires only 1 Gb memory per core (when `dask`-backed data has chunks of 25 days)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
### Object Detection & Tracking
|
|
24
|
+
**Object Detection**:
|
|
25
|
+
- Implements efficient algorithms for object detection in 2D geographical data.
|
|
26
|
+
- Fully-parallelised workflow built on `dask` for extremely fast & larger-than-memory computation.
|
|
27
|
+
- Uses morphological opening & closing to fill small holes and gaps in binary features.
|
|
28
|
+
- Filters out small objects based on area thresholds.
|
|
29
|
+
- Identifies and labels connected regions in binary data representing arbitrary events (e.g. SST or SSS extrema, tracer presence, eddies, etc...).
|
|
30
|
+
- Performance/Scaling Test: 100 years of daily 0.25° resolution binary data with 64 cores...
|
|
31
|
+
- Takes ~5 wall-minutes per _century_
|
|
32
|
+
- Requires only 1 Gb memory per core (with `dask` chunks of 25 days)
|
|
33
|
+
|
|
34
|
+
**Object Tracking**:
|
|
35
|
+
- Implements strict event tracking conditions to avoid very few, very large objects.
|
|
36
|
+
- Permits temporal gaps (of `T_fill` days) between objects, to allow more continuous event tracking.
|
|
37
|
+
- Requires objects to overlap by at least `overlap_threshold` fraction of the smaller objects's area to be considered the same event and continue tracking with the same ID.
|
|
38
|
+
- Accounts for & keeps a history of object splitting & merging events, ensuring objects are more coherent and retain their previous identities & histories.
|
|
39
|
+
- Improves upon the splitting & merging logic of [Sun et al. (2023)](https://doi.org/10.1038/s41561-023-01325-w):
|
|
40
|
+
- _In this New Version_: Partition the child object based on the parent of the _nearest-neighbour_ cell (_not_ the nearest parent centroid).
|
|
41
|
+
- Provides much more accessible and usable tracking outputs:
|
|
42
|
+
- Tracked object properties (such as area, centroid, and any other user-defined properties) are mapped into `ID`-`time` space
|
|
43
|
+
- Details & Properties of all Merging/Splitting events are recorded.
|
|
44
|
+
- Provides other useful information that may be difficult to extract from the large `object ID field`, such as:
|
|
45
|
+
- Event presence in time
|
|
46
|
+
- Event start/end times and duration
|
|
47
|
+
- etc...
|
|
48
|
+
- Performance/Scaling Test: 100 years of daily 0.25° resolution binary data with 64 cores...
|
|
49
|
+
- Takes ~8 wall-minutes per decade (cf. _Old_ Method, i.e. _without_ merge-split-tracking, time-gap filling, overlap-thresholding, et al., but here updated to leverage `dask`, now takes 1 wall-minute per decade!)
|
|
50
|
+
- Requires only ~2 Gb memory per core (wwith `dask` chunks of 25 days)
|
|
51
|
+
|
|
52
|
+
### Visualisation
|
|
53
|
+
**Plotting**:
|
|
54
|
+
- Provides a few helper functions to create pretty plots, wrapped subplots, and animations (e.g. below).
|
|
55
|
+
|
|
56
|
+
cf. Old Method vs. New Tracking Algorithms (Centroidal & Nearest-Neighbour Partitioning):
|
|
57
|
+
|
|
58
|
+
https://github.com/user-attachments/assets/9936fb43-f7a3-461e-aa82-9beb2826619c
|
|
59
|
+
|
|
60
|
+
https://github.com/user-attachments/assets/462acafd-b2af-467c-b2c5-efec28ed24f1
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
## Usage
|
|
65
|
+
|
|
66
|
+
### 1. Pre-process SST Data: cf. `01_preprocess_extremes.ipynb`
|
|
67
|
+
```python
|
|
68
|
+
import xarray as xr
|
|
69
|
+
import dask
|
|
70
|
+
import marEx
|
|
71
|
+
|
|
72
|
+
# Load SST data & rechunk for optimal processing
|
|
73
|
+
file_name = 'path/to/sst/data'
|
|
74
|
+
sst = xr.open_dataset(file_name, chunks={'time':500}).sst
|
|
75
|
+
|
|
76
|
+
# Process Data
|
|
77
|
+
extremes_ds = hot.preprocess_data(sst, threshold_percentile=95)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The resulting xarray dataset `extremes_ds` will have the following structure & entries:
|
|
81
|
+
```
|
|
82
|
+
xarray.Dataset
|
|
83
|
+
Dimensions: (lat, lon, time)
|
|
84
|
+
Coordinates:
|
|
85
|
+
lat (lat)
|
|
86
|
+
lon (lon)
|
|
87
|
+
time (time)
|
|
88
|
+
Data variables:
|
|
89
|
+
dat_detrend (time, lat, lon) float64 dask.array
|
|
90
|
+
mask (lat, lon) bool dask.array
|
|
91
|
+
extreme_events (time, lat, lon) bool dask.array
|
|
92
|
+
```
|
|
93
|
+
where `dat_detrend` is the detrended SST data, `mask` is the deduced land-sea mask, and `extreme_events` is the binary field locating extreme events. Additionally, the STD-renormalised anomalies, `extreme_events_stn`, will be output if `normalise=True` is set in `preprocess_data()`.
|
|
94
|
+
|
|
95
|
+
Optional arguments for `marEx.preprocess_data()` include:
|
|
96
|
+
- `std_normalise`: Whether to normalise the anomalies using a 30-day rolling standard deviation. Default is `False`.
|
|
97
|
+
- `threshold_percentile`: The percentile threshold for extreme event detection. Default is `95`.
|
|
98
|
+
- `detrend_orders`: List of polynomial orders for detrending. Default is `[1]`, i.e. 1st order (linear) detrend. `[1,2]` e.g. would use a linear+quadratic detrending.
|
|
99
|
+
- `dimensions`: The names of the time, latitude, and longitude dimensions in the data array. Default is `('time', 'lat', 'lon')`.
|
|
100
|
+
- `dask_chunks`: The chunk size for the output dataset. Default is `{'time': 25}`.
|
|
101
|
+
|
|
102
|
+
See, e.g. `./examples/unstructured data/01_preprocess_extremes.ipynb` for a detailed example of pre-processing on an _unstructured_ grid.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
### 2. Identify & Track Marine Heatwaves: cf. `02_id_track_events.ipynb`
|
|
106
|
+
```python
|
|
107
|
+
import xarray as xr
|
|
108
|
+
import dask
|
|
109
|
+
import marEx
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# Load Pre-processed Data
|
|
113
|
+
file_name = 'path/to/binary/extreme/data'
|
|
114
|
+
chunk_size = {'time': 25, 'lat': -1, 'lon': -1}
|
|
115
|
+
ds_hot = xr.open_dataset(file_name, chunks=chunk_size)
|
|
116
|
+
|
|
117
|
+
# Extract Extreme Binary Features and Modify Mask
|
|
118
|
+
extreme_bin = ds_hot.dat_stn
|
|
119
|
+
mask = ds_hot.mask.where((ds_hot.lat < 85) & (ds_hot.lat > -90), other=False)
|
|
120
|
+
|
|
121
|
+
# ID, Track, & Merge
|
|
122
|
+
tracker = marEx.tracker(extreme_bin, mask, area_filter_quartile=0.5, R_fill=8, T_fill=2, allow_merging=True, overlap_threshold=0.5, nn_partitioning = True)
|
|
123
|
+
extreme_events_ds, merges_ds = tracker.run(return_merges=True)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
The resulting xarray dataset `extreme_events_ds` will have the following structure & entries:
|
|
127
|
+
```
|
|
128
|
+
xarray.Dataset
|
|
129
|
+
Dimensions: (lat, lon, time, ID, component, sibling_ID)
|
|
130
|
+
Coordinates:
|
|
131
|
+
lat (lat)
|
|
132
|
+
lon (lon)
|
|
133
|
+
time (time)
|
|
134
|
+
ID (ID)
|
|
135
|
+
Data variables:
|
|
136
|
+
ID_field (time, lat, lon) int32 dask.array
|
|
137
|
+
global_ID (time, ID) int32 ndarray
|
|
138
|
+
area (time, ID) float32 ndarray
|
|
139
|
+
centroid (component, time, ID) float32 ndarray
|
|
140
|
+
presence (time, ID) bool ndarray
|
|
141
|
+
time_start (ID) datetime64 ndarray
|
|
142
|
+
time_end (ID) datetime64 ndarray
|
|
143
|
+
merge_ledger (time, ID, sibling_ID) int32 ndarray
|
|
144
|
+
|
|
145
|
+
```
|
|
146
|
+
where
|
|
147
|
+
- `ID_field` is the binary field of tracked events,
|
|
148
|
+
- `global_ID` is the unique ID of each object. `global_ID.sel(ID=10)` tells you the corresponding mapped `original_id` of event ID 10 at every time,
|
|
149
|
+
- `area` is the area of each event as a function of time,
|
|
150
|
+
- `centroid` is the (x,y) centroid of each event as a function of time,
|
|
151
|
+
- `presence` indicates the presence of each event at each time (anywhere in space),
|
|
152
|
+
- `time_start` and `time_end` are the start and end times of each event,
|
|
153
|
+
- `merge_ledger` gives the sibling IDs (matching `ID_field`) of each merging event. Values of `-1` indicate no merging event occurred.
|
|
154
|
+
|
|
155
|
+
Additionally, if running with `return_merges=True`, the resulting xarray dataset `merges_ds` will have the following structure & entries:
|
|
156
|
+
```
|
|
157
|
+
xarray.Dataset
|
|
158
|
+
Dimensions: (merge_ID, parent_idx, child_idx)
|
|
159
|
+
Data variables:
|
|
160
|
+
parent_IDs (merge_ID, parent_idx) int32 ndarray
|
|
161
|
+
child_IDs (merge_ID, child_idx) int32 ndarray
|
|
162
|
+
overlap_areas (merge_ID, parent_idx) int32 ndarray
|
|
163
|
+
merge_time (merge_ID) datetime64 ndarray
|
|
164
|
+
n_parents (merge_ID) int8 ndarray
|
|
165
|
+
n_children (merge_ID) int8 ndarray
|
|
166
|
+
```
|
|
167
|
+
where
|
|
168
|
+
- `parent_IDs` and `child_IDs` are the _original_ parent and child IDs of each merging event,
|
|
169
|
+
- `overlap_areas` is the area of overlap between the parent and child objects in each merging event,
|
|
170
|
+
- `merge_time` is the time of each merging event,
|
|
171
|
+
- `n_parents` and `n_children` are the number of parent and child objects participating in each merging event.
|
|
172
|
+
|
|
173
|
+
Arguments for `marEx.tracker()` include:
|
|
174
|
+
- `data_bin`: The binary field of events to group & label. _Must represent an underlying `dask` array_.
|
|
175
|
+
- `mask`: The land-sea mask to apply to the binary field, indicating points to keep.
|
|
176
|
+
- `area_filter_quartile`: The fraction of the smallest objects to discard, i.e. the quantile defining the smallest area object retained.
|
|
177
|
+
- `R_fill`: The size of the structuring element used in morphological opening & closing, relating to the largest hole that can be filled. In units of pixels.
|
|
178
|
+
- `T_fill`: The permissible temporal gap between objects for tracking continuity to be maintained. Default is `2` days.
|
|
179
|
+
- `allow_merging`:
|
|
180
|
+
- `True`: (Default) Apply splitting & merging criteria, track merge events, and maintain original identities of merged objects across time.
|
|
181
|
+
- `False`: Classical `ndmeasure.label` with simple time connectivity, i.e. Scannell et al.
|
|
182
|
+
- `nn_partitioning`:
|
|
183
|
+
- `True`: (Default) Implement a better partitioning of merged child objects _based on closest parent cell_.
|
|
184
|
+
- `False`: Use the _parent centroids_ to determine partitioning between new child objects, i.e. Di Sun & Bohai Zhang 2023. N.B.: This has major problems with small merging objects suddenly obtaining unrealistically-large (and often disjoint) fractions of the larger object.
|
|
185
|
+
- `overlap_threshold`: The fraction of the smaller object's area that must overlap with the larger object's area to be considered the same event and continue tracking with the same ID. Default is `0.5`.
|
|
186
|
+
- `timedim`, `xdim`, `ydim`: The names of the time, latitude, and longitude dimensions in the data array. Default is `('time', 'lat', 'lon')`.
|
|
187
|
+
|
|
188
|
+
See, e.g. `./examples/unstructured data/02_id_track_events.ipynb` for a detailed example of identification, tracking, & merging on an _unstructured_ grid.
|
|
189
|
+
|
|
190
|
+
## Installation
|
|
191
|
+
|
|
192
|
+
**PyPI**
|
|
193
|
+
|
|
194
|
+
To install the full package, run: `pip install marEx[full]`
|
|
195
|
+
Note: JAX may be difficult to install on some systems. If you encounter issues, you can install without JAX support by running: `pip install marEx`
|
|
196
|
+
|
|
197
|
+
**GitHub**
|
|
198
|
+
|
|
199
|
+
1. Clone `marEx`: `git clone https://github.com/wienkers/marEx.git`
|
|
200
|
+
2. Change to the parent directory of `marEx`
|
|
201
|
+
3. Install `marEx` with `pip install -e ./`marEx`.
|
|
202
|
+
This will allow changes you make locally, to be reflected when you import the package in Python
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
Please contact [Aaron Wienkers](mailto:aaron.wienkers@gmail.com) with any questions, comments, issues, or bugs.
|