@cosyte/dicom 0.0.13 → 0.0.14

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.d.cts CHANGED
@@ -114,6 +114,18 @@ interface UidEntry {
114
114
  * throw `TypeError` in strict mode (which all of `@cosyte/dicom`'s emitted code
115
115
  * runs under, given `"use strict"` is implicit for ES modules).
116
116
  *
117
+ * @example
118
+ * ```ts
119
+ * import { Dictionary } from "@cosyte/dicom";
120
+ *
121
+ * // Tag or keyword, either way round, and `undefined` rather than a throw on a miss.
122
+ * Dictionary.lookup("00100020")?.name; // "Patient ID"
123
+ * Dictionary.byKeyword("Modality")?.tag; // "00080060"
124
+ *
125
+ * // UIDs resolve to their registered name, so nobody hard-codes a SOP Class table.
126
+ * Dictionary.uid("1.2.840.10008.5.1.4.1.1.2")?.name; // "CT Image Storage"
127
+ * ```
128
+ *
117
129
  * @module
118
130
  */
119
131
 
@@ -317,8 +329,13 @@ interface ParseOptions {
317
329
  *
318
330
  * 🛑 **THE ESCALATED DIAGNOSTIC CARRIES SOURCE BYTES THAT THE WARNING DOES
319
331
  * NOT.** A `DicomParseWarning.message` is a frozen registry string with only
320
- * structural tokens filled in, so it is safe to log whole; the
321
- * `DicomParseError` this option raises in its place also carries `snippet`,
332
+ * structural tokens filled in, so it is safe to log whole **on a well-formed
333
+ * file, and it is not unconditionally safe**: the `{tag}` slot is filled by a
334
+ * shape check, which cannot refuse a tag a lying Value Length composed out of
335
+ * somebody's value (measured, `PRE-EXISTING`, disclosed rather than guarded
336
+ * because withholding the tag would take it off every private element in every
337
+ * conformant file). The `DicomParseError` this option raises in its place is a
338
+ * different and larger surface: it also carries `snippet`,
322
339
  * **16 raw bytes, unredacted** (D-10), read at the warning's own `byteOffset`.
323
340
  * A message-only PHI review of the lenient path therefore does not transfer to
324
341
  * the strict one. Log `err.code`, `err.byteOffset` and `err.message`; treat
@@ -921,6 +938,21 @@ declare class Element {
921
938
  * `(0002,0102)` Private Information - is captured here as raw bytes (the
922
939
  * on-wire value, even-length per PS3.5 §6.2) rather than dropped. `value` is a
923
940
  * defensive copy, so the view never aliases the parsed input buffer.
941
+ *
942
+ * @example
943
+ * ```ts
944
+ * import { parseDicom } from "@cosyte/dicom";
945
+ * const ds = parseDicom(buf);
946
+ * // Anything the typed FileMeta fields do not model, in ascending tag order.
947
+ * for (const raw of ds.fileMeta?.extraElements ?? []) {
948
+ * raw.tag; // e.g. "00020017" Sending Application Entity Title
949
+ * raw.vr; // the VR the source wrote (File Meta is always Explicit VR LE)
950
+ * raw.value.length; // even, per PS3.5 2026c section 7.1.1
951
+ * }
952
+ * ```
953
+ *
954
+ * `(0002,0016)` Source Application Entity Title is deliberately NOT the example: it is one of the
955
+ * typed fields, so it never reaches this array.
924
956
  */
925
957
  interface FileMetaRawElement {
926
958
  /** 8-char uppercase hex tag, e.g. `"00020100"`. */
@@ -2448,15 +2480,29 @@ interface DeidentifiedAttribute {
2448
2480
  * (PS3.15 §E.1 "all instances"; §E.3.5 is the standard's own precedent for
2449
2481
  * removing identifying information embedded inside a string attribute).
2450
2482
  *
2451
- * `tag` and `vr` are the carrier's own and are structural. **`hidden` is not,
2452
- * and calling this finding "safe to log" was wrong.** Each entry is composed
2453
- * from four bytes that were sitting *inside* the carrier's value - that is the
2454
- * whole reason the field exists - so on any file that populates it those bytes
2455
- * are document content. Measured: a `CS` carrier over-declaring over the bytes
2456
- * `"SABC"` reports `hidden: ["41534342"]`, which is `"SABC"` in wire order.
2457
- * `PRE-EXISTING` and identical on every release that has shipped the field.
2458
- * Treat `hidden` at the sensitivity of the file, and see
2459
- * {@link DeidentifyReport} for the other fields in this class.
2483
+ * `tag` and `vr` are the carrier's own and are structural. **`hidden` was not,
2484
+ * and it is bound now.** Every tag in an embedded run is composed from four
2485
+ * bytes that were sitting *inside* the carrier's value - that is the whole
2486
+ * reason this type exists - and a run needs only ONE actionable attribute to be
2487
+ * reported, so through `0.0.13` the rest of the run was listed beside it.
2488
+ * Measured: a `CS` carrier over-declaring across a fabricated `"SMIT"` header
2489
+ * beside a genuine `(0010,0020)` reported `hidden: ["4D535449", "00100020"]`,
2490
+ * and `4D535449` is `"SMIT"` in wire order.
2491
+ *
2492
+ * **An entry is now one of the 652 literal rows of PS3.15 Table E.1-1 that this
2493
+ * run's options left actionable.** That is a **membership** bound rather than a
2494
+ * shape one - the posture this package already takes for a VR and for a Private
2495
+ * Creator - so what survives names a published table entry rather than a
2496
+ * document byte, the same trade rendering a VR makes with the 34. **A
2497
+ * repeating-group mask hit is NOT in that set and is excluded**: `(50xx,xxxx)`
2498
+ * Curve Data leaves the whole 16-bit element number free, so a mask match proves
2499
+ * a rule exists without making the membership finite. A graded pass caught a
2500
+ * draft of this filter that admitted it.
2501
+ *
2502
+ * **🛑 THAT IS NOT AN ALL-CLEAR OVER THIS TYPE.** `contextPath` below is
2503
+ * unbound and unchanged, `hidden` is uncapped, and
2504
+ * {@link DeidentifyReport} names the report's other value-bearing fields. A
2505
+ * `DeidentifyReport` is still not safe to log whole.
2460
2506
  *
2461
2507
  * @example
2462
2508
  * ```ts
@@ -2472,7 +2518,18 @@ interface EmbeddedAttributeFinding {
2472
2518
  readonly tag: Tag;
2473
2519
  /** The carrier's VR. Always one of the string VRs; binary VRs are not scanned. */
2474
2520
  readonly vr: VR;
2475
- /** The tags of the Data Elements found inside the carrier's value, in wire order. */
2521
+ /**
2522
+ * The tags found inside the carrier's value **that this run acts on and that a
2523
+ * published table names**, in wire order. **Not every tag in the run** - see
2524
+ * this type's own remarks.
2525
+ *
2526
+ * **🩺 IT MAY BE EMPTY, AND EMPTY DOES NOT MEAN "NOTHING WAS HIDDEN HERE".**
2527
+ * A run whose only actionable members are private attributes, Curve Data or
2528
+ * Overlay elements reports a finding with no tags: the carrier was still
2529
+ * emptied, and the accompanying `DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED`
2530
+ * warning still counts the whole run. The presence of the finding is the fact;
2531
+ * this list is the part of it that can be named.
2532
+ */
2476
2533
  readonly hidden: readonly Tag[];
2477
2534
  /**
2478
2535
  * Tag/index chain when the carrier is inside a sequence item; omitted at the
@@ -2697,13 +2754,6 @@ interface UndefinedVrFinding {
2697
2754
  * and still answers it whenever the fabricated VR is outside the 34 PS3.5
2698
2755
  * §6.2 defines. It cannot when the fabricated VR is one of them, because those
2699
2756
  * two files are byte-identical.
2700
- * - **`embeddedAttributes[].hidden`** - see {@link EmbeddedAttributeFinding}.
2701
- * Each entry is a tag composed from four bytes that were sitting **inside**
2702
- * a value, which is the position this type exists to distrust, so on a file
2703
- * that populates the field at all they are document content by
2704
- * construction. `PRE-EXISTING` and identical on every release that has
2705
- * shipped the field; it is disclosed here and its remedy is its own slice,
2706
- * alongside the cap that field is also missing.
2707
2757
  * - **`contextPath`, on all four findings that carry one** - see
2708
2758
  * {@link DeidentifiedAttribute.contextPath}, which holds the measurement. The
2709
2759
  * segment tags come off the wire with no table behind them, so a fabricated
@@ -2714,8 +2764,18 @@ interface UndefinedVrFinding {
2714
2764
  * same file the de-identified object itself re-emits the fabricated header, so
2715
2765
  * redacting this field does not make the object safe.**
2716
2766
  *
2767
+ * `embeddedAttributes[].hidden` **left that list** in
2768
+ * `DICOM-DIAGNOSTIC-PHI-RESIDUALS` - an entry is now a literal PS3.15 Table
2769
+ * E.1-1 row, so it is not value-bearing. Its own disclosure had been reworded
2770
+ * twice by then, and this repo deletes a disclosure at that point rather than
2771
+ * writing a third; what is true of the field is stated once, on
2772
+ * {@link EmbeddedAttributeFinding}. The field is **still uncapped**.
2773
+ *
2717
2774
  * So "the report is safe to log apart from `uidMap`" is **not** an accurate
2718
2775
  * description of this type, and was corrected rather than kept convenient.
2776
+ * **This is the only copy of that list.** Two others lived in module docstrings,
2777
+ * still naming `hidden` after it left and never naming `contextPath` at all; a
2778
+ * graded pass found them and they were deleted rather than resynced.
2719
2779
  *
2720
2780
  * @example
2721
2781
  * ```ts
@@ -2994,9 +3054,8 @@ declare class DeidentifyError extends Error {
2994
3054
  * action map ({@link annexE}). It is a **pure** function: the input {@link Dataset}
2995
3055
  * is never mutated; a fresh `Dataset` (with a rebuilt element map and File Meta)
2996
3056
  * is returned alongside a {@link DeidentifyReport} whose non-value-free fields
2997
- * are named on that type: `uidMap`, `removedPrivateTags`,
2998
- * `unauditableSequences[].tag` and `embeddedAttributes[].hidden`. Read the list
2999
- * there, never a count quoted elsewhere.
3057
+ * are named on that type. **The list is deliberately not copied here** - see
3058
+ * `./index.ts` for what a third copy of it cost.
3000
3059
  *
3001
3060
  * **What it does**
3002
3061
  * - Resolves each attribute's action (basic profile, overridden by an active
package/dist/index.d.ts CHANGED
@@ -114,6 +114,18 @@ interface UidEntry {
114
114
  * throw `TypeError` in strict mode (which all of `@cosyte/dicom`'s emitted code
115
115
  * runs under, given `"use strict"` is implicit for ES modules).
116
116
  *
117
+ * @example
118
+ * ```ts
119
+ * import { Dictionary } from "@cosyte/dicom";
120
+ *
121
+ * // Tag or keyword, either way round, and `undefined` rather than a throw on a miss.
122
+ * Dictionary.lookup("00100020")?.name; // "Patient ID"
123
+ * Dictionary.byKeyword("Modality")?.tag; // "00080060"
124
+ *
125
+ * // UIDs resolve to their registered name, so nobody hard-codes a SOP Class table.
126
+ * Dictionary.uid("1.2.840.10008.5.1.4.1.1.2")?.name; // "CT Image Storage"
127
+ * ```
128
+ *
117
129
  * @module
118
130
  */
119
131
 
@@ -317,8 +329,13 @@ interface ParseOptions {
317
329
  *
318
330
  * 🛑 **THE ESCALATED DIAGNOSTIC CARRIES SOURCE BYTES THAT THE WARNING DOES
319
331
  * NOT.** A `DicomParseWarning.message` is a frozen registry string with only
320
- * structural tokens filled in, so it is safe to log whole; the
321
- * `DicomParseError` this option raises in its place also carries `snippet`,
332
+ * structural tokens filled in, so it is safe to log whole **on a well-formed
333
+ * file, and it is not unconditionally safe**: the `{tag}` slot is filled by a
334
+ * shape check, which cannot refuse a tag a lying Value Length composed out of
335
+ * somebody's value (measured, `PRE-EXISTING`, disclosed rather than guarded
336
+ * because withholding the tag would take it off every private element in every
337
+ * conformant file). The `DicomParseError` this option raises in its place is a
338
+ * different and larger surface: it also carries `snippet`,
322
339
  * **16 raw bytes, unredacted** (D-10), read at the warning's own `byteOffset`.
323
340
  * A message-only PHI review of the lenient path therefore does not transfer to
324
341
  * the strict one. Log `err.code`, `err.byteOffset` and `err.message`; treat
@@ -921,6 +938,21 @@ declare class Element {
921
938
  * `(0002,0102)` Private Information - is captured here as raw bytes (the
922
939
  * on-wire value, even-length per PS3.5 §6.2) rather than dropped. `value` is a
923
940
  * defensive copy, so the view never aliases the parsed input buffer.
941
+ *
942
+ * @example
943
+ * ```ts
944
+ * import { parseDicom } from "@cosyte/dicom";
945
+ * const ds = parseDicom(buf);
946
+ * // Anything the typed FileMeta fields do not model, in ascending tag order.
947
+ * for (const raw of ds.fileMeta?.extraElements ?? []) {
948
+ * raw.tag; // e.g. "00020017" Sending Application Entity Title
949
+ * raw.vr; // the VR the source wrote (File Meta is always Explicit VR LE)
950
+ * raw.value.length; // even, per PS3.5 2026c section 7.1.1
951
+ * }
952
+ * ```
953
+ *
954
+ * `(0002,0016)` Source Application Entity Title is deliberately NOT the example: it is one of the
955
+ * typed fields, so it never reaches this array.
924
956
  */
925
957
  interface FileMetaRawElement {
926
958
  /** 8-char uppercase hex tag, e.g. `"00020100"`. */
@@ -2448,15 +2480,29 @@ interface DeidentifiedAttribute {
2448
2480
  * (PS3.15 §E.1 "all instances"; §E.3.5 is the standard's own precedent for
2449
2481
  * removing identifying information embedded inside a string attribute).
2450
2482
  *
2451
- * `tag` and `vr` are the carrier's own and are structural. **`hidden` is not,
2452
- * and calling this finding "safe to log" was wrong.** Each entry is composed
2453
- * from four bytes that were sitting *inside* the carrier's value - that is the
2454
- * whole reason the field exists - so on any file that populates it those bytes
2455
- * are document content. Measured: a `CS` carrier over-declaring over the bytes
2456
- * `"SABC"` reports `hidden: ["41534342"]`, which is `"SABC"` in wire order.
2457
- * `PRE-EXISTING` and identical on every release that has shipped the field.
2458
- * Treat `hidden` at the sensitivity of the file, and see
2459
- * {@link DeidentifyReport} for the other fields in this class.
2483
+ * `tag` and `vr` are the carrier's own and are structural. **`hidden` was not,
2484
+ * and it is bound now.** Every tag in an embedded run is composed from four
2485
+ * bytes that were sitting *inside* the carrier's value - that is the whole
2486
+ * reason this type exists - and a run needs only ONE actionable attribute to be
2487
+ * reported, so through `0.0.13` the rest of the run was listed beside it.
2488
+ * Measured: a `CS` carrier over-declaring across a fabricated `"SMIT"` header
2489
+ * beside a genuine `(0010,0020)` reported `hidden: ["4D535449", "00100020"]`,
2490
+ * and `4D535449` is `"SMIT"` in wire order.
2491
+ *
2492
+ * **An entry is now one of the 652 literal rows of PS3.15 Table E.1-1 that this
2493
+ * run's options left actionable.** That is a **membership** bound rather than a
2494
+ * shape one - the posture this package already takes for a VR and for a Private
2495
+ * Creator - so what survives names a published table entry rather than a
2496
+ * document byte, the same trade rendering a VR makes with the 34. **A
2497
+ * repeating-group mask hit is NOT in that set and is excluded**: `(50xx,xxxx)`
2498
+ * Curve Data leaves the whole 16-bit element number free, so a mask match proves
2499
+ * a rule exists without making the membership finite. A graded pass caught a
2500
+ * draft of this filter that admitted it.
2501
+ *
2502
+ * **🛑 THAT IS NOT AN ALL-CLEAR OVER THIS TYPE.** `contextPath` below is
2503
+ * unbound and unchanged, `hidden` is uncapped, and
2504
+ * {@link DeidentifyReport} names the report's other value-bearing fields. A
2505
+ * `DeidentifyReport` is still not safe to log whole.
2460
2506
  *
2461
2507
  * @example
2462
2508
  * ```ts
@@ -2472,7 +2518,18 @@ interface EmbeddedAttributeFinding {
2472
2518
  readonly tag: Tag;
2473
2519
  /** The carrier's VR. Always one of the string VRs; binary VRs are not scanned. */
2474
2520
  readonly vr: VR;
2475
- /** The tags of the Data Elements found inside the carrier's value, in wire order. */
2521
+ /**
2522
+ * The tags found inside the carrier's value **that this run acts on and that a
2523
+ * published table names**, in wire order. **Not every tag in the run** - see
2524
+ * this type's own remarks.
2525
+ *
2526
+ * **🩺 IT MAY BE EMPTY, AND EMPTY DOES NOT MEAN "NOTHING WAS HIDDEN HERE".**
2527
+ * A run whose only actionable members are private attributes, Curve Data or
2528
+ * Overlay elements reports a finding with no tags: the carrier was still
2529
+ * emptied, and the accompanying `DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED`
2530
+ * warning still counts the whole run. The presence of the finding is the fact;
2531
+ * this list is the part of it that can be named.
2532
+ */
2476
2533
  readonly hidden: readonly Tag[];
2477
2534
  /**
2478
2535
  * Tag/index chain when the carrier is inside a sequence item; omitted at the
@@ -2697,13 +2754,6 @@ interface UndefinedVrFinding {
2697
2754
  * and still answers it whenever the fabricated VR is outside the 34 PS3.5
2698
2755
  * §6.2 defines. It cannot when the fabricated VR is one of them, because those
2699
2756
  * two files are byte-identical.
2700
- * - **`embeddedAttributes[].hidden`** - see {@link EmbeddedAttributeFinding}.
2701
- * Each entry is a tag composed from four bytes that were sitting **inside**
2702
- * a value, which is the position this type exists to distrust, so on a file
2703
- * that populates the field at all they are document content by
2704
- * construction. `PRE-EXISTING` and identical on every release that has
2705
- * shipped the field; it is disclosed here and its remedy is its own slice,
2706
- * alongside the cap that field is also missing.
2707
2757
  * - **`contextPath`, on all four findings that carry one** - see
2708
2758
  * {@link DeidentifiedAttribute.contextPath}, which holds the measurement. The
2709
2759
  * segment tags come off the wire with no table behind them, so a fabricated
@@ -2714,8 +2764,18 @@ interface UndefinedVrFinding {
2714
2764
  * same file the de-identified object itself re-emits the fabricated header, so
2715
2765
  * redacting this field does not make the object safe.**
2716
2766
  *
2767
+ * `embeddedAttributes[].hidden` **left that list** in
2768
+ * `DICOM-DIAGNOSTIC-PHI-RESIDUALS` - an entry is now a literal PS3.15 Table
2769
+ * E.1-1 row, so it is not value-bearing. Its own disclosure had been reworded
2770
+ * twice by then, and this repo deletes a disclosure at that point rather than
2771
+ * writing a third; what is true of the field is stated once, on
2772
+ * {@link EmbeddedAttributeFinding}. The field is **still uncapped**.
2773
+ *
2717
2774
  * So "the report is safe to log apart from `uidMap`" is **not** an accurate
2718
2775
  * description of this type, and was corrected rather than kept convenient.
2776
+ * **This is the only copy of that list.** Two others lived in module docstrings,
2777
+ * still naming `hidden` after it left and never naming `contextPath` at all; a
2778
+ * graded pass found them and they were deleted rather than resynced.
2719
2779
  *
2720
2780
  * @example
2721
2781
  * ```ts
@@ -2994,9 +3054,8 @@ declare class DeidentifyError extends Error {
2994
3054
  * action map ({@link annexE}). It is a **pure** function: the input {@link Dataset}
2995
3055
  * is never mutated; a fresh `Dataset` (with a rebuilt element map and File Meta)
2996
3056
  * is returned alongside a {@link DeidentifyReport} whose non-value-free fields
2997
- * are named on that type: `uidMap`, `removedPrivateTags`,
2998
- * `unauditableSequences[].tag` and `embeddedAttributes[].hidden`. Read the list
2999
- * there, never a count quoted elsewhere.
3057
+ * are named on that type. **The list is deliberately not copied here** - see
3058
+ * `./index.ts` for what a third copy of it cost.
3000
3059
  *
3001
3060
  * **What it does**
3002
3061
  * - Resolves each attribute's action (basic profile, overridden by an active
package/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ var __export = (target, all) => {
9
9
  };
10
10
 
11
11
  // src/version.ts
12
- var VERSION = "0.0.13";
12
+ var VERSION = "0.0.14";
13
13
 
14
14
  // src/dictionary/index.ts
15
15
  var dictionary_exports = {};
@@ -12206,7 +12206,11 @@ var WARNING_MESSAGES = Object.freeze({
12206
12206
  DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR: "Element ({tag}) uses undefined length (0xFFFFFFFF) under an Explicit VR transfer syntax.",
12207
12207
  DICOM_ODD_LENGTH_VALUE_PADDED: "Element ({tag}) has odd declared length {n}; cursor advanced by one padding byte to maintain alignment.",
12208
12208
  DICOM_VR_MISMATCH: "Element ({tag}) on-wire VR is {vr2}; dictionary lists {vr}. Trusting on-wire VR.",
12209
- DICOM_PRIVATE_TAG_NO_CREATOR: "Private element ({tag}) has no Private Creator registered for its block; treating as VR=UN.",
12209
+ // No tag, and the reason is the one `nonzeroReservedBytes` states: this code's
12210
+ // three siblings below fire only on an ODD group, and an odd group is the one
12211
+ // class of tag no closed table this library holds can ever vouch for. See
12212
+ // `privateTagNoCreator`.
12213
+ DICOM_PRIVATE_TAG_NO_CREATOR: "A private element has no Private Creator registered for its block; treating as VR=UN. Its tag is withheld; the byte offset identifies the element.",
12210
12214
  DICOM_GROUP_LENGTH_IN_DATASET: "Retired Group Length element ({tag}) encountered in dataset; preserved as-is.",
12211
12215
  // The tag is deliberately absent, and this is the only *parser* message where
12212
12216
  // that is true. See `nonzeroReservedBytes`. Kept terse for the same reason the
@@ -12252,10 +12256,10 @@ var WARNING_MESSAGES = Object.freeze({
12252
12256
  // `endLimit < buffer.length` conjunct bounds it by the buffer.
12253
12257
  DICOM_ITEM_CROSSES_SEQUENCE_END: "Item ({tag}) declares a length reaching past its enclosing sequence's declared end, so it reads the enclosing Data Set's bytes; {n2} bytes remained inside the sequence. The file's two length fields disagree (PS3.5 7.5.1 and 7.5.2 govern them); the item's is used. The declared length is withheld; the byte offset locates the item.",
12254
12258
  DICOM_PIXEL_DATA_LENGTH_MISMATCH: "(7FE0,0010) PixelData declared length {n} does not match computed {n2} bytes.",
12255
- DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "Private element ({tag}) under Implicit VR LE has no VR override; falling back to UN.",
12256
- 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.",
12259
+ DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "A private element under Implicit VR LE has no VR override; falling back to UN. Its tag is withheld; the byte offset identifies the element.",
12260
+ DICOM_PRIVATE_CREATOR_UNKNOWN: "A private element 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. Its tag is withheld; the byte offset identifies the element.",
12257
12261
  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.",
12258
- 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.",
12262
+ 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. report.embeddedAttributes names the ones this run acts on that also have a literal Table E.1-1 row, which may be none of them.",
12259
12263
  // Deliberately short. One of these is raised per un-auditable element, so a
12260
12264
  // long message is multiplied by an element count the input controls; the
12261
12265
  // reasoning belongs in the docs, not in a string repeated thousands of times.
@@ -12318,8 +12322,8 @@ function oddLengthValuePadded(position, tag, declaredLength) {
12318
12322
  function vrMismatch(position, tag, dictVR, fileVR) {
12319
12323
  return build2(WARNING_CODES.DICOM_VR_MISMATCH, position, { tag, vr: dictVR, vr2: fileVR });
12320
12324
  }
12321
- function privateTagNoCreator(position, tag) {
12322
- return build2(WARNING_CODES.DICOM_PRIVATE_TAG_NO_CREATOR, position, { tag });
12325
+ function privateTagNoCreator(position) {
12326
+ return build2(WARNING_CODES.DICOM_PRIVATE_TAG_NO_CREATOR, position);
12323
12327
  }
12324
12328
  function groupLengthInDataset(position, tag) {
12325
12329
  return build2(WARNING_CODES.DICOM_GROUP_LENGTH_IN_DATASET, position, { tag });
@@ -12351,11 +12355,11 @@ function duplicateTagInDataSet(position) {
12351
12355
  function duplicateFileMetaElement(position) {
12352
12356
  return build2(WARNING_CODES.DICOM_DUPLICATE_FILE_META_ELEMENT, position);
12353
12357
  }
12354
- function implicitVRForPrivateTagWithoutVR(position, tag) {
12355
- return build2(WARNING_CODES.DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR, position, { tag });
12358
+ function implicitVRForPrivateTagWithoutVR(position) {
12359
+ return build2(WARNING_CODES.DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR, position);
12356
12360
  }
12357
- function privateCreatorUnknown(position, tag) {
12358
- return build2(WARNING_CODES.DICOM_PRIVATE_CREATOR_UNKNOWN, position, { tag });
12361
+ function privateCreatorUnknown(position) {
12362
+ return build2(WARNING_CODES.DICOM_PRIVATE_CREATOR_UNKNOWN, position);
12359
12363
  }
12360
12364
  function burnedInAnnotationNotRemoved(position) {
12361
12365
  return build2(WARNING_CODES.DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED, position);
@@ -12460,18 +12464,18 @@ function resolveImplicitVR(tag, ctx, emit, position) {
12460
12464
  }
12461
12465
  const creator = resolvePrivateCreator(tag, ctx);
12462
12466
  if (creator === void 0) {
12463
- emit(privateTagNoCreator(position, tag));
12464
- emit(implicitVRForPrivateTagWithoutVR(position, tag));
12467
+ emit(privateTagNoCreator(position));
12468
+ emit(implicitVRForPrivateTagWithoutVR(position));
12465
12469
  return "UN";
12466
12470
  }
12467
12471
  if (ctx.profile !== void 0) {
12468
12472
  const def = resolvePrivateTag(ctx.profile, tag, creator);
12469
12473
  if (def !== void 0) return def.vr;
12470
12474
  if (!ctx.profile.privateDictionary.has(creator)) {
12471
- emit(privateCreatorUnknown(position, tag));
12475
+ emit(privateCreatorUnknown(position));
12472
12476
  }
12473
12477
  }
12474
- emit(implicitVRForPrivateTagWithoutVR(position, tag));
12478
+ emit(implicitVRForPrivateTagWithoutVR(position));
12475
12479
  return "UN";
12476
12480
  }
12477
12481
  const direct = TAGS[tag];
@@ -15223,6 +15227,10 @@ function hasByteOutsideRepertoire(region, carrierVr) {
15223
15227
  }
15224
15228
  return false;
15225
15229
  }
15230
+ function isTableBound(tag) {
15231
+ const action = annexE(tag);
15232
+ return action !== void 0 && action.repeatingGroup === void 0;
15233
+ }
15226
15234
  function findEmbeddedAttributes(value, carrierVr, encoding, isActionable) {
15227
15235
  if (!isScannableCarrier(carrierVr)) return void 0;
15228
15236
  if (value.length < MIN_HEADER) return void 0;
@@ -15246,15 +15254,19 @@ function findEmbeddedAttributes(value, carrierVr, encoding, isActionable) {
15246
15254
  for (let off = 0; off + MIN_HEADER <= n; off += 2) {
15247
15255
  if (hit[off >> 1] !== 1) continue;
15248
15256
  if (!hasByteOutsideRepertoire(window.subarray(off), carrierVr)) return void 0;
15249
- const tags = [];
15257
+ const hidden = [];
15258
+ let elementCount = 0;
15250
15259
  let pos = off;
15251
15260
  while (pos < n) {
15252
15261
  const decoded = decodeAt(window, pos, encoding);
15253
15262
  if (decoded === void 0) break;
15254
- if (decoded.tag !== void 0) tags.push(decoded.tag);
15263
+ if (decoded.tag !== void 0) {
15264
+ elementCount += 1;
15265
+ if (isActionable(decoded.tag) && isTableBound(decoded.tag)) hidden.push(decoded.tag);
15266
+ }
15255
15267
  pos = decoded.next;
15256
15268
  }
15257
- return tags;
15269
+ return { hidden, elementCount };
15258
15270
  }
15259
15271
  return void 0;
15260
15272
  }
@@ -15459,8 +15471,8 @@ function keepOrEmpty(el, ctx, contextPath, out) {
15459
15471
  emptyUndefinedVrElement(el, ctx, contextPath, out);
15460
15472
  return false;
15461
15473
  }
15462
- const hidden = findEmbeddedAttributes(el.rawBytes, el.vr, ctx.encoding, (t) => actsOnTag(t, ctx));
15463
- if (hidden === void 0) {
15474
+ const run = findEmbeddedAttributes(el.rawBytes, el.vr, ctx.encoding, (t) => actsOnTag(t, ctx));
15475
+ if (run === void 0) {
15464
15476
  out.elements.set(el.tag, el);
15465
15477
  return true;
15466
15478
  }
@@ -15468,11 +15480,11 @@ function keepOrEmpty(el, ctx, contextPath, out) {
15468
15480
  out.embeddedAttributes.push({
15469
15481
  tag: el.tag,
15470
15482
  vr: el.vr,
15471
- hidden,
15483
+ hidden: run.hidden,
15472
15484
  ...contextPath.length > 0 ? { contextPath: [...contextPath] } : {}
15473
15485
  });
15474
15486
  out.warnings.push(
15475
- embeddedAttributeRemoved({ byteOffset: el.byteOffset }, el.tag, el.vr, hidden.length)
15487
+ embeddedAttributeRemoved({ byteOffset: el.byteOffset }, el.tag, el.vr, run.elementCount)
15476
15488
  );
15477
15489
  return false;
15478
15490
  }