From 77468ebc9e4bbfdc67e28ef579d1db43da121d61 Mon Sep 17 00:00:00 2001 From: Suyash Kumar Date: Sun, 26 May 2024 23:59:30 -0400 Subject: [PATCH] Initial hacking to explore multi-value native pixel data--still a lot of cleanup required and things to explore, tests to fix, things to rename, etc --- element.go | 8 ++- element_test.go | 40 +++++------ go.mod | 7 +- go.sum | 4 ++ pkg/frame/encapsulated.go | 4 +- pkg/frame/frame.go | 14 ++-- pkg/frame/native.go | 85 +++++++++++++++++------ pkg/frame/native_test.go | 26 +++---- read.go | 84 +++++++++++++++-------- read_test.go | 140 +++++++++++++++++++------------------- write.go | 12 ++-- write_test.go | 80 +++++++++++----------- 12 files changed, 295 insertions(+), 209 deletions(-) diff --git a/element.go b/element.go index 2682a311..abff0552 100644 --- a/element.go +++ b/element.go @@ -424,8 +424,10 @@ type PixelDataInfo struct { // IntentionallyUnprocessed indicates that the PixelData Value was actually // read (as opposed to skipped over, as in IntentionallySkipped above) and - // blindly placed into RawData (if possible). Writing this element back out - // should work. This will be true if the + // blindly placed into UnprocessedValueData (if possible). Writing this + // element back out using the dicom.Writer API should work. + // + // IntentionallyUnprocessed will be true if the // dicom.SkipProcessingPixelDataValue flag is set with a PixelData tag. IntentionallyUnprocessed bool `json:"intentionallyUnprocessed"` // UnprocessedValueData holds the unprocessed Element value data if @@ -451,7 +453,7 @@ func (p *pixelDataValue) String() string { if p.ParseErr != nil { return fmt.Sprintf("parseErr err=%s FramesLength=%d Frame[0] size=%d", p.ParseErr.Error(), len(p.Frames), len(p.Frames[0].EncapsulatedData.Data)) } - return fmt.Sprintf("FramesLength=%d FrameSize rows=%d cols=%d", len(p.Frames), p.Frames[0].NativeData.Rows, p.Frames[0].NativeData.Cols) + return fmt.Sprintf("FramesLength=%d FrameSize rows=%d cols=%d", len(p.Frames), p.Frames[0].NativeData.Rows(), p.Frames[0].NativeData.Cols()) } func (p *pixelDataValue) MarshalJSON() ([]byte, error) { diff --git a/element_test.go b/element_test.go index 3d825a8c..a57c96cd 100644 --- a/element_test.go +++ b/element_test.go @@ -245,11 +245,11 @@ func TestElement_Equals(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {4}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {4}}, }, }, }, @@ -259,11 +259,11 @@ func TestElement_Equals(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {4}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {4}}, }, }, }, @@ -277,11 +277,11 @@ func TestElement_Equals(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {6}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {6}}, }, }, }, @@ -291,11 +291,11 @@ func TestElement_Equals(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {4}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {4}}, }, }, }, diff --git a/go.mod b/go.mod index cc32e7da..420db23e 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,11 @@ go 1.18 require ( github.com/golang/mock v1.4.4 - github.com/google/go-cmp v0.5.2 + github.com/google/go-cmp v0.6.0 golang.org/x/text v0.3.8 ) -require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect +require ( + golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d // indirect + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect +) diff --git a/go.sum b/go.sum index 6cf56017..31f70fe0 100644 --- a/go.sum +++ b/go.sum @@ -2,7 +2,11 @@ github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d h1:N0hmiNbwsSNwHBAvR3QB5w25pUwH4tK0Y/RltD1j1h4= +golang.org/x/exp v0.0.0-20240525044651-4c93da0ed11d/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= diff --git a/pkg/frame/encapsulated.go b/pkg/frame/encapsulated.go index 32517740..177bd787 100644 --- a/pkg/frame/encapsulated.go +++ b/pkg/frame/encapsulated.go @@ -22,13 +22,13 @@ func (e *EncapsulatedFrame) GetEncapsulatedFrame() (*EncapsulatedFrame, error) { // GetNativeFrame returns ErrorFrameTypeNotPresent, because this struct does // not hold a NativeFrame. -func (e *EncapsulatedFrame) GetNativeFrame() (*NativeFrame, error) { +func (e *EncapsulatedFrame) GetNativeFrame() (INativeFrame, error) { return nil, ErrorFrameTypeNotPresent } // GetImage returns a Go image.Image from the underlying frame. func (e *EncapsulatedFrame) GetImage() (image.Image, error) { - // Decoding the data to only re-encode it as a JPEG *without* modifications + // Decoding the Data to only re-encode it as a JPEG *without* modifications // is very inefficient. If all you want to do is write the JPEG to disk, // you should fetch the EncapsulatedFrame and grab the []byte Data from // there. diff --git a/pkg/frame/frame.go b/pkg/frame/frame.go index 5532de1a..9deb58bf 100644 --- a/pkg/frame/frame.go +++ b/pkg/frame/frame.go @@ -16,12 +16,12 @@ var ErrorFrameTypeNotPresent = errors.New("the frame type you requested is not p type CommonFrame interface { // GetImage gets this frame as an image.Image. Beware that the underlying frame may perform // some default rendering and conversions. Operate on the raw NativeFrame or EncapsulatedFrame - // if you need to do some custom rendering work or want the data from the dicom. + // if you need to do some custom rendering work or want the Data from the dicom. GetImage() (image.Image, error) // IsEncapsulated indicates if the underlying Frame is an EncapsulatedFrame. IsEncapsulated() bool // GetNativeFrame attempts to get the underlying NativeFrame (or returns an error) - GetNativeFrame() (*NativeFrame, error) + GetNativeFrame() (INativeFrame, error) // GetEncapsulatedFrame attempts to get the underlying EncapsulatedFrame (or returns an error) GetEncapsulatedFrame() (*EncapsulatedFrame, error) } @@ -33,12 +33,12 @@ type Frame struct { // Encapsulated indicates whether the underlying frame is encapsulated or // not. Encapsulated bool - // EncapsulatedData holds the encapsulated data for this frame if + // EncapsulatedData holds the encapsulated Data for this frame if // Encapsulated is set to true. EncapsulatedData EncapsulatedFrame - // NativeData holds the native data for this frame if Encapsulated is set + // NativeData holds the native Data for this frame if Encapsulated is set // to false. - NativeData NativeFrame + NativeData INativeFrame } // IsEncapsulated indicates if the frame is encapsulated or not. @@ -46,7 +46,7 @@ func (f *Frame) IsEncapsulated() bool { return f.Encapsulated } // GetNativeFrame returns a NativeFrame from this frame. If the underlying frame // is not a NativeFrame, ErrorFrameTypeNotPresent will be returned. -func (f *Frame) GetNativeFrame() (*NativeFrame, error) { +func (f *Frame) GetNativeFrame() (INativeFrame, error) { if f.Encapsulated { return f.EncapsulatedData.GetNativeFrame() } @@ -84,7 +84,7 @@ func (f *Frame) Equals(target *Frame) bool { if f.Encapsulated && !f.EncapsulatedData.Equals(&target.EncapsulatedData) { return false } - if !f.Encapsulated && !f.NativeData.Equals(&target.NativeData) { + if !f.Encapsulated && !f.NativeData.Equals(target.NativeData) { return false } return true diff --git a/pkg/frame/native.go b/pkg/frame/native.go index 0c43ada8..805c52a1 100644 --- a/pkg/frame/native.go +++ b/pkg/frame/native.go @@ -3,57 +3,104 @@ package frame import ( "image" "image/color" + + "golang.org/x/exp/constraints" ) +type INativeFrame interface { + Rows() int + Cols() int + BitsPerSample() int + GetPixel(x, y int) []int + GetPixelAtIdx(idx int) []int + RawDataSlice() any + Equals(frame INativeFrame) bool + CommonFrame +} + // NativeFrame represents a native image frame -type NativeFrame struct { +type NativeFrame[I constraints.Integer] struct { // Data is a slice of pixels, where each pixel can have multiple values - Data [][]int - Rows int - Cols int - BitsPerSample int + Data [][]I + InternalRows int + InternalCols int + InternalBitsPerSample int +} + +func NewNativeFrame[I constraints.Integer](bitsPerSample, rows, cols, pixelsPerFrame int) *NativeFrame[I] { + return &NativeFrame[I]{ + InternalBitsPerSample: bitsPerSample, + InternalRows: rows, + InternalCols: cols, + Data: make([][]I, pixelsPerFrame), + } +} + +func (n *NativeFrame[I]) Rows() int { return n.InternalRows } +func (n *NativeFrame[I]) Cols() int { return n.InternalCols } +func (n *NativeFrame[I]) BitsPerSample() int { return n.InternalBitsPerSample } +func (n *NativeFrame[I]) GetPixelAtIdx(idx int) []int { + rawPixel := n.Data[idx] + vals := make([]int, len(rawPixel)) + for i, val := range rawPixel { + vals[i] = int(val) + } + return vals } +func (n *NativeFrame[I]) GetPixel(x, y int) []int { + dataIdx := x + (y * n.Cols()) + return n.GetPixelAtIdx(dataIdx) +} +func (n *NativeFrame[I]) RawDataSlice() any { return n.Data } // IsEncapsulated indicates if the frame is encapsulated or not. -func (n *NativeFrame) IsEncapsulated() bool { return false } +func (n *NativeFrame[I]) IsEncapsulated() bool { return false } // GetNativeFrame returns a NativeFrame from this frame. If the underlying frame // is not a NativeFrame, ErrorFrameTypeNotPresent will be returned. -func (n *NativeFrame) GetNativeFrame() (*NativeFrame, error) { +func (n *NativeFrame[I]) GetNativeFrame() (INativeFrame, error) { return n, nil } // GetEncapsulatedFrame returns ErrorFrameTypeNotPresent, because this struct -// does not hold encapsulated frame data. -func (n *NativeFrame) GetEncapsulatedFrame() (*EncapsulatedFrame, error) { +// does not hold encapsulated frame Data. +func (n *NativeFrame[I]) GetEncapsulatedFrame() (*EncapsulatedFrame, error) { return nil, ErrorFrameTypeNotPresent } // GetImage returns an image.Image representation the frame, using default // processing. This default processing is basic at the moment, and does not // autoscale pixel values or use window width or level info. -func (n *NativeFrame) GetImage() (image.Image, error) { - i := image.NewGray16(image.Rect(0, 0, n.Cols, n.Rows)) +func (n *NativeFrame[I]) GetImage() (image.Image, error) { + i := image.NewGray16(image.Rect(0, 0, n.Cols(), n.Rows())) for j := 0; j < len(n.Data); j++ { - i.SetGray16(j%n.Cols, j/n.Cols, color.Gray16{Y: uint16(n.Data[j][0])}) // for now, assume we're not overflowing uint16, assume gray image + i.SetGray16(j%n.Cols(), j/n.Cols(), color.Gray16{Y: uint16(n.Data[j][0])}) // for now, assume we're not overflowing uint16, assume gray image } return i, nil } // Equals returns true if this frame equals the provided target frame, otherwise -// false. -func (n *NativeFrame) Equals(target *NativeFrame) bool { +// false. This may be expensive. +func (n *NativeFrame[I]) Equals(target INativeFrame) bool { if target == nil || n == nil { - return n == target + return INativeFrame(n) == target } - if n.Rows != target.Rows || - n.Cols != target.Cols || - n.BitsPerSample != n.BitsPerSample { + if n.Rows() != target.Rows() || + n.Cols() != target.Cols() || + n.BitsPerSample() != n.BitsPerSample() { return false } + + // If BitsPerSample are equal, we assume the target is of type + // *NativeFrame[I] + rawTarget, ok := target.(*NativeFrame[I]) + if !ok { + + } + for pixIdx, pix := range n.Data { for valIdx, val := range pix { - if val != target.Data[pixIdx][valIdx] { + if val != rawTarget.Data[pixIdx][valIdx] { return false } } diff --git a/pkg/frame/native_test.go b/pkg/frame/native_test.go index 3d3ed3ba..95c40c47 100644 --- a/pkg/frame/native_test.go +++ b/pkg/frame/native_test.go @@ -16,33 +16,33 @@ type point struct { func TestNativeFrame_GetImage(t *testing.T) { cases := []struct { Name string - NativeFrame frame.NativeFrame + NativeFrame frame.NativeFrame[int] SetPoints []point }{ { Name: "Square", - NativeFrame: frame.NativeFrame{ - Rows: 2, - Cols: 2, - Data: [][]int{{0}, {0}, {1}, {0}}, + NativeFrame: frame.NativeFrame[int]{ + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{0}, {0}, {1}, {0}}, }, SetPoints: []point{{0, 1}}, }, { Name: "Rectangle", - NativeFrame: frame.NativeFrame{ - Rows: 3, - Cols: 2, - Data: [][]int{{0}, {0}, {0}, {0}, {1}, {0}}, + NativeFrame: frame.NativeFrame[int]{ + InternalRows: 3, + InternalCols: 2, + Data: [][]int{{0}, {0}, {0}, {0}, {1}, {0}}, }, SetPoints: []point{{0, 2}}, }, { Name: "Rectangle - multiple points", - NativeFrame: frame.NativeFrame{ - Rows: 5, - Cols: 3, - Data: [][]int{{0}, {0}, {0}, {0}, {1}, {1}, {0}, {0}, {0}, {0}, {1}, {0}, {0}, {0}, {0}}, + NativeFrame: frame.NativeFrame[int]{ + InternalRows: 5, + InternalCols: 3, + Data: [][]int{{0}, {0}, {0}, {0}, {1}, {1}, {0}, {0}, {0}, {0}, {1}, {0}, {0}, {0}, {0}}, }, SetPoints: []point{{1, 1}, {2, 1}, {1, 3}}, }, diff --git a/read.go b/read.go index 337735f9..b878cf66 100644 --- a/read.go +++ b/read.go @@ -17,6 +17,7 @@ import ( "github.com/suyashkumar/dicom/pkg/dicomio" "github.com/suyashkumar/dicom/pkg/frame" "github.com/suyashkumar/dicom/pkg/tag" + "golang.org/x/exp/constraints" ) var ( @@ -444,42 +445,28 @@ func (r *reader) readNativeFrames(parsedData *Dataset, fc chan<- *frame.Frame, v // Init current frame currentFrame := frame.Frame{ Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: bitsAllocated, - Rows: MustGetInts(rows.Value)[0], - Cols: MustGetInts(cols.Value)[0], - Data: make([][]int, pixelsPerFrame), - }, - } - buf := make([]int, pixelsPerFrame*samplesPerPixel) + } + if bitsAllocated == 1 { + buf := make([]int, pixelsPerFrame*samplesPerPixel) // override buf for now if err := fillBufferSingleBitAllocated(buf, r.rawReader, bo); err != nil { return nil, bytesToRead, err } + nativeFrame := frame.NewNativeFrame[int](bitsAllocated, MustGetInts(rows.Value)[0], MustGetInts(cols.Value)[0], pixelsPerFrame) for pixel := 0; pixel < pixelsPerFrame; pixel++ { for value := 0; value < samplesPerPixel; value++ { - currentFrame.NativeData.Data[pixel] = buf[pixel*samplesPerPixel : (pixel+1)*samplesPerPixel] + nativeFrame.Data[pixel] = buf[pixel*samplesPerPixel : (pixel+1)*samplesPerPixel] } } + currentFrame.NativeData = nativeFrame } else { - for pixel := 0; pixel < pixelsPerFrame; pixel++ { - for value := 0; value < samplesPerPixel; value++ { - _, err := io.ReadFull(r.rawReader, pixelBuf) - if err != nil { - return nil, bytesToRead, - fmt.Errorf("could not read uint%d from input: %w", bitsAllocated, err) - } - if bitsAllocated == 8 { - buf[(pixel*samplesPerPixel)+value] = int(pixelBuf[0]) - } else if bitsAllocated == 16 { - buf[(pixel*samplesPerPixel)+value] = int(bo.Uint16(pixelBuf)) - } else if bitsAllocated == 32 { - buf[(pixel*samplesPerPixel)+value] = int(bo.Uint32(pixelBuf)) - } else { - return nil, bytesToRead, fmt.Errorf("bitsAllocated=%d : %w", bitsAllocated, ErrorUnsupportedBitsAllocated) - } - } - currentFrame.NativeData.Data[pixel] = buf[pixel*samplesPerPixel : (pixel+1)*samplesPerPixel] + switch bitsAllocated { + case 8: + currentFrame, _, err = readNativeFrame[uint8](bitsAllocated, MustGetInts(rows.Value)[0], MustGetInts(cols.Value)[0], bytesToRead, samplesPerPixel, pixelsPerFrame, pixelBuf, r.rawReader) + case 16: + currentFrame, _, err = readNativeFrame[uint16](bitsAllocated, MustGetInts(rows.Value)[0], MustGetInts(cols.Value)[0], bytesToRead, samplesPerPixel, pixelsPerFrame, pixelBuf, r.rawReader) + case 32: + currentFrame, _, err = readNativeFrame[uint32](bitsAllocated, MustGetInts(rows.Value)[0], MustGetInts(cols.Value)[0], bytesToRead, samplesPerPixel, pixelsPerFrame, pixelBuf, r.rawReader) } } image.Frames[frameIdx] = ¤tFrame @@ -497,6 +484,49 @@ func (r *reader) readNativeFrames(parsedData *Dataset, fc chan<- *frame.Frame, v return &image, bytesToRead, nil } +func readNativeFrame[I constraints.Integer](bitsAllocated, rows, cols, bytesToRead, samplesPerPixel, pixelsPerFrame int, pixelBuf []byte, rawReader dicomio.Reader) (frame.Frame, int, error) { + // Init current frame + nativeFrame := frame.NewNativeFrame[I](bitsAllocated, rows, cols, pixelsPerFrame) + currentFrame := frame.Frame{ + Encapsulated: false, + NativeData: nativeFrame, + } + buf := make([]I, pixelsPerFrame*samplesPerPixel) + bo := rawReader.ByteOrder() + for pixel := 0; pixel < pixelsPerFrame; pixel++ { + for value := 0; value < samplesPerPixel; value++ { + _, err := io.ReadFull(rawReader, pixelBuf) + if err != nil { + return frame.Frame{}, bytesToRead, + fmt.Errorf("could not read uint%d from input: %w", bitsAllocated, err) + } + if bitsAllocated == 8 { + v, ok := any(pixelBuf[0]).(I) + if !ok { + + } + buf[(pixel*samplesPerPixel)+value] = v + } else if bitsAllocated == 16 { + v, ok := any(bo.Uint16(pixelBuf)).(I) + if !ok { + + } + buf[(pixel*samplesPerPixel)+value] = v + } else if bitsAllocated == 32 { + v, ok := any(bo.Uint32(pixelBuf)).(I) + if !ok { + + } + buf[(pixel*samplesPerPixel)+value] = v + } else { + return frame.Frame{}, bytesToRead, fmt.Errorf("bitsAllocated=%d : %w", bitsAllocated, ErrorUnsupportedBitsAllocated) + } + } + nativeFrame.Data[pixel] = buf[pixel*samplesPerPixel : (pixel+1)*samplesPerPixel] + } + return currentFrame, bytesToRead, nil +} + // readSequence reads a sequence element (VR = SQ) that contains a subset of Items. Each item contains // a set of Elements. // See https://dicom.nema.org/medical/dicom/current/output/chtml/part05/sect_7.5.2.html#table_7.5-1 diff --git a/read_test.go b/read_test.go index b8fe8002..734957e7 100644 --- a/read_test.go +++ b/read_test.go @@ -232,11 +232,11 @@ func TestReadNativeFrames(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 16, - Rows: 5, - Cols: 5, - Data: [][]int{{1}, {2}, {3}, {4}, {5}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 16, + InternalRows: 5, + InternalCols: 5, + Data: [][]int{{1}, {2}, {3}, {4}, {5}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}, }, }, }, @@ -258,29 +258,29 @@ func TestReadNativeFrames(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 16, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {2}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 16, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {2}}, }, }, { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 16, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {2}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 16, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {2}}, }, }, { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 16, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {0}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 16, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {0}}, }, }, }, @@ -302,20 +302,20 @@ func TestReadNativeFrames(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 16, - Rows: 2, - Cols: 2, - Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 2}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 16, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 2}}, }, }, { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 16, - Rows: 2, - Cols: 2, - Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 5}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 16, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 5}}, }, }, }, @@ -411,30 +411,30 @@ func TestReadNativeFrames(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 3, - Cols: 3, - Data: [][]int{{11}, {12}, {13}, {21}, {22}, {23}, {31}, {32}, {33}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 3, + InternalCols: 3, + Data: [][]int{{11}, {12}, {13}, {21}, {22}, {23}, {31}, {32}, {33}}, }, }, { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 3, - Cols: 3, - Data: [][]int{{11}, {12}, {13}, {21}, {22}, {23}, {31}, {32}, {33}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 3, + InternalCols: 3, + Data: [][]int{{11}, {12}, {13}, {21}, {22}, {23}, {31}, {32}, {33}}, }, }, { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 3, - Cols: 3, - Data: [][]int{{11}, {12}, {13}, {21}, {22}, {23}, {31}, {32}, {33}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 3, + InternalCols: 3, + Data: [][]int{{11}, {12}, {13}, {21}, {22}, {23}, {31}, {32}, {33}}, }, }, }, @@ -456,29 +456,29 @@ func TestReadNativeFrames(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 1, - Cols: 1, - Data: [][]int{{1, 2, 3}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 1, + InternalCols: 1, + Data: [][]int{{1, 2, 3}}, }, }, { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 1, - Cols: 1, - Data: [][]int{{1, 2, 3}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 1, + InternalCols: 1, + Data: [][]int{{1, 2, 3}}, }, }, { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 1, - Cols: 1, - Data: [][]int{{1, 2, 3}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 1, + InternalCols: 1, + Data: [][]int{{1, 2, 3}}, }, }, }, @@ -798,11 +798,11 @@ func TestReadNativeFrames_OneBitAllocated(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 1, - Rows: 4, - Cols: 4, - Data: [][]int{{0}, {0}, {0}, {1}, {0}, {1}, {1}, {1}, {1}, {0}, {0}, {1}, {0}, {1}, {1}, {1}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 1, + InternalRows: 4, + InternalCols: 4, + Data: [][]int{{0}, {0}, {0}, {1}, {0}, {1}, {1}, {1}, {1}, {0}, {0}, {1}, {0}, {1}, {1}, {1}}, }, }, }, @@ -825,11 +825,11 @@ func TestReadNativeFrames_OneBitAllocated(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 1, - Rows: 4, - Cols: 4, - Data: [][]int{{0}, {0}, {0}, {1}, {0}, {1}, {1}, {1}, {1}, {0}, {0}, {1}, {0}, {1}, {1}, {1}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 1, + InternalRows: 4, + InternalCols: 4, + Data: [][]int{{0}, {0}, {0}, {1}, {0}, {1}, {1}, {1}, {1}, {0}, {0}, {1}, {0}, {1}, {1}, {1}}, }, }, }, diff --git a/write.go b/write.go index d58aca1e..9c6182b2 100644 --- a/write.go +++ b/write.go @@ -573,19 +573,19 @@ func writePixelData(w dicomio.Writer, t tag.Tag, value Value, vr string, vl uint return nil } numFrames := len(image.Frames) - numPixels := len(image.Frames[0].NativeData.Data) - numValues := len(image.Frames[0].NativeData.Data[0]) + numPixels := image.Frames[0].NativeData.Rows() * image.Frames[0].NativeData.Cols() + numValues := len(image.Frames[0].NativeData.GetPixelAtIdx(0)) // Total required buffer length in bytes: - length := numFrames * numPixels * numValues * image.Frames[0].NativeData.BitsPerSample / 8 + length := numFrames * numPixels * numValues * image.Frames[0].NativeData.BitsPerSample() / 8 buf := &bytes.Buffer{} buf.Grow(length) bo, _ := w.GetTransferSyntax() 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 { + pixelSlice := image.Frames[frame].NativeData.GetPixelAtIdx(pixel) + for _, pixelValue := range pixelSlice { + switch image.Frames[frame].NativeData.BitsPerSample() { case 8: if err := binary.Write(buf, bo, uint8(pixelValue)); err != nil { return err diff --git a/write_test.go b/write_test.go index 2c134c8a..49c31978 100644 --- a/write_test.go +++ b/write_test.go @@ -289,11 +289,11 @@ func TestWrite(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {4}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {4}}, }, }, }, @@ -319,11 +319,11 @@ func TestWrite(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 16, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {4}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 16, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {4}}, }, }, }, @@ -347,11 +347,11 @@ func TestWrite(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 32, - Rows: 2, - Cols: 2, - Data: [][]int{{1}, {2}, {3}, {4}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 32, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1}, {2}, {3}, {4}}, }, }, }, @@ -375,20 +375,20 @@ func TestWrite(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 32, - Rows: 2, - Cols: 2, - Data: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 32, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}}, }, }, { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 32, - Rows: 2, - Cols: 2, - Data: [][]int{{5, 1}, {2, 2}, {3, 3}, {4, 5}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 32, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{5, 1}, {2, 2}, {3, 3}, {4, 5}}, }, }, }, @@ -458,20 +458,20 @@ func TestWrite(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 32, - Rows: 2, - Cols: 2, - Data: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 32, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{1, 1}, {2, 2}, {3, 3}, {4, 4}}, }, }, { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 32, - Rows: 2, - Cols: 2, - Data: [][]int{{5, 1}, {2, 2}, {3, 3}, {4, 5}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 32, + InternalRows: 2, + InternalCols: 2, + Data: [][]int{{5, 1}, {2, 2}, {3, 3}, {4, 5}}, }, }, }, @@ -495,11 +495,11 @@ func TestWrite(t *testing.T) { Frames: []*frame.Frame{ { Encapsulated: false, - NativeData: frame.NativeFrame{ - BitsPerSample: 8, - Rows: 1, - Cols: 3, - Data: [][]int{{1}, {2}, {3}}, + NativeData: &frame.NativeFrame[int]{ + InternalBitsPerSample: 8, + InternalRows: 1, + InternalCols: 3, + Data: [][]int{{1}, {2}, {3}}, }, }, },