@ubercode/dcmtk 0.12.1 → 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.
@@ -1,7 +1,7 @@
1
1
  import { L as LineSource, R as Result } from './types-Cgumy1N4.cjs';
2
2
  import { EventEmitter } from 'node:events';
3
3
  import * as net from 'node:net';
4
- import { d as DicomInstance } from './DicomInstance-CxLKC-oM.cjs';
4
+ import { d as DicomInstance, e as DicomOpenOptions } from './DicomInstance-BGXtON9T.cjs';
5
5
 
6
6
  /**
7
7
  * Base class for long-lived DCMTK processes (servers).
@@ -1622,6 +1622,8 @@ interface DicomReceiverOptions {
1622
1622
  readonly filenameExtension?: string | undefined;
1623
1623
  /** Storage mode for received files (passed through to Dcmrecv workers). */
1624
1624
  readonly storageMode?: StorageModeValue | undefined;
1625
+ /** Options passed to DicomInstance.open() for each received file. Defaults charsetFallback to `'Latin1'`. */
1626
+ readonly instanceOpenOptions?: DicomOpenOptions | undefined;
1625
1627
  /** AbortSignal for external cancellation. */
1626
1628
  readonly signal?: AbortSignal | undefined;
1627
1629
  }
@@ -1654,6 +1656,7 @@ declare class DicomReceiver extends EventEmitter<DicomReceiverEventMap> {
1654
1656
  private readonly minPoolSize;
1655
1657
  private readonly maxPoolSize;
1656
1658
  private readonly connectionTimeoutMs;
1659
+ private readonly resolvedInstanceOpenOptions;
1657
1660
  private readonly workers;
1658
1661
  private tcpServer;
1659
1662
  private associationCounter;
@@ -1,7 +1,7 @@
1
1
  import { L as LineSource, R as Result } from './types-Cgumy1N4.js';
2
2
  import { EventEmitter } from 'node:events';
3
3
  import * as net from 'node:net';
4
- import { d as DicomInstance } from './DicomInstance-BKUiir0G.js';
4
+ import { d as DicomInstance, e as DicomOpenOptions } from './DicomInstance-DGWB2mfD.js';
5
5
 
6
6
  /**
7
7
  * Base class for long-lived DCMTK processes (servers).
@@ -1622,6 +1622,8 @@ interface DicomReceiverOptions {
1622
1622
  readonly filenameExtension?: string | undefined;
1623
1623
  /** Storage mode for received files (passed through to Dcmrecv workers). */
1624
1624
  readonly storageMode?: StorageModeValue | undefined;
1625
+ /** Options passed to DicomInstance.open() for each received file. Defaults charsetFallback to `'Latin1'`. */
1626
+ readonly instanceOpenOptions?: DicomOpenOptions | undefined;
1625
1627
  /** AbortSignal for external cancellation. */
1626
1628
  readonly signal?: AbortSignal | undefined;
1627
1629
  }
