copper3d 3.4.7 → 3.4.9

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.
@@ -40826,6 +40826,17 @@ function copperDicomLoader(url, callback) {
40826
40826
  windowCenter = 226;
40827
40827
  windowWidth = 537;
40828
40828
  }
40829
+ // Spatial geometry tags (DS VR, backslash-delimited) used to place the image plane
40830
+ // at its true world pose, plus InstanceNumber as an ordering fallback.
40831
+ const ipp = parseDS(dataSet.string("x00200032")); // ImagePositionPatient
40832
+ const iop = parseDS(dataSet.string("x00200037")); // ImageOrientationPatient
40833
+ const spacing = parseDS(dataSet.string("x00280030")); // PixelSpacing
40834
+ let instanceNumber = parseInt(dataSet.string("x00200013"));
40835
+ if (Number.isNaN(instanceNumber))
40836
+ instanceNumber = undefined;
40837
+ const corners = ipp && iop && spacing
40838
+ ? computeImagePlaneCorners(ipp, iop, spacing, w, h)
40839
+ : undefined;
40829
40840
  let pixelData = dataSet.elements.x7fe00010;
40830
40841
  let uint16 = convertImplicitElement(pixelData, dicomFileAsBuffer);
40831
40842
  let voiLUT;
@@ -40844,10 +40855,44 @@ function copperDicomLoader(url, callback) {
40844
40855
  uint16,
40845
40856
  uint8,
40846
40857
  order,
40858
+ instanceNumber,
40859
+ imagePositionPatient: ipp,
40860
+ imageOrientationPatient: iop,
40861
+ pixelSpacing: spacing,
40862
+ corners,
40847
40863
  };
40848
40864
  callback && callback(copperVolume);
40849
40865
  });
40850
40866
  }
40867
+ // Parse a DICOM DS/IS multi-value string ("a\\b\\c") into a number[].
40868
+ function parseDS(s) {
40869
+ if (!s)
40870
+ return undefined;
40871
+ const a = s.split("\\").map((v) => parseFloat(v));
40872
+ return a.length && !a.some((n) => Number.isNaN(n)) ? a : undefined;
40873
+ }
40874
+ /**
40875
+ * Compute the 4 world-space corners of a DICOM image plane from its geometry tags.
40876
+ * IPP is the CENTER of pixel (0,0), so corners sit half a pixel out. PixelSpacing is
40877
+ * [rowSpacing, colSpacing]; IOP's first vector is the +column-index direction.
40878
+ */
40879
+ function computeImagePlaneCorners(ipp, iop, spacing, cols, rows) {
40880
+ const row = new Vector3(iop[0], iop[1], iop[2]); // +column-index dir
40881
+ const col = new Vector3(iop[3], iop[4], iop[5]); // +row-index dir
40882
+ const sc = spacing[1]; // column spacing (along row)
40883
+ const sr = spacing[0]; // row spacing (along col)
40884
+ const O = new Vector3(ipp[0], ipp[1], ipp[2]); // center of pixel (0,0)
40885
+ const p = (a, b) => O.clone()
40886
+ .addScaledVector(row, a * sc)
40887
+ .addScaledVector(col, b * sr)
40888
+ .toArray();
40889
+ return {
40890
+ tl: p(-0.5, -0.5),
40891
+ tr: p(cols - 0.5, -0.5),
40892
+ bl: p(-0.5, rows - 0.5),
40893
+ br: p(cols - 0.5, rows - 0.5),
40894
+ };
40895
+ }
40851
40896
  function convertImplicitElement(elementData, dicomFileAsBuffer) {
40852
40897
  let w1Buffer = dicomParser.sharedCopy(dicomFileAsBuffer, elementData.dataOffset, elementData.length);
40853
40898
  let unit16 = new Uint16Array(w1Buffer.buffer, w1Buffer.byteOffset, w1Buffer.byteLength / Uint16Array.BYTES_PER_ELEMENT);
@@ -40884,514 +40929,23 @@ function getLut(data, windowWidth, windowCenter, invert, voiLUT) {
40884
40929
  };
40885
40930
  }
40886
40931
 
40887
- // DEFLATE is a complex format; to read this code, you should probably check the RFC first:
40932
+ var vert_2d = "#define GLSLIFY 1\nout vec2 vUv;void main(){gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);vUv=vec2(uv.x,1.0-uv.y);}"; // eslint-disable-line
40888
40933
 
