Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow custom pixel data parsing and streaming pixel data directly from the reader. #223

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
82 changes: 76 additions & 6 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"errors"
"io"
"os"
"strconv"

"github.com/suyashkumar/dicom/pkg/charset"
"github.com/suyashkumar/dicom/pkg/debug"
Expand Down Expand Up @@ -95,12 +96,13 @@ func ParseFile(filepath string, frameChan chan *frame.Frame) (Dataset, error) {
// useful for some streaming processing applications. If you instead just want to parse the whole input DICOM at once,
// just use the dicom.Parse(...) method.
type Parser struct {
reader dicomio.Reader
dataset Dataset
metadata Dataset
reader dicomio.Reader
dataset Dataset
metadata Dataset
// file is optional, might be populated if reading from an underlying file
file *os.File
frameChannel chan *frame.Frame
stopAtPixelData bool
}

// NewParser returns a new Parser that points to the provided io.Reader, with bytesToRead bytes left to read. NewParser
Expand All @@ -118,6 +120,7 @@ func NewParser(in io.Reader, bytesToRead int64, frameChannel chan *frame.Frame,
p := Parser{
reader: reader,
frameChannel: frameChannel,
stopAtPixelData: optSet.stopAtPixelDataStart,
}

elems := []*Element{}
Expand Down Expand Up @@ -165,7 +168,7 @@ func (p *Parser) Next() (*Element, error) {
}
return nil, ErrorEndOfDICOM
}
elem, err := readElement(p.reader, &p.dataset, p.frameChannel)
elem, err := readElement(p.reader, &p.dataset, p.frameChannel, p.stopAtPixelData)
if err != nil {
// TODO: tolerate some kinds of errors and continue parsing
return nil, err
Expand All @@ -189,6 +192,66 @@ func (p *Parser) Next() (*Element, error) {

}

// GetDataset returns just the set of metadata elements that have been parsed
// so far.
func (p *Parser) GetDataset() Dataset {
return p.dataset
}

func (p *Parser) GetPixelDataSize() (int64, error) {
parsedData := p.dataset
nof, err := parsedData.FindElementByTag(tag.NumberOfFrames)
if err != nil {
return -1, err
}
nFrames, err := strconv.Atoi(MustGetStrings(nof.Value)[0]) // odd that number of frames is encoded as a string...
if err != nil {
return -1, err
}

// Parse information from previously parsed attributes that are needed to parse NativeData Frames:
rows, err := parsedData.FindElementByTag(tag.Rows)
if err != nil {
return -1, err
}

cols, err := parsedData.FindElementByTag(tag.Columns)
if err != nil {
return -1, err
}

b, err := parsedData.FindElementByTag(tag.BitsAllocated)
if err != nil {
return -1, err
}
bitsAllocated := MustGetInts(b.Value)[0]

s, err := parsedData.FindElementByTag(tag.SamplesPerPixel)
if err != nil {
return -1, err
}
samplesPerPixel := MustGetInts(s.Value)[0]

pixelsPerFrame := MustGetInts(rows.Value)[0] * MustGetInts(cols.Value)[0]

// Parse the pixels:
bytesAllocated := bitsAllocated / 8

frameSize := bytesAllocated * samplesPerPixel * pixelsPerFrame
var bytesTotal int64
bytesTotal = int64(frameSize) * int64(nFrames)

return bytesTotal, nil
}

func (p *Parser) GetPixelDataReader() (io.Reader, error) {
bytesTotal, err := p.GetPixelDataSize()
if err != nil {
return nil, err
}
return io.LimitReader(p.reader, bytesTotal), nil
}

// GetMetadata returns just the set of metadata elements that have been parsed
// so far.
func (p *Parser) GetMetadata() Dataset {
Expand Down Expand Up @@ -221,7 +284,7 @@ func (p *Parser) readHeader() ([]*Element, error) {

// Must read metadata as LittleEndian explicit VR
// Read the length of the metadata elements: (0002,0000) MetaElementGroupLength
maybeMetaLen, err := readElement(p.reader, nil, nil)
maybeMetaLen, err := readElement(p.reader, nil, nil, p.stopAtPixelData)
if err != nil {
return nil, err
}
Expand All @@ -241,7 +304,7 @@ func (p *Parser) readHeader() ([]*Element, error) {
}
defer p.reader.PopLimit()
for !p.reader.IsLimitExhausted() {
elem, err := readElement(p.reader, nil, nil)
elem, err := readElement(p.reader, nil, nil, p.stopAtPixelData)
if err != nil {
// TODO: see if we can skip over malformed elements somehow
return nil, err
Expand All @@ -258,6 +321,7 @@ type ParseOption func(*parseOptSet)
// parseOptSet represents the flattened option set after all ParseOptions have been applied.
type parseOptSet struct {
skipMetadataReadOnNewParserInit bool
stopAtPixelDataStart bool
}

func toParseOptSet(opts ...ParseOption) *parseOptSet {
Expand All @@ -275,3 +339,9 @@ func SkipMetadataReadOnNewParserInit() ParseOption {
set.skipMetadataReadOnNewParserInit = true
}
}

func StopAtPixelData() ParseOption {
return func(set *parseOptSet) {
set.stopAtPixelDataStart = true
}
}
15 changes: 9 additions & 6 deletions read.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ func readSequence(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value, err

if vl == tag.VLUndefinedLength {
for {
subElement, err := readElement(r, nil, nil)
subElement, err := readElement(r, nil, nil, false)
if err != nil {
// Stop reading due to error
log.Println("error reading subitem, ", err)
Expand All @@ -362,7 +362,7 @@ func readSequence(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value, err
return nil, err
}
for !r.IsLimitExhausted() {
subElement, err := readElement(r, nil, nil)
subElement, err := readElement(r, nil, nil, false)
if err != nil {
// TODO: option to ignore errors parsing subelements?
return nil, err
Expand All @@ -388,7 +388,7 @@ func readSequenceItem(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value,

if vl == tag.VLUndefinedLength {
for {
subElem, err := readElement(r, &seqElements, nil)
subElem, err := readElement(r, &seqElements, nil, false)
if err != nil {
return nil, err
}
Expand All @@ -406,7 +406,7 @@ func readSequenceItem(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value,
}

for !r.IsLimitExhausted() {
subElem, err := readElement(r, &seqElements, nil)
subElem, err := readElement(r, &seqElements, nil, false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -568,7 +568,7 @@ func readInt(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value, error) {
// elements read so far, since previously read elements may be needed to parse
// certain Elements (like native PixelData). If the Dataset is nil, it is
// treated as an empty Dataset.
func readElement(r dicomio.Reader, d *Dataset, fc chan<- *frame.Frame) (*Element, error) {
func readElement(r dicomio.Reader, d *Dataset, fc chan<- *frame.Frame, stopAtPixelDataValue bool) (*Element, error) {
t, err := readTag(r)
if err != nil {
return nil, err
Expand All @@ -593,14 +593,17 @@ func readElement(r dicomio.Reader, d *Dataset, fc chan<- *frame.Frame) (*Element
}
debug.Logf("readElement: vl: %d", vl)

if stopAtPixelDataValue && *t == tag.PixelData {
return &Element{Tag: *t, ValueRepresentation: tag.GetVRKind(*t, vr), RawValueRepresentation: vr, ValueLength: vl, Value: nil}, nil
}

val, err := readValue(r, *t, vr, vl, readImplicit, d, fc)
if err != nil {
log.Println("error reading value ", err)
return nil, err
}

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

}

// Read an Item object as raw bytes, useful when parsing encapsulated PixelData.
Expand Down