Known limitations: the "do not over-trust" list
Read this page before you put @cosyte/dicom in front of real data.
Mis-reading a patient identifier, an image's signedness, or a rescale slope can cause real clinical harm, and a de-identifier that reports a clean run it did not perform is worse than one that refuses. This package is engineered to prevent over-trust, so the boundary is a deliverable, not a footnote: it is a page of its own, held to the same bar as the rest of these docs, and the introduction and the cookbook link into it from every route that depends on it.
This page is an index, not a census. Each entry is one line plus a link to the section that owns it, and the owning section is where the measurements, the fixtures and the residuals live. Read the section before concluding a class is closed. Nothing here is a count: counts in this repository have been corrected twice and then deleted.
1. Scope: what v1 does not do at all
These are non-goals, not gaps. Each is a companion package or another tool's job.
| Not in v1 | Where it goes |
|---|---|
| Pixel decode or decompression in any transfer syntax, frame assembly from fragments, offset-table interpretation, rendering, windowing, or any measurement computed from pixels | @cosyte/dicom-pixel |
Repairing a fragment stream on write. serializeDicom writes each PS3.5 2026c section A.4 syntax and refuses a malformed top-level Pixel Data with INVALID_ENCAPSULATED_PIXEL_DATA | out of scope |
| Burned-in annotation removal / Clean Pixel Data. A de-identified output from this package is metadata-de-identified only, and it warns rather than claiming otherwise | @cosyte/dicom-pixel |
| Networking. No DIMSE: no C-STORE, C-FIND, C-MOVE, MWL or MPPS | @cosyte/dicom-net |
| Web services. No DICOMweb: no QIDO, WADO or STOW | @cosyte/dicomweb |
| Transcoding. The serializer re-emits in the dataset's source transfer syntax only | out of scope |
Terminology resolution. Coded values are surfaced with their designator and canonical source but are never validated, looked up, or cross-mapped; SRT is not normalized to SCT | @cosyte/terminology |
| Typed SR and RT models. SR content trees and RT objects are navigable as raw structure, with no clinical model over them. Deliberately flagged and deferred rather than half-built | out of scope for v1 |
| Patient matching. Patient ID is surfaced with its issuer; the library never decides that two identifiers are the same person | out of scope |
| A File-set view. A DICOMDIR's Directory Record tree is read and its offsets rewritten on write (below), but no DICOMDIR is built from files and nothing is removed from a File-set | out of scope for v1 |
Supported transfer syntaxes: the four native ones, Implicit VR LE 1.2.840.10008.1.2, Explicit VR
LE ...1.2.1, Deflated Explicit VR LE ...1.2.1.99 and Explicit VR BE ...1.2.2 (retired,
legacy-only), plus every encapsulation syntax PS3.5 2026c section A.4 names: JPEG Baseline,
Extended, Lossless and Lossless First-Order Prediction; RLE Lossless; JPEG-LS Lossless and
Near-Lossless; JPEG 2000 and HTJ2K; MPEG2; MPEG-4 AVC/H.264; HEVC/H.265 Main and Main 10; JPEG XL;
Deflated Image Frame Compression; and Encapsulated Uncompressed Explicit VR LE. Dictionary.uid()
names each UID. An encapsulated object is read under Explicit VR LE rules, as section A.4 requires,
so its metadata parses in full and readPixelDataFragments hands back its Basic Offset Table and
fragments as raw bytes. Its pixels are never decoded. serializeDicom writes each of those
syntaxes back under the object's own UID, a de-identified object included: the Data Set as Explicit
VR LE, and the top-level Pixel Data as OB of undefined length, its Basic Offset Table and fragment
Items copied byte for byte and in order, then a Sequence Delimitation Item. Nothing is transcoded,
re-framed or re-encoded, and no offset table is rebuilt. The writer's limit sits with it: a
top-level Data Set section A.4 does not allow is refused with INVALID_ENCAPSULATED_PIXEL_DATA,
never repaired (see below). The four JPIP Referenced syntaxes are read too, for metadata only (next
paragraph). Every other registered UID stays the fatal UNSUPPORTED_TRANSFER_SYNTAX, named by its
PS3.6 registry name: the SMPTE ST 2110 syntaxes and every retired UID, the retired JPEG processes
included; serializeDicom refuses the same UIDs with its own UNSUPPORTED_TRANSFER_SYNTAX.
Deflated Explicit VR LE deflates the whole dataset stream rather than the pixels, and it is inflated
on parse.
The four JPIP Referenced syntaxes are read for metadata, and only read. JPIP Referenced
...1.2.4.94 and JPIP HTJ2K Referenced ...1.2.4.204 (PS3.5 2026c sections A.6 and A.11) are read
under Explicit VR LE rules, as those sections require. JPIP Referenced Deflate ...1.2.4.95 and JPIP
HTJ2K Referenced Deflate ...1.2.4.205 (sections A.7 and A.12) are that same Data Set compressed per
RFC 1951, so they are inflated first, under the decompression cap and the inflate fatals Deflated
Explicit VR LE has. Such an object carries no Pixel Data: it references its pixels through Pixel
Data Provider URL (0028,7FE0), which ds.get("00287FE0") returns as the UR element the file
carries, its decoded value the URL text with only its trailing padding removed. The URL is never
fetched, resolved or validated, and nothing in this package opens a connection to the host it
names. The limits sit with that capability:
serializeDicomrefuses all four withUNSUPPORTED_TRANSFER_SYNTAXand returns no bytes. A JPIP object is read, never written.deidentify()refuses all four: it throws aDeidentifyErrorcarrying theUNSUPPORTED_TRANSFER_SYNTAXcode, whatever options you pass, and returns no dataset and no report.(0028,7FE0)has no PS3.15 Table E.1-1 row, so a de-identified copy would keep the URL by omission, and whether that URL can carry identity is an open question this package has not ruled on. The refusal keys on the File Meta Transfer Syntax alone: a non-JPIP object that carries(0028,7FE0)is de-identified like any other and keeps it (see De-identification).- Three misuses section A.6 forbids parse without a code, as the section A.4 ones above do: an
object with no
(0028,7FE0)(thends.get("00287FE0")isundefined, never an empty string or a placeholder), one that carries a top-level Pixel Data anyway, and one whose Photometric Interpretation is outside the four Values section A.6 allows. No IOD or module validation is done.
Three limits sit with that capability. A fragment stream that ends before its Sequence Delimitation
Item still parses, with DICOM_PIXEL_DATA_FRAGMENTS_NOT_DELIMITED saying the fragment list may be
short. Two misuses of an encapsulation syntax that PS3.5 2026c section A.4 forbids parse
without a code: an object with no top-level Pixel Data, and one whose top-level Pixel Data is
native, for which readPixelDataFragments returns undefined, as it does under Explicit VR LE.
And the writer is stricter than the reader: serializeDicom throws a DicomSerializeError
with code INVALID_ENCAPSULATED_PIXEL_DATA, and returns no bytes, for every top-level Data Set
under a section A.4 syntax that the section does not allow, rather than repairing it. That is no
top-level Pixel Data, native top-level Pixel Data, Float or Double Float Pixel Data
(7FE0,0008) / (7FE0,0009), a fragment stream not ended by its Sequence Delimitation Item (so an
object read with DICOM_PIXEL_DATA_FRAGMENTS_NOT_DELIMITED is read and not written, since a
delimiter appended to a stream that may be short would be a well-formed file missing frames),
anything other than an Item of defined length in the stream, an Item of odd length, a fragment
Item of length zero after the Basic Offset Table (an empty Basic Offset Table is fine), and a stream
with no Basic Offset Table Item or no fragment Item after it. Its message
is a fixed string. Pixel Data inside a Sequence Item, such as an Icon Image Sequence, is written as
read and not checked.
DICOMDIR: the record tree is read, and its offsets are rewritten on write. For an object whose
(0002,0002) is 1.2.840.10008.1.3.10 (Media Storage Directory Storage), ds.directory is a
DicomDirectory: every Item of the Directory Record Sequence (0004,1220) as a DirectoryRecord
with its (0004,1430) type, its Referenced File ID (0004,1500) components and the records its
(0004,1420) offset names as lower-level, and the root entity (0004,1200) names. An offset
resolves only when it is exactly where a record's (FFFE,E000) Item tag sits in the file, counted
from the first byte of the File Preamble, which is how PS3.3's Basic Directory IOD defines these
offsets (PS3.3 is not vendored here, so no clause is claimed for it); one that is not, a Value Length
other than 4, and a record reached twice each raise a warning
(DICOM_DIRECTORY_OFFSET_UNRESOLVED, DICOM_DIRECTORY_OFFSET_MALFORMED,
DICOM_DIRECTORY_RECORD_REVISITED) and resolve nothing. serializeDicom writes (0004,1200),
(0004,1202), (0004,1400) and (0004,1420) as the byte offsets of the records they named when
the file was read, including after deidentify() has moved every record. The limits sit with it:
- Referenced File IDs are kept verbatim: never de-identified, rewritten or re-pointed, by the
reader,
deidentify()or the writer. A path that carries identifying text carries it into the output. - The two File-set clauses of PS3.15 §E.1.1 are not discharged. No DICOMDIR is created from the
de-identified files, and the non-de-identified DICOMDIR is not removed from its File-set;
deidentify()raisesDICOM_DEIDENT_DICOMDIR_FILE_SET_NOT_DISCHARGEDon every DICOMDIR run. - A DICOMDIR carrying an offset that names no record is refused on write, with
DicomSerializeErrorcodeDIRECTORY_OFFSET_UNRESOLVEDand no bytes returned: an offset that did not resolve on read, a value that is not one 32-bit unsigned integer, and a non-zero offset on aDatasetwhose Directory Record Sequence was removed or emptied, or was never there. It is never written stale (it would name the wrong bytes) and never written as zero (that prunes the tree). - A Deflated DICOMDIR with records is refused on write, with
DIRECTORY_OFFSET_DEFLATED: a position inside a deflated stream names no Item a reader can seek to, so any non-zero offset is refused, andparseDicomresolves no offset of such a file and warnsDICOM_DIRECTORY_OFFSET_DEFLATED, because PS3.10 requires a DICOMDIR File to use Explicit VR Little Endian. One whose offsets are all zero is written. - Not checked: record keys against PS3.3's Directory Record definitions,
(0004,1202)against the end of the root chain, the File-set Consistency Flag, and Private Record UIDs. The retired MRDR offset(0004,1504)is written as read and not rewritten. An Implicit VR LE or Explicit VR BE DICOMDIR is read and written without a warning, although PS3.10 makes Explicit VR LE the only conformant syntax for a DICOMDIR File.
2. Open PHI residuals: measured, disclosed, and NOT closed
None of these is an all-clear, and a DeidentifyReport that reads clean does not by itself close
any of them. Each is either a product decision this package has deliberately not made, or a
structural fact about DICOM that no reader can resolve from the wire.
-
RetainSafePrivatenow keeps only what this run could account for, and everything else private is REMOVED. Two routes in the package write a private value into de-identified output: aProfileyou pass, and the file's own Private Data Element Characteristics Sequence(0008,0300), which needs no profile. Four classes of value take them: one the run walked as Data Elements and put through the Annex E action table (a privateSQwhose items the parser materialized), a Private Creator(gggg,00EE)whose whole decoded value is a member of your profile's private dictionary, a zero-length value, which encodes no Data Set, and one the file itself declares non-identifying - a block whose Block Identifying Information Status(0008,0303)readsSAFE, or an element aMIXEDblock lists in Nonidentifying Private Elements(0008,0304). 🩺 The fourth class is the sender's word and not this run's finding, and that is the limitation rather than a feature note. The value is retained unexamined because the system that wrote the file asserted the block is safe; an Item that does not resolve keeps nothing and says so underDICOM_DEIDENT_PRIVATE_DECLARATION_NOT_RESOLVED, but a well-formed declaration is taken at its word. PS3.15 §E.3.10 offers exactly one mitigation and it is yours: if you do not trust the sender, do not passRetainSafePrivate, and every private attribute is removed. Anything else a profile vouches for is removed, recorded per instance inreport.unenumerablePrivateRemovalswithapplied: "removed"andreason: "unenumerable", named inreport.removedPrivateTags, and warned underDICOM_DEIDENT_PRIVATE_CARRIER_NOT_AUDITABLE. The removal record is complete and never capped at any input size; only the warnings are bounded. 🛑 The cost is over-redaction and it is nearly all of the profile route: an ordinary vendor scalar under an ordinary string VR that the file's own declaration does not name safe is removed. Decoding a value under the VR your profile declares for it is not an enumeration of what the value encodes, and neither is the embedded-attribute scanner's silence: that scanner reads string carriers only and decodes tiles in the file's own encoding, so a nested Data Set written in another transfer syntax passed it untouched on a perfectly scannableLOcarrier - measured, and the reason the predicate is what the run DID with the value rather than the VR or the scanner's reach. If you need those values back, that needs a content test separating a nested Data Set from a legitimate binary blob, which is an open product question and not a flag. Earlier releases kept this class verbatim under(0012,0062) = YESand merely disclosed it, which was a disclosure and not a fix. The surface it shipped through is pinned as a measured matrix intest/integration/deident-private-reservation.test.tsrather than described in prose, because the prose has been wrong twice. Detail: Known limitations in the README. -
Two audit-contract changes came with that, and a consumer switching on either has to act.
report.unauditableSequencesno longer produces its retiredkeptoutcome for a retained private value - itsappliedfield was"emptied" | "kept"and is"emptied"alone now, so an entry there means content is not in your output, which is what it meant before that second outcome joined it - andDICOM_DEIDENT_PRIVATE_CARRIER_NOT_AUDITABLEchanges meaning from "this value was shipped unexamined" to "this attribute was removed unexamined". No published warning code is renamed or retired, so narrowing on that code name keeps compiling: re-read what it now means rather than re-typing it.DICOM_DEIDENT_PRIVATE_DECLARATION_NOT_RESOLVEDis added, raised per Item of(0008,0300)that does not resolve to a block of private Data Elements reserved in that Data Set, so a declaration you expected to keep something and that kept nothing is never silent. The removal record (report.unenumerablePrivateRemovals) is a new report surface, and it is the only one that can tell you an attribute went for being unenumerable rather than by the action table. -
An over-declared Value Length into a binary carrier still leaks. A length that swallows the following element into an
OB,OW,USorUNvalue is not detected, and a(0010,0020)Patient ID inside it reaches de-identified output with no warning and no report entry. That is measured and still exactly true for this route, which is every defaultdeidentify()run. The only carrier that gets a diagnostic is one that is itself a private attribute reached throughRetainSafePrivateplus aProfile(the bullet above). A carrier reached through the file's own(0008,0300)declaration gets none: the declaration is what makes that value known safe, so the run keeps it rather than judging it, which is the sender-trust cost stated in that bullet. What the profile-route diagnostic says is "this value was not enumerated, so the attribute was removed" or "this value was dropped" (emptied, where the profile declares itSQ), never "a swallow was detected here". Arbitrary bytes are exactly what those VRs are for, so no content test can decide it. String carriers are covered, because there the same bytes are provably outside the VR's repertoire. If you accept files from a sender you do not control, treat a binary attribute's declared length as untrusted. -
(0012,0063)De-identification Method can carry a value longer than the VR permits, and this library is the likeliest writer of it. PS3.5 2026c Table 6.2-1 caps anLOat 64 characters per Value, and(0012,0063)is1-n. Every object this package de-identified without a caller-supplied method string, in every release up to and including this one, carried a single Value longer than that, in the one attribute a strict receiver reads to decide whether the object was de-identified at all. The method text is multi-valued now, one Value for the Profile and one per active Option. Re-de-identifying an object written by an earlier release keeps the over-long Value, because a prior record is added to rather than rewritten, which is the conformant act (PS3.15 2026c §E.1.1: the method text is "inserted in or added to" the attribute). Your owndeidentificationMethodis not bounded for you either: split it on\yourself if a strict receiver is in your path. -
contextPathon aDeidentifyReportfinding is inert and is not structural. It is not a key you can look anything up with, it is corroborated by nothing else in the report, and each segment's tag half is read off the wire with nothing behind it, so a fabricatedSQheader the reader was desynchronized onto is named there. It is not safe to log. -
What a warning message may contain, as a mechanism rather than as a verdict. The verdict form of this bullet ("safe on a well-formed file, not unconditionally safe") was corrected twice, so it is deleted rather than given a third wording. The registry's
{tag}slot renders only a tag PS3.6's element registry carries a literal row for, and<withheld>otherwise;{vr}renders only one of the 34 VRs PS3.5 2026c §6.2 defines; and a raw number a header carries is bound out of the factory signature where it is bound at all, because a declared Value Length has neither a shape nor a membership a renderer could test.w.codeandw.positioncarry nothing from the document. And a raw number SHIFTED by a constant the reader can compute is that raw number, which is the eighth instance of this class:DICOM_ITEM_CROSSES_SEQUENCE_ENDprinted the bytes that remained inside the sequence, and that count is the sequence's own 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 - theWARNING_MESSAGESdocblock insrc/parser/warnings.ts. No count of the copies is quoted, here or there: this package deletes a count it has corrected twice rather than incrementing it. What that closed, measured on anSTcarrying"MR BRAIN SMITHSON "whose Value Length under-declares: under Explicit VR LE the reader desynchronizes onto a fabricated header whose declared length is odd, and in an earlier releaseDICOM_ODD_LENGTH_VALUE_PADDEDrendered four bytes of the name as the tag and four more as the decimal length - eight payload bytes in one message.DICOM_NONZERO_RESERVED_BYTESprinted two more as decimals on six other deltas. Both are closed. What it costs, stated rather than minimised: a message about a private element, a Group Length(gggg,0000)or a repeating-group member such as(6000,3000)Overlay Data no longer names its tag on any file, well-formed or not. The element is still in the Data Set under that tag, andposition.byteOffsetlocates the header. The channel still matters as much as the field. The fixtures that produce a desynchronized read mostly die before aDatasetexists, so what carries their messages isonWarningand the{ strict: true }DicomParseErrorrather than a survivingds.warnings- a fact about those fixtures and not a promise about the parser. Treat both channels the same way. Full treatment: Keeping PHI out of logs. -
Two carriers this list once named as open are now bound, and neither bound is an all-clear.
DICOM_PRIVATE_TAG_NO_CREATOR,DICOM_IMPLICIT_VR_FOR_PRIVATE_TAG_WITHOUT_VRandDICOM_PRIVATE_CREATOR_UNKNOWNtake no tag at all any more, because an odd group is the one class of tag no closed table this library holds can vouch for; the element is still in the object andposition.byteOffsetstill locates it.report.embeddedAttributes[].hiddennow lists only tags this run acted on that have a literal row in PS3.15 Table E.1-1 - 653 of them - rather than any four bytes a run tiled over. A repeating-group mask hit is excluded and that exclusion is the whole bound:(50xx,xxxx)Curve Data leaves the entire 16-bit element number free, so a mask match proves a rule exists without making the membership finite, and a draft that stopped at "an even group" was measured admitting500C5241. Two consequences:hiddencan be empty on a real finding, which does not mean nothing was hidden, and it is still uncapped.report.removedPrivateTagsis deliberately unchanged: narrowing it would empty the field on every well-formed file, which is what it exists to record. -
A standard attribute from a PS3.6 edition newer than this build is REMOVED, conformant or not.
deidentify()removes a non-private attribute that neither this build's PS3.6 2026d registry nor Table E.1-1 carries, because nothing on the wire separates a later edition's attribute from one a sender invented. The cost is content on a conformant newer-edition object, with no switch to keep it in this release; the removal is recorded by byte offset and never by tag. It narrows the older gap and does not close it: an attribute PS3.6 registers and the pinned Table E.1-1 does not list is still kept as the source wrote it. Detail: Attributes neither table carries. -
A de-identified DICOMDIR is NOT File-set conformant, and the run says so rather than implying otherwise. PS3.15 §E.1.1's group-0004 bullet removes
(0004,xxxx)from everything that is not a DICOMDIR, and this package does that; for an object whose(0002,0002)is1.2.840.10008.1.3.10it honours the carve-out and keeps them. Its Directory Records are de-identified as Data Sets, like any other Sequence Item, so a PATIENT record's Patient's Name and Patient ID are handled by Table E.1-1, andserializeDicomrewrites each record offset to where its record lands. The two File-set clauses are not discharged: no DICOMDIR is created from the de-identified files it references, and no non-de-identified DICOMDIR is removed from the File-set, because this library has no view of a File-set. Every such run raisesDICOM_DEIDENT_DICOMDIR_FILE_SET_NOT_DISCHARGED, including one where the object carried no(0004,xxxx)element at all, because what was not discharged has nothing to do with what the object happened to carry. Referenced File IDs(0004,1500)are kept verbatim, so a File-set whose paths carry identifying text keeps it; the record keys are only what Table E.1-1 names. Build a de-identified File-set from the de-identified files, not from this output. -
(0028,0303) = MODIFIEDis written, and this library performs no date transformation.deidentify()writes(0028,0303) Longitudinal Temporal Information Modifiedon every run, in the three states PS3.15 2026c defines:REMOVEDwhen no Retain Longitudinal Temporal Information Option was active,UNMODIFIEDwhenRetainLongitudinalTemporalwas, andMODIFIEDwhenRetainLongitudinalTemporalModifiedDateswas. §E.3.6 defines those last two as mutually exclusive Options, so a call naming both is rejected with aDeidentifyError.The limitation is what
MODIFIEDdoes not mean here. §E.3.6 has two halves: the object's dates "shall be modified", and "the manner of date modification shall be described in the Conformance Statement". This library delivers the half a library can: it resolves Table E.1-1's modified-dates column, which is the more protective of the two temporal columns wherever they differ, and it writes the declaration. It performs no date transformation at all, on either branch, and it states no Conformance Statement, because PS3.2 Annex N scopes one to a named product and version and a library is neither. So you perform the date transformation, and aMODIFIEDon an object whose dates nobody shifted is a caller defect this package cannot detect. It is never silent about it: every run under that Option carriesDICOM_DEIDENT_DATES_NOT_TRANSFORMEDonreport.warnings, naming the half of §E.3.6 the run did not discharge. Nothing here claims PS3.15 Annex E conformance for the Retain Longitudinal Temporal Information With Modified Dates Option, which §E.1.1 makes all-or-nothing.If you shift dates yourself after the call under
RetainLongitudinalTemporal, theUNMODIFIEDin your output is wrong for your object: useRetainLongitudinalTemporalModifiedDatesinstead or overwrite the attribute, and describe the manner of modification in your Conformance Statement, which §E.3.6 requires of anyone claiming that Option. The declaration is the top-level Data Set's own, which is where §E.2 and §E.3.6 put it:(0028,0303)has no row in Table E.1-1, so a copy the sender nested inside a Sequence Item is retained by omission like every other registered attribute the table does not list, and still says whatever that sender wrote. Read the Data Set's own(0028,0303), never a nested one. -
A
DeidentifyReportis not safe to log whole. The value-bearing fields are named on theDeidentifyReporttype. Read the list on the type, never a count quoted anywhere (including here): the count read one, then two, then three, and was wrong each time.(0028,0303)is not on it either: like(0012,0062), it is a statement the object makes about itself, and the report's shape is unchanged by it. -
A
DicomParseErroris not safe to log whole. It carriessnippet, up to 16 raw source bytes as hex, and the library does not redact them. Logerr.code,err.byteOffset,err.offsetFrameanderr.message.{ strict: true }turns every Tier-2 warning into one of these, so a PHI review of the lenient path does not transfer to the strict one.
3. Structural facts that no reader can resolve
These are not defects and they will not be fixed, because the information needed to fix them is not on the wire.
- An over-declaring element and a well-formed one are byte-identical. Intent is not encoded. Any remedy therefore lives at the de-identify boundary or is a warning, never a parser bound.
- A Data Set is a
Map<Tag, Element>, so a repeated tag loses a value. The last read wins and nothing is guessed for the one it replaced;DICOM_DUPLICATE_TAG_IN_DATA_SETreports the loss. The File Meta group loses a repeat the opposite way round, first-match wins, reported byDICOM_DUPLICATE_FILE_META_ELEMENT. Neither can fire on a conformant file. - The writer orders what it can walk, and only that. A tag repeated inside an Item is written
twice, a Sequence that cannot be walked, is nested past
NESTING_DEPTH_LIMITor whose parseditemsdo not match its bytes and anyUN-carried Sequence are written as read, an element whose own bytes do not show where a reader ends it is written after the ascending rest of its Data Set whatever its tag, and an element a lying length relocated stays where it was placed. See Serialization. Element.byteOffsetinside a sequence item disagrees with itself and always has:0inside a defined-length item (its own frame), file-absolute inside an undefined-length one. The same is true of a warning'sposition.byteOffset. There is no frame-of-reference contract on either, and neither is covered byDicomParseError.offsetFrame- that names the frame for a thrown fatal and for the{ strict: true }escalation of a warning, and for nothing on the lenient path. Measure it rather than assuming one.- A failed CP-246
UNdescent emits nothing. The honest test for a consumer isel.items === undefined, notds.warnings. - The byte-for-byte File Meta round trip is scoped to parse-then-serialize, and
deidentify()is outside it on purpose. A de-identified File Meta group describes this de-identifying application (PS3.15 §E.1.1), so every non-modeled(0002,xxxx)element the source carried is dropped and the Source AE Title and the source's implementation identity go with it. This is a deliberate fidelity loss, recorded onreport.fileMetaElementsDroppedand counted onreport.fileMetaElementsDroppedCount, and it is not recoverable from the output. If you need the source group verbatim, read it off the parsed dataset before the call. NESTING_DEPTH_LIMITis this library's bound, not the standard's. A fully conformant file nested deeper than it is refused, and a sequence refused that way is emptied bydeidentify()rather than shipped, so expect data loss on such a file.
4. What is deliberately never defaulted
The dangerous DICOM failure is the confident, wrong image, so the typed views answer "absent" rather
than substituting a plausible value. undefined means the object did not carry the attribute.
image.rescaleSlope/image.rescaleIntercept:undefinedmust not be read as 1 and 0.image.signed:undefinedwhen(0028,0103)Pixel Representation was absent, neverfalse.image.photometricInterpretation: never defaulted toMONOCHROME2.image.pixelSpacingandimage.imagerPixelSpacing: different measurements, never substituted for each other.patient.id: a string the sending system chose. Not globally unique, and never matched for you.- A malformed
DSorIStoken decodes tonull, never toNaNcoerced to0.
5. Where the detail lives
| Topic | Page |
|---|---|
| The error model and symptom-by-symptom triage | Troubleshooting |
| What a diagnostic carries and what it does not | Tolerance |
| Keeping PHI out of logs | Keeping PHI out of logs |
| The safety-critical views and their fail-safe rules | Safety |
| Writing spec-clean bytes back out, and what the writer will not do | Serialization |
| The de-identification surface and the scope limits on it | De-identification |
| Working recipes, each citing the PS3 clause it reads | Cookbook |
Everything on this page is a documented boundary rather than a bug. Where a limitation applies, the raw bytes are preserved (usually with a warning); they are simply not interpreted further.