@ubercode/dcmtk 0.12.1 → 0.13.1

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/servers.cjs CHANGED
@@ -2312,6 +2312,7 @@ var Dcm2jsonOptionsSchema = zod.z.object({
2312
2312
  signal: zod.z.instanceof(AbortSignal).optional(),
2313
2313
  directOnly: zod.z.boolean().optional(),
2314
2314
  charsetAssume: zod.z.string().min(1).optional(),
2315
+ charsetFallback: zod.z.string().min(1).optional(),
2315
2316
  verbosity: zod.z.enum(["verbose", "debug"]).optional()
2316
2317
  }).strict().optional();
2317
2318
  var VERBOSITY_FLAGS = { verbose: "-v", debug: "-d" };
@@ -2325,27 +2326,34 @@ function buildXmlOpts(options) {
2325
2326
  const result = {};
2326
2327
  if (options?.verbosity !== void 0) result["verbosity"] = options.verbosity;
2327
2328
  if (options?.charsetAssume !== void 0) result["charsetAssume"] = options.charsetAssume;
2329
+ if (options?.charsetFallback !== void 0) result["charsetFallback"] = options.charsetFallback;
2328
2330
  return result;
2329
2331
  }
2332
+ function isCharsetError(stderrOutput) {
2333
+ return stderrOutput.includes("convert character encoding") || stderrOutput.includes("Illegal byte sequence");
2334
+ }
2335
+ async function runXmlAndParse(binary, args, execOpts) {
2336
+ const xmlResult = await execCommand(binary, args, execOpts);
2337
+ if (!xmlResult.ok) return err(xmlResult.error);
2338
+ if (xmlResult.value.exitCode !== 0) {
2339
+ return err(createToolError("dcm2xml", args, xmlResult.value.exitCode, xmlResult.value.stderr));
2340
+ }
2341
+ const jsonResult = xmlToJson(xmlResult.value.stdout);
2342
+ if (!jsonResult.ok) return err(jsonResult.error);
2343
+ return ok({ data: jsonResult.value, source: "xml" });
2344
+ }
2330
2345
  async function tryXmlPath(inputPath, timeoutMs, signal, opts) {
2331
2346
  const xmlBinary = resolveBinary("dcm2xml");
2332
- if (!xmlBinary.ok) {
2333
- return err(xmlBinary.error);
2334
- }
2347
+ if (!xmlBinary.ok) return err(xmlBinary.error);
2335
2348
  const charsetArgs = opts?.charsetAssume !== void 0 ? ["+Ca", opts.charsetAssume] : [];
2336
2349
  const xmlArgs = [...buildVerbosityArgs(opts?.verbosity), ...charsetArgs, "-nat", inputPath];
2337
- const xmlResult = await execCommand(xmlBinary.value, xmlArgs, { timeoutMs, signal });
2338
- if (!xmlResult.ok) {
2339
- return err(xmlResult.error);
2350
+ const execOpts = signal !== void 0 ? { timeoutMs, signal } : { timeoutMs };
2351
+ const result = await runXmlAndParse(xmlBinary.value, xmlArgs, execOpts);
2352
+ if (!result.ok && opts?.charsetFallback !== void 0 && isCharsetError(result.error.message)) {
2353
+ const fallbackArgs = [...buildVerbosityArgs(opts.verbosity), "+Ca", opts.charsetFallback, "-nat", inputPath];
2354
+ return runXmlAndParse(xmlBinary.value, fallbackArgs, execOpts);
2340
2355
  }
2341
- if (xmlResult.value.exitCode !== 0) {
2342
- return err(createToolError("dcm2xml", xmlArgs, xmlResult.value.exitCode, xmlResult.value.stderr));
2343
- }
2344
- const jsonResult = xmlToJson(xmlResult.value.stdout);
2345
- if (!jsonResult.ok) {
2346
- return err(jsonResult.error);
2347
- }
2348
- return ok({ data: jsonResult.value, source: "xml" });
2356
+ return result;
2349
2357
  }
2350
2358
  async function tryDirectPath(inputPath, timeoutMs, signal, verbosity) {
2351
2359
  const jsonBinary = resolveBinary("dcm2json");
@@ -2530,7 +2538,8 @@ var DicomInstance = class _DicomInstance {
2530
2538
  const jsonResult = await dcm2json(path2, {
2531
2539
  timeoutMs: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
2532
2540
  signal: options?.signal,
2533
- charsetAssume: options?.charsetAssume
2541
+ charsetAssume: options?.charsetAssume,
2542
+ charsetFallback: options?.charsetFallback
2534
2543
  });
2535
2544
  if (!jsonResult.ok) return err(jsonResult.error);
2536
2545
  const datasetResult = DicomDataset.fromJson(jsonResult.value.data);
@@ -2848,6 +2857,13 @@ var DicomReceiverOptionsSchema = zod.z.object({
2848
2857
  filenameMode: zod.z.enum(["default", "unique", "short-unique", "system-time"]).optional(),
2849
2858
  filenameExtension: zod.z.string().min(1).optional(),
2850
2859
  storageMode: zod.z.enum(["normal", "bit-preserving", "ignore"]).optional(),
2860
+ instanceOpenOptions: zod.z.object({
2861
+ timeoutMs: zod.z.number().int().positive().optional(),
2862
+ signal: zod.z.instanceof(AbortSignal).optional(),
2863
+ charsetAssume: zod.z.string().min(1).optional(),
2864
+ charsetFallback: zod.z.string().min(1).optional()
2865
+ }).strict().optional(),
2866
+ parseInstances: zod.z.boolean().optional(),
2851
2867
  signal: zod.z.instanceof(AbortSignal).optional()
2852
2868
  }).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
2853
2869
  message: "minPoolSize must be <= maxPoolSize"
@@ -3017,6 +3033,8 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
3017
3033
  __publicField(this, "minPoolSize");
3018
3034
  __publicField(this, "maxPoolSize");
3019
3035
  __publicField(this, "connectionTimeoutMs");
3036
+ __publicField(this, "resolvedInstanceOpenOptions");
3037
+ __publicField(this, "parseInstances");
3020
3038
  __publicField(this, "workers", /* @__PURE__ */ new Map());
3021
3039
  __publicField(this, "tcpServer");
3022
3040
  __publicField(this, "associationCounter", 0);
@@ -3028,6 +3046,8 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
3028
3046
  this.minPoolSize = options.minPoolSize ?? DEFAULT_MIN_POOL_SIZE;
3029
3047
  this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
3030
3048
  this.connectionTimeoutMs = options.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS;
3049
+ this.resolvedInstanceOpenOptions = { charsetFallback: "Latin1", ...options.instanceOpenOptions };
3050
+ this.parseInstances = options.parseInstances ?? true;
3031
3051
  }
3032
3052
  // -----------------------------------------------------------------------
3033
3053
  // Public API
@@ -3480,21 +3500,23 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
3480
3500
  calledAE: data.calledAE,
3481
3501
  source: data.source
3482
3502
  });
3483
- const instanceCtx = {
3484
- filePath: finalPath,
3485
- fileSize,
3486
- associationId: ctx.associationId,
3487
- associationDir: ctx.associationDir,
3488
- callingAE: data.callingAE,
3489
- calledAE: data.calledAE,
3490
- source: data.source
3491
- };
3492
- worker.trackInstance(this.parseAndEmitInstance(worker, instanceCtx));
3503
+ if (this.parseInstances) {
3504
+ const instanceCtx = {
3505
+ filePath: finalPath,
3506
+ fileSize,
3507
+ associationId: ctx.associationId,
3508
+ associationDir: ctx.associationDir,
3509
+ callingAE: data.callingAE,
3510
+ calledAE: data.calledAE,
3511
+ source: data.source
3512
+ };
3513
+ worker.trackInstance(this.parseAndEmitInstance(worker, instanceCtx));
3514
+ }
3493
3515
  }
3494
3516
  /** Parses a DICOM file and emits INSTANCE_RECEIVED or INSTANCE_ERROR. */
3495
3517
  async parseAndEmitInstance(worker, ctx) {
3496
3518
  try {
3497
- const openResult = await DicomInstance.open(ctx.filePath);
3519
+ const openResult = await DicomInstance.open(ctx.filePath, this.resolvedInstanceOpenOptions);
3498
3520
  if (!openResult.ok) {
3499
3521
  worker.recordInstanceError();
3500
3522
  this.emit("INSTANCE_ERROR", { error: openResult.error, thrown: false, ...ctx });