@cosyte/dicom 0.0.7 → 0.0.10
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 +423 -0
- package/README.md +4 -3
- package/dist/index.cjs +40 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -10
- package/dist/index.d.ts +58 -10
- package/dist/index.mjs +40 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.d.cts
CHANGED
|
@@ -2526,13 +2526,21 @@ interface UndefinedVrFinding {
|
|
|
2526
2526
|
/**
|
|
2527
2527
|
* The audit trail returned alongside the de-identified dataset.
|
|
2528
2528
|
*
|
|
2529
|
-
*
|
|
2530
|
-
*
|
|
2531
|
-
*
|
|
2532
|
-
*
|
|
2533
|
-
*
|
|
2534
|
-
*
|
|
2535
|
-
*
|
|
2529
|
+
* Most fields are composed from static tables: Part 6 keywords, Annex E action
|
|
2530
|
+
* codes, structural `TAG[index]` sequence paths, and registry warning messages.
|
|
2531
|
+
* **Two are not, and both are named here rather than in a footnote.**
|
|
2532
|
+
*
|
|
2533
|
+
* 1. **`uidMap`** - its keys are the source UIDs read out of the file, kept so a
|
|
2534
|
+
* caller can make UID replacement consistent across a study or an archive. A
|
|
2535
|
+
* Study or SOP Instance UID is a unique identifying number, so treat it as
|
|
2536
|
+
* PHI.
|
|
2537
|
+
* 2. **`removedPrivateTags`** - see the field's own note. On a *well-formed*
|
|
2538
|
+
* file these are the sender's own private tag numbers and carry nothing; on a
|
|
2539
|
+
* malformed one a tag can be four bytes of a value, and it is measured, not
|
|
2540
|
+
* theoretical.
|
|
2541
|
+
*
|
|
2542
|
+
* So "the report is safe to log apart from `uidMap`" is **not** an accurate
|
|
2543
|
+
* description of this type, and was corrected rather than kept convenient.
|
|
2536
2544
|
*
|
|
2537
2545
|
* @example
|
|
2538
2546
|
* ```ts
|
|
@@ -2545,7 +2553,24 @@ interface UndefinedVrFinding {
|
|
|
2545
2553
|
interface DeidentifyReport {
|
|
2546
2554
|
/** Per-attribute outcomes for every attribute Annex E acted on. */
|
|
2547
2555
|
readonly attributes: readonly DeidentifiedAttribute[];
|
|
2548
|
-
/**
|
|
2556
|
+
/**
|
|
2557
|
+
* Private tags removed under the Basic Profile (kept ones are omitted).
|
|
2558
|
+
*
|
|
2559
|
+
* **This is the second field that is not value-free, and the qualification is
|
|
2560
|
+
* measured.** A tag here is composed from four bytes of the source, and on a
|
|
2561
|
+
* file whose Value Lengths disagree with its bytes those four bytes can be
|
|
2562
|
+
* document content rather than a tag the sender wrote: an `OB` carrier holding
|
|
2563
|
+
* `"SECRET-NOTE-"` followed by a well-formed odd-group header reports
|
|
2564
|
+
* `["41534342"]`, whose wire-order bytes read `"SABC"`. That reproduces
|
|
2565
|
+
* identically on every release that has shipped this field.
|
|
2566
|
+
*
|
|
2567
|
+
* It is reported anyway, because *which* private tags were removed is the
|
|
2568
|
+
* whole audit value of the field and withholding them would destroy it on
|
|
2569
|
+
* every well-formed file to bound a malformed one. Narrowing it - to blocks a
|
|
2570
|
+
* creator in the same Data Set actually reserved, say - is a product decision
|
|
2571
|
+
* about audit value versus a four-byte echo, not a defect fix, and it has not
|
|
2572
|
+
* been made. Treat this array as PHI when the source is untrusted.
|
|
2573
|
+
*/
|
|
2549
2574
|
readonly removedPrivateTags: readonly Tag[];
|
|
2550
2575
|
/**
|
|
2551
2576
|
* Values emptied because whole Data Elements were encoded inside them by an
|
|
@@ -2728,8 +2753,8 @@ declare class DeidentifyError extends Error {
|
|
|
2728
2753
|
* nine *metadata-affecting* Annex E Options, driven by the generated Table E.1-1
|
|
2729
2754
|
* action map ({@link annexE}). It is a **pure** function: the input {@link Dataset}
|
|
2730
2755
|
* is never mutated; a fresh `Dataset` (with a rebuilt element map and File Meta)
|
|
2731
|
-
* is returned alongside a {@link DeidentifyReport}
|
|
2732
|
-
*
|
|
2756
|
+
* is returned alongside a {@link DeidentifyReport} whose two non-value-free
|
|
2757
|
+
* fields are named on that type: `uidMap` and `removedPrivateTags`.
|
|
2733
2758
|
*
|
|
2734
2759
|
* **What it does**
|
|
2735
2760
|
* - Resolves each attribute's action (basic profile, overridden by an active
|
|
@@ -2804,6 +2829,29 @@ declare class DeidentifyError extends Error {
|
|
|
2804
2829
|
* `35adc2d`. No content test can decide it - arbitrary bytes are what those
|
|
2805
2830
|
* VRs are for - and the one candidate remedy empties conformant binary values.
|
|
2806
2831
|
* See `./embedded.ts`.
|
|
2832
|
+
* - `RetainSafePrivate` **retains nothing inside a Sequence Item whose Data Set
|
|
2833
|
+
* boundary the file contradicts** - an item stream that ran past its
|
|
2834
|
+
* sequence's own declared Value Length. Which elements are in the Item is then
|
|
2835
|
+
* not determined by the file, and PS3.5 §7.8.1 scopes a private block
|
|
2836
|
+
* reservation to exactly that boundary, so PS3.15 §E.3.10's "known ... to be
|
|
2837
|
+
* safe" cannot be established and its "all other Private Attributes shall be
|
|
2838
|
+
* removed **or processed in the element-specific manner recommended by
|
|
2839
|
+
* Deidentification Action (0008,0307), if present within Private Data Element
|
|
2840
|
+
* Characteristics Sequence (0008,0300)**" applies - a two-branch clause, of
|
|
2841
|
+
* which removal is the branch available here (`(0008,0307)` is not
|
|
2842
|
+
* implemented). Every private element the recursion **reaches** in such an
|
|
2843
|
+
* Item, at any depth below it, is removed and named in
|
|
2844
|
+
* `report.removedPrivateTags`. **There is one carve-out, and it is the same one
|
|
2845
|
+
* `#54` was refused for asserting away**: `keepsPrivate` decides *before* the
|
|
2846
|
+
* descent, so a **private `SQ`** the profile vouches for is kept **verbatim**,
|
|
2847
|
+
* its items are never walked, and this rule is never consulted inside it -
|
|
2848
|
+
* measured, and it still leaks a private value absorbed from the root
|
|
2849
|
+
* (`PRE-EXISTING`, identical on `164eb39`, its own item). That costs content
|
|
2850
|
+
* on a malformed file and nothing at all on a conformant one; without it a
|
|
2851
|
+
* private value the sender wrote **outside** the sequence was retained on the
|
|
2852
|
+
* Item's reservation and written into output stamped `PatientIdentityRemoved =
|
|
2853
|
+
* YES` with `removedPrivateTags: []` (`DICOM-PRIVATE-CREATOR-RESERVATION-LEAK`,
|
|
2854
|
+
* live through `0.0.9`). See {@link itemStreamOverrunsSequence}.
|
|
2807
2855
|
*
|
|
2808
2856
|
* @module
|
|
2809
2857
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -2526,13 +2526,21 @@ interface UndefinedVrFinding {
|
|
|
2526
2526
|
/**
|
|
2527
2527
|
* The audit trail returned alongside the de-identified dataset.
|
|
2528
2528
|
*
|
|
2529
|
-
*
|
|
2530
|
-
*
|
|
2531
|
-
*
|
|
2532
|
-
*
|
|
2533
|
-
*
|
|
2534
|
-
*
|
|
2535
|
-
*
|
|
2529
|
+
* Most fields are composed from static tables: Part 6 keywords, Annex E action
|
|
2530
|
+
* codes, structural `TAG[index]` sequence paths, and registry warning messages.
|
|
2531
|
+
* **Two are not, and both are named here rather than in a footnote.**
|
|
2532
|
+
*
|
|
2533
|
+
* 1. **`uidMap`** - its keys are the source UIDs read out of the file, kept so a
|
|
2534
|
+
* caller can make UID replacement consistent across a study or an archive. A
|
|
2535
|
+
* Study or SOP Instance UID is a unique identifying number, so treat it as
|
|
2536
|
+
* PHI.
|
|
2537
|
+
* 2. **`removedPrivateTags`** - see the field's own note. On a *well-formed*
|
|
2538
|
+
* file these are the sender's own private tag numbers and carry nothing; on a
|
|
2539
|
+
* malformed one a tag can be four bytes of a value, and it is measured, not
|
|
2540
|
+
* theoretical.
|
|
2541
|
+
*
|
|
2542
|
+
* So "the report is safe to log apart from `uidMap`" is **not** an accurate
|
|
2543
|
+
* description of this type, and was corrected rather than kept convenient.
|
|
2536
2544
|
*
|
|
2537
2545
|
* @example
|
|
2538
2546
|
* ```ts
|
|
@@ -2545,7 +2553,24 @@ interface UndefinedVrFinding {
|
|
|
2545
2553
|
interface DeidentifyReport {
|
|
2546
2554
|
/** Per-attribute outcomes for every attribute Annex E acted on. */
|
|
2547
2555
|
readonly attributes: readonly DeidentifiedAttribute[];
|
|
2548
|
-
/**
|
|
2556
|
+
/**
|
|
2557
|
+
* Private tags removed under the Basic Profile (kept ones are omitted).
|
|
2558
|
+
*
|
|
2559
|
+
* **This is the second field that is not value-free, and the qualification is
|
|
2560
|
+
* measured.** A tag here is composed from four bytes of the source, and on a
|
|
2561
|
+
* file whose Value Lengths disagree with its bytes those four bytes can be
|
|
2562
|
+
* document content rather than a tag the sender wrote: an `OB` carrier holding
|
|
2563
|
+
* `"SECRET-NOTE-"` followed by a well-formed odd-group header reports
|
|
2564
|
+
* `["41534342"]`, whose wire-order bytes read `"SABC"`. That reproduces
|
|
2565
|
+
* identically on every release that has shipped this field.
|
|
2566
|
+
*
|
|
2567
|
+
* It is reported anyway, because *which* private tags were removed is the
|
|
2568
|
+
* whole audit value of the field and withholding them would destroy it on
|
|
2569
|
+
* every well-formed file to bound a malformed one. Narrowing it - to blocks a
|
|
2570
|
+
* creator in the same Data Set actually reserved, say - is a product decision
|
|
2571
|
+
* about audit value versus a four-byte echo, not a defect fix, and it has not
|
|
2572
|
+
* been made. Treat this array as PHI when the source is untrusted.
|
|
2573
|
+
*/
|
|
2549
2574
|
readonly removedPrivateTags: readonly Tag[];
|
|
2550
2575
|
/**
|
|
2551
2576
|
* Values emptied because whole Data Elements were encoded inside them by an
|
|
@@ -2728,8 +2753,8 @@ declare class DeidentifyError extends Error {
|
|
|
2728
2753
|
* nine *metadata-affecting* Annex E Options, driven by the generated Table E.1-1
|
|
2729
2754
|
* action map ({@link annexE}). It is a **pure** function: the input {@link Dataset}
|
|
2730
2755
|
* is never mutated; a fresh `Dataset` (with a rebuilt element map and File Meta)
|
|
2731
|
-
* is returned alongside a {@link DeidentifyReport}
|
|
2732
|
-
*
|
|
2756
|
+
* is returned alongside a {@link DeidentifyReport} whose two non-value-free
|
|
2757
|
+
* fields are named on that type: `uidMap` and `removedPrivateTags`.
|
|
2733
2758
|
*
|
|
2734
2759
|
* **What it does**
|
|
2735
2760
|
* - Resolves each attribute's action (basic profile, overridden by an active
|
|
@@ -2804,6 +2829,29 @@ declare class DeidentifyError extends Error {
|
|
|
2804
2829
|
* `35adc2d`. No content test can decide it - arbitrary bytes are what those
|
|
2805
2830
|
* VRs are for - and the one candidate remedy empties conformant binary values.
|
|
2806
2831
|
* See `./embedded.ts`.
|
|
2832
|
+
* - `RetainSafePrivate` **retains nothing inside a Sequence Item whose Data Set
|
|
2833
|
+
* boundary the file contradicts** - an item stream that ran past its
|
|
2834
|
+
* sequence's own declared Value Length. Which elements are in the Item is then
|
|
2835
|
+
* not determined by the file, and PS3.5 §7.8.1 scopes a private block
|
|
2836
|
+
* reservation to exactly that boundary, so PS3.15 §E.3.10's "known ... to be
|
|
2837
|
+
* safe" cannot be established and its "all other Private Attributes shall be
|
|
2838
|
+
* removed **or processed in the element-specific manner recommended by
|
|
2839
|
+
* Deidentification Action (0008,0307), if present within Private Data Element
|
|
2840
|
+
* Characteristics Sequence (0008,0300)**" applies - a two-branch clause, of
|
|
2841
|
+
* which removal is the branch available here (`(0008,0307)` is not
|
|
2842
|
+
* implemented). Every private element the recursion **reaches** in such an
|
|
2843
|
+
* Item, at any depth below it, is removed and named in
|
|
2844
|
+
* `report.removedPrivateTags`. **There is one carve-out, and it is the same one
|
|
2845
|
+
* `#54` was refused for asserting away**: `keepsPrivate` decides *before* the
|
|
2846
|
+
* descent, so a **private `SQ`** the profile vouches for is kept **verbatim**,
|
|
2847
|
+
* its items are never walked, and this rule is never consulted inside it -
|
|
2848
|
+
* measured, and it still leaks a private value absorbed from the root
|
|
2849
|
+
* (`PRE-EXISTING`, identical on `164eb39`, its own item). That costs content
|
|
2850
|
+
* on a malformed file and nothing at all on a conformant one; without it a
|
|
2851
|
+
* private value the sender wrote **outside** the sequence was retained on the
|
|
2852
|
+
* Item's reservation and written into output stamped `PatientIdentityRemoved =
|
|
2853
|
+
* YES` with `removedPrivateTags: []` (`DICOM-PRIVATE-CREATOR-RESERVATION-LEAK`,
|
|
2854
|
+
* live through `0.0.9`). See {@link itemStreamOverrunsSequence}.
|
|
2807
2855
|
*
|
|
2808
2856
|
* @module
|
|
2809
2857
|
*/
|
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.10";
|
|
13
13
|
|
|
14
14
|
// src/dictionary/index.ts
|
|
15
15
|
var dictionary_exports = {};
|
|
@@ -11701,6 +11701,12 @@ var BE_VR_STRIDE = Object.freeze({
|
|
|
11701
11701
|
UT: 0,
|
|
11702
11702
|
SQ: 0
|
|
11703
11703
|
});
|
|
11704
|
+
var KNOWN_VRS = Object.freeze(
|
|
11705
|
+
new Set(Object.keys(BE_VR_STRIDE))
|
|
11706
|
+
);
|
|
11707
|
+
function isRecognizedVr(vr) {
|
|
11708
|
+
return KNOWN_VRS.has(vr);
|
|
11709
|
+
}
|
|
11704
11710
|
|
|
11705
11711
|
// src/parser/warnings.ts
|
|
11706
11712
|
var WARNING_CODES = {
|
|
@@ -11751,7 +11757,11 @@ var WARNING_MESSAGES = Object.freeze({
|
|
|
11751
11757
|
DICOM_VR_MISMATCH: "Element ({tag}) on-wire VR is {vr2}; dictionary lists {vr}. Trusting on-wire VR.",
|
|
11752
11758
|
DICOM_PRIVATE_TAG_NO_CREATOR: "Private element ({tag}) has no Private Creator registered for its block; treating as VR=UN.",
|
|
11753
11759
|
DICOM_GROUP_LENGTH_IN_DATASET: "Retired Group Length element ({tag}) encountered in dataset; preserved as-is.",
|
|
11754
|
-
|
|
11760
|
+
// The tag is deliberately absent, and this is the only *parser* message where
|
|
11761
|
+
// that is true. See `nonzeroReservedBytes`. Kept terse for the same reason the
|
|
11762
|
+
// de-identify messages are: it is emitted once per element, and element count
|
|
11763
|
+
// is attacker-chosen.
|
|
11764
|
+
DICOM_NONZERO_RESERVED_BYTES: "Non-zero reserved bytes between VR and length (first byte {n}, second byte {n2}); ignoring. Tag withheld; the byte offset identifies the element.",
|
|
11755
11765
|
DICOM_SQ_NOT_DESCENDED: "Element ({tag}) resolved to VR=SQ from the dictionary but its defined-length value is not a valid item stream; kept as opaque bytes and NOT descended, so nothing nested inside it is visible to navigation, and deidentify() cannot audit it. See DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE.",
|
|
11756
11766
|
DICOM_UN_PARSED_AS_SQ: "Element ({tag}) has VR=UN with undefined length; descended as Implicit VR LE sequence per CP-246.",
|
|
11757
11767
|
DICOM_EMPTY_ITEM_IN_SEQUENCE: "Sequence ({tag}) contains an empty item (length=0); tolerated.",
|
|
@@ -11786,7 +11796,6 @@ var WARNING_MESSAGES = Object.freeze({
|
|
|
11786
11796
|
DICOM_UNSUPPORTED_CHARSET: "(0008,0005) Specific Character Set value {n} names a defined term this build does not support; decoding text as UTF-8 best-effort. The term is not reproduced here - read the element if you need it.",
|
|
11787
11797
|
DICOM_CHARSET_AMBIGUOUS_SEPARATOR: "(0008,0005) Specific Character Set value {n} is ambiguous under the active code extensions; decoding text as UTF-8 best-effort."
|
|
11788
11798
|
});
|
|
11789
|
-
var KNOWN_VRS = new Set(Object.keys(BE_VR_STRIDE));
|
|
11790
11799
|
var TAG_SHAPE = /^[0-9A-F]{8}$/u;
|
|
11791
11800
|
function renderTag(tag) {
|
|
11792
11801
|
return tag !== void 0 && TAG_SHAPE.test(tag) ? tag : WITHHELD;
|
|
@@ -11828,9 +11837,8 @@ function privateTagNoCreator(position, tag) {
|
|
|
11828
11837
|
function groupLengthInDataset(position, tag) {
|
|
11829
11838
|
return build(WARNING_CODES.DICOM_GROUP_LENGTH_IN_DATASET, position, { tag });
|
|
11830
11839
|
}
|
|
11831
|
-
function nonzeroReservedBytes(position,
|
|
11840
|
+
function nonzeroReservedBytes(position, first, second) {
|
|
11832
11841
|
return build(WARNING_CODES.DICOM_NONZERO_RESERVED_BYTES, position, {
|
|
11833
|
-
tag,
|
|
11834
11842
|
n: first,
|
|
11835
11843
|
n2: second
|
|
11836
11844
|
});
|
|
@@ -12034,12 +12042,12 @@ function readExplicitElementHeader(cursor, _ctx, emit) {
|
|
|
12034
12042
|
const vr = vrSlice.toString("ascii");
|
|
12035
12043
|
let length;
|
|
12036
12044
|
let headerLength;
|
|
12037
|
-
if (LONG_FORM_VRS.has(vr)) {
|
|
12045
|
+
if (LONG_FORM_VRS.has(vr) || !isRecognizedVr(vr)) {
|
|
12038
12046
|
const reserved0 = cursor.buffer[cursor.position];
|
|
12039
12047
|
const reserved1 = cursor.buffer[cursor.position + 1];
|
|
12040
12048
|
cursor.position += 2;
|
|
12041
12049
|
if ((reserved0 ?? 0) !== 0 || (reserved1 ?? 0) !== 0) {
|
|
12042
|
-
emit(nonzeroReservedBytes({ byteOffset: headerStart },
|
|
12050
|
+
emit(nonzeroReservedBytes({ byteOffset: headerStart }, reserved0 ?? 0, reserved1 ?? 0));
|
|
12043
12051
|
}
|
|
12044
12052
|
length = cursor.readUInt32();
|
|
12045
12053
|
headerLength = 12;
|
|
@@ -12177,7 +12185,7 @@ function readExplicitLeElement(cursor) {
|
|
|
12177
12185
|
const vr = cursor.slice(2).toString("ascii");
|
|
12178
12186
|
let length;
|
|
12179
12187
|
let headerLength;
|
|
12180
|
-
if (LONG_FORM_VRS.has(vr)) {
|
|
12188
|
+
if (LONG_FORM_VRS.has(vr) || !isRecognizedVr(vr)) {
|
|
12181
12189
|
cursor.slice(2);
|
|
12182
12190
|
length = cursor.readUInt32();
|
|
12183
12191
|
headerLength = 12;
|
|
@@ -13451,7 +13459,7 @@ function encodeDatasetElement(el, encoding) {
|
|
|
13451
13459
|
}
|
|
13452
13460
|
const littleEndian = encoding === "explicitLE";
|
|
13453
13461
|
const vrBuf = Buffer.from(el.vr, "ascii");
|
|
13454
|
-
if (LONG_FORM_VRS.has(el.vr)) {
|
|
13462
|
+
if (LONG_FORM_VRS.has(el.vr) || !isRecognizedVr(el.vr)) {
|
|
13455
13463
|
return Buffer.concat([
|
|
13456
13464
|
uint16(group, littleEndian),
|
|
13457
13465
|
uint16(element, littleEndian),
|
|
@@ -13502,7 +13510,7 @@ function encodeMetaElement(tag, vr, rawValue) {
|
|
|
13502
13510
|
const elementBuf = Buffer.alloc(2);
|
|
13503
13511
|
elementBuf.writeUInt16LE(element, 0);
|
|
13504
13512
|
const vrBuf = Buffer.from(vr, "ascii");
|
|
13505
|
-
if (LONG_FORM_VRS.has(vr)) {
|
|
13513
|
+
if (LONG_FORM_VRS.has(vr) || !isRecognizedVr(vr)) {
|
|
13506
13514
|
const lengthBuf2 = Buffer.alloc(4);
|
|
13507
13515
|
lengthBuf2.writeUInt32LE(value.length, 0);
|
|
13508
13516
|
return Buffer.concat([
|
|
@@ -14697,7 +14705,6 @@ function uidValueMultiplicity(rawBytes) {
|
|
|
14697
14705
|
}
|
|
14698
14706
|
|
|
14699
14707
|
// src/deident/embedded.ts
|
|
14700
|
-
var KNOWN_VRS2 = new Set(Object.keys(BE_VR_STRIDE));
|
|
14701
14708
|
var UNDEFINED_LENGTH4 = 4294967295;
|
|
14702
14709
|
var FFFE_GROUP = 65534;
|
|
14703
14710
|
var SCANNABLE_CARRIER_VRS = /* @__PURE__ */ new Set([
|
|
@@ -14758,7 +14765,7 @@ function decodeAt(value, off, encoding) {
|
|
|
14758
14765
|
return next2 > value.length ? void 0 : { next: next2, tag };
|
|
14759
14766
|
}
|
|
14760
14767
|
const vr = value.toString("latin1", off + 4, off + 6);
|
|
14761
|
-
if (!
|
|
14768
|
+
if (!KNOWN_VRS.has(vr)) return void 0;
|
|
14762
14769
|
if (LONG_FORM_VRS.has(vr)) {
|
|
14763
14770
|
if (off + 12 > value.length) return void 0;
|
|
14764
14771
|
if (value[off + 6] !== 0 || value[off + 7] !== 0) return void 0;
|
|
@@ -14895,7 +14902,6 @@ var BODY_ENCODING2 = {
|
|
|
14895
14902
|
};
|
|
14896
14903
|
var MAX_UNAUDITABLE_SEQUENCE_FINDINGS = 64;
|
|
14897
14904
|
var MAX_UNDEFINED_VR_FINDINGS = 64;
|
|
14898
|
-
var KNOWN_VRS3 = new Set(Object.keys(BE_VR_STRIDE));
|
|
14899
14905
|
function validateRetain(retain) {
|
|
14900
14906
|
const valid = new Set(DEIDENTIFY_OPTIONS);
|
|
14901
14907
|
const active = /* @__PURE__ */ new Set();
|
|
@@ -15016,7 +15022,7 @@ function actsOnTag(tag, ctx) {
|
|
|
15016
15022
|
return resolveAction(effectiveCode(action, ctx.active)) !== "K";
|
|
15017
15023
|
}
|
|
15018
15024
|
function hasUndefinedVr(el) {
|
|
15019
|
-
return !
|
|
15025
|
+
return !KNOWN_VRS.has(el.vr);
|
|
15020
15026
|
}
|
|
15021
15027
|
function keepOrEmpty(el, ctx, contextPath, out) {
|
|
15022
15028
|
if (hasUndefinedVr(el)) {
|
|
@@ -15080,6 +15086,13 @@ function creatorFor(tag, creators) {
|
|
|
15080
15086
|
const { group, element } = splitTag(tag);
|
|
15081
15087
|
return creators.get(`${String(group)}:${String(element >> 8 & 255)}`);
|
|
15082
15088
|
}
|
|
15089
|
+
var UNDEFINED_LENGTH5 = 4294967295;
|
|
15090
|
+
var EXPLICIT_SQ_HEADER_BYTES = 12;
|
|
15091
|
+
function itemStreamOverrunsSequence(el, encoding) {
|
|
15092
|
+
if (el.vr !== "SQ" || el.length === UNDEFINED_LENGTH5) return false;
|
|
15093
|
+
const headerBytes = encoding === "implicit" ? 0 : EXPLICIT_SQ_HEADER_BYTES;
|
|
15094
|
+
return el.rawBytes.length - headerBytes > el.length;
|
|
15095
|
+
}
|
|
15083
15096
|
function keepsPrivate(el, ctx, creators) {
|
|
15084
15097
|
if (!ctx.active.has("RetainSafePrivate") || ctx.profile === void 0) return false;
|
|
15085
15098
|
if (isPrivateCreatorElement(el.tag)) {
|
|
@@ -15089,7 +15102,7 @@ function keepsPrivate(el, ctx, creators) {
|
|
|
15089
15102
|
if (creator === void 0) return false;
|
|
15090
15103
|
return resolvePrivateTag(ctx.profile, el.tag, creator) !== void 0;
|
|
15091
15104
|
}
|
|
15092
|
-
function processElements(source, ctx, contextPath) {
|
|
15105
|
+
function processElements(source, ctx, contextPath, reservationsUsable) {
|
|
15093
15106
|
const out = {
|
|
15094
15107
|
elements: /* @__PURE__ */ new Map(),
|
|
15095
15108
|
attributes: [],
|
|
@@ -15102,7 +15115,8 @@ function processElements(source, ctx, contextPath) {
|
|
|
15102
15115
|
const creators = creatorsInScope(source);
|
|
15103
15116
|
for (const el of source) {
|
|
15104
15117
|
if (isPrivateTag(el.tag)) {
|
|
15105
|
-
if (keepsPrivate(el, ctx, creators))
|
|
15118
|
+
if (reservationsUsable && keepsPrivate(el, ctx, creators))
|
|
15119
|
+
keepOrEmpty(el, ctx, contextPath, out);
|
|
15106
15120
|
else out.removedPrivateTags.push(el.tag);
|
|
15107
15121
|
continue;
|
|
15108
15122
|
}
|
|
@@ -15110,7 +15124,8 @@ function processElements(source, ctx, contextPath) {
|
|
|
15110
15124
|
if (action === void 0) {
|
|
15111
15125
|
if (el.vr === "SQ") {
|
|
15112
15126
|
if (isUnauditableSequence(el)) emptyUnauditableSequence(el, ctx, contextPath, out);
|
|
15113
|
-
else
|
|
15127
|
+
else
|
|
15128
|
+
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out, reservationsUsable));
|
|
15114
15129
|
} else {
|
|
15115
15130
|
keepOrEmpty(el, ctx, contextPath, out);
|
|
15116
15131
|
}
|
|
@@ -15118,7 +15133,7 @@ function processElements(source, ctx, contextPath) {
|
|
|
15118
15133
|
}
|
|
15119
15134
|
const resolved = resolveAction(effectiveCode(action, ctx.active));
|
|
15120
15135
|
if (el.vr === "SQ") {
|
|
15121
|
-
applySequenceAction(el, resolved, action, ctx, contextPath, out);
|
|
15136
|
+
applySequenceAction(el, resolved, action, ctx, contextPath, out, reservationsUsable);
|
|
15122
15137
|
continue;
|
|
15123
15138
|
}
|
|
15124
15139
|
let applied;
|
|
@@ -15159,11 +15174,12 @@ function processElements(source, ctx, contextPath) {
|
|
|
15159
15174
|
}
|
|
15160
15175
|
return out;
|
|
15161
15176
|
}
|
|
15162
|
-
function descendSequence(el, ctx, contextPath, out) {
|
|
15177
|
+
function descendSequence(el, ctx, contextPath, out, reservationsUsable) {
|
|
15178
|
+
const childReservationsUsable = reservationsUsable && !itemStreamOverrunsSequence(el, ctx.encoding);
|
|
15163
15179
|
const newItems = [];
|
|
15164
15180
|
(el.items ?? []).forEach((item, index) => {
|
|
15165
15181
|
const childPath = [...contextPath, `${el.tag}[${String(index)}]`];
|
|
15166
|
-
const inner = processElements(item.elements(), ctx, childPath);
|
|
15182
|
+
const inner = processElements(item.elements(), ctx, childPath, childReservationsUsable);
|
|
15167
15183
|
out.attributes.push(...inner.attributes);
|
|
15168
15184
|
out.removedPrivateTags.push(...inner.removedPrivateTags);
|
|
15169
15185
|
out.embeddedAttributes.push(...inner.embeddedAttributes);
|
|
@@ -15174,7 +15190,7 @@ function descendSequence(el, ctx, contextPath, out) {
|
|
|
15174
15190
|
});
|
|
15175
15191
|
return rebuildSequence(el, newItems, ctx.encoding);
|
|
15176
15192
|
}
|
|
15177
|
-
function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
|
|
15193
|
+
function applySequenceAction(el, resolved, action, ctx, contextPath, out, reservationsUsable) {
|
|
15178
15194
|
let applied;
|
|
15179
15195
|
switch (resolved) {
|
|
15180
15196
|
case "X":
|
|
@@ -15190,7 +15206,7 @@ function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
|
|
|
15190
15206
|
emptyUnauditableSequence(el, ctx, contextPath, out);
|
|
15191
15207
|
applied = "emptied";
|
|
15192
15208
|
} else {
|
|
15193
|
-
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
|
|
15209
|
+
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out, reservationsUsable));
|
|
15194
15210
|
applied = "cleaned";
|
|
15195
15211
|
}
|
|
15196
15212
|
break;
|
|
@@ -15200,7 +15216,7 @@ function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
|
|
|
15200
15216
|
emptyUnauditableSequence(el, ctx, contextPath, out);
|
|
15201
15217
|
applied = "emptied";
|
|
15202
15218
|
} else {
|
|
15203
|
-
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
|
|
15219
|
+
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out, reservationsUsable));
|
|
15204
15220
|
applied = "kept";
|
|
15205
15221
|
}
|
|
15206
15222
|
break;
|
|
@@ -15251,7 +15267,7 @@ function deidentify(ds, options = {}) {
|
|
|
15251
15267
|
encoding,
|
|
15252
15268
|
budget: { unauditableSequences: 0, undefinedVrElements: 0 }
|
|
15253
15269
|
};
|
|
15254
|
-
const processed = processElements(ds.elements(), ctx, []);
|
|
15270
|
+
const processed = processElements(ds.elements(), ctx, [], true);
|
|
15255
15271
|
const {
|
|
15256
15272
|
elements,
|
|
15257
15273
|
attributes,
|