@aics/vole-core 3.12.4

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 (141) hide show
  1. package/LICENSE.txt +26 -0
  2. package/README.md +119 -0
  3. package/es/Atlas2DSlice.js +224 -0
  4. package/es/Channel.js +264 -0
  5. package/es/FileSaver.js +31 -0
  6. package/es/FusedChannelData.js +192 -0
  7. package/es/Histogram.js +250 -0
  8. package/es/ImageInfo.js +127 -0
  9. package/es/Light.js +74 -0
  10. package/es/Lut.js +500 -0
  11. package/es/MarchingCubes.js +507 -0
  12. package/es/MeshVolume.js +334 -0
  13. package/es/NaiveSurfaceNets.js +251 -0
  14. package/es/PathTracedVolume.js +482 -0
  15. package/es/RayMarchedAtlasVolume.js +250 -0
  16. package/es/RenderToBuffer.js +31 -0
  17. package/es/ThreeJsPanel.js +633 -0
  18. package/es/Timing.js +28 -0
  19. package/es/TrackballControls.js +538 -0
  20. package/es/View3d.js +848 -0
  21. package/es/Volume.js +352 -0
  22. package/es/VolumeCache.js +161 -0
  23. package/es/VolumeDims.js +16 -0
  24. package/es/VolumeDrawable.js +702 -0
  25. package/es/VolumeMaker.js +101 -0
  26. package/es/VolumeRenderImpl.js +1 -0
  27. package/es/VolumeRenderSettings.js +203 -0
  28. package/es/constants/basicShaders.js +29 -0
  29. package/es/constants/colors.js +59 -0
  30. package/es/constants/denoiseShader.js +43 -0
  31. package/es/constants/lights.js +42 -0
  32. package/es/constants/materials.js +85 -0
  33. package/es/constants/pathtraceOutputShader.js +13 -0
  34. package/es/constants/scaleBarSVG.js +21 -0
  35. package/es/constants/time.js +34 -0
  36. package/es/constants/volumePTshader.js +153 -0
  37. package/es/constants/volumeRayMarchShader.js +123 -0
  38. package/es/constants/volumeSliceShader.js +115 -0
  39. package/es/index.js +21 -0
  40. package/es/loaders/IVolumeLoader.js +131 -0
  41. package/es/loaders/JsonImageInfoLoader.js +255 -0
  42. package/es/loaders/OmeZarrLoader.js +495 -0
  43. package/es/loaders/OpenCellLoader.js +65 -0
  44. package/es/loaders/RawArrayLoader.js +89 -0
  45. package/es/loaders/TiffLoader.js +219 -0
  46. package/es/loaders/VolumeLoadError.js +44 -0
  47. package/es/loaders/VolumeLoaderUtils.js +221 -0
  48. package/es/loaders/index.js +40 -0
  49. package/es/loaders/zarr_utils/ChunkPrefetchIterator.js +143 -0
  50. package/es/loaders/zarr_utils/WrappedStore.js +51 -0
  51. package/es/loaders/zarr_utils/types.js +24 -0
  52. package/es/loaders/zarr_utils/utils.js +225 -0
  53. package/es/loaders/zarr_utils/validation.js +49 -0
  54. package/es/test/ChunkPrefetchIterator.test.js +208 -0
  55. package/es/test/RequestQueue.test.js +442 -0
  56. package/es/test/SubscribableRequestQueue.test.js +244 -0
  57. package/es/test/VolumeCache.test.js +118 -0
  58. package/es/test/VolumeRenderSettings.test.js +71 -0
  59. package/es/test/lut.test.js +671 -0
  60. package/es/test/num_utils.test.js +140 -0
  61. package/es/test/volume.test.js +98 -0
  62. package/es/test/zarr_utils.test.js +358 -0
  63. package/es/types/Atlas2DSlice.d.ts +41 -0
  64. package/es/types/Channel.d.ts +44 -0
  65. package/es/types/FileSaver.d.ts +6 -0
  66. package/es/types/FusedChannelData.d.ts +26 -0
  67. package/es/types/Histogram.d.ts +57 -0
  68. package/es/types/ImageInfo.d.ts +87 -0
  69. package/es/types/Light.d.ts +27 -0
  70. package/es/types/Lut.d.ts +67 -0
  71. package/es/types/MarchingCubes.d.ts +53 -0
  72. package/es/types/MeshVolume.d.ts +40 -0
  73. package/es/types/NaiveSurfaceNets.d.ts +11 -0
  74. package/es/types/PathTracedVolume.d.ts +65 -0
  75. package/es/types/RayMarchedAtlasVolume.d.ts +41 -0
  76. package/es/types/RenderToBuffer.d.ts +17 -0
  77. package/es/types/ThreeJsPanel.d.ts +107 -0
  78. package/es/types/Timing.d.ts +11 -0
  79. package/es/types/TrackballControls.d.ts +51 -0
  80. package/es/types/View3d.d.ts +357 -0
  81. package/es/types/Volume.d.ts +152 -0
  82. package/es/types/VolumeCache.d.ts +43 -0
  83. package/es/types/VolumeDims.d.ts +28 -0
  84. package/es/types/VolumeDrawable.d.ts +108 -0
  85. package/es/types/VolumeMaker.d.ts +49 -0
  86. package/es/types/VolumeRenderImpl.d.ts +22 -0
  87. package/es/types/VolumeRenderSettings.d.ts +98 -0
  88. package/es/types/constants/basicShaders.d.ts +4 -0
  89. package/es/types/constants/colors.d.ts +2 -0
  90. package/es/types/constants/denoiseShader.d.ts +40 -0
  91. package/es/types/constants/lights.d.ts +38 -0
  92. package/es/types/constants/materials.d.ts +20 -0
  93. package/es/types/constants/pathtraceOutputShader.d.ts +11 -0
  94. package/es/types/constants/scaleBarSVG.d.ts +2 -0
  95. package/es/types/constants/time.d.ts +19 -0
  96. package/es/types/constants/volumePTshader.d.ts +137 -0
  97. package/es/types/constants/volumeRayMarchShader.d.ts +117 -0
  98. package/es/types/constants/volumeSliceShader.d.ts +109 -0
  99. package/es/types/glsl.d.js +0 -0
  100. package/es/types/index.d.ts +28 -0
  101. package/es/types/loaders/IVolumeLoader.d.ts +113 -0
  102. package/es/types/loaders/JsonImageInfoLoader.d.ts +80 -0
  103. package/es/types/loaders/OmeZarrLoader.d.ts +87 -0
  104. package/es/types/loaders/OpenCellLoader.d.ts +9 -0
  105. package/es/types/loaders/RawArrayLoader.d.ts +33 -0
  106. package/es/types/loaders/TiffLoader.d.ts +45 -0
  107. package/es/types/loaders/VolumeLoadError.d.ts +18 -0
  108. package/es/types/loaders/VolumeLoaderUtils.d.ts +38 -0
  109. package/es/types/loaders/index.d.ts +22 -0
  110. package/es/types/loaders/zarr_utils/ChunkPrefetchIterator.d.ts +22 -0
  111. package/es/types/loaders/zarr_utils/WrappedStore.d.ts +24 -0
  112. package/es/types/loaders/zarr_utils/types.d.ts +94 -0
  113. package/es/types/loaders/zarr_utils/utils.d.ts +23 -0
  114. package/es/types/loaders/zarr_utils/validation.d.ts +7 -0
  115. package/es/types/test/ChunkPrefetchIterator.test.d.ts +1 -0
  116. package/es/types/test/RequestQueue.test.d.ts +1 -0
  117. package/es/types/test/SubscribableRequestQueue.test.d.ts +1 -0
  118. package/es/types/test/VolumeCache.test.d.ts +1 -0
  119. package/es/types/test/VolumeRenderSettings.test.d.ts +1 -0
  120. package/es/types/test/lut.test.d.ts +1 -0
  121. package/es/types/test/num_utils.test.d.ts +1 -0
  122. package/es/types/test/volume.test.d.ts +1 -0
  123. package/es/types/test/zarr_utils.test.d.ts +1 -0
  124. package/es/types/types.d.ts +115 -0
  125. package/es/types/utils/RequestQueue.d.ts +112 -0
  126. package/es/types/utils/SubscribableRequestQueue.d.ts +52 -0
  127. package/es/types/utils/num_utils.d.ts +43 -0
  128. package/es/types/workers/VolumeLoaderContext.d.ts +106 -0
  129. package/es/types/workers/types.d.ts +101 -0
  130. package/es/types/workers/util.d.ts +3 -0
  131. package/es/types.js +75 -0
  132. package/es/typings.d.js +0 -0
  133. package/es/utils/RequestQueue.js +267 -0
  134. package/es/utils/SubscribableRequestQueue.js +187 -0
  135. package/es/utils/num_utils.js +231 -0
  136. package/es/workers/FetchTiffWorker.js +153 -0
  137. package/es/workers/VolumeLoadWorker.js +129 -0
  138. package/es/workers/VolumeLoaderContext.js +271 -0
  139. package/es/workers/types.js +41 -0
  140. package/es/workers/util.js +8 -0
  141. package/package.json +83 -0
