Skip to content

fix(DicomMessage): parse UN-encoded sequences using Implicit VR LE per PS3.5 §6.2.2#492

Open
jimOnAir wants to merge 1 commit into
dcmjs-org:masterfrom
MDWio:fix/un-sq-implicit-vr-ps3-5-6-2-2
Open

fix(DicomMessage): parse UN-encoded sequences using Implicit VR LE per PS3.5 §6.2.2#492
jimOnAir wants to merge 1 commit into
dcmjs-org:masterfrom
MDWio:fix/un-sq-implicit-vr-ps3-5-6-2-2

Conversation

@jimOnAir

Copy link
Copy Markdown

Problem

When a DICOM file encodes a sequence element with VR=UN and a defined length (valid per DICOM PS3.5 §6.2.2), dcmjs fails to parse it correctly.

Root cause

DICOM PS3.5 §6.2.2 requires that when a sequence is stored with VR=UN and a defined length, its items must be parsed using Implicit VR Little Endian, regardless of the file's overall transfer syntax.

dcmjs did not implement this rule. It attempted to read the item bytes as Explicit VR, encountered 28 00 as the VR string (the character ( + NUL), emitted Invalid vr type ( - using UN, and stored the value as [{}].

Consequence

On write-back, the corrupt [{}] payload caused a ~843 MB length field to be emitted for the element (e.g. files containing UDISequence (0018,100A)), inflating an 788 KB file to 843 MB.

Fix

In DicomMessage._readTag():

  1. Record the wire VR (wireVrType) immediately after stream.readVR(), before any dictionary override changes vrType.
  2. After reading the element length, if wireVrType === "UN" and the payload starts with FE FF (the little-endian group bytes of the DICOM item-start tag 0xFFFEE000), promote the element to SQ and switch the recursive read to IMPLICIT_LITTLE_ENDIAN.

The wireVrType approach correctly handles both:

  • Unknown tags — where vr.type stays "UN"
  • Known tags like UDISequence (0018,100A) — where the existing dictionary-override already promotes vrType to "SQ" before our check runs

Test

Adds a regression test that constructs a hand-crafted Explicit VR Little Endian buffer encoding UDISequence (0018,100A) as VR=UN with a defined length, containing one Implicit VR item with UniqueDeviceIdentifier (0018,1009). Verifies:

  • The element is promoted to SQ
  • The inner item is a non-empty array (not [{}])
  • The UDI string value is correctly recovered

…r PS3.5 §6.2.2

When an Explicit VR file encodes a sequence element with VR=UN and a
defined length, DICOM PS3.5 §6.2.2 requires that its items be parsed
using Implicit VR Little Endian regardless of the file's transfer syntax.

Without this fix, dcmjs tried to interpret the item bytes as Explicit VR,
emitted 'Invalid vr type ( - using UN', stored the value as [{}], and then
on write inflated the element length to ~843 MB (the 843 MB inflation bug
reported for files containing UDISequence (0018,100A)).

The fix records the literal wire VR before any dictionary override
(wireVrType), then after reading the length checks whether the payload
starts with the FFFE item-delimiter group bytes (FE FF in LE). If so,
the element is re-typed as SQ and the recursive item read is forced to
IMPLICIT_LITTLE_ENDIAN.

Adds a regression test that builds a hand-crafted Explicit VR buffer with
UDISequence (0018,100A) encoded as UN and verifies the inner
UniqueDeviceIdentifier (0018,1009) value is correctly recovered.
@netlify

netlify Bot commented Apr 23, 2026

Copy link
Copy Markdown

Deploy Preview for dcmjs2 ready!

Name Link
🔨 Latest commit 42de140
🔍 Latest deploy log https://app.netlify.com/projects/dcmjs2/deploys/69e9fd7dc4afc40008ed5779
😎 Deploy Preview https://deploy-preview-492--dcmjs2.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@pieper pieper requested a review from wayfarer3130 April 23, 2026 14:04
@pieper

pieper commented Apr 23, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the nice PR, thanks. I was totally unaware of this particular scenario.

I assume this arose in practice with a real datafile? I'm guessing you can't share it, but maybe you can describe the equipment / vendor that it came from?

@jimOnAir

Copy link
Copy Markdown
Author

Hi.
Yes. Main issue was on saving dataset. File size was increased from 700KB to 800MB because of UDISequence was parsed as Array with empty object and then written to file zero padded 843,197,226 bytes value.

Here is data from the image:

(0008,0070) | LO | Manufacturer | TERARECON
(0008,1090) | LO | ManufacturerModelName | AQNET

@jimOnAir

jimOnAir commented May 7, 2026

Copy link
Copy Markdown
Author

@wayfarer3130 When you have time, could you please review this PR?

@wayfarer3130 wayfarer3130 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some additional tests on this, plus the fix for the AsyncDicomReader side of things. Could you give me write access as a maintainer to push to your PR so that I can incorporate the other changes? Then I can go ahead and approve it after pushing the additional fixes.

@wayfarer3130

Copy link
Copy Markdown
Contributor

I added some additional changes in #496 to fix it more broadly and have a bit mroe comprehensive tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants