@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.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.10";
|
|
15
15
|
|
|
16
16
|
// src/dictionary/index.ts
|
|
17
17
|
var dictionary_exports = {};
|
|
@@ -11703,6 +11703,12 @@ var BE_VR_STRIDE = Object.freeze({
|
|
|
11703
11703
|
UT: 0,
|
|
11704
11704
|
SQ: 0
|
|
11705
11705
|
});
|
|
11706
|
+
var KNOWN_VRS = Object.freeze(
|
|
11707
|
+
new Set(Object.keys(BE_VR_STRIDE))
|
|
11708
|
+
);
|
|
11709
|
+
function isRecognizedVr(vr) {
|
|
11710
|
+
return KNOWN_VRS.has(vr);
|
|
11711
|
+
}
|
|
11706
11712
|
|
|
11707
11713
|
// src/parser/warnings.ts
|
|
11708
11714
|
var WARNING_CODES = {
|
|
@@ -11753,7 +11759,11 @@ var WARNING_MESSAGES = Object.freeze({
|
|
|
11753
11759
|
DICOM_VR_MISMATCH: "Element ({tag}) on-wire VR is {vr2}; dictionary lists {vr}. Trusting on-wire VR.",
|
|
11754
11760
|
DICOM_PRIVATE_TAG_NO_CREATOR: "Private element ({tag}) has no Private Creator registered for its block; treating as VR=UN.",
|
|
11755
11761
|
DICOM_GROUP_LENGTH_IN_DATASET: "Retired Group Length element ({tag}) encountered in dataset; preserved as-is.",
|
|
11756
|
-
|
|
11762
|
+
// The tag is deliberately absent, and this is the only *parser* message where
|
|
11763
|
+
// that is true. See `nonzeroReservedBytes`. Kept terse for the same reason the
|
|
11764
|
+
// de-identify messages are: it is emitted once per element, and element count
|
|
11765
|
+
// is attacker-chosen.
|
|
11766
|
+
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.",
|
|
11757
11767
|
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.",
|
|
11758
11768
|
DICOM_UN_PARSED_AS_SQ: "Element ({tag}) has VR=UN with undefined length; descended as Implicit VR LE sequence per CP-246.",
|
|
11759
11769
|
DICOM_EMPTY_ITEM_IN_SEQUENCE: "Sequence ({tag}) contains an empty item (length=0); tolerated.",
|
|
@@ -11788,7 +11798,6 @@ var WARNING_MESSAGES = Object.freeze({
|
|
|
11788
11798
|
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.",
|
|
11789
11799
|
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."
|
|
11790
11800
|
});
|
|
11791
|
-
var KNOWN_VRS = new Set(Object.keys(BE_VR_STRIDE));
|
|
11792
11801
|
var TAG_SHAPE = /^[0-9A-F]{8}$/u;
|
|
11793
11802
|
function renderTag(tag) {
|
|
11794
11803
|
return tag !== void 0 && TAG_SHAPE.test(tag) ? tag : WITHHELD;
|
|
@@ -11830,9 +11839,8 @@ function privateTagNoCreator(position, tag) {
|
|
|
11830
11839
|
function groupLengthInDataset(position, tag) {
|
|
11831
11840
|
return build(WARNING_CODES.DICOM_GROUP_LENGTH_IN_DATASET, position, { tag });
|
|
11832
11841
|
}
|
|
11833
|
-
function nonzeroReservedBytes(position,
|
|
11842
|
+
function nonzeroReservedBytes(position, first, second) {
|
|
11834
11843
|
return build(WARNING_CODES.DICOM_NONZERO_RESERVED_BYTES, position, {
|
|
11835
|
-
tag,
|
|
11836
11844
|
n: first,
|
|
11837
11845
|
n2: second
|
|
11838
11846
|
});
|
|
@@ -12036,12 +12044,12 @@ function readExplicitElementHeader(cursor, _ctx, emit) {
|
|
|
12036
12044
|
const vr = vrSlice.toString("ascii");
|
|
12037
12045
|
let length;
|
|
12038
12046
|
let headerLength;
|
|
12039
|
-
if (LONG_FORM_VRS.has(vr)) {
|
|
12047
|
+
if (LONG_FORM_VRS.has(vr) || !isRecognizedVr(vr)) {
|
|
12040
12048
|
const reserved0 = cursor.buffer[cursor.position];
|
|
12041
12049
|
const reserved1 = cursor.buffer[cursor.position + 1];
|
|
12042
12050
|
cursor.position += 2;
|
|
12043
12051
|
if ((reserved0 ?? 0) !== 0 || (reserved1 ?? 0) !== 0) {
|
|
12044
|
-
emit(nonzeroReservedBytes({ byteOffset: headerStart },
|
|
12052
|
+
emit(nonzeroReservedBytes({ byteOffset: headerStart }, reserved0 ?? 0, reserved1 ?? 0));
|
|
12045
12053
|
}
|
|
12046
12054
|
length = cursor.readUInt32();
|
|
12047
12055
|
headerLength = 12;
|
|
@@ -12179,7 +12187,7 @@ function readExplicitLeElement(cursor) {
|
|
|
12179
12187
|
const vr = cursor.slice(2).toString("ascii");
|
|
12180
12188
|
let length;
|
|
12181
12189
|
let headerLength;
|
|
12182
|
-
if (LONG_FORM_VRS.has(vr)) {
|
|
12190
|
+
if (LONG_FORM_VRS.has(vr) || !isRecognizedVr(vr)) {
|
|
12183
12191
|
cursor.slice(2);
|
|
12184
12192
|
length = cursor.readUInt32();
|
|
12185
12193
|
headerLength = 12;
|
|
@@ -13453,7 +13461,7 @@ function encodeDatasetElement(el, encoding) {
|
|
|
13453
13461
|
}
|
|
13454
13462
|
const littleEndian = encoding === "explicitLE";
|
|
13455
13463
|
const vrBuf = buffer.Buffer.from(el.vr, "ascii");
|
|
13456
|
-
if (LONG_FORM_VRS.has(el.vr)) {
|
|
13464
|
+
if (LONG_FORM_VRS.has(el.vr) || !isRecognizedVr(el.vr)) {
|
|
13457
13465
|
return buffer.Buffer.concat([
|
|
13458
13466
|
uint16(group, littleEndian),
|
|
13459
13467
|
uint16(element, littleEndian),
|
|
@@ -13504,7 +13512,7 @@ function encodeMetaElement(tag, vr, rawValue) {
|
|
|
13504
13512
|
const elementBuf = buffer.Buffer.alloc(2);
|
|
13505
13513
|
elementBuf.writeUInt16LE(element, 0);
|
|
13506
13514
|
const vrBuf = buffer.Buffer.from(vr, "ascii");
|
|
13507
|
-
if (LONG_FORM_VRS.has(vr)) {
|
|
13515
|
+
if (LONG_FORM_VRS.has(vr) || !isRecognizedVr(vr)) {
|
|
13508
13516
|
const lengthBuf2 = buffer.Buffer.alloc(4);
|
|
13509
13517
|
lengthBuf2.writeUInt32LE(value.length, 0);
|
|
13510
13518
|
return buffer.Buffer.concat([
|
|
@@ -14699,7 +14707,6 @@ function uidValueMultiplicity(rawBytes) {
|
|
|
14699
14707
|
}
|
|
14700
14708
|
|
|
14701
14709
|
// src/deident/embedded.ts
|
|
14702
|
-
var KNOWN_VRS2 = new Set(Object.keys(BE_VR_STRIDE));
|
|
14703
14710
|
var UNDEFINED_LENGTH4 = 4294967295;
|
|
14704
14711
|
var FFFE_GROUP = 65534;
|
|
14705
14712
|
var SCANNABLE_CARRIER_VRS = /* @__PURE__ */ new Set([
|
|
@@ -14760,7 +14767,7 @@ function decodeAt(value, off, encoding) {
|
|
|
14760
14767
|
return next2 > value.length ? void 0 : { next: next2, tag };
|
|
14761
14768
|
}
|
|
14762
14769
|
const vr = value.toString("latin1", off + 4, off + 6);
|
|
14763
|
-
if (!
|
|
14770
|
+
if (!KNOWN_VRS.has(vr)) return void 0;
|
|
14764
14771
|
if (LONG_FORM_VRS.has(vr)) {
|
|
14765
14772
|
if (off + 12 > value.length) return void 0;
|
|
14766
14773
|
if (value[off + 6] !== 0 || value[off + 7] !== 0) return void 0;
|
|
@@ -14897,7 +14904,6 @@ var BODY_ENCODING2 = {
|
|
|
14897
14904
|
};
|
|
14898
14905
|
var MAX_UNAUDITABLE_SEQUENCE_FINDINGS = 64;
|
|
14899
14906
|
var MAX_UNDEFINED_VR_FINDINGS = 64;
|
|
14900
|
-
var KNOWN_VRS3 = new Set(Object.keys(BE_VR_STRIDE));
|
|
14901
14907
|
function validateRetain(retain) {
|
|
14902
14908
|
const valid = new Set(DEIDENTIFY_OPTIONS);
|
|
14903
14909
|
const active = /* @__PURE__ */ new Set();
|
|
@@ -15018,7 +15024,7 @@ function actsOnTag(tag, ctx) {
|
|
|
15018
15024
|
return resolveAction(effectiveCode(action, ctx.active)) !== "K";
|
|
15019
15025
|
}
|
|
15020
15026
|
function hasUndefinedVr(el) {
|
|
15021
|
-
return !
|
|
15027
|
+
return !KNOWN_VRS.has(el.vr);
|
|
15022
15028
|
}
|
|
15023
15029
|
function keepOrEmpty(el, ctx, contextPath, out) {
|
|
15024
15030
|
if (hasUndefinedVr(el)) {
|
|
@@ -15082,6 +15088,13 @@ function creatorFor(tag, creators) {
|
|
|
15082
15088
|
const { group, element } = splitTag(tag);
|
|
15083
15089
|
return creators.get(`${String(group)}:${String(element >> 8 & 255)}`);
|
|
15084
15090
|
}
|
|
15091
|
+
var UNDEFINED_LENGTH5 = 4294967295;
|
|
15092
|
+
var EXPLICIT_SQ_HEADER_BYTES = 12;
|
|
15093
|
+
function itemStreamOverrunsSequence(el, encoding) {
|
|
15094
|
+
if (el.vr !== "SQ" || el.length === UNDEFINED_LENGTH5) return false;
|
|
15095
|
+
const headerBytes = encoding === "implicit" ? 0 : EXPLICIT_SQ_HEADER_BYTES;
|
|
15096
|
+
return el.rawBytes.length - headerBytes > el.length;
|
|
15097
|
+
}
|
|
15085
15098
|
function keepsPrivate(el, ctx, creators) {
|
|
15086
15099
|
if (!ctx.active.has("RetainSafePrivate") || ctx.profile === void 0) return false;
|
|
15087
15100
|
if (isPrivateCreatorElement(el.tag)) {
|
|
@@ -15091,7 +15104,7 @@ function keepsPrivate(el, ctx, creators) {
|
|
|
15091
15104
|
if (creator === void 0) return false;
|
|
15092
15105
|
return resolvePrivateTag(ctx.profile, el.tag, creator) !== void 0;
|
|
15093
15106
|
}
|
|
15094
|
-
function processElements(source, ctx, contextPath) {
|
|
15107
|
+
function processElements(source, ctx, contextPath, reservationsUsable) {
|
|
15095
15108
|
const out = {
|
|
15096
15109
|
elements: /* @__PURE__ */ new Map(),
|
|
15097
15110
|
attributes: [],
|
|
@@ -15104,7 +15117,8 @@ function processElements(source, ctx, contextPath) {
|
|
|
15104
15117
|
const creators = creatorsInScope(source);
|
|
15105
15118
|
for (const el of source) {
|
|
15106
15119
|
if (isPrivateTag(el.tag)) {
|
|
15107
|
-
if (keepsPrivate(el, ctx, creators))
|
|
15120
|
+
if (reservationsUsable && keepsPrivate(el, ctx, creators))
|
|
15121
|
+
keepOrEmpty(el, ctx, contextPath, out);
|
|
15108
15122
|
else out.removedPrivateTags.push(el.tag);
|
|
15109
15123
|
continue;
|
|
15110
15124
|
}
|
|
@@ -15112,7 +15126,8 @@ function processElements(source, ctx, contextPath) {
|
|
|
15112
15126
|
if (action === void 0) {
|
|
15113
15127
|
if (el.vr === "SQ") {
|
|
15114
15128
|
if (isUnauditableSequence(el)) emptyUnauditableSequence(el, ctx, contextPath, out);
|
|
15115
|
-
else
|
|
15129
|
+
else
|
|
15130
|
+
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out, reservationsUsable));
|
|
15116
15131
|
} else {
|
|
15117
15132
|
keepOrEmpty(el, ctx, contextPath, out);
|
|
15118
15133
|
}
|
|
@@ -15120,7 +15135,7 @@ function processElements(source, ctx, contextPath) {
|
|
|
15120
15135
|
}
|
|
15121
15136
|
const resolved = resolveAction(effectiveCode(action, ctx.active));
|
|
15122
15137
|
if (el.vr === "SQ") {
|
|
15123
|
-
applySequenceAction(el, resolved, action, ctx, contextPath, out);
|
|
15138
|
+
applySequenceAction(el, resolved, action, ctx, contextPath, out, reservationsUsable);
|
|
15124
15139
|
continue;
|
|
15125
15140
|
}
|
|
15126
15141
|
let applied;
|
|
@@ -15161,11 +15176,12 @@ function processElements(source, ctx, contextPath) {
|
|
|
15161
15176
|
}
|
|
15162
15177
|
return out;
|
|
15163
15178
|
}
|
|
15164
|
-
function descendSequence(el, ctx, contextPath, out) {
|
|
15179
|
+
function descendSequence(el, ctx, contextPath, out, reservationsUsable) {
|
|
15180
|
+
const childReservationsUsable = reservationsUsable && !itemStreamOverrunsSequence(el, ctx.encoding);
|
|
15165
15181
|
const newItems = [];
|
|
15166
15182
|
(el.items ?? []).forEach((item, index) => {
|
|
15167
15183
|
const childPath = [...contextPath, `${el.tag}[${String(index)}]`];
|
|
15168
|
-
const inner = processElements(item.elements(), ctx, childPath);
|
|
15184
|
+
const inner = processElements(item.elements(), ctx, childPath, childReservationsUsable);
|
|
15169
15185
|
out.attributes.push(...inner.attributes);
|
|
15170
15186
|
out.removedPrivateTags.push(...inner.removedPrivateTags);
|
|
15171
15187
|
out.embeddedAttributes.push(...inner.embeddedAttributes);
|
|
@@ -15176,7 +15192,7 @@ function descendSequence(el, ctx, contextPath, out) {
|
|
|
15176
15192
|
});
|
|
15177
15193
|
return rebuildSequence(el, newItems, ctx.encoding);
|
|
15178
15194
|
}
|
|
15179
|
-
function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
|
|
15195
|
+
function applySequenceAction(el, resolved, action, ctx, contextPath, out, reservationsUsable) {
|
|
15180
15196
|
let applied;
|
|
15181
15197
|
switch (resolved) {
|
|
15182
15198
|
case "X":
|
|
@@ -15192,7 +15208,7 @@ function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
|
|
|
15192
15208
|
emptyUnauditableSequence(el, ctx, contextPath, out);
|
|
15193
15209
|
applied = "emptied";
|
|
15194
15210
|
} else {
|
|
15195
|
-
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
|
|
15211
|
+
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out, reservationsUsable));
|
|
15196
15212
|
applied = "cleaned";
|
|
15197
15213
|
}
|
|
15198
15214
|
break;
|
|
@@ -15202,7 +15218,7 @@ function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
|
|
|
15202
15218
|
emptyUnauditableSequence(el, ctx, contextPath, out);
|
|
15203
15219
|
applied = "emptied";
|
|
15204
15220
|
} else {
|
|
15205
|
-
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
|
|
15221
|
+
out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out, reservationsUsable));
|
|
15206
15222
|
applied = "kept";
|
|
15207
15223
|
}
|
|
15208
15224
|
break;
|
|
@@ -15253,7 +15269,7 @@ function deidentify(ds, options = {}) {
|
|
|
15253
15269
|
encoding,
|
|
15254
15270
|
budget: { unauditableSequences: 0, undefinedVrElements: 0 }
|
|
15255
15271
|
};
|
|
15256
|
-
const processed = processElements(ds.elements(), ctx, []);
|
|
15272
|
+
const processed = processElements(ds.elements(), ctx, [], true);
|
|
15257
15273
|
const {
|
|
15258
15274
|
elements,
|
|
15259
15275
|
attributes,
|