Skip to main content
Version: v0.0.12

@cosyte/dicom

Namespaces

Classes

Dataset

One parsed DICOM dataset - the structural shell shipped by Phase 2.

Phase 2 public surface: fileMeta, warnings. The internal element map is stored on _elements (protected) and Phase 3 promotes it to the public get / has / elements / getAll navigation API per D-42.

Example

import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
console.log(ds.fileMeta?.transferSyntaxUID);
for (const w of ds.warnings) {
console.warn(w.code, w.message);
}

Extended by

Constructors

Constructor

new Dataset(init): Dataset

Internal

Construct a new structural Dataset. Phase 2 freezes the warnings array at the model boundary (sibling message.ts lines 117–118).

Parameters
init

DatasetInit

Returns

Dataset

Properties

_elements

protected readonly _elements: ReadonlyMap<string, Element>

Internal

Element map, keyed by uppercase 8-hex tag. Phase 3 promotes to public navigation surface; Phase 2 keeps it protected so only subclasses (Item, future Phase-3 extensions) can introspect.

fileMeta

readonly fileMeta: FileMeta | undefined

warnings

readonly warnings: readonly DicomParseWarning[]

Accessors

image
Get Signature

get image(): ImageView

Pixel-interpretation + geometry view (§4.2–§4.5). Surfaces exactly what a renderer needs without guessing: rescaleSlope/signed/ photometricInterpretation stay absent rather than defaulted, and the three pixel-spacing tags are distinct. Memoised on first read.

Example
import { parseDicom } from "@cosyte/dicom";
const img = parseDicom(buf).image;
img.rescaleSlope; // undefined ⇒ MUST NOT assume 1
Returns

ImageView

patient
Get Signature

get patient(): PatientView

Patient-identity view (§4.1). Fail-safe typed-absent fields; id is not globally unique - match on the {id, issuerOfId, ...} tuple plus otherIds, never on a bare (0010,0020). Memoised on first read.

Example
import { parseDicom } from "@cosyte/dicom";
const p = parseDicom(buf).patient;
p.name?.alphabetic.familyName; // structured PN, never flattened
Returns

PatientView

series
Get Signature

get series(): SeriesView

Series-identity & co-registration view (§4.1, §4.3). A shared frameOfReferenceUid means images are spatially co-registered. Memoised on first read.

Example
import { parseDicom } from "@cosyte/dicom";
const s = parseDicom(buf).series;
s.modality; // "CT"
Returns

SeriesView

study
Get Signature

get study(): StudyView

Study-identity view (§4.1). instanceUid is the cross-system study key; accessionNumber ties it to the order. Memoised on first read.

Example
import { parseDicom } from "@cosyte/dicom";
const s = parseDicom(buf).study;
s.instanceUid; // "1.2.840.113619..."
Returns

StudyView

Methods

elements()

elements(): readonly Element[]

All elements in this dataset, in parse (insertion) order.

Returns

readonly Element[]

Example
import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
for (const el of ds.elements()) console.log(el.tag, el.vr);
get()

get(tag): Element | undefined

Look up a single element by tag. Tags are normalised to 8-char uppercase hex, so "7fe00010" and "7FE00010" resolve to the same element. Returns undefined when the tag is absent.

Parameters
tag

string

Returns

Element | undefined

Example
import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
const rows = ds.get("00280010"); // Rows
if (rows?.value.kind === "numbers") console.log(rows.value.values[0]);
getAll()

getAll(tag): readonly Element[]

All elements matching a tag as an array (never undefined). A dataset holds at most one element per tag, so this returns a 0- or 1-length array - the convenience complement of Dataset.get for callers that prefer an always-array shape.

Parameters
tag

string

Returns

readonly Element[]

Example
import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
for (const el of ds.getAll("00080060")) console.log(el.value);
has()

has(tag): boolean

true when an element with the given tag is present (case-insensitive).

Parameters
tag

string

Returns

boolean

Example
import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
if (ds.has("00100010")) console.log("has Patient's Name");

DeidentifyError

Thrown for an author-time misconfiguration of deidentify (an unknown Retain option, a malformed UID root). Distinct from the parser's fatal codes, the value layer's DicomValueError, and the serializer's DicomSerializeError. The message carries only structural facts (option names, the UID root) - never a decoded value.

Example

import { deidentify, DeidentifyError } from "@cosyte/dicom";
try {
// @ts-expect-error - not a valid option
deidentify(ds, { retain: ["RetainEverything"] });
} catch (e) {
if (e instanceof DeidentifyError) console.error(e.code); // "INVALID_OPTIONS"
}

Extends

  • Error

Constructors

Constructor

new DeidentifyError(message, code): DeidentifyError

Parameters
message

string

Human-readable, PHI-free description.

code

"INVALID_OPTIONS"

One of DEIDENTIFY_ERROR_CODES.

Returns

DeidentifyError

Overrides

Error.constructor

Properties

code

readonly code: "INVALID_OPTIONS"


DicomParseError

Thrown by parseDicom when the input violates one of the four unrecoverable Tier-3 structural rules - or, under { strict: true }, when any Tier-2 warning is escalated through the single emit chokepoint (D-35). Carries byte-offset positional context plus a short source snippet so consumers can log actionable errors.

Message format: [CODE] msg (offset=N), with … in a/b/c appended when contextPath is provided.

Remarks

Snippets may contain PHI when parsing real clinical files - redact at the call site if required by your compliance posture. The library does not redact snippets itself.

Example

import { parseDicom, DicomParseError } from "@cosyte/dicom";
try {
parseDicom(buffer);
} catch (err) {
if (err instanceof DicomParseError && err.code === "NOT_DICOM_PART_10") {
// err.byteOffset, err.snippet, err.contextPath all available
}
}

Extends

  • Error

Constructors

Constructor

new DicomParseError(code, message, byteOffset, snippet, contextPath?): DicomParseError

Internal

Construct a new DicomParseError. All fields except contextPath are required so every thrower populates positional context per TOL-02.

Parameters
code

FatalCode

message

string

byteOffset

number

snippet

string

contextPath?

readonly string[]

Returns

DicomParseError

Overrides

Error.constructor

Properties

byteOffset

readonly byteOffset: number

code

readonly code: FatalCode

contextPath

readonly contextPath: readonly string[] | undefined

snippet

readonly snippet: string


DicomSerializeError

Thrown by serializeDicom when a Dataset cannot be emitted as spec-clean Part 10. Never carries a decoded value, so it is safe to log without leaking PHI: the message is built only from the code and the offending Transfer Syntax UID.

Example

import { parseDicom, serializeDicom, DicomSerializeError } from "@cosyte/dicom";
const ds = parseDicom(buf);
try {
serializeDicom(ds);
} catch (err) {
if (err instanceof DicomSerializeError && err.code === "MISSING_TRANSFER_SYNTAX") {
// dataset is missing the File Meta Transfer Syntax UID
}
}

Extends

  • Error

Constructors

Constructor

new DicomSerializeError(code, message): DicomSerializeError

Internal

Construct a new DicomSerializeError. The message MUST be built only from structural facts (never a decoded attribute value) so the error is always safe to log.

Parameters
code

SerializeErrorCode

message

string

Returns

DicomSerializeError

Overrides

Error.constructor

Properties

code

readonly code: SerializeErrorCode


DicomValueError

Thrown by the Phase 4 helpers for a structural contract violation that cannot be answered safely (see module doc). Never carries a decoded value, so it is safe to log without leaking PHI: the message is built only from the code and structural facts (indices, tag/macro names).

Example

import { parseDicom, DicomValueError } from "@cosyte/dicom";
const img = parseDicom(buf).image;
try {
img.frame(9999);
} catch (err) {
if (err instanceof DicomValueError && err.code === "FRAME_INDEX_OUT_OF_RANGE") {
// handle the out-of-range request
}
}

Extends

  • Error

Constructors

Constructor

new DicomValueError(code, message): DicomValueError

Internal

Construct a new DicomValueError. The message MUST be built only from structural facts (never a decoded attribute value) so the error is always safe to log.

Parameters
code

ValueErrorCode

message

string

Returns

DicomValueError

Overrides

Error.constructor

Properties

code

readonly code: ValueErrorCode


Element

One DICOM Data Element as parsed by Phase 2.

Phase 2 surface (this plan): tag, vr, vm, length, rawBytes, byteOffset, privateCreator. NO .value getter, NO decoders, NO navigation methods. Phase 3 (per D-42) extends this class with a lazy, memoized .value getter and the VR-aware decoders under src/dataset/vr/.

rawBytes retention behaviour: zero-copy Buffer.subarray view by default (pins source ArrayBuffer); pass { copyValues: true } to parseDicom for Buffer.from(slice) per element when the source needs to be released.

Example

import { Buffer } from "node:buffer";
import { Element } from "@cosyte/dicom";
const el = new Element({
tag: "00100010",
vr: "PN",
vm: 1,
length: 8,
rawBytes: Buffer.from("DOE^JANE"),
byteOffset: 200,
littleEndian: true,
});
// el.tag === "00100010"; el.rawBytes is a Buffer of 8 bytes.
// el.value.kind === "personName".

Constructors

Constructor

new Element(init): Element

Internal

Construct a new structural Element. Producers (parser plans 02-03 onward) build these directly from on-wire bytes; consumers receive them via Dataset after parseDicom.

Parameters
init

ElementInit

Returns

Element

Properties

byteOffset

readonly byteOffset: number

cp246Promoted

readonly cp246Promoted: boolean | undefined

Internal

Phase 2 hint for Phase 3's lazy SQ decoder per D-30. true when this Element was promoted from VR=UN with undefined length to VR=SQ via the CP-246 fallback. Phase 3 uses this to choose the Implicit VR LE inner decoder. Always undefined on standard SQ elements.

items

readonly items: readonly Item[] | undefined

Parsed items for an SQ element; undefined otherwise.

length

readonly length: number

littleEndian

readonly littleEndian: boolean

Value byte order (per transfer syntax). See ElementInit.littleEndian.

privateCreator

readonly privateCreator: string | undefined

rawBytes

readonly rawBytes: Buffer

specificCharacterSet

readonly specificCharacterSet: readonly string[] | undefined

In-effect (0008,0005) terms, or undefined for the Default Repertoire.

tag

readonly tag: string

vm

readonly vm: number

vr

readonly vr: VR

Accessors

value
Get Signature

get value(): DicomValue

The decoded value of this element, by VR. Decode is lazy (the structural parse stays eager; field decode runs on first access) and memoized - the documented ~30× win on large studies where most fields are never read. Fail-safe: never throws; a malformed value surfaces as typed-absent with the deviation on the returned value's warnings.

Example
import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
const v = ds.get("00100010")?.value; // Patient's Name (PN)
if (v?.kind === "personName") console.log(v.values[0]?.alphabetic.familyName);
Returns

DicomValue


Item

One sequence item. Inherits fileMeta (always undefined for nested items), warnings, and the protected element map from Dataset.

Phase 3 surfaces Item.get(...) / Item.has(...) / etc. via the Dataset superclass extension per D-42.

Example

import { Item } from "@cosyte/dicom";
// Producers (parser plan 02-04) construct items as follows:
// const item = new Item({ index: 0, warnings: [], elements: new Map() });

Extends

Constructors

Constructor

new Item(init): Item

Internal

Construct a new structural Item. Producers are the SQ / FFFE marker parsers in plan 02-04.

Parameters
init

ItemInit

Returns

Item

Overrides

Dataset.constructor

Properties

_elements

protected readonly _elements: ReadonlyMap<string, Element>

Internal

Element map, keyed by uppercase 8-hex tag. Phase 3 promotes to public navigation surface; Phase 2 keeps it protected so only subclasses (Item, future Phase-3 extensions) can introspect.

Inherited from

Dataset._elements

fileMeta

readonly fileMeta: FileMeta | undefined

Inherited from

Dataset.fileMeta

index

readonly index: number

warnings

