Skip to content

Commit

Permalink
Initial implementation of opportunistic transfer syntax inference
Browse files Browse the repository at this point in the history
  • Loading branch information
suyashkumar committed Jun 10, 2024
1 parent 0b4bb9f commit 2597952
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 26 deletions.
69 changes: 63 additions & 6 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@ package dicom

import (
"bufio"
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"os"

Expand Down Expand Up @@ -170,14 +172,69 @@ func NewParser(in io.Reader, bytesToRead int64, frameChannel chan *frame.Frame,
if tsStr == uid.DeflatedExplicitVRLittleEndian {
p.reader.rawReader.SetDeflate()
}
} else {
// No transfer syntax found, warn the user we're proceeding with the
// default Little Endian implicit.
debug.Log("WARN: could not find transfer syntax uid in metadata, proceeding with little endian implicit")
p.SetTransferSyntax(bo, implicit)
return &p, nil
}
p.SetTransferSyntax(bo, implicit)

return &p, nil
// No transfer syntax found, so let's try to infer the transfer syntax by
// trying to read the next element under various transfer syntaxes.
next100, err := p.reader.rawReader.Peek(100)
if errors.Is(err, io.EOF) {
// DICOM is shorter than 100 bytes.
return nil, fmt.Errorf("dicom with missing transfer syntax metadata is shorter than 100 bytes, so cannot infer transfer syntax")
}

syntaxes := []struct {
name string
bo binary.ByteOrder
implicit bool
}{
{
name: "Little Endian Implicit",
bo: binary.LittleEndian,
implicit: true,
},
{
name: "Big Endian Implicit",
bo: binary.BigEndian,
implicit: true,
},
{
name: "Big Endian Explicit",
bo: binary.BigEndian,
implicit: false,
},
{
name: "Little Endian Explicit",
bo: binary.LittleEndian,
implicit: false,
},
}

for _, syntax := range syntaxes {
if canReadElementFromBytes(next100, optSet, syntax.bo, syntax.implicit) {
debug.Logf("WARN: could not find transfer syntax uid in metadata, proceeding with %v", syntax.name)
p.SetTransferSyntax(syntax.bo, syntax.implicit)
return &p, nil
}
}
// TODO(https://github.com/suyashkumar/dicom/issues/329): consider trying
// deflated parsing as a fallback as well.
return &p, errors.New("dicom missing transfer syntax uid in metadata, and it was not possible to successfully infer it using the next 100 bytes of the dicom")
}

func canReadElementFromBytes(buf []byte, optSet parseOptSet, bo binary.ByteOrder, implicit bool) bool {
next100Reader := bytes.NewReader(buf)
subR := &reader{
rawReader: dicomio.NewReader(bufio.NewReader(next100Reader), bo, int64(len(buf))),
opts: optSet,
}
subR.rawReader.SetTransferSyntax(bo, implicit)
_, err := subR.readElement(nil, nil)
if err == nil {
return true
}
return false
}

// Next parses and returns the next top-level element from the DICOM this Parser points to.
Expand Down
46 changes: 26 additions & 20 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,37 +100,43 @@ func TestParseFile_SkipPixelData(t *testing.T) {
runForEveryTestFile(t, func(t *testing.T, filename string) {
dataset, err := dicom.ParseFile(filename, nil, dicom.SkipPixelData())
if err != nil {
t.Errorf("Unexpected error parsing dataset: %v", dataset)
t.Errorf("Unexpected error parsing dataset: %v, dataset: %v", err, dataset)
}
// If PixelData present in this DICOM, check if it's populated
// correctly. The current test assumption is that if PixelData is
// missing, it was not originally in the dicom (which we should
// consider revisiting).
el, err := dataset.FindElementByTag(tag.PixelData)
if err != nil {
t.Errorf("Unexpected error when finding PixelData in Dataset: %v", err)
}
pixelData := dicom.MustGetPixelDataInfo(el.Value)
if !pixelData.IntentionallySkipped {
t.Errorf("Expected pixelData.IntentionallySkipped=true, got false")
}
if got := len(pixelData.Frames); got != 0 {
t.Errorf("unexpected frames length. got: %v, want: %v", got, 0)
if err == nil {
pixelData := dicom.MustGetPixelDataInfo(el.Value)
if !pixelData.IntentionallySkipped {
t.Errorf("Expected pixelData.IntentionallySkipped=true, got false")
}
if got := len(pixelData.Frames); got != 0 {
t.Errorf("unexpected frames length. got: %v, want: %v", got, 0)
}
}
})
})
t.Run("WithNOSkipPixelData", func(t *testing.T) {
runForEveryTestFile(t, func(t *testing.T, filename string) {
dataset, err := dicom.ParseFile(filename, nil)
if err != nil {
t.Errorf("Unexpected error parsing dataset: %v", dataset)
t.Errorf("Unexpected error parsing dataset: %v, dataset: %v", err, dataset)
}
// If PixelData present in this DICOM, check if it's populated
// correctly. The current test assumption is that if PixelData is
// missing, it was not originally in the dicom (which we should
// consider revisiting).
el, err := dataset.FindElementByTag(tag.PixelData)
if err != nil {
t.Errorf("Unexpected error when finding PixelData in Dataset: %v", err)
}
pixelData := dicom.MustGetPixelDataInfo(el.Value)
if pixelData.IntentionallySkipped {
t.Errorf("Expected pixelData.IntentionallySkipped=false when SkipPixelData option not present, got true")
}
if len(pixelData.Frames) == 0 {
t.Errorf("unexpected frames length when SkipPixelData=false. got: %v, want: >0", len(pixelData.Frames))
if err == nil {
pixelData := dicom.MustGetPixelDataInfo(el.Value)
if pixelData.IntentionallySkipped {
t.Errorf("Expected pixelData.IntentionallySkipped=false when SkipPixelData option not present, got true")
}
if len(pixelData.Frames) == 0 {
t.Errorf("unexpected frames length when SkipPixelData=false. got: %v, want: >0", len(pixelData.Frames))
}
}
})
})
Expand Down

0 comments on commit 2597952

Please sign in to comment.