Skip to main content
Version: v0.0.19

@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 frame=F), 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.

byteOffset is only an index into your own buffer when offsetFrame is "input". This parser reads a Data Set out of a slice in two situations - a defined-length Sequence Item, and an SQ/UN descent - and out of an inflated stream in a third, and an offset raised in any of them counts from that buffer's byte 0. OFFSET_FRAMES names which; where a slice begins is deliberately not published, because the distance between two frames is a declared length off the wire.

snippet is cut in the frame offsetFrame names, on every fatal but one. The exception is UNSUPPORTED_TRANSFER_SYNTAX, whose snippet slot carries PS3.6's own NAME for the unsupported UID when the registry publishes one ("RLE Lossless"), and 16 raw bytes only when it does not. That is deliberate and predates the frame; it is named here because a universal about snippet written without it is false on the code a compressed object reaches first. Everywhere else the two agree, so a consumer that only wants the bytes at the offset already has them. The frame is what a consumer needs before indexing anything of its OWN by byteOffset, which is the case no field on this class used to cover.

Example​

import { parseDicom, DicomParseError, OFFSET_FRAMES } from "@cosyte/dicom";
try {
parseDicom(buffer);
} catch (err) {
if (err instanceof DicomParseError && err.code === "NOT_DICOM_PART_10") {
// err.byteOffset, err.offsetFrame, err.snippet, err.contextPath
if (err.offsetFrame !== OFFSET_FRAMES.INPUT) {
// `byteOffset` counts from somewhere inside the file, not from its start.
}
}
}

Extends​

  • Error

Constructors​

Constructor​

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

Internal

Construct a new DicomParseError. All fields except contextPath are required so every thrower populates positional context per TOL-02 - and offsetFrame is required for the same reason byteOffset is, because an offset whose frame is optional is an offset whose frame is usually missing.

Parameters​
code​

FatalCode

message​

string

byteOffset​

number

offsetFrame​

OffsetFrame

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

offsetFrame​

readonly offsetFrame: OffsetFrame

The coordinate system DicomParseError.byteOffset is counted in. See OFFSET_FRAMES.

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. tag, keyword, action, applied and repeatingGroup carry never a decoded value: keyword, action and repeatingGroup come from the Part 6 and Annex E tables, and tag is bound to a tag those tables carry a row for, which is membership in a closed table rather than a shape test.

🩺 contextPath is NOT in that class and this docstring used to say it was. It is a chain of tags read off the wire, bound by nothing - see DeidentifiedAttribute.contextPath and the note on DeidentifyReport.

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); // composed from tables - 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.

🩺 Each segment is TAG[index], and the TAG half is read off the wire with no table behind it. It is whatever tag the descent walked, so it is bound by neither a shape test nor membership in a closed one - which is what separates it from tag, keyword, action and repeatingGroup. On a file where an under-declared Value Length desynchronized the reader onto four bytes sitting inside somebody's value, those four bytes become a segment here. It is not the only identifier on this report read off the wire - DeidentifyReport.removedPrivateTags and UnauditableSequenceFinding.tag are too - but those two are disclosed as such, and this one was documented as structural.

Measured on a synthetic LO carrier holding "MRS BRAIN SMITHSON" that under-declares by four: the reader resynchronizes onto a fabricated SQ header, descends it, and the report reads contextPath: ["53484E4F[0]"] - "HSON" in wire order, recovered by writing the two halves back with writeUInt16LE. No warning is raised and every finding array on the report is empty. Change the surname and the published segment changes with it. PRE-EXISTING, on every release that has shipped the field.

🛑 IT IS NOT THE ONLY PLACE THOSE BYTES SURFACE, AND AN EARLIER DRAFT OF THIS NOTE SAID IT WAS. A GRADED PASS REFUTED THAT AND IT MUST NOT COME BACK. On the same file the de-identified Dataset still carries the fabricated (5348,4E4F), so serializeDicom writes its header back out in full - "HSON" included - inside an object stamped (0012,0062) Patient Identity Removed = YES. That re-emission belongs to the disclosed under-declare carrier class, not to this field, and neither is a bound on the other: redacting contextPath from a log does not make the object safe to share. Pinned in test/integration/phi-diagnostic-surface.test.ts.