readonly warnings: readonly DicomParseWarning[]

Inherited from

Dataset.warnings

Accessors

image
Get Signature

get image(): ImageView

Pixel-interpretation + geometry view (§4.2–§4.5). Surfaces exactly what a renderer needs without guessing: rescaleSlope/signed/ photometricInterpretation stay absent rather than defaulted, and the three pixel-spacing tags are distinct. Memoised on first read.

Example
import { parseDicom } from "@cosyte/dicom";
const img = parseDicom(buf).image;
img.rescaleSlope; // undefined ⇒ MUST NOT assume 1
Returns

ImageView

Inherited from

Dataset.image

patient
Get Signature

get patient(): PatientView

Patient-identity view (§4.1). Fail-safe typed-absent fields; id is not globally unique - match on the {id, issuerOfId, ...} tuple plus otherIds, never on a bare (0010,0020). Memoised on first read.

Example
import { parseDicom } from "@cosyte/dicom";
const p = parseDicom(buf).patient;
p.name?.alphabetic.familyName; // structured PN, never flattened
Returns

PatientView

Inherited from

Dataset.patient

series
Get Signature

get series(): SeriesView

Series-identity & co-registration view (§4.1, §4.3). A shared frameOfReferenceUid means images are spatially co-registered. Memoised on first read.

Example
import { parseDicom } from "@cosyte/dicom";
const s = parseDicom(buf).series;
s.modality; // "CT"
Returns

SeriesView

Inherited from

Dataset.series

study
Get Signature

get study(): StudyView

Study-identity view (§4.1). instanceUid is the cross-system study key; accessionNumber ties it to the order. Memoised on first read.

Example
import { parseDicom } from "@cosyte/dicom";
const s = parseDicom(buf).study;
s.instanceUid; // "1.2.840.113619..."
Returns

StudyView

Inherited from

Dataset.study

Methods

elements()

elements(): readonly Element[]

All elements in this dataset, in parse (insertion) order.

Returns

readonly Element[]

Example
import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
for (const el of ds.elements()) console.log(el.tag, el.vr);
Inherited from

Dataset.elements

get()

get(tag): Element | undefined

Look up a single element by tag. Tags are normalised to 8-char uppercase hex, so "7fe00010" and "7FE00010" resolve to the same element. Returns undefined when the tag is absent.

Parameters
tag

string

Returns

Element | undefined

Example
import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
const rows = ds.get("00280010"); // Rows
if (rows?.value.kind === "numbers") console.log(rows.value.values[0]);
Inherited from

Dataset.get

getAll()

getAll(tag): readonly Element[]

All elements matching a tag as an array (never undefined). A dataset holds at most one element per tag, so this returns a 0- or 1-length array - the convenience complement of Dataset.get for callers that prefer an always-array shape.

Parameters
tag

string

Returns

readonly Element[]

Example
import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
for (const el of ds.getAll("00080060")) console.log(el.value);
Inherited from

Dataset.getAll

has()

has(tag): boolean

true when an element with the given tag is present (case-insensitive).

Parameters
tag

string

Returns

boolean

Example
import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
if (ds.has("00100010")) console.log("has Patient's Name");
Inherited from

Dataset.has


ProfileDefinitionError

Thrown by defineProfile() when the supplied options are invalid.

Example

import { defineProfile, ProfileDefinitionError } from "@cosyte/dicom";
try {
defineProfile({ name: "" });
} catch (err) {
if (err instanceof ProfileDefinitionError) {
console.error(err.message); // "Profile name must be a non-empty string."
}
}

Extends

  • Error

Constructors

Constructor

new ProfileDefinitionError(message, profileName?): ProfileDefinitionError

Parameters
message

string

Actionable description of what made the options invalid.

profileName?

string

The offending profile's name, when known.

Returns

ProfileDefinitionError

Overrides

Error.constructor

Properties

profileName?

readonly optional profileName?: string

The offending profile's name when known (undefined when the failure is the name itself). Lets callers attribute a composed-lineage failure to the specific profile that introduced it.


Sequence

One SQ (Sequence) element's structural body.

length is the on-wire length: a real byte count for defined-length SQ, or 0xFFFFFFFF (4_294_967_295) when undefined-length per D-29.

Example

import { Sequence } from "@cosyte/dicom";
// Producers (parser plan 02-04) construct sequences as follows:
// const sq = new Sequence([item0, item1], 0xFFFFFFFF);

Constructors

Constructor

new Sequence(items, length): Sequence

Internal

Construct a new structural Sequence. The items array is frozen at the constructor boundary so downstream mutation cannot escape.

Parameters
items

readonly Item[]

length

number

Returns

Sequence

Properties

items

readonly items: readonly Item[]

length

readonly length: number

Interfaces

CodedConcept

A coded triplet (PS3.16 §8, Table 8-1): Code Value (0008,0100), Coding Scheme Designator (0008,0102), Code Meaning (0008,0104). The canonical scheme OID is resolved only for the four standard designators; legacy SNOMED designators (SRT/SNM3/99SDM) deliberately resolve to undefined because their code values differ from SCT (CP-730).

Example

import type { CodedConcept } from "@cosyte/dicom";
const c: CodedConcept = {
codeValue: "C-B1003",
codingSchemeDesignator: "SCT",
codeMeaning: "Hounsfield unit",
schemeUid: "2.16.840.1.113883.6.96",
};

Properties

codeMeaning?

readonly optional codeMeaning?: string

codeValue?

readonly optional codeValue?: string

codingSchemeDesignator?

readonly optional codingSchemeDesignator?: string

schemeUid?

readonly optional schemeUid?: string


DefineProfileOptions

Options accepted by defineProfile. Only name is required; every other field defaults to empty. With exactOptionalPropertyTypes, omit an unset key rather than passing undefined.

Example

import { defineProfile, WARNING_CODES } from "@cosyte/dicom";
const lenientCd = defineProfile({
name: "lenient-cd",
description: "Tolerant of conformance-loose archive CDs",
suppress: [WARNING_CODES.DICOM_ODD_LENGTH_VALUE_PADDED],
});

Properties

description?

readonly optional description?: string

escalate?

readonly optional escalate?: readonly WarningCode[]

extends?

readonly optional extends?: Profile | readonly Profile[]

name

readonly name: string

privateTags?

readonly optional privateTags?: Readonly<Record<string, Readonly<Record<string, PrivateTagDefinition>>>>

suppress?

readonly optional suppress?: readonly WarningCode[]


DeidentifiedAttribute

One audited attribute outcome. Carries only structural facts - tag, keyword, the resolved Annex E action code, and the SQ context path - never a decoded value, so a report is always safe to log.

Example

import { deidentify, parseDicom, type DeidentifiedAttribute } from "@cosyte/dicom";
const { report } = deidentify(parseDicom(buf));
report.attributes.forEach((a: DeidentifiedAttribute) => {
console.log(a.keyword, a.action, a.applied); // structural facts only - safe to log
});

Properties

action

readonly action: "D" | "Z" | "X" | "K" | "C" | "U"

The resolved single action after collapsing any conditional code.

applied

readonly applied: AppliedAction

contextPath?

readonly optional contextPath?: readonly string[]

Tag/index chain for an attribute inside a sequence; omitted at the root.

keyword

readonly keyword: string

repeatingGroup?

readonly optional repeatingGroup?: string

Present when the action came from a Table E.1-1 row that names a repeating-group family rather than this single tag: the mask that matched, e.g. "60xx4000" for Overlay Comments in any overlay plane. tag is always the concrete tag that was in the file. Absent for every exact-tag row.

tag

readonly tag: string


DeidentifyOptions

Options controlling a de-identification run. All optional - the default is the Basic Application Level Confidentiality Profile with no Retain options.

Example

import { deidentify, parseDicom, type DeidentifyOptions } from "@cosyte/dicom";
const opts: DeidentifyOptions = { retain: ["RetainLongitudinalTemporal", "CleanDescriptors"] };
const { dataset } = deidentify(parseDicom(buf), opts);

Properties

deidentificationMethod?

readonly optional deidentificationMethod?: string

Text added to (0012,0063) De-identification Method. Default names the Basic Profile and the active options.

The default is multi-valued, one Value per name, and that is a conformance fix rather than a style choice. PS3.5 2026c Table 6.2-1 caps an LO at 64 characters per Value, and (0012,0063) is 1-n; the single-value default this replaced measured 76 characters with no options and 272 with all nine, so every run this library ever made wrote a value no LO may legally carry. The Profile name is now one Value of 61 characters and each active option is its own, so no option subset can exceed the maximum.

Your string is not bounded here. A value of your own longer than 64 characters is written through as given, with no warning: it is yours, and splitting or truncating it would invent a record you did not write. Split it on `` yourself if a strict receiver is in your path.

PS3.15 E.1.1 says this string is "inserted in or added to" the attribute, so a value the incoming Data Set already carried is kept and this one is appended after a \ as a further value of the 1-n attribute - the provenance chain, not a replacement.

This string is itself a 1-n value: it is split on `` and only the values not already recorded are added.

