@cosyte/dicom 0.0.4 → 0.0.6

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.4";
12
+ var VERSION = "0.0.6";
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",
@@ -11686,159 +11736,123 @@ var WARNING_CODES = {
11686
11736
  DICOM_PRIVATE_CREATOR_UNKNOWN: "DICOM_PRIVATE_CREATOR_UNKNOWN"
11687
11737
  // reserved by Phase 6 - not emitted in Phase 2
11688
11738
  };
11739
+ var WARNING_MESSAGES = Object.freeze({
11740
+ DICOM_MISSING_PREAMBLE: "No DICM magic at offset 128; falling back to offset-0 dataset.",
11741
+ DICOM_FILE_META_GROUP_LENGTH_MISSING: "(0002,0000) FileMetaInformationGroupLength missing; scanning forward to first non-(0002,xxxx) element.",
11742
+ DICOM_FILE_META_GROUP_LENGTH_MISMATCH: "(0002,0000) FileMetaInformationGroupLength declared {n} bytes; actual File Meta group size is {n2} bytes. Trusting actual.",
11743
+ DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR: "Element ({tag}) uses undefined length (0xFFFFFFFF) under an Explicit VR transfer syntax.",
11744
+ DICOM_ODD_LENGTH_VALUE_PADDED: "Element ({tag}) has odd declared length {n}; cursor advanced by one padding byte to maintain alignment.",
11745
+ DICOM_VR_MISMATCH: "Element ({tag}) on-wire VR is {vr2}; dictionary lists {vr}. Trusting on-wire VR.",
11746
+ DICOM_PRIVATE_TAG_NO_CREATOR: "Private element ({tag}) has no Private Creator registered for its block; treating as VR=UN.",
11747
+ DICOM_GROUP_LENGTH_IN_DATASET: "Retired Group Length element ({tag}) encountered in dataset; preserved as-is.",
11748
+ DICOM_NONZERO_RESERVED_BYTES: "Element ({tag}) has non-zero reserved bytes between VR and length (first byte {n}, second byte {n2}); ignoring.",
11749
+ 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 or de-identification.",
11750
+ DICOM_UN_PARSED_AS_SQ: "Element ({tag}) has VR=UN with undefined length; descended as Implicit VR LE sequence per CP-246.",
11751
+ DICOM_EMPTY_ITEM_IN_SEQUENCE: "Sequence ({tag}) contains an empty item (length=0); tolerated.",
11752
+ DICOM_PIXEL_DATA_LENGTH_MISMATCH: "(7FE0,0010) PixelData declared length {n} does not match computed {n2} bytes.",
11753
+ DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "Private element ({tag}) under Implicit VR LE has no VR override; falling back to UN.",
11754
+ 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.",
11755
+ 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.",
11756
+ DICOM_BOM_IN_TEXT_VR: "Element ({tag}) {vr} value begins with a UTF-8 BOM; stripped on decode.",
11757
+ DICOM_TRAILING_NULL_IN_TEXT_VR: "Element ({tag}) {vr} value has a trailing NULL pad where SPACE is expected; trimmed.",
11758
+ DICOM_UI_TRAILING_SPACE: "Element ({tag}) UI value is SPACE-padded; UI requires NULL padding (PS3.5 \xA76.2). Trimmed.",
11759
+ 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.",
11760
+ DICOM_IS_NONINTEGER_VALUE: "Element ({tag}) IS value is not a base-10 integer; surfaced as null with raw bytes preserved.",
11761
+ DICOM_DA_LEGACY_FORMAT: "Element ({tag}) DA value is not in canonical YYYYMMDD form; decoded best-effort, raw preserved.",
11762
+ DICOM_DT_NONSTANDARD_OFFSET: "Element ({tag}) DT value has a non-standard UTC offset; decoded best-effort, raw preserved.",
11763
+ 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.",
11764
+ 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."
11765
+ });
11766
+ var KNOWN_VRS = new Set(Object.keys(BE_VR_STRIDE));
11767
+ var TAG_SHAPE = /^[0-9A-F]{8}$/u;
11768
+ function renderTag(tag) {
11769
+ return tag !== void 0 && TAG_SHAPE.test(tag) ? tag : WITHHELD;
11770
+ }
11771
+ function renderVr(vr) {
11772
+ return vr !== void 0 && KNOWN_VRS.has(vr) ? vr : WITHHELD;
11773
+ }
11774
+ function build(code, position, tokens = {}) {
11775
+ 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));
11776
+ return { code, message, position };
11777
+ }
11689
11778
  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
