Skip to content

Implement full "point" decoding by processing "null marking" transformations#11

Merged
pjones merged 4 commits into
trunkfrom
pjones/point
Jul 14, 2026
Merged

Implement full "point" decoding by processing "null marking" transformations#11
pjones merged 4 commits into
trunkfrom
pjones/point

Conversation

@pjones

@pjones pjones commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added support for PSI transformation metadata, including zero-intensity trimming and interpolation.
    • Improved spectrum decoding with delta-model support for marked null values.
    • Added reusable decoding utilities for numeric and missing-value handling.
    • Added algorithms for calculating value deltas and median deltas.
  • Bug Fixes

    • Corrected data-type dispatch for 8-bit and unsigned 32-bit values.
    • Improved validation and error handling for unsupported or incomplete array encodings.
  • Tests

    • Expanded spectrum validation, including expected values and monotonic ordering checks.

pjones added 2 commits July 14, 2026 10:58
Also incorporate the dimension struct into the array index where it
belongs.
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@pjones, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6dd6a087-1f7e-42c6-a6e8-c90d1793ede7

📥 Commits

Reviewing files that changed from the base of the PR and between 728dde4 and 534c0f6.

📒 Files selected for processing (10)
  • include/mzpeak/data/encoding.h
  • include/mzpeak/data/null_marking.h
  • include/mzpeak/spectrum.h
  • include/mzpeak/util/algorithm.h
  • include/mzpeak/util/decoders.h
  • include/mzpeak/util/delta_estimator.h
  • meson.build
  • src/spectra.cpp
  • src/spectrum.cpp
  • test/spectra_test.cpp
📝 Walkthrough

Walkthrough

Changes

The PR replaces untyped dimension transforms with PSI transform objects, introduces grouped ArrayIndex::Dimension metadata, and refactors spectrum decoding around typed decimal/integer decoders, delta estimation, and null-marking interpolation.

Typed spectrum decoding

Layer / File(s) Summary
Array-index dimension and transform contract
include/mzpeak/data/array_index.h, include/mzpeak/schema/psi/transform.h, src/data/array_index.cpp, src/schema/psi/transform.cpp
Array-index entries use typed PSI transforms, and entries are grouped into dimensions carrying transform metadata and delta-model state.
Null and delta decoding infrastructure
include/mzpeak/data/encoding.h, include/mzpeak/data/null_marking.h, include/mzpeak/util/algorithm.h, include/mzpeak/util/decoders.h, include/mzpeak/util/delta_estimator.h, include/mzpeak/schema/psi/data_type.h
Typed decoding supports decimal and integer remapping, null strategies, delta estimation, and null-marking interpolation.
Signals and spectrum integration
include/mzpeak/data/signals.h, src/data/signals.cpp, include/mzpeak/spectrum.h, src/spectra.cpp, src/spectrum.cpp
Projection and spectrum construction use ArrayIndex::Dimension, shared signal ownership, and the new decoder.
Build and decoding validation
meson.build, test/array_index_test.cpp, test/spectra_test.cpp
The transform source and public headers are installed, test selection is updated, and typed transforms plus monotonic m/z output are checked.

Sequence Diagram(s)

sequenceDiagram
  participant Spectra
  participant Signals
  participant Spectrum
  participant Decoder
  Spectra->>Signals: select projected dimensions
  Signals-->>Spectra: return slice
  Spectra->>Spectrum: construct spectrum
  Spectrum->>Decoder: decode m/z and intensity
  Decoder-->>Spectrum: return decoded arrays
Loading

Possibly related PRs

  • OpenMS/mzpeak#7: Refactors the same array-index, transform, and spectrum-decoding APIs.

Poem

I’m a rabbit with typed fields in tow,
Through null-marked valleys, smooth deltas flow.
Transforms now bloom where strings once stayed,
And m/z hops in order, neatly arrayed.
Snuffle-snuffle—decoding’s bright! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: implementing point decoding with null-marking transformation handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pjones/point

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/data/array_index.cpp (1)

88-88: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Ensure deterministic ordering of array entries.

std::ranges::sort is not stable, so sorting solely by array_name can scramble the relative order of entries that share the same array_name (e.g., sequentially chunked arrays mapped to the same dimension). Consider adding name as a secondary sort key to guarantee a deterministic chunk order prior to grouping.

