Skip to content

Commit

Permalink
[Rewritten Prototype] Implement SQ Item parsing (#73)
Browse files Browse the repository at this point in the history
This change implements correct parsing of sequence elements. More details on the structure of nested items can be found at the standard here.

Previously, the complete SQ items were read, but not parsed correctly into sequence items.

This change also added support for additional integer VR parsing.
  • Loading branch information
suyashkumar committed Nov 2, 2020
1 parent 8491cf6 commit 2d5912a
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 15 deletions.
31 changes: 22 additions & 9 deletions element.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ const (
Strings ValueType = iota
Bytes
Ints
ElementPtrs
PixelData
SequenceItem
Sequences
)

// Begin definitions of Values:
Expand Down Expand Up @@ -86,17 +87,29 @@ func (s *IntsValue) String() string {
return fmt.Sprintf("%v", s.value)
}

// ElementPtrsValue represents a slice of pointers to other Elements (in the case of sequences)
type ElementPtrsValue struct {
value []*Element
type SequenceItemValue struct {
elements []*Element
}

func (e *ElementPtrsValue) isElementValue() {}
func (e *ElementPtrsValue) ValueType() ValueType { return ElementPtrs }
func (e *ElementPtrsValue) GetValue() interface{} { return e.value }
func (e *ElementPtrsValue) String() string {
func (s *SequenceItemValue) isElementValue() {}
func (s *SequenceItemValue) ValueType() ValueType { return SequenceItem }
func (s *SequenceItemValue) GetValue() interface{} { return s.elements }
func (s *SequenceItemValue) String() string {
// TODO: consider adding more sophisticated formatting
return ""
return fmt.Sprintf("%+v", s.elements)
}

// SequencesValue represents a set of items in a DICOM sequence.
type SequencesValue struct {
value []*SequenceItemValue
}

func (s *SequencesValue) isElementValue() {}
func (s *SequencesValue) ValueType() ValueType { return Sequences }
func (s *SequencesValue) GetValue() interface{} { return s.value }
func (s *SequencesValue) String() string {
// TODO: consider adding more sophisticated formatting
return fmt.Sprintf("%+v", s.value)
}

type PixelDataInfo struct {
Expand Down
2 changes: 2 additions & 0 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ func (p *parser) Parse() (Dataset, error) {
return Dataset{}, err
}

log.Println("Read tag: ", elem.Tag)

// TODO: if we encounter a dicomtag.SpecificCharacterSet, update the reader to accommodate
// TODO: add dicom options to only keep track of certain tags

Expand Down
68 changes: 62 additions & 6 deletions read.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ func readValue(r dicomio.Reader, t tag.Tag, vr string, vl uint32, isImplicit boo
return readInt(r, t, vr, vl)
case tag.VRSequence:
return readSequence(r, t, vr, vl)
case tag.VRItem:
return readSequenceItem(r, t, vr, vl)
case tag.VRPixelData:
return readPixelData(r, t, vr, vl)
default:
Expand Down Expand Up @@ -136,26 +138,32 @@ func readPixelData(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value, er
return nil, nil
}

// readSequence reads a sequence element (VR = SQ) that contains a subset of Items. Each item contains
// a set of Elements.
// See http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.5.2.html#table_7.5-1
func readSequence(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value, error) {
var subElements ElementPtrsValue
var sequences SequencesValue

if vl == tag.VLUndefinedLength {
for {
subElement, err := readElement(r)
if err != nil {
// Stop reading due to error
log.Println("error reading subitem, ", err)
return nil, err
}
if subElement.Tag == tag.SequenceDelimitationItem {
// Stop reading
break
}
if subElement.Tag != tag.Item {
if subElement.Tag != tag.Item || subElement.Value.ValueType() != SequenceItem {
// This is an error, should be an Item!
// TODO: use error var
return nil, fmt.Errorf("non item found in sequence")
}
subElements.value = append(subElements.value, subElement)

// Append the Item element's dataset of elements to this Sequence's SequencesValue.
sequences.value = append(sequences.value, subElement.Value.(*SequenceItemValue))
}
} else {
// Sequence of elements for a total of VL bytes
Expand All @@ -164,17 +172,55 @@ func readSequence(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value, err
return nil, err
}
for !r.IsLimitExhausted() {
subElem, err := readElement(r)
subElement, err := readElement(r)
if err != nil {
// TODO: option to ignore errors parsing subelements?
return nil, err
}
subElements.value = append(subElements.value, subElem)

// Append the Item element's dataset of elements to this Sequence's SequencesValue.
sequences.value = append(sequences.value, subElement.Value.(*SequenceItemValue))
}
r.PopLimit()
}

return &subElements, nil
return &sequences, nil
}

// readSequenceItem reads an item component of a sequence dicom element and returns an Element
// with a SequenceItem value.
func readSequenceItem(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value, error) {
var sequenceItem SequenceItemValue

if vl == tag.VLUndefinedLength {
for {
subElem, err := readElement(r)
if err != nil {
return nil, err
}
if subElem.Tag == tag.ItemDelimitationItem {
break
}

sequenceItem.elements = append(sequenceItem.elements, subElem)
}
} else {
err := r.PushLimit(int64(vl))
if err != nil {
return nil, err
}

for !r.IsLimitExhausted() {
subElem, err := readElement(r)
if err != nil {
return nil, err
}

sequenceItem.elements = append(sequenceItem.elements, subElem)
}
}

return &sequenceItem, nil
}

func readBytes(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value, error) {
Expand Down Expand Up @@ -239,6 +285,12 @@ func readInt(r dicomio.Reader, t tag.Tag, vr string, vl uint32) (Value, error) {
case "UL":
val, err := r.ReadUInt32()
return &IntsValue{value: []int{int(val)}}, err
case "SL":
val, err := r.ReadInt32()
return &IntsValue{value: []int{int(val)}}, err
case "SS":
val, err := r.ReadInt16()
return &IntsValue{value: []int{int(val)}}, err
}

return nil, errors.New("could not parse integer type correctly")
Expand All @@ -263,6 +315,10 @@ func readElement(r dicomio.Reader) (*Element, error) {
}

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

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

Expand Down

0 comments on commit 2d5912a

Please sign in to comment.