Files

377 lines
12 KiB
Go

package dicom
import (
"encoding/json"
"errors"
"fmt"
"log"
"github.com/suyashkumar/dicom/pkg/frame"
"github.com/suyashkumar/dicom/pkg/tag"
)
// ErrorUnexpectedDataType indicates that an unexpected (not allowed) data type was sent to NewValue.
var ErrorUnexpectedDataType = errors.New("the type of the data was unexpected or not allowed")
// Element represents a standard DICOM data element (see the DICOM standard:
// http://dicom.nema.org/medical/dicom/current/output/html/part05.html#sect_7.1 ).
// This Element can be serialized to JSON out of the box and pretty printed as a string via the String() method.
type Element struct {
Tag tag.Tag `json:"tag"`
ValueRepresentation tag.VRKind `json:"VR"`
RawValueRepresentation string `json:"rawVR"`
ValueLength uint32 `json:"valueLength"`
Value Value `json:"value"`
}
func (e *Element) String() string {
var tagName string
if tagInfo, err := tag.Find(e.Tag); err == nil {
tagName = tagInfo.Name
}
return fmt.Sprintf("[\n Tag: %s\n Tag Name: %s\n VR: %s\n VR Raw: %s\n VL: %d\n Value: %s\n]\n\n",
e.Tag.String(),
tagName,
e.ValueRepresentation.String(),
e.RawValueRepresentation,
e.ValueLength,
e.Value.String())
}
// Value represents a DICOM value. The underlying data that a Value stores can be determined by inspecting its
// ValueType. DICOM values typically can be one of many types (ints, strings, bytes, sequences of other elements, etc),
// so this Value interface attempts to represent this as canoically as possible in Golang (since generics do not exist
// yet).
//
// Value is JSON serializable out of the box (implements json.Marshaler).
//
// If necessary, a Value's data can be efficiently unpacked by inspecting its underlying ValueType and either using a
// Golang type assertion or using the helper functions provided (like MustGetStrings). Because for each ValueType there
// is exactly one underlying Golang type, this should be safe, efficient, and straightforward.
//
// switch(myvalue.ValueType()) {
// case dicom.Strings:
// // We know the underlying Golang type is []string
// fmt.Println(dicom.MustGetStrings(myvalue)[0])
// // or
// s := myvalue.GetValue().([]string)
// break;
// case dicom.Bytes:
// // ...
// }
//
// Unpacking the data like above is only necessary if something specific needs to be done with the underlying data.
// See the Element and Dataset examples as well to see how to work with this kind of data, and common patterns for doing
// so.
type Value interface {
// All types that can be a "Value" for an element will implement this empty method, similar to how protocol buffers
// implement "oneof" in Go
isElementValue()
// ValueType returns the underlying ValueType of this Value. This can be used to unpack the underlying data in this
// Value.
ValueType() ValueType
// GetValue returns the underlying value that this Value holds. What type is returned here can be determined exactly
// from the ValueType() of this Value (see the ValueType godoc).
GetValue() interface{} // TODO: rename to Get to read cleaner
String() string
MarshalJSON() ([]byte, error)
}
// NewValue creates a new DICOM value for the supplied data. Likely most useful
// if creating an Element in testing or write scenarios.
//
// Data must be one of the following types, otherwise and error will be returned
// (ErrorUnexpectedDataType).
//
// Acceptable types: []int, []string, []byte, []float64, PixelDataInfo,
// [][]*Element (represents a sequence, which contains several
// items which each contain several elements).
func NewValue(data interface{}) (Value, error) {
switch data.(type) {
case []int:
return &intsValue{value: data.([]int)}, nil
case []string:
return &stringsValue{value: data.([]string)}, nil
case []byte:
return &bytesValue{value: data.([]byte)}, nil
case PixelDataInfo:
return &pixelDataValue{PixelDataInfo: data.(PixelDataInfo)}, nil
case []float64:
return &floatsValue{value: data.([]float64)}, nil
case [][]*Element:
items := data.([][]*Element)
sequenceItems := make([]*SequenceItemValue, 0, len(items))
for _, item := range items {
sequenceItems = append(sequenceItems, &SequenceItemValue{elements: item})
}
return &sequencesValue{value: sequenceItems}, nil
default:
return nil, ErrorUnexpectedDataType
}
}
func mustNewValue(data interface{}) Value {
v, err := NewValue(data)
if err != nil {
panic(err)
}
return v
}
// NewElement creates a new DICOM Element with the supplied tag and with a value
// built from the provided data. The data can be one of the types that is
// acceptable to NewValue.
func NewElement(t tag.Tag, data interface{}) (*Element, error) {
tagInfo, err := tag.Find(t)
if err != nil {
return nil, err
}
rawVR := tagInfo.VR
value, err := NewValue(data)
if err != nil {
return nil, err
}
return &Element{
Tag: t,
ValueRepresentation: tag.GetVRKind(t, rawVR),
RawValueRepresentation: rawVR,
Value: value,
}, nil
}
func mustNewElement(t tag.Tag, data interface{}) *Element {
elem, err := NewElement(t, data)
if err != nil {
log.Panic(err)
}
return elem
}
func mustNewPrivateElement(t tag.Tag, rawVR string, data interface{}) *Element {
value, err := NewValue(data)
if err != nil {
log.Panic(fmt.Errorf("error creating value: %w", err))
}
return &Element{
Tag: t,
ValueRepresentation: tag.GetVRKind(t, rawVR),
RawValueRepresentation: rawVR,
ValueLength: 0,
Value: value,
}
}
// ValueType is a type that represents the type of a Value. It is an enumerated
// set, and the set of values can be found below.
type ValueType int
// Possible ValueTypes that represent the different value types for information parsed into DICOM element values.
// Each ValueType corresponds to exactly one underlying Golang type.
const (
// Strings represents an underlying value of []string
Strings ValueType = iota
// Bytes represents an underlying value of []byte
Bytes
// Ints represents an underlying value of []int
Ints
// PixelData represents an underlying value of PixelDataInfo
PixelData
// SequenceItem represents an underlying value of []*Element
SequenceItem
// Sequences represents an underlying value of []SequenceItem
Sequences
// Floats represents an underlying value of []float64
Floats
)
// Begin definitions of Values:
// bytesValue represents a value of []byte.
type bytesValue struct {
value []byte
}
func (b *bytesValue) isElementValue() {}
func (b *bytesValue) ValueType() ValueType { return Bytes }
func (b *bytesValue) GetValue() interface{} { return b.value }
func (b *bytesValue) String() string {
return fmt.Sprintf("%v", b.value)
}
func (b *bytesValue) MarshalJSON() ([]byte, error) {
return json.Marshal(b.value)
}
// stringsValue represents a value of []string.
type stringsValue struct {
value []string
}
func (s *stringsValue) isElementValue() {}
func (s *stringsValue) ValueType() ValueType { return Strings }
func (s *stringsValue) GetValue() interface{} { return s.value }
func (s *stringsValue) String() string {
return fmt.Sprintf("%v", s.value)
}
func (s *stringsValue) MarshalJSON() ([]byte, error) {
return json.Marshal(s.value)
}
// intsValue represents a value of []int.
type intsValue struct {
value []int
}
func (s *intsValue) isElementValue() {}
func (s *intsValue) ValueType() ValueType { return Ints }
func (s *intsValue) GetValue() interface{} { return s.value }
func (s *intsValue) String() string {
return fmt.Sprintf("%v", s.value)
}
func (s *intsValue) MarshalJSON() ([]byte, error) {
return json.Marshal(s.value)
}
// floatsValue represents a value of []float64.
type floatsValue struct {
value []float64
}
func (s *floatsValue) isElementValue() {}
func (s *floatsValue) ValueType() ValueType { return Floats }
func (s *floatsValue) GetValue() interface{} { return s.value }
func (s *floatsValue) String() string {
return fmt.Sprintf("%v", s.value)
}
func (s *floatsValue) MarshalJSON() ([]byte, error) {
return json.Marshal(s.value)
}
// SequenceItemValue is a Value that represents a single Sequence Item. Learn
// more about Sequences at
// http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.5.html.
type SequenceItemValue struct {
elements []*Element
}
func (s *SequenceItemValue) isElementValue() {}
// ValueType returns the underlying ValueType of this Value. This can be used
// to unpack the underlying data in this Value.
func (s *SequenceItemValue) ValueType() ValueType { return SequenceItem }
// GetValue returns the underlying value that this Value holds. What type is
// returned here can be determined exactly from the ValueType() of this Value
// (see the ValueType godoc).
func (s *SequenceItemValue) GetValue() interface{} { return s.elements }
// String is used to get a string representation of this struct.
func (s *SequenceItemValue) String() string {
// TODO: consider adding more sophisticated formatting
return fmt.Sprintf("%+v", s.elements)
}
// MarshalJSON is the method used to marshal this struct to JSON.
func (s *SequenceItemValue) MarshalJSON() ([]byte, error) {
return json.Marshal(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)
}
func (s *sequencesValue) MarshalJSON() ([]byte, error) {
return json.Marshal(s.value)
}
// PixelDataInfo is a representation of DICOM PixelData.
type PixelDataInfo struct {
Frames []frame.Frame
IsEncapsulated bool `json:"isEncapsulated"`
Offsets []uint32
}
// pixelDataValue represents DICOM PixelData
type pixelDataValue struct {
PixelDataInfo
}
func (e *pixelDataValue) isElementValue() {}
func (e *pixelDataValue) ValueType() ValueType { return PixelData }
func (e *pixelDataValue) GetValue() interface{} { return e.PixelDataInfo }
func (e *pixelDataValue) String() string {
// TODO: consider adding more sophisticated formatting
return ""
}
func (e *pixelDataValue) MarshalJSON() ([]byte, error) {
return json.Marshal(e.PixelDataInfo)
}
// MustGetInts attempts to get an Ints value out of the provided value, and will
// panic if it is unable to do so.
func MustGetInts(v Value) []int {
if v.ValueType() != Ints {
log.Panicf("MustGetInts expected ValueType of Ints, got: %v", v.ValueType())
}
return v.GetValue().([]int)
}
// MustGetStrings attempts to get a Strings value out of the provided Value, and
// will panic if it is unable to do so.
func MustGetStrings(v Value) []string {
if v.ValueType() != Strings {
log.Panicf("MustGetStrings expected ValueType of Strings, got: %v", v.ValueType())
}
return v.GetValue().([]string)
}
// MustGetBytes attempts to get a Bytes value out of the provided Value, and
// will panic if it is unable to do so.
func MustGetBytes(v Value) []byte {
if v.ValueType() != Bytes {
log.Panicf("MustGetBytes expected ValueType of Bytes, got: %v", v.ValueType())
}
return v.GetValue().([]byte)
}
// MustGetFloats attempts to get a Floats value out of the provided Value, and
// will panic if it is unable to do so.
func MustGetFloats(v Value) []float64 {
if v.ValueType() != Floats {
log.Panicf("MustGetFloats expected ValueType of Floats, got: %v", v.ValueType())
}
return v.GetValue().([]float64)
}
// MustGetPixelDataInfo attempts to get a PixelDataInfo value out of the
// provided Value, and will panic if it is unable to do so.
func MustGetPixelDataInfo(v Value) PixelDataInfo {
if v.ValueType() != PixelData {
log.Panicf("MustGetPixelDataInfo expected ValueType of PixelData, got: %v", v.ValueType())
}
return v.GetValue().(PixelDataInfo)
}
// allValues is used for tests that need to pass in instances of the unexported
// value structs to cmp.AllowUnexported.
var allValues = []interface{}{
floatsValue{},
intsValue{},
stringsValue{},
pixelDataValue{},
sequencesValue{},
bytesValue{},
SequenceItemValue{},
}