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