40889
- // aliases for shorter compressed code (most minifers don't do this)
40890
- var u8$1 = Uint8Array, u16$1 = Uint16Array, u32 = Uint32Array;
40891
- // fixed length extra bits
40892
- var fleb$1 = new u8$1([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0]);
40893
- // fixed distance extra bits
40894
- // see fleb note
40895
- var fdeb$1 = new u8$1([0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0]);
40896
- // code length index map
40897
- var clim$1 = new u8$1([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]);
40898
- // get base, reverse index map from extra bits
40899
- var freb$1 = function (eb, start) {
40900
- var b = new u16$1(31);
40901
- for (var i = 0; i < 31; ++i) {
40902
- b[i] = start += 1 << eb[i - 1];
40903
- }
40904
- // numbers here are at max 18 bits
40905
- var r = new u32(b[30]);
40906
- for (var i = 1; i < 30; ++i) {
40907
- for (var j = b[i]; j < b[i + 1]; ++j) {
40908
- r[j] = ((j - b[i]) << 5) | i;
40909
- }
40910
- }
40911
- return [b, r];
40912
- };
40913
- var _a$1 = freb$1(fleb$1, 2), fl$1 = _a$1[0], revfl$1 = _a$1[1];
40914
- // we can ignore the fact that the other numbers are wrong; they never happen anyway
40915
- fl$1[28] = 258, revfl$1[258] = 28;
40916
- var _b$1 = freb$1(fdeb$1, 0), fd$1 = _b$1[0];
40917
- // map of value to reverse (assuming 16 bits)
40918
- var rev$1 = new u16$1(32768);
40919
- for (var i$1 = 0; i$1 < 32768; ++i$1) {
40920
- // reverse table algorithm from SO
40921
- var x$1 = ((i$1 & 0xAAAA) >>> 1) | ((i$1 & 0x5555) << 1);
40922
- x$1 = ((x$1 & 0xCCCC) >>> 2) | ((x$1 & 0x3333) << 2);
40923
- x$1 = ((x$1 & 0xF0F0) >>> 4) | ((x$1 & 0x0F0F) << 4);
40924
- rev$1[i$1] = (((x$1 & 0xFF00) >>> 8) | ((x$1 & 0x00FF) << 8)) >>> 1;
40925
- }
40926
- // create huffman tree from u8 "map": index -> code length for code index
40927
- // mb (max bits) must be at most 15
40928
- // TODO: optimize/split up?
40929
- var hMap$1 = (function (cd, mb, r) {
40930
- var s = cd.length;
40931
- // index
40932
- var i = 0;
40933
- // u16 "map": index -> # of codes with bit length = index
40934
- var l = new u16$1(mb);
40935
- // length of cd must be 288 (total # of codes)
40936
- for (; i < s; ++i) {
40937
- if (cd[i])
40938
- ++l[cd[i] - 1];
40939
- }
40940
- // u16 "map": index -> minimum code for bit length = index
40941
- var le = new u16$1(mb);
40942
- for (i = 0; i < mb; ++i) {
40943
- le[i] = (le[i - 1] + l[i - 1]) << 1;
40944
- }
40945
- var co;
40946
- if (r) {
40947
- // u16 "map": index -> number of actual bits, symbol for code
40948
- co = new u16$1(1 << mb);
40949
- // bits to remove for reverser
40950
- var rvb = 15 - mb;
40951
- for (i = 0; i < s; ++i) {
40952
- // ignore 0 lengths
40953
- if (cd[i]) {
40954
- // num encoding both symbol and bits read
40955
- var sv = (i << 4) | cd[i];
40956
- // free bits
40957
- var r_1 = mb - cd[i];
40958
- // start value
40959
- var v = le[cd[i] - 1]++ << r_1;
40960
- // m is end value
40961
- for (var m = v | ((1 << r_1) - 1); v <= m; ++v) {
40962
- // every 16 bit value starting with the code yields the same result
40963
- co[rev$1[v] >>> rvb] = sv;
40964
- }
40965
- }
40966
- }
40967
- }
40968
- else {
40969
- co = new u16$1(s);
40970
- for (i = 0; i < s; ++i) {
40971
- if (cd[i]) {
40972
- co[i] = rev$1[le[cd[i] - 1]++] >>> (15 - cd[i]);
40973
- }
40974
- }
40975
- }
40976
- return co;
40977
- });
40978
- // fixed length tree
40979
- var flt$1 = new u8$1(288);
40980
- for (var i$1 = 0; i$1 < 144; ++i$1)
40981
- flt$1[i$1] = 8;
40982
- for (var i$1 = 144; i$1 < 256; ++i$1)
40983
- flt$1[i$1] = 9;
40984
- for (var i$1 = 256; i$1 < 280; ++i$1)
40985
- flt$1[i$1] = 7;
40986
- for (var i$1 = 280; i$1 < 288; ++i$1)
40987
- flt$1[i$1] = 8;
40988
- // fixed distance tree
40989
- var fdt$1 = new u8$1(32);
40990
- for (var i$1 = 0; i$1 < 32; ++i$1)
40991
- fdt$1[i$1] = 5;
40992
- // fixed length map
40993
- var flrm$1 = /*#__PURE__*/ hMap$1(flt$1, 9, 1);
40994
- // fixed distance map
40995
- var fdrm$1 = /*#__PURE__*/ hMap$1(fdt$1, 5, 1);
40996
- // find max of array
40997
- var max$1 = function (a) {
40998
- var m = a[0];
40999
- for (var i = 1; i < a.length; ++i) {
41000
- if (a[i] > m)
41001
- m = a[i];
41002
- }
41003
- return m;
41004
- };
41005
- // read d, starting at bit p and mask with m
41006
- var bits$1 = function (d, p, m) {
41007
- var o = (p / 8) | 0;
41008
- return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m;
41009
- };
41010
- // read d, starting at bit p continuing for at least 16 bits
41011
- var bits16$1 = function (d, p) {
41012
- var o = (p / 8) | 0;
41013
- return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7));
41014
- };
41015
- // get end of byte
41016
- var shft$1 = function (p) { return ((p + 7) / 8) | 0; };
41017
- // typed array slice - allows garbage collector to free original reference,
41018
- // while being more compatible than .slice
41019
- var slc$1 = function (v, s, e) {
41020
- if (s == null || s < 0)
41021
- s = 0;
41022
- if (e == null || e > v.length)
41023
- e = v.length;
41024
- // can't use .constructor in case user-supplied
41025
- var n = new (v.BYTES_PER_ELEMENT == 2 ? u16$1 : v.BYTES_PER_ELEMENT == 4 ? u32 : u8$1)(e - s);
41026
- n.set(v.subarray(s, e));
41027
- return n;
41028
- };
41029
- // error codes
41030
- var ec$1 = [
41031
- 'unexpected EOF',
41032
- 'invalid block type',
41033
- 'invalid length/literal',
41034
- 'invalid distance',
41035
- 'stream finished',
41036
- 'no stream handler',
41037
- ,
41038
- 'no callback',
41039
- 'invalid UTF-8 data',
41040
- 'extra field too long',
41041
- 'date not in range 1980-2099',
41042
- 'filename too long',
41043
- 'stream finishing',
41044
- 'invalid zip data'
41045
- // determined by unknown compression method
41046
- ];
41047
- var err$1 = function (ind, msg, nt) {
41048
- var e = new Error(msg || ec$1[ind]);
41049
- e.code = ind;
41050
- if (Error.captureStackTrace)
41051
- Error.captureStackTrace(e, err$1);
41052
- if (!nt)
41053
- throw e;
41054
- return e;
41055
- };
41056
- // expands raw DEFLATE data
41057
- var inflt$1 = function (dat, buf, st) {
41058
- // source length
41059
- var sl = dat.length;
41060
- if (!sl || (st && st.f && !st.l))
41061
- return buf || new u8$1(0);
41062
- // have to estimate size
41063
- var noBuf = !buf || st;
41064
- // no state
41065
- var noSt = !st || st.i;
41066
- if (!st)
41067
- st = {};
41068
- // Assumes roughly 33% compression ratio average
41069
- if (!buf)
41070
- buf = new u8$1(sl * 3);
41071
- // ensure buffer can fit at least l elements
41072
- var cbuf = function (l) {
41073
- var bl = buf.length;
41074
- // need to increase size to fit
41075
- if (l > bl) {
41076
- // Double or set to necessary, whichever is greater
41077
- var nbuf = new u8$1(Math.max(bl * 2, l));
41078
- nbuf.set(buf);
41079
- buf = nbuf;
41080
- }
41081
- };
41082
- // last chunk bitpos bytes
41083
- var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n;
41084
- // total bits
41085
- var tbts = sl * 8;
41086
- do {
41087
- if (!lm) {
41088
- // BFINAL - this is only 1 when last chunk is next
41089
- final = bits$1(dat, pos, 1);
41090
- // type: 0 = no compression, 1 = fixed huffman, 2 = dynamic huffman
41091
- var type = bits$1(dat, pos + 1, 3);
41092
- pos += 3;
41093
- if (!type) {
41094
- // go to end of byte boundary
41095
- var s = shft$1(pos) + 4, l = dat[s - 4] | (dat[s - 3] << 8), t = s + l;
41096
- if (t > sl) {
41097
- if (noSt)
41098
- err$1(0);
41099
- break;
41100
- }
41101
- // ensure size
41102
- if (noBuf)
41103
- cbuf(bt + l);
41104
- // Copy over uncompressed data
41105
- buf.set(dat.subarray(s, t), bt);
41106
- // Get new bitpos, update byte count
41107
- st.b = bt += l, st.p = pos = t * 8, st.f = final;
41108
- continue;
41109
- }
41110
- else if (type == 1)
41111
- lm = flrm$1, dm = fdrm$1, lbt = 9, dbt = 5;
41112
- else if (type == 2) {
41113
- // literal lengths
41114
- var hLit = bits$1(dat, pos, 31) + 257, hcLen = bits$1(dat, pos + 10, 15) + 4;
41115
- var tl = hLit + bits$1(dat, pos + 5, 31) + 1;
41116
- pos += 14;
41117
- // length+distance tree
41118
- var ldt = new u8$1(tl);
41119
- // code length tree
41120
- var clt = new u8$1(19);
41121
- for (var i = 0; i < hcLen; ++i) {
41122
- // use index map to get real code
41123
- clt[clim$1[i]] = bits$1(dat, pos + i * 3, 7);
41124
- }
41125
- pos += hcLen * 3;
41126
- // code lengths bits
41127
- var clb = max$1(clt), clbmsk = (1 << clb) - 1;
41128
- // code lengths map
41129
- var clm = hMap$1(clt, clb, 1);
41130
- for (var i = 0; i < tl;) {
41131
- var r = clm[bits$1(dat, pos, clbmsk)];
41132
- // bits read
41133
- pos += r & 15;
41134
- // symbol
41135
- var s = r >>> 4;
41136
- // code length to copy
41137
- if (s < 16) {
41138
- ldt[i++] = s;
41139
- }
41140
- else {
41141
- // copy count
41142
- var c = 0, n = 0;
41143
- if (s == 16)
41144
- n = 3 + bits$1(dat, pos, 3), pos += 2, c = ldt[i - 1];
41145
- else if (s == 17)
41146
- n = 3 + bits$1(dat, pos, 7), pos += 3;
41147
- else if (s == 18)
41148
- n = 11 + bits$1(dat, pos, 127), pos += 7;
41149
- while (n--)
41150
- ldt[i++] = c;
41151
- }
41152
- }
41153
- // length tree distance tree
41154
- var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit);
41155
- // max length bits
41156
- lbt = max$1(lt);
41157
- // max dist bits
41158
- dbt = max$1(dt);
41159
- lm = hMap$1(lt, lbt, 1);
41160
- dm = hMap$1(dt, dbt, 1);
41161
- }
41162
- else
41163
- err$1(1);
41164
- if (pos > tbts) {
41165
- if (noSt)
41166
- err$1(0);
41167
- break;
41168
- }
41169
- }
41170
- // Make sure the buffer can hold this + the largest possible addition
41171
- // Maximum chunk size (practically, theoretically infinite) is 2^17;
41172
- if (noBuf)
41173
- cbuf(bt + 131072);
41174
- var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1;
41175
- var lpos = pos;
41176
- for (;; lpos = pos) {
41177
- // bits read, code
41178
- var c = lm[bits16$1(dat, pos) & lms], sym = c >>> 4;
41179
- pos += c & 15;
41180
- if (pos > tbts) {
41181
- if (noSt)
41182
- err$1(0);
41183
- break;
41184
- }
41185
- if (!c)
41186
- err$1(2);
41187
- if (sym < 256)
41188
- buf[bt++] = sym;
41189
- else if (sym == 256) {
41190
- lpos = pos, lm = null;
41191
- break;
41192
- }
41193
- else {
41194
- var add = sym - 254;
41195
- // no extra bits needed if less
41196
- if (sym > 264) {
41197
- // index
41198
- var i = sym - 257, b = fleb$1[i];
41199
- add = bits$1(dat, pos, (1 << b) - 1) + fl$1[i];
41200
- pos += b;
41201
- }
41202
- // dist
41203
- var d = dm[bits16$1(dat, pos) & dms], dsym = d >>> 4;
41204
- if (!d)
41205
- err$1(3);
41206
- pos += d & 15;
41207
- var dt = fd$1[dsym];
41208
- if (dsym > 3) {
41209
- var b = fdeb$1[dsym];
41210
- dt += bits16$1(dat, pos) & ((1 << b) - 1), pos += b;
41211
- }
41212
- if (pos > tbts) {
41213
- if (noSt)
41214
- err$1(0);
41215
- break;
41216
- }
41217
- if (noBuf)
41218
- cbuf(bt + 131072);
41219
- var end = bt + add;
41220
- for (; bt < end; bt += 4) {
41221
- buf[bt] = buf[bt - dt];
41222
- buf[bt + 1] = buf[bt + 1 - dt];
41223
- buf[bt + 2] = buf[bt + 2 - dt];
41224
- buf[bt + 3] = buf[bt + 3 - dt];
41225
- }
41226
- bt = end;
41227
- }
41228
- }
41229
- st.l = lm, st.p = lpos, st.b = bt, st.f = final;
41230
- if (lm)
41231
- final = 1, st.m = lbt, st.d = dm, st.n = dbt;
41232
- } while (!final);
41233
- return bt == buf.length ? buf : slc$1(buf, 0, bt);
41234
- };
41235
- // empty
41236
- var et$1 = /*#__PURE__*/ new u8$1(0);
41237
- // read 2 bytes
41238
- var b2 = function (d, b) { return d[b] | (d[b + 1] << 8); };
41239
- // read 4 bytes
41240
- var b4 = function (d, b) { return (d[b] | (d[b + 1] << 8) | (d[b + 2] << 16) | (d[b + 3] << 24)) >>> 0; };
41241
- var b8 = function (d, b) { return b4(d, b) + (b4(d, b + 4) * 4294967296); };
41242
- /**
41243
- * Expands DEFLATE data with no wrapper
41244
- * @param data The data to decompress
41245
- * @param out Where to write the data. Saves memory if you know the decompressed size and provide an output buffer of that length.
41246
- * @returns The decompressed version of the data
41247
- */
41248
- function inflateSync(data, out) {
41249
- return inflt$1(data, out);
41250
- }
41251
- // text decoder
41252
- var td$1 = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();
41253
- // text decoder stream
41254
- var tds$1 = 0;
41255
- try {
41256
- td$1.decode(et$1, { stream: true });
41257
- tds$1 = 1;
41258
- }
41259
- catch (e) { }
41260
- // decode UTF8
41261
- var dutf8 = function (d) {
41262
- for (var r = '', i = 0;;) {
41263
- var c = d[i++];
41264
- var eb = (c > 127) + (c > 223) + (c > 239);
41265
- if (i + eb > d.length)
41266
- return [r, slc$1(d, i - 1)];
41267
- if (!eb)
41268
- r += String.fromCharCode(c);
41269
- else if (eb == 3) {
41270
- c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63)) - 65536,
41271
- r += String.fromCharCode(55296 | (c >> 10), 56320 | (c & 1023));
41272
- }
41273
- else if (eb & 1)
41274
- r += String.fromCharCode((c & 31) << 6 | (d[i++] & 63));
41275
- else
41276
- r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | (d[i++] & 63));
41277
- }
41278
- };
41279
- /**
41280
- * Converts a Uint8Array to a string
41281
- * @param dat The data to decode to string
41282
- * @param latin1 Whether or not to interpret the data as Latin-1. This should
41283
- * not need to be true unless encoding to binary string.
41284
- * @returns The original UTF-8/Latin-1 string
41285
- */
41286
- function strFromU8(dat, latin1) {
41287
- if (latin1) {
41288
- var r = '';
41289
- for (var i = 0; i < dat.length; i += 16384)
41290
- r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384));
41291
- return r;
41292
- }
41293
- else if (td$1)
41294
- return td$1.decode(dat);
41295
- else {
41296
- var _a = dutf8(dat), out = _a[0], ext = _a[1];
41297
- if (ext.length)
41298
- err$1(8);
41299
- return out;
41300
- }
41301
- }
41302
- // skip local zip header
41303
- var slzh = function (d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); };
41304
- // read zip header
41305
- var zh = function (d, b, z) {
41306
- var fnl = b2(d, b + 28), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl, bs = b4(d, b + 20);
41307
- var _a = z && bs == 4294967295 ? z64e(d, es) : [bs, b4(d, b + 24), b4(d, b + 42)], sc = _a[0], su = _a[1], off = _a[2];
41308
- return [b2(d, b + 10), sc, su, fn, es + b2(d, b + 30) + b2(d, b + 32), off];
41309
- };
41310
- // read zip64 extra field
41311
- var z64e = function (d, b) {
41312
- for (; b2(d, b) != 1; b += 4 + b2(d, b + 2))
41313
- ;
41314
- return [b8(d, b + 12), b8(d, b + 4), b8(d, b + 20)];
41315
- };
41316
- /**
41317
- * Synchronously decompresses a ZIP archive. Prefer using `unzip` for better
41318
- * performance with more than one file.
41319
- * @param data The raw compressed ZIP file
41320
- * @param opts The ZIP extraction options
41321
- * @returns The decompressed files
41322
- */
41323
- function unzipSync(data, opts) {
41324
- var files = {};
41325
- var e = data.length - 22;
41326
- for (; b4(data, e) != 0x6054B50; --e) {
41327
- if (!e || data.length - e > 65558)
41328
- err$1(13);
41329
- }
41330
- var c = b2(data, e + 8);
41331
- if (!c)
41332
- return {};
41333
- var o = b4(data, e + 16);
41334
- var z = o == 4294967295 || c == 65535;
41335
- if (z) {
41336
- var ze = b4(data, e - 12);
41337
- z = b4(data, ze) == 0x6064B50;
41338
- if (z) {
41339
- c = b4(data, ze + 32);
41340
- o = b4(data, ze + 48);
41341
- }
41342
- }
41343
- var fltr = opts && opts.filter;
41344
- for (var i = 0; i < c; ++i) {
41345
- var _a = zh(data, o, z), c_2 = _a[0], sc = _a[1], su = _a[2], fn = _a[3], no = _a[4], off = _a[5], b = slzh(data, off);
41346
- o = no;
41347
- if (!fltr || fltr({
41348
- name: fn,
41349
- size: sc,
41350
- originalSize: su,
41351
- compression: c_2
41352
- })) {
41353
- if (!c_2)
41354
- files[fn] = slc$1(data, b, b + sc);
41355
- else if (c_2 == 8)
41356
- files[fn] = inflateSync(data.subarray(b, b + sc), new u8$1(su));
41357
- else
41358
- err$1(14, 'unknown compression type ' + c_2);
41359
- }
41360
- }
41361
- return files;
41362
- }
41363
-
41364
- var vert_2d = "#define GLSLIFY 1\nuniform vec2 size;out vec2 vUv;void main(){gl_Position=projectionMatrix*modelViewMatrix*vec4(position,1.0);vUv.xy=position.xy/size+0.5;vUv.y=1.0-vUv.y;}"; // eslint-disable-line
41365
-
41366
- var frag_2d = "precision highp float;precision highp int;precision highp sampler2DArray;\n#define GLSLIFY 1\nuniform sampler2DArray diffuse;in vec2 vUv;uniform int depth;out vec4 outColor;void main(){vec4 color=texture(diffuse,vec3(vUv,depth));outColor=vec4(color.rrr*1.5,1.0);}"; // eslint-disable-line
40934
+ var frag_2d = "precision highp float;precision highp int;precision highp sampler2DArray;\n#define GLSLIFY 1\nuniform sampler2DArray diffuse;in vec2 vUv;uniform int depth;uniform float uOpacity;out vec4 outColor;void main(){vec4 color=texture(diffuse,vec3(vUv,depth));outColor=vec4(color.rrr*1.5,uOpacity);}"; // eslint-disable-line
41367
40935
 
