dicom

package module
v0.0.0-...-4353d90 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 14, 2021 License: MIT Imports: 16 Imported by: 0

README

GoDoc Build Status

DICOM parser in Go

This is a fork of github.com/gillesdemey/go-dicom. Changes are:

  • Many bug fixes, especially around handling of sequences.
  • Handle non-ASCII characters more properly.
  • Simplify the API. All the functions are synchronous.
  • Better library supports around tags & uids.
  • Rudimentary support for writing DICOM files. This part is not complete yet.
  • Adds fuzz tests and tests that ensure compatibility with pydicom.

TODO:

  • Implement mixed-coding-system files more properly. We currently botch patient-name (PN) elements that mixes coding systems.

  • A multi-image file. Functionality is almost there, but I haven't had time to complete it.

  • Native pixeldata format. It'll be parsed as just []byte.

See doc.go for usage. dicomutil contains a sample program that dumps DICOM elements in a file.

Acknowledgements

I'd like to thank my friend Seppe Stas for helping me get through the horrific DICOM image specification and some of the harder parts of the parser.

Some more inspiration and helpful resource that brought this library to life (in no particular order):

DWV by ivmartel https://github.com/ivmartel/dwv/
dicomParser by Chris Hafey https://github.com/chafey/dicomParser
http://www.dicomlibrary.com
http://dicom.nema.org/medical/dicom/current/output/pdf/part05.pdf

Documentation

Overview

Package dicom provides DICOM reader and writer.

Directory dicomutil contains a sample DICOM file viewer

Example (Read)
package main

import (
	"fmt"

	dicom "github.com/BTsykaniuk/go-dicom"
	"github.com/BTsykaniuk/go-dicom/dicomtag"
)

func main() {
	ds, err := dicom.ReadDataSetFromFile("examples/IM-0001-0003.dcm", dicom.ReadOptions{})
	if err != nil {
		panic(err)
	}
	patientID, err := ds.FindElementByTag(dicomtag.PatientID)
	if err != nil {
		panic(err)
	}
	patientBirthDate, err := ds.FindElementByTag(dicomtag.PatientBirthDate)
	if err != nil {
		panic(err)
	}
	fmt.Println("ID: " + patientID.String())
	fmt.Println("BirthDate: " + patientBirthDate.String())
}
Output:

ID:  (0010,0020)[PatientID] LO  [7DkT2Tp]
BirthDate:  (0010,0030)[PatientBirthDate] DA  [19530828]
Example (UpdateExistingFile)
package main

import (
	"bytes"
	"fmt"

	dicom "github.com/BTsykaniuk/go-dicom"
	"github.com/BTsykaniuk/go-dicom/dicomtag"
)

func main() {
	ds, err := dicom.ReadDataSetFromFile("examples/IM-0001-0003.dcm", dicom.ReadOptions{})
	if err != nil {
		panic(err)
	}
	patientID, err := ds.FindElementByTag(dicomtag.PatientID)
	if err != nil {
		panic(err)
	}
	patientID.Value = []interface{}{"John Doe"}

	buf := bytes.Buffer{}
	if err := dicom.WriteDataSet(&buf, ds); err != nil {
		panic(err)
	}

	ds2, err := dicom.ReadDataSet(&buf, dicom.ReadOptions{})
	if err != nil {
		panic(err)
	}
	patientID, err = ds2.FindElementByTag(dicomtag.PatientID)
	if err != nil {
		panic(err)
	}
	fmt.Println("ID: " + patientID.String())
}
Output:

ID:  (0010,0020)[PatientID] LO  [John Doe]
Example (Write)
package main

import (
	"fmt"

	dicom "github.com/BTsykaniuk/go-dicom"
	"github.com/BTsykaniuk/go-dicom/dicomtag"
	"github.com/BTsykaniuk/go-dicom/dicomuid"
)

func main() {
	elems := []*dicom.Element{
		dicom.MustNewElement(dicomtag.TransferSyntaxUID, dicomuid.ExplicitVRLittleEndian),
		dicom.MustNewElement(dicomtag.MediaStorageSOPClassUID, "1.2.840.10008.5.1.4.1.1.1.2"),
		dicom.MustNewElement(dicomtag.MediaStorageSOPInstanceUID, "1.2.840.113857.113857.1528.141452.1.5"),
		dicom.MustNewElement(dicomtag.PatientName, "Alice Doe"),
	}
	ds := dicom.DataSet{Elements: elems}
	err := dicom.WriteDataSetToFile("/tmp/test.dcm", &ds)
	if err != nil {
		panic(err)
	}

	ds2, err := dicom.ReadDataSetFromFile("/tmp/test.dcm", dicom.ReadOptions{})
	if err != nil {
		panic(err)
	}
	for _, elem := range ds2.Elements {
		fmt.Println(elem.String())
	}
}
Output:

(0002,0000)[FileMetaInformationGroupLength] UL  [184]
 (0002,0001)[FileMetaInformationVersion] OB  [[48 32 49 0]]
 (0002,0002)[MediaStorageSOPClassUID] UI  [1.2.840.10008.5.1.4.1.1.1.2]
 (0002,0003)[MediaStorageSOPInstanceUID] UI  [1.2.840.113857.113857.1528.141452.1.5]
 (0002,0010)[TransferSyntaxUID] UI  [1.2.840.10008.1.2.1]
 (0002,0012)[ImplementationClassUID] UI  [1.2.826.0.1.3680043.9.7133.1.1]
 (0002,0013)[ImplementationVersionName] SH  [GODICOM_1_1]
 (0010,0010)[PatientName] PN  [Alice Doe]

Index

Examples

Constants

View Source
const (
	InvalidYear  = -1
	InvalidMonth = -1
	InvalidDay   = -1
)
View Source
const GoDICOMImplementationClassUIDPrefix = "1.2.826.0.1.3680043.9.7133"

GoDICOMImplementationClassUIDPrefix defines the UID prefix for go-dicom. Provided by https://www.medicalconnections.co.uk/Free_UID

View Source
const GoDICOMImplementationVersionName = "GODICOM_1_1"

Variables

View Source
var GoDICOMImplementationClassUID = GoDICOMImplementationClassUIDPrefix + ".1.1"

Functions

func WriteDataSet

func WriteDataSet(out io.Writer, ds *DataSet) error

WriteDataSet writes the dataset into the stream in DICOM file format, complete with the magic header and metadata elements.

The transfer syntax (byte order, etc) of the file is determined by the TransferSyntax element in "ds". If ds is missing that or a few other essential elements, this function returns an error.

ds := ... read or create dicom.Dataset ...
out, err := os.Create("test.dcm")
err := dicom.Write(out, ds)

func WriteDataSetToFile

func WriteDataSetToFile(path string, ds *DataSet) error

WriteDataSetToFile writes "ds" to the given file. If the file already exists, existing contents are clobbered. Else, the file is newly created.

func WriteElement

func WriteElement(e *dicomio.Encoder, elem *Element)

WriteElement encodes one data element. Errors are reported through e.Error() and/or E.Finish().

REQUIRES: Each value in values[] must match the VR of the tag. E.g., if tag is for UL, then each value must be uint32.

func WriteFileHeader

func WriteFileHeader(e *dicomio.Encoder, metaElems []*Element)

WriteFileHeader produces a DICOM file header. metaElems[] is be a list of elements to be embedded in the header part. Every element in metaElems[] must have Tag.Group==2. It must contain at least the following three elements: TagTransferSyntaxUID, TagMediaStorageSOPClassUID, TagMediaStorageSOPInstanceUID. The list may contain other meta elements as long as their Tag.Group==2; they are added to the header.

Errors are reported via e.Error().

Consult the following page for the DICOM file header format.

http://dicom.nema.org/dicom/2013/output/chtml/part10/chapter_7.html

Types

type DataSet

