marEx 4.1__tar.gz → 4.1.2__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.
Files changed (59) hide show
  1. {marex-4.1 → marex-4.1.2}/PKG-INFO +2 -2
  2. {marex-4.1 → marex-4.1.2}/marEx/detect.py +8 -3
  3. {marex-4.1 → marex-4.1.2}/marEx/track.py +5 -1
  4. {marex-4.1 → marex-4.1.2}/marEx.egg-info/PKG-INFO +2 -2
  5. {marex-4.1 → marex-4.1.2}/marEx.egg-info/requires.txt +1 -1
  6. {marex-4.1 → marex-4.1.2}/pyproject.toml +1 -1
  7. {marex-4.1 → marex-4.1.2}/tests/test_gridded_preprocessing.py +58 -0
  8. {marex-4.1 → marex-4.1.2}/.codecov.yml +0 -0
  9. {marex-4.1 → marex-4.1.2}/.github/workflows/README.md +0 -0
  10. {marex-4.1 → marex-4.1.2}/.github/workflows/ci.yml +0 -0
  11. {marex-4.1 → marex-4.1.2}/.github/workflows/release.yml +0 -0
  12. {marex-4.1 → marex-4.1.2}/.github/workflows/validate.py +0 -0
  13. {marex-4.1 → marex-4.1.2}/.readthedocs.yaml +0 -0
  14. {marex-4.1 → marex-4.1.2}/CITATION.cff +0 -0
  15. {marex-4.1 → marex-4.1.2}/CONTRIBUTING.md +0 -0
  16. {marex-4.1 → marex-4.1.2}/MANIFEST.in +0 -0
  17. {marex-4.1 → marex-4.1.2}/README.md +0 -0
  18. {marex-4.1 → marex-4.1.2}/examples/batch jobs/run_detect.py +0 -0
  19. {marex-4.1 → marex-4.1.2}/examples/batch jobs/run_track.py +0 -0
  20. {marex-4.1 → marex-4.1.2}/examples/batch jobs/submit_track.sh +0 -0
  21. {marex-4.1 → marex-4.1.2}/examples/gridded data/01_preprocess_extremes.ipynb +0 -0
  22. {marex-4.1 → marex-4.1.2}/examples/gridded data/02_id_track_events.ipynb +0 -0
  23. {marex-4.1 → marex-4.1.2}/examples/gridded data/03_visualise_events.ipynb +0 -0
  24. {marex-4.1 → marex-4.1.2}/examples/regional data/01_preprocess_extremes.ipynb +0 -0
  25. {marex-4.1 → marex-4.1.2}/examples/regional data/02_id_track_events.ipynb +0 -0
  26. {marex-4.1 → marex-4.1.2}/examples/regional data/03_visualise_events.ipynb +0 -0
  27. {marex-4.1 → marex-4.1.2}/examples/unstructured data/01_preprocess_extremes.ipynb +0 -0
  28. {marex-4.1 → marex-4.1.2}/examples/unstructured data/02_id_track_events.ipynb +0 -0
  29. {marex-4.1 → marex-4.1.2}/examples/unstructured data/03_visualise_events.ipynb +0 -0
  30. {marex-4.1 → marex-4.1.2}/marEx/__init__.py +0 -0
  31. {marex-4.1 → marex-4.1.2}/marEx/_coverage_init.py +0 -0
  32. {marex-4.1 → marex-4.1.2}/marEx/_dependencies.py +0 -0
  33. {marex-4.1 → marex-4.1.2}/marEx/exceptions.py +0 -0
  34. {marex-4.1 → marex-4.1.2}/marEx/helper.py +0 -0
  35. {marex-4.1 → marex-4.1.2}/marEx/logging_config.py +0 -0
  36. {marex-4.1 → marex-4.1.2}/marEx/plotX/__init__.py +0 -0
  37. {marex-4.1 → marex-4.1.2}/marEx/plotX/base.py +0 -0
  38. {marex-4.1 → marex-4.1.2}/marEx/plotX/gridded.py +0 -0
  39. {marex-4.1 → marex-4.1.2}/marEx/plotX/unstructured.py +0 -0
  40. {marex-4.1 → marex-4.1.2}/marEx.egg-info/SOURCES.txt +0 -0
  41. {marex-4.1 → marex-4.1.2}/marEx.egg-info/dependency_links.txt +0 -0
  42. {marex-4.1 → marex-4.1.2}/marEx.egg-info/top_level.txt +0 -0
  43. {marex-4.1 → marex-4.1.2}/setup.cfg +0 -0
  44. {marex-4.1 → marex-4.1.2}/tests/__init__.py +0 -0
  45. {marex-4.1 → marex-4.1.2}/tests/conftest.py +0 -0
  46. {marex-4.1 → marex-4.1.2}/tests/coverage_config.py +0 -0
  47. {marex-4.1 → marex-4.1.2}/tests/make_test_data.ipynb +0 -0
  48. {marex-4.1 → marex-4.1.2}/tests/test_detect_detrending.py +0 -0
  49. {marex-4.1 → marex-4.1.2}/tests/test_detect_helpers.py +0 -0
  50. {marex-4.1 → marex-4.1.2}/tests/test_error_handling.py +0 -0
  51. {marex-4.1 → marex-4.1.2}/tests/test_exceptions.py +0 -0
  52. {marex-4.1 → marex-4.1.2}/tests/test_gridded_tracking.py +0 -0
  53. {marex-4.1 → marex-4.1.2}/tests/test_integration.py +0 -0
  54. {marex-4.1 → marex-4.1.2}/tests/test_logging_system.py +0 -0
  55. {marex-4.1 → marex-4.1.2}/tests/test_plotx.py +0 -0
  56. {marex-4.1 → marex-4.1.2}/tests/test_track_edge_cases.py +0 -0
  57. {marex-4.1 → marex-4.1.2}/tests/test_track_helpers.py +0 -0
  58. {marex-4.1 → marex-4.1.2}/tests/test_unstructured_preprocessing.py +0 -0
  59. {marex-4.1 → marex-4.1.2}/tests/test_unstructured_tracking.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: marEx
