@ubercode/dcmtk 0.11.1 → 0.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dicom.cjs +6 -1
- package/dist/dicom.cjs.map +1 -1
- package/dist/dicom.js +6 -1
- package/dist/dicom.js.map +1 -1
- package/dist/{index--dDQfutR.d.ts → index-B5FQoIqM.d.ts} +47 -5
- package/dist/{index-CVDMaxd8.d.cts → index-DV8HaHEX.d.cts} +47 -5
- package/dist/index.cjs +97 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +97 -15
- package/dist/index.js.map +1 -1
- package/dist/servers.cjs +97 -15
- package/dist/servers.cjs.map +1 -1
- package/dist/servers.d.cts +1 -1
- package/dist/servers.d.ts +1 -1
- package/dist/servers.js +97 -15
- package/dist/servers.js.map +1 -1
- package/dist/tools.cjs +6 -1
- package/dist/tools.cjs.map +1 -1
- package/dist/tools.js +6 -1
- package/dist/tools.js.map +1 -1
- package/package.json +1 -1
package/dist/servers.cjs
CHANGED
|
@@ -2246,7 +2246,12 @@ var parser = new fastXmlParser.XMLParser({
|
|
|
2246
2246
|
ignoreAttributes: false,
|
|
2247
2247
|
attributeNamePrefix: "@_",
|
|
2248
2248
|
parseTagValue: false,
|
|
2249
|
-
isArray: (name) => ARRAY_TAG_NAMES.has(name)
|
|
2249
|
+
isArray: (name) => ARRAY_TAG_NAMES.has(name),
|
|
2250
|
+
// DICOM XML does not use XML entities. Disable entity processing to avoid
|
|
2251
|
+
// the default expansion limit (1000) which rejects files with >1000 tags.
|
|
2252
|
+
// Without this, large studies fall through to dcm2json which hangs on
|
|
2253
|
+
// compressed pixel data (DCMTK bug).
|
|
2254
|
+
processEntities: false
|
|
2250
2255
|
});
|
|
2251
2256
|
function xmlToJson(xml) {
|
|
2252
2257
|
try {
|
|
@@ -2855,6 +2860,9 @@ var Worker = class {
|
|
|
2855
2860
|
__publicField(this, "_state", "idle");
|
|
2856
2861
|
__publicField(this, "_context");
|
|
2857
2862
|
__publicField(this, "_pending", /* @__PURE__ */ new Set());
|
|
2863
|
+
__publicField(this, "_instancePending", /* @__PURE__ */ new Set());
|
|
2864
|
+
__publicField(this, "_instancesReceived", 0);
|
|
2865
|
+
__publicField(this, "_instanceErrors", 0);
|
|
2858
2866
|
__publicField(this, "_files", []);
|
|
2859
2867
|
__publicField(this, "_fileSizes", []);
|
|
2860
2868
|
__publicField(this, "_outputLines", []);
|
|
@@ -2892,6 +2900,9 @@ var Worker = class {
|
|
|
2892
2900
|
this._fileSizes.length = 0;
|
|
2893
2901
|
this._outputLines.length = 0;
|
|
2894
2902
|
this._pending.clear();
|
|
2903
|
+
this._instancePending.clear();
|
|
2904
|
+
this._instancesReceived = 0;
|
|
2905
|
+
this._instanceErrors = 0;
|
|
2895
2906
|
}
|
|
2896
2907
|
/** Returns the worker to idle and clears all association state. */
|
|
2897
2908
|
endAssociation() {
|
|
@@ -2901,6 +2912,9 @@ var Worker = class {
|
|
|
2901
2912
|
this._fileSizes.length = 0;
|
|
2902
2913
|
this._outputLines.length = 0;
|
|
2903
2914
|
this._pending.clear();
|
|
2915
|
+
this._instancePending.clear();
|
|
2916
|
+
this._instancesReceived = 0;
|
|
2917
|
+
this._instanceErrors = 0;
|
|
2904
2918
|
this._remoteSocket = void 0;
|
|
2905
2919
|
this._workerSocket = void 0;
|
|
2906
2920
|
}
|
|
@@ -2911,6 +2925,35 @@ var Worker = class {
|
|
|
2911
2925
|
this._pending.delete(promise);
|
|
2912
2926
|
});
|
|
2913
2927
|
}
|
|
2928
|
+
/** Tracks an in-flight instance parsing promise; auto-removes on completion. */
|
|
2929
|
+
trackInstance(promise) {
|
|
2930
|
+
this._instancePending.add(promise);
|
|
2931
|
+
void promise.finally(() => {
|
|
2932
|
+
this._instancePending.delete(promise);
|
|
2933
|
+
});
|
|
2934
|
+
}
|
|
2935
|
+
/** Records a successful instance parse. */
|
|
2936
|
+
recordInstanceSuccess() {
|
|
2937
|
+
this._instancesReceived++;
|
|
2938
|
+
}
|
|
2939
|
+
/** Records a failed instance parse. */
|
|
2940
|
+
recordInstanceError() {
|
|
2941
|
+
this._instanceErrors++;
|
|
2942
|
+
}
|
|
2943
|
+
/** Awaits all in-flight instance parsing promises. */
|
|
2944
|
+
async drainInstancePending() {
|
|
2945
|
+
if (this._instancePending.size > 0) {
|
|
2946
|
+
await Promise.all(this._instancePending);
|
|
2947
|
+
}
|
|
2948
|
+
}
|
|
2949
|
+
/** Number of successfully parsed instances in this association. */
|
|
2950
|
+
get instancesReceived() {
|
|
2951
|
+
return this._instancesReceived;
|
|
2952
|
+
}
|
|
2953
|
+
/** Number of failed instance parses in this association. */
|
|
2954
|
+
get instanceErrors() {
|
|
2955
|
+
return this._instanceErrors;
|
|
2956
|
+
}
|
|
2914
2957
|
/** Records a successfully received file path and its size. */
|
|
2915
2958
|
recordFile(filePath, size) {
|
|
2916
2959
|
this._files.push(filePath);
|
|
@@ -3082,7 +3125,16 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
3082
3125
|
return this.on("INSTANCE_RECEIVED", listener);
|
|
3083
3126
|
}
|
|
3084
3127
|
/**
|
|
3085
|
-
* Registers a listener for
|
|
3128
|
+
* Registers a listener for DicomInstance.open failures.
|
|
3129
|
+
*
|
|
3130
|
+
* @param listener - Callback receiving instance error data
|
|
3131
|
+
* @returns this for chaining
|
|
3132
|
+
*/
|
|
3133
|
+
onInstanceError(listener) {
|
|
3134
|
+
return this.on("INSTANCE_ERROR", listener);
|
|
3135
|
+
}
|
|
3136
|
+
/**
|
|
3137
|
+
* Registers a listener for completed associations (file moves done).
|
|
3086
3138
|
*
|
|
3087
3139
|
* @param listener - Callback receiving association data
|
|
3088
3140
|
* @returns this for chaining
|
|
@@ -3090,6 +3142,15 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
3090
3142
|
onAssociationComplete(listener) {
|
|
3091
3143
|
return this.on("ASSOCIATION_COMPLETE", listener);
|
|
3092
3144
|
}
|
|
3145
|
+
/**
|
|
3146
|
+
* Registers a listener for fully finalized associations (all parsing done).
|
|
3147
|
+
*
|
|
3148
|
+
* @param listener - Callback receiving finalized data with instance counts
|
|
3149
|
+
* @returns this for chaining
|
|
3150
|
+
*/
|
|
3151
|
+
onAssociationFinalized(listener) {
|
|
3152
|
+
return this.on("ASSOCIATION_FINALIZED", listener);
|
|
3153
|
+
}
|
|
3093
3154
|
/**
|
|
3094
3155
|
* Registers a listener for new associations (bubbled from workers).
|
|
3095
3156
|
*
|
|
@@ -3419,28 +3480,33 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
3419
3480
|
calledAE: data.calledAE,
|
|
3420
3481
|
source: data.source
|
|
3421
3482
|
});
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
emitInstanceReceived(filePath, fileSize, data, ctx) {
|
|
3426
|
-
const errorCtx = {
|
|
3427
|
-
filePath,
|
|
3483
|
+
const instanceCtx = {
|
|
3484
|
+
filePath: finalPath,
|
|
3485
|
+
fileSize,
|
|
3428
3486
|
associationId: ctx.associationId,
|
|
3429
3487
|
associationDir: ctx.associationDir,
|
|
3430
3488
|
callingAE: data.callingAE,
|
|
3431
3489
|
calledAE: data.calledAE,
|
|
3432
3490
|
source: data.source
|
|
3433
3491
|
};
|
|
3434
|
-
|
|
3492
|
+
worker.trackInstance(this.parseAndEmitInstance(worker, instanceCtx));
|
|
3493
|
+
}
|
|
3494
|
+
/** Parses a DICOM file and emits INSTANCE_RECEIVED or INSTANCE_ERROR. */
|
|
3495
|
+
async parseAndEmitInstance(worker, ctx) {
|
|
3496
|
+
try {
|
|
3497
|
+
const openResult = await DicomInstance.open(ctx.filePath);
|
|
3435
3498
|
if (!openResult.ok) {
|
|
3436
|
-
|
|
3499
|
+
worker.recordInstanceError();
|
|
3500
|
+
this.emit("INSTANCE_ERROR", { error: openResult.error, thrown: false, ...ctx });
|
|
3437
3501
|
return;
|
|
3438
3502
|
}
|
|
3439
|
-
|
|
3440
|
-
|
|
3503
|
+
worker.recordInstanceSuccess();
|
|
3504
|
+
this.emit("INSTANCE_RECEIVED", { ...ctx, instance: openResult.value });
|
|
3505
|
+
} catch (thrown) {
|
|
3441
3506
|
const error = thrown instanceof Error ? thrown : new Error(String(thrown));
|
|
3442
|
-
|
|
3443
|
-
|
|
3507
|
+
worker.recordInstanceError();
|
|
3508
|
+
this.emit("INSTANCE_ERROR", { error, thrown: true, ...ctx });
|
|
3509
|
+
}
|
|
3444
3510
|
}
|
|
3445
3511
|
/** Returns worker to idle pool on association complete, emits summary. */
|
|
3446
3512
|
wireAssociationComplete(worker) {
|
|
@@ -3448,10 +3514,12 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
3448
3514
|
void this.finalizeAssociation(worker, data);
|
|
3449
3515
|
});
|
|
3450
3516
|
}
|
|
3451
|
-
/** Awaits
|
|
3517
|
+
/** Awaits file ops, emits ASSOCIATION_COMPLETE, awaits parsing, emits ASSOCIATION_FINALIZED. */
|
|
3452
3518
|
async finalizeAssociation(worker, data) {
|
|
3453
3519
|
await worker.drainPendingFiles();
|
|
3454
3520
|
this.emitAssociationComplete(worker, data);
|
|
3521
|
+
await worker.drainInstancePending();
|
|
3522
|
+
this.emitAssociationFinalized(worker, data);
|
|
3455
3523
|
worker.endAssociation();
|
|
3456
3524
|
void this.scaleDown();
|
|
3457
3525
|
}
|
|
@@ -3483,6 +3551,20 @@ var DicomReceiver = class _DicomReceiver extends events.EventEmitter {
|
|
|
3483
3551
|
output
|
|
3484
3552
|
});
|
|
3485
3553
|
}
|
|
3554
|
+
/** Emits ASSOCIATION_FINALIZED after all instance parsing is done. */
|
|
3555
|
+
emitAssociationFinalized(worker, data) {
|
|
3556
|
+
const ctx = worker.context;
|
|
3557
|
+
this.emit("ASSOCIATION_FINALIZED", {
|
|
3558
|
+
associationId: ctx?.associationId ?? data.associationId,
|
|
3559
|
+
associationDir: ctx?.associationDir ?? "",
|
|
3560
|
+
callingAE: data.callingAE,
|
|
3561
|
+
calledAE: data.calledAE,
|
|
3562
|
+
source: data.source,
|
|
3563
|
+
files: [...worker.files],
|
|
3564
|
+
instancesReceived: worker.instancesReceived,
|
|
3565
|
+
instanceErrors: worker.instanceErrors
|
|
3566
|
+
});
|
|
3567
|
+
}
|
|
3486
3568
|
/** Bubbles ASSOCIATION_RECEIVED from dcmrecv worker. */
|
|
3487
3569
|
wireAssociationReceived(worker) {
|
|
3488
3570
|
worker.dcmrecv.onEvent("ASSOCIATION_RECEIVED", (data) => {
|