Trailing SPACE and NUL are padding, not content (PS3.5 Table 6.2-1's LO row, which describes a Value - and LO is 1-n). They are ignored when a value here is matched against one already recorded, per value, on both sides, and they are trimmed from the value written. That makes repeated de-identification a fixed point from the first pass, for every string: with or without a delimiter, and with a pad byte on any value, last or not. A string that is padding only records nothing. Leading spaces are yours and are written through untouched.

One bound, and it is over the value that would be written, not over the join: when that value would exceed the largest Value Length an LO can encode, the prior value is replaced rather than added to - an element the serializer cannot encode would take the whole de-identified object down - and report.warnings carries DICOM_DEIDENT_METHOD_NOT_ADDED. That includes a prior value already at the ceiling which records this method already, where there is no join to exceed anything. A string longer than that ceiling on its own is not bounded here and will fail to serialize, exactly as it did before this option grew a join.

DICOM_DEIDENT_METHOD_NOT_ADDED means "the length ceiling was reached", never "every fallback is disclosed": a (0012,0063) a file encoded under a VR other than LO is also replaced, and that one raises DICOM_DEIDENT_METHOD_NOT_LO. Two codes rather than one because the causes are unrelated - the chain outgrew the VR, or the bytes were never in that VR at all - and a prior value that is empty or padding only raises neither, because nothing was lost.

When the prior value is kept, report.warnings carries DICOM_DEIDENT_METHOD_PRIOR_RETAINED: (0012,0063) is not in Table E.1-1, so nothing in the run inspected or redacted those bytes, and a name a sender wrote there is in output stamped (0012,0062) = YES.

profile?

readonly optional profile?: Profile

A Phase 6 Profile whose private-dictionary overlay names the known-safe private attributes to keep when RetainSafePrivate is active. Without it, RetainSafePrivate keeps nothing (fail-safe).

retain?

readonly optional retain?: readonly DeidentifyOption[]

Annex E option sets to activate (Retain* / Clean*). Default: none.

uidMap?

readonly optional uidMap?: Map<string, string>

A caller-owned source→replacement UID cache. Pass one shared map across a whole study/archive to make UID remapping consistent by construction even across separate calls (it is consistent anyway - the mapping is content- derived - but a shared map also makes repeats O(1)).

uidRoot?

readonly optional uidRoot?: string

Root for generated UIDs (action U). Default "2.25".


DeidentifyReport

The audit trail returned alongside the de-identified dataset.

Most fields are composed from static tables: Part 6 keywords, Annex E action codes, structural TAG[index] sequence paths, and registry warning messages. Two are not, and both are named here rather than in a footnote.

  1. uidMap - its keys are the source UIDs read out of the file, kept so a caller can make UID replacement consistent across a study or an archive. A Study or SOP Instance UID is a unique identifying number, so treat it as PHI.
  2. removedPrivateTags - see the field's own note. On a well-formed file these are the sender's own private tag numbers and carry nothing; on a malformed one a tag can be four bytes of a value, and it is measured, not theoretical.

So "the report is safe to log apart from uidMap" is not an accurate description of this type, and was corrected rather than kept convenient.

Example

import { deidentify, parseDicom, type DeidentifyReport } from "@cosyte/dicom";
const { report }: { report: DeidentifyReport } = deidentify(parseDicom(buf));
console.log(report.attributes.length, "attributes acted on");
console.log(report.warnings.map((w) => w.code)); // e.g. burned-in annotation

Properties

attributes

readonly attributes: readonly DeidentifiedAttribute[]

Per-attribute outcomes for every attribute Annex E acted on.

embeddedAttributes

readonly embeddedAttributes: readonly EmbeddedAttributeFinding[]

Values emptied because whole Data Elements were encoded inside them by an over-declared Value Length. Empty on a well-formed file; a non-empty array means the source was malformed in a way that hid attributes from the action table, so treat it as a data-quality alarm on the sender as well as an audit line. See EmbeddedAttributeFinding.

removedPrivateTags

readonly removedPrivateTags: readonly string[]

Private tags removed under the Basic Profile (kept ones are omitted).

This is the second field that is not value-free, and the qualification is measured. A tag here is composed from four bytes of the source, and on a file whose Value Lengths disagree with its bytes those four bytes can be document content rather than a tag the sender wrote: an OB carrier holding "SECRET-NOTE-" followed by a well-formed odd-group header reports ["41534342"], whose wire-order bytes read "SABC". That reproduces identically on every release that has shipped this field.

It is reported anyway, because which private tags were removed is the whole audit value of the field and withholding them would destroy it on every well-formed file to bound a malformed one. Narrowing it - to blocks a creator in the same Data Set actually reserved, say - is a product decision about audit value versus a four-byte echo, not a defect fix, and it has not been made. Treat this array as PHI when the source is untrusted.

retained

readonly retained: readonly DeidentifyOption[]

The Retain/Clean options that were active for this run.

Not a list of what survived. Attributes Table E.1-1 does not list are kept without appearing anywhere in this field - (0012,0063) De-identification Method is the one whose retention is disclosed, and it is disclosed as DICOM_DEIDENT_METHOD_PRIOR_RETAINED on DeidentifyReport.warnings, because it is not an option set.

uidMap

readonly uidMap: ReadonlyMap<string, string>

Source UID → replacement UID, for cross-file consistency. The keys are document values, not composed identifiers: this is the one field of the report that carries PHI.

unauditableSequences

readonly unauditableSequences: readonly UnauditableSequenceFinding[]

SQ elements emptied because the parser did not materialize their items, so the run had no Data Sets to walk and could not discharge PS3.15 §E.1.1's obligation inside them. Empty on a well-formed file; a non-empty array means content was dropped from the de-identified output, and the matching DICOM_SQ_NOT_DESCENDED entry on Dataset.warnings says why the parse refused. See UnauditableSequenceFinding.

Capped, and the cap is on the record only. A crafted input can carry tens of thousands of un-auditable elements, so this array (and its matching warnings) stops at MAX_UNAUDITABLE_SEQUENCE_FINDINGS. Every un-auditable sequence is still emptied; an array exactly that long means "at least this many", so read it as truncated rather than as a total.

It is not a complete list of what went un-audited, either: a private SQ a Profile vouches for under RetainSafePrivate is kept verbatim and never appears here.

undefinedVrElements

readonly undefinedVrElements: readonly UndefinedVrFinding[]

Elements emptied because their on-wire VR is not one of the 34 PS3.5 §6.2 defines, so their bytes are not a Value Field this library decoded and PS3.15 §E.1.1's obligation over what is inside them could not be discharged. Empty on a file conformant to PS3.5 2026c: a sender that writes one of the 34 VRs that edition defines never produces one, and an Implicit VR LE file cannot - there the VR comes from the dictionary. The edition is not pedantry: §6.2 exists precisely to say how a future VR will be encoded, so a file conformant to a later edition using a newly defined VR is the population that sentence exists for. (What such a file does on this library's parse path is not summarized here - it was measured and the shapes disagree.) A non-empty array means the source desynchronized the reader, usually by under-declaring a Value Length somewhere earlier. See UndefinedVrFinding.

Capped, and the cap is on the record only, exactly as DeidentifyReport.unauditableSequences is: a crafted 1 MiB input can carry over a hundred thousand such elements, so this array and its matching warnings stop at MAX_UNDEFINED_VR_FINDINGS. Every one of them is still emptied; an array exactly that long means "at least this many".

A finding here names a byte offset, not a tag - uniquely among the report's findings, and for a reason worth reading in UndefinedVrFinding: the tag of a fabricated header is itself part of some element's value.

Unlike its sibling this list has no carve-out, and the reason is structural rather than a promise: keepOrEmpty is the only path that writes a source value into de-identified output unchanged, and the test sits at the top of it. Every other outcome - X remove, Z/C empty, D dummy, U remap, and a private tag the Basic Profile drops - already replaces the value. So a RetainSafePrivate element a Profile vouches for still reaches this test and is still emptied, which is where the sibling SQ-with-no-items rule has a real carve-out and this one does not.

warnings

readonly warnings: readonly DicomParseWarning[]

Safety warnings - notably burned-in-pixel annotation that cannot be cleaned.


DeidentifyResult

The result of deidentify: a new dataset plus its audit report.

Example

import { deidentify, parseDicom, serializeDicom, type DeidentifyResult } from "@cosyte/dicom";
const { dataset, report }: DeidentifyResult<ReturnType<typeof parseDicom>> = deidentify(parseDicom(buf));
const safe = serializeDicom(dataset); // input dataset is never mutated
void report;

Type Parameters

TDataset

TDataset

Properties

dataset

readonly dataset: TDataset

report

readonly report: DeidentifyReport


DicomDate

A tolerantly-decoded DA (Date) value. raw always carries the on-wire string (PHI-safe - a date is not an identifier on its own, but is preserved verbatim regardless). valid is true only when the string parsed cleanly to a YYYYMMDD calendar date; otherwise the numeric fields are omitted and raw is the source of truth.

Example

import type { DicomDate } from "@cosyte/dicom";
const d: DicomDate = { raw: "20240115", valid: true, year: 2024, month: 1, day: 15 };

Properties

day?

readonly optional day?: number

month?

readonly optional month?: number

raw

readonly raw: string

valid

readonly valid: boolean

year?

readonly optional year?: number


DicomDateTime

A tolerantly-decoded DT (DateTime) value (PS3.5 §6.2). offsetMinutes is the signed UTC offset (&ZZXX suffix) in minutes when present.

Example

import type { DicomDateTime } from "@cosyte/dicom";
const dt: DicomDateTime = { raw: "20240115133015", valid: true, year: 2024, month: 1, day: 15, hours: 13, minutes: 30, seconds: 15 };

Properties

day?

readonly optional day?: number

fractionalSeconds?

readonly optional fractionalSeconds?: number

hours?

readonly optional hours?: number

minutes?

readonly optional minutes?: number

month?

readonly optional month?: number

offsetMinutes?

readonly optional offsetMinutes?: number

raw

readonly raw: string

seconds?

readonly optional seconds?: number

valid

readonly valid: boolean

year?

readonly optional year?: number


DicomParseWarning

Data shape for every Tier-2 warning emitted by the parser. Warnings are plain data (distinct from DicomParseError, which is a thrown Error subclass) so they can be safely accumulated into Dataset.warnings and passed to onWarning callbacks.

Per D-07 there is intentionally NO snippet field on warnings: real-world files routinely produce 50+ warnings and a per-warning snippet would balloon retained memory. Snippets appear only on DicomParseError (the strict-mode escalation path).

Example

import type { DicomParseWarning } from "@cosyte/dicom";
const w: DicomParseWarning = {
code: "DICOM_MISSING_PREAMBLE",
message: "No DICM magic at offset 128.",
position: { byteOffset: 0 },
};

Properties

code

readonly code: WarningCode

message

readonly message: string

position

readonly position: DicomPosition


DicomPosition

Positional context for a DicomParseWarning or DicomParseError.

Byte offsets are relative to the source buffer for non-deflated transfer syntaxes; for the Deflated Explicit VR LE transfer syntax (D-27), deflated: true indicates the offset is into the inflated dataset buffer rather than the on-disk source.

With exactOptionalPropertyTypes: true, callers should omit unset keys rather than passing undefined (mirrors @cosyte/hl7 sibling discipline).

Example

import type { DicomPosition } from "@cosyte/dicom";
const p: DicomPosition = { byteOffset: 132, fileMeta: true };

Properties

byteOffset

readonly byteOffset: number

contextPath?

readonly optional contextPath?: readonly string[]

Tag chain for nested SQ items, e.g. ["0040A730", "0", "00080100"]. Omit when at root.

deflated?

readonly optional deflated?: boolean

True when offset is into the inflated dataset buffer (Deflated TS only). Omit when not applicable.

fileMeta?

readonly optional fileMeta?: boolean

True when offset is inside the File Meta group. Omit (do not pass undefined) when not applicable.


DicomTime

A tolerantly-decoded TM (Time) value (PS3.5 §6.2; max length 14 bytes). fractionalSeconds is the value after the decimal point as a number in [0,1) when present.

Example

import type { DicomTime } from "@cosyte/dicom";
const t: DicomTime = { raw: "133015.250000", valid: true, hours: 13, minutes: 30, seconds: 15, fractionalSeconds: 0.25 };

Properties

fractionalSeconds?

readonly optional fractionalSeconds?: number

hours?

readonly optional hours?: number

minutes?

readonly optional minutes?: number

raw

readonly raw: string

seconds?

readonly optional seconds?: number

valid

readonly valid: boolean


EmbeddedAttributeFinding

One value that was emptied because a Data Element was found inside it.

PS3.5 defines Value Length as the length of that element's own Value Field. A sender that over-declares it produces a file whose reading is self-consistent and whose next element has been absorbed into the previous one's value - and Table E.1-1 is keyed by tag, so an absorbed (0010,0020) is not an attribute any longer and no action fires on it. deidentify therefore refuses to keep a value whose tail decodes as whole Data Elements it would have acted on (PS3.15 §E.1 "all instances"; §E.3.5 is the standard's own precedent for removing identifying information embedded inside a string attribute).

Every field is structural: tag and vr are the carrier's, and hidden holds tags composed from four bytes each. No decoded value appears here, so this is safe to log.

Example

import { deidentify, parseDicom } from "@cosyte/dicom";
const { report } = deidentify(parseDicom(buf));
for (const e of report.embeddedAttributes) {
console.warn(`${e.tag} hid ${e.hidden.join(", ")} in its value`);
}

Properties

contextPath?

readonly optional contextPath?: readonly string[]

Tag/index chain when the carrier is inside a sequence item; omitted at the root.

hidden

readonly hidden: readonly string[]

The tags of the Data Elements found inside the carrier's value, in wire order.

tag

readonly tag: string

The carrier - the element whose over-declared value held the others.

vr

readonly vr: VR

The carrier's VR. Always one of the string VRs; binary VRs are not scanned.


FileMeta

The Part-10 File Meta Information group, projected as a typed view over (0002,xxxx) elements parsed during the File Meta pre-pass.

Only transferSyntaxUID is required because it is the dispatch input for the four v1 transfer-syntax parsers; everything else is optional because real-world clinical files routinely omit one or more Type-1 elements. Phase 7's validate() adds opinion-bearing checks for those missing elements.

Example

import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
if (ds.fileMeta !== undefined) {
const ts = ds.fileMeta.transferSyntaxUID; // always present when fileMeta is defined
}

Properties

extraElements?

readonly optional extraElements?: readonly FileMetaRawElement[]

Any (0002,xxxx) elements the source carried that the typed fields above do not model, preserved in tag order so a round-trip re-emits the File Meta group byte-for-byte. Omitted when the group held only modeled elements.

fileMetaInformationVersion?

readonly optional fileMetaInformationVersion?: Buffer<ArrayBufferLike>

implementationClassUID?

readonly optional implementationClassUID?: string

implementationVersionName?

readonly optional implementationVersionName?: string

mediaStorageSOPClassUID?

readonly optional mediaStorageSOPClassUID?: string

mediaStorageSOPInstanceUID?

readonly optional mediaStorageSOPInstanceUID?: string

sourceApplicationEntityTitle?

readonly optional sourceApplicationEntityTitle?: string

transferSyntaxUID

readonly transferSyntaxUID: string


FileMetaRawElement

A non-modeled (0002,xxxx) File Meta element, preserved verbatim so the serializer can re-emit an exotic File Meta group byte-for-byte.

The typed FileMeta fields cover the common Type-1/Type-3 elements; anything else a source file carried - e.g. (0002,0017)/(0002,0018) Sending/Receiving AE Title, (0002,0100) Private Information Creator UID, (0002,0102) Private Information - is captured here as raw bytes (the on-wire value, even-length per PS3.5 §6.2) rather than dropped. value is a defensive copy, so the view never aliases the parsed input buffer.

Properties

tag

readonly tag: string

8-char uppercase hex tag, e.g. "00020100".

value

readonly value: Buffer

The raw on-wire value bytes (even-length), copied out of the input.

vr

readonly vr: VR

The element's Value Representation as read under Explicit VR LE.


FrameFunctionalGroups

The five Enhanced multi-frame functional-group macros, resolved for a single frame Per-Frame-else-Shared (§4.4, PS3.3 §C.7.6.16). Each macro is typed-absent when present in neither the per-frame nor the shared group (the three geometry macros are treated as required for an enhanced object - see ImageView.frame).

Example

import { parseDicom } from "@cosyte/dicom";
const f = parseDicom(enhancedBuf).image.frame(0);
f.planePosition?.imagePositionPatient; // this frame's [x,y,z]
f.pixelMeasures?.pixelSpacing; // this frame's [row,col] mm

Properties

frameVoiLut?

readonly optional frameVoiLut?: object

windowCenter?

readonly optional windowCenter?: readonly (number | null)[]

windowWidth?

readonly optional windowWidth?: readonly (number | null)[]

index

readonly index: number

pixelMeasures?

readonly optional pixelMeasures?: object

pixelSpacing?

readonly optional pixelSpacing?: readonly (number | null)[]

sliceThickness?

readonly optional sliceThickness?: number

spacingBetweenSlices?

readonly optional spacingBetweenSlices?: number

pixelValueTransformation?

readonly optional pixelValueTransformation?: object

rescaleIntercept?

readonly optional rescaleIntercept?: number

rescaleSlope?

readonly optional rescaleSlope?: number

rescaleType?

readonly optional rescaleType?: string

planeOrientation?

readonly optional planeOrientation?: object

imageOrientationPatient?

readonly optional imageOrientationPatient?: readonly (number | null)[]

planePosition?

readonly optional planePosition?: object

imagePositionPatient?

readonly optional imagePositionPatient?: readonly (number | null)[]


ImageView

Pixel-interpretation + geometry metadata (§4.2 / §4.3 / §4.4 / §4.5) - the "wrong pixels look fine" and "looks fine, measures wrong" classes. v1 does not decode pixels; this view surfaces exactly what a renderer needs so it does not have to guess.

Safety-critical omissions are intentional: rescaleSlope is absent (not 1) when the tag is absent; signed is absent (not a guess) unless (0028,0103) was present; photometricInterpretation is absent (not MONOCHROME2) when absent; the three pixel-spacing fields are distinct. When modalityLutSequence / voiLutSequence are present they are authoritative over the linear rescale* / window* pairs.

Example

import { parseDicom } from "@cosyte/dicom";
const img = parseDicom(buf).image;
img.rescaleSlope; // undefined ⇒ caller MUST NOT assume 1
img.signed; // undefined ⇒ signedness unknown, never guess
img.pixelSpacing; // patient-plane mm - distinct from imagerPixelSpacing

Properties

bitsAllocated?

readonly optional bitsAllocated?: number

bitsStored?

readonly optional bitsStored?: number

columns?

readonly optional columns?: number

frameOfReferenceUid?

readonly optional frameOfReferenceUid?: string

highBit?

readonly optional highBit?: number

imageOrientationPatient?

readonly optional imageOrientationPatient?: readonly (number | null)[]

imagePositionPatient?

readonly optional imagePositionPatient?: readonly (number | null)[]

imagerPixelSpacing?

readonly optional imagerPixelSpacing?: readonly (number | null)[]

isEnhancedMultiFrame

readonly isEnhancedMultiFrame: boolean

true when this object carries Per-Frame/Shared Functional Groups.

modalityLutSequence?

readonly optional modalityLutSequence?: readonly Item[]

nominalScannedPixelSpacing?

readonly optional nominalScannedPixelSpacing?: readonly (number | null)[]

numberOfFrames?

readonly optional numberOfFrames?: number

photometricInterpretation?

readonly optional photometricInterpretation?: string

pixelRepresentation?

readonly optional pixelRepresentation?: number

Raw (0028,0103) value: 0 = unsigned, 1 = signed. Absent ⇒ unknown.

pixelSpacing?

readonly optional pixelSpacing?: readonly (number | null)[]

planarConfiguration?

readonly optional planarConfiguration?: number

realWorldValueMaps?

readonly optional realWorldValueMaps?: readonly RealWorldValueMap[]

rescaleIntercept?

readonly optional rescaleIntercept?: number

rescaleSlope?

readonly optional rescaleSlope?: number

rescaleType?

readonly optional rescaleType?: string

rows?

readonly optional rows?: number

samplesPerPixel?

readonly optional samplesPerPixel?: number

signed?

readonly optional signed?: boolean

true/false only when (0028,0103) was 1/0; absent ⇒ never guessed.

sliceThickness?

readonly optional sliceThickness?: number

sopInstanceUid?

readonly optional sopInstanceUid?: string

spacingBetweenSlices?

readonly optional spacingBetweenSlices?: number

units?

readonly optional units?: string

voiLutSequence?

readonly optional voiLutSequence?: readonly Item[]

windowCenter?

readonly optional windowCenter?: readonly (number | null)[]

windowWidth?

readonly optional windowWidth?: readonly (number | null)[]

Methods

frame()

frame(index): FrameFunctionalGroups

Resolve the functional-group macros for frame index Per-Frame-else-Shared. Throws DicomValueError FRAME_INDEX_OUT_OF_RANGE for an index outside [0, numberOfFrames) and MISSING_REQUIRED_FUNCTIONAL_GROUP when an enhanced object lacks a required geometry macro in both groups.

Parameters
index

number

Returns

FrameFunctionalGroups


OtherPatientId

One entry of Other Patient IDs Sequence (0010,1002) - a {id, issuer, typeCode} triple (PS3.3 §10.15, the DICOM analogue of an HL7 v2 CX repetition). Surfaced so a caller never matches on a bare (0010,0020).

Example

import type { OtherPatientId } from "@cosyte/dicom";
const o: OtherPatientId = { id: "MRN-42", issuer: "HOSP_A", typeCode: "TEXT" };

Properties

id?

readonly optional id?: string

issuer?

readonly optional issuer?: string

typeCode?

readonly optional typeCode?: string


ParseOptions

Options accepted by parseDicom.

Per 02-CONTEXT.md D-02 - Phase 2 form only. No profile field; Phase 6 adds it. With exactOptionalPropertyTypes: true, callers omit unset keys rather than passing undefined for any field below.

Example

import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf, {
strict: false,
stripPreamble: "tolerate",
copyValues: false,
onWarning: (w) => console.warn(w.code),
});

Properties

copyValues?

readonly optional copyValues?: boolean

When true, every Element.rawBytes is Buffer.from(slice) - copying each value out so the source buffer can be released. When false (the default), Element.rawBytes is Buffer.subarray(slice) - a zero-copy view that pins the source ArrayBuffer until every Element is GC'd.

Per D-16 / MODEL-03. Omit to use the default.

onWarning?

readonly optional onWarning?: OnWarningCallback

Synchronous callback invoked once per Tier-2 warning, after the warning has been pushed to Dataset.warnings. Throwing handlers are silently swallowed (parser-state safety per D-03).

Omit to skip the callback entirely.

profile?

readonly optional profile?: Profile

Source/vendor tolerance preset (Phase 6, D-45). Applies the profile's escalations / suppressions to warning emission and its privateDictionary to Implicit-VR resolution of private data elements. A profile only tightens or annotates - it never makes the default lenient parse throw outside the four Tier-3 fatals, and a private creator the profile does not recognize degrades to generic UN handling plus a DICOM_PRIVATE_CREATOR_UNKNOWN warning, never a wrong decode.

Omit (do not pass undefined) for the unprofiled default behaviour.

strict?

readonly optional strict?: boolean

When true, every Tier-2 warning is escalated to a thrown DicomParseError carrying the warning code. Default false.

🛑 THE ESCALATED DIAGNOSTIC CARRIES SOURCE BYTES THAT THE WARNING DOES NOT. A DicomParseWarning.message is a frozen registry string with only structural tokens filled in, so it is safe to log whole; the DicomParseError this option raises in its place also carries snippet, 16 raw bytes, unredacted (D-10), read at the warning's own byteOffset. Which element those bytes belong to is not contracted: that offset's frame follows where the element was read - file-absolute at the root, relative to the enclosing slice inside a defined-length Sequence or Item, and into the inflated stream under Deflated Explicit VR LE - while the snippet is cut from whichever buffer the parse is holding, so the two can disagree and the bytes can be an unrelated element's value. Do not reason from a code's message to what its snippet holds - measure it, and treat every one of them as document content.

That is the documented design of snippet rather than a defect in any one code, and turning this option on does not change what any of them means - but a message-only PHI review of the lenient path does not transfer to the strict one. Log err.code, err.byteOffset and err.message; treat err.snippet as PHI.

Omit (do not pass undefined) to use the default.

stripPreamble?

readonly optional stripPreamble?: "tolerate" | "require"

Preamble policy:

  • "tolerate" (default): attempt to start at offset 0 if DICM magic is missing at offset 128; emit DICOM_MISSING_PREAMBLE.
  • "require": throw DicomParseError(NOT_DICOM_PART_10) when no DICM magic is present.

Omit to use the default.


PatientView

Patient & study identity (§4.1 - the wrong-patient failure class).

id is not globally unique; correct cross-system matching needs the {id, issuerOfId, issuerQualifiers} tuple plus otherIds. name keeps its full PN component structure, never flattened to a string.

Example

import { parseDicom } from "@cosyte/dicom";
const p = parseDicom(buf).patient;
p.id; // "MRN-42" - meaningless without the issuer
p.issuerOfId; // "HOSP_A"
p.name?.alphabetic.familyName; // "Doe"

Properties

birthDate?

readonly optional birthDate?: DicomDate

id?

readonly optional id?: string

issuerOfId?

readonly optional issuerOfId?: string

issuerQualifiers?

readonly optional issuerQualifiers?: readonly Item[]

name?

readonly optional name?: PersonName

otherIds

readonly otherIds: readonly OtherPatientId[]

sex?

readonly optional sex?: string


PersonName

A decoded PN value - up to three component groups separated on-wire by = (PS3.5 §6.2.1.1). alphabetic is always present; ideographic and phonetic are present only when the value supplied them.

Example

import type { PersonName } from "@cosyte/dicom";
// "Yamada^Tarou=山田^太郎=やまだ^たろう"
declare const pn: PersonName;
pn.alphabetic.familyName; // "Yamada"
pn.ideographic?.familyName; // "山田"

Properties

alphabetic

readonly alphabetic: PersonNameGroup

ideographic?

readonly optional ideographic?: PersonNameGroup

phonetic?

readonly optional phonetic?: PersonNameGroup


PersonNameGroup