3
- Version: 4.1
3
+ Version: 4.1.2
4
4
  Summary: Marine Extremes Detection and Tracking
5
5
  Author-email: Aaron Wienkers <aaron@wienkers.com>
6
6
  Project-URL: Homepage, https://github.com/wienkers/marEx
@@ -21,7 +21,7 @@ Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  Requires-Dist: numpy
23
23
  Requires-Dist: xarray[complete]
24
- Requires-Dist: dask[complete]==2025.3.0
24
+ Requires-Dist: dask[complete]>=2025.9
25
25
  Requires-Dist: netcdf4
26
26
  Requires-Dist: scipy
27
27
  Requires-Dist: scikit-image
@@ -819,9 +819,14 @@ def preprocess_data(
819
819
  ds["mask"] = ds.mask.compute()
820
820
  ds["dat_anomaly"] = fix_dask_tuple_array(ds.dat_anomaly)
821
821
 
822
- # Patch for same dask-Zarr bug:
823
- ds[coordinates["x"]] = ds[coordinates["x"]].compute()
824
- ds[coordinates["y"]] = ds[coordinates["y"]].compute()
822
+ # Patch for same dask-Zarr bug: materialise *any* remaining Dask-backed coordinates.
823
+ # Auxiliary (non-index) coordinates (e.g. gridded `x`/`y` or unstructured `lon`/`lat`)
824
+ # become Dask-backed via `.chunk()` and otherwise retain tuple chunk references that
825
+ # break distributed serialisation on save. This is seen as an
826
+ # "AttributeError: 'tuple' object has no attribute 'size'" from dask.array.store.
827
+ for coord_name in list(ds.coords):
828
+ if is_dask_collection(ds[coord_name].data):
829
+ ds[coord_name] = ds[coord_name].compute()
825
830
  if "neighbours" in ds.data_vars:
826
831
  ds["neighbours"] = ds.neighbours.compute()
827
832
  if "cell_areas" in ds.data_vars:
@@ -1612,7 +1612,11 @@ class tracker:
1612
1612
  # Generate structuring element (disk-shaped)
1613
1613
  y, x = np.ogrid[-R_fill : R_fill + 1, -R_fill : R_fill + 1]
1614
1614
  r = x**2 + y**2
1615
- diameter = 2 * R_fill
1615
+ # Pad/trim width for the closing+opening morphology. Closing has reach 2*R_fill and the
1616
+ # subsequent opening another 2*R_fill, so the total reach is 4*R_fill. Padding by only
1617
+ # 2*R_fill (the old value) leaves an under-padded artifact in the 2R-4R band around the
1618
+ # periodic-longitude seam. (Matches the clean_up-branch EDT fill_holes, which pads 4*R_fill.)
1619
+ diameter = 4 * R_fill
1616
1620
  se_kernel = r < (R_fill**2) + 1
1617
1621
  mode = "wrap" if not self.regional_mode else "edge"
1618
1622
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: marEx
3
- Version: 4.1
3
+ Version: 4.1.2
4
4
  Summary: Marine Extremes Detection and Tracking
5
5
  Author-email: Aaron Wienkers <aaron@wienkers.com>
6
6
  Project-URL: Homepage, https://github.com/wienkers/marEx
@@ -21,7 +21,7 @@ Requires-Python: >=3.10
21
21
  Description-Content-Type: text/markdown
22
22
  Requires-Dist: numpy
23
23
  Requires-Dist: xarray[complete]
24
- Requires-Dist: dask[complete]==2025.3.0
24
+ Requires-Dist: dask[complete]>=2025.9
25
25
  Requires-Dist: netcdf4
26
26
  Requires-Dist: scipy
27
27
  Requires-Dist: scikit-image
@@ -1,6 +1,6 @@
1
1
  numpy
2
2
  xarray[complete]
3
- dask[complete]==2025.3.0
3
+ dask[complete]>=2025.9
4
4
  netcdf4
5
5
  scipy
6
6
  scikit-image
@@ -28,7 +28,7 @@ dynamic = ["version"]
28
28
  dependencies = [
29
29
  "numpy",
30
30
  "xarray[complete]",
31
- "dask[complete]==2025.3.0",
31
+ "dask[complete]>=2025.9",
32
32
  "netcdf4",
33
33
  "scipy",
34
34
  "scikit-image",
@@ -846,3 +846,61 @@ class TestGriddedPreprocessing:
846
846
  dimensions=self.dimensions,
847
847
  dask_chunks=self.dask_chunks,
848
848
  )
849
+
850
+ def test_auxiliary_coords_are_materialised(self, tmp_path):
851
+ """Regression: auxiliary (non-index) coordinates must not remain Dask-backed.
852
+
853
+ Datasets often carry auxiliary spatial coordinates (e.g. integer grid
854
+ indices ``x``/``y`` alongside ``lon``/``lat``). ``.chunk()`` turns these
855
+ into Dask arrays, and prior to the fix they survived ``preprocess_data``
856
+ un-materialised. Their lazy graphs retain tuple chunk references that
857
+ raise ``AttributeError: 'tuple' object has no attribute 'size'`` when the
858
+ result is written under a distributed scheduler (e.g. ``to_netcdf`` on a
859
+ ``LocalCluster``). ``preprocess_data`` must therefore compute every
860
+ remaining Dask-backed coordinate. This invariant is scheduler- and
861
+ Dask-version-independent, so it is checked directly here.
862
+ """
863
+ import dask
864
+ import pandas as pd
865
+
866
+ times = pd.date_range("2000-01-01", "2002-12-31", freq="D")
867
+ nlat, nlon = 4, 4
868
+ da = xr.DataArray(
869
+ np.random.default_rng(7).standard_normal((len(times), nlat, nlon)).astype(np.float32),
870
+ dims=["time", "lat", "lon"],
871
+ coords={
872
+ "time": times,
873
+ "lat": np.arange(nlat, dtype=np.float32),
874
+ "lon": np.arange(nlon, dtype=np.float32),
875
+ # Auxiliary, non-index coordinates that become Dask-backed via .chunk()
876
+ "x": ("lon", np.arange(nlon, dtype=np.int64)),
877
+ "y": ("lat", np.arange(nlat, dtype=np.int64)),
878
+ },
879
+ ).chunk({"time": -1})
880
+
881
+ # Sanity check: the aux coords are genuinely Dask-backed on the input
882
+ assert dask.base.is_dask_collection(da.coords["x"].data)
883
+ assert dask.base.is_dask_collection(da.coords["y"].data)
884
+
885
+ result = marEx.preprocess_data(
886
+ da,
887
+ method_anomaly="fixed_baseline",
888
+ method_extreme="global_extreme",
889
+ threshold_percentile=95,
890
+ dimensions={"time": "time", "x": "lon", "y": "lat"},
891
+ dask_chunks={"time": 25},
892
+ )
893
+
894
+ # No coordinate in the output may remain Dask-backed (the actual fix)
895
+ dask_coords = [name for name in result.coords if dask.base.is_dask_collection(result[name].data)]
896
+ assert dask_coords == [], f"Output coordinates still Dask-backed: {dask_coords}"
897
+
898
+ # The aux coords must still be present and carry the right values
899
+ assert "x" in result.coords and "y" in result.coords
900
+ np.testing.assert_array_equal(result["x"].values, np.arange(nlon, dtype=np.int64))
901
+ np.testing.assert_array_equal(result["y"].values, np.arange(nlat, dtype=np.int64))
902
+
903
+ # End-to-end: the result must be writable to NetCDF
904
+ out_path = tmp_path / "aux_coords_roundtrip.nc"
905
+ result.to_netcdf(out_path)
906
+ assert out_path.exists()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes