Skip to content

Commit

Permalink
[Rewrite] Native Pixel Data Parsing, handle value multiplicity. (#86)
Browse files Browse the repository at this point in the history
This implements native pixel data parsing on the rewrite branch. This includes fixes done on the mainline branch to parse PixelData properly if it appears multiple times in a DICOM (PR #79). This also includes some fixes to better handle value multiplicity.
  • Loading branch information
suyashkumar committed Sep 17, 2020
1 parent 1cfebc8 commit 441d2c4
Show file tree
Hide file tree
Showing 7 changed files with 264 additions and 39 deletions.
47 changes: 38 additions & 9 deletions cmd/dicomtest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ package main
import (
"flag"
"fmt"
"io/ioutil"
"image/jpeg"
"log"
"os"
"strconv"

"github.com/suyashkumar/dicom"
"github.com/suyashkumar/dicom/pkg/tag"
Expand Down Expand Up @@ -44,21 +45,49 @@ func main() {
return
}

for _, elem := range ds.Elements {
for z, elem := range ds.Elements {
if elem.Tag != tag.PixelData {
log.Println(elem.Tag)
log.Println(elem.ValueLength)
log.Println(elem.Value)
} else {
imageInfo := elem.Value.GetValue().(dicom.PixelDataInfo)
for i, frame := range imageInfo.Frames {
err := ioutil.WriteFile(fmt.Sprintf("image_%d.jpg", i), frame.EncapsulatedData.Data,
0644)
if err != nil {
log.Println(err)
// TODO: remove image icon hack after implementing flat iterator
if elem.Tag == tag.IconImageSequence {
for _, item := range elem.Value.GetValue().([]*dicom.SequenceItemValue) {
for _, subElem := range item.GetValue().([]*dicom.Element) {
if subElem.Tag == tag.PixelData {
writePixelDataElement(subElem, strconv.Itoa(z))
}
}
}
}
} else {
writePixelDataElement(elem, strconv.Itoa(z))
}

}
}
}

func writePixelDataElement(e *dicom.Element, id string) {
imageInfo := e.Value.GetValue().(dicom.PixelDataInfo)
for idx, f := range imageInfo.Frames {
i, err := f.GetImage()
if err != nil {
log.Fatal("Error while getting image")
}

name := fmt.Sprintf("image_%d_%s.jpg", idx, id)
f, err := os.Create(name)
if err != nil {
fmt.Printf("Error while creating file: %s", err.Error())
}
err = jpeg.Encode(f, i, &jpeg.Options{Quality: 100})
if err != nil {
log.Println(err)
}
if err = f.Close(); err != nil {
log.Println("ERROR: unable to properly close file: ", f.Name())
}

}
}
20 changes: 19 additions & 1 deletion dataset.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
package dicom

import (
"errors"

"github.com/suyashkumar/dicom/pkg/tag"
)

var ErrorElementNotFound = errors.New("element not found")

type Dataset struct {
Elements []*Element
Size uint64
}

// FindElementByTag searches through the dataset and returns a pointer to the matching element.
// It DOES NOT search within Sequences as well.
func (d *Dataset) FindElementByTag(tag tag.Tag) (*Element, error) {
for _, e := range d.Elements {
if e.Tag == tag {
return e, nil
}
}
return nil, ErrorElementNotFound
}
12 changes: 12 additions & 0 deletions element.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,15 @@ func (e *PixelDataValue) String() string {
// TODO: consider adding more sophisticated formatting
return ""
}

func MustGetInt(v Value) int {
return v.GetValue().([]int)[0]
}

func MustGetInts(v Value) []int {
return v.GetValue().([]int)
}

func MustGetString(v Value) string {
return v.GetValue().([]string)[0]
}
15 changes: 15 additions & 0 deletions mocks/pkg/dicomio/mock_reader.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (p *parser) readHeader() ([]*Element, error) {
}

// Read the length of the metadata elements: (0002,0000) MetaElementGroupLength
maybeMetaLen, err := readElement(p.reader)
maybeMetaLen, err := readElement(p.reader, nil)
if err != nil {
log.Println("read element err")
return nil, err
Expand All @@ -93,7 +93,7 @@ func (p *parser) readHeader() ([]*Element, error) {
}
defer p.reader.PopLimit()
for !p.reader.IsLimitExhausted() {
elem, err := readElement(p.reader)
elem, err := readElement(p.reader, nil)
if err != nil {
// TODO: see if we can skip over malformed elements somehow
log.Println("read element err")
Expand All @@ -109,7 +109,7 @@ func (p *parser) readHeader() ([]*Element, error) {
func (p *parser) Parse() (Dataset, error) {
for !p.reader.IsLimitExhausted() {
// TODO: avoid silent looping
elem, err := readElement(p.reader)
elem, err := readElement(p.reader, &p.dataset)
if err != nil {
// TODO: tolerate some kinds of errors and continue parsing
return Dataset{}, err
Expand Down
8 changes: 8 additions & 0 deletions pkg/dicomio/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ var (
// Reader provides common functionality for reading underlying DICOM data.
type Reader interface {
io.Reader
// ReadUInt8 reads a uint16 from the underlying reader
ReadUInt8() (uint8, error)
// ReadUInt16 reads a uint16 from the underlying reader
ReadUInt16() (uint16, error)
// ReadUInt32 reads a uint32 from the underlying reader
Expand Down Expand Up @@ -80,6 +82,12 @@ func (r *reader) Read(p []byte) (int, error) {
return n, err
}

func (r *reader) ReadUInt8() (uint8, error) {
var out uint8
err := binary.Read(r, r.bo, &out)
return out, err
}

func (r *reader) ReadUInt16() (uint16, error) {
var out uint16
err := binary.Read(r, r.bo, &out)
Expand Down
Loading

0 comments on commit 441d2c4

Please sign in to comment.