Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ Everything is exported from the single top-level entry point, `@cosyte/dicom`. T
- **Two access patterns**: named views, or structural `ds.get("00100010")` by 8-character `(group,element)` tag (resolve a keyword to its tag with `Dictionary.byKeyword`), plus `ds.elements()` to walk everything.
- **Compressed objects read, pixels untouched**: every PS3.5 2026c section A.4 encapsulation syntax (JPEG, JPEG-LS, JPEG 2000, HTJ2K, RLE and the rest) parses under Explicit VR LE rules, and `readPixelDataFragments(ds)` hands back the Basic Offset Table and every fragment as raw bytes. Pixels are never decoded. `serializeDicom` writes those syntaxes back, fragments byte for byte, and refuses with `INVALID_ENCAPSULATED_PIXEL_DATA` a top-level Pixel Data section A.4 does not allow.
- **Lazy typed value decode**: `element.value` decodes raw bytes into a discriminated `DicomValue` across all 34 VRs (numbers, `bigint`s, person names, dates/times, sequences, raw `binary`), honoring `(0008,0005)` Specific Character Set through nested items.
- **Real-world tolerance, Postel's Law**: a lenient reader emits stable warning codes for what it tolerated; only 4 truly-structural conditions are fatal. The serializer always writes spec-clean Part 10. (The count used to be quoted here and had drifted: it read `25` against a `WARNING_CODES` of `28`. The locked snapshot in `test/property/__snapshots__/warning-codes.snapshot.test.ts.snap` is the pin, and it is measured on every run rather than narrated here.)
- **Real-world tolerance, Postel's Law**: a lenient reader emits stable warning codes for what it tolerated; only 4 truly-structural conditions are fatal. (The count used to be quoted here and had drifted: it read `25` against a `WARNING_CODES` of `28`. The locked snapshot in `test/property/__snapshots__/warning-codes.snapshot.test.ts.snap` is the pin, and it is measured on every run rather than narrated here.)
- **Source/vendor profile system**: `defineProfile()` + 5 built-ins (`ge`, `siemens`, `philips`, `strict`, `lenient`) that only ever _tighten or annotate_ a parse, resolving vendor private tags by the file's live Private Creator string, never a wrong decode.
- **Metadata-level de-identification**: `deidentify()` applies the PS3.15 Annex E Basic Profile + the metadata-affecting Options, returning a fresh dataset and an audit report built from static tables, with two fields that carry source bytes and are named as such on the type: `report.uidMap` (source UIDs) and `report.removedPrivateTags`.
- **Spec-clean serializer**: `serializeDicom(ds)` round-trips a dataset back to Part 10 bytes in its source transfer syntax (no transcode; the four native syntaxes and every PS3.5 2026c section A.4 encapsulation syntax, whose top-level Pixel Data is written as an `OB` fragment stream with its Items byte for byte, or refused with `INVALID_ENCAPSULATED_PIXEL_DATA` when section A.4 does not allow it), with correct File Meta group length, even-length padding, every Data Set it can walk (the root and each Sequence Item, at every depth up to `NESTING_DEPTH_LIMIT`) emitted in ascending tag order with Items kept in order and no value changed, and lossless File Meta: non-modeled `(0002,xxxx)` elements are preserved and re-emitted in tag order. Not ordered: a tag repeated inside an Item (both copies kept), a Sequence that cannot be walked, is nested past the bound or whose parsed `items` do not match its bytes, and any `UN`-carried Sequence. Written last whatever its tag: an element whose own bytes do not show where a reader ends it, such as an undefined-length `UN` the parser could not read as a Sequence.
Expand Down Expand Up @@ -306,7 +306,7 @@ At an RSNA-era interoperability test, ~80% of real-world patient CDs failed stri

| Tier | Behavior | When | Example codes |
| ---- | -------------- | ------------------------------ | ------------------------ |
| 0 | Silent | Spec-compliant input | none |
| 0 | Silent | No deviation detected | none |
| 1 | Auto-handled | Trivial deviation, no warning | trailing-space tidy |
| 2 | Warning | Recoverable deviation | `DICOM_MISSING_PREAMBLE` |
| 3 | Fatal (always) | Unrecoverable structural error | `NOT_DICOM_PART_10` |
Expand Down Expand Up @@ -346,7 +346,7 @@ const ds = parseDicom(buf, { profile: profiles.siemens });
A profile bundles three things that only ever **tighten or annotate** a parse, never loosen it past the lenient default:

- **Private-dictionary overlay**: resolves the Implicit VR of vendor private data elements by the file's _live_ Private Creator string (e.g. `"SIEMENS CSA HEADER"`), keyed canonically as `"GGGGxxLL"` (PS3.5 §7.8.1), never a hard-coded block number. (This is why Agfa IMPAX re-assigning blocks still resolves.) An unknown creator degrades to `UN` plus a `DICOM_PRIVATE_CREATOR_UNKNOWN` warning. The lookup is scoped to one Data Set, and every Sequence Item is its own (PS3.5 §7.5.1, §7.8.1): a block claimed at the root does not resolve an element inside an item, and an element whose block was never claimed in its own Data Set reads `UN` plus `DICOM_PRIVATE_TAG_NO_CREATOR` rather than borrowing a neighbour's VR. Declare the creator in each item that writes private data.
- **Escalations**: Tier-2 warning codes promoted to a thrown `DicomParseError` (a stricter posture for known-unsafe deviations).
- **Escalations**: Tier-2 warning codes promoted to a thrown `DicomParseError` (a stricter posture).
- **Suppressions**: benign, high-volume warning codes silenced for a known-quirky source.

