forked from suyashkumar/dicom
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dataset.go
227 lines (206 loc) · 7.59 KB
/
dataset.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
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
package dicom
import (
"encoding/binary"
"errors"
"fmt"
"strings"
"github.com/suyashkumar/dicom/pkg/tag"
"github.com/suyashkumar/dicom/pkg/uid"
)
// ErrorElementNotFound indicates that the requested element was not found in
// the Dataset.
var ErrorElementNotFound = errors.New("element not found")
// Dataset represents a DICOM dataset, see
// http://dicom.nema.org/medical/dicom/current/output/html/part05.html#chapter_7.
//
// This Dataset representation is JSON serializable out of the box
// (implements json.Marshaler) and will also pretty print as a string nicely (see String example).
// This Dataset includes several helper methods to find Elements within this dataset or iterate over every Element
// within this Dataset (including Elements nested within Sequences).
type Dataset struct {
Elements []*Element `json:"elements"`
}
// 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
}
func (d *Dataset) transferSyntax() (binary.ByteOrder, bool, error) {
elem, err := d.FindElementByTag(tag.TransferSyntaxUID)
if err != nil {
return nil, false, err
}
value, ok := elem.Value.GetValue().([]string)
if !ok || len(value) != 1 {
return nil, false, fmt.Errorf("failed to retrieve TransferSyntaxUID. Unable to cast elem.Value to []string")
}
transferSyntaxUID := value[0]
return uid.ParseTransferSyntaxUID(transferSyntaxUID)
}
// FindElementByTagNested searches through the dataset and returns a pointer to the matching element.
// This call searches through a flat representation of the dataset, including within sequences.
func (d *Dataset) FindElementByTagNested(tag tag.Tag) (*Element, error) {
for e := range d.FlatIterator() {
if e.Tag == tag {
return e, nil
}
}
return nil, ErrorElementNotFound
}
// FlatIterator will be deprecated soon in favor of
// Dataset.FlatStatefulIterator. Use FlatStatefulIterator instead of this,
// unless the channel API really makes your life a lot easier (and let the
// maintainers know on GitHub).
//
// FlatIterator returns a channel upon which every element in this Dataset will
// be sent, including elements nested inside sequences.
//
// If for some reason your code will not exhaust the iterator (read all
// elements), be sure to call ExhaustElementChannel to prevent leaving the
// underlying Goroutine alive (you can safely do this in a defer).
// c := dataset.FlatIterator()
// defer ExhaustElementChannel(c)
// for elem := range c {
// // Even if you exit before reading everything in c (e.g. due to an
// // error)
// // things will be ok.
// }
//
// Note that the sequence element itself is sent on the channel in addition to
// the child elements in the sequence.
// TODO(suyashkumar): decide if the sequence element itself should be sent or not
func (d *Dataset) FlatIterator() <-chan *Element {
elemChan := make(chan *Element)
go func() {
flatElementsIterator(d.Elements, elemChan)
close(elemChan)
}()
return elemChan
}
// ExhaustElementChannel exhausts the channel iterator returned by
// Dataset.FlatIterator, ensuring that the underlying Go routine completes.
// When using Dataset.FlatIterator, if your program will exit for some reason
// without reading all the elements of the channel, you should be sure to call
// this function to prevent a phantom Goroutine.
// Or, if you don't need the channel interface, simply use
// Dataset.FlatStatefulIterator.
func ExhaustElementChannel(c <-chan *Element) {
for range c {
}
}
func flatElementsIterator(elems []*Element, elemChan chan<- *Element) {
for _, elem := range elems {
if elem.Value.ValueType() == Sequences {
elemChan <- elem
for _, seqItem := range elem.Value.GetValue().([]*SequenceItemValue) {
flatElementsIterator(seqItem.elements, elemChan)
}
continue
}
elemChan <- elem
}
}
// FlatDatasetIterator is a stateful iterator over a Dataset.
type FlatDatasetIterator struct {
flattenedDataset []*Element
idx int
}
// HasNext indicates if the iterator as another element.
func (f *FlatDatasetIterator) HasNext() bool {
return f.idx < len(f.flattenedDataset)
}
// Next gets and returns the next element in the iterator.
func (f *FlatDatasetIterator) Next() *Element {
elem := f.flattenedDataset[f.idx]
f.idx++
return elem
}
// FlatStatefulIterator returns a stateful iterator that adheres to
// FlatDatasetIterator interface. This allows the caller to iterate over every
// element in the dataset, including elements nested inside sequences.
//
// Important note: if the Dataset changes during the iteration (e.g. if elements
// are added or removed), those elements will not be included until a new
// iterator is created.
//
// If you don't need to receive elements on a channel, and don't want to worry
// about always exhausting this iterator, this is the best and safest way to
// iterate over a Dataset. Unlike FlatIterator(), no special cleanup or channel
// exhausting is needed with this iterator.
func (d *Dataset) FlatStatefulIterator() *FlatDatasetIterator {
return &FlatDatasetIterator{flattenedDataset: flatSliceBuilder(d.Elements)}
}
func flatSliceBuilder(datasetElems []*Element) []*Element {
var current []*Element
for _, elem := range datasetElems {
if elem.Value.ValueType() == Sequences {
current = append(current, elem)
for _, seqItem := range elem.Value.GetValue().([]*SequenceItemValue) {
current = append(current, flatSliceBuilder(seqItem.elements)...)
}
continue
}
current = append(current, elem)
}
return current
}
// String returns a printable representation of this dataset as a string, including printing out elements nested inside
// sequence elements.
func (d *Dataset) String() string {
var b strings.Builder
b.Grow(len(d.Elements) * 100) // Underestimate of the size of the final string in an attempt to limit buffer copying
for elem := range d.flatIteratorWithLevel() {
tabs := buildTabs(elem.l)
var tagName string
if tagInfo, err := tag.Find(elem.e.Tag); err == nil {
tagName = tagInfo.Name
}
b.WriteString(fmt.Sprintf("%s[\n", tabs))
b.WriteString(fmt.Sprintf("%s Tag: %s\n", tabs, elem.e.Tag))
b.WriteString(fmt.Sprintf("%s Tag Name: %s\n", tabs, tagName))
b.WriteString(fmt.Sprintf("%s VR: %s\n", tabs, elem.e.ValueRepresentation))
b.WriteString(fmt.Sprintf("%s VR Raw: %s\n", tabs, elem.e.RawValueRepresentation))
b.WriteString(fmt.Sprintf("%s VL: %d\n", tabs, elem.e.ValueLength))
b.WriteString(fmt.Sprintf("%s Value: %d\n", tabs, elem.e.Value))
b.WriteString(fmt.Sprintf("%s]\n\n", tabs))
}
return b.String()
}
type elementWithLevel struct {
e *Element
// l represents the nesting level of the Element
l uint
}
func (d *Dataset) flatIteratorWithLevel() <-chan *elementWithLevel {
elemChan := make(chan *elementWithLevel)
go func() {
flatElementsIteratorWithLevel(d.Elements, 0, elemChan)
close(elemChan)
}()
return elemChan
}
func flatElementsIteratorWithLevel(elems []*Element, level uint, eWithLevelChan chan<- *elementWithLevel) {
for _, elem := range elems {
if elem.Value.ValueType() == Sequences {
eWithLevelChan <- &elementWithLevel{elem, level}
for _, seqItem := range elem.Value.GetValue().([]*SequenceItemValue) {
flatElementsIteratorWithLevel(seqItem.elements, level+1, eWithLevelChan)
}
continue
}
eWithLevelChan <- &elementWithLevel{elem, level}
}
}
func buildTabs(number uint) string {
var b strings.Builder
b.Grow(int(number))
for i := 0; i < int(number); i++ {
b.WriteString("\t")
}
return b.String()
}