🐛 Proposed fix
-  std::ranges::sort(entries_, {}, &Entry::array_name);
+  std::ranges::sort(entries_, [](const auto& a, const auto& b) {
+    if (a.array_name != b.array_name) return a.array_name < b.array_name;
+    return a.name < b.name;
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/data/array_index.cpp` at line 88, Update the entries_ sort in the
array-index construction path to use array_name as the primary key and name as a
secondary key, ensuring entries sharing an array_name have deterministic chunk
ordering before grouping.
🧹 Nitpick comments (5)
test/array_index_test.cpp (1)

34-36: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer .value() over * to prevent potential undefined behavior.

BOOST_TEST does not halt execution on failure. If Schema::CV::from_string were to fail in the future, *transform_cv would invoke undefined behavior. Using .value() will throw std::bad_optional_access instead, safely failing the test.

♻️ Proposed refactor
   std::optional<Schema::CV> transform_cv(Schema::CV::from_string("MS:1003901"));
   BOOST_TEST((transform_cv.has_value()));
-  std::optional<Schema::PSI::Transform> transform(*transform_cv);
+  std::optional<Schema::PSI::Transform> transform(transform_cv.value());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/array_index_test.cpp` around lines 34 - 36, Update the construction of
Schema::PSI::Transform from transform_cv to use transform_cv.value() instead of
dereferencing it with *. Keep the existing BOOST_TEST assertion and surrounding
test flow unchanged.
include/mzpeak/util/algorithm.h (1)

61-76: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Redundant sort before boost::math::statistics::median.

Boost's median() is a wrapper around std::nth_element, which doesn't require (or benefit from) pre-sorted input. The manual std::ranges::sort(ds) here is redundant O(n log n) work on the decode hot path (invoked per null-run boundary during spectrum decoding).

♻️ Suggested simplification
   std::vector<T> ds(deltas(values));
   if (ds.empty()) return {};
 
-  std::ranges::sort(ds);
   T median = boost::math::statistics::median(ds.begin(), ds.end());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@include/mzpeak/util/algorithm.h` around lines 61 - 76, Remove the redundant
std::ranges::sort call from median_delta and rely on
boost::math::statistics::median for the initial median calculation. Preserve the
existing delta filtering, empty-result handling, and final median behavior.
src/schema/psi/transform.cpp (1)

67-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fix unconditionally evaluated static_assert.

The static_assert(false) in the else branch of the if constexpr will evaluate unconditionally and fail compilation on compilers that haven't fully implemented CWG2518 from C++23 (such as Clang 14, as indicated by the static analysis error).

To ensure compatibility across a broader range of compilers, make the assertion dependent on the template type T.

♻️ Proposed refactor
   return std::visit(
       [](auto&& arg) -> CV {
         using T = std::decay_t<decltype(arg)>;
         if constexpr (std::is_same_v<T, Type>) {
           return type_to_cv(arg);
         } else if constexpr (std::is_same_v<T, CV>) {
           return arg;
         } else {
-          static_assert(false, "variant not handled");
+          static_assert(sizeof(T) == 0, "variant not handled");
         }
       },
       val_);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/schema/psi/transform.cpp` around lines 67 - 78, Update the fallback
branch in the std::visit lambda within the CV conversion flow to make the static
assertion dependent on the template type T, preventing unconditional evaluation
on older compilers while retaining the compile-time failure for unhandled
variant alternatives.
src/data/array_index.cpp (1)

19-25: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Fix typo in variable name.

💡 Proposed change
 bool ArrayIndex::Dimension::needs_delta_model() const
 {
-  bool from_tansform = transform.has_value() && transform->needs_delta_model();
-  return from_tansform || std::ranges::any_of(entries, [](const auto& e) {
+  bool from_transform = transform.has_value() && transform->needs_delta_model();
+  return from_transform || std::ranges::any_of(entries, [](const auto& e) {
            return e.sorting_rank.has_value() && e.sorting_rank.value() == 0;
          });
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/data/array_index.cpp` around lines 19 - 25, Rename the misspelled local
variable from_tansform to from_transform within
ArrayIndex::Dimension::needs_delta_model, updating its declaration and return
expression while preserving the existing logic.
src/spectrum.cpp (1)

25-27: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Avoid copying the shared_ptr.

data is passed to the constructor by value, meaning it already owns its reference count. You can safely std::move it into decoder_ to save an unnecessary atomic reference count increment and decrement.

♻️ Proposed refactor
-    , decoder_(data,
+    , decoder_(std::move(data),
                std::move(slice),
                Util::DeltaEstimator<double>(md_spec_.delta_model()))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/spectrum.cpp` around lines 25 - 27, Update the decoder_ initialization in
the Spectrum constructor to move the by-value data shared_ptr into the decoder
instead of copying it, while preserving the existing slice and delta estimator
arguments.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@include/mzpeak/data/encoding.h`:
- Around line 153-156: Replace the bare string throws in the encoding.h
chunked-layout branch and both null-marking failure paths in null_marking.h with
structured exception types derived from std::exception. Use ParquetError for the
unimplemented chunked-layout path, including the affected dimension name in its
message, and use one consistent structured exception type for the two
null-marking errors while preserving their existing diagnostic context.

In `@include/mzpeak/data/null_marking.h`:
- Around line 90-123: Update Decoder<T>::chunk so the trailing range uses the
exclusive array length as its end: set range.end to array_->length() after the
index loop and before the final range.empty() check/push. Preserve the existing
range construction and null handling.
- Around line 126-189: In Decoder<T>::operator(), replace the range-size-based
delta scaling with the actual distance between the requested null index and its
anchor valid sample. Compute the index distance from index to range.end - 1 when
range.begin < index, or to range.begin otherwise, and apply the corresponding
signed adjustment to prior_.value while preserving prior_.delta estimation.

In `@include/mzpeak/util/algorithm.h`:
- Around line 42-52: Update deltas() to handle an empty values vector before
calculating the reserve size or iterating, returning an empty result directly.
Preserve the existing delta computation for non-empty inputs and avoid
values.size() - 1 underflow.

In `@test/spectra_test.cpp`:
- Around line 39-42: Correct the monotonicity assertion in the loop over mz so
each current value is compared with the preceding value rather than itself.
Update the BOOST_TEST expression within the visible range-based iteration,
preserving the existing nondecreasing-order requirement.

---

Outside diff comments:
In `@src/data/array_index.cpp`:
- Line 88: Update the entries_ sort in the array-index construction path to use
array_name as the primary key and name as a secondary key, ensuring entries
sharing an array_name have deterministic chunk ordering before grouping.

---

Nitpick comments:
In `@include/mzpeak/util/algorithm.h`:
- Around line 61-76: Remove the redundant std::ranges::sort call from
median_delta and rely on boost::math::statistics::median for the initial median
calculation. Preserve the existing delta filtering, empty-result handling, and
final median behavior.

In `@src/data/array_index.cpp`:
- Around line 19-25: Rename the misspelled local variable from_tansform to
from_transform within ArrayIndex::Dimension::needs_delta_model, updating its
declaration and return expression while preserving the existing logic.

In `@src/schema/psi/transform.cpp`:
- Around line 67-78: Update the fallback branch in the std::visit lambda within
the CV conversion flow to make the static assertion dependent on the template
type T, preventing unconditional evaluation on older compilers while retaining
the compile-time failure for unhandled variant alternatives.

In `@src/spectrum.cpp`:
- Around line 25-27: Update the decoder_ initialization in the Spectrum
constructor to move the by-value data shared_ptr into the decoder instead of
copying it, while preserving the existing slice and delta estimator arguments.

In `@test/array_index_test.cpp`:
- Around line 34-36: Update the construction of Schema::PSI::Transform from
transform_cv to use transform_cv.value() instead of dereferencing it with *.
Keep the existing BOOST_TEST assertion and surrounding test flow unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0d0f9284-d5f6-41d4-8fc4-239c041ec6a9

📥 Commits

Reviewing files that changed from the base of the PR and between 0b1baae and 728dde4.

📒 Files selected for processing (19)
  • include/mzpeak/data/array_index.h
  • include/mzpeak/data/dimension.h
  • include/mzpeak/data/encoding.h
  • include/mzpeak/data/null_marking.h
  • include/mzpeak/data/signals.h
  • include/mzpeak/schema/psi/data_type.h
  • include/mzpeak/schema/psi/transform.h
  • include/mzpeak/spectrum.h
  • include/mzpeak/util/algorithm.h
  • include/mzpeak/util/decoders.h
  • include/mzpeak/util/delta_estimator.h
  • meson.build
  • src/data/array_index.cpp
  • src/data/signals.cpp
  • src/schema/psi/transform.cpp
  • src/spectra.cpp
  • src/spectrum.cpp
  • test/array_index_test.cpp
  • test/spectra_test.cpp
💤 Files with no reviewable changes (1)
  • include/mzpeak/data/dimension.h

Comment thread include/mzpeak/data/encoding.h
Comment thread include/mzpeak/data/null_marking.h
Comment thread include/mzpeak/data/null_marking.h
Comment thread include/mzpeak/util/algorithm.h
Comment thread test/spectra_test.cpp
pjones added 2 commits July 14, 2026 15:36
Also, make the process of decoding signals abstract so that any class
can use it.
@pjones

pjones commented Jul 14, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai recheck.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

@pjones Sure, I'll take another look at the PR.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@pjones
pjones merged commit 25eb913 into trunk Jul 14, 2026
3 checks passed
@pjones
pjones deleted the pjones/point branch July 14, 2026 13:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant