Skip to content

fix(lcms): correct a UV/VIS axis that arrives in seconds behind a MINUTES label - #326

Open
PiTrem wants to merge 1 commit into
masterfrom
fix/lcms-uvvis-seconds-mislabelled
Open

fix(lcms): correct a UV/VIS axis that arrives in seconds behind a MINUTES label#326
PiTrem wants to merge 1 commit into
masterfrom
fix/lcms-uvvis-seconds-mislabelled

Conversation

@PiTrem

@PiTrem PiTrem commented Aug 26, 2026

Copy link
Copy Markdown
Member

Problem

An LC/MS dataset renders its UV/VIS trace across 600 "minutes" while its TIC covers 10. Both describe the same injection, so one is wrong.

Measured across two real converter outputs:

consistent dataset affected dataset
UV/VIS −0.04 – 19.96 MINUTES 0.00 – 599.825 MINUTES
TIC 1.02 – 15.98 MINUTES 0.005 – 9.999 MINUTES
ratio 1.25 59.99

599.825 s ÷ 60 = 9.997 min, matching that TIC's 9.9994 to three decimals. The UV/VIS is in seconds.

Since #323 gives the two panes a shared x-domain, the mismatch is now plainly visible: both span 0–600 and the TIC's real 10 minutes is squashed into the leftmost 1/60.

Why nothing catches it today

chemotion-converter-app 1.9.3 emits the UV/VIS axis in seconds while labelling it ##XUNITS=MINUTES — and the ##UNITS X slot agrees. The file is internally consistent and simply wrong.

chem.js's seconds-to-minutes pass is therefore vetoed by the explicit MINUTES, and the maxAbsX > 60 magnitude check that would have caught a 600-long axis sits behind that veto and never runs. No per-file heuristic can fix this — including the "parse ##UNITS by ##SYMBOL slot" work discussed elsewhere, since that slot also says MINUTES.

Approach

The sibling TIC is independent evidence. Reconcile the group's time axes before the curves are dispatched: a UV/VIS span 30–120× its TIC's is a unit mismatch, not a longer run. Healthy exports sit near 1, so the window is far from any plausible true ratio.

Deliberately narrow:

  • Only the UV/VIS is adjusted, and only against a TIC. A TIC that is itself in seconds is chem.js's job at parse time, where the file's own units still say so.
  • The m/z entity is never touched — its x is m/z, not time (it runs to ~1450, which would otherwise look like a large ratio).
  • Returns the input by reference when there is nothing to reconcile.

Applied in layer_init before setAllCurves rather than inside a reducer, because reducer_curve and reducer_hplc_ms both consume that one action's payload and must see the same corrected entities.

Scaling covers every x-bearing field a curve carries — data blocks, peaks, integrations, cached extrema — mirroring what chem.js already scales when it converts at parse time, so a curve corrected here is indistinguishable from one that arrived correct. The xUnit is left agreeing with the data.

Scope

The converter is the root cause and is worth fixing there too — this keeps already-converted datasets rendering correctly meanwhile, and catches future mislabelling.

Independent of #323, which is about TIC normalization and pane alignment.

Test plan

  • Verified against both real converter outputs: the affected UV/VIS goes 0..599.825 → 0..9.997 beside its 0.005..9.999 TIC; the consistent dataset is returned untouched.
  • 79 suites / 686 unit tests pass, including 10 new cases covering the rescale, the untouched-consistent case, m/z exclusion, no-TIC, lone-entity, a merely-large ratio, peaks/integrations/extrema, and non-mutation.
  • Production build compiles clean through its lint gate.
  • Full e2e suite 81/81 across all ten specs.
  • Live in the ELN against the affected dataset.

…UTES label

An LC/MS dataset rendered its UV/VIS trace across 600 "minutes" while its TIC
covered 10. Both describe the same injection, so one of them was wrong: 599.825
seconds is 9.997 minutes, matching the TIC's 9.9994 to three decimals.

chemotion-converter-app 1.9.3 emits that UV/VIS axis in seconds while labelling
it ##XUNITS=MINUTES, and the ##UNITS X slot agrees, so nothing inside the file
contradicts the label. chem.js's seconds-to-minutes pass is vetoed by the
explicit MINUTES, and its magnitude check - which would have caught a 600-long
axis - never runs. No per-file heuristic can catch this, because the file is
internally consistent and simply wrong.

The sibling TIC is independent evidence. Reconcile the group's time axes before
the curves are dispatched: when a UV/VIS span is 30-120x its TIC's, that is a
unit mismatch rather than a longer run, so rescale it. Healthy exports from the
same instrument sit near 1 (a second dataset here measures 19.96 against 15.98,
a ratio of 1.25), so the window is far from any plausible true value.

Only the UV/VIS is adjusted, and only against a TIC. A TIC that is itself in
seconds is chem.js's job at parse time, where the file's own units still say so;
the m/z entity is never touched, since its x is m/z rather than time.

Applied in layer_init before setAllCurves rather than inside a reducer, because
reducer_curve and reducer_hplc_ms both consume that one action's payload and
must see the same corrected entities. Scaling covers every x-bearing field a
curve carries - data blocks, peaks, integrations and the cached extrema - so a
curve corrected here is indistinguishable from one that arrived correct, and the
xUnit is left agreeing with the data.

Verified against both real converter outputs: the mislabelled dataset's UV/VIS
goes from 0..599.825 to 0..9.997 beside its 0.005..9.999 TIC, and the consistent
dataset is returned untouched, by reference.

The converter remains the root cause and is worth fixing there too; this keeps
already-converted datasets rendering correctly meanwhile.
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