@cosyte/dicom 0.0.5 → 0.0.7

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.cjs CHANGED
@@ -11,7 +11,7 @@ var __export = (target, all) => {
11
11
  };
12
12
 
13
13
  // src/version.ts
14
- var VERSION = "0.0.5";
14
+ var VERSION = "0.0.7";
15
15
 
16
16
  // src/dictionary/index.ts
17
17
  var dictionary_exports = {};
@@ -11574,6 +11574,13 @@ var ByteCursor = class {
11574
11574
  }
11575
11575
  };
11576
11576
 
11577
+ // src/parser/tokens.ts
11578
+ var WITHHELD = "<withheld>";
11579
+ function safePrivateCreator(creator, profile) {
11580
+ if (profile === void 0) return WITHHELD;
11581
+ return profile.privateDictionary.has(creator) ? creator : WITHHELD;
11582
+ }
11583
+
11577
11584
  // src/dataset/vr/charset.ts
11578
11585
  var TERM_TO_LABEL = /* @__PURE__ */ new Map([
11579
11586
  // Default repertoire (ISO 646 IRV / ASCII). Latin-1 is a lenient,
@@ -11626,7 +11633,7 @@ var MULTIBYTE_LABELS = /* @__PURE__ */ new Set([
11626
11633
  ]);
11627
11634
  function parseSpecificCharacterSet(bytes) {
11628
11635
  const raw = bytes.toString("latin1");
11629
- return raw.split("\\").map((t) => t.replace(/[ ]+$/u, "").replace(/^[ ]+/u, ""));
11636
+ return raw.split("\\").map((t) => t.replace(/[ ]+$/u, "").replace(/^[ ]+/u, "")).map((t) => t === "" || TERM_TO_LABEL.has(t) ? t : WITHHELD);
11630
11637
  }
11631
11638
  function isKnownCharsetTerm(term) {
11632
11639
  return TERM_TO_LABEL.has(term);
@@ -11655,6 +11662,48 @@ function decodeText(bytes, terms) {
11655
11662
  }
11656
11663
  }
11657
11664
 
11665
+ // src/parser/endian.ts
11666
+ var BE_VR_STRIDE = Object.freeze({
11667
+ // 2-byte swaps
11668
+ AT: 2,
11669
+ US: 2,
11670
+ SS: 2,
11671
+ OW: 2,
11672
+ // 4-byte swaps
11673
+ UL: 4,
11674
+ SL: 4,
11675
+ FL: 4,
11676
+ OF: 4,
11677
+ OL: 4,
11678
+ // 8-byte swaps (64-bit floats + 2018 64-bit integer additions)
11679
+ FD: 8,
11680
+ OD: 8,
11681
+ OV: 8,
11682
+ SV: 8,
11683
+ UV: 8,
11684
+ // No swap - byte streams (D-24) and ASCII / spec-defined VRs.
11685
+ OB: 0,
11686
+ UN: 0,
11687
+ AE: 0,
11688
+ AS: 0,
11689
+ CS: 0,
11690
+ DA: 0,
11691
+ DS: 0,
11692
+ DT: 0,
11693
+ IS: 0,
11694
+ LO: 0,
11695
+ LT: 0,
11696
+ PN: 0,
11697
+ SH: 0,
11698
+ ST: 0,
11699
+ TM: 0,
11700
+ UC: 0,
11701
+ UI: 0,
11702
+ UR: 0,
11703
+ UT: 0,
11704
+ SQ: 0
11705
+ });
11706
+
11658
11707
  // src/parser/warnings.ts
11659
11708
  var WARNING_CODES = {
11660
11709
  // === Phase 2 actively emits (D-08 active list - alphabetical-within-prefix per CONTEXT specifics §) ===
@@ -11668,6 +11717,7 @@ var WARNING_CODES = {
11668
11717
  DICOM_ODD_LENGTH_VALUE_PADDED: "DICOM_ODD_LENGTH_VALUE_PADDED",
11669
11718
  DICOM_PIXEL_DATA_LENGTH_MISMATCH: "DICOM_PIXEL_DATA_LENGTH_MISMATCH",
11670
11719
  DICOM_PRIVATE_TAG_NO_CREATOR: "DICOM_PRIVATE_TAG_NO_CREATOR",
11720
+ DICOM_SQ_NOT_DESCENDED: "DICOM_SQ_NOT_DESCENDED",
11671
11721
  DICOM_UN_PARSED_AS_SQ: "DICOM_UN_PARSED_AS_SQ",
11672
11722
  DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR: "DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR",
11673
11723
  DICOM_VR_MISMATCH: "DICOM_VR_MISMATCH",
@@ -11685,162 +11735,168 @@ var WARNING_CODES = {
11685
11735
  // === Reserved by later phases (declared, not emitted in Phase 2) ===
11686
11736
  DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED",
11687
11737
  // reserved by Phase 7 - not emitted in Phase 2
11738
+ DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED",
11739
+ // emitted by deidentify(), never by the parser
11740
+ DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE",
11741
+ // emitted by deidentify(), never by the parser
11742
+ DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE",
11743
+ // emitted by deidentify(), never by the parser
11688
11744
  DICOM_PRIVATE_CREATOR_UNKNOWN: "DICOM_PRIVATE_CREATOR_UNKNOWN"
11689
11745
  // reserved by Phase 6 - not emitted in Phase 2
11690
11746
  };
11747
+ var WARNING_MESSAGES = Object.freeze({
11748
+ DICOM_MISSING_PREAMBLE: "No DICM magic at offset 128; falling back to offset-0 dataset.",
11749
+ DICOM_FILE_META_GROUP_LENGTH_MISSING: "(0002,0000) FileMetaInformationGroupLength missing; scanning forward to first non-(0002,xxxx) element.",
11750
+ DICOM_FILE_META_GROUP_LENGTH_MISMATCH: "(0002,0000) FileMetaInformationGroupLength declared {n} bytes; actual File Meta group size is {n2} bytes. Trusting actual.",
11751
+ DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR: "Element ({tag}) uses undefined length (0xFFFFFFFF) under an Explicit VR transfer syntax.",
11752
+ DICOM_ODD_LENGTH_VALUE_PADDED: "Element ({tag}) has odd declared length {n}; cursor advanced by one padding byte to maintain alignment.",
11753
+ DICOM_VR_MISMATCH: "Element ({tag}) on-wire VR is {vr2}; dictionary lists {vr}. Trusting on-wire VR.",
11754
+ DICOM_PRIVATE_TAG_NO_CREATOR: "Private element ({tag}) has no Private Creator registered for its block; treating as VR=UN.",
11755
+ DICOM_GROUP_LENGTH_IN_DATASET: "Retired Group Length element ({tag}) encountered in dataset; preserved as-is.",
11756
+ DICOM_NONZERO_RESERVED_BYTES: "Element ({tag}) has non-zero reserved bytes between VR and length (first byte {n}, second byte {n2}); ignoring.",
11757
+ 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
+ DICOM_UN_PARSED_AS_SQ: "Element ({tag}) has VR=UN with undefined length; descended as Implicit VR LE sequence per CP-246.",
11759
+ DICOM_EMPTY_ITEM_IN_SEQUENCE: "Sequence ({tag}) contains an empty item (length=0); tolerated.",
11760
+ DICOM_PIXEL_DATA_LENGTH_MISMATCH: "(7FE0,0010) PixelData declared length {n} does not match computed {n2} bytes.",
11761
+ DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "Private element ({tag}) under Implicit VR LE has no VR override; falling back to UN.",
11762
+ 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.",
11763
+ DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "Pixel Data is present and Burned In Annotation is not 'NO'; this metadata-only de-identifier cannot inspect or clean pixels. Recognizable text may remain burned into the image.",
11764
+ DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "Element ({tag}) {vr} was kept by the action table, but its value ends with {n} whole Data Element(s) - an over-declared Value Length swallowed what followed it. Emptied rather than kept, because the action table cannot see an attribute encoded inside a value. Read report.embeddedAttributes for the tags that were hidden there.",
11765
+ // Deliberately short. One of these is raised per un-auditable element, so a
11766
+ // long message is multiplied by an element count the input controls; the
11767
+ // reasoning belongs in the docs, not in a string repeated thousands of times.
11768
+ DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "Element ({tag}) is VR=SQ with no parsed items, so its {n} value bytes could not be audited (PS3.15 E.1.1); emptied. See report.unauditableSequences.",
11769
+ // Deliberately short for the same reason as the code above: one per element,
11770
+ // and the element count is chosen by the input.
11771
+ //
11772
+ // NEITHER THE TAG NOR THE VR IS ECHOED, and that is specific to this code
11773
+ // rather than caution. Every other factory's {tag} is composed from a real
11774
+ // Data Element header; the condition that raises THIS one is that the header
11775
+ // was fabricated from bytes inside some element's value, so its four tag bytes
11776
+ // and its two VR bytes are document content. `renderTag` shape-checks a tag
11777
+ // and cannot refuse one, so the withholding has to happen at the call site.
11778
+ // The byte offset locates the element instead, and is a position this parser
11779
+ // counted rather than anything the document said.
11780
+ DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "An element at byte offset {n2} carries an on-wire VR that is not one of the 34 PS3.5 6.2 defines, so its {n} value bytes are not a Value Field this library decoded; emptied (PS3.15 E.1.1). Its tag and VR are withheld: an earlier under-declared length can make them fragments of some element's value. See report.undefinedVrElements.",
11781
+ DICOM_BOM_IN_TEXT_VR: "Element ({tag}) {vr} value begins with a UTF-8 BOM; stripped on decode.",
11782
+ DICOM_TRAILING_NULL_IN_TEXT_VR: "Element ({tag}) {vr} value has a trailing NULL pad where SPACE is expected; trimmed.",
11783
+ DICOM_UI_TRAILING_SPACE: "Element ({tag}) UI value is SPACE-padded; UI requires NULL padding (PS3.5 \xA76.2). Trimmed.",
11784
+ DICOM_NON_ASCII_IN_ASCII_VR: "Element ({tag}) {vr} is an ASCII-only VR but contains non-ASCII bytes; decoded as Latin-1 best-effort.",
11785
+ DICOM_IS_NONINTEGER_VALUE: "Element ({tag}) IS value is not a base-10 integer; surfaced as null with raw bytes preserved.",
11786
+ DICOM_DA_LEGACY_FORMAT: "Element ({tag}) DA value is not in canonical YYYYMMDD form; decoded best-effort, raw preserved.",
11787
+ DICOM_DT_NONSTANDARD_OFFSET: "Element ({tag}) DT value has a non-standard UTC offset; decoded best-effort, raw preserved.",
11788
+ 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
+ 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
+ });
11791
+ var KNOWN_VRS = new Set(Object.keys(BE_VR_STRIDE));
11792
+ var TAG_SHAPE = /^[0-9A-F]{8}$/u;
11793
+ function renderTag(tag) {
11794
+ return tag !== void 0 && TAG_SHAPE.test(tag) ? tag : WITHHELD;
11795
+ }
11796
+ function renderVr(vr) {
11797
+ return vr !== void 0 && KNOWN_VRS.has(vr) ? vr : WITHHELD;
11798
+ }
11799
+ function build(code, position, tokens = {}) {
11800
+ const message = WARNING_MESSAGES[code].replace("{tag}", renderTag(tokens.tag)).replace("{vr2}", renderVr(tokens.vr2)).replace("{vr}", renderVr(tokens.vr)).replace("{n2}", String(tokens.n2 ?? 0)).replace("{n}", String(tokens.n ?? 0));
11801
+ return { code, message, position };
11802
+ }
11691
11803
  function missingPreamble(position) {
11692
- return {
11693
- code: WARNING_CODES.DICOM_MISSING_PREAMBLE,
11694
- message: "No DICM magic at offset 128; falling back to offset-0 dataset.",
11695
- position
11696
- };
11804
+ return build(WARNING_CODES.DICOM_MISSING_PREAMBLE, position);
11697
11805
  }
11698
11806
  function fileMetaGroupLengthMissing(position) {
11699
- return {
11700
- code: WARNING_CODES.DICOM_FILE_META_GROUP_LENGTH_MISSING,
11701
- message: "(0002,0000) FileMetaInformationGroupLength missing; scanning forward to first non-(0002,xxxx) element.",
11702
- position
11703
- };
11807
+ return build(WARNING_CODES.DICOM_FILE_META_GROUP_LENGTH_MISSING, position);
11704
11808
  }
11705
11809
  function fileMetaGroupLengthMismatch(position, declared, actual) {
11706
- return {
11707
- code: WARNING_CODES.DICOM_FILE_META_GROUP_LENGTH_MISMATCH,
11708
- message: `(0002,0000) FileMetaInformationGroupLength declared ${String(declared)} bytes; actual File Meta group size is ${String(actual)} bytes. Trusting actual.`,
11709
- position
11710
- };
11810
+ return build(WARNING_CODES.DICOM_FILE_META_GROUP_LENGTH_MISMATCH, position, {
11811
+ n: declared,
11812
+ n2: actual
11813
+ });
11711
11814
  }
11712
11815
  function undefinedLengthInExplicitVR(position, tag) {
11713
- return {
11714
- code: WARNING_CODES.DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR,
11715
- message: `Element (${tag}) uses undefined length (0xFFFFFFFF) under an Explicit VR transfer syntax.`,
11716
- position
11717
- };
11816
+ return build(WARNING_CODES.DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR, position, { tag });
11718
11817
  }
11719
11818
  function oddLengthValuePadded(position, tag, declaredLength) {
11720
- return {
11721
- code: WARNING_CODES.DICOM_ODD_LENGTH_VALUE_PADDED,
11722
- message: `Element (${tag}) has odd declared length ${String(declaredLength)}; cursor advanced by one padding byte to maintain alignment.`,
11723
- position
11724
- };
11819
+ return build(WARNING_CODES.DICOM_ODD_LENGTH_VALUE_PADDED, position, {
11820
+ tag,
11821
+ n: declaredLength
11822
+ });
11725
11823
  }
11726
11824
  function vrMismatch(position, tag, dictVR, fileVR) {
11727
- return {
11728
- code: WARNING_CODES.DICOM_VR_MISMATCH,
11729
- message: `Element (${tag}) on-wire VR is ${fileVR}; dictionary lists ${dictVR}. Trusting on-wire VR.`,
11730
- position
11731
- };
11825
+ return build(WARNING_CODES.DICOM_VR_MISMATCH, position, { tag, vr: dictVR, vr2: fileVR });
11732
11826
  }
11733
11827
  function privateTagNoCreator(position, tag) {
11734
- return {
11735
- code: WARNING_CODES.DICOM_PRIVATE_TAG_NO_CREATOR,
11736
- message: `Private element (${tag}) has no Private Creator registered for its block; treating as VR=UN.`,
11737
- position
11738
- };
11828
+ return build(WARNING_CODES.DICOM_PRIVATE_TAG_NO_CREATOR, position, { tag });
11739
11829
  }
11740
11830
  function groupLengthInDataset(position, tag) {
11741
- return {
11742
- code: WARNING_CODES.DICOM_GROUP_LENGTH_IN_DATASET,
11743
- message: `Retired Group Length element (${tag}) encountered in dataset; preserved as-is.`,
11744
- position
11745
- };
11831
+ return build(WARNING_CODES.DICOM_GROUP_LENGTH_IN_DATASET, position, { tag });
11746
11832
  }
11747
- function nonzeroReservedBytes(position, tag, observed) {
11748
- return {
11749
- code: WARNING_CODES.DICOM_NONZERO_RESERVED_BYTES,
11750
- message: `Element (${tag}) has non-zero reserved bytes (${observed}) between VR and length; ignoring.`,
11751
- position
11752
- };
11833
+ function nonzeroReservedBytes(position, tag, first, second) {
11834
+ return build(WARNING_CODES.DICOM_NONZERO_RESERVED_BYTES, position, {
11835
+ tag,
11836
+ n: first,
11837
+ n2: second
11838
+ });
11753
11839
  }
11754
11840
  function unParsedAsSQ(position, tag) {
11755
- return {
11756
- code: WARNING_CODES.DICOM_UN_PARSED_AS_SQ,
11757
- message: `Element (${tag}) declared VR=UN with undefined length; descended as Implicit VR LE sequence per CP-246.`,
11758
- position
11759
- };
11841
+ return build(WARNING_CODES.DICOM_UN_PARSED_AS_SQ, position, { tag });
11842
+ }
11843
+ function sqNotDescended(position, tag) {
11844
+ return build(WARNING_CODES.DICOM_SQ_NOT_DESCENDED, position, { tag });
11760
11845
  }
11761
11846
  function emptyItemInSequence(position, tag) {
11762
- return {
11763
- code: WARNING_CODES.DICOM_EMPTY_ITEM_IN_SEQUENCE,
11764
- message: `Sequence (${tag}) contains an empty item (length=0); tolerated.`,
11765
- position
11766
- };
11847
+ return build(WARNING_CODES.DICOM_EMPTY_ITEM_IN_SEQUENCE, position, { tag });
11767
11848
  }
11768
11849
  function implicitVRForPrivateTagWithoutVR(position, tag) {
11769
- return {
11770
- code: WARNING_CODES.DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR,
11771
- message: `Private element (${tag}) under Implicit VR LE has no VR override; falling back to UN.`,
11772
- position
11773
- };
11850
+ return build(WARNING_CODES.DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR, position, { tag });
11774
11851
  }
11775
- function privateCreatorUnknown(position, tag, creator) {
11776
- return {
11777
- code: WARNING_CODES.DICOM_PRIVATE_CREATOR_UNKNOWN,
11778
- message: `Private element (${tag}) creator "${creator}" is not in the active profile's private dictionary; falling back to UN.`,
11779
- position
11780
- };
11852
+ function privateCreatorUnknown(position, tag) {
11853
+ return build(WARNING_CODES.DICOM_PRIVATE_CREATOR_UNKNOWN, position, { tag });
11781
11854
  }
11782
11855
  function burnedInAnnotationNotRemoved(position) {
11783
- return {
11784
- code: WARNING_CODES.DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED,
11785
- message: "Pixel Data is present and Burned In Annotation is not 'NO'; this metadata-only de-identifier cannot inspect or clean pixels. Recognizable text may remain burned into the image.",
11786
- position
11787
- };
11856
+ return build(WARNING_CODES.DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED, position);
11857
+ }
11858
+ function embeddedAttributeRemoved(position, tag, vr, count) {
11859
+ return build(WARNING_CODES.DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED, position, {
11860
+ tag,
11861
+ vr,
11862
+ n: count
11863
+ });
11864
+ }
11865
+ function sequenceNotAuditable(position, tag, byteLength) {
11866
+ return build(WARNING_CODES.DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE, position, {
11867
+ tag,
11868
+ n: byteLength
11869
+ });
11870
+ }
11871
+ function undefinedVrNotAuditable(position, byteLength) {
11872
+ return build(WARNING_CODES.DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE, position, {
11873
+ n: byteLength,
11874
+ n2: position.byteOffset
11875
+ });
11788
11876
  }
11789
11877
  function bomInTextVR(position, tag, vr) {
11790
- return {
11791
- code: WARNING_CODES.DICOM_BOM_IN_TEXT_VR,
11792
- message: `Element (${tag}) ${vr} value begins with a UTF-8 BOM; stripped on decode.`,
11793
- position
11794
- };
11878
+ return build(WARNING_CODES.DICOM_BOM_IN_TEXT_VR, position, { tag, vr });
11795
11879
  }
11796
11880
  function trailingNullInTextVR(position, tag, vr) {
11797
- return {
11798
- code: WARNING_CODES.DICOM_TRAILING_NULL_IN_TEXT_VR,
11799
- message: `Element (${tag}) ${vr} value has a trailing NULL pad where SPACE is expected; trimmed.`,
11800
- position
11801
- };
11881
+ return build(WARNING_CODES.DICOM_TRAILING_NULL_IN_TEXT_VR, position, { tag, vr });
11802
11882
  }
11803
11883
  function uiTrailingSpace(position, tag) {
11804
- return {
11805
- code: WARNING_CODES.DICOM_UI_TRAILING_SPACE,
11806
- message: `Element (${tag}) UI value is SPACE-padded; UI requires NULL padding (PS3.5 \xA76.2). Trimmed.`,
11807
- position
11808
- };
11884
+ return build(WARNING_CODES.DICOM_UI_TRAILING_SPACE, position, { tag });
11809
11885
  }
11810
11886
  function nonAsciiInAsciiVR(position, tag, vr) {
11811
- return {
11812
- code: WARNING_CODES.DICOM_NON_ASCII_IN_ASCII_VR,
11813
- message: `Element (${tag}) ${vr} is an ASCII-only VR but contains non-ASCII bytes; decoded as Latin-1 best-effort.`,
11814
- position
11815
- };
11887
+ return build(WARNING_CODES.DICOM_NON_ASCII_IN_ASCII_VR, position, { tag, vr });
11816
11888
  }
11817
11889
  function isNonintegerValue(position, tag) {
11818
- return {
11819
- code: WARNING_CODES.DICOM_IS_NONINTEGER_VALUE,
11820
- message: `Element (${tag}) IS value is not a base-10 integer; surfaced as null with raw bytes preserved.`,
11821
- position
11822
- };
11890
+ return build(WARNING_CODES.DICOM_IS_NONINTEGER_VALUE, position, { tag });
11823
11891
  }
11824
11892
  function daLegacyFormat(position, tag) {
11825
- return {
11826
- code: WARNING_CODES.DICOM_DA_LEGACY_FORMAT,
11827
- message: `Element (${tag}) DA value is not in canonical YYYYMMDD form; decoded best-effort, raw preserved.`,
11828
- position
11829
- };
11893
+ return build(WARNING_CODES.DICOM_DA_LEGACY_FORMAT, position, { tag });
11830
11894
  }
11831
11895
  function dtNonstandardOffset(position, tag) {
11832
- return {
11833
- code: WARNING_CODES.DICOM_DT_NONSTANDARD_OFFSET,
11834
- message: `Element (${tag}) DT value has a non-standard UTC offset; decoded best-effort, raw preserved.`,
11835
- position
11836
- };
11896
+ return build(WARNING_CODES.DICOM_DT_NONSTANDARD_OFFSET, position, { tag });
11837
11897
  }
11838
- function unsupportedCharset(position, term) {
11839
- return {
11840
- code: WARNING_CODES.DICOM_UNSUPPORTED_CHARSET,
11841
- message: `Specific Character Set term "${term}" is not supported; decoding text as UTF-8 best-effort.`,
11842
- position
11843
- };
11898
+ function unsupportedCharset(position, valueIndex) {
11899
+ return build(WARNING_CODES.DICOM_UNSUPPORTED_CHARSET, position, { n: valueIndex });
11844
11900
  }
11845
11901
 
11846
11902
  // src/profiles/lookup.ts
@@ -11860,9 +11916,9 @@ var SPECIFIC_CHARACTER_SET_TAG = "00080005";
11860
11916
  function applySpecificCharacterSet(tag, value, ctx, emit, position) {
11861
11917
  if (tag !== SPECIFIC_CHARACTER_SET_TAG) return;
11862
11918
  const terms = parseSpecificCharacterSet(value);
11863
- for (const term of terms) {
11864
- if (!isKnownCharsetTerm(term)) emit(unsupportedCharset(position, term));
11865
- }
11919
+ terms.forEach((term, index) => {
11920
+ if (!isKnownCharsetTerm(term)) emit(unsupportedCharset(position, index + 1));
11921
+ });
11866
11922
  ctx.currentCharset = terms;
11867
11923
  }
11868
11924
  var LONG_FORM_VRS = /* @__PURE__ */ new Set([
@@ -11898,7 +11954,7 @@ function resolveImplicitVR(tag, ctx, emit, position) {
11898
11954
  const def = resolvePrivateTag(ctx.profile, tag, creator);
11899
11955
  if (def !== void 0) return def.vr;
11900
11956
  if (!ctx.profile.privateDictionary.has(creator)) {
11901
- emit(privateCreatorUnknown(position, tag, creator));
11957
+ emit(privateCreatorUnknown(position, tag));
11902
11958
  }
11903
11959
  }
11904
11960
  emit(implicitVRForPrivateTagWithoutVR(position, tag));
@@ -11923,6 +11979,9 @@ function resolvePrivateCreator(tag, ctx) {
11923
11979
  if (blockId < 16 || blockId > 255) return void 0;
11924
11980
  return ctx.creators.get(group)?.get(blockId);
11925
11981
  }
11982
+ function safeModelCreator(creator, ctx) {
11983
+ return creator === void 0 ? void 0 : safePrivateCreator(creator, ctx.profile);
11984
+ }
11926
11985
  function registerPrivateCreator(tag, value, ctx) {
11927
11986
  const group = parseInt(tag.slice(0, 4), 16);
11928
11987
  if (group % 2 === 0) return;
@@ -11982,8 +12041,7 @@ function readExplicitElementHeader(cursor, _ctx, emit) {
11982
12041
  const reserved1 = cursor.buffer[cursor.position + 1];
11983
12042
  cursor.position += 2;
11984
12043
  if ((reserved0 ?? 0) !== 0 || (reserved1 ?? 0) !== 0) {
11985
- const observed = (reserved0 ?? 0).toString(16).padStart(2, "0") + (reserved1 ?? 0).toString(16).padStart(2, "0");
11986
- emit(nonzeroReservedBytes({ byteOffset: headerStart }, tag, observed));
12044
+ emit(nonzeroReservedBytes({ byteOffset: headerStart }, tag, reserved0 ?? 0, reserved1 ?? 0));
11987
12045
  }
11988
12046
  length = cursor.readUInt32();
11989
12047
  headerLength = 12;
@@ -12619,6 +12677,13 @@ function parseSequence(buffer, valueStart, ctx, emit, opts) {
12619
12677
  ctx.currentCharset = parentCharset;
12620
12678
  }
12621
12679
  };
12680
+ const parentCreators = ctx.creators;
12681
+ const restoreParentCreators = () => {
12682
+ ctx.creators = parentCreators;
12683
+ };
12684
+ const beginItemCreatorScope = () => {
12685
+ ctx.creators = /* @__PURE__ */ new Map();
12686
+ };
12622
12687
  try {
12623
12688
  const cursor = new ByteCursor(buffer, opts.littleEndian, valueStart);
12624
12689
  const items = [];
@@ -12683,6 +12748,7 @@ function parseSequence(buffer, valueStart, ctx, emit, opts) {
12683
12748
  continue;
12684
12749
  }
12685
12750
  restoreParentCharset();
12751
+ beginItemCreatorScope();
12686
12752
  if (itemLength === UNDEFINED_LENGTH) {
12687
12753
  const inner = opts.innerStrategy(buffer, cursor.position, ctx, emit, {
12688
12754
  stopOnItemDelim: true
@@ -12710,9 +12776,39 @@ function parseSequence(buffer, valueStart, ctx, emit, opts) {
12710
12776
  ctx.nestingDepth -= 1;
12711
12777
  ctx.encodingContextStack.pop();
12712
12778
  restoreParentCharset();
12779
+ restoreParentCreators();
12780
+ }
12781
+ }
12782
+ function isStrictEscalation(err) {
12783
+ if (!(err instanceof DicomParseError)) return false;
12784
+ return Object.values(WARNING_CODES).includes(err.code);
12785
+ }
12786
+ function tryParseDefinedLengthSQ(buffer, valueStart, valueLength, ctx, emit, implicitLeInner, tag) {
12787
+ const savedDepth = ctx.nestingDepth;
12788
+ const savedStackLen = ctx.encodingContextStack.length;
12789
+ const savedWarningsLen = ctx.warnings.length;
12790
+ try {
12791
+ const slice = buffer.subarray(valueStart, valueStart + valueLength);
12792
+ const result = parseSequence(slice, 0, ctx, emit, {
12793
+ explicitLength: valueLength,
12794
+ littleEndian: true,
12795
+ innerStrategy: implicitLeInner
12796
+ });
12797
+ return { success: true, items: result.items };
12798
+ } catch (err) {
12799
+ if (isStrictEscalation(err)) throw err;
12800
+ ctx.nestingDepth = savedDepth;
12801
+ while (ctx.encodingContextStack.length > savedStackLen) {
12802
+ ctx.encodingContextStack.pop();
12803
+ }
12804
+ while (ctx.warnings.length > savedWarningsLen) {
12805
+ ctx.warnings.pop();
12806
+ }
12807
+ emit(sqNotDescended({ byteOffset: valueStart }, tag));
12808
+ return { success: false, items: [] };
12713
12809
  }
12714
12810
  }
12715
- function tryParseUnAsSQ(buffer, valueStart, valueLength, ctx, emit, implicitLeInner) {
12811
+ function tryParseUnAsSQ(buffer, valueStart, valueLength, ctx, emit, implicitLeInner, tag) {
12716
12812
  const savedDepth = ctx.nestingDepth;
12717
12813
  const savedStackLen = ctx.encodingContextStack.length;
12718
12814
  const savedWarningsLen = ctx.warnings.length;
@@ -12725,18 +12821,15 @@ function tryParseUnAsSQ(buffer, valueStart, valueLength, ctx, emit, implicitLeIn
12725
12821
  innerStrategy: implicitLeInner
12726
12822
  };
12727
12823
  const result = parseSequence(slice, 0, ctx, emit, opts);
12728
- emit(unParsedAsSQ({ byteOffset: valueStart }, "UN"));
12824
+ emit(unParsedAsSQ({ byteOffset: valueStart }, tag));
12729
12825
  return {
12730
12826
  success: true,
12731
12827
  items: result.items,
12732
12828
  endOffset: valueStart + result.endOffset
12733
12829
  };
12734
12830
  } catch (err) {
12735
- if (err instanceof DicomParseError) {
12736
- const warningCodeValues = Object.values(WARNING_CODES);
12737
- if (warningCodeValues.includes(err.code)) {
12738
- throw err;
12739
- }
12831
+ if (isStrictEscalation(err)) {
12832
+ throw err;
12740
12833
  }
12741
12834
  ctx.nestingDepth = savedDepth;
12742
12835
  while (ctx.encodingContextStack.length > savedStackLen) {
@@ -12797,7 +12890,7 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12797
12890
  const valueRawStart = headerStart;
12798
12891
  cursor.position = seq.endOffset;
12799
12892
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(valueRawStart, cursor.position)) : buffer.subarray(valueRawStart, cursor.position);
12800
- const privateCreator2 = resolvePrivateCreator(tag, ctx);
12893
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12801
12894
  elements.set(
12802
12895
  tag,
12803
12896
  new Element({
@@ -12814,6 +12907,40 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12814
12907
  );
12815
12908
  continue;
12816
12909
  }
12910
+ if (vr === "UN") {
12911
+ const cp246 = tryParseUnAsSQ(
12912
+ buffer,
12913
+ cursor.position,
12914
+ 4294967295,
12915
+ ctx,
12916
+ emit,
12917
+ parseImplicitLE,
12918
+ tag
12919
+ );
12920
+ if (cp246.success) {
12921
+ const valueRawStart = headerStart;
12922
+ cursor.position = cp246.endOffset;
12923
+ const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(valueRawStart, cursor.position)) : buffer.subarray(valueRawStart, cursor.position);
12924
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12925
+ elements.set(
12926
+ tag,
12927
+ new Element({
12928
+ tag,
12929
+ vr: "SQ",
12930
+ // Promoted, exactly as the Explicit-VR path promotes it.
12931
+ vm: cp246.items.length,
12932
+ length: 4294967295,
12933
+ rawBytes,
12934
+ byteOffset: headerStart,
12935
+ cp246Promoted: true,
12936
+ littleEndian: true,
12937
+ items: cp246.items,
12938
+ ...privateCreator2 !== void 0 ? { privateCreator: privateCreator2 } : {}
12939
+ })
12940
+ );
12941
+ continue;
12942
+ }
12943
+ }
12817
12944
  throw new DicomParseError(
12818
12945
  FATAL_CODES.INVALID_FILE_META,
12819
12946
  `Undefined length on non-SQ element ${tag} (vr=${vr}) under Implicit VR LE.`,
@@ -12833,6 +12960,35 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12833
12960
  const valueEnd = valueStart + length;
12834
12961
  const valueSlice = ctx.copyValues ? copyValueBytes(buffer.subarray(valueStart, valueEnd)) : buffer.subarray(valueStart, valueEnd);
12835
12962
  cursor.position = valueEnd;
12963
+ if (vr === "SQ") {
12964
+ const seq = tryParseDefinedLengthSQ(
12965
+ buffer,
12966
+ valueStart,
12967
+ length,
12968
+ ctx,
12969
+ emit,
12970
+ parseImplicitLE,
12971
+ tag
12972
+ );
12973
+ if (seq.success) {
12974
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12975
+ elements.set(
12976
+ tag,
12977
+ new Element({
12978
+ tag,
12979
+ vr,
12980
+ vm: seq.items.length,
12981
+ length,
12982
+ rawBytes: valueSlice,
12983
+ byteOffset: headerStart,
12984
+ littleEndian: true,
12985
+ items: seq.items,
12986
+ ...privateCreator2 !== void 0 ? { privateCreator: privateCreator2 } : {}
12987
+ })
12988
+ );
12989
+ continue;
12990
+ }
12991
+ }
12836
12992
  if (element === 0 && group !== 2) {
12837
12993
  emit(groupLengthInDataset(position, tag));
12838
12994
  }
@@ -12840,7 +12996,7 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12840
12996
  registerPrivateCreator(tag, valueSlice, ctx);
12841
12997
  }
12842
12998
  applySpecificCharacterSet(tag, valueSlice, ctx, emit, position);
12843
- const privateCreator = resolvePrivateCreator(tag, ctx);
12999
+ const privateCreator = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12844
13000
  elements.set(
12845
13001
  tag,
12846
13002
  new Element({
@@ -12960,7 +13116,7 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
12960
13116
  }
12961
13117
  cursor.position = seqResult.endOffset;
12962
13118
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, cursor.position)) : buffer.subarray(headerStart, cursor.position);
12963
- const privateCreator2 = resolvePrivateCreator(tag, ctx);
13119
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12964
13120
  elements.set(
12965
13121
  tag,
12966
13122
  new Element({
@@ -13012,12 +13168,13 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13012
13168
  UNDEFINED_LENGTH2,
13013
13169
  ctx,
13014
13170
  emit,
13015
- parseImplicitLE
13171
+ parseImplicitLE,
13172
+ tag
13016
13173
  );
13017
13174
  if (cp246.success) {
13018
13175
  cursor.position = cp246.endOffset;
13019
13176
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, cursor.position)) : buffer.subarray(headerStart, cursor.position);
13020
- const privateCreator3 = resolvePrivateCreator(tag, ctx);
13177
+ const privateCreator3 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13021
13178
  elements.set(
13022
13179
  tag,
13023
13180
  new Element({
@@ -13036,7 +13193,7 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13036
13193
  );
13037
13194
  continue;
13038
13195
  }
13039
- const privateCreator2 = resolvePrivateCreator(tag, ctx);
13196
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13040
13197
  const fallbackEnd = buffer.length;
13041
13198
  const fallbackBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, fallbackEnd)) : buffer.subarray(headerStart, fallbackEnd);
13042
13199
  elements.set(
@@ -13083,7 +13240,7 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13083
13240
  registerPrivateCreator(tag, valueSlice, ctx);
13084
13241
  }
13085
13242
  applySpecificCharacterSet(tag, valueSlice, ctx, emit, position);
13086
- const privateCreator = resolvePrivateCreator(tag, ctx);
13243
+ const privateCreator = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13087
13244
  const finalVr = vr;
13088
13245
  elements.set(
13089
13246
  tag,
@@ -13136,20 +13293,22 @@ function parseDeflatedLEWithCap(buffer, datasetStart, ctx, emit, maxInflatedByte
13136
13293
  buildSnippet(buffer, datasetStart)
13137
13294
  );
13138
13295
  }
13296
+ const zlibCode = typeof code === "string" && code.length > 0 ? code : "unknown";
13139
13297
  throw new DicomParseError(
13140
13298
  FATAL_CODES.INVALID_FILE_META,
13141
- `Failed to inflate Deflated TS payload: ${message}`,
13299
+ `Failed to inflate Deflated TS payload (zlib error code: ${zlibCode}).`,
13142
13300
  datasetStart,
13143
13301
  buildSnippet(buffer, datasetStart)
13144
13302
  );
13145
13303
  }
13146
13304
  const innerCtx = { ...ctx, buffer: inflated };
13305
+ const innerChokepoint = makeEmitter(innerCtx);
13147
13306
  const innerEmit = (w) => {
13148
13307
  const wrapped = {
13149
13308
  ...w,
13150
13309
  position: { ...w.position, deflated: true }
13151
13310
  };
13152
- emit(wrapped);
13311
+ innerChokepoint(wrapped);
13153
13312
  };
13154
13313
  const result = parseExplicitLE(inflated, 0, innerCtx, innerEmit);
13155
13314
  return { elements: result.elements, endOffset: buffer.length };
@@ -13176,6 +13335,7 @@ var TRANSFER_SYNTAX_PARSERS = Object.freeze({
13176
13335
  });
13177
13336
 
13178
13337
  // src/parser/index.ts
13338
+ var SUPPORTED_TRANSFER_SYNTAXES = Object.keys(TRANSFER_SYNTAX_PARSERS).join(", ");
13179
13339
  function parseDicom(input, options = {}) {
13180
13340
  if (rawInputIsEmpty(input)) {
13181
13341
  throw new DicomParseError(FATAL_CODES.EMPTY_INPUT, "Input is empty.", 0, "");
@@ -13198,9 +13358,10 @@ function parseDicom(input, options = {}) {
13198
13358
  const strategy = TRANSFER_SYNTAX_PARSERS[tsUid];
13199
13359
  if (strategy === void 0) {
13200
13360
  const tsName = uid(tsUid)?.name ?? "";
13361
+ const named = tsName.length > 0 ? `Transfer Syntax ${tsName}` : "The Transfer Syntax UID";
13201
13362
  throw new DicomParseError(
13202
13363
  FATAL_CODES.UNSUPPORTED_TRANSFER_SYNTAX,
13203
- `Transfer Syntax UID "${tsUid}" is not supported by @cosyte/dicom v1.`,
13364
+ `${named} in (0002,0010) is not supported by @cosyte/dicom v1 (supported: ${SUPPORTED_TRANSFER_SYNTAXES}).`,
13204
13365
  fileMetaEnd,
13205
13366
  tsName.length > 0 ? tsName : buildSnippet(buffer, fileMetaEnd)
13206
13367
  );
@@ -13438,7 +13599,7 @@ function serializeDicom(ds) {
13438
13599
  if (encoding === void 0) {
13439
13600
  throw new DicomSerializeError(
13440
13601
  SERIALIZE_ERROR_CODES.UNSUPPORTED_TRANSFER_SYNTAX,
13441
- `Transfer Syntax UID "${tsUid}" is not supported by the @cosyte/dicom v1 writer.`
13602
+ `The Dataset's File Meta Transfer Syntax UID is not supported by the @cosyte/dicom v1 writer (supported: ${Object.keys(BODY_ENCODING).join(", ")}).`
13442
13603
  );
13443
13604
  }
13444
13605
  const preamble = part10Preamble();
@@ -14537,6 +14698,132 @@ function uidValueMultiplicity(rawBytes) {
14537
14698
  return raw.split("\\").length;
14538
14699
  }
14539
14700
 
14701
+ // src/deident/embedded.ts
14702
+ var KNOWN_VRS2 = new Set(Object.keys(BE_VR_STRIDE));
14703
+ var UNDEFINED_LENGTH4 = 4294967295;
14704
+ var FFFE_GROUP = 65534;
14705
+ var SCANNABLE_CARRIER_VRS = /* @__PURE__ */ new Set([
14706
+ "AE",
14707
+ "AS",
14708
+ "CS",
14709
+ "DA",
14710
+ "DS",
14711
+ "DT",
14712
+ "IS",
14713
+ "LO",
14714
+ "LT",
14715
+ "PN",
14716
+ "SH",
14717
+ "ST",
14718
+ "TM",
14719
+ "UC",
14720
+ "UI",
14721
+ "UR",
14722
+ "UT"
14723
+ ]);
14724
+ var ALLOWED_CONTROL_BYTES = /* @__PURE__ */ new Set([
14725
+ 9,
14726
+ // TAB
14727
+ 10,
14728
+ // LF
14729
+ 12,
14730
+ // FF
14731
+ 13,
14732
+ // CR
14733
+ 27
14734
+ // ESC
14735
+ ]);
14736
+ var CONTROL_TOLERANT_VRS = /* @__PURE__ */ new Set(["LT", "ST", "UT"]);
14737
+ var ESC_ONLY_VRS = /* @__PURE__ */ new Set(["LO", "SH", "UC", "PN"]);
14738
+ var MAX_SCAN_BYTES = 1 << 20;
14739
+ var MIN_HEADER = 8;
14740
+ function readU16(buf, off, littleEndian) {
14741
+ return littleEndian ? buf.readUInt16LE(off) : buf.readUInt16BE(off);
14742
+ }
14743
+ function readU32(buf, off, littleEndian) {
14744
+ return littleEndian ? buf.readUInt32LE(off) : buf.readUInt32BE(off);
14745
+ }
14746
+ function decodeAt(value, off, encoding) {
14747
+ if (off + MIN_HEADER > value.length) return void 0;
14748
+ const littleEndian = encoding !== "explicitBE";
14749
+ const group = readU16(value, off, littleEndian);
14750
+ const element = readU16(value, off + 2, littleEndian);
14751
+ if (group === FFFE_GROUP) {
14752
+ return { next: off + 8, tag: void 0 };
14753
+ }
14754
+ if (group === 0 && element === 0) return void 0;
14755
+ const tag = joinTag(group, element);
14756
+ if (encoding === "implicit") {
14757
+ const length2 = readU32(value, off + 4, littleEndian);
14758
+ if (length2 === UNDEFINED_LENGTH4 || length2 % 2 !== 0) return void 0;
14759
+ const next2 = off + 8 + length2;
14760
+ return next2 > value.length ? void 0 : { next: next2, tag };
14761
+ }
14762
+ const vr = value.toString("latin1", off + 4, off + 6);
14763
+ if (!KNOWN_VRS2.has(vr)) return void 0;
14764
+ if (LONG_FORM_VRS.has(vr)) {
14765
+ if (off + 12 > value.length) return void 0;
14766
+ if (value[off + 6] !== 0 || value[off + 7] !== 0) return void 0;
14767
+ const length2 = readU32(value, off + 8, littleEndian);
14768
+ if (length2 === UNDEFINED_LENGTH4 || length2 % 2 !== 0) return void 0;
14769
+ const next2 = off + 12 + length2;
14770
+ return next2 > value.length ? void 0 : { next: next2, tag };
14771
+ }
14772
+ const length = readU16(value, off + 6, littleEndian);
14773
+ if (length % 2 !== 0) return void 0;
14774
+ const next = off + 8 + length;
14775
+ return next > value.length ? void 0 : { next, tag };
14776
+ }
14777
+ function isScannableCarrier(vr) {
14778
+ return SCANNABLE_CARRIER_VRS.has(vr);
14779
+ }
14780
+ function hasByteOutsideRepertoire(region, carrierVr) {
14781
+ const allFive = CONTROL_TOLERANT_VRS.has(carrierVr);
14782
+ const escOnly = ESC_ONLY_VRS.has(carrierVr);
14783
+ for (const byte of region) {
14784
+ if (byte >= 32 && byte !== 127) continue;
14785
+ if (allFive && ALLOWED_CONTROL_BYTES.has(byte)) continue;
14786
+ if (escOnly && byte === 27) continue;
14787
+ return true;
14788
+ }
14789
+ return false;
14790
+ }
14791
+ function findEmbeddedAttributes(value, carrierVr, encoding, isActionable) {
14792
+ if (!isScannableCarrier(carrierVr)) return void 0;
14793
+ if (value.length < MIN_HEADER) return void 0;
14794
+ const windowStart = value.length > MAX_SCAN_BYTES ? value.length - MAX_SCAN_BYTES : 0;
14795
+ const window = value.subarray(windowStart);
14796
+ const n = window.length;
14797
+ const half = (n >> 1) + 1;
14798
+ const tiles = new Uint8Array(half);
14799
+ const hit = new Uint8Array(half);
14800
+ for (let off = n - MIN_HEADER & -2; off >= 0; off -= 2) {
14801
+ const decoded = decodeAt(window, off, encoding);
14802
+ if (decoded === void 0) continue;
14803
+ const atEnd = decoded.next === n;
14804
+ const onward = decoded.next < n ? tiles[decoded.next >> 1] === 1 : false;
14805
+ if (!atEnd && !onward) continue;
14806
+ tiles[off >> 1] = 1;
14807
+ const actionable = decoded.tag !== void 0 && isActionable(decoded.tag);
14808
+ const onwardHit = decoded.next < n ? hit[decoded.next >> 1] === 1 : false;
14809
+ if (actionable || onwardHit) hit[off >> 1] = 1;
14810
+ }
14811
+ for (let off = 0; off + MIN_HEADER <= n; off += 2) {
14812
+ if (hit[off >> 1] !== 1) continue;
14813
+ if (!hasByteOutsideRepertoire(window.subarray(off), carrierVr)) return void 0;
14814
+ const tags = [];
14815
+ let pos = off;
14816
+ while (pos < n) {
14817
+ const decoded = decodeAt(window, pos, encoding);
14818
+ if (decoded === void 0) break;
14819
+ if (decoded.tag !== void 0) tags.push(decoded.tag);
14820
+ pos = decoded.next;
14821
+ }
14822
+ return tags;
14823
+ }
14824
+ return void 0;
14825
+ }
14826
+
14540
14827
  // src/deident/types.ts
14541
14828
  var DEIDENTIFY_OPTIONS = Object.freeze([
14542
14829
  "CleanGraphics",
@@ -14608,6 +14895,9 @@ var BODY_ENCODING2 = {
14608
14895
  "1.2.840.10008.1.2.2": "explicitBE",
14609
14896
  "1.2.840.10008.1.2.1.99": "explicitLE"
14610
14897
  };
14898
+ var MAX_UNAUDITABLE_SEQUENCE_FINDINGS = 64;
14899
+ var MAX_UNDEFINED_VR_FINDINGS = 64;
14900
+ var KNOWN_VRS3 = new Set(Object.keys(BE_VR_STRIDE));
14611
14901
  function validateRetain(retain) {
14612
14902
  const valid = new Set(DEIDENTIFY_OPTIONS);
14613
14903
  const active = /* @__PURE__ */ new Set();
@@ -14721,12 +15011,83 @@ function isPrivateCreatorElement(tag) {
14721
15011
  const { element } = splitTag(tag);
14722
15012
  return element >= 16 && element <= 255;
14723
15013
  }
14724
- function keepsPrivate(el, ctx) {
15014
+ function actsOnTag(tag, ctx) {
15015
+ if (isPrivateTag(tag)) return true;
15016
+ const action = annexE(tag);
15017
+ if (action === void 0) return false;
15018
+ return resolveAction(effectiveCode(action, ctx.active)) !== "K";
15019
+ }
15020
+ function hasUndefinedVr(el) {
15021
+ return !KNOWN_VRS3.has(el.vr);
15022
+ }
15023
+ function keepOrEmpty(el, ctx, contextPath, out) {
15024
+ if (hasUndefinedVr(el)) {
15025
+ emptyUndefinedVrElement(el, ctx, contextPath, out);
15026
+ return false;
15027
+ }
15028
+ const hidden = findEmbeddedAttributes(el.rawBytes, el.vr, ctx.encoding, (t) => actsOnTag(t, ctx));
15029
+ if (hidden === void 0) {
15030
+ out.elements.set(el.tag, el);
15031
+ return true;
15032
+ }
15033
+ out.elements.set(el.tag, freshScalar(el, buffer.Buffer.alloc(0), 0));
15034
+ out.embeddedAttributes.push({
15035
+ tag: el.tag,
15036
+ vr: el.vr,
15037
+ hidden,
15038
+ ...contextPath.length > 0 ? { contextPath: [...contextPath] } : {}
15039
+ });
15040
+ out.warnings.push(
15041
+ embeddedAttributeRemoved({ byteOffset: el.byteOffset }, el.tag, el.vr, hidden.length)
15042
+ );
15043
+ return false;
15044
+ }
15045
+ function emptyUndefinedVrElement(el, ctx, contextPath, out) {
15046
+ out.elements.set(el.tag, freshScalar(el, buffer.Buffer.alloc(0), 0));
15047
+ if (ctx.budget.undefinedVrElements >= MAX_UNDEFINED_VR_FINDINGS) return;
15048
+ ctx.budget.undefinedVrElements += 1;
15049
+ out.undefinedVrElements.push({
15050
+ byteOffset: el.byteOffset,
15051
+ byteLength: el.rawBytes.length,
15052
+ ...contextPath.length > 0 ? { contextPath: [...contextPath] } : {}
15053
+ });
15054
+ out.warnings.push(undefinedVrNotAuditable({ byteOffset: el.byteOffset }, el.rawBytes.length));
15055
+ }
15056
+ function isUnauditableSequence(el) {
15057
+ return el.vr === "SQ" && el.items === void 0;
15058
+ }
15059
+ function emptyUnauditableSequence(el, ctx, contextPath, out) {
15060
+ out.elements.set(el.tag, rebuildSequence(el, [], ctx.encoding));
15061
+ if (ctx.budget.unauditableSequences >= MAX_UNAUDITABLE_SEQUENCE_FINDINGS) return;
15062
+ ctx.budget.unauditableSequences += 1;
15063
+ out.unauditableSequences.push({
15064
+ tag: el.tag,
15065
+ byteLength: el.rawBytes.length,
15066
+ ...contextPath.length > 0 ? { contextPath: [...contextPath] } : {}
15067
+ });
15068
+ out.warnings.push(
15069
+ sequenceNotAuditable({ byteOffset: el.byteOffset }, el.tag, el.rawBytes.length)
15070
+ );
15071
+ }
15072
+ function creatorsInScope(source) {
15073
+ const byBlock = /* @__PURE__ */ new Map();
15074
+ for (const el of source) {
15075
+ if (!isPrivateTag(el.tag) || !isPrivateCreatorElement(el.tag)) continue;
15076
+ const { group, element } = splitTag(el.tag);
15077
+ byBlock.set(`${String(group)}:${String(element & 255)}`, decodeCreator(el));
15078
+ }
15079
+ return byBlock;
15080
+ }
15081
+ function creatorFor(tag, creators) {
15082
+ const { group, element } = splitTag(tag);
15083
+ return creators.get(`${String(group)}:${String(element >> 8 & 255)}`);
15084
+ }
15085
+ function keepsPrivate(el, ctx, creators) {
14725
15086
  if (!ctx.active.has("RetainSafePrivate") || ctx.profile === void 0) return false;
14726
15087
  if (isPrivateCreatorElement(el.tag)) {
14727
15088
  return ctx.profile.privateDictionary.has(decodeCreator(el));
14728
15089
  }
14729
- const creator = el.privateCreator;
15090
+ const creator = creatorFor(el.tag, creators);
14730
15091
  if (creator === void 0) return false;
14731
15092
  return resolvePrivateTag(ctx.profile, el.tag, creator) !== void 0;
14732
15093
  }
@@ -14734,20 +15095,26 @@ function processElements(source, ctx, contextPath) {
14734
15095
  const out = {
14735
15096
  elements: /* @__PURE__ */ new Map(),
14736
15097
  attributes: [],
14737
- removedPrivateTags: []
15098
+ removedPrivateTags: [],
15099
+ embeddedAttributes: [],
15100
+ unauditableSequences: [],
15101
+ undefinedVrElements: [],
15102
+ warnings: []
14738
15103
  };
15104
+ const creators = creatorsInScope(source);
14739
15105
  for (const el of source) {
14740
15106
  if (isPrivateTag(el.tag)) {
14741
- if (keepsPrivate(el, ctx)) out.elements.set(el.tag, el);
15107
+ if (keepsPrivate(el, ctx, creators)) keepOrEmpty(el, ctx, contextPath, out);
14742
15108
  else out.removedPrivateTags.push(el.tag);
14743
15109
  continue;
14744
15110
  }
14745
15111
  const action = annexE(el.tag);
14746
15112
  if (action === void 0) {
14747
- if (el.vr === "SQ" && el.items !== void 0) {
14748
- out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
15113
+ if (el.vr === "SQ") {
15114
+ if (isUnauditableSequence(el)) emptyUnauditableSequence(el, ctx, contextPath, out);
15115
+ else out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
14749
15116
  } else {
14750
- out.elements.set(el.tag, el);
15117
+ keepOrEmpty(el, ctx, contextPath, out);
14751
15118
  }
14752
15119
  continue;
14753
15120
  }
@@ -14759,8 +15126,7 @@ function processElements(source, ctx, contextPath) {
14759
15126
  let applied;
14760
15127
  switch (resolved) {
14761
15128
  case "K":
14762
- out.elements.set(el.tag, el);
14763
- applied = "kept";
15129
+ applied = keepOrEmpty(el, ctx, contextPath, out) ? "kept" : "emptied";
14764
15130
  break;
14765
15131
  case "X":
14766
15132
  applied = "removed";
@@ -14802,6 +15168,10 @@ function descendSequence(el, ctx, contextPath, out) {
14802
15168
  const inner = processElements(item.elements(), ctx, childPath);
14803
15169
  out.attributes.push(...inner.attributes);
14804
15170
  out.removedPrivateTags.push(...inner.removedPrivateTags);
15171
+ out.embeddedAttributes.push(...inner.embeddedAttributes);
15172
+ out.unauditableSequences.push(...inner.unauditableSequences);
15173
+ out.undefinedVrElements.push(...inner.undefinedVrElements);
15174
+ out.warnings.push(...inner.warnings);
14805
15175
  newItems.push(new Item({ index, warnings: [], elements: inner.elements }));
14806
15176
  });
14807
15177
  return rebuildSequence(el, newItems, ctx.encoding);
@@ -14818,13 +15188,23 @@ function applySequenceAction(el, resolved, action, ctx, contextPath, out) {
14818
15188
  applied = "emptied";
14819
15189
  break;
14820
15190
  case "C":
14821
- out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
14822
- applied = "cleaned";
15191
+ if (isUnauditableSequence(el)) {
15192
+ emptyUnauditableSequence(el, ctx, contextPath, out);
15193
+ applied = "emptied";
15194
+ } else {
15195
+ out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
15196
+ applied = "cleaned";
15197
+ }
14823
15198
  break;
14824
15199
  case "U":
14825
15200
  case "K":
14826
- out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
14827
- applied = "kept";
15201
+ if (isUnauditableSequence(el)) {
15202
+ emptyUnauditableSequence(el, ctx, contextPath, out);
15203
+ applied = "emptied";
15204
+ } else {
15205
+ out.elements.set(el.tag, descendSequence(el, ctx, contextPath, out));
15206
+ applied = "kept";
15207
+ }
14828
15208
  break;
14829
15209
  }
14830
15210
  out.attributes.push(auditAttribute(el.tag, action, resolved, applied, contextPath));
@@ -14870,8 +15250,18 @@ function deidentify(ds, options = {}) {
14870
15250
  active,
14871
15251
  remap,
14872
15252
  profile: options.profile,
14873
- encoding};
14874
- const { elements, attributes, removedPrivateTags } = processElements(ds.elements(), ctx, []);
15253
+ encoding,
15254
+ budget: { unauditableSequences: 0, undefinedVrElements: 0 }
15255
+ };
15256
+ const processed = processElements(ds.elements(), ctx, []);
15257
+ const {
15258
+ elements,
15259
+ attributes,
15260
+ removedPrivateTags,
15261
+ embeddedAttributes,
15262
+ unauditableSequences,
15263
+ undefinedVrElements
15264
+ } = processed;
14875
15265
  elements.set(
14876
15266
  TAG_PATIENT_IDENTITY_REMOVED,
14877
15267
  insertedScalar(TAG_PATIENT_IDENTITY_REMOVED, "CS", buffer.Buffer.from("YES", "latin1"), littleEndian)
@@ -14881,7 +15271,7 @@ function deidentify(ds, options = {}) {
14881
15271
  TAG_DEIDENTIFICATION_METHOD,
14882
15272
  insertedScalar(TAG_DEIDENTIFICATION_METHOD, "LO", buffer.Buffer.from(method, "latin1"), littleEndian)
14883
15273
  );
14884
- const warnings = [];
15274
+ const warnings = [...processed.warnings];
14885
15275
  if (hasUncleanedBurnedIn(ds)) {
14886
15276
  const offset = ds.get(TAG_PIXEL_DATA)?.byteOffset ?? 0;
14887
15277
  warnings.push(burnedInAnnotationNotRemoved({ byteOffset: offset, fileMeta: false }));
@@ -14896,6 +15286,9 @@ function deidentify(ds, options = {}) {
14896
15286
  const report = {
14897
15287
  attributes,
14898
15288
  removedPrivateTags,
15289
+ embeddedAttributes,
15290
+ unauditableSequences,
15291
+ undefinedVrElements,
14899
15292
  uidMap: remap.cache,
14900
15293
  warnings,
14901
15294
  retained: [...active]