Skip to content

Commit 6357e3e

Browse files
Parse undefined length OB/OW elements (#350)
This addresses #349. OB and OW fields with undefined length now parse their contents as a sequence, concatenating all the bytes in the items within.
1 parent 4c45b44 commit 6357e3e

2 files changed

Lines changed: 106 additions & 5 deletions

File tree

read.go

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ var (
2929
// ErrorUnsupportedBitsAllocated indicates that the BitsAllocated in the
3030
// NativeFrame PixelData is unsupported. In this situation, the rest of the
3131
// dataset returned is still valid.
32-
ErrorUnsupportedBitsAllocated = errors.New("unsupported BitsAllocated")
33-
errorUnableToParseFloat = errors.New("unable to parse float type")
34-
ErrorExpectedEvenLength = errors.New("field length is not even, in violation of DICOM spec")
32+
ErrorUnsupportedBitsAllocated = errors.New("unsupported BitsAllocated")
33+
errorUnableToParseFloat = errors.New("unable to parse float type")
34+
ErrorExpectedEvenLength = errors.New("field length is not even, in violation of DICOM spec")
35+
ErrorExpectedDefinedLength = errors.New("unable to read field of undefined length")
36+
ErrorExpectedSequenceDelimitation = errors.New("expected to find sequence delimitation item (fffe,e0dd)")
3537
)
3638

3739
// reader is responsible for mid-level dicom parsing capabilities, like
@@ -122,6 +124,9 @@ func (r *reader) readValue(t tag.Tag, vr string, vl uint32, isImplicit bool, d *
122124
// TODO: if we keep consistent function signature, consider a static map of VR to func?
123125
switch vrkind {
124126
case tag.VRBytes:
127+
if vl == tag.VLUndefinedLength {
128+
return r.readUndefinedLengthByteValue()
129+
}
125130
return r.readBytes(t, vr, vl)
126131
case tag.VRString:
127132
return r.readString(t, vr, vl)
@@ -154,6 +159,34 @@ func (r *reader) readValue(t tag.Tag, vr string, vl uint32, isImplicit bool, d *
154159
}
155160
}
156161

162+
// readUndefinedLengthByteValue reads a value of type OB or OW of undefined length.
163+
// see https://github.com/suyashkumar/dicom/issues/349 for details.
164+
// pixel data should not use this and instead use readPixelData.
165+
func (r *reader) readUndefinedLengthByteValue() (Value, error) {
166+
var allData []byte
167+
foundDelimeter := false
168+
169+
for !r.rawReader.IsLimitExhausted() {
170+
data, terminated, err := r.readRawItem(false)
171+
if err != nil {
172+
return nil, fmt.Errorf("error reading undefined byte value: %w", err)
173+
}
174+
175+
if terminated {
176+
foundDelimeter = true
177+
break
178+
}
179+
180+
allData = append(allData, data...)
181+
}
182+
183+
if !foundDelimeter {
184+
return nil, ErrorExpectedSequenceDelimitation
185+
}
186+
187+
return &bytesValue{value: allData}, nil
188+
}
189+
157190
// readHeader reads the DICOM magic header and group two metadata elements.
158191
// This should only be called once per DICOM at the start of parsing.
159192
func (r *reader) readHeader() ([]*Element, error) {
@@ -641,6 +674,10 @@ func (r *reader) readSequenceItem(t tag.Tag, vr string, vl uint32, d *Dataset) (
641674
}
642675

643676
func (r *reader) readBytes(t tag.Tag, vr string, vl uint32) (Value, error) {
677+
if vl == tag.VLUndefinedLength {
678+
return nil, ErrorExpectedDefinedLength
679+
}
680+
644681
// TODO: add special handling of PixelData
645682
if vr == vrraw.OtherByte || vr == vrraw.Unknown {
646683
data := make([]byte, vl)
@@ -672,6 +709,10 @@ func (r *reader) readBytes(t tag.Tag, vr string, vl uint32) (Value, error) {
672709
}
673710

674711
func (r *reader) readString(t tag.Tag, vr string, vl uint32) (Value, error) {
712+
if vl == tag.VLUndefinedLength {
713+
return nil, ErrorExpectedDefinedLength
714+
}
715+
675716
str, err := r.rawReader.ReadString(vl)
676717
if err != nil {
677718
return nil, fmt.Errorf("error reading string element (%v) value: %w", t, err)
@@ -821,12 +862,11 @@ func (r *reader) readElement(d *Dataset, fc chan<- *frame.Frame) (*Element, erro
821862
}
822863

823864
return &Element{Tag: *t, ValueRepresentation: tag.GetVRKind(*t, vr), RawValueRepresentation: vr, ValueLength: vl, Value: val}, nil
824-
825865
}
826866

827867
// Read an Item object as raw bytes, useful when parsing encapsulated PixelData.
828868
// This returns the read raw item, an indication if this is the end of the set
829-
// of items, and a possible errorawReader.
869+
// of items, and a possible error.
830870
func (r *reader) readRawItem(shouldSkip bool) ([]byte, bool, error) {
831871
t, err := r.readTag()
832872
if err != nil {

read_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ func TestReadOWBytes(t *testing.T) {
206206
}
207207

208208
r := &reader{rawReader: dicomio.NewReader(bufio.NewReader(&data), binary.LittleEndian, int64(data.Len()))}
209+
209210
got, err := r.readBytes(tag.Tag{}, tc.VR, uint32(data.Len()))
210211
if !errors.Is(err, tc.expectedErr) {
211212
t.Fatalf("readBytes(r, tg, %s, %d) got unexpected error: got: %v, want: %v", tc.VR, data.Len(), err, tc.expectedErr)
@@ -217,6 +218,66 @@ func TestReadOWBytes(t *testing.T) {
217218
}
218219
}
219220

221+
func TestReadUndefinedLengthByteValue(t *testing.T) {
222+
cases := []struct {
223+
name string
224+
bytes []byte
225+
want Value
226+
expectedErr error
227+
}{
228+
{
229+
name: "happy path",
230+
bytes: []byte{
231+
0xfe, 0xff, 0x00, 0xe0, // item tag (fffe, e000)
232+
0x04, 0x00, 0x00, 0x00, // length 4
233+
0x01, 0x02, 0x03, 0x04, // data (4 bytes)
234+
0xfe, 0xff, 0xdd, 0xe0, // sequence delimitation item tag (fffe, e0dd)
235+
0x00, 0x00, 0x00, 0x00, // length 0 (required)
236+
},
237+
want: &bytesValue{value: []byte{0x01, 0x02, 0x03, 0x04}},
238+
expectedErr: nil,
239+
},
240+
{
241+
name: "not formatted as sequence",
242+
bytes: []byte{
243+
0x01, 0x02, 0x03, 0x04, // data (4 bytes)
244+
},
245+
want: &bytesValue{value: []byte{}},
246+
expectedErr: io.EOF,
247+
},
248+
{
249+
name: "missing delimitation item",
250+
bytes: []byte{
251+
0xfe, 0xff, 0x00, 0xe0, // item tag (fffe, e000)
252+
0x04, 0x00, 0x00, 0x00, // length 4
253+
0x01, 0x02, 0x03, 0x04, // data (4 bytes)
254+
},
255+
want: &bytesValue{value: []byte{}},
256+
expectedErr: ErrorExpectedSequenceDelimitation,
257+
},
258+
}
259+
260+
for _, tc := range cases {
261+
t.Run(tc.name, func(t *testing.T) {
262+
data := bytes.Buffer{}
263+
if err := binary.Write(&data, binary.LittleEndian, tc.bytes); err != nil {
264+
t.Errorf("TestReadOWBytes: Unable to setup test buffer")
265+
}
266+
267+
r := &reader{rawReader: dicomio.NewReader(bufio.NewReader(&data), binary.LittleEndian, int64(data.Len()))}
268+
269+
got, err := r.readUndefinedLengthByteValue()
270+
if tc.expectedErr != nil {
271+
if !errors.Is(err, tc.expectedErr) {
272+
t.Fatalf("readUndefinedLengthByteValue got unexpected error: got: %v, want: %v", err, tc.expectedErr)
273+
}
274+
} else if diff := cmp.Diff(got, tc.want, cmp.AllowUnexported(bytesValue{})); diff != "" {
275+
t.Errorf("readUndefinedLengthByteValue unexpected diff: %s", diff)
276+
}
277+
})
278+
}
279+
}
280+
220281
func TestReadNativeFrames(t *testing.T) {
221282
cases := []struct {
222283
Name string

0 commit comments

Comments
 (0)