@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
Properties
_elements
protectedreadonly_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
readonlyfileMeta:FileMeta|undefined
warnings
readonlywarnings: readonlyDicomParseWarning[]
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
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
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
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
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): readonlyElement[]
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
Overrides
Error.constructor
Properties
code
readonlycode:"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
message
string
byteOffset
number
offsetFrame
snippet
string
contextPath?
readonly string[]
Returns
Overrides
Error.constructor
Properties
byteOffset
readonlybyteOffset:number
code
readonlycode:FatalCode
contextPath
readonlycontextPath: readonlystring[] |undefined
offsetFrame
readonlyoffsetFrame:OffsetFrame
The coordinate system DicomParseError.byteOffset is counted in. See OFFSET_FRAMES.
snippet
readonlysnippet: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
message
string
Returns
Overrides
Error.constructor
Properties
code
readonlycode: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
message
string
Returns
Overrides
Error.constructor
Properties
code
readonlycode: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
Properties
byteOffset
readonlybyteOffset:number
cp246Promoted
readonlycp246Promoted: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
readonlyitems: readonlyItem[] |undefined
Parsed items for an SQ element; undefined otherwise.
length
readonlylength:number
littleEndian
readonlylittleEndian:boolean
Value byte order (per transfer syntax). See ElementInit.littleEndian.
privateCreator
readonlyprivateCreator:string|undefined
rawBytes
readonlyrawBytes:Buffer
specificCharacterSet
readonlyspecificCharacterSet: readonlystring[] |undefined
In-effect (0008,0005) terms, or undefined for the Default Repertoire.
tag
readonlytag:string
vm
readonlyvm:number
vr
readonlyvr: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
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
Overrides
Properties
_elements
protectedreadonly_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
fileMeta
readonlyfileMeta:FileMeta|undefined
Inherited from
index
readonlyindex:number
warnings
readonlywarnings: readonlyDicomParseWarning[]
Inherited from
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
Inherited from
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
Inherited from
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
Inherited from
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
Inherited from
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
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
getAll()
getAll(
tag): readonlyElement[]
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
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
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
Overrides
Error.constructor
Properties
profileName?
readonlyoptionalprofileName?: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
Properties
items
readonlyitems: readonlyItem[]
length
readonlylength: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?
readonlyoptionalcodeMeaning?:string
codeValue?
readonlyoptionalcodeValue?:string
codingSchemeDesignator?
readonlyoptionalcodingSchemeDesignator?:string
schemeUid?
readonlyoptionalschemeUid?: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?
readonlyoptionaldescription?:string
escalate?
readonlyoptionalescalate?: readonlyWarningCode[]
extends?
name
readonlyname:string
privateTags?
readonlyoptionalprivateTags?:Readonly<Record<string,Readonly<Record<string,PrivateTagDefinition>>>>
suppress?
readonlyoptionalsuppress?: readonlyWarningCode[]
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
readonlyaction:"D"|"Z"|"X"|"K"|"C"|"U"
The resolved single action after collapsing any conditional code.
applied
readonlyapplied:AppliedAction
contextPath?
readonlyoptionalcontextPath?: readonlystring[]
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
readonlykeyword:string
repeatingGroup?
readonlyoptionalrepeatingGroup?: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
readonlytag: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?
readonlyoptionaldeidentificationMethod?: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?
readonlyoptionalprofile?: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?
readonlyoptionalretain?: readonlyDeidentifyOption[]
Annex E option sets to activate (Retain* / Clean*). Default: none.
uidMap?
readonlyoptionaluidMap?: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?
readonlyoptionaluidRoot?: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 asremovedPrivateTagsby a narrower route: a private carrier a Profile declaresSQis 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[].byteLengthandunauditableSequences[].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"publishes20307, two letters of a surname, put back with onereadUInt32LE. They JOINED this list rather than always having been on it. Through0.0.14the twoDICOM_DEIDENT_*_NOT_AUDITABLEmessages 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 fabricatedSQheader 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
readonlyattributes: readonlyDeidentifiedAttribute[]
Per-attribute outcomes for every attribute Annex E acted on.
embeddedAttributes
readonlyembeddedAttributes: readonlyEmbeddedAttributeFinding[]
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
readonlyremovedPrivateTags: readonlystring[]
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
readonlyretained: readonlyDeidentifyOption[]
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
readonlyuidMap: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
readonlyunauditableSequences: readonlyUnauditableSequenceFinding[]
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
readonlyundefinedVrElements: readonlyUndefinedVrFinding[]
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
readonlywarnings: readonlyDicomParseWarning[]
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
readonlydataset:TDataset
report
readonlyreport: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?
readonlyoptionalday?:number
month?
readonlyoptionalmonth?:number
raw
readonlyraw:string
valid
readonlyvalid:boolean
year?
readonlyoptionalyear?: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?
readonlyoptionalday?:number
fractionalSeconds?
readonlyoptionalfractionalSeconds?:number
hours?
readonlyoptionalhours?:number
minutes?
readonlyoptionalminutes?:number
month?
readonlyoptionalmonth?:number
offsetMinutes?
readonlyoptionaloffsetMinutes?:number
raw
readonlyraw:string
seconds?
readonlyoptionalseconds?:number
valid
readonlyvalid:boolean
year?
readonlyoptionalyear?: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
readonlycode:WarningCode
message
readonlymessage:string
position
readonlyposition: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
readonlybyteOffset:number
contextPath?
readonlyoptionalcontextPath?: readonlystring[]
Tag chain for nested SQ items, e.g. ["0040A730", "0", "00080100"]. Omit when at root.
deflated?
readonlyoptionaldeflated?:boolean
True when offset is into the inflated dataset buffer (Deflated TS only). Omit when not applicable.
fileMeta?
readonlyoptionalfileMeta?: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?
readonlyoptionalfractionalSeconds?:number
hours?
readonlyoptionalhours?:number
minutes?
readonlyoptionalminutes?:number
raw
readonlyraw:string
seconds?
readonlyoptionalseconds?:number
valid
readonlyvalid: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?
readonlyoptionalcontextPath?: readonlystring[]
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
readonlyhidden: readonlystring[]
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
readonlytag:string
The carrier - the element whose over-declared value held the others.
vr
readonlyvr: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?
readonlyoptionalextraElements?: readonlyFileMetaRawElement[]
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?
readonlyoptionalfileMetaInformationVersion?:Buffer<ArrayBufferLike>
implementationClassUID?
readonlyoptionalimplementationClassUID?:string
implementationVersionName?
readonlyoptionalimplementationVersionName?:string
mediaStorageSOPClassUID?
readonlyoptionalmediaStorageSOPClassUID?:string
mediaStorageSOPInstanceUID?
readonlyoptionalmediaStorageSOPInstanceUID?:string
sourceApplicationEntityTitle?
readonlyoptionalsourceApplicationEntityTitle?:string
transferSyntaxUID
readonlytransferSyntaxUID: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
readonlytag:string
8-char uppercase hex tag, e.g. "00020100".
value
readonlyvalue:Buffer
The raw on-wire value bytes (even-length), copied out of the input.
vr
readonlyvr: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?
readonlyoptionalframeVoiLut?:object
windowCenter?
readonlyoptionalwindowCenter?: readonly (number|null)[]
windowWidth?
readonlyoptionalwindowWidth?: readonly (number|null)[]
index
readonlyindex:number
pixelMeasures?
readonlyoptionalpixelMeasures?:object
pixelSpacing?
readonlyoptionalpixelSpacing?: readonly (number|null)[]
sliceThickness?
readonlyoptionalsliceThickness?:number
spacingBetweenSlices?
readonlyoptionalspacingBetweenSlices?:number
pixelValueTransformation?
readonlyoptionalpixelValueTransformation?:object
rescaleIntercept?
readonlyoptionalrescaleIntercept?:number
rescaleSlope?
readonlyoptionalrescaleSlope?:number
rescaleType?
readonlyoptionalrescaleType?:string
planeOrientation?
readonlyoptionalplaneOrientation?:object
imageOrientationPatient?
readonlyoptionalimageOrientationPatient?: readonly (number|null)[]
planePosition?
readonlyoptionalplanePosition?:object
imagePositionPatient?
readonlyoptionalimagePositionPatient?: 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?
readonlyoptionalbitsAllocated?:number
bitsStored?
readonlyoptionalbitsStored?:number
columns?
readonlyoptionalcolumns?:number
frameOfReferenceUid?
readonlyoptionalframeOfReferenceUid?:string
highBit?
readonlyoptionalhighBit?:number
imageOrientationPatient?
readonlyoptionalimageOrientationPatient?: readonly (number|null)[]
imagePositionPatient?
readonlyoptionalimagePositionPatient?: readonly (number|null)[]
imagerPixelSpacing?
readonlyoptionalimagerPixelSpacing?: readonly (number|null)[]
isEnhancedMultiFrame
readonlyisEnhancedMultiFrame:boolean
true when this object carries Per-Frame/Shared Functional Groups.
modalityLutSequence?
readonlyoptionalmodalityLutSequence?: readonlyItem[]
nominalScannedPixelSpacing?
readonlyoptionalnominalScannedPixelSpacing?: readonly (number|null)[]
numberOfFrames?
readonlyoptionalnumberOfFrames?:number
photometricInterpretation?
readonlyoptionalphotometricInterpretation?:string
pixelRepresentation?
readonlyoptionalpixelRepresentation?:number
Raw (0028,0103) value: 0 = unsigned, 1 = signed. Absent ⇒ unknown.
pixelSpacing?
readonlyoptionalpixelSpacing?: readonly (number|null)[]
planarConfiguration?
readonlyoptionalplanarConfiguration?:number
realWorldValueMaps?
readonlyoptionalrealWorldValueMaps?: readonlyRealWorldValueMap[]
rescaleIntercept?
readonlyoptionalrescaleIntercept?:number
rescaleSlope?
readonlyoptionalrescaleSlope?:number
rescaleType?
readonlyoptionalrescaleType?:string
rows?
readonlyoptionalrows?:number
samplesPerPixel?
readonlyoptionalsamplesPerPixel?:number
signed?
readonlyoptionalsigned?:boolean
true/false only when (0028,0103) was 1/0; absent ⇒ never guessed.
sliceThickness?
readonlyoptionalsliceThickness?:number
sopInstanceUid?
readonlyoptionalsopInstanceUid?:string
spacingBetweenSlices?
readonlyoptionalspacingBetweenSlices?:number
units?
readonlyoptionalunits?:string
voiLutSequence?
readonlyoptionalvoiLutSequence?: readonlyItem[]
windowCenter?
readonlyoptionalwindowCenter?: readonly (number|null)[]
windowWidth?
readonlyoptionalwindowWidth?: 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
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?
readonlyoptionalid?:string
issuer?
readonlyoptionalissuer?:string
typeCode?
readonlyoptionaltypeCode?: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?
readonlyoptionalcopyValues?: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?
readonlyoptionalonWarning?: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?
readonlyoptionalprofile?: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?
readonlyoptionalstrict?: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?
readonlyoptionalstripPreamble?:"tolerate"|"require"
Preamble policy:
"tolerate"(default): attempt to start at offset 0 ifDICMmagic is missing at offset 128; emitDICOM_MISSING_PREAMBLE."require": throwDicomParseError(NOT_DICOM_PART_10)when noDICMmagic 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?
readonlyoptionalbirthDate?:DicomDate
id?
readonlyoptionalid?:string
issuerOfId?
readonlyoptionalissuerOfId?:string
issuerQualifiers?
readonlyoptionalissuerQualifiers?: readonlyItem[]
name?
readonlyoptionalname?:PersonName
otherIds
readonlyotherIds: readonlyOtherPatientId[]
sex?
readonlyoptionalsex?: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
readonlyalphabetic:PersonNameGroup
ideographic?
readonlyoptionalideographic?:PersonNameGroup
phonetic?
readonlyoptionalphonetic?: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
readonlyfamilyName:string
givenName
readonlygivenName:string
middleName
readonlymiddleName:string
namePrefix
readonlynamePrefix:string
nameSuffix
readonlynameSuffix: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
readonlykeyword:string
name
readonlyname:string
vr
readonlyvr: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 thrownDicomParseError(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?
readonlyoptionaldescribe?: () =>string
Render a human-readable, deterministic one-line summary of the profile.
Returns
string
description?
readonlyoptionaldescription?:string
escalations
readonlyescalations:ReadonlySet<WarningCode>
lineage
readonlylineage: readonlystring[]
name
readonlyname:string
privateDictionary
readonlyprivateDictionary: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
readonlysuppressions: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?
readonlyoptionalintercept?:number
slope?
readonlyoptionalslope?:number
unitsCode?
readonlyoptionalunitsCode?: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?
readonlyoptionaldescription?:string
frameOfReferenceUid?
readonlyoptionalframeOfReferenceUid?:string
instanceUid?
readonlyoptionalinstanceUid?:string
modality?
readonlyoptionalmodality?:string
number?
readonlyoptionalnumber?: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?
readonlyoptionalaccessionNumber?:string
date?
readonlyoptionaldate?:DicomDate
description?
readonlyoptionaldescription?:string
id?
readonlyoptionalid?:string
instanceUid?
readonlyoptionalinstanceUid?:string
time?
readonlyoptionaltime?: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
readonlycache:Map<string,string>
The source→replacement cache, exposed for reporting / reuse.
map
readonlymap: (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
readonlybyteLength:number
Byte length of the value field that was dropped. Structural, never a value.
contextPath?
readonlyoptionalcontextPath?: readonlystring[]
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
readonlytag: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
readonlybyteLength: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
readonlybyteOffset: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?
readonlyoptionalcontextPath?: readonlystring[]
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 typeofDEIDENTIFY_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?: readonlyDicomParseWarning[]; } | {kind:"strings";values: readonlystring[];warnings?: readonlyDicomParseWarning[]; } | {kind:"personName";values: readonlyPersonName[];warnings?: readonlyDicomParseWarning[]; } | {kind:"numbers";values: readonlynumber[]; } | {kind:"bigints";values: readonlybigint[]; } | {kind:"attributeTags";values: readonlyTag[]; } | {kind:"decimalString";values: readonly (number|null)[];warnings?: readonlyDicomParseWarning[]; } | {kind:"integerString";values: readonly (number|null)[];warnings?: readonlyDicomParseWarning[]; } | {kind:"dates";values: readonlyDicomDate[];warnings?: readonlyDicomParseWarning[]; } | {kind:"times";values: readonlyDicomTime[];warnings?: readonlyDicomParseWarning[]; } | {kind:"dateTimes";values: readonlyDicomDateTime[];warnings?: readonlyDicomParseWarning[]; } | {bytes:Buffer;kind:"binary"; } | {items: readonlyItem[];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 typeofFATAL_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 typeofOFFSET_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
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 typeofSERIALIZE_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 typeofVALUE_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 typeofWARNING_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
constCODING_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
readonlyDCM:"1.2.840.10008.2.16.4"="1.2.840.10008.2.16.4"
LN
readonlyLN:"2.16.840.1.113883.6.1"="2.16.840.1.113883.6.1"
SCT
readonlySCT:"2.16.840.1.113883.6.96"="2.16.840.1.113883.6.96"
UCUM
readonlyUCUM:"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
constDEFAULT_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
constDEIDENTIFY_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
constDEIDENTIFY_OPTIONS: readonlyDeidentifyOption[]
The nine metadata option-set names, frozen for runtime validation.
Example
import { DEIDENTIFY_OPTIONS } from "@cosyte/dicom";
DEIDENTIFY_OPTIONS.includes("RetainUIDs"); // true
FATAL_CODES
constFATAL_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
readonlyEMPTY_INPUT:"EMPTY_INPUT"="EMPTY_INPUT"
INVALID_FILE_META
readonlyINVALID_FILE_META:"INVALID_FILE_META"="INVALID_FILE_META"
NOT_DICOM_PART_10
readonlyNOT_DICOM_PART_10:"NOT_DICOM_PART_10"="NOT_DICOM_PART_10"
UNSUPPORTED_TRANSFER_SYNTAX
readonlyUNSUPPORTED_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
constOFFSET_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
readonlyINFLATED_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
readonlyINPUT:"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
readonlyVALUE_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
constprofiles: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
constSERIALIZE_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
readonlyMISSING_TRANSFER_SYNTAX:"MISSING_TRANSFER_SYNTAX"="MISSING_TRANSFER_SYNTAX"
UNSUPPORTED_TRANSFER_SYNTAX
readonlyUNSUPPORTED_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
constVALUE_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
readonlyFRAME_INDEX_OUT_OF_RANGE:"FRAME_INDEX_OUT_OF_RANGE"="FRAME_INDEX_OUT_OF_RANGE"
MISSING_REQUIRED_FUNCTIONAL_GROUP
readonlyMISSING_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
constVERSION:string="0.0.18"
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
constWARNING_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
readonlyDICOM_BOM_IN_TEXT_VR:"DICOM_BOM_IN_TEXT_VR"="DICOM_BOM_IN_TEXT_VR"
DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED
readonlyDICOM_BURNED_IN_ANNOTATION_NOT_REMOVED:"DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED"="DICOM_BURNED_IN_ANNOTATION_NOT_REMOVED"
DICOM_CHARSET_AMBIGUOUS_SEPARATOR
readonlyDICOM_CHARSET_AMBIGUOUS_SEPARATOR:"DICOM_CHARSET_AMBIGUOUS_SEPARATOR"="DICOM_CHARSET_AMBIGUOUS_SEPARATOR"
DICOM_DA_LEGACY_FORMAT
readonlyDICOM_DA_LEGACY_FORMAT:"DICOM_DA_LEGACY_FORMAT"="DICOM_DA_LEGACY_FORMAT"
DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED
readonlyDICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED:"DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED"="DICOM_DEIDENT_EMBEDDED_ATTRIBUTE_REMOVED"
DICOM_DEIDENT_METHOD_NOT_ADDED
readonlyDICOM_DEIDENT_METHOD_NOT_ADDED:"DICOM_DEIDENT_METHOD_NOT_ADDED"="DICOM_DEIDENT_METHOD_NOT_ADDED"
DICOM_DEIDENT_METHOD_NOT_LO
readonlyDICOM_DEIDENT_METHOD_NOT_LO:"DICOM_DEIDENT_METHOD_NOT_LO"="DICOM_DEIDENT_METHOD_NOT_LO"
DICOM_DEIDENT_METHOD_PRIOR_RETAINED
readonlyDICOM_DEIDENT_METHOD_PRIOR_RETAINED:"DICOM_DEIDENT_METHOD_PRIOR_RETAINED"="DICOM_DEIDENT_METHOD_PRIOR_RETAINED"
DICOM_DEIDENT_METHOD_VALUE_OVER_LENGTH
readonlyDICOM_DEIDENT_METHOD_VALUE_OVER_LENGTH:"DICOM_DEIDENT_METHOD_VALUE_OVER_LENGTH"="DICOM_DEIDENT_METHOD_VALUE_OVER_LENGTH"
DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE
readonlyDICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE:"DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE"="DICOM_DEIDENT_SEQUENCE_NOT_AUDITABLE"
DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE
readonlyDICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE:"DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE"="DICOM_DEIDENT_UNDEFINED_VR_NOT_AUDITABLE"
DICOM_DT_NONSTANDARD_OFFSET
readonlyDICOM_DT_NONSTANDARD_OFFSET:"DICOM_DT_NONSTANDARD_OFFSET"="DICOM_DT_NONSTANDARD_OFFSET"
DICOM_DUPLICATE_FILE_META_ELEMENT
readonlyDICOM_DUPLICATE_FILE_META_ELEMENT:"DICOM_DUPLICATE_FILE_META_ELEMENT"="DICOM_DUPLICATE_FILE_META_ELEMENT"
DICOM_DUPLICATE_TAG_IN_DATA_SET
readonlyDICOM_DUPLICATE_TAG_IN_DATA_SET:"DICOM_DUPLICATE_TAG_IN_DATA_SET"="DICOM_DUPLICATE_TAG_IN_DATA_SET"
DICOM_EMPTY_ITEM_IN_SEQUENCE
readonlyDICOM_EMPTY_ITEM_IN_SEQUENCE:"DICOM_EMPTY_ITEM_IN_SEQUENCE"="DICOM_EMPTY_ITEM_IN_SEQUENCE"
DICOM_FILE_META_GROUP_LENGTH_MISMATCH
readonlyDICOM_FILE_META_GROUP_LENGTH_MISMATCH:"DICOM_FILE_META_GROUP_LENGTH_MISMATCH"="DICOM_FILE_META_GROUP_LENGTH_MISMATCH"
DICOM_FILE_META_GROUP_LENGTH_MISSING
readonlyDICOM_FILE_META_GROUP_LENGTH_MISSING:"DICOM_FILE_META_GROUP_LENGTH_MISSING"="DICOM_FILE_META_GROUP_LENGTH_MISSING"
DICOM_GROUP_LENGTH_IN_DATASET
readonlyDICOM_GROUP_LENGTH_IN_DATASET:"DICOM_GROUP_LENGTH_IN_DATASET"="DICOM_GROUP_LENGTH_IN_DATASET"
DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VR
readonlyDICOM_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
readonlyDICOM_IS_NONINTEGER_VALUE:"DICOM_IS_NONINTEGER_VALUE"="DICOM_IS_NONINTEGER_VALUE"
DICOM_ITEM_CROSSES_SEQUENCE_END
readonlyDICOM_ITEM_CROSSES_SEQUENCE_END:"DICOM_ITEM_CROSSES_SEQUENCE_END"="DICOM_ITEM_CROSSES_SEQUENCE_END"
DICOM_MISSING_PREAMBLE
readonlyDICOM_MISSING_PREAMBLE:"DICOM_MISSING_PREAMBLE"="DICOM_MISSING_PREAMBLE"
DICOM_NON_ASCII_IN_ASCII_VR
readonlyDICOM_NON_ASCII_IN_ASCII_VR:"DICOM_NON_ASCII_IN_ASCII_VR"="DICOM_NON_ASCII_IN_ASCII_VR"
DICOM_NONZERO_RESERVED_BYTES
readonlyDICOM_NONZERO_RESERVED_BYTES:"DICOM_NONZERO_RESERVED_BYTES"="DICOM_NONZERO_RESERVED_BYTES"
DICOM_ODD_LENGTH_VALUE_PADDED
readonlyDICOM_ODD_LENGTH_VALUE_PADDED:"DICOM_ODD_LENGTH_VALUE_PADDED"="DICOM_ODD_LENGTH_VALUE_PADDED"
DICOM_PIXEL_DATA_LENGTH_MISMATCH
readonlyDICOM_PIXEL_DATA_LENGTH_MISMATCH:"DICOM_PIXEL_DATA_LENGTH_MISMATCH"="DICOM_PIXEL_DATA_LENGTH_MISMATCH"
DICOM_PRIVATE_CREATOR_UNKNOWN
readonlyDICOM_PRIVATE_CREATOR_UNKNOWN:"DICOM_PRIVATE_CREATOR_UNKNOWN"="DICOM_PRIVATE_CREATOR_UNKNOWN"
DICOM_PRIVATE_TAG_NO_CREATOR
readonlyDICOM_PRIVATE_TAG_NO_CREATOR:"DICOM_PRIVATE_TAG_NO_CREATOR"="DICOM_PRIVATE_TAG_NO_CREATOR"
DICOM_SQ_NOT_DESCENDED
readonlyDICOM_SQ_NOT_DESCENDED:"DICOM_SQ_NOT_DESCENDED"="DICOM_SQ_NOT_DESCENDED"
DICOM_TRAILING_NULL_IN_TEXT_VR
readonlyDICOM_TRAILING_NULL_IN_TEXT_VR:"DICOM_TRAILING_NULL_IN_TEXT_VR"="DICOM_TRAILING_NULL_IN_TEXT_VR"
DICOM_UI_TRAILING_SPACE
readonlyDICOM_UI_TRAILING_SPACE:"DICOM_UI_TRAILING_SPACE"="DICOM_UI_TRAILING_SPACE"
DICOM_UN_PARSED_AS_SQ
readonlyDICOM_UN_PARSED_AS_SQ:"DICOM_UN_PARSED_AS_SQ"="DICOM_UN_PARSED_AS_SQ"
DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR
readonlyDICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR:"DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR"="DICOM_UNDEFINED_LENGTH_IN_EXPLICIT_VR"
DICOM_UNSUPPORTED_CHARSET
readonlyDICOM_UNSUPPORTED_CHARSET:"DICOM_UNSUPPORTED_CHARSET"="DICOM_UNSUPPORTED_CHARSET"
DICOM_VR_MISMATCH
readonlyDICOM_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
Returns
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
Returns
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
options?
DeidentifyOptions = {}
Returns
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
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- emptyBuffer | Uint8Array | ArrayBuffer.NOT_DICOM_PART_10- input lacks bothDICMmagic 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
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- emptyBuffer | Uint8Array | ArrayBuffer.NOT_DICOM_PART_10- input lacks bothDICMmagic 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
Returns
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
Example
import { parsePersonName } from "@cosyte/dicom";
const pn = parsePersonName("Doe^Jane^^Dr^");
pn.alphabetic.familyName; // "Doe"
pn.alphabetic.namePrefix; // "Dr"
parseSpecificCharacterSet()
parseSpecificCharacterSet(
bytes): readonlystring[]
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
Returns
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
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.