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