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