-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCHANGELOG
More file actions
1915 lines (1590 loc) · 106 KB
/
Copy pathCHANGELOG
File metadata and controls
1915 lines (1590 loc) · 106 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Changelog
## [v0.6.8] - 2026-06-22 - Visualisation module extraction
### Added
- Internal `src/ramanpl/visualisation/` package for plotting implementation code.
- `src/ramanpl/visualisation/_batch.py`, `_mapping.py`, `_single_fit.py`, and `_preview.py`.
- Internal `_plot_raw_spectrum(...)` helper for array and mapping-pixel preview use. This is package-private in v0.6.8 and is not a stable public API.
- `tests/test_visualisation_facades.py` with facade-signature checks and lightweight structural figure checks under the Agg backend.
### Changed
- Batch plotting implementations moved behind `ramanpl.batch.plot_overlay`, `plot_waterfall`, and `plot_fitted_parameters`; existing imports from `ramanpl.batch` remain valid.
- `RamanMapping` and `PLMapping` fitted-map plotting methods now delegate to internal visualisation helpers.
- `RamanFit.plot_fit` and `PLfit.plot_fit` now delegate to internal visualisation helpers.
- Version bumped to 0.6.8 in `pyproject.toml`, `src/ramanpl/__init__.py`, `CITATION.cff`, `docs/source/conf.py`, and `tests/test_packaging_smoke.py`.
### Not changed
- Public plotting method signatures and legacy return contracts are preserved: batch plotting returns `(fig, ax)`, while mapping and single-fit plotting keep the existing `plt.show()` / `None` behavior.
- Integration-class plotting (`Raman_Integration`, `PL_Integration`) remains unchanged and is deferred for later cleanup.
- No fitting, preprocessing, baseline, peak-model, export, feature-table schema, parallel, or cluster-seed behavior changes.
### Verification
- `py -3.14 -m py_compile` passed for all touched production modules and `tests/test_visualisation_facades.py`.
- Pytest and import smoke commands were not run in this workspace because the project `.venv` points to a missing Python interpreter and the available system/bundled Python installations lack the scientific/test dependencies.
## [v0.6.7] — 2026-06-05 — Integrated component area in the feature table
### Added
- `{peak}_component_area`, `{peak}_component_area_norm`, `{peak}_component_area_fraction`
columns in `feature_table()` for all fitter classes (RamanFit, PLfit, RamanMapping,
PLMapping, RamanBatch, PLBatch). Values are exact analytic areas of the fitted lineshape
(`amp × intensity_scale` and `amp` respectively); fraction sums to 1 across all peaks in
a row.
- `area_ratios=` keyword on all five `feature_table()` entry points; each `(P1, P2)` pair
emits `{P1}_{P2}_area_ratio = amp[P1] / amp[P2]` (zero denominator → NaN).
- `tests/test_component_area_identity.py` — verifies the area = `amp` identity numerically
for Lorentzian, Gaussian, and pseudo-Voigt (16 parameterised cases).
- `docs/source/api-stability.md` §14 — v0.6.7 component-area freeze contract; §3 suffix
table extended with four new frozen suffixes.
- `snapshot/featuretable_*_v0.6.6.csv` — golden CSVs for byte-parity regression.
### Changed
- `src/ramanpl/descriptors.py` — `build_feature_row` gains `area_ratios=` parameter and
emits three new per-peak columns and optional area-ratio pair columns.
- `src/ramanpl/mapping/_preprocess.py` — `_params_to_export_dict` adds `amp_scaled` to
both lorentzian and pvoigt out-dicts; `feature_table` signature gains `area_ratios=` and
emits NaN for new columns in the failed-pixel branch.
- `src/ramanpl/single_fit/RamanFit.py` and `PLfit.py` — `get_fitted_parameters` adds
`amp_scaled` to the returned dict; `feature_table` forwards `amp`/`amp_scaled` and gains
`area_ratios=`.
- `src/ramanpl/batch.py` — `feature_table` forwards `amp`/`amp_scaled` and gains
`area_ratios=`.
- `tests/test_api_stability.py` — `_FROZEN_SUFFIXES` extended; reference column lists
updated; three new v0.6.7 contract tests added.
- `tests/test_descriptors_unit.py`, `test_feature_table_mapping.py`,
`test_feature_table_batch_and_single.py` — extended with component-area correctness,
byte-parity, and cross-path consistency tests.
- `docs/source/quickstart.md` — feature-table section documents new columns.
- `example-usage/Ramanfit/Raman_component.ipynb`, `example-usage/PLfit/PL_component.ipynb`,
`example-usage/Mapping/Mapping Raman Example.ipynb`, `example-usage/Mapping/Mapping PL Example.ipynb`
— new section in each notebook demonstrating `feature_table()` component-area columns and
`area_ratios=` usage (with heatmap visualisation for the mapping notebooks).
- Version bumped to 0.6.7 in `pyproject.toml`, `__init__.py`, `CITATION.cff`, `conf.py`,
`test_packaging_smoke.py`.
### Not changed (scope boundary)
- `export()` and long-format exporters — `amp` was already present; no schema change.
- Fitting algorithms, peak models, preprocessing — zero changes.
- Benchmarks — zero new `curve_fit` calls; no performance impact.
- Plotting — deferred to v0.6.8.
## [v0.6.6] — 2026-06-02 — Consolidation pause: API freeze, validation, docs
### Added
- `benchmarks/validation_v0.6.6_vs_v0.6.0.py` — reproducible validation harness
covering 7 gates: fit-output parity, export-schema stability, call-count sanity,
parallel safety, cluster-seed boundary, autotune non-mutation, batch-progress
default.
- `benchmarks/results/v0.6.6_validation.csv` — versioned raw gate evidence.
- `benchmarks/results/v0.6.6_validation_summary.json` — machine-readable gate
summary; all 7 gates passed.
- `docs/source/validation/v0.6.6.md` — citable validation report.
- `docs/source/api-stability.md` §9–§13 — freeze contracts for v0.6.1–v0.6.5
additive surface (`show_progress`, autotune, `method_grids`, `n_jobs`,
`cluster_seeds`).
- `tests/test_api_stability.py` — six new contract tests: `show_progress=True`
default on mapping and batch entry points; `n_jobs=1` default on both mapping
classes; `autotune_baseline`/`apply_choice` presence on all four fit objects;
`method_grids` keyword present; removed `methods`/`lam_grid` absent.
- `closeout_v0.6.6.md` — release closeout document.
### Changed
- `docs/source/user-guide/mapping.md` — `show_progress` and `n_jobs` usage
added; `parallel-fitting` cross-reference added.
- `docs/source/user-guide/batch.md` — `show_progress=True` default documented.
- `docs/source/user-guide/low_snr_advisory.md` — autotune baseline workflow
cross-referenced at Step 3; cluster-seeds advisory added as Step 5b.
- `docs/source/user-guide/baseline-autotune.md` — `methods`/`lam_grid` removal
made explicit; `TypeError` on use stated.
- `checklist.md` — `show_progress` default corrected from `False` to `True`
(source default confirmed by Gate 0.2).
### Known limitations
- `cluster_seeds=True` remains serial-only (`n_jobs=1` required).
- On homogeneous synthetic cubes, `cluster_seeds=True` does not reduce
`n_curve_fit_calls`. Benefit depends on multi-domain data.
---
## [v0.6.5] — 2026-05-28 — Similarity-based seed selection for warm-start
### Added
- `cluster_seeds` keyword on `RamanMapping.fit_spectra` and `PLMapping.fit_spectra`
(default `False`; backward-compatible). When `True` or a config dict, preprocessed
spectra are clustered with PCA + k-means (scikit-learn), one representative pixel
per cluster is fitted first, and each representative's fitted parameters are used as
the initial guess for remaining pixels in that cluster.
- `src/ramanpl/mapping/_cluster_seeds.py` — package-private helpers:
`_require_sklearn_for_cluster_seeds`, `_normalise_cluster_seed_config`,
`_spectral_feature_matrix`, `_cluster_spectra`, `_representative_pixels`,
`_build_cluster_schedule`. All sklearn imports are lazy; base install unaffected.
- `_fit_single_pixel` private method on both mapping classes — minimal per-pixel
fit wrapper enabling the cluster-seeded dispatch without modifying `_fit_rows`.
- `tests/test_cluster_seed_helpers.py` — 15 tests for helpers and schedule invariants.
- `tests/test_cluster_seed_fit_mapping.py` — 15 integration tests covering parity,
serial completion, success-rate gate, tolerance, mutual exclusions, parallel guard.
- `benchmarks/results/mapping_fit_benchmark_v0.6.5.csv` — versioned benchmark
results including `cluster_seeds` axis across 4 dataset sizes.
- `benchmarks/results/cluster_seed_speedup_v0.6.5.txt` — call-count comparison.
- `docs/source/user-guide/mapping.md` — `cluster_seeds` usage example.
- `docs/source/user-guide/parallel-fitting.md` — v0.6.5 serial-only constraint.
- `docs/source/api-stability.md` — §9 documents the additive `cluster_seeds` change.
- `closeout_v0.6.5.md` — release closeout document.
### Changed
- `benchmarks/benchmark_mapping_fit.py` — `cluster_seeds` axis added to result
schema and `build_fit_kwargs_variants`; new `noisy_5x8` benchmark case added.
- `tests/test_api_stability.py` — new test pins `cluster_seeds` keyword presence
and default on both mapping classes.
- `tests/test_release_benchmark_smoke.py` — expects `cluster_seeds` field in
fit benchmark records.
- `example-usage/Mapping/Mapping Raman Example.ipynb` and
`example-usage/Mapping/Mapping PL Example.ipynb` — `cluster_seeds` demo cells
added (Option B: markdown explanation + code cell after the existing `n_jobs`
section in each notebook).
### Fixed
- `benchmarks/validation_v0.6.0_vs_v0.5.0.py` — `"cluster_seeds"` and `"n_jobs"`
added to `_CSV_FIELDS`; `"n_jobs": 1` and `"cluster_seeds": False` added to the
hand-built `_run_hard_case` return dict. CI regression: `run_mapping_fit_case`
(extended in v0.6.5) returns a `cluster_seeds` key that `csv.DictWriter` rejected
with `ValueError: dict contains fields not in fieldnames: 'cluster_seeds'`.
- `src/ramanpl/mapping/_cluster_seeds.py` — `_representative_pixels` now returns
`(cluster_id, (x, y))` pairs instead of bare `(x, y)` tuples; `_build_cluster_schedule`
unpacks the actual cluster ID rather than using `enumerate`. When KMeans produces
non-contiguous labels (e.g. `{0, 2}` on low-diversity or duplicate spectra), the old
`enumerate` index `k=1` looked for pixels with `labels[j, i] == 1`, found none, and
silently skipped every pixel in cluster 2, leaving NaNs in `fitted_params` and
`residual_map` under `cluster_seeds=True`.
- `tests/test_cluster_seed_helpers.py` — updated six `_build_cluster_schedule` fixtures
to the new `(cluster_id, (x, y))` representative format.
- `benchmarks/benchmark_mapping_fit.py` — speedup summary header corrected from
`n_starts=1` to `n_starts=4`; the baseline rows were already filtered to
`n_starts=4` and the cluster-seed variants are built with `n_starts=4`, so the
file was documenting the wrong experimental setting.
- `src/ramanpl/mapping/_raman_mapping.py` and `_pl_mapping.py` — invalid pixels
(those labeled `-1` by `_cluster_spectra`) are now explicitly NaN-ised in
`residual_map`, `norm_scale_map`, `peak_positions`, `peak_intensities`, and the
Raman-specific derived maps (`Peaks_distance`, `ratio_A1g_E2g`, `ratio_E2g_A1g`)
before the cluster schedule loop. Previously those pixels were silently skipped,
so stale values from a prior `fit_spectra()` call were retained and could be
counted or exported as successful fits.
- `src/ramanpl/mapping/_raman_mapping.py` and `_pl_mapping.py` — cluster seed
broadcast now gates on `warm_start_rmse_gate` before setting `cluster_p0`.
Previously only finiteness was checked, so a representative whose fit converged but
exceeded the gate would still seed every cluster member — exactly the propagation the
gate exists to prevent. The fix reads `self.residual_map[sy, sx]` (written by
`_fit_single_pixel`) and falls back to `p0_base` when the RMSE is non-finite or
above the gate.
### Known limitations
- `cluster_seeds=True` is serial-only (`n_jobs=1` required). Two-phase parallel
cluster dispatch is deferred.
- On homogeneous synthetic benchmark cubes, `n_curve_fit_calls` is identical for
`cluster_seeds=True` and `cluster_seeds=False` because the optimizer converges
to the global minimum from any reasonable starting point. The call-count benefit
manifests on real multi-domain spectroscopic data.
---
## [v0.6.4] — 2026-05-24 — Parallel mapping fit with row-band warm-start
### Added
- `n_jobs` keyword on `RamanMapping.fit_spectra` and `PLMapping.fit_spectra`
(default `1`, serial — byte-parity with v0.6.3 preserved). When `n_jobs > 1`
the row loop is distributed across loky worker processes via `joblib.Parallel`.
- `src/ramanpl/mapping/_parallel.py` — module-level band workers
(`_raman_fit_band`, `_pl_fit_band`), row-splitting helper (`_split_rows`),
validation helper (`_validate_parallel_kwargs`), and result merger
(`_merge_band_outputs`).
- `joblib>=1.3` as a hard runtime dependency.
- `tests/test_parallel_fit_mapping.py` — 11 new tests covering byte-parity,
unsafe-mode ValueError, call-count invariant, clamping, and utility functions.
- `benchmarks/results/mapping_fit_benchmark_v0.6.4.csv` — versioned benchmark
results over 3 dataset sizes × 8 fit-kwargs variants (including n_jobs axis).
- `benchmarks/results/parallel_speedup_v0.6.4.txt` — wall-clock speedup table
for `extended_15x15` dataset; 2.42× at n_jobs=4 (n_starts=1).
- `docs/source/user-guide/parallel-fitting.md` — user guide for `n_jobs`.
- `closeout_v0.6.4.md` — release closeout document.
### Changed
- `benchmarks/benchmark_mapping_fit.py` — `n_jobs` axis added to
`build_fit_kwargs_variants`; result records include `n_jobs` field.
- `RamanMapping.fit_spectra` and `PLMapping.fit_spectra` docstrings updated
to document `n_jobs` parameter.
- Example notebooks (`Mapping Raman Example.ipynb`, `Mapping PL Example.ipynb`)
extended with `n_jobs` parallel-fit demonstration cells.
### Removed
- `methods` / `lam_grid` deprecation shim (`_shim_methods_lam_grid`) from
`_autotune.py`. Both kwargs now raise `TypeError` when passed. (Deprecated
in v0.6.3; removal announced in that release.)
### Fixed
- `tol` parameter added to `_ALLOWED_PARAMS["arpls"]` and
`_ALLOWED_PARAMS["airpls"]`, enabling `tol` sweeps via `method_grids`.
- `_validate_parallel_kwargs` now returns `1` immediately when `Y=0`, preventing
a `ZeroDivisionError` in `_split_rows` that was a regression over the prior
serial no-op behaviour on empty cubes.
- `show_progress=True` now shows a tqdm bar for `n_jobs > 1` fits, using
`Parallel(return_as="generator")` to report per-band completion progress.
Previously the flag was silently ignored in the parallel branch.
- `benchmarks/validation_v0.6.0_vs_v0.5.0.py` — `"n_jobs"` added to
`_CSV_FIELDS` so `DictWriter` accepts the extended `run_mapping_fit_case`
result dict (CI regression introduced by Step 6).
---
## [v0.6.3] — 2026-05-23 — Autotune API refinement, real-data notebooks, API docs
### Added
- `method_grids` parameter on `autotune_baseline()` for `RamanMapping`, `PLMapping`,
`RamanFit`, and `PLfit`. Accepts a `{method: {param: [values]}}` dict; candidates are
the Cartesian product over each method's parameter lists.
- `_shim_methods_lam_grid` internal function that converts the deprecated `methods` /
`lam_grid` keyword pair into an equivalent `method_grids` dict, preserving v0.6.2
byte-parity on the default 24-candidate grid.
- `_validate_method_grids` internal function with five guards: not-a-dict, empty dict,
unknown method, unknown parameter, empty value list.
- Module constants in `_autotune.py`: `_KNOWN_METHODS`, `_ALLOWED_PARAMS`,
`_DEFAULT_LAM_5`, `_DEFAULT_LAM_4`, `_DEFAULT_METHODS`.
- Real-data section "5. Real-data example: non-linear background" in
`Baseline_Autotune_Demo.ipynb` using a bilayer-graphene spectrum
(`Raman Sample 532nm 2L-Graphene.txt`).
- Autotune blocks ("Optional: use `autotune_baseline`") in
`Raman_background-remove.ipynb` with a focused 7-candidate grid.
- Sphinx autodoc page `docs/source/api/autotune.rst` for `autotune_baseline`,
`apply_choice`, and `BaselineAutotuneResult`.
- `docs/source/user-guide/baseline-autotune.md` rewritten with `method_grids` examples
and a "Deprecated arguments" subsection.
- `Raman_background-remove.ipynb` added to the notebook smoke-test suite (`CANONICAL_NOTEBOOKS`).
- `benchmarks/results/bench_snapshot_v0.6.2.txt` — pre-v0.6.3 baseline snapshot.
- 7 new mapping-autotune tests and 2 new single-fit-autotune tests.
### Changed
- `autotune_baseline()` signature: `method_grids` keyword added before the deprecated
`methods` and `lam_grid` kwargs on all four façade methods.
- `_default_baseline_grid` now accepts an optional `method_grids` dict; `method_grids=None`
reproduces the v0.6.2 24-candidate grid byte-for-byte.
- All 19 autotune test call sites converted from `methods=[...]` to `method_grids={...}`.
### Fixed
- `autotune_baseline(plot=True)` rendered the comparison figure twice in Jupyter
notebooks. After `IPython.display.display(fig)` the figure remained registered in
matplotlib's figure manager, causing a second render at cell end via the
`%matplotlib inline` hook. Fixed by calling `plt.close(fig)` immediately after the
explicit `display()` call; the figure object is still returned in
`BaselineAutotuneResult.figure`.
- `method_grids` parameter annotated as `dict | None` in the three façade methods
(`_preprocess.py`, `RamanFit.py`, `PLfit.py`), which is a Python 3.10+ syntax and
raises `TypeError` at import time on Python 3.9. Annotation removed; type is
documented in the docstring. Caught by CI Python 3.9 matrix job.
- `_validate_method_grids` called `len(list(v))` to check for empty value sequences,
which consumed one-shot iterators (e.g. generators) before the candidate-building
loop in `_default_baseline_grid` could iterate them, silently producing zero
candidates and an `IndexError` at `ranking[0]`. Fixed by materialising all value
sequences to plain lists in `_default_baseline_grid` before validation; the
per-value check in `_validate_method_grids` tightened to `isinstance(v, list)` +
`len(v) == 0`.
### Deprecated
- `methods` and `lam_grid` keyword arguments on `autotune_baseline()`. Both emit
`DeprecationWarning` in v0.6.3 and will be removed in v0.6.4.
### Notebooks
- `Baseline_Autotune_Demo.ipynb` section 5 ("Real-data example"): filled in the
observation narrative — Gaussian (σ = 50) won the autotune with RMSE = 0.0712;
explains why iterative methods underperformed (peak-to-window ratio) and documents
the amplitude trade-off of the Gaussian baseline.
- `Raman_background-remove.ipynb` section 6 ("Using `gaussian` baseline"): new
worked example using `gaussian_sigma=50`, matching the structure of sections 1–4
(load → fit → plot). Includes tuning rules and a note on the amplitude trade-off.
---
## [v0.6.2] — 2026-05-20 — Seed-pixel baseline auto-tuning
### Added
- `autotune_baseline()` method on `RamanMapping` and `PLMapping` (via
`_MappingPreprocessMixin`). Scores a configurable grid of 24 baseline
candidates on a user-chosen seed pixel and returns a ranked
`BaselineAutotuneResult` without modifying the object.
- `apply_choice(choice)` method on `RamanMapping` and `PLMapping`. Commits
a baseline spec dict to `self.preprocessing`, refreshes all legacy
baseline attributes, and invalidates the preprocessed-cube cache so the
next `fit_spectra()` call picks up the new baseline.
- `autotune_baseline()` method on `RamanFit` and `PLfit`. Same diagnostic,
but scores against the single stored spectrum (`_raw_spectra_pristine`).
- `apply_choice(choice)` method on `RamanFit` and `PLfit`. Re-applies the
full pipeline from the pristine raw spectrum and refreshes all seven
derived attributes (`processed_spectra`, `_baseline`, `_smoothed_spectra`,
`_corrected_spectra`, `peak_intensity`, `preprocessing_backend_*`,
`_backend_outcome`).
- `src/ramanpl/_autotune.py` — internal module providing:
`BaselineCandidate`, `BaselineAutotuneResult`,
`_swap_baseline_step_in_pipeline`, `_score_candidate`,
`_make_comparison_figure`, `_default_baseline_grid` (24 candidates),
`autotune_baseline_for_object`.
- `_raw_spectra_pristine` and `_x_axis_pristine` attributes on `RamanFit`
and `PLfit` (set once at `__init__`, never mutated). Allow `apply_choice`
to re-apply any pipeline, including those with `CropByRange`.
- `baseline_autotune` provenance block added to mapping and single-fit
export metadata when `_last_autotune_result` is set (keys: `methods`,
`n_candidates`, `seed_coord`, `winner`, `winner_rmse`, `ranking_top5`).
- `tests/test_autotune_baseline_mapping.py` — 10 tests covering all
checklist items for mapping autotune.
- `tests/test_autotune_baseline_single_fit.py` — 12 tests covering all
checklist items for single-fit autotune.
- `benchmarks/results/bench_snapshot_v0.6.1.txt` — before-snapshot
artefact anchoring the v0.6.2 build.
- `benchmarks/results/mapping_fit_benchmark_v0.6.1.csv` — archived
v0.6.1 mapping-fit benchmark for algorithmic parity verification.
### Notes
- No changes to `baselineAPI.py`. Autotune only composes existing baseline
methods into a grid.
- No new keywords on `fit_spectra` or `fit_spectrum`. API surface unchanged.
- No new runtime dependencies. `matplotlib` and `scipy` are already required.
- `autotune_baseline()` is read-only: it never mutates the object. Only
`apply_choice()` writes to `self`.
- `apply_choice()` raises `ValueError` if the pipeline has zero or more than
one `BaselineSubtract` steps (fail-loud contract).
- Algorithmic invariance versus v0.6.1: `n_curve_fit_calls` row-by-row
identical on the v0.6.1 benchmark cube when `apply_choice` is not called.
- Behaviour notes (updated 2026-5-21): autone_baseline(plot = True) will always show a inline figure in notebooks
## [v0.6.1] — 2026-05-19 — Progress indicators for mapping & batch fits
### Added
- `tqdm>=4.66` added as a hard runtime dependency in `pyproject.toml`.
- `show_progress: bool = True` keyword added to `RamanMapping.fit_spectra` and
`PLMapping.fit_spectra`. Progress bar throttled at `mininterval=0.5`;
suppressed when `show_progress=False`.
- `show_progress: bool = True` keyword added to `_BaseBatch.fit` and threaded
through to `fit_spectra_batch`. Both `RamanBatch` and `PLBatch` inherit this
via `_BaseBatch`.
- `test_tqdm_importable` added to `tests/test_packaging_smoke.py`: verifies
`import tqdm` and `from tqdm.auto import tqdm` succeed in the installed
package.
- `benchmarks/results/mapping_fit_benchmark_v0.6.0.csv`: archived baseline
snapshot for algorithmic parity verification.
- `benchmarks/results/overhead_v0.6.1.txt`: overhead measurement record.
- `bench_snapshot_v0.6.0.txt`: git hash + pytest counts at start of this build.
### Changed
- Pixel loop in `RamanMapping.fit_spectra` (`_raman_mapping.py`) wrapped with
`tqdm`; `pbar.update(1)` called at the top of the inner loop so all pixels
(including `continue` paths) are counted correctly.
- Pixel loop in `PLMapping.fit_spectra` (`_pl_mapping.py`) wrapped identically.
- File loop in `fit_spectra_batch` (`batch.py`) wrapped: `for s in tqdm(spectra,
desc="Fitting (batch)", disable=not show_progress, mininterval=0.5)`.
### Notes
- No changes to fitting algorithms, fitted values, preprocessing, or export
schemas.
- `n_curve_fit_calls` is identical row-by-row versus the v0.6.0 baseline on
all 12 benchmark rows (algorithmic invariance confirmed).
- tqdm overhead is below single-run measurement noise (±10-20% on this machine)
and analytically < 0.01% (225 `pbar.update()` calls × ~0.5 μs each over
12–68 s of fitting). The ≤ 1% acceptance criterion is satisfied.
- Per-class `desc` strings ("Fitting (Raman batch)" / "Fitting (PL batch)")
were consolidated to `"Fitting (batch)"` because both classes share a single
inherited `_BaseBatch.fit` → `fit_spectra_batch` call path.
- Deferred: `tqdm` inside single-fit multistart loops (negligible runtime),
preprocessing pipelines (preprocessing is fast relative to fitting),
per-tile bars (tile parallelism arrives in v0.6.3).
---
## [v0.6.0] — 2026-05-13 — Stable interpretable-analysis milestone
### Declared
- First stable interpretable-analysis milestone built on v0.5.1–v0.5.5.
- Stable components: per-pixel QA-augmented mapping export, generalised peak
descriptors, `feature_table()` accessors, classical peak-proposal aid for
failed-fit recovery, optional unsupervised clustering on fitted descriptors.
- Deterministic physical fitting (Lorentzian / pseudo-Voigt least squares)
remains the final authority for all reported peak parameters.
### Added
- `benchmarks/validation_v0.6.0_vs_v0.5.0.py` (new): reproducible validation
harness comparing v0.6.0 to v0.5.0 on four synthetic cubes across fit
quality, runtime, and failure-mode axes. Reuses helpers from
`benchmark_mapping_fit.py`.
- `benchmarks/results/v0.5.0_baseline/v0.5.0_baseline.json` (tracked): v0.5.0
reference runtime and `n_curve_fit_calls` for the standard small_3x4 cube.
- `benchmarks/results/v0.5.0_baseline/checksums.txt` (tracked): SHA256 of the
v0.5.0 export, anchoring the byte-level fit-parity invariant.
- `example-usage/Validation/Validation_v0.6.0_vs_v0.5.0.ipynb` (new):
executable validation notebook covering fit-quality parity, runtime
comparison, and failure-mode comparison. Added to `test_notebook_smoke.py`.
- `docs/source/validation/v0.6.0.md` (new): citable validation report
summarising the three findings.
### Changed
- `docs/source/api-stability.md`: title and §1 scope wording updated to
"v0.5.5–v0.6.0"; the four frozen surfaces are unchanged.
- `docs/source/examples/canonical-notebooks.md`: restructured into three
sections (Backend behaviour, Interpretable-analysis, Additional examples);
added Interpretable-analysis subsection covering `Feature_Table_Example.ipynb`,
`Peak_Proposal_Demo.ipynb`, `Clustering_Demo.ipynb`, and
`Validation_v0.6.0_vs_v0.5.0.ipynb`; added `Area Integration Example.ipynb`
to Additional examples; updated Mapping Raman/PL Example descriptions to
note that integration-under-peaks moved out in v0.5.5.
- `tests/test_notebook_smoke.py`: validation notebook added to
`CANONICAL_NOTEBOOKS` (6 notebooks in standard suite); `Clustering_Demo.ipynb`
moved to `_SLOW_NOTEBOOKS` / `pytest -m slow` due to >600 s/cell runtime on
real WDF data. `slow` marker registered in `pyproject.toml`.
- `README.md`: v0.6.0 roadmap row updated to shipped state.
- `docs/source/changelog.md`: Recent releases list extended through v0.6.0.
- `RELEASE.md`: "Validation report" check section added.
- Version bumps: `pyproject.toml`, `src/ramanpl/__init__.py`, `CITATION.cff`,
`docs/source/conf.py`, `tests/test_packaging_smoke.py`.
### Notes
- No changes to fitting algorithms, preprocessing, backend resolution, export
schemas, descriptors, ml, or `feature_table()` output. v0.6.0 is a milestone
freeze; the only change under `src/ramanpl/` is the `__version__` line.
- Supervised classification (e.g. layer-number labels) remains deferred to
v0.7.x, conditional on the availability of curated, labelled, multi-instrument
datasets.
- Validation evidence committed in `benchmarks/results/v0.5.0_baseline/` and
reproduced by `benchmarks/validation_v0.6.0_vs_v0.5.0.py`.
- Test suite: PASS=253, SKIP=3, FAIL=0 (base, excluding
`test_mapping_backend_parity.py` and `test_preprocessing_backend_resolution.py`;
full suite 270 passed).
- Notebook smoke: 6 notebooks executed (standard suite), 0 failures.
`Clustering_Demo.ipynb` runs under `pytest -m slow` (not counted here).
## [v0.5.5] — 2026-05-10 — Consolidation pause: API freeze, docs, dependency hygiene
### Added
- `docs/source/api-stability.md`: written, citable freeze contract for the
public surface introduced in v0.5.1–v0.5.4. Pins suffix vocabulary
(`_position`, `_fwhm`, `_peak_height`, `_peak_height_norm`, `_separation`,
`_ratio`), QA column names (`rmse`, `ok`, `n_starts`, `n_params_at_bounds`),
all six `feature_table()` methods, `build_feature_row`, `validate_peak_pairs`,
`pca_reduce`, `kmeans_cluster`, and the `[ml]` extra boundary.
- `tests/test_api_stability.py` (new, 8 tests): enforces the additive-only
column rule, frozen suffix vocabulary, and frozen QA/ML/descriptor public
surfaces as regression tests. Uses the same synthetic mapping fixtures as
`test_feature_table_mapping.py`.
- Three new tests in `tests/test_descriptors_unit.py`: ratio/separation order
convention — `test_ratio_order_swap_yields_reciprocal_when_both_finite_nonzero`,
`test_separation_order_swap_yields_negation`,
`test_ratio_zero_denominator_asymmetry`.
- One new test in `tests/test_feature_table_mapping.py`:
`test_feature_table_pl_mapping_ratios_and_separations` — PL mapping with
both ratios and separations, values verified against hand calculation.
- One new test in `tests/test_packaging_smoke.py`:
`test_descriptors_top_level_import_works` — verifies the new top-level
`from ramanpl import descriptors` import path.
- `docs/source/installation.md`: "Optional ML extra" section with explicit
base-install guarantee (safe import, `ImportError` only on call).
- `docs/source/quickstart.md`: `feature_table()` mention with one-line
code example.
- User-guide cross-links: `mapping.md`, `batch.md`, `single-spectrum.md`
each link to `clustering`; `clustering.md` gains a "Where the feature
table comes from" subsection linking back to all three sources.
- PL-parity paragraphs added to `mapping.md` and `clustering.md`.
- Order-convention paragraphs added to `mapping.md`, `batch.md`, and
`single-spectrum.md`.
- Three new notebooks added to `tests/test_notebook_smoke.py`:
`Feature_Table_Example.ipynb`, `Peak_Proposal_Demo.ipynb`,
`Clustering_Demo.ipynb` (the last guarded by a scikit-learn skipif).
`Integration Raman Example.ipynb` changed to `Area Integration Example.ipynb` to reflect the integration under a peak of both PL and Raman mapped spectrum.
### Changed
- `src/ramanpl/__init__.py`: `"descriptors"` added to `__all__` and
`_MODULE_EXPORTS`; `from ramanpl import descriptors` now works.
- `src/ramanpl/__init__.py`: `__version__` `0.5.4` → `0.5.5`.
- `pyproject.toml`: version `0.5.4` → `0.5.5`.
- `CITATION.cff`: version `0.5.4` → `0.5.5`; `date-released` → `2026-05-10`.
- `docs/source/conf.py`: release `0.5.4` → `0.5.5`.
- `README.md`: `[ml]` install option added to Installation section; v0.5.5
roadmap row updated to reflect shipped items.
- `tests/test_packaging_smoke.py`: version assertion updated to `0.5.5`.
- `.github/workflows/ci.yml` `notebook-smoke`: install extended from
`.[ramanspy]` to `.[ramanspy,ml]`.
### Notes
- No changes to fitting algorithms, preprocessing, backend resolution, or
export schemas. v0.5.5 is a freeze build.
- `ramanpl.descriptors` was already present in the API reference (since
v0.5.2); this release adds it to `__all__` and `_MODULE_EXPORTS` to
match its documented status.
- Notebook smoke timeout raised from 120 s to 600 s per cell to accommodate
the three new notebooks, which execute real map fits on WDF datasets.
- Test delta: +13 tests over v0.5.4 baseline (226 base, 14 ml-extras).
Base suite: PASS=226, SKIP=9, FAIL=0. ML extras: PASS=14, SKIP=0, FAIL=0.
Notebook smoke: 6 notebooks executed, 0 failures. Sphinx docs: 0 new
warnings (43 pre-existing autodoc warnings unchanged).
## [v0.5.4] — 2026-05-09 — Optional [ml] extra: unsupervised clustering on feature tables
### Added
- `src/ramanpl/ml/__init__.py` and `src/ramanpl/ml/clustering.py`: new optional
subpackage. Two composable public functions:
- `pca_reduce(df, n_components, *, feature_columns=None, scale=True)` — appends
`pc1`…`pck` columns; explained variance attached to
`df.attrs["explained_variance_ratio_"]` and feature columns used to
`df.attrs["pca_feature_columns"]`.
- `kmeans_cluster(df, n_clusters, *, feature_columns=None, scale=True,
random_state=None)` — appends a `cluster` column (nullable `Int64`).
Both functions consume any wide DataFrame with numeric columns (canonically
`feature_table()` output from v0.5.2). Failed-fit rows (NaN features) are
dropped internally and re-injected with NaN labels, preserving the input
index. Both lazy-import scikit-learn; raise a clean `ImportError` mentioning
the `[ml]` extra when scikit-learn is absent. Importing `ramanpl.ml` and
`ramanpl.ml.clustering` without scikit-learn installed is safe.
- `pyproject.toml`: new `[ml]` optional dependency on `scikit-learn>=1.2`.
Base install is unaffected.
- `tests/test_ml_clustering.py`: 14 unit tests covering returned columns,
index alignment, NaN reinjection, recovery of known cluster structure
(`adjusted_rand_score >= 0.99`), determinism under `random_state`, validation
errors, and a chained `pca_reduce` → `kmeans_cluster` composition test.
- `tests/test_packaging_smoke.py`: extended with `test_ml_namespace_importable_in_base_install`
and `test_ml_functions_raise_clean_error_without_sklearn`.
- `example-usage/Mapping/Clustering_Demo.ipynb`: end-to-end notebook
demonstrating `feature_table` → `pca_reduce` → `kmeans_cluster` → 2-D
cluster map and PC scatter plot on the Mapping Raman Sample dataset.
- `docs/source/user-guide/clustering.md`: user guide page covering
installation, `pca_reduce` usage, chained `pca_reduce` → `kmeans_cluster`
usage, and the `df.attrs["explained_variance_ratio_"]` accessor.
- `docs/source/api/ramanpl.ml.rst`: API reference for `ramanpl.ml` and
`ramanpl.ml.clustering` via `automodule`.
- `.github/workflows/ci.yml` and `.gitlab-ci.yml`: new `ml-extras-tests` /
`ml-extras-smoke` CI jobs mirroring the `[ramanspy]` extras pattern.
### Changed
- `pyproject.toml`: version `0.5.3` → `0.5.4`.
- `src/ramanpl/__init__.py`: `__version__` `0.5.3` → `0.5.4`.
- `CITATION.cff`: version `0.5.3` → `0.5.4`; date-released updated.
- `docs/source/conf.py`: release `0.5.2` → `0.5.4` (corrects drift from the
v0.5.3 cycle); `"sklearn"` added to `autodoc_mock_imports`.
- `.github/workflows/ci.yml` `base-tests` and `.gitlab-ci.yml` `base-tests`:
added `--ignore=tests/test_ml_clustering.py` so base CI does not require
scikit-learn.
### Notes
- No changes to fitting, preprocessing, backend resolution, export schemas, or
`feature_table()` output. v0.5.4 is purely additive.
- `ramanpl.ml.clustering` operates on fitted peak descriptors, not on raw
spectra. No claim of automatic material identification is made; clustering
is for exploratory domain discovery only.
- Test suite: PASS=213, SKIP=6, FAIL=0 (base); PASS=13, SKIP=0, FAIL=0 (ml-extras).
## [v0.5.3] — 2026-05-06 — Classical peak-proposal aid for failed-fit recovery
### Added
- `src/ramanpl/single_fit/initialisation.py` (new): classical peak-proposal helpers
based on `scipy.signal.find_peaks`. Two public functions:
`propose_peaks(spectrum, wavenumber, n_peaks, prominence_rel, width_min_pts)` returns
a list of candidate peak dicts (centre, width/FWHM, height) sorted by height; returns
an empty list when no peaks meet the prominence threshold.
`p0_from_proposals(proposals, peak_profile, current_p0, bounds)` substitutes proposal
centres and widths into a parameter vector where they lie within bounds, using greedy
nearest-neighbour matching so each proposal is used at most once.
Module imports only `numpy` and `scipy.signal` — no ramanpl internal coupling.
- `use_peak_proposals=True` keyword argument on `_run_mapping_curve_fit_trials` in
`src/ramanpl/mapping/_fit_utils.py`. When `True` (default) and all normal multistart
trials have failed, one additional curve-fit attempt is made from a proposal-corrected
starting point using `max(maxfev, 6400)` evaluations.
- `tests/test_peak_proposal.py` (new): 9 unit tests for `propose_peaks` and
`p0_from_proposals` (single/two-peak detection, n_peaks limit, flat-spectrum guard,
width estimate tolerance, centre replacement, out-of-bounds fallback, empty-proposals
fallback, amplitude/eta preservation for lorentzian and pvoigt).
- `tests/test_peak_proposal_integration.py` (new): 8 integration tests exercising the
wired fallback path in `_fit_utils.py` (overlapping-pixel rescue, weak-pixel rescue,
easy-pixel Lorentzian parity, easy-pixel pvoigt parity, disabled-flag guard,
position-drift tolerance, FWHM-drift tolerance, hard-cube failure-count decrease).
- `example-usage/Mapping/Peak_Proposal_Demo.ipynb` (new): end-to-end notebook
demonstrating the peak-proposal fallback on a synthetic Raman mapping dataset —
covers `propose_peaks` / `p0_from_proposals` directly, the wired fallback via
`fit_spectra()`, and residual-map comparison before and after enabling proposals.
- `docs/source/user-guide/low_snr_advisory.md` (new): seven-step advisory guide for
lower-SNR or more challenging datasets — seed-pixel selection, single-spectrum
bounds validation, preprocessing comparison, bound tightening, Lorentzian vs
pseudo-Voigt choice, multistart tuning, and `prominence_rel` / `width_min_pts`
sweep for the proposal fallback. Wired into the Sphinx User guide toctree in
`docs/source/index.md`.
### Changed
- `src/ramanpl/mapping/_fit_utils.py`: one new import (`propose_peaks`,
`p0_from_proposals` from `ramanpl.single_fit.initialisation`), one new `use_peak_proposals`
keyword in `_run_mapping_curve_fit_trials`, and a ≤ 16-line proposal fallback block
inserted after the normal multistart loop. All other lines unchanged.
### Notes
- No changes to fitting algorithms, preprocessing, backend resolution, or export schemas.
- All pixels that succeed via the existing multistart path produce bit-identical output
to v0.5.2 (verified by parity tests `test_no_change_on_easy_pixel` and
`test_no_change_on_easy_pixel_pvoigt`).
- No new dependencies — `scipy.signal` is already a base dependency.
- Benchmark evidence (hard-pixel cube, 16 pixels, maxfev=1 normal trial): 16/16 fail
without proposals, 0/16 fail with proposals. Easy-cube (8×8 standard cube):
n_curve_fit_calls unchanged, mean_rmse unchanged (proposal path not entered).
- `ramanpl.ml` namespace and unsupervised clustering deferred to v0.5.4 per roadmap.
## [v0.5.2] — 2026-05-05 — Generalised peak descriptors and feature-table accessor
### Added
- `src/ramanpl/descriptors.py` (new): pure-numpy module with two public functions:
`build_feature_row(per_peak_dict, qa_dict, peak_labels, *, ratios, separations)` constructs a
flat feature dict for one pixel/spectrum (per-peak columns `{name}_position`, `{name}_fwhm`,
`{name}_peak_height`, `{name}_peak_height_norm`; optional `{P1}_{P2}_separation` and
`{P1}_{P2}_ratio` derived columns; QA passthrough); `validate_peak_pairs(pairs, peak_labels)`
raises `ValueError` naming the unknown label when a pair references an undefined peak.
Module imports only `numpy` — no pandas, no internal coupling.
- `feature_table(*, coord_mode, scaled, ratios, separations)` method on `_MappingPreprocessMixin`
in `src/ramanpl/mapping/_preprocess.py`. Inherited by both `RamanMapping` and `PLMapping`.
Returns a wide-format `pandas.DataFrame` (one row per pixel). Failed pixels emit a full row with
`ok=False` and NaN per-peak/derived fields. Pandas imported lazily inside the method.
- `feature_table(*, ratios, separations)` method on `_BaseBatch` in `src/ramanpl/batch.py`.
Returns one row per source file with a leading `source` column. Requires
`.fit(return_fitters=True)` (same requirement as `summary_by_peak()`).
- `feature_table(*, ratios, separations)` method on `RamanFit` in
`src/ramanpl/single_fit/RamanFit.py`: single-row DataFrame.
- `feature_table(*, ratios, separations)` method on `PLfit` in
`src/ramanpl/single_fit/PLfit.py`: single-row DataFrame.
- `tests/test_descriptors_unit.py` (new): 5 unit tests for the descriptor builder (two-peak
hand calculation, pVoigt eta silently ignored, NaN propagation, zero-denominator ratio → NaN,
`validate_peak_pairs` error message names offending label).
- `tests/test_feature_table_mapping.py` (new): 9 tests covering DataFrame shape, column schema,
ratio/separation values, failed-pixel NaN row, invalid-pair ValueError, PLMapping parity,
peak-height vs `peak_intensities` array agreement, no-pairs column absence, no-fit guard.
- `tests/test_feature_table_batch_and_single.py` (new): 6 tests covering RamanBatch and PLBatch
one-row-per-source, RamanFit and PLfit single-row, separation-vs-summary numerical agreement,
and QA column name consistency across all three class families.
- User-guide "Feature tables" subsections in `docs/source/user-guide/mapping.md`,
`docs/source/user-guide/batch.md`, and `docs/source/user-guide/single-spectrum.md`.
- `ramanpl.descriptors` module added to `docs/source/api/ramanpl.rst` API reference.
- `examples/Mapping/Feature_Table_Example.ipynb` (new): example usage of `Mapping.feature_table()` to build a DataFrame with peak positions, heights, ratios, and separations; pivoted into 2-D maps for visualisation.
### Fixed (post-merge cleanup)
- Untracked benchmarks/results/v0.5.0_baseline/ — these snapshot files
were inadvertently committed in the initial v0.5.2 commit and are not
portable across LAPACK/scipy builds. .gitignore extended to prevent
re-committal. Restores v0.5.1's CI behaviour where the byte-parity
trip-wire test is skipped on runners and runs locally for developers
who have regenerated the snapshot.
- tests/test_export_fit_map_qa_columns.py::
test_wide_format_existing_columns_unchanged — relaxed from byte-level
string equality to numerical tolerance (rtol=atol=1e-3). The test
still catches genuine scientific drift but no longer fails on FP
noise across LAPACK builds.
### Notes
- **No scientific changes.** Fitting algorithms, baseline algorithms, preprocessing, and all
scientific output values are unchanged. Parity verified against the v0.5.0 reference snapshot via
`benchmarks/_step4_parity.py`: `fitted_params`, `residual_map`, and pre-existing per-peak export
columns are bit-identical.
- **v0.5.1 export schema unchanged.** `export_fit_map` output (wide and long formats) is
unmodified. `_raman_mapping.py` and `_pl_mapping.py` are untouched.
- **No new dependencies.** `pandas` is already a base dependency. `ramanpl.descriptors` imports
only `numpy`.
- **Additive API only.** `feature_table()` is the only new public method per class. No existing
methods were renamed or removed.
- **Test suite:** 218 passed, 5 skipped, 0 failures (198 v0.5.1 baseline + 20 new tests).
- **Out of scope, deferred to later builds:** vectorising `_params_to_export_dict()`,
caching `feature_table()` output, long-format `feature_table()` (use `.melt()` instead),
refactoring `plot_map`/`plot_ratio_heatmap` to share code with the descriptor builder
(deferred to v0.5.3+), peak proposals (v0.5.3), ML extras (v0.5.4).
--------
## [v0.5.1] — 2026-05-03 Mapping-fit benchmark and per-pixel QA columns
### Added
- `benchmarks/benchmark_mapping_fit.py` (new): mapping-fit performance benchmark mirroring the
structure of `benchmark_mapping_preprocessing.py`. Sweeps synthetic cubes across `warm_start`
on/off and `n_starts ∈ {1, 4}` with fixed `random_state=42`. Records per-case `runtime_s`,
`n_curve_fit_calls`, `success_rate`, `mean_rmse_finite`, and `n_failed_pixels` to
`benchmarks/results/mapping_fit_benchmark.csv`. Module docstring records the runtime-fluctuation
caveats: wall-clock is advisory because curve_fit iteration count, warm-start state propagation,
and adaptive-multistart retries introduce structural variability that is not noise.
`n_curve_fit_calls` (counted via local `unittest.mock.patch` on `scipy.optimize.curve_fit`) is
the hardware-independent primary metric; future builds claiming a speed-up must show a reduction
in this count, not only in `runtime_s`.
- `_qa_columns_for_pixel()` helper on `_MappingPreprocessMixin` returning a fixed-schema dict with
`rmse`, `ok`, `n_starts`, `n_params_at_bounds` for any pixel, regardless of fit success.
Resolves `rmse`/`ok` from `residual_map` and the diagnostic counts from `fit_diagnostics_map`,
with NaN fall-back when `diagnostics='none'` was used.
- Per-pixel QA columns (`rmse`, `ok`, `n_starts`, `n_params_at_bounds`) appended to every row of
`export_fit_map` in both `RamanMapping` and `PLMapping`. Failed pixels now emit a fully-populated
row with NaN-valued parameter columns and `ok=False`, replacing the previous `{x, y}`-only
rows for failed pixels. This makes the export a fixed-schema, machine-readable table.
- `long=True` keyword-only argument on `export_fit_map` in both mapping classes. Produces
one-row-per-(pixel, peak) output with schema
`x, y, peak, centre, fwhm, peak_height, peak_height_norm, amp, scale[, eta], rmse, ok, n_starts, n_params_at_bounds`.
The metadata header records `export_format: "long"` to distinguish from the default wide format.
- `tests/test_export_fit_map_qa_columns.py` (new): 5 tests covering header content, value parity
with `residual_map`, failed-pixel row emission, fall-back when `fit_diagnostics_map=None`, and
byte-level parity of pre-existing per-peak columns against a v0.5.0 reference snapshot.
- `tests/test_export_fit_map_long_format.py` (new): 5 tests covering row count, column schema,
pixel-peak round-trip via `pivot_parameters`, failed-pixel row count, and `export_format`
metadata field.
- `tests/test_release_benchmark_smoke.py`: 3 new smoke tests confirming `benchmark_mapping_fit.py`
builds cases, runs the smallest case, and produces records with the expected fields.
### Changed
- `pyproject.toml`: added `pytest.ini_options.pythonpath = ["src"]` so tests resolve `ramanpl`
imports without an editable install. Test command unchanged for users.
### Notes
- **No scientific changes.** Fitting algorithms, baseline algorithms, preprocessing, and all
scientific output values are unchanged. Parity was verified against a v0.5.0 reference snapshot:
`fitted_params`, `residual_map`, `fit_diagnostics_map`, and the pre-existing per-peak columns of
the wide-format export are bit-identical to v0.5.0 on the same synthetic cube.
- **Backward compatible.** Existing wide-format column names and ordering are preserved. The four
QA columns are appended at the end. Default behaviour with `long=False` (the default) reads
identically to v0.5.0 except for the appended QA columns and the new fully-populated rows for
failed pixels.
- **Failed-pixel row format change.** In v0.5.0, failed pixels emitted rows containing only `x`
and `y` (other columns blank). In v0.5.1, failed pixels emit a complete row with NaN-valued
parameter and QA columns and `ok=False`. Downstream readers that depended on blank cells as the
failure indicator should switch to checking `ok` or `np.isnan(rmse)` instead.
- **Heatmap plotting unaffected.** `plot_heatmap`, `plot_residual_map`, `plot_ratio_heatmap`, and
`inspect_residuals` read directly from in-memory arrays (`fitted_params`, `residual_map`,
`peak_intensities`), not from the export file. The export-row change is decoupled from the
rendering path.
- **Test suite:** 198 passed, 5 skipped, 0 failures (full local run). 13 new tests across the
three test files listed above.
- **Out of scope, deferred to later builds:** generalised peak ratios and separations and
`Mapping.feature_table()` accessor are deferred to v0.5.2; classical peak-proposal aid for
failed-fit recovery is deferred to v0.5.3; any `ramanpl.ml` namespace and `[ml]` extra are
deferred to v0.5.4 (see roadmap in `README.md`).
--------
## [v0.5.0] — 2026-04-26 — Stable public milestone
### Added
- Stable public milestone release for RamanPL_2D.
- Finalised documentation, release validation, and public repository presentation.
- Read the Docs documentation configuration linked from README once hosted site is live.
### Changed
- Synchronised package version metadata across `pyproject.toml`, `__init__.py`, documentation config, and citation metadata.
- Clarified public API and internal API boundaries in generated documentation.
- Simplified README into a package landing page.
- Fixed packaging metadata: README is now declared statically in `pyproject.toml` (`readme = {file = ...}`) rather than via `dynamic = ["readme"]`.
### CI
- Fixed base CI: `test_batch_backend_regressions.py` and `test_single_fit_regressions.py` now run in base CI (no longer silently ignored).
- Fixed extras CI: `test_single_fit_backend_parity.py` now runs in the RamanSPy extras job.
- Fixed notebook-smoke CI: installs `./src[ramanspy]` so the RamanSPy-backed path is actually exercised.
- Aligned GitLab CI test categorisation with GitHub Actions.
### Notes
- No new fitting, baseline, preprocessing, or backend algorithms are introduced.
- Public preprocessing/backend behaviour is frozen for the supported Raman workflows.
- PL workflows and Gaussian baseline remain native-only.
---
## [v0.4.12] — 2026-04-24 Documentation scaffold and hosted docs preparation
### Added
- **`docs/`** — in-repository Sphinx documentation scaffold with `docs/source/` and
`docs/requirements.txt`.
- **Furo theme** — configured via `html_theme = "furo"` in `docs/source/conf.py`.
- **MyST Markdown support** — `myst-parser` extension enabled; all user-guide and
reference pages are written in Markdown.
- **User-guide pages** — `installation.md`, `quickstart.md`,
`user-guide/preprocessing.md`, `user-guide/backend-behaviour.md`,
`user-guide/single-spectrum.md`, `user-guide/mapping.md`,
`user-guide/batch.md`, `user-guide/export-provenance.md`.
- **Examples index** — `examples/canonical-notebooks.md` linking the three canonical
backend-behaviour notebooks and additional example notebooks.
- **API reference** — `docs/source/api/index.rst` covering stable public modules:
`ramanpl.single_fit`, `ramanpl.mapping`, `ramanpl.batch`, `ramanpl.preprocessing`,
`ramanpl.baselineAPI`, `ramanpl.exporter`, `ramanpl.dataImporter`.
- **`.readthedocs.yaml`** — Read the Docs build configuration at repository root;
targets Python 3.11, ubuntu-24.04, installs from `docs/requirements.txt` and `./src`.
- **CI docs-build job** — smoke build added to `.github/workflows/ci.yml`; fails CI if
Sphinx cannot build the documentation.
### Changed
- **`README.md`** — added `Documentation` section with local build instructions and
links to key documentation pages; detailed usage guidance now lives in `docs/`.
- **`.gitignore`** — added `docs/build/` and `docs/source/_build/` to prevent
generated HTML from being committed.
### Notes
- No scientific behaviour changes.
- No fitting, preprocessing, backend, or export API changes.
- Documentation lives in the main `RamanPL_2D` repository (no separate docs repo).
- `fail_on_warning: false` in `.readthedocs.yaml` — to be tightened in a later build
once API cross-references are stable.
---
## [v0.4.11] — 2026-04-23 Repository hardening and milestone freeze
### Added
- **`SECURITY.md`** — vulnerability reporting policy: supported versions, private disclosure
route, and response expectations.
- **`CONTRIBUTING.md`** — contribution guidelines: fork/branch/test/PR/changelog flow,
scientific-behaviour preservation rule, issue vs feature-request distinction.
- **`.github/CODEOWNERS`** — review ownership for `src/`, `tests/`, `benchmarks/`,
`.github/workflows/`, `README.md`, and `CHANGELOG`.
- **`CITATION.cff`** — CFF 1.2.0 citation metadata for the package as research software.
- **`RELEASE.md`** — milestone-freeze checklist: the operational gate used to validate
a release candidate before tagging; serves as the bridge toward v0.5.0.
### Changed
- **`src/pyproject.toml`** — promoted from minimal build-system stub to main packaging
metadata source: full `[project]` table (`name`, `version`, `description`, `requires-python`,
`license`, `authors`, `classifiers`, `dependencies`, `[project.optional-dependencies]`,
`[project.urls]`), `[tool.setuptools.dynamic]` readme (resolved from `../README.md`),
`[tool.setuptools.packages.find]`, and `[tool.setuptools.package-data]`.
Build-system requirement updated to `setuptools>=61`.
- **`src/setup.py`** — reduced to a two-line shim (`from setuptools import setup; setup()`);
all metadata now lives in `pyproject.toml`.
- **`README.md`** — Source Code Structure tree updated (`install.ipynb` removed, `pyproject.toml`
added); installation section updated to remove the `install.ipynb` option; Option 3 renumbered
to Option 2.
- **`.gitignore`** — extended to cover standard local/build/release artefacts: `build/`,
`dist/`, `.pytest_cache/`, `.mypy_cache/`, `.ruff_cache/`, `.coverage`, `coverage.xml`,
`coverage.lcov`, and `benchmarks/results/*.csv`.
### Removed
- **`src/install.ipynb`** — installation notebook removed from the source tree; `src/` now
contains only package and build content. Installation instructions remain available in
`README.md`.
### Notes
- No new scientific or algorithmic features are introduced in this release.
- No public API or backend behaviour changes.
- This release serves as milestone freeze preparation for v0.5.0.
- `benchmarks/results/mapping_preprocess_benchmark.csv` is a pre-existing tracked generated
file; the new `.gitignore` rule prevents future regenerations from being committed.
Run `git rm --cached benchmarks/results/mapping_preprocess_benchmark.csv` to untrack it.
---
## [v0.4.10] — 2026-04-22 Release validation and packaging gates
### Added
- **`src/pyproject.toml`** — PEP 517/518 build-system declaration (`setuptools` + `wheel`);
enables `python -m build` from the `src/` directory in clean environments.
- **`MANIFEST.in`** — explicit sdist manifest ensuring `README.md`, `LICENSE`, and
`raman_materials.json` are always present in source distributions.
- **`.github/workflows/ci.yml`** — GitHub Actions CI matrix (Python 3.9–3.11) with six jobs:
- `base-tests`: install base package, run pytest suite without RamanSPy.
- `extras-tests`: install `.[ramanspy]`, run backend-parity and resolution tests.
- `package-build`: build wheel and sdist via `python -m build`.
- `clean-install-smoke`: install built wheel into a fresh environment, run packaging smoke tests.
- `notebook-smoke`: execute the three canonical backend-behaviour notebooks headlessly.
- `benchmark-smoke`: validate benchmark harness execution and output structure.
- **`.gitlab-ci.yml`** — lightweight GitLab CI mirror covering base tests, package build,
clean-install smoke, benchmark smoke, and optional RamanSPy extras smoke (`allow_failure: true`).
- **`tests/test_packaging_smoke.py`** — five packaging smoke tests:
- `test_import_top_level_package` — confirms `ramanpl.__version__` after install.
- `test_import_public_entry_points` — confirms all `__all__` names resolve without error.
- `test_package_data_materials_json_present` — confirms `raman_materials.json` is discoverable
at runtime via `importlib.resources`.
- `test_base_install_does_not_require_ramanspy` — confirms `has_ramanspy()` returns a bool
without raising when RamanSPy is absent.
- `test_optional_ramanspy_import_path_is_guarded_cleanly` — confirms integration submodules
import without `ImportError` in a base-only install.
- **`tests/test_notebook_smoke.py`** — parametrised smoke execution of the three canonical
backend notebooks using `nbconvert`; skipped automatically when `nbconvert` is absent.
- **`tests/test_release_benchmark_smoke.py`** — seven smoke tests validating that the baseline
kernel and mapping preprocessing benchmark harnesses run, return records, and emit
structurally valid fields; no runtime thresholds are enforced.
### Changed
- **`src/setup.py`** — version bumped to `0.4.10`.
- **`src/ramanpl/__init__.py`** — `__version__` bumped to `0.4.10`.
- **`README.md`** — added **Release validation** section documenting the canonical commands
for base install, extras install, wheel/sdist build, clean-install smoke, notebook smoke,
and benchmark smoke; clarifies that full performance comparisons remain advisory.
### Tests
- New packaging smoke coverage (`test_packaging_smoke.py`): import integrity, data-file
presence, optional-dependency boundary.
- New notebook smoke coverage (`test_notebook_smoke.py`): headless execution of
`Raman_backend_demo.ipynb`, `Raman_mapping_backend_demo.ipynb`, `Backend_fallback_cases.ipynb`.
- New benchmark smoke coverage (`test_release_benchmark_smoke.py`): harness health for
both baseline-kernel and mapping-preprocessing benchmarks.
### Notes
- Public API unchanged. No scientific algorithm changes introduced.
- Export provenance and metadata fields unchanged.
- Benchmark smoke tests assert structural validity only; full performance comparisons
remain advisory and are run manually outside CI.
---
## [v0.4.9] — 2026-04-21 Native baseline optimisation
### Changed
- **`src/ramanpl/baselineAPI.py`** — iterative Whittaker kernel optimisation (asLS, arPLS, airPLS):
- Sparse diagonal matrix `W = diag(w)` is now pre-allocated once before the iteration loop;
`W.data[:]` is updated in-place each iteration, eliminating one `sparse.diags()` allocation per
iteration.
- System matrix `Z = W + lam*DtD` (or `W + lam*DtD + ridge*I` for airPLS) is now pre-built once
from the fixed penalty structure; only the diagonal entries are overwritten in-place each
iteration via a pre-computed index array, eliminating one sparse addition allocation per
iteration. `scipy.sparse.linalg.spsolve` reads from the updated `Z.data` each call without
modifying it.
- RHS vector `w * y` is computed into a pre-allocated buffer with `numpy.multiply(..., out=rhs)`
to avoid a temporary allocation per iteration.
- Mask float array `m.astype(float)` is pre-cast once before the loop (was re-cast each
iteration in asLS and arPLS).
- `w_prev` convergence buffer (arPLS) is pre-allocated and updated with `numpy.copyto`.
- Benchmark result (n=1024): asLS ~22% faster, arPLS ~29% faster, airPLS ~3% faster; at
n=512 airPLS ~71% faster.
- **`src/ramanpl/preprocessing.py`** — native iterative baselines promoted to mapping fast path: