@ubercode/dcmtk 1.0.0-rc.1 → 1.0.0-rc.2

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.
package/dist/index.cjs CHANGED
@@ -10,6 +10,8 @@ var stderrLib = require('stderr-lib');
10
10
  var promises$1 = require('fs/promises');
11
11
  var os = require('os');
12
12
  var fastXmlParser = require('fast-xml-parser');
13
+ var zlib = require('zlib');
14
+ var dicomParser = require('dicom-parser');
13
15
  var net = require('net');
14
16
  var promises = require('timers/promises');
15
17
  var crypto = require('crypto');
@@ -37,6 +39,7 @@ function _interopNamespace(e) {
37
39
  var path__namespace = /*#__PURE__*/_interopNamespace(path);
38
40
  var kill__default = /*#__PURE__*/_interopDefault(kill);
39
41
  var os__namespace = /*#__PURE__*/_interopNamespace(os);
42
+ var dicomParser__default = /*#__PURE__*/_interopDefault(dicomParser);
40
43
  var net__namespace = /*#__PURE__*/_interopNamespace(net);
41
44
 
42
45
  var __defProp = Object.defineProperty;
@@ -323,7 +326,7 @@ async function execCommand(binary, args, options) {
323
326
  }
324
327
  function wireSpawnListeners(binary, child, timeoutMs, resolve) {
325
328
  let stdout = "";
326
- let stderr8 = "";
329
+ let stderr9 = "";
327
330
  let settled = false;
328
331
  const name = binary.split("/").pop() ?? binary;
329
332
  const settle = (result) => {
@@ -338,14 +341,14 @@ function wireSpawnListeners(binary, child, timeoutMs, resolve) {
338
341
  }, timeoutMs);
339
342
  child.stdout?.on("data", (chunk) => {
340
343
  stdout += String(chunk);
341
- if (stdout.length + stderr8.length > MAX_OUTPUT_BYTES) {
344
+ if (stdout.length + stderr9.length > MAX_OUTPUT_BYTES) {
342
345
  if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
343
346
  settle(err(new Error(`${name} output exceeded ${MAX_OUTPUT_BYTES} bytes`)));
344
347
  }
345
348
  });
346
349
  child.stderr?.on("data", (chunk) => {
347
- stderr8 += String(chunk);
348
- if (stdout.length + stderr8.length > MAX_OUTPUT_BYTES) {
350
+ stderr9 += String(chunk);
351
+ if (stdout.length + stderr9.length > MAX_OUTPUT_BYTES) {
349
352
  if (child.pid !== void 0 && child.pid !== null) killTree(child.pid);
350
353
  settle(err(new Error(`${name} output exceeded ${MAX_OUTPUT_BYTES} bytes`)));
351
354
  }
@@ -355,7 +358,7 @@ function wireSpawnListeners(binary, child, timeoutMs, resolve) {
355
358
  settle(err(new Error(`${name} error: ${error.message}`)));
356
359
  });
357
360
  child.on("close", (code) => {
358
- settle(ok({ stdout, stderr: stderr8, exitCode: code ?? 1 }));
361
+ settle(ok({ stdout, stderr: stderr9, exitCode: code ?? 1 }));
359
362
  });
360
363
  }
361
364
  async function spawnCommand(binary, args, options) {
@@ -734,9 +737,9 @@ var ToolExecutionError = class extends Error {
734
737
  this.exitCode = details.exitCode;
735
738
  }
736
739
  };
737
- function createToolError(toolName, args, exitCode, stderr8, stdout = "") {
740
+ function createToolError(toolName, args, exitCode, stderr9, stdout = "") {
738
741
  const argsStr = truncate(args.join(" "), MAX_ARGS_LENGTH);
739
- const stderrStr = truncate(stderr8.trim(), MAX_STDERR_LENGTH);
742
+ const stderrStr = truncate(stderr9.trim(), MAX_STDERR_LENGTH);
740
743
  const parts = [`${toolName} failed (exit code ${String(exitCode)})`];
741
744
  if (argsStr.length > 0) {
742
745
  parts.push(`args: ${argsStr}`);
@@ -744,7 +747,7 @@ function createToolError(toolName, args, exitCode, stderr8, stdout = "") {
744
747
  if (stderrStr.length > 0) {
745
748
  parts.push(`stderr: ${stderrStr}`);
746
749
  }
747
- return new ToolExecutionError(parts.join(" | "), { stdout, stderr: stderr8, exitCode });
750
+ return new ToolExecutionError(parts.join(" | "), { stdout, stderr: stderr9, exitCode });
748
751
  }
749
752
  function createValidationError(toolName, zodError) {
750
753
  const parts = [];
@@ -31364,8 +31367,8 @@ function unwrapValue(v) {
31364
31367
  const obj = v;
31365
31368
  if ("#text" in obj) return obj["#text"];
31366
31369
  const keys = Object.keys(obj);
31367
- if (keys.length === 1 && keys[0] !== void 0 && keys[0].startsWith("@_")) {
31368
- return obj[keys[0]];
31370
+ if (keys.every((k) => k.startsWith("@_"))) {
31371
+ return "";
31369
31372
  }
31370
31373
  return v;
31371
31374
  }
@@ -31571,16 +31574,553 @@ async function dcm2json(inputPath, options) {
31571
31574
  const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
31572
31575
  const signal = options?.signal;
31573
31576
  const startTime = Date.now();
31574
- const remaining = () => Math.max(1e3, timeoutMs - (Date.now() - startTime));
31577
+ const remaining = () => Math.max(0, timeoutMs - (Date.now() - startTime));
31575
31578
  const verbosity = options?.verbosity;
31576
31579
  if (options?.directOnly === true) {
31577
- return tryDirectPath(inputPath, remaining(), signal, verbosity);
31580
+ return tryDirectPath(inputPath, timeoutMs, signal, verbosity);
31578
31581
  }
31579
- const xmlResult = await tryXmlPath(inputPath, remaining(), signal, buildXmlOpts(options));
31582
+ const xmlResult = await tryXmlPath(inputPath, timeoutMs, signal, buildXmlOpts(options));
31580
31583
  if (xmlResult.ok) {
31581
31584
  return xmlResult;
31582
31585
  }
31583
- return tryDirectPath(inputPath, remaining(), signal, verbosity);
31586
+ return fallbackToDirect(inputPath, xmlResult.error, { budget: remaining(), timeoutMs, signal, verbosity });
31587
+ }
31588
+ async function fallbackToDirect(inputPath, xmlError, context) {
31589
+ if (context.budget === 0) {
31590
+ return err(
31591
+ new Error(
31592
+ `dcm2json: XML path failed and timeout budget (${String(context.timeoutMs)}ms) is exhausted (skipping direct fallback) | xml: ${xmlError.message}`
31593
+ )
31594
+ );
31595
+ }
31596
+ const directResult = await tryDirectPath(inputPath, context.budget, context.signal, context.verbosity);
31597
+ if (directResult.ok) {
31598
+ return directResult;
31599
+ }
31600
+ return err(new Error(`dcm2json: both paths failed | xml: ${xmlError.message} | direct: ${directResult.error.message}`));
31601
+ }
31602
+
31603
+ // src/tools/_charset.ts
31604
+ var LATIN1 = { kind: "latin1" };
31605
+ function label(name) {
31606
+ return { kind: "label", label: name };
31607
+ }
31608
+ var CHARSET_DECODERS = {
31609
+ "ISO_IR 6": LATIN1,
31610
+ "ISO_IR 100": LATIN1,
31611
+ "ISO_IR 101": label("iso-8859-2"),
31612
+ "ISO_IR 109": label("iso-8859-3"),
31613
+ "ISO_IR 110": label("iso-8859-4"),
31614
+ "ISO_IR 144": label("iso-8859-5"),
31615
+ "ISO_IR 127": label("iso-8859-6"),
31616
+ "ISO_IR 126": label("iso-8859-7"),
31617
+ "ISO_IR 138": label("iso-8859-8"),
31618
+ "ISO_IR 148": label("iso-8859-9"),
31619
+ "ISO_IR 203": label("iso-8859-15"),
31620
+ "ISO_IR 13": label("shift_jis"),
31621
+ "ISO_IR 166": label("windows-874"),
31622
+ "ISO_IR 192": label("utf-8"),
31623
+ GB18030: label("gb18030"),
31624
+ GBK: label("gbk")
31625
+ };
31626
+ var ISO2022_INITIAL = {
31627
+ "ISO 2022 IR 6": LATIN1,
31628
+ "ISO 2022 IR 13": label("shift_jis"),
31629
+ "ISO 2022 IR 87": LATIN1,
31630
+ "ISO 2022 IR 159": LATIN1,
31631
+ "ISO 2022 IR 149": label("euc-kr"),
31632
+ "ISO 2022 IR 58": label("gbk"),
31633
+ "ISO 2022 IR 100": LATIN1,
31634
+ "ISO 2022 IR 101": label("iso-8859-2"),
31635
+ "ISO 2022 IR 109": label("iso-8859-3"),
31636
+ "ISO 2022 IR 110": label("iso-8859-4"),
31637
+ "ISO 2022 IR 144": label("iso-8859-5"),
31638
+ "ISO 2022 IR 127": label("iso-8859-6"),
31639
+ "ISO 2022 IR 126": label("iso-8859-7"),
31640
+ "ISO 2022 IR 138": label("iso-8859-8"),
31641
+ "ISO 2022 IR 148": label("iso-8859-9"),
31642
+ "ISO 2022 IR 166": label("windows-874"),
31643
+ "ISO 2022 IR 203": label("iso-8859-15")
31644
+ };
31645
+ var ESCAPE_DECODERS = {
31646
+ "(B": LATIN1,
31647
+ // G0 = ASCII
31648
+ "(J": LATIN1,
31649
+ // G0 = JIS X 0201 romaji
31650
+ ")I": { kind: "katakana" },
31651
+ // G1 = JIS X 0201 katakana
31652
+ "$@": { kind: "iso2022jp", escape: [27, 36, 64] },
31653
+ // G0 = JIS X 0208-1978
31654
+ $B: { kind: "iso2022jp", escape: [27, 36, 66] },
31655
+ // G0 = JIS X 0208
31656
+ "$(D": { kind: "iso2022jp", escape: [27, 36, 40, 68] },
31657
+ // G0 = JIS X 0212
31658
+ "$)C": label("euc-kr"),
31659
+ // G1 = KS X 1001
31660
+ "$)A": label("gbk"),
31661
+ // G1 = GB 2312
31662
+ "-A": LATIN1,
31663
+ // G1 = ISO-8859-1
31664
+ "-B": label("iso-8859-2"),
31665
+ "-C": label("iso-8859-3"),
31666
+ "-D": label("iso-8859-4"),
31667
+ "-F": label("iso-8859-7"),
31668
+ "-G": label("iso-8859-6"),
31669
+ "-H": label("iso-8859-8"),
31670
+ "-L": label("iso-8859-5"),
31671
+ "-M": label("iso-8859-9"),
31672
+ "-T": label("windows-874"),
31673
+ "-b": label("iso-8859-15")
31674
+ };
31675
+ var CHARSET_ALIASES = {
31676
+ ascii: "ISO_IR 6",
31677
+ "latin-1": "ISO_IR 100",
31678
+ latin1: "ISO_IR 100",
31679
+ "iso-8859-1": "ISO_IR 100",
31680
+ "latin-2": "ISO_IR 101",
31681
+ "latin-3": "ISO_IR 109",
31682
+ "latin-4": "ISO_IR 110",
31683
+ "latin-5": "ISO_IR 148",
31684
+ "latin-9": "ISO_IR 203",
31685
+ cyrillic: "ISO_IR 144",
31686
+ arabic: "ISO_IR 127",
31687
+ greek: "ISO_IR 126",
31688
+ hebrew: "ISO_IR 138",
31689
+ thai: "ISO_IR 166",
31690
+ "shift-jis": "ISO_IR 13",
31691
+ shift_jis: "ISO_IR 13",
31692
+ "utf-8": "ISO_IR 192",
31693
+ utf8: "ISO_IR 192",
31694
+ gb18030: "GB18030",
31695
+ gbk: "GBK"
31696
+ };
31697
+ var decoderCache = /* @__PURE__ */ new Map();
31698
+ function getTextDecoder(labelName) {
31699
+ if (decoderCache.has(labelName)) {
31700
+ return decoderCache.get(labelName);
31701
+ }
31702
+ let decoder;
31703
+ try {
31704
+ decoder = new TextDecoder(labelName);
31705
+ } catch {
31706
+ decoder = void 0;
31707
+ }
31708
+ decoderCache.set(labelName, decoder);
31709
+ return decoder;
31710
+ }
31711
+ function decodeLatin1(bytes) {
31712
+ return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("latin1");
31713
+ }
31714
+ function decodeSegment(bytes, decoder) {
31715
+ if (bytes.length === 0) return "";
31716
+ switch (decoder.kind) {
31717
+ case "latin1":
31718
+ return decodeLatin1(bytes);
31719
+ case "label": {
31720
+ const td = getTextDecoder(decoder.label);
31721
+ if (td === void 0) return decodeLatin1(bytes);
31722
+ return td.decode(bytes);
31723
+ }
31724
+ case "iso2022jp": {
31725
+ const td = getTextDecoder("iso-2022-jp");
31726
+ if (td === void 0) return decodeLatin1(bytes);
31727
+ const wrapped = new Uint8Array(decoder.escape.length + bytes.length);
31728
+ wrapped.set(decoder.escape, 0);
31729
+ wrapped.set(bytes, decoder.escape.length);
31730
+ return td.decode(wrapped);
31731
+ }
31732
+ case "katakana": {
31733
+ const td = getTextDecoder("shift_jis");
31734
+ if (td === void 0) return decodeLatin1(bytes);
31735
+ return td.decode(bytes);
31736
+ }
31737
+ }
31738
+ }
31739
+ function readEscape(bytes, start) {
31740
+ let i = start + 1;
31741
+ while (i < bytes.length && bytes[i] >= 32 && bytes[i] <= 47) {
31742
+ i++;
31743
+ }
31744
+ if (i < bytes.length && bytes[i] >= 48 && bytes[i] <= 126) {
31745
+ i++;
31746
+ }
31747
+ const seq = bytes.subarray(start + 1, i);
31748
+ return { key: decodeLatin1(seq), length: i - start };
31749
+ }
31750
+ function decodeIso2022(bytes, initial) {
31751
+ let out = "";
31752
+ let current = initial;
31753
+ let segStart = 0;
31754
+ let i = 0;
31755
+ while (i < bytes.length) {
31756
+ if (bytes[i] !== 27) {
31757
+ i++;
31758
+ continue;
31759
+ }
31760
+ out += decodeSegment(bytes.subarray(segStart, i), current);
31761
+ const esc = readEscape(bytes, i);
31762
+ current = ESCAPE_DECODERS[esc.key] ?? current;
31763
+ i += esc.length;
31764
+ segStart = i;
31765
+ }
31766
+ out += decodeSegment(bytes.subarray(segStart), current);
31767
+ return out;
31768
+ }
31769
+ var DEFAULT_CONTEXT = { terms: ["ISO_IR 6"], iso2022: false, initial: LATIN1 };
31770
+ function normalizeCharsetName(input) {
31771
+ const trimmed = input.trim();
31772
+ if (CHARSET_DECODERS[trimmed] !== void 0 || ISO2022_INITIAL[trimmed] !== void 0) {
31773
+ return trimmed;
31774
+ }
31775
+ return CHARSET_ALIASES[trimmed.toLowerCase()];
31776
+ }
31777
+ function buildContext(terms) {
31778
+ const iso2022 = terms.length > 1 || terms.some((t) => t.startsWith("ISO 2022"));
31779
+ const first = terms[0] ?? "ISO_IR 6";
31780
+ const initial = iso2022 ? ISO2022_INITIAL[first] : CHARSET_DECODERS[first];
31781
+ if (initial === void 0) return void 0;
31782
+ return { terms, iso2022, initial };
31783
+ }
31784
+ function parseSpecificCharacterSet(raw) {
31785
+ const values = raw.split("\\").map((v) => v.trim());
31786
+ return values.map((v, idx) => v === "" && idx === 0 ? "ISO 2022 IR 6" : v).filter((v) => v !== "");
31787
+ }
31788
+ function contextFromTerms(terms, fallback, source) {
31789
+ const context = buildContext(terms);
31790
+ if (context !== void 0) {
31791
+ return ok(context);
31792
+ }
31793
+ if (fallback !== void 0) {
31794
+ const fallbackTerm = normalizeCharsetName(fallback);
31795
+ const fallbackContext = fallbackTerm !== void 0 ? buildContext([fallbackTerm]) : void 0;
31796
+ if (fallbackContext !== void 0) {
31797
+ return ok(fallbackContext);
31798
+ }
31799
+ return err(new Error(`Unsupported charset fallback '${fallback}' (while handling unsupported ${source})`));
31800
+ }
31801
+ return err(new Error(`Unsupported character set: ${source} \u2014 provide charsetFallback (e.g. 'latin-1') to decode best-effort`));
31802
+ }
31803
+ function resolveCharsetContext(specificCharacterSet, charsetAssume, charsetFallback) {
31804
+ if (specificCharacterSet === void 0 || specificCharacterSet.trim() === "") {
31805
+ if (charsetAssume === void 0) {
31806
+ return ok(DEFAULT_CONTEXT);
31807
+ }
31808
+ const term = normalizeCharsetName(charsetAssume);
31809
+ if (term === void 0) {
31810
+ return contextFromTerms([charsetAssume], charsetFallback, `charsetAssume '${charsetAssume}'`);
31811
+ }
31812
+ return contextFromTerms([term], charsetFallback, `charsetAssume '${charsetAssume}'`);
31813
+ }
31814
+ const terms = parseSpecificCharacterSet(specificCharacterSet);
31815
+ return contextFromTerms(terms, charsetFallback, `'${specificCharacterSet}' (0008,0005)`);
31816
+ }
31817
+ function decodeDicomText(bytes, context) {
31818
+ if (context.iso2022) {
31819
+ return decodeIso2022(bytes, context.initial);
31820
+ }
31821
+ return decodeSegment(bytes, context.initial);
31822
+ }
31823
+
31824
+ // src/tools/_p10ToJson.ts
31825
+ var BINARY_VRS = /* @__PURE__ */ new Set(["OB", "OW", "OD", "OF", "OL", "OV", "UN"]);
31826
+ var CHARSET_VRS = /* @__PURE__ */ new Set(["SH", "LO", "ST", "LT", "UC", "UT", "PN"]);
31827
+ var NO_SPLIT_VRS = /* @__PURE__ */ new Set(["ST", "LT", "UT", "UR"]);
31828
+ var NUMERIC_VR_SIZE = { US: 2, SS: 2, UL: 4, SL: 4, FL: 4, FD: 8, SV: 8, UV: 8 };
31829
+ var MAX_DATASETS = 1e5;
31830
+ function toJsonTag(tag2) {
31831
+ return tag2.slice(1).toUpperCase();
31832
+ }
31833
+ function isEncodingArtifact(tag2) {
31834
+ return tag2.endsWith("0000") && tag2.length === 9 || tag2.startsWith("xfffe");
31835
+ }
31836
+ function resolveVr(element) {
31837
+ const vr = element.vr;
31838
+ if (vr !== void 0 && KNOWN_VR_CODES.has(vr)) {
31839
+ return vr;
31840
+ }
31841
+ return "UN";
31842
+ }
31843
+ function valueBytes(element, byteArray) {
31844
+ return byteArray.subarray(element.dataOffset, element.dataOffset + element.length);
31845
+ }
31846
+ function trimPadding(value) {
31847
+ let end = value.length;
31848
+ while (end > 0) {
31849
+ const ch = value.charCodeAt(end - 1);
31850
+ if (ch !== 32 && ch !== 0) break;
31851
+ end--;
31852
+ }
31853
+ return value.slice(0, end);
31854
+ }
31855
+ function readNumericAt(view, offset, vr, littleEndian) {
31856
+ switch (vr) {
31857
+ case "US":
31858
+ return view.getUint16(offset, littleEndian);
31859
+ case "SS":
31860
+ return view.getInt16(offset, littleEndian);
31861
+ case "UL":
31862
+ return view.getUint32(offset, littleEndian);
31863
+ case "SL":
31864
+ return view.getInt32(offset, littleEndian);
31865
+ case "FL":
31866
+ return view.getFloat32(offset, littleEndian);
31867
+ case "FD":
31868
+ return view.getFloat64(offset, littleEndian);
31869
+ case "SV":
31870
+ return Number(view.getBigInt64(offset, littleEndian));
31871
+ /* v8 ignore next 2 -- 'UV' is the only remaining caller-provided VR */
31872
+ default:
31873
+ return Number(view.getBigUint64(offset, littleEndian));
31874
+ }
31875
+ }
31876
+ function convertNumeric(element, byteArray, vr, littleEndian) {
31877
+ const size = NUMERIC_VR_SIZE[vr];
31878
+ const count = Math.floor(element.length / size);
31879
+ if (count === 0) {
31880
+ return { vr };
31881
+ }
31882
+ const view = new DataView(byteArray.buffer, byteArray.byteOffset + element.dataOffset, element.length);
31883
+ const values = [];
31884
+ for (let i = 0; i < count; i++) {
31885
+ values.push(readNumericAt(view, i * size, vr, littleEndian));
31886
+ }
31887
+ return { vr, Value: values };
31888
+ }
31889
+ function convertAttributeTag(element, byteArray, littleEndian) {
31890
+ const count = Math.floor(element.length / 4);
31891
+ if (count === 0) {
31892
+ return { vr: "AT" };
31893
+ }
31894
+ const view = new DataView(byteArray.buffer, byteArray.byteOffset + element.dataOffset, element.length);
31895
+ const values = [];
31896
+ for (let i = 0; i < count; i++) {
31897
+ const group = view.getUint16(i * 4, littleEndian);
31898
+ const elem = view.getUint16(i * 4 + 2, littleEndian);
31899
+ values.push(group.toString(16).padStart(4, "0").toUpperCase() + elem.toString(16).padStart(4, "0").toUpperCase());
31900
+ }
31901
+ return { vr: "AT", Value: values };
31902
+ }
31903
+ function personNameToJson(value) {
31904
+ const groups = trimPadding(value).split("=");
31905
+ const result = {};
31906
+ const names = ["Alphabetic", "Ideographic", "Phonetic"];
31907
+ for (let i = 0; i < names.length; i++) {
31908
+ const group = (groups[i] ?? "").replace(/\^+$/u, "");
31909
+ const name = names[i];
31910
+ if (group !== "" && name !== void 0) {
31911
+ result[name] = group;
31912
+ }
31913
+ }
31914
+ return result;
31915
+ }
31916
+ function convertPersonName2(element, byteArray, charset) {
31917
+ const decoded = decodeDicomText(valueBytes(element, byteArray), charset);
31918
+ const values = decoded.split("\\").map(personNameToJson);
31919
+ return { vr: "PN", Value: values };
31920
+ }
31921
+ function convertString(element, byteArray, vr, charset) {
31922
+ const bytes = valueBytes(element, byteArray);
31923
+ const decoded = CHARSET_VRS.has(vr) ? decodeDicomText(bytes, charset) : Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("latin1");
31924
+ const rawValues = NO_SPLIT_VRS.has(vr) ? [decoded] : decoded.split("\\");
31925
+ const values = rawValues.map((v) => {
31926
+ const trimmed = trimPadding(v);
31927
+ return vr === "DS" || vr === "IS" ? coerceNumeric(trimmed.trim()) : trimmed;
31928
+ });
31929
+ if (values.length === 1 && values[0] === "") {
31930
+ return { vr };
31931
+ }
31932
+ return { vr, Value: values };
31933
+ }
31934
+ function convertLeaf(element, byteArray, vr, settings) {
31935
+ if (element.fragments !== void 0) {
31936
+ return { vr: "OB" };
31937
+ }
31938
+ if (BINARY_VRS.has(vr)) {
31939
+ return { vr };
31940
+ }
31941
+ if (element.length === 0) {
31942
+ return { vr };
31943
+ }
31944
+ if (NUMERIC_VR_SIZE[vr] !== void 0) {
31945
+ return convertNumeric(element, byteArray, vr, settings.littleEndian);
31946
+ }
31947
+ if (vr === "AT") {
31948
+ return convertAttributeTag(element, byteArray, settings.littleEndian);
31949
+ }
31950
+ if (vr === "PN") {
31951
+ return convertPersonName2(element, byteArray, settings.charset);
31952
+ }
31953
+ return convertString(element, byteArray, vr, settings.charset);
31954
+ }
31955
+ function datasetCharset(src, parent, options) {
31956
+ const raw = src.string("x00080005");
31957
+ if (raw === void 0) {
31958
+ return ok(parent);
31959
+ }
31960
+ return resolveCharsetContext(raw, void 0, options?.charsetFallback);
31961
+ }
31962
+ function convertSequence2(element, stack, charset) {
31963
+ const items = element.items ?? [];
31964
+ const values = [];
31965
+ for (const item of items) {
31966
+ const model = {};
31967
+ values.push(model);
31968
+ if (item.dataSet !== void 0) {
31969
+ stack.push({ src: item.dataSet, out: model, parentCharset: charset });
31970
+ }
31971
+ }
31972
+ if (values.length === 0) {
31973
+ return { vr: "SQ" };
31974
+ }
31975
+ return { vr: "SQ", Value: values };
31976
+ }
31977
+ function isSequence(element, vr) {
31978
+ if (element.fragments !== void 0) {
31979
+ return false;
31980
+ }
31981
+ return vr === "SQ" || element.vr === void 0 && element.items !== void 0;
31982
+ }
31983
+ function convertDataset(unit, stack, littleEndian, options) {
31984
+ const charsetResult = datasetCharset(unit.src, unit.parentCharset, options);
31985
+ if (!charsetResult.ok) {
31986
+ return err(charsetResult.error);
31987
+ }
31988
+ const charset = charsetResult.value;
31989
+ const keys = Object.keys(unit.src.elements).sort();
31990
+ for (const key of keys) {
31991
+ const element = unit.src.elements[key];
31992
+ if (element === void 0) continue;
31993
+ if (isEncodingArtifact(element.tag)) continue;
31994
+ const tag2 = toJsonTag(element.tag);
31995
+ const vr = resolveVr(element);
31996
+ if (isSequence(element, vr)) {
31997
+ unit.out[tag2] = convertSequence2(element, stack, charset);
31998
+ } else {
31999
+ unit.out[tag2] = convertLeaf(element, unit.src.byteArray, vr, { charset, littleEndian });
32000
+ }
32001
+ }
32002
+ return ok(void 0);
32003
+ }
32004
+ function inflateDeflated(byteArray, position) {
32005
+ const inflated = zlib.inflateRawSync(byteArray.subarray(position));
32006
+ const combined = new Uint8Array(position + inflated.byteLength);
32007
+ combined.set(byteArray.subarray(0, position), 0);
32008
+ combined.set(inflated, position);
32009
+ return combined;
32010
+ }
32011
+ function implicitVrLookup(tag2) {
32012
+ return lookupTag(tag2.slice(1))?.vr;
32013
+ }
32014
+ function parseDicomBuffer(buffer, options) {
32015
+ let dataSet;
32016
+ try {
32017
+ dataSet = dicomParser__default.default.parseDicom(buffer, { vrCallback: implicitVrLookup, inflater: inflateDeflated });
32018
+ } catch (error) {
32019
+ return err(new Error(`Failed to parse DICOM data: ${stderrLib.stderr(error).message}`));
32020
+ }
32021
+ const littleEndian = dataSet.string("x00020010") !== "1.2.840.10008.1.2.2";
32022
+ const rootCharset = resolveCharsetContext(void 0, options?.charsetAssume, options?.charsetFallback);
32023
+ if (!rootCharset.ok) {
32024
+ return err(rootCharset.error);
32025
+ }
32026
+ const data = {};
32027
+ const stack = [{ src: dataSet, out: data, parentCharset: rootCharset.value }];
32028
+ let processed = 0;
32029
+ while (stack.length > 0) {
32030
+ processed++;
32031
+ if (processed > MAX_DATASETS) {
32032
+ return err(new Error(`Failed to parse DICOM data: more than ${String(MAX_DATASETS)} nested datasets`));
32033
+ }
32034
+ const unit = stack.pop();
32035
+ if (unit === void 0) break;
32036
+ const converted = convertDataset(unit, stack, littleEndian, options);
32037
+ if (!converted.ok) {
32038
+ return err(converted.error);
32039
+ }
32040
+ }
32041
+ return ok({ data, warnings: dataSet.warnings });
32042
+ }
32043
+
32044
+ // src/tools/dicom2json.ts
32045
+ var Dicom2jsonOptionsSchema = zod.z.object({
32046
+ timeoutMs: zod.z.number().int().positive().optional(),
32047
+ signal: zod.z.instanceof(AbortSignal).optional(),
32048
+ charsetAssume: zod.z.string().min(1).optional(),
32049
+ charsetFallback: zod.z.string().min(1).optional(),
32050
+ dcmtkFallback: zod.z.boolean().optional()
32051
+ }).strict().optional();
32052
+ var Dicom2jsonBufferOptionsSchema = zod.z.object({
32053
+ charsetAssume: zod.z.string().min(1).optional(),
32054
+ charsetFallback: zod.z.string().min(1).optional()
32055
+ }).strict().optional();
32056
+ async function readFileBounded(inputPath, timeoutMs, signal) {
32057
+ const timeoutSignal = AbortSignal.timeout(timeoutMs);
32058
+ const combined = signal !== void 0 ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
32059
+ try {
32060
+ return ok(await promises$1.readFile(inputPath, { signal: combined }));
32061
+ } catch (error) {
32062
+ if (timeoutSignal.aborted) {
32063
+ return err(new Error(`dicom2json: reading '${inputPath}' timed out after ${String(timeoutMs)}ms`));
32064
+ }
32065
+ if (signal?.aborted === true) {
32066
+ return err(new Error(`dicom2json: aborted while reading '${inputPath}'`));
32067
+ }
32068
+ const message = error instanceof Error ? error.message : String(error);
32069
+ return err(new Error(`dicom2json: failed to read '${inputPath}': ${message}`));
32070
+ }
32071
+ }
32072
+ async function runDcmtkFallback(inputPath, jsError, remainingMs, options) {
32073
+ if (remainingMs <= 0) {
32074
+ return err(new Error(`dicom2json: JS parse failed and timeout budget exhausted (skipping DCMTK fallback) | js: ${jsError.message}`));
32075
+ }
32076
+ const fallback = await dcm2json(inputPath, {
32077
+ timeoutMs: remainingMs,
32078
+ signal: options?.signal,
32079
+ charsetAssume: options?.charsetAssume,
32080
+ charsetFallback: options?.charsetFallback
32081
+ });
32082
+ if (!fallback.ok) {
32083
+ return err(new Error(`dicom2json: both engines failed | js: ${jsError.message} | dcmtk: ${fallback.error.message}`));
32084
+ }
32085
+ return ok({ data: fallback.value.data, warnings: [], source: fallback.value.source });
32086
+ }
32087
+ async function dicom2json(inputPath, options) {
32088
+ const validation = Dicom2jsonOptionsSchema.safeParse(options);
32089
+ if (!validation.success) {
32090
+ return err(createValidationError("dicom2json", validation.error));
32091
+ }
32092
+ const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
32093
+ const startTime = Date.now();
32094
+ const parsed = await parseFromFile(inputPath, timeoutMs, options);
32095
+ if (parsed.ok) {
32096
+ return parsed;
32097
+ }
32098
+ if (options?.dcmtkFallback === true) {
32099
+ return runDcmtkFallback(inputPath, parsed.error, timeoutMs - (Date.now() - startTime), options);
32100
+ }
32101
+ return err(parsed.error);
32102
+ }
32103
+ async function parseFromFile(inputPath, timeoutMs, options) {
32104
+ const fileResult = await readFileBounded(inputPath, timeoutMs, options?.signal);
32105
+ if (!fileResult.ok) {
32106
+ return err(fileResult.error);
32107
+ }
32108
+ const parsed = parseDicomBuffer(fileResult.value, { charsetAssume: options?.charsetAssume, charsetFallback: options?.charsetFallback });
32109
+ if (!parsed.ok) {
32110
+ return err(parsed.error);
32111
+ }
32112
+ return ok({ data: parsed.value.data, warnings: parsed.value.warnings, source: "js" });
32113
+ }
32114
+ function dicom2jsonFromBuffer(buffer, options) {
32115
+ const validation = Dicom2jsonBufferOptionsSchema.safeParse(options);
32116
+ if (!validation.success) {
32117
+ return err(createValidationError("dicom2jsonFromBuffer", validation.error));
32118
+ }
32119
+ const parsed = parseDicomBuffer(buffer, options);
32120
+ if (!parsed.ok) {
32121
+ return err(parsed.error);
32122
+ }
32123
+ return ok({ data: parsed.value.data, warnings: parsed.value.warnings, source: "js" });
31584
32124
  }
31585
32125
  var DcmdumpFormat = {
31586
32126
  /** Print standard DCMTK format. */
@@ -34393,6 +34933,17 @@ async function unlinkFile(path2) {
34393
34933
  }
34394
34934
 
34395
34935
  // src/dicom/DicomInstance.ts
34936
+ async function readDicomJson(path2, options) {
34937
+ const shared = {
34938
+ timeoutMs: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
34939
+ signal: options?.signal,
34940
+ charsetAssume: options?.charsetAssume,
34941
+ charsetFallback: options?.charsetFallback
34942
+ };
34943
+ const result = options?.engine === "dcmtk" ? await dcm2json(path2, shared) : await dicom2json(path2, { ...shared, dcmtkFallback: true });
34944
+ if (!result.ok) return err(result.error);
34945
+ return ok(result.value.data);
34946
+ }
34396
34947
  var DicomInstance = class _DicomInstance {
34397
34948
  constructor(dataset, changes, filePath, metadata) {
34398
34949
  __publicField(this, "dicomDataset");
@@ -34417,14 +34968,9 @@ var DicomInstance = class _DicomInstance {
34417
34968
  static async open(path2, options) {
34418
34969
  const filePathResult = createDicomFilePath(path2);
34419
34970
  if (!filePathResult.ok) return err(filePathResult.error);
34420
- const jsonResult = await dcm2json(path2, {
34421
- timeoutMs: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
34422
- signal: options?.signal,
34423
- charsetAssume: options?.charsetAssume,
34424
- charsetFallback: options?.charsetFallback
34425
- });
34971
+ const jsonResult = await readDicomJson(path2, options);
34426
34972
  if (!jsonResult.ok) return err(jsonResult.error);
34427
- const datasetResult = DicomDataset.fromJson(jsonResult.value.data);
34973
+ const datasetResult = DicomDataset.fromJson(jsonResult.value);
34428
34974
  if (!datasetResult.ok) return err(datasetResult.error);
34429
34975
  return ok(new _DicomInstance(datasetResult.value, ChangeSet.empty(), filePathResult.value, /* @__PURE__ */ new Map()));
34430
34976
  }
@@ -37952,8 +38498,8 @@ function createStorescuExecutor(options) {
37952
38498
  if (!result.ok) {
37953
38499
  const e = result.error;
37954
38500
  const stdout = e instanceof ToolExecutionError ? e.stdout : "";
37955
- const stderr8 = e instanceof ToolExecutionError ? e.stderr : "";
37956
- return { stdout, stderr: stderr8, error: e };
38501
+ const stderr9 = e instanceof ToolExecutionError ? e.stderr : "";
38502
+ return { stdout, stderr: stderr9, error: e };
37957
38503
  }
37958
38504
  return { stdout: result.value.stdout, stderr: result.value.stderr };
37959
38505
  };
@@ -38210,8 +38756,8 @@ function createDcmsendExecutor(options) {
38210
38756
  if (!result.ok) {
38211
38757
  const e = result.error;
38212
38758
  const stdout = e instanceof ToolExecutionError ? e.stdout : "";
38213
- const stderr8 = e instanceof ToolExecutionError ? e.stderr : "";
38214
- return { stdout, stderr: stderr8, error: e };
38759
+ const stderr9 = e instanceof ToolExecutionError ? e.stderr : "";
38760
+ return { stdout, stderr: stderr9, error: e };
38215
38761
  }
38216
38762
  return { stdout: result.value.stdout, stderr: result.value.stderr };
38217
38763
  };
@@ -38551,7 +39097,7 @@ async function cleanupTempDir(directory) {
38551
39097
  }
38552
39098
  }
38553
39099
  async function parseSingleFile(filePath, timeoutMs, signal) {
38554
- const jsonResult = await dcm2json(filePath, { timeoutMs, signal });
39100
+ const jsonResult = await dicom2json(filePath, { timeoutMs, signal, dcmtkFallback: true });
38555
39101
  if (!jsonResult.ok) {
38556
39102
  return err(jsonResult.error);
38557
39103
  }
@@ -39330,6 +39876,8 @@ exports.dcmscale = dcmscale;
39330
39876
  exports.dcmsend = dcmsend;
39331
39877
  exports.dcod2lum = dcod2lum;
39332
39878
  exports.dconvlum = dconvlum;
39879
+ exports.dicom2json = dicom2json;
39880
+ exports.dicom2jsonFromBuffer = dicom2jsonFromBuffer;
39333
39881
  exports.drtdump = drtdump;
39334
39882
  exports.dsr2xml = dsr2xml;
39335
39883
  exports.dsrdump = dsrdump;