@@ -0,0 +1,671 @@
1
+ import { expect } from "chai";
2
+ import { Lut, remapLut, remapControlPoints } from "../Lut";
3
+ import Histogram from "../Histogram";
4
+ import VolumeMaker from "../VolumeMaker";
5
+ function clamp(val, cmin, cmax) {
6
+ return Math.min(Math.max(cmin, val), cmax);
7
+ }
8
+ describe("test histogram", () => {
9
+ const conedata = VolumeMaker.createCone(128, 128, 128, 24, 128);
10
+ const histogram = new Histogram(conedata);
11
+ describe("binary volume data", () => {
12
+ it("has a min of 0", () => {
13
+ expect(histogram.getMin()).to.equal(0);
14
+ });
15
+ it("has a max of 255", () => {
16
+ expect(histogram.getMax()).to.equal(255);
17
+ });
18
+ it("is created", () => {
19
+ expect(histogram.maxBin).to.equal(255);
20
+ expect(histogram["bins"][255]).to.be.greaterThan(0);
21
+ expect(histogram["bins"][128]).to.equal(0);
22
+ expect(histogram["bins"][0]).to.be.greaterThan(0);
23
+ });
24
+ });
25
+ describe("generated lut from control points", () => {
26
+ const controlPoints = [{
27
+ x: 0,
28
+ color: [255, 255, 255],
29
+ opacity: 0
30
+ }, {
31
+ x: 126,
32
+ color: [255, 255, 255],
33
+ opacity: 0
34
+ }, {
35
+ x: 128,
36
+ color: [255, 255, 255],
37
+ opacity: 1.0
38
+ }, {
39
+ x: 255,
40
+ color: [255, 255, 255],
41
+ opacity: 1.0
42
+ }];
43
+ const lut = new Lut().createFromControlPoints(controlPoints);
44
+ it("has interpolated opacity correctly", () => {
45
+ expect(lut.lut[126 * 4 + 3]).to.equal(0);
46
+ expect(lut.lut[127 * 4 + 3]).to.equal(127);
47
+ expect(lut.lut[128 * 4 + 3]).to.equal(255);
48
+ });
49
+ it("has interpolated color correctly", () => {
50
+ expect(lut.lut[126 * 4]).to.equal(255);
51
+ expect(lut.lut[127 * 4]).to.equal(255);
52
+ expect(lut.lut[128 * 4]).to.equal(255);
53
+ });
54
+ });
55
+ describe("interpolates opacity across consecutive control points", () => {
56
+ it("has interpolated opacity correctly", () => {
57
+ const controlPoints = [{
58
+ x: 0,
59
+ color: [255, 255, 255],
60
+ opacity: 0
61
+ }, {
62
+ x: 1,
63
+ color: [255, 255, 255],
64
+ opacity: 1.0
65
+ }, {
66
+ x: 255,
67
+ color: [255, 255, 255],
68
+ opacity: 1.0
69
+ }];
70
+ const lut = new Lut().createFromControlPoints(controlPoints);
71
+ expect(lut.lut[0 * 4 + 3]).to.equal(0);
72
+ expect(lut.lut[1 * 4 + 3]).to.equal(255);
73
+ expect(lut.lut[2 * 4 + 3]).to.equal(255);
74
+ });
75
+ it("has interpolated opacity correctly with fractional control point positions", () => {
76
+ const controlPoints = [{
77
+ x: 0,
78
+ color: [255, 255, 255],
79
+ opacity: 0
80
+ }, {
81
+ x: 0.1,
82
+ color: [255, 255, 255],
83
+ opacity: 0
84
+ }, {
85
+ x: 0.9,
86
+ color: [255, 255, 255],
87
+ opacity: 1.0
88
+ }, {
89
+ x: 1,
90
+ color: [255, 255, 255],
91
+ opacity: 1.0
92
+ }, {
93
+ x: 255,
94
+ color: [255, 255, 255],
95
+ opacity: 1.0
96
+ }];
97
+ const lut = new Lut().createFromControlPoints(controlPoints);
98
+ expect(lut.lut[0 * 4 + 3]).to.equal(0);
99
+ expect(lut.lut[1 * 4 + 3]).to.equal(255);
100
+ expect(lut.lut[2 * 4 + 3]).to.equal(255);
101
+ });
102
+ });
103
+ describe("generated lut single control point", () => {
104
+ const controlPoints = [{
105
+ x: 127,
106
+ color: [255, 255, 255],
107
+ opacity: 1.0
108
+ }];
109
+ const lut = new Lut().createFromControlPoints(controlPoints);
110
+ it("has interpolated opacity correctly", () => {
111
+ expect(lut.lut[0 * 4 + 3]).to.equal(0);
112
+ expect(lut.lut[126 * 4 + 3]).to.equal(0);
113
+ expect(lut.lut[128 * 4 + 3]).to.equal(255);
114
+ expect(lut.lut[255 * 4 + 3]).to.equal(255);
115
+ });
116
+ it("has interpolated color correctly", () => {
117
+ expect(lut.lut[0 * 4]).to.equal(0);
118
+ expect(lut.lut[126 * 4]).to.equal(0);
119
+ expect(lut.lut[128 * 4]).to.equal(255);
120
+ expect(lut.lut[255 * 4]).to.equal(255);
121
+ });
122
+ });
123
+ describe("generate lut for segmentation data labels", () => {
124
+ // make some data with label values
125
+ const labeldata = new Uint8Array([0, 1, 1, 1, 2, 2, 2, 4, 4, 4, 12, 12, 12]);
126
+ const labelHistogram = new Histogram(labeldata);
127
+ const lutObj = new Lut().createLabelColors(labelHistogram);
128
+ it("has nonzero opacity values where expected", () => {
129
+ expect(lutObj.lut[labelHistogram.findBinOfValue(0) * 4 + 3]).to.equal(0);
130
+ expect(lutObj.lut[labelHistogram.findBinOfValue(1) * 4 + 3]).to.equal(255);
131
+ expect(lutObj.lut[labelHistogram.findBinOfValue(2) * 4 + 3]).to.equal(255);
132
+ expect(lutObj.lut[labelHistogram.findBinOfValue(4) * 4 + 3]).to.equal(255);
133
+ expect(lutObj.lut[labelHistogram.findBinOfValue(12) * 4 + 3]).to.equal(255);
134
+
135
+ // test some values expected to be zero
136
+ expect(lutObj.lut[labelHistogram.findBinOfValue(3) * 4 + 3]).to.equal(0);
137
+ expect(lutObj.lut[labelHistogram.findBinOfValue(11) * 4 + 3]).to.equal(0);
138
+ });
139
+
140
+ // reconcile lut with control points
141
+ const secondlut = new Lut().createFromControlPoints(lutObj.controlPoints);
142
+ it("generates consistent lut from control points", () => {
143
+ expect(secondlut.lut).to.eql(lutObj.lut);
144
+ });
145
+ });
146
+ describe("validate auto generators control points against their Lut", () => {
147
+ // create a random dataset
148
+ const data = new Uint8Array(1024);
149
+ for (let i = 0; i < 1024; ++i) {
150
+ data[i] = clamp(Math.floor(Math.random() * 256), 0, 255);
151
+ }
152
+ const histogram = new Histogram(data);
153
+ describe("lutGenerator_minMax", () => {
154
+ it("is consistent for minMax (typical case)", () => {
155
+ const lut = new Lut().createFromMinMax(64, 192);
156
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
157
+ expect(lut.lut).to.eql(secondlut.lut);
158
+ });
159
+ it("is consistent for minMax full range", () => {
160
+ const lut = new Lut().createFromMinMax(0, 255);
161
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
162
+ expect(lut.lut).to.eql(secondlut.lut);
163
+ });
164
+ it("is consistent when min and max are both 0", () => {
165
+ const lut = new Lut().createFromMinMax(0, 0);
166
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
167
+ expect(lut.lut).to.eql(secondlut.lut);
168
+ expect(lut.lut[3]).to.eql(0);
169
+ expect(secondlut.lut[3]).to.eql(0);
170
+ expect(lut.lut[1 * 4 + 3]).to.eql(255);
171
+ expect(secondlut.lut[1 * 4 + 3]).to.eql(255);
172
+ expect(lut.lut[255 * 4 + 3]).to.eql(255);
173
+ expect(secondlut.lut[255 * 4 + 3]).to.eql(255);
174
+ // Make sure no NaN values
175
+ lut.controlPoints.forEach(controlPoint => {
176
+ expect(controlPoint.opacity).to.be.finite;
177
+ });
178
+ });
179
+ it("is consistent when min and max are the same positive number less than 255", () => {
180
+ const lut = new Lut().createFromMinMax(120, 120);
181
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
182
+ expect(lut.lut).to.eql(secondlut.lut);
183
+ expect(lut.lut[3]).to.eql(0);
184
+ expect(secondlut.lut[3]).to.eql(0);
185
+ expect(lut.lut[120 * 4 + 3]).to.eql(0);
186
+ expect(secondlut.lut[120 * 4 + 3]).to.eql(0);
187
+ expect(lut.lut[121 * 4 + 3]).to.eql(255);
188
+ expect(secondlut.lut[121 * 4 + 3]).to.eql(255);
189
+ expect(lut.lut[255 * 4 + 3]).to.eql(255);
190
+ expect(secondlut.lut[255 * 4 + 3]).to.eql(255);
191
+ // Make sure no NaN values
192
+ lut.controlPoints.forEach(controlPoint => {
193
+ expect(controlPoint.opacity).to.be.finite;
194
+ });
195
+ });
196
+ it("is consistent when min and max are both negative", () => {
197
+ const lut = new Lut().createFromMinMax(-10, -5);
198
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
199
+ expect(lut.lut).to.eql(secondlut.lut);
200
+ // Spot check but all opacity values should be 255
201
+ expect(lut.lut[3]).to.eql(255);
202
+ expect(secondlut.lut[3]).to.eql(255);
203
+ expect(lut.lut[120 * 4 + 3]).to.eql(255);
204
+ expect(secondlut.lut[120 * 4 + 3]).to.eql(255);
205
+ expect(lut.lut[255 * 4 + 3]).to.eql(255);
206
+ expect(secondlut.lut[255 * 4 + 3]).to.eql(255);
207
+ lut.controlPoints.forEach(controlPoint => {
208
+ expect(controlPoint.opacity).to.eql(1);
209
+ });
210
+ });
211
+ it("is consistent when min is 0 and max is 1", () => {
212
+ const lut = new Lut().createFromMinMax(0, 1);
213
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
214
+ expect(lut.lut).to.eql(secondlut.lut);
215
+ expect(lut.lut[3]).to.eql(0);
216
+ expect(secondlut.lut[3]).to.eql(0);
217
+ expect(lut.lut[1 * 4 + 3]).to.eql(255);
218
+ expect(secondlut.lut[1 * 4 + 3]).to.eql(255);
219
+ expect(lut.lut[255 * 4 + 3]).to.eql(255);
220
+ expect(secondlut.lut[255 * 4 + 3]).to.eql(255);
221
+ // Make sure no NaN values
222
+ lut.controlPoints.forEach(controlPoint => {
223
+ expect(controlPoint.opacity).to.be.finite;
224
+ });
225
+ });
226
+ it("is consistent when min is 244 and max is 255", () => {
227
+ const lut = new Lut().createFromMinMax(254, 255);
228
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
229
+ expect(lut.lut).to.eql(secondlut.lut);
230
+ expect(lut.lut[3]).to.eql(0);
231
+ expect(secondlut.lut[3]).to.eql(0);
232
+ expect(lut.lut[254 * 4 + 3]).to.eql(0);
233
+ expect(secondlut.lut[254 * 4 + 3]).to.eql(0);
234
+ expect(lut.lut[255 * 4 + 3]).to.eql(255);
235
+ expect(secondlut.lut[255 * 4 + 3]).to.eql(255);
236
+ // Make sure no NaN values
237
+ lut.controlPoints.forEach(controlPoint => {
238
+ expect(controlPoint.opacity).to.be.finite;
239
+ });
240
+ });
241
+ it("is consistent when min and max are both 255", () => {
242
+ const lut = new Lut().createFromMinMax(255, 255);
243
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
244
+ expect(lut.lut).to.eql(secondlut.lut);
245
+ expect(lut.lut[3]).to.eql(0);
246
+ expect(secondlut.lut[3]).to.eql(0);
247
+ expect(lut.lut[255 * 4 + 3]).to.eql(0);
248
+ expect(secondlut.lut[255 * 4 + 3]).to.eql(0);
249
+ lut.controlPoints.forEach(controlPoint => {
250
+ expect(controlPoint.opacity).to.eql(0);
251
+ });
252
+ });
253
+ it("is consistent when min and max are both greater than 255", () => {
254
+ const lut = new Lut().createFromMinMax(300, 400);
255
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
256
+ expect(lut.lut).to.eql(secondlut.lut);
257
+ // Spot check but all opacity values should be 0
258
+ expect(lut.lut[3]).to.eql(0);
259
+ expect(secondlut.lut[3]).to.eql(0);
260
+ expect(lut.lut[120 * 4 + 3]).to.eql(0);
261
+ expect(secondlut.lut[120 * 4 + 3]).to.eql(0);
262
+ expect(lut.lut[255 * 4 + 3]).to.eql(0);
263
+ expect(secondlut.lut[255 * 4 + 3]).to.eql(0);
264
+ lut.controlPoints.forEach(controlPoint => {
265
+ expect(controlPoint.opacity).to.eql(0);
266
+ });
267
+ });
268
+ });
269
+ describe("lutGenerator_windowLevel", () => {
270
+ it("is consistent for windowLevel", () => {
271
+ const lut = new Lut().createFromWindowLevel(0.25, 0.333);
272
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
273
+ expect(lut.lut).to.eql(secondlut.lut);
274
+ });
275
+ it("is consistent for windowLevel extending below bounds", () => {
276
+ const lut = new Lut().createFromWindowLevel(0.5, 0.25);
277
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
278
+ expect(lut.lut).to.eql(secondlut.lut);
279
+ });
280
+ it("is consistent for windowLevel extending above bounds", () => {
281
+ const lut = new Lut().createFromWindowLevel(0.5, 0.75);
282
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
283
+ expect(lut.lut).to.eql(secondlut.lut);
284
+ });
285
+ // TODO this test almost works but there are some very slight rounding errors
286
+ // keeping things from being perfectly equal. Need to work out the precision issue.
287
+ it("is consistent for windowLevel extending beyond bounds", () => {
288
+ const lut = new Lut().createFromWindowLevel(1.5, 0.5);
289
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
290
+ for (let i = 0; i < 256 * 4; ++i) {
291
+ expect(lut.lut[i]).to.be.closeTo(secondlut.lut[i], 1);
292
+ }
293
+ });
294
+ });
295
+ it("is consistent for fullRange", () => {
296
+ const lut = new Lut().createFullRange();
297
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
298
+ expect(lut.lut).to.eql(secondlut.lut);
299
+ });
300
+ it("is consistent for dataRange", () => {
301
+ const lut = new Lut().createFromMinMax(histogram.getMin(), histogram.getMax());
302
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
303
+ expect(lut.lut).to.eql(secondlut.lut);
304
+ });
305
+ it("is consistent for percentiles", () => {
306
+ const hmin = histogram.findBinOfPercentile(0.5);
307
+ const hmax = histogram.findBinOfPercentile(0.983);
308
+ const lut = new Lut().createFromMinMax(hmin, hmax);
309
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
310
+ expect(lut.lut).to.eql(secondlut.lut);
311
+ });
312
+ it("is consistent for bestFit", () => {
313
+ const [hmin, hmax] = histogram.findBestFitBins();
314
+ const lut = new Lut().createFromMinMax(hmin, hmax);
315
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
316
+ expect(lut.lut).to.eql(secondlut.lut);
317
+ });
318
+ it("is consistent for auto2", () => {
319
+ const [hmin, hmax] = histogram.findAutoIJBins();
320
+ const lut = new Lut().createFromMinMax(hmin, hmax);
321
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
322
+ expect(lut.lut).to.eql(secondlut.lut);
323
+ });
324
+ it("is consistent for auto", () => {
325
+ const [b, e] = histogram.findAutoMinMax();
326
+ const lut = new Lut().createFromMinMax(b, e);
327
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
328
+ expect(lut.lut).to.eql(secondlut.lut);
329
+ });
330
+ it("is consistent for equalize", () => {
331
+ const lut = new Lut().createFromEqHistogram(histogram);
332
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
333
+ expect(lut.lut).to.eql(secondlut.lut);
334
+ });
335
+ it("is consistent for a degenerate set of white,0 control points", () => {
336
+ const lutArr = new Uint8Array(1024);
337
+ for (let i = 0; i < 1024 / 4; ++i) {
338
+ lutArr[i * 4 + 0] = 255;
339
+ lutArr[i * 4 + 1] = 255;
340
+ lutArr[i * 4 + 2] = 255;
341
+ lutArr[i * 4 + 3] = 0;
342
+ }
343
+ const lut = new Lut();
344
+ lut.lut = lutArr;
345
+ lut.controlPoints = [{
346
+ x: 0,
347
+ color: [255, 255, 255],
348
+ opacity: 0
349
+ }, {
350
+ x: 0,
351
+ color: [255, 255, 255],
352
+ opacity: 0
353
+ }, {
354
+ x: 4,
355
+ color: [255, 255, 255],
356
+ opacity: 0
357
+ }, {
358
+ x: 8,
359
+ color: [255, 255, 255],
360
+ opacity: 0
361
+ }, {
362
+ x: 255,
363
+ color: [255, 255, 255],
364
+ opacity: 0
365
+ }];
366
+ const secondlut = new Lut().createFromControlPoints(lut.controlPoints);
367
+ expect(lut.lut).to.eql(secondlut.lut);
368
+ });
369
+ });
370
+ });
371
+ describe("test remapping lut when raw data range is updated", () => {
372
+ it("remaps the lut when the new data range is completely contained", () => {
373
+ // the full 0-255 domain of this lut is representing the raw intensity range 50-100
374
+ // we will choose a min/max range within the 0-255 domain of the lut.
375
+ const lut = new Lut().createFromMinMax(64, 192);
376
+ // this lut now has a ramp from 64 to 192 in the 0-255 domain
377
+ // (which correspond exactly to the newMin-newMax range in the raw intensities)
378
+ expect(lut.lut[63 * 4 + 3]).to.equal(0);
379
+ expect(lut.lut[64 * 4 + 3]).to.equal(0);
380
+ expect(lut.lut[65 * 4 + 3]).to.be.greaterThan(0);
381
+ expect(lut.lut[191 * 4 + 3]).to.be.lessThan(255);
382
+ expect(lut.lut[192 * 4 + 3]).to.equal(255);
383
+ expect(lut.lut[193 * 4 + 3]).to.equal(255);
384
+
385
+ // artificial data min and max:
386
+ const oldMin = 50;
387
+ const oldMax = 100;
388
+
389
+ // to test this, I will find the values that should exactly fit this ramp in the new range.
390
+ /**
391
+ * Old LUT:
392
+ * 255 | o ------o
393
+ * | /
394
+ * | /
395
+ * | /
396
+ * 0 | o----o
397
+ * +-------------------
398
+ * 0 64 192 255
399
+ * v v v v
400
+ * data: 50 62.5 87.6 100
401
+ */
402
+ const newMin = oldMin + (oldMax - oldMin) * (64 / 255);
403
+ const newMax = oldMin + (oldMax - oldMin) * (192 / 255);
404
+
405
+ /**
406
+ * New LUT:
407
+ * 255 | o
408
+ * | /
409
+ * | /
410
+ * | /
411
+ * 0 | o
412
+ * +--------
413
+ * 0 255
414
+ * v v
415
+ * data: 62.5 87.6
416
+ */
417
+
418
+ // now, remap for a new raw intensity range of newMin to newMax.
419
+ // because the actual slope started at newMin, and ended at newMax,
420
+ // we expect this new lut to ramp up linearly from 0 to 255.
421
+ const secondLut = remapLut(lut.lut, oldMin, oldMax, newMin, newMax);
422
+ // the new lut must represent the range 25-75, and the old lut represented 50-100
423
+ // so the min/max slope of our lut should be reduced, or stretched horizontally
424
+ // the new lut should slope up linearly from 0 to 255.
425
+ expect(secondLut[0 * 4 + 3]).to.equal(0);
426
+ expect(secondLut[1 * 4 + 3]).to.be.closeTo(1, 1);
427
+ expect(secondLut[2 * 4 + 3]).to.be.closeTo(2, 1);
428
+ expect(secondLut[3 * 4 + 3]).to.be.closeTo(3, 1);
429
+ expect(secondLut[63 * 4 + 3]).to.be.closeTo(63, 1);
430
+ expect(secondLut[64 * 4 + 3]).to.be.closeTo(64, 1);
431
+ expect(secondLut[65 * 4 + 3]).to.be.closeTo(65, 1);
432
+ expect(secondLut[126 * 4 + 3]).to.be.closeTo(126, 1);
433
+ expect(secondLut[127 * 4 + 3]).to.be.closeTo(127, 1);
434
+ expect(secondLut[128 * 4 + 3]).to.be.closeTo(128, 1);
435
+ expect(secondLut[191 * 4 + 3]).to.be.closeTo(191, 1);
436
+ expect(secondLut[192 * 4 + 3]).to.be.closeTo(192, 1);
437
+ expect(secondLut[193 * 4 + 3]).to.be.closeTo(193, 1);
438
+ expect(secondLut[253 * 4 + 3]).to.be.closeTo(253, 1);
439
+ expect(secondLut[254 * 4 + 3]).to.be.closeTo(254, 1);
440
+ expect(secondLut[255 * 4 + 3]).to.be.closeTo(255, 1);
441
+ const compareLut = new Lut().createFromMinMax(0, 255);
442
+ for (let i = 0; i < 256 * 4; ++i) {
443
+ expect(secondLut[i]).to.be.closeTo(compareLut.lut[i], 1);
444
+ }
445
+
446
+ // for good measure, just test the reverse mapping too.
447
+ const thirdLut = remapLut(secondLut, newMin, newMax, oldMin, oldMax);
448
+ for (let i = 0; i < 256 * 4; ++i) {
449
+ expect(thirdLut[i]).to.be.closeTo(lut.lut[i], 1);
450
+ }
451
+ });
452
+ it("remaps the lut when the new data range is identical to previous", () => {
453
+ const lut = new Lut().createFromMinMax(0, 255);
454
+
455
+ // artificial min and max:
456
+ const oldMin = 50;
457
+ const oldMax = 100;
458
+ const newMin = oldMin;
459
+ const newMax = oldMax;
460
+ const secondLut = remapLut(lut.lut, oldMin, oldMax, newMin, newMax);
461
+ for (let i = 0; i < 256 * 4; ++i) {
462
+ expect(secondLut[i]).to.eql(lut.lut[i]);
463
+ }
464
+ });
465
+ it("remaps the lut when the new data range is completely overlapped", () => {
466
+ // artificial data min and max absolute intensities:
467
+ const oldMin = 50;
468
+ const oldMax = 100;
469
+ // the full 0-255 domain of this lut is representing the above raw intensity domain 50-100.
470
+ // For test, we will choose a min/max within the 0-255 domain of the lut.
471
+ const lut = new Lut().createFromMinMax(64, 192);
472
+ // this lut now has a ramp from 64 to 192 in the 0-255 range
473
+ // (which correspond exactly to the newMin-newMax range in the raw intensities)
474
+ expect(lut.lut[63 * 4 + 3]).to.equal(0);
475
+ expect(lut.lut[64 * 4 + 3]).to.equal(0);
476
+ expect(lut.lut[65 * 4 + 3]).to.be.greaterThan(0);
477
+ expect(lut.lut[191 * 4 + 3]).to.be.lessThan(255);
478
+ expect(lut.lut[192 * 4 + 3]).to.equal(255);
479
+ expect(lut.lut[193 * 4 + 3]).to.equal(255);
480
+
481
+ /**
482
+ * Old LUT:
483
+ * 255 | o ------o
484
+ * | /
485
+ * | /
486
+ * | /
487
+ * 0 | o----o
488
+ * +-------------------
489
+ * 0 64 192 255
490
+ * v v v v
491
+ * data: 50 62.5 87.6 100
492
+ */
493
+
494
+ // lets remap so that the new lut has a minmax of 96-160
495
+ // we will carefully pick these values so that it's easy to check the result.
496
+
497
+ // Because we chose (64, 192) in the lut above, the min and max inflection points are at:
498
+ const mini = oldMin + 64 / 255 * (oldMax - oldMin);
499
+ const maxi = oldMin + 192 / 255 * (oldMax - oldMin);
500
+ //console.log((mini + maxi) / 2);
501
+
502
+ // what do the data min and max have to be for mini and maxi to map to 96 and 160?
503
+ // should now span 1/4 of the range, still centered.
504
+ const totalrange = (maxi - mini) / ((160 - 96) / (255 - 0));
505
+ // symmetrical spread about the center
506
+ const newMin = (oldMin + oldMax) / 2 - totalrange / 2;
507
+ const newMax = (oldMin + oldMax) / 2 + totalrange / 2;
508
+ /**
509
+ * NEW LUT:
510
+ * 255 | o--------o
511
+ * | /
512
+ * | /
513
+ * | /
514
+ * 0 | o------o
515
+ * +------------------------
516
+ * 0 96 160 255
517
+ * v v v v
518
+ * data: 25 62.5 87.6 125
519
+ */
520
+
521
+ // now, remap for a new raw intensity range of newMin to newMax.
522
+ const secondLut = remapLut(lut.lut, oldMin, oldMax, newMin, newMax);
523
+ expect(secondLut[0 * 4 + 3]).to.equal(0);
524
+ expect(secondLut[1 * 4 + 3]).to.equal(0);
525
+ expect(secondLut[2 * 4 + 3]).to.equal(0);
526
+ expect(secondLut[3 * 4 + 3]).to.equal(0);
527
+ expect(secondLut[63 * 4 + 3]).to.equal(0);
528
+ expect(secondLut[64 * 4 + 3]).to.equal(0);
529
+ expect(secondLut[65 * 4 + 3]).to.equal(0);
530
+ expect(secondLut[128 * 4 + 3]).to.be.closeTo(128, 1);
531
+ expect(secondLut[191 * 4 + 3]).to.equal(255);
532
+ expect(secondLut[192 * 4 + 3]).to.equal(255);
533
+ expect(secondLut[193 * 4 + 3]).to.equal(255);
534
+ expect(secondLut[253 * 4 + 3]).to.equal(255);
535
+ expect(secondLut[254 * 4 + 3]).to.equal(255);
536
+ expect(secondLut[255 * 4 + 3]).to.equal(255);
537
+
538
+ // 96, 160 were chosen above
539
+ const compareLut = new Lut().createFromMinMax(96, 160);
540
+ for (let i = 0; i < 256 * 4; ++i) {
541
+ expect(secondLut[i]).to.be.closeTo(compareLut.lut[i], 1);
542
+ }
543
+
544
+ // for good measure, just test the reverse mapping too.
545
+ const thirdLut = remapLut(secondLut, newMin, newMax, oldMin, oldMax);
546
+ for (let i = 0; i < 256 * 4; ++i) {
547
+ expect(thirdLut[i]).to.be.closeTo(lut.lut[i], 1);
548
+ }
549
+ });
550
+ });
551
+ describe("test remapping control points when raw data range is updated", () => {
552
+ const createMockCPs = () => [{
553
+ x: 0,
554
+ color: [255, 255, 255],
555
+ opacity: 0
556
+ }, {
557
+ x: 64,
558
+ color: [255, 255, 255],
559
+ opacity: 0
560
+ }, {
561
+ x: 192,
562
+ color: [255, 255, 255],
563
+ opacity: 1.0
564
+ }, {
565
+ x: 255,
566
+ color: [255, 255, 255],
567
+ opacity: 1.0
568
+ }];
569
+ it("remaps the control points correctly when new intensity range contracted", () => {
570
+ const cp = createMockCPs();
571
+ /**
572
+ * Old CPs:
573
+ * 255 | o ------o
574
+ * | /
575
+ * | /
576
+ * | /
577
+ * 0 | o----o
578
+ * +-------------------
579
+ * 0 64 192 255
580
+ * v v v v
581
+ */
582
+ const cp2 = remapControlPoints(cp, 0, 255, 64, 192, false);
583
+ /**
584
+ * New CPs:
585
+ * 255 | o
586
+ * | /
587
+ * | /
588
+ * | /
589
+ * 0 | o
590
+ * +-------------------
591
+ * 0 255
592
+ * v v
593
+ * raw values: 64 192
594
+ */
595
+ // intensity range contracted from 0-255 to 64-192
596
+ // therefore the cps should just outside of 64-192 are gone and
597
+ // the new cp's capture only the ramp up from 64-192 in the original cp list.
598
+ const positions = cp2.map(cp => Math.round(cp.x));
599
+ expect(positions).to.include.members([-127, 0, 255, 381]);
600
+ });
601
+ it("remaps the control points correctly when new intensity range expanded", () => {
602
+ const cp = createMockCPs();
603
+ /**
604
+ * Old CPs:
605
+ * 255 | o ------o
606
+ * | /
607
+ * | /
608
+ * | /
609
+ * 0 | o----o
610
+ * +-------------------
611
+ * 0 64 192 255
612
+ * v v v v
613
+ */
614
+ const cp2 = remapControlPoints(cp, 0, 255, -64, 320, false);
615
+ /**
616
+ * New CPs:
617
+ * 255 | o ------o
618
+ * | /
619
+ * | /
620
+ * | /
621
+ * 0 | o----o
622
+ * +-------------------
623
+ * 0 85 170 255
624
+ * v v v v
625
+ * -64 64 192 320 (abs intensities)
626
+ */
627
+ const positions = cp2.map(cp => Math.round(cp.x));
628
+ expect(positions).to.include.members([43, 85, 170, 212]);
629
+ });
630
+ it("keeps ending points on the edge of the data range when new intensity range contracted and nudge is enabled", () => {
631
+ const cp = createMockCPs();
632
+ // NOTE: this range is not contracted as much as the previous test above. That range pushes the inner points to the
633
+ // edge as well, which would cause different behavior. That behavior is checked two tests down.
634
+ const cp2 = remapControlPoints(cp, 0, 255, 32, 224, true);
635
+ const positions = cp2.map(cp => Math.round(cp.x));
636
+ expect(positions).to.include.members([0, 43, 213, 255]);
637
+ });
638
+ it("keeps ending points on the edge of the data range when new intensity range expanded and nudge is enabled", () => {
639
+ const cp = createMockCPs();
640
+ const cp2 = remapControlPoints(cp, 0, 255, -64, 320, true);
641
+ const positions = cp2.map(cp => Math.round(cp.x));
642
+ expect(positions).to.include.members([0, 85, 170, 255]);
643
+ });
644
+ it("avoids nudging ending points into an out-of-order position when the next points in are also mapped outside the range", () => {
645
+ const cp = createMockCPs();
646
+ const cp2 = remapControlPoints(cp, 0, 255, 96, 160, true);
647
+ const [first, second, secondLast, last] = cp2.map(cp => Math.round(cp.x));
648
+ // Inner points are outside the range. If the ending points were nudged into the range, they'd be out of order!
649
+ expect([second, secondLast]).to.include.members([-127, 383]);
650
+ // Instead, they should be kept in order, and at a safe distance from their inner neighbors.
651
+ expect(first).to.be.lessThanOrEqual(second - 1);
652
+ expect(last).to.be.greaterThanOrEqual(secondLast + 1);
653
+ });
654
+ it("does not try to keep ending points in range when they define a line with nonzero slope", () => {
655
+ const cp = createMockCPs();
656
+ cp[1].opacity = 0.2;
657
+ cp[2].opacity = 0.8;
658
+ // Now, if the outer control points were nudged, the slope of the lines would be changed.
659
+ const cp2 = remapControlPoints(cp, 0, 255, 64, 192, true);
660
+ const positions = cp2.map(cp => Math.round(cp.x));
661
+ expect(positions).to.include.members([-127, 0, 255, 381]);
662
+ });
663
+ it("does not snap outer control points to the edge if they did not start on the edge", () => {
664
+ const cp = createMockCPs();
665
+ cp[0].x = 24;
666
+ cp[3].x = 231;
667
+ const cp2 = remapControlPoints(cp, 0, 255, -64, 320, true);
668
+ const positions = cp2.map(cp => Math.round(cp.x));
669
+ expect(positions).to.include.members([58, 85, 170, 196]);
670
+ });
671
+ });