@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/CHANGELOG.md +438 -31
- package/README.md +12 -7
- package/dist/index.cjs +548 -155
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +307 -12
- package/dist/index.d.ts +307 -12
- package/dist/index.mjs +549 -156
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.cts
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
|
-
*
|
|
2343
|
-
*
|
|
2344
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
2512
|
-
*
|
|
2513
|
-
*
|
|
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
|
|
2523
|
-
* {@link DeidentifyReport}
|
|
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 };
|