@ubercode/dcmtk 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -462,6 +462,8 @@ interface FileIOOptions {
462
462
  interface DicomOpenOptions extends FileIOOptions {
463
463
  /** Assume the specified character set when SpecificCharacterSet (0008,0005) is absent. Maps to dcm2xml `+Ca`. */
464
464
  readonly charsetAssume?: string | undefined;
465
+ /** Fallback charset to retry with when UTF-8 conversion fails due to illegal byte sequences. Maps to dcm2xml `+Ca`. When set, a charset conversion failure triggers an automatic retry with this charset assumed. `'Latin1'` is recommended — it maps every byte 0x00-0xFF to a valid character, so conversion never fails. */
466
+ readonly charsetFallback?: string | undefined;
465
467
  }
466
468
 
467
469
  /**
@@ -462,6 +462,8 @@ interface FileIOOptions {
462
462
  interface DicomOpenOptions extends FileIOOptions {
463
463
  /** Assume the specified character set when SpecificCharacterSet (0008,0005) is absent. Maps to dcm2xml `+Ca`. */
464
464
  readonly charsetAssume?: string | undefined;
465
+ /** Fallback charset to retry with when UTF-8 conversion fails due to illegal byte sequences. Maps to dcm2xml `+Ca`. When set, a charset conversion failure triggers an automatic retry with this charset assumed. `'Latin1'` is recommended — it maps every byte 0x00-0xFF to a valid character, so conversion never fails. */
466
+ readonly charsetFallback?: string | undefined;
465
467
  }
466
468
 
467
469
  /**
package/dist/dicom.cjs CHANGED
@@ -30726,7 +30726,12 @@ var parser = new fastXmlParser.XMLParser({
30726
30726
  ignoreAttributes: false,
30727
30727
  attributeNamePrefix: "@_",
30728
30728
  parseTagValue: false,
30729
- isArray: (name) => ARRAY_TAG_NAMES.has(name)
30729
+ isArray: (name) => ARRAY_TAG_NAMES.has(name),
30730
+ // DICOM XML does not use XML entities. Disable entity processing to avoid
30731
+ // the default expansion limit (1000) which rejects files with >1000 tags.
30732
+ // Without this, large studies fall through to dcm2json which hangs on
30733
+ // compressed pixel data (DCMTK bug).
30734
+ processEntities: false
30730
30735
  });
30731
30736
  function xmlToJson(xml) {
30732
30737
  try {
@@ -30787,6 +30792,7 @@ var Dcm2jsonOptionsSchema = zod.z.object({
30787
30792
  signal: zod.z.instanceof(AbortSignal).optional(),
30788
30793
  directOnly: zod.z.boolean().optional(),
30789
30794
  charsetAssume: zod.z.string().min(1).optional(),
30795
+ charsetFallback: zod.z.string().min(1).optional(),
30790
30796
  verbosity: zod.z.enum(["verbose", "debug"]).optional()
30791
30797
  }).strict().optional();
30792
30798
  var VERBOSITY_FLAGS = { verbose: "-v", debug: "-d" };
@@ -30800,27 +30806,34 @@ function buildXmlOpts(options) {
30800
30806
  const result = {};
30801
30807
  if (options?.verbosity !== void 0) result["verbosity"] = options.verbosity;
30802
30808
  if (options?.charsetAssume !== void 0) result["charsetAssume"] = options.charsetAssume;
30809
+ if (options?.charsetFallback !== void 0) result["charsetFallback"] = options.charsetFallback;
30803
30810
  return result;
30804
30811
  }
30812
+ function isCharsetError(stderrOutput) {
30813
+ return stderrOutput.includes("convert character encoding") || stderrOutput.includes("Illegal byte sequence");
30814
+ }
30815
+ async function runXmlAndParse(binary, args, execOpts) {
30816
+ const xmlResult = await execCommand(binary, args, execOpts);
30817
+ if (!xmlResult.ok) return err(xmlResult.error);
30818
+ if (xmlResult.value.exitCode !== 0) {
30819
+ return err(createToolError("dcm2xml", args, xmlResult.value.exitCode, xmlResult.value.stderr));
30820
+ }
30821
+ const jsonResult = xmlToJson(xmlResult.value.stdout);
30822
+ if (!jsonResult.ok) return err(jsonResult.error);
30823
+ return ok({ data: jsonResult.value, source: "xml" });
30824
+ }
30805
30825
  async function tryXmlPath(inputPath, timeoutMs, signal, opts) {
30806
30826
  const xmlBinary = resolveBinary("dcm2xml");
30807
- if (!xmlBinary.ok) {
30808
- return err(xmlBinary.error);
30809
- }
30827
+ if (!xmlBinary.ok) return err(xmlBinary.error);
30810
30828
  const charsetArgs = opts?.charsetAssume !== void 0 ? ["+Ca", opts.charsetAssume] : [];
30811
30829
  const xmlArgs = [...buildVerbosityArgs(opts?.verbosity), ...charsetArgs, "-nat", inputPath];
30812
- const xmlResult = await execCommand(xmlBinary.value, xmlArgs, { timeoutMs, signal });
30813
- if (!xmlResult.ok) {
30814
- return err(xmlResult.error);
30830
+ const execOpts = signal !== void 0 ? { timeoutMs, signal } : { timeoutMs };
30831
+ const result = await runXmlAndParse(xmlBinary.value, xmlArgs, execOpts);
30832
+ if (!result.ok && opts?.charsetFallback !== void 0 && isCharsetError(result.error.message)) {
30833
+ const fallbackArgs = [...buildVerbosityArgs(opts.verbosity), "+Ca", opts.charsetFallback, "-nat", inputPath];
30834
+ return runXmlAndParse(xmlBinary.value, fallbackArgs, execOpts);
30815
30835
  }
30816
- if (xmlResult.value.exitCode !== 0) {
30817
- return err(createToolError("dcm2xml", xmlArgs, xmlResult.value.exitCode, xmlResult.value.stderr));
30818
- }
30819
- const jsonResult = xmlToJson(xmlResult.value.stdout);
30820
- if (!jsonResult.ok) {
30821
- return err(jsonResult.error);
30822
- }
30823
- return ok({ data: jsonResult.value, source: "xml" });
30836
+ return result;
30824
30837
  }
30825
30838
  async function tryDirectPath(inputPath, timeoutMs, signal, verbosity) {
30826
30839
  const jsonBinary = resolveBinary("dcm2json");
@@ -31005,7 +31018,8 @@ var DicomInstance = class _DicomInstance {
31005
31018
  const jsonResult = await dcm2json(path, {
31006
31019
  timeoutMs: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
31007
31020
  signal: options?.signal,
31008
- charsetAssume: options?.charsetAssume
31021
+ charsetAssume: options?.charsetAssume,
31022
+ charsetFallback: options?.charsetFallback
31009
31023
  });
31010
31024
  if (!jsonResult.ok) return err(jsonResult.error);
31011
31025
  const datasetResult = DicomDataset.fromJson(jsonResult.value.data);