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.
Files changed (33) hide show
  1. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/.gitignore +1 -0
  2. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/AGENTS.md +124 -52
  3. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/CHANGELOG.md +58 -16
  4. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/PKG-INFO +1 -1
  5. dct_toolkit-0.5.0/dct_toolkit/__init__.py +100 -0
  6. dct_toolkit-0.5.0/dct_toolkit/_widths.py +34 -0
  7. dct_toolkit-0.5.0/dct_toolkit/cartesian.py +56 -0
  8. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/dct_toolkit/polar.py +97 -86
  9. dct_toolkit-0.5.0/dct_toolkit/stats.py +559 -0
  10. dct_toolkit-0.5.0/docs/API_REFERENCE.md +203 -0
  11. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/pyproject.toml +1 -1
  12. dct_toolkit-0.5.0/tests/test_api.py +56 -0
  13. dct_toolkit-0.5.0/tests/test_cartesian.py +104 -0
  14. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/tests/test_polar.py +38 -14
  15. dct_toolkit-0.5.0/tests/test_stats.py +473 -0
  16. dct_toolkit-0.5.0/tests/test_stats_nd.py +37 -0
  17. dct_toolkit-0.4.0/dct_toolkit/__init__.py +0 -63
  18. dct_toolkit-0.4.0/dct_toolkit/cartesian.py +0 -41
  19. dct_toolkit-0.4.0/dct_toolkit/stats.py +0 -300
  20. dct_toolkit-0.4.0/docs/API_REFERENCE.md +0 -94
  21. dct_toolkit-0.4.0/docs/GAP_FILLING_BASIS.md +0 -166
  22. dct_toolkit-0.4.0/docs/PUBLICATION_GUIDE.md +0 -76
  23. dct_toolkit-0.4.0/tests/test_cartesian.py +0 -48
  24. dct_toolkit-0.4.0/tests/test_stats.py +0 -211
  25. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/LICENSE +0 -0
  26. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/README.md +0 -0
  27. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/dct_toolkit/core.py +0 -0
  28. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/dct_toolkit/gap_filling.py +0 -0
  29. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/docs/MATHEMATICAL_BASIS.md +0 -0
  30. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/examples/basic_polar.py +0 -0
  31. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/examples/comprehensive_demo.py +0 -0
  32. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/tests/test_core.py +0 -0
  33. {dct_toolkit-0.4.0 → dct_toolkit-0.5.0}/tests/test_gap_filling.py +0 -0
@@ -40,6 +40,7 @@ Thumbs.db
40
40
  # Jupyter
41
41
  .ipynb_checkpoints/
42
42
  *.ipynb
43
+ !notebooks/*.ipynb
43
44
 
44
45
  # Environment
45
46
  .env
@@ -1,9 +1,9 @@
1
1
  # AGENTS.md - DCT Toolkit Agent Guidelines
2
2
 
3
3
  **Project**: dct-toolkit
4
- **Version**: 0.4.0
5
- **Status**: Private Repository in Publication Preparation
6
- **Last Updated**: 2026-04-10
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 myenv
207
+ conda activate dct-publish
190
208
  ```
191
209
 
192
210
  2. **Verify Baseline**
193
211
  ```bash
194
- export PYTHONPATH=$PYTHONPATH:$(pwd)/dct_toolkit
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
- export PYTHONPATH=$PYTHONPATH:$(pwd)/dct_toolkit
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
- ### 🚧 In Progress: Publication Readiness (stats-first)
273
- - [x] Fix immediate correctness issues in core statistics path
274
- - [x] Create a dedicated publication-prep branch
275
- - [ ] Update `AGENTS.md` / docs to reflect publication plan and current state
276
- - [ ] Build clean public-facing scope focused on convolution/statistics
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
- - [ ] Define public API surface (exclude gap filling for initial public release)
282
- - [ ] Remove or relocate experimental assets from public-facing surface
283
- - [x] Add CI for tests and minimum quality checks
284
-
285
- ### 🔭 Future (post-public v1)
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
- export PYTHONPATH=$PYTHONPATH:$(pwd)/dct_toolkit
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
- ### Private Repository (Current)
353
- - Hosted on: GitHub (private)
354
- - Access: Core team only
355
- - Branching: feature branches → main
356
- - Manual testing required until CI is added
357
-
358
- ### Planned Public Release Scope
359
- - Initial public release is **convolution/statistics focused**
360
- - Keep DCT + periodic FFT boundary support in smoothing/statistical APIs
361
- - Defer gap-filling methods from initial public API surface
362
- - Add packaging metadata + license before publication
363
-
364
- ### Publication Assets (Current Branch)
365
- - `pyproject.toml` for pip builds and installation
366
- - `LICENSE` (MIT)
367
- - `.github/workflows/tests.yml` (pytest on push/PR)
368
- - `docs/PUBLICATION_GUIDE.md` with pip + conda-forge steps
369
- - `conda.recipe/meta.yaml` starter recipe for staged-recipes submission
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
- - **Core Test Report**: `exp_v3/TEST_REPORT_CORE.md`
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
- # Setup
399
- conda activate myenv
400
- export PYTHONPATH=$PYTHONPATH:$(pwd)/dct_toolkit
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
- # Test
403
- python -m pytest tests/ -v
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
- # Benchmark
409
- python exp_v3/test_width_impact.py
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
- - **Packaging + distribution groundwork**:
12
- - Added `pyproject.toml` (PEP 517/518 via hatchling) for pip/wheel builds.
13
- - Added `LICENSE` (MIT).
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
- - `stats.py`: improved dtype handling for integer inputs in `dct_mean`/`dct_variance`
24
- with floating-point outputs and explicit mask-shape validation.
25
- - `README.md` and `docs/API_REFERENCE.md`: refocused public surface on
26
- convolution/statistics and documented `dct_prefill`.
27
- - `examples/comprehensive_demo.py`: removed stale experimental imports and aligned
28
- with stats-first public API.
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dct-toolkit
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: DCT/FFT-based convolutional statistics for Cartesian and polar data
5
5
  Author-email: "Jairo M. Valdivia" <Jairo.ValdiviaPrado@colorado.edu>
6
6
  License: MIT License
@@ -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")