# DICOM Desk > Take the DICOM series you just pulled off a scanner or out of a research archive and get it to the > point where it can safely leave the hospital and be reconstructed into a training volume, in one > sitting. A real DICOM Part 10 reader runs free in the browser; four AI lanes then run over the same > header. Live at: https://dicom-desk.skillsafe.ai/ API tutorial: https://dicom-desk.skillsafe.ai/api.html Token management: https://dicom-desk.skillsafe.ai/tokens.html (noindex) ## What it is for One work object: **one DICOM series**, supplied either as the `.dcm` files themselves (dropped into the browser and parsed there) or as a header dump - `dcmdump`, `gdcmdump`, or a pydicom `print(ds)`. The question the app answers is the one that sits between "the scanner produced this" and "the model trained on this": is this actually a volume, and is it allowed to leave? ## The free part - no account, no model, no network call A DICOM Part 10 reader written for this app, in plain JavaScript, running in the user's own tab: - 128-byte preamble and the `DICM` magic at byte 128; a file with neither is recognised as a raw dataset rather than rejected, because research pipelines and some archives emit those constantly. - The file meta group (group 0002) is read as **Explicit VR Little Endian regardless** of what `(0002,0010) TransferSyntaxUID` then declares the dataset to be. A reader that applies the dataset's transfer syntax to the meta group reads garbage on every implicit-VR file. - The dataset in Implicit VR Little Endian (VR from the dictionary, 32-bit lengths), Explicit VR Little Endian, or Explicit VR Big Endian. The 32-bit-length VRs (`OB OW OF OD OL OV SQ UT UN UC UR`) get their two reserved bytes; reading that length as 16-bit desynchronises the rest of the file. - Undefined length (`0xFFFFFFFF`) runs to its delimiter: a sequence to `(FFFE,E0DD)`, an item to `(FFFE,E00D)`. - Sequences are entered to a bounded depth, so a `PersonName` nested inside a content sequence is found. A de-identifier that only walks the top level misses exactly those. - Deflated Explicit VR Little Endian is reported as unreadable - meta group read, dataset not read - rather than silently returned empty. - **Pixel data is never decoded.** The walk stops at `(7FE0,0010)` and records only its length, which is why a 300-slice series is tolerable in a browser tab and why no pixel bytes can reach a prompt. Then the arithmetic that a header field cannot give you: - The slice normal is the cross product of the two direction cosines in `(0020,0037)`. - Each slice's position along the stack is the dot product of `(0020,0032)` with that normal. - Slice spacing is the difference between consecutive projections. It is **not** `SliceThickness` and **not** `SpacingBetweenSlices`, both of which are claims about the acquisition that the positions can and do contradict. Where the steps read 1.25, 1.25, 2.50, 1.25 there is a slice missing, whatever the header says. - Gaps (with an estimate of how many slices are missing), duplicate positions, orientation drift in degrees, `InstanceNumber` order that disagrees with the geometric order, missing `InstanceNumber`s in the run, mixed `FrameOfReferenceUID`s, localizers and derived images mixed into an acquisition, multi-frame instances, lossy compression, colour data, and CT with no rescale - all found and quantified, with the required and the measured number side by side. - Instances are grouped into series by `SeriesInstanceUID` (and by `(SeriesNumber, Modality)` when a trimmed dump lost the UID), so a dump of a whole folder is reported as a mixed folder rather than as one broken volume. And the identifier audit: - Every tag on the DICOM PS3.15 Annex E Basic Application Level Confidentiality Profile list that this app reads, with the profile's action code (`X` remove, `Z` zero, `D` dummy, `U` remap the UID, `C` clean the free text, `K` keep) and the reason for it. - Direct identifiers, quasi-identifiers and UIDs are counted **separately**. UIDs are present in every DICOM file ever written and need consistent remapping rather than removal, so counting them as an exposure would make the headline flag fire on every input. Retained `PatientSex` or `PatientWeight` is a quasi-identifier and is reported at medium, not critical. - Private tags (odd groups), `BurnedInAnnotation`, `PatientIdentityRemoved` and `DeidentificationMethod` are reported as found - and **absent is not NO**: ultrasound, secondary captures and scanned film routinely carry names in the pixels with `(0028,0301)` missing entirely. - Tag **values** are shown in the browser, because nobody can judge a free-text field without seeing it, and are **withheld from the model** by construction. Free exports, no account: series manifest CSV, instance sort-order CSV (in the order a conversion script must use, so nobody has to trust `InstanceNumber`), the de-identification plan CSV with a sign-off column, the parsed header as JSON, and the whole read-out as Markdown. ## The four AI lanes One system prompt, one model (`gpt-terra`, currently resolving to `gpt-5.6-terra`), routed on an explicit `task` field. All four run over the same header, and each result offers the next lane as a button that carries the previous lane's verdict and findings forward. 1. **`preflight`** - can this series be used as it stands? Eight fixed areas, in order: object type and SOP class; transfer syntax and pixel encoding; series and study separation; slice geometry and spacing; instance completeness; intensity calibration; acquisition consistency; identifiers present. Each `pass`, `warn` or `fail`, and a `pass` has to be earned by a fact in the input - a silent header is `warn`, never `pass`. 2. **`deid`** - what has to happen before this leaves, at the stated intent. A tag-by-tag plan covering every identifying tag the browser found, every private tag and `BurnedInAnnotation`, with `remove` / `zero` / `replace` / `remap-uid` / `shift-date` / `clean-text` / `keep` per tag; every `keep` has to say what it is being kept for. Plus the residual risks and what the pixels may still carry. 3. **`volume`** - how this stack becomes a correctly oriented, correctly spaced array. What to sort on and why not `InstanceNumber`; where every geometry number comes from, tag by tag; how the 4x4 patient-space affine is built; the ordered procedure; and the pitfalls with their guards. A stack that cannot be stacked gets `blocked` or `fix-first`, not a confident plan - resampling across a gap is inventing tissue and is named as such. 4. **`catalog`** - which header fields become the columns of a research manifest. Column names, types, units and source tags; the derived fields with their formulas; the quality columns that let a later reader filter the cohort honestly; the controlled vocabularies; and the CSV header row. Every lane ends in a runnable pydicom script, and that is the artifact the "Copy the script" button copies. ## How the model is held to the browser - The prescan facts go into the run input and the model is instructed that they win over its own reading. - Every prescan flag at `critical` or `high` severity must come back with one `reconciliation` entry (`confirmed`, `noted`, `set-aside` or `superseded`). A required flag with no entry is displayed as **not accounted for** rather than quietly dropped. - Every tag, series label and instance name the model cites is checked against the pasted header. One that does not exist is shown, marked, rather than trusted. - Every DICOM tag in the returned script is checked the same way, and the count of verified and unverified tags is displayed above the code. - On `deid`, any identifying tag the browser found that the plan does not mention is listed as a hole in the plan. - On `catalog`, the `csv_header` row is compared against the declared column list position by position, and a divergence is named rather than left for the reader to diff by eye. ## Contract Input: `{task, headers_text, intent, context_note, prescan, clip_note?, prior_preflight?}`. `task` is one of `preflight`, `deid`, `volume`, `catalog`. `intent` is one of `research-volume`, `archive-transfer`, `public-release`, `unspecified`. Output: one JSON object - `{task, task_inferred, title, verdict, summary, assumptions[], open_questions[], findings[], reconciliation[], next_lane{lane,reason}, body{...}}`. `verdict` is one of `ready`, `fix-first`, `blocked`, `unreadable`. `findings[].severity` is one of `critical`, `high`, `medium`, `low`. Every lane's `body` ends with `script` and `script_note`. Full worked examples per lane, in cURL, Python, JavaScript, Go, Java, Ruby, PHP and C#: https://dicom-desk.skillsafe.ai/api.html ## What it is not DICOM Desk is **not a de-identifier**. It never rewrites a file - everything it produces is a plan and a script for the user to run and review themselves. It cannot see an identifier burned into pixel data; where that risk is unresolved it says so rather than pretending to have checked. The PS3.15 Annex E action codes it reports are a transcription of one published baseline profile: not legal advice, not a compliance certificate, and not a substitute for the study's own data-use agreement or its review board. It is not a medical device and nothing it produces is for clinical use. ## Sources DICOM Desk is a derived work inspired by four published agent skills, credited in the app footer: - **@nvidia/dicom-series-preflight** - checking a DICOM series before it is used (https://github.com/nvidia/skills) - **@nvidia/dicom-metadata-extract** - extracting a series' metadata (https://github.com/nvidia/skills) - **@nvidia/dicom-series-to-volume** - turning a series into a volume (https://github.com/nvidia/skills) - **@k-dense-ai/pydicom** - reading, editing and de-identifying DICOM files with pydicom The in-browser DICOM Part 10 reader, the geometry mathematics, the PS3.15 Annex E tag table, the flags and the exports are an independent implementation written for this app. DICOM Desk is not affiliated with, endorsed by, or maintained by any of those authors, by NVIDIA, by the pydicom project, or by DICOM's standards body. DICOM is a registered trademark of the National Electrical Manufacturers Association. Built on SkillSafe (https://skillsafe.ai/).