@ubercode/dcmtk 0.1.1 → 0.2.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/LICENSE +21 -21
- package/README.md +18 -15
- package/dist/DicomInstance-By9zd7GM.d.cts +625 -0
- package/dist/DicomInstance-CQEIuF_x.d.ts +625 -0
- package/dist/{dcmodify-CTXBWKU9.d.cts → dcmodify-B-_uUIKB.d.ts} +4 -2
- package/dist/{dcmodify-Daeafqrm.d.ts → dcmodify-Gds9u5Vj.d.cts} +4 -2
- package/dist/dicom.cjs +329 -51
- package/dist/dicom.cjs.map +1 -1
- package/dist/dicom.d.cts +368 -3
- package/dist/dicom.d.ts +368 -3
- package/dist/dicom.js +329 -51
- package/dist/dicom.js.map +1 -1
- package/dist/index.cjs +1460 -419
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +1432 -413
- package/dist/index.js.map +1 -1
- package/dist/servers.cjs +2379 -196
- package/dist/servers.cjs.map +1 -1
- package/dist/servers.d.cts +1654 -3
- package/dist/servers.d.ts +1654 -3
- package/dist/servers.js +2305 -145
- package/dist/servers.js.map +1 -1
- package/dist/tools.cjs +97 -50
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.d.cts +21 -4
- package/dist/tools.d.ts +21 -4
- package/dist/tools.js +97 -51
- package/dist/tools.js.map +1 -1
- package/dist/{types-zHhxS7d2.d.cts → types-Cgumy1N4.d.cts} +1 -24
- package/dist/{types-zHhxS7d2.d.ts → types-Cgumy1N4.d.ts} +1 -24
- package/dist/utils.cjs.map +1 -1
- package/dist/utils.d.cts +1 -1
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js.map +1 -1
- package/package.json +8 -8
- package/dist/index-BZxi4104.d.ts +0 -826
- package/dist/index-CapkWqxy.d.ts +0 -1295
- package/dist/index-DX4C3zbo.d.cts +0 -826
- package/dist/index-r7AvpkCE.d.cts +0 -1295
package/dist/index.cjs
CHANGED
|
@@ -2,18 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
var path = require('path');
|
|
4
4
|
var zod = require('zod');
|
|
5
|
-
var fs = require('fs');
|
|
5
|
+
var fs$1 = require('fs');
|
|
6
6
|
var child_process = require('child_process');
|
|
7
7
|
var kill = require('tree-kill');
|
|
8
8
|
var events = require('events');
|
|
9
9
|
var stderrLib = require('stderr-lib');
|
|
10
|
-
var promises = require('fs/promises');
|
|
11
10
|
var fastXmlParser = require('fast-xml-parser');
|
|
11
|
+
var fs = require('fs/promises');
|
|
12
|
+
var net = require('net');
|
|
12
13
|
var os = require('os');
|
|
13
14
|
|
|
14
15
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
16
|
|
|
17
|
+
function _interopNamespace(e) {
|
|
18
|
+
if (e && e.__esModule) return e;
|
|
19
|
+
var n = Object.create(null);
|
|
20
|
+
if (e) {
|
|
21
|
+
Object.keys(e).forEach(function (k) {
|
|
22
|
+
if (k !== 'default') {
|
|
23
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
24
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
25
|
+
enumerable: true,
|
|
26
|
+
get: function () { return e[k]; }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
n.default = e;
|
|
32
|
+
return Object.freeze(n);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
16
36
|
var kill__default = /*#__PURE__*/_interopDefault(kill);
|
|
37
|
+
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
38
|
+
var net__namespace = /*#__PURE__*/_interopNamespace(net);
|
|
39
|
+
var os__namespace = /*#__PURE__*/_interopNamespace(os);
|
|
17
40
|
|
|
18
41
|
var __defProp = Object.defineProperty;
|
|
19
42
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
@@ -29,12 +52,6 @@ function err(error) {
|
|
|
29
52
|
function assertUnreachable(x) {
|
|
30
53
|
throw new Error(`Exhaustive check failed: ${JSON.stringify(x)}`);
|
|
31
54
|
}
|
|
32
|
-
function unwrap(result) {
|
|
33
|
-
if (result.ok) {
|
|
34
|
-
return result.value;
|
|
35
|
-
}
|
|
36
|
-
throw result.error;
|
|
37
|
-
}
|
|
38
55
|
function mapResult(result, fn) {
|
|
39
56
|
if (result.ok) {
|
|
40
57
|
return ok(fn(result.value));
|
|
@@ -124,6 +141,11 @@ function createPort(input) {
|
|
|
124
141
|
}
|
|
125
142
|
return ok(input);
|
|
126
143
|
}
|
|
144
|
+
function tag(input) {
|
|
145
|
+
const result = createDicomTagPath(input);
|
|
146
|
+
if (!result.ok) throw result.error;
|
|
147
|
+
return result.value;
|
|
148
|
+
}
|
|
127
149
|
var AETitleSchema = zod.z.string().min(AE_TITLE_MIN_LENGTH).max(AE_TITLE_MAX_LENGTH).regex(AE_TITLE_PATTERN);
|
|
128
150
|
var PortSchema = zod.z.number().int().min(PORT_MIN).max(PORT_MAX);
|
|
129
151
|
var DicomTagSchema = zod.z.string().regex(DICOM_TAG_PATTERN);
|
|
@@ -201,7 +223,7 @@ function binaryName(name) {
|
|
|
201
223
|
}
|
|
202
224
|
function hasRequiredBinaries(dir) {
|
|
203
225
|
for (const bin of REQUIRED_BINARIES) {
|
|
204
|
-
if (!fs.existsSync(path.join(dir, binaryName(bin)))) {
|
|
226
|
+
if (!fs$1.existsSync(path.join(dir, binaryName(bin)))) {
|
|
205
227
|
return false;
|
|
206
228
|
}
|
|
207
229
|
}
|
|
@@ -30259,8 +30281,8 @@ var dictionary_default = {
|
|
|
30259
30281
|
|
|
30260
30282
|
// src/dicom/dictionary.ts
|
|
30261
30283
|
var dictionary = dictionary_default;
|
|
30262
|
-
function lookupTag(
|
|
30263
|
-
const key =
|
|
30284
|
+
function lookupTag(tag2) {
|
|
30285
|
+
const key = tag2.includes(",") ? tag2.replace(/[(),]/g, "") : tag2;
|
|
30264
30286
|
return dictionary[key.toUpperCase()];
|
|
30265
30287
|
}
|
|
30266
30288
|
var nameIndex;
|
|
@@ -30427,13 +30449,13 @@ function advancePastMatch(remaining, matchLength) {
|
|
|
30427
30449
|
if (after.length === 1) throw new Error("Tag path cannot end with a dot separator");
|
|
30428
30450
|
return after.slice(1);
|
|
30429
30451
|
}
|
|
30430
|
-
function tagPathToSegments(
|
|
30452
|
+
function tagPathToSegments(path2) {
|
|
30431
30453
|
const segments = [];
|
|
30432
|
-
let remaining =
|
|
30454
|
+
let remaining = path2;
|
|
30433
30455
|
for (let i = 0; i < MAX_TRAVERSAL_DEPTH && remaining.length > 0; i++) {
|
|
30434
30456
|
const match = SEGMENT_PATTERN.exec(remaining);
|
|
30435
30457
|
if (match === null) {
|
|
30436
|
-
throw new Error(`Invalid tag path segment at position ${
|
|
30458
|
+
throw new Error(`Invalid tag path segment at position ${path2.length - remaining.length}: "${remaining}"`);
|
|
30437
30459
|
}
|
|
30438
30460
|
segments.push(matchToSegment(match));
|
|
30439
30461
|
remaining = advancePastMatch(remaining, match[0].length);
|
|
@@ -30474,18 +30496,18 @@ function isValidDicomJsonModel(obj) {
|
|
|
30474
30496
|
}
|
|
30475
30497
|
var TAG_WITH_PARENS = /^\(([0-9A-Fa-f]{4}),([0-9A-Fa-f]{4})\)$/;
|
|
30476
30498
|
var TAG_HEX_ONLY = /^[0-9A-Fa-f]{8}$/;
|
|
30477
|
-
function normalizeTag(
|
|
30478
|
-
const parenMatch = TAG_WITH_PARENS.exec(
|
|
30499
|
+
function normalizeTag(tag2) {
|
|
30500
|
+
const parenMatch = TAG_WITH_PARENS.exec(tag2);
|
|
30479
30501
|
if (parenMatch !== null) {
|
|
30480
30502
|
const group = parenMatch[1];
|
|
30481
30503
|
const element = parenMatch[2];
|
|
30482
|
-
if (group === void 0 || element === void 0) return err(new Error(`Invalid tag: "${
|
|
30504
|
+
if (group === void 0 || element === void 0) return err(new Error(`Invalid tag: "${tag2}"`));
|
|
30483
30505
|
return ok(`${group}${element}`.toUpperCase());
|
|
30484
30506
|
}
|
|
30485
|
-
if (TAG_HEX_ONLY.test(
|
|
30486
|
-
return ok(
|
|
30507
|
+
if (TAG_HEX_ONLY.test(tag2)) {
|
|
30508
|
+
return ok(tag2.toUpperCase());
|
|
30487
30509
|
}
|
|
30488
|
-
return err(new Error(`Invalid tag format: "${
|
|
30510
|
+
return err(new Error(`Invalid tag format: "${tag2}". Expected (XXXX,XXXX) or XXXXXXXX`));
|
|
30489
30511
|
}
|
|
30490
30512
|
function resolveElement(data, tagKey) {
|
|
30491
30513
|
return data[tagKey];
|
|
@@ -30666,11 +30688,11 @@ var DicomDataset = class _DicomDataset {
|
|
|
30666
30688
|
* @param tag - A DicomTag `(0010,0010)` or hex string `00100010`
|
|
30667
30689
|
* @returns Result containing the element or an error if not found
|
|
30668
30690
|
*/
|
|
30669
|
-
getElement(
|
|
30670
|
-
const norm = normalizeTag(
|
|
30691
|
+
getElement(tag2) {
|
|
30692
|
+
const norm = normalizeTag(tag2);
|
|
30671
30693
|
if (!norm.ok) return err(norm.error);
|
|
30672
30694
|
const element = resolveElement(this.data, norm.value);
|
|
30673
|
-
if (element === void 0) return err(new Error(`Tag ${
|
|
30695
|
+
if (element === void 0) return err(new Error(`Tag ${tag2} not found`));
|
|
30674
30696
|
return ok(element);
|
|
30675
30697
|
}
|
|
30676
30698
|
/**
|
|
@@ -30679,11 +30701,11 @@ var DicomDataset = class _DicomDataset {
|
|
|
30679
30701
|
* @param tag - A DicomTag `(0010,0010)` or hex string `00100010`
|
|
30680
30702
|
* @returns Result containing the readonly Value array or an error
|
|
30681
30703
|
*/
|
|
30682
|
-
getValue(
|
|
30683
|
-
const elemResult = this.getElement(
|
|
30704
|
+
getValue(tag2) {
|
|
30705
|
+
const elemResult = this.getElement(tag2);
|
|
30684
30706
|
if (!elemResult.ok) return err(elemResult.error);
|
|
30685
30707
|
const values = elemResult.value.Value;
|
|
30686
|
-
if (values === void 0) return err(new Error(`Tag ${
|
|
30708
|
+
if (values === void 0) return err(new Error(`Tag ${tag2} has no Value`));
|
|
30687
30709
|
return ok(values);
|
|
30688
30710
|
}
|
|
30689
30711
|
/**
|
|
@@ -30692,11 +30714,11 @@ var DicomDataset = class _DicomDataset {
|
|
|
30692
30714
|
* @param tag - A DicomTag `(0010,0010)` or hex string `00100010`
|
|
30693
30715
|
* @returns Result containing the first value or an error
|
|
30694
30716
|
*/
|
|
30695
|
-
getFirstValue(
|
|
30696
|
-
const valResult = this.getValue(
|
|
30717
|
+
getFirstValue(tag2) {
|
|
30718
|
+
const valResult = this.getValue(tag2);
|
|
30697
30719
|
if (!valResult.ok) return err(valResult.error);
|
|
30698
30720
|
const first = valResult.value[0];
|
|
30699
|
-
if (first === void 0) return err(new Error(`Tag ${
|
|
30721
|
+
if (first === void 0) return err(new Error(`Tag ${tag2} Value array is empty`));
|
|
30700
30722
|
return ok(first);
|
|
30701
30723
|
}
|
|
30702
30724
|
/**
|
|
@@ -30709,8 +30731,8 @@ var DicomDataset = class _DicomDataset {
|
|
|
30709
30731
|
* @param fallback - Value to return if tag is missing (default: `''`)
|
|
30710
30732
|
* @returns The string value or the fallback
|
|
30711
30733
|
*/
|
|
30712
|
-
getString(
|
|
30713
|
-
const elemResult = this.getElement(
|
|
30734
|
+
getString(tag2, fallback = "") {
|
|
30735
|
+
const elemResult = this.getElement(tag2);
|
|
30714
30736
|
if (!elemResult.ok) return fallback;
|
|
30715
30737
|
const str = extractString(elemResult.value);
|
|
30716
30738
|
return str.length > 0 ? str : fallback;
|
|
@@ -30721,8 +30743,8 @@ var DicomDataset = class _DicomDataset {
|
|
|
30721
30743
|
* @param tag - A DicomTag `(0010,0010)` or hex string `00100010`
|
|
30722
30744
|
* @returns Result containing the number or an error
|
|
30723
30745
|
*/
|
|
30724
|
-
getNumber(
|
|
30725
|
-
const elemResult = this.getElement(
|
|
30746
|
+
getNumber(tag2) {
|
|
30747
|
+
const elemResult = this.getElement(tag2);
|
|
30726
30748
|
if (!elemResult.ok) return err(elemResult.error);
|
|
30727
30749
|
return extractNumber(elemResult.value);
|
|
30728
30750
|
}
|
|
@@ -30734,8 +30756,8 @@ var DicomDataset = class _DicomDataset {
|
|
|
30734
30756
|
* @param tag - A DicomTag `(0010,0010)` or hex string `00100010`
|
|
30735
30757
|
* @returns Result containing the readonly string array or an error
|
|
30736
30758
|
*/
|
|
30737
|
-
getStrings(
|
|
30738
|
-
const elemResult = this.getElement(
|
|
30759
|
+
getStrings(tag2) {
|
|
30760
|
+
const elemResult = this.getElement(tag2);
|
|
30739
30761
|
if (!elemResult.ok) return err(elemResult.error);
|
|
30740
30762
|
return ok(extractStrings(elemResult.value));
|
|
30741
30763
|
}
|
|
@@ -30745,8 +30767,8 @@ var DicomDataset = class _DicomDataset {
|
|
|
30745
30767
|
* @param tag - A DicomTag `(0010,0010)` or hex string `00100010`
|
|
30746
30768
|
* @returns `true` if the tag is present
|
|
30747
30769
|
*/
|
|
30748
|
-
hasTag(
|
|
30749
|
-
const norm = normalizeTag(
|
|
30770
|
+
hasTag(tag2) {
|
|
30771
|
+
const norm = normalizeTag(tag2);
|
|
30750
30772
|
if (!norm.ok) return false;
|
|
30751
30773
|
return resolveElement(this.data, norm.value) !== void 0;
|
|
30752
30774
|
}
|
|
@@ -30756,10 +30778,10 @@ var DicomDataset = class _DicomDataset {
|
|
|
30756
30778
|
* @param path - A branded DicomTagPath, e.g. `(0040,A730)[0].(0040,A160)`
|
|
30757
30779
|
* @returns Result containing the element at the path or an error
|
|
30758
30780
|
*/
|
|
30759
|
-
getElementAtPath(
|
|
30781
|
+
getElementAtPath(path2) {
|
|
30760
30782
|
let segments;
|
|
30761
30783
|
try {
|
|
30762
|
-
segments = tagPathToSegments(
|
|
30784
|
+
segments = tagPathToSegments(path2);
|
|
30763
30785
|
} catch (e) {
|
|
30764
30786
|
return err(stderrLib.stderr(e));
|
|
30765
30787
|
}
|
|
@@ -30776,10 +30798,10 @@ var DicomDataset = class _DicomDataset {
|
|
|
30776
30798
|
* @param path - A branded DicomTagPath, e.g. `(0040,A730)[*].(0040,A160)`
|
|
30777
30799
|
* @returns A readonly array of all matching values (may be empty)
|
|
30778
30800
|
*/
|
|
30779
|
-
findValues(
|
|
30801
|
+
findValues(path2) {
|
|
30780
30802
|
let segments;
|
|
30781
30803
|
try {
|
|
30782
|
-
segments = tagPathToSegments(
|
|
30804
|
+
segments = tagPathToSegments(path2);
|
|
30783
30805
|
} catch {
|
|
30784
30806
|
return [];
|
|
30785
30807
|
}
|
|
@@ -30862,6 +30884,18 @@ function buildMergedModifications(base, other, erasures) {
|
|
|
30862
30884
|
}
|
|
30863
30885
|
return merged;
|
|
30864
30886
|
}
|
|
30887
|
+
function applyBatchEntries(initial, entries) {
|
|
30888
|
+
const keys = Object.keys(entries);
|
|
30889
|
+
let cs = initial;
|
|
30890
|
+
for (let i = 0; i < keys.length; i++) {
|
|
30891
|
+
const key = keys[i];
|
|
30892
|
+
if (key === void 0) continue;
|
|
30893
|
+
const value = entries[key];
|
|
30894
|
+
if (value === void 0) continue;
|
|
30895
|
+
cs = cs.setTag(key, value);
|
|
30896
|
+
}
|
|
30897
|
+
return cs;
|
|
30898
|
+
}
|
|
30865
30899
|
var ChangeSet = class _ChangeSet {
|
|
30866
30900
|
constructor(mods, erasures) {
|
|
30867
30901
|
__publicField(this, "mods");
|
|
@@ -30879,22 +30913,22 @@ var ChangeSet = class _ChangeSet {
|
|
|
30879
30913
|
* Control characters (except LF/CR) are stripped from the value.
|
|
30880
30914
|
* If the tag was previously erased, it is removed from the erasure set.
|
|
30881
30915
|
*
|
|
30882
|
-
* @param path - The DICOM tag path to set
|
|
30916
|
+
* @param path - The DICOM tag path to set (e.g. `'(0010,0010)'`)
|
|
30883
30917
|
* @param value - The new value for the tag
|
|
30884
30918
|
* @returns A new ChangeSet with the modification applied
|
|
30885
30919
|
* @throws Error if operation count would exceed MAX_CHANGESET_OPERATIONS
|
|
30886
30920
|
*/
|
|
30887
|
-
setTag(
|
|
30921
|
+
setTag(path2, value) {
|
|
30888
30922
|
const totalOps = this.mods.size + this.erased.size;
|
|
30889
30923
|
if (totalOps >= MAX_CHANGESET_OPERATIONS) {
|
|
30890
30924
|
throw new Error(`ChangeSet operation limit (${MAX_CHANGESET_OPERATIONS}) exceeded`);
|
|
30891
30925
|
}
|
|
30892
|
-
tagPathToSegments(
|
|
30926
|
+
tagPathToSegments(path2);
|
|
30893
30927
|
const sanitized = sanitizeValue(value);
|
|
30894
30928
|
const newMods = new Map(this.mods);
|
|
30895
|
-
newMods.set(
|
|
30929
|
+
newMods.set(path2, sanitized);
|
|
30896
30930
|
const newErasures = new Set(this.erased);
|
|
30897
|
-
newErasures.delete(
|
|
30931
|
+
newErasures.delete(path2);
|
|
30898
30932
|
return new _ChangeSet(newMods, newErasures);
|
|
30899
30933
|
}
|
|
30900
30934
|
/**
|
|
@@ -30902,20 +30936,20 @@ var ChangeSet = class _ChangeSet {
|
|
|
30902
30936
|
*
|
|
30903
30937
|
* If the tag was previously set, the modification is removed.
|
|
30904
30938
|
*
|
|
30905
|
-
* @param path - The DICOM tag path to erase
|
|
30939
|
+
* @param path - The DICOM tag path to erase (e.g. `'(0010,0010)'`)
|
|
30906
30940
|
* @returns A new ChangeSet with the erasure applied
|
|
30907
30941
|
* @throws Error if operation count would exceed MAX_CHANGESET_OPERATIONS
|
|
30908
30942
|
*/
|
|
30909
|
-
eraseTag(
|
|
30943
|
+
eraseTag(path2) {
|
|
30910
30944
|
const totalOps = this.mods.size + this.erased.size;
|
|
30911
30945
|
if (totalOps >= MAX_CHANGESET_OPERATIONS) {
|
|
30912
30946
|
throw new Error(`ChangeSet operation limit (${MAX_CHANGESET_OPERATIONS}) exceeded`);
|
|
30913
30947
|
}
|
|
30914
|
-
tagPathToSegments(
|
|
30948
|
+
tagPathToSegments(path2);
|
|
30915
30949
|
const newMods = new Map(this.mods);
|
|
30916
|
-
newMods.delete(
|
|
30950
|
+
newMods.delete(path2);
|
|
30917
30951
|
const newErasures = new Set(this.erased);
|
|
30918
|
-
newErasures.add(
|
|
30952
|
+
newErasures.add(path2);
|
|
30919
30953
|
return new _ChangeSet(newMods, newErasures);
|
|
30920
30954
|
}
|
|
30921
30955
|
/**
|
|
@@ -30933,6 +30967,50 @@ var ChangeSet = class _ChangeSet {
|
|
|
30933
30967
|
newErasures.add(ERASE_PRIVATE_SENTINEL);
|
|
30934
30968
|
return new _ChangeSet(new Map(this.mods), newErasures);
|
|
30935
30969
|
}
|
|
30970
|
+
// -----------------------------------------------------------------------
|
|
30971
|
+
// Convenience setters for common DICOM tags
|
|
30972
|
+
// -----------------------------------------------------------------------
|
|
30973
|
+
/** Sets Patient's Name (0010,0010). */
|
|
30974
|
+
setPatientName(value) {
|
|
30975
|
+
return this.setTag("(0010,0010)", value);
|
|
30976
|
+
}
|
|
30977
|
+
/** Sets Patient ID (0010,0020). */
|
|
30978
|
+
setPatientID(value) {
|
|
30979
|
+
return this.setTag("(0010,0020)", value);
|
|
30980
|
+
}
|
|
30981
|
+
/** Sets Study Date (0008,0020). */
|
|
30982
|
+
setStudyDate(value) {
|
|
30983
|
+
return this.setTag("(0008,0020)", value);
|
|
30984
|
+
}
|
|
30985
|
+
/** Sets Modality (0008,0060). */
|
|
30986
|
+
setModality(value) {
|
|
30987
|
+
return this.setTag("(0008,0060)", value);
|
|
30988
|
+
}
|
|
30989
|
+
/** Sets Accession Number (0008,0050). */
|
|
30990
|
+
setAccessionNumber(value) {
|
|
30991
|
+
return this.setTag("(0008,0050)", value);
|
|
30992
|
+
}
|
|
30993
|
+
/** Sets Study Description (0008,1030). */
|
|
30994
|
+
setStudyDescription(value) {
|
|
30995
|
+
return this.setTag("(0008,1030)", value);
|
|
30996
|
+
}
|
|
30997
|
+
/** Sets Series Description (0008,103E). */
|
|
30998
|
+
setSeriesDescription(value) {
|
|
30999
|
+
return this.setTag("(0008,103E)", value);
|
|
31000
|
+
}
|
|
31001
|
+
/** Sets Institution Name (0008,0080). */
|
|
31002
|
+
setInstitutionName(value) {
|
|
31003
|
+
return this.setTag("(0008,0080)", value);
|
|
31004
|
+
}
|
|
31005
|
+
/**
|
|
31006
|
+
* Sets multiple tags at once, returning a new ChangeSet.
|
|
31007
|
+
*
|
|
31008
|
+
* @param entries - A record of tag path → value pairs
|
|
31009
|
+
* @returns A new ChangeSet with all modifications applied
|
|
31010
|
+
*/
|
|
31011
|
+
setBatch(entries) {
|
|
31012
|
+
return applyBatchEntries(this, entries);
|
|
31013
|
+
}
|
|
30936
31014
|
/** All pending tag modifications as a readonly map of path → value. */
|
|
30937
31015
|
get modifications() {
|
|
30938
31016
|
return this.mods;
|
|
@@ -30975,8 +31053,8 @@ var ChangeSet = class _ChangeSet {
|
|
|
30975
31053
|
*/
|
|
30976
31054
|
toModifications() {
|
|
30977
31055
|
const result = [];
|
|
30978
|
-
for (const [
|
|
30979
|
-
result.push({ tag, value });
|
|
31056
|
+
for (const [tag2, value] of this.mods) {
|
|
31057
|
+
result.push({ tag: tag2, value });
|
|
30980
31058
|
}
|
|
30981
31059
|
return result;
|
|
30982
31060
|
}
|
|
@@ -30990,9 +31068,9 @@ var ChangeSet = class _ChangeSet {
|
|
|
30990
31068
|
*/
|
|
30991
31069
|
toErasureArgs() {
|
|
30992
31070
|
const result = [];
|
|
30993
|
-
for (const
|
|
30994
|
-
if (
|
|
30995
|
-
result.push(
|
|
31071
|
+
for (const path2 of this.erased) {
|
|
31072
|
+
if (path2 !== ERASE_PRIVATE_SENTINEL) {
|
|
31073
|
+
result.push(path2);
|
|
30996
31074
|
}
|
|
30997
31075
|
}
|
|
30998
31076
|
return result;
|
|
@@ -31034,12 +31112,72 @@ function createValidationError(toolName, zodError) {
|
|
|
31034
31112
|
for (let i = 0; i < zodError.issues.length; i++) {
|
|
31035
31113
|
const issue = zodError.issues[i];
|
|
31036
31114
|
if (issue === void 0) continue;
|
|
31037
|
-
const
|
|
31038
|
-
parts.push(`${
|
|
31115
|
+
const path2 = issue.path.length > 0 ? issue.path.map(String).join(".") : "(root)";
|
|
31116
|
+
parts.push(`${path2}: ${issue.message}`);
|
|
31039
31117
|
}
|
|
31040
31118
|
const detail = parts.length > 0 ? parts.join("; ") : "unknown validation error";
|
|
31041
31119
|
return new Error(`${toolName}: invalid options \u2014 ${detail}`);
|
|
31042
31120
|
}
|
|
31121
|
+
|
|
31122
|
+
// src/tools/dcm2xml.ts
|
|
31123
|
+
var Dcm2xmlCharset = {
|
|
31124
|
+
/** Use UTF-8 encoding (default). */
|
|
31125
|
+
UTF8: "utf8",
|
|
31126
|
+
/** Use Latin-1 encoding. */
|
|
31127
|
+
LATIN1: "latin1",
|
|
31128
|
+
/** Use ASCII encoding. */
|
|
31129
|
+
ASCII: "ascii"
|
|
31130
|
+
};
|
|
31131
|
+
var Dcm2xmlOptionsSchema = zod.z.object({
|
|
31132
|
+
timeoutMs: zod.z.number().int().positive().optional(),
|
|
31133
|
+
signal: zod.z.instanceof(AbortSignal).optional(),
|
|
31134
|
+
namespace: zod.z.boolean().optional(),
|
|
31135
|
+
charset: zod.z.enum(["utf8", "latin1", "ascii"]).optional(),
|
|
31136
|
+
writeBinaryData: zod.z.boolean().optional(),
|
|
31137
|
+
encodeBinaryBase64: zod.z.boolean().optional()
|
|
31138
|
+
}).strict().optional();
|
|
31139
|
+
function buildArgs(inputPath, options) {
|
|
31140
|
+
const args = [];
|
|
31141
|
+
if (options?.namespace === true) {
|
|
31142
|
+
args.push("+Xn");
|
|
31143
|
+
}
|
|
31144
|
+
if (options?.charset === "latin1") {
|
|
31145
|
+
args.push("+Cl");
|
|
31146
|
+
} else if (options?.charset === "ascii") {
|
|
31147
|
+
args.push("+Ca");
|
|
31148
|
+
}
|
|
31149
|
+
if (options?.writeBinaryData === true) {
|
|
31150
|
+
args.push("+Wb");
|
|
31151
|
+
if (options.encodeBinaryBase64 !== false) {
|
|
31152
|
+
args.push("+Eb");
|
|
31153
|
+
}
|
|
31154
|
+
}
|
|
31155
|
+
args.push(inputPath);
|
|
31156
|
+
return args;
|
|
31157
|
+
}
|
|
31158
|
+
async function dcm2xml(inputPath, options) {
|
|
31159
|
+
const validation = Dcm2xmlOptionsSchema.safeParse(options);
|
|
31160
|
+
if (!validation.success) {
|
|
31161
|
+
return err(createValidationError("dcm2xml", validation.error));
|
|
31162
|
+
}
|
|
31163
|
+
const binaryResult = resolveBinary("dcm2xml");
|
|
31164
|
+
if (!binaryResult.ok) {
|
|
31165
|
+
return err(binaryResult.error);
|
|
31166
|
+
}
|
|
31167
|
+
const args = buildArgs(inputPath, options);
|
|
31168
|
+
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
31169
|
+
const result = await execCommand(binaryResult.value, args, {
|
|
31170
|
+
timeoutMs,
|
|
31171
|
+
signal: options?.signal
|
|
31172
|
+
});
|
|
31173
|
+
if (!result.ok) {
|
|
31174
|
+
return err(result.error);
|
|
31175
|
+
}
|
|
31176
|
+
if (result.value.exitCode !== 0) {
|
|
31177
|
+
return err(createToolError("dcm2xml", args, result.value.exitCode, result.value.stderr));
|
|
31178
|
+
}
|
|
31179
|
+
return ok({ xml: result.value.stdout });
|
|
31180
|
+
}
|
|
31043
31181
|
var PN_REPS = ["Alphabetic", "Ideographic", "Phonetic"];
|
|
31044
31182
|
var ARRAY_TAG_NAMES = /* @__PURE__ */ new Set(["DicomAttribute", "Value", "PersonName", "Item"]);
|
|
31045
31183
|
var KNOWN_VR_CODES = /* @__PURE__ */ new Set([
|
|
@@ -31175,9 +31313,9 @@ function convertAttributes(obj) {
|
|
|
31175
31313
|
for (const attr of attrs) {
|
|
31176
31314
|
if (typeof attr !== "object" || attr === null) continue;
|
|
31177
31315
|
const xmlAttr = attr;
|
|
31178
|
-
const
|
|
31179
|
-
if (
|
|
31180
|
-
result[
|
|
31316
|
+
const tag2 = xmlAttr["@_tag"];
|
|
31317
|
+
if (tag2 === void 0) continue;
|
|
31318
|
+
result[tag2] = convertElement(xmlAttr);
|
|
31181
31319
|
}
|
|
31182
31320
|
return result;
|
|
31183
31321
|
}
|
|
@@ -31300,263 +31438,6 @@ async function dcm2json(inputPath, options) {
|
|
|
31300
31438
|
}
|
|
31301
31439
|
return tryDirectPath(inputPath, timeoutMs, signal);
|
|
31302
31440
|
}
|
|
31303
|
-
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+\])?)*)?$/;
|
|
31304
|
-
var TagModificationSchema = zod.z.object({
|
|
31305
|
-
tag: zod.z.string().regex(TAG_OR_PATH_PATTERN),
|
|
31306
|
-
value: zod.z.string()
|
|
31307
|
-
});
|
|
31308
|
-
var DcmodifyOptionsSchema = zod.z.object({
|
|
31309
|
-
timeoutMs: zod.z.number().int().positive().optional(),
|
|
31310
|
-
signal: zod.z.instanceof(AbortSignal).optional(),
|
|
31311
|
-
modifications: zod.z.array(TagModificationSchema).optional().default([]),
|
|
31312
|
-
erasures: zod.z.array(zod.z.string()).optional(),
|
|
31313
|
-
erasePrivateTags: zod.z.boolean().optional(),
|
|
31314
|
-
noBackup: zod.z.boolean().optional(),
|
|
31315
|
-
insertIfMissing: zod.z.boolean().optional()
|
|
31316
|
-
}).strict().refine((data) => data.modifications.length > 0 || data.erasures !== void 0 && data.erasures.length > 0 || data.erasePrivateTags === true, {
|
|
31317
|
-
message: "At least one of modifications, erasures, or erasePrivateTags is required"
|
|
31318
|
-
});
|
|
31319
|
-
function buildArgs(inputPath, options) {
|
|
31320
|
-
const args = [];
|
|
31321
|
-
if (options.noBackup !== false) {
|
|
31322
|
-
args.push("-nb");
|
|
31323
|
-
}
|
|
31324
|
-
const flag = options.insertIfMissing === true ? "-i" : "-m";
|
|
31325
|
-
const modifications = options.modifications ?? [];
|
|
31326
|
-
for (const mod of modifications) {
|
|
31327
|
-
args.push(flag, `${mod.tag}=${mod.value}`);
|
|
31328
|
-
}
|
|
31329
|
-
if (options.erasures !== void 0) {
|
|
31330
|
-
for (const erasure of options.erasures) {
|
|
31331
|
-
args.push("-e", erasure);
|
|
31332
|
-
}
|
|
31333
|
-
}
|
|
31334
|
-
if (options.erasePrivateTags === true) {
|
|
31335
|
-
args.push("-ep");
|
|
31336
|
-
}
|
|
31337
|
-
args.push(inputPath);
|
|
31338
|
-
return args;
|
|
31339
|
-
}
|
|
31340
|
-
async function dcmodify(inputPath, options) {
|
|
31341
|
-
const validation = DcmodifyOptionsSchema.safeParse(options);
|
|
31342
|
-
if (!validation.success) {
|
|
31343
|
-
return err(new Error(`dcmodify: invalid options: ${validation.error.message}`));
|
|
31344
|
-
}
|
|
31345
|
-
const binaryResult = resolveBinary("dcmodify");
|
|
31346
|
-
if (!binaryResult.ok) {
|
|
31347
|
-
return err(binaryResult.error);
|
|
31348
|
-
}
|
|
31349
|
-
const args = buildArgs(inputPath, options);
|
|
31350
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
31351
|
-
const result = await spawnCommand(binaryResult.value, args, {
|
|
31352
|
-
timeoutMs,
|
|
31353
|
-
signal: options.signal
|
|
31354
|
-
});
|
|
31355
|
-
if (!result.ok) {
|
|
31356
|
-
return err(result.error);
|
|
31357
|
-
}
|
|
31358
|
-
if (result.value.exitCode !== 0) {
|
|
31359
|
-
return err(createToolError("dcmodify", args, result.value.exitCode, result.value.stderr));
|
|
31360
|
-
}
|
|
31361
|
-
return ok({ filePath: inputPath });
|
|
31362
|
-
}
|
|
31363
|
-
|
|
31364
|
-
// src/dicom/DicomFile.ts
|
|
31365
|
-
async function applyModifications(filePath, changeset, options) {
|
|
31366
|
-
const modifications = changeset.toModifications();
|
|
31367
|
-
const erasures = changeset.toErasureArgs();
|
|
31368
|
-
const result = await dcmodify(filePath, {
|
|
31369
|
-
modifications: modifications.length > 0 ? modifications : void 0,
|
|
31370
|
-
erasures: erasures.length > 0 ? erasures : void 0,
|
|
31371
|
-
erasePrivateTags: changeset.erasePrivate || void 0,
|
|
31372
|
-
insertIfMissing: true,
|
|
31373
|
-
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
31374
|
-
signal: options.signal
|
|
31375
|
-
});
|
|
31376
|
-
if (!result.ok) return err(result.error);
|
|
31377
|
-
return ok(void 0);
|
|
31378
|
-
}
|
|
31379
|
-
async function copyFileSafe(source, dest) {
|
|
31380
|
-
return stderrLib.tryCatch(
|
|
31381
|
-
() => promises.copyFile(source, dest),
|
|
31382
|
-
(e) => new Error(`Failed to copy file: ${e.message}`)
|
|
31383
|
-
);
|
|
31384
|
-
}
|
|
31385
|
-
async function statFileSize(path) {
|
|
31386
|
-
return stderrLib.tryCatch(
|
|
31387
|
-
async () => (await promises.stat(path)).size,
|
|
31388
|
-
(e) => new Error(`Failed to stat file: ${e.message}`)
|
|
31389
|
-
);
|
|
31390
|
-
}
|
|
31391
|
-
async function unlinkFile(path) {
|
|
31392
|
-
return stderrLib.tryCatch(
|
|
31393
|
-
() => promises.unlink(path),
|
|
31394
|
-
(e) => new Error(`Failed to delete file: ${e.message}`)
|
|
31395
|
-
);
|
|
31396
|
-
}
|
|
31397
|
-
var DicomFile = class _DicomFile {
|
|
31398
|
-
constructor(dataset, filePath, changes) {
|
|
31399
|
-
/** The immutable DICOM dataset read from the file. */
|
|
31400
|
-
__publicField(this, "dataset");
|
|
31401
|
-
/** The branded file path. */
|
|
31402
|
-
__publicField(this, "filePath");
|
|
31403
|
-
/** The accumulated pending changes. */
|
|
31404
|
-
__publicField(this, "changes");
|
|
31405
|
-
this.dataset = dataset;
|
|
31406
|
-
this.filePath = filePath;
|
|
31407
|
-
this.changes = changes;
|
|
31408
|
-
}
|
|
31409
|
-
/**
|
|
31410
|
-
* Opens a DICOM file and reads its dataset.
|
|
31411
|
-
*
|
|
31412
|
-
* @param path - Filesystem path to the DICOM file
|
|
31413
|
-
* @param options - Timeout and abort options
|
|
31414
|
-
* @returns A Result containing the DicomFile or an error
|
|
31415
|
-
*/
|
|
31416
|
-
static async open(path, options) {
|
|
31417
|
-
const filePathResult = createDicomFilePath(path);
|
|
31418
|
-
if (!filePathResult.ok) return err(filePathResult.error);
|
|
31419
|
-
const jsonResult = await dcm2json(path, {
|
|
31420
|
-
timeoutMs: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
31421
|
-
signal: options?.signal
|
|
31422
|
-
});
|
|
31423
|
-
if (!jsonResult.ok) return err(jsonResult.error);
|
|
31424
|
-
const datasetResult = DicomDataset.fromJson(jsonResult.value.data);
|
|
31425
|
-
if (!datasetResult.ok) return err(datasetResult.error);
|
|
31426
|
-
return ok(new _DicomFile(datasetResult.value, filePathResult.value, ChangeSet.empty()));
|
|
31427
|
-
}
|
|
31428
|
-
/**
|
|
31429
|
-
* Returns a new DicomFile with the given changes merged into the pending changes.
|
|
31430
|
-
*
|
|
31431
|
-
* @param changes - A ChangeSet to merge with existing pending changes
|
|
31432
|
-
* @returns A new DicomFile with accumulated changes
|
|
31433
|
-
*/
|
|
31434
|
-
withChanges(changes) {
|
|
31435
|
-
return new _DicomFile(this.dataset, this.filePath, this.changes.merge(changes));
|
|
31436
|
-
}
|
|
31437
|
-
/**
|
|
31438
|
-
* Returns a new DicomFile with a different file path.
|
|
31439
|
-
*
|
|
31440
|
-
* Preserves the dataset and pending changes.
|
|
31441
|
-
*
|
|
31442
|
-
* @param newPath - The new branded file path
|
|
31443
|
-
* @returns A new DicomFile pointing to the new path
|
|
31444
|
-
*/
|
|
31445
|
-
withFilePath(newPath) {
|
|
31446
|
-
return new _DicomFile(this.dataset, newPath, this.changes);
|
|
31447
|
-
}
|
|
31448
|
-
/**
|
|
31449
|
-
* Applies pending changes to the file in-place using dcmodify.
|
|
31450
|
-
*
|
|
31451
|
-
* If there are no pending changes, this is a no-op that returns success.
|
|
31452
|
-
* After applying, the dataset is NOT refreshed — call {@link DicomFile.open}
|
|
31453
|
-
* again if you need fresh data.
|
|
31454
|
-
*
|
|
31455
|
-
* @param options - Timeout and abort options
|
|
31456
|
-
* @returns A Result indicating success or failure
|
|
31457
|
-
*/
|
|
31458
|
-
async applyChanges(options) {
|
|
31459
|
-
if (this.changes.isEmpty) return ok(void 0);
|
|
31460
|
-
return applyModifications(this.filePath, this.changes, options ?? {});
|
|
31461
|
-
}
|
|
31462
|
-
/**
|
|
31463
|
-
* Copies the file to a new path and applies pending changes to the copy.
|
|
31464
|
-
*
|
|
31465
|
-
* If there are no pending changes, only the copy is performed.
|
|
31466
|
-
* On dcmodify failure, the copy is cleaned up.
|
|
31467
|
-
*
|
|
31468
|
-
* @param outputPath - Destination filesystem path
|
|
31469
|
-
* @param options - Timeout and abort options
|
|
31470
|
-
* @returns A Result containing the branded output path or an error
|
|
31471
|
-
*/
|
|
31472
|
-
async writeAs(outputPath, options) {
|
|
31473
|
-
const outPathResult = createDicomFilePath(outputPath);
|
|
31474
|
-
if (!outPathResult.ok) return err(outPathResult.error);
|
|
31475
|
-
const copyResult = await copyFileSafe(this.filePath, outputPath);
|
|
31476
|
-
if (!copyResult.ok) return err(copyResult.error);
|
|
31477
|
-
if (this.changes.isEmpty) return ok(outPathResult.value);
|
|
31478
|
-
const applyResult = await applyModifications(outPathResult.value, this.changes, options ?? {});
|
|
31479
|
-
if (!applyResult.ok) {
|
|
31480
|
-
await unlinkFile(outputPath);
|
|
31481
|
-
return err(applyResult.error);
|
|
31482
|
-
}
|
|
31483
|
-
return ok(outPathResult.value);
|
|
31484
|
-
}
|
|
31485
|
-
/**
|
|
31486
|
-
* Gets the file size in bytes.
|
|
31487
|
-
*
|
|
31488
|
-
* @returns A Result containing the size or an error
|
|
31489
|
-
*/
|
|
31490
|
-
async fileSize() {
|
|
31491
|
-
return statFileSize(this.filePath);
|
|
31492
|
-
}
|
|
31493
|
-
/**
|
|
31494
|
-
* Deletes the file from the filesystem.
|
|
31495
|
-
*
|
|
31496
|
-
* @returns A Result indicating success or failure
|
|
31497
|
-
*/
|
|
31498
|
-
async unlink() {
|
|
31499
|
-
return unlinkFile(this.filePath);
|
|
31500
|
-
}
|
|
31501
|
-
};
|
|
31502
|
-
var Dcm2xmlCharset = {
|
|
31503
|
-
/** Use UTF-8 encoding (default). */
|
|
31504
|
-
UTF8: "utf8",
|
|
31505
|
-
/** Use Latin-1 encoding. */
|
|
31506
|
-
LATIN1: "latin1",
|
|
31507
|
-
/** Use ASCII encoding. */
|
|
31508
|
-
ASCII: "ascii"
|
|
31509
|
-
};
|
|
31510
|
-
var Dcm2xmlOptionsSchema = zod.z.object({
|
|
31511
|
-
timeoutMs: zod.z.number().int().positive().optional(),
|
|
31512
|
-
signal: zod.z.instanceof(AbortSignal).optional(),
|
|
31513
|
-
namespace: zod.z.boolean().optional(),
|
|
31514
|
-
charset: zod.z.enum(["utf8", "latin1", "ascii"]).optional(),
|
|
31515
|
-
writeBinaryData: zod.z.boolean().optional(),
|
|
31516
|
-
encodeBinaryBase64: zod.z.boolean().optional()
|
|
31517
|
-
}).strict().optional();
|
|
31518
|
-
function buildArgs2(inputPath, options) {
|
|
31519
|
-
const args = [];
|
|
31520
|
-
if (options?.namespace === true) {
|
|
31521
|
-
args.push("+Xn");
|
|
31522
|
-
}
|
|
31523
|
-
if (options?.charset === "latin1") {
|
|
31524
|
-
args.push("+Cl");
|
|
31525
|
-
} else if (options?.charset === "ascii") {
|
|
31526
|
-
args.push("+Ca");
|
|
31527
|
-
}
|
|
31528
|
-
if (options?.writeBinaryData === true) {
|
|
31529
|
-
args.push("+Wb");
|
|
31530
|
-
if (options.encodeBinaryBase64 !== false) {
|
|
31531
|
-
args.push("+Eb");
|
|
31532
|
-
}
|
|
31533
|
-
}
|
|
31534
|
-
args.push(inputPath);
|
|
31535
|
-
return args;
|
|
31536
|
-
}
|
|
31537
|
-
async function dcm2xml(inputPath, options) {
|
|
31538
|
-
const validation = Dcm2xmlOptionsSchema.safeParse(options);
|
|
31539
|
-
if (!validation.success) {
|
|
31540
|
-
return err(new Error(`dcm2xml: invalid options: ${validation.error.message}`));
|
|
31541
|
-
}
|
|
31542
|
-
const binaryResult = resolveBinary("dcm2xml");
|
|
31543
|
-
if (!binaryResult.ok) {
|
|
31544
|
-
return err(binaryResult.error);
|
|
31545
|
-
}
|
|
31546
|
-
const args = buildArgs2(inputPath, options);
|
|
31547
|
-
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
31548
|
-
const result = await execCommand(binaryResult.value, args, {
|
|
31549
|
-
timeoutMs,
|
|
31550
|
-
signal: options?.signal
|
|
31551
|
-
});
|
|
31552
|
-
if (!result.ok) {
|
|
31553
|
-
return err(result.error);
|
|
31554
|
-
}
|
|
31555
|
-
if (result.value.exitCode !== 0) {
|
|
31556
|
-
return err(createToolError("dcm2xml", args, result.value.exitCode, result.value.stderr));
|
|
31557
|
-
}
|
|
31558
|
-
return ok({ xml: result.value.stdout });
|
|
31559
|
-
}
|
|
31560
31441
|
var DcmdumpFormat = {
|
|
31561
31442
|
/** Print standard DCMTK format. */
|
|
31562
31443
|
STANDARD: "standard",
|
|
@@ -31571,7 +31452,7 @@ var DcmdumpOptionsSchema = zod.z.object({
|
|
|
31571
31452
|
searchTag: zod.z.string().regex(/^\([0-9A-Fa-f]{4},[0-9A-Fa-f]{4}\)$/).optional(),
|
|
31572
31453
|
printValues: zod.z.boolean().optional()
|
|
31573
31454
|
}).strict().optional();
|
|
31574
|
-
function
|
|
31455
|
+
function buildArgs2(inputPath, options) {
|
|
31575
31456
|
const args = [];
|
|
31576
31457
|
if (options?.format === "short") {
|
|
31577
31458
|
args.push("+L");
|
|
@@ -31580,8 +31461,8 @@ function buildArgs3(inputPath, options) {
|
|
|
31580
31461
|
args.push("+P", "all");
|
|
31581
31462
|
}
|
|
31582
31463
|
if (options?.searchTag !== void 0) {
|
|
31583
|
-
const
|
|
31584
|
-
args.push("+P",
|
|
31464
|
+
const tag2 = options.searchTag.replace(/[()]/g, "");
|
|
31465
|
+
args.push("+P", tag2);
|
|
31585
31466
|
}
|
|
31586
31467
|
if (options?.printValues === true) {
|
|
31587
31468
|
args.push("+Vr");
|
|
@@ -31592,13 +31473,13 @@ function buildArgs3(inputPath, options) {
|
|
|
31592
31473
|
async function dcmdump(inputPath, options) {
|
|
31593
31474
|
const validation = DcmdumpOptionsSchema.safeParse(options);
|
|
31594
31475
|
if (!validation.success) {
|
|
31595
|
-
return err(
|
|
31476
|
+
return err(createValidationError("dcmdump", validation.error));
|
|
31596
31477
|
}
|
|
31597
31478
|
const binaryResult = resolveBinary("dcmdump");
|
|
31598
31479
|
if (!binaryResult.ok) {
|
|
31599
31480
|
return err(binaryResult.error);
|
|
31600
31481
|
}
|
|
31601
|
-
const args =
|
|
31482
|
+
const args = buildArgs2(inputPath, options);
|
|
31602
31483
|
const timeoutMs = options?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
31603
31484
|
const result = await execCommand(binaryResult.value, args, {
|
|
31604
31485
|
timeoutMs,
|
|
@@ -31637,7 +31518,7 @@ var DcmconvOptionsSchema = zod.z.object({
|
|
|
31637
31518
|
async function dcmconv(inputPath, outputPath, options) {
|
|
31638
31519
|
const validation = DcmconvOptionsSchema.safeParse(options);
|
|
31639
31520
|
if (!validation.success) {
|
|
31640
|
-
return err(
|
|
31521
|
+
return err(createValidationError("dcmconv", validation.error));
|
|
31641
31522
|
}
|
|
31642
31523
|
const binaryResult = resolveBinary("dcmconv");
|
|
31643
31524
|
if (!binaryResult.ok) {
|
|
@@ -31657,6 +31538,70 @@ async function dcmconv(inputPath, outputPath, options) {
|
|
|
31657
31538
|
}
|
|
31658
31539
|
return ok({ outputPath });
|
|
31659
31540
|
}
|
|
31541
|
+
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+\])?)*)?$/;
|
|
31542
|
+
var TagModificationSchema = zod.z.object({
|
|
31543
|
+
tag: zod.z.string().regex(TAG_OR_PATH_PATTERN),
|
|
31544
|
+
value: zod.z.string()
|
|
31545
|
+
});
|
|
31546
|
+
var DcmodifyOptionsSchema = zod.z.object({
|
|
31547
|
+
timeoutMs: zod.z.number().int().positive().optional(),
|
|
31548
|
+
signal: zod.z.instanceof(AbortSignal).optional(),
|
|
31549
|
+
modifications: zod.z.array(TagModificationSchema).optional().default([]),
|
|
31550
|
+
erasures: zod.z.array(zod.z.string()).optional(),
|
|
31551
|
+
erasePrivateTags: zod.z.boolean().optional(),
|
|
31552
|
+
noBackup: zod.z.boolean().optional(),
|
|
31553
|
+
insertIfMissing: zod.z.boolean().optional(),
|
|
31554
|
+
ignoreMissingTags: zod.z.boolean().optional()
|
|
31555
|
+
}).strict().refine((data) => data.modifications.length > 0 || data.erasures !== void 0 && data.erasures.length > 0 || data.erasePrivateTags === true, {
|
|
31556
|
+
message: "At least one of modifications, erasures, or erasePrivateTags is required"
|
|
31557
|
+
});
|
|
31558
|
+
function buildArgs3(inputPath, options) {
|
|
31559
|
+
const args = [];
|
|
31560
|
+
if (options.noBackup !== false) {
|
|
31561
|
+
args.push("-nb");
|
|
31562
|
+
}
|
|
31563
|
+
if (options.ignoreMissingTags === true) {
|
|
31564
|
+
args.push("-imt");
|
|
31565
|
+
}
|
|
31566
|
+
const flag = options.insertIfMissing === true ? "-i" : "-m";
|
|
31567
|
+
const modifications = options.modifications ?? [];
|
|
31568
|
+
for (const mod of modifications) {
|
|
31569
|
+
args.push(flag, `${mod.tag}=${mod.value}`);
|
|
31570
|
+
}
|
|
31571
|
+
if (options.erasures !== void 0) {
|
|
31572
|
+
for (const erasure of options.erasures) {
|
|
31573
|
+
args.push("-e", erasure);
|
|
31574
|
+
}
|
|
31575
|
+
}
|
|
31576
|
+
if (options.erasePrivateTags === true) {
|
|
31577
|
+
args.push("-ep");
|
|
31578
|
+
}
|
|
31579
|
+
args.push(inputPath);
|
|
31580
|
+
return args;
|
|
31581
|
+
}
|
|
31582
|
+
async function dcmodify(inputPath, options) {
|
|
31583
|
+
const validation = DcmodifyOptionsSchema.safeParse(options);
|
|
31584
|
+
if (!validation.success) {
|
|
31585
|
+
return err(createValidationError("dcmodify", validation.error));
|
|
31586
|
+
}
|
|
31587
|
+
const binaryResult = resolveBinary("dcmodify");
|
|
31588
|
+
if (!binaryResult.ok) {
|
|
31589
|
+
return err(binaryResult.error);
|
|
31590
|
+
}
|
|
31591
|
+
const args = buildArgs3(inputPath, options);
|
|
31592
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
31593
|
+
const result = await spawnCommand(binaryResult.value, args, {
|
|
31594
|
+
timeoutMs,
|
|
31595
|
+
signal: options.signal
|
|
31596
|
+
});
|
|
31597
|
+
if (!result.ok) {
|
|
31598
|
+
return err(result.error);
|
|
31599
|
+
}
|
|
31600
|
+
if (result.value.exitCode !== 0) {
|
|
31601
|
+
return err(createToolError("dcmodify", args, result.value.exitCode, result.value.stderr));
|
|
31602
|
+
}
|
|
31603
|
+
return ok({ filePath: inputPath });
|
|
31604
|
+
}
|
|
31660
31605
|
var DcmftestOptionsSchema = zod.z.object({
|
|
31661
31606
|
timeoutMs: zod.z.number().int().positive().optional(),
|
|
31662
31607
|
signal: zod.z.instanceof(AbortSignal).optional()
|
|
@@ -31664,7 +31609,7 @@ var DcmftestOptionsSchema = zod.z.object({
|
|
|
31664
31609
|
async function dcmftest(inputPath, options) {
|
|
31665
31610
|
const validation = DcmftestOptionsSchema.safeParse(options);
|
|
31666
31611
|
if (!validation.success) {
|
|
31667
|
-
return err(
|
|
31612
|
+
return err(createValidationError("dcmftest", validation.error));
|
|
31668
31613
|
}
|
|
31669
31614
|
const binaryResult = resolveBinary("dcmftest");
|
|
31670
31615
|
if (!binaryResult.ok) {
|
|
@@ -31719,7 +31664,7 @@ function buildArgs4(options) {
|
|
|
31719
31664
|
async function dcmgpdir(options) {
|
|
31720
31665
|
const validation = DcmgpdirOptionsSchema.safeParse(options);
|
|
31721
31666
|
if (!validation.success) {
|
|
31722
|
-
return err(
|
|
31667
|
+
return err(createValidationError("dcmgpdir", validation.error));
|
|
31723
31668
|
}
|
|
31724
31669
|
const binaryResult = resolveBinary("dcmgpdir");
|
|
31725
31670
|
if (!binaryResult.ok) {
|
|
@@ -31778,7 +31723,7 @@ function buildArgs5(options) {
|
|
|
31778
31723
|
async function dcmmkdir(options) {
|
|
31779
31724
|
const validation = DcmmkdirOptionsSchema.safeParse(options);
|
|
31780
31725
|
if (!validation.success) {
|
|
31781
|
-
return err(
|
|
31726
|
+
return err(createValidationError("dcmmkdir", validation.error));
|
|
31782
31727
|
}
|
|
31783
31728
|
const binaryResult = resolveBinary("dcmmkdir");
|
|
31784
31729
|
if (!binaryResult.ok) {
|
|
@@ -31826,7 +31771,7 @@ function buildArgs6(options) {
|
|
|
31826
31771
|
async function dcmqridx(options) {
|
|
31827
31772
|
const validation = DcmqridxOptionsSchema.safeParse(options);
|
|
31828
31773
|
if (!validation.success) {
|
|
31829
|
-
return err(
|
|
31774
|
+
return err(createValidationError("dcmqridx", validation.error));
|
|
31830
31775
|
}
|
|
31831
31776
|
const binaryResult = resolveBinary("dcmqridx");
|
|
31832
31777
|
if (!binaryResult.ok) {
|
|
@@ -31869,7 +31814,7 @@ function buildArgs7(inputPath, outputPath, options) {
|
|
|
31869
31814
|
async function xml2dcm(inputPath, outputPath, options) {
|
|
31870
31815
|
const validation = Xml2dcmOptionsSchema.safeParse(options);
|
|
31871
31816
|
if (!validation.success) {
|
|
31872
|
-
return err(
|
|
31817
|
+
return err(createValidationError("xml2dcm", validation.error));
|
|
31873
31818
|
}
|
|
31874
31819
|
const binaryResult = resolveBinary("xml2dcm");
|
|
31875
31820
|
if (!binaryResult.ok) {
|
|
@@ -31896,7 +31841,7 @@ var Json2dcmOptionsSchema = zod.z.object({
|
|
|
31896
31841
|
async function json2dcm(inputPath, outputPath, options) {
|
|
31897
31842
|
const validation = Json2dcmOptionsSchema.safeParse(options);
|
|
31898
31843
|
if (!validation.success) {
|
|
31899
|
-
return err(
|
|
31844
|
+
return err(createValidationError("json2dcm", validation.error));
|
|
31900
31845
|
}
|
|
31901
31846
|
const binaryResult = resolveBinary("json2dcm");
|
|
31902
31847
|
if (!binaryResult.ok) {
|
|
@@ -31936,7 +31881,7 @@ function buildArgs8(inputPath, outputPath, options) {
|
|
|
31936
31881
|
async function dump2dcm(inputPath, outputPath, options) {
|
|
31937
31882
|
const validation = Dump2dcmOptionsSchema.safeParse(options);
|
|
31938
31883
|
if (!validation.success) {
|
|
31939
|
-
return err(
|
|
31884
|
+
return err(createValidationError("dump2dcm", validation.error));
|
|
31940
31885
|
}
|
|
31941
31886
|
const binaryResult = resolveBinary("dump2dcm");
|
|
31942
31887
|
if (!binaryResult.ok) {
|
|
@@ -31986,7 +31931,7 @@ function buildArgs9(inputPath, outputPath, options) {
|
|
|
31986
31931
|
async function img2dcm(inputPath, outputPath, options) {
|
|
31987
31932
|
const validation = Img2dcmOptionsSchema.safeParse(options);
|
|
31988
31933
|
if (!validation.success) {
|
|
31989
|
-
return err(
|
|
31934
|
+
return err(createValidationError("img2dcm", validation.error));
|
|
31990
31935
|
}
|
|
31991
31936
|
const binaryResult = resolveBinary("img2dcm");
|
|
31992
31937
|
if (!binaryResult.ok) {
|
|
@@ -32013,7 +31958,7 @@ var Pdf2dcmOptionsSchema = zod.z.object({
|
|
|
32013
31958
|
async function pdf2dcm(inputPath, outputPath, options) {
|
|
32014
31959
|
const validation = Pdf2dcmOptionsSchema.safeParse(options);
|
|
32015
31960
|
if (!validation.success) {
|
|
32016
|
-
return err(
|
|
31961
|
+
return err(createValidationError("pdf2dcm", validation.error));
|
|
32017
31962
|
}
|
|
32018
31963
|
const binaryResult = resolveBinary("pdf2dcm");
|
|
32019
31964
|
if (!binaryResult.ok) {
|
|
@@ -32040,7 +31985,7 @@ var Dcm2pdfOptionsSchema = zod.z.object({
|
|
|
32040
31985
|
async function dcm2pdf(inputPath, outputPath, options) {
|
|
32041
31986
|
const validation = Dcm2pdfOptionsSchema.safeParse(options);
|
|
32042
31987
|
if (!validation.success) {
|
|
32043
|
-
return err(
|
|
31988
|
+
return err(createValidationError("dcm2pdf", validation.error));
|
|
32044
31989
|
}
|
|
32045
31990
|
const binaryResult = resolveBinary("dcm2pdf");
|
|
32046
31991
|
if (!binaryResult.ok) {
|
|
@@ -32067,7 +32012,7 @@ var Cda2dcmOptionsSchema = zod.z.object({
|
|
|
32067
32012
|
async function cda2dcm(inputPath, outputPath, options) {
|
|
32068
32013
|
const validation = Cda2dcmOptionsSchema.safeParse(options);
|
|
32069
32014
|
if (!validation.success) {
|
|
32070
|
-
return err(
|
|
32015
|
+
return err(createValidationError("cda2dcm", validation.error));
|
|
32071
32016
|
}
|
|
32072
32017
|
const binaryResult = resolveBinary("cda2dcm");
|
|
32073
32018
|
if (!binaryResult.ok) {
|
|
@@ -32094,7 +32039,7 @@ var Dcm2cdaOptionsSchema = zod.z.object({
|
|
|
32094
32039
|
async function dcm2cda(inputPath, outputPath, options) {
|
|
32095
32040
|
const validation = Dcm2cdaOptionsSchema.safeParse(options);
|
|
32096
32041
|
if (!validation.success) {
|
|
32097
|
-
return err(
|
|
32042
|
+
return err(createValidationError("dcm2cda", validation.error));
|
|
32098
32043
|
}
|
|
32099
32044
|
const binaryResult = resolveBinary("dcm2cda");
|
|
32100
32045
|
if (!binaryResult.ok) {
|
|
@@ -32121,7 +32066,7 @@ var Stl2dcmOptionsSchema = zod.z.object({
|
|
|
32121
32066
|
async function stl2dcm(inputPath, outputPath, options) {
|
|
32122
32067
|
const validation = Stl2dcmOptionsSchema.safeParse(options);
|
|
32123
32068
|
if (!validation.success) {
|
|
32124
|
-
return err(
|
|
32069
|
+
return err(createValidationError("stl2dcm", validation.error));
|
|
32125
32070
|
}
|
|
32126
32071
|
const binaryResult = resolveBinary("stl2dcm");
|
|
32127
32072
|
if (!binaryResult.ok) {
|
|
@@ -32157,7 +32102,7 @@ function buildArgs10(inputPath, outputPath, options) {
|
|
|
32157
32102
|
async function dcmcrle(inputPath, outputPath, options) {
|
|
32158
32103
|
const validation = DcmcrleOptionsSchema.safeParse(options);
|
|
32159
32104
|
if (!validation.success) {
|
|
32160
|
-
return err(
|
|
32105
|
+
return err(createValidationError("dcmcrle", validation.error));
|
|
32161
32106
|
}
|
|
32162
32107
|
const binaryResult = resolveBinary("dcmcrle");
|
|
32163
32108
|
if (!binaryResult.ok) {
|
|
@@ -32193,7 +32138,7 @@ function buildArgs11(inputPath, outputPath, options) {
|
|
|
32193
32138
|
async function dcmdrle(inputPath, outputPath, options) {
|
|
32194
32139
|
const validation = DcmdrleOptionsSchema.safeParse(options);
|
|
32195
32140
|
if (!validation.success) {
|
|
32196
|
-
return err(
|
|
32141
|
+
return err(createValidationError("dcmdrle", validation.error));
|
|
32197
32142
|
}
|
|
32198
32143
|
const binaryResult = resolveBinary("dcmdrle");
|
|
32199
32144
|
if (!binaryResult.ok) {
|
|
@@ -32229,7 +32174,7 @@ function buildArgs12(inputPath, outputPath, options) {
|
|
|
32229
32174
|
async function dcmencap(inputPath, outputPath, options) {
|
|
32230
32175
|
const validation = DcmencapOptionsSchema.safeParse(options);
|
|
32231
32176
|
if (!validation.success) {
|
|
32232
|
-
return err(
|
|
32177
|
+
return err(createValidationError("dcmencap", validation.error));
|
|
32233
32178
|
}
|
|
32234
32179
|
const binaryResult = resolveBinary("dcmencap");
|
|
32235
32180
|
if (!binaryResult.ok) {
|
|
@@ -32256,7 +32201,7 @@ var DcmdecapOptionsSchema = zod.z.object({
|
|
|
32256
32201
|
async function dcmdecap(inputPath, outputPath, options) {
|
|
32257
32202
|
const validation = DcmdecapOptionsSchema.safeParse(options);
|
|
32258
32203
|
if (!validation.success) {
|
|
32259
|
-
return err(
|
|
32204
|
+
return err(createValidationError("dcmdecap", validation.error));
|
|
32260
32205
|
}
|
|
32261
32206
|
const binaryResult = resolveBinary("dcmdecap");
|
|
32262
32207
|
if (!binaryResult.ok) {
|
|
@@ -32296,7 +32241,7 @@ function buildArgs13(inputPath, outputPath, options) {
|
|
|
32296
32241
|
async function dcmcjpeg(inputPath, outputPath, options) {
|
|
32297
32242
|
const validation = DcmcjpegOptionsSchema.safeParse(options);
|
|
32298
32243
|
if (!validation.success) {
|
|
32299
|
-
return err(
|
|
32244
|
+
return err(createValidationError("dcmcjpeg", validation.error));
|
|
32300
32245
|
}
|
|
32301
32246
|
const binaryResult = resolveBinary("dcmcjpeg");
|
|
32302
32247
|
if (!binaryResult.ok) {
|
|
@@ -32345,7 +32290,7 @@ function buildArgs14(inputPath, outputPath, options) {
|
|
|
32345
32290
|
async function dcmdjpeg(inputPath, outputPath, options) {
|
|
32346
32291
|
const validation = DcmdjpegOptionsSchema.safeParse(options);
|
|
32347
32292
|
if (!validation.success) {
|
|
32348
|
-
return err(
|
|
32293
|
+
return err(createValidationError("dcmdjpeg", validation.error));
|
|
32349
32294
|
}
|
|
32350
32295
|
const binaryResult = resolveBinary("dcmdjpeg");
|
|
32351
32296
|
if (!binaryResult.ok) {
|
|
@@ -32387,7 +32332,7 @@ function buildArgs15(inputPath, outputPath, options) {
|
|
|
32387
32332
|
async function dcmcjpls(inputPath, outputPath, options) {
|
|
32388
32333
|
const validation = DcmcjplsOptionsSchema.safeParse(options);
|
|
32389
32334
|
if (!validation.success) {
|
|
32390
|
-
return err(
|
|
32335
|
+
return err(createValidationError("dcmcjpls", validation.error));
|
|
32391
32336
|
}
|
|
32392
32337
|
const binaryResult = resolveBinary("dcmcjpls");
|
|
32393
32338
|
if (!binaryResult.ok) {
|
|
@@ -32436,7 +32381,7 @@ function buildArgs16(inputPath, outputPath, options) {
|
|
|
32436
32381
|
async function dcmdjpls(inputPath, outputPath, options) {
|
|
32437
32382
|
const validation = DcmdjplsOptionsSchema.safeParse(options);
|
|
32438
32383
|
if (!validation.success) {
|
|
32439
|
-
return err(
|
|
32384
|
+
return err(createValidationError("dcmdjpls", validation.error));
|
|
32440
32385
|
}
|
|
32441
32386
|
const binaryResult = resolveBinary("dcmdjpls");
|
|
32442
32387
|
if (!binaryResult.ok) {
|
|
@@ -32495,7 +32440,7 @@ function buildArgs17(inputPath, outputPath, options) {
|
|
|
32495
32440
|
async function dcmj2pnm(inputPath, outputPath, options) {
|
|
32496
32441
|
const validation = Dcmj2pnmOptionsSchema.safeParse(options);
|
|
32497
32442
|
if (!validation.success) {
|
|
32498
|
-
return err(
|
|
32443
|
+
return err(createValidationError("dcmj2pnm", validation.error));
|
|
32499
32444
|
}
|
|
32500
32445
|
const binaryResult = resolveBinary("dcmj2pnm");
|
|
32501
32446
|
if (!binaryResult.ok) {
|
|
@@ -32551,7 +32496,7 @@ function buildArgs18(inputPath, outputPath, options) {
|
|
|
32551
32496
|
async function dcm2pnm(inputPath, outputPath, options) {
|
|
32552
32497
|
const validation = Dcm2pnmOptionsSchema.safeParse(options);
|
|
32553
32498
|
if (!validation.success) {
|
|
32554
|
-
return err(
|
|
32499
|
+
return err(createValidationError("dcm2pnm", validation.error));
|
|
32555
32500
|
}
|
|
32556
32501
|
const binaryResult = resolveBinary("dcm2pnm");
|
|
32557
32502
|
if (!binaryResult.ok) {
|
|
@@ -32599,7 +32544,7 @@ function buildArgs19(inputPath, outputPath, options) {
|
|
|
32599
32544
|
async function dcmscale(inputPath, outputPath, options) {
|
|
32600
32545
|
const validation = DcmscaleOptionsSchema.safeParse(options);
|
|
32601
32546
|
if (!validation.success) {
|
|
32602
|
-
return err(
|
|
32547
|
+
return err(createValidationError("dcmscale", validation.error));
|
|
32603
32548
|
}
|
|
32604
32549
|
const binaryResult = resolveBinary("dcmscale");
|
|
32605
32550
|
if (!binaryResult.ok) {
|
|
@@ -32639,7 +32584,7 @@ function buildArgs20(inputPath, outputPath, options) {
|
|
|
32639
32584
|
async function dcmquant(inputPath, outputPath, options) {
|
|
32640
32585
|
const validation = DcmquantOptionsSchema.safeParse(options);
|
|
32641
32586
|
if (!validation.success) {
|
|
32642
|
-
return err(
|
|
32587
|
+
return err(createValidationError("dcmquant", validation.error));
|
|
32643
32588
|
}
|
|
32644
32589
|
const binaryResult = resolveBinary("dcmquant");
|
|
32645
32590
|
if (!binaryResult.ok) {
|
|
@@ -32686,7 +32631,7 @@ function buildArgs21(options) {
|
|
|
32686
32631
|
async function dcmdspfn(options) {
|
|
32687
32632
|
const validation = DcmdspfnOptionsSchema.safeParse(options);
|
|
32688
32633
|
if (!validation.success) {
|
|
32689
|
-
return err(
|
|
32634
|
+
return err(createValidationError("dcmdspfn", validation.error));
|
|
32690
32635
|
}
|
|
32691
32636
|
const binaryResult = resolveBinary("dcmdspfn");
|
|
32692
32637
|
if (!binaryResult.ok) {
|
|
@@ -32713,7 +32658,7 @@ var Dcod2lumOptionsSchema = zod.z.object({
|
|
|
32713
32658
|
async function dcod2lum(inputPath, outputPath, options) {
|
|
32714
32659
|
const validation = Dcod2lumOptionsSchema.safeParse(options);
|
|
32715
32660
|
if (!validation.success) {
|
|
32716
|
-
return err(
|
|
32661
|
+
return err(createValidationError("dcod2lum", validation.error));
|
|
32717
32662
|
}
|
|
32718
32663
|
const binaryResult = resolveBinary("dcod2lum");
|
|
32719
32664
|
if (!binaryResult.ok) {
|
|
@@ -32749,7 +32694,7 @@ function buildArgs22(inputPath, outputPath, options) {
|
|
|
32749
32694
|
async function dconvlum(inputPath, outputPath, options) {
|
|
32750
32695
|
const validation = DconvlumOptionsSchema.safeParse(options);
|
|
32751
32696
|
if (!validation.success) {
|
|
32752
|
-
return err(
|
|
32697
|
+
return err(createValidationError("dconvlum", validation.error));
|
|
32753
32698
|
}
|
|
32754
32699
|
const binaryResult = resolveBinary("dconvlum");
|
|
32755
32700
|
if (!binaryResult.ok) {
|
|
@@ -32839,7 +32784,7 @@ function buildArgs24(options) {
|
|
|
32839
32784
|
async function dcmsend(options) {
|
|
32840
32785
|
const validation = DcmsendOptionsSchema.safeParse(options);
|
|
32841
32786
|
if (!validation.success) {
|
|
32842
|
-
return err(
|
|
32787
|
+
return err(createValidationError("dcmsend", validation.error));
|
|
32843
32788
|
}
|
|
32844
32789
|
const binaryResult = resolveBinary("dcmsend");
|
|
32845
32790
|
if (!binaryResult.ok) {
|
|
@@ -32859,6 +32804,32 @@ async function dcmsend(options) {
|
|
|
32859
32804
|
}
|
|
32860
32805
|
return ok({ success: true, stderr: result.value.stderr });
|
|
32861
32806
|
}
|
|
32807
|
+
var ProposedTransferSyntax = {
|
|
32808
|
+
UNCOMPRESSED: "uncompressed",
|
|
32809
|
+
LITTLE_ENDIAN: "littleEndian",
|
|
32810
|
+
BIG_ENDIAN: "bigEndian",
|
|
32811
|
+
IMPLICIT_VR: "implicitVR",
|
|
32812
|
+
JPEG_LOSSLESS: "jpegLossless",
|
|
32813
|
+
JPEG_8BIT: "jpeg8Bit",
|
|
32814
|
+
JPEG_12BIT: "jpeg12Bit",
|
|
32815
|
+
J2K_LOSSLESS: "j2kLossless",
|
|
32816
|
+
J2K_LOSSY: "j2kLossy",
|
|
32817
|
+
JLS_LOSSLESS: "jlsLossless",
|
|
32818
|
+
JLS_LOSSY: "jlsLossy"
|
|
32819
|
+
};
|
|
32820
|
+
var PROPOSED_TS_FLAG_MAP = {
|
|
32821
|
+
[ProposedTransferSyntax.UNCOMPRESSED]: "-x=",
|
|
32822
|
+
[ProposedTransferSyntax.LITTLE_ENDIAN]: "-xe",
|
|
32823
|
+
[ProposedTransferSyntax.BIG_ENDIAN]: "-xb",
|
|
32824
|
+
[ProposedTransferSyntax.IMPLICIT_VR]: "-xi",
|
|
32825
|
+
[ProposedTransferSyntax.JPEG_LOSSLESS]: "-xs",
|
|
32826
|
+
[ProposedTransferSyntax.JPEG_8BIT]: "-xy",
|
|
32827
|
+
[ProposedTransferSyntax.JPEG_12BIT]: "-xx",
|
|
32828
|
+
[ProposedTransferSyntax.J2K_LOSSLESS]: "-xv",
|
|
32829
|
+
[ProposedTransferSyntax.J2K_LOSSY]: "-xw",
|
|
32830
|
+
[ProposedTransferSyntax.JLS_LOSSLESS]: "-xt",
|
|
32831
|
+
[ProposedTransferSyntax.JLS_LOSSY]: "-xu"
|
|
32832
|
+
};
|
|
32862
32833
|
var StorescuOptionsSchema = zod.z.object({
|
|
32863
32834
|
timeoutMs: zod.z.number().int().positive().optional(),
|
|
32864
32835
|
signal: zod.z.instanceof(AbortSignal).optional(),
|
|
@@ -32868,7 +32839,20 @@ var StorescuOptionsSchema = zod.z.object({
|
|
|
32868
32839
|
callingAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
32869
32840
|
calledAETitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
32870
32841
|
scanDirectories: zod.z.boolean().optional(),
|
|
32871
|
-
recurse: zod.z.boolean().optional()
|
|
32842
|
+
recurse: zod.z.boolean().optional(),
|
|
32843
|
+
proposedTransferSyntax: zod.z.enum([
|
|
32844
|
+
"uncompressed",
|
|
32845
|
+
"littleEndian",
|
|
32846
|
+
"bigEndian",
|
|
32847
|
+
"implicitVR",
|
|
32848
|
+
"jpegLossless",
|
|
32849
|
+
"jpeg8Bit",
|
|
32850
|
+
"jpeg12Bit",
|
|
32851
|
+
"j2kLossless",
|
|
32852
|
+
"j2kLossy",
|
|
32853
|
+
"jlsLossless",
|
|
32854
|
+
"jlsLossy"
|
|
32855
|
+
]).optional()
|
|
32872
32856
|
}).strict();
|
|
32873
32857
|
function buildArgs25(options) {
|
|
32874
32858
|
const args = [];
|
|
@@ -32884,6 +32868,9 @@ function buildArgs25(options) {
|
|
|
32884
32868
|
if (options.recurse === true) {
|
|
32885
32869
|
args.push("+r");
|
|
32886
32870
|
}
|
|
32871
|
+
if (options.proposedTransferSyntax !== void 0) {
|
|
32872
|
+
args.push(PROPOSED_TS_FLAG_MAP[options.proposedTransferSyntax]);
|
|
32873
|
+
}
|
|
32887
32874
|
args.push(options.host, String(options.port));
|
|
32888
32875
|
args.push(...options.files);
|
|
32889
32876
|
return args;
|
|
@@ -32891,7 +32878,7 @@ function buildArgs25(options) {
|
|
|
32891
32878
|
async function storescu(options) {
|
|
32892
32879
|
const validation = StorescuOptionsSchema.safeParse(options);
|
|
32893
32880
|
if (!validation.success) {
|
|
32894
|
-
return err(
|
|
32881
|
+
return err(createValidationError("storescu", validation.error));
|
|
32895
32882
|
}
|
|
32896
32883
|
const binaryResult = resolveBinary("storescu");
|
|
32897
32884
|
if (!binaryResult.ok) {
|
|
@@ -33031,7 +33018,7 @@ function buildArgs27(options) {
|
|
|
33031
33018
|
async function movescu(options) {
|
|
33032
33019
|
const validation = MovescuOptionsSchema.safeParse(options);
|
|
33033
33020
|
if (!validation.success) {
|
|
33034
|
-
return err(
|
|
33021
|
+
return err(createValidationError("movescu", validation.error));
|
|
33035
33022
|
}
|
|
33036
33023
|
const binaryResult = resolveBinary("movescu");
|
|
33037
33024
|
if (!binaryResult.ok) {
|
|
@@ -33095,7 +33082,7 @@ function buildArgs28(options) {
|
|
|
33095
33082
|
async function getscu(options) {
|
|
33096
33083
|
const validation = GetscuOptionsSchema.safeParse(options);
|
|
33097
33084
|
if (!validation.success) {
|
|
33098
|
-
return err(
|
|
33085
|
+
return err(createValidationError("getscu", validation.error));
|
|
33099
33086
|
}
|
|
33100
33087
|
const binaryResult = resolveBinary("getscu");
|
|
33101
33088
|
if (!binaryResult.ok) {
|
|
@@ -33137,7 +33124,7 @@ function buildArgs29(options) {
|
|
|
33137
33124
|
async function termscu(options) {
|
|
33138
33125
|
const validation = TermscuOptionsSchema.safeParse(options);
|
|
33139
33126
|
if (!validation.success) {
|
|
33140
|
-
return err(
|
|
33127
|
+
return err(createValidationError("termscu", validation.error));
|
|
33141
33128
|
}
|
|
33142
33129
|
const binaryResult = resolveBinary("termscu");
|
|
33143
33130
|
if (!binaryResult.ok) {
|
|
@@ -33181,7 +33168,7 @@ function buildArgs30(inputPath, options) {
|
|
|
33181
33168
|
async function dsrdump(inputPath, options) {
|
|
33182
33169
|
const validation = DsrdumpOptionsSchema.safeParse(options);
|
|
33183
33170
|
if (!validation.success) {
|
|
33184
|
-
return err(
|
|
33171
|
+
return err(createValidationError("dsrdump", validation.error));
|
|
33185
33172
|
}
|
|
33186
33173
|
const binaryResult = resolveBinary("dsrdump");
|
|
33187
33174
|
if (!binaryResult.ok) {
|
|
@@ -33221,7 +33208,7 @@ function buildArgs31(inputPath, options) {
|
|
|
33221
33208
|
async function dsr2xml(inputPath, options) {
|
|
33222
33209
|
const validation = Dsr2xmlOptionsSchema.safeParse(options);
|
|
33223
33210
|
if (!validation.success) {
|
|
33224
|
-
return err(
|
|
33211
|
+
return err(createValidationError("dsr2xml", validation.error));
|
|
33225
33212
|
}
|
|
33226
33213
|
const binaryResult = resolveBinary("dsr2xml");
|
|
33227
33214
|
if (!binaryResult.ok) {
|
|
@@ -33261,7 +33248,7 @@ function buildArgs32(inputPath, outputPath, options) {
|
|
|
33261
33248
|
async function xml2dsr(inputPath, outputPath, options) {
|
|
33262
33249
|
const validation = Xml2dsrOptionsSchema.safeParse(options);
|
|
33263
33250
|
if (!validation.success) {
|
|
33264
|
-
return err(
|
|
33251
|
+
return err(createValidationError("xml2dsr", validation.error));
|
|
33265
33252
|
}
|
|
33266
33253
|
const binaryResult = resolveBinary("xml2dsr");
|
|
33267
33254
|
if (!binaryResult.ok) {
|
|
@@ -33297,7 +33284,7 @@ function buildArgs33(inputPath, options) {
|
|
|
33297
33284
|
async function drtdump(inputPath, options) {
|
|
33298
33285
|
const validation = DrtdumpOptionsSchema.safeParse(options);
|
|
33299
33286
|
if (!validation.success) {
|
|
33300
|
-
return err(
|
|
33287
|
+
return err(createValidationError("drtdump", validation.error));
|
|
33301
33288
|
}
|
|
33302
33289
|
const binaryResult = resolveBinary("drtdump");
|
|
33303
33290
|
if (!binaryResult.ok) {
|
|
@@ -33324,7 +33311,7 @@ var DcmpsmkOptionsSchema = zod.z.object({
|
|
|
33324
33311
|
async function dcmpsmk(inputPath, outputPath, options) {
|
|
33325
33312
|
const validation = DcmpsmkOptionsSchema.safeParse(options);
|
|
33326
33313
|
if (!validation.success) {
|
|
33327
|
-
return err(
|
|
33314
|
+
return err(createValidationError("dcmpsmk", validation.error));
|
|
33328
33315
|
}
|
|
33329
33316
|
const binaryResult = resolveBinary("dcmpsmk");
|
|
33330
33317
|
if (!binaryResult.ok) {
|
|
@@ -33351,7 +33338,7 @@ var DcmpschkOptionsSchema = zod.z.object({
|
|
|
33351
33338
|
async function dcmpschk(inputPath, options) {
|
|
33352
33339
|
const validation = DcmpschkOptionsSchema.safeParse(options);
|
|
33353
33340
|
if (!validation.success) {
|
|
33354
|
-
return err(
|
|
33341
|
+
return err(createValidationError("dcmpschk", validation.error));
|
|
33355
33342
|
}
|
|
33356
33343
|
const binaryResult = resolveBinary("dcmpschk");
|
|
33357
33344
|
if (!binaryResult.ok) {
|
|
@@ -33397,7 +33384,7 @@ function buildArgs34(options) {
|
|
|
33397
33384
|
async function dcmprscu(options) {
|
|
33398
33385
|
const validation = DcmprscuOptionsSchema.safeParse(options);
|
|
33399
33386
|
if (!validation.success) {
|
|
33400
|
-
return err(
|
|
33387
|
+
return err(createValidationError("dcmprscu", validation.error));
|
|
33401
33388
|
}
|
|
33402
33389
|
const binaryResult = resolveBinary("dcmprscu");
|
|
33403
33390
|
if (!binaryResult.ok) {
|
|
@@ -33433,7 +33420,7 @@ function buildArgs35(inputPath, options) {
|
|
|
33433
33420
|
async function dcmpsprt(inputPath, options) {
|
|
33434
33421
|
const validation = DcmpsprtOptionsSchema.safeParse(options);
|
|
33435
33422
|
if (!validation.success) {
|
|
33436
|
-
return err(
|
|
33423
|
+
return err(createValidationError("dcmpsprt", validation.error));
|
|
33437
33424
|
}
|
|
33438
33425
|
const binaryResult = resolveBinary("dcmpsprt");
|
|
33439
33426
|
if (!binaryResult.ok) {
|
|
@@ -33473,7 +33460,7 @@ function buildArgs36(inputPath, outputPath, options) {
|
|
|
33473
33460
|
async function dcmp2pgm(inputPath, outputPath, options) {
|
|
33474
33461
|
const validation = Dcmp2pgmOptionsSchema.safeParse(options);
|
|
33475
33462
|
if (!validation.success) {
|
|
33476
|
-
return err(
|
|
33463
|
+
return err(createValidationError("dcmp2pgm", validation.error));
|
|
33477
33464
|
}
|
|
33478
33465
|
const binaryResult = resolveBinary("dcmp2pgm");
|
|
33479
33466
|
if (!binaryResult.ok) {
|
|
@@ -33500,7 +33487,7 @@ var DcmmkcrvOptionsSchema = zod.z.object({
|
|
|
33500
33487
|
async function dcmmkcrv(inputPath, outputPath, options) {
|
|
33501
33488
|
const validation = DcmmkcrvOptionsSchema.safeParse(options);
|
|
33502
33489
|
if (!validation.success) {
|
|
33503
|
-
return err(
|
|
33490
|
+
return err(createValidationError("dcmmkcrv", validation.error));
|
|
33504
33491
|
}
|
|
33505
33492
|
const binaryResult = resolveBinary("dcmmkcrv");
|
|
33506
33493
|
if (!binaryResult.ok) {
|
|
@@ -33561,7 +33548,7 @@ function buildArgs37(outputPath, options) {
|
|
|
33561
33548
|
async function dcmmklut(outputPath, options) {
|
|
33562
33549
|
const validation = DcmmklutOptionsSchema.safeParse(options);
|
|
33563
33550
|
if (!validation.success) {
|
|
33564
|
-
return err(
|
|
33551
|
+
return err(createValidationError("dcmmklut", validation.error));
|
|
33565
33552
|
}
|
|
33566
33553
|
const binaryResult = resolveBinary("dcmmklut");
|
|
33567
33554
|
if (!binaryResult.ok) {
|
|
@@ -33581,6 +33568,363 @@ async function dcmmklut(outputPath, options) {
|
|
|
33581
33568
|
}
|
|
33582
33569
|
return ok({ outputPath });
|
|
33583
33570
|
}
|
|
33571
|
+
async function applyModifications(filePath, changeset, options) {
|
|
33572
|
+
const modifications = changeset.toModifications();
|
|
33573
|
+
const erasures = changeset.toErasureArgs();
|
|
33574
|
+
const hasErasures = erasures.length > 0 || changeset.erasePrivate;
|
|
33575
|
+
const result = await dcmodify(filePath, {
|
|
33576
|
+
modifications: modifications.length > 0 ? modifications : void 0,
|
|
33577
|
+
erasures: erasures.length > 0 ? erasures : void 0,
|
|
33578
|
+
erasePrivateTags: changeset.erasePrivate || void 0,
|
|
33579
|
+
insertIfMissing: true,
|
|
33580
|
+
ignoreMissingTags: hasErasures || void 0,
|
|
33581
|
+
timeoutMs: options.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
33582
|
+
signal: options.signal
|
|
33583
|
+
});
|
|
33584
|
+
if (!result.ok) return err(result.error);
|
|
33585
|
+
return ok(void 0);
|
|
33586
|
+
}
|
|
33587
|
+
async function copyFileSafe(source, dest) {
|
|
33588
|
+
return stderrLib.tryCatch(
|
|
33589
|
+
() => fs.copyFile(source, dest),
|
|
33590
|
+
(e) => new Error(`Failed to copy file: ${e.message}`)
|
|
33591
|
+
);
|
|
33592
|
+
}
|
|
33593
|
+
async function statFileSize(path2) {
|
|
33594
|
+
return stderrLib.tryCatch(
|
|
33595
|
+
async () => (await fs.stat(path2)).size,
|
|
33596
|
+
(e) => new Error(`Failed to stat file: ${e.message}`)
|
|
33597
|
+
);
|
|
33598
|
+
}
|
|
33599
|
+
async function unlinkFile(path2) {
|
|
33600
|
+
return stderrLib.tryCatch(
|
|
33601
|
+
() => fs.unlink(path2),
|
|
33602
|
+
(e) => new Error(`Failed to delete file: ${e.message}`)
|
|
33603
|
+
);
|
|
33604
|
+
}
|
|
33605
|
+
|
|
33606
|
+
// src/dicom/DicomInstance.ts
|
|
33607
|
+
var DicomInstance = class _DicomInstance {
|
|
33608
|
+
constructor(dataset, changes, filePath, metadata) {
|
|
33609
|
+
__publicField(this, "dicomDataset");
|
|
33610
|
+
__publicField(this, "changeSet");
|
|
33611
|
+
__publicField(this, "filepath");
|
|
33612
|
+
__publicField(this, "meta");
|
|
33613
|
+
this.dicomDataset = dataset;
|
|
33614
|
+
this.changeSet = changes;
|
|
33615
|
+
this.filepath = filePath;
|
|
33616
|
+
this.meta = metadata;
|
|
33617
|
+
}
|
|
33618
|
+
// -----------------------------------------------------------------------
|
|
33619
|
+
// Factories
|
|
33620
|
+
// -----------------------------------------------------------------------
|
|
33621
|
+
/**
|
|
33622
|
+
* Opens a DICOM file and creates a DicomInstance.
|
|
33623
|
+
*
|
|
33624
|
+
* @param path - Filesystem path to the DICOM file
|
|
33625
|
+
* @param options - Timeout and abort options
|
|
33626
|
+
* @returns A Result containing the DicomInstance or an error
|
|
33627
|
+
*/
|
|
33628
|
+
static async open(path2, options) {
|
|
33629
|
+
const filePathResult = createDicomFilePath(path2);
|
|
33630
|
+
if (!filePathResult.ok) return err(filePathResult.error);
|
|
33631
|
+
const jsonResult = await dcm2json(path2, {
|
|
33632
|
+
timeoutMs: options?.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
33633
|
+
signal: options?.signal
|
|
33634
|
+
});
|
|
33635
|
+
if (!jsonResult.ok) return err(jsonResult.error);
|
|
33636
|
+
const datasetResult = DicomDataset.fromJson(jsonResult.value.data);
|
|
33637
|
+
if (!datasetResult.ok) return err(datasetResult.error);
|
|
33638
|
+
return ok(new _DicomInstance(datasetResult.value, ChangeSet.empty(), filePathResult.value, /* @__PURE__ */ new Map()));
|
|
33639
|
+
}
|
|
33640
|
+
/**
|
|
33641
|
+
* Creates a DicomInstance from an existing DicomDataset.
|
|
33642
|
+
*
|
|
33643
|
+
* @param dataset - The DicomDataset to wrap
|
|
33644
|
+
* @param filePath - Optional file path (e.g. if the dataset came from a file)
|
|
33645
|
+
* @returns A Result containing the DicomInstance
|
|
33646
|
+
*/
|
|
33647
|
+
static fromDataset(dataset, filePath) {
|
|
33648
|
+
let fp;
|
|
33649
|
+
if (filePath !== void 0) {
|
|
33650
|
+
const fpResult = createDicomFilePath(filePath);
|
|
33651
|
+
if (!fpResult.ok) return err(fpResult.error);
|
|
33652
|
+
fp = fpResult.value;
|
|
33653
|
+
}
|
|
33654
|
+
return ok(new _DicomInstance(dataset, ChangeSet.empty(), fp, /* @__PURE__ */ new Map()));
|
|
33655
|
+
}
|
|
33656
|
+
// -----------------------------------------------------------------------
|
|
33657
|
+
// Read accessors (delegate to DicomDataset)
|
|
33658
|
+
// -----------------------------------------------------------------------
|
|
33659
|
+
/** The underlying immutable DICOM dataset. */
|
|
33660
|
+
get dataset() {
|
|
33661
|
+
return this.dicomDataset;
|
|
33662
|
+
}
|
|
33663
|
+
/** The pending change set. */
|
|
33664
|
+
get changes() {
|
|
33665
|
+
return this.changeSet;
|
|
33666
|
+
}
|
|
33667
|
+
/** Whether there are unsaved changes. */
|
|
33668
|
+
get hasUnsavedChanges() {
|
|
33669
|
+
return !this.changeSet.isEmpty;
|
|
33670
|
+
}
|
|
33671
|
+
/** The file path, or undefined if this instance has no associated file. */
|
|
33672
|
+
get filePath() {
|
|
33673
|
+
return this.filepath;
|
|
33674
|
+
}
|
|
33675
|
+
/** Patient's Name (0010,0010). */
|
|
33676
|
+
get patientName() {
|
|
33677
|
+
return this.dicomDataset.patientName;
|
|
33678
|
+
}
|
|
33679
|
+
/** Patient ID (0010,0020). */
|
|
33680
|
+
get patientID() {
|
|
33681
|
+
return this.dicomDataset.patientID;
|
|
33682
|
+
}
|
|
33683
|
+
/** Study Date (0008,0020). */
|
|
33684
|
+
get studyDate() {
|
|
33685
|
+
return this.dicomDataset.studyDate;
|
|
33686
|
+
}
|
|
33687
|
+
/** Modality (0008,0060). */
|
|
33688
|
+
get modality() {
|
|
33689
|
+
return this.dicomDataset.modality;
|
|
33690
|
+
}
|
|
33691
|
+
/** Accession Number (0008,0050). */
|
|
33692
|
+
get accession() {
|
|
33693
|
+
return this.dicomDataset.accession;
|
|
33694
|
+
}
|
|
33695
|
+
/** SOP Class UID (0008,0016). */
|
|
33696
|
+
get sopClassUID() {
|
|
33697
|
+
return this.dicomDataset.sopClassUID;
|
|
33698
|
+
}
|
|
33699
|
+
/** Study Instance UID (0020,000D). */
|
|
33700
|
+
get studyInstanceUID() {
|
|
33701
|
+
return this.dicomDataset.studyInstanceUID;
|
|
33702
|
+
}
|
|
33703
|
+
/** Series Instance UID (0020,000E). */
|
|
33704
|
+
get seriesInstanceUID() {
|
|
33705
|
+
return this.dicomDataset.seriesInstanceUID;
|
|
33706
|
+
}
|
|
33707
|
+
/** SOP Instance UID (0008,0018). */
|
|
33708
|
+
get sopInstanceUID() {
|
|
33709
|
+
return this.dicomDataset.sopInstanceUID;
|
|
33710
|
+
}
|
|
33711
|
+
/** Transfer Syntax UID (0002,0010). */
|
|
33712
|
+
get transferSyntaxUID() {
|
|
33713
|
+
return this.dicomDataset.transferSyntaxUID;
|
|
33714
|
+
}
|
|
33715
|
+
/**
|
|
33716
|
+
* Gets a tag value as a string with optional fallback.
|
|
33717
|
+
*
|
|
33718
|
+
* @param tag - A DICOM tag, e.g. `'(0010,0010)'` or `'00100010'`
|
|
33719
|
+
* @param fallback - Value to return if tag is missing (default: `''`)
|
|
33720
|
+
*/
|
|
33721
|
+
getString(tag2, fallback = "") {
|
|
33722
|
+
return this.dicomDataset.getString(tag2, fallback);
|
|
33723
|
+
}
|
|
33724
|
+
/**
|
|
33725
|
+
* Gets a tag value as a number.
|
|
33726
|
+
*
|
|
33727
|
+
* @param tag - A DICOM tag, e.g. `'(0020,0013)'`
|
|
33728
|
+
*/
|
|
33729
|
+
getNumber(tag2) {
|
|
33730
|
+
return this.dicomDataset.getNumber(tag2);
|
|
33731
|
+
}
|
|
33732
|
+
/** Checks whether a tag exists in the dataset. */
|
|
33733
|
+
hasTag(tag2) {
|
|
33734
|
+
return this.dicomDataset.hasTag(tag2);
|
|
33735
|
+
}
|
|
33736
|
+
/**
|
|
33737
|
+
* Finds all values matching a wildcard path.
|
|
33738
|
+
*
|
|
33739
|
+
* @param path - A DicomTagPath with optional wildcard indices
|
|
33740
|
+
*/
|
|
33741
|
+
findValues(path2) {
|
|
33742
|
+
return this.dicomDataset.findValues(path2);
|
|
33743
|
+
}
|
|
33744
|
+
// -----------------------------------------------------------------------
|
|
33745
|
+
// Write methods (return new instance)
|
|
33746
|
+
// -----------------------------------------------------------------------
|
|
33747
|
+
/**
|
|
33748
|
+
* Sets a tag value, returning a new DicomInstance.
|
|
33749
|
+
*
|
|
33750
|
+
* @param path - The DICOM tag path (e.g. `'(0010,0010)'`)
|
|
33751
|
+
* @param value - The new value
|
|
33752
|
+
*/
|
|
33753
|
+
setTag(path2, value) {
|
|
33754
|
+
return new _DicomInstance(this.dicomDataset, this.changeSet.setTag(path2, value), this.filepath, this.meta);
|
|
33755
|
+
}
|
|
33756
|
+
/**
|
|
33757
|
+
* Erases a tag, returning a new DicomInstance.
|
|
33758
|
+
*
|
|
33759
|
+
* @param path - The DICOM tag path to erase
|
|
33760
|
+
*/
|
|
33761
|
+
eraseTag(path2) {
|
|
33762
|
+
return new _DicomInstance(this.dicomDataset, this.changeSet.eraseTag(path2), this.filepath, this.meta);
|
|
33763
|
+
}
|
|
33764
|
+
/** Erases all private tags, returning a new DicomInstance. */
|
|
33765
|
+
erasePrivateTags() {
|
|
33766
|
+
return new _DicomInstance(this.dicomDataset, this.changeSet.erasePrivateTags(), this.filepath, this.meta);
|
|
33767
|
+
}
|
|
33768
|
+
/** Sets Patient's Name (0010,0010). */
|
|
33769
|
+
setPatientName(value) {
|
|
33770
|
+
return this.setTag("(0010,0010)", value);
|
|
33771
|
+
}
|
|
33772
|
+
/** Sets Patient ID (0010,0020). */
|
|
33773
|
+
setPatientID(value) {
|
|
33774
|
+
return this.setTag("(0010,0020)", value);
|
|
33775
|
+
}
|
|
33776
|
+
/** Sets Study Date (0008,0020). */
|
|
33777
|
+
setStudyDate(value) {
|
|
33778
|
+
return this.setTag("(0008,0020)", value);
|
|
33779
|
+
}
|
|
33780
|
+
/** Sets Modality (0008,0060). */
|
|
33781
|
+
setModality(value) {
|
|
33782
|
+
return this.setTag("(0008,0060)", value);
|
|
33783
|
+
}
|
|
33784
|
+
/** Sets Accession Number (0008,0050). */
|
|
33785
|
+
setAccessionNumber(value) {
|
|
33786
|
+
return this.setTag("(0008,0050)", value);
|
|
33787
|
+
}
|
|
33788
|
+
/** Sets Study Description (0008,1030). */
|
|
33789
|
+
setStudyDescription(value) {
|
|
33790
|
+
return this.setTag("(0008,1030)", value);
|
|
33791
|
+
}
|
|
33792
|
+
/** Sets Series Description (0008,103E). */
|
|
33793
|
+
setSeriesDescription(value) {
|
|
33794
|
+
return this.setTag("(0008,103E)", value);
|
|
33795
|
+
}
|
|
33796
|
+
/** Sets Institution Name (0008,0080). */
|
|
33797
|
+
setInstitutionName(value) {
|
|
33798
|
+
return this.setTag("(0008,0080)", value);
|
|
33799
|
+
}
|
|
33800
|
+
/**
|
|
33801
|
+
* Transforms a tag value using a function.
|
|
33802
|
+
*
|
|
33803
|
+
* The function receives the current string value (or undefined if tag is missing)
|
|
33804
|
+
* and returns the new value. Returns a new DicomInstance with the modification.
|
|
33805
|
+
*
|
|
33806
|
+
* @param path - The DICOM tag path
|
|
33807
|
+
* @param fn - Transform function receiving the current value
|
|
33808
|
+
*/
|
|
33809
|
+
transformTag(path2, fn) {
|
|
33810
|
+
const current = this.dicomDataset.getString(path2);
|
|
33811
|
+
const newValue = fn(current.length > 0 ? current : void 0);
|
|
33812
|
+
return this.setTag(path2, newValue);
|
|
33813
|
+
}
|
|
33814
|
+
/**
|
|
33815
|
+
* Sets multiple tags at once, returning a new DicomInstance.
|
|
33816
|
+
*
|
|
33817
|
+
* @param entries - A record of tag path → value pairs
|
|
33818
|
+
*/
|
|
33819
|
+
setBatch(entries) {
|
|
33820
|
+
return new _DicomInstance(this.dicomDataset, this.changeSet.setBatch(entries), this.filepath, this.meta);
|
|
33821
|
+
}
|
|
33822
|
+
/**
|
|
33823
|
+
* Returns a new DicomInstance with the given changes merged into pending changes.
|
|
33824
|
+
*
|
|
33825
|
+
* @param changes - A ChangeSet to merge with existing pending changes
|
|
33826
|
+
* @returns A new DicomInstance with accumulated changes
|
|
33827
|
+
*/
|
|
33828
|
+
withChanges(changes) {
|
|
33829
|
+
return new _DicomInstance(this.dicomDataset, this.changeSet.merge(changes), this.filepath, this.meta);
|
|
33830
|
+
}
|
|
33831
|
+
/**
|
|
33832
|
+
* Returns a new DicomInstance pointing to a different file path.
|
|
33833
|
+
*
|
|
33834
|
+
* Preserves the dataset, pending changes, and metadata.
|
|
33835
|
+
*
|
|
33836
|
+
* @param newPath - The new filesystem path (validated via createDicomFilePath)
|
|
33837
|
+
* @returns A new DicomInstance with the updated path
|
|
33838
|
+
* @throws If the path is invalid
|
|
33839
|
+
*/
|
|
33840
|
+
withFilePath(newPath) {
|
|
33841
|
+
const result = createDicomFilePath(newPath);
|
|
33842
|
+
if (!result.ok) throw result.error;
|
|
33843
|
+
return new _DicomInstance(this.dicomDataset, this.changeSet, result.value, this.meta);
|
|
33844
|
+
}
|
|
33845
|
+
// -----------------------------------------------------------------------
|
|
33846
|
+
// File I/O
|
|
33847
|
+
// -----------------------------------------------------------------------
|
|
33848
|
+
/**
|
|
33849
|
+
* Applies pending changes to the file in-place.
|
|
33850
|
+
*
|
|
33851
|
+
* Requires that the instance has an associated file path.
|
|
33852
|
+
*
|
|
33853
|
+
* @param options - Timeout and abort options
|
|
33854
|
+
*/
|
|
33855
|
+
async applyChanges(options) {
|
|
33856
|
+
if (this.filepath === void 0) return err(new Error("No file path associated with this instance"));
|
|
33857
|
+
if (this.changeSet.isEmpty) return ok(void 0);
|
|
33858
|
+
return applyModifications(this.filepath, this.changeSet, options ?? {});
|
|
33859
|
+
}
|
|
33860
|
+
/**
|
|
33861
|
+
* Copies the file to a new path and applies pending changes to the copy.
|
|
33862
|
+
*
|
|
33863
|
+
* Returns a new DicomInstance pointing to the output path.
|
|
33864
|
+
*
|
|
33865
|
+
* @param outputPath - Destination filesystem path
|
|
33866
|
+
* @param options - Timeout and abort options
|
|
33867
|
+
*/
|
|
33868
|
+
async writeAs(outputPath, options) {
|
|
33869
|
+
if (this.filepath === void 0) return err(new Error("No file path associated with this instance"));
|
|
33870
|
+
const outPathResult = createDicomFilePath(outputPath);
|
|
33871
|
+
if (!outPathResult.ok) return err(outPathResult.error);
|
|
33872
|
+
const copyResult = await copyFileSafe(this.filepath, outputPath);
|
|
33873
|
+
if (!copyResult.ok) return err(copyResult.error);
|
|
33874
|
+
if (!this.changeSet.isEmpty) {
|
|
33875
|
+
const applyResult = await applyModifications(outPathResult.value, this.changeSet, options ?? {});
|
|
33876
|
+
if (!applyResult.ok) {
|
|
33877
|
+
await unlinkFile(outputPath);
|
|
33878
|
+
return err(applyResult.error);
|
|
33879
|
+
}
|
|
33880
|
+
}
|
|
33881
|
+
return ok(new _DicomInstance(this.dicomDataset, ChangeSet.empty(), outPathResult.value, this.meta));
|
|
33882
|
+
}
|
|
33883
|
+
/**
|
|
33884
|
+
* Gets the file size in bytes.
|
|
33885
|
+
*
|
|
33886
|
+
* @returns A Result containing the size or an error
|
|
33887
|
+
*/
|
|
33888
|
+
async fileSize() {
|
|
33889
|
+
if (this.filepath === void 0) return err(new Error("No file path associated with this instance"));
|
|
33890
|
+
return statFileSize(this.filepath);
|
|
33891
|
+
}
|
|
33892
|
+
/**
|
|
33893
|
+
* Deletes the associated file from the filesystem.
|
|
33894
|
+
*
|
|
33895
|
+
* @returns A Result indicating success or failure
|
|
33896
|
+
*/
|
|
33897
|
+
async unlink() {
|
|
33898
|
+
if (this.filepath === void 0) return err(new Error("No file path associated with this instance"));
|
|
33899
|
+
return unlinkFile(this.filepath);
|
|
33900
|
+
}
|
|
33901
|
+
// -----------------------------------------------------------------------
|
|
33902
|
+
// Metadata (non-DICOM app context)
|
|
33903
|
+
// -----------------------------------------------------------------------
|
|
33904
|
+
/**
|
|
33905
|
+
* Returns a new DicomInstance with application metadata attached.
|
|
33906
|
+
*
|
|
33907
|
+
* Metadata is not stored in the DICOM file — it's for application context
|
|
33908
|
+
* (e.g. tracking source association, processing status, etc.).
|
|
33909
|
+
*
|
|
33910
|
+
* @param key - Metadata key
|
|
33911
|
+
* @param value - Metadata value
|
|
33912
|
+
*/
|
|
33913
|
+
withMetadata(key, value) {
|
|
33914
|
+
const newMeta = new Map(this.meta);
|
|
33915
|
+
newMeta.set(key, value);
|
|
33916
|
+
return new _DicomInstance(this.dicomDataset, this.changeSet, this.filepath, newMeta);
|
|
33917
|
+
}
|
|
33918
|
+
/**
|
|
33919
|
+
* Gets application metadata by key.
|
|
33920
|
+
*
|
|
33921
|
+
* @param key - Metadata key
|
|
33922
|
+
* @returns The metadata value or undefined
|
|
33923
|
+
*/
|
|
33924
|
+
getMetadata(key) {
|
|
33925
|
+
return this.meta.get(key);
|
|
33926
|
+
}
|
|
33927
|
+
};
|
|
33584
33928
|
|
|
33585
33929
|
// src/events/dcmrecv.ts
|
|
33586
33930
|
var DcmrecvEvent = {
|
|
@@ -33593,7 +33937,11 @@ var DcmrecvEvent = {
|
|
|
33593
33937
|
ASSOCIATION_ABORTED: "ASSOCIATION_ABORTED",
|
|
33594
33938
|
ECHO_REQUEST: "ECHO_REQUEST",
|
|
33595
33939
|
CANNOT_START_LISTENER: "CANNOT_START_LISTENER",
|
|
33596
|
-
REFUSING_ASSOCIATION: "REFUSING_ASSOCIATION"
|
|
33940
|
+
REFUSING_ASSOCIATION: "REFUSING_ASSOCIATION",
|
|
33941
|
+
/** Synthetic: STORED_FILE enriched with association context. */
|
|
33942
|
+
FILE_RECEIVED: "FILE_RECEIVED",
|
|
33943
|
+
/** Synthetic: emitted on association release/abort with summary. */
|
|
33944
|
+
ASSOCIATION_COMPLETE: "ASSOCIATION_COMPLETE"
|
|
33597
33945
|
};
|
|
33598
33946
|
var DCMRECV_PATTERNS = [
|
|
33599
33947
|
{
|
|
@@ -33603,9 +33951,9 @@ var DCMRECV_PATTERNS = [
|
|
|
33603
33951
|
},
|
|
33604
33952
|
{
|
|
33605
33953
|
event: DcmrecvEvent.ASSOCIATION_RECEIVED,
|
|
33606
|
-
pattern: /Association Received\s{1,100}(
|
|
33954
|
+
pattern: /Association Received\s{1,100}(\S+):\s+(\S+)\s+->\s+(\S+)/,
|
|
33607
33955
|
processor: (match) => ({
|
|
33608
|
-
|
|
33956
|
+
source: match[1] ?? "",
|
|
33609
33957
|
callingAE: match[2] ?? "",
|
|
33610
33958
|
calledAE: match[3] ?? ""
|
|
33611
33959
|
})
|
|
@@ -33633,7 +33981,7 @@ var DCMRECV_PATTERNS = [
|
|
|
33633
33981
|
},
|
|
33634
33982
|
{
|
|
33635
33983
|
event: DcmrecvEvent.ASSOCIATION_RELEASE,
|
|
33636
|
-
pattern: /
|
|
33984
|
+
pattern: /Association Release/i,
|
|
33637
33985
|
processor: () => void 0
|
|
33638
33986
|
},
|
|
33639
33987
|
{
|
|
@@ -33669,6 +34017,11 @@ var StorescpEvent = {
|
|
|
33669
34017
|
STORING_FILE: "STORING_FILE",
|
|
33670
34018
|
SUBDIRECTORY_CREATED: "SUBDIRECTORY_CREATED"
|
|
33671
34019
|
};
|
|
34020
|
+
var STORESCP_ASSOCIATION_RECEIVED = {
|
|
34021
|
+
event: StorescpEvent.ASSOCIATION_RECEIVED,
|
|
34022
|
+
pattern: /Association Received/i,
|
|
34023
|
+
processor: () => ({ source: "", callingAE: "", calledAE: "" })
|
|
34024
|
+
};
|
|
33672
34025
|
var STORESCP_ADDITIONAL_PATTERNS = [
|
|
33673
34026
|
{
|
|
33674
34027
|
event: StorescpEvent.STORING_FILE,
|
|
@@ -33685,7 +34038,11 @@ var STORESCP_ADDITIONAL_PATTERNS = [
|
|
|
33685
34038
|
})
|
|
33686
34039
|
}
|
|
33687
34040
|
];
|
|
33688
|
-
var STORESCP_PATTERNS = [
|
|
34041
|
+
var STORESCP_PATTERNS = [
|
|
34042
|
+
...DCMRECV_PATTERNS.filter((p) => p.event !== DcmrecvEvent.ASSOCIATION_RECEIVED),
|
|
34043
|
+
STORESCP_ASSOCIATION_RECEIVED,
|
|
34044
|
+
...STORESCP_ADDITIONAL_PATTERNS
|
|
34045
|
+
];
|
|
33689
34046
|
var STORESCP_FATAL_EVENTS = /* @__PURE__ */ new Set([...DCMRECV_FATAL_EVENTS]);
|
|
33690
34047
|
|
|
33691
34048
|
// src/events/dcmprscp.ts
|
|
@@ -33992,6 +34349,97 @@ var WLMSCPFS_PATTERNS = [
|
|
|
33992
34349
|
}
|
|
33993
34350
|
];
|
|
33994
34351
|
var WLMSCPFS_FATAL_EVENTS = /* @__PURE__ */ new Set([WlmscpfsEvent.CANNOT_START_LISTENER]);
|
|
34352
|
+
|
|
34353
|
+
// src/servers/AssociationTracker.ts
|
|
34354
|
+
var AssociationTracker = class {
|
|
34355
|
+
constructor() {
|
|
34356
|
+
__publicField(this, "association");
|
|
34357
|
+
__publicField(this, "counter", 0);
|
|
34358
|
+
}
|
|
34359
|
+
/**
|
|
34360
|
+
* Begins a new association, transitioning from IDLE to ACTIVE.
|
|
34361
|
+
*
|
|
34362
|
+
* If an association is already active, it is silently ended (abort)
|
|
34363
|
+
* and the new one begins.
|
|
34364
|
+
*
|
|
34365
|
+
* @param data - Association metadata
|
|
34366
|
+
* @returns The unique association ID
|
|
34367
|
+
*/
|
|
34368
|
+
beginAssociation(data) {
|
|
34369
|
+
this.counter++;
|
|
34370
|
+
const associationId = `assoc-${String(this.counter)}`;
|
|
34371
|
+
this.association = {
|
|
34372
|
+
associationId,
|
|
34373
|
+
callingAE: data.callingAE,
|
|
34374
|
+
calledAE: data.calledAE,
|
|
34375
|
+
source: data.source,
|
|
34376
|
+
startTime: Date.now(),
|
|
34377
|
+
files: []
|
|
34378
|
+
};
|
|
34379
|
+
return associationId;
|
|
34380
|
+
}
|
|
34381
|
+
/**
|
|
34382
|
+
* Tracks a file received during the current association.
|
|
34383
|
+
*
|
|
34384
|
+
* If no association is active, returns a TrackedFile with empty context.
|
|
34385
|
+
*
|
|
34386
|
+
* @param filePath - Path to the received file
|
|
34387
|
+
* @returns A TrackedFile enriched with association context
|
|
34388
|
+
*/
|
|
34389
|
+
trackFile(filePath) {
|
|
34390
|
+
if (this.association === void 0) {
|
|
34391
|
+
return {
|
|
34392
|
+
filePath,
|
|
34393
|
+
associationId: "",
|
|
34394
|
+
callingAE: "",
|
|
34395
|
+
calledAE: "",
|
|
34396
|
+
source: ""
|
|
34397
|
+
};
|
|
34398
|
+
}
|
|
34399
|
+
this.association.files.push(filePath);
|
|
34400
|
+
return {
|
|
34401
|
+
filePath,
|
|
34402
|
+
associationId: this.association.associationId,
|
|
34403
|
+
callingAE: this.association.callingAE,
|
|
34404
|
+
calledAE: this.association.calledAE,
|
|
34405
|
+
source: this.association.source
|
|
34406
|
+
};
|
|
34407
|
+
}
|
|
34408
|
+
/**
|
|
34409
|
+
* Ends the current association, transitioning from ACTIVE to IDLE.
|
|
34410
|
+
*
|
|
34411
|
+
* @param reason - Why the association ended
|
|
34412
|
+
* @returns An AssociationSummary, or undefined if no association was active
|
|
34413
|
+
*/
|
|
34414
|
+
endAssociation(reason) {
|
|
34415
|
+
if (this.association === void 0) return void 0;
|
|
34416
|
+
const summary = {
|
|
34417
|
+
associationId: this.association.associationId,
|
|
34418
|
+
callingAE: this.association.callingAE,
|
|
34419
|
+
calledAE: this.association.calledAE,
|
|
34420
|
+
source: this.association.source,
|
|
34421
|
+
files: [...this.association.files],
|
|
34422
|
+
durationMs: Date.now() - this.association.startTime,
|
|
34423
|
+
endReason: reason
|
|
34424
|
+
};
|
|
34425
|
+
this.association = void 0;
|
|
34426
|
+
return summary;
|
|
34427
|
+
}
|
|
34428
|
+
/** The currently active association context, or undefined. */
|
|
34429
|
+
get current() {
|
|
34430
|
+
return this.association;
|
|
34431
|
+
}
|
|
34432
|
+
/** Whether an association is currently active. */
|
|
34433
|
+
get isActive() {
|
|
34434
|
+
return this.association !== void 0;
|
|
34435
|
+
}
|
|
34436
|
+
/** Resets the tracker to IDLE, discarding any active association. */
|
|
34437
|
+
reset() {
|
|
34438
|
+
this.association = void 0;
|
|
34439
|
+
}
|
|
34440
|
+
};
|
|
34441
|
+
|
|
34442
|
+
// src/servers/Dcmrecv.ts
|
|
33995
34443
|
var SubdirectoryMode = {
|
|
33996
34444
|
NONE: "none",
|
|
33997
34445
|
SERIES_DATE: "series-date"
|
|
@@ -34078,10 +34526,13 @@ var Dcmrecv = class _Dcmrecv extends DcmtkProcess {
|
|
|
34078
34526
|
constructor(config, parser2, signal) {
|
|
34079
34527
|
super(config);
|
|
34080
34528
|
__publicField(this, "parser");
|
|
34529
|
+
__publicField(this, "tracker");
|
|
34081
34530
|
__publicField(this, "abortSignal");
|
|
34082
34531
|
__publicField(this, "abortHandler");
|
|
34083
34532
|
this.parser = parser2;
|
|
34533
|
+
this.tracker = new AssociationTracker();
|
|
34084
34534
|
this.wireParser();
|
|
34535
|
+
this.wireTracker();
|
|
34085
34536
|
if (signal !== void 0) {
|
|
34086
34537
|
this.wireAbortSignal(signal);
|
|
34087
34538
|
}
|
|
@@ -34122,6 +34573,24 @@ var Dcmrecv = class _Dcmrecv extends DcmtkProcess {
|
|
|
34122
34573
|
onStoredFile(listener) {
|
|
34123
34574
|
return this.onEvent("STORED_FILE", listener);
|
|
34124
34575
|
}
|
|
34576
|
+
/**
|
|
34577
|
+
* Registers a listener for received files enriched with association context.
|
|
34578
|
+
*
|
|
34579
|
+
* @param listener - Callback receiving tracked file data
|
|
34580
|
+
* @returns this for chaining
|
|
34581
|
+
*/
|
|
34582
|
+
onFileReceived(listener) {
|
|
34583
|
+
return this.onEvent("FILE_RECEIVED", listener);
|
|
34584
|
+
}
|
|
34585
|
+
/**
|
|
34586
|
+
* Registers a listener for completed associations.
|
|
34587
|
+
*
|
|
34588
|
+
* @param listener - Callback receiving association summary
|
|
34589
|
+
* @returns this for chaining
|
|
34590
|
+
*/
|
|
34591
|
+
onAssociationComplete(listener) {
|
|
34592
|
+
return this.onEvent("ASSOCIATION_COMPLETE", listener);
|
|
34593
|
+
}
|
|
34125
34594
|
/**
|
|
34126
34595
|
* Creates a new Dcmrecv server instance.
|
|
34127
34596
|
*
|
|
@@ -34131,7 +34600,7 @@ var Dcmrecv = class _Dcmrecv extends DcmtkProcess {
|
|
|
34131
34600
|
static create(options) {
|
|
34132
34601
|
const validation = DcmrecvOptionsSchema.safeParse(options);
|
|
34133
34602
|
if (!validation.success) {
|
|
34134
|
-
return err(
|
|
34603
|
+
return err(createValidationError("dcmrecv", validation.error));
|
|
34135
34604
|
}
|
|
34136
34605
|
const binaryResult = resolveBinary("dcmrecv");
|
|
34137
34606
|
if (!binaryResult.ok) {
|
|
@@ -34164,7 +34633,29 @@ var Dcmrecv = class _Dcmrecv extends DcmtkProcess {
|
|
|
34164
34633
|
this.emit("error", { error: new Error(`Fatal: ${event}`), fatal: true });
|
|
34165
34634
|
void this.stop();
|
|
34166
34635
|
}
|
|
34167
|
-
this.emit(event,
|
|
34636
|
+
this.emit(event, data);
|
|
34637
|
+
});
|
|
34638
|
+
}
|
|
34639
|
+
/** Wires the AssociationTracker to server events. */
|
|
34640
|
+
wireTracker() {
|
|
34641
|
+
this.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
34642
|
+
this.tracker.beginAssociation(data);
|
|
34643
|
+
});
|
|
34644
|
+
this.onEvent("STORED_FILE", (data) => {
|
|
34645
|
+
const tracked = this.tracker.trackFile(data.filePath);
|
|
34646
|
+
this.emit(DcmrecvEvent.FILE_RECEIVED, tracked);
|
|
34647
|
+
});
|
|
34648
|
+
this.onEvent("ASSOCIATION_RELEASE", () => {
|
|
34649
|
+
const summary = this.tracker.endAssociation("release");
|
|
34650
|
+
if (summary !== void 0) {
|
|
34651
|
+
this.emit(DcmrecvEvent.ASSOCIATION_COMPLETE, summary);
|
|
34652
|
+
}
|
|
34653
|
+
});
|
|
34654
|
+
this.onEvent("ASSOCIATION_ABORTED", () => {
|
|
34655
|
+
const summary = this.tracker.endAssociation("abort");
|
|
34656
|
+
if (summary !== void 0) {
|
|
34657
|
+
this.emit(DcmrecvEvent.ASSOCIATION_COMPLETE, summary);
|
|
34658
|
+
}
|
|
34168
34659
|
});
|
|
34169
34660
|
}
|
|
34170
34661
|
/** Wires an AbortSignal to stop the server. */
|
|
@@ -34312,21 +34803,17 @@ var StoreSCP = class _StoreSCP extends DcmtkProcess {
|
|
|
34312
34803
|
constructor(config, parser2, signal) {
|
|
34313
34804
|
super(config);
|
|
34314
34805
|
__publicField(this, "parser");
|
|
34806
|
+
__publicField(this, "tracker");
|
|
34315
34807
|
__publicField(this, "abortSignal");
|
|
34316
34808
|
__publicField(this, "abortHandler");
|
|
34317
34809
|
this.parser = parser2;
|
|
34810
|
+
this.tracker = new AssociationTracker();
|
|
34318
34811
|
this.wireParser();
|
|
34812
|
+
this.wireTracker();
|
|
34319
34813
|
if (signal !== void 0) {
|
|
34320
34814
|
this.wireAbortSignal(signal);
|
|
34321
34815
|
}
|
|
34322
34816
|
}
|
|
34323
|
-
/**
|
|
34324
|
-
* Registers a typed listener for a storescp-specific event.
|
|
34325
|
-
*
|
|
34326
|
-
* @param event - The event name from StoreSCPEventMap
|
|
34327
|
-
* @param listener - Callback receiving typed event data
|
|
34328
|
-
* @returns this for chaining
|
|
34329
|
-
*/
|
|
34330
34817
|
/** Disposes the server and its parser, preventing listener leaks. */
|
|
34331
34818
|
[Symbol.dispose]() {
|
|
34332
34819
|
if (this.abortSignal !== void 0 && this.abortHandler !== void 0) {
|
|
@@ -34335,6 +34822,13 @@ var StoreSCP = class _StoreSCP extends DcmtkProcess {
|
|
|
34335
34822
|
this.parser[Symbol.dispose]();
|
|
34336
34823
|
super[Symbol.dispose]();
|
|
34337
34824
|
}
|
|
34825
|
+
/**
|
|
34826
|
+
* Registers a typed listener for a storescp-specific event.
|
|
34827
|
+
*
|
|
34828
|
+
* @param event - The event name from StoreSCPEventMap
|
|
34829
|
+
* @param listener - Callback receiving typed event data
|
|
34830
|
+
* @returns this for chaining
|
|
34831
|
+
*/
|
|
34338
34832
|
onEvent(event, listener) {
|
|
34339
34833
|
return this.on(event, listener);
|
|
34340
34834
|
}
|
|
@@ -34356,6 +34850,24 @@ var StoreSCP = class _StoreSCP extends DcmtkProcess {
|
|
|
34356
34850
|
onStoringFile(listener) {
|
|
34357
34851
|
return this.onEvent("STORING_FILE", listener);
|
|
34358
34852
|
}
|
|
34853
|
+
/**
|
|
34854
|
+
* Registers a listener for received files enriched with association context.
|
|
34855
|
+
*
|
|
34856
|
+
* @param listener - Callback receiving tracked file data
|
|
34857
|
+
* @returns this for chaining
|
|
34858
|
+
*/
|
|
34859
|
+
onFileReceived(listener) {
|
|
34860
|
+
return this.onEvent("FILE_RECEIVED", listener);
|
|
34861
|
+
}
|
|
34862
|
+
/**
|
|
34863
|
+
* Registers a listener for completed associations.
|
|
34864
|
+
*
|
|
34865
|
+
* @param listener - Callback receiving association summary
|
|
34866
|
+
* @returns this for chaining
|
|
34867
|
+
*/
|
|
34868
|
+
onAssociationComplete(listener) {
|
|
34869
|
+
return this.onEvent("ASSOCIATION_COMPLETE", listener);
|
|
34870
|
+
}
|
|
34359
34871
|
/**
|
|
34360
34872
|
* Creates a new StoreSCP server instance.
|
|
34361
34873
|
*
|
|
@@ -34365,7 +34877,7 @@ var StoreSCP = class _StoreSCP extends DcmtkProcess {
|
|
|
34365
34877
|
static create(options) {
|
|
34366
34878
|
const validation = StoreSCPOptionsSchema.safeParse(options);
|
|
34367
34879
|
if (!validation.success) {
|
|
34368
|
-
return err(
|
|
34880
|
+
return err(createValidationError("storescp", validation.error));
|
|
34369
34881
|
}
|
|
34370
34882
|
const binaryResult = resolveBinary("storescp");
|
|
34371
34883
|
if (!binaryResult.ok) {
|
|
@@ -34398,7 +34910,33 @@ var StoreSCP = class _StoreSCP extends DcmtkProcess {
|
|
|
34398
34910
|
this.emit("error", { error: new Error(`Fatal: ${event}`), fatal: true });
|
|
34399
34911
|
void this.stop();
|
|
34400
34912
|
}
|
|
34401
|
-
this.emit(event,
|
|
34913
|
+
this.emit(event, data);
|
|
34914
|
+
});
|
|
34915
|
+
}
|
|
34916
|
+
/** Wires the AssociationTracker to server events. */
|
|
34917
|
+
wireTracker() {
|
|
34918
|
+
this.onEvent("ASSOCIATION_RECEIVED", (data) => {
|
|
34919
|
+
this.tracker.beginAssociation(data);
|
|
34920
|
+
});
|
|
34921
|
+
this.onEvent("STORING_FILE", (data) => {
|
|
34922
|
+
const tracked = this.tracker.trackFile(data.filePath);
|
|
34923
|
+
this.emit(DcmrecvEvent.FILE_RECEIVED, tracked);
|
|
34924
|
+
});
|
|
34925
|
+
this.onEvent("STORED_FILE", (data) => {
|
|
34926
|
+
const tracked = this.tracker.trackFile(data.filePath);
|
|
34927
|
+
this.emit(DcmrecvEvent.FILE_RECEIVED, tracked);
|
|
34928
|
+
});
|
|
34929
|
+
this.onEvent("ASSOCIATION_RELEASE", () => {
|
|
34930
|
+
const summary = this.tracker.endAssociation("release");
|
|
34931
|
+
if (summary !== void 0) {
|
|
34932
|
+
this.emit(DcmrecvEvent.ASSOCIATION_COMPLETE, summary);
|
|
34933
|
+
}
|
|
34934
|
+
});
|
|
34935
|
+
this.onEvent("ASSOCIATION_ABORTED", () => {
|
|
34936
|
+
const summary = this.tracker.endAssociation("abort");
|
|
34937
|
+
if (summary !== void 0) {
|
|
34938
|
+
this.emit(DcmrecvEvent.ASSOCIATION_COMPLETE, summary);
|
|
34939
|
+
}
|
|
34402
34940
|
});
|
|
34403
34941
|
}
|
|
34404
34942
|
/** Wires an AbortSignal to stop the server. */
|
|
@@ -34497,7 +35035,7 @@ var DcmprsCP = class _DcmprsCP extends DcmtkProcess {
|
|
|
34497
35035
|
static create(options) {
|
|
34498
35036
|
const validation = DcmprsCPOptionsSchema.safeParse(options);
|
|
34499
35037
|
if (!validation.success) {
|
|
34500
|
-
return err(
|
|
35038
|
+
return err(createValidationError("dcmprscp", validation.error));
|
|
34501
35039
|
}
|
|
34502
35040
|
const binaryResult = resolveBinary("dcmprscp");
|
|
34503
35041
|
if (!binaryResult.ok) {
|
|
@@ -34530,7 +35068,7 @@ var DcmprsCP = class _DcmprsCP extends DcmtkProcess {
|
|
|
34530
35068
|
this.emit("error", { error: new Error(`Fatal: ${event}`), fatal: true });
|
|
34531
35069
|
void this.stop();
|
|
34532
35070
|
}
|
|
34533
|
-
this.emit(event,
|
|
35071
|
+
this.emit(event, data);
|
|
34534
35072
|
});
|
|
34535
35073
|
}
|
|
34536
35074
|
/** Wires an AbortSignal to stop the server. */
|
|
@@ -34626,7 +35164,7 @@ var Dcmpsrcv = class _Dcmpsrcv extends DcmtkProcess {
|
|
|
34626
35164
|
static create(options) {
|
|
34627
35165
|
const validation = DcmpsrcvOptionsSchema.safeParse(options);
|
|
34628
35166
|
if (!validation.success) {
|
|
34629
|
-
return err(
|
|
35167
|
+
return err(createValidationError("dcmpsrcv", validation.error));
|
|
34630
35168
|
}
|
|
34631
35169
|
const binaryResult = resolveBinary("dcmpsrcv");
|
|
34632
35170
|
if (!binaryResult.ok) {
|
|
@@ -34659,7 +35197,7 @@ var Dcmpsrcv = class _Dcmpsrcv extends DcmtkProcess {
|
|
|
34659
35197
|
this.emit("error", { error: new Error(`Fatal: ${event}`), fatal: true });
|
|
34660
35198
|
void this.stop();
|
|
34661
35199
|
}
|
|
34662
|
-
this.emit(event,
|
|
35200
|
+
this.emit(event, data);
|
|
34663
35201
|
});
|
|
34664
35202
|
}
|
|
34665
35203
|
/** Wires an AbortSignal to stop the server. */
|
|
@@ -34737,13 +35275,6 @@ var DcmQRSCP = class _DcmQRSCP extends DcmtkProcess {
|
|
|
34737
35275
|
this.wireAbortSignal(signal);
|
|
34738
35276
|
}
|
|
34739
35277
|
}
|
|
34740
|
-
/**
|
|
34741
|
-
* Registers a typed listener for a dcmqrscp-specific event.
|
|
34742
|
-
*
|
|
34743
|
-
* @param event - The event name from DcmQRSCPEventMap
|
|
34744
|
-
* @param listener - Callback receiving typed event data
|
|
34745
|
-
* @returns this for chaining
|
|
34746
|
-
*/
|
|
34747
35278
|
/** Disposes the server and its parser, preventing listener leaks. */
|
|
34748
35279
|
[Symbol.dispose]() {
|
|
34749
35280
|
if (this.abortSignal !== void 0 && this.abortHandler !== void 0) {
|
|
@@ -34752,6 +35283,13 @@ var DcmQRSCP = class _DcmQRSCP extends DcmtkProcess {
|
|
|
34752
35283
|
this.parser[Symbol.dispose]();
|
|
34753
35284
|
super[Symbol.dispose]();
|
|
34754
35285
|
}
|
|
35286
|
+
/**
|
|
35287
|
+
* Registers a typed listener for a dcmqrscp-specific event.
|
|
35288
|
+
*
|
|
35289
|
+
* @param event - The event name from DcmQRSCPEventMap
|
|
35290
|
+
* @param listener - Callback receiving typed event data
|
|
35291
|
+
* @returns this for chaining
|
|
35292
|
+
*/
|
|
34755
35293
|
onEvent(event, listener) {
|
|
34756
35294
|
return this.on(event, listener);
|
|
34757
35295
|
}
|
|
@@ -34782,7 +35320,7 @@ var DcmQRSCP = class _DcmQRSCP extends DcmtkProcess {
|
|
|
34782
35320
|
static create(options) {
|
|
34783
35321
|
const validation = DcmQRSCPOptionsSchema.safeParse(options);
|
|
34784
35322
|
if (!validation.success) {
|
|
34785
|
-
return err(
|
|
35323
|
+
return err(createValidationError("dcmqrscp", validation.error));
|
|
34786
35324
|
}
|
|
34787
35325
|
const binaryResult = resolveBinary("dcmqrscp");
|
|
34788
35326
|
if (!binaryResult.ok) {
|
|
@@ -34814,7 +35352,7 @@ var DcmQRSCP = class _DcmQRSCP extends DcmtkProcess {
|
|
|
34814
35352
|
this.emit("error", { error: new Error(`Fatal: ${event}`), fatal: true });
|
|
34815
35353
|
void this.stop();
|
|
34816
35354
|
}
|
|
34817
|
-
this.emit(event,
|
|
35355
|
+
this.emit(event, data);
|
|
34818
35356
|
});
|
|
34819
35357
|
}
|
|
34820
35358
|
/** Wires an AbortSignal to stop the server. */
|
|
@@ -34884,13 +35422,6 @@ var Wlmscpfs = class _Wlmscpfs extends DcmtkProcess {
|
|
|
34884
35422
|
this.wireAbortSignal(signal);
|
|
34885
35423
|
}
|
|
34886
35424
|
}
|
|
34887
|
-
/**
|
|
34888
|
-
* Registers a typed listener for a wlmscpfs-specific event.
|
|
34889
|
-
*
|
|
34890
|
-
* @param event - The event name from WlmscpfsEventMap
|
|
34891
|
-
* @param listener - Callback receiving typed event data
|
|
34892
|
-
* @returns this for chaining
|
|
34893
|
-
*/
|
|
34894
35425
|
/** Disposes the server and its parser, preventing listener leaks. */
|
|
34895
35426
|
[Symbol.dispose]() {
|
|
34896
35427
|
if (this.abortSignal !== void 0 && this.abortHandler !== void 0) {
|
|
@@ -34899,6 +35430,13 @@ var Wlmscpfs = class _Wlmscpfs extends DcmtkProcess {
|
|
|
34899
35430
|
this.parser[Symbol.dispose]();
|
|
34900
35431
|
super[Symbol.dispose]();
|
|
34901
35432
|
}
|
|
35433
|
+
/**
|
|
35434
|
+
* Registers a typed listener for a wlmscpfs-specific event.
|
|
35435
|
+
*
|
|
35436
|
+
* @param event - The event name from WlmscpfsEventMap
|
|
35437
|
+
* @param listener - Callback receiving typed event data
|
|
35438
|
+
* @returns this for chaining
|
|
35439
|
+
*/
|
|
34902
35440
|
onEvent(event, listener) {
|
|
34903
35441
|
return this.on(event, listener);
|
|
34904
35442
|
}
|
|
@@ -34929,7 +35467,7 @@ var Wlmscpfs = class _Wlmscpfs extends DcmtkProcess {
|
|
|
34929
35467
|
static create(options) {
|
|
34930
35468
|
const validation = WlmscpfsOptionsSchema.safeParse(options);
|
|
34931
35469
|
if (!validation.success) {
|
|
34932
|
-
return err(
|
|
35470
|
+
return err(createValidationError("wlmscpfs", validation.error));
|
|
34933
35471
|
}
|
|
34934
35472
|
const binaryResult = resolveBinary("wlmscpfs");
|
|
34935
35473
|
if (!binaryResult.ok) {
|
|
@@ -34962,7 +35500,7 @@ var Wlmscpfs = class _Wlmscpfs extends DcmtkProcess {
|
|
|
34962
35500
|
this.emit("error", { error: new Error(`Fatal: ${event}`), fatal: true });
|
|
34963
35501
|
void this.stop();
|
|
34964
35502
|
}
|
|
34965
|
-
this.emit(event,
|
|
35503
|
+
this.emit(event, data);
|
|
34966
35504
|
});
|
|
34967
35505
|
}
|
|
34968
35506
|
/** Wires an AbortSignal to stop the server. */
|
|
@@ -34978,6 +35516,506 @@ var Wlmscpfs = class _Wlmscpfs extends DcmtkProcess {
|
|
|
34978
35516
|
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
34979
35517
|
}
|
|
34980
35518
|
};
|
|
35519
|
+
var DEFAULT_MIN_POOL_SIZE = 2;
|
|
35520
|
+
var DEFAULT_MAX_POOL_SIZE = 10;
|
|
35521
|
+
var DEFAULT_CONNECTION_TIMEOUT_MS = 1e4;
|
|
35522
|
+
var CONNECTION_RETRY_INTERVAL_MS = 500;
|
|
35523
|
+
var MAX_CONNECTION_RETRIES = 200;
|
|
35524
|
+
var DicomReceiverOptionsSchema = zod.z.object({
|
|
35525
|
+
port: zod.z.number().int().min(1).max(65535),
|
|
35526
|
+
storageDir: zod.z.string().min(1).refine(isSafePath, { message: "path traversal detected in storageDir" }),
|
|
35527
|
+
aeTitle: zod.z.string().min(1).max(16).refine(isValidAETitle, { message: "AE Title contains invalid characters" }).optional(),
|
|
35528
|
+
minPoolSize: zod.z.number().int().min(1).max(100).optional(),
|
|
35529
|
+
maxPoolSize: zod.z.number().int().min(1).max(100).optional(),
|
|
35530
|
+
connectionTimeoutMs: zod.z.number().int().positive().optional(),
|
|
35531
|
+
configFile: zod.z.string().min(1).refine(isSafePath, { message: "path traversal detected in configFile" }).optional(),
|
|
35532
|
+
configProfile: zod.z.string().min(1).optional(),
|
|
35533
|
+
signal: zod.z.instanceof(AbortSignal).optional()
|
|
35534
|
+
}).strict().refine((data) => (data.minPoolSize ?? DEFAULT_MIN_POOL_SIZE) <= (data.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE), {
|
|
35535
|
+
message: "minPoolSize must be <= maxPoolSize"
|
|
35536
|
+
});
|
|
35537
|
+
function allocatePort() {
|
|
35538
|
+
return new Promise((resolve) => {
|
|
35539
|
+
const server = net__namespace.createServer();
|
|
35540
|
+
server.listen(0, "127.0.0.1", () => {
|
|
35541
|
+
const addr = server.address();
|
|
35542
|
+
if (addr === null || typeof addr === "string") {
|
|
35543
|
+
server.close(() => resolve(err(new Error("Failed to allocate port"))));
|
|
35544
|
+
return;
|
|
35545
|
+
}
|
|
35546
|
+
const port = addr.port;
|
|
35547
|
+
server.close(() => resolve(ok(port)));
|
|
35548
|
+
});
|
|
35549
|
+
server.on("error", (e) => {
|
|
35550
|
+
resolve(err(new Error(`Port allocation failed: ${e.message}`)));
|
|
35551
|
+
});
|
|
35552
|
+
});
|
|
35553
|
+
}
|
|
35554
|
+
var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
35555
|
+
constructor(options) {
|
|
35556
|
+
super();
|
|
35557
|
+
__publicField(this, "options");
|
|
35558
|
+
__publicField(this, "minPoolSize");
|
|
35559
|
+
__publicField(this, "maxPoolSize");
|
|
35560
|
+
__publicField(this, "connectionTimeoutMs");
|
|
35561
|
+
__publicField(this, "workers", /* @__PURE__ */ new Map());
|
|
35562
|
+
__publicField(this, "tcpServer");
|
|
35563
|
+
__publicField(this, "associationCounter", 0);
|
|
35564
|
+
__publicField(this, "started", false);
|
|
35565
|
+
__publicField(this, "stopping", false);
|
|
35566
|
+
__publicField(this, "abortHandler");
|
|
35567
|
+
this.setMaxListeners(20);
|
|
35568
|
+
this.on("error", () => {
|
|
35569
|
+
});
|
|
35570
|
+
this.options = options;
|
|
35571
|
+
this.minPoolSize = options.minPoolSize ?? DEFAULT_MIN_POOL_SIZE;
|
|
35572
|
+
this.maxPoolSize = options.maxPoolSize ?? DEFAULT_MAX_POOL_SIZE;
|
|
35573
|
+
this.connectionTimeoutMs = options.connectionTimeoutMs ?? DEFAULT_CONNECTION_TIMEOUT_MS;
|
|
35574
|
+
}
|
|
35575
|
+
// -----------------------------------------------------------------------
|
|
35576
|
+
// Public API
|
|
35577
|
+
// -----------------------------------------------------------------------
|
|
35578
|
+
/**
|
|
35579
|
+
* Creates a new DicomReceiver instance.
|
|
35580
|
+
*
|
|
35581
|
+
* @param options - Configuration options
|
|
35582
|
+
* @returns A Result containing the instance or a validation error
|
|
35583
|
+
*/
|
|
35584
|
+
static create(options) {
|
|
35585
|
+
const validation = DicomReceiverOptionsSchema.safeParse(options);
|
|
35586
|
+
if (!validation.success) {
|
|
35587
|
+
return err(createValidationError("DicomReceiver", validation.error));
|
|
35588
|
+
}
|
|
35589
|
+
return ok(new _DicomReceiver(options));
|
|
35590
|
+
}
|
|
35591
|
+
/**
|
|
35592
|
+
* Starts the TCP proxy and spawns the initial worker pool.
|
|
35593
|
+
*
|
|
35594
|
+
* @returns A Result indicating success or failure
|
|
35595
|
+
*/
|
|
35596
|
+
async start() {
|
|
35597
|
+
if (this.started) {
|
|
35598
|
+
return err(new Error("DicomReceiver: already started"));
|
|
35599
|
+
}
|
|
35600
|
+
this.started = true;
|
|
35601
|
+
const storageDirResult = await ensureDirectory(this.options.storageDir);
|
|
35602
|
+
if (!storageDirResult.ok) return storageDirResult;
|
|
35603
|
+
const spawnResults = await this.spawnWorkers(this.minPoolSize);
|
|
35604
|
+
if (!spawnResults.ok) return spawnResults;
|
|
35605
|
+
const listenResult = await this.startTcpProxy();
|
|
35606
|
+
if (!listenResult.ok) return listenResult;
|
|
35607
|
+
if (this.options.signal !== void 0) {
|
|
35608
|
+
this.wireAbortSignal(this.options.signal);
|
|
35609
|
+
}
|
|
35610
|
+
return ok(void 0);
|
|
35611
|
+
}
|
|
35612
|
+
/**
|
|
35613
|
+
* Stops the TCP proxy and all workers.
|
|
35614
|
+
*/
|
|
35615
|
+
async stop() {
|
|
35616
|
+
if (!this.started || this.stopping) {
|
|
35617
|
+
return;
|
|
35618
|
+
}
|
|
35619
|
+
this.stopping = true;
|
|
35620
|
+
if (this.options.signal !== void 0 && this.abortHandler !== void 0) {
|
|
35621
|
+
this.options.signal.removeEventListener("abort", this.abortHandler);
|
|
35622
|
+
}
|
|
35623
|
+
await this.closeTcpProxy();
|
|
35624
|
+
const stopPromises = [];
|
|
35625
|
+
for (const worker of this.workers.values()) {
|
|
35626
|
+
stopPromises.push(this.stopWorker(worker));
|
|
35627
|
+
}
|
|
35628
|
+
await Promise.all(stopPromises);
|
|
35629
|
+
this.workers.clear();
|
|
35630
|
+
this.started = false;
|
|
35631
|
+
this.stopping = false;
|
|
35632
|
+
}
|
|
35633
|
+
/**
|
|
35634
|
+
* Registers a typed listener for a DicomReceiver-specific event.
|
|
35635
|
+
*
|
|
35636
|
+
* @param event - The event name from DicomReceiverEventMap
|
|
35637
|
+
* @param listener - Callback receiving typed event data
|
|
35638
|
+
* @returns this for chaining
|
|
35639
|
+
*/
|
|
35640
|
+
onEvent(event, listener) {
|
|
35641
|
+
return this.on(event, listener);
|
|
35642
|
+
}
|
|
35643
|
+
/**
|
|
35644
|
+
* Registers a listener for received files.
|
|
35645
|
+
*
|
|
35646
|
+
* @param listener - Callback receiving file data
|
|
35647
|
+
* @returns this for chaining
|
|
35648
|
+
*/
|
|
35649
|
+
onFileReceived(listener) {
|
|
35650
|
+
return this.on("FILE_RECEIVED", listener);
|
|
35651
|
+
}
|
|
35652
|
+
/**
|
|
35653
|
+
* Registers a listener for completed associations.
|
|
35654
|
+
*
|
|
35655
|
+
* @param listener - Callback receiving association data
|
|
35656
|
+
* @returns this for chaining
|
|
35657
|
+
*/
|
|
35658
|
+
onAssociationComplete(listener) {
|
|
35659
|
+
return this.on("ASSOCIATION_COMPLETE", listener);
|
|
35660
|
+
}
|
|
35661
|
+
/** Current pool status. */
|
|
35662
|
+
get poolStatus() {
|
|
35663
|
+
let idle = 0;
|
|
35664
|
+
let busy = 0;
|
|
35665
|
+
for (const w of this.workers.values()) {
|
|
35666
|
+
if (w.state === "idle") idle++;
|
|
35667
|
+
else busy++;
|
|
35668
|
+
}
|
|
35669
|
+
return { idle, busy, total: this.workers.size };
|
|
35670
|
+
}
|
|
35671
|
+
// -----------------------------------------------------------------------
|
|
35672
|
+
// TCP proxy
|
|
35673
|
+
// -----------------------------------------------------------------------
|
|
35674
|
+
/** Starts the TCP proxy on the configured port. */
|
|
35675
|
+
startTcpProxy() {
|
|
35676
|
+
return new Promise((resolve) => {
|
|
35677
|
+
this.tcpServer = net__namespace.createServer((socket) => {
|
|
35678
|
+
void this.handleConnection(socket);
|
|
35679
|
+
});
|
|
35680
|
+
this.tcpServer.on("error", (e) => {
|
|
35681
|
+
if (!this.started) {
|
|
35682
|
+
resolve(err(new Error(`DicomReceiver: TCP proxy failed: ${e.message}`)));
|
|
35683
|
+
} else {
|
|
35684
|
+
this.emit("error", { error: e instanceof Error ? e : new Error(String(e)) });
|
|
35685
|
+
}
|
|
35686
|
+
});
|
|
35687
|
+
this.tcpServer.listen(this.options.port, () => {
|
|
35688
|
+
resolve(ok(void 0));
|
|
35689
|
+
});
|
|
35690
|
+
});
|
|
35691
|
+
}
|
|
35692
|
+
/** Closes the TCP proxy server. */
|
|
35693
|
+
closeTcpProxy() {
|
|
35694
|
+
return new Promise((resolve) => {
|
|
35695
|
+
if (this.tcpServer === void 0) {
|
|
35696
|
+
resolve();
|
|
35697
|
+
return;
|
|
35698
|
+
}
|
|
35699
|
+
this.tcpServer.close(() => resolve());
|
|
35700
|
+
});
|
|
35701
|
+
}
|
|
35702
|
+
// -----------------------------------------------------------------------
|
|
35703
|
+
// Connection routing
|
|
35704
|
+
// -----------------------------------------------------------------------
|
|
35705
|
+
/** Routes an incoming connection to an idle worker. */
|
|
35706
|
+
async handleConnection(remoteSocket) {
|
|
35707
|
+
remoteSocket.pause();
|
|
35708
|
+
const worker = await this.findIdleWorker();
|
|
35709
|
+
if (worker === void 0) {
|
|
35710
|
+
remoteSocket.destroy(new Error("DicomReceiver: no idle worker available"));
|
|
35711
|
+
this.emit("error", { error: new Error("DicomReceiver: connection rejected \u2014 pool exhausted") });
|
|
35712
|
+
return;
|
|
35713
|
+
}
|
|
35714
|
+
this.associationCounter++;
|
|
35715
|
+
const associationId = `assoc-${String(this.associationCounter)}`;
|
|
35716
|
+
const associationDir = path__namespace.join(this.options.storageDir, associationId);
|
|
35717
|
+
const mkdirResult = await ensureDirectory(associationDir);
|
|
35718
|
+
if (!mkdirResult.ok) {
|
|
35719
|
+
remoteSocket.destroy();
|
|
35720
|
+
this.emit("error", { error: mkdirResult.error });
|
|
35721
|
+
return;
|
|
35722
|
+
}
|
|
35723
|
+
worker.state = "busy";
|
|
35724
|
+
worker.associationId = associationId;
|
|
35725
|
+
worker.associationDir = associationDir;
|
|
35726
|
+
worker.files = [];
|
|
35727
|
+
worker.fileSizes = [];
|
|
35728
|
+
worker.startAt = Date.now();
|
|
35729
|
+
this.pipeConnection(worker, remoteSocket);
|
|
35730
|
+
void this.replenishPool();
|
|
35731
|
+
}
|
|
35732
|
+
/** Finds an idle worker, retrying up to connectionTimeoutMs. */
|
|
35733
|
+
async findIdleWorker() {
|
|
35734
|
+
const idle = this.getIdleWorker();
|
|
35735
|
+
if (idle !== void 0) return idle;
|
|
35736
|
+
const maxRetries = Math.min(Math.ceil(this.connectionTimeoutMs / CONNECTION_RETRY_INTERVAL_MS), MAX_CONNECTION_RETRIES);
|
|
35737
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
35738
|
+
await delay(CONNECTION_RETRY_INTERVAL_MS);
|
|
35739
|
+
const found = this.getIdleWorker();
|
|
35740
|
+
if (found !== void 0) return found;
|
|
35741
|
+
if (this.stopping) return void 0;
|
|
35742
|
+
}
|
|
35743
|
+
return void 0;
|
|
35744
|
+
}
|
|
35745
|
+
/** Returns the first idle worker, or undefined. */
|
|
35746
|
+
getIdleWorker() {
|
|
35747
|
+
for (const w of this.workers.values()) {
|
|
35748
|
+
if (w.state === "idle") return w;
|
|
35749
|
+
}
|
|
35750
|
+
return void 0;
|
|
35751
|
+
}
|
|
35752
|
+
/** Pipes remote socket bidirectionally to the worker's port. */
|
|
35753
|
+
pipeConnection(worker, remoteSocket) {
|
|
35754
|
+
const workerSocket = net__namespace.createConnection({ port: worker.port, host: "127.0.0.1" });
|
|
35755
|
+
worker.remoteSocket = remoteSocket;
|
|
35756
|
+
worker.workerSocket = workerSocket;
|
|
35757
|
+
remoteSocket.pipe(workerSocket);
|
|
35758
|
+
workerSocket.pipe(remoteSocket);
|
|
35759
|
+
remoteSocket.resume();
|
|
35760
|
+
const cleanup = () => {
|
|
35761
|
+
remoteSocket.unpipe(workerSocket);
|
|
35762
|
+
workerSocket.unpipe(remoteSocket);
|
|
35763
|
+
if (!remoteSocket.destroyed) remoteSocket.destroy();
|
|
35764
|
+
if (!workerSocket.destroyed) workerSocket.destroy();
|
|
35765
|
+
};
|
|
35766
|
+
remoteSocket.on("error", cleanup);
|
|
35767
|
+
workerSocket.on("error", cleanup);
|
|
35768
|
+
remoteSocket.on("close", cleanup);
|
|
35769
|
+
workerSocket.on("close", cleanup);
|
|
35770
|
+
}
|
|
35771
|
+
// -----------------------------------------------------------------------
|
|
35772
|
+
// Worker pool management
|
|
35773
|
+
// -----------------------------------------------------------------------
|
|
35774
|
+
/** Spawns `count` new workers and adds them to the pool. */
|
|
35775
|
+
async spawnWorkers(count) {
|
|
35776
|
+
const promises = [];
|
|
35777
|
+
for (let i = 0; i < count; i++) {
|
|
35778
|
+
promises.push(this.spawnWorker());
|
|
35779
|
+
}
|
|
35780
|
+
const results = await Promise.all(promises);
|
|
35781
|
+
for (const result of results) {
|
|
35782
|
+
if (!result.ok) return err(result.error);
|
|
35783
|
+
}
|
|
35784
|
+
return ok(void 0);
|
|
35785
|
+
}
|
|
35786
|
+
/** Spawns a single Dcmrecv worker with an ephemeral port. */
|
|
35787
|
+
async spawnWorker() {
|
|
35788
|
+
const portResult = await allocatePort();
|
|
35789
|
+
if (!portResult.ok) return portResult;
|
|
35790
|
+
const port = portResult.value;
|
|
35791
|
+
const tempDir = path__namespace.join(os__namespace.tmpdir(), `dcmrecv-pool-${String(port)}-${String(Date.now())}`);
|
|
35792
|
+
const mkdirResult = await ensureDirectory(tempDir);
|
|
35793
|
+
if (!mkdirResult.ok) return mkdirResult;
|
|
35794
|
+
const createResult = Dcmrecv.create({
|
|
35795
|
+
port,
|
|
35796
|
+
aeTitle: this.options.aeTitle ?? "DCMRECV",
|
|
35797
|
+
outputDirectory: tempDir,
|
|
35798
|
+
configFile: this.options.configFile,
|
|
35799
|
+
configProfile: this.options.configProfile
|
|
35800
|
+
});
|
|
35801
|
+
if (!createResult.ok) return createResult;
|
|
35802
|
+
const dcmrecv = createResult.value;
|
|
35803
|
+
const worker = {
|
|
35804
|
+
dcmrecv,
|
|
35805
|
+
port,
|
|
35806
|
+
tempDir,
|
|
35807
|
+
state: "idle",
|
|
35808
|
+
associationId: void 0,
|
|
35809
|
+
associationDir: void 0,
|
|
35810
|
+
files: [],
|
|
35811
|
+
fileSizes: [],
|
|
35812
|
+
startAt: void 0,
|
|
35813
|
+
remoteSocket: void 0,
|
|
35814
|
+
workerSocket: void 0
|
|
35815
|
+
};
|
|
35816
|
+
this.wireWorkerEvents(worker);
|
|
35817
|
+
const startResult = await dcmrecv.start();
|
|
35818
|
+
if (!startResult.ok) {
|
|
35819
|
+
return err(new Error(`DicomReceiver: worker start failed on port ${String(port)}: ${startResult.error.message}`));
|
|
35820
|
+
}
|
|
35821
|
+
this.workers.set(port, worker);
|
|
35822
|
+
return ok(worker);
|
|
35823
|
+
}
|
|
35824
|
+
/** Stops a single worker: stop process, clean temp dir, remove from pool. */
|
|
35825
|
+
async stopWorker(worker) {
|
|
35826
|
+
if (worker.remoteSocket !== void 0 && !worker.remoteSocket.destroyed) {
|
|
35827
|
+
worker.remoteSocket.destroy();
|
|
35828
|
+
}
|
|
35829
|
+
if (worker.workerSocket !== void 0 && !worker.workerSocket.destroyed) {
|
|
35830
|
+
worker.workerSocket.destroy();
|
|
35831
|
+
}
|
|
35832
|
+
await worker.dcmrecv.stop();
|
|
35833
|
+
worker.dcmrecv[Symbol.dispose]();
|
|
35834
|
+
await removeDirSafe(worker.tempDir);
|
|
35835
|
+
this.workers.delete(worker.port);
|
|
35836
|
+
}
|
|
35837
|
+
/** Pre-emptively spawns workers to keep idle count >= minPoolSize. */
|
|
35838
|
+
async replenishPool() {
|
|
35839
|
+
const status = this.poolStatus;
|
|
35840
|
+
const needed = this.minPoolSize - status.idle;
|
|
35841
|
+
const capacity = this.maxPoolSize - status.total;
|
|
35842
|
+
const toSpawn = Math.min(needed, capacity);
|
|
35843
|
+
if (toSpawn <= 0) return;
|
|
35844
|
+
const promises = [];
|
|
35845
|
+
for (let i = 0; i < toSpawn; i++) {
|
|
35846
|
+
promises.push(this.spawnWorker());
|
|
35847
|
+
}
|
|
35848
|
+
const results = await Promise.all(promises);
|
|
35849
|
+
for (const result of results) {
|
|
35850
|
+
if (!result.ok) {
|
|
35851
|
+
this.emit("error", { error: result.error });
|
|
35852
|
+
}
|
|
35853
|
+
}
|
|
35854
|
+
}
|
|
35855
|
+
/** Stops excess idle workers when idle count > minPoolSize + 2. */
|
|
35856
|
+
async scaleDown() {
|
|
35857
|
+
const idleWorkers = [];
|
|
35858
|
+
for (const w of this.workers.values()) {
|
|
35859
|
+
if (w.state === "idle") idleWorkers.push(w);
|
|
35860
|
+
}
|
|
35861
|
+
const excess = idleWorkers.length - (this.minPoolSize + 2);
|
|
35862
|
+
if (excess <= 0) return;
|
|
35863
|
+
const toStop = idleWorkers.slice(0, excess);
|
|
35864
|
+
const promises = [];
|
|
35865
|
+
for (const w of toStop) {
|
|
35866
|
+
promises.push(this.stopWorker(w));
|
|
35867
|
+
}
|
|
35868
|
+
await Promise.all(promises);
|
|
35869
|
+
}
|
|
35870
|
+
// -----------------------------------------------------------------------
|
|
35871
|
+
// Worker event wiring
|
|
35872
|
+
// -----------------------------------------------------------------------
|
|
35873
|
+
/** Wires FILE_RECEIVED and ASSOCIATION_COMPLETE events on a worker. */
|
|
35874
|
+
wireWorkerEvents(worker) {
|
|
35875
|
+
this.wireFileReceived(worker);
|
|
35876
|
+
this.wireAssociationComplete(worker);
|
|
35877
|
+
}
|
|
35878
|
+
/** Wires FILE_RECEIVED from dcmrecv worker to handleFileReceived. */
|
|
35879
|
+
wireFileReceived(worker) {
|
|
35880
|
+
worker.dcmrecv.onFileReceived((data) => {
|
|
35881
|
+
void this.handleFileReceived(worker, data);
|
|
35882
|
+
});
|
|
35883
|
+
}
|
|
35884
|
+
/** Moves a received file, opens it as DicomInstance, and emits FILE_RECEIVED. */
|
|
35885
|
+
async handleFileReceived(worker, data) {
|
|
35886
|
+
if (worker.associationDir === void 0 || worker.associationId === void 0) return;
|
|
35887
|
+
const srcPath = data.filePath;
|
|
35888
|
+
const destPath = path__namespace.join(worker.associationDir, path__namespace.basename(srcPath));
|
|
35889
|
+
const assocId = worker.associationId;
|
|
35890
|
+
const assocDir = worker.associationDir;
|
|
35891
|
+
const moveResult = await moveFile(srcPath, destPath);
|
|
35892
|
+
const finalPath = moveResult.ok ? destPath : srcPath;
|
|
35893
|
+
worker.files.push(finalPath);
|
|
35894
|
+
worker.fileSizes.push(await statFileSafe(finalPath));
|
|
35895
|
+
const openResult = await DicomInstance.open(finalPath);
|
|
35896
|
+
if (!openResult.ok) {
|
|
35897
|
+
this.emit("error", {
|
|
35898
|
+
error: openResult.error,
|
|
35899
|
+
filePath: finalPath,
|
|
35900
|
+
associationId: assocId,
|
|
35901
|
+
associationDir: assocDir,
|
|
35902
|
+
callingAE: data.callingAE,
|
|
35903
|
+
calledAE: data.calledAE,
|
|
35904
|
+
source: data.source
|
|
35905
|
+
});
|
|
35906
|
+
return;
|
|
35907
|
+
}
|
|
35908
|
+
this.emit("FILE_RECEIVED", {
|
|
35909
|
+
filePath: finalPath,
|
|
35910
|
+
associationId: assocId,
|
|
35911
|
+
associationDir: assocDir,
|
|
35912
|
+
callingAE: data.callingAE,
|
|
35913
|
+
calledAE: data.calledAE,
|
|
35914
|
+
source: data.source,
|
|
35915
|
+
instance: openResult.value
|
|
35916
|
+
});
|
|
35917
|
+
}
|
|
35918
|
+
/** Returns worker to idle pool on association complete, emits summary. */
|
|
35919
|
+
wireAssociationComplete(worker) {
|
|
35920
|
+
worker.dcmrecv.onAssociationComplete((data) => {
|
|
35921
|
+
const assocId = worker.associationId ?? data.associationId;
|
|
35922
|
+
const assocDir = worker.associationDir ?? "";
|
|
35923
|
+
const files = [...worker.files];
|
|
35924
|
+
const endAt = Date.now();
|
|
35925
|
+
const startAt = worker.startAt ?? endAt;
|
|
35926
|
+
const totalBytes = sumArray(worker.fileSizes);
|
|
35927
|
+
const elapsedMs = endAt - startAt;
|
|
35928
|
+
const bytesPerSecond = elapsedMs > 0 ? Math.round(totalBytes / elapsedMs * 1e3) : 0;
|
|
35929
|
+
this.emit("ASSOCIATION_COMPLETE", {
|
|
35930
|
+
associationId: assocId,
|
|
35931
|
+
associationDir: assocDir,
|
|
35932
|
+
callingAE: data.callingAE,
|
|
35933
|
+
calledAE: data.calledAE,
|
|
35934
|
+
source: data.source,
|
|
35935
|
+
files,
|
|
35936
|
+
durationMs: data.durationMs,
|
|
35937
|
+
endReason: data.endReason,
|
|
35938
|
+
totalBytes,
|
|
35939
|
+
bytesPerSecond,
|
|
35940
|
+
startAt,
|
|
35941
|
+
endAt
|
|
35942
|
+
});
|
|
35943
|
+
worker.state = "idle";
|
|
35944
|
+
worker.associationId = void 0;
|
|
35945
|
+
worker.associationDir = void 0;
|
|
35946
|
+
worker.files = [];
|
|
35947
|
+
worker.fileSizes = [];
|
|
35948
|
+
worker.startAt = void 0;
|
|
35949
|
+
worker.remoteSocket = void 0;
|
|
35950
|
+
worker.workerSocket = void 0;
|
|
35951
|
+
void this.scaleDown();
|
|
35952
|
+
});
|
|
35953
|
+
}
|
|
35954
|
+
// -----------------------------------------------------------------------
|
|
35955
|
+
// Abort signal
|
|
35956
|
+
// -----------------------------------------------------------------------
|
|
35957
|
+
/** Wires an AbortSignal to stop the receiver. */
|
|
35958
|
+
wireAbortSignal(signal) {
|
|
35959
|
+
if (signal.aborted) {
|
|
35960
|
+
void this.stop();
|
|
35961
|
+
return;
|
|
35962
|
+
}
|
|
35963
|
+
this.abortHandler = () => {
|
|
35964
|
+
void this.stop();
|
|
35965
|
+
};
|
|
35966
|
+
signal.addEventListener("abort", this.abortHandler, { once: true });
|
|
35967
|
+
}
|
|
35968
|
+
};
|
|
35969
|
+
async function ensureDirectory(dirPath) {
|
|
35970
|
+
try {
|
|
35971
|
+
await fs__namespace.mkdir(dirPath, { recursive: true });
|
|
35972
|
+
return ok(void 0);
|
|
35973
|
+
} catch (e) {
|
|
35974
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
35975
|
+
return err(new Error(`Failed to create directory ${dirPath}: ${msg}`));
|
|
35976
|
+
}
|
|
35977
|
+
}
|
|
35978
|
+
async function moveFile(src, dest) {
|
|
35979
|
+
try {
|
|
35980
|
+
await fs__namespace.rename(src, dest);
|
|
35981
|
+
return ok(void 0);
|
|
35982
|
+
} catch {
|
|
35983
|
+
try {
|
|
35984
|
+
await fs__namespace.copyFile(src, dest);
|
|
35985
|
+
await fs__namespace.unlink(src);
|
|
35986
|
+
return ok(void 0);
|
|
35987
|
+
} catch (e) {
|
|
35988
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
35989
|
+
return err(new Error(`Failed to move file ${src} \u2192 ${dest}: ${msg}`));
|
|
35990
|
+
}
|
|
35991
|
+
}
|
|
35992
|
+
}
|
|
35993
|
+
async function statFileSafe(filePath) {
|
|
35994
|
+
try {
|
|
35995
|
+
const stat3 = await fs__namespace.stat(filePath);
|
|
35996
|
+
return stat3.size;
|
|
35997
|
+
} catch {
|
|
35998
|
+
return 0;
|
|
35999
|
+
}
|
|
36000
|
+
}
|
|
36001
|
+
function sumArray(arr) {
|
|
36002
|
+
let total = 0;
|
|
36003
|
+
for (let i = 0; i < arr.length; i++) {
|
|
36004
|
+
total += arr[i] ?? 0;
|
|
36005
|
+
}
|
|
36006
|
+
return total;
|
|
36007
|
+
}
|
|
36008
|
+
async function removeDirSafe(dirPath) {
|
|
36009
|
+
try {
|
|
36010
|
+
await fs__namespace.rm(dirPath, { recursive: true, force: true });
|
|
36011
|
+
} catch {
|
|
36012
|
+
}
|
|
36013
|
+
}
|
|
36014
|
+
function delay(ms) {
|
|
36015
|
+
return new Promise((resolve) => {
|
|
36016
|
+
setTimeout(resolve, ms);
|
|
36017
|
+
});
|
|
36018
|
+
}
|
|
34981
36019
|
|
|
34982
36020
|
// src/pacs/types.ts
|
|
34983
36021
|
var QueryLevel = {
|
|
@@ -35060,11 +36098,11 @@ function addFilterKeys(keys, filter) {
|
|
|
35060
36098
|
for (let i = 0; i < fields.length; i += 1) {
|
|
35061
36099
|
const field = fields[i];
|
|
35062
36100
|
if (field === void 0) continue;
|
|
35063
|
-
const
|
|
35064
|
-
if (
|
|
36101
|
+
const tag2 = FILTER_TAG_MAP[field];
|
|
36102
|
+
if (tag2 === void 0) continue;
|
|
35065
36103
|
const value = filter[field];
|
|
35066
36104
|
if (typeof value !== "string") continue;
|
|
35067
|
-
keys.push(`${
|
|
36105
|
+
keys.push(`${tag2}=${value}`);
|
|
35068
36106
|
}
|
|
35069
36107
|
}
|
|
35070
36108
|
function extractTag(key) {
|
|
@@ -35073,11 +36111,11 @@ function extractTag(key) {
|
|
|
35073
36111
|
}
|
|
35074
36112
|
function addReturnKeys(keys, returnKeys) {
|
|
35075
36113
|
for (let i = 0; i < returnKeys.length; i += 1) {
|
|
35076
|
-
const
|
|
35077
|
-
if (
|
|
35078
|
-
const alreadyPresent = keys.some((k) => extractTag(k) ===
|
|
36114
|
+
const tag2 = returnKeys[i];
|
|
36115
|
+
if (tag2 === void 0) continue;
|
|
36116
|
+
const alreadyPresent = keys.some((k) => extractTag(k) === tag2);
|
|
35079
36117
|
if (!alreadyPresent) {
|
|
35080
|
-
keys.push(`${
|
|
36118
|
+
keys.push(`${tag2}=`);
|
|
35081
36119
|
}
|
|
35082
36120
|
}
|
|
35083
36121
|
}
|
|
@@ -35105,13 +36143,13 @@ function buildWorklistKeys(filter) {
|
|
|
35105
36143
|
var MAX_RESPONSE_FILES = 1e4;
|
|
35106
36144
|
async function createTempDir() {
|
|
35107
36145
|
return stderrLib.tryCatch(
|
|
35108
|
-
() =>
|
|
36146
|
+
() => fs.mkdtemp(path.join(os.tmpdir(), "dcmtk-pacs-")),
|
|
35109
36147
|
(e) => new Error(`Failed to create temp directory: ${e.message}`)
|
|
35110
36148
|
);
|
|
35111
36149
|
}
|
|
35112
36150
|
async function listDcmFiles(directory) {
|
|
35113
36151
|
try {
|
|
35114
|
-
const entries = await
|
|
36152
|
+
const entries = await fs.readdir(directory);
|
|
35115
36153
|
const dcmFiles = [];
|
|
35116
36154
|
for (let i = 0; i < entries.length; i += 1) {
|
|
35117
36155
|
const entry = entries[i];
|
|
@@ -35129,7 +36167,7 @@ async function listDcmFiles(directory) {
|
|
|
35129
36167
|
}
|
|
35130
36168
|
async function cleanupTempDir(directory) {
|
|
35131
36169
|
try {
|
|
35132
|
-
await
|
|
36170
|
+
await fs.rm(directory, { recursive: true, force: true });
|
|
35133
36171
|
} catch {
|
|
35134
36172
|
}
|
|
35135
36173
|
}
|
|
@@ -35588,6 +36626,7 @@ async function retry(operation, options) {
|
|
|
35588
36626
|
}
|
|
35589
36627
|
|
|
35590
36628
|
exports.AETitleSchema = AETitleSchema;
|
|
36629
|
+
exports.AssociationTracker = AssociationTracker;
|
|
35591
36630
|
exports.ChangeSet = ChangeSet;
|
|
35592
36631
|
exports.ColorConversion = ColorConversion;
|
|
35593
36632
|
exports.DCMPRSCP_FATAL_EVENTS = DCMPRSCP_FATAL_EVENTS;
|
|
@@ -35618,7 +36657,8 @@ exports.Dcmrecv = Dcmrecv;
|
|
|
35618
36657
|
exports.DcmrecvEvent = DcmrecvEvent;
|
|
35619
36658
|
exports.DcmtkProcess = DcmtkProcess;
|
|
35620
36659
|
exports.DicomDataset = DicomDataset;
|
|
35621
|
-
exports.
|
|
36660
|
+
exports.DicomInstance = DicomInstance;
|
|
36661
|
+
exports.DicomReceiver = DicomReceiver;
|
|
35622
36662
|
exports.DicomTagPathSchema = DicomTagPathSchema;
|
|
35623
36663
|
exports.DicomTagSchema = DicomTagSchema;
|
|
35624
36664
|
exports.FilenameMode = FilenameMode;
|
|
@@ -35637,6 +36677,7 @@ exports.PacsClient = PacsClient;
|
|
|
35637
36677
|
exports.PortSchema = PortSchema;
|
|
35638
36678
|
exports.PreferredTransferSyntax = PreferredTransferSyntax;
|
|
35639
36679
|
exports.ProcessState = ProcessState;
|
|
36680
|
+
exports.ProposedTransferSyntax = ProposedTransferSyntax;
|
|
35640
36681
|
exports.QueryLevel = QueryLevel;
|
|
35641
36682
|
exports.QueryModel = QueryModel;
|
|
35642
36683
|
exports.REQUIRED_BINARIES = REQUIRED_BINARIES;
|
|
@@ -35742,9 +36783,9 @@ exports.sopClassNameFromUID = sopClassNameFromUID;
|
|
|
35742
36783
|
exports.spawnCommand = spawnCommand;
|
|
35743
36784
|
exports.stl2dcm = stl2dcm;
|
|
35744
36785
|
exports.storescu = storescu;
|
|
36786
|
+
exports.tag = tag;
|
|
35745
36787
|
exports.tagPathToSegments = tagPathToSegments;
|
|
35746
36788
|
exports.termscu = termscu;
|
|
35747
|
-
exports.unwrap = unwrap;
|
|
35748
36789
|
exports.xml2dcm = xml2dcm;
|
|
35749
36790
|
exports.xml2dsr = xml2dsr;
|
|
35750
36791
|
exports.xmlToJson = xmlToJson;
|