@cosyte/dicom 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -401,6 +401,7 @@ declare const WARNING_CODES: {
401
401
  readonly DICOM_ODD_LENGTH_VALUE_PADDED: "DICOM_ODD_LENGTH_VALUE_PADDED";
402
402
  readonly DICOM_PIXEL_DATA_LENGTH_MISMATCH: "DICOM_PIXEL_DATA_LENGTH_MISMATCH";
403
403
  readonly DICOM_PRIVATE_TAG_NO_CREATOR: "DICOM_PRIVATE_TAG_NO_CREATOR";
404
+ readonly DICOM_SQ_NOT_DESCENDED: "DICOM_SQ_NOT_DESCENDED";
404
405
  readonly DICOM_UN_PARSED_AS_SQ: "DICOM_UN_PARSED_AS_SQ";
405
406
  readonly DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR: "DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR";
406
407
  readonly DICOM_VR_MISMATCH: "DICOM_VR_MISMATCH";
@@ -414,6 +415,9 @@ declare const WARNING_CODES: {
414
415
  readonly DICOM_CHARSET_AMBIGUOUS_SEPARATOR: "DICOM_CHARSET_AMBIGUOUS_SEPARATOR";
415
416
  readonly DICOM_UNSUPPORTED_CHARSET: "DICOM_UNSUPPORTED_CHARSET";
416
417
  readonly DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED";
418
+ readonly DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED";
419
+ readonly DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE";
420
+ readonly DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE";
417
421
  readonly DICOM_PRIVATE_CREATOR_UNKNOWN: "DICOM_PRIVATE_CREATOR_UNKNOWN";
418
422
  };
419
423
  /**
@@ -1624,12 +1628,21 @@ declare function decodeElementValue(element: Element): DicomValue;
1624
1628
 
1625
1629
  /**
1626
1630
  * Parse a `(0008,0005)` Specific Character Set value (a `CS`, possibly
1627
- * multi-valued via `\`) into its trimmed defined terms.
1631
+ * multi-valued via `\`) into its trimmed **defined terms**.
1628
1632
  *
1629
1633
  * The first value may be empty (the ISO-2022 "G0 starts as ISO_IR 6" rule);
1630
1634
  * it is preserved as `""` rather than dropped so {@link resolveDecoderLabel}
1631
1635
  * can apply the default-repertoire fallback.
1632
1636
  *
1637
+ * A component this closed table does not name is not a defined term, and it is
1638
+ * **withheld** rather than returned. This value is multi-valued on the
1639
+ * backslash, every component is a string a sender authored, and the result
1640
+ * lands on `Element.specificCharacterSet`, where it presents itself as an
1641
+ * identifier a downstream package may interpolate: that is the field, and the
1642
+ * delimiter, this package's measured PHI leak ran through. Decoding is
1643
+ * unaffected, because {@link resolveDecoderLabel} already skipped any term it
1644
+ * could not map and skips the withheld marker for the same reason.
1645
+ *
1633
1646
  * @example
1634
1647
  * ```ts
1635
1648
  * import { parseSpecificCharacterSet } from "@cosyte/dicom";
@@ -2339,9 +2352,187 @@ interface DeidentifiedAttribute {
2339
2352
  readonly repeatingGroup?: string;
2340
2353
  }
2341
2354
  /**
2342
- * The audit trail returned alongside the de-identified dataset. Contains no
2343
- * decoded values - only tags, keywords, action codes, and the UID map (whose
2344
- * keys/values are UIDs, not patient data).
2355
+ * One value that was emptied because a Data Element was found **inside** it.
2356
+ *
2357
+ * PS3.5 defines Value Length as the length of that element's own Value Field. A
2358
+ * sender that over-declares it produces a file whose reading is self-consistent
2359
+ * and whose next element has been absorbed into the previous one's value - and
2360
+ * Table E.1-1 is keyed by tag, so an absorbed `(0010,0020)` is not an attribute
2361
+ * any longer and no action fires on it. `deidentify` therefore refuses to keep a
2362
+ * value whose tail decodes as whole Data Elements it would have acted on
2363
+ * (PS3.15 §E.1 "all instances"; §E.3.5 is the standard's own precedent for
2364
+ * removing identifying information embedded inside a string attribute).
2365
+ *
2366
+ * Every field is structural: `tag` and `vr` are the carrier's, and `hidden`
2367
+ * holds tags composed from four bytes each. No decoded value appears here, so
2368
+ * this is safe to log.
2369
+ *
2370
+ * @example
2371
+ * ```ts
2372
+ * import { deidentify, parseDicom } from "@cosyte/dicom";
2373
+ * const { report } = deidentify(parseDicom(buf));
2374
+ * for (const e of report.embeddedAttributes) {
2375
+ * console.warn(`${e.tag} hid ${e.hidden.join(", ")} in its value`);
2376
+ * }
2377
+ * ```
2378
+ */
2379
+ interface EmbeddedAttributeFinding {
2380
+ /** The carrier - the element whose over-declared value held the others. */
2381
+ readonly tag: Tag;
2382
+ /** The carrier's VR. Always one of the string VRs; binary VRs are not scanned. */
2383
+ readonly vr: VR;
2384
+ /** The tags of the Data Elements found inside the carrier's value, in wire order. */
2385
+ readonly hidden: readonly Tag[];
2386
+ /** Tag/index chain when the carrier is inside a sequence item; omitted at the root. */
2387
+ readonly contextPath?: readonly string[];
2388
+ }
2389
+ /**
2390
+ * One `SQ` element that was emptied because the parser never materialized its
2391
+ * items, so the de-identifier had no Data Sets to walk.
2392
+ *
2393
+ * PS3.5 2026c §7.5.1 "Item Encoding Rules" states that "Each Item Value shall
2394
+ * contain a DICOM Data Set composed of Data Elements", so an `SQ` element's
2395
+ * value is never opaque bytes - it is Data Elements this run is obliged to
2396
+ * reach. PS3.15 2026c §E.1.1 "De-identifier" states that obligation directly:
2397
+ * an implementation claiming the Basic Application Level Confidentiality
2398
+ * Profile "shall protect or retain all instances of the Attributes listed in
2399
+ * [Table E.1-1], whether contained in the top level Data Set or embedded in an
2400
+ * Item of a Sequence of Items". When the item stream cannot be enumerated the
2401
+ * obligation cannot be discharged element by element, so it falls on the
2402
+ * enclosing attribute - the escalation §E.1.1 itself uses for a SOP Instance
2403
+ * UID inside a Sequence, where "the enclosing Attribute in the top-level Data
2404
+ * Set must be encrypted in its entirety". (That sentence is written about the
2405
+ * encrypt-and-replace mechanism for SOP Instance UIDs, not about Table E.1-1
2406
+ * generally; it is cited here as the standard's own precedent for escalating to
2407
+ * the carrier, not as a rule about this case.)
2408
+ *
2409
+ * Both fields are structural: `tag` is the carrier's and `byteLength` is the
2410
+ * declared size of the value that was dropped. No decoded value appears here,
2411
+ * so this is safe to log.
2412
+ *
2413
+ * The parser always announces the underlying refusal first, on
2414
+ * `Dataset.warnings`: `DICOM_SQ_NOT_DESCENDED` for a defined-length Implicit VR
2415
+ * LE value whose dictionary-resolved `SQ` was not a valid item stream.
2416
+ *
2417
+ * @example
2418
+ * ```ts
2419
+ * import { deidentify, parseDicom } from "@cosyte/dicom";
2420
+ * const { report } = deidentify(parseDicom(buf));
2421
+ * for (const s of report.unauditableSequences) {
2422
+ * console.warn(`${s.tag}: ${String(s.byteLength)} bytes dropped, item stream unreadable`);
2423
+ * }
2424
+ * ```
2425
+ */
2426
+ interface UnauditableSequenceFinding {
2427
+ /** The `SQ` element that was emptied. */
2428
+ readonly tag: Tag;
2429
+ /** Byte length of the value field that was dropped. Structural, never a value. */
2430
+ readonly byteLength: number;
2431
+ /** Tag/index chain when the carrier is inside a sequence item; omitted at the root. */
2432
+ readonly contextPath?: readonly string[];
2433
+ }
2434
+ /**
2435
+ * One element that was emptied because its **on-wire VR is not one of the 34
2436
+ * PS3.5 section 6.2 defines**, so nothing this library did to its bytes counts
2437
+ * as decoding a Value Field.
2438
+ *
2439
+ * ## Why such an element exists at all
2440
+ *
2441
+ * Under an Explicit VR Transfer Syntax the VR is two bytes the sender wrote, and
2442
+ * this parser trusts them (Postel's Law on the read path). The routine way two
2443
+ * arbitrary bytes end up in a VR field is an **under**-declared Value Length
2444
+ * upstream: the reader finishes the short value, and the leftover bytes of the
2445
+ * value that was actually encoded are read as the next Data Element header. Tag,
2446
+ * VR and length are then all fragments of somebody's value, and the element that
2447
+ * genuinely followed is consumed as this fabricated element's "value".
2448
+ *
2449
+ * Measured on `scripts/measure-sq-bound-grid.ts`: a carrier under-declaring by 6
2450
+ * produces `(4156,554C)` with the VR bytes `"E "`, whose value holds the source
2451
+ * `(0010,0020)` Patient ID in full. It reaches **string** carriers exactly as it
2452
+ * reaches binary ones, because the carrier's own VR is not what decides it.
2453
+ *
2454
+ * ## Why emptying, and why it is not a guess
2455
+ *
2456
+ * PS3.5 2026c section 6.2 requires every VR not yet defined to use the long-form
2457
+ * Data Element Structure - "with reserved bytes after the VR and a 32-bit
2458
+ * unsigned integer VL" - so an unrecognized VR read short-form, which is what
2459
+ * this parser does, is by the standard's own structure rule not a reading of a
2460
+ * Value Field. There is nothing to prove about the content: the test is a
2461
+ * membership check against the closed 34-VR set on a field the parser already
2462
+ * recorded, so there is no scan, no per-offset loop, and no cost that follows an
2463
+ * attacker-chosen value length.
2464
+ *
2465
+ * PS3.15 2026c section E.1.1 obliges an implementation claiming the Basic
2466
+ * Application Level Confidentiality Profile to "protect or retain all instances
2467
+ * of the Attributes listed in [Table E.1-1]". Those instances cannot be reached
2468
+ * inside bytes that were never a value, so the obligation falls on the carrier.
2469
+ *
2470
+ * **`UN` is not this.** `UN` is one of the 34, so an ordinary unknown-VR element
2471
+ * - the Implicit VR fallback for a tag this build's dictionary does not publish,
2472
+ * and the CP-246 shape - never reaches here. That is the line the sibling
2473
+ * `SQ`-with-no-items rule could not draw.
2474
+ *
2475
+ * ## Why this finding names no tag, when every sibling finding does
2476
+ *
2477
+ * Because the tag **may be content**, and nothing here can tell. The paragraph
2478
+ * above is the whole argument: when an under-declare desynchronized the reader,
2479
+ * the four tag bytes and the two VR bytes were read out of the middle of some
2480
+ * element's Value Field, so reporting the "tag" would republish four bytes of
2481
+ * the document. An unrecognized VR written honestly, at a correct length, raises
2482
+ * this same code and has an ordinary tag - **and the two are indistinguishable
2483
+ * here**, so the tag is withheld on both routes rather than on a guess. Measured on a synthetic `ST` carrier holding
2484
+ * `"MR BRAIN SMITHSON"`, the fabricated tag is `48544F53` - four bytes of the
2485
+ * surname. {@link EmbeddedAttributeFinding} and
2486
+ * {@link UnauditableSequenceFinding} may carry a tag because theirs came from a
2487
+ * header the sender really wrote; this one may not, and the asymmetry is the
2488
+ * finding rather than an inconsistency.
2489
+ *
2490
+ * `byteOffset` locates the element instead - a position this parser counted.
2491
+ * Nothing here renders a document byte: an offset the parser counted, a decoded
2492
+ * length, and the structural `contextPath`. Safe to log.
2493
+ *
2494
+ * @example
2495
+ * ```ts
2496
+ * import { deidentify, parseDicom } from "@cosyte/dicom";
2497
+ * const { report } = deidentify(parseDicom(buf));
2498
+ * for (const u of report.undefinedVrElements) {
2499
+ * console.warn(`offset ${String(u.byteOffset)}: ${String(u.byteLength)} bytes dropped`);
2500
+ * }
2501
+ * ```
2502
+ */
2503
+ interface UndefinedVrFinding {
2504
+ /**
2505
+ * Byte offset of the emptied element's header. **This is how the element is
2506
+ * identified, and there is deliberately no `tag` field** - see the note above:
2507
+ * a fabricated header's tag bytes are part of some element's value.
2508
+ */
2509
+ readonly byteOffset: number;
2510
+ /**
2511
+ * Byte length of the value field that was dropped.
2512
+ *
2513
+ * **An input-derived count, and on this finding specifically that is not a
2514
+ * formality.** For the sibling findings the length came from a header the
2515
+ * sender wrote; here the two length bytes can themselves be value bytes, like
2516
+ * the tag bytes. What is published is the *number* they decode to, never the
2517
+ * bytes - the same footing as every `{n}` in the warning registry, which is
2518
+ * documented there as "an input-derived count". A number is not a rendering,
2519
+ * and the reach is at most a character or so, but do not describe this field
2520
+ * as "structural, never a value" the way its siblings are described.
2521
+ */
2522
+ readonly byteLength: number;
2523
+ /** Tag/index chain when the carrier is inside a sequence item; omitted at the root. */
2524
+ readonly contextPath?: readonly string[];
2525
+ }
2526
+ /**
2527
+ * The audit trail returned alongside the de-identified dataset.
2528
+ *
2529
+ * Every field except one is composed from static tables: tags, Part 6 keywords,
2530
+ * Annex E action codes, structural `TAG[index]` sequence paths, and registry
2531
+ * warning messages. **`uidMap` is the exception, and it is not value-free.** Its
2532
+ * keys are the source UIDs read out of the file, kept so a caller can make UID
2533
+ * replacement consistent across a study or an archive. A Study or SOP Instance
2534
+ * UID is a unique identifying number, so treat `uidMap` as PHI: the rest of the
2535
+ * report is safe to log, and that field is not.
2345
2536
  *
2346
2537
  * @example
2347
2538
  * ```ts
@@ -2356,7 +2547,74 @@ interface DeidentifyReport {
2356
2547
  readonly attributes: readonly DeidentifiedAttribute[];
2357
2548
  /** Private tags removed under the Basic Profile (kept ones are omitted). */
2358
2549
  readonly removedPrivateTags: readonly Tag[];
2359
- /** Source UID → replacement UID, for cross-file consistency. */
2550
+ /**
2551
+ * Values emptied because whole Data Elements were encoded inside them by an
2552
+ * over-declared Value Length. Empty on a well-formed file; a non-empty array
2553
+ * means the *source* was malformed in a way that hid attributes from the
2554
+ * action table, so treat it as a data-quality alarm on the sender as well as
2555
+ * an audit line. See {@link EmbeddedAttributeFinding}.
2556
+ */
2557
+ readonly embeddedAttributes: readonly EmbeddedAttributeFinding[];
2558
+ /**
2559
+ * `SQ` elements emptied because the parser did not materialize their items, so
2560
+ * the run had no Data Sets to walk and could not discharge PS3.15 §E.1.1's
2561
+ * obligation inside them. Empty on a well-formed file; a non-empty array means
2562
+ * content was dropped from the de-identified output, and the matching
2563
+ * `DICOM_SQ_NOT_DESCENDED` entry on `Dataset.warnings` says why the parse
2564
+ * refused. See {@link UnauditableSequenceFinding}.
2565
+ *
2566
+ * **Capped, and the cap is on the record only.** A crafted input can carry
2567
+ * tens of thousands of un-auditable elements, so this array (and its matching
2568
+ * warnings) stops at `MAX_UNAUDITABLE_SEQUENCE_FINDINGS`. Every un-auditable
2569
+ * sequence is still emptied; an array exactly that long means "at least this
2570
+ * many", so read it as truncated rather than as a total.
2571
+ *
2572
+ * **It is not a complete list of what went un-audited, either**: a private
2573
+ * `SQ` a {@link Profile} vouches for under `RetainSafePrivate` is kept
2574
+ * verbatim and never appears here.
2575
+ */
2576
+ readonly unauditableSequences: readonly UnauditableSequenceFinding[];
2577
+ /**
2578
+ * Elements emptied because their on-wire VR is not one of the 34 PS3.5 §6.2
2579
+ * defines, so their bytes are not a Value Field this library decoded and
2580
+ * PS3.15 §E.1.1's obligation over what is inside them could not be discharged.
2581
+ * Empty on a file conformant to **PS3.5 2026c**: a sender that writes one of
2582
+ * the 34 VRs that edition defines never produces one, and an Implicit VR LE
2583
+ * file **cannot** - there the VR comes from the dictionary. The edition is not
2584
+ * pedantry: §6.2 exists precisely to say how a *future* VR will be encoded, so
2585
+ * a file conformant to a later edition using a newly defined VR is the
2586
+ * population that sentence exists for. (What such a file does on this
2587
+ * library's parse path is not summarized here - it was measured and the
2588
+ * shapes disagree.) A non-empty array
2589
+ * means the source desynchronized the reader, usually by under-declaring a
2590
+ * Value Length somewhere earlier. See {@link UndefinedVrFinding}.
2591
+ *
2592
+ * **Capped, and the cap is on the record only**, exactly as
2593
+ * {@link DeidentifyReport.unauditableSequences} is: a crafted 1 MiB input can
2594
+ * carry over a hundred thousand such elements, so this array and its matching
2595
+ * warnings stop at `MAX_UNDEFINED_VR_FINDINGS`. Every one of them is still
2596
+ * emptied; an array exactly that long means "at least this many".
2597
+ *
2598
+ * **A finding here names a byte offset, not a tag** - uniquely among the
2599
+ * report's findings, and for a reason worth reading in
2600
+ * {@link UndefinedVrFinding}: the tag of a fabricated header is itself part of
2601
+ * some element's value.
2602
+ *
2603
+ * Unlike its sibling this list has **no carve-out**, and the reason is
2604
+ * structural rather than a promise: `keepOrEmpty` is the **only** path that
2605
+ * writes a source value into de-identified output unchanged, and the test sits
2606
+ * at the top of it. Every other outcome - `X` remove, `Z`/`C` empty, `D` dummy,
2607
+ * `U` remap, and a private tag the Basic Profile drops - already replaces the
2608
+ * value. So a `RetainSafePrivate` element a {@link Profile} vouches for still
2609
+ * reaches this test and is still emptied, which is where the sibling
2610
+ * `SQ`-with-no-items rule has a real carve-out and this one does not.
2611
+ */
2612
+ readonly undefinedVrElements: readonly UndefinedVrFinding[];
2613
+ /**
2614
+ * Source UID → replacement UID, for cross-file consistency. The **keys are
2615
+ * document values**, not composed identifiers: this is the one field of the
2616
+ * report that carries PHI.
2617
+ */
2360
2618
  readonly uidMap: ReadonlyMap<string, string>;
2361
2619
  /** Safety warnings - notably burned-in-pixel annotation that cannot be cleaned. */
2362
2620
  readonly warnings: readonly DicomParseWarning[];
@@ -2470,7 +2728,8 @@ declare class DeidentifyError extends Error {
2470
2728
  * nine *metadata-affecting* Annex E Options, driven by the generated Table E.1-1
2471
2729
  * action map ({@link annexE}). It is a **pure** function: the input {@link Dataset}
2472
2730
  * is never mutated; a fresh `Dataset` (with a rebuilt element map and File Meta)
2473
- * is returned alongside a value-free {@link DeidentifyReport}.
2731
+ * is returned alongside a {@link DeidentifyReport} that is value-free except for
2732
+ * `uidMap`, whose keys are the file's own source UIDs.
2474
2733
  *
2475
2734
  * **What it does**
2476
2735
  * - Resolves each attribute's action (basic profile, overridden by an active
@@ -2508,9 +2767,43 @@ declare class DeidentifyError extends Error {
2508
2767
  * - A private data element kept under `RetainSafePrivate` is kept *verbatim* - if
2509
2768
  * it is itself a sequence carrying standard PHI attributes, that nested content
2510
2769
  * is not recursed. The profile vouches the element is safe; nest accordingly.
2511
- * - A sequence whose `items` the parser did not materialize (e.g. an
2512
- * undefined-length `UN` value stored as an opaque span) is kept verbatim rather
2513
- * than recursed, so any nested listed attributes are not de-identified.
2770
+ * - A **standard** (non-private) `SQ` whose `items` the parser did not
2771
+ * materialize is **emptied**, not kept:
2772
+ * its value is by PS3.5 §7.5.1 a stream of Data Sets, and a run that cannot
2773
+ * enumerate them cannot discharge §E.1.1's obligation inside them, so the
2774
+ * fail-safe answer is to drop the carrier and say so
2775
+ * (`DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE` + `report.unauditableSequences`).
2776
+ * That costs content, and the trade is deliberate: it used to be *kept
2777
+ * verbatim*, which wrote the sender's own un-audited Data Elements - measured,
2778
+ * `(0010,0020)` Patient IDs among them - into output stamped
2779
+ * `(0012,0062) PatientIdentityRemoved = YES`
2780
+ * (`DICOM-DEIDENT-RAWBYTES-PASSTHROUGH`, live through `0.0.6`).
2781
+ * - Two things are still kept verbatim, deliberately. A **private** `SQ`
2782
+ * retained under `RetainSafePrivate` + a {@link Profile}, where the profile has
2783
+ * vouched for the element by creator and tag - the pre-existing "kept
2784
+ * verbatim" limitation above, unchanged. And an undefined-length **`UN`** whose
2785
+ * CP-246 descent was refused: it keeps `vr === "UN"`, and since every ordinary
2786
+ * `UN` element also has no items, the test above cannot be applied there
2787
+ * without emptying every unknown-VR element in every file. That one is
2788
+ * measured and **still leaks** (`PRE-EXISTING`); the reliable consumer-side
2789
+ * test remains `el.items === undefined` on a `UN` element you are trusting a
2790
+ * report about.
2791
+ * - An element whose **on-wire VR is not one of the 34** PS3.5 §6.2 defines is
2792
+ * **emptied**, not kept (`DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE` +
2793
+ * `report.undefinedVrElements`). Such an element is what an *under*-declared
2794
+ * Value Length upstream produces: the reader desynchronizes, reads leftover
2795
+ * value bytes as a Data Element header, and the element that genuinely
2796
+ * followed becomes its "value" - measured carrying a `(0010,0020)` Patient ID
2797
+ * in full. §6.2 requires every undefined VR to be long-form, and this parser
2798
+ * reads it short-form, so those bytes are not a Value Field this library
2799
+ * decoded under any VR. See {@link hasUndefinedVr}. **Unlike the `SQ` rule
2800
+ * above this one has no carve-out** - it sits in `keepOrEmpty`, the only path
2801
+ * that keeps a value verbatim, so `RetainSafePrivate` does not exempt it.
2802
+ * - **Still leaking, measured, and its own decision:** the over-declare swallow
2803
+ * into a **binary** carrier (`OB`/`OW`/`US`/`UN`), 11 grid cells at
2804
+ * `35adc2d`. No content test can decide it - arbitrary bytes are what those
2805
+ * VRs are for - and the one candidate remedy empties conformant binary values.
2806
+ * See `./embedded.ts`.
2514
2807
  *
2515
2808
  * @module
2516
2809
  */
@@ -2519,8 +2812,10 @@ declare class DeidentifyError extends Error {
2519
2812
  * De-identify a {@link Dataset} per PS3.15 Annex E - the Basic Application Level
2520
2813
  * Confidentiality Profile, plus any Retain/Clean Options passed in `retain`.
2521
2814
  *
2522
- * Pure: `ds` is never mutated. Returns a fresh dataset and a value-free
2523
- * {@link DeidentifyReport} (tags, keywords, action codes, the UID map, warnings).
2815
+ * Pure: `ds` is never mutated. Returns a fresh dataset and a
2816
+ * {@link DeidentifyReport} of tags, keywords, action codes, warnings and the UID
2817
+ * map. Everything in it is composed from static tables except `uidMap`, whose
2818
+ * keys are the source UIDs the file carried: treat that field as PHI.
2524
2819
  *
2525
2820
  * @throws {@link DeidentifyError} (`INVALID_OPTIONS`) for an unknown Retain option
2526
2821
  * or a malformed `uidRoot`.
@@ -2599,4 +2894,4 @@ interface UidRemapper {
2599
2894
  */
2600
2895
  declare function makeUidRemapper(root?: string, cache?: Map<string, string>): UidRemapper;
2601
2896
 
2602
- export { type AppliedAction, CODING_SCHEME_OIDS, type CodedConcept, DEFAULT_UID_ROOT, DEIDENTIFY_ERROR_CODES, DEIDENTIFY_OPTIONS, Dataset, type DefineProfileOptions, type DeidentifiedAttribute, DeidentifyError, type DeidentifyErrorCode, type DeidentifyOption, type DeidentifyOptions, type DeidentifyReport, type DeidentifyResult, type DicomDate, type DicomDateTime, DicomParseError, type DicomParseWarning, type DicomPosition, DicomSerializeError, type DicomTime, type DicomValue, DicomValueError, index as Dictionary, Element, FATAL_CODES, type FatalCode, type FileMeta, type FileMetaRawElement, type FrameFunctionalGroups, type ImageView, Item, type OnWarningCallback, type OtherPatientId, type ParseOptions, type PatientView, type PersonName, type PersonNameGroup, type PrivateTagDefinition, type Profile, ProfileDefinitionError, type ProfilePrivateTags, type RealWorldValueMap, SERIALIZE_ERROR_CODES, Sequence, type SerializeErrorCode, type SeriesView, type StudyView, type UidRemapper, VALUE_ERROR_CODES, VERSION, type ValueErrorCode, WARNING_CODES, type WarningCode, codingSchemeOid, decodeElementValue, decodeText, defineProfile, deidentify, isKnownCharsetTerm, makeUidRemapper, parseDate, parseDateTime, parseDicom, parsePersonName, parseSpecificCharacterSet, parseTime, profiles, readCode, resolveDecoderLabel, serializeDicom };
2897
+ export { type AppliedAction, CODING_SCHEME_OIDS, type CodedConcept, DEFAULT_UID_ROOT, DEIDENTIFY_ERROR_CODES, DEIDENTIFY_OPTIONS, Dataset, type DefineProfileOptions, type DeidentifiedAttribute, DeidentifyError, type DeidentifyErrorCode, type DeidentifyOption, type DeidentifyOptions, type DeidentifyReport, type DeidentifyResult, type DicomDate, type DicomDateTime, DicomParseError, type DicomParseWarning, type DicomPosition, DicomSerializeError, type DicomTime, type DicomValue, DicomValueError, index as Dictionary, Element, type EmbeddedAttributeFinding, FATAL_CODES, type FatalCode, type FileMeta, type FileMetaRawElement, type FrameFunctionalGroups, type ImageView, Item, type OnWarningCallback, type OtherPatientId, type ParseOptions, type PatientView, type PersonName, type PersonNameGroup, type PrivateTagDefinition, type Profile, ProfileDefinitionError, type ProfilePrivateTags, type RealWorldValueMap, SERIALIZE_ERROR_CODES, Sequence, type SerializeErrorCode, type SeriesView, type StudyView, type UidRemapper, type UnauditableSequenceFinding, type UndefinedVrFinding, VALUE_ERROR_CODES, VERSION, type ValueErrorCode, WARNING_CODES, type WarningCode, codingSchemeOid, decodeElementValue, decodeText, defineProfile, deidentify, isKnownCharsetTerm, makeUidRemapper, parseDate, parseDateTime, parseDicom, parsePersonName, parseSpecificCharacterSet, parseTime, profiles, readCode, resolveDecoderLabel, serializeDicom };