dct-toolkit 0.4.0__tar.gz → 0.5.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.
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/.gitignore +1 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/AGENTS.md +124 -52
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/CHANGELOG.md +58 -16
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/PKG-INFO +1 -1
- dct_toolkit-0.5.0/dct_toolkit/__init__.py +100 -0
- dct_toolkit-0.5.0/dct_toolkit/_widths.py +34 -0
- dct_toolkit-0.5.0/dct_toolkit/cartesian.py +56 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/dct_toolkit/polar.py +97 -86
- dct_toolkit-0.5.0/dct_toolkit/stats.py +559 -0
- dct_toolkit-0.5.0/docs/API_REFERENCE.md +203 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/pyproject.toml +1 -1
- dct_toolkit-0.5.0/tests/test_api.py +56 -0
- dct_toolkit-0.5.0/tests/test_cartesian.py +104 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/tests/test_polar.py +38 -14
- dct_toolkit-0.5.0/tests/test_stats.py +473 -0
- dct_toolkit-0.5.0/tests/test_stats_nd.py +37 -0
- dct_toolkit-0.4.0/dct_toolkit/__init__.py +0 -63
- dct_toolkit-0.4.0/dct_toolkit/cartesian.py +0 -41
- dct_toolkit-0.4.0/dct_toolkit/stats.py +0 -300
- dct_toolkit-0.4.0/docs/API_REFERENCE.md +0 -94
- dct_toolkit-0.4.0/docs/GAP_FILLING_BASIS.md +0 -166
- dct_toolkit-0.4.0/docs/PUBLICATION_GUIDE.md +0 -76
- dct_toolkit-0.4.0/tests/test_cartesian.py +0 -48
- dct_toolkit-0.4.0/tests/test_stats.py +0 -211
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/LICENSE +0 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/README.md +0 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/dct_toolkit/core.py +0 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/dct_toolkit/gap_filling.py +0 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/docs/MATHEMATICAL_BASIS.md +0 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/examples/basic_polar.py +0 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/examples/comprehensive_demo.py +0 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/tests/test_core.py +0 -0
- {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/tests/test_gap_filling.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# AGENTS.md - DCT Toolkit Agent Guidelines
|
|
2
2
|
|
|
3
3
|
**Project**: dct-toolkit
|
|
4
|
-
**Version**: 0.
|
|
5
|
-
**Status**:
|
|
6
|
-
**Last Updated**: 2026-04-
|
|
4
|
+
**Version**: 0.5.0
|
|
5
|
+
**Status**: Published — PyPI live, conda-forge PR under review
|
|
6
|
+
**Last Updated**: 2026-04-14
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
@@ -34,6 +34,19 @@ conda activate myenv
|
|
|
34
34
|
python --version # Should be 3.8+
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
### dct-publish Environment (Publication & Development)
|
|
38
|
+
Pre-configured environment for publication, testing, and notebook development:
|
|
39
|
+
```bash
|
|
40
|
+
conda activate dct-publish
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Packages installed:**
|
|
44
|
+
- Python 3.11, numpy, scipy, pytest
|
|
45
|
+
- xarray, matplotlib, cmweather (radar colormaps)
|
|
46
|
+
- open-radar-data (sample radar datasets)
|
|
47
|
+
- build, twine (for PyPI publishing)
|
|
48
|
+
- dct-toolkit v0.4.0 (editable install)
|
|
49
|
+
|
|
37
50
|
### PYTHONPATH
|
|
38
51
|
When running tests or examples from project root:
|
|
39
52
|
```bash
|
|
@@ -65,6 +78,8 @@ conda deactivate
|
|
|
65
78
|
|
|
66
79
|
**Why isolated?** Building tools (`build`, `twine`) and their dependencies can conflict with other packages in your base environment (e.g., Sphinx, Streamlit). Always use an isolated environment for package operations.
|
|
67
80
|
|
|
81
|
+
**Note:** The `dct-publish` environment is pre-configured for development, testing, and notebook work. Use it for day-to-day development. Create a fresh isolated environment only when you need to build/publish to PyPI.
|
|
82
|
+
|
|
68
83
|
---
|
|
69
84
|
|
|
70
85
|
## Code Style - Scientific Repository Standards
|
|
@@ -159,6 +174,9 @@ dct_toolkit/ # PROJECT ROOT
|
|
|
159
174
|
│ ├── basic_polar.py
|
|
160
175
|
│ └── comprehensive_demo.py
|
|
161
176
|
│
|
|
177
|
+
├── notebooks/ # JUPYTER NOTEBOOKS
|
|
178
|
+
│ └── 01_getting_started.ipynb
|
|
179
|
+
│
|
|
162
180
|
├── exp_v3/ # EXPERIMENTS (reports + code)
|
|
163
181
|
│ ├── figures/ # Generated figures (PNG/PDF)
|
|
164
182
|
│ ├── test_width_impact.py
|
|
@@ -186,13 +204,12 @@ dct_toolkit/ # PROJECT ROOT
|
|
|
186
204
|
|
|
187
205
|
1. **Activate Environment**
|
|
188
206
|
```bash
|
|
189
|
-
conda activate
|
|
207
|
+
conda activate dct-publish
|
|
190
208
|
```
|
|
191
209
|
|
|
192
210
|
2. **Verify Baseline**
|
|
193
211
|
```bash
|
|
194
|
-
|
|
195
|
-
python -m pytest tests/ -v
|
|
212
|
+
python -m pytest /path/to/dct_toolkit/tests/ -v
|
|
196
213
|
```
|
|
197
214
|
All tests in `tests/` must pass before you start.
|
|
198
215
|
|
|
@@ -222,8 +239,8 @@ dct_toolkit/ # PROJECT ROOT
|
|
|
222
239
|
|
|
223
240
|
**Run Tests**:
|
|
224
241
|
```bash
|
|
225
|
-
|
|
226
|
-
python -m pytest tests/ -v
|
|
242
|
+
conda activate dct-publish
|
|
243
|
+
python -m pytest /path/to/dct_toolkit/tests/ -v
|
|
227
244
|
```
|
|
228
245
|
|
|
229
246
|
**Expected Output**: all tests passing
|
|
@@ -269,25 +286,27 @@ Before declaring task complete:
|
|
|
269
286
|
- [x] Test suite (`tests/`) passing
|
|
270
287
|
- [x] Documentation suite
|
|
271
288
|
|
|
272
|
-
###
|
|
273
|
-
- [x] Fix immediate correctness issues in core statistics path
|
|
274
|
-
- [x] Create
|
|
275
|
-
- [
|
|
276
|
-
- [
|
|
277
|
-
|
|
278
|
-
### 📋 Before Public Release
|
|
289
|
+
### ✅ Completed (Publication Readiness)
|
|
290
|
+
- [x] Fix immediate correctness issues in core statistics path (integer dtype bug)
|
|
291
|
+
- [x] Create dedicated publication-prep branches
|
|
292
|
+
- [x] Update `AGENTS.md` / docs to reflect publication plan
|
|
293
|
+
- [x] Build clean public-facing scope focused on convolution/statistics
|
|
279
294
|
- [x] Add packaging metadata (`pyproject.toml`)
|
|
280
|
-
- [x] Add `LICENSE` and align README badges/claims
|
|
281
|
-
- [
|
|
282
|
-
- [
|
|
283
|
-
- [x] Add CI for tests
|
|
284
|
-
|
|
285
|
-
|
|
295
|
+
- [x] Add `LICENSE` (MIT) and align README badges/claims
|
|
296
|
+
- [x] Define public API surface (exclude gap filling for initial public release)
|
|
297
|
+
- [x] Remove experimental assets from public-facing surface
|
|
298
|
+
- [x] Add CI for tests (`.github/workflows/tests.yml`)
|
|
299
|
+
- [x] Publish to PyPI (v0.4.0 live at https://pypi.org/project/dct-toolkit/)
|
|
300
|
+
- [x] Submit conda-forge recipe (PR #32930 in review)
|
|
301
|
+
- [x] Merge publication work to `main` branch
|
|
302
|
+
- [x] Tag `v0.4.0` release
|
|
303
|
+
- [x] Create `research/gap-filling` branch for long-term research
|
|
304
|
+
|
|
305
|
+
### 🔭 Future (v0.5.0 and beyond)
|
|
286
306
|
- [ ] 3D support (volumetric data)
|
|
287
307
|
- [ ] Additional kernels (Savitzky-Golay, Hanning)
|
|
288
308
|
- [ ] Performance optimizations (numba?)
|
|
289
309
|
- [ ] Jupyter notebook examples
|
|
290
|
-
- [ ] conda-forge distribution
|
|
291
310
|
- [ ] Advanced gap filling (constrained optimization)
|
|
292
311
|
- [ ] GPU acceleration (CuPy)
|
|
293
312
|
|
|
@@ -326,7 +345,7 @@ Formula for robust mean with gaps:
|
|
|
326
345
|
**Problem**: `ModuleNotFoundError: No module named 'dct_toolkit'`
|
|
327
346
|
**Solution**:
|
|
328
347
|
```bash
|
|
329
|
-
|
|
348
|
+
conda activate dct-publish
|
|
330
349
|
```
|
|
331
350
|
|
|
332
351
|
### 2. Path Confusion
|
|
@@ -349,24 +368,69 @@ export PYTHONPATH=$PYTHONPATH:$(pwd)/dct_toolkit
|
|
|
349
368
|
|
|
350
369
|
## Repository Management
|
|
351
370
|
|
|
352
|
-
###
|
|
353
|
-
|
|
354
|
-
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
- `
|
|
366
|
-
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
-
|
|
371
|
+
### Branch Strategy
|
|
372
|
+
|
|
373
|
+
We use a **trunk-based workflow** with dedicated branches for different purposes:
|
|
374
|
+
|
|
375
|
+
| Branch | Purpose | Stability |
|
|
376
|
+
|--------|---------|-----------|
|
|
377
|
+
| `main` | Public releases, stable code | **Production-ready** |
|
|
378
|
+
| `research/gap-filling` | Long-term gap-filling research | Experimental |
|
|
379
|
+
| `feature/*` | Short-lived feature development | In-progress |
|
|
380
|
+
|
|
381
|
+
#### `main` Branch (The Public Package)
|
|
382
|
+
- Always matches the latest PyPI release
|
|
383
|
+
- Contains only public API: core, cartesian, polar, stats
|
|
384
|
+
- Gap-filling code exists in `gap_filling.py` but is **not exported** in `__init__.py`
|
|
385
|
+
- All tests must pass before merging
|
|
386
|
+
|
|
387
|
+
#### `research/gap-filling` Branch
|
|
388
|
+
- Dedicated branch for long-term gap-filling research
|
|
389
|
+
- Safe to experiment with `dct_inpaint`, `iterative_gap_fill`
|
|
390
|
+
- Regularly merge `main` into this branch to get bug fixes and new features
|
|
391
|
+
- Not merged back to `main` until research matures
|
|
392
|
+
|
|
393
|
+
**To sync research branch with latest main:**
|
|
394
|
+
```bash
|
|
395
|
+
git checkout research/gap-filling
|
|
396
|
+
git merge main
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
#### Feature Branches
|
|
400
|
+
For new development (Hanning window, 3D support, notebooks):
|
|
401
|
+
```bash
|
|
402
|
+
# Create feature branch from main
|
|
403
|
+
git checkout main
|
|
404
|
+
git checkout -b feature/hanning-window
|
|
405
|
+
|
|
406
|
+
# Work, commit, push
|
|
407
|
+
git push origin feature/hanning-window
|
|
408
|
+
|
|
409
|
+
# When done, merge to main via PR
|
|
410
|
+
git checkout main
|
|
411
|
+
git merge feature/hanning-window
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Repository Status
|
|
415
|
+
- GitHub: Private (core team access only)
|
|
416
|
+
- PyPI: **Public** — `pip install dct-toolkit` works
|
|
417
|
+
- conda-forge: **Submitted** — PR #32930 in review at `conda-forge/staged-recipes`
|
|
418
|
+
|
|
419
|
+
**Check conda-forge PR Status**:
|
|
420
|
+
```bash
|
|
421
|
+
gh pr view 32930 --repo conda-forge/staged-recipes
|
|
422
|
+
# Or visit: https://github.com/conda-forge/staged-recipes/pull/32930
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Current Version
|
|
426
|
+
- `v0.5.0` — N-D Cartesian smoothing backend upgrade (`dctn/idctn`) with
|
|
427
|
+
validated 3D/4D parity and stats compatibility.
|
|
428
|
+
- Tag: `git checkout v0.5.0`
|
|
429
|
+
|
|
430
|
+
### Gap-Filling Status
|
|
431
|
+
- Code exists in `dct_toolkit/gap_filling.py` (available for import directly)
|
|
432
|
+
- **Not exported** in public API (`__init__.py`) — research-internal
|
|
433
|
+
- Full gap-filling to be released in future v1.x after research matures
|
|
370
434
|
|
|
371
435
|
---
|
|
372
436
|
|
|
@@ -374,15 +438,13 @@ export PYTHONPATH=$PYTHONPATH:$(pwd)/dct_toolkit
|
|
|
374
438
|
|
|
375
439
|
### Documentation
|
|
376
440
|
- **Math Theory**: `docs/MATHEMATICAL_BASIS.md`
|
|
377
|
-
- **Gap Filling Basis**: `docs/GAP_FILLING_BASIS.md`
|
|
378
441
|
- **API Details**: `docs/API_REFERENCE.md`
|
|
379
|
-
- **
|
|
380
|
-
- **Gap Filling Report**: `exp_v3/TEST_REPORT_GAP_FILLING.md`
|
|
442
|
+
- **Getting Started**: `notebooks/01_getting_started.ipynb`
|
|
381
443
|
|
|
382
444
|
### Code Examples
|
|
445
|
+
- **Getting Started Notebook**: `notebooks/01_getting_started.ipynb`
|
|
383
446
|
- **Polar Smoothing**: `examples/basic_polar.py`
|
|
384
447
|
- **Full Demo**: `examples/comprehensive_demo.py`
|
|
385
|
-
- **Gap Filling**: `exp_v3/test_width_impact.py`
|
|
386
448
|
|
|
387
449
|
### Key Papers & References
|
|
388
450
|
- Garcia (2010): DCT-PLS (contrast/background)
|
|
@@ -395,18 +457,28 @@ export PYTHONPATH=$PYTHONPATH:$(pwd)/dct_toolkit
|
|
|
395
457
|
## Quick Reference Card
|
|
396
458
|
|
|
397
459
|
```bash
|
|
398
|
-
#
|
|
399
|
-
|
|
400
|
-
|
|
460
|
+
# Install (pip)
|
|
461
|
+
pip install dct-toolkit
|
|
462
|
+
|
|
463
|
+
# Install (conda - once feed stock is merged)
|
|
464
|
+
conda install -c conda-forge dct-toolkit
|
|
465
|
+
|
|
466
|
+
# Development install from source
|
|
467
|
+
git clone https://github.com/JValdivia23/dct-toolkit.git
|
|
468
|
+
cd dct-toolkit
|
|
469
|
+
pip install -e .
|
|
470
|
+
|
|
471
|
+
# Activate dct-publish environment (for testing/notebooks)
|
|
472
|
+
conda activate dct-publish
|
|
401
473
|
|
|
402
|
-
#
|
|
403
|
-
python -m pytest tests
|
|
474
|
+
# Run tests (from project root)
|
|
475
|
+
python -m pytest tests -v
|
|
404
476
|
|
|
405
477
|
# Run example
|
|
406
478
|
python examples/basic_polar.py
|
|
407
479
|
|
|
408
|
-
#
|
|
409
|
-
|
|
480
|
+
# Open Jupyter notebook
|
|
481
|
+
jupyter notebook notebooks/01_getting_started.ipynb
|
|
410
482
|
|
|
411
483
|
# Check imports (should be empty)
|
|
412
484
|
grep -r "^from \.\./" dct_toolkit/dct_toolkit/
|
|
@@ -7,25 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
### Changed
|
|
11
|
+
- Added anisotropic width support while keeping scalar width as the default path:
|
|
12
|
+
- Cartesian smoothing/stats now accept `width` as scalar or length-`ndim` sequence.
|
|
13
|
+
- Polar smoothing/stats now accept `width` as scalar or
|
|
14
|
+
`(width_azimuth, width_range)`.
|
|
15
|
+
- `dct_count` now uses anisotropic window area (`prod(widths)` in Cartesian,
|
|
16
|
+
`width_range * width_azimuth/(r*dtheta)` in polar).
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- Added parity and artifact-regression tests for scalar-vs-vector isotropic widths,
|
|
20
|
+
plus anisotropic Cartesian/polar smoke coverage.
|
|
21
|
+
|
|
22
|
+
## [0.5.0] - 2026-04-14
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- `smooth_cartesian` now uses a single N-D spectral pass (`dctn/idctn`) with
|
|
26
|
+
broadcasted separable transfer functions, preserving output equivalence while
|
|
27
|
+
reducing transform overhead for higher-dimensional Cartesian arrays.
|
|
28
|
+
- Default smoothing kernel is now `'gaussian'` for Cartesian and polar smoothing paths
|
|
29
|
+
(`smooth_cartesian`, `smooth_polar`, and all wrapper/statistics calls that inherit
|
|
30
|
+
those defaults).
|
|
31
|
+
- `dct_smooth` now enforces a NaN-safe smoothing pipeline: if NaNs are present, it
|
|
32
|
+
automatically pre-fills missing values before spectral smoothing and restores the
|
|
33
|
+
original NaN mask afterward.
|
|
34
|
+
- `dct_prefill` now applies mandatory nearest-neighbor residual fill for unresolved
|
|
35
|
+
targets, guaranteeing finite arrays for downstream spectral smoothing when support
|
|
36
|
+
exists.
|
|
37
|
+
- Default prefill iteration count is now `max_iter=3` for predictable runtime. You can
|
|
38
|
+
still set `max_iter=None` to iterate until convergence (safety cap: 20).
|
|
39
|
+
- `dct_mean`, `dct_variance`, and `dct_std` now include stable fallback handling for
|
|
40
|
+
poorly conditioned normalized-convolution regions (heavy-gap volumes), preventing
|
|
41
|
+
NaN/overflow behavior in low-support areas when valid support exists.
|
|
42
|
+
- `dct_count` now clips smoothed density to `[0, 1]` before area scaling to keep
|
|
43
|
+
effective sample counts physically valid.
|
|
44
|
+
- `dct_count`, `dct_mean`, `dct_variance`, and `dct_std` now default to
|
|
45
|
+
`restore_input_nan=True`, masking outputs at original invalid locations while
|
|
46
|
+
keeping valid locations stable and finite.
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
- Added NaN-policy regression tests for `dct_smooth` and new `dct_prefill` iteration/
|
|
50
|
+
residual behavior (`tests/test_api.py`, updates in `tests/test_stats.py`).
|
|
51
|
+
- Added Cartesian N-D equivalence tests to verify artifact-free 3D/4D outputs and
|
|
52
|
+
new N-D statistics smoke tests (`tests/test_cartesian.py`, `tests/test_stats_nd.py`).
|
|
53
|
+
|
|
54
|
+
## [0.4.1] - 2026-04-12
|
|
55
|
+
|
|
10
56
|
### Added
|
|
11
|
-
- **
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
- Added GitHub Actions CI workflow `.github/workflows/tests.yml`.
|
|
15
|
-
- Added `docs/PUBLICATION_GUIDE.md` with PyPI + conda-forge release steps.
|
|
16
|
-
- Added `conda.recipe/meta.yaml` starter recipe for conda-forge submission prep.
|
|
17
|
-
- **Statistics API**:
|
|
18
|
-
- Added `dct_prefill` (iterative normalized-convolution prefill) and exported it
|
|
19
|
-
in the top-level API.
|
|
20
|
-
- Added regression tests for `dct_prefill` behavior and parameter validation.
|
|
57
|
+
- **Jupyter Notebook**: Added `notebooks/01_getting_started.ipynb` with interactive
|
|
58
|
+
tutorial for DCT smoothing and statistics.
|
|
59
|
+
- Updated `.gitignore` to allow notebooks in `notebooks/` folder.
|
|
21
60
|
|
|
22
61
|
### Changed
|
|
23
|
-
-
|
|
24
|
-
|
|
25
|
-
- `
|
|
26
|
-
|
|
27
|
-
- `
|
|
28
|
-
|
|
62
|
+
- **Repository Cleanup**: Moved experimental content to `research/gap-filling` branch:
|
|
63
|
+
- Removed `exp_v3/` and `exp_v4/` experimental folders from main branch.
|
|
64
|
+
- Removed `docs/GAP_FILLING_BASIS.md` and `docs/PUBLICATION_GUIDE.md` from main branch.
|
|
65
|
+
- `gap_filling.py` remains in codebase but is **not exported** in public API.
|
|
66
|
+
- `__init__.py`: Version bumped to `0.4.1`.
|
|
67
|
+
|
|
68
|
+
### Notes
|
|
69
|
+
- Main branch now contains only core package functionality for stable PyPI releases.
|
|
70
|
+
- All experimental work (gap filling, inpainting) preserved in `research/gap-filling` branch.
|
|
29
71
|
|
|
30
72
|
## [0.4.0] - 2026-02-28
|
|
31
73
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Public API for DCT-based convolution and statistical operations.
|
|
2
|
+
|
|
3
|
+
The public top-level surface is intentionally scoped to smoothing and
|
|
4
|
+
normalized-convolution statistics for the publication-focused release track.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Optional
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
from .core import dct_convolve_1d, get_dct_transfer_function
|
|
12
|
+
from ._widths import WidthLike
|
|
13
|
+
from .cartesian import smooth_cartesian
|
|
14
|
+
from .polar import smooth_polar
|
|
15
|
+
from .stats import dct_count, dct_mean, dct_prefill, dct_std, dct_variance
|
|
16
|
+
|
|
17
|
+
__version__ = "0.5.0"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"__version__",
|
|
22
|
+
"get_dct_transfer_function",
|
|
23
|
+
"dct_convolve_1d",
|
|
24
|
+
"smooth_cartesian",
|
|
25
|
+
"smooth_polar",
|
|
26
|
+
"dct_smooth",
|
|
27
|
+
"dct_count",
|
|
28
|
+
"dct_mean",
|
|
29
|
+
"dct_prefill",
|
|
30
|
+
"dct_variance",
|
|
31
|
+
"dct_std",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def dct_smooth(
|
|
36
|
+
data: np.ndarray,
|
|
37
|
+
width: WidthLike,
|
|
38
|
+
coordinates: str = "cartesian",
|
|
39
|
+
prefill_max_iter: Optional[int] = 3,
|
|
40
|
+
**kwargs: Any,
|
|
41
|
+
) -> np.ndarray:
|
|
42
|
+
"""
|
|
43
|
+
Apply DCT-based smoothing.
|
|
44
|
+
|
|
45
|
+
Parameters
|
|
46
|
+
----------
|
|
47
|
+
data : np.ndarray
|
|
48
|
+
Input data.
|
|
49
|
+
width : float or sequence of float
|
|
50
|
+
Smoothing width. Scalar input keeps isotropic behavior. Sequence
|
|
51
|
+
input allows anisotropic widths.
|
|
52
|
+
coordinates : str, default='cartesian'
|
|
53
|
+
'cartesian' or 'polar'.
|
|
54
|
+
prefill_max_iter : int or None, default=3
|
|
55
|
+
Number of normalized-convolution prefill iterations used when input
|
|
56
|
+
contains NaNs. If None, prefill iterates until convergence or a
|
|
57
|
+
safety cap of 20 iterations.
|
|
58
|
+
**kwargs
|
|
59
|
+
Additional arguments (kernel_type, az_res_deg, etc.)
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
smoothed : np.ndarray
|
|
64
|
+
Smoothed data.
|
|
65
|
+
"""
|
|
66
|
+
data_array = np.asarray(data)
|
|
67
|
+
nan_mask = np.isnan(data_array)
|
|
68
|
+
|
|
69
|
+
# Support legacy alias in wrapper calls.
|
|
70
|
+
if "max_iter" in kwargs:
|
|
71
|
+
prefill_max_iter = kwargs.pop("max_iter")
|
|
72
|
+
|
|
73
|
+
if not np.any(nan_mask):
|
|
74
|
+
if coordinates == "cartesian":
|
|
75
|
+
return smooth_cartesian(data_array, width, **kwargs)
|
|
76
|
+
if coordinates == "polar":
|
|
77
|
+
return smooth_polar(data_array, width_pixels=width, **kwargs)
|
|
78
|
+
raise ValueError(f"Unknown coordinates: {coordinates}")
|
|
79
|
+
|
|
80
|
+
# No support exists to estimate an all-NaN field; preserve mask semantics.
|
|
81
|
+
if np.all(nan_mask):
|
|
82
|
+
return np.full(data_array.shape, np.nan, dtype=np.float64)
|
|
83
|
+
|
|
84
|
+
filled = dct_prefill(
|
|
85
|
+
data_array,
|
|
86
|
+
width=width,
|
|
87
|
+
coordinates=coordinates,
|
|
88
|
+
max_iter=prefill_max_iter,
|
|
89
|
+
**kwargs,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if coordinates == "cartesian":
|
|
93
|
+
smoothed = smooth_cartesian(filled, width, **kwargs)
|
|
94
|
+
elif coordinates == "polar":
|
|
95
|
+
smoothed = smooth_polar(filled, width_pixels=width, **kwargs)
|
|
96
|
+
else:
|
|
97
|
+
raise ValueError(f"Unknown coordinates: {coordinates}")
|
|
98
|
+
|
|
99
|
+
smoothed[nan_mask] = np.nan
|
|
100
|
+
return smoothed
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Internal helpers for isotropic/anisotropic width handling."""
|
|
2
|
+
|
|
3
|
+
from typing import Sequence, Union
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
WidthLike = Union[float, Sequence[float], np.ndarray]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def normalize_widths(width: WidthLike, ndim: int, name: str = "width") -> np.ndarray:
|
|
12
|
+
"""Normalize scalar/sequence width input to a positive 1-D array."""
|
|
13
|
+
if ndim < 0:
|
|
14
|
+
raise ValueError(f"ndim must be >= 0, got {ndim}")
|
|
15
|
+
|
|
16
|
+
values = np.asarray(width, dtype=float)
|
|
17
|
+
|
|
18
|
+
if values.ndim == 0:
|
|
19
|
+
widths = np.full(ndim, float(values), dtype=float)
|
|
20
|
+
elif values.ndim == 1:
|
|
21
|
+
if values.size != ndim:
|
|
22
|
+
raise ValueError(
|
|
23
|
+
f"{name} must be a scalar or length-{ndim} sequence, got length {values.size}"
|
|
24
|
+
)
|
|
25
|
+
widths = values.astype(float, copy=False)
|
|
26
|
+
else:
|
|
27
|
+
raise ValueError(f"{name} must be a scalar or 1-D sequence")
|
|
28
|
+
|
|
29
|
+
if not np.all(np.isfinite(widths)):
|
|
30
|
+
raise ValueError(f"{name} must contain only finite values")
|
|
31
|
+
if np.any(widths <= 0):
|
|
32
|
+
raise ValueError(f"{name} must be > 0")
|
|
33
|
+
|
|
34
|
+
return widths
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
N-D Cartesian Smoothing.
|
|
3
|
+
|
|
4
|
+
This module provides separable smoothing for N-D Cartesian data.
|
|
5
|
+
It applies a separable N-D transfer function in the DCT domain.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import scipy.fft
|
|
10
|
+
|
|
11
|
+
from .core import get_dct_transfer_function
|
|
12
|
+
from ._widths import WidthLike, normalize_widths
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def smooth_cartesian(
|
|
16
|
+
data: np.ndarray,
|
|
17
|
+
width: WidthLike,
|
|
18
|
+
kernel_type: str = "gaussian",
|
|
19
|
+
) -> np.ndarray:
|
|
20
|
+
"""
|
|
21
|
+
Apply separable DCT smoothing to N-D Cartesian data.
|
|
22
|
+
|
|
23
|
+
The smoothing uses a single N-D forward DCT and inverse DCT. The
|
|
24
|
+
separable transfer function is applied by multiplying the spectrum by
|
|
25
|
+
one 1-D transfer function per axis using broadcasting.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
data : np.ndarray
|
|
30
|
+
Input data array (any dimension).
|
|
31
|
+
width : float or sequence of float
|
|
32
|
+
Smoothing width. Scalar input applies isotropic smoothing. Sequence
|
|
33
|
+
input must have length ``data.ndim`` and applies per-axis widths.
|
|
34
|
+
kernel_type : str, default='gaussian'
|
|
35
|
+
Kernel type ('boxcar', 'boxcar_discrete', 'gaussian').
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
smoothed : np.ndarray
|
|
40
|
+
Smoothed data with same shape as input.
|
|
41
|
+
"""
|
|
42
|
+
data_array = np.asarray(data)
|
|
43
|
+
if data_array.ndim == 0:
|
|
44
|
+
return data_array.copy()
|
|
45
|
+
|
|
46
|
+
widths = normalize_widths(width, data_array.ndim, name="width")
|
|
47
|
+
|
|
48
|
+
spectrum = scipy.fft.dctn(data_array, type=2, norm="ortho")
|
|
49
|
+
|
|
50
|
+
for axis, (n, width_axis) in enumerate(zip(data_array.shape, widths)):
|
|
51
|
+
H = get_dct_transfer_function(n, kernel_type, float(width_axis))
|
|
52
|
+
transfer_shape = [1] * data_array.ndim
|
|
53
|
+
transfer_shape[axis] = n
|
|
54
|
+
spectrum *= H.reshape(transfer_shape)
|
|
55
|
+
|
|
56
|
+
return scipy.fft.idctn(spectrum, type=2, norm="ortho")
|