41368
40936
  let planeWidth = 80;
41369
40937
  let planeHeight = 80;
41370
- function createTexture2D_Zip(url, scene) {
41371
- new FileLoader()
41372
- .setResponseType("arraybuffer")
41373
- .load(url, function (data) {
41374
- const zip = unzipSync(new Uint8Array(data));
41375
- const array = new Uint8Array(zip["head256x256x109"].buffer);
41376
- const texture = new DataArrayTexture(array, 256, 256, 109);
41377
- texture.format = RedFormat;
41378
- texture.needsUpdate = true;
41379
- const material = new ShaderMaterial({
41380
- uniforms: {
41381
- diffuse: { value: texture },
41382
- depth: { value: 1 },
41383
- size: { value: new Vector2(planeWidth, planeHeight) },
41384
- },
41385
- vertexShader: vert_2d,
41386
- fragmentShader: frag_2d,
41387
- glslVersion: GLSL3,
41388
- side: DoubleSide,
41389
- });
41390
- const geometry = new PlaneGeometry(planeWidth, planeHeight);
41391
- const mesh = new Mesh(geometry, material);
41392
- mesh.name = "texture2d_mesh_zip";
41393
- scene.add(mesh);
41394
- });
40938
+ // Build a quad from the 4 world-space image-plane corners (preserves real pose).
40939
+ function buildAlignedQuad(c) {
40940
+ const g = new BufferGeometry();
40941
+ // two triangles: tl,bl,tr / tr,bl,br
40942
+ g.setAttribute("position", new BufferAttribute(new Float32Array([...c.tl, ...c.bl, ...c.tr, ...c.tr, ...c.bl, ...c.br]), 3));
40943
+ // vertex order is tl, bl, tr, tr, bl, br. The fragment shader flips v (vUv.y = 1-uv.y)
40944
+ // and DataArrayTexture has no flipY, so the image-top (data row 0) must map to the
40945
+ // top world corner: tl/tr get uv.y = 1.
40946
+ g.setAttribute("uv", new BufferAttribute(new Float32Array([0, 1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0]), 2));
40947
+ g.computeVertexNormals();
40948
+ return g;
41395
40949
  }
41396
40950
  function createTexture2D_NRRD(data, width, height, depth, callback) {
41397
40951
  const texture = new DataArrayTexture(data, width, height, depth);
@@ -41401,7 +40955,7 @@ function createTexture2D_NRRD(data, width, height, depth, callback) {
41401
40955
  uniforms: {
41402
40956
  diffuse: { value: texture },
41403
40957
  depth: { value: 1 },
41404
- size: { value: new Vector2(planeWidth, planeHeight) },
40958
+ uOpacity: { value: 1 },
41405
40959
  },
41406
40960
  vertexShader: vert_2d,
41407
40961
  fragmentShader: frag_2d,
@@ -41413,13 +40967,9 @@ function createTexture2D_NRRD(data, width, height, depth, callback) {
41413
40967
  mesh.name = "texture2d_mesh_zip";
41414
40968
  callback(mesh);
41415
40969
  }
41416
- function createTexture2D_Array(copperVolume, depth, scene, gui) {
40970
+ function createTexture2D_Array(copperVolume, depth, scene, gui, aligned = false) {
41417
40971
  planeWidth = copperVolume.width / 2;
41418
40972
  planeHeight = copperVolume.height / 2;
41419
- ({
41420
- windowWidth: copperVolume.windowWidth,
41421
- windowCenter: copperVolume.windowCenter,
41422
- });
41423
40973
  const texture = new DataArrayTexture(copperVolume.uint8, copperVolume.width, copperVolume.height, depth);
41424
40974
  texture.format = RedFormat;
41425
40975
  texture.needsUpdate = true;
@@ -41446,18 +40996,22 @@ function createTexture2D_Array(copperVolume, depth, scene, gui) {
41446
40996
  const material = new ShaderMaterial({
41447
40997
  uniforms: {
41448
40998
  diffuse: { value: texture },
41449
- depth: { value: 1 },
41450
- size: { value: new Vector2(planeWidth, planeHeight) },
40999
+ depth: { value: 0 },
41000
+ uOpacity: { value: 1 },
41451
41001
  },
41452
41002
  vertexShader: vert_2d,
41453
41003
  fragmentShader: frag_2d,
41454
41004
  glslVersion: GLSL3,
41455
41005
  side: DoubleSide,
41456
41006
  });
41457
- const geometry = new PlaneGeometry(planeWidth, planeHeight);
41007
+ // Aligned world-space quad only when explicitly requested and geometry tags exist;
41008
+ // otherwise the legacy centred plane (keeps existing loadDicom behaviour unchanged).
41009
+ const geometry = aligned && copperVolume.corners
41010
+ ? buildAlignedQuad(copperVolume.corners)
41011
+ : new PlaneGeometry(planeWidth, planeHeight);
41458
41012
  const mesh = new Mesh(geometry, material);
41459
- scene.add(mesh);
41460
41013
  mesh.name = "texture2d_mesh_array";
41014
+ scene.add(mesh);
41461
41015
  function updateTexture(copperVolumeUp) {
41462
41016
  if (!!copperVolumeUp) {
41463
41017
  let voiLUT;
@@ -41468,7 +41022,15 @@ function createTexture2D_Array(copperVolume, depth, scene, gui) {
41468
41022
  texture.needsUpdate = true;
41469
41023
  }
41470
41024
  }
41471
- return { mesh, copperVolume, updateTexture };
41025
+ function setFrame(i) {
41026
+ material.uniforms.depth.value = i;
41027
+ }
41028
+ function setWindow(center, width) {
41029
+ copperVolume.windowCenter = center;
41030
+ copperVolume.windowWidth = width;
41031
+ updateTexture(copperVolume);
41032
+ }
41033
+ return { mesh, copperVolume, updateTexture, setFrame, setWindow };
41472
41034
  }
41473
41035
 
41474
41036
  /*!
@@ -59300,6 +58862,7 @@ function onTouchEnd( event ) {
59300
58862
  }
59301
58863
 
59302
58864
  class copperScene extends baseScene {
58865
+ // rayster pick
59303
58866
  constructor(container, renderer, opt) {
59304
58867
  super(container, renderer, opt);
59305
58868
  this.clock = new Clock();
@@ -59307,9 +58870,6 @@ class copperScene extends baseScene {
59307
58870
  this.mixer = null;
59308
58871
  this.playRate = 1.0;
59309
58872
  this.modelReady = false;
59310
- // rayster pick
59311
- // texture2d
59312
- this.texture2dMesh = null;
59313
58873
  if ((opt === null || opt === void 0 ? void 0 : opt.controls) === "trackball") {
59314
58874
  this.controls = new TrackballControls(this.camera, this.renderer.domElement);
59315
58875
  }
@@ -59515,38 +59075,177 @@ class copperScene extends baseScene {
59515
59075
  this.exportContent.animations.push(clip);
59516
59076
  };
59517
59077
  }
59518
- // texture2d
59519
- texture2d(url) {
59520
- createTexture2D_Zip(url, this.scene);
59521
- const textureInterval = setInterval(() => {
59522
- var _a;
59523
- this.scene.children.forEach((child) => {
59524
- if (child.isMesh) {
59525
- if (child.name === "texture2d_mesh_zip") {
59526
- this.texture2dMesh = child;
59527
- const render_texture2d = () => {
59528
- if (this.texture2dMesh) {
59529
- let value = this.texture2dMesh.material.uniforms["depth"].value;
59530
- value += this.depthStep;
59531
- if (value > 109.0 || value < 0.0) {
59532
- if (value > 1.0)
59533
- value = 109.0 * 2.0 - value;
59534
- if (value < 0.0)
59535
- value = -value;
59536
- this.depthStep = -this.depthStep;
59537
- }
59538
- this.texture2dMesh.material.uniforms["depth"].value =
59539
- value;
59540
- }
59541
- };
59542
- this.addPreRenderCallbackFunction(render_texture2d);
59543
- }
59544
- }
59078
+ /**
59079
+ * Load an aligned 4D scene: a cine MRI (one slice, N cardiac phases) rendered as a
59080
+ * world-placed plane, plus 0..N deforming surface sequences (e.g. LV endo/epi). MRI
59081
+ * and surfaces stay in their shared patient coordinate frame (no center/scale), and
59082
+ * advance together off ONE shared frame clock. Returns a controller (play/pause/...).
59083
+ */
59084
+ loadAligned4D(opts, callback) {
59085
+ var _a;
59086
+ const dicomDepth = opts.dicomUrls.length;
59087
+ const loadDicomStack = () => new Promise((resolve) => {
59088
+ const volumes = [];
59089
+ opts.dicomUrls.forEach((url) => {
59090
+ copperDicomLoader(url, (volume) => {
59091
+ volumes.push(volume);
59092
+ if (volumes.length !== dicomDepth)
59093
+ return;
59094
+ // order frames by cardiac phase (TriggerTime / SliceLocation)
59095
+ volumes.sort((a, b) => a.order - b.order);
59096
+ const frameSize = volumes[0].width * volumes[0].height;
59097
+ const uint8 = new Uint8ClampedArray(frameSize * dicomDepth);
59098
+ const uint16 = new Uint16Array(uint8.length);
59099
+ volumes.forEach((v, index) => {
59100
+ uint8.set(v.uint8, index * frameSize);
59101
+ uint16.set(v.uint16, index * frameSize);
59102
+ });
59103
+ const stacked = Object.assign(Object.assign({}, volumes[0]), { uint8,
59104
+ uint16 });
59105
+ resolve(stacked);
59106
+ });
59545
59107
  });
59546
- if (((_a = this.texture2dMesh) === null || _a === void 0 ? void 0 : _a.name) === "texture2d_mesh_zip") {
59547
- clearInterval(textureInterval);
59108
+ });
59109
+ const loadSurfaceSequence = (urls) => new Promise((resolve) => {
59110
+ const loader = new VTKLoader();
59111
+ const geometries = new Array(urls.length);
59112
+ let done = 0;
59113
+ urls.forEach((url, index) => {
59114
+ loader.load(url, (geometry) => {
59115
+ // keep world coordinates — no center()/scale()
59116
+ geometry.computeVertexNormals();
59117
+ geometries[index] = geometry;
59118
+ if (++done === urls.length)
59119
+ resolve(geometries);
59120
+ });
59121
+ });
59122
+ });
59123
+ const surfaceDefs = (_a = opts.surfaces) !== null && _a !== void 0 ? _a : [];
59124
+ Promise.all([
59125
+ loadDicomStack(),
59126
+ ...surfaceDefs.map((s) => loadSurfaceSequence(s.urls)),
59127
+ ]).then(([stacked, ...surfaceGeoms]) => {
59128
+ var _a;
59129
+ if (opts.window) {
59130
+ stacked.windowCenter = opts.window.center;
59131
+ stacked.windowWidth = opts.window.width;
59548
59132
  }
59549
- }, 500);
59133
+ const tex = createTexture2D_Array(stacked, dicomDepth, this.scene, undefined, true);
59134
+ tex.setFrame(0);
59135
+ if (opts.window)
59136
+ tex.setWindow(opts.window.center, opts.window.width);
59137
+ const surfaceMeshes = {};
59138
+ const seqs = [];
59139
+ surfaceDefs.forEach((def, i) => {
59140
+ var _a;
59141
+ const geometries = surfaceGeoms[i];
59142
+ const { vtkmaterial } = copperMultipleVtk(def.opts);
59143
+ const mesh = new Mesh(geometries[0], vtkmaterial);
59144
+ mesh.name = def.name;
59145
+ this.scene.add(mesh);
59146
+ surfaceMeshes[def.name] = mesh;
59147
+ seqs.push({ mesh, geometries, offset: (_a = def.offset) !== null && _a !== void 0 ? _a : 0 });
59148
+ });
59149
+ const frameCount = dicomDepth;
59150
+ // Default one cardiac cycle ≈ 1012ms (32 phases × ~31.6ms) unless overridden.
59151
+ const cycleMs = (_a = opts.cycleMs) !== null && _a !== void 0 ? _a : 1012;
59152
+ let frameIndex = 0;
59153
+ let speed = 1;
59154
+ let playing = true;
59155
+ let disposed = false;
59156
+ let lastStep = performance.now();
59157
+ const dtBase = cycleMs / frameCount;
59158
+ const applyFrame = () => {
59159
+ tex.setFrame(frameIndex);
59160
+ for (const s of seqs) {
59161
+ const i = (frameIndex + s.offset + s.geometries.length) % s.geometries.length;
59162
+ s.mesh.geometry = s.geometries[i];
59163
+ }
59164
+ };
59165
+ const tick = () => {
59166
+ if (disposed || !playing)
59167
+ return;
59168
+ const now = performance.now();
59169
+ if (now - lastStep >= dtBase / speed) {
59170
+ lastStep = now;
59171
+ frameIndex = (frameIndex + 1) % frameCount;
59172
+ applyFrame();
59173
+ }
59174
+ };
59175
+ this.addPreRenderCallbackFunction(tick);
59176
+ const clockId = tick.id;
59177
+ const ctrl = {
59178
+ plane: tex.mesh,
59179
+ surfaceMeshes,
59180
+ frameCount,
59181
+ play: () => {
59182
+ playing = true;
59183
+ lastStep = performance.now();
59184
+ },
59185
+ pause: () => {
59186
+ playing = false;
59187
+ },
59188
+ toggle: () => {
59189
+ playing = !playing;
59190
+ lastStep = performance.now();
59191
+ },
59192
+ setSpeed: (x) => {
59193
+ speed = Math.max(0.01, x);
59194
+ },
59195
+ setFrame: (i) => {
59196
+ frameIndex = ((i % frameCount) + frameCount) % frameCount;
59197
+ applyFrame();
59198
+ },
59199
+ setFrameOffset: (name, n) => {
59200
+ const s = seqs.find((q) => q.mesh.name === name);
59201
+ if (s) {
59202
+ s.offset = n;
59203
+ applyFrame();
59204
+ }
59205
+ },
59206
+ setWindow: (center, width) => {
59207
+ tex.setWindow(center, width);
59208
+ },
59209
+ setPlaneOpacity: (v) => {
59210
+ const m = tex.mesh.material;
59211
+ m.transparent = v < 1;
59212
+ m.uniforms.uOpacity.value = v;
59213
+ m.needsUpdate = true;
59214
+ },
59215
+ setSurfaceOpacity: (name, v) => {
59216
+ const mesh = surfaceMeshes[name];
59217
+ if (!mesh)
59218
+ return;
59219
+ const m = mesh.material;
59220
+ m.transparent = v < 1;
59221
+ m.opacity = v;
59222
+ m.needsUpdate = true;
59223
+ },
59224
+ setSurfaceVisible: (name, visible) => {
59225
+ const mesh = surfaceMeshes[name];
59226
+ if (mesh)
59227
+ mesh.visible = visible;
59228
+ },
59229
+ dispose: () => {
59230
+ var _a, _b;
59231
+ disposed = true;
59232
+ this.removePreRenderCallbackFunction(clockId);
59233
+ // plane
59234
+ this.scene.remove(tex.mesh);
59235
+ tex.mesh.geometry.dispose();
59236
+ const pm = tex.mesh.material;
59237
+ (_b = (_a = pm.uniforms.diffuse.value) === null || _a === void 0 ? void 0 : _a.dispose) === null || _b === void 0 ? void 0 : _b.call(_a);
59238
+ pm.dispose();
59239
+ // surfaces
59240
+ for (const s of seqs) {
59241
+ this.scene.remove(s.mesh);
59242
+ s.geometries.forEach((g) => g.dispose());
59243
+ s.mesh.material.dispose();
59244
+ }
59245
+ },
59246
+ };
59247
+ callback && callback(ctrl);
59248
+ });
59550
59249
  }
59551
59250
  getPlayRate() {
59552
59251
  return this.playRate;
@@ -59652,8 +59351,11 @@ class copperRenderer extends baseRenderer {
59652
59351
  this.delta = 0;
59653
59352
  this.interval = 1 / this.fps;
59654
59353
  this.preRenderCallbackFunctions = [];
59354
+ this.running = true;
59655
59355
  this.animate = (time) => {
59656
59356
  var _a, _b, _c;
59357
+ if (!this.running)
59358
+ return;
59657
59359
  switch ((_a = this.options) === null || _a === void 0 ? void 0 : _a.fpsMode) {
59658
59360
  case "1":
59659
59361
  // fpsControl one: 30fps
@@ -59680,6 +59382,10 @@ class copperRenderer extends baseRenderer {
59680
59382
  }
59681
59383
  };
59682
59384
  }
59385
+ // Stop the render loop (call on teardown to release the RAF and let GC reclaim).
59386
+ stop() {
59387
+ this.running = false;
59388
+ }
59683
59389
  getSceneByName(name) {
59684
59390
  return this.sceneMap[name];
59685
59391
  }
@@ -78410,6 +78116,12 @@ class NrrdTools {
78410
78116
  updateWindowLow: this.guiParameterSettings.windowLow.onChange,
78411
78117
  finishContrastAdjustment: this.guiParameterSettings.windowHigh.onFinished,
78412
78118
  };
78119
+ // setupGUI may run AFTER images are loaded (e.g. the register/origin-switch load
78120
+ // path), in which case the on-load syncGuiParameterSettings() ran while
78121
+ // guiParameterSettings was still undefined and silently skipped the binding. Bind
78122
+ // now so the window slider reflects the current volume's real min/max/window instead
78123
+ // of the default 0/0 (which froze the slider and turned the image white on zoom).
78124
+ this.syncGuiParameterSettings();
78413
78125
  }
78414
78126
  /**
78415
78127
  * Sync guiParameterSettings with current volume metadata.
@@ -79442,7 +79154,7 @@ function evaluateElement(element, weights) {
79442
79154
  }
79443
79155
 
79444
79156
  // import * as kiwrious from "copper3d_plugin_heart_k";
79445
- const REVISION = "v3.4.7-beta";
79157
+ const REVISION = "v3.4.9-beta";
79446
79158
  console.log(`%cCopper3D Visualisation %cBeta:${REVISION}`, "padding: 3px;color:white; background:#023047", "padding: 3px;color:white; background:#f50a25");
79447
79159
 
79448
79160
  export { CHANNEL_COLORS, CHANNEL_HEX_COLORS, CameraViewPoint, Copper3dTrackballControls, GaussianSmoother, MeshNodeTool, NrrdTools, REVISION, addBoxHelper, addLabelToScene, configKiwriousHeart, convert3DPostoScreenPos, convertScreenPosto3DPos, copperMScene, copperMSceneRenderer, copperRenderer, copperRendererOnDemond, copperScene, copperSceneOnDemond, createTexture2D_NRRD, fullScreenListenner, kiwrious, loading, removeGuiFolderChilden, rgbaToCss, rgbaToHex, setHDRFilePath, throttle };