type DataSet struct {
	// Elements in the file, in order of appearance.
	//
	// Note: unlike pydicom, Elements also contains meta elements (those
	// with Tag.Group==2).
	Elements []*Element
}

DataSet represents contents of one DICOM file.

func ReadDataSet

func ReadDataSet(in io.Reader, options ReadOptions) (*DataSet, error)

ReadDataSet reads a DICOM file from "io".

On parse error, this function may return a non-nil dataset and a non-nil error. In such case, the dataset will contain parts of the file that are parsable, and error will show the first error found by the parser.

func ReadDataSetFromFile

func ReadDataSetFromFile(path string, options ReadOptions) (*DataSet, error)

ReadDataSetFromFile parses file cotents into dicom.DataSet. It is a thin wrapper around ReadDataSet.

On parse error, this function may return a non-nil dataset and a non-nil error. In such case, the dataset will contain parts of the file that are parsable, and error will show the first error found by the parser.

func ReadDataSetInBytes

func ReadDataSetInBytes(data []byte, options ReadOptions) (*DataSet, error)

ReadDataSetInBytes is a shorthand for ReadDataSet(bytes.NewBuffer(data), len(data)).

On parse error, this function may return a non-nil dataset and a non-nil error. In such case, the dataset will contain parts of the file that are parsable, and error will show the first error found by the parser.

func (*DataSet) FindElementByName

func (f *DataSet) FindElementByName(name string) (*Element, error)

FindElementByName finds an element from the dataset given the element name, such as "PatientName".

func (*DataSet) FindElementByTag

func (f *DataSet) FindElementByTag(tag dicomtag.Tag) (*Element, error)

FindElementByTag finds an element from the dataset given its tag, such as Tag{0x0010, 0x0010}.

type DateInfo

type DateInfo struct {
	// Input string.
	Str string
	// Results of parsing Str
	Year  int // Year (CE), in range [0,9999]. E.g., 2015
	Month int // Month of year, in range [1,12].
	Day   int // Day of month, in range [1,31].
}

DateInfo is a result of parsing a date string.

func ParseDate

func ParseDate(s string) (startDate, endDate DateInfo, err error)

ParseDate parses a date string or date-range string as defined for the VR type "DA". See https://www.medicalconnections.co.uk/kb/Dicom_Query_DateTime_range.

