@jbrowse/render-core 4.3.0

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 (164) hide show
  1. package/LICENSE +201 -0
  2. package/esm/OverlayCanvas.d.ts +29 -0
  3. package/esm/OverlayCanvas.js +43 -0
  4. package/esm/RenderCanvas.d.ts +76 -0
  5. package/esm/RenderCanvas.js +38 -0
  6. package/esm/RenderLifecycleMixin.d.ts +180 -0
  7. package/esm/RenderLifecycleMixin.js +279 -0
  8. package/esm/ScrollLockedOverlay.d.ts +28 -0
  9. package/esm/ScrollLockedOverlay.js +42 -0
  10. package/esm/blockClipUtils.d.ts +27 -0
  11. package/esm/blockClipUtils.js +101 -0
  12. package/esm/canvas2dUtils.d.ts +372 -0
  13. package/esm/canvas2dUtils.js +564 -0
  14. package/esm/canvasContext.d.ts +68 -0
  15. package/esm/canvasContext.js +74 -0
  16. package/esm/colorRampLut.d.ts +17 -0
  17. package/esm/colorRampLut.js +25 -0
  18. package/esm/coverageBand.d.ts +107 -0
  19. package/esm/coverageBand.js +109 -0
  20. package/esm/coverageBandBuffers.d.ts +30 -0
  21. package/esm/coverageBandBuffers.js +13 -0
  22. package/esm/createRenderingBackend.d.ts +52 -0
  23. package/esm/createRenderingBackend.js +58 -0
  24. package/esm/displayPhase.d.ts +155 -0
  25. package/esm/displayPhase.js +97 -0
  26. package/esm/globalRenderingBackend.d.ts +90 -0
  27. package/esm/globalRenderingBackend.js +53 -0
  28. package/esm/gpuDevice.d.ts +91 -0
  29. package/esm/gpuDevice.js +277 -0
  30. package/esm/graphicsCapabilities.d.ts +98 -0
  31. package/esm/graphicsCapabilities.js +165 -0
  32. package/esm/hal/createHal.d.ts +22 -0
  33. package/esm/hal/createHal.js +91 -0
  34. package/esm/hal/deviceGpuCache.d.ts +36 -0
  35. package/esm/hal/deviceGpuCache.js +120 -0
  36. package/esm/hal/gpuHalBase.d.ts +55 -0
  37. package/esm/hal/gpuHalBase.js +81 -0
  38. package/esm/hal/index.d.ts +7 -0
  39. package/esm/hal/index.js +5 -0
  40. package/esm/hal/mockHal.d.ts +153 -0
  41. package/esm/hal/mockHal.js +297 -0
  42. package/esm/hal/oomReporter.d.ts +15 -0
  43. package/esm/hal/oomReporter.js +22 -0
  44. package/esm/hal/passIds.d.ts +22 -0
  45. package/esm/hal/passIds.js +36 -0
  46. package/esm/hal/regionRegistry.d.ts +11 -0
  47. package/esm/hal/regionRegistry.js +66 -0
  48. package/esm/hal/types.d.ts +116 -0
  49. package/esm/hal/types.js +1 -0
  50. package/esm/hal/webgl2Hal.d.ts +47 -0
  51. package/esm/hal/webgl2Hal.js +490 -0
  52. package/esm/hal/webgpuHal.d.ts +169 -0
  53. package/esm/hal/webgpuHal.js +722 -0
  54. package/esm/installUpload.d.ts +91 -0
  55. package/esm/installUpload.js +94 -0
  56. package/esm/instanceCache.d.ts +43 -0
  57. package/esm/instanceCache.js +53 -0
  58. package/esm/instancePass.d.ts +50 -0
  59. package/esm/instancePass.js +34 -0
  60. package/esm/keyedRenderingBackend.d.ts +41 -0
  61. package/esm/keyedRenderingBackend.js +19 -0
  62. package/esm/mapUploadSync.d.ts +31 -0
  63. package/esm/mapUploadSync.js +45 -0
  64. package/esm/namedReactions.d.ts +29 -0
  65. package/esm/namedReactions.js +62 -0
  66. package/esm/perRegionRenderingBackend.d.ts +114 -0
  67. package/esm/perRegionRenderingBackend.js +93 -0
  68. package/esm/recoveryBudget.d.ts +40 -0
  69. package/esm/recoveryBudget.js +56 -0
  70. package/esm/regionDataMap.d.ts +60 -0
  71. package/esm/regionDataMap.js +98 -0
  72. package/esm/renderBlock.d.ts +25 -0
  73. package/esm/renderBlock.js +10 -0
  74. package/esm/renderingBackendBase.d.ts +71 -0
  75. package/esm/renderingBackendBase.js +50 -0
  76. package/esm/shaders/antialias.js.generated.d.ts +4 -0
  77. package/esm/shaders/antialias.js.generated.js +25 -0
  78. package/esm/shaders/capsule.generated.d.ts +1 -0
  79. package/esm/shaders/capsule.generated.js +3 -0
  80. package/esm/shaders/colorRampLut.generated.d.ts +1 -0
  81. package/esm/shaders/colorRampLut.generated.js +3 -0
  82. package/esm/shaders/coverageBand.generated.d.ts +2 -0
  83. package/esm/shaders/coverageBand.generated.js +4 -0
  84. package/esm/shaders/coverageBar.generated.d.ts +4 -0
  85. package/esm/shaders/coverageBar.generated.js +6 -0
  86. package/esm/shaders/coverageBar.iface.generated.d.ts +85 -0
  87. package/esm/shaders/coverageBar.iface.generated.js +114 -0
  88. package/esm/shaders/coverageIndicator.generated.d.ts +4 -0
  89. package/esm/shaders/coverageIndicator.generated.js +6 -0
  90. package/esm/shaders/coverageIndicator.iface.generated.d.ts +85 -0
  91. package/esm/shaders/coverageIndicator.iface.generated.js +114 -0
  92. package/esm/shaders/coverageInterbase.generated.d.ts +4 -0
  93. package/esm/shaders/coverageInterbase.generated.js +6 -0
  94. package/esm/shaders/coverageInterbase.iface.generated.d.ts +93 -0
  95. package/esm/shaders/coverageInterbase.iface.generated.js +134 -0
  96. package/esm/shaders/coverageMod.generated.d.ts +4 -0
  97. package/esm/shaders/coverageMod.generated.js +6 -0
  98. package/esm/shaders/coverageMod.iface.generated.d.ts +97 -0
  99. package/esm/shaders/coverageMod.iface.generated.js +144 -0
  100. package/esm/shaders/coverageSnp.generated.d.ts +4 -0
  101. package/esm/shaders/coverageSnp.generated.js +6 -0
  102. package/esm/shaders/coverageSnp.iface.generated.d.ts +97 -0
  103. package/esm/shaders/coverageSnp.iface.generated.js +144 -0
  104. package/esm/shaders/hpmath.js.generated.d.ts +3 -0
  105. package/esm/shaders/hpmath.js.generated.js +48 -0
  106. package/esm/shaders/pointGlyph.generated.d.ts +1 -0
  107. package/esm/shaders/pointGlyph.generated.js +3 -0
  108. package/esm/shaders/pointGlyph.js.generated.d.ts +1 -0
  109. package/esm/shaders/pointGlyph.js.generated.js +7 -0
  110. package/esm/shaders/rowRect.generated.d.ts +3 -0
  111. package/esm/shaders/rowRect.generated.js +5 -0
  112. package/esm/shaders/rowRect.js.generated.d.ts +2 -0
  113. package/esm/shaders/rowRect.js.generated.js +13 -0
  114. package/esm/shaders/scoreScale.js.generated.d.ts +1 -0
  115. package/esm/shaders/scoreScale.js.generated.js +46 -0
  116. package/esm/slangPass.d.ts +49 -0
  117. package/esm/slangPass.js +22 -0
  118. package/esm/useEventCallback.d.ts +1 -0
  119. package/esm/useEventCallback.js +19 -0
  120. package/esm/useGraphicsCapabilities.d.ts +17 -0
  121. package/esm/useGraphicsCapabilities.js +38 -0
  122. package/esm/useRenderingBackend.d.ts +83 -0
  123. package/esm/useRenderingBackend.js +422 -0
  124. package/esm/useTabVisibilityRerender.d.ts +13 -0
  125. package/esm/useTabVisibilityRerender.js +39 -0
  126. package/esm/webgpuUtils.d.ts +23 -0
  127. package/esm/webgpuUtils.js +76 -0
  128. package/package.json +91 -0
  129. package/src/shaders/antialias.js.generated.ts +33 -0
  130. package/src/shaders/antialias.slang +112 -0
  131. package/src/shaders/capsule.generated.ts +4 -0
  132. package/src/shaders/capsule.slang +62 -0
  133. package/src/shaders/colorPack.slang +26 -0
  134. package/src/shaders/colorRampLut.generated.ts +4 -0
  135. package/src/shaders/colorRampLut.slang +51 -0
  136. package/src/shaders/coverageBand.generated.ts +6 -0
  137. package/src/shaders/coverageBand.slang +276 -0
  138. package/src/shaders/coverageBar.generated.ts +10 -0
  139. package/src/shaders/coverageBar.iface.generated.ts +172 -0
  140. package/src/shaders/coverageBar.slang +37 -0
  141. package/src/shaders/coverageIndicator.generated.ts +10 -0
  142. package/src/shaders/coverageIndicator.iface.generated.ts +172 -0
  143. package/src/shaders/coverageIndicator.slang +61 -0
  144. package/src/shaders/coverageInterbase.generated.ts +10 -0
  145. package/src/shaders/coverageInterbase.iface.generated.ts +198 -0
  146. package/src/shaders/coverageInterbase.slang +63 -0
  147. package/src/shaders/coverageMod.generated.ts +10 -0
  148. package/src/shaders/coverageMod.iface.generated.ts +211 -0
  149. package/src/shaders/coverageMod.slang +36 -0
  150. package/src/shaders/coverageSnp.generated.ts +10 -0
  151. package/src/shaders/coverageSnp.iface.generated.ts +211 -0
  152. package/src/shaders/coverageSnp.slang +52 -0
  153. package/src/shaders/diagonalGrid.slang +25 -0
  154. package/src/shaders/glyphEdgeAlpha.test.ts +241 -0
  155. package/src/shaders/hpmath.js.generated.ts +47 -0
  156. package/src/shaders/hpmath.slang +226 -0
  157. package/src/shaders/pointGlyph.generated.ts +4 -0
  158. package/src/shaders/pointGlyph.js.generated.ts +9 -0
  159. package/src/shaders/pointGlyph.slang +93 -0
  160. package/src/shaders/rowRect.generated.ts +8 -0
  161. package/src/shaders/rowRect.js.generated.ts +17 -0
  162. package/src/shaders/rowRect.slang +160 -0
  163. package/src/shaders/scoreScale.js.generated.ts +51 -0
  164. package/src/shaders/scoreScale.slang +68 -0
