@cosyte/dicom 0.0.10 โ†’ 0.0.12

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/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/version.ts
12
- var VERSION = "0.0.10";
12
+ var VERSION = "0.0.12";
13
13
 
14
14
  // src/dictionary/index.ts
15
15
  var dictionary_exports = {};
@@ -11711,11 +11711,14 @@ function isRecognizedVr(vr) {
11711
11711
  // src/parser/warnings.ts
11712
11712
  var WARNING_CODES = {
11713
11713
  // === Phase 2 actively emits (D-08 active list - alphabetical-within-prefix per CONTEXT specifics ยง) ===
11714
+ DICOM_DUPLICATE_FILE_META_ELEMENT: "DICOM_DUPLICATE_FILE_META_ELEMENT",
11715
+ DICOM_DUPLICATE_TAG_IN_DATA_SET: "DICOM_DUPLICATE_TAG_IN_DATA_SET",
11714
11716
  DICOM_EMPTY_ITEM_IN_SEQUENCE: "DICOM_EMPTY_ITEM_IN_SEQUENCE",
11715
11717
  DICOM_FILE_META_GROUP_LENGTH_MISMATCH: "DICOM_FILE_META_GROUP_LENGTH_MISMATCH",
11716
11718
  DICOM_FILE_META_GROUP_LENGTH_MISSING: "DICOM_FILE_META_GROUP_LENGTH_MISSING",
11717
11719
  DICOM_GROUP_LENGTH_IN_DATASET: "DICOM_GROUP_LENGTH_IN_DATASET",
11718
11720
  DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR",
11721
+ DICOM_ITEM_CROSSES_SEQUENCE_END: "DICOM_ITEM_CROSSES_SEQUENCE_END",
11719
11722
  DICOM_MISSING_PREAMBLE: "DICOM_MISSING_PREAMBLE",
11720
11723
  DICOM_NONZERO_RESERVED_BYTES: "DICOM_NONZERO_RESERVED_BYTES",
11721
11724
  DICOM_ODD_LENGTH_VALUE_PADDED: "DICOM_ODD_LENGTH_VALUE_PADDED",
@@ -11741,6 +11744,12 @@ var WARNING_CODES = {
11741
11744
  // reserved by Phase 7 - not emitted in Phase 2
11742
11745
  DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED",
11743
11746
  // emitted by deidentify(), never by the parser
11747
+ DICOM_DEIDENT_METHOD_NOT_ADDED: "DICOM_DEIDENT_METHOD_NOT_ADDED",
11748
+ // emitted by deidentify(), never by the parser
11749
+ DICOM_DEIDENT_METHOD_NOT_LO: "DICOM_DEIDENT_METHOD_NOT_LO",
11750
+ // emitted by deidentify(), never by the parser
11751
+ DICOM_DEIDENT_METHOD_PRIOR_RETAINED: "DICOM_DEIDENT_METHOD_PRIOR_RETAINED",
11752
+ // emitted by deidentify(), never by the parser
11744
11753
  DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE",
11745
11754
  // emitted by deidentify(), never by the parser
11746
11755
  DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE",
@@ -11765,6 +11774,41 @@ var WARNING_MESSAGES = Object.freeze({
11765
11774
  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.",
11766
11775
  DICOM_UN_PARSED_AS_SQ: "Element ({tag}) has VR=UN with undefined length; descended as Implicit VR LE sequence per CP-246.",
11767
11776
  DICOM_EMPTY_ITEM_IN_SEQUENCE: "Sequence ({tag}) contains an empty item (length=0); tolerated.",
11777
+ // Neither the tag nor the replaced element's value is here, and the tag's
11778
+ // absence is specific to this code rather than caution. See
11779
+ // `duplicateTagInDataSet`. Short by measurement rather than by intention: one
11780
+ // is raised per collision and the collision count is chosen by the input, so a
11781
+ // 400-character draft was measured at 50 M characters over a 1 MiB file and
11782
+ // cut. The reasoning lives in the factory's JSDoc, not in the string.
11783
+ DICOM_DUPLICATE_TAG_IN_DATA_SET: "A Data Element carrying a tag this Data Set already holds replaced the earlier one at this byte offset; that element's value is not in the parsed object. PS3.5 7.1 and 7.5.1. Tag withheld.",
11784
+ // The tag is deliberately absent for the same reason as the code above, and
11785
+ // the surviving copy is named because the File Meta group resolves a repeat
11786
+ // the OPPOSITE way round to a Data Set: first copy wins here, last read wins
11787
+ // there. Short for the same measured reason - one is raised per repeat and the
11788
+ // repeat count is chosen by the input.
11789
+ // ๐Ÿ›‘ NO SPEC CITATION HERE, AND ITS ABSENCE IS DELIBERATE. A draft ended this
11790
+ // string "PS3.5 7.1.", which asserts that section's "at most once in a Data
11791
+ // Set" governs the File Meta group - the applicability this slice states it
11792
+ // cannot establish, because PS3.10 governs that group and is not vendored
11793
+ // here. Under `{ strict: true }` this string is also the thrown message, so an
11794
+ // unqualified citation there is the claim, not a footnote. Do not put it back.
11795
+ DICOM_DUPLICATE_FILE_META_ELEMENT: "A File Meta Data Element repeats a (0002,xxxx) tag the group already carries; the copy at this byte offset is not in the parsed File Meta, and the FIRST copy of that tag is the one projected. Tag withheld.",
11796
+ // No value and no length: the prior value is the file's own text. The tag is a
11797
+ // constant of this code rather than composed from input, as in the two
11798
+ // (0002,0000) messages above.
11799
+ DICOM_DEIDENT_METHOD_NOT_ADDED: "The De-identification Method this run recorded could not be added beside the value (0012,0063) already carried without exceeding the largest Value Length that VR can encode, so the earlier value was replaced (PS3.15 E.1.1). The replaced text is not in the output.",
11800
+ // No value, no length AND NO VR. The VR is two bytes read from the file, and a
11801
+ // fabricated header makes those two bytes document content - the shape that
11802
+ // put "ITHS" into DICOM_NONZERO_RESERVED_BYTES. Naming the VR here would read
11803
+ // as harmless and is exactly the same defect.
11804
+ DICOM_DEIDENT_METHOD_NOT_LO: "The (0012,0063) value the source file carried is encoded under a Value Representation other than LO, which is not a De-identification Method this run can add its own text to (PS3.15 E.1.1), so the earlier value was replaced. The replaced text is not in the output. The text and the VR are both withheld from this message.",
11805
+ // No value and no length, for the same reason as the code above: the retained
11806
+ // text is the file's own. The tag is a constant of this code.
11807
+ DICOM_DEIDENT_METHOD_PRIOR_RETAINED: "A De-identification Method (0012,0063) value the source file already carried was kept beside the one this run recorded, as PS3.15 E.1.1 requires. That attribute is not in Table E.1-1, so no rule in this run inspected, audited or redacted those bytes: if the sender wrote identifying text there it is in the de-identified output, under (0012,0062) = YES. The text is withheld from this message.",
11808
+ // The Item's own declared length is deliberately absent. See
11809
+ // `itemCrossesSequenceEnd`; `{n2}` stays because the emit site's
11810
+ // `endLimit < buffer.length` conjunct bounds it by the buffer.
11811
+ DICOM_ITEM_CROSSES_SEQUENCE_END: "Item ({tag}) declares a length reaching past its enclosing sequence's declared end, so it reads the enclosing Data Set's bytes; {n2} bytes remained inside the sequence. The file's two length fields disagree (PS3.5 7.5.1 and 7.5.2 govern them); the item's is used. The declared length is withheld; the byte offset locates the item.",
11768
11812
  DICOM_PIXEL_DATA_LENGTH_MISMATCH: "(7FE0,0010) PixelData declared length {n} does not match computed {n2} bytes.",
11769
11813
  DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "Private element ({tag}) under Implicit VR LE has no VR override; falling back to UN.",
11770
11814
  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.",
@@ -11849,9 +11893,21 @@ function unParsedAsSQ(position, tag) {
11849
11893
  function sqNotDescended(position, tag) {
11850
11894
  return build(WARNING_CODES.DICOM_SQ_NOT_DESCENDED, position, { tag });
11851
11895
  }
11896
+ function itemCrossesSequenceEnd(position, tag, availableLength) {
11897
+ return build(WARNING_CODES.DICOM_ITEM_CROSSES_SEQUENCE_END, position, {
11898
+ tag,
11899
+ n2: availableLength
11900
+ });
11901
+ }
11852
11902
  function emptyItemInSequence(position, tag) {
11853
11903
  return build(WARNING_CODES.DICOM_EMPTY_ITEM_IN_SEQUENCE, position, { tag });
11854
11904
  }
11905
+ function duplicateTagInDataSet(position) {
11906
+ return build(WARNING_CODES.DICOM_DUPLICATE_TAG_IN_DATA_SET, position);
11907
+ }
11908
+ function duplicateFileMetaElement(position) {
11909
+ return build(WARNING_CODES.DICOM_DUPLICATE_FILE_META_ELEMENT, position);
11910
+ }
11855
11911
  function implicitVRForPrivateTagWithoutVR(position, tag) {
11856
11912
  return build(WARNING_CODES.DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR, position, { tag });
11857
11913
  }
@@ -11861,6 +11917,15 @@ function privateCreatorUnknown(position, tag) {
11861
11917
  function burnedInAnnotationNotRemoved(position) {
11862
11918
  return build(WARNING_CODES.DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED, position);
11863
11919
  }
11920
+ function deidentMethodNotAdded(position) {
11921
+ return build(WARNING_CODES.DICOM_DEIDENT_METHOD_NOT_ADDED, position);
11922
+ }
11923
+ function deidentMethodNotLo(position) {
11924
+ return build(WARNING_CODES.DICOM_DEIDENT_METHOD_NOT_LO, position);
11925
+ }
11926
+ function deidentMethodPriorRetained(position) {
11927
+ return build(WARNING_CODES.DICOM_DEIDENT_METHOD_PRIOR_RETAINED, position);
11928
+ }
11864
11929
  function embeddedAttributeRemoved(position, tag, vr, count) {
11865
11930
  return build(WARNING_CODES.DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED, position, {
11866
11931
  tag,
@@ -12155,6 +12220,16 @@ function parseFileMeta(buffer, datasetStart, ctx, emit) {
12155
12220
  fmElements.push(next);
12156
12221
  }
12157
12222
  }
12223
+ const seenModeledTags = /* @__PURE__ */ new Set();
12224
+ if (declaredFmLength !== void 0) seenModeledTags.add("00020000");
12225
+ for (const e of fmElements) {
12226
+ if (!MODELED_FM_TAGS.has(e.tag)) continue;
12227
+ if (seenModeledTags.has(e.tag)) {
12228
+ emit(duplicateFileMetaElement({ byteOffset: e.byteOffset, fileMeta: true }));
12229
+ continue;
12230
+ }
12231
+ seenModeledTags.add(e.tag);
12232
+ }
12158
12233
  const tsElement = fmElements.find((e) => e.tag === "00020010");
12159
12234
  if (tsElement === void 0 || tsElement.vr !== "UI") {
12160
12235
  throw new DicomParseError(
@@ -12201,7 +12276,7 @@ function readExplicitLeElement(cursor) {
12201
12276
  if (bytesConsumed !== headerLength + length) {
12202
12277
  throw new RangeError("File Meta element accounting mismatch");
12203
12278
  }
12204
- return { tag, vr, value, bytesConsumed };
12279
+ return { tag, vr, value, bytesConsumed, byteOffset: headerStart };
12205
12280
  }
12206
12281
  function trimUI(buf) {
12207
12282
  let end = buf.length;
@@ -12641,6 +12716,14 @@ var Element = class {
12641
12716
  }
12642
12717
  };
12643
12718
 
12719
+ // src/parser/data-set-map.ts
12720
+ function defineElement(elements, element, emit) {
12721
+ if (elements.has(element.tag)) {
12722
+ emit(duplicateTagInDataSet({ byteOffset: element.byteOffset }));
12723
+ }
12724
+ elements.set(element.tag, element);
12725
+ }
12726
+
12644
12727
  // src/dataset/item.ts
12645
12728
  var Item = class extends Dataset {
12646
12729
  index;
@@ -12762,6 +12845,15 @@ function parseSequence(buffer, valueStart, ctx, emit, opts) {
12762
12845
  items.push(new Item({ warnings: [], elements: inner.elements, index: itemIndex }));
12763
12846
  cursor.position = inner.endOffset;
12764
12847
  } else {
12848
+ if (opts.explicitLength !== void 0 && endLimit < buffer.length && cursor.position + itemLength > endLimit) {
12849
+ emit(
12850
+ itemCrossesSequenceEnd(
12851
+ { byteOffset: itemHeaderStart },
12852
+ itemTag,
12853
+ Math.max(0, endLimit - cursor.position)
12854
+ )
12855
+ );
12856
+ }
12765
12857
  if (cursor.position + itemLength > buffer.length) {
12766
12858
  throw new DicomParseError(
12767
12859
  FATAL_CODES.INVALID_FILE_META,
@@ -12897,8 +12989,8 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12897
12989
  cursor.position = seq.endOffset;
12898
12990
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(valueRawStart, cursor.position)) : buffer.subarray(valueRawStart, cursor.position);
12899
12991
  const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12900
- elements.set(
12901
- tag,
12992
+ defineElement(
12993
+ elements,
12902
12994
  new Element({
12903
12995
  tag,
12904
12996
  vr,
@@ -12909,7 +13001,8 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12909
13001
  littleEndian: true,
12910
13002
  items: seq.items,
12911
13003
  ...privateCreator2 !== void 0 ? { privateCreator: privateCreator2 } : {}
12912
- })
13004
+ }),
13005
+ emit
12913
13006
  );
12914
13007
  continue;
12915
13008
  }
@@ -12928,8 +13021,8 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12928
13021
  cursor.position = cp246.endOffset;
12929
13022
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(valueRawStart, cursor.position)) : buffer.subarray(valueRawStart, cursor.position);
12930
13023
  const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12931
- elements.set(
12932
- tag,
13024
+ defineElement(
13025
+ elements,
12933
13026
  new Element({
12934
13027
  tag,
12935
13028
  vr: "SQ",
@@ -12942,7 +13035,8 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12942
13035
  littleEndian: true,
12943
13036
  items: cp246.items,
12944
13037
  ...privateCreator2 !== void 0 ? { privateCreator: privateCreator2 } : {}
12945
- })
13038
+ }),
13039
+ emit
12946
13040
  );
12947
13041
  continue;
12948
13042
  }
@@ -12978,8 +13072,8 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12978
13072
  );
12979
13073
  if (seq.success) {
12980
13074
  const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12981
- elements.set(
12982
- tag,
13075
+ defineElement(
13076
+ elements,
12983
13077
  new Element({
12984
13078
  tag,
12985
13079
  vr,
@@ -12990,7 +13084,8 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12990
13084
  littleEndian: true,
12991
13085
  items: seq.items,
12992
13086
  ...privateCreator2 !== void 0 ? { privateCreator: privateCreator2 } : {}
12993
- })
13087
+ }),
13088
+ emit
12994
13089
  );
12995
13090
  continue;
12996
13091
  }
@@ -13003,8 +13098,8 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
13003
13098
  }
13004
13099
  applySpecificCharacterSet(tag, valueSlice, ctx, emit, position);
13005
13100
  const privateCreator = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13006
- elements.set(
13007
- tag,
13101
+ defineElement(
13102
+ elements,
13008
13103
  new Element({
13009
13104
  tag,
13010
13105
  vr,
@@ -13016,7 +13111,8 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
13016
13111
  littleEndian: true,
13017
13112
  ...ctx.currentCharset !== void 0 ? { specificCharacterSet: ctx.currentCharset } : {},
13018
13113
  ...privateCreator !== void 0 ? { privateCreator } : {}
13019
- })
13114
+ }),
13115
+ emit
13020
13116
  );
13021
13117
  }
13022
13118
  return { elements, endOffset: cursor.position };
@@ -13123,8 +13219,8 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13123
13219
  cursor.position = seqResult.endOffset;
13124
13220
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, cursor.position)) : buffer.subarray(headerStart, cursor.position);
13125
13221
  const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13126
- elements.set(
13127
- tag,
13222
+ defineElement(
13223
+ elements,
13128
13224
  new Element({
13129
13225
  tag,
13130
13226
  vr: "SQ",
@@ -13135,7 +13231,8 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13135
13231
  littleEndian: mode.littleEndian,
13136
13232
  items: seqResult.items,
13137
13233
  ...privateCreator2 !== void 0 ? { privateCreator: privateCreator2 } : {}
13138
- })
13234
+ }),
13235
+ emit
13139
13236
  );
13140
13237
  continue;
13141
13238
  }
@@ -13150,8 +13247,8 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13150
13247
  const result = parseSequence(buffer, valueStart2, ctx, emit, seqOpts);
13151
13248
  cursor.position = result.endOffset;
13152
13249
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, cursor.position)) : buffer.subarray(headerStart, cursor.position);
13153
- elements.set(
13154
- tag,
13250
+ defineElement(
13251
+ elements,
13155
13252
  new Element({
13156
13253
  tag,
13157
13254
  vr: "OB",
@@ -13162,7 +13259,8 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13162
13259
  byteOffset: headerStart,
13163
13260
  littleEndian: mode.littleEndian,
13164
13261
  items: result.items
13165
- })
13262
+ }),
13263
+ emit
13166
13264
  );
13167
13265
  continue;
13168
13266
  }
@@ -13181,8 +13279,8 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13181
13279
  cursor.position = cp246.endOffset;
13182
13280
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, cursor.position)) : buffer.subarray(headerStart, cursor.position);
13183
13281
  const privateCreator3 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13184
- elements.set(
13185
- tag,
13282
+ defineElement(
13283
+ elements,
13186
13284
  new Element({
13187
13285
  tag,
13188
13286
  vr: "SQ",
@@ -13195,15 +13293,16 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13195
13293
  littleEndian: mode.littleEndian,
13196
13294
  items: cp246.items,
13197
13295
  ...privateCreator3 !== void 0 ? { privateCreator: privateCreator3 } : {}
13198
- })
13296
+ }),
13297
+ emit
13199
13298
  );
