/
tag.go
210 lines (193 loc) · 5.3 KB
/
tag.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
// Package dicomtag enumerates element tags defined in the DICOM standard.
//
// ftp://medical.nema.org/medical/dicom/2011/11_06pu.pdf
package dicomtag
//go:generate ./generate_tag_definitions.py
//go:generate stringer -type VRKind
import (
"fmt"
"strconv"
"strings"
)
const (
GROUP_ItemSeq = 0xFFFE
)
// Tag is a <group, element> tuple that identifies an element type in a DICOM
// file. List of standard tags are defined in tag.go. See also:
//
// ftp://medical.nema.org/medical/dicom/2011/11_06pu.pdf
type Tag struct {
// Group and element are results of parsing the hex-pair tag, such as
// (1000,10008)
Group uint16
Element uint16
}
// Compare returns -1, 0, or 1 if t<other, t==other, t>other, respectively.
// Tags are ordered first by Group, then by Element.
func (t Tag) Compare(other Tag) int {
if t.Group < other.Group {
return -1
}
if t.Group > other.Group {
return 1
}
if t.Element < other.Element {
return -1
}
if t.Element > other.Element {
return 1
}
return 0
}
func IsPrivate(group uint16) bool {
return group%2 == 1
}
// String returns a string of form "(0008,1234)", where 0x0008 is t.Group,
// 0x1234 is t.Element.
func (t Tag) String() string {
return fmt.Sprintf("(%04x,%04x)", t.Group, t.Element)
}
// TagInfo stores detailed information about a Tag defined in the DICOM
// standard.
type TagInfo struct {
Tag Tag
// Data encoding "UL", "CS", etc.
VR string
// Human-readable name of the tag, e.g., "CommandDataSetType"
Name string
// Cardinality (# of values expected in the element)
VM string
}
// MetadataGroup is the value of Tag.Group for metadata tags.
const MetadataGroup = 2
// VRKind defines the golang encoding of a VR.
type VRKind int
const (
// VRStringList means the element stores a list of strings
VRStringList VRKind = iota
// VRBytes means the element stores a []byte
VRBytes
// VRString means the element stores a string
VRString
// VRUInt16List means the element stores a list of uint16s
VRUInt16List
// VRUInt32List means the element stores a list of uint32s
VRUInt32List
// VRInt16List means the element stores a list of int16s
VRInt16List
// VRInt32List element stores a list of int32s
VRInt32List
// VRFloat32List element stores a list of float32s
VRFloat32List
// VRFloat64List element stores a list of float64s
VRFloat64List
// VRSequence means the element stores a list of *Elements, w/ TagItem
VRSequence
// VRItem means the element stores a list of *Elements
VRItem
// VRTagList element stores a list of Tags
VRTagList
// VRDate means the element stores a date string. Use ParseDate() to
// parse the date string.
VRDate
// VRPixelData means the element stores a PixelDataInfo
VRPixelData
)
// GetVRKind returns the golang value encoding of an element with <tag, vr>.
func GetVRKind(tag Tag, vr string) VRKind {
if tag == Item {
return VRItem
} else if tag == PixelData {
return VRPixelData
}
switch vr {
case "DA":
return VRDate
case "AT":
return VRTagList
case "OW", "OB":
return VRBytes
case "LT", "UT":
return VRString
case "UL":
return VRUInt32List
case "SL":
return VRInt32List
case "US":
return VRUInt16List
case "SS":
return VRInt16List
case "FL":
return VRFloat32List
case "FD":
return VRFloat64List
case "SQ":
return VRSequence
default:
return VRStringList
}
}
// Find finds information about the given tag. If the tag is not part of
// the DICOM standard, or is retired from the standard, it returns an error.
func Find(tag Tag) (TagInfo, error) {
maybeInitTagDict()
entry, ok := tagDict[tag]
if !ok {
// (0000-u-ffff,0000) UL GenericGroupLength 1 GENERIC
if tag.Group%2 == 0 && tag.Element == 0x0000 {
entry = TagInfo{tag, "UL", "GenericGroupLength", "1"}
} else {
return TagInfo{}, fmt.Errorf("Could not find tag (0x%x, 0x%x) in dictionary", tag.Group, tag.Element)
}
}
return entry, nil
}
// MustFind is like FindTag, but panics on error.
func MustFind(tag Tag) TagInfo {
e, err := Find(tag)
if err != nil {
panic(fmt.Sprintf("tag %v not found: %s", tag, err))
}
return e
}
// FindByName finds information about the tag with the given name. If the tag is
// not part of the DICOM standard, or is retired from the standard, it returns
// an error.
//
// Example: FindTagByName("TransferSyntaxUID")
func FindByName(name string) (TagInfo, error) {
maybeInitTagDict()
for _, ent := range tagDict {
if ent.Name == name {
return ent, nil
}
}
return TagInfo{}, fmt.Errorf("Could not find tag with name %s", name)
}
// DebugString returns a human-readable diagnostic string for the tag, in format
// "(group, elem)[name]".
func DebugString(tag Tag) string {
e, err := Find(tag)
if err != nil {
if IsPrivate(tag.Group) {
return fmt.Sprintf("(%04x,%04x)[private]", tag.Group, tag.Element)
} else {
return fmt.Sprintf("(%04x,%04x)[??]", tag.Group, tag.Element)
}
}
return fmt.Sprintf("(%04x,%04x)[%s]", tag.Group, tag.Element, e.Name)
}
// Split a tag into a group and element, represented as a hex value
// TODO: support group ranges (6000-60FF,0803)
func parseTag(tag string) (Tag, error) {
parts := strings.Split(strings.Trim(tag, "()"), ",")
group, err := strconv.ParseInt(parts[0], 16, 0)
if err != nil {
return Tag{}, err
}
elem, err := strconv.ParseInt(parts[1], 16, 0)
if err != nil {
return Tag{}, err
}
return Tag{Group: uint16(group), Element: uint16(elem)}, nil
}