@@ -0,0 +1,160 @@
1
+ // Shared "row of colored rects" primitive: one instance is a rectangle
2
+ // spanning [startBp, endBp) in absolute genomic coords, drawn on the row given
3
+ // by `rowIndex` and filled with a per-instance packed ABGR color.
4
+ //
5
+ // Two renderers draw exactly this: the MAF alignment renderer (run-merged
6
+ // per-base cells) and the multi-row feature painter / "chromosome painting"
7
+ // (features are already intervals). They shipped as byte-identical copies of
8
+ // one another joined only by a "probably belongs there too" comment; owning the
9
+ // geometry here makes a change to the primitive land in both by construction.
10
+ //
11
+ // Row placement comes from uniforms rather than the instance data, so a
12
+ // rowHeight / rowProportion change re-renders without a refetch.
13
+ //
14
+ // What the callers legitimately differ on is the min-width floor: both the
15
+ // width they write into `minCellPx` and the *unit* they measure it in, via
16
+ // `minCellDenomPx` (CSS px vs device px) — see GpuMafRenderer's note. Those are
17
+ // uniform-write decisions, not a reason for two shaders. The unit is also
18
+ // unsettled, and device px additionally makes MAF's floor depend on the
19
+ // reader's monitor: `agent-docs/ideas/maf-subpixel-cells.md`.
20
+ //! export-consts: MIN_DRAWN_ROW_PX, MIN_DRAWN_CELL_PX, MULTI_ROW_MIN_CELL_PX
21
+ //! js-export: drawnRowHeightPx, rowBandOffsetPx
22
+ module rowRect;
23
+
24
+ import colorPack;
25
+ import hpmath;
26
+
27
+ public struct RowRectInstance {
28
+ public uint startBp : ATTR0;
29
+ public uint endBp : ATTR1;
30
+ public uint rowIndex : ATTR2;
31
+ public uint color : ATTR3; // packed ABGR
32
+ };
33
+
34
+ public struct RowRectUniforms {
35
+ public float3 bpRangeX; // [bpStartHi, bpStartLo, ±clippedLengthBp]
36
+ public float canvasHeight; // CSS px
37
+ // The width `minCellPx` is measured against, and so the thing that decides
38
+ // what unit `minCellPx` IS. Named for that rather than for the viewport
39
+ // because the three callers do not agree: multi-row and wiggle density pass
40
+ // a CSS width (a 1 CSS px floor), MAF passes a device one (1 device px, so
41
+ // half a CSS px on a retina screen and a floor that moves with the monitor).
42
+ // That disagreement is live and parked, not settled —
43
+ // agent-docs/ideas/maf-subpixel-cells.md — so the name carries it to every
44
+ // call site instead of a comment forty lines away.
45
+ public float minCellDenomPx;
46
+ // Narrowest a cell is painted, in whatever unit `minCellDenomPx` is in.
47
+ // MIN_DRAWN_CELL_PX is the floor a caller takes when it has no reason to
48
+ // widen further; the multi-row painter does (see MULTI_ROW_MIN_CELL_PX).
49
+ public float minCellPx;
50
+ public float zero; // always 0.0 — prevents hp-split collapse
51
+ public float rowHeight; // CSS px per row
52
+ public float rowProportion; // rect height = rowHeight * rowProportion
53
+ // Rows-area scroll offset in CSS px. The canvas is the viewport, not the
54
+ // content: a caller whose rows overflow it paints row i at
55
+ // `rowHeight*i - scrollTop` and lets the off-screen rows clip. Callers that
56
+ // size their canvas to the content (the multi-row painter) write 0.
57
+ public float scrollTop;
58
+ };
59
+
60
+ public struct RowRectVsOut {
61
+ public float4 position : SV_Position;
62
+ public float4 color : COLOR0;
63
+ };
64
+
65
+ // Thinnest a row is ever painted. Vertical twin of MIN_DRAWN_CELL_PX below: a
66
+ // row thinner than a pixel can miss every pixel center and disappear, so
67
+ // without this rows drop out as they get denser instead of just getting
68
+ // thinner. Floored, neighbours overlap instead.
69
+ static const float MIN_DRAWN_ROW_PX = 1.0;
70
+
71
+ // Thinnest a cell can be painted and still be drawable at all, for the same
72
+ // reason as the row floor above on the other axis. A caller writes this into
73
+ // `minCellPx` when a cell being *visible* is not its problem, only a cell
74
+ // existing; one whose whole encoding is carried by sub-pixel cells asks for
75
+ // more (MULTI_ROW_MIN_CELL_PX).
76
+ //
77
+ // One caller mirrors it on the Canvas2D side and one deliberately does not: the
78
+ // multi-row painter floors, MAF draws cells at their natural sub-pixel width.
79
+ // That asymmetry is real and parked in `agent-docs/ideas/maf-subpixel-cells.md`;
80
+ // sharing the constant does not decide it, it just stops the value being
81
+ // anonymous in the places that DO agree.
82
+ static const float MIN_DRAWN_CELL_PX = 1.0;
83
+
84
+ // What the multi-row painter ("chromosome painting") writes into `minCellPx`.
85
+ //
86
+ // Wider than a pixel because this display has no second channel to fall back
87
+ // on. MAF's cells tile the row, so a sub-pixel cell sits in a run of its
88
+ // neighbours and the run is what is read; a painting's features are sparse
89
+ // intervals, and at chromosome zoom a RepeatMasker element is a single tick
90
+ // against white paper with nothing beside it. At one pixel — anti-aliased
91
+ // across two, so neither reaches full opacity — the tick reads as a smudge, and
92
+ // the eye scanning for structure across the rows finds none.
93
+ //
94
+ // Both floors distort width; that is what a floor is. This one distorts more,
95
+ // and only where the alternative was showing nothing legible: a feature wide
96
+ // enough to matter is already past it.
97
+ static const float MULTI_ROW_MIN_CELL_PX = 2.0;
98
+
99
+ public struct RowBand {
100
+ public float height; // what the rect is painted at
101
+ public float offset; // inset from the row's top, negative once floored
102
+ };
103
+
104
+ // The band a row's rects occupy, as two scalars so `//! js-export` can lift
105
+ // them: rowBand.ts is the Canvas2D painter's, the indel glyphs' and the hover
106
+ // box's definition of the same band, and a row drawn here but hit-tested there
107
+ // has to land in the same place. The struct below is only packaging for the
108
+ // vertex stage.
109
+ //
110
+ // Centered, so a floored band overhangs its row evenly on both sides rather
111
+ // than pushing every row down.
112
+ public float drawnRowHeightPx(float rowHeight, float rowProportion) {
113
+ return max(rowHeight * rowProportion, MIN_DRAWN_ROW_PX);
114
+ }
115
+
116
+ public float rowBandOffsetPx(float rowHeight, float rowProportion) {
117
+ return (rowHeight - drawnRowHeightPx(rowHeight, rowProportion)) * 0.5;
118
+ }
119
+
120
+ public RowBand rowBandPx(float rowHeight, float rowProportion) {
121
+ RowBand band;
122
+ band.height = drawnRowHeightPx(rowHeight, rowProportion);
123
+ band.offset = rowBandOffsetPx(rowHeight, rowProportion);
124
+ return band;
125
+ }
126
+
127
+ // The shape's geometry alone — row band, hp clip-x span, min-width widening —
128
+ // so a consumer whose colour is not a per-instance packed value (wiggle
129
+ // density: a per-instance score through a uniform scale) composes the same
130
+ // rect without routing a resolved colour through the instance buffer.
131
+ // `rowIndex` is float here because the row transform is float arithmetic;
132
+ // each caller converts at its own boundary (`RowRectInstance` stores uint,
133
+ // wiggle's fill record stores float).
134
+ public float4 rowRectClipPos(
135
+ uint startBp, uint endBp, float rowIndex, uint vid, RowRectUniforms u
136
+ ) {
137
+ float2 local = quadLocal(vid);
138
+
139
+ RowBand band = rowBandPx(u.rowHeight, u.rowProportion);
140
+ float rowY = band.offset + u.rowHeight * rowIndex - u.scrollTop;
141
+
142
+ float sx1 = hpToClipX(hpSplitUint(startBp), u.bpRangeX, u.zero);
143
+ float sx2 = hpToClipX(hpSplitUint(endBp), u.bpRangeX, u.zero);
144
+ sx2 = extendToMinWidthX(sx1, sx2, u.minCellPx, u.minCellDenomPx);
145
+
146
+ float yPx = lerp(rowY, rowY + band.height, local.y);
147
+
148
+ return float4(
149
+ lerp(sx1, sx2, local.x), yPxToClipY(yPx, u.canvasHeight), 0.0, 1.0);
150
+ }
151
+
152
+ public RowRectVsOut rowRectVertex(
153
+ RowRectInstance inst, uint vid, RowRectUniforms u
154
+ ) {
155
+ RowRectVsOut o;
156
+ o.position = rowRectClipPos(
157
+ inst.startBp, inst.endBp, float(inst.rowIndex), vid, u);
158
+ o.color = unpackRGBA(inst.color);
159
+ return o;
160
+ }
@@ -0,0 +1,51 @@
1
+ // AUTO-GENERATED by packages/shader-tools/src/shader-codegen from scoreScale.slang.
2
+ // Do not edit. Run `pnpm gen:shaders` to regenerate.
3
+
4
+ // Scalar twins of scoreScale.slang, transliterated from slangc's WGSL so
5
+ // the Canvas2D and SVG paths run the shader's own math. See adr-051.
6
+
7
+ function _clamp(x: number, lo: number, hi: number) {
8
+ return _min(_max(x, lo), hi)
9
+ }
10
+
11
+ function _max(a: number, b: number) {
12
+ return b > a || Number.isNaN(a) ? b : a
13
+ }
14
+
15
+ function _min(a: number, b: number) {
16
+ return b < a || Number.isNaN(a) ? b : a
17
+ }
18
+
19
+ function symlogTransform(x: number, c: number): number {
20
+ return ((((Math.sign(x)) | 0)) * Math.log((1.0 + Math.abs((x / c)))))
21
+ }
22
+
23
+ export function normalizeScore(score: number, domainMin: number, domainMax: number, scaleType: number, symlogConstant: number): number {
24
+ if ((scaleType == 2)) {
25
+ let tMin = symlogTransform(domainMin, symlogConstant)
26
+ let tRange = (symlogTransform(domainMax, symlogConstant) - tMin)
27
+ if ((tRange <= 0.0)) {
28
+ return 0.0
29
+ }
30
+ return _clamp(((symlogTransform(score, symlogConstant) - tMin) / tRange), 0.0, 1.0)
31
+ }
32
+ if ((scaleType == 1)) {
33
+ let floorV: number
34
+ if ((domainMin > 0.0)) {
35
+ floorV = domainMin
36
+ } else {
37
+ floorV = 1.0
38
+ }
39
+ let logMin = Math.log2(floorV)
40
+ let logRange = (Math.log2(_max(domainMax, floorV)) - logMin)
41
+ if ((logRange <= 0.0)) {
42
+ return 0.0
43
+ }
44
+ return _clamp(((Math.log2(_max(score, floorV)) - logMin) / logRange), 0.0, 1.0)
45
+ }
46
+ let range = (domainMax - domainMin)
47
+ if ((range <= 0.0)) {
48
+ return 0.0
49
+ }
50
+ return _clamp(((score - domainMin) / range), 0.0, 1.0)
51
+ }
@@ -0,0 +1,68 @@
1
+ // The score → [0,1] normalizer every score-scaled track is placed by, and the
2
+ // scale-type vocabulary its branches switch on.
3
+ //
4
+ // Two real consumers with non-obvious math, which is ADR-040's bar: the wiggle
5
+ // renderer's four modes (`wiggleCommon.slang`, via `scoreToY`) and the coverage
6
+ // band's depth axis (`coverageBand.slang`, via `normalizeDepthScalar`). They
7
+ // carried a branch-for-branch copy each, and the copies did not agree: the
8
+ // coverage one compared `scaleType` against bare 2 and 1 rather than the
9
+ // constants below, and answered 0 on a degenerate domain where wiggle's divided
10
+ // by a 1e-6 floor and saturated. This module is one answer to both.
11
+ //
12
+ // The degenerate domain answers 0, which is what the JS twin
13
+ // (`makeScoreNormalizer`, packages/wiggle-core/src/normalize.ts) has always
14
+ // answered. Returning 0 avoids the NaN the 1e-6 floor was there for just as
15
+ // well, so the divergence bought nothing and cost a documented exception.
16
+ //
17
+ // `SCALE_TYPE_LINEAR` is deliberately absent: it is "neither of these", and a
18
+ // third constant would be a value nothing compares against.
19
+ //! js-export: normalizeScore
20
+ //! js-skip: symlogTransform — the JS twin (wiggle-core normalize.ts) spells it Math.log1p, which is accurate near zero where the shader's log(1.0 + x) is not; a generated twin would be a faithful copy of the worse of the two. What both backends must agree on is normalizeScore, which is exported and swept by normalizeScoreParity.test.ts
21
+ module scoreScale;
22
+
23
+ // The values the `scaleType` uniform is compared against. Emitted into
24
+ // @jbrowse/wiggle-core through `wiggleCommon.slang`'s `export-consts` — the
25
+ // constant evaluator resolves through imports, and wiggle-core is where
26
+ // `WiggleScaleType` is declared, so the numbers stay next to the type they
27
+ // name rather than next to the module that happens to author them.
28
+ public static const int SCALE_TYPE_LOG = 1;
29
+ public static const int SCALE_TYPE_SYMLOG = 2;
30
+
31
+ public float symlogTransform(float x, float c) {
32
+ return sign(x) * log(1.0 + abs(x / c));
33
+ }
34
+
35
+ public float normalizeScore(float score, float domainMin, float domainMax, int scaleType, float symlogConstant) {
36
+ if (scaleType == SCALE_TYPE_SYMLOG) {
37
+ // Defined at and either side of zero, so unlike the log branch there is
38
+ // nothing to floor: the domain arrives as-is and 0 lands where it belongs
39
+ // instead of on the baseline with everything else below the floor. On a
40
+ // coverage band that is the whole reason symlog is offered — a depth of 0
41
+ // is real, common, and what log cannot separate from a depth of 1.
42
+ float tMin = symlogTransform(domainMin, symlogConstant);
43
+ float tMax = symlogTransform(domainMax, symlogConstant);
44
+ float tRange = tMax - tMin;
45
+ if (tRange <= 0.0) { return 0.0; }
46
+ float t = symlogTransform(score, symlogConstant);
47
+ return clamp((t - tMin) / tRange, 0.0, 1.0);
48
+ }
49
+ if (scaleType == SCALE_TYPE_LOG) {
50
+ // Floor at the domain's own min, not at 1, so a domain sitting entirely
51
+ // below 1 (mappability, a methylation fraction, any normalized ratio) still
52
+ // has a range to spread over instead of collapsing to a flat baseline. Same
53
+ // rule and same reasoning as JS `makeScoreNormalizer`, which keeps its own
54
+ // hand-written copy because it hoists this arithmetic out of a per-feature
55
+ // loop — so this one is `//! js-export`ed as the ORACLE the two are swept
56
+ // against (`normalizeScoreParity.test.ts`), not to retire it.
57
+ float floorV = domainMin > 0.0 ? domainMin : 1.0;
58
+ float logMin = log2(floorV);
59
+ float logMax = log2(max(domainMax, floorV));
60
+ float logRange = logMax - logMin;
61
+ if (logRange <= 0.0) { return 0.0; }
62
+ float logScore = log2(max(score, floorV));
63
+ return clamp((logScore - logMin) / logRange, 0.0, 1.0);
64
+ }
65
+ float range = domainMax - domainMin;
66
+ if (range <= 0.0) { return 0.0; }
67
+ return clamp((score - domainMin) / range, 0.0, 1.0);
68
+ }