13200
13299
  continue;
13201
13300
  }
13202
13301
  const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13203
13302
  const fallbackEnd = buffer.length;
13204
13303
  const fallbackBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, fallbackEnd)) : buffer.subarray(headerStart, fallbackEnd);
13205
- elements.set(
13206
- tag,
13304
+ defineElement(
13305
+ elements,
13207
13306
  new Element({
13208
13307
  tag,
13209
13308
  vr: "UN",
@@ -13213,7 +13312,8 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13213
13312
  byteOffset: headerStart,
13214
13313
  littleEndian: mode.littleEndian,
13215
13314
  ...privateCreator2 !== void 0 ? { privateCreator: privateCreator2 } : {}
13216
- })
13315
+ }),
13316
+ emit
13217
13317
  );
13218
13318
  cursor.position = fallbackEnd;
13219
13319
  continue;
@@ -13248,8 +13348,8 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13248
13348
  applySpecificCharacterSet(tag, valueSlice, ctx, emit, position);
13249
13349
  const privateCreator = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13250
13350
  const finalVr = vr;
13251
- elements.set(
13252
- tag,
13351
+ defineElement(
13352
+ elements,
13253
13353
  new Element({
13254
13354
  tag,
13255
13355
  vr: finalVr,
@@ -13261,7 +13361,8 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13261
13361
  littleEndian: mode.littleEndian,
13262
13362
  ...ctx.currentCharset !== void 0 ? { specificCharacterSet: ctx.currentCharset } : {},
13263
13363
  ...privateCreator !== void 0 ? { privateCreator } : {}
13264
- })
13364
+ }),
13365
+ emit
13265
13366
  );