@@ -1654,6 +1656,7 @@ declare class DicomReceiver extends EventEmitter<DicomReceiverEventMap> {
1654
1656
  private readonly minPoolSize;
1655
1657
  private readonly maxPoolSize;
1656
1658
  private readonly connectionTimeoutMs;
1659
+ private readonly resolvedInstanceOpenOptions;
1657
1660
  private readonly workers;
1658
1661
  private tcpServer;
1659
1662
  private associationCounter;
package/dist/index.cjs CHANGED
@@ -31468,6 +31468,7 @@ var Dcm2jsonOptionsSchema = zod.z.object({
31468
31468
  signal: zod.z.instanceof(AbortSignal).optional(),
31469
31469
  directOnly: zod.z.boolean().optional(),
31470
31470
  charsetAssume: zod.z.string().min(1).optional(),
31471
+ charsetFallback: zod.z.string().min(1).optional(),
31471
31472
  verbosity: zod.z.enum(["verbose", "debug"]).optional()
31472
31473
  }).strict().optional();
31473
31474
  var VERBOSITY_FLAGS2 = { verbose: "-v", debug: "-d" };
@@ -31481,27 +31482,34 @@ function buildXmlOpts(options) {
31481
31482
  const result = {};
31482
31483
  if (options?.verbosity !== void 0) result["verbosity"] = options.verbosity;
31483
31484
  if (options?.charsetAssume !== void 0) result["charsetAssume"] = options.charsetAssume;
31485
+ if (options?.charsetFallback !== void 0) result["charsetFallback"] = options.charsetFallback;
31484
31486
  return result;
31485
31487
  }
31488
+ function isCharsetError(stderrOutput) {
31489
+ return stderrOutput.includes("convert character encoding") || stderrOutput.includes("Illegal byte sequence");
31490
+ }
31491
+ async function runXmlAndParse(binary, args, execOpts) {
31492
+ const xmlResult = await execCommand(binary, args, execOpts);
31493
+ if (!xmlResult.ok) return err(xmlResult.error);
31494
+ if (xmlResult.value.exitCode !== 0) {
31495
+ return err(createToolError("dcm2xml", args, xmlResult.value.exitCode, xmlResult.value.stderr));
31496
+ }
31497
+ const jsonResult = xmlToJson(xmlResult.value.stdout);
31498
+ if (!jsonResult.ok) return err(jsonResult.error);
31499
+ return ok({ data: jsonResult.value, source: "xml" });
31500
+ }
31486
31501
  async function tryXmlPath(inputPath, timeoutMs, signal, opts) {
31487
31502
  const xmlBinary = resolveBinary("dcm2xml");
31488
- if (!xmlBinary.ok) {
31489
- return err(xmlBinary.error);
31490
- }
31503
+ if (!xmlBinary.ok) return err(xmlBinary.error);
31491
31504
  const charsetArgs = opts?.charsetAssume !== void 0 ? ["+Ca", opts.charsetAssume] : [];
31492
31505
  const xmlArgs = [...buildVerbosityArgs(opts?.verbosity), ...charsetArgs, "-nat", inputPath];
31493
- const xmlResult = await execCommand(xmlBinary.value, xmlArgs, { timeoutMs, signal });
31494
- if (!xmlResult.ok) {
31495
- return err(xmlResult.error);
31506
+ const execOpts = signal !== void 0 ? { timeoutMs, signal } : { timeoutMs };
31507
+ const result = await runXmlAndParse(xmlBinary.value, xmlArgs, execOpts);
31508
+ if (!result.ok && opts?.charsetFallback !== void 0 && isCharsetError(result.error.message)) {
31509
+ const fallbackArgs = [...buildVerbosityArgs(opts.verbosity), "+Ca", opts.charsetFallback, "-nat", inputPath];
31510
+ return runXmlAndParse(xmlBinary.value, fallbackArgs, execOpts);
31496
31511
  }
31497
- if (xmlResult.value.exitCode !== 0) {
31498
- return err(createToolError("dcm2xml", xmlArgs, xmlResult.value.exitCode, xmlResult.value.stderr));
31499
- }
31500
- const jsonResult = xmlToJson(xmlResult.value.stdout);
31501
- if (!jsonResult.ok) {
31502
- return err(jsonResult.error);
31503
- }
31504
- return ok({ data: jsonResult.value, source: "xml" });
31512
+ return result;
31505
31513
  }
31506
31514
  async function tryDirectPath(inputPath, timeoutMs, signal, verbosity) {
31507
31515
  const jsonBinary = resolveBinary("dcm2json");
@@ -34392,7 +34400,8 @@ var DicomInstance = class _DicomInstance {
34392
34400
  const jsonResult = await dcm2json(path2, {
34393
34401
  timeoutMs: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
34394
34402
  signal: options?.signal,
34395
- charsetAssume: options?.charsetAssume
34403
+ charsetAssume: options?.charsetAssume,
34404
+ charsetFallback: options?.charsetFallback
34396
34405
  });
34397
34406
  if (!jsonResult.ok) return err(jsonResult.error);
34398
34407
  const datasetResult = DicomDataset.fromJson(jsonResult.value.data);
@@ -36477,6 +36486,12 @@ var DicomReceiverOptionsSchema = zod.z.object({
36477
36486
  filenameMode: zod.z.enum(["default", "unique", "short-unique", "system-time"]).optional(),
36478
36487
  filenameExtension: zod.z.string().min(1).optional(),
36479
36488
  storageMode: zod.z.enum(["normal", "bit-preserving", "ignore"]).optional(),
36489
+ instanceOpenOptions: zod.z.object({
36490
+ timeoutMs: zod.z.number().int().positive().optional(),
36491
+ signal: zod.z.instanceof(AbortSignal).optional(),
36492
+ charsetAssume: zod.z.string().min(1).optional(),
36493
+ charsetFallback: zod.z.string().min(1).optional()
36494
+ }).strict().optional(),
36480
36495
  signal: zod.z.instanceof(AbortSignal).optional()
36481
36496
  }).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
36482
36497
  message: "minPoolSize must be <= maxPoolSize"
@@ -36646,6 +36661,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
36646
36661
  __publicField(this, "minPoolSize");
36647
36662
  __publicField(this, "maxPoolSize");
36648
36663
  __publicField(this, "connectionTimeoutMs");
36664
+ __publicField(this, "resolvedInstanceOpenOptions");
36649
36665
  __publicField(this, "workers", /* @__PURE__ */ new Map());
36650
36666
  __publicField(this, "tcpServer");
36651
36667
  __publicField(this, "associationCounter", 0);
@@ -36657,6 +36673,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
36657
36673
  this.minPoolSize = options.minPoolSize ?? DEFAULT_MIN_POOL_SIZE;
36658
36674
  this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
36659
36675
  this.connectionTimeoutMs = options.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS;
36676
+ this.resolvedInstanceOpenOptions = { charsetFallback: "Latin1", ...options.instanceOpenOptions };
36660
36677
  }
36661
36678
  // -----------------------------------------------------------------------
36662
36679
  // Public API
@@ -37123,7 +37140,7 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
37123
37140
  /** Parses a DICOM file and emits INSTANCE_RECEIVED or INSTANCE_ERROR. */
37124
37141
  async parseAndEmitInstance(worker, ctx) {
37125
37142
  try {
37126
- const openResult = await DicomInstance.open(ctx.filePath);
37143
+ const openResult = await DicomInstance.open(ctx.filePath, this.resolvedInstanceOpenOptions);
37127
37144
  if (!openResult.ok) {
37128
37145
  worker.recordInstanceError();
37129
37146
  this.emit("INSTANCE_ERROR", { error: openResult.error, thrown: false, ...ctx });