If "s" is for a point in time, startDate will show that point, and endDate will be {Year:-1,Month:-1,Day:-1}. Is "s" is for a range of dates, [startDate, endDate] stores the range (note: both ends are closed, even though the DICOM spec isn't clear about it.).

func (DateInfo) String

func (d DateInfo) String() string

type DirectoryRecord

type DirectoryRecord struct {
	Path string
}

DirectoryRecord contains info about one DICOM file mentioned in DICOMDIR.

func ParseDICOMDIR

func ParseDICOMDIR(in io.Reader) (recs []DirectoryRecord, err error)

ParseDICOMDIR parses contents of a "DICOMDIR" stored in "in".

http://dicom.nema.org/medical/Dicom/2016b/output/chtml/part03/sect_F.2.2.2.html

type Element

type Element struct {
	// Tag is a pair of <group, element>. See tags.go for possible values.
	Tag dicomtag.Tag

	// List of values in the element. Their types depends on value
	// representation (VR) of the Tag; Cf. tag.go.
	//
	// If Tag==TagPixelData, len(Value)==1, and Value[0] is PixelDataInfo.
	// Else if Tag==TagItem, each Value[i] is a *Element.
	//    a value's Tag can be any (including TagItem, which represents a nested Item)
	// Else if VR=="SQ", Value[i] is a *Element, with Tag=TagItem.
	// Else if VR=="LT", or "UT", then len(Value)==1, and Value[0] is string
	// Else if VR=="DA", then len(Value)==1, and Value[0] is string. Use ParseDate() to parse the date string.
	// Else if VR=="US", Value[] is a list of uint16s (len(Value) matches VM of the Tag; PS 3.5 6.4)
	// Else if VR=="UL", Value[] is a list of uint32s (len(Value) matches VM of the Tag; PS 3.5 6.4)
	// Else if VR=="SS", Value[] is a list of int16s (len(Value) matches VM of the Tag; PS 3.5 6.4)
	// Else if VR=="SL", Value[] is a list of int32s (len(Value) matches VM of the Tag; PS 3.5 6.4)
	// Else if VR=="FL", Value[] is a list of float32s (len(Value) matches VM of the Tag; PS 3.5 6.4)
	// Else if VR=="FD", Value[] is a list of float64s (len(Value) matches VM of the Tag; PS 3.5 6.4)
	// Else if VR=="AT", Value[] is a list of Tag's. (len(Value) matches VM of the Tag; PS 3.5 6.4)
	// Else if VR=="OF", Value[] is a list of float32s
	// Else if VR=="OD", Value[] is a list of float64s
	// Else if VR=="OW" or "OB", len(Value)==1, and Value[0] is []byte.
	// Else, Value[] is a list of strings.
	//
	// Note: Use GetVRKind() to map VR string to the go representation of
	// VR.
	Value []interface{} // Value Multiplicity PS 3.5 6.4

	// VR defines the encoding of Value[] in two-letter alphabets, e.g.,
	// "AE", "UL". See P3.5 6.2. This field MUST be set.
	//
	// dicom.ReadElement() will fill this field with the VR of the tag,
	// either read from input stream (for explicit repl), or from the dicom
	// tag table (for implicit decl). This field need not be set in
	// WriteElement().
	//
	// Note: In a conformant DICOM file, the VR value of an element is
	// determined by its Tag, so this field is redundant.  This field is
	// still required because a non-conformant file with with explicitVR
	// encoding may have an element with VR that's different from the
	// standard's. In such case, this library honors the VR value found in
	// the file, and this field stores the VR used for parsing Values[].
	VR string

	// UndefinedLength is true if, in the DICOM file, the element is encoded
	// as having undefined length, and is delimited by end-sequence or
	// end-item element.  This flag is meaningful only if VR=="SQ" or
	// VR=="NA". Feel free to ignore this field if you don't understand what
	// this means.  It's one of the pointless complexities in the DICOM
	// standard.
	UndefinedLength bool
}

Element represents a single DICOM element. Use NewElement() to create a element denovo. Avoid creating a struct manually, because setting the VR field is a bit tricky.

func FindElementByName

func FindElementByName(elems []*Element, name string) (*Element, error)

FindElementByName finds an element with the given Element.Name in "elems" If not found, returns an error.

func FindElementByTag

func FindElementByTag(elems []*Element, tag dicomtag.Tag) (*Element, error)

FindElementByTag finds an element with the given Element.Tag in "elems" If not found, returns an error.

func MustNewElement

func MustNewElement(tag dicomtag.Tag, values ...interface{}) *Element

MustNewElement is similar to NewElement, but it crashes the process on any error.

func NewElement

func NewElement(tag dicomtag.Tag, values ...interface{}) (*Element, error)

NewElement creates a new Element with the given tag and values. The type of each each value must match the VR (value representation) of the tag (see tag_definition.go).

func ParseFileHeader

func ParseFileHeader(d *dicomio.Decoder) []*Element

ParseFileHeader consumes the DICOM magic header and metadata elements (whose elements with tag group==2) from a Dicom file. Errors are reported through d.Error().

func Query

func Query(ds *DataSet, f *Element) (match bool, matchedElem *Element, err error)

Query checks if the dataset matches a QR condition "f". If so, it returns the <true, matched element, nil>. If "f" asks for a universal match (i.e., empty query value), and the element for f.Tag doesn't exist, the function returns <true, nil, nil>. If "f" is malformed, the function returns <false, nil, error reason>.

func ReadElement

func ReadElement(d *dicomio.Decoder, options ReadOptions) *Element

ReadElement reads one DICOM data element. It returns three kind of values.

- On parse error, it returns nil and sets the error in d.Error().

- It returns (endOfDataElement, nil) if options.DropPixelData=true and the element is a pixel data, or it sees an element defined by options.StopAtTag.

- On successful parsing, it returns non-nil and non-endOfDataElement value.

func (*Element) GetString

func (e *Element) GetString() (string, error)

GetString gets a string value from an element. It returns an error if the element contains zero or >1 values, or the value is not a string.

func (*Element) GetStrings

func (e *Element) GetStrings() ([]string, error)

GetStrings returns the list of strings stored in the elment. Returns an error if the VR of e.Tag is not a string.

func (*Element) GetUInt16

func (e *Element) GetUInt16() (uint16, error)

GetUInt16 gets a uint16 value from an element. It returns an error if the element contains zero or >1 values, or the value is not a uint16.

func (*Element) GetUInt32

func (e *Element) GetUInt32() (uint32, error)

GetUInt32 gets a uint32 value from an element. It returns an error if the element contains zero or >1 values, or the value is not a uint32.

func (*Element) GetUint16s

func (e *Element) GetUint16s() ([]uint16, error)

GetUint16s returns the list of uint16 values stored in the elment. Returns an error if the VR of e.Tag is not a uint16.

func (*Element) GetUint32s

func (e *Element) GetUint32s() ([]uint32, error)

GetUint32s returns the list of uint32 values stored in the elment. Returns an error if the VR of e.Tag is not a uint32.

func (*Element) MustGetString

func (e *Element) MustGetString() string

MustGetString is similar to GetString(), but panics on error.

TODO(saito): Add other variants of MustGet<type>.

func (*Element) MustGetStrings

func (e *Element) MustGetStrings() []string

MustGetStrings is similar to GetStrings, but crashes the process on error.

func (*Element) MustGetUInt16

func (e *Element) MustGetUInt16() uint16

MustGetUInt16 is similar to GetUInt16, but panics on error.

func (*Element) MustGetUInt32

func (e *Element) MustGetUInt32() uint32

MustGetUInt32 is similar to GetUInt32, but panics on error.

func (*Element) MustGetUint16s

func (e *Element) MustGetUint16s() []uint16

MustGetUint16s is similar to GetUint16s, but crashes the process on error.

func (*Element) MustGetUint32s

func (e *Element) MustGetUint32s() []uint32

MustGetUint32s is similar to GetUint32s, but crashes the process on error.

func (*Element) String

func (e *Element) String() string

Stringer

type PixelDataInfo

type PixelDataInfo struct {
	Offsets []uint32 // BasicOffsetTable
	Frames  [][]byte // Parsed images
}

PixelDataInfo is the Element.Value payload for PixelData element.

func (PixelDataInfo) String

func (data PixelDataInfo) String() string

type ReadOptions

type ReadOptions struct {
	// DropPixelData will cause the parser to skip the PixelData element
	// (bulk images) in ReadDataSet.
	DropPixelData bool

	// ReturnTags is a whitelist of tags to return.
	ReturnTags []dicomtag.Tag

	// StopAtag defines a tag at which when read (or a tag with a greater
	// value than it is read), the program will stop parsing the dicom file.
	StopAtTag *dicomtag.Tag
}

ReadOptions defines how DataSets and Elements are parsed.

Directories

Path Synopsis
Package dicomio provides utility functions for encoding and decoding low-level DICOM data types, such as integers and strings.
Package dicomio provides utility functions for encoding and decoding low-level DICOM data types, such as integers and strings.
Package dicomlog performs logging for go-dicom or go-netdicom.
Package dicomlog performs logging for go-dicom or go-netdicom.
Package dicomtag enumerates element tags defined in the DICOM standard.
Package dicomtag enumerates element tags defined in the DICOM standard.
Package dicomuid defines standard UIDs, as defined in P3.6.
Package dicomuid defines standard UIDs, as defined in P3.6.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL