Skip to content

Commit ef3fc47

Browse files
committed
release: v0.2.5
2 parents 3856069 + 591b192 commit ef3fc47

22 files changed

+2837
-1881
lines changed

.github/workflows/pixi-locked.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Pixi Locked Environments
2+
3+
on:
4+
pull_request:
5+
push:
6+
branches:
7+
- main
8+
workflow_dispatch:
9+
10+
jobs:
11+
test:
12+
name: pixi test (locked)
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: prefix-dev/setup-pixi@v0.9.4
17+
with:
18+
cache: true
19+
environments: test
20+
- name: Install locked test environment
21+
run: pixi install --locked -e test
22+
- name: Run unit test task
23+
run: pixi run -e test test-unit
24+
25+
docs:
26+
name: pixi docs (locked)
27+
runs-on: ubuntu-latest
28+
steps:
29+
- uses: actions/checkout@v4
30+
- uses: prefix-dev/setup-pixi@v0.9.4
31+
with:
32+
cache: true
33+
environments: docs
34+
- name: Install locked docs environment
35+
run: pixi install --locked -e docs
36+
- name: Build docs task
37+
run: pixi run -e docs docs-build

CHANGELOG.md

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# Changelog
2+
3+
All notable changes are documented in this file.
4+
5+
## v0.2.5 (2026-02-15)
6+
7+
- Range: `v0.2.4..ee58532`
8+
- Snapshot commit date: 2026-02-15
9+
10+
### User impact summary
11+
12+
This change set is primarily a stability and portability update rather than a major new algorithm release.
13+
14+
#### What it means for CryoSwath usage
15+
16+
- Day-to-day processing in `l1b`, `l2`, `l3`, `l4`, `gis`, and `misc` is more robust on edge cases (empty chunks/windows, missing columns, unnamed indexes, scalar coordinates, and missing region matches).
17+
- Error handling is clearer in several paths that previously relied on assertions; failures should now be easier to diagnose in production runs.
18+
- `l2` multiprocessing is safer across platforms (`spawn` context + CPU core detection), which helps on macOS/Windows and mixed cluster environments.
19+
- `l3` chunk-time reindexing for zarr should reduce write/read issues related to non-contiguous chunk regions.
20+
- Tutorial notebooks were updated to current APIs and now have automated workflow coverage, making the tutorial path more reliable as a usage reference.
21+
22+
#### New features
23+
24+
- Pixi-first environment support and workflow hardening were added and expanded (including locked runtime solve strategy and feature-scoped tasks).
25+
- A `production` optional dependency group was introduced.
26+
- Tutorial workflow automation was expanded with Snakemake and tox entrypoints for notebook/pipeline validation.
27+
- A CRISTAL mission portability checklist was added to docs.
28+
29+
#### Compatibility notes
30+
31+
- Dependency manifests now pin `xarray < 2025.12` due to upstream regression handling in this cycle.
32+
- If your environment already uses newer `xarray`, expect to align to the pinned version before running CryoSwath reliably.
33+
34+
### Commits since v0.2.4
35+
36+
- 2025-08-31 `2629767` chore: add gitignores
37+
- 2025-08-31 `4e8cb24` chore: bump version
38+
- 2025-12-20 `6d039f5` misc: update rgi_code_translator
39+
- 2025-12-20 `ea142fe` update pyproject.toml
40+
- 2025-12-20 `d6c4bd8` misc: fix previous commit
41+
- 2026-01-24 `a8a2313` fix(misc): allow scalar dimensions in fill_missing_coords
42+
- 2026-01-28 `403658c` fix: revise download_dem
43+
- 2026-01-29 `8c55ef1` chore(l1b): polish code a bit
44+
- 2026-01-29 `296ec1a` fix(test_plots): correct label
45+
- 2026-01-29 `1239d01` fix(misc): use make_valid in load_glacier_outlines
46+
- 2026-01-29 `81be744` chore(misc): format
47+
- 2026-01-29 `a9d55a7` chore: update readme
48+
- 2026-01-29 `d87a946` chore: update docker
49+
- 2026-01-29 `075adc2` Add pixi compatibility and installation instructions
50+
- 2026-01-30 `09f7c90` fix(misc): fix index issue
51+
- 2026-01-30 `bdd8a0c` Merge pull request #52 from Tanmay-Ts/add-pixi-support
52+
- 2026-02-01 `436de23` !fix(misc): update default DEM names
53+
- 2026-02-01 `5faa58a` Merge remote-tracking branch 'origin' into develop
54+
- 2026-02-11 `117c755` chore: add opt.dep. group production
55+
- 2026-02-11 `89d1447` fix(misc): fix building paths from config
56+
- 2026-02-11 `3a94789` chore(l2): simplify code
57+
- 2026-02-11 `27c7079` fix(l3): fix handling even agg. windows
58+
- 2026-02-11 `bb4d0f4` fix(l2): guard grid chunk split for small data
59+
- 2026-02-11 `a9a61d1` fix(misc): handle default dir in ESRI conversion
60+
- 2026-02-11 `e67840b` fix(gis): robust ESRI feather target path
61+
- 2026-02-11 `9973b3f` fix(l4): guard optional filled_flag updates
62+
- 2026-02-11 `97c1f26` fix(l3): raise for unsupported joined L2 aggregation
63+
- 2026-02-11 `a39ba02` fix(l4): raise reference deviation sanity check
64+
- 2026-02-11 `bf819eb` fix(gis): accept scalar lon/lat in CRS selection
65+
- 2026-02-11 `b2ccf6c` fix(l2): replace assert-based stale cache checks
66+
- 2026-02-11 `eab85a5` fix(l2): handle unnamed index in l1b conversion
67+
- 2026-02-11 `8e0f77a` fix(l2): guard empty grid aggregation paths
68+
- 2026-02-11 `40ab2e4` fix(l1b): replace assert-only threshold checks
69+
- 2026-02-11 `ba8ea86` fix(l4): call local elevation reference helper
70+
- 2026-02-11 `f5860f6` fix(l4): use coefficient arrays in trend reconstruction
71+
- 2026-02-11 `cf15928` fix(misc): fail clearly when region lookup is empty
72+
- 2026-02-11 `4daf415` fix(l3): honor l2_type when reading cache chunks
73+
- 2026-02-11 `3d652ba` fix(gis): fail clearly when no o2 region matches
74+
- 2026-02-12 `59443cb` fix(l1b): narrow xarray timedelta patch version gate
75+
- 2026-02-12 `a48cd87` docs(cryoswath): revise RTD pages and API docstrings
76+
- 2026-02-12 `13a0e48` docs(sphinx): refine docs style and refresh README warnings
77+
- 2026-02-12 `7af912d` docs(theme): switch to pydata and clean sidebar/header
78+
- 2026-02-13 `d8ec019` docs(linkcode): resolve source refs from git metadata
79+
- 2026-02-13 `de9fda1` docs: move env warning to install pages and align manager wording
80+
- 2026-02-13 `d5e1cef` merge(main): integrate develop (docs refresh, linkcode hardening, and processing fixes)
81+
- 2026-02-14 `8c86f40` fix(l2): detect CPU cores cross-platform (refs #38)
82+
- 2026-02-14 `100418c` fix(l2): use spawn context for multiprocessing safety (refs #35)
83+
- 2026-02-14 `eadd824` fix(l3): reindex chunk time to contiguous zarr regions (refs #37)
84+
- 2026-02-14 `5ce48ec` docs: add CRISTAL portability checklist (refs #42)
85+
- 2026-02-14 `4e49632` build: document and harden pixi workflow (refs #51)
86+
- 2026-02-14 `bf771bc` refactor(l1b): clarify download status messages (refs #20)
87+
- 2026-02-14 `7b1d3b8` Pin xarray below 2025.12 across dependency manifests
88+
- 2026-02-14 `b66e084` chore: finalize xarray<2025.12 regression fix
89+
- 2026-02-14 `0761831` build(deps): align dependency policy and add py3.13 CI coverage
90+
- 2026-02-14 `a00e721` fix(test_plots): update dem_transect
91+
- 2026-02-14 `e6aec9e` fix(l2): fix missing column issue
92+
- 2026-02-14 `822a044` test(reports): rename notebooks and add Snakemake conda workflow
93+
- 2026-02-15 `7f36903` fix(tests): remove debugging helpers
94+
- 2026-02-15 `5d85e9e` chore(test): add non-failing anonymous FTP login probe
95+
- 2026-02-15 `4e9d342` fix(misc): recover stale cache backup and guard writes with lock
96+
- 2026-02-15 `3eec709` fix(l3): handle empty roll aggregations in build_dataset
97+
- 2026-02-15 `f2da694` fix(tutorial): use misc.fill_missing_coords in step-by-step notebook
98+
- 2026-02-15 `c376355` build(pixi): modernize pyproject metadata and dependency ownership
99+
- 2026-02-15 `e8929b5` fix(tutorial): call l4.append_elevation_reference in step-by-step notebook
100+
- 2026-02-15 `637377a` fix(tutorial): call l4.fill_voids in step-by-step notebook
101+
- 2026-02-15 `9905f68` build: scope pixi tasks by feature and align setup docs
102+
- 2026-02-15 `ad7410a` build(pixi): pin runtime solve strategy and add locked env workflow
103+
- 2026-02-15 `6b98cc7` fix(tutorial): replace removed misc.load_basins in process-first-swath
104+
- 2026-02-15 `8c725d4` test(tutorials): add snakemake workflow for tutorial notebooks
105+
- 2026-02-15 `1840ad0` test(pipeline): add tox entrypoint, config templates, and netrc helper tests
106+
- 2026-02-15 `5bf70af` test(pipeline): add tutorial notebook workflow and update tutorial API calls
107+
- 2026-02-15 `ee58532` merge(build): integrate codex/pixi-followup-envdocs into main

README.md

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ from waveform-level processing to gridded elevation products.
2626
- Supported Python version: **>=3.11** (regularly tested on 3.11 and 3.12).
2727
- Starting **Monday, February 16, 2026**, downloading CryoSat resources
2828
requires an **[ESA EO account](https://eoiam-idp.eo.esa.int/)**.
29+
- FTP credentials are resolved in this order:
30+
`~/.netrc` (with explicit login/password), then
31+
`CRYOSWATH_FTP_USER`/`CRYOSWATH_FTP_PASSWORD`, then legacy `config.ini [user]`
32+
`name/password` (temporary fallback).
33+
- Anonymous FTP login is no longer supported.
2934
- Install `xarray` and `zarr` together to avoid version mismatches.
3035

3136
## Dependency policy
@@ -46,8 +51,8 @@ For full setup details, see the docs:
4651
```sh
4752
git clone https://github.com/j-haacker/cryoswath.git
4853
cd cryoswath
49-
pixi install
50-
pixi run -e test pytest -q tests/test_l1b.py
54+
pixi install --locked -e test
55+
pixi run -e test test-unit
5156
```
5257

5358
For an interactive shell in the project environment:
@@ -73,18 +78,34 @@ mamba install pip
7378
pip install --editable cryoswath
7479
```
7580

76-
### Option 3: reproducible Pixi environment
81+
### Option 4: reproducible Pixi environment
7782

7883
```sh
7984
git clone https://github.com/j-haacker/cryoswath.git
8085
cd cryoswath
81-
pixi install -e test
86+
pixi install --locked -e test
8287
pixi shell -e test
8388
```
8489

8590
This uses the lock file and is the most robust option when dependency
8691
resolvers disagree.
8792

93+
### Contributor lockfile workflow
94+
95+
For regular development runs:
96+
97+
```sh
98+
pixi install --locked -e test
99+
```
100+
101+
If you change dependency manifests (`pyproject.toml` and/or `pixi.toml`):
102+
103+
```sh
104+
pixi lock
105+
pixi run -e test test-unit
106+
pixi run -e docs docs-build
107+
```
108+
88109
### Optional: Docker image
89110

90111
If local dependency resolution fails, you can use Docker:
@@ -108,6 +129,12 @@ cryoswath-init
108129
`scripts/config.ini` with your base data path. The paths can be
109130
reconfigured in `config.ini` if you use a different layout.
110131

132+
To avoid storing secrets in `config.ini`, use `~/.netrc` (preferred) or
133+
environment variables for FTP credentials and keep `config.ini` focused on
134+
paths.
135+
To create or update your `~/.netrc` entry interactively, run:
136+
`cryoswath-update-netrc`.
137+
111138
## Tutorials and documentation
112139

113140
- Main docs: [cryoswath.readthedocs.io](https://cryoswath.readthedocs.io/)
@@ -118,6 +145,33 @@ reconfigured in `config.ini` if you use a different layout.
118145
- First swath tutorial:
119146
[`scripts/tutorial__process_first_swath.ipynb`](https://github.com/j-haacker/cryoswath/blob/main/scripts/tutorial__process_first_swath.ipynb)
120147

148+
## Local testing
149+
150+
Run the full local test pipeline:
151+
152+
```sh
153+
pixi run -e test test-all
154+
```
155+
156+
Run report notebooks only:
157+
158+
```sh
159+
pixi run -e test test-notebooks
160+
```
161+
162+
Run tutorial notebooks only:
163+
164+
```sh
165+
pixi run -e test test-tutorial-notebooks
166+
```
167+
168+
If tutorials are stored outside the current checkout, set
169+
`CRYOSWATH_TUTORIAL_DIR` to the directory containing
170+
`tutorial__*.ipynb` before running this task.
171+
172+
Notebook tests may download required larger data from first-hand sources
173+
at runtime, so network availability and valid ESA credentials matter.
174+
121175
## External dependencies and data
122176

123177
CryoSwath relies on:
@@ -144,11 +198,11 @@ If you use CryoSwath, please cite:
144198
```bibtex
145199
@software{cryoswath,
146200
author = {Haacker, Jan},
147-
title = {CryoSwath: v0.2.4},
148-
month = aug,
149-
year = 2025,
201+
title = {CryoSwath: v0.2.5},
202+
month = feb,
203+
year = 2026,
150204
publisher = {Zenodo},
151-
version = {v0.2.4},
205+
version = {v0.2.5},
152206
doi = {10.5281/zenodo.17011635}
153207
}
154208
```

config.example.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[path]
2+
data = /absolute/path/to/cryoswath-project/data
3+
l1b = L1b
4+
5+
[user]
6+
# Legacy fallback only. Prefer ~/.netrc or CRYOSWATH_FTP_* env vars.
7+
name = your-esa-user
8+
password = your-esa-password

cryoswath/l3.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ def collect_chunk_names(name, node):
542542
l2_df[f"roll_{i}"].astype("i4") // window_ntimesteps
543543
)
544544
results_list = [None] * window_ntimesteps
545+
expected_stats = list(agg_func_and_meta[1].keys())
545546
for i in range(window_ntimesteps):
546547

547548
def local_closure(roll_iteration):
@@ -557,7 +558,30 @@ def local_closure(roll_iteration):
557558
results_list[i] = local_closure(i)
558559
del l2_df
559560
for i in range(window_ntimesteps):
560-
results_list[i] = results_list[i].droplevel(3, axis=0)
561+
result = results_list[i]
562+
if isinstance(result, pd.Series):
563+
if result.empty:
564+
result = pd.DataFrame(
565+
index=pd.MultiIndex.from_arrays(
566+
[
567+
np.array([], dtype="i8"),
568+
np.array([], dtype="i8"),
569+
np.array([], dtype="i8"),
570+
],
571+
names=["time_idx", "x", "y"],
572+
),
573+
columns=expected_stats,
574+
)
575+
else:
576+
result = result.unstack(level=-1)
577+
if result.index.nlevels == 4:
578+
result = result.droplevel(3, axis=0)
579+
elif result.index.nlevels != 3:
580+
raise ValueError(
581+
"Unexpected grouped result index depth in l3 aggregation: "
582+
f"{result.index.nlevels}."
583+
)
584+
results_list[i] = result.reindex(columns=expected_stats)
561585
results_list[i].index = (
562586
results_list[i]
563587
.index.set_levels(

0 commit comments

Comments
 (0)