@cosyte/dicom 0.0.6 → 0.0.8
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 +376 -0
- package/README.md +6 -2
- package/dist/index.cjs +276 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +276 -10
- package/dist/index.d.ts +276 -10
- package/dist/index.mjs +276 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -11,7 +11,7 @@ var __export = (target, all) => {
|
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// src/version.ts
|
|
14
|
-
var VERSION = "0.0.
|
|
14
|
+
var VERSION = "0.0.8";
|
|
15
15
|
|
|
16
16
|
// src/dictionary/index.ts
|
|
17
17
|
var dictionary_exports = {};
|
|
@@ -11735,6 +11735,12 @@ var WARNING_CODES = {
|
|
|
11735
11735
|
// === Reserved by later phases (declared, not emitted in Phase 2) ===
|
|
11736
11736
|
DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED",
|
|
11737
11737
|
// reserved by Phase 7 - not emitted in Phase 2
|
|
11738
|
+
DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED",
|
|
11739
|
+
// emitted by deidentify(), never by the parser
|
|
11740
|
+
DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE",
|
|
11741
|
+
// emitted by deidentify(), never by the parser
|
|
11742
|
+
DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE",
|
|
11743
|
+
// emitted by deidentify(), never by the parser
|
|
11738
11744
|
DICOM_PRIVATE_CREATOR_UNKNOWN: "DICOM_PRIVATE_CREATOR_UNKNOWN"
|
|
11739
11745
|
// reserved by Phase 6 - not emitted in Phase 2
|
|
11740
11746
|
};
|
|
@@ -11748,13 +11754,30 @@ var WARNING_MESSAGES = Object.freeze({
|
|
|
11748
11754
|
DICOM_PRIVATE_TAG_NO_CREATOR: "Private element ({tag}) has no Private Creator registered for its block; treating as VR=UN.",
|
|
11749
11755
|
DICOM_GROUP_LENGTH_IN_DATASET: "Retired Group Length element ({tag}) encountered in dataset; preserved as-is.",
|
|
11750
11756
|
DICOM_NONZERO_RESERVED_BYTES: "Element ({tag}) has non-zero reserved bytes between VR and length (first byte {n}, second byte {n2}); ignoring.",
|
|
11751
|
-
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
|
|
11757
|
+
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.",
|
|
11752
11758
|
DICOM_UN_PARSED_AS_SQ: "Element ({tag}) has VR=UN with undefined length; descended as Implicit VR LE sequence per CP-246.",
|
|
11753
11759
|
DICOM_EMPTY_ITEM_IN_SEQUENCE: "Sequence ({tag}) contains an empty item (length=0); tolerated.",
|
|
11754
11760
|
DICOM_PIXEL_DATA_LENGTH_MISMATCH: "(7FE0,0010) PixelData declared length {n} does not match computed {n2} bytes.",
|
|
11755
11761
|
DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "Private element ({tag}) under Implicit VR LE has no VR override; falling back to UN.",
|
|
11756
11762
|
DICOM_PRIVATE_CREATOR_UNKNOWN: "Private element ({tag}) has a Private Creator the active profile's private dictionary does not name; falling back to UN. The creator string is not reproduced here - read the (gggg,00EE) element if you need it.",
|
|
11757
11763
|
DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "Pixel Data is present and Burned In Annotation is not 'NO'; this metadata-only de-identifier cannot inspect or clean pixels. Recognizable text may remain burned into the image.",
|
|
11764
|
+
DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "Element ({tag}) {vr} was kept by the action table, but its value ends with {n} whole Data Element(s) - an over-declared Value Length swallowed what followed it. Emptied rather than kept, because the action table cannot see an attribute encoded inside a value. Read report.embeddedAttributes for the tags that were hidden there.",
|
|
11765
|
+
// Deliberately short. One of these is raised per un-auditable element, so a
|
|
11766
|
+
// long message is multiplied by an element count the input controls; the
|
|
11767
|
+
// reasoning belongs in the docs, not in a string repeated thousands of times.
|
|
11768
|
+
DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "Element ({tag}) is VR=SQ with no parsed items, so its {n} value bytes could not be audited (PS3.15 E.1.1); emptied. See report.unauditableSequences.",
|
|
11769
|
+
// Deliberately short for the same reason as the code above: one per element,
|
|
11770
|
+
// and the element count is chosen by the input.
|
|
11771
|
+
//
|
|
11772
|
+
// NEITHER THE TAG NOR THE VR IS ECHOED, and that is specific to this code
|
|
11773
|
+
// rather than caution. Every other factory's {tag} is composed from a real
|
|
11774
|
+
// Data Element header; the condition that raises THIS one is that the header
|
|
11775
|
+
// was fabricated from bytes inside some element's value, so its four tag bytes
|
|
11776
|
+
// and its two VR bytes are document content. `renderTag` shape-checks a tag
|
|
11777
|
+
// and cannot refuse one, so the withholding has to happen at the call site.
|
|
11778
|
+
// The byte offset locates the element instead, and is a position this parser
|
|
11779
|
+
// counted rather than anything the document said.
|
|
11780
|
+
DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "An element at byte offset {n2} carries an on-wire VR that is not one of the 34 PS3.5 6.2 defines, so its {n} value bytes are not a Value Field this library decoded; emptied (PS3.15 E.1.1). Its tag and VR are withheld: an earlier under-declared length can make them fragments of some element's value. See report.undefinedVrElements.",
|
|
11758
11781
|
DICOM_BOM_IN_TEXT_VR: "Element ({tag}) {vr} value begins with a UTF-8 BOM; stripped on decode.",
|
|
11759
11782
|
DICOM_TRAILING_NULL_IN_TEXT_VR: "Element ({tag}) {vr} value has a trailing NULL pad where SPACE is expected; trimmed.",
|
|
11760
11783
|
DICOM_UI_TRAILING_SPACE: "Element ({tag}) UI value is SPACE-padded; UI requires NULL padding (PS3.5 \xA76.2). Trimmed.",
|
|
@@ -11832,6 +11855,25 @@ function privateCreatorUnknown(position, tag) {
|
|
|
11832
11855
|
function burnedInAnnotationNotRemoved(position) {
|
|
11833
11856
|
return build(WARNING_CODES.DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED, position);
|
|
11834
11857
|
}
|
|
11858
|
+
function embeddedAttributeRemoved(position, tag, vr, count) {
|
|
11859
|
+
return build(WARNING_CODES.DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED, position, {
|
|
11860
|
+
tag,
|
|
11861
|
+
vr,
|
|
11862
|
+
n: count
|
|
11863
|
+
});
|
|
11864
|
+
}
|
|
11865
|
+
function sequenceNotAuditable(position, tag, byteLength) {
|
|
11866
|
+
return build(WARNING_CODES.DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE, position, {
|
|
11867
|
+
tag,
|
|
11868
|
+
n: byteLength
|
|
11869
|
+
});
|
|
11870
|
+
}
|
|
11871
|
+
function undefinedVrNotAuditable(position, byteLength) {
|
|
11872
|
+
return build(WARNING_CODES.DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE, position, {
|
|
11873
|
+
n: byteLength,
|
|
11874
|
+
n2: position.byteOffset
|
|
11875
|
+
});
|
|
11876
|
+
}
|
|
11835
11877
|
function bomInTextVR(position, tag, vr) {
|
|
11836
11878
|
return build(WARNING_CODES.DICOM_BOM_IN_TEXT_VR, position, { tag, vr });
|
|
11837
11879
|
}
|
|
@@ -14656,6 +14698,132 @@ function uidValueMultiplicity(rawBytes) {
|
|
|
14656
14698
|
return raw.split("\\").length;
|
|
14657
14699
|
}
|
|
14658
14700
|
|
|
14701
|
+
// src/deident/embedded.ts
|
|
14702
|
+
var KNOWN_VRS2 = new Set(Object.keys(BE_VR_STRIDE));
|
|
14703
|
+
var UNDEFINED_LENGTH4 = 4294967295;
|
|
14704
|
+
var FFFE_GROUP = 65534;
|
|
14705
|
+
var SCANNABLE_CARRIER_VRS = /* @__PURE__ */ new Set([
|
|
14706
|
+
"AE",
|
|
14707
|
+
"AS",
|
|
14708
|
+
"CS",
|
|
14709
|
+
"DA",
|
|
14710
|
+
"DS",
|
|
14711
|
+
"DT",
|
|
14712
|
+
"IS",
|
|
14713
|
+
"LO",
|
|
14714
|
+
"LT",
|
|
14715
|
+
"PN",
|
|
14716
|
+
"SH",
|
|
14717
|
+
"ST",
|
|
14718
|
+
"TM",
|
|
14719
|
+
"UC",
|
|
14720
|
+
"UI",
|
|
14721
|
+
"UR",
|
|
14722
|
+
"UT"
|
|
14723
|
+
]);
|
|
14724
|
+
var ALLOWED_CONTROL_BYTES = /* @__PURE__ */ new Set([
|
|
14725
|
+
9,
|
|
14726
|
+
// TAB
|
|
14727
|
+
10,
|
|
14728
|
+
// LF
|
|
14729
|
+
12,
|
|
14730
|
+
// FF
|
|
14731
|
+
13,
|
|
14732
|
+
// CR
|
|
14733
|
+
27
|
|
14734
|
+
// ESC
|
|
14735
|
+
]);
|
|
14736
|
+
var CONTROL_TOLERANT_VRS = /* @__PURE__ */ new Set(["LT", "ST", "UT"]);
|
|
14737
|
+
var ESC_ONLY_VRS = /* @__PURE__ */ new Set(["LO", "SH", "UC", "PN"]);
|
|
14738
|
+
var MAX_SCAN_BYTES = 1 << 20;
|
|
14739
|
+
var MIN_HEADER = 8;
|
|
14740
|
+
function readU16(buf, off, littleEndian) {
|
|
14741
|
+
return littleEndian ? buf.readUInt16LE(off) : buf.readUInt16BE(off);
|
|
14742
|
+
}
|
|
14743
|
+
function readU32(buf, off, littleEndian) {
|
|
14744
|
+
return littleEndian ? buf.readUInt32LE(off) : buf.readUInt32BE(off);
|
|
14745
|
+
}
|
|
14746
|
+
function decodeAt(value, off, encoding) {
|
|
14747
|
+
if (off + MIN_HEADER > value.length) return void 0;
|
|
14748
|
+
const littleEndian = encoding !== "explicitBE";
|
|
14749
|
+
const group = readU16(value, off, littleEndian);
|
|
14750
|
+
const element = readU16(value, off + 2, littleEndian);
|
|
14751
|
+
if (group === FFFE_GROUP) {
|
|
14752
|
+
return { next: off + 8, tag: void 0 };
|
|
14753
|
+
}
|
|
14754
|
+
if (group === 0 && element === 0) return void 0;
|
|
14755
|
+
const tag = joinTag(group, element);
|
|
14756
|
+
if (encoding === "implicit") {
|
|
14757
|
+
const length2 = readU32(value, off + 4, littleEndian);
|
|
14758
|
+
if (length2 === UNDEFINED_LENGTH4 || length2 % 2 !== 0) return void 0;
|
|
14759
|
+
const next2 = off + 8 + length2;
|
|
14760
|
+
return next2 > value.length ? void 0 : { next: next2, tag };
|
|
14761
|
+
}
|
|
14762
|
+
const vr = value.toString("latin1", off + 4, off + 6);
|
|
14763
|
+
if (!KNOWN_VRS2.has(vr)) return void 0;
|
|
14764
|
+
if (LONG_FORM_VRS.has(vr)) {
|
|
14765
|
+
if (off + 12 > value.length) return void 0;
|
|
14766
|
+
if (value[off + 6] !== 0 || value[off + 7] !== 0) return void 0;
|
|
14767
|
+
const length2 = readU32(value, off + 8, littleEndian);
|
|
14768
|
+
if (length2 === UNDEFINED_LENGTH4 || length2 % 2 !== 0) return void 0;
|
|
14769
|
+
const next2 = off + 12 + length2;
|
|
14770
|
+
return next2 > value.length ? void 0 : { next: next2, tag };
|
|
14771
|
+
}
|
|
14772
|
+
const length = readU16(value, off + 6, littleEndian);
|
|
14773
|
+
if (length % 2 !== 0) return void 0;
|
|
14774
|
+
const next = off + 8 + length;
|
|
14775
|
+
return next > value.length ? void 0 : { next, tag };
|
|
14776
|
+
}
|
|
14777
|
+
function isScannableCarrier(vr) {
|
|
14778
|
+
return SCANNABLE_CARRIER_VRS.has(vr);
|
|
14779
|
+
}
|
|
14780
|
+
function hasByteOutsideRepertoire(region, carrierVr) {
|
|
14781
|
+
const allFive = CONTROL_TOLERANT_VRS.has(carrierVr);
|
|
14782
|
+
const escOnly = ESC_ONLY_VRS.has(carrierVr);
|
|
14783
|
+
for (const byte of region) {
|
|
14784
|
+
if (byte >= 32 && byte !== 127) continue;
|
|
14785
|
+
if (allFive && ALLOWED_CONTROL_BYTES.has(byte)) continue;
|
|
14786
|
+
if (escOnly && byte === 27) continue;
|
|
14787
|
+
return true;
|
|
14788
|
+
}
|
|
14789
|
+
return false;
|
|
14790
|
+
}
|
|
14791
|
+
function findEmbeddedAttributes(value, carrierVr, encoding, isActionable) {
|
|
14792
|
+
if (!isScannableCarrier(carrierVr)) return void 0;
|
|
14793
|
+
if (value.length < MIN_HEADER) return void 0;
|
|
14794
|
+
const windowStart = value.length > MAX_SCAN_BYTES ? value.length - MAX_SCAN_BYTES : 0;
|
|
14795
|
+
const window = value.subarray(windowStart);
|
|
14796
|
+
const n = window.length;
|
|
14797
|
+
const half = (n >> 1) + 1;
|
|
14798
|
+
const tiles = new Uint8Array(half);
|
|
14799
|
+
const hit = new Uint8Array(half);
|
|
14800
|
+
for (let off = n - MIN_HEADER & -2; off >= 0; off -= 2) {
|
|
14801
|
+
const decoded = decodeAt(window, off, encoding);
|
|
14802
|
+
if (decoded === void 0) continue;
|
|
14803
|
+
const atEnd = decoded.next === n;
|
|
14804
|
+
const onward = decoded.next < n ? tiles[decoded.next >> 1] === 1 : false;
|
|
14805
|
+
if (!atEnd && !onward) continue;
|
|
14806
|
+
tiles[off >> 1] = 1;
|
|
14807
|
+
const actionable = decoded.tag !== void 0 && isActionable(decoded.tag);
|
|
14808
|
+
const onwardHit = decoded.next < n ? hit[decoded.next >> 1] === 1 : false;
|
|
14809
|
+
if (actionable || onwardHit) hit[off >> 1] = 1;
|
|
14810
|
+
}
|
|
14811
|
+
for (let off = 0; off + MIN_HEADER <= n; off += 2) {
|
|
14812
|
+
if (hit[off >> 1] !== 1) continue;
|
|
14813
|
+
if (!hasByteOutsideRepertoire(window.subarray(off), carrierVr)) return void 0;
|
|
14814
|
+
const tags = [];
|
|
14815
|
+
let pos = off;
|
|
14816
|
+
while (pos < n) {
|
|
14817
|
+
const decoded = decodeAt(window, pos, encoding);
|
|
14818
|
+
if (decoded === void 0) break;
|
|
14819
|
+
if (decoded.tag !== void 0) tags.push(decoded.tag);
|
|
14820
|
+
pos = decoded.next;
|
|
14821
|
+
}
|
|
14822
|
+
return tags;
|
|
14823
|
+
}
|
|
14824
|
+
return void 0;
|
|
14825
|
+
}
|
|
14826
|
+
|
|
14659
14827
|
// src/deident/types.ts
|
|
14660
14828
|
var DEIDENTIFY_OPTIONS = Object.freeze([
|
|
14661
14829
|
"CleanGraphics",
|
|
@@ -14727,6 +14895,9 @@ var BODY_ENCODING2 = {
|
|
|
14727
14895
|
"1.2.840.10008.1.2.2": "explicitBE",
|
|
14728
14896
|
"1.2.840.10008.1.2.1.99": "explicitLE"
|
|
14729
14897
|
};
|
|
14898
|
+
var MAX_UNAUDITABLE_SEQUENCE_FINDINGS = 64;
|
|
14899
|
+
var MAX_UNDEFINED_VR_FINDINGS = 64;
|
|
14900
|
+
var KNOWN_VRS3 = new Set(Object.keys(BE_VR_STRIDE));
|
|
14730
14901
|
function validateRetain(retain) {
|
|
14731
14902
|
const valid = new Set(DEIDENTIFY_OPTIONS);
|
|
14732
14903
|
const active = /* @__PURE__ */ new Set();
|
|
@@ -14840,6 +15011,64 @@ function isPrivateCreatorElement(tag) {
|
|
|
14840
15011
|
const { element } = splitTag(tag);
|
|
14841
15012
|
return element >= 16 && element <= 255;
|
|
14842
15013
|
}
|
|
15014
|
+
function actsOnTag(tag, ctx) {
|
|
15015
|
+
if (isPrivateTag(tag)) return true;
|
|
15016
|
+
const action = annexE(tag);
|
|
15017
|
+
if (action === void 0) return false;
|
|
15018
|
+
return resolveAction(effectiveCode(action, ctx.active)) !== "K";
|
|
15019
|
+
}
|
|
15020
|
+
function hasUndefinedVr(el) {
|
|
15021
|
+
return !KNOWN_VRS3.has(el.vr);
|
|
15022
|
+
}
|
|
15023
|
+
function keepOrEmpty(el, ctx, contextPath, out) {
|
|
15024
|
+
if (hasUndefinedVr(el)) {
|
|
15025
|
+
emptyUndefinedVrElement(el, ctx, contextPath, out);
|
|
15026
|
+
return false;
|
|
15027
|
+
}
|
|
15028
|
+
const hidden = findEmbeddedAttributes(el.rawBytes, el.vr, ctx.encoding, (t) => actsOnTag(t, ctx));
|
|
15029
|
+
if (hidden === void 0) {
|
|
15030
|
+
out.elements.set(el.tag, el);
|
|
15031
|
+
return true;
|
|
15032
|
+
}
|
|
15033
|
+
out.elements.set(el.tag, freshScalar(el, buffer.Buffer.alloc(0), 0));
|
|
15034
|
+
out.embeddedAttributes.push({
|
|
15035
|
+
tag: el.tag,
|
|
15036
|
+
vr: el.vr,
|
|
15037
|
+
hidden,
|
|
15038
|
+
...contextPath.length > 0 ? { contextPath: [...contextPath] } : {}
|
|
15039
|
+
});
|
|
15040
|
+
out.warnings.push(
|
|
15041
|
+
embeddedAttributeRemoved({ byteOffset: el.byteOffset }, el.tag, el.vr, hidden.length)
|
|
15042
|
+
);
|
|
15043
|
+
return false;
|
|
15044
|
+
}
|
|
15045
|
+
function emptyUndefinedVrElement(el, ctx, contextPath, out) {
|
|
15046
|
+
out.elements.set(el.tag, freshScalar(el, buffer.Buffer.alloc(0), 0));
|
|
15047
|
+
if (ctx.budget.undefinedVrElements >= MAX_UNDEFINED_VR_FINDINGS) return;
|
|
15048
|
+
ctx.budget.undefinedVrElements += 1;
|
|
15049
|
+
out.undefinedVrElements.push({
|
|
15050
|
+
byteOffset: el.byteOffset,
|
|
15051
|
+
byteLength: el.rawBytes.length,
|
|
15052
|
+
...contextPath.length > 0 ? { contextPath: [...contextPath] } : {}
|
|
15053
|
+
});
|
|
15054
|
+
out.warnings.push(undefinedVrNotAuditable({ byteOffset: el.byteOffset }, el.rawBytes.length));
|
|
15055
|
+
}
|
|
15056
|
+
function isUnauditableSequence(el) {
|
|
15057
|
+
return el.vr === "SQ" && el.items === void 0;
|
|
15058
|
+
}
|
|
15059
|
+
function emptyUnauditableSequence(el, ctx, contextPath, out) {
|
|
15060
|
+
out.elements.set(el.tag, rebuildSequence(el, [], ctx.encoding));
|
|
15061
|
+
if (ctx.budget.unauditableSequences >= MAX_UNAUDITABLE_SEQUENCE_FINDINGS) return;
|
|
15062
|
+
ctx.budget.unauditableSequences += 1;
|
|
15063
|
+
out.unauditableSequences.push({
|
|
15064
|
+
tag: el.tag,
|
|
15065
|
+
byteLength: el.rawBytes.length,
|
|
15066
|
+
...contextPath.length > 0 ? { contextPath: [...contextPath] } : {}
|
|
15067
|
+
});
|
|
15068
|
+
out.warnings.push(
|
|
15069
|
+
sequenceNotAuditable({ byteOffset: el.byteOffset }, el.tag, el.rawBytes.length)
|
|
15070
|
+
);
|
|
15071
|
+
}
|
|
14843
15072
|
function creatorsInScope(source) {
|
|
14844
15073
|
const byBlock = /* @__PURE__ */ new Map();
|
|
14845
15074
|
for (const el of source) {
|
|
@@ -14866,21 +15095,26 @@ function processElements(source, ctx, contextPath) {
|
|
|
14866
15095
|
const out = {
|
|
14867
15096
|
elements: /* @__PURE__ */ new Map(),
|
|
14868
15097
|
attributes: [],
|
|
14869
|
-
removedPrivateTags: []
|
|
15098
|
+
removedPrivateTags: [],
|
|
15099
|
+
embeddedAttributes: [],
|
|
15100
|
+
unauditableSequences: [],
|
|
15101
|
+
undefinedVrElements: [],
|
|
15102
|
+
warnings: []
|
|
14870
15103
|
};
|
|
14871
15104
|
const creators = creatorsInScope(source);
|
|
14872
15105
|
for (const el of source) {
|
|
14873
15106
|
if (isPrivateTag(el.tag)) {
|
|
14874
|
-
if (keepsPrivate(el, ctx, creators))
|
|
15107
|
+
if (keepsPrivate(el, ctx, creators)) keepOrEmpty(el, ctx, contextPath, out);
|
|
14875
15108
|
else out.removedPrivateTags.push(el.tag);
|
|
14876
15109
|
continue;
|
|
14877
15110
|
}
|
|
14878
15111
|
const action = annexE(el.tag);
|
|
14879
15112
|
if (action === void 0) {
|
|
14880
|
-
if (el.vr === "SQ"
|
|
14881
|
-
|
|
15113
|
+
if (el.vr === "SQ") {
|
|
15114
|
+
if (isUnauditableSequence(el)) emptyUnauditableSequence(el, ctx, contextPath, out);
|
|
15115
|
+
else out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
|
|
14882
15116
|
} else {
|
|
14883
|
-
|
|
15117
|
+
keepOrEmpty(el, ctx, contextPath, out);
|
|
14884
15118
|
}
|
|
14885
15119
|
continue;
|
|
14886
15120
|
}
|
|
@@ -14892,8 +15126,7 @@ function processElements(source, ctx, contextPath) {
|
|
|
14892
15126
|
let applied;
|
|
14893
15127
|
switch (resolved) {
|
|
14894
15128
|
case "K":
|
|
14895
|
-
|
|
14896
|
-
applied = "kept";
|
|
15129
|
+
applied = keepOrEmpty(el, ctx, contextPath, out) ? "kept" : "emptied";
|
|
14897
15130
|
break;
|
|
14898
15131
|
case "X":
|
|
14899
15132
|
applied = "removed";
|
|
@@ -14935,6 +15168,10 @@ function descendSequence(el, ctx, contextPath, out) {
|
|
|
14935
15168
|
const inner = processElements(item.elements(), ctx, childPath);
|
|
14936
15169
|
out.attributes.push(...inner.attributes);
|
|
14937
15170
|
out.removedPrivateTags.push(...inner.removedPrivateTags);
|
|
15171
|
+
out.embeddedAttributes.push(...inner.embeddedAttributes);
|
|
15172
|
+
out.unauditableSequences.push(...inner.unauditableSequences);
|
|
15173
|
+
out.undefinedVrElements.push(...inner.undefinedVrElements);
|
|
15174
|
+
out.warnings.push(...inner.warnings);
|
|
14938
15175
|
newItems.push(new Item({ index, warnings: [], elements: inner.elements }));
|
|
14939
15176
|
});
|
|
14940
15177
|
return rebuildSequence(el, newItems, ctx.encoding);
|
|
@@ -14951,13 +15188,23 @@ function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
|
|
|
14951
15188
|
applied = "emptied";
|
|
14952
15189
|
break;
|
|
14953
15190
|
case "C":
|
|
14954
|
-
|
|
14955
|
-
|
|
15191
|
+
if (isUnauditableSequence(el)) {
|
|
15192
|
+
emptyUnauditableSequence(el, ctx, contextPath, out);
|
|
15193
|
+
applied = "emptied";
|
|
15194
|
+
} else {
|
|
15195
|
+
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
|
|
15196
|
+
applied = "cleaned";
|
|
15197
|
+
}
|
|
14956
15198
|
break;
|
|
14957
15199
|
case "U":
|
|
14958
15200
|
case "K":
|
|
14959
|
-
|
|
14960
|
-
|
|
15201
|
+
if (isUnauditableSequence(el)) {
|
|
15202
|
+
emptyUnauditableSequence(el, ctx, contextPath, out);
|
|
15203
|
+
applied = "emptied";
|
|
15204
|
+
} else {
|
|
15205
|
+
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
|
|
15206
|
+
applied = "kept";
|
|
15207
|
+
}
|
|
14961
15208
|
break;
|
|
14962
15209
|
}
|
|
14963
15210
|
out.attributes.push(auditAttribute(el.tag, action, resolved, applied, contextPath));
|
|
@@ -15003,8 +15250,18 @@ function deidentify(ds, options = {}) {
|
|
|
15003
15250
|
active,
|
|
15004
15251
|
remap,
|
|
15005
15252
|
profile: options.profile,
|
|
15006
|
-
encoding
|
|
15007
|
-
|
|
15253
|
+
encoding,
|
|
15254
|
+
budget: { unauditableSequences: 0, undefinedVrElements: 0 }
|
|
15255
|
+
};
|
|
15256
|
+
const processed = processElements(ds.elements(), ctx, []);
|
|
15257
|
+
const {
|
|
15258
|
+
elements,
|
|
15259
|
+
attributes,
|
|
15260
|
+
removedPrivateTags,
|
|
15261
|
+
embeddedAttributes,
|
|
15262
|
+
unauditableSequences,
|
|
15263
|
+
undefinedVrElements
|
|
15264
|
+
} = processed;
|
|
15008
15265
|
elements.set(
|
|
15009
15266
|
TAG_PATIENT_IDENTITY_REMOVED,
|
|
15010
15267
|
insertedScalar(TAG_PATIENT_IDENTITY_REMOVED, "CS", buffer.Buffer.from("YES", "latin1"), littleEndian)
|
|
@@ -15014,7 +15271,7 @@ function deidentify(ds, options = {}) {
|
|
|
15014
15271
|
TAG_DEIDENTIFICATION_METHOD,
|
|
15015
15272
|
insertedScalar(TAG_DEIDENTIFICATION_METHOD, "LO", buffer.Buffer.from(method, "latin1"), littleEndian)
|
|
15016
15273
|
);
|
|
15017
|
-
const warnings = [];
|
|
15274
|
+
const warnings = [...processed.warnings];
|
|
15018
15275
|
if (hasUncleanedBurnedIn(ds)) {
|
|
15019
15276
|
const offset = ds.get(TAG_PIXEL_DATA)?.byteOffset ?? 0;
|
|
15020
15277
|
warnings.push(burnedInAnnotationNotRemoved({ byteOffset: offset, fileMeta: false }));
|
|
@@ -15029,6 +15286,9 @@ function deidentify(ds, options = {}) {
|
|
|
15029
15286
|
const report = {
|
|
15030
15287
|
attributes,
|
|
15031
15288
|
removedPrivateTags,
|
|
15289
|
+
embeddedAttributes,
|
|
15290
|
+
unauditableSequences,
|
|
15291
|
+
undefinedVrElements,
|
|
15032
15292
|
uidMap: remap.cache,
|
|
15033
15293
|
warnings,
|
|
15034
15294
|
retained: [...active]
|