One of the three component groups of a PN value (PS3.5 §6.2.1.1): alphabetic, ideographic, or phonetic. Each holds the five ^-delimited components; missing components are the empty string (never undefined).

Example

import type { PersonNameGroup } from "@cosyte/dicom";
const g: PersonNameGroup = {
familyName: "Doe",
givenName: "Jane",
middleName: "",
namePrefix: "",
nameSuffix: "",
};

Properties

familyName

readonly familyName: string

givenName

readonly givenName: string

middleName

readonly middleName: string

namePrefix

readonly namePrefix: string

nameSuffix

readonly nameSuffix: string


PrivateTagDefinition

One private-data attribute definition supplied by a Profile's private-dictionary overlay. The vr resolves the Implicit-VR of a private data element whose on-wire encoding carries no VR; keyword / name carry the vendor-documented identity for tooling and docs.

Example

import type { PrivateTagDefinition } from "@cosyte/dicom";
const def: PrivateTagDefinition = {
vr: "OB",
keyword: "CSAImageHeaderInfo",
name: "CSA Image Header Info",
};

Properties

keyword

readonly keyword: string

name

readonly name: string

vr

readonly vr: VR


Profile

A source/vendor tolerance preset (Phase 6). A Profile bundles three things that only ever tighten or annotate a parse - never loosen it past the Postel's-Law default:

  • escalations - Tier-2 warning codes promoted to a thrown DicomParseError (a stricter posture for known-unsafe deviations).
  • suppressions - Tier-2 warning codes silenced because they are a documented, benign quirk of the named source (annotation, not loss).
  • privateDictionary - a private-creator-keyed overlay resolving the Implicit-VR of vendor private data elements via the file's live private-creator string (never a hard-coded block number).

Build one with defineProfile(); never hand-author the frozen shape. Profiles are immutable and composable via extends.

Example

import { parseDicom, profiles } from "@cosyte/dicom";
const ds = parseDicom(buf, { profile: profiles.siemens });
console.log(ds.fileMeta?.transferSyntaxUID);

Properties

describe?

readonly optional describe?: () => string

Render a human-readable, deterministic one-line summary of the profile.

Returns

string

description?

readonly optional description?: string

escalations

readonly escalations: ReadonlySet<WarningCode>

lineage

readonly lineage: readonly string[]

name

readonly name: string

privateDictionary

readonly privateDictionary: ReadonlyMap<string, ReadonlyMap<string, PrivateTagDefinition>>

Creator string → canonical private-tag key ("GGGGxxEE", e.g. "0029xx10") → definition. The xx placeholder stands for the file-assigned private block byte, mirroring the published DICOM private-dictionary notation; resolution is therefore by creator string, never by a fixed block number.

suppressions

readonly suppressions: ReadonlySet<WarningCode>


RealWorldValueMap

A Real World Value Mapping (§4.5) - slope/intercept bound atomically to its UCUM measurement-units code, so a number is never detached from its units. From Real World Value Mapping Sequence (0040,9096).

Example

import type { RealWorldValueMap } from "@cosyte/dicom";
const m: RealWorldValueMap = { slope: 1, intercept: 0, unitsCode: { codeValue: "[hnsf'U]" } };

Properties

intercept?

readonly optional intercept?: number

slope?

readonly optional slope?: number

unitsCode?

readonly optional unitsCode?: CodedConcept


SeriesView

Series-level identity & co-registration (§4.1, §4.3). Images sharing a frameOfReferenceUid are spatially co-registered.

Example

import { parseDicom } from "@cosyte/dicom";
const s = parseDicom(buf).series;
s.modality; // "CT"
s.frameOfReferenceUid; // shared ⇒ co-registered

Properties

description?

readonly optional description?: string

frameOfReferenceUid?

readonly optional frameOfReferenceUid?: string

instanceUid?

readonly optional instanceUid?: string

modality?

readonly optional modality?: string

number?

readonly optional number?: number


StudyView

Study-level identity (§4.1). instanceUid is the cross-system study key; accessionNumber ties the study to the order.

Example

import { parseDicom } from "@cosyte/dicom";
const s = parseDicom(buf).study;
s.instanceUid; // "1.2.840.113619..."
s.accessionNumber; // "ACC123"

Properties

accessionNumber?

readonly optional accessionNumber?: string

date?

readonly optional date?: DicomDate

description?

readonly optional description?: string

id?

readonly optional id?: string

instanceUid?

readonly optional instanceUid?: string

time?

readonly optional time?: DicomTime


UidRemapper

A UID remapper: a stable map(src) plus the backing cache it fills.

Example

import { makeUidRemapper, type UidRemapper } from "@cosyte/dicom";
const remap: UidRemapper = makeUidRemapper();
const replaced = remap.map("1.2.840.10008.5.1.4.1.1.2");
remap.cache.get("1.2.840.10008.5.1.4.1.1.2") === replaced; // true

Properties

cache

readonly cache: Map<string, string>

The source→replacement cache, exposed for reporting / reuse.

map

readonly map: (sourceUid) => string

Map one source UID to its deterministic replacement (cached).

Parameters
sourceUid

string

Returns

string


UnauditableSequenceFinding

One SQ element that was emptied because the parser never materialized its items, so the de-identifier had no Data Sets to walk.

PS3.5 2026c §7.5.1 "Item Encoding Rules" states that "Each Item Value shall contain a DICOM Data Set composed of Data Elements", so an SQ element's value is never opaque bytes - it is Data Elements this run is obliged to reach. PS3.15 2026c §E.1.1 "De-identifier" states that obligation directly: an implementation claiming the Basic Application Level Confidentiality Profile "shall protect or retain all instances of the Attributes listed in [Table E.1-1], whether contained in the top level Data Set or embedded in an Item of a Sequence of Items". When the item stream cannot be enumerated the obligation cannot be discharged element by element, so it falls on the enclosing attribute - the escalation §E.1.1 itself uses for a SOP Instance UID inside a Sequence, where "the enclosing Attribute in the top-level Data Set must be encrypted in its entirety". (That sentence is written about the encrypt-and-replace mechanism for SOP Instance UIDs, not about Table E.1-1 generally; it is cited here as the standard's own precedent for escalating to the carrier, not as a rule about this case.)

Both fields are structural: tag is the carrier's and byteLength is the declared size of the value that was dropped. No decoded value appears here, so this is safe to log.

The parser always announces the underlying refusal first, on Dataset.warnings: DICOM_SQ_NOT_DESCENDED for a defined-length Implicit VR LE value whose dictionary-resolved SQ was not a valid item stream.

Example

import { deidentify, parseDicom } from "@cosyte/dicom";
const { report } = deidentify(parseDicom(buf));
for (const s of report.unauditableSequences) {
console.warn(`${s.tag}: ${String(s.byteLength)} bytes dropped, item stream unreadable`);
}

Properties

byteLength

readonly byteLength: number

Byte length of the value field that was dropped. Structural, never a value.

contextPath?

readonly optional contextPath?: readonly string[]

Tag/index chain when the carrier is inside a sequence item; omitted at the root.

tag

readonly tag: string

The SQ element that was emptied.


UndefinedVrFinding

One element that was emptied because its on-wire VR is not one of the 34 PS3.5 section 6.2 defines, so nothing this library did to its bytes counts as decoding a Value Field.

Why such an element exists at all

Under an Explicit VR Transfer Syntax the VR is two bytes the sender wrote, and this parser trusts them (Postel's Law on the read path). The routine way two arbitrary bytes end up in a VR field is an under-declared Value Length upstream: the reader finishes the short value, and the leftover bytes of the value that was actually encoded are read as the next Data Element header. Tag, VR and length are then all fragments of somebody's value, and the element that genuinely followed is consumed as this fabricated element's "value".

Measured on scripts/measure-sq-bound-grid.ts: a carrier under-declaring by 6 produces (4156,554C) with the VR bytes "E ", whose value holds the source (0010,0020) Patient ID in full. It reaches string carriers exactly as it reaches binary ones, because the carrier's own VR is not what decides it.

Why emptying, and why it is not a guess

PS3.5 2026c section 6.2 requires every VR not yet defined to use the long-form Data Element Structure - "with reserved bytes after the VR and a 32-bit unsigned integer VL" - so an unrecognized VR read short-form, which is what this parser does, is by the standard's own structure rule not a reading of a Value Field. There is nothing to prove about the content: the test is a membership check against the closed 34-VR set on a field the parser already recorded, so there is no scan, no per-offset loop, and no cost that follows an attacker-chosen value length.

PS3.15 2026c section E.1.1 obliges an implementation claiming the Basic Application Level Confidentiality Profile to "protect or retain all instances of the Attributes listed in [Table E.1-1]". Those instances cannot be reached inside bytes that were never a value, so the obligation falls on the carrier.

UN is not this. UN is one of the 34, so an ordinary unknown-VR element

  • the Implicit VR fallback for a tag this build's dictionary does not publish, and the CP-246 shape - never reaches here. That is the line the sibling SQ-with-no-items rule could not draw.

Why this finding names no tag, when every sibling finding does

Because the tag may be content, and nothing here can tell. The paragraph above is the whole argument: when an under-declare desynchronized the reader, the four tag bytes and the two VR bytes were read out of the middle of some element's Value Field, so reporting the "tag" would republish four bytes of the document. An unrecognized VR written honestly, at a correct length, raises this same code and has an ordinary tag - and the two are indistinguishable here, so the tag is withheld on both routes rather than on a guess. Measured on a synthetic ST carrier holding "MR BRAIN SMITHSON", the fabricated tag is 48544F53 - four bytes of the surname. EmbeddedAttributeFinding and UnauditableSequenceFinding may carry a tag because theirs came from a header the sender really wrote; this one may not, and the asymmetry is the finding rather than an inconsistency.

byteOffset locates the element instead - a position this parser counted. Nothing here renders a document byte: an offset the parser counted, a decoded length, and the structural contextPath. Safe to log.

Example

import { deidentify, parseDicom } from "@cosyte/dicom";
const { report } = deidentify(parseDicom(buf));
for (const u of report.undefinedVrElements) {
console.warn(`offset ${String(u.byteOffset)}: ${String(u.byteLength)} bytes dropped`);
}

Properties

byteLength

readonly byteLength: number

Byte length of the value field that was dropped.

An input-derived count, and on this finding specifically that is not a formality. For the sibling findings the length came from a header the sender wrote; here the two length bytes can themselves be value bytes, like the tag bytes. What is published is the number they decode to, never the bytes - the same footing as every {n} in the warning registry, which is documented there as "an input-derived count". A number is not a rendering, and the reach is at most a character or so, but do not describe this field as "structural, never a value" the way its siblings are described.

byteOffset

readonly byteOffset: number

Byte offset of the emptied element's header. This is how the element is identified, and there is deliberately no tag field - see the note above: a fabricated header's tag bytes are part of some element's value.

contextPath?

readonly optional contextPath?: readonly string[]

Tag/index chain when the carrier is inside a sequence item; omitted at the root.

Type Aliases

AppliedAction

AppliedAction = "removed" | "emptied" | "dummied" | "uid-remapped" | "cleaned" | "kept"

What deidentify actually did to one attribute - the concrete outcome of the resolved Annex E action.

  • removed - the element was deleted (X).
  • emptied - replaced with a zero-length value (Z).
  • dummied - replaced with a non-identifying dummy of compatible VR (D).
  • uid-remapped - UID(s) replaced with internally-consistent UIDs (U).
  • cleaned - conservatively blanked because a safe similar-meaning value cannot be synthesised at the metadata layer (C; see known limitations).
  • kept - retained, either by an active Retain option or because the SQ was kept and its items cleaned recursively.

Example

import { deidentify, parseDicom, type AppliedAction } from "@cosyte/dicom";
const { report } = deidentify(parseDicom(buf));
const removed = report.attributes.filter((a) => a.applied === ("removed" satisfies AppliedAction));

DeidentifyErrorCode

DeidentifyErrorCode = typeof DEIDENTIFY_ERROR_CODES[keyof typeof DEIDENTIFY_ERROR_CODES]

One of the DEIDENTIFY_ERROR_CODES values.

Example

import { DeidentifyError, type DeidentifyErrorCode } from "@cosyte/dicom";
const code: DeidentifyErrorCode = "INVALID_OPTIONS";
throw new DeidentifyError("unknown retain option", code);

DeidentifyOption

DeidentifyOption = Exclude<AnnexEOption, "CleanPixelData" | "CleanRecognizableVisual">

The PS3.15 Annex E option sets deidentify honours - the nine metadata-affecting columns of Table E.1-1. The two pixel-level options (CleanPixelData §E.3.1, CleanRecognizableVisual §E.3.2) are deliberately excluded: this is a metadata-only de-identifier and cannot inspect pixels (deferred to @cosyte/dicom-pixel). When pixel data is present it always warns rather than claiming the image is clean.

RetainLongitudinalTemporal gives you the full-dates branch. PS3.15 §E.3.6 is two options, and Table E.1-1 gives them separate columns: Rtn. Long. Full Dates (keep dates and times as they are) and Rtn. Long. Modif. Dates (keep them only as modified/shifted values). One name here covers both, and it carries the full-dates column - the less protective branch. That is not a rounding difference: the two columns disagree on 169 rows, and on every one of them full-dates says K (keep the real value) where modified-dates says C (clean it). Activate it only when real dates are genuinely required; leave it off and the Basic Profile action applies, which removes or empties them. Date shifting is not implemented at this layer - a caller who needs the modified-dates behaviour shifts the values themselves after the call.

Example

const retain: DeidentifyOption[] = ["RetainLongitudinalTemporal", "RetainSafePrivate"];

DicomValue

DicomValue = { kind: "empty"; } | { kind: "text"; value: string; warnings?: readonly DicomParseWarning[]; } | { kind: "strings"; values: readonly string[]; warnings?: readonly DicomParseWarning[]; } | { kind: "personName"; values: readonly PersonName[]; warnings?: readonly DicomParseWarning[]; } | { kind: "numbers"; values: readonly number[]; } | { kind: "bigints"; values: readonly bigint[]; } | { kind: "attributeTags"; values: readonly Tag[]; } | { kind: "decimalString"; values: readonly (number | null)[]; warnings?: readonly DicomParseWarning[]; } | { kind: "integerString"; values: readonly (number | null)[]; warnings?: readonly DicomParseWarning[]; } | { kind: "dates"; values: readonly DicomDate[]; warnings?: readonly DicomParseWarning[]; } | { kind: "times"; values: readonly DicomTime[]; warnings?: readonly DicomParseWarning[]; } | { kind: "dateTimes"; values: readonly DicomDateTime[]; warnings?: readonly DicomParseWarning[]; } | { bytes: Buffer; kind: "binary"; } | { items: readonly Item[]; kind: "sequence"; }

The lazily-decoded value of an Element, as a discriminated union on kind. See the module doc for the VR → kind mapping. Narrow on kind (the switch-exhaustiveness-check lint rule keeps consumers honest).

Example

import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
const v = ds.get("00100010")?.value; // Patient's Name (PN)
if (v?.kind === "personName") {
console.log(v.values[0]?.alphabetic.familyName);
}

FatalCode

FatalCode = typeof FATAL_CODES[keyof typeof FATAL_CODES]

Discriminant type for DicomParseError.code. Narrowing a caught error by this code lets consumers write exhaustive switch blocks (enabled by the switch-exhaustiveness-check lint rule) and guarantees a typo-free comparison against the FATAL_CODES registry.

Example

import type { FatalCode } from "@cosyte/dicom";
function describe(code: FatalCode): string {
switch (code) {
case "EMPTY_INPUT":
return "input was empty";
case "NOT_DICOM_PART_10":
return "input is not a DICOM Part 10 file";
case "INVALID_FILE_META":
return "File Meta group is missing or malformed";
case "UNSUPPORTED_TRANSFER_SYNTAX":
return "Transfer Syntax UID is not supported by v1";
}
}

OnWarningCallback

OnWarningCallback = (warning) => void

Synchronous callback invoked once per Tier-2 warning emitted during parse.

Per 02-CONTEXT.md D-03, the callback fires AFTER the warning has been pushed to ctx.warnings; if the callback throws, the parser silently swallows the exception and continues (mirrors @cosyte/hl7 sibling).

Parameters

warning

DicomParseWarning

Returns

void

Example

import type { OnWarningCallback } from "@cosyte/dicom";
const onWarning: OnWarningCallback = (w) => {
if (w.code === "DICOM_MISSING_PREAMBLE") {
// ...
}
};

ProfilePrivateTags

ProfilePrivateTags = Readonly<Record<string, PrivateTagDefinition>>

One vendor's private-dictionary overlay as authored: canonical "GGGGXXLL" key → definition. Case-insensitive on input; normalized to uppercase on store.

Example

import type { ProfilePrivateTags } from "@cosyte/dicom";
const csa: ProfilePrivateTags = {
"0029XX10": { vr: "OB", keyword: "CSAImageHeaderInfo", name: "CSA Image Header Info" },
};

SerializeErrorCode

SerializeErrorCode = typeof SERIALIZE_ERROR_CODES[keyof typeof SERIALIZE_ERROR_CODES]

Discriminant for DicomSerializeError.code, enabling exhaustive switch narrowing (the switch-exhaustiveness-check lint rule).

Example

import type { SerializeErrorCode } from "@cosyte/dicom";
function describe(code: SerializeErrorCode): string {
switch (code) {
case "MISSING_TRANSFER_SYNTAX":
return "dataset has no Transfer Syntax UID to serialize under";
case "UNSUPPORTED_TRANSFER_SYNTAX":
return "Transfer Syntax UID is outside the v1 set";
}
}

ValueErrorCode

ValueErrorCode = typeof VALUE_ERROR_CODES[keyof typeof VALUE_ERROR_CODES]

Discriminant for DicomValueError.code, enabling exhaustive switch narrowing (the switch-exhaustiveness-check lint rule).

Example

import type { ValueErrorCode } from "@cosyte/dicom";
function describe(code: ValueErrorCode): string {
switch (code) {
case "FRAME_INDEX_OUT_OF_RANGE":
return "frame index outside [0, numberOfFrames)";
case "MISSING_REQUIRED_FUNCTIONAL_GROUP":
return "enhanced object lacks a required geometry macro";
}
}

WarningCode

WarningCode = typeof WARNING_CODES[keyof typeof WARNING_CODES]

Discriminant type for DicomParseWarning.code. Narrowing a warning by this code lets consumers write exhaustive switch blocks (enabled by the switch-exhaustiveness-check lint rule) and guarantees a typo-free comparison against the WARNING_CODES registry.

Example

import type { DicomParseWarning, WarningCode } from "@cosyte/dicom";
function describe(w: DicomParseWarning): string {
const code: WarningCode = w.code;
if (code === "DICOM_MISSING_PREAMBLE") return "preamble missing";
return `warning: ${code}`;
}

Variables

CODING_SCHEME_OIDS

const CODING_SCHEME_OIDS: object

Canonical coding-scheme OIDs (PS3.16 §8). Only the four standard designators are mapped. Legacy SNOMED designators (SRT / SNM3 / 99SDM) are intentionally absent: their code values differ from SCT (CP-730), so resolving them to the SNOMED OID would imply a false equality.

Type Declaration

DCM

readonly DCM: "1.2.840.10008.2.16.4" = "1.2.840.10008.2.16.4"

LN

readonly LN: "2.16.840.1.113883.6.1" = "2.16.840.1.113883.6.1"

SCT

readonly SCT: "2.16.840.1.113883.6.96" = "2.16.840.1.113883.6.96"

UCUM

readonly UCUM: "2.16.840.1.113883.6.8" = "2.16.840.1.113883.6.8"

Example

import { CODING_SCHEME_OIDS } from "@cosyte/dicom";
CODING_SCHEME_OIDS.SCT; // "2.16.840.1.113883.6.96"

DEFAULT_UID_ROOT

const DEFAULT_UID_ROOT: "2.25" = "2.25"

DICOM 2.25 UUID-derived root (PS3.5 §B.2) - no registration required.

Example

import { makeUidRemapper, DEFAULT_UID_ROOT } from "@cosyte/dicom";
const remap = makeUidRemapper(DEFAULT_UID_ROOT);
remap.map("1.2.840.113619.2.55.3").startsWith("2.25."); // true

DEIDENTIFY_ERROR_CODES

const DEIDENTIFY_ERROR_CODES: Readonly<{ INVALID_OPTIONS: "INVALID_OPTIONS"; }>

Stable codes for DeidentifyError.

Example

import { DEIDENTIFY_ERROR_CODES } from "@cosyte/dicom";
DEIDENTIFY_ERROR_CODES.INVALID_OPTIONS; // "INVALID_OPTIONS"

DEIDENTIFY_OPTIONS

const DEIDENTIFY_OPTIONS: readonly DeidentifyOption[]

The nine metadata option-set names, frozen for runtime validation.

Example

import { DEIDENTIFY_OPTIONS } from "@cosyte/dicom";
DEIDENTIFY_OPTIONS.includes("RetainUIDs"); // true

FATAL_CODES

const FATAL_CODES: object

Stable string codes for every Tier-3 fatal the parser may throw.

Locked at four codes per PROJECT.md "Fatal errors only for unrecoverable structural corruption": anything less severe MUST be a Tier-2 warning (see ./warnings.ts). Consumers narrow on err.code to react to specific structural failures.

Type Declaration

EMPTY_INPUT

readonly EMPTY_INPUT: "EMPTY_INPUT" = "EMPTY_INPUT"

INVALID_FILE_META

readonly INVALID_FILE_META: "INVALID_FILE_META" = "INVALID_FILE_META"

NOT_DICOM_PART_10

readonly NOT_DICOM_PART_10: "NOT_DICOM_PART_10" = "NOT_DICOM_PART_10"

UNSUPPORTED_TRANSFER_SYNTAX

readonly UNSUPPORTED_TRANSFER_SYNTAX: "UNSUPPORTED_TRANSFER_SYNTAX" = "UNSUPPORTED_TRANSFER_SYNTAX"

Example

import { parseDicom, FATAL_CODES, DicomParseError } from "@cosyte/dicom";
try {
parseDicom(Buffer.alloc(0));
} catch (err) {
if (err instanceof DicomParseError && err.code === FATAL_CODES.EMPTY_INPUT) {
// handle empty input
}
}

profiles

const profiles: Readonly<{ ge: Profile; lenient: Profile; philips: Profile; siemens: Profile; strict: Profile; }>

Frozen namespace of every built-in profile: three vendor private-dictionary overlays (ge, siemens, philips) and two posture presets (strict, lenient). Pass one straight to parseDicom.

Example

import { parseDicom, profiles } from "@cosyte/dicom";
const ds = parseDicom(buf, { profile: profiles.siemens });
console.log(profiles.siemens.describe?.());

SERIALIZE_ERROR_CODES

const SERIALIZE_ERROR_CODES: object

Stable string codes the Phase 5 serializer may throw. Narrow on DicomSerializeError.code to react to a specific failure.

Type Declaration

MISSING_TRANSFER_SYNTAX

readonly MISSING_TRANSFER_SYNTAX: "MISSING_TRANSFER_SYNTAX" = "MISSING_TRANSFER_SYNTAX"

UNSUPPORTED_TRANSFER_SYNTAX

readonly UNSUPPORTED_TRANSFER_SYNTAX: "UNSUPPORTED_TRANSFER_SYNTAX" = "UNSUPPORTED_TRANSFER_SYNTAX"

Example

import { SERIALIZE_ERROR_CODES } from "@cosyte/dicom";
SERIALIZE_ERROR_CODES.MISSING_TRANSFER_SYNTAX; // "MISSING_TRANSFER_SYNTAX"

VALUE_ERROR_CODES

const VALUE_ERROR_CODES: object

Stable string codes the Phase 4 helpers may throw. Narrow on DicomValueError.code to react to a specific contract violation.

Type Declaration

FRAME_INDEX_OUT_OF_RANGE

readonly FRAME_INDEX_OUT_OF_RANGE: "FRAME_INDEX_OUT_OF_RANGE" = "FRAME_INDEX_OUT_OF_RANGE"

MISSING_REQUIRED_FUNCTIONAL_GROUP

readonly MISSING_REQUIRED_FUNCTIONAL_GROUP: "MISSING_REQUIRED_FUNCTIONAL_GROUP" = "MISSING_REQUIRED_FUNCTIONAL_GROUP"

Example

import { VALUE_ERROR_CODES } from "@cosyte/dicom";
VALUE_ERROR_CODES.FRAME_INDEX_OUT_OF_RANGE; // "FRAME_INDEX_OUT_OF_RANGE"

VERSION

const VERSION: string = "0.0.12"

Package version string for @cosyte/dicom. Synchronized with package.json#version by scripts/sync-version.mjs, which the version script runs after changeset version so the bump and this constant land in the same "Version Packages" commit.

Stays on the uniform 0.0.x-until-first-alpha ladder (locked across the @cosyte/* suite): patch bumps via Changesets through pre-alpha, with no 0.1.0 milestone bump.

Example

import { VERSION } from "@cosyte/dicom";
console.log(`@cosyte/dicom v${VERSION}`);

WARNING_CODES

const WARNING_CODES: object

Stable string codes for every Tier-2 warning the parser may emit.

The registry is frozen via as const so TypeScript infers the exact string-literal union for WarningCode - there is zero runtime cost and no magic-string comparisons for consumers. Reserved-but-not-emitted codes carry inline comments documenting which phase activates them (Phase 2 declares the union so the schema is stable for downstream phases per D-08, D-42, D-43).

Type Declaration

DICOM_BOM_IN_TEXT_VR

readonly DICOM_BOM_IN_TEXT_VR: "DICOM_BOM_IN_TEXT_VR" = "DICOM_BOM_IN_TEXT_VR"

DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED

readonly DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED: "DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED" = "DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED"

DICOM_CHARSET_AMBIGUOUS_SEPARATOR

readonly DICOM_CHARSET_AMBIGUOUS_SEPARATOR: "DICOM_CHARSET_AMBIGUOUS_SEPARATOR" = "DICOM_CHARSET_AMBIGUOUS_SEPARATOR"

DICOM_DA_LEGACY_FORMAT

readonly DICOM_DA_LEGACY_FORMAT: "DICOM_DA_LEGACY_FORMAT" = "DICOM_DA_LEGACY_FORMAT"

DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED

readonly DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED: "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED" = "DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED"

DICOM_DEIDENT_METHOD_NOT_ADDED

readonly DICOM_DEIDENT_METHOD_NOT_ADDED: "DICOM_DEIDENT_METHOD_NOT_ADDED" = "DICOM_DEIDENT_METHOD_NOT_ADDED"

DICOM_DEIDENT_METHOD_NOT_LO

readonly DICOM_DEIDENT_METHOD_NOT_LO: "DICOM_DEIDENT_METHOD_NOT_LO" = "DICOM_DEIDENT_METHOD_NOT_LO"

DICOM_DEIDENT_METHOD_PRIOR_RETAINED

readonly DICOM_DEIDENT_METHOD_PRIOR_RETAINED: "DICOM_DEIDENT_METHOD_PRIOR_RETAINED" = "DICOM_DEIDENT_METHOD_PRIOR_RETAINED"

DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE

readonly DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE: "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE" = "DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE"

DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE

readonly DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE: "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE" = "DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE"

DICOM_DT_NONSTANDARD_OFFSET

readonly DICOM_DT_NONSTANDARD_OFFSET: "DICOM_DT_NONSTANDARD_OFFSET" = "DICOM_DT_NONSTANDARD_OFFSET"

DICOM_DUPLICATE_FILE_META_ELEMENT

readonly DICOM_DUPLICATE_FILE_META_ELEMENT: "DICOM_DUPLICATE_FILE_META_ELEMENT" = "DICOM_DUPLICATE_FILE_META_ELEMENT"

DICOM_DUPLICATE_TAG_IN_DATA_SET

readonly DICOM_DUPLICATE_TAG_IN_DATA_SET: "DICOM_DUPLICATE_TAG_IN_DATA_SET" = "DICOM_DUPLICATE_TAG_IN_DATA_SET"

DICOM_EMPTY_ITEM_IN_SEQUENCE

readonly DICOM_EMPTY_ITEM_IN_SEQUENCE: "DICOM_EMPTY_ITEM_IN_SEQUENCE" = "DICOM_EMPTY_ITEM_IN_SEQUENCE"

DICOM_FILE_META_GROUP_LENGTH_MISMATCH

readonly DICOM_FILE_META_GROUP_LENGTH_MISMATCH: "DICOM_FILE_META_GROUP_LENGTH_MISMATCH" = "DICOM_FILE_META_GROUP_LENGTH_MISMATCH"

DICOM_FILE_META_GROUP_LENGTH_MISSING

readonly DICOM_FILE_META_GROUP_LENGTH_MISSING: "DICOM_FILE_META_GROUP_LENGTH_MISSING" = "DICOM_FILE_META_GROUP_LENGTH_MISSING"

DICOM_GROUP_LENGTH_IN_DATASET

readonly DICOM_GROUP_LENGTH_IN_DATASET: "DICOM_GROUP_LENGTH_IN_DATASET" = "DICOM_GROUP_LENGTH_IN_DATASET"

DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR

readonly DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR: "DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR" = "DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR"

DICOM_IS_NONINTEGER_VALUE

readonly DICOM_IS_NONINTEGER_VALUE: "DICOM_IS_NONINTEGER_VALUE" = "DICOM_IS_NONINTEGER_VALUE"

DICOM_ITEM_CROSSES_SEQUENCE_END

readonly DICOM_ITEM_CROSSES_SEQUENCE_END: "DICOM_ITEM_CROSSES_SEQUENCE_END" = "DICOM_ITEM_CROSSES_SEQUENCE_END"

DICOM_MISSING_PREAMBLE

readonly DICOM_MISSING_PREAMBLE: "DICOM_MISSING_PREAMBLE" = "DICOM_MISSING_PREAMBLE"

DICOM_NON_ASCII_IN_ASCII_VR

readonly DICOM_NON_ASCII_IN_ASCII_VR: "DICOM_NON_ASCII_IN_ASCII_VR" = "DICOM_NON_ASCII_IN_ASCII_VR"

DICOM_NONZERO_RESERVED_BYTES

readonly DICOM_NONZERO_RESERVED_BYTES: "DICOM_NONZERO_RESERVED_BYTES" = "DICOM_NONZERO_RESERVED_BYTES"

DICOM_ODD_LENGTH_VALUE_PADDED

readonly DICOM_ODD_LENGTH_VALUE_PADDED: "DICOM_ODD_LENGTH_VALUE_PADDED" = "DICOM_ODD_LENGTH_VALUE_PADDED"

DICOM_PIXEL_DATA_LENGTH_MISMATCH

readonly DICOM_PIXEL_DATA_LENGTH_MISMATCH: "DICOM_PIXEL_DATA_LENGTH_MISMATCH" = "DICOM_PIXEL_DATA_LENGTH_MISMATCH"

DICOM_PRIVATE_CREATOR_UNKNOWN

readonly DICOM_PRIVATE_CREATOR_UNKNOWN: "DICOM_PRIVATE_CREATOR_UNKNOWN" = "DICOM_PRIVATE_CREATOR_UNKNOWN"

DICOM_PRIVATE_TAG_NO_CREATOR

readonly DICOM_PRIVATE_TAG_NO_CREATOR: "DICOM_PRIVATE_TAG_NO_CREATOR" = "DICOM_PRIVATE_TAG_NO_CREATOR"

DICOM_SQ_NOT_DESCENDED

readonly DICOM_SQ_NOT_DESCENDED: "DICOM_SQ_NOT_DESCENDED" = "DICOM_SQ_NOT_DESCENDED"

DICOM_TRAILING_NULL_IN_TEXT_VR

readonly DICOM_TRAILING_NULL_IN_TEXT_VR: "DICOM_TRAILING_NULL_IN_TEXT_VR" = "DICOM_TRAILING_NULL_IN_TEXT_VR"

DICOM_UI_TRAILING_SPACE

readonly DICOM_UI_TRAILING_SPACE: "DICOM_UI_TRAILING_SPACE" = "DICOM_UI_TRAILING_SPACE"

DICOM_UN_PARSED_AS_SQ

readonly DICOM_UN_PARSED_AS_SQ: "DICOM_UN_PARSED_AS_SQ" = "DICOM_UN_PARSED_AS_SQ"

DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR

readonly DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR: "DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR" = "DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR"

DICOM_UNSUPPORTED_CHARSET

readonly DICOM_UNSUPPORTED_CHARSET: "DICOM_UNSUPPORTED_CHARSET" = "DICOM_UNSUPPORTED_CHARSET"

DICOM_VR_MISMATCH

readonly DICOM_VR_MISMATCH: "DICOM_VR_MISMATCH" = "DICOM_VR_MISMATCH"

Example

import { parseDicom, WARNING_CODES } from "@cosyte/dicom";
const ds = parseDicom(buf);
if (ds.warnings.some((w) => w.code === WARNING_CODES.DICOM_MISSING_PREAMBLE)) {
// handle bare File Meta input
}

Functions

codingSchemeOid()

codingSchemeOid(designator): string | undefined

Resolve a coding-scheme designator to its canonical OID, or undefined for any non-standard / legacy designator (including SRT/SNM3/99SDM, which are NOT treated as SCT).

Parameters

designator

string | undefined

Returns

string | undefined

Example

import { codingSchemeOid } from "@cosyte/dicom";
codingSchemeOid("UCUM"); // "2.16.840.1.113883.6.8"
codingSchemeOid("SRT"); // undefined - not SCT (CP-730)

decodeElementValue()

decodeElementValue(element): DicomValue

Decode an Element's value to a typed DicomValue. Pure + fail-safe: it never throws and never coerces a malformed value to a plausible-but-wrong one. Called lazily (and memoized) by Element.value.

Parameters

element

Element

Returns

DicomValue

Example

import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
const v = ds.get("00280010")?.value; // Rows (US)
if (v?.kind === "numbers") console.log(v.values[0]);

decodeText()

decodeText(bytes, terms): string

Decode bytes to a string under the resolved Specific Character Set, never throwing: an unsupported decoder label falls back to UTF-8, then to Latin-1 (1:1, cannot fail).

Parameters

bytes

Buffer

terms

readonly string[] | undefined

Returns

string

Example

import { decodeText } from "@cosyte/dicom";
const s = decodeText(buf, ["ISO_IR 192"]); // UTF-8

defineProfile()

defineProfile(opts): Profile

Build a frozen Profile from a validated options object.

Parameters

opts

DefineProfileOptions

Returns

Profile

Example

import { defineProfile, parseDicom } from "@cosyte/dicom";
const acme = defineProfile({
name: "acme",
description: "ACME PACS quirks",
privateTags: {
"ACME_PRIV_01": {
"0019XX10": { vr: "DS", keyword: "AcmeDose", name: "ACME Dose" },
},
},
});
const ds = parseDicom(buf, { profile: acme });
console.log(acme.describe?.());

deidentify()

deidentify(ds, options?): DeidentifyResult<Dataset>

De-identify a Dataset per PS3.15 Annex E - the Basic Application Level Confidentiality Profile, plus any Retain/Clean Options passed in retain.

Pure: ds is never mutated. Returns a fresh dataset and a DeidentifyReport of tags, keywords, action codes, warnings and the UID map. Everything in it is composed from static tables except uidMap, whose keys are the source UIDs the file carried: treat that field as PHI.

Parameters

ds

Dataset

options?

DeidentifyOptions = {}

Returns

DeidentifyResult<Dataset>

Throws

DeidentifyError (INVALID_OPTIONS) for an unknown Retain option or a malformed uidRoot.

Example

import { parseDicom, deidentify, serializeDicom } from "@cosyte/dicom";
const { dataset, report } = deidentify(parseDicom(buf));
const clean = serializeDicom(dataset); // safe to share
console.log(report.attributes.length, "attributes acted on");

isKnownCharsetTerm()

isKnownCharsetTerm(term): boolean

true when term is a Specific Character Set defined term this build can map to a decoder (the empty default-repertoire term counts as known).

Parameters

term

string

Returns

boolean

Example

import { isKnownCharsetTerm } from "@cosyte/dicom";
isKnownCharsetTerm("ISO_IR 192"); // true
isKnownCharsetTerm("ISO_IR 14"); // false - does not exist

makeUidRemapper()

makeUidRemapper(root?, cache?): UidRemapper

Build a UidRemapper rooted at root, optionally seeded with (and writing through to) a caller-owned cache for cross-call sharing.

Parameters

root?

string = DEFAULT_UID_ROOT

Dotted-decimal UID root (default DEFAULT_UID_ROOT).

cache?

Map<string, string> = ...

Source→replacement map to fill (default a fresh Map).

Returns

UidRemapper

Throws

when root is not a valid dotted-decimal OID prefix or leaves no room for a value component within the 64-char UID limit.

Example

import { makeUidRemapper } from "@cosyte/dicom";
const remap = makeUidRemapper();
remap.map("1.2.840.113619.2.55.3") === remap.map("1.2.840.113619.2.55.3"); // true

parseDate()

parseDate(raw): object

Decode a single DA value. legacy is true for any tolerated non-canonical form (retired dotted YYYY.MM.DD, or an unparseable value such as "ANONYMIZED"), signalling the caller to emit DICOM_DA_LEGACY_FORMAT.

Parameters

raw

string

Returns

object

legacy

legacy: boolean

value

value: DicomDate

Example

import { parseDate } from "@cosyte/dicom";
parseDate("20240115").value.valid; // true
parseDate("2024.01.15").legacy; // true

parseDateTime()

parseDateTime(raw): object

Decode a single DT value. nonstandardOffset is true when a UTC offset suffix is present but malformed / out of range, signalling the caller to emit DICOM_DT_NONSTANDARD_OFFSET.

Parameters

raw

string

Returns

object

nonstandardOffset

nonstandardOffset: boolean

value

value: DicomDateTime

Example

import { parseDateTime } from "@cosyte/dicom";
parseDateTime("20240115133015+0100").value.offsetMinutes; // 60

parseDicom()

Internal

  • implementation signature. Public JSDoc lives on the overloads above.

Call Signature

parseDicom(input): Dataset

Parse a DICOM Part 10 buffer into a structural Dataset.

Lenient by default - recoverable deviations (missing preamble, File Meta group-length mismatch, odd-length value, etc.) are pushed into ds.warnings with stable codes from WARNING_CODES. Four unrecoverable structural failures throw DicomParseError:

  • EMPTY_INPUT - empty Buffer | Uint8Array | ArrayBuffer.
  • NOT_DICOM_PART_10 - input lacks both DICM magic at offset 128 and a recognizable (0002,0000) File Meta Group Length at offset 0.
  • INVALID_FILE_META - File Meta is truncated or (0002,0010) Transfer Syntax UID is missing.
  • UNSUPPORTED_TRANSFER_SYNTAX - Transfer Syntax UID is not one of the four v1 UIDs (1.2.840.10008.1.2, …1.2.1, …1.2.2, …1.2.1.99).

Pass { strict: true } to escalate every Tier-2 warning to a thrown DicomParseError carrying the warning code.

Parameters
input

ArrayBuffer | Buffer<ArrayBufferLike> | Uint8Array<ArrayBufferLike>

Returns

Dataset

Example
import { parseDicom, WARNING_CODES, DicomParseError } from "@cosyte/dicom";
import { readFileSync } from "node:fs";

// Three input shapes (PARSE-04): Buffer, Uint8Array, ArrayBuffer.
const bytes = readFileSync("study.dcm");
const ds1 = parseDicom(bytes);
const ds2 = parseDicom(new Uint8Array(bytes));
const ds3 = parseDicom(bytes.buffer);

// Inspect File Meta + warnings.
console.log(ds1.fileMeta?.transferSyntaxUID);
for (const w of ds1.warnings) {
if (w.code === WARNING_CODES.DICOM_MISSING_PREAMBLE) {
console.warn("bare File Meta input at offset", w.position.byteOffset);
}
}

// Strict mode + onWarning callback.
try {
parseDicom(bytes, {
strict: true,
onWarning: (w) => console.error(w.code, "at offset", w.position.byteOffset),
});
} catch (err) {
if (err instanceof DicomParseError) {
console.error(err.code, err.byteOffset, err.snippet);
}
}

Call Signature

parseDicom(input, options): Dataset

Parse a DICOM Part 10 buffer into a structural Dataset.

Lenient by default - recoverable deviations (missing preamble, File Meta group-length mismatch, odd-length value, etc.) are pushed into ds.warnings with stable codes from WARNING_CODES. Four unrecoverable structural failures throw DicomParseError:

  • EMPTY_INPUT - empty Buffer | Uint8Array | ArrayBuffer.
  • NOT_DICOM_PART_10 - input lacks both DICM magic at offset 128 and a recognizable (0002,0000) File Meta Group Length at offset 0.
  • INVALID_FILE_META - File Meta is truncated or (0002,0010) Transfer Syntax UID is missing.
  • UNSUPPORTED_TRANSFER_SYNTAX - Transfer Syntax UID is not one of the four v1 UIDs (1.2.840.10008.1.2, …1.2.1, …1.2.2, …1.2.1.99).

Pass { strict: true } to escalate every Tier-2 warning to a thrown DicomParseError carrying the warning code.

Parameters
input

ArrayBuffer | Buffer<ArrayBufferLike> | Uint8Array<ArrayBufferLike>

options

ParseOptions

Returns

Dataset

Example
import { parseDicom, WARNING_CODES, DicomParseError } from "@cosyte/dicom";
import { readFileSync } from "node:fs";

// Three input shapes (PARSE-04): Buffer, Uint8Array, ArrayBuffer.
const bytes = readFileSync("study.dcm");
const ds1 = parseDicom(bytes);
const ds2 = parseDicom(new Uint8Array(bytes));
const ds3 = parseDicom(bytes.buffer);

// Inspect File Meta + warnings.
console.log(ds1.fileMeta?.transferSyntaxUID);
for (const w of ds1.warnings) {
if (w.code === WARNING_CODES.DICOM_MISSING_PREAMBLE) {
console.warn("bare File Meta input at offset", w.position.byteOffset);
}
}

// Strict mode + onWarning callback.
try {
parseDicom(bytes, {
strict: true,
onWarning: (w) => console.error(w.code, "at offset", w.position.byteOffset),
});
} catch (err) {
if (err instanceof DicomParseError) {
console.error(err.code, err.byteOffset, err.snippet);
}
}

parsePersonName()

parsePersonName(value): PersonName

Parse a single (already charset-decoded, pad-trimmed) PN value string into its structured PersonName form.

Parameters

value

string

Returns

PersonName

Example

import { parsePersonName } from "@cosyte/dicom";
const pn = parsePersonName("Doe^Jane^^Dr^");
pn.alphabetic.familyName; // "Doe"
pn.alphabetic.namePrefix; // "Dr"

parseSpecificCharacterSet()

parseSpecificCharacterSet(bytes): readonly string[]

Parse a (0008,0005) Specific Character Set value (a CS, possibly multi-valued via ``) into its trimmed defined terms.

The first value may be empty (the ISO-2022 "G0 starts as ISO_IR 6" rule); it is preserved as "" rather than dropped so resolveDecoderLabel can apply the default-repertoire fallback.

A component this closed table does not name is not a defined term, and it is withheld rather than returned. This value is multi-valued on the backslash, every component is a string a sender authored, and the result lands on Element.specificCharacterSet, where it presents itself as an identifier a downstream package may interpolate: that is the field, and the delimiter, this package's measured PHI leak ran through. Decoding is unaffected, because resolveDecoderLabel already skipped any term it could not map and skips the withheld marker for the same reason.

Parameters

bytes

Buffer

Returns

readonly string[]

Example

import { parseSpecificCharacterSet } from "@cosyte/dicom";
// bytes for "ISO 2022 IR 6\\ISO 2022 IR 87"
const terms = parseSpecificCharacterSet(buf);

parseTime()

parseTime(raw): object

Decode a single TM value (max 14 bytes). Precision may be truncated from the right (HH, HHMM, HHMMSS, HHMMSS.FFFFFF).

Parameters

raw

string

Returns

object

value

value: DicomTime

Example

import { parseTime } from "@cosyte/dicom";
parseTime("133015.5").value.fractionalSeconds; // 0.5

readCode()

readCode(item): CodedConcept

Read the coded triplet off a code-item dataset (e.g. one item of a Code Sequence). Every part is independently fail-safe; schemeUid is the resolved OID for the designator when standard, else undefined.

Parameters

item

Dataset

Returns

CodedConcept

Example

import { parseDicom, readCode } from "@cosyte/dicom";
const units = parseDicom(buf).image.realWorldValueMaps?.[0]?.unitsCode;
// units?.codeValue / units?.codingSchemeDesignator / units?.codeMeaning

resolveDecoderLabel()

resolveDecoderLabel(terms): string

Resolve the TextDecoder label to use for a value, given the dataset's Specific Character Set terms. Prefers a multibyte/extended decoder when the term list mixes single-byte and code-extension sets; falls back to the default repertoire (Latin-1, lenient) when no term resolves.

Parameters

terms

readonly string[] | undefined

Returns

string

Example

import { resolveDecoderLabel } from "@cosyte/dicom";
resolveDecoderLabel(["ISO 2022 IR 6", "ISO 2022 IR 87"]); // "iso-2022-jp"
resolveDecoderLabel(undefined); // "latin1"

serializeDicom()

serializeDicom(ds): Buffer

Serialize a Dataset to a spec-clean DICOM Part 10 Buffer.

The dataset's transfer syntax is preserved (no transcoding): pixel-data fragments and nested sequences are written back byte-for-byte, while scalar values are re-emitted with correct even-length padding and File Meta group length. Pure function - the input Dataset is never mutated.

Input contract. The writer is designed for a Dataset produced by parseDicom: it relies on the parser's Element.rawBytes representation (value-only for scalars and Implicit-LE defined-length SQ; full on-wire span for Explicit SQ, undefined-length spans, encapsulated Pixel Data, and the UN/CP-246 fallbacks). A hand-built Dataset must follow the same convention for its bytes to be encoded correctly.

Round-trip scope. parseDicom(out) re-reads to a dataset that is equal over the modeled surface (every dataset element + the typed "../dataset/file-meta".FileMeta fields plus any non-modeled File Meta elements preserved on extraElements), not a byte-exact copy of the original file: the 128-byte preamble is normalized to zeros, the File Meta group is rebuilt in ascending tag order (modeled fields + extraElements - see encodeFileMeta), odd-length values are padded even, and retired (gggg,0000) group lengths are dropped.

Parameters

ds

Dataset

Returns

Buffer

Throws

DicomSerializeError with code MISSING_TRANSFER_SYNTAX when the dataset has no File Meta Transfer Syntax UID, or UNSUPPORTED_TRANSFER_SYNTAX when that UID is outside the v1 set.

Example

import { parseDicom, serializeDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
const out = serializeDicom(ds); // spec-clean Part 10, same transfer syntax
// parseDicom(out) re-reads to a structurally-equal dataset.