@cosyte/dicom 0.0.11 → 0.0.12
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/CHANGELOG.md +59 -0
- package/README.md +1 -1
- package/dist/index.cjs +27 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -1
- package/dist/index.d.ts +38 -1
- package/dist/index.mjs +27 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -307,6 +307,26 @@ interface ParseOptions {
|
|
|
307
307
|
* When `true`, every Tier-2 warning is escalated to a thrown
|
|
308
308
|
* `DicomParseError` carrying the warning code. Default `false`.
|
|
309
309
|
*
|
|
310
|
+
* 🛑 **THE ESCALATED DIAGNOSTIC CARRIES SOURCE BYTES THAT THE WARNING DOES
|
|
311
|
+
* NOT.** A `DicomParseWarning.message` is a frozen registry string with only
|
|
312
|
+
* structural tokens filled in, so it is safe to log whole; the
|
|
313
|
+
* `DicomParseError` this option raises in its place also carries `snippet`,
|
|
314
|
+
* **16 raw bytes, unredacted** (D-10), read at the warning's own `byteOffset`.
|
|
315
|
+
* **Which element those bytes belong to is not contracted**: that offset's
|
|
316
|
+
* frame follows where the element was read - file-absolute at the root,
|
|
317
|
+
* relative to the enclosing slice inside a defined-length Sequence or Item,
|
|
318
|
+
* and into the inflated stream under Deflated Explicit VR LE - while the
|
|
319
|
+
* snippet is cut from whichever buffer the parse is holding, so the two can
|
|
320
|
+
* disagree and the bytes can be an unrelated element's value. Do not reason
|
|
321
|
+
* from a code's message to what its snippet holds - **measure it, and treat
|
|
322
|
+
* every one of them as document content.**
|
|
323
|
+
*
|
|
324
|
+
* That is the documented design of `snippet` rather than a defect in any one
|
|
325
|
+
* code, and turning this option on does not change what any of them means -
|
|
326
|
+
* but a message-only PHI review of the lenient path does not transfer to the
|
|
327
|
+
* strict one. Log `err.code`, `err.byteOffset` and `err.message`; treat
|
|
328
|
+
* `err.snippet` as PHI.
|
|
329
|
+
*
|
|
310
330
|
* Omit (do not pass `undefined`) to use the default.
|
|
311
331
|
*/
|
|
312
332
|
readonly strict?: boolean;
|
|
@@ -420,6 +440,7 @@ declare const WARNING_CODES: {
|
|
|
420
440
|
readonly DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED";
|
|
421
441
|
readonly DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED";
|
|
422
442
|
readonly DICOM_DEIDENT_METHOD_NOT_ADDED: "DICOM_DEIDENT_METHOD_NOT_ADDED";
|
|
443
|
+
readonly DICOM_DEIDENT_METHOD_NOT_LO: "DICOM_DEIDENT_METHOD_NOT_LO";
|
|
423
444
|
readonly DICOM_DEIDENT_METHOD_PRIOR_RETAINED: "DICOM_DEIDENT_METHOD_PRIOR_RETAINED";
|
|
424
445
|
readonly DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE";
|
|
425
446
|
readonly DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE";
|
|
@@ -2692,6 +2713,19 @@ interface DeidentifyOptions {
|
|
|
2692
2713
|
* Text **added to** `(0012,0063)` De-identification Method. Default names the
|
|
2693
2714
|
* Basic Profile and the active options.
|
|
2694
2715
|
*
|
|
2716
|
+
* **The default is multi-valued, one Value per name, and that is a conformance
|
|
2717
|
+
* fix rather than a style choice.** PS3.5 2026c Table 6.2-1 caps an `LO` at
|
|
2718
|
+
* **64 characters per Value**, and `(0012,0063)` is `1-n`; the single-value
|
|
2719
|
+
* default this replaced measured 76 characters with no options and 272 with
|
|
2720
|
+
* all nine, so every run this library ever made wrote a value no `LO` may
|
|
2721
|
+
* legally carry. The Profile name is now one Value of 61 characters and each
|
|
2722
|
+
* active option is its own, so no option subset can exceed the maximum.
|
|
2723
|
+
*
|
|
2724
|
+
* **Your string is not bounded here.** A value of your own longer than 64
|
|
2725
|
+
* characters is written through as given, with no warning: it is yours, and
|
|
2726
|
+
* splitting or truncating it would invent a record you did not write. Split it
|
|
2727
|
+
* on `\` yourself if a strict receiver is in your path.
|
|
2728
|
+
*
|
|
2695
2729
|
* PS3.15 E.1.1 says this string is "inserted in or added to" the attribute, so
|
|
2696
2730
|
* a value the incoming Data Set already carried is kept and this one is
|
|
2697
2731
|
* appended after a `\` as a further value of the `1-n` attribute - the
|
|
@@ -2721,7 +2755,10 @@ interface DeidentifyOptions {
|
|
|
2721
2755
|
*
|
|
2722
2756
|
* `DICOM_DEIDENT_METHOD_NOT_ADDED` means "the length ceiling was reached", never
|
|
2723
2757
|
* "every fallback is disclosed": a `(0012,0063)` a file encoded under a VR other
|
|
2724
|
-
* than `LO` is also replaced, and that one
|
|
2758
|
+
* than `LO` is also replaced, and that one raises `DICOM_DEIDENT_METHOD_NOT_LO`.
|
|
2759
|
+
* Two codes rather than one because the causes are unrelated - the chain outgrew
|
|
2760
|
+
* the VR, or the bytes were never in that VR at all - and a prior value that is
|
|
2761
|
+
* empty or padding only raises neither, because nothing was lost.
|
|
2725
2762
|
*
|
|
2726
2763
|
* When the prior value **is** kept, `report.warnings` carries
|
|
2727
2764
|
* `DICOM_DEIDENT_METHOD_PRIOR_RETAINED`: `(0012,0063)` is not in Table E.1-1, so
|
package/dist/index.d.ts
CHANGED
|
@@ -307,6 +307,26 @@ interface ParseOptions {
|
|
|
307
307
|
* When `true`, every Tier-2 warning is escalated to a thrown
|
|
308
308
|
* `DicomParseError` carrying the warning code. Default `false`.
|
|
309
309
|
*
|
|
310
|
+
* 🛑 **THE ESCALATED DIAGNOSTIC CARRIES SOURCE BYTES THAT THE WARNING DOES
|
|
311
|
+
* NOT.** A `DicomParseWarning.message` is a frozen registry string with only
|
|
312
|
+
* structural tokens filled in, so it is safe to log whole; the
|
|
313
|
+
* `DicomParseError` this option raises in its place also carries `snippet`,
|
|
314
|
+
* **16 raw bytes, unredacted** (D-10), read at the warning's own `byteOffset`.
|
|
315
|
+
* **Which element those bytes belong to is not contracted**: that offset's
|
|
316
|
+
* frame follows where the element was read - file-absolute at the root,
|
|
317
|
+
* relative to the enclosing slice inside a defined-length Sequence or Item,
|
|
318
|
+
* and into the inflated stream under Deflated Explicit VR LE - while the
|
|
319
|
+
* snippet is cut from whichever buffer the parse is holding, so the two can
|
|
320
|
+
* disagree and the bytes can be an unrelated element's value. Do not reason
|
|
321
|
+
* from a code's message to what its snippet holds - **measure it, and treat
|
|
322
|
+
* every one of them as document content.**
|
|
323
|
+
*
|
|
324
|
+
* That is the documented design of `snippet` rather than a defect in any one
|
|
325
|
+
* code, and turning this option on does not change what any of them means -
|
|
326
|
+
* but a message-only PHI review of the lenient path does not transfer to the
|
|
327
|
+
* strict one. Log `err.code`, `err.byteOffset` and `err.message`; treat
|
|
328
|
+
* `err.snippet` as PHI.
|
|
329
|
+
*
|
|
310
330
|
* Omit (do not pass `undefined`) to use the default.
|
|
311
331
|
*/
|
|
312
332
|
readonly strict?: boolean;
|
|
@@ -420,6 +440,7 @@ declare const WARNING_CODES: {
|
|
|
420
440
|
readonly DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED";
|
|
421
441
|
readonly DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED";
|
|
422
442
|
readonly DICOM_DEIDENT_METHOD_NOT_ADDED: "DICOM_DEIDENT_METHOD_NOT_ADDED";
|
|
443
|
+
readonly DICOM_DEIDENT_METHOD_NOT_LO: "DICOM_DEIDENT_METHOD_NOT_LO";
|
|
423
444
|
readonly DICOM_DEIDENT_METHOD_PRIOR_RETAINED: "DICOM_DEIDENT_METHOD_PRIOR_RETAINED";
|
|
424
445
|
readonly DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE";
|
|
425
446
|
readonly DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE";
|
|
@@ -2692,6 +2713,19 @@ interface DeidentifyOptions {
|
|
|
2692
2713
|
* Text **added to** `(0012,0063)` De-identification Method. Default names the
|
|
2693
2714
|
* Basic Profile and the active options.
|
|
2694
2715
|
*
|
|
2716
|
+
* **The default is multi-valued, one Value per name, and that is a conformance
|
|
2717
|
+
* fix rather than a style choice.** PS3.5 2026c Table 6.2-1 caps an `LO` at
|
|
2718
|
+
* **64 characters per Value**, and `(0012,0063)` is `1-n`; the single-value
|
|
2719
|
+
* default this replaced measured 76 characters with no options and 272 with
|
|
2720
|
+
* all nine, so every run this library ever made wrote a value no `LO` may
|
|
2721
|
+
* legally carry. The Profile name is now one Value of 61 characters and each
|
|
2722
|
+
* active option is its own, so no option subset can exceed the maximum.
|
|
2723
|
+
*
|
|
2724
|
+
* **Your string is not bounded here.** A value of your own longer than 64
|
|
2725
|
+
* characters is written through as given, with no warning: it is yours, and
|
|
2726
|
+
* splitting or truncating it would invent a record you did not write. Split it
|
|
2727
|
+
* on `\` yourself if a strict receiver is in your path.
|
|
2728
|
+
*
|
|
2695
2729
|
* PS3.15 E.1.1 says this string is "inserted in or added to" the attribute, so
|
|
2696
2730
|
* a value the incoming Data Set already carried is kept and this one is
|
|
2697
2731
|
* appended after a `\` as a further value of the `1-n` attribute - the
|
|
@@ -2721,7 +2755,10 @@ interface DeidentifyOptions {
|
|
|
2721
2755
|
*
|
|
2722
2756
|
* `DICOM_DEIDENT_METHOD_NOT_ADDED` means "the length ceiling was reached", never
|
|
2723
2757
|
* "every fallback is disclosed": a `(0012,0063)` a file encoded under a VR other
|
|
2724
|
-
* than `LO` is also replaced, and that one
|
|
2758
|
+
* than `LO` is also replaced, and that one raises `DICOM_DEIDENT_METHOD_NOT_LO`.
|
|
2759
|
+
* Two codes rather than one because the causes are unrelated - the chain outgrew
|
|
2760
|
+
* the VR, or the bytes were never in that VR at all - and a prior value that is
|
|
2761
|
+
* empty or padding only raises neither, because nothing was lost.
|
|
2725
2762
|
*
|
|
2726
2763
|
* When the prior value **is** kept, `report.warnings` carries
|
|
2727
2764
|
* `DICOM_DEIDENT_METHOD_PRIOR_RETAINED`: `(0012,0063)` is not in Table E.1-1, so
|
package/dist/index.mjs
CHANGED
|
@@ -9,7 +9,7 @@ var __export = (target, all) => {
|
|
|
9
9
|
};
|
|
10
10
|
|
|
11
11
|
// src/version.ts
|
|
12
|
-
var VERSION = "0.0.
|
|
12
|
+
var VERSION = "0.0.12";
|
|
13
13
|
|
|
14
14
|
// src/dictionary/index.ts
|
|
15
15
|
var dictionary_exports = {};
|
|
@@ -11746,6 +11746,8 @@ var WARNING_CODES = {
|
|
|
11746
11746
|
// emitted by deidentify(), never by the parser
|
|
11747
11747
|
DICOM_DEIDENT_METHOD_NOT_ADDED: "DICOM_DEIDENT_METHOD_NOT_ADDED",
|
|
11748
11748
|
// emitted by deidentify(), never by the parser
|
|
11749
|
+
DICOM_DEIDENT_METHOD_NOT_LO: "DICOM_DEIDENT_METHOD_NOT_LO",
|
|
11750
|
+
// emitted by deidentify(), never by the parser
|
|
11749
11751
|
DICOM_DEIDENT_METHOD_PRIOR_RETAINED: "DICOM_DEIDENT_METHOD_PRIOR_RETAINED",
|
|
11750
11752
|
// emitted by deidentify(), never by the parser
|
|
11751
11753
|
DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE",
|
|
@@ -11795,6 +11797,11 @@ var WARNING_MESSAGES = Object.freeze({
|
|
|
11795
11797
|
// constant of this code rather than composed from input, as in the two
|
|
11796
11798
|
// (0002,0000) messages above.
|
|
11797
11799
|
DICOM_DEIDENT_METHOD_NOT_ADDED: "The De-identification Method this run recorded could not be added beside the value (0012,0063) already carried without exceeding the largest Value Length that VR can encode, so the earlier value was replaced (PS3.15 E.1.1). The replaced text is not in the output.",
|
|
11800
|
+
// No value, no length AND NO VR. The VR is two bytes read from the file, and a
|
|
11801
|
+
// fabricated header makes those two bytes document content - the shape that
|
|
11802
|
+
// put "ITHS" into DICOM_NONZERO_RESERVED_BYTES. Naming the VR here would read
|
|
11803
|
+
// as harmless and is exactly the same defect.
|
|
11804
|
+
DICOM_DEIDENT_METHOD_NOT_LO: "The (0012,0063) value the source file carried is encoded under a Value Representation other than LO, which is not a De-identification Method this run can add its own text to (PS3.15 E.1.1), so the earlier value was replaced. The replaced text is not in the output. The text and the VR are both withheld from this message.",
|
|
11798
11805
|
// No value and no length, for the same reason as the code above: the retained
|
|
11799
11806
|
// text is the file's own. The tag is a constant of this code.
|
|
11800
11807
|
DICOM_DEIDENT_METHOD_PRIOR_RETAINED: "A De-identification Method (0012,0063) value the source file already carried was kept beside the one this run recorded, as PS3.15 E.1.1 requires. That attribute is not in Table E.1-1, so no rule in this run inspected, audited or redacted those bytes: if the sender wrote identifying text there it is in the de-identified output, under (0012,0062) = YES. The text is withheld from this message.",
|
|
@@ -11913,6 +11920,9 @@ function burnedInAnnotationNotRemoved(position) {
|
|
|
11913
11920
|
function deidentMethodNotAdded(position) {
|
|
11914
11921
|
return build(WARNING_CODES.DICOM_DEIDENT_METHOD_NOT_ADDED, position);
|
|
11915
11922
|
}
|
|
11923
|
+
function deidentMethodNotLo(position) {
|
|
11924
|
+
return build(WARNING_CODES.DICOM_DEIDENT_METHOD_NOT_LO, position);
|
|
11925
|
+
}
|
|
11916
11926
|
function deidentMethodPriorRetained(position) {
|
|
11917
11927
|
return build(WARNING_CODES.DICOM_DEIDENT_METHOD_PRIOR_RETAINED, position);
|
|
11918
11928
|
}
|
|
@@ -15349,19 +15359,23 @@ function rebuildFileMeta(fileMeta, ctx) {
|
|
|
15349
15359
|
mediaStorageSOPInstanceUID: ctx.remap.map(fileMeta.mediaStorageSOPInstanceUID)
|
|
15350
15360
|
};
|
|
15351
15361
|
}
|
|
15362
|
+
var DEFAULT_METHOD_PROFILE = "@cosyte/dicom Basic Application Level Confidentiality Profile";
|
|
15352
15363
|
function defaultMethod(active) {
|
|
15353
|
-
const
|
|
15354
|
-
return
|
|
15364
|
+
const options = DEIDENTIFY_OPTIONS.filter((option) => active.has(option));
|
|
15365
|
+
return [DEFAULT_METHOD_PROFILE, ...options].join(String.fromCharCode(VALUE_DELIMITER));
|
|
15355
15366
|
}
|
|
15356
15367
|
var VALUE_DELIMITER = 92;
|
|
15357
15368
|
var MAX_SHORT_FORM_VALUE_BYTES = 65534;
|
|
15358
15369
|
function addDeidentificationMethod(existing, method) {
|
|
15359
15370
|
const added = trimTrailingPad(Buffer.from(method, "latin1"));
|
|
15360
|
-
|
|
15361
|
-
|
|
15371
|
+
const none = { replacedPrior: false, retainedPrior: false, replacedNonLoPrior: false };
|
|
15372
|
+
if (existing === void 0) return { value: added, ...none };
|
|
15373
|
+
if (existing.vr !== "LO") {
|
|
15374
|
+
const lost = trimTrailingPad(existing.rawBytes).length > 0;
|
|
15375
|
+
return { value: added, ...none, replacedNonLoPrior: lost };
|
|
15362
15376
|
}
|
|
15363
15377
|
const kept = Buffer.from(trimTrailingPad(existing.rawBytes));
|
|
15364
|
-
if (kept.length === 0) return { value: added,
|
|
15378
|
+
if (kept.length === 0) return { value: added, ...none };
|
|
15365
15379
|
const keptValues = splitValues(kept).map(trimTrailingPad);
|
|
15366
15380
|
const missing = added.length === 0 ? [] : splitValues(added).filter((v) => !keptValues.some((k) => k.equals(trimTrailingPad(v))));
|
|
15367
15381
|
const parts = [kept];
|
|
@@ -15370,9 +15384,9 @@ function addDeidentificationMethod(existing, method) {
|
|
|
15370
15384
|
}
|
|
15371
15385
|
const value = trimTrailingPad(Buffer.concat(parts));
|
|
15372
15386
|
if (value.length > MAX_SHORT_FORM_VALUE_BYTES) {
|
|
15373
|
-
return { value: added, replacedPrior: true
|
|
15387
|
+
return { value: added, ...none, replacedPrior: true };
|
|
15374
15388
|
}
|
|
15375
|
-
return { value,
|
|
15389
|
+
return { value, ...none, retainedPrior: true };
|
|
15376
15390
|
}
|
|
15377
15391
|
function trimTrailingPad(value) {
|
|
15378
15392
|
let end = value.length;
|
|
@@ -15444,6 +15458,11 @@ function deidentify(ds, options = {}) {
|
|
|
15444
15458
|
deidentMethodPriorRetained({ byteOffset: priorMethod?.byteOffset ?? 0, fileMeta: false })
|
|
15445
15459
|
);
|
|
15446
15460
|
}
|
|
15461
|
+
if (deidentMethod.replacedNonLoPrior) {
|
|
15462
|
+
warnings.push(
|
|
15463
|
+
deidentMethodNotLo({ byteOffset: priorMethod?.byteOffset ?? 0, fileMeta: false })
|
|
15464
|
+
);
|
|
15465
|
+
}
|
|
15447
15466
|
if (hasUncleanedBurnedIn(ds)) {
|
|
15448
15467
|
const offset = ds.get(TAG_PIXEL_DATA)?.byteOffset ?? 0;
|
|
15449
15468
|
warnings.push(burnedInAnnotationNotRemoved({ byteOffset: offset, fileMeta: false }));
|