13266
13367
  }
13267
13368
  return { elements, endOffset: cursor.position };
@@ -15093,6 +15194,19 @@ function itemStreamOverrunsSequence(el, encoding) {
15093
15194
  const headerBytes = encoding === "implicit" ? 0 : EXPLICIT_SQ_HEADER_BYTES;
15094
15195
  return el.rawBytes.length - headerBytes > el.length;
15095
15196
  }
15197
+ function settledBound(source, encoding) {
15198
+ let index = source.length;
15199
+ let byteOffset;
15200
+ for (const [at, el] of source.entries()) {
15201
+ if (!itemStreamOverrunsSequence(el, encoding) && !isUnauditableSequence(el)) continue;
15202
+ if (index === source.length) index = at + 1;
15203
+ if (byteOffset === void 0 || el.byteOffset < byteOffset) byteOffset = el.byteOffset;
15204
+ }
15205
+ return { index, byteOffset };
15206
+ }
15207
+ function isSettled(el, at, bound) {
15208
+ return at < bound.index && (bound.byteOffset === void 0 || el.byteOffset <= bound.byteOffset);
15209
+ }
15096
15210
  function keepsPrivate(el, ctx, creators) {
15097
15211
  if (!ctx.active.has("RetainSafePrivate") || ctx.profile === void 0) return false;
15098
15212
  if (isPrivateCreatorElement(el.tag)) {
@@ -15112,10 +15226,11 @@ function processElements(source, ctx, contextPath, reservationsUsable) {
15112
15226
  undefinedVrElements: [],
15113
15227
  warnings: []
15114
15228
  };
15115
- const creators = creatorsInScope(source);
15116
- for (const el of source) {
15229
+ const bound = settledBound(source, ctx.encoding);
15230
+ const creators = creatorsInScope(source.filter((el, at) => isSettled(el, at, bound)));
15231
+ for (const [at, el] of source.entries()) {
15117
15232
  if (isPrivateTag(el.tag)) {
15118
- if (reservationsUsable && keepsPrivate(el, ctx, creators))
15233
+ if (reservationsUsable && isSettled(el, at, bound) && keepsPrivate(el, ctx, creators))
15119
15234
  keepOrEmpty(el, ctx, contextPath, out);
15120
15235
  else out.removedPrivateTags.push(el.tag);
15121
15236
  continue;
@@ -15244,9 +15359,54 @@ function rebuildFileMeta(fileMeta, ctx) {
15244
15359
  mediaStorageSOPInstanceUID: ctx.remap.map(fileMeta.mediaStorageSOPInstanceUID)
15245
15360
  };
15246
15361
  }
15362
+ var DEFAULT_METHOD_PROFILE = "@cosyte/dicom Basic Application Level Confidentiality Profile";
15247
15363
  function defaultMethod(active) {
15248
- const base = "Cosyte @cosyte/dicom: PS3.15 Basic Application Level Confidentiality Profile";
15249
- return active.size === 0 ? base : `${base} + ${[...active].join(", ")}`;
15364
+ const options = DEIDENTIFY_OPTIONS.filter((option) => active.has(option));
15365
+ return [DEFAULT_METHOD_PROFILE, ...options].join(String.fromCharCode(VALUE_DELIMITER));
15366
+ }
15367
+ var VALUE_DELIMITER = 92;
15368
+ var MAX_SHORT_FORM_VALUE_BYTES = 65534;
15369
+ function addDeidentificationMethod(existing, method) {
15370
+ const added = trimTrailingPad(Buffer.from(method, "latin1"));
15371
+ const none = { replacedPrior: false, retainedPrior: false, replacedNonLoPrior: false };
15372
+ if (existing === void 0) return { value: added, ...none };
15373
+ if (existing.vr !== "LO") {
15374
+ const lost = trimTrailingPad(existing.rawBytes).length > 0;
15375
+ return { value: added, ...none, replacedNonLoPrior: lost };
15376
+ }
15377
+ const kept = Buffer.from(trimTrailingPad(existing.rawBytes));
15378
+ if (kept.length === 0) return { value: added, ...none };
15379
+ const keptValues = splitValues(kept).map(trimTrailingPad);
15380
+ const missing = added.length === 0 ? [] : splitValues(added).filter((v) => !keptValues.some((k) => k.equals(trimTrailingPad(v))));
15381
+ const parts = [kept];
15382
+ for (const value2 of missing) {
15383
+ parts.push(Buffer.from([VALUE_DELIMITER]), value2);
15384
+ }
15385
+ const value = trimTrailingPad(Buffer.concat(parts));
15386
+ if (value.length > MAX_SHORT_FORM_VALUE_BYTES) {
15387
+ return { value: added, ...none, replacedPrior: true };
15388
+ }
15389
+ return { value, ...none, retainedPrior: true };
15390
+ }
15391
+ function trimTrailingPad(value) {
15392
+ let end = value.length;
15393
+ while (end > 0) {
15394
+ const last = value[end - 1];
15395
+ if (last === 32 || last === 0) end--;
15396
+ else break;
15397
+ }
15398
+ return value.subarray(0, end);
15399
+ }
15400
+ function splitValues(value) {
15401
+ const out = [];
15402
+ let start = 0;
15403
+ for (let i = 0; i <= value.length; i++) {
15404
+ if (i === value.length || value[i] === VALUE_DELIMITER) {
15405
+ out.push(value.subarray(start, i));
15406
+ start = i + 1;
15407
+ }
15408
+ }
15409
+ return out;
15250
15410
  }
15251
15411
  function hasUncleanedBurnedIn(ds) {
15252
15412
  if (!ds.has(TAG_PIXEL_DATA)) return false;
@@ -15281,11 +15441,28 @@ function deidentify(ds, options = {}) {
15281
15441
  insertedScalar(TAG_PATIENT_IDENTITY_REMOVED, "CS", Buffer.from("YES", "latin1"), littleEndian)
15282
15442
  );
15283
15443
  const method = options.deidentificationMethod ?? defaultMethod(active);
15444
+ const priorMethod = elements.get(TAG_DEIDENTIFICATION_METHOD);
15445
+ const deidentMethod = addDeidentificationMethod(priorMethod, method);
15284
15446
  elements.set(
15285
15447
  TAG_DEIDENTIFICATION_METHOD,
15286
- insertedScalar(TAG_DEIDENTIFICATION_METHOD, "LO", Buffer.from(method, "latin1"), littleEndian)
15448
+ insertedScalar(TAG_DEIDENTIFICATION_METHOD, "LO", deidentMethod.value, littleEndian)
15287
15449
  );
15288
15450
  const warnings = [...processed.warnings];
15451
+ if (deidentMethod.replacedPrior) {
15452
+ warnings.push(
15453
+ deidentMethodNotAdded({ byteOffset: priorMethod?.byteOffset ?? 0, fileMeta: false })
15454
+ );
15455
+ }
15456
+ if (deidentMethod.retainedPrior) {
15457
+ warnings.push(
15458
+ deidentMethodPriorRetained({ byteOffset: priorMethod?.byteOffset ?? 0, fileMeta: false })
15459
+ );
15460
+ }
15461
+ if (deidentMethod.replacedNonLoPrior) {
15462
+ warnings.push(
15463
+ deidentMethodNotLo({ byteOffset: priorMethod?.byteOffset ?? 0, fileMeta: false })
15464
+ );
15465
+ }
15289
15466
  if (hasUncleanedBurnedIn(ds)) {
15290
15467
  const offset = ds.get(TAG_PIXEL_DATA)?.byteOffset ?? 0;
15291
15468
  warnings.push(burnedInAnnotationNotRemoved({ byteOffset: offset, fileMeta: false }));