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
6 changes: 4 additions & 2 deletions dataset.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ func (d *Dataset) transferSyntax() (binary.ByteOrder, bool, error) {
// FindElementByTagNested searches through the dataset and returns a pointer to the matching element.
// This call searches through a flat representation of the dataset, including within sequences.
func (d *Dataset) FindElementByTagNested(tag tag.Tag) (*Element, error) {
for e := range d.FlatIterator() {
c := d.FlatIterator()
defer ExhaustElementChannel(c)
for e := range c {
if e.Tag == tag {
return e, nil
}
Expand Down Expand Up @@ -106,7 +108,7 @@ func ExhaustElementChannel(c <-chan *Element) {

func flatElementsIterator(elems []*Element, elemChan chan<- *Element) {
for _, elem := range elems {
if elem.Value.ValueType() == Sequences {
if elem.Value != nil && elem.Value.ValueType() == Sequences {
elemChan <- elem
for _, seqItem := range elem.Value.GetValue().([]*SequenceItemValue) {
flatElementsIterator(seqItem.elements, elemChan)
Expand Down
78 changes: 74 additions & 4 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 @@ -102,8 +103,9 @@ type Parser struct {
dataset Dataset
metadata Dataset
// file is optional, might be populated if reading from an underlying file
file *os.File
frameChannel chan *frame.Frame
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,7 +120,8 @@ func NewParser(in io.Reader, bytesToRead int64, frameChannel chan *frame.Frame,
rawReader: dicomio.NewReader(bufio.NewReader(in), binary.LittleEndian, bytesToRead),
opts: optSet,
},
frameChannel: frameChannel,
frameChannel: frameChannel,
stopAtPixelData: optSet.stopAtPixelDataStart,
}

elems := []*Element{}
Expand Down Expand Up @@ -167,7 +170,7 @@ func (p *Parser) Next() (*Element, error) {
}
return nil, ErrorEndOfDICOM
}
elem, err := p.reader.readElement(&p.dataset, p.frameChannel)
elem, err := p.reader.readElement(&p.dataset, p.frameChannel, p.stopAtPixelData)
if err != nil {
// TODO: tolerate some kinds of errors and continue parsing
return nil, err
Expand All @@ -191,6 +194,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(optionalNumberOfFrames bool) (int64, error) {
parsedData := p.dataset
nof, err := parsedData.FindElementByTag(tag.NumberOfFrames)
var nFrames = 1
if err != nil {
if !optionalNumberOfFrames {
return -1, err
}
} else {
nFrames, err = strconv.Atoi(MustGetStrings(nof.Value)[0])
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(bytesTotal int64) (io.Reader, error) {
return io.LimitReader(p.reader.rawReader, bytesTotal), nil
}

// GetMetadata returns just the set of metadata elements that have been parsed
// so far.
func (p *Parser) GetMetadata() Dataset {
Expand All @@ -211,6 +274,7 @@ type parseOptSet struct {
allowMismatchPixelDataLength bool
skipPixelData bool
skipProcessingPixelDataValue bool
stopAtPixelDataStart bool
}

func toParseOptSet(opts ...ParseOption) parseOptSet {
Expand Down Expand Up @@ -262,3 +326,9 @@ func SkipProcessingPixelDataValue() ParseOption {
set.skipProcessingPixelDataValue = true
}
}

func StopAtPixelData() ParseOption {
return func(set *parseOptSet) {
set.stopAtPixelDataStart = true
}
}
20 changes: 12 additions & 8 deletions read.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ func (r *reader) readHeader() ([]*Element, error) {

// Must read metadata as LittleEndian explicit VR
// Read the length of the metadata elements: (0002,0000) MetaElementGroupLength
maybeMetaLen, err := r.readElement(nil, nil)
maybeMetaLen, err := r.readElement(nil, nil, r.opts.stopAtPixelDataStart)
if err != nil {
return nil, err
}
Expand All @@ -174,7 +174,7 @@ func (r *reader) readHeader() ([]*Element, error) {
}
defer r.rawReader.PopLimit()
for !r.rawReader.IsLimitExhausted() {
elem, err := r.readElement(nil, nil)
elem, err := r.readElement(nil, nil, r.opts.stopAtPixelDataStart)
if err != nil {
// TODO: see if we can skip over malformed elements somehow
return nil, err
Expand Down Expand Up @@ -466,7 +466,7 @@ func (r *reader) readSequence(t tag.Tag, vr string, vl uint32, d *Dataset) (Valu
seqElements := &Dataset{}
if vl == tag.VLUndefinedLength {
for {
subElement, err := r.readElement(seqElements, nil)
subElement, err := r.readElement(seqElements, nil, false)
if err != nil {
// Stop reading due to error
log.Println("error reading subitem, ", err)
Expand All @@ -493,7 +493,7 @@ func (r *reader) readSequence(t tag.Tag, vr string, vl uint32, d *Dataset) (Valu
return nil, err
}
for !r.rawReader.IsLimitExhausted() {
subElement, err := r.readElement(seqElements, nil)
subElement, err := r.readElement(seqElements, nil, false)
if err != nil {
// TODO: option to ignore errors parsing subelements?
return nil, err
Expand All @@ -519,7 +519,7 @@ func (r *reader) readSequenceItem(t tag.Tag, vr string, vl uint32, d *Dataset) (

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

for !r.rawReader.IsLimitExhausted() {
subElem, err := r.readElement(&seqElements, nil)
subElem, err := r.readElement(&seqElements, nil, false)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -698,7 +698,7 @@ func (r *reader) readInt(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 (r *reader) readElement(d *Dataset, fc chan<- *frame.Frame) (*Element, error) {
func (r *reader) readElement(d *Dataset, fc chan<- *frame.Frame, stopAtPixelDataValue bool) (*Element, error) {
t, err := r.readTag()
if err != nil {
return nil, err
Expand All @@ -723,14 +723,18 @@ func (r *reader) readElement(d *Dataset, fc chan<- *frame.Frame) (*Element, erro
}
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)
val, err := r.readValue(*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