It is published anyway, on the same footing as DeidentifyReport.removedPrivateTags: where an attribute sat is the whole audit value of the field, and withholding it would destroy that on every well-formed file in order to bound a malformed one. Treat it as PHI when the source is untrusted.

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, but it is no longer silent. A value of your own longer than 64 characters is written through as given - it is yours, and splitting or truncating it would invent a record you did not write - and report.warnings carries DICOM_DEIDENT_METHOD_VALUE_OVER_LENGTH to say so. Split it on `` yourself if a strict receiver is in your path. The same code covers an over-long value the source file wrote and this run kept, which in the common case is this library's own 76-character text from a release before the default became multi-valued. The measurement is over bytes: a Value of 64 bytes or fewer can never carry more than 64 characters, so it cannot miss one that is genuinely over, but PS3.5 §6.2 specifies the bound in characters rather than bytes and excludes Code Extension escape sequences from the count, so it raises on any conformant Value whose bytes outnumber its counted characters.

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. That code discloses the retention only; the length of what is written, from whatever source, is disclosed by DICOM_DEIDENT_METHOD_VALUE_OVER_LENGTH and by nothing else.

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, the active option names. Several fields are not, and they are named here rather than in a footnote. 🛑 Do not quote a COUNT of them, here or anywhere else. The count read "one" and then "two" and then "three", and was wrong every time it was read, because each correction bumped the numeral without re-deriving the list. The list below carried its own numerals until one had to be added, which is the same disease one step removed, so they are gone. Treat it as the current reading of a surface that has grown, not as a proof of exhaustiveness.

  • 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.
  • 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.
  • unauditableSequences[].tag - see UnauditableSequenceFinding. Same shape as removedPrivateTags by a narrower route: a private carrier a Profile declares SQ is named there, and an under-declared length upstream can resynchronize the reader onto four bytes that spell such a block. The package's usual answer to a fabricated header, undefinedVrElements, carries a byte offset and no tag, and still answers it whenever the fabricated VR is outside the 34 PS3.5 §6.2 defines. It cannot when the fabricated VR is one of them, because those two files are byte-identical.
  • undefinedVrElements[].byteLength and unauditableSequences[].byteLength - the declared Value Length read off the element header, so on a fabricated header it is four document bytes wearing a decimal: "SO\0\0" publishes 20307, two letters of a surname, put back with one readUInt32LE. They JOINED this list rather than always having been on it. Through 0.0.14 the two DICOM_DEIDENT_*_NOT_AUDITABLE messages rendered the same number; binding it out of those factory signatures left these model fields as its only publisher, which is a smaller surface and not a closed one. Narrowing them is a product call rather than a defect fix: a bound empties the field on every well-formed file, where the number is exactly the audit information it exists to carry.
  • contextPath, on all four findings that carry one - see DeidentifiedAttribute.contextPath, which holds the measurement. The segment tags come off the wire with no table behind them, so a fabricated SQ header the reader descended is named there, PRE-EXISTING, with no warning and no finding array to correlate it with. This is the field the rest of this docstring, the tolerance table and the troubleshooting guide all called structural. It is a logging hazard and nothing more: on that same file the de-identified object itself re-emits the fabricated header, so redacting this field does not make the object safe.

embeddedAttributes[].hidden left that list in DICOM-DIAGNOSTIC-PHI-RESIDUALS - an entry is now a literal PS3.15 Table E.1-1 row, so it is not value-bearing. Its own disclosure had been reworded twice by then, and this repo deletes a disclosure at that point rather than writing a third; what is true of the field is stated once, on EmbeddedAttributeFinding. The field is still uncapped.

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. This is the only copy of that list. Two others lived in module docstrings, still naming hidden after it left and never naming contextPath at all; a graded pass found them and they were deleted rather than resynced.

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 covers private sequences too, since DICOM-PRIVATE-SQ-CARVE-OUT. A private SQ a Profile vouches for under RetainSafePrivate used to be kept verbatim and never appear here; it is now emptied and listed on the same terms as any other, because the profile's licence under PS3.15 §E.3.10 is over a private attribute and not over an item stream nothing could read.

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.

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. Its sibling DeidentifyReport.unauditableSequences had a real one until DICOM-PRIVATE-SQ-CARVE-OUT; neither has one now, and they arrive at that by different routes - this one because nothing bypasses keepOrEmpty, that one because a vouched-for private SQ is routed into the ordinary SQ branches.

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.

🛑 byteOffset IS NOT ALWAYS RELATIVE TO THE SOURCE BUFFER, AND THIS JSDOC SAID IT WAS. For the Deflated Explicit VR LE transfer syntax (D-27), deflated: true says the offset indexes the inflated dataset buffer rather than the on-disk source. That flag is the only frame this type carries, and it is not the only frame this parser has: a defined-length Sequence Item is parsed from a slice, so a warning raised inside one carries an item-relative offset with nothing on the position to say so. The same is true of Element.byteOffset, and has been since the parser was written.

The residual is PRE-EXISTING and is not closed here. What is closed is the thrown side: DicomParseError.offsetFrame names the coordinate system from a closed set (see OFFSET_FRAMES), and that covers a Tier-3 fatal and the { strict: true } escalation of a Tier-2 warning. It does not reach a warning on the lenient path, which is this type. Do not read the fatal's frame contract as one this type has.

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).

tag and vr are the carrier's own and are structural. hidden was not, and it is bound now. Every tag in an embedded run is composed from four bytes that were sitting inside the carrier's value - that is the whole reason this type exists - and a run needs only ONE actionable attribute to be reported, so through 0.0.13 the rest of the run was listed beside it. Measured: a CS carrier over-declaring across a fabricated "SMIT" header beside a genuine (0010,0020) reported hidden: ["4D535449", "00100020"], and 4D535449 is "SMIT" in wire order.

An entry is now one of the 652 literal rows of PS3.15 Table E.1-1 that this run's options left actionable. That is a membership bound rather than a shape one - the posture this package already takes for a VR and for a Private Creator - so what survives names a published table entry rather than a document byte, the same trade rendering a VR makes with the 34. A repeating-group mask hit is NOT in that set and is excluded: (50xx,xxxx) Curve Data leaves the whole 16-bit element number free, so a mask match proves a rule exists without making the membership finite. A graded pass caught a draft of this filter that admitted it.

🛑 THAT IS NOT AN ALL-CLEAR OVER THIS TYPE. contextPath below is unbound and unchanged, hidden is uncapped, and DeidentifyReport names the report's other value-bearing fields. A DeidentifyReport is still not safe to log whole.

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. Built by the same descent as DeidentifiedAttribute.contextPath and carrying the same caveat: each segment's tag is read off the wire, bound by nothing, so on a desynchronized read it can be four bytes of a value. Read that field's note before logging this one.

hidden​

readonly hidden: readonly string[]

The tags found inside the carrier's value that this run acts on and that a published table names, in wire order. Not every tag in the run - see this type's own remarks.

🩺 IT MAY BE EMPTY, AND EMPTY DOES NOT MEAN "NOTHING WAS HIDDEN HERE". A run whose only actionable members are private attributes, Curve Data or Overlay elements reports a finding with no tags: the carrier was still emptied, and the accompanying DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED warning still counts the whole run. The presence of the finding is the fact; this list is the part of it that can be named.

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.

Example​

import { parseDicom } from "@cosyte/dicom";
const ds = parseDicom(buf);
// Anything the typed FileMeta fields do not model, in ascending tag order.
for (const raw of ds.fileMeta?.extraElements ?? []) {
raw.tag; // e.g. "00020017" Sending Application Entity Title
raw.vr; // the VR the source wrote (File Meta is always Explicit VR LE)
raw.value.length; // even, per PS3.5 2026c section 7.1.1
}

(0002,0016) Source Application Entity Title is deliberately NOT the example: it is one of the typed fields, so it never reaches this array.

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: {tag} renders only a tag PS3.6's element registry carries a literal row for, {vr} only one of the 34 VRs PS3.5 section 6.2 defines, and a raw length or byte value a header carries is bound out of the factory signature rather than rendered. That now holds on the deidentify() codes too: DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE and DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE rendered Element.rawBytes.length through 0.0.14, which equals the declared Value Length and was reachable from a fabricated header, and both slots are gone. So is DICOM_ITEM_CROSSES_SEQUENCE_END's remaining-bytes count, because a raw number shifted by a constant the reader can compute is that raw number: it was the enclosing sequence's declared Value Length less the bytes of that sequence already consumed. The exceptions are named in one place that is not a record of a past change, and are deliberately not restated here - the WARNING_MESSAGES docblock in ./warnings.ts, which this JSDoc used to carry a copy of. This is a statement about w.message and about nothing else: the two byte counts still exist on report.undefinedVrElements[].byteLength and report.unauditableSequences[].byteLength, model fields on a type whose own docs say it is not a value-free surface. No safe-to-log verdict is stated here - that sentence was corrected twice and is deleted rather than tried a third time; the mechanism is above and the treatment is in the package's troubleshooting docs. The DicomParseError this option raises in its place is a different and larger surface: it also carries snippet, 16 raw bytes, unredacted (D-10), read at the warning's own byteOffset. A message-only PHI review of the lenient path therefore does not transfer to the strict one. Log err.code, err.byteOffset, err.offsetFrame and err.message; treat err.snippet as PHI.

The snippet is cut in the SAME FRAME the byteOffset is counted in, so it is the bytes at the offset the diagnostic names: 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. It was not always: until DICOM-FATAL-MESSAGE-REGISTRY the offset moved with the frame while the cut was always taken from the whole file, so inside a defined-length Item the 16 bytes were an unrelated element's - a diagnostic disclosing data from a part of the document the reader was never asked about. That is closed. What is NOT closed, and never was a defect: the bytes are still raw source bytes. Reading them as safe because the message beside them is registry-bound is the mistake this whole paragraph exists to prevent.

byteOffset NOW CARRIES A FRAME-OF-REFERENCE CONTRACT, AND IT IS A NAME AND NOT AN ORIGIN. err.offsetFrame says which of three coordinate systems the number is counted in (OFFSET_FRAMES), so a consumer can tell a root offset from an Item-relative one instead of guessing. A nested offset is still not a key you can look up against the root, and it is not made into one here: where a slice begins is deliberately unpublished, because the distance between two frames is a declared Value Length off the wire. The escalated warning's own position is unchanged and still carries no frame beyond deflated - see DicomPosition.

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 Sequence-of-Items carrier that was emptied because this run had no item stream to walk, so the de-identifier had no Data Sets to reach.

Two producers, and the second is not a parsed SQ. The ordinary one is an SQ element whose items the parser never materialized. The other is a private data element retained under RetainSafePrivate whose Profile entry declares it SQ while the parse tree says otherwise - UN under Implicit VR LE when the profile was passed to deidentify() but not to parseDicom, or whatever binary VR the sender wrote under Explicit VR, which wins in the parser. The profile is the authority that retained the element, and it has said the value is a Sequence of Items; with no items on the tree, the §E.1.1 obligation below falls on the carrier just the same. Such an element keeps its parsed VR in the output and is emptied rather than re-typed to SQ (DICOM-PRIVATE-SQ-PARSE-VR).

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 recorded span of the value that was dropped. No decoded value appears here, and that is not the same as "safe to log". On the second producer tag can be four bytes of another element's value: a length under-declared upstream resynchronizes the reader mid-value, and if the bytes it lands on spell a private block this caller's profile declares SQ, followed by a VR that is one of the 34, the fabricated header is what you get here. The package's answer to that class is normally report.undefinedVrElements, which names a byte offset and no tag, and it still answers the case where the fabricated VR is outside the 34 - but it cannot answer this one, because a fabricated OB header and a genuine one are byte-identical. So this shares the standing exception report.removedPrivateTags and uidMap already have: a DeidentifyReport is not a value-free surface. Treat it as document content, at the sensitivity of the file it came from.

For the first producer the parser announces the underlying refusal 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. Do not generalise that to the second. There the file may be entirely conformant - an honest defined-length OB carrier raises nothing at all - so this report field, not Dataset.warnings, is where that drop is visible.

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. Built by the same descent as DeidentifiedAttribute.contextPath and carrying the same caveat: each segment's tag is read off the wire, bound by nothing, so on a desynchronized read it can be four bytes of a value. Read that field's note before logging this one.

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.

🩺 THAT WITHHOLDING IS NOT WHOLE, AND THIS PARAGRAPH USED TO CLAIM IT WAS. It said "nothing here renders a document byte ... and the structural contextPath. Safe to log." contextPath is not structural: its segments are tags read off the wire by the same descent, so the header this type refuses to name by tag can be named by the contextPath of a finding one level down. See DeidentifiedAttribute.contextPath for the measurement. byteOffset and byteLength are unaffected and the reasoning above them still stands.

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 four length bytes can themselves be value bytes, like the tag bytes. What is published is the number they decode to, and one readUInt32LE puts the bytes back: a fabricated header reading "SO\0\0" publishes 20307, two letters of a surname. Do not describe this field as "structural, never a value" the way its siblings are described.

🩺 IT IS NO LONGER "the same footing as every {n} in the warning registry", AND THAT SENTENCE WAS THE ONE THIS FIELD SHIPPED WITH. DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE rendered exactly this number and does not any more - it is bound out of undefinedVrNotAuditable's signature. So this field is now a model field, on the same standing exception as removedPrivateTags, unauditableSequences[].tag, uidMap and contextPath: narrowing it is a product call, because a bound empties it on every well-formed file, where it is exactly the audit number the field exists to carry. Deliberately unchanged; see this type's own summary.

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. Built by the same descent as DeidentifiedAttribute.contextPath and carrying the same caveat: each segment's tag is read off the wire, bound by nothing, so on a desynchronized read it can be four bytes of a value. Read that field's note before logging this one.

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";
}
}

OffsetFrame​

OffsetFrame = typeof OFFSET_FRAMES[keyof typeof OFFSET_FRAMES]

The frame a DicomParseError.byteOffset is counted in. See OFFSET_FRAMES.

Example​

import type { OffsetFrame } from "@cosyte/dicom";
function indexable(frame: OffsetFrame): boolean {
// Only the root frame's offsets index the buffer the caller passed in.
switch (frame) {
case "input":
return true;
case "inflated-dataset":
case "value-slice":
return false;
}
}

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
}
}

OFFSET_FRAMES​

const OFFSET_FRAMES: object

The coordinate systems a byteOffset this parser publishes can be counted in.

A byte offset is a number and a number alone says nothing about where its zero is. This parser reads a Data Set out of three different buffers over one parseDicom call, so the same small integer means three different things depending on which one is being read - and until DICOM-DIAGNOSTIC-PHI- RESIDUALS closed it, nothing on the thrown error said which. A consumer cutting input.subarray(err.byteOffset, err.byteOffset + 16) to see what upset the parser was, inside a Sequence Item, cutting an unrelated element - the exact defect the { strict: true } snippet itself was fixed for in #80.

A frame NAME is published; a frame ORIGIN is not, and that asymmetry is deliberate. The name is drawn from the closed set below, which the parser chooses and no sender can influence, and that membership is the whole of its bound. An origin has no such table: it is a position reached by summing the declared lengths that led to it, so two of them differ by a wire field, and a message that already publishes byteOffset would be one number short of one. That is a weaker argument than an impossibility and is stated as weaker, since the library publishes positions freely in the "input" frame; a graded pass said so. The item asked for the frame to be NAMED, and nothing here needs the origin, so the cheap side of the trade is taken.

Type Declaration​

INFLATED_DATASET​

readonly INFLATED_DATASET: "inflated-dataset" = "inflated-dataset"

Byte 0 is byte 0 of the inflated Data Set of a Deflated Explicit VR LE object (1.2.840.10008.1.2.1.99). The compressed input holds no such byte, so the offset does not index it at any scale.

INPUT​

readonly INPUT: "input" = "input"

Byte 0 is byte 0 of the buffer handed to parseDicom. The only frame in which indexing the caller's own input by byteOffset is meaningful.

VALUE_SLICE​

readonly VALUE_SLICE: "value-slice" = "value-slice"

Byte 0 is byte 0 of a slice this parser cut from inside a Value Field: a defined-length Sequence Item's value, or an SQ/UN value handed to a descent. Where that slice begins is deliberately not published - see this table's own note.

Example​

import { parseDicom, DicomParseError, OFFSET_FRAMES } from "@cosyte/dicom";
try {
parseDicom(buffer);
} catch (err) {
if (err instanceof DicomParseError && err.offsetFrame === OFFSET_FRAMES.INPUT) {
// Only here is `err.byteOffset` an index into the buffer you passed in.
console.error(buffer.subarray(err.byteOffset, err.byteOffset + 16));
}
}

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.19"

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_METHOD_VALUE_OVER_LENGTH​

readonly DICOM_DEIDENT_METHOD_VALUE_OVER_LENGTH: "DICOM_DEIDENT_METHOD_VALUE_OVER_LENGTH" = "DICOM_DEIDENT_METHOD_VALUE_OVER_LENGTH"

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.