dicom-synth 1.17.0 → 1.19.0
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/README.md +1 -1
- package/bin/dicom-synth-describe.mjs +12 -1
- package/dist/esm/collection/generate.js +977 -0
- package/dist/esm/collection/writer.js +243 -109
- package/dist/esm/describe/describe.js +281 -25
- package/dist/esm/index.js +535 -243
- package/dist/esm/nonStandardDicom/dicomdir.js +14 -15
- package/dist/esm/platform/bytes.js +39 -0
- package/dist/esm/platform/random.js +22 -0
- package/dist/esm/platform/sha256.js +131 -0
- package/dist/esm/schema/parametric.js +73 -4
- package/dist/esm/schema/validate.js +1 -0
- package/dist/esm/syntheticFixtures/generator.js +31 -14
- package/dist/esm/syntheticFixtures/streamWrite.js +156 -13
- package/dist/esm/syntheticFixtures/uid.js +148 -4
- package/dist/esm/syntheticFixtures/violations.js +18 -6
- package/dist/types/collection/generate.d.ts +43 -0
- package/dist/types/collection/writer.d.ts +0 -27
- package/dist/types/describe/describe.d.ts +12 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/nonStandardDicom/dicomdir.d.ts +1 -6
- package/dist/types/platform/bytes.d.ts +4 -0
- package/dist/types/platform/random.d.ts +2 -0
- package/dist/types/platform/sha256.d.ts +1 -0
- package/dist/types/schema/validate.d.ts +1 -0
- package/dist/types/syntheticFixtures/generator.d.ts +2 -2
- package/dist/types/syntheticFixtures/violations.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/describe/describe.ts
|
|
2
|
-
import { createHash
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
3
|
import {
|
|
4
4
|
closeSync,
|
|
5
5
|
openSync,
|
|
@@ -687,11 +687,148 @@ function validateDatasetSpec(raw) {
|
|
|
687
687
|
};
|
|
688
688
|
}
|
|
689
689
|
|
|
690
|
+
// src/platform/bytes.ts
|
|
691
|
+
function bytesToHex(bytes) {
|
|
692
|
+
let hex = "";
|
|
693
|
+
for (const byte of bytes) {
|
|
694
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
695
|
+
}
|
|
696
|
+
return hex;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// src/platform/sha256.ts
|
|
700
|
+
var K = new Uint32Array([
|
|
701
|
+
1116352408,
|
|
702
|
+
1899447441,
|
|
703
|
+
3049323471,
|
|
704
|
+
3921009573,
|
|
705
|
+
961987163,
|
|
706
|
+
1508970993,
|
|
707
|
+
2453635748,
|
|
708
|
+
2870763221,
|
|
709
|
+
3624381080,
|
|
710
|
+
310598401,
|
|
711
|
+
607225278,
|
|
712
|
+
1426881987,
|
|
713
|
+
1925078388,
|
|
714
|
+
2162078206,
|
|
715
|
+
2614888103,
|
|
716
|
+
3248222580,
|
|
717
|
+
3835390401,
|
|
718
|
+
4022224774,
|
|
719
|
+
264347078,
|
|
720
|
+
604807628,
|
|
721
|
+
770255983,
|
|
722
|
+
1249150122,
|
|
723
|
+
1555081692,
|
|
724
|
+
1996064986,
|
|
725
|
+
2554220882,
|
|
726
|
+
2821834349,
|
|
727
|
+
2952996808,
|
|
728
|
+
3210313671,
|
|
729
|
+
3336571891,
|
|
730
|
+
3584528711,
|
|
731
|
+
113926993,
|
|
732
|
+
338241895,
|
|
733
|
+
666307205,
|
|
734
|
+
773529912,
|
|
735
|
+
1294757372,
|
|
736
|
+
1396182291,
|
|
737
|
+
1695183700,
|
|
738
|
+
1986661051,
|
|
739
|
+
2177026350,
|
|
740
|
+
2456956037,
|
|
741
|
+
2730485921,
|
|
742
|
+
2820302411,
|
|
743
|
+
3259730800,
|
|
744
|
+
3345764771,
|
|
745
|
+
3516065817,
|
|
746
|
+
3600352804,
|
|
747
|
+
4094571909,
|
|
748
|
+
275423344,
|
|
749
|
+
430227734,
|
|
750
|
+
506948616,
|
|
751
|
+
659060556,
|
|
752
|
+
883997877,
|
|
753
|
+
958139571,
|
|
754
|
+
1322822218,
|
|
755
|
+
1537002063,
|
|
756
|
+
1747873779,
|
|
757
|
+
1955562222,
|
|
758
|
+
2024104815,
|
|
759
|
+
2227730452,
|
|
760
|
+
2361852424,
|
|
761
|
+
2428436474,
|
|
762
|
+
2756734187,
|
|
763
|
+
3204031479,
|
|
764
|
+
3329325298
|
|
765
|
+
]);
|
|
766
|
+
var rotr = (x, n) => x >>> n | x << 32 - n;
|
|
767
|
+
function sha256(input) {
|
|
768
|
+
const data2 = typeof input === "string" ? new TextEncoder().encode(input) : input;
|
|
769
|
+
const padded = new Uint8Array((data2.length + 8 >> 6) + 1 << 6);
|
|
770
|
+
padded.set(data2);
|
|
771
|
+
padded[data2.length] = 128;
|
|
772
|
+
new DataView(padded.buffer).setBigUint64(
|
|
773
|
+
padded.length - 8,
|
|
774
|
+
BigInt(data2.length * 8),
|
|
775
|
+
false
|
|
776
|
+
);
|
|
777
|
+
const h = new Uint32Array([
|
|
778
|
+
1779033703,
|
|
779
|
+
3144134277,
|
|
780
|
+
1013904242,
|
|
781
|
+
2773480762,
|
|
782
|
+
1359893119,
|
|
783
|
+
2600822924,
|
|
784
|
+
528734635,
|
|
785
|
+
1541459225
|
|
786
|
+
]);
|
|
787
|
+
const w = new Uint32Array(64);
|
|
788
|
+
const view = new DataView(padded.buffer);
|
|
789
|
+
for (let offset = 0; offset < padded.length; offset += 64) {
|
|
790
|
+
for (let t = 0; t < 16; t++) w[t] = view.getUint32(offset + t * 4, false);
|
|
791
|
+
for (let t = 16; t < 64; t++) {
|
|
792
|
+
const s0 = rotr(w[t - 15], 7) ^ rotr(w[t - 15], 18) ^ w[t - 15] >>> 3;
|
|
793
|
+
const s1 = rotr(w[t - 2], 17) ^ rotr(w[t - 2], 19) ^ w[t - 2] >>> 10;
|
|
794
|
+
w[t] = w[t - 16] + s0 + w[t - 7] + s1 >>> 0;
|
|
795
|
+
}
|
|
796
|
+
let [a, b, c, d, e, f, g, hh] = h;
|
|
797
|
+
for (let t = 0; t < 64; t++) {
|
|
798
|
+
const S1 = rotr(e, 6) ^ rotr(e, 11) ^ rotr(e, 25);
|
|
799
|
+
const ch = e & f ^ ~e & g;
|
|
800
|
+
const temp1 = hh + S1 + ch + K[t] + w[t] >>> 0;
|
|
801
|
+
const S0 = rotr(a, 2) ^ rotr(a, 13) ^ rotr(a, 22);
|
|
802
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
803
|
+
const temp2 = S0 + maj >>> 0;
|
|
804
|
+
hh = g;
|
|
805
|
+
g = f;
|
|
806
|
+
f = e;
|
|
807
|
+
e = d + temp1 >>> 0;
|
|
808
|
+
d = c;
|
|
809
|
+
c = b;
|
|
810
|
+
b = a;
|
|
811
|
+
a = temp1 + temp2 >>> 0;
|
|
812
|
+
}
|
|
813
|
+
h[0] = h[0] + a >>> 0;
|
|
814
|
+
h[1] = h[1] + b >>> 0;
|
|
815
|
+
h[2] = h[2] + c >>> 0;
|
|
816
|
+
h[3] = h[3] + d >>> 0;
|
|
817
|
+
h[4] = h[4] + e >>> 0;
|
|
818
|
+
h[5] = h[5] + f >>> 0;
|
|
819
|
+
h[6] = h[6] + g >>> 0;
|
|
820
|
+
h[7] = h[7] + hh >>> 0;
|
|
821
|
+
}
|
|
822
|
+
const out = new Uint8Array(32);
|
|
823
|
+
const outView = new DataView(out.buffer);
|
|
824
|
+
for (let i = 0; i < 8; i++) outView.setUint32(i * 4, h[i], false);
|
|
825
|
+
return out;
|
|
826
|
+
}
|
|
827
|
+
|
|
690
828
|
// src/syntheticFixtures/uid.ts
|
|
691
|
-
import { createHash, randomBytes } from "node:crypto";
|
|
692
829
|
function hashUid(salt, key) {
|
|
693
|
-
const digest =
|
|
694
|
-
const n = BigInt(`0x${digest.subarray(0, 16)
|
|
830
|
+
const digest = sha256(`${salt} ${key}`);
|
|
831
|
+
const n = BigInt(`0x${bytesToHex(digest.subarray(0, 16))}`);
|
|
695
832
|
return `2.25.${n.toString()}`;
|
|
696
833
|
}
|
|
697
834
|
|
|
@@ -718,13 +855,28 @@ function buildHexToKeyword() {
|
|
|
718
855
|
}
|
|
719
856
|
return map;
|
|
720
857
|
}
|
|
858
|
+
function buildKeepPlan(options) {
|
|
859
|
+
const { keywords, privateHex } = options.keepTags ? resolveKeepTags(options.keepTags) : { keywords: [], privateHex: /* @__PURE__ */ new Set() };
|
|
860
|
+
const allPrivate = options.keepPrivateTags === true;
|
|
861
|
+
return {
|
|
862
|
+
keywords,
|
|
863
|
+
privateHex,
|
|
864
|
+
allPrivate,
|
|
865
|
+
capturingPrivate: allPrivate || privateHex.size > 0
|
|
866
|
+
};
|
|
867
|
+
}
|
|
721
868
|
function resolveKeepTags(keepTags) {
|
|
722
869
|
const nameMap = dcmjsAny.data.DicomMetaDictionary.nameMap;
|
|
723
870
|
let hexToKeyword;
|
|
724
871
|
const kept = [];
|
|
872
|
+
const privateHex = /* @__PURE__ */ new Set();
|
|
725
873
|
for (const tag of keepTags) {
|
|
726
874
|
let keyword;
|
|
727
875
|
if (HEX_TAG_RE2.test(tag)) {
|
|
876
|
+
if (isPrivateHexTag(tag)) {
|
|
877
|
+
privateHex.add(tag.toUpperCase());
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
728
880
|
hexToKeyword ?? (hexToKeyword = buildHexToKeyword());
|
|
729
881
|
const mapped = hexToKeyword.get(tag.toUpperCase());
|
|
730
882
|
if (!mapped) {
|
|
@@ -751,7 +903,71 @@ function resolveKeepTags(keepTags) {
|
|
|
751
903
|
}
|
|
752
904
|
kept.push(keyword);
|
|
753
905
|
}
|
|
754
|
-
return kept;
|
|
906
|
+
return { keywords: kept, privateHex };
|
|
907
|
+
}
|
|
908
|
+
function isUnrepresentableValue(value) {
|
|
909
|
+
return value === null || value === void 0 || typeof value === "number" && !Number.isFinite(value) || value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
910
|
+
}
|
|
911
|
+
function toRawCapture(el, skipped) {
|
|
912
|
+
const vr = el.vr;
|
|
913
|
+
if (typeof vr !== "string") {
|
|
914
|
+
skipped.count++;
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
if (vr === "SQ") {
|
|
918
|
+
const items = [];
|
|
919
|
+
for (const item of el.Value ?? []) {
|
|
920
|
+
if (typeof item !== "object" || item === null) continue;
|
|
921
|
+
const converted = {};
|
|
922
|
+
for (const [tag, nested] of Object.entries(item)) {
|
|
923
|
+
if (!HEX_TAG_RE2.test(tag)) continue;
|
|
924
|
+
const capture = toRawCapture(nested, skipped);
|
|
925
|
+
if (capture) converted[tag.toUpperCase()] = capture;
|
|
926
|
+
}
|
|
927
|
+
items.push(converted);
|
|
928
|
+
}
|
|
929
|
+
return { vr, value: items };
|
|
930
|
+
}
|
|
931
|
+
const values = el.Value ?? [];
|
|
932
|
+
if (values.some(isUnrepresentableValue)) {
|
|
933
|
+
skipped.count++;
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
return { vr, value: values.length === 1 ? values[0] : values };
|
|
937
|
+
}
|
|
938
|
+
function capturePrivateTags(rawDict, keep, skipped) {
|
|
939
|
+
const captured = {};
|
|
940
|
+
let found = false;
|
|
941
|
+
for (const [tag, el] of Object.entries(rawDict)) {
|
|
942
|
+
if (!HEX_TAG_RE2.test(tag) || !isPrivateHexTag(tag)) continue;
|
|
943
|
+
if (!keep.allPrivate && !keep.privateHex.has(tag.toUpperCase())) continue;
|
|
944
|
+
const capture = toRawCapture(el, skipped);
|
|
945
|
+
if (capture) {
|
|
946
|
+
captured[tag.toUpperCase()] = capture;
|
|
947
|
+
found = true;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return found ? captured : void 0;
|
|
951
|
+
}
|
|
952
|
+
function hashRawCaptureUids(capture, salt, map) {
|
|
953
|
+
if (capture.vr === "UI") {
|
|
954
|
+
return isUidLeaf(capture.value) ? { vr: "UI", value: hashUidValue(capture.value, salt, map) } : capture;
|
|
955
|
+
}
|
|
956
|
+
if (capture.vr === "SQ") {
|
|
957
|
+
const items = capture.value;
|
|
958
|
+
return {
|
|
959
|
+
vr: "SQ",
|
|
960
|
+
value: items.map(
|
|
961
|
+
(item) => Object.fromEntries(
|
|
962
|
+
Object.entries(item).map(([tag, nested]) => [
|
|
963
|
+
tag,
|
|
964
|
+
hashRawCaptureUids(nested, salt, map)
|
|
965
|
+
])
|
|
966
|
+
)
|
|
967
|
+
)
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
return capture;
|
|
755
971
|
}
|
|
756
972
|
function* walkFiles(dir) {
|
|
757
973
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -769,13 +985,16 @@ function toArrayBuffer(buf) {
|
|
|
769
985
|
buf.byteOffset + buf.byteLength
|
|
770
986
|
);
|
|
771
987
|
}
|
|
772
|
-
function
|
|
988
|
+
function parseParts(ab) {
|
|
773
989
|
const parsed = dcmjsAny.data.DicomMessage.readFile(ab);
|
|
774
|
-
return
|
|
990
|
+
return {
|
|
991
|
+
record: dcmjsAny.data.DicomMetaDictionary.naturalizeDataset(parsed.dict),
|
|
992
|
+
rawDict: parsed.dict
|
|
993
|
+
};
|
|
775
994
|
}
|
|
776
995
|
function parseFull(path) {
|
|
777
996
|
try {
|
|
778
|
-
return
|
|
997
|
+
return parseParts(toArrayBuffer(readFileSync(path)));
|
|
779
998
|
} catch {
|
|
780
999
|
return null;
|
|
781
1000
|
}
|
|
@@ -833,7 +1052,7 @@ function readHeaderOnly(path) {
|
|
|
833
1052
|
EMPTY_OW_PIXEL_DATA
|
|
834
1053
|
]);
|
|
835
1054
|
try {
|
|
836
|
-
return
|
|
1055
|
+
return parseParts(toArrayBuffer(headerOnly));
|
|
837
1056
|
} catch {
|
|
838
1057
|
return null;
|
|
839
1058
|
}
|
|
@@ -950,7 +1169,7 @@ function hashUidTags(uids, salt, map) {
|
|
|
950
1169
|
return tags;
|
|
951
1170
|
}
|
|
952
1171
|
function deriveSalt(studyUids) {
|
|
953
|
-
const h =
|
|
1172
|
+
const h = createHash("sha256");
|
|
954
1173
|
for (const uid of [...studyUids].sort()) h.update(uid).update("\n");
|
|
955
1174
|
return h.digest("hex");
|
|
956
1175
|
}
|
|
@@ -959,6 +1178,20 @@ function mergeTags(a, b) {
|
|
|
959
1178
|
if (!b) return a;
|
|
960
1179
|
return { ...a, ...b };
|
|
961
1180
|
}
|
|
1181
|
+
function mergePrivateTags(rec, salt, uidMap) {
|
|
1182
|
+
if (!rec.privateTags) return;
|
|
1183
|
+
const hashed = Object.fromEntries(
|
|
1184
|
+
Object.entries(rec.privateTags).map(([tag, capture]) => [
|
|
1185
|
+
tag,
|
|
1186
|
+
salt === void 0 ? capture : hashRawCaptureUids(
|
|
1187
|
+
capture,
|
|
1188
|
+
salt,
|
|
1189
|
+
uidMap
|
|
1190
|
+
)
|
|
1191
|
+
])
|
|
1192
|
+
);
|
|
1193
|
+
rec.tags = mergeTags(rec.tags, hashed);
|
|
1194
|
+
}
|
|
962
1195
|
function scanFile(path) {
|
|
963
1196
|
let size;
|
|
964
1197
|
try {
|
|
@@ -968,7 +1201,7 @@ function scanFile(path) {
|
|
|
968
1201
|
}
|
|
969
1202
|
if (size > MAX_STREAM_BYTES) return null;
|
|
970
1203
|
const large = size > MAX_PIXEL_BYTES;
|
|
971
|
-
return { size, large,
|
|
1204
|
+
return { size, large, parsed: large ? readHeaderOnly(path) : parseFull(path) };
|
|
972
1205
|
}
|
|
973
1206
|
function presetModalityOf(record) {
|
|
974
1207
|
if (typeof record.Modality !== "string") return void 0;
|
|
@@ -983,6 +1216,13 @@ function hashRecordUids(rec, salt, uidMap, sweptKeepTags) {
|
|
|
983
1216
|
}
|
|
984
1217
|
rec.tags = mergeTags(rec.tags, hashUidTags(rec.uidOriginals, salt, uidMap));
|
|
985
1218
|
}
|
|
1219
|
+
function warnPrivateBinarySkipped(count) {
|
|
1220
|
+
if (count > 0) {
|
|
1221
|
+
console.warn(
|
|
1222
|
+
`describe: ${count} private element(s) with binary payloads were not captured \u2014 binary values cannot ride a JSON spec`
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
986
1226
|
function warnSweptKeepTags(sweptKeepTags) {
|
|
987
1227
|
for (const keyword of sweptKeepTags) {
|
|
988
1228
|
console.warn(
|
|
@@ -1128,14 +1368,16 @@ function emitTree(acc) {
|
|
|
1128
1368
|
}
|
|
1129
1369
|
return nodes;
|
|
1130
1370
|
}
|
|
1131
|
-
function describeTree(dir, options,
|
|
1371
|
+
function describeTree(dir, options, keep) {
|
|
1132
1372
|
const root = { dirs: /* @__PURE__ */ new Map(), files: [] };
|
|
1133
1373
|
const stats = {
|
|
1134
1374
|
dicomFiles: 0,
|
|
1135
1375
|
skipped: 0,
|
|
1136
1376
|
unknownModality: 0,
|
|
1137
|
-
nonDicomFiles: 0
|
|
1377
|
+
nonDicomFiles: 0,
|
|
1378
|
+
privateBinarySkipped: 0
|
|
1138
1379
|
};
|
|
1380
|
+
const privateSkipped = { count: 0 };
|
|
1139
1381
|
const studyUids = /* @__PURE__ */ new Set();
|
|
1140
1382
|
const dicomLeaves = [];
|
|
1141
1383
|
for (const path of [...walkFiles(dir)].sort()) {
|
|
@@ -1144,7 +1386,8 @@ function describeTree(dir, options, keepKeywords) {
|
|
|
1144
1386
|
stats.skipped++;
|
|
1145
1387
|
continue;
|
|
1146
1388
|
}
|
|
1147
|
-
const { size, large,
|
|
1389
|
+
const { size, large, parsed } = scanned;
|
|
1390
|
+
const record = parsed?.record;
|
|
1148
1391
|
const studyUid = record?.StudyInstanceUID;
|
|
1149
1392
|
const seriesUid = record?.SeriesInstanceUID;
|
|
1150
1393
|
const relDirs = relative(dir, path).split(sep).slice(0, -1);
|
|
@@ -1175,9 +1418,10 @@ function describeTree(dir, options, keepKeywords) {
|
|
|
1175
1418
|
modality,
|
|
1176
1419
|
tags: mergeTags(
|
|
1177
1420
|
instanceNumber,
|
|
1178
|
-
|
|
1421
|
+
keep.keywords.length ? extractTags(record, keep.keywords) : void 0
|
|
1179
1422
|
),
|
|
1180
|
-
uidOriginals: collectUidOriginals(record)
|
|
1423
|
+
uidOriginals: collectUidOriginals(record),
|
|
1424
|
+
privateTags: keep.capturingPrivate && parsed ? capturePrivateTags(parsed.rawDict, keep, privateSkipped) : void 0
|
|
1181
1425
|
},
|
|
1182
1426
|
studyUid,
|
|
1183
1427
|
seriesUid
|
|
@@ -1193,18 +1437,21 @@ function describeTree(dir, options, keepKeywords) {
|
|
|
1193
1437
|
const sweptKeepTags = /* @__PURE__ */ new Set();
|
|
1194
1438
|
for (const { rec, studyUid, seriesUid } of dicomLeaves) {
|
|
1195
1439
|
hashRecordUids(rec, salt, uidMap, sweptKeepTags);
|
|
1440
|
+
mergePrivateTags(rec, salt, uidMap);
|
|
1196
1441
|
rec.tags = mergeTags(rec.tags, {
|
|
1197
1442
|
StudyInstanceUID: hashUidVia(uidMap, salt, studyUid),
|
|
1198
1443
|
SeriesInstanceUID: hashUidVia(uidMap, salt, seriesUid)
|
|
1199
1444
|
});
|
|
1200
1445
|
}
|
|
1201
1446
|
warnSweptKeepTags(sweptKeepTags);
|
|
1447
|
+
stats.privateBinarySkipped = privateSkipped.count;
|
|
1448
|
+
warnPrivateBinarySkipped(privateSkipped.count);
|
|
1202
1449
|
const spec = { tree: emitTree(root), uidSalt: salt };
|
|
1203
1450
|
validateDatasetSpec(spec);
|
|
1204
1451
|
return { spec, stats };
|
|
1205
1452
|
}
|
|
1206
1453
|
function describeDirectory(dir, options = {}) {
|
|
1207
|
-
const
|
|
1454
|
+
const keep = buildKeepPlan(options);
|
|
1208
1455
|
const preserveUids = options.preserveUids ?? true;
|
|
1209
1456
|
if (options.captureTree) {
|
|
1210
1457
|
if (!preserveUids) {
|
|
@@ -1212,10 +1459,10 @@ function describeDirectory(dir, options = {}) {
|
|
|
1212
1459
|
"describe: captureTree requires preserveUids \u2014 the captured tree carries its grouping as hashed UID tags"
|
|
1213
1460
|
);
|
|
1214
1461
|
}
|
|
1215
|
-
return describeTree(dir, options,
|
|
1462
|
+
return describeTree(dir, options, keep);
|
|
1216
1463
|
}
|
|
1217
1464
|
const uidMap = /* @__PURE__ */ new Map();
|
|
1218
|
-
const useRecords =
|
|
1465
|
+
const useRecords = keep.keywords.length > 0 || preserveUids || keep.capturingPrivate;
|
|
1219
1466
|
const tolerance = options.sizeTolerance ?? 0;
|
|
1220
1467
|
if (!Number.isFinite(tolerance) || tolerance < 0) {
|
|
1221
1468
|
throw new Error(
|
|
@@ -1227,15 +1474,18 @@ function describeDirectory(dir, options = {}) {
|
|
|
1227
1474
|
dicomFiles: 0,
|
|
1228
1475
|
skipped: 0,
|
|
1229
1476
|
unknownModality: 0,
|
|
1230
|
-
nonDicomFiles: 0
|
|
1477
|
+
nonDicomFiles: 0,
|
|
1478
|
+
privateBinarySkipped: 0
|
|
1231
1479
|
};
|
|
1480
|
+
const privateSkipped = { count: 0 };
|
|
1232
1481
|
for (const path of [...walkFiles(dir)].sort()) {
|
|
1233
1482
|
const scanned = scanFile(path);
|
|
1234
1483
|
if (!scanned) {
|
|
1235
1484
|
stats.skipped++;
|
|
1236
1485
|
continue;
|
|
1237
1486
|
}
|
|
1238
|
-
const { size, large,
|
|
1487
|
+
const { size, large, parsed } = scanned;
|
|
1488
|
+
const record = parsed?.record;
|
|
1239
1489
|
const studyUid = record?.StudyInstanceUID;
|
|
1240
1490
|
const seriesUid = record?.SeriesInstanceUID;
|
|
1241
1491
|
if (!record || typeof studyUid !== "string" || typeof seriesUid !== "string") {
|
|
@@ -1246,7 +1496,7 @@ function describeDirectory(dir, options = {}) {
|
|
|
1246
1496
|
const preset = presetModalityOf(record);
|
|
1247
1497
|
if (preset === "unsupported") stats.unknownModality++;
|
|
1248
1498
|
const modality = preset === "unsupported" ? void 0 : preset;
|
|
1249
|
-
const keptTags =
|
|
1499
|
+
const keptTags = keep.keywords.length ? extractTags(record, keep.keywords) : void 0;
|
|
1250
1500
|
const uidOriginals = preserveUids ? collectUidOriginals(record) : void 0;
|
|
1251
1501
|
const bytes = large ? size : Math.min(bucketBytes(size, tolerance), MAX_PIXEL_BYTES);
|
|
1252
1502
|
const fileRecord = {
|
|
@@ -1254,7 +1504,8 @@ function describeDirectory(dir, options = {}) {
|
|
|
1254
1504
|
bytes,
|
|
1255
1505
|
modality,
|
|
1256
1506
|
tags: keptTags,
|
|
1257
|
-
uidOriginals
|
|
1507
|
+
uidOriginals,
|
|
1508
|
+
privateTags: keep.capturingPrivate && parsed ? capturePrivateTags(parsed.rawDict, keep, privateSkipped) : void 0
|
|
1258
1509
|
};
|
|
1259
1510
|
let study = studies.get(studyUid);
|
|
1260
1511
|
if (!study) {
|
|
@@ -1273,18 +1524,23 @@ function describeDirectory(dir, options = {}) {
|
|
|
1273
1524
|
}
|
|
1274
1525
|
}
|
|
1275
1526
|
const salt = preserveUids ? options.uidSalt ?? deriveSalt([...studies.keys()]) : void 0;
|
|
1276
|
-
if (salt !== void 0) {
|
|
1527
|
+
if (salt !== void 0 || keep.capturingPrivate) {
|
|
1277
1528
|
const sweptKeepTags = /* @__PURE__ */ new Set();
|
|
1278
1529
|
for (const study of studies.values()) {
|
|
1279
1530
|
for (const series of study.values()) {
|
|
1280
1531
|
if (!Array.isArray(series)) continue;
|
|
1281
1532
|
for (const rec of series) {
|
|
1282
|
-
|
|
1533
|
+
if (salt !== void 0) {
|
|
1534
|
+
hashRecordUids(rec, salt, uidMap, sweptKeepTags);
|
|
1535
|
+
}
|
|
1536
|
+
mergePrivateTags(rec, salt, uidMap);
|
|
1283
1537
|
}
|
|
1284
1538
|
}
|
|
1285
1539
|
}
|
|
1286
1540
|
warnSweptKeepTags(sweptKeepTags);
|
|
1287
1541
|
}
|
|
1542
|
+
stats.privateBinarySkipped = privateSkipped.count;
|
|
1543
|
+
warnPrivateBinarySkipped(privateSkipped.count);
|
|
1288
1544
|
const withGroupUid = (spec2, seriesUid) => salt === void 0 ? spec2 : {
|
|
1289
1545
|
...spec2,
|
|
1290
1546
|
tags: {
|