- };
11779
+ return build(WARNING_CODES.DICOM_MISSING_PREAMBLE, position);
11695
11780
  }
11696
11781
  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
- };
11782
+ return build(WARNING_CODES.DICOM_FILE_META_GROUP_LENGTH_MISSING, position);
11702
11783
  }
11703
11784
  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
- };
11785
+ return build(WARNING_CODES.DICOM_FILE_META_GROUP_LENGTH_MISMATCH, position, {
11786
+ n: declared,
11787
+ n2: actual
11788
+ });
11709
11789
  }
11710
11790
  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
- };
11791
+ return build(WARNING_CODES.DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR, position, { tag });
11716
11792
  }
11717
11793
  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
- };
11794
+ return build(WARNING_CODES.DICOM_ODD_LENGTH_VALUE_PADDED, position, {
11795
+ tag,
11796
+ n: declaredLength
11797
+ });
11723
11798
  }
11724
11799
  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
- };
11800
+ return build(WARNING_CODES.DICOM_VR_MISMATCH, position, { tag, vr: dictVR, vr2: fileVR });
11730
11801
  }
11731
11802
  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
- };
11803
+ return build(WARNING_CODES.DICOM_PRIVATE_TAG_NO_CREATOR, position, { tag });
11737
11804
  }
11738
11805
  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
- };
11806
+ return build(WARNING_CODES.DICOM_GROUP_LENGTH_IN_DATASET, position, { tag });
11744
11807
  }
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
- };
11808
+ function nonzeroReservedBytes(position, tag, first, second) {
11809
+ return build(WARNING_CODES.DICOM_NONZERO_RESERVED_BYTES, position, {
11810
+ tag,
11811
+ n: first,
11812
+ n2: second
11813
+ });
11751
11814
  }
11752
11815
  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
- };
11816
+ return build(WARNING_CODES.DICOM_UN_PARSED_AS_SQ, position, { tag });
11817
+ }
11818
+ function sqNotDescended(position, tag) {
11819
+ return build(WARNING_CODES.DICOM_SQ_NOT_DESCENDED, position, { tag });
11758
11820
  }
11759
11821
  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
- };
11822
+ return build(WARNING_CODES.DICOM_EMPTY_ITEM_IN_SEQUENCE, position, { tag });
11765
11823
  }
11766
11824
  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
- };
11825
+ return build(WARNING_CODES.DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR, position, { tag });
11772
11826
  }
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
- };
11827
+ function privateCreatorUnknown(position, tag) {
11828
+ return build(WARNING_CODES.DICOM_PRIVATE_CREATOR_UNKNOWN, position, { tag });
11779
11829
  }
11780
11830
  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
- };
11831
+ return build(WARNING_CODES.DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED, position);
11786
11832
  }
11787
11833
  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
- };
11834
+ return build(WARNING_CODES.DICOM_BOM_IN_TEXT_VR, position, { tag, vr });
11793
11835
  }
11794
11836
  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
- };
11837
+ return build(WARNING_CODES.DICOM_TRAILING_NULL_IN_TEXT_VR, position, { tag, vr });
11800
11838
  }
11801
11839
  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
- };
11840
+ return build(WARNING_CODES.DICOM_UI_TRAILING_SPACE, position, { tag });
11807
11841
  }
11808
11842
  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
- };
11843
+ return build(WARNING_CODES.DICOM_NON_ASCII_IN_ASCII_VR, position, { tag, vr });
11814
11844
  }
11815
11845
  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
