@cosyte/dicom 0.0.8 → 0.0.11
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 +747 -0
- package/README.md +12 -5
- package/dist/index.cjs +229 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +127 -14
- package/dist/index.d.ts +127 -14
- package/dist/index.mjs +229 -55
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -391,11 +391,14 @@ interface ParseOptions {
|
|
|
391
391
|
* ```
|
|
392
392
|
*/
|
|
393
393
|
declare const WARNING_CODES: {
|
|
394
|
+
readonly DICOM_DUPLICATE_FILE_META_ELEMENT: "DICOM_DUPLICATE_FILE_META_ELEMENT";
|
|
395
|
+
readonly DICOM_DUPLICATE_TAG_IN_DATA_SET: "DICOM_DUPLICATE_TAG_IN_DATA_SET";
|
|
394
396
|
readonly DICOM_EMPTY_ITEM_IN_SEQUENCE: "DICOM_EMPTY_ITEM_IN_SEQUENCE";
|
|
395
397
|
readonly DICOM_FILE_META_GROUP_LENGTH_MISMATCH: "DICOM_FILE_META_GROUP_LENGTH_MISMATCH";
|
|
396
398
|
readonly DICOM_FILE_META_GROUP_LENGTH_MISSING: "DICOM_FILE_META_GROUP_LENGTH_MISSING";
|
|
397
399
|
readonly DICOM_GROUP_LENGTH_IN_DATASET: "DICOM_GROUP_LENGTH_IN_DATASET";
|
|
398
400
|
readonly DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR";
|
|
401
|
+
readonly DICOM_ITEM_CROSSES_SEQUENCE_END: "DICOM_ITEM_CROSSES_SEQUENCE_END";
|
|
399
402
|
readonly DICOM_MISSING_PREAMBLE: "DICOM_MISSING_PREAMBLE";
|
|
400
403
|
readonly DICOM_NONZERO_RESERVED_BYTES: "DICOM_NONZERO_RESERVED_BYTES";
|
|
401
404
|
readonly DICOM_ODD_LENGTH_VALUE_PADDED: "DICOM_ODD_LENGTH_VALUE_PADDED";
|
|
@@ -416,6 +419,8 @@ declare const WARNING_CODES: {
|
|
|
416
419
|
readonly DICOM_UNSUPPORTED_CHARSET: "DICOM_UNSUPPORTED_CHARSET";
|
|
417
420
|
readonly DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED";
|
|
418
421
|
readonly DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED";
|
|
422
|
+
readonly DICOM_DEIDENT_METHOD_NOT_ADDED: "DICOM_DEIDENT_METHOD_NOT_ADDED";
|
|
423
|
+
readonly DICOM_DEIDENT_METHOD_PRIOR_RETAINED: "DICOM_DEIDENT_METHOD_PRIOR_RETAINED";
|
|
419
424
|
readonly DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE";
|
|
420
425
|
readonly DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE";
|
|
421
426
|
readonly DICOM_PRIVATE_CREATOR_UNKNOWN: "DICOM_PRIVATE_CREATOR_UNKNOWN";
|
|
@@ -2526,13 +2531,21 @@ interface UndefinedVrFinding {
|
|
|
2526
2531
|
/**
|
|
2527
2532
|
* The audit trail returned alongside the de-identified dataset.
|
|
2528
2533
|
*
|
|
2529
|
-
*
|
|
2530
|
-
*
|
|
2531
|
-
*
|
|
2532
|
-
*
|
|
2533
|
-
*
|
|
2534
|
-
*
|
|
2535
|
-
*
|
|
2534
|
+
* Most fields are composed from static tables: Part 6 keywords, Annex E action
|
|
2535
|
+
* codes, structural `TAG[index]` sequence paths, and registry warning messages.
|
|
2536
|
+
* **Two are not, and both are named here rather than in a footnote.**
|
|
2537
|
+
*
|
|
2538
|
+
* 1. **`uidMap`** - its keys are the source UIDs read out of the file, kept so a
|
|
2539
|
+
* caller can make UID replacement consistent across a study or an archive. A
|
|
2540
|
+
* Study or SOP Instance UID is a unique identifying number, so treat it as
|
|
2541
|
+
* PHI.
|
|
2542
|
+
* 2. **`removedPrivateTags`** - see the field's own note. On a *well-formed*
|
|
2543
|
+
* file these are the sender's own private tag numbers and carry nothing; on a
|
|
2544
|
+
* malformed one a tag can be four bytes of a value, and it is measured, not
|
|
2545
|
+
* theoretical.
|
|
2546
|
+
*
|
|
2547
|
+
* So "the report is safe to log apart from `uidMap`" is **not** an accurate
|
|
2548
|
+
* description of this type, and was corrected rather than kept convenient.
|
|
2536
2549
|
*
|
|
2537
2550
|
* @example
|
|
2538
2551
|
* ```ts
|
|
@@ -2545,7 +2558,24 @@ interface UndefinedVrFinding {
|
|
|
2545
2558
|
interface DeidentifyReport {
|
|
2546
2559
|
/** Per-attribute outcomes for every attribute Annex E acted on. */
|
|
2547
2560
|
readonly attributes: readonly DeidentifiedAttribute[];
|
|
2548
|
-
/**
|
|
2561
|
+
/**
|
|
2562
|
+
* Private tags removed under the Basic Profile (kept ones are omitted).
|
|
2563
|
+
*
|
|
2564
|
+
* **This is the second field that is not value-free, and the qualification is
|
|
2565
|
+
* measured.** A tag here is composed from four bytes of the source, and on a
|
|
2566
|
+
* file whose Value Lengths disagree with its bytes those four bytes can be
|
|
2567
|
+
* document content rather than a tag the sender wrote: an `OB` carrier holding
|
|
2568
|
+
* `"SECRET-NOTE-"` followed by a well-formed odd-group header reports
|
|
2569
|
+
* `["41534342"]`, whose wire-order bytes read `"SABC"`. That reproduces
|
|
2570
|
+
* identically on every release that has shipped this field.
|
|
2571
|
+
*
|
|
2572
|
+
* It is reported anyway, because *which* private tags were removed is the
|
|
2573
|
+
* whole audit value of the field and withholding them would destroy it on
|
|
2574
|
+
* every well-formed file to bound a malformed one. Narrowing it - to blocks a
|
|
2575
|
+
* creator in the same Data Set actually reserved, say - is a product decision
|
|
2576
|
+
* about audit value versus a four-byte echo, not a defect fix, and it has not
|
|
2577
|
+
* been made. Treat this array as PHI when the source is untrusted.
|
|
2578
|
+
*/
|
|
2549
2579
|
readonly removedPrivateTags: readonly Tag[];
|
|
2550
2580
|
/**
|
|
2551
2581
|
* Values emptied because whole Data Elements were encoded inside them by an
|
|
@@ -2618,7 +2648,15 @@ interface DeidentifyReport {
|
|
|
2618
2648
|
readonly uidMap: ReadonlyMap<string, string>;
|
|
2619
2649
|
/** Safety warnings - notably burned-in-pixel annotation that cannot be cleaned. */
|
|
2620
2650
|
readonly warnings: readonly DicomParseWarning[];
|
|
2621
|
-
/**
|
|
2651
|
+
/**
|
|
2652
|
+
* The Retain/Clean options that were active for this run.
|
|
2653
|
+
*
|
|
2654
|
+
* **Not a list of what survived.** Attributes Table E.1-1 does not list are
|
|
2655
|
+
* kept without appearing anywhere in this field - `(0012,0063)`
|
|
2656
|
+
* De-identification Method is the one whose retention is disclosed, and it is
|
|
2657
|
+
* disclosed as `DICOM_DEIDENT_METHOD_PRIOR_RETAINED` on
|
|
2658
|
+
* {@link DeidentifyReport.warnings}, because it is not an option set.
|
|
2659
|
+
*/
|
|
2622
2660
|
readonly retained: readonly DeidentifyOption[];
|
|
2623
2661
|
}
|
|
2624
2662
|
/**
|
|
@@ -2651,8 +2689,44 @@ interface DeidentifyOptions {
|
|
|
2651
2689
|
*/
|
|
2652
2690
|
readonly profile?: Profile;
|
|
2653
2691
|
/**
|
|
2654
|
-
* Text
|
|
2692
|
+
* Text **added to** `(0012,0063)` De-identification Method. Default names the
|
|
2655
2693
|
* Basic Profile and the active options.
|
|
2694
|
+
*
|
|
2695
|
+
* PS3.15 E.1.1 says this string is "inserted in or added to" the attribute, so
|
|
2696
|
+
* a value the incoming Data Set already carried is kept and this one is
|
|
2697
|
+
* appended after a `\` as a further value of the `1-n` attribute - the
|
|
2698
|
+
* provenance chain, not a replacement.
|
|
2699
|
+
*
|
|
2700
|
+
* **This string is itself a `1-n` value**: it is split on `\` and only the
|
|
2701
|
+
* values not already recorded are added.
|
|
2702
|
+
*
|
|
2703
|
+
* **Trailing SPACE and NUL are padding, not content** (PS3.5 Table 6.2-1's `LO`
|
|
2704
|
+
* row, which describes a **Value** - and `LO` is `1-n`). They are ignored when
|
|
2705
|
+
* a value here is matched against one already recorded, **per value, on both
|
|
2706
|
+
* sides**, and they are trimmed from the value written. That makes repeated
|
|
2707
|
+
* de-identification a fixed point **from the first pass**, for every string:
|
|
2708
|
+
* with or without a delimiter, and with a pad byte on any value, last or not.
|
|
2709
|
+
* A string that is padding only records nothing. Leading spaces are yours and
|
|
2710
|
+
* are written through untouched.
|
|
2711
|
+
*
|
|
2712
|
+
* One bound, and it is over the value that would be **written**, not over the
|
|
2713
|
+
* join: when that value would exceed the largest Value Length an `LO` can
|
|
2714
|
+
* encode, the prior value is **replaced** rather than added to - an element the
|
|
2715
|
+
* serializer cannot encode would take the whole de-identified object down - and
|
|
2716
|
+
* `report.warnings` carries `DICOM_DEIDENT_METHOD_NOT_ADDED`. That includes a
|
|
2717
|
+
* prior value already at the ceiling which records this method already, where
|
|
2718
|
+
* there is no join to exceed anything. A string longer than that ceiling on its
|
|
2719
|
+
* own is not bounded here and will fail to serialize, exactly as it did before
|
|
2720
|
+
* this option grew a join.
|
|
2721
|
+
*
|
|
2722
|
+
* `DICOM_DEIDENT_METHOD_NOT_ADDED` means "the length ceiling was reached", never
|
|
2723
|
+
* "every fallback is disclosed": a `(0012,0063)` a file encoded under a VR other
|
|
2724
|
+
* than `LO` is also replaced, and that one is silent.
|
|
2725
|
+
*
|
|
2726
|
+
* When the prior value **is** kept, `report.warnings` carries
|
|
2727
|
+
* `DICOM_DEIDENT_METHOD_PRIOR_RETAINED`: `(0012,0063)` is not in Table E.1-1, so
|
|
2728
|
+
* nothing in the run inspected or redacted those bytes, and a name a sender
|
|
2729
|
+
* wrote there is in output stamped `(0012,0062) = YES`.
|
|
2656
2730
|
*/
|
|
2657
2731
|
readonly deidentificationMethod?: string;
|
|
2658
2732
|
}
|
|
@@ -2728,8 +2802,8 @@ declare class DeidentifyError extends Error {
|
|
|
2728
2802
|
* nine *metadata-affecting* Annex E Options, driven by the generated Table E.1-1
|
|
2729
2803
|
* action map ({@link annexE}). It is a **pure** function: the input {@link Dataset}
|
|
2730
2804
|
* is never mutated; a fresh `Dataset` (with a rebuilt element map and File Meta)
|
|
2731
|
-
* is returned alongside a {@link DeidentifyReport}
|
|
2732
|
-
*
|
|
2805
|
+
* is returned alongside a {@link DeidentifyReport} whose two non-value-free
|
|
2806
|
+
* fields are named on that type: `uidMap` and `removedPrivateTags`.
|
|
2733
2807
|
*
|
|
2734
2808
|
* **What it does**
|
|
2735
2809
|
* - Resolves each attribute's action (basic profile, overridden by an active
|
|
@@ -2750,8 +2824,10 @@ declare class DeidentifyError extends Error {
|
|
|
2750
2824
|
* {@link Profile}, keeps the private data elements the profile's overlay names
|
|
2751
2825
|
* as safe (and the private-creator elements the profile recognizes).
|
|
2752
2826
|
* - Remaps `(0002,0003)` Media Storage SOP Instance UID consistently (unless
|
|
2753
|
-
* `RetainUIDs`), writes `(0012,0062)` Patient Identity Removed = `YES
|
|
2754
|
-
* `(0012,0063)` De-identification Method
|
|
2827
|
+
* `RetainUIDs`), writes `(0012,0062)` Patient Identity Removed = `YES`, **adds**
|
|
2828
|
+
* its method text to `(0012,0063)` De-identification Method rather than
|
|
2829
|
+
* replacing what the file already recorded there (PS3.15 E.1.1 - see
|
|
2830
|
+
* `addDeidentificationMethod`), and warns
|
|
2755
2831
|
* (`DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED`) when Pixel Data is present and not
|
|
2756
2832
|
* marked free of burned-in annotation - this metadata-only pass cannot clean
|
|
2757
2833
|
* pixels (deferred to `@cosyte/dicom-pixel`).
|
|
@@ -2804,6 +2880,43 @@ declare class DeidentifyError extends Error {
|
|
|
2804
2880
|
* `35adc2d`. No content test can decide it - arbitrary bytes are what those
|
|
2805
2881
|
* VRs are for - and the one candidate remedy empties conformant binary values.
|
|
2806
2882
|
* See `./embedded.ts`.
|
|
2883
|
+
* - `RetainSafePrivate` **retains nothing inside a Sequence Item whose Data Set
|
|
2884
|
+
* boundary the file contradicts** - an item stream that ran past its
|
|
2885
|
+
* sequence's own declared Value Length. Which elements are in the Item is then
|
|
2886
|
+
* not determined by the file, and PS3.5 §7.8.1 scopes a private block
|
|
2887
|
+
* reservation to exactly that boundary, so PS3.15 §E.3.10's "known ... to be
|
|
2888
|
+
* safe" cannot be established and its "all other Private Attributes shall be
|
|
2889
|
+
* removed **or processed in the element-specific manner recommended by
|
|
2890
|
+
* Deidentification Action (0008,0307), if present within Private Data Element
|
|
2891
|
+
* Characteristics Sequence (0008,0300)**" applies - a two-branch clause, of
|
|
2892
|
+
* which removal is the branch available here (`(0008,0307)` is not
|
|
2893
|
+
* implemented). Every private element the recursion **reaches** in such an
|
|
2894
|
+
* Item, at any depth below it, is removed and named in
|
|
2895
|
+
* `report.removedPrivateTags`. **There is one carve-out, and it is the same one
|
|
2896
|
+
* `#54` was refused for asserting away**: `keepsPrivate` decides *before* the
|
|
2897
|
+
* descent, so a **private `SQ`** the profile vouches for is kept **verbatim**,
|
|
2898
|
+
* its items are never walked, and this rule is never consulted inside it -
|
|
2899
|
+
* measured, and it still leaks a private value absorbed from the root
|
|
2900
|
+
* (`PRE-EXISTING`, identical on `164eb39`, its own item). That costs content
|
|
2901
|
+
* on a malformed file and nothing at all on a conformant one; without it a
|
|
2902
|
+
* private value the sender wrote **outside** the sequence was retained on the
|
|
2903
|
+
* Item's reservation and written into output stamped `PatientIdentityRemoved =
|
|
2904
|
+
* YES` with `removedPrivateTags: []` (`DICOM-PRIVATE-CREATOR-RESERVATION-LEAK`,
|
|
2905
|
+
* measured on the published `0.0.8` tarball and fixed on `0.0.10`; there is no `0.0.9` on
|
|
2906
|
+
* the registry). See {@link itemStreamOverrunsSequence}.
|
|
2907
|
+
* - `RetainSafePrivate` **retains nothing that a Data Set holds after a sequence
|
|
2908
|
+
* whose own contents contradict the extent it declared** - the mirror
|
|
2909
|
+
* direction, where an Item that *under*-declares **ejects** its trailing
|
|
2910
|
+
* elements out and a Private Creator landing in the enclosing Data Set reserves
|
|
2911
|
+
* a block for elements the sender never put beside it. The same false
|
|
2912
|
+
* attestation, and it applies **in every Data Set, not just the root**: an
|
|
2913
|
+
* inner sequence ejecting a creator into the still-usable Item that encloses it
|
|
2914
|
+
* is measured and pinned. The cut is positional, so a reservation the sender
|
|
2915
|
+
* wrote **ahead of** the offending sequence is untouched. Every private element
|
|
2916
|
+
* after that point is removed and named in `report.removedPrivateTags`, and the
|
|
2917
|
+
* **private-`SQ` carve-out above still applies**: a private `SQ` inside the
|
|
2918
|
+
* settled run that the profile vouches for is kept verbatim and never walked.
|
|
2919
|
+
* See {@link settledBound}.
|
|
2807
2920
|
*
|
|
2808
2921
|
* @module
|
|
2809
2922
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -391,11 +391,14 @@ interface ParseOptions {
|
|
|
391
391
|
* ```
|
|
392
392
|
*/
|
|
393
393
|
declare const WARNING_CODES: {
|
|
394
|
+
readonly DICOM_DUPLICATE_FILE_META_ELEMENT: "DICOM_DUPLICATE_FILE_META_ELEMENT";
|
|
395
|
+
readonly DICOM_DUPLICATE_TAG_IN_DATA_SET: "DICOM_DUPLICATE_TAG_IN_DATA_SET";
|
|
394
396
|
readonly DICOM_EMPTY_ITEM_IN_SEQUENCE: "DICOM_EMPTY_ITEM_IN_SEQUENCE";
|
|
395
397
|
readonly DICOM_FILE_META_GROUP_LENGTH_MISMATCH: "DICOM_FILE_META_GROUP_LENGTH_MISMATCH";
|
|
396
398
|
readonly DICOM_FILE_META_GROUP_LENGTH_MISSING: "DICOM_FILE_META_GROUP_LENGTH_MISSING";
|
|
397
399
|
readonly DICOM_GROUP_LENGTH_IN_DATASET: "DICOM_GROUP_LENGTH_IN_DATASET";
|
|
398
400
|
readonly DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR";
|
|
401
|
+
readonly DICOM_ITEM_CROSSES_SEQUENCE_END: "DICOM_ITEM_CROSSES_SEQUENCE_END";
|
|
399
402
|
readonly DICOM_MISSING_PREAMBLE: "DICOM_MISSING_PREAMBLE";
|
|
400
403
|
readonly DICOM_NONZERO_RESERVED_BYTES: "DICOM_NONZERO_RESERVED_BYTES";
|
|
401
404
|
readonly DICOM_ODD_LENGTH_VALUE_PADDED: "DICOM_ODD_LENGTH_VALUE_PADDED";
|
|
@@ -416,6 +419,8 @@ declare const WARNING_CODES: {
|
|
|
416
419
|
readonly DICOM_UNSUPPORTED_CHARSET: "DICOM_UNSUPPORTED_CHARSET";
|
|
417
420
|
readonly DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED";
|
|
418
421
|
readonly DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED";
|
|
422
|
+
readonly DICOM_DEIDENT_METHOD_NOT_ADDED: "DICOM_DEIDENT_METHOD_NOT_ADDED";
|
|
423
|
+
readonly DICOM_DEIDENT_METHOD_PRIOR_RETAINED: "DICOM_DEIDENT_METHOD_PRIOR_RETAINED";
|
|
419
424
|
readonly DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE";
|
|
420
425
|
readonly DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE";
|
|
421
426
|
readonly DICOM_PRIVATE_CREATOR_UNKNOWN: "DICOM_PRIVATE_CREATOR_UNKNOWN";
|
|
@@ -2526,13 +2531,21 @@ interface UndefinedVrFinding {
|
|
|
2526
2531
|
/**
|
|
2527
2532
|
* The audit trail returned alongside the de-identified dataset.
|
|
2528
2533
|
*
|
|
2529
|
-
*
|
|
2530
|
-
*
|
|
2531
|
-
*
|
|
2532
|
-
*
|
|
2533
|
-
*
|
|
2534
|
-
*
|
|
2535
|
-
*
|
|
2534
|
+
* Most fields are composed from static tables: Part 6 keywords, Annex E action
|
|
2535
|
+
* codes, structural `TAG[index]` sequence paths, and registry warning messages.
|
|
2536
|
+
* **Two are not, and both are named here rather than in a footnote.**
|
|
2537
|
+
*
|
|
2538
|
+
* 1. **`uidMap`** - its keys are the source UIDs read out of the file, kept so a
|
|
2539
|
+
* caller can make UID replacement consistent across a study or an archive. A
|
|
2540
|
+
* Study or SOP Instance UID is a unique identifying number, so treat it as
|
|
2541
|
+
* PHI.
|
|
2542
|
+
* 2. **`removedPrivateTags`** - see the field's own note. On a *well-formed*
|
|
2543
|
+
* file these are the sender's own private tag numbers and carry nothing; on a
|
|
2544
|
+
* malformed one a tag can be four bytes of a value, and it is measured, not
|
|
2545
|
+
* theoretical.
|
|
2546
|
+
*
|
|
2547
|
+
* So "the report is safe to log apart from `uidMap`" is **not** an accurate
|
|
2548
|
+
* description of this type, and was corrected rather than kept convenient.
|
|
2536
2549
|
*
|
|
2537
2550
|
* @example
|
|
2538
2551
|
* ```ts
|
|
@@ -2545,7 +2558,24 @@ interface UndefinedVrFinding {
|
|
|
2545
2558
|
interface DeidentifyReport {
|
|
2546
2559
|
/** Per-attribute outcomes for every attribute Annex E acted on. */
|
|
2547
2560
|
readonly attributes: readonly DeidentifiedAttribute[];
|
|
2548
|
-
/**
|
|
2561
|
+
/**
|
|
2562
|
+
* Private tags removed under the Basic Profile (kept ones are omitted).
|
|
2563
|
+
*
|
|
2564
|
+
* **This is the second field that is not value-free, and the qualification is
|
|
2565
|
+
* measured.** A tag here is composed from four bytes of the source, and on a
|
|
2566
|
+
* file whose Value Lengths disagree with its bytes those four bytes can be
|
|
2567
|
+
* document content rather than a tag the sender wrote: an `OB` carrier holding
|
|
2568
|
+
* `"SECRET-NOTE-"` followed by a well-formed odd-group header reports
|
|
2569
|
+
* `["41534342"]`, whose wire-order bytes read `"SABC"`. That reproduces
|
|
2570
|
+
* identically on every release that has shipped this field.
|
|
2571
|
+
*
|
|
2572
|
+
* It is reported anyway, because *which* private tags were removed is the
|
|
2573
|
+
* whole audit value of the field and withholding them would destroy it on
|
|
2574
|
+
* every well-formed file to bound a malformed one. Narrowing it - to blocks a
|
|
2575
|
+
* creator in the same Data Set actually reserved, say - is a product decision
|
|
2576
|
+
* about audit value versus a four-byte echo, not a defect fix, and it has not
|
|
2577
|
+
* been made. Treat this array as PHI when the source is untrusted.
|
|
2578
|
+
*/
|
|
2549
2579
|
readonly removedPrivateTags: readonly Tag[];
|
|
2550
2580
|
/**
|
|
2551
2581
|
* Values emptied because whole Data Elements were encoded inside them by an
|
|
@@ -2618,7 +2648,15 @@ interface DeidentifyReport {
|
|
|
2618
2648
|
readonly uidMap: ReadonlyMap<string, string>;
|
|
2619
2649
|
/** Safety warnings - notably burned-in-pixel annotation that cannot be cleaned. */
|
|
2620
2650
|
readonly warnings: readonly DicomParseWarning[];
|
|
2621
|
-
/**
|
|
2651
|
+
/**
|
|
2652
|
+
* The Retain/Clean options that were active for this run.
|
|
2653
|
+
*
|
|
2654
|
+
* **Not a list of what survived.** Attributes Table E.1-1 does not list are
|
|
2655
|
+
* kept without appearing anywhere in this field - `(0012,0063)`
|
|
2656
|
+
* De-identification Method is the one whose retention is disclosed, and it is
|
|
2657
|
+
* disclosed as `DICOM_DEIDENT_METHOD_PRIOR_RETAINED` on
|
|
2658
|
+
* {@link DeidentifyReport.warnings}, because it is not an option set.
|
|
2659
|
+
*/
|
|
2622
2660
|
readonly retained: readonly DeidentifyOption[];
|
|
2623
2661
|
}
|
|
2624
2662
|
/**
|
|
@@ -2651,8 +2689,44 @@ interface DeidentifyOptions {
|
|
|
2651
2689
|
*/
|
|
2652
2690
|
readonly profile?: Profile;
|
|
2653
2691
|
/**
|
|
2654
|
-
* Text
|
|
2692
|
+
* Text **added to** `(0012,0063)` De-identification Method. Default names the
|
|
2655
2693
|
* Basic Profile and the active options.
|
|
2694
|
+
*
|
|
2695
|
+
* PS3.15 E.1.1 says this string is "inserted in or added to" the attribute, so
|
|
2696
|
+
* a value the incoming Data Set already carried is kept and this one is
|
|
2697
|
+
* appended after a `\` as a further value of the `1-n` attribute - the
|
|
2698
|
+
* provenance chain, not a replacement.
|
|
2699
|
+
*
|
|
2700
|
+
* **This string is itself a `1-n` value**: it is split on `\` and only the
|
|
2701
|
+
* values not already recorded are added.
|
|
2702
|
+
*
|
|
2703
|
+
* **Trailing SPACE and NUL are padding, not content** (PS3.5 Table 6.2-1's `LO`
|
|
2704
|
+
* row, which describes a **Value** - and `LO` is `1-n`). They are ignored when
|
|
2705
|
+
* a value here is matched against one already recorded, **per value, on both
|
|
2706
|
+
* sides**, and they are trimmed from the value written. That makes repeated
|
|
2707
|
+
* de-identification a fixed point **from the first pass**, for every string:
|
|
2708
|
+
* with or without a delimiter, and with a pad byte on any value, last or not.
|
|
2709
|
+
* A string that is padding only records nothing. Leading spaces are yours and
|
|
2710
|
+
* are written through untouched.
|
|
2711
|
+
*
|
|
2712
|
+
* One bound, and it is over the value that would be **written**, not over the
|
|
2713
|
+
* join: when that value would exceed the largest Value Length an `LO` can
|
|
2714
|
+
* encode, the prior value is **replaced** rather than added to - an element the
|
|
2715
|
+
* serializer cannot encode would take the whole de-identified object down - and
|
|
2716
|
+
* `report.warnings` carries `DICOM_DEIDENT_METHOD_NOT_ADDED`. That includes a
|
|
2717
|
+
* prior value already at the ceiling which records this method already, where
|
|
2718
|
+
* there is no join to exceed anything. A string longer than that ceiling on its
|
|
2719
|
+
* own is not bounded here and will fail to serialize, exactly as it did before
|
|
2720
|
+
* this option grew a join.
|
|
2721
|
+
*
|
|
2722
|
+
* `DICOM_DEIDENT_METHOD_NOT_ADDED` means "the length ceiling was reached", never
|
|
2723
|
+
* "every fallback is disclosed": a `(0012,0063)` a file encoded under a VR other
|
|
2724
|
+
* than `LO` is also replaced, and that one is silent.
|
|
2725
|
+
*
|
|
2726
|
+
* When the prior value **is** kept, `report.warnings` carries
|
|
2727
|
+
* `DICOM_DEIDENT_METHOD_PRIOR_RETAINED`: `(0012,0063)` is not in Table E.1-1, so
|
|
2728
|
+
* nothing in the run inspected or redacted those bytes, and a name a sender
|
|
2729
|
+
* wrote there is in output stamped `(0012,0062) = YES`.
|
|
2656
2730
|
*/
|
|
2657
2731
|
readonly deidentificationMethod?: string;
|
|
2658
2732
|
}
|
|
@@ -2728,8 +2802,8 @@ declare class DeidentifyError extends Error {
|
|
|
2728
2802
|
* nine *metadata-affecting* Annex E Options, driven by the generated Table E.1-1
|
|
2729
2803
|
* action map ({@link annexE}). It is a **pure** function: the input {@link Dataset}
|
|
2730
2804
|
* is never mutated; a fresh `Dataset` (with a rebuilt element map and File Meta)
|
|
2731
|
-
* is returned alongside a {@link DeidentifyReport}
|
|
2732
|
-
*
|
|
2805
|
+
* is returned alongside a {@link DeidentifyReport} whose two non-value-free
|
|
2806
|
+
* fields are named on that type: `uidMap` and `removedPrivateTags`.
|
|
2733
2807
|
*
|
|
2734
2808
|
* **What it does**
|
|
2735
2809
|
* - Resolves each attribute's action (basic profile, overridden by an active
|
|
@@ -2750,8 +2824,10 @@ declare class DeidentifyError extends Error {
|
|
|
2750
2824
|
* {@link Profile}, keeps the private data elements the profile's overlay names
|
|
2751
2825
|
* as safe (and the private-creator elements the profile recognizes).
|
|
2752
2826
|
* - Remaps `(0002,0003)` Media Storage SOP Instance UID consistently (unless
|
|
2753
|
-
* `RetainUIDs`), writes `(0012,0062)` Patient Identity Removed = `YES
|
|
2754
|
-
* `(0012,0063)` De-identification Method
|
|
2827
|
+
* `RetainUIDs`), writes `(0012,0062)` Patient Identity Removed = `YES`, **adds**
|
|
2828
|
+
* its method text to `(0012,0063)` De-identification Method rather than
|
|
2829
|
+
* replacing what the file already recorded there (PS3.15 E.1.1 - see
|
|
2830
|
+
* `addDeidentificationMethod`), and warns
|
|
2755
2831
|
* (`DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED`) when Pixel Data is present and not
|
|
2756
2832
|
* marked free of burned-in annotation - this metadata-only pass cannot clean
|
|
2757
2833
|
* pixels (deferred to `@cosyte/dicom-pixel`).
|
|
@@ -2804,6 +2880,43 @@ declare class DeidentifyError extends Error {
|
|
|
2804
2880
|
* `35adc2d`. No content test can decide it - arbitrary bytes are what those
|
|
2805
2881
|
* VRs are for - and the one candidate remedy empties conformant binary values.
|
|
2806
2882
|
* See `./embedded.ts`.
|
|
2883
|
+
* - `RetainSafePrivate` **retains nothing inside a Sequence Item whose Data Set
|
|
2884
|
+
* boundary the file contradicts** - an item stream that ran past its
|
|
2885
|
+
* sequence's own declared Value Length. Which elements are in the Item is then
|
|
2886
|
+
* not determined by the file, and PS3.5 §7.8.1 scopes a private block
|
|
2887
|
+
* reservation to exactly that boundary, so PS3.15 §E.3.10's "known ... to be
|
|
2888
|
+
* safe" cannot be established and its "all other Private Attributes shall be
|
|
2889
|
+
* removed **or processed in the element-specific manner recommended by
|
|
2890
|
+
* Deidentification Action (0008,0307), if present within Private Data Element
|
|
2891
|
+
* Characteristics Sequence (0008,0300)**" applies - a two-branch clause, of
|
|
2892
|
+
* which removal is the branch available here (`(0008,0307)` is not
|
|
2893
|
+
* implemented). Every private element the recursion **reaches** in such an
|
|
2894
|
+
* Item, at any depth below it, is removed and named in
|
|
2895
|
+
* `report.removedPrivateTags`. **There is one carve-out, and it is the same one
|
|
2896
|
+
* `#54` was refused for asserting away**: `keepsPrivate` decides *before* the
|
|
2897
|
+
* descent, so a **private `SQ`** the profile vouches for is kept **verbatim**,
|
|
2898
|
+
* its items are never walked, and this rule is never consulted inside it -
|
|
2899
|
+
* measured, and it still leaks a private value absorbed from the root
|
|
2900
|
+
* (`PRE-EXISTING`, identical on `164eb39`, its own item). That costs content
|
|
2901
|
+
* on a malformed file and nothing at all on a conformant one; without it a
|
|
2902
|
+
* private value the sender wrote **outside** the sequence was retained on the
|
|
2903
|
+
* Item's reservation and written into output stamped `PatientIdentityRemoved =
|
|
2904
|
+
* YES` with `removedPrivateTags: []` (`DICOM-PRIVATE-CREATOR-RESERVATION-LEAK`,
|
|
2905
|
+
* measured on the published `0.0.8` tarball and fixed on `0.0.10`; there is no `0.0.9` on
|
|
2906
|
+
* the registry). See {@link itemStreamOverrunsSequence}.
|
|
2907
|
+
* - `RetainSafePrivate` **retains nothing that a Data Set holds after a sequence
|
|
2908
|
+
* whose own contents contradict the extent it declared** - the mirror
|
|
2909
|
+
* direction, where an Item that *under*-declares **ejects** its trailing
|
|
2910
|
+
* elements out and a Private Creator landing in the enclosing Data Set reserves
|
|
2911
|
+
* a block for elements the sender never put beside it. The same false
|
|
2912
|
+
* attestation, and it applies **in every Data Set, not just the root**: an
|
|
2913
|
+
* inner sequence ejecting a creator into the still-usable Item that encloses it
|
|
2914
|
+
* is measured and pinned. The cut is positional, so a reservation the sender
|
|
2915
|
+
* wrote **ahead of** the offending sequence is untouched. Every private element
|
|
2916
|
+
* after that point is removed and named in `report.removedPrivateTags`, and the
|
|
2917
|
+
* **private-`SQ` carve-out above still applies**: a private `SQ` inside the
|
|
2918
|
+
* settled run that the profile vouches for is kept verbatim and never walked.
|
|
2919
|
+
* See {@link settledBound}.
|
|
2807
2920
|
*
|
|
2808
2921
|
* @module
|
|
2809
2922
|
*/
|