dicom-synth 1.16.0 → 1.18.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/bin/dicom-synth-describe.mjs +12 -1
- package/dist/esm/collection/writer.js +68 -32
- package/dist/esm/describe/describe.js +247 -22
- package/dist/esm/index.js +312 -54
- package/dist/esm/schema/parametric.js +108 -2
- package/dist/esm/schema/validate.js +110 -2
- package/dist/esm/syntheticFixtures/generator.js +50 -21
- package/dist/esm/syntheticFixtures/streamWrite.js +47 -18
- package/dist/esm/syntheticFixtures/violations.js +9 -3
- package/dist/types/describe/describe.d.ts +12 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/schema/types.d.ts +4 -0
- package/dist/types/schema/validate.d.ts +5 -0
- package/dist/types/syntheticFixtures/generator.d.ts +12 -4
- package/dist/types/syntheticFixtures/violations.d.ts +2 -1
- package/package.json +3 -1
|
@@ -92,6 +92,92 @@ var VALID_VIOLATIONS = {
|
|
|
92
92
|
};
|
|
93
93
|
var HEX_TAG_RE = /^[0-9a-fA-F]{8}$/;
|
|
94
94
|
var KEYWORD_RE = /^[A-Z][A-Za-z0-9]*$/;
|
|
95
|
+
var VALID_VRS = /* @__PURE__ */ new Set([
|
|
96
|
+
"AE",
|
|
97
|
+
"AS",
|
|
98
|
+
"AT",
|
|
99
|
+
"CS",
|
|
100
|
+
"DA",
|
|
101
|
+
"DS",
|
|
102
|
+
"DT",
|
|
103
|
+
"FL",
|
|
104
|
+
"FD",
|
|
105
|
+
"IS",
|
|
106
|
+
"LO",
|
|
107
|
+
"LT",
|
|
108
|
+
"OB",
|
|
109
|
+
"OD",
|
|
110
|
+
"OF",
|
|
111
|
+
"OL",
|
|
112
|
+
"OV",
|
|
113
|
+
"OW",
|
|
114
|
+
"PN",
|
|
115
|
+
"SH",
|
|
116
|
+
"SL",
|
|
117
|
+
"SQ",
|
|
118
|
+
"SS",
|
|
119
|
+
"ST",
|
|
120
|
+
"SV",
|
|
121
|
+
"TM",
|
|
122
|
+
"UC",
|
|
123
|
+
"UI",
|
|
124
|
+
"UL",
|
|
125
|
+
"UN",
|
|
126
|
+
"UR",
|
|
127
|
+
"US",
|
|
128
|
+
"UT",
|
|
129
|
+
"UV"
|
|
130
|
+
]);
|
|
131
|
+
function isPrivateHexTag(key) {
|
|
132
|
+
return HEX_TAG_RE.test(key) && Number.parseInt(key.slice(0, 4), 16) % 2 === 1;
|
|
133
|
+
}
|
|
134
|
+
function isRawTagValue(value) {
|
|
135
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && typeof value.vr === "string";
|
|
136
|
+
}
|
|
137
|
+
function validateRawTagValue(raw, path) {
|
|
138
|
+
for (const key of Object.keys(raw)) {
|
|
139
|
+
if (key !== "vr" && key !== "value") {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`${path}: raw tag form accepts only "vr" and "value"; got "${key}"`
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const vr = raw.vr;
|
|
146
|
+
if (!VALID_VRS.has(vr)) {
|
|
147
|
+
throw new Error(`${path}.vr: "${vr}" is not a standard DICOM VR`);
|
|
148
|
+
}
|
|
149
|
+
if (!("value" in raw)) {
|
|
150
|
+
throw new Error(`${path}.value: is required in the raw tag form`);
|
|
151
|
+
}
|
|
152
|
+
if (vr === "SQ") {
|
|
153
|
+
if (!Array.isArray(raw.value)) {
|
|
154
|
+
throw new Error(`${path}.value: an SQ raw tag requires an array of items`);
|
|
155
|
+
}
|
|
156
|
+
raw.value.forEach((item, i) => {
|
|
157
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`${path}.value[${i}]: must be an object of hex-tag entries`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
for (const [tag, nested] of Object.entries(item)) {
|
|
163
|
+
if (!HEX_TAG_RE.test(tag)) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`${path}.value[${i}]: invalid item tag "${tag}" \u2014 SQ items use 8-hex-char tags`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (!isRawTagValue(nested)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`${path}.value[${i}].${tag}: SQ item entries must use the raw { vr, value } form`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
validateRawTagValue(
|
|
174
|
+
nested,
|
|
175
|
+
`${path}.value[${i}].${tag}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
95
181
|
function validatePositiveInt(value, path) {
|
|
96
182
|
if (typeof value !== "number" || !Number.isInteger(value) || value < 1) {
|
|
97
183
|
throw new Error(
|
|
@@ -289,13 +375,33 @@ function validateEntry(entry, path) {
|
|
|
289
375
|
return e;
|
|
290
376
|
}
|
|
291
377
|
var MODALITY_TAG = "00080060";
|
|
292
|
-
function
|
|
378
|
+
function validateModalityGuard(key, candidate, path) {
|
|
293
379
|
const isModalityKey = key === "Modality" || key.toUpperCase() === MODALITY_TAG;
|
|
294
|
-
if (isModalityKey && typeof
|
|
380
|
+
if (isModalityKey && typeof candidate === "string" && Object.hasOwn(VALID_MODALITIES, candidate)) {
|
|
295
381
|
throw new Error(
|
|
296
382
|
`${path}.${key}: set a preset modality (${Object.keys(VALID_MODALITIES).join("/")}) via the "modality" field, not tags \u2014 it is coupled to SOPClassUID and type-1 attributes`
|
|
297
383
|
);
|
|
298
384
|
}
|
|
385
|
+
}
|
|
386
|
+
function validateTagValue(key, value, path) {
|
|
387
|
+
if (isRawTagValue(value)) {
|
|
388
|
+
if (!HEX_TAG_RE.test(key)) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
`${path}.${key}: the raw { vr, value } form requires an 8-hex-char tag key \u2014 keyword tags take plain values`
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
validateRawTagValue(value, `${path}.${key}`);
|
|
394
|
+
const raw = value.value;
|
|
395
|
+
const scalar = Array.isArray(raw) && raw.length === 1 ? raw[0] : raw;
|
|
396
|
+
validateModalityGuard(key, scalar, path);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (isPrivateHexTag(key)) {
|
|
400
|
+
throw new Error(
|
|
401
|
+
`${path}.${key}: private tags have no dictionary VR \u2014 use the raw form, e.g. { "vr": "LO", "value": \u2026 }`
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
validateModalityGuard(key, value, path);
|
|
299
405
|
if (typeof value === "string") {
|
|
300
406
|
for (const name of findTemplateTokens(value)) {
|
|
301
407
|
if (!TEMPLATE_VOCAB.includes(name)) {
|
|
@@ -612,13 +718,28 @@ function buildHexToKeyword() {
|
|
|
612
718
|
}
|
|
613
719
|
return map;
|
|
614
720
|
}
|
|
721
|
+
function buildKeepPlan(options) {
|
|
722
|
+
const { keywords, privateHex } = options.keepTags ? resolveKeepTags(options.keepTags) : { keywords: [], privateHex: /* @__PURE__ */ new Set() };
|
|
723
|
+
const allPrivate = options.keepPrivateTags === true;
|
|
724
|
+
return {
|
|
725
|
+
keywords,
|
|
726
|
+
privateHex,
|
|
727
|
+
allPrivate,
|
|
728
|
+
capturingPrivate: allPrivate || privateHex.size > 0
|
|
729
|
+
};
|
|
730
|
+
}
|
|
615
731
|
function resolveKeepTags(keepTags) {
|
|
616
732
|
const nameMap = dcmjsAny.data.DicomMetaDictionary.nameMap;
|
|
617
733
|
let hexToKeyword;
|
|
618
734
|
const kept = [];
|
|
735
|
+
const privateHex = /* @__PURE__ */ new Set();
|
|
619
736
|
for (const tag of keepTags) {
|
|
620
737
|
let keyword;
|
|
621
738
|
if (HEX_TAG_RE2.test(tag)) {
|
|
739
|
+
if (isPrivateHexTag(tag)) {
|
|
740
|
+
privateHex.add(tag.toUpperCase());
|
|
741
|
+
continue;
|
|
742
|
+
}
|
|
622
743
|
hexToKeyword ?? (hexToKeyword = buildHexToKeyword());
|
|
623
744
|
const mapped = hexToKeyword.get(tag.toUpperCase());
|
|
624
745
|
if (!mapped) {
|
|
@@ -645,7 +766,71 @@ function resolveKeepTags(keepTags) {
|
|
|
645
766
|
}
|
|
646
767
|
kept.push(keyword);
|
|
647
768
|
}
|
|
648
|
-
return kept;
|
|
769
|
+
return { keywords: kept, privateHex };
|
|
770
|
+
}
|
|
771
|
+
function isUnrepresentableValue(value) {
|
|
772
|
+
return value === null || value === void 0 || typeof value === "number" && !Number.isFinite(value) || value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
773
|
+
}
|
|
774
|
+
function toRawCapture(el, skipped) {
|
|
775
|
+
const vr = el.vr;
|
|
776
|
+
if (typeof vr !== "string") {
|
|
777
|
+
skipped.count++;
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
if (vr === "SQ") {
|
|
781
|
+
const items = [];
|
|
782
|
+
for (const item of el.Value ?? []) {
|
|
783
|
+
if (typeof item !== "object" || item === null) continue;
|
|
784
|
+
const converted = {};
|
|
785
|
+
for (const [tag, nested] of Object.entries(item)) {
|
|
786
|
+
if (!HEX_TAG_RE2.test(tag)) continue;
|
|
787
|
+
const capture = toRawCapture(nested, skipped);
|
|
788
|
+
if (capture) converted[tag.toUpperCase()] = capture;
|
|
789
|
+
}
|
|
790
|
+
items.push(converted);
|
|
791
|
+
}
|
|
792
|
+
return { vr, value: items };
|
|
793
|
+
}
|
|
794
|
+
const values = el.Value ?? [];
|
|
795
|
+
if (values.some(isUnrepresentableValue)) {
|
|
796
|
+
skipped.count++;
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
return { vr, value: values.length === 1 ? values[0] : values };
|
|
800
|
+
}
|
|
801
|
+
function capturePrivateTags(rawDict, keep, skipped) {
|
|
802
|
+
const captured = {};
|
|
803
|
+
let found = false;
|
|
804
|
+
for (const [tag, el] of Object.entries(rawDict)) {
|
|
805
|
+
if (!HEX_TAG_RE2.test(tag) || !isPrivateHexTag(tag)) continue;
|
|
806
|
+
if (!keep.allPrivate && !keep.privateHex.has(tag.toUpperCase())) continue;
|
|
807
|
+
const capture = toRawCapture(el, skipped);
|
|
808
|
+
if (capture) {
|
|
809
|
+
captured[tag.toUpperCase()] = capture;
|
|
810
|
+
found = true;
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return found ? captured : void 0;
|
|
814
|
+
}
|
|
815
|
+
function hashRawCaptureUids(capture, salt, map) {
|
|
816
|
+
if (capture.vr === "UI") {
|
|
817
|
+
return isUidLeaf(capture.value) ? { vr: "UI", value: hashUidValue(capture.value, salt, map) } : capture;
|
|
818
|
+
}
|
|
819
|
+
if (capture.vr === "SQ") {
|
|
820
|
+
const items = capture.value;
|
|
821
|
+
return {
|
|
822
|
+
vr: "SQ",
|
|
823
|
+
value: items.map(
|
|
824
|
+
(item) => Object.fromEntries(
|
|
825
|
+
Object.entries(item).map(([tag, nested]) => [
|
|
826
|
+
tag,
|
|
827
|
+
hashRawCaptureUids(nested, salt, map)
|
|
828
|
+
])
|
|
829
|
+
)
|
|
830
|
+
)
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
return capture;
|
|
649
834
|
}
|
|
650
835
|
function* walkFiles(dir) {
|
|
651
836
|
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
@@ -663,13 +848,16 @@ function toArrayBuffer(buf) {
|
|
|
663
848
|
buf.byteOffset + buf.byteLength
|
|
664
849
|
);
|
|
665
850
|
}
|
|
666
|
-
function
|
|
851
|
+
function parseParts(ab) {
|
|
667
852
|
const parsed = dcmjsAny.data.DicomMessage.readFile(ab);
|
|
668
|
-
return
|
|
853
|
+
return {
|
|
854
|
+
record: dcmjsAny.data.DicomMetaDictionary.naturalizeDataset(parsed.dict),
|
|
855
|
+
rawDict: parsed.dict
|
|
856
|
+
};
|
|
669
857
|
}
|
|
670
858
|
function parseFull(path) {
|
|
671
859
|
try {
|
|
672
|
-
return
|
|
860
|
+
return parseParts(toArrayBuffer(readFileSync(path)));
|
|
673
861
|
} catch {
|
|
674
862
|
return null;
|
|
675
863
|
}
|
|
@@ -727,7 +915,7 @@ function readHeaderOnly(path) {
|
|
|
727
915
|
EMPTY_OW_PIXEL_DATA
|
|
728
916
|
]);
|
|
729
917
|
try {
|
|
730
|
-
return
|
|
918
|
+
return parseParts(toArrayBuffer(headerOnly));
|
|
731
919
|
} catch {
|
|
732
920
|
return null;
|
|
733
921
|
}
|
|
@@ -853,6 +1041,20 @@ function mergeTags(a, b) {
|
|
|
853
1041
|
if (!b) return a;
|
|
854
1042
|
return { ...a, ...b };
|
|
855
1043
|
}
|
|
1044
|
+
function mergePrivateTags(rec, salt, uidMap) {
|
|
1045
|
+
if (!rec.privateTags) return;
|
|
1046
|
+
const hashed = Object.fromEntries(
|
|
1047
|
+
Object.entries(rec.privateTags).map(([tag, capture]) => [
|
|
1048
|
+
tag,
|
|
1049
|
+
salt === void 0 ? capture : hashRawCaptureUids(
|
|
1050
|
+
capture,
|
|
1051
|
+
salt,
|
|
1052
|
+
uidMap
|
|
1053
|
+
)
|
|
1054
|
+
])
|
|
1055
|
+
);
|
|
1056
|
+
rec.tags = mergeTags(rec.tags, hashed);
|
|
1057
|
+
}
|
|
856
1058
|
function scanFile(path) {
|
|
857
1059
|
let size;
|
|
858
1060
|
try {
|
|
@@ -862,7 +1064,7 @@ function scanFile(path) {
|
|
|
862
1064
|
}
|
|
863
1065
|
if (size > MAX_STREAM_BYTES) return null;
|
|
864
1066
|
const large = size > MAX_PIXEL_BYTES;
|
|
865
|
-
return { size, large,
|
|
1067
|
+
return { size, large, parsed: large ? readHeaderOnly(path) : parseFull(path) };
|
|
866
1068
|
}
|
|
867
1069
|
function presetModalityOf(record) {
|
|
868
1070
|
if (typeof record.Modality !== "string") return void 0;
|
|
@@ -877,6 +1079,13 @@ function hashRecordUids(rec, salt, uidMap, sweptKeepTags) {
|
|
|
877
1079
|
}
|
|
878
1080
|
rec.tags = mergeTags(rec.tags, hashUidTags(rec.uidOriginals, salt, uidMap));
|
|
879
1081
|
}
|
|
1082
|
+
function warnPrivateBinarySkipped(count) {
|
|
1083
|
+
if (count > 0) {
|
|
1084
|
+
console.warn(
|
|
1085
|
+
`describe: ${count} private element(s) with binary payloads were not captured \u2014 binary values cannot ride a JSON spec`
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
880
1089
|
function warnSweptKeepTags(sweptKeepTags) {
|
|
881
1090
|
for (const keyword of sweptKeepTags) {
|
|
882
1091
|
console.warn(
|
|
@@ -1022,14 +1231,16 @@ function emitTree(acc) {
|
|
|
1022
1231
|
}
|
|
1023
1232
|
return nodes;
|
|
1024
1233
|
}
|
|
1025
|
-
function describeTree(dir, options,
|
|
1234
|
+
function describeTree(dir, options, keep) {
|
|
1026
1235
|
const root = { dirs: /* @__PURE__ */ new Map(), files: [] };
|
|
1027
1236
|
const stats = {
|
|
1028
1237
|
dicomFiles: 0,
|
|
1029
1238
|
skipped: 0,
|
|
1030
1239
|
unknownModality: 0,
|
|
1031
|
-
nonDicomFiles: 0
|
|
1240
|
+
nonDicomFiles: 0,
|
|
1241
|
+
privateBinarySkipped: 0
|
|
1032
1242
|
};
|
|
1243
|
+
const privateSkipped = { count: 0 };
|
|
1033
1244
|
const studyUids = /* @__PURE__ */ new Set();
|
|
1034
1245
|
const dicomLeaves = [];
|
|
1035
1246
|
for (const path of [...walkFiles(dir)].sort()) {
|
|
@@ -1038,7 +1249,8 @@ function describeTree(dir, options, keepKeywords) {
|
|
|
1038
1249
|
stats.skipped++;
|
|
1039
1250
|
continue;
|
|
1040
1251
|
}
|
|
1041
|
-
const { size, large,
|
|
1252
|
+
const { size, large, parsed } = scanned;
|
|
1253
|
+
const record = parsed?.record;
|
|
1042
1254
|
const studyUid = record?.StudyInstanceUID;
|
|
1043
1255
|
const seriesUid = record?.SeriesInstanceUID;
|
|
1044
1256
|
const relDirs = relative(dir, path).split(sep).slice(0, -1);
|
|
@@ -1069,9 +1281,10 @@ function describeTree(dir, options, keepKeywords) {
|
|
|
1069
1281
|
modality,
|
|
1070
1282
|
tags: mergeTags(
|
|
1071
1283
|
instanceNumber,
|
|
1072
|
-
|
|
1284
|
+
keep.keywords.length ? extractTags(record, keep.keywords) : void 0
|
|
1073
1285
|
),
|
|
1074
|
-
uidOriginals: collectUidOriginals(record)
|
|
1286
|
+
uidOriginals: collectUidOriginals(record),
|
|
1287
|
+
privateTags: keep.capturingPrivate && parsed ? capturePrivateTags(parsed.rawDict, keep, privateSkipped) : void 0
|
|
1075
1288
|
},
|
|
1076
1289
|
studyUid,
|
|
1077
1290
|
seriesUid
|
|
@@ -1087,18 +1300,21 @@ function describeTree(dir, options, keepKeywords) {
|
|
|
1087
1300
|
const sweptKeepTags = /* @__PURE__ */ new Set();
|
|
1088
1301
|
for (const { rec, studyUid, seriesUid } of dicomLeaves) {
|
|
1089
1302
|
hashRecordUids(rec, salt, uidMap, sweptKeepTags);
|
|
1303
|
+
mergePrivateTags(rec, salt, uidMap);
|
|
1090
1304
|
rec.tags = mergeTags(rec.tags, {
|
|
1091
1305
|
StudyInstanceUID: hashUidVia(uidMap, salt, studyUid),
|
|
1092
1306
|
SeriesInstanceUID: hashUidVia(uidMap, salt, seriesUid)
|
|
1093
1307
|
});
|
|
1094
1308
|
}
|
|
1095
1309
|
warnSweptKeepTags(sweptKeepTags);
|
|
1310
|
+
stats.privateBinarySkipped = privateSkipped.count;
|
|
1311
|
+
warnPrivateBinarySkipped(privateSkipped.count);
|
|
1096
1312
|
const spec = { tree: emitTree(root), uidSalt: salt };
|
|
1097
1313
|
validateDatasetSpec(spec);
|
|
1098
1314
|
return { spec, stats };
|
|
1099
1315
|
}
|
|
1100
1316
|
function describeDirectory(dir, options = {}) {
|
|
1101
|
-
const
|
|
1317
|
+
const keep = buildKeepPlan(options);
|
|
1102
1318
|
const preserveUids = options.preserveUids ?? true;
|
|
1103
1319
|
if (options.captureTree) {
|
|
1104
1320
|
if (!preserveUids) {
|
|
@@ -1106,10 +1322,10 @@ function describeDirectory(dir, options = {}) {
|
|
|
1106
1322
|
"describe: captureTree requires preserveUids \u2014 the captured tree carries its grouping as hashed UID tags"
|
|
1107
1323
|
);
|
|
1108
1324
|
}
|
|
1109
|
-
return describeTree(dir, options,
|
|
1325
|
+
return describeTree(dir, options, keep);
|
|
1110
1326
|
}
|
|
1111
1327
|
const uidMap = /* @__PURE__ */ new Map();
|
|
1112
|
-
const useRecords =
|
|
1328
|
+
const useRecords = keep.keywords.length > 0 || preserveUids || keep.capturingPrivate;
|
|
1113
1329
|
const tolerance = options.sizeTolerance ?? 0;
|
|
1114
1330
|
if (!Number.isFinite(tolerance) || tolerance < 0) {
|
|
1115
1331
|
throw new Error(
|
|
@@ -1121,15 +1337,18 @@ function describeDirectory(dir, options = {}) {
|
|
|
1121
1337
|
dicomFiles: 0,
|
|
1122
1338
|
skipped: 0,
|
|
1123
1339
|
unknownModality: 0,
|
|
1124
|
-
nonDicomFiles: 0
|
|
1340
|
+
nonDicomFiles: 0,
|
|
1341
|
+
privateBinarySkipped: 0
|
|
1125
1342
|
};
|
|
1343
|
+
const privateSkipped = { count: 0 };
|
|
1126
1344
|
for (const path of [...walkFiles(dir)].sort()) {
|
|
1127
1345
|
const scanned = scanFile(path);
|
|
1128
1346
|
if (!scanned) {
|
|
1129
1347
|
stats.skipped++;
|
|
1130
1348
|
continue;
|
|
1131
1349
|
}
|
|
1132
|
-
const { size, large,
|
|
1350
|
+
const { size, large, parsed } = scanned;
|
|
1351
|
+
const record = parsed?.record;
|
|
1133
1352
|
const studyUid = record?.StudyInstanceUID;
|
|
1134
1353
|
const seriesUid = record?.SeriesInstanceUID;
|
|
1135
1354
|
if (!record || typeof studyUid !== "string" || typeof seriesUid !== "string") {
|
|
@@ -1140,7 +1359,7 @@ function describeDirectory(dir, options = {}) {
|
|
|
1140
1359
|
const preset = presetModalityOf(record);
|
|
1141
1360
|
if (preset === "unsupported") stats.unknownModality++;
|
|
1142
1361
|
const modality = preset === "unsupported" ? void 0 : preset;
|
|
1143
|
-
const keptTags =
|
|
1362
|
+
const keptTags = keep.keywords.length ? extractTags(record, keep.keywords) : void 0;
|
|
1144
1363
|
const uidOriginals = preserveUids ? collectUidOriginals(record) : void 0;
|
|
1145
1364
|
const bytes = large ? size : Math.min(bucketBytes(size, tolerance), MAX_PIXEL_BYTES);
|
|
1146
1365
|
const fileRecord = {
|
|
@@ -1148,7 +1367,8 @@ function describeDirectory(dir, options = {}) {
|
|
|
1148
1367
|
bytes,
|
|
1149
1368
|
modality,
|
|
1150
1369
|
tags: keptTags,
|
|
1151
|
-
uidOriginals
|
|
1370
|
+
uidOriginals,
|
|
1371
|
+
privateTags: keep.capturingPrivate && parsed ? capturePrivateTags(parsed.rawDict, keep, privateSkipped) : void 0
|
|
1152
1372
|
};
|
|
1153
1373
|
let study = studies.get(studyUid);
|
|
1154
1374
|
if (!study) {
|
|
@@ -1167,18 +1387,23 @@ function describeDirectory(dir, options = {}) {
|
|
|
1167
1387
|
}
|
|
1168
1388
|
}
|
|
1169
1389
|
const salt = preserveUids ? options.uidSalt ?? deriveSalt([...studies.keys()]) : void 0;
|
|
1170
|
-
if (salt !== void 0) {
|
|
1390
|
+
if (salt !== void 0 || keep.capturingPrivate) {
|
|
1171
1391
|
const sweptKeepTags = /* @__PURE__ */ new Set();
|
|
1172
1392
|
for (const study of studies.values()) {
|
|
1173
1393
|
for (const series of study.values()) {
|
|
1174
1394
|
if (!Array.isArray(series)) continue;
|
|
1175
1395
|
for (const rec of series) {
|
|
1176
|
-
|
|
1396
|
+
if (salt !== void 0) {
|
|
1397
|
+
hashRecordUids(rec, salt, uidMap, sweptKeepTags);
|
|
1398
|
+
}
|
|
1399
|
+
mergePrivateTags(rec, salt, uidMap);
|
|
1177
1400
|
}
|
|
1178
1401
|
}
|
|
1179
1402
|
}
|
|
1180
1403
|
warnSweptKeepTags(sweptKeepTags);
|
|
1181
1404
|
}
|
|
1405
|
+
stats.privateBinarySkipped = privateSkipped.count;
|
|
1406
|
+
warnPrivateBinarySkipped(privateSkipped.count);
|
|
1182
1407
|
const withGroupUid = (spec2, seriesUid) => salt === void 0 ? spec2 : {
|
|
1183
1408
|
...spec2,
|
|
1184
1409
|
tags: {
|