mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2024-04-21 12:32:08 +00:00
692 lines
19 KiB
Go
692 lines
19 KiB
Go
package dicom
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/suyashkumar/dicom/pkg/vrraw"
|
|
|
|
"github.com/suyashkumar/dicom/pkg/uid"
|
|
|
|
"github.com/suyashkumar/dicom/pkg/dicomio"
|
|
"github.com/suyashkumar/dicom/pkg/tag"
|
|
)
|
|
|
|
var (
|
|
// ErrorUnimplemented is for not yet finished things.
|
|
ErrorUnimplemented = errors.New("this functionality is not yet implemented")
|
|
// ErrorMismatchValueTypeAndVR is for when there's a discrepency betweeen the ValueType and what the VR specifies.
|
|
ErrorMismatchValueTypeAndVR = errors.New("ValueType does not match the VR required")
|
|
// ErrorUnexpectedValueType indicates an unexpected value type was seen.
|
|
ErrorUnexpectedValueType = errors.New("Unexpected ValueType")
|
|
// ErrorUnsupportedBitsPerSample indicates that the BitsPerSample in this
|
|
// Dataset is not supported when unpacking native PixelData.
|
|
ErrorUnsupportedBitsPerSample = errors.New("unsupported BitsPerSample value")
|
|
)
|
|
|
|
// Writer is a struct that allows element-by element writing to a DICOM writer.
|
|
type Writer struct {
|
|
writer dicomio.Writer
|
|
optSet *writeOptSet
|
|
}
|
|
|
|
// NewWriter returns a new Writer, that points to the provided io.Writer.
|
|
func NewWriter(out io.Writer, opts ...WriteOption) *Writer {
|
|
optSet := toWriteOptSet(opts...)
|
|
w := dicomio.NewWriter(out, nil, false)
|
|
|
|
return &Writer{
|
|
writer: w,
|
|
optSet: optSet,
|
|
}
|
|
}
|
|
|
|
// SetTransferSyntax sets the transfer syntax for the underlying dicomio.Writer.
|
|
func (w *Writer) SetTransferSyntax(bo binary.ByteOrder, implicit bool) {
|
|
w.writer.SetTransferSyntax(bo, implicit)
|
|
}
|
|
|
|
// writeDataset writes the provided DICOM dataset to the Writer, including headers if available.
|
|
func (w *Writer) writeDataset(ds Dataset) error {
|
|
var metaElems []*Element
|
|
for _, elem := range ds.Elements {
|
|
if elem.Tag.Group == tag.MetadataGroup {
|
|
metaElems = append(metaElems, elem)
|
|
}
|
|
}
|
|
|
|
err := writeFileHeader(w.writer, &ds, metaElems, *w.optSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
endian, implicit, err := ds.transferSyntax()
|
|
if (err != nil && err != ErrorElementNotFound) || (err == ErrorElementNotFound && !w.optSet.defaultMissingTransferSyntax) {
|
|
return err
|
|
}
|
|
|
|
if err == ErrorElementNotFound && w.optSet.defaultMissingTransferSyntax {
|
|
w.writer.SetTransferSyntax(binary.LittleEndian, true)
|
|
} else {
|
|
w.writer.SetTransferSyntax(endian, implicit)
|
|
}
|
|
|
|
for _, elem := range ds.Elements {
|
|
if elem.Tag.Group != tag.MetadataGroup {
|
|
err = writeElement(w.writer, elem, *w.optSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteElement writes a single DICOM element to a Writer.
|
|
func (w *Writer) WriteElement(e *Element) error {
|
|
return writeElement(w.writer, e, *w.optSet)
|
|
}
|
|
|
|
// Write will write the input DICOM dataset to the provided io.Writer as a complete DICOM (including any header
|
|
// information if available).
|
|
func Write(out io.Writer, ds Dataset, opts ...WriteOption) error {
|
|
w := NewWriter(out, opts...)
|
|
return w.writeDataset(ds)
|
|
}
|
|
|
|
// WriteOption represents an option that can be passed to WriteDataset. Later options will override previous options if
|
|
// applicable.
|
|
type WriteOption func(*writeOptSet)
|
|
|
|
// SkipVRVerification returns a WriteOption that skips VR verification.
|
|
func SkipVRVerification() WriteOption {
|
|
return func(set *writeOptSet) {
|
|
set.skipVRVerification = true
|
|
}
|
|
}
|
|
|
|
// SkipValueTypeVerification returns WriteOption function that skips checking ValueType
|
|
// for concurrency with VR and casting
|
|
func SkipValueTypeVerification() WriteOption {
|
|
return func(set *writeOptSet) {
|
|
set.skipValueTypeVerification = true
|
|
}
|
|
}
|
|
|
|
// DefaultMissingTransferSyntax returns a WriteOption indicating that a missing
|
|
// transferSyntax should not raise an error, and instead the default
|
|
// LittleEndian Implicit transfer syntax should be used and written out as a
|
|
// Metadata element in the Dataset.
|
|
func DefaultMissingTransferSyntax() WriteOption {
|
|
return func(set *writeOptSet) {
|
|
set.defaultMissingTransferSyntax = true
|
|
}
|
|
}
|
|
|
|
// writeOptSet represents the flattened option set after all WriteOptions have been applied.
|
|
type writeOptSet struct {
|
|
skipVRVerification bool
|
|
skipValueTypeVerification bool
|
|
defaultMissingTransferSyntax bool
|
|
}
|
|
|
|
func toWriteOptSet(opts ...WriteOption) *writeOptSet {
|
|
optSet := &writeOptSet{}
|
|
for _, opt := range opts {
|
|
opt(optSet)
|
|
}
|
|
return optSet
|
|
}
|
|
|
|
func writeFileHeader(w dicomio.Writer, ds *Dataset, metaElems []*Element, opts writeOptSet) error {
|
|
// File headers are always written in littleEndian explicit
|
|
w.SetTransferSyntax(binary.LittleEndian, false)
|
|
|
|
metaBytes := &bytes.Buffer{}
|
|
subWriter := dicomio.NewWriter(metaBytes, binary.LittleEndian, false)
|
|
tagsUsed := make(map[tag.Tag]bool)
|
|
tagsUsed[tag.FileMetaInformationGroupLength] = true
|
|
|
|
err := writeMetaElem(subWriter, tag.FileMetaInformationVersion, ds, &tagsUsed, opts)
|
|
if err != nil && err != ErrorElementNotFound {
|
|
return err
|
|
}
|
|
err = writeMetaElem(subWriter, tag.MediaStorageSOPClassUID, ds, &tagsUsed, opts)
|
|
if err != nil && err != ErrorElementNotFound {
|
|
return err
|
|
}
|
|
err = writeMetaElem(subWriter, tag.MediaStorageSOPInstanceUID, ds, &tagsUsed, opts)
|
|
if err != nil && err != ErrorElementNotFound {
|
|
return err
|
|
}
|
|
err = writeMetaElem(subWriter, tag.TransferSyntaxUID, ds, &tagsUsed, opts)
|
|
if err != nil && err != ErrorElementNotFound || err == ErrorElementNotFound && !opts.defaultMissingTransferSyntax {
|
|
return err
|
|
}
|
|
if err == ErrorElementNotFound && opts.defaultMissingTransferSyntax {
|
|
// Write the default transfer syntax
|
|
if err = writeElement(subWriter, mustNewElement(tag.TransferSyntaxUID, []string{uid.ImplicitVRLittleEndian}), opts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
for _, elem := range metaElems {
|
|
if elem.Tag.Group == tag.MetadataGroup {
|
|
if _, ok := tagsUsed[elem.Tag]; !ok {
|
|
err = writeElement(subWriter, elem, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := w.WriteZeros(128); err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteString(magicWord); err != nil {
|
|
return err
|
|
}
|
|
lengthElem, err := NewElement(tag.FileMetaInformationGroupLength, []int{len(metaBytes.Bytes())})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = writeElement(w, lengthElem, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = w.WriteBytes(metaBytes.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func writeElement(w dicomio.Writer, elem *Element, opts writeOptSet) error {
|
|
vr := elem.RawValueRepresentation
|
|
var err error
|
|
vr, err = verifyVROrDefault(elem.Tag, elem.RawValueRepresentation, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !opts.skipValueTypeVerification && elem.Value != nil {
|
|
err := verifyValueType(elem.Tag, elem.Value, vr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
length := elem.ValueLength
|
|
var valueData = &bytes.Buffer{}
|
|
if elem.Value != nil {
|
|
bo, implicit := w.GetTransferSyntax()
|
|
subWriter := dicomio.NewWriter(valueData, bo, implicit)
|
|
err := writeValue(subWriter, elem.Tag, elem.Value, elem.Value.ValueType(), vr, elem.ValueLength, opts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
length = uint32(len(valueData.Bytes()))
|
|
if elem.ValueLength == tag.VLUndefinedLength {
|
|
length = tag.VLUndefinedLength
|
|
}
|
|
}
|
|
|
|
err = encodeElementHeader(w, elem.Tag, vr, length)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if elem.Value != nil {
|
|
// Write the bytes to the original writer
|
|
err = w.WriteBytes(valueData.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeMetaElem(w dicomio.Writer, t tag.Tag, ds *Dataset, tagsUsed *map[tag.Tag]bool, optSet writeOptSet) error {
|
|
elem, err := ds.FindElementByTag(t)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = writeElement(w, elem, optSet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
(*tagsUsed)[t] = true
|
|
return nil
|
|
}
|
|
|
|
func verifyVROrDefault(t tag.Tag, vr string, opts writeOptSet) (string, error) {
|
|
// If our VR is not blank and we are skipping VF verification, nothing needs to be
|
|
// done, so we can immediately return.
|
|
if vr != "" && opts.skipVRVerification {
|
|
return vr, nil
|
|
}
|
|
|
|
// Otherwise, get our tag info.
|
|
tagInfo, err := tag.Find(t)
|
|
if err != nil {
|
|
// If we cannot find information about the tag and our VR is blank, we will use
|
|
// "UN" (Unknown). Otherwise we will fallback to the caller's VR and trust that
|
|
// they know more about this tag than we do. This could be a private tag, or a
|
|
// tag from a newer version of the DICOM spec.
|
|
if vr == "" {
|
|
vr = vrraw.Unknown
|
|
}
|
|
return vr, nil
|
|
}
|
|
|
|
if vr == "" {
|
|
// Otherwise if we did find it, and our VR is blank, we'll return the known vr
|
|
// we just pulled.
|
|
return tagInfo.VR, nil
|
|
}
|
|
|
|
// Verify the VR on the way out if the caller wants it.
|
|
if !opts.skipVRVerification && tagInfo.VR != vr {
|
|
return "", fmt.Errorf("ERROR dicomio.veryifyElement: VR mismatch for tag %v. Element.VR=%v, but DICOM standard defines VR to be %v",
|
|
tag.DebugString(t), vr, tagInfo.VR)
|
|
}
|
|
|
|
return vr, nil
|
|
}
|
|
|
|
func verifyValueType(t tag.Tag, value Value, vr string) error {
|
|
valueType := value.ValueType()
|
|
var ok bool
|
|
switch vr {
|
|
case vrraw.UnsignedShort, vrraw.UnsignedLong, vrraw.SignedLong, vrraw.SignedShort, vrraw.AttributeTag:
|
|
ok = valueType == Ints
|
|
case vrraw.Sequence:
|
|
ok = valueType == Sequences
|
|
case "NA":
|
|
ok = valueType == SequenceItem
|
|
case vrraw.OtherWord, vrraw.OtherByte:
|
|
if t == tag.PixelData {
|
|
ok = valueType == PixelData
|
|
} else {
|
|
ok = valueType == Bytes
|
|
}
|
|
case vrraw.FloatingPointSingle, vrraw.FloatingPointDouble:
|
|
ok = valueType == Floats
|
|
default:
|
|
ok = valueType == Strings
|
|
}
|
|
|
|
if !ok {
|
|
return fmt.Errorf("ValueType does not match the specified type in the VR")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeTag(w dicomio.Writer, t tag.Tag, vl uint32) error {
|
|
if vl%2 != 0 && vl != tag.VLUndefinedLength {
|
|
return fmt.Errorf("ERROR dicomio.writeTag: Value Length must be even, but for Tag=%v, ValueLength=%v",
|
|
tag.DebugString(t), vl)
|
|
}
|
|
if err := w.WriteUInt16(t.Group); err != nil {
|
|
return err
|
|
}
|
|
return w.WriteUInt16(t.Element)
|
|
}
|
|
|
|
func writeVRVL(w dicomio.Writer, t tag.Tag, vr string, vl uint32) error {
|
|
// Rectify Undefined Length VL
|
|
if vl == 0xffff {
|
|
vl = tag.VLUndefinedLength
|
|
}
|
|
|
|
if vr == vrraw.Sequence {
|
|
// We are going to write these out with undefined length always.
|
|
vl = tag.VLUndefinedLength
|
|
}
|
|
|
|
// We want to make sure there is any VR unless this is a Sequence delimiter.
|
|
if len(vr) != 2 && vl != tag.VLUndefinedLength && t != tag.SequenceDelimitationItem && t != tag.ItemDelimitationItem {
|
|
return fmt.Errorf("ERROR dicomio.writeVRVL: Value Representation must be of length 2, e.g. 'UN'. For tag=%v, it was RawValueRepresentation=%v",
|
|
tag.DebugString(t), vr)
|
|
}
|
|
|
|
// Write VR then VL
|
|
_, implicit := w.GetTransferSyntax()
|
|
if t.Group == tag.GroupSeqItem {
|
|
implicit = true
|
|
}
|
|
if !implicit { // Explicit
|
|
if err := w.WriteString(vr); err != nil {
|
|
return err
|
|
}
|
|
switch vr {
|
|
case "NA", vrraw.OtherByte, vrraw.OtherDouble, vrraw.OtherFloat,
|
|
vrraw.OtherLong, vrraw.OtherWord, vrraw.Sequence, vrraw.Unknown,
|
|
vrraw.UnlimitedCharacters, vrraw.UniversalResourceIdentifier,
|
|
vrraw.UnlimitedText:
|
|
if err := w.WriteZeros(2); err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteUInt32(vl); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
if err := w.WriteUInt16(uint16(vl)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
if err := w.WriteUInt32(vl); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeRawItem(w dicomio.Writer, data []byte) error {
|
|
length := uint32(len(data))
|
|
if err := writeTag(w, tag.Item, length); err != nil {
|
|
return err
|
|
}
|
|
if err := writeVRVL(w, tag.Item, "NA", length); err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteBytes(data); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeBasicOffsetTable(w dicomio.Writer, offsets []uint32) error {
|
|
byteOrder, implicit := w.GetTransferSyntax()
|
|
data := &bytes.Buffer{}
|
|
subWriter := dicomio.NewWriter(data, byteOrder, implicit)
|
|
for _, offset := range offsets {
|
|
if err := subWriter.WriteUInt32(offset); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return writeRawItem(w, data.Bytes())
|
|
}
|
|
|
|
func encodeElementHeader(w dicomio.Writer, t tag.Tag, vr string, vl uint32) error {
|
|
err := writeTag(w, t, vl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = writeVRVL(w, t, vr, vl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeValue(w dicomio.Writer, t tag.Tag, value Value, valueType ValueType, vr string, vl uint32, opts writeOptSet) error {
|
|
if vl == tag.VLUndefinedLength && valueType <= 2 { // strings, bytes or ints
|
|
return fmt.Errorf("encoding undefined-length element not yet supported: %v", t)
|
|
}
|
|
|
|
v := value.GetValue()
|
|
switch valueType {
|
|
case Strings:
|
|
return writeStrings(w, v.([]string), vr)
|
|
case Bytes:
|
|
return writeBytes(w, v.([]byte), vr)
|
|
case Ints:
|
|
return writeInts(w, v.([]int), vr)
|
|
case PixelData:
|
|
return writePixelData(w, t, value, vr, vl)
|
|
case SequenceItem:
|
|
return writeSequenceItem(w, t, v.([]*Element), vr, vl, opts)
|
|
case Sequences:
|
|
return writeSequence(w, t, v.([]*SequenceItemValue), vr, vl, opts)
|
|
case Floats:
|
|
return writeFloats(w, value, vr)
|
|
default:
|
|
return fmt.Errorf("ValueType not supported")
|
|
}
|
|
}
|
|
|
|
func writeStrings(w dicomio.Writer, values []string, vr string) error {
|
|
s := ""
|
|
for i, substr := range values {
|
|
if i > 0 {
|
|
s += "\\"
|
|
}
|
|
s += substr
|
|
}
|
|
if err := w.WriteString(s); err != nil {
|
|
return err
|
|
}
|
|
if len(s)%2 == 1 {
|
|
switch vr {
|
|
case vrraw.DateTime, vrraw.LongString, vrraw.LongText, vrraw.PersonName,
|
|
vrraw.ShortString, vrraw.ShortText, vrraw.UnlimitedText,
|
|
vrraw.DecimalString, vrraw.CodeString, vrraw.Time,
|
|
vrraw.IntegerString, vrraw.Unknown:
|
|
if err := w.WriteString(" "); err != nil { // http://dicom.nema.org/medical/dicom/current/output/html/part05.html#sect_6.2
|
|
return err
|
|
}
|
|
default:
|
|
if err := w.WriteByte(0); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeBytes(w dicomio.Writer, values []byte, vr string) error {
|
|
var err error
|
|
switch vr {
|
|
case vrraw.OtherWord:
|
|
err = writeOtherWordString(w, values)
|
|
case vrraw.OtherByte:
|
|
err = writeOtherByteString(w, values)
|
|
default:
|
|
return ErrorMismatchValueTypeAndVR
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeInts(w dicomio.Writer, values []int, vr string) error {
|
|
for _, value := range values {
|
|
switch vr {
|
|
// TODO(suyashkumar): consider additional validation of VR=AT elements.
|
|
case vrraw.UnsignedShort, vrraw.SignedShort, vrraw.AttributeTag:
|
|
if err := w.WriteUInt16(uint16(value)); err != nil {
|
|
return err
|
|
}
|
|
case vrraw.UnsignedLong, vrraw.SignedLong:
|
|
if err := w.WriteUInt32(uint32(value)); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return ErrorMismatchValueTypeAndVR
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeFloats(w dicomio.Writer, v Value, vr string) error {
|
|
if v.ValueType() != Floats {
|
|
return ErrorUnexpectedValueType
|
|
}
|
|
floats := MustGetFloats(v)
|
|
for _, fl := range floats {
|
|
switch vr {
|
|
case vrraw.FloatingPointSingle:
|
|
// NOTE: this is a conversion from float64 -> float32 which may lead to a loss in precision. The assumption
|
|
// is that the value sitting in the float64 was originally at float32 precision if the VR is FL for this
|
|
// element. We will need to revisit this. Maybe we can detect if there will be a loss of precision and if so
|
|
// indicate an error or warning.
|
|
err := w.WriteFloat32(float32(fl))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
case vrraw.FloatingPointDouble:
|
|
err := w.WriteFloat64(fl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writePixelData(w dicomio.Writer, t tag.Tag, value Value, vr string, vl uint32) error {
|
|
image := MustGetPixelDataInfo(value)
|
|
if vl == tag.VLUndefinedLength {
|
|
if err := writeBasicOffsetTable(w, image.Offsets); err != nil {
|
|
return err
|
|
}
|
|
for _, frame := range image.Frames {
|
|
if err := writeRawItem(w, frame.EncapsulatedData.Data); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
err := encodeElementHeader(w, tag.SequenceDelimitationItem, "", 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
numFrames := len(image.Frames)
|
|
numPixels := len(image.Frames[0].NativeData.Data)
|
|
numValues := len(image.Frames[0].NativeData.Data[0])
|
|
// Total required buffer length in bytes:
|
|
length := numFrames * numPixels * numValues * image.Frames[0].NativeData.BitsPerSample / 8
|
|
|
|
buf := &bytes.Buffer{}
|
|
buf.Grow(length)
|
|
for frame := 0; frame < numFrames; frame++ {
|
|
for pixel := 0; pixel < numPixels; pixel++ {
|
|
for value := 0; value < numValues; value++ {
|
|
pixelValue := image.Frames[frame].NativeData.Data[pixel][value]
|
|
switch image.Frames[frame].NativeData.BitsPerSample {
|
|
case 8:
|
|
if err := binary.Write(buf, binary.LittleEndian, uint8(pixelValue)); err != nil {
|
|
return err
|
|
}
|
|
case 16:
|
|
if err := binary.Write(buf, binary.LittleEndian, uint16(pixelValue)); err != nil {
|
|
return err
|
|
}
|
|
case 32:
|
|
if err := binary.Write(buf, binary.LittleEndian, uint32(pixelValue)); err != nil {
|
|
return err
|
|
}
|
|
default:
|
|
return ErrorUnsupportedBitsPerSample
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if err := w.WriteBytes(buf.Bytes()); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
var sequenceDelimitationItem = &Element{
|
|
Tag: tag.SequenceDelimitationItem,
|
|
ValueLength: 0, // This should be 00000000H in base32
|
|
}
|
|
|
|
func writeSequence(w dicomio.Writer, t tag.Tag, values []*SequenceItemValue, vr string, vl uint32, opts writeOptSet) error {
|
|
// We always write out sequences using the undefined length encoding.
|
|
// Note: we currently don't validate that the length of the sequence matches
|
|
// the VL if it's not undefined VL.
|
|
// More details about the sequence structure can be found at:
|
|
// http://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.5.html
|
|
|
|
// Write out the items.
|
|
for _, seqItem := range values {
|
|
if err := writeSequenceItem(w, t, seqItem.elements, vr, vl, opts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write Sequence Delimitation Item as implicit VR
|
|
oldBO, oldImplicit := w.GetTransferSyntax()
|
|
w.SetTransferSyntax(oldBO, true)
|
|
if err := writeElement(w, sequenceDelimitationItem, opts); err != nil {
|
|
return err
|
|
}
|
|
w.SetTransferSyntax(oldBO, oldImplicit) // Return TS to what it was before.
|
|
|
|
return nil
|
|
}
|
|
|
|
var sequenceItemDelimitationItem = &Element{
|
|
Tag: tag.ItemDelimitationItem,
|
|
ValueLength: 0, // This should be 00000000H in base32
|
|
}
|
|
|
|
var item = &Element{
|
|
Tag: tag.Item,
|
|
ValueLength: tag.VLUndefinedLength,
|
|
}
|
|
|
|
func writeSequenceItem(w dicomio.Writer, t tag.Tag, values []*Element, vr string, vl uint32, opts writeOptSet) error {
|
|
// Write out item header.
|
|
if err := writeElement(w, item, opts); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Write out nested Dataset elements.
|
|
for _, elem := range values {
|
|
if err := writeElement(w, elem, opts); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// Write ItemDelimitationItem.
|
|
return writeElement(w, sequenceItemDelimitationItem, opts)
|
|
}
|
|
|
|
func writeOtherWordString(w dicomio.Writer, data []byte) error {
|
|
if len(data)%2 != 0 {
|
|
return ErrorOWRequiresEvenVL
|
|
}
|
|
bo, _ := w.GetTransferSyntax()
|
|
r, err := dicomio.NewReader(bufio.NewReader(bytes.NewBuffer(data)), bo, int64(len(data)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i := 0; i < int(len(data)/2); i++ {
|
|
v, err := r.ReadUInt16()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := w.WriteUInt16(v); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func writeOtherByteString(w dicomio.Writer, data []byte) error {
|
|
if err := w.WriteBytes(data); err != nil {
|
|
return err
|
|
}
|
|
if len(data)%2 == 1 {
|
|
if err := w.WriteByte(0); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|