@cosyte/dicom 0.1.4 → 0.1.6
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 +16 -0
- package/dist/index.cjs +57 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +57 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 39ef9b3: `deidentify()` and `Dataset.directory` now strip a value's padding in linear time, so a long run of NULL or SPACE bytes in a file no longer stalls them for seconds.
|
|
8
|
+
|
|
9
|
+
Seven trims of file-controlled text used patterns that backtracked quadratically on a run of pad bytes ending in any other byte: the NULL trim of every UID `deidentify()` remaps, its reads of a Private Creator, a Block Identifying Information Status and the Burned In Annotation flag, and the DICOMDIR's reads of its `CS` values. A SOP Instance UID of 50,000 NULL bytes and one letter took `deidentify()` close to four seconds, and the time grew with the square of the run. Each trim now scans the value once from its end, or from each end, and returns the same result as before.
|
|
10
|
+
|
|
11
|
+
## 0.1.5
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- c827734: `serializeDicom()` and `deidentify()` now write a value over 65,534 bytes in a VR with a 16-bit length field as `UN`, as PS3.5 section 6.2.2 requires, instead of throwing a `RangeError`.
|
|
16
|
+
|
|
17
|
+
Under the Explicit VR transfer syntaxes, VRs such as `CS`, `DS`, `LO`, `PN`, `UI` and `US` carry a 16-bit length field, and the element encoder put every length through it unchecked. A value the reader tolerated at the odd length FFFFH, padded to 65,536 bytes on write, made `serializeDicom()` throw a `RangeError` it does not document, and so did a multi-valued UID that `deidentify()` remapped past 65,534 bytes; inside a Sequence, `deidentify()` threw it itself while rebuilding the Item. Such a value is now written in the long form with VR `UN`, Little Endian under Explicit VR Big Endian too, and reads back byte for byte. A Private Creator, for which the same section forbids `UN`, is not rewritten.
|
|
18
|
+
|
|
3
19
|
## 0.1.4
|
|
4
20
|
|
|
5
21
|
### Patch Changes
|
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.1.
|
|
14
|
+
var VERSION = "0.1.6";
|
|
15
15
|
|
|
16
16
|
// src/dictionary/index.ts
|
|
17
17
|
var dictionary_exports = {};
|
|
@@ -11141,8 +11141,24 @@ function directoryRecords(source) {
|
|
|
11141
11141
|
}
|
|
11142
11142
|
function csValues(el) {
|
|
11143
11143
|
if (el === void 0) return void 0;
|
|
11144
|
-
const text = el.rawBytes.toString("latin1")
|
|
11145
|
-
return Object.freeze(text === "" ? [] : text.split("\\").map(
|
|
11144
|
+
const text = stripTrailingPad(el.rawBytes.toString("latin1"));
|
|
11145
|
+
return Object.freeze(text === "" ? [] : text.split("\\").map(trimSpaces));
|
|
11146
|
+
}
|
|
11147
|
+
function stripTrailingPad(text) {
|
|
11148
|
+
let end = text.length;
|
|
11149
|
+
while (end > 0) {
|
|
11150
|
+
const code = text.charCodeAt(end - 1);
|
|
11151
|
+
if (code !== 0 && code !== 32) break;
|
|
11152
|
+
end--;
|
|
11153
|
+
}
|
|
11154
|
+
return text.slice(0, end);
|
|
11155
|
+
}
|
|
11156
|
+
function trimSpaces(value) {
|
|
11157
|
+
let start = 0;
|
|
11158
|
+
let end = value.length;
|
|
11159
|
+
while (start < end && value.charCodeAt(start) === 32) start++;
|
|
11160
|
+
while (end > start && value.charCodeAt(end - 1) === 32) end--;
|
|
11161
|
+
return value.slice(start, end);
|
|
11146
11162
|
}
|
|
11147
11163
|
function analyzeDirectory(source) {
|
|
11148
11164
|
if (!isDicomdir(source.fileMeta)) return void 0;
|
|
@@ -13801,7 +13817,7 @@ function stripPad(bytes, tag, vr, position, warnings) {
|
|
|
13801
13817
|
}
|
|
13802
13818
|
return bytes.subarray(0, end);
|
|
13803
13819
|
}
|
|
13804
|
-
function
|
|
13820
|
+
function trimSpaces2(value) {
|
|
13805
13821
|
let start = 0;
|
|
13806
13822
|
let end = value.length;
|
|
13807
13823
|
while (start < end && value.charCodeAt(start) === SPACE2) start++;
|
|
@@ -13860,7 +13876,7 @@ function decodeElementValue(element) {
|
|
|
13860
13876
|
}
|
|
13861
13877
|
if (vr === "LO" || vr === "SH" || vr === "UC") {
|
|
13862
13878
|
const text = decodeCharsetString(trimmed, tag, vr, charset, position, warnings);
|
|
13863
|
-
const values = text.split("\\").map(
|
|
13879
|
+
const values = text.split("\\").map(trimSpaces2);
|
|
13864
13880
|
return attachWarnings({ kind: "strings", values }, warnings);
|
|
13865
13881
|
}
|
|
13866
13882
|
if (vr === "LT" || vr === "ST" || vr === "UT") {
|
|
@@ -13873,7 +13889,7 @@ function decodeElementValue(element) {
|
|
|
13873
13889
|
}
|
|
13874
13890
|
if (vr === "AE" || vr === "CS" || vr === "AS" || vr === "UI") {
|
|
13875
13891
|
const text = decodeAsciiString(trimmed, tag, vr, position, warnings);
|
|
13876
|
-
const values = text.split("\\").map(
|
|
13892
|
+
const values = text.split("\\").map(trimSpaces2);
|
|
13877
13893
|
return attachWarnings({ kind: "strings", values }, warnings);
|
|
13878
13894
|
}
|
|
13879
13895
|
if (vr === "DS") {
|
|
@@ -14943,6 +14959,7 @@ function toDate(value, options) {
|
|
|
14943
14959
|
return Number.isNaN(instant.getTime()) ? void 0 : instant;
|
|
14944
14960
|
}
|
|
14945
14961
|
var UNDEFINED_LENGTH4 = 4294967295;
|
|
14962
|
+
var MAX_SHORT_FORM_LENGTH = 65534;
|
|
14946
14963
|
var NULL_PAD_VRS = /* @__PURE__ */ new Set(["UI", "OB", "OW", "OF", "OD", "OL", "OV", "UN"]);
|
|
14947
14964
|
function padValue(value, vr) {
|
|
14948
14965
|
if (value.length % 2 === 0) return buffer.Buffer.from(value);
|
|
@@ -14961,6 +14978,19 @@ function uint32(n, littleEndian) {
|
|
|
14961
14978
|
else buf.writeUInt32BE(n, 0);
|
|
14962
14979
|
return buf;
|
|
14963
14980
|
}
|
|
14981
|
+
function littleEndianValue(value, vr, littleEndian) {
|
|
14982
|
+
const stride = BE_VR_STRIDE[vr];
|
|
14983
|
+
if (littleEndian || stride === 0) return value;
|
|
14984
|
+
const swapped = buffer.Buffer.from(value);
|
|
14985
|
+
const whole = swapped.subarray(0, swapped.length - swapped.length % stride);
|
|
14986
|
+
if (stride === 2) whole.swap16();
|
|
14987
|
+
else if (stride === 4) whole.swap32();
|
|
14988
|
+
else whole.swap64();
|
|
14989
|
+
return swapped;
|
|
14990
|
+
}
|
|
14991
|
+
function isPrivateCreator(group, element) {
|
|
14992
|
+
return group % 2 === 1 && element >= 16 && element <= 255;
|
|
14993
|
+
}
|
|
14964
14994
|
function isFullSpanElement(el, encoding) {
|
|
14965
14995
|
if (el.length === UNDEFINED_LENGTH4) return true;
|
|
14966
14996
|
return el.vr === "SQ" && encoding !== "implicit";
|
|
@@ -14992,6 +15022,17 @@ function encodeDatasetElement(el, encoding, rawBytes = el.rawBytes) {
|
|
|
14992
15022
|
value
|
|
14993
15023
|
]);
|
|
14994
15024
|
}
|
|
15025
|
+
if (value.length > MAX_SHORT_FORM_LENGTH && !isPrivateCreator(group, element)) {
|
|
15026
|
+
return buffer.Buffer.concat([
|
|
15027
|
+
uint16(group, littleEndian),
|
|
15028
|
+
uint16(element, littleEndian),
|
|
15029
|
+
buffer.Buffer.from("UN", "ascii"),
|
|
15030
|
+
buffer.Buffer.from([0, 0]),
|
|
15031
|
+
// reserved (PS3.5 §7.1.2)
|
|
15032
|
+
uint32(value.length, littleEndian),
|
|
15033
|
+
littleEndianValue(value, el.vr, littleEndian)
|
|
15034
|
+
]);
|
|
15035
|
+
}
|
|
14995
15036
|
return buffer.Buffer.concat([
|
|
14996
15037
|
uint16(group, littleEndian),
|
|
14997
15038
|
uint16(element, littleEndian),
|
|
@@ -16741,14 +16782,19 @@ function dummyBytes(vr) {
|
|
|
16741
16782
|
const buf = buffer.Buffer.from(text, "latin1");
|
|
16742
16783
|
return buf.length % 2 === 0 ? buf : buffer.Buffer.concat([buf, buffer.Buffer.from([32])]);
|
|
16743
16784
|
}
|
|
16785
|
+
function stripTrailingNulls(text) {
|
|
16786
|
+
let end = text.length;
|
|
16787
|
+
while (end > 0 && text.charCodeAt(end - 1) === 0) end--;
|
|
16788
|
+
return text.slice(0, end);
|
|
16789
|
+
}
|
|
16744
16790
|
function remapUidBytes(rawBytes, remap) {
|
|
16745
|
-
const raw = rawBytes.toString("latin1")
|
|
16791
|
+
const raw = stripTrailingNulls(rawBytes.toString("latin1"));
|
|
16746
16792
|
const remapped = raw.split("\\").map((uid2) => remap(uid2.trim())).join("\\");
|
|
16747
16793
|
const buf = buffer.Buffer.from(remapped, "latin1");
|
|
16748
16794
|
return buf.length % 2 === 0 ? buf : buffer.Buffer.concat([buf, buffer.Buffer.from([0])]);
|
|
16749
16795
|
}
|
|
16750
16796
|
function uidValueMultiplicity(rawBytes) {
|
|
16751
|
-
const raw = rawBytes.toString("latin1")
|
|
16797
|
+
const raw = stripTrailingNulls(rawBytes.toString("latin1"));
|
|
16752
16798
|
if (raw.length === 0) return 0;
|
|
16753
16799
|
return raw.split("\\").length;
|
|
16754
16800
|
}
|
|
@@ -17150,7 +17196,7 @@ function insertedSequence(tag, items, encoding) {
|
|
|
17150
17196
|
});
|
|
17151
17197
|
}
|
|
17152
17198
|
function decodeCreator(el) {
|
|
17153
|
-
return el.rawBytes.toString("latin1").
|
|
17199
|
+
return trimTrailingPad(el.rawBytes).toString("latin1").trim();
|
|
17154
17200
|
}
|
|
17155
17201
|
function isPrivateCreatorElement(tag) {
|
|
17156
17202
|
const { element } = splitTag(tag);
|
|
@@ -17278,7 +17324,7 @@ function declarationKey(group, creator) {
|
|
|
17278
17324
|
return `${String(group)}:${creator}`;
|
|
17279
17325
|
}
|
|
17280
17326
|
function decodeEnumerated(el) {
|
|
17281
|
-
return el.rawBytes.toString("latin1").
|
|
17327
|
+
return trimTrailingPad(el.rawBytes).toString("latin1").trim();
|
|
17282
17328
|
}
|
|
17283
17329
|
function readUnsignedShorts(el) {
|
|
17284
17330
|
const values = [];
|
|
@@ -17715,7 +17761,7 @@ function hasUncleanedBurnedIn(ds) {
|
|
|
17715
17761
|
if (!ds.has(TAG_PIXEL_DATA)) return false;
|
|
17716
17762
|
const flag = ds.get(TAG_BURNED_IN_ANNOTATION);
|
|
17717
17763
|
if (flag === void 0) return true;
|
|
17718
|
-
return flag.rawBytes.toString("latin1").
|
|
17764
|
+
return trimTrailingPad(flag.rawBytes).toString("latin1").trim().toUpperCase() !== "NO";
|
|
17719
17765
|
}
|
|
17720
17766
|
function deidentify(ds, options = {}) {
|
|
17721
17767
|
if (isRefusedTransferSyntax(ds.fileMeta?.transferSyntaxUID)) {
|