- };
11846
+ return build(WARNING_CODES.DICOM_IS_NONINTEGER_VALUE, position, { tag });
11821
11847
  }
11822
11848
  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
- };
11849
+ return build(WARNING_CODES.DICOM_DA_LEGACY_FORMAT, position, { tag });
11828
11850
  }
11829
11851
  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
- };
11852
+ return build(WARNING_CODES.DICOM_DT_NONSTANDARD_OFFSET, position, { tag });
11835
11853
  }
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
- };
11854
+ function unsupportedCharset(position, valueIndex) {
11855
+ return build(WARNING_CODES.DICOM_UNSUPPORTED_CHARSET, position, { n: valueIndex });
11842
11856
  }
11843
11857
 
11844
11858
  // src/profiles/lookup.ts
@@ -11858,9 +11872,9 @@ var SPECIFIC_CHARACTER_SET_TAG = "00080005";
11858
11872
  function applySpecificCharacterSet(tag, value, ctx, emit, position) {
11859
11873
  if (tag !== SPECIFIC_CHARACTER_SET_TAG) return;
11860
11874
  const terms = parseSpecificCharacterSet(value);
11861
- for (const term of terms) {
11862
- if (!isKnownCharsetTerm(term)) emit(unsupportedCharset(position, term));
11863
- }
11875
+ terms.forEach((term, index) => {
11876
+ if (!isKnownCharsetTerm(term)) emit(unsupportedCharset(position, index + 1));
11877
+ });
11864
11878
  ctx.currentCharset = terms;
11865
11879
  }
11866
11880
  var LONG_FORM_VRS = /* @__PURE__ */ new Set([
@@ -11896,7 +11910,7 @@ function resolveImplicitVR(tag, ctx, emit, position) {
11896
11910
  const def = resolvePrivateTag(ctx.profile, tag, creator);
11897
11911
  if (def !== void 0) return def.vr;
11898
11912
  if (!ctx.profile.privateDictionary.has(creator)) {
11899
- emit(privateCreatorUnknown(position, tag, creator));
11913
+ emit(privateCreatorUnknown(position, tag));
11900
11914
  }
11901
11915
  }
11902
11916
  emit(implicitVRForPrivateTagWithoutVR(position, tag));
@@ -11921,6 +11935,9 @@ function resolvePrivateCreator(tag, ctx) {
11921
11935
  if (blockId < 16 || blockId > 255) return void 0;
11922
11936
  return ctx.creators.get(group)?.get(blockId);
11923
11937
  }
11938
+ function safeModelCreator(creator, ctx) {
11939
+ return creator === void 0 ? void 0 : safePrivateCreator(creator, ctx.profile);
11940
+ }
11924
11941
  function registerPrivateCreator(tag, value, ctx) {
11925
11942
  const group = parseInt(tag.slice(0, 4), 16);
11926
11943
  if (group % 2 === 0) return;
@@ -11980,8 +11997,7 @@ function readExplicitElementHeader(cursor, _ctx, emit) {
11980
11997
  const reserved1 = cursor.buffer[cursor.position + 1];
11981
11998
  cursor.position += 2;
11982
11999
  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));
12000
+ emit(nonzeroReservedBytes({ byteOffset: headerStart }, tag, reserved0 ?? 0, reserved1 ?? 0));
11985
12001
  }
11986
12002
  length = cursor.readUInt32();
11987
12003
  headerLength = 12;
@@ -12617,6 +12633,13 @@ function parseSequence(buffer, valueStart, ctx, emit, opts) {
12617
12633
  ctx.currentCharset = parentCharset;
12618
12634
  }
12619
12635
  };
