@ubercode/dcmtk 0.2.0 → 0.4.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.
package/README.md CHANGED
@@ -9,12 +9,13 @@
9
9
  [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D20-green.svg)](https://nodejs.org/)
10
10
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
11
11
 
12
- Type-safe Node.js bindings for the [DCMTK](https://dicom.offis.de/dcmtk.php.en) (DICOM Toolkit) command-line utilities. Wraps 51 DCMTK binaries, 6 long-lived server processes, and a pooled DicomReceiver with auto-scaling workers, all with a modern async/await API, branded types, and the Result pattern for safe error handling.
12
+ Type-safe Node.js bindings for the [DCMTK](https://dicom.offis.de/dcmtk.php.en) (DICOM Toolkit) command-line utilities. Wraps 51 DCMTK binaries, 6 long-lived server processes, a pooled DicomReceiver with auto-scaling workers, and a high-throughput DicomSender with queuing and backpressure — all with a modern async/await API, branded types, and the Result pattern for safe error handling.
13
13
 
14
14
  ## Features
15
15
 
16
- - **51 tool wrappers** — async functions for every DCMTK command-line binary (data conversion, network, image processing, structured reports, presentation state)
17
- - **6 server classes + DicomReceiver** — long-lived DICOM listeners with typed EventEmitter APIs, plus a pooled receiver with auto-scaling workers
16
+ - **51 tool wrappers** — async functions for every DCMTK command-line binary with `verbosity` control and full CLI flag coverage
17
+ - **Network resilience** — all 7 network tools support PDU sizing, ACSE/DIMSE/association timeouts, and hostname lookup control
18
+ - **6 server classes + DicomReceiver + DicomSender** — long-lived DICOM listeners with typed EventEmitter APIs, a pooled receiver with auto-scaling workers, and a high-throughput sender with queuing, bucketing, and backpressure
18
19
  - **PacsClient** — high-level PACS client with Echo, Query, Retrieve, and Store operations
19
20
  - **DICOM data layer** — immutable `DicomDataset`, explicit `ChangeSet` builder, and `DicomInstance` unified file I/O
20
21
  - **Result pattern** — all fallible operations return `Result<T>` instead of throwing
@@ -63,6 +64,8 @@ const result = await echoscu({
63
64
  host: '127.0.0.1',
64
65
  port: 4242,
65
66
  calledAETitle: 'PACS',
67
+ verbosity: 'verbose',
68
+ associationTimeout: 10,
66
69
  });
67
70
 
68
71
  if (result.ok) {
@@ -103,6 +106,7 @@ if (result.ok) {
103
106
  | [PACS Client](docs/pacs-client.md) | High-level Echo, Query, Retrieve, Store API |
104
107
  | [DICOM Data Layer](docs/dicom-data-layer.md) | DicomDataset, ChangeSet, DicomInstance |
105
108
  | [Servers](docs/servers.md) | 6 server classes + DicomReceiver pooled receiver |
109
+ | [Senders](docs/senders.md) | DicomSender high-throughput sender with backpressure |
106
110
  | [Utilities](docs/utilities.md) | batch processing, retry with backoff |
107
111
 
108
112
  ## Tool Reference
@@ -121,15 +125,16 @@ if (result.ok) {
121
125
 
122
126
  ## Server Reference
123
127
 
124
- | Class | Binary | Description | Docs |
125
- | --------------- | -------------- | ------------------------------------------ | ------------------------------------------- |
126
- | `Dcmrecv` | dcmrecv | DICOM receiver (C-STORE SCP) | [servers.md](docs/servers.md#dcmrecv) |
127
- | `StoreSCP` | storescp | Storage SCP with advanced options | [servers.md](docs/servers.md#storescp) |
128
- | `DcmQRSCP` | dcmqrscp | Query/Retrieve SCP (C-FIND, C-MOVE, C-GET) | [servers.md](docs/servers.md#dcmqrscp) |
129
- | `Wlmscpfs` | wlmscpfs | Worklist Management SCP | [servers.md](docs/servers.md#wlmscpfs) |
130
- | `DcmprsCP` | dcmprscp | Print Management SCP | [servers.md](docs/servers.md#dcmprscp) |
131
- | `Dcmpsrcv` | dcmpsrcv | Viewer network receiver | [servers.md](docs/servers.md#dcmpsrcv) |
132
- | `DicomReceiver` | dcmrecv (pool) | Pooled receiver with auto-scaling workers | [servers.md](docs/servers.md#dicomreceiver) |
128
+ | Class | Binary | Description | Docs |
129
+ | --------------- | --------------- | ------------------------------------------ | ------------------------------------------- |
130
+ | `Dcmrecv` | dcmrecv | DICOM receiver (C-STORE SCP) | [servers.md](docs/servers.md#dcmrecv) |
131
+ | `StoreSCP` | storescp | Storage SCP with advanced options | [servers.md](docs/servers.md#storescp) |
132
+ | `DcmQRSCP` | dcmqrscp | Query/Retrieve SCP (C-FIND, C-MOVE, C-GET) | [servers.md](docs/servers.md#dcmqrscp) |
133
+ | `Wlmscpfs` | wlmscpfs | Worklist Management SCP | [servers.md](docs/servers.md#wlmscpfs) |
134
+ | `DcmprsCP` | dcmprscp | Print Management SCP | [servers.md](docs/servers.md#dcmprscp) |
135
+ | `Dcmpsrcv` | dcmpsrcv | Viewer network receiver | [servers.md](docs/servers.md#dcmpsrcv) |
136
+ | `DicomReceiver` | dcmrecv (pool) | Pooled receiver with auto-scaling workers | [servers.md](docs/servers.md#dicomreceiver) |
137
+ | `DicomSender` | storescu (pool) | High-throughput sender with backpressure | [senders.md](docs/senders.md) |
133
138
 
134
139
  ## License
135
140
 
@@ -1,5 +1,5 @@
1
1
  import { R as Result } from './types-Cgumy1N4.js';
2
- import { D as DicomJsonElement, T as TagModification } from './dcmodify-B-_uUIKB.js';
2
+ import { D as DicomJsonElement, T as TagModification } from './dcmodify-BvaIeyJg.js';
3
3
 
4
4
  /**
5
5
  * Branded types for domain primitives.
@@ -90,7 +90,7 @@ declare function createDicomTag(input: string): Result<DicomTag>;
90
90
  /**
91
91
  * Creates a validated AETitle from a raw string.
92
92
  *
93
- * @param input - A string expected to be 1-16 chars of letters, digits, spaces, or hyphens
93
+ * @param input - A string expected to be 1-16 printable ASCII chars (no backslash)
94
94
  * @returns A Result containing the branded AETitle or an error
95
95
  */
96
96
  declare function createAETitle(input: string): Result<AETitle>;
@@ -1,5 +1,5 @@
1
1
  import { R as Result } from './types-Cgumy1N4.cjs';
2
- import { D as DicomJsonElement, T as TagModification } from './dcmodify-Gds9u5Vj.cjs';
2
+ import { D as DicomJsonElement, T as TagModification } from './dcmodify-B9js5K1f.cjs';
3
3
 
4
4
  /**
5
5
  * Branded types for domain primitives.
@@ -90,7 +90,7 @@ declare function createDicomTag(input: string): Result<DicomTag>;
90
90
  /**
91
91
  * Creates a validated AETitle from a raw string.
92
92
  *
93
- * @param input - A string expected to be 1-16 chars of letters, digits, spaces, or hyphens
93
+ * @param input - A string expected to be 1-16 printable ASCII chars (no backslash)
94
94
  * @returns A Result containing the branded AETitle or an error
95
95
  */
96
96
  declare function createAETitle(input: string): Result<AETitle>;
@@ -94,6 +94,8 @@ interface DcmodifyOptions extends ToolBaseOptions {
94
94
  readonly insertIfMissing?: boolean | undefined;
95
95
  /** Treat 'tag not found' as success when erasing (uses -imt flag). Defaults to false. */
96
96
  readonly ignoreMissingTags?: boolean | undefined;
97
+ /** Verbosity level for diagnostic output. `'verbose'` maps to `-v`, `'debug'` maps to `-d`. */
98
+ readonly verbosity?: 'verbose' | 'debug' | undefined;
97
99
  }
98
100
  /** Result of a successful dcmodify operation. */
99
101
  interface DcmodifyResult {
@@ -94,6 +94,8 @@ interface DcmodifyOptions extends ToolBaseOptions {
94
94
  readonly insertIfMissing?: boolean | undefined;
95
95
  /** Treat 'tag not found' as success when erasing (uses -imt flag). Defaults to false. */
96
96
  readonly ignoreMissingTags?: boolean | undefined;
97
+ /** Verbosity level for diagnostic output. `'verbose'` maps to `-v`, `'debug'` maps to `-d`. */
98
+ readonly verbosity?: 'verbose' | 'debug' | undefined;
97
99
  }
98
100
  /** Result of a successful dcmodify operation. */
99
101
  interface DcmodifyResult {
package/dist/dicom.cjs CHANGED
@@ -30718,19 +30718,28 @@ function repairJson(raw) {
30718
30718
  var Dcm2jsonOptionsSchema = zod.z.object({
30719
30719
  timeoutMs: zod.z.number().int().positive().optional(),
30720
30720
  signal: zod.z.instanceof(AbortSignal).optional(),
30721
- directOnly: zod.z.boolean().optional()
30721
+ directOnly: zod.z.boolean().optional(),
30722
+ verbosity: zod.z.enum(["verbose", "debug"]).optional()
30722
30723
  }).strict().optional();
30723
- async function tryXmlPath(inputPath, timeoutMs, signal) {
30724
+ var VERBOSITY_FLAGS = { verbose: "-v", debug: "-d" };
30725
+ function buildVerbosityArgs(verbosity) {
30726
+ if (verbosity !== void 0) {
30727
+ return [VERBOSITY_FLAGS[verbosity]];
30728
+ }
30729
+ return [];
30730
+ }
30731
+ async function tryXmlPath(inputPath, timeoutMs, signal, verbosity) {
30724
30732
  const xmlBinary = resolveBinary("dcm2xml");
30725
30733
  if (!xmlBinary.ok) {
30726
30734
  return err(xmlBinary.error);
30727
30735
  }
30728
- const xmlResult = await execCommand(xmlBinary.value, ["-nat", inputPath], { timeoutMs, signal });
30736
+ const xmlArgs = [...buildVerbosityArgs(verbosity), "-nat", inputPath];
30737
+ const xmlResult = await execCommand(xmlBinary.value, xmlArgs, { timeoutMs, signal });
30729
30738
  if (!xmlResult.ok) {
30730
30739
  return err(xmlResult.error);
30731
30740
  }
30732
30741
  if (xmlResult.value.exitCode !== 0) {
30733
- return err(createToolError("dcm2xml", ["-nat", inputPath], xmlResult.value.exitCode, xmlResult.value.stderr));
30742
+ return err(createToolError("dcm2xml", xmlArgs, xmlResult.value.exitCode, xmlResult.value.stderr));
30734
30743
  }
30735
30744
  const jsonResult = xmlToJson(xmlResult.value.stdout);
30736
30745
  if (!jsonResult.ok) {
@@ -30738,17 +30747,18 @@ async function tryXmlPath(inputPath, timeoutMs, signal) {
30738
30747
  }
30739
30748
  return ok({ data: jsonResult.value, source: "xml" });
30740
30749
  }
30741
- async function tryDirectPath(inputPath, timeoutMs, signal) {
30750
+ async function tryDirectPath(inputPath, timeoutMs, signal, verbosity) {
30742
30751
  const jsonBinary = resolveBinary("dcm2json");
30743
30752
  if (!jsonBinary.ok) {
30744
30753
  return err(jsonBinary.error);
30745
30754
  }
30746
- const result = await execCommand(jsonBinary.value, [inputPath], { timeoutMs, signal });
30755
+ const directArgs = [...buildVerbosityArgs(verbosity), inputPath];
30756
+ const result = await execCommand(jsonBinary.value, directArgs, { timeoutMs, signal });
30747
30757
  if (!result.ok) {
30748
30758
  return err(result.error);
30749
30759
  }
30750
30760
  if (result.value.exitCode !== 0) {
30751
- return err(createToolError("dcm2json", [inputPath], result.value.exitCode, result.value.stderr));
30761
+ return err(createToolError("dcm2json", directArgs, result.value.exitCode, result.value.stderr));
30752
30762
  }
30753
30763
  try {
30754
30764
  const repaired = repairJson(result.value.stdout);
@@ -30765,20 +30775,22 @@ async function dcm2json(inputPath, options) {
30765
30775
  }
30766
30776
  const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
30767
30777
  const signal = options?.signal;
30778
+ const verbosity = options?.verbosity;
30768
30779
  if (options?.directOnly === true) {
30769
- return tryDirectPath(inputPath, timeoutMs, signal);
30780
+ return tryDirectPath(inputPath, timeoutMs, signal, verbosity);
30770
30781
  }
30771
- const xmlResult = await tryXmlPath(inputPath, timeoutMs, signal);
30782
+ const xmlResult = await tryXmlPath(inputPath, timeoutMs, signal, verbosity);
30772
30783
  if (xmlResult.ok) {
30773
30784
  return xmlResult;
30774
30785
  }
30775
- return tryDirectPath(inputPath, timeoutMs, signal);
30786
+ return tryDirectPath(inputPath, timeoutMs, signal, verbosity);
30776
30787
  }
30777
30788
  var TAG_OR_PATH_PATTERN = /^\([0-9A-Fa-f]{4},[0-9A-Fa-f]{4}\)(\[\d+\](\.\([0-9A-Fa-f]{4},[0-9A-Fa-f]{4}\)(\[\d+\])?)*)?$/;
30778
30789
  var TagModificationSchema = zod.z.object({
30779
30790
  tag: zod.z.string().regex(TAG_OR_PATH_PATTERN),
30780
30791
  value: zod.z.string()
30781
30792
  });
30793
+ var VERBOSITY_FLAGS2 = { verbose: "-v", debug: "-d" };
30782
30794
  var DcmodifyOptionsSchema = zod.z.object({
30783
30795
  timeoutMs: zod.z.number().int().positive().optional(),
30784
30796
  signal: zod.z.instanceof(AbortSignal).optional(),
@@ -30787,12 +30799,16 @@ var DcmodifyOptionsSchema = zod.z.object({
30787
30799
  erasePrivateTags: zod.z.boolean().optional(),
30788
30800
  noBackup: zod.z.boolean().optional(),
30789
30801
  insertIfMissing: zod.z.boolean().optional(),
30790
- ignoreMissingTags: zod.z.boolean().optional()
30802
+ ignoreMissingTags: zod.z.boolean().optional(),
30803
+ verbosity: zod.z.enum(["verbose", "debug"]).optional()
30791
30804
  }).strict().refine((data) => data.modifications.length > 0 || data.erasures !== void 0 && data.erasures.length > 0 || data.erasePrivateTags === true, {
30792
30805
  message: "At least one of modifications, erasures, or erasePrivateTags is required"
30793
30806
  });
30794
30807
  function buildArgs(inputPath, options) {
30795
30808
  const args = [];
30809
+ if (options.verbosity !== void 0) {
30810
+ args.push(VERBOSITY_FLAGS2[options.verbosity]);
30811
+ }
30796
30812
  if (options.noBackup !== false) {
30797
30813
  args.push("-nb");
30798
30814
  }