Five built-ins ship under the `profiles` namespace: `ge`, `siemens`, `philips` (vendor overlays, grounded in the public GDCM / dcm4che / dcm2niix dictionaries) and `strict` / `lenient` (posture presets). Build your own with `defineProfile()`. It validates input, composes via `extends`, and returns a frozen profile:
Expand Down
2 changes: 1 addition & 1 deletion docs-content/cookbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -712,7 +712,7 @@ ds.warnings.every((w) => typeof w.code === "string"); // => true
```

**Escalate when you want strictness.** A [source profile](./spec-notes-profiles) can promote chosen
warning codes to a thrown `DicomParseError` (a spec-conformance gate for a trusted sender) or
warning codes to a thrown `DicomParseError` (a stricter gate for a trusted sender) or
suppress benign, high-volume codes for a known-quirky source. Note that `{ strict: true }` turns
every Tier-2 warning into a `DicomParseError`, which carries the raw `snippet` the warning does not,
so a PHI review of the lenient path does not transfer to the strict one.
8 changes: 4 additions & 4 deletions docs-content/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ if (img.isEnhancedMultiFrame) {
The parser is **lenient by default**: the quirks real scanners emit (odd-length values, missing
padding, off-spec VRs) become warnings carrying a stable code and the byte offset where they
occurred, not failures. Only an unrecoverable structural condition throws, and `FATAL_CODES` is the
whole set of them. When you re-serialize, the writer always emits spec-clean Part 10: correct File
Meta group length, even-length values, proper padding (Postel's Law). See
whole set of them. When you re-serialize, the writer emits a correct File Meta group length,
even-length values and proper padding (Postel's Law). See
[Serialization](./serialization) for what the writer will and will not do.

## Source profiles
Expand All @@ -168,8 +168,8 @@ the lenient default:
- **Private-dictionary overlay**: resolves the Implicit VR of vendor private data elements by the
file's _live_ private-creator string (e.g. `"SIEMENS CSA HEADER"`), never a hard-coded block number.
A creator the profile does not know degrades to `UN` plus a `DICOM_PRIVATE_CREATOR_UNKNOWN` warning.
- **Escalations**: Tier-2 warning codes promoted to a thrown `DicomParseError` (a stricter posture
for known-unsafe deviations).
- **Escalations**: Tier-2 warning codes promoted to a thrown `DicomParseError` (a stricter
posture).
- **Suppressions**: benign, high-volume warning codes silenced for a known-quirky source.

Five built-ins ship under the `profiles` namespace: `ge`, `siemens`, `philips` (vendor overlays) and
Expand Down
2 changes: 1 addition & 1 deletion docs-content/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ than substituting a plausible value. `undefined` means the object did not carry
| What a diagnostic carries and what it does not | [Tolerance](./spec-notes-tolerance) |
| Keeping PHI out of logs | [Keeping PHI out of logs](./troubleshooting#keeping-phi-out-of-logs) |
| The safety-critical views and their fail-safe rules | [Safety](./spec-notes-safety) |
| Writing spec-clean bytes back out, and what the writer will not do | [Serialization](./serialization) |
| Writing bytes back out, and what the writer will not do | [Serialization](./serialization) |
| The de-identification surface and the scope limits on it | [De-identification](./deidentification) |
| Working recipes, each citing the PS3 clause it reads | [Cookbook](./cookbook) |

Expand Down
12 changes: 6 additions & 6 deletions docs-content/serialization.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
id: serialization
title: Serializing spec-clean Part 10
title: Serializing Part 10
sidebar_label: Serialization
---

# Serializing spec-clean Part 10
# Serializing Part 10

`serializeDicom(ds)` writes a `Dataset` back to a Part 10 `Buffer`. It is the conservative half of
Postel's Law: the parser accepts what real scanners emit, and the writer emits only what the standard
describes. Whatever quirks came in, what goes out is spec-clean.
Postel's Law: the parser accepts what real scanners emit, and the writer applies the guarantees
below.

Every DICOM object on this page is **synthetic** (an invented patient, obviously-fake UIDs) and
encoded as a base64 buffer, so an example needs no file on disk. Never paste a real object into a
Expand Down Expand Up @@ -89,8 +89,8 @@ serializeDicom(parseDicom(out)).equals(out); // => true
## When it throws, and what the error carries

The writer has its own error taxonomy, separate from the parser's `FATAL_CODES` (which are locked to
read-side structural corruption) and from the value layer's `DicomValueError`. It throws only when it
is asked to emit a buffer it cannot make spec-clean.
read-side structural corruption) and from the value layer's `DicomValueError`. It throws only with
a code listed below.

| Export | What it is |
| ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
Expand Down
2 changes: 1 addition & 1 deletion docs-content/spec-notes-profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ default:
parses under the lenient default and throws under `strict`.

- **Escalations**: chosen Tier-2 warning codes promoted to a thrown `DicomParseError`, a stricter
posture for known-unsafe deviations from a trusted sender.
posture for a trusted sender.
- **Suppressions**: benign, high-volume warning codes silenced for a known-quirky source.

## The five built-ins
Expand Down
4 changes: 2 additions & 2 deletions docs-content/spec-notes-tolerance.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ sidebar_label: Tolerance & warnings
Real scanners and archives emit objects that deviate from the letter of the standard in documented,
recoverable ways: odd-length values with no padding, a missing preamble, an off-spec VR, a
group-length that disagrees with reality. `@cosyte/dicom` follows **Postel's Law**: the parser is
liberal (it recovers and records a stable-coded warning), and the serializer is conservative (it
always emits spec-clean Part 10). A recoverable quirk is **never** a silent change and never a throw.
liberal (it recovers and records a stable-coded warning), and the serializer is conservative. A
recoverable quirk is **never** a silent change and never a throw.

## Two tiers plus a small fatal set

Expand Down
Loading
Loading