12636
+ const parentCreators = ctx.creators;
12637
+ const restoreParentCreators = () => {
12638
+ ctx.creators = parentCreators;
12639
+ };
12640
+ const beginItemCreatorScope = () => {
12641
+ ctx.creators = /* @__PURE__ */ new Map();
12642
+ };
12620
12643
  try {
12621
12644
  const cursor = new ByteCursor(buffer, opts.littleEndian, valueStart);
12622
12645
  const items = [];
@@ -12681,6 +12704,7 @@ function parseSequence(buffer, valueStart, ctx, emit, opts) {
12681
12704
  continue;
12682
12705
  }
12683
12706
  restoreParentCharset();
12707
+ beginItemCreatorScope();
12684
12708
  if (itemLength === UNDEFINED_LENGTH) {
12685
12709
  const inner = opts.innerStrategy(buffer, cursor.position, ctx, emit, {
12686
12710
  stopOnItemDelim: true
@@ -12708,9 +12732,39 @@ function parseSequence(buffer, valueStart, ctx, emit, opts) {
12708
12732
  ctx.nestingDepth -= 1;
12709
12733
  ctx.encodingContextStack.pop();
12710
12734
  restoreParentCharset();
12735
+ restoreParentCreators();
12711
12736
  }
12712
12737
  }
12713
- function tryParseUnAsSQ(buffer, valueStart, valueLength, ctx, emit, implicitLeInner) {
12738
+ function isStrictEscalation(err) {
12739
+ if (!(err instanceof DicomParseError)) return false;
12740
+ return Object.values(WARNING_CODES).includes(err.code);
12741
+ }
12742
+ function tryParseDefinedLengthSQ(buffer, valueStart, valueLength, ctx, emit, implicitLeInner, tag) {
12743
+ const savedDepth = ctx.nestingDepth;
12744
+ const savedStackLen = ctx.encodingContextStack.length;
12745
+ const savedWarningsLen = ctx.warnings.length;
12746
+ try {
12747
+ const slice = buffer.subarray(valueStart, valueStart + valueLength);
12748
+ const result = parseSequence(slice, 0, ctx, emit, {
12749
+ explicitLength: valueLength,
12750
+ littleEndian: true,
12751
+ innerStrategy: implicitLeInner
12752
+ });
12753
+ return { success: true, items: result.items };
12754
+ } catch (err) {
12755
+ if (isStrictEscalation(err)) throw err;
12756
+ ctx.nestingDepth = savedDepth;
12757
+ while (ctx.encodingContextStack.length > savedStackLen) {
12758
+ ctx.encodingContextStack.pop();
12759
+ }
12760
+ while (ctx.warnings.length > savedWarningsLen) {
12761
+ ctx.warnings.pop();
12762
+ }
12763
+ emit(sqNotDescended({ byteOffset: valueStart }, tag));
12764
+ return { success: false, items: [] };
12765
+ }
12766
+ }
12767
+ function tryParseUnAsSQ(buffer, valueStart, valueLength, ctx, emit, implicitLeInner, tag) {
12714
12768
  const savedDepth = ctx.nestingDepth;
12715
12769
  const savedStackLen = ctx.encodingContextStack.length;
12716
12770
  const savedWarningsLen = ctx.warnings.length;
@@ -12723,18 +12777,15 @@ function tryParseUnAsSQ(buffer, valueStart, valueLength, ctx, emit, implicitLeIn
12723
12777
  innerStrategy: implicitLeInner
12724
12778
  };
12725
12779
  const result = parseSequence(slice, 0, ctx, emit, opts);
12726
- emit(unParsedAsSQ({ byteOffset: valueStart }, "UN"));
12780
+ emit(unParsedAsSQ({ byteOffset: valueStart }, tag));
12727
12781
  return {
12728
12782
  success: true,
12729
12783
  items: result.items,
12730
12784
  endOffset: valueStart + result.endOffset
12731
12785
  };
12732
12786
  } catch (err) {
12733
- if (err instanceof DicomParseError) {
12734
- const warningCodeValues = Object.values(WARNING_CODES);
12735
- if (warningCodeValues.includes(err.code)) {
12736
- throw err;
12737
- }
12787
+ if (isStrictEscalation(err)) {
12788
+ throw err;
12738
12789
  }
12739
12790
  ctx.nestingDepth = savedDepth;
12740
12791
  while (ctx.encodingContextStack.length > savedStackLen) {
@@ -12795,7 +12846,7 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12795
12846
  const valueRawStart = headerStart;
12796
12847
  cursor.position = seq.endOffset;
12797
12848
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(valueRawStart, cursor.position)) : buffer.subarray(valueRawStart, cursor.position);
12798
- const privateCreator2 = resolvePrivateCreator(tag, ctx);
12849
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12799
12850
  elements.set(
12800
12851
  tag,
12801
12852
  new Element({
@@ -12812,6 +12863,40 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12812
12863
  );
12813
12864
  continue;
12814
12865
  }
12866
+ if (vr === "UN") {
12867
+ const cp246 = tryParseUnAsSQ(
12868
+ buffer,
12869
+ cursor.position,
12870
+ 4294967295,
12871
+ ctx,
12872
+ emit,
12873
+ parseImplicitLE,
12874
+ tag
12875
+ );
12876
+ if (cp246.success) {
12877
+ const valueRawStart = headerStart;
12878
+ cursor.position = cp246.endOffset;
12879
+ const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(valueRawStart, cursor.position)) : buffer.subarray(valueRawStart, cursor.position);
12880
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12881
+ elements.set(
12882
+ tag,
12883
+ new Element({
12884
+ tag,
12885
+ vr: "SQ",
12886
+ // Promoted, exactly as the Explicit-VR path promotes it.
12887
+ vm: cp246.items.length,
12888
+ length: 4294967295,
12889
+ rawBytes,
12890
+ byteOffset: headerStart,
12891
+ cp246Promoted: true,
12892
+ littleEndian: true,
12893
+ items: cp246.items,
12894
+ ...privateCreator2 !== void 0 ? { privateCreator: privateCreator2 } : {}
12895
+ })
12896
+ );
12897
+ continue;
12898
+ }
12899
+ }
12815
12900
  throw new DicomParseError(
12816
12901
  FATAL_CODES.INVALID_FILE_META,
12817
12902
  `Undefined length on non-SQ element ${tag} (vr=${vr}) under Implicit VR LE.`,
@@ -12831,6 +12916,35 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12831
12916
  const valueEnd = valueStart + length;
12832
12917
  const valueSlice = ctx.copyValues ? copyValueBytes(buffer.subarray(valueStart, valueEnd)) : buffer.subarray(valueStart, valueEnd);
12833
12918
  cursor.position = valueEnd;
12919
+ if (vr === "SQ") {
12920
+ const seq = tryParseDefinedLengthSQ(
12921
+ buffer,
12922
+ valueStart,
12923
+ length,
12924
+ ctx,
12925
+ emit,
12926
+ parseImplicitLE,
12927
+ tag
12928
+ );
12929
+ if (seq.success) {
12930
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12931
+ elements.set(
12932
+ tag,
12933
+ new Element({
12934
+ tag,
12935
+ vr,
12936
+ vm: seq.items.length,
12937
+ length,
12938
+ rawBytes: valueSlice,
12939
+ byteOffset: headerStart,
12940
+ littleEndian: true,
12941
+ items: seq.items,
12942
+ ...privateCreator2 !== void 0 ? { privateCreator: privateCreator2 } : {}
12943
+ })
12944
+ );
12945
+ continue;
12946
+ }
12947
+ }
12834
12948
  if (element === 0 && group !== 2) {
12835
12949
  emit(groupLengthInDataset(position, tag));
12836
12950
  }
@@ -12838,7 +12952,7 @@ function parseImplicitLE(buffer, datasetStart, ctx, emit, opts = {}) {
12838
12952
  registerPrivateCreator(tag, valueSlice, ctx);
12839
12953
  }
12840
12954
  applySpecificCharacterSet(tag, valueSlice, ctx, emit, position);
12841
- const privateCreator = resolvePrivateCreator(tag, ctx);
12955
+ const privateCreator = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12842
12956
  elements.set(
12843
12957
  tag,
12844
12958
  new Element({
@@ -12958,7 +13072,7 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
12958
13072
  }
12959
13073
  cursor.position = seqResult.endOffset;
12960
13074
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, cursor.position)) : buffer.subarray(headerStart, cursor.position);
12961
- const privateCreator2 = resolvePrivateCreator(tag, ctx);
13075
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
12962
13076
  elements.set(
12963
13077
  tag,
12964
13078
  new Element({
@@ -13010,12 +13124,13 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13010
13124
  UNDEFINED_LENGTH2,
13011
13125
  ctx,
13012
13126
  emit,
13013
- parseImplicitLE
13127
+ parseImplicitLE,
13128
+ tag
13014
13129
  );
13015
13130
  if (cp246.success) {
13016
13131
  cursor.position = cp246.endOffset;
13017
13132
  const rawBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, cursor.position)) : buffer.subarray(headerStart, cursor.position);
13018
- const privateCreator3 = resolvePrivateCreator(tag, ctx);
13133
+ const privateCreator3 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13019
13134
  elements.set(
13020
13135
  tag,
13021
13136
  new Element({
@@ -13034,7 +13149,7 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13034
13149
  );
13035
13150
  continue;
13036
13151
  }
13037
- const privateCreator2 = resolvePrivateCreator(tag, ctx);
13152
+ const privateCreator2 = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13038
13153
  const fallbackEnd = buffer.length;
13039
13154
  const fallbackBytes = ctx.copyValues ? copyValueBytes(buffer.subarray(headerStart, fallbackEnd)) : buffer.subarray(headerStart, fallbackEnd);
13040
13155
  elements.set(
@@ -13081,7 +13196,7 @@ function _parseExplicit(buffer, datasetStart, ctx, emit, mode, opts = {}) {
13081
13196
  registerPrivateCreator(tag, valueSlice, ctx);
13082
13197
  }
13083
13198
  applySpecificCharacterSet(tag, valueSlice, ctx, emit, position);
13084
- const privateCreator = resolvePrivateCreator(tag, ctx);
13199
+ const privateCreator = safeModelCreator(resolvePrivateCreator(tag, ctx), ctx);
13085
13200
  const finalVr = vr;
13086
13201
  elements.set(
13087
13202
  tag,
@@ -13134,20 +13249,22 @@ function parseDeflatedLEWithCap(buffer, datasetStart, ctx, emit, maxInflatedByte
13134
13249
  buildSnippet(buffer, datasetStart)
13135
13250
  );
13136
13251
  }
13252
+ const zlibCode = typeof code === "string" && code.length > 0 ? code : "unknown";
13137
13253
  throw new DicomParseError(
13138
13254
  FATAL_CODES.INVALID_FILE_META,
13139
- `Failed to inflate Deflated TS payload: ${message}`,
13255
+ `Failed to inflate Deflated TS payload (zlib error code: ${zlibCode}).`,
13140
13256
  datasetStart,
13141
13257
  buildSnippet(buffer, datasetStart)
13142
13258
  );
13143
13259
  }
13144
13260
  const innerCtx = { ...ctx, buffer: inflated };
13261
+ const innerChokepoint = makeEmitter(innerCtx);
13145
13262
  const innerEmit = (w) => {
13146
13263
  const wrapped = {
13147
13264
  ...w,
13148
13265
  position: { ...w.position, deflated: true }
13149
13266
  };
13150
- emit(wrapped);
13267
+ innerChokepoint(wrapped);
13151
13268
  };
13152
13269
  const result = parseExplicitLE(inflated, 0, innerCtx, innerEmit);
13153
13270
  return { elements: result.elements, endOffset: buffer.length };
@@ -13174,6 +13291,7 @@ var TRANSFER_SYNTAX_PARSERS = Object.freeze({
13174
13291
  });
13175
13292
 
13176
13293
  // src/parser/index.ts
13294
+ var SUPPORTED_TRANSFER_SYNTAXES = Object.keys(TRANSFER_SYNTAX_PARSERS).join(", ");
13177
13295
  function parseDicom(input, options = {}) {
13178
13296
  if (rawInputIsEmpty(input)) {
13179
13297
  throw new DicomParseError(FATAL_CODES.EMPTY_INPUT, "Input is empty.", 0, "");
@@ -13196,9 +13314,10 @@ function parseDicom(input, options = {}) {
13196
13314
  const strategy = TRANSFER_SYNTAX_PARSERS[tsUid];
13197
13315
  if (strategy === void 0) {
13198
13316
  const tsName = uid(tsUid)?.name ?? "";
13317
+ const named = tsName.length > 0 ? `Transfer Syntax ${tsName}` : "The Transfer Syntax UID";
13199
13318
  throw new DicomParseError(
13200
13319
  FATAL_CODES.UNSUPPORTED_TRANSFER_SYNTAX,
13201
- `Transfer Syntax UID "${tsUid}" is not supported by @cosyte/dicom v1.`,
13320
+ `${named} in (0002,0010) is not supported by @cosyte/dicom v1 (supported: ${SUPPORTED_TRANSFER_SYNTAXES}).`,
13202
13321
  fileMetaEnd,
13203
13322
  tsName.length > 0 ? tsName : buildSnippet(buffer, fileMetaEnd)
13204
13323
  );
@@ -13436,7 +13555,7 @@ function serializeDicom(ds) {
13436
13555
  if (encoding === void 0) {
13437
13556
  throw new DicomSerializeError(
13438
13557
  SERIALIZE_ERROR_CODES.UNSUPPORTED_TRANSFER_SYNTAX,
13439
- `Transfer Syntax UID "${tsUid}" is not supported by the @cosyte/dicom v1 writer.`
13558
+ `The Dataset's File Meta Transfer Syntax UID is not supported by the @cosyte/dicom v1 writer (supported: ${Object.keys(BODY_ENCODING).join(", ")}).`
13440
13559
  );
13441
13560
  }
13442
13561
  const preamble = part10Preamble();
@@ -14719,12 +14838,25 @@ function isPrivateCreatorElement(tag) {
14719
14838
  const { element } = splitTag(tag);
14720
14839
  return element >= 16 && element <= 255;
14721
14840
  }
14722
- function keepsPrivate(el, ctx) {
14841
+ function creatorsInScope(source) {
14842
+ const byBlock = /* @__PURE__ */ new Map();
14843
+ for (const el of source) {
14844
+ if (!isPrivateTag(el.tag) || !isPrivateCreatorElement(el.tag)) continue;
14845
+ const { group, element } = splitTag(el.tag);
14846
+ byBlock.set(`${String(group)}:${String(element & 255)}`, decodeCreator(el));
14847
+ }
14848
+ return byBlock;
14849
+ }
14850
+ function creatorFor(tag, creators) {
14851
+ const { group, element } = splitTag(tag);
14852
+ return creators.get(`${String(group)}:${String(element >> 8 & 255)}`);
14853
+ }
14854
+ function keepsPrivate(el, ctx, creators) {
14723
14855
  if (!ctx.active.has("RetainSafePrivate") || ctx.profile === void 0) return false;
14724
14856
  if (isPrivateCreatorElement(el.tag)) {
14725
14857
  return ctx.profile.privateDictionary.has(decodeCreator(el));
14726
14858
  }
14727
- const creator = el.privateCreator;
14859
+ const creator = creatorFor(el.tag, creators);
14728
14860
  if (creator === void 0) return false;
14729
14861
  return resolvePrivateTag(ctx.profile, el.tag, creator) !== void 0;
14730
14862
  }
@@ -14734,9 +14866,10 @@ function processElements(source, ctx, contextPath) {
14734
14866
  attributes: [],
14735
14867
  removedPrivateTags: []
14736
14868
  };
14869
+ const creators = creatorsInScope(source);
14737
14870
  for (const el of source) {
14738
14871
  if (isPrivateTag(el.tag)) {
14739
- if (keepsPrivate(el, ctx)) out.elements.set(el.tag, el);
14872
+ if (keepsPrivate(el, ctx, creators)) out.elements.set(el.tag, el);
14740
14873
  else out.removedPrivateTags.push(el.tag);
14741
14874
  continue;
14742
14875
  }