From 3055908111cef79c8b76acf16606b42acea00553 Mon Sep 17 00:00:00 2001 From: quang18 Date: Fri, 3 Nov 2023 11:13:12 +0700 Subject: [PATCH 1/6] Fix: tmp --- pkg/frame/inplace/pixeldata_info.go | 117 ++++++++++++++++++++++++++++ pkg/frame/inplace/read.go | 35 +++++++++ pkg/frame/inplace/write.go | 39 ++++++++++ read.go | 1 + 4 files changed, 192 insertions(+) create mode 100644 pkg/frame/inplace/pixeldata_info.go create mode 100644 pkg/frame/inplace/read.go create mode 100644 pkg/frame/inplace/write.go diff --git a/pkg/frame/inplace/pixeldata_info.go b/pkg/frame/inplace/pixeldata_info.go new file mode 100644 index 00000000..91139941 --- /dev/null +++ b/pkg/frame/inplace/pixeldata_info.go @@ -0,0 +1,117 @@ +package inplace + +import ( + "errors" + "fmt" + "strconv" + + "github.com/suyashkumar/dicom" + "github.com/suyashkumar/dicom/pkg/tag" +) + +// GetIntFromValue is similar to dicom.MustGetInts but without panic +func GetIntFromValue(v dicom.Value) (int, error) { + if v.ValueType() != dicom.Ints { + return 0, errors.New("value is not ints") + } + arr, ok := v.GetValue().([]int) + if !ok { + return 0, errors.New("actual value is not ints") + } + if len(arr) == 0 { + return 0, errors.New("empty ints value") + } + return arr[0], nil +} + +// GetStringFromValue is similar to dicom.MustGetStrings but without panic +func GetStringFromValue(v dicom.Value) (string, error) { + if v.ValueType() != dicom.Strings { + return "", errors.New("value is not ints") + } + arr, ok := v.GetValue().([]string) + if !ok { + return "", errors.New("actual value is not ints") + } + if len(arr) == 0 { + return "", errors.New("empty ints value") + } + return arr[0], nil +} + +// PixelDataMetadata is the metadata for tag.PixelData +type PixelDataMetadata struct { + Rows int + Cols int + Frames int + SamplesPerPixel int + BitsAllocated int +} + +// GetPixelDataMetadata returns the pixel data metadata. +func GetPixelDataMetadata(ds *dicom.Dataset) (*PixelDataMetadata, error) { + re := &PixelDataMetadata{} + rows, err := ds.FindElementByTag(tag.Rows) + if err != nil { + return nil, fmt.Errorf("get Rows element: %w", err) + } + if re.Rows, err = GetIntFromValue(rows.Value); err != nil { + return nil, fmt.Errorf("convert Rows element to int: %w", err) + } + + cols, err := ds.FindElementByTag(tag.Columns) + if err != nil { + return nil, fmt.Errorf("get Columns element: %w", err) + } + if re.Cols, err = GetIntFromValue(cols.Value); err != nil { + return nil, fmt.Errorf("convert Columns element to int: %w", err) + } + + numberOfFrames, err := ds.FindElementByTag(tag.NumberOfFrames) + if err != nil { + re.Frames = 1 + } else { + var framesStr string + framesStr, err = GetStringFromValue(numberOfFrames.Value) + if err != nil { + return nil, fmt.Errorf("convert NumberOfFrames element to str: %w", err) + } + if re.Frames, err = strconv.Atoi(framesStr); err != nil { + return nil, fmt.Errorf("convert NumberOfFrames to int: %w", err) + } + } + + samplesPerPixel, err := ds.FindElementByTag(tag.SamplesPerPixel) + if err != nil { + return nil, fmt.Errorf("get SamplesPerPixel element: %w", err) + } + if re.SamplesPerPixel, err = GetIntFromValue(samplesPerPixel.Value); err != nil { + return nil, fmt.Errorf("convert SamplesPerPixel element to int: %w", err) + } + bitsAllocated, err := ds.FindElementByTag(tag.BitsAllocated) + if err != nil { + return nil, fmt.Errorf("get BitsAllocated element: %w", err) + } + if re.BitsAllocated, err = GetIntFromValue(bitsAllocated.Value); err != nil { + return nil, fmt.Errorf("convert BitsAllocated element to int: %w", err) + } + return re, nil +} + +// SanityCheckUnprocessedValueDataHandling check if we can support in-place read-write +// from Pixeldata.UnprocessedValueData +func SanityCheckUnprocessedValueDataHandling(info PixelDataMetadata, unprocessedValueData []byte) error { + switch info.BitsAllocated { + case 8, 16, 32: + default: // bitsAllocated = 1 and other cases + return fmt.Errorf("unsupported bit allocated: %d", info.BitsAllocated) + } + pixelsPerFrame := info.Rows * info.Cols + bytesAllocated := info.BitsAllocated / 8 + expectedBytes := bytesAllocated * info.SamplesPerPixel * info.Frames * pixelsPerFrame + if len(unprocessedValueData) != expectedBytes { + return errors.New("mismatch data size") + } + return nil +} + diff --git a/pkg/frame/inplace/read.go b/pkg/frame/inplace/read.go new file mode 100644 index 00000000..dfefa559 --- /dev/null +++ b/pkg/frame/inplace/read.go @@ -0,0 +1,35 @@ +// Package inplace contains code for handling UnprocessedValueData +package inplace + +import ( + "encoding/binary" +) + +// ReadUnprocessedValueData read the value of Dicom image directly to +// byte array of PixelData.UnprocessedValueData +func ReadUnprocessedValueData(info PixelDataMetadata, unprocessedValueData []byte, + bo binary.ByteOrder, frameIndex int) ([][]int, error) { + + pixelsPerFrame := info.Rows * info.Cols + bytesAllocated := info.BitsAllocated / 8 + offset := bytesAllocated * info.SamplesPerPixel * frameIndex * pixelsPerFrame + samplesPerPixel := info.SamplesPerPixel + + re := make([][]int, pixelsPerFrame) + for i := 0; i < pixelsPerFrame; i++ { + re[i] = make([]int, bytesAllocated) + for j := 0; j < samplesPerPixel; j++ { + pointOffset := offset + info.SamplesPerPixel*bytesAllocated*i + j*samplesPerPixel + switch bytesAllocated { + case 1: + re[i][j] = int(unprocessedValueData[pointOffset]) + case 2: + re[i][j] = int(bo.Uint16(unprocessedValueData[pointOffset : pointOffset+bytesAllocated])) + case 4: + re[i][j] = int(bo.Uint32(unprocessedValueData[pointOffset : pointOffset+bytesAllocated])) + } + } + } + + return nil, nil +} diff --git a/pkg/frame/inplace/write.go b/pkg/frame/inplace/write.go new file mode 100644 index 00000000..56fb38bd --- /dev/null +++ b/pkg/frame/inplace/write.go @@ -0,0 +1,39 @@ +package inplace + +import ( + "bytes" + "encoding/binary" +) + +// WriteUnprocessedValueData write the value of Dicom image directly to +// byte array of PixelData.UnprocessedValueData +func WriteUnprocessedValueData(info PixelDataMetadata, unprocessedValueData []byte, + rowIndex, colIndex int, frameIndex int, sampleIndex int, newValue int) error { + pixelsPerFrame := info.Rows * info.Cols + bytesAllocated := info.BitsAllocated / 8 + buf := &bytes.Buffer{} + buf.Grow(bytesAllocated) + switch bytesAllocated { + case 1: + if err := binary.Write(buf, binary.LittleEndian, uint8(newValue)); err != nil { + return err + } + case 2: + if err := binary.Write(buf, binary.LittleEndian, uint16(newValue)); err != nil { + return err + } + case 4: + if err := binary.Write(buf, binary.LittleEndian, uint32(newValue)); err != nil { + return err + } + } + paintedPixel := buf.Bytes() + offset := bytesAllocated * info.SamplesPerPixel * frameIndex * pixelsPerFrame + offset += info.SamplesPerPixel * bytesAllocated * (rowIndex*info.Cols + colIndex) + + offset += bytesAllocated * sampleIndex + for k := 0; k < bytesAllocated; k++ { + unprocessedValueData[offset+k] = paintedPixel[k] + } + return nil +} diff --git a/read.go b/read.go index 337735f9..00411736 100644 --- a/read.go +++ b/read.go @@ -348,6 +348,7 @@ func makeErrorPixelData(reader io.Reader, vl uint32, fc chan<- *frame.Frame, par } f := frame.Frame{ + NativeData: EncapsulatedData: frame.EncapsulatedFrame{ Data: data, }, From 8896fd2a4cd193d10043433541ceaec62bf264a7 Mon Sep 17 00:00:00 2001 From: quang18 Date: Fri, 1 Mar 2024 12:07:51 +1100 Subject: [PATCH 2/6] Fix: inplace read --- dataset.go | 2 +- go.mod | 9 +- go.sum | 17 +++ pkg/frame/inplace/pixeldata_info.go | 11 +- pkg/frame/inplace/read.go | 22 +-- pkg/frame/inplace/read_test.go | 223 ++++++++++++++++++++++++++++ read.go | 1 - write.go | 4 +- 8 files changed, 267 insertions(+), 22 deletions(-) create mode 100644 pkg/frame/inplace/read_test.go diff --git a/dataset.go b/dataset.go index 4d8a5cb7..ae7b376d 100644 --- a/dataset.go +++ b/dataset.go @@ -36,7 +36,7 @@ func (d *Dataset) FindElementByTag(tag tag.Tag) (*Element, error) { return nil, ErrorElementNotFound } -func (d *Dataset) transferSyntax() (binary.ByteOrder, bool, error) { +func (d *Dataset) TransferSyntax() (binary.ByteOrder, bool, error) { elem, err := d.FindElementByTag(tag.TransferSyntaxUID) if err != nil { return nil, false, err diff --git a/go.mod b/go.mod index cc32e7da..35313d70 100644 --- a/go.mod +++ b/go.mod @@ -8,4 +8,11 @@ require ( golang.org/x/text v0.3.8 ) -require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/stretchr/testify v1.8.4 // indirect + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum index 6cf56017..f9de78b1 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,20 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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= @@ -12,3 +25,7 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/frame/inplace/pixeldata_info.go b/pkg/frame/inplace/pixeldata_info.go index 91139941..63e05a89 100644 --- a/pkg/frame/inplace/pixeldata_info.go +++ b/pkg/frame/inplace/pixeldata_info.go @@ -1,6 +1,7 @@ package inplace import ( + "encoding/binary" "errors" "fmt" "strconv" @@ -46,6 +47,7 @@ type PixelDataMetadata struct { Frames int SamplesPerPixel int BitsAllocated int + Bo binary.ByteOrder } // GetPixelDataMetadata returns the pixel data metadata. @@ -95,12 +97,16 @@ func GetPixelDataMetadata(ds *dicom.Dataset) (*PixelDataMetadata, error) { if re.BitsAllocated, err = GetIntFromValue(bitsAllocated.Value); err != nil { return nil, fmt.Errorf("convert BitsAllocated element to int: %w", err) } + re.Bo, _, err = ds.TransferSyntax() + if err != nil { + return nil, fmt.Errorf("get byteOrder: %w", err) + } return re, nil } -// SanityCheckUnprocessedValueDataHandling check if we can support in-place read-write +// IsSafeForUnprocessedValueDataHandling check if we can support in-place read-write // from Pixeldata.UnprocessedValueData -func SanityCheckUnprocessedValueDataHandling(info PixelDataMetadata, unprocessedValueData []byte) error { +func IsSafeForUnprocessedValueDataHandling(info *PixelDataMetadata, unprocessedValueData []byte) error { switch info.BitsAllocated { case 8, 16, 32: default: // bitsAllocated = 1 and other cases @@ -114,4 +120,3 @@ func SanityCheckUnprocessedValueDataHandling(info PixelDataMetadata, unprocessed } return nil } - diff --git a/pkg/frame/inplace/read.go b/pkg/frame/inplace/read.go index dfefa559..b1ecbe54 100644 --- a/pkg/frame/inplace/read.go +++ b/pkg/frame/inplace/read.go @@ -1,15 +1,10 @@ // Package inplace contains code for handling UnprocessedValueData package inplace -import ( - "encoding/binary" -) - -// ReadUnprocessedValueData read the value of Dicom image directly to -// byte array of PixelData.UnprocessedValueData -func ReadUnprocessedValueData(info PixelDataMetadata, unprocessedValueData []byte, - bo binary.ByteOrder, frameIndex int) ([][]int, error) { - +// ReadUnprocessedValueData read the value of Dicom image directly +// from the byte array of PixelData.UnprocessedValueData with given frame ID. +// This ease the memory usage of reading DICOM image. +func ReadUnprocessedValueData(info *PixelDataMetadata, unprocessedValueData []byte, frameIndex int) ([][]int, error) { pixelsPerFrame := info.Rows * info.Cols bytesAllocated := info.BitsAllocated / 8 offset := bytesAllocated * info.SamplesPerPixel * frameIndex * pixelsPerFrame @@ -17,19 +12,18 @@ func ReadUnprocessedValueData(info PixelDataMetadata, unprocessedValueData []byt re := make([][]int, pixelsPerFrame) for i := 0; i < pixelsPerFrame; i++ { - re[i] = make([]int, bytesAllocated) + re[i] = make([]int, samplesPerPixel) for j := 0; j < samplesPerPixel; j++ { pointOffset := offset + info.SamplesPerPixel*bytesAllocated*i + j*samplesPerPixel switch bytesAllocated { case 1: re[i][j] = int(unprocessedValueData[pointOffset]) case 2: - re[i][j] = int(bo.Uint16(unprocessedValueData[pointOffset : pointOffset+bytesAllocated])) + re[i][j] = int(info.Bo.Uint16(unprocessedValueData[pointOffset : pointOffset+bytesAllocated])) case 4: - re[i][j] = int(bo.Uint32(unprocessedValueData[pointOffset : pointOffset+bytesAllocated])) + re[i][j] = int(info.Bo.Uint32(unprocessedValueData[pointOffset : pointOffset+bytesAllocated])) } } } - - return nil, nil + return re, nil } diff --git a/pkg/frame/inplace/read_test.go b/pkg/frame/inplace/read_test.go new file mode 100644 index 00000000..2c55e466 --- /dev/null +++ b/pkg/frame/inplace/read_test.go @@ -0,0 +1,223 @@ +package inplace + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/suyashkumar/dicom" + "github.com/suyashkumar/dicom/pkg/frame" + "github.com/suyashkumar/dicom/pkg/tag" + "github.com/suyashkumar/dicom/pkg/uid" +) + +func mustNewElement(t *testing.T, tag tag.Tag, data interface{}) *dicom.Element { + elem, err := dicom.NewElement(tag, data) + require.NoError(t, err) + return elem +} + +func TestReadReadUnprocessedValueData(t *testing.T) { + cases := []struct { + Name string + existingData dicom.Dataset + }{ + { + Name: "5x5_1_frame_1_samples/pixel", + existingData: dicom.Dataset{Elements: []*dicom.Element{ + mustNewElement(t, tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), + mustNewElement(t, tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), + mustNewElement(t, tag.TransferSyntaxUID, []string{uid.ImplicitVRLittleEndian}), + mustNewElement(t, tag.Rows, []int{5}), + mustNewElement(t, tag.Columns, []int{5}), + mustNewElement(t, tag.NumberOfFrames, []string{"1"}), + mustNewElement(t, tag.BitsAllocated, []int{16}), + mustNewElement(t, tag.SamplesPerPixel, []int{1}), + mustNewElement(t, tag.PixelData, dicom.PixelDataInfo{ + IsEncapsulated: false, + 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}, + }, + }, + }, + }, + }), + }}, + }, + { + Name: "2x2, 3 frames, 1 samples/pixel", + existingData: dicom.Dataset{ + Elements: []*dicom.Element{ + mustNewElement(t, tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), + mustNewElement(t, tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), + mustNewElement(t, tag.TransferSyntaxUID, []string{uid.ImplicitVRLittleEndian}), + mustNewElement(t, tag.Rows, []int{2}), + mustNewElement(t, tag.Columns, []int{2}), + mustNewElement(t, tag.NumberOfFrames, []string{"3"}), + mustNewElement(t, tag.BitsAllocated, []int{16}), + mustNewElement(t, tag.SamplesPerPixel, []int{1}), + mustNewElement(t, tag.PixelData, dicom.PixelDataInfo{ + IsEncapsulated: false, + Frames: []*frame.Frame{ + { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 16, + Rows: 2, + Cols: 2, + Data: [][]int{{1}, {2}, {3}, {2}}, + }, + }, + { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 16, + Rows: 2, + Cols: 2, + Data: [][]int{{1}, {2}, {3}, {2}}, + }, + }, + { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 16, + Rows: 2, + Cols: 2, + Data: [][]int{{1}, {2}, {3}, {0}}, + }, + }, + }, + }), + }, + }, + }, + { + Name: "2x2, 2 frames, 2 samples/pixel", + existingData: dicom.Dataset{ + Elements: []*dicom.Element{ + mustNewElement(t, tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), + mustNewElement(t, tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), + mustNewElement(t, tag.TransferSyntaxUID, []string{uid.ImplicitVRLittleEndian}), + mustNewElement(t, tag.Rows, []int{2}), + mustNewElement(t, tag.Columns, []int{2}), + mustNewElement(t, tag.NumberOfFrames, []string{"2"}), + mustNewElement(t, tag.BitsAllocated, []int{16}), + mustNewElement(t, tag.SamplesPerPixel, []int{2}), + + mustNewElement(t, tag.PixelData, dicom.PixelDataInfo{ + IsEncapsulated: false, + Frames: []*frame.Frame{ + { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 16, + Rows: 2, + Cols: 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}}, + }, + }, + }, + }), + }, + }, + }, + /* + TODO: This test is failed due to dicomio.writeTag does not support even length yet. + */ + //{ + // Name: "1x1, 3 frames, 3 samples/pixel, data bytes with padded 0", + // existingData: dicom.Dataset{ + // Elements: []*dicom.Element{ + // mustNewElement(t, tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), + // mustNewElement(t, tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), + // mustNewElement(t, tag.TransferSyntaxUID, []string{uid.ImplicitVRLittleEndian}), + // mustNewElement(t, tag.Rows, []int{1}), + // mustNewElement(t, tag.Columns, []int{1}), + // mustNewElement(t, tag.NumberOfFrames, []string{"3"}), + // mustNewElement(t, tag.BitsAllocated, []int{8}), + // mustNewElement(t, tag.SamplesPerPixel, []int{3}), + // + // mustNewElement(t, tag.PixelData, dicom.PixelDataInfo{ + // IsEncapsulated: false, + // Frames: []*frame.Frame{ + // { + // Encapsulated: false, + // NativeData: frame.NativeFrame{ + // BitsPerSample: 8, Rows: 1, Cols: 1, + // Data: [][]int{{1, 2, 3}}, + // }, + // }, { + // Encapsulated: false, + // NativeData: frame.NativeFrame{ + // BitsPerSample: 8, Rows: 1, Cols: 1, + // Data: [][]int{{1, 2, 3}}, + // }, + // }, { + // Encapsulated: false, + // NativeData: frame.NativeFrame{ + // BitsPerSample: 8, Rows: 1, Cols: 1, + // Data: [][]int{{1, 2, 3}}, + // }, + // }, + // }, + // }), + // }, + // }, + //}, + } + + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + var filesOut bytes.Buffer + require.NoError(t, dicom.Write(io.Writer(&filesOut), tc.existingData)) + rawData := filesOut.Bytes() + + dataset, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil, dicom.SkipProcessingPixelDataValue()) + require.NoError(t, err) + metadata, err := GetPixelDataMetadata(&dataset) + require.NoError(t, err) + pixelElement, err := dataset.FindElementByTag(tag.PixelData) + require.NoError(t, err) + pixelDataInfo := dicom.MustGetPixelDataInfo(pixelElement.Value) + assert.Equal(t, pixelDataInfo.IntentionallyUnprocessed, true) + assert.Equal(t, pixelDataInfo.IsEncapsulated, false) + + require.NoError(t, IsSafeForUnprocessedValueDataHandling(metadata, pixelDataInfo.UnprocessedValueData)) + + originPixelElement, err := tc.existingData.FindElementByTag(tag.PixelData) + require.NoError(t, err) + originPixelDataInfo := dicom.MustGetPixelDataInfo(originPixelElement.Value) + + for i := 0; i < metadata.Frames; i++ { + originData := originPixelDataInfo.Frames[i].NativeData.Data + inplaceData, err := ReadUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, i) + require.NoError(t, err) + require.Equal(t, originData, inplaceData, "missing match value", i) + } + + }) + } +} diff --git a/read.go b/read.go index 00411736..337735f9 100644 --- a/read.go +++ b/read.go @@ -348,7 +348,6 @@ func makeErrorPixelData(reader io.Reader, vl uint32, fc chan<- *frame.Frame, par } f := frame.Frame{ - NativeData: EncapsulatedData: frame.EncapsulatedFrame{ Data: data, }, diff --git a/write.go b/write.go index d58aca1e..80a561e3 100644 --- a/write.go +++ b/write.go @@ -64,7 +64,7 @@ func (w *Writer) writeDataset(ds Dataset) error { return err } - endian, implicit, err := ds.transferSyntax() + endian, implicit, err := ds.TransferSyntax() if (err != nil && err != ErrorElementNotFound) || (err == ErrorElementNotFound && !w.optSet.defaultMissingTransferSyntax) { return err } @@ -119,7 +119,7 @@ func SkipValueTypeVerification() WriteOption { } // DefaultMissingTransferSyntax returns a WriteOption indicating that a missing -// transferSyntax should not raise an error, and instead the default +// 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 { From 434c58126ddbbd1b0ead077672f1b91fedfba645 Mon Sep 17 00:00:00 2001 From: quang18 Date: Fri, 1 Mar 2024 13:40:12 +1100 Subject: [PATCH 3/6] Fix: add test to write pixel data --- pkg/frame/inplace/write.go | 2 +- pkg/frame/inplace/write_test.go | 126 ++++++++++++++++++++++++++++++++ write.go | 2 +- 3 files changed, 128 insertions(+), 2 deletions(-) create mode 100644 pkg/frame/inplace/write_test.go diff --git a/pkg/frame/inplace/write.go b/pkg/frame/inplace/write.go index 56fb38bd..7dab3180 100644 --- a/pkg/frame/inplace/write.go +++ b/pkg/frame/inplace/write.go @@ -7,7 +7,7 @@ import ( // WriteUnprocessedValueData write the value of Dicom image directly to // byte array of PixelData.UnprocessedValueData -func WriteUnprocessedValueData(info PixelDataMetadata, unprocessedValueData []byte, +func WriteUnprocessedValueData(info *PixelDataMetadata, unprocessedValueData []byte, rowIndex, colIndex int, frameIndex int, sampleIndex int, newValue int) error { pixelsPerFrame := info.Rows * info.Cols bytesAllocated := info.BitsAllocated / 8 diff --git a/pkg/frame/inplace/write_test.go b/pkg/frame/inplace/write_test.go new file mode 100644 index 00000000..32148546 --- /dev/null +++ b/pkg/frame/inplace/write_test.go @@ -0,0 +1,126 @@ +package inplace + +import ( + "bytes" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/suyashkumar/dicom" + "github.com/suyashkumar/dicom/pkg/frame" + "github.com/suyashkumar/dicom/pkg/tag" + "github.com/suyashkumar/dicom/pkg/uid" +) + +func TestWriteUnprocessedValueData(t *testing.T) { + originDataset := dicom.Dataset{ + Elements: []*dicom.Element{ + mustNewElement(t, tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), + mustNewElement(t, tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), + mustNewElement(t, tag.TransferSyntaxUID, []string{uid.ImplicitVRLittleEndian}), + mustNewElement(t, tag.Rows, []int{2}), + mustNewElement(t, tag.Columns, []int{2}), + mustNewElement(t, tag.NumberOfFrames, []string{"2"}), + mustNewElement(t, tag.BitsAllocated, []int{16}), + mustNewElement(t, tag.SamplesPerPixel, []int{2}), + + mustNewElement(t, tag.PixelData, dicom.PixelDataInfo{ + IsEncapsulated: false, + Frames: []*frame.Frame{ + { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 16, + Rows: 2, + Cols: 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}}, + }, + }, + }, + }), + }, + } + var buffer bytes.Buffer + require.NoError(t, dicom.Write(io.Writer(&buffer), originDataset)) + rawData := buffer.Bytes() + + for _, tc := range []struct { + sampleIndex, rowIndex, colIndex, frameIndex int + expectedPixelInfo dicom.PixelDataInfo + }{ + { + sampleIndex: 0, colIndex: 1, rowIndex: 0, frameIndex: 0, + expectedPixelInfo: dicom.PixelDataInfo{ + Frames: []*frame.Frame{ + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {100, 2}, {1, 2}, {3, 2}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 5}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + }, + }, + }, + { + sampleIndex: 1, colIndex: 1, rowIndex: 0, frameIndex: 0, + expectedPixelInfo: dicom.PixelDataInfo{ + Frames: []*frame.Frame{ + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 100}, {1, 2}, {3, 2}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 5}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + }, + }, + }, + { + sampleIndex: 1, colIndex: 0, rowIndex: 1, frameIndex: 0, + expectedPixelInfo: dicom.PixelDataInfo{ + Frames: []*frame.Frame{ + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 100}, {3, 2}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 5}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + }, + }, + }, + { + sampleIndex: 1, colIndex: 0, rowIndex: 1, frameIndex: 1, + expectedPixelInfo: dicom.PixelDataInfo{ + Frames: []*frame.Frame{ + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 2}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 100}, {3, 5}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + }, + }, + }, + } { + dataset, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil, dicom.SkipProcessingPixelDataValue()) + require.NoError(t, err) + metadata, err := GetPixelDataMetadata(&dataset) + require.NoError(t, err) + pixelElement, err := dataset.FindElementByTag(tag.PixelData) + require.NoError(t, err) + pixelDataInfo := dicom.MustGetPixelDataInfo(pixelElement.Value) + assert.Equal(t, pixelDataInfo.IntentionallyUnprocessed, true) + assert.Equal(t, pixelDataInfo.IsEncapsulated, false) + // Sanity check if the dataset is suitable for in-place read-write + require.NoError(t, IsSafeForUnprocessedValueDataHandling(metadata, pixelDataInfo.UnprocessedValueData)) + // in-place write then converts to bytes + require.NoError(t, WriteUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, tc.rowIndex, tc.colIndex, tc.frameIndex, tc.sampleIndex, 100)) + var buffer2 bytes.Buffer + require.NoError(t, dicom.Write(io.Writer(&buffer2), dataset)) + outputBytes := buffer2.Bytes() + assertPixelDataInfo(t, outputBytes, tc.expectedPixelInfo) + } +} + +func assertPixelDataInfo(t testing.TB, rawData []byte, expectedPixelDataInfo dicom.PixelDataInfo) { + dataset2, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil) + require.NoError(t, err) + pixelElement, err := dataset2.FindElementByTag(tag.PixelData) + require.NoError(t, err) + pixelDataInfo := dicom.MustGetPixelDataInfo(pixelElement.Value) + require.Equal(t, pixelDataInfo, expectedPixelDataInfo) +} diff --git a/write.go b/write.go index 80a561e3..b0814c88 100644 --- a/write.go +++ b/write.go @@ -119,7 +119,7 @@ func SkipValueTypeVerification() WriteOption { } // DefaultMissingTransferSyntax returns a WriteOption indicating that a missing -// TransferSyntax should not raise an error, and instead the default +// 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 { From 3161ae4c5d17018e5a10ea112356df41e5dc4c93 Mon Sep 17 00:00:00 2001 From: quang18 Date: Fri, 1 Mar 2024 13:55:59 +1100 Subject: [PATCH 4/6] Fix: bug write dicom --- pkg/frame/inplace/pixeldata_info.go | 5 ++ pkg/frame/inplace/read.go | 4 +- pkg/frame/inplace/read_test.go | 92 ++++++++++++------------- pkg/frame/inplace/write.go | 6 +- pkg/frame/inplace/write_test.go | 102 ++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+), 51 deletions(-) diff --git a/pkg/frame/inplace/pixeldata_info.go b/pkg/frame/inplace/pixeldata_info.go index 63e05a89..85a43477 100644 --- a/pkg/frame/inplace/pixeldata_info.go +++ b/pkg/frame/inplace/pixeldata_info.go @@ -107,6 +107,7 @@ func GetPixelDataMetadata(ds *dicom.Dataset) (*PixelDataMetadata, error) { // IsSafeForUnprocessedValueDataHandling check if we can support in-place read-write // from Pixeldata.UnprocessedValueData func IsSafeForUnprocessedValueDataHandling(info *PixelDataMetadata, unprocessedValueData []byte) error { + // TODO: support for BitsAllocated == 1 switch info.BitsAllocated { case 8, 16, 32: default: // bitsAllocated = 1 and other cases @@ -115,6 +116,10 @@ func IsSafeForUnprocessedValueDataHandling(info *PixelDataMetadata, unprocessedV pixelsPerFrame := info.Rows * info.Cols bytesAllocated := info.BitsAllocated / 8 expectedBytes := bytesAllocated * info.SamplesPerPixel * info.Frames * pixelsPerFrame + // odd number of bytes. + if expectedBytes%2 == 1 { + expectedBytes += 1 + } if len(unprocessedValueData) != expectedBytes { return errors.New("mismatch data size") } diff --git a/pkg/frame/inplace/read.go b/pkg/frame/inplace/read.go index b1ecbe54..6e6d40a0 100644 --- a/pkg/frame/inplace/read.go +++ b/pkg/frame/inplace/read.go @@ -7,14 +7,14 @@ package inplace func ReadUnprocessedValueData(info *PixelDataMetadata, unprocessedValueData []byte, frameIndex int) ([][]int, error) { pixelsPerFrame := info.Rows * info.Cols bytesAllocated := info.BitsAllocated / 8 - offset := bytesAllocated * info.SamplesPerPixel * frameIndex * pixelsPerFrame + offset := frameIndex * pixelsPerFrame * info.SamplesPerPixel * bytesAllocated samplesPerPixel := info.SamplesPerPixel re := make([][]int, pixelsPerFrame) for i := 0; i < pixelsPerFrame; i++ { re[i] = make([]int, samplesPerPixel) for j := 0; j < samplesPerPixel; j++ { - pointOffset := offset + info.SamplesPerPixel*bytesAllocated*i + j*samplesPerPixel + pointOffset := offset + i*info.SamplesPerPixel*bytesAllocated + j*bytesAllocated switch bytesAllocated { case 1: re[i][j] = int(unprocessedValueData[pointOffset]) diff --git a/pkg/frame/inplace/read_test.go b/pkg/frame/inplace/read_test.go index 2c55e466..7c876055 100644 --- a/pkg/frame/inplace/read_test.go +++ b/pkg/frame/inplace/read_test.go @@ -2,6 +2,7 @@ package inplace import ( "bytes" + "fmt" "io" "testing" @@ -79,7 +80,7 @@ func TestReadReadUnprocessedValueData(t *testing.T) { BitsPerSample: 16, Rows: 2, Cols: 2, - Data: [][]int{{1}, {2}, {3}, {2}}, + Data: [][]int{{4}, {5}, {6}, {7}}, }, }, { @@ -144,49 +145,46 @@ func TestReadReadUnprocessedValueData(t *testing.T) { }, }, }, - /* - TODO: This test is failed due to dicomio.writeTag does not support even length yet. - */ - //{ - // Name: "1x1, 3 frames, 3 samples/pixel, data bytes with padded 0", - // existingData: dicom.Dataset{ - // Elements: []*dicom.Element{ - // mustNewElement(t, tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), - // mustNewElement(t, tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), - // mustNewElement(t, tag.TransferSyntaxUID, []string{uid.ImplicitVRLittleEndian}), - // mustNewElement(t, tag.Rows, []int{1}), - // mustNewElement(t, tag.Columns, []int{1}), - // mustNewElement(t, tag.NumberOfFrames, []string{"3"}), - // mustNewElement(t, tag.BitsAllocated, []int{8}), - // mustNewElement(t, tag.SamplesPerPixel, []int{3}), - // - // mustNewElement(t, tag.PixelData, dicom.PixelDataInfo{ - // IsEncapsulated: false, - // Frames: []*frame.Frame{ - // { - // Encapsulated: false, - // NativeData: frame.NativeFrame{ - // BitsPerSample: 8, Rows: 1, Cols: 1, - // Data: [][]int{{1, 2, 3}}, - // }, - // }, { - // Encapsulated: false, - // NativeData: frame.NativeFrame{ - // BitsPerSample: 8, Rows: 1, Cols: 1, - // Data: [][]int{{1, 2, 3}}, - // }, - // }, { - // Encapsulated: false, - // NativeData: frame.NativeFrame{ - // BitsPerSample: 8, Rows: 1, Cols: 1, - // Data: [][]int{{1, 2, 3}}, - // }, - // }, - // }, - // }), - // }, - // }, - //}, + { + Name: "1x1, 3 frames, 3 samples/pixel, data bytes with padded 0", + existingData: dicom.Dataset{ + Elements: []*dicom.Element{ + mustNewElement(t, tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), + mustNewElement(t, tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), + mustNewElement(t, tag.TransferSyntaxUID, []string{uid.ImplicitVRLittleEndian}), + mustNewElement(t, tag.Rows, []int{1}), + mustNewElement(t, tag.Columns, []int{1}), + mustNewElement(t, tag.NumberOfFrames, []string{"3"}), + mustNewElement(t, tag.BitsAllocated, []int{8}), + mustNewElement(t, tag.SamplesPerPixel, []int{3}), + + mustNewElement(t, tag.PixelData, dicom.PixelDataInfo{ + IsEncapsulated: false, + Frames: []*frame.Frame{ + { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 8, Rows: 1, Cols: 1, + Data: [][]int{{1, 2, 3}}, + }, + }, { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 8, Rows: 1, Cols: 1, + Data: [][]int{{4, 5, 6}}, + }, + }, { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 8, Rows: 1, Cols: 1, + Data: [][]int{{7, 8, 9}}, + }, + }, + }, + }), + }, + }, + }, } for _, tc := range cases { @@ -210,14 +208,16 @@ func TestReadReadUnprocessedValueData(t *testing.T) { originPixelElement, err := tc.existingData.FindElementByTag(tag.PixelData) require.NoError(t, err) originPixelDataInfo := dicom.MustGetPixelDataInfo(originPixelElement.Value) + fmt.Println(pixelDataInfo.UnprocessedValueData) for i := 0; i < metadata.Frames; i++ { originData := originPixelDataInfo.Frames[i].NativeData.Data inplaceData, err := ReadUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, i) require.NoError(t, err) - require.Equal(t, originData, inplaceData, "missing match value", i) + fmt.Println(originData) + fmt.Println(inplaceData) + assert.Equal(t, originData, inplaceData, "missing match value", i) } - }) } } diff --git a/pkg/frame/inplace/write.go b/pkg/frame/inplace/write.go index 7dab3180..dcd7b5a4 100644 --- a/pkg/frame/inplace/write.go +++ b/pkg/frame/inplace/write.go @@ -15,15 +15,15 @@ func WriteUnprocessedValueData(info *PixelDataMetadata, unprocessedValueData []b buf.Grow(bytesAllocated) switch bytesAllocated { case 1: - if err := binary.Write(buf, binary.LittleEndian, uint8(newValue)); err != nil { + if err := binary.Write(buf, info.Bo, uint8(newValue)); err != nil { return err } case 2: - if err := binary.Write(buf, binary.LittleEndian, uint16(newValue)); err != nil { + if err := binary.Write(buf, info.Bo, uint16(newValue)); err != nil { return err } case 4: - if err := binary.Write(buf, binary.LittleEndian, uint32(newValue)); err != nil { + if err := binary.Write(buf, info.Bo, uint32(newValue)); err != nil { return err } } diff --git a/pkg/frame/inplace/write_test.go b/pkg/frame/inplace/write_test.go index 32148546..75d970a9 100644 --- a/pkg/frame/inplace/write_test.go +++ b/pkg/frame/inplace/write_test.go @@ -116,6 +116,108 @@ func TestWriteUnprocessedValueData(t *testing.T) { } } +func TestWriteUnprocessedValueData_BigEndian(t *testing.T) { + originDataset := dicom.Dataset{ + Elements: []*dicom.Element{ + mustNewElement(t, tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), + mustNewElement(t, tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), + mustNewElement(t, tag.TransferSyntaxUID, []string{uid.ExplicitVRBigEndian}), + mustNewElement(t, tag.Rows, []int{2}), + mustNewElement(t, tag.Columns, []int{2}), + mustNewElement(t, tag.NumberOfFrames, []string{"2"}), + mustNewElement(t, tag.BitsAllocated, []int{16}), + mustNewElement(t, tag.SamplesPerPixel, []int{2}), + + mustNewElement(t, tag.PixelData, dicom.PixelDataInfo{ + IsEncapsulated: false, + Frames: []*frame.Frame{ + { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 16, + Rows: 2, + Cols: 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}}, + }, + }, + }, + }), + }, + } + var buffer bytes.Buffer + require.NoError(t, dicom.Write(io.Writer(&buffer), originDataset)) + rawData := buffer.Bytes() + + for _, tc := range []struct { + sampleIndex, rowIndex, colIndex, frameIndex int + expectedPixelInfo dicom.PixelDataInfo + }{ + { + sampleIndex: 0, colIndex: 1, rowIndex: 0, frameIndex: 0, + expectedPixelInfo: dicom.PixelDataInfo{ + Frames: []*frame.Frame{ + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {100, 2}, {1, 2}, {3, 2}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 5}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + }, + }, + }, + { + sampleIndex: 1, colIndex: 1, rowIndex: 0, frameIndex: 0, + expectedPixelInfo: dicom.PixelDataInfo{ + Frames: []*frame.Frame{ + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 100}, {1, 2}, {3, 2}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 5}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + }, + }, + }, + { + sampleIndex: 1, colIndex: 0, rowIndex: 1, frameIndex: 0, + expectedPixelInfo: dicom.PixelDataInfo{ + Frames: []*frame.Frame{ + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 100}, {3, 2}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 5}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + }, + }, + }, + { + sampleIndex: 1, colIndex: 0, rowIndex: 1, frameIndex: 1, + expectedPixelInfo: dicom.PixelDataInfo{ + Frames: []*frame.Frame{ + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 2}, {3, 2}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + {NativeData: frame.NativeFrame{Data: [][]int{{1, 2}, {3, 2}, {1, 100}, {3, 5}}, BitsPerSample: 16, Rows: 2, Cols: 2}}, + }, + }, + }, + } { + dataset, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil, dicom.SkipProcessingPixelDataValue()) + require.NoError(t, err) + metadata, err := GetPixelDataMetadata(&dataset) + require.NoError(t, err) + pixelElement, err := dataset.FindElementByTag(tag.PixelData) + require.NoError(t, err) + pixelDataInfo := dicom.MustGetPixelDataInfo(pixelElement.Value) + assert.Equal(t, pixelDataInfo.IntentionallyUnprocessed, true) + assert.Equal(t, pixelDataInfo.IsEncapsulated, false) + // Sanity check if the dataset is suitable for in-place read-write + require.NoError(t, IsSafeForUnprocessedValueDataHandling(metadata, pixelDataInfo.UnprocessedValueData)) + // in-place write then converts to bytes + require.NoError(t, WriteUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, tc.rowIndex, tc.colIndex, tc.frameIndex, tc.sampleIndex, 100)) + var buffer2 bytes.Buffer + require.NoError(t, dicom.Write(io.Writer(&buffer2), dataset)) + outputBytes := buffer2.Bytes() + assertPixelDataInfo(t, outputBytes, tc.expectedPixelInfo) + } +} + func assertPixelDataInfo(t testing.TB, rawData []byte, expectedPixelDataInfo dicom.PixelDataInfo) { dataset2, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil) require.NoError(t, err) From 2a226e480459da227710035493fce32f1f565efc Mon Sep 17 00:00:00 2001 From: quang18 Date: Fri, 22 Mar 2024 11:44:31 +1100 Subject: [PATCH 5/6] Fix: raise an error for unsupported case --- pkg/frame/inplace/pixeldata_info.go | 28 ++++++++++++++++++++++------ pkg/frame/inplace/read_test.go | 4 ---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/pkg/frame/inplace/pixeldata_info.go b/pkg/frame/inplace/pixeldata_info.go index 85a43477..df1f612b 100644 --- a/pkg/frame/inplace/pixeldata_info.go +++ b/pkg/frame/inplace/pixeldata_info.go @@ -42,12 +42,13 @@ func GetStringFromValue(v dicom.Value) (string, error) { // PixelDataMetadata is the metadata for tag.PixelData type PixelDataMetadata struct { - Rows int - Cols int - Frames int - SamplesPerPixel int - BitsAllocated int - Bo binary.ByteOrder + Rows int + Cols int + Frames int + SamplesPerPixel int + BitsAllocated int + PlanarConfiguration int + Bo binary.ByteOrder } // GetPixelDataMetadata returns the pixel data metadata. @@ -101,12 +102,27 @@ func GetPixelDataMetadata(ds *dicom.Dataset) (*PixelDataMetadata, error) { if err != nil { return nil, fmt.Errorf("get byteOrder: %w", err) } + + planarConfElement, err := ds.FindElementByTag(tag.PlanarConfiguration) + if err != nil { + re.PlanarConfiguration = 0 + } else { + if re.PlanarConfiguration, err = GetIntFromValue(planarConfElement.Value); err != nil { + return nil, fmt.Errorf("convert Rows element to int: %w", err) + } + } + return re, nil } // IsSafeForUnprocessedValueDataHandling check if we can support in-place read-write // from Pixeldata.UnprocessedValueData +// This avoids the case that we can not handle it, yet. func IsSafeForUnprocessedValueDataHandling(info *PixelDataMetadata, unprocessedValueData []byte) error { + // https://dicom.innolitics.com/ciods/enhanced-mr-image/enhanced-mr-image/00280006 + if info.PlanarConfiguration == 1 { + return fmt.Errorf("unsupported PlanarConfiguration: %d", info.PlanarConfiguration) + } // TODO: support for BitsAllocated == 1 switch info.BitsAllocated { case 8, 16, 32: diff --git a/pkg/frame/inplace/read_test.go b/pkg/frame/inplace/read_test.go index 7c876055..af729f26 100644 --- a/pkg/frame/inplace/read_test.go +++ b/pkg/frame/inplace/read_test.go @@ -2,7 +2,6 @@ package inplace import ( "bytes" - "fmt" "io" "testing" @@ -208,14 +207,11 @@ func TestReadReadUnprocessedValueData(t *testing.T) { originPixelElement, err := tc.existingData.FindElementByTag(tag.PixelData) require.NoError(t, err) originPixelDataInfo := dicom.MustGetPixelDataInfo(originPixelElement.Value) - fmt.Println(pixelDataInfo.UnprocessedValueData) for i := 0; i < metadata.Frames; i++ { originData := originPixelDataInfo.Frames[i].NativeData.Data inplaceData, err := ReadUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, i) require.NoError(t, err) - fmt.Println(originData) - fmt.Println(inplaceData) assert.Equal(t, originData, inplaceData, "missing match value", i) } }) From 16701faf01880474451f742d3a8c7b4aff370b61 Mon Sep 17 00:00:00 2001 From: quang18 Date: Tue, 28 May 2024 13:42:02 +1000 Subject: [PATCH 6/6] Fix: remove test helpers library and change the byte array order --- go.mod | 9 +--- go.sum | 17 ------ pkg/frame/inplace/read.go | 10 ++-- pkg/frame/inplace/read_test.go | 64 ++++++++++++++++++----- pkg/frame/inplace/write_test.go | 93 +++++++++++++++++++++------------ 5 files changed, 117 insertions(+), 76 deletions(-) diff --git a/go.mod b/go.mod index 35313d70..cc32e7da 100644 --- a/go.mod +++ b/go.mod @@ -8,11 +8,4 @@ require ( golang.org/x/text v0.3.8 ) -require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.4 // indirect - golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) +require golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect diff --git a/go.sum b/go.sum index f9de78b1..6cf56017 100644 --- a/go.sum +++ b/go.sum @@ -1,20 +1,7 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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= @@ -25,7 +12,3 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/frame/inplace/read.go b/pkg/frame/inplace/read.go index 6e6d40a0..871ea4e9 100644 --- a/pkg/frame/inplace/read.go +++ b/pkg/frame/inplace/read.go @@ -10,11 +10,11 @@ func ReadUnprocessedValueData(info *PixelDataMetadata, unprocessedValueData []by offset := frameIndex * pixelsPerFrame * info.SamplesPerPixel * bytesAllocated samplesPerPixel := info.SamplesPerPixel - re := make([][]int, pixelsPerFrame) - for i := 0; i < pixelsPerFrame; i++ { - re[i] = make([]int, samplesPerPixel) - for j := 0; j < samplesPerPixel; j++ { - pointOffset := offset + i*info.SamplesPerPixel*bytesAllocated + j*bytesAllocated + re := make([][]int, samplesPerPixel) + for i := 0; i < samplesPerPixel; i++ { + re[i] = make([]int, pixelsPerFrame) + for j := 0; j < pixelsPerFrame; j++ { + pointOffset := offset + j*info.SamplesPerPixel*bytesAllocated + i*bytesAllocated switch bytesAllocated { case 1: re[i][j] = int(unprocessedValueData[pointOffset]) diff --git a/pkg/frame/inplace/read_test.go b/pkg/frame/inplace/read_test.go index af729f26..1461047c 100644 --- a/pkg/frame/inplace/read_test.go +++ b/pkg/frame/inplace/read_test.go @@ -2,11 +2,12 @@ package inplace import ( "bytes" + "fmt" "io" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/suyashkumar/dicom" "github.com/suyashkumar/dicom/pkg/frame" @@ -15,8 +16,11 @@ import ( ) func mustNewElement(t *testing.T, tag tag.Tag, data interface{}) *dicom.Element { + t.Helper() elem, err := dicom.NewElement(tag, data) - require.NoError(t, err) + if err != nil { + t.Errorf("dicom.NewElement: %v", err) + } return elem } @@ -189,31 +193,65 @@ func TestReadReadUnprocessedValueData(t *testing.T) { for _, tc := range cases { t.Run(tc.Name, func(t *testing.T) { var filesOut bytes.Buffer - require.NoError(t, dicom.Write(io.Writer(&filesOut), tc.existingData)) + var err error + if err = dicom.Write(io.Writer(&filesOut), tc.existingData); err != nil { + t.Errorf("Write DICOM obj to bytes: %v", err) + } rawData := filesOut.Bytes() dataset, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil, dicom.SkipProcessingPixelDataValue()) - require.NoError(t, err) + if err != nil { + t.Errorf("dicom.Parse: %v", err) + } metadata, err := GetPixelDataMetadata(&dataset) - require.NoError(t, err) + if err != nil { + t.Errorf("GetPixelDataMetadata: %v", err) + } pixelElement, err := dataset.FindElementByTag(tag.PixelData) - require.NoError(t, err) + if err != nil { + t.Errorf("tag.PixelData not found: %v", err) + } pixelDataInfo := dicom.MustGetPixelDataInfo(pixelElement.Value) - assert.Equal(t, pixelDataInfo.IntentionallyUnprocessed, true) - assert.Equal(t, pixelDataInfo.IsEncapsulated, false) + if pixelDataInfo.IsEncapsulated || !pixelDataInfo.IntentionallyUnprocessed { + t.Errorf("pixelDataInfo should be IntentionallyUnprocessed: IsEncapsulated=%t IntentionallyUnprocessed%t", + pixelDataInfo.IsEncapsulated, pixelDataInfo.IntentionallyUnprocessed) + } - require.NoError(t, IsSafeForUnprocessedValueDataHandling(metadata, pixelDataInfo.UnprocessedValueData)) + if err = IsSafeForUnprocessedValueDataHandling(metadata, pixelDataInfo.UnprocessedValueData); err != nil { + t.Errorf("IsSafeForUnprocessedValueDataHandling(%v)", err) + } originPixelElement, err := tc.existingData.FindElementByTag(tag.PixelData) - require.NoError(t, err) + if err != nil { + t.Errorf("Find tag.PixelData(%v)", err) + } originPixelDataInfo := dicom.MustGetPixelDataInfo(originPixelElement.Value) for i := 0; i < metadata.Frames; i++ { originData := originPixelDataInfo.Frames[i].NativeData.Data inplaceData, err := ReadUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, i) - require.NoError(t, err) - assert.Equal(t, originData, inplaceData, "missing match value", i) + if err != nil { + t.Errorf("ReadUnprocessedValueData(%v)", err) + } + assertUnprocessedPixelData(t, originData, inplaceData, fmt.Sprint("frame:", i)) } }) } } + +func assertUnprocessedPixelData(t *testing.T, inplaceData [][]int, originData [][]int, msg string) { + if len(inplaceData[0]) != len(originData) || len(inplaceData) != len(originData[0]) { + t.Errorf("Mismatch data size: origin-data(%d-%d) inplace-data(%d-%d)", + len(originData), len(originData[0]), len(inplaceData), len(inplaceData[0])) + } + tmp := make([][]int, len(originData)) + for i := 0; i < len(tmp); i++ { + tmp[i] = make([]int, len(originData[0])) + for j := 0; j < len(originData[0]); j++ { + tmp[i][j] = inplaceData[j][i] + } + } + if diff := cmp.Diff(originData, tmp, cmpopts.EquateErrors()); diff != "" { + t.Errorf("assertUnprocessedPixelData(%v-%s): unexpected diff: %v", inplaceData, msg, diff) + } +} diff --git a/pkg/frame/inplace/write_test.go b/pkg/frame/inplace/write_test.go index 75d970a9..6b81f400 100644 --- a/pkg/frame/inplace/write_test.go +++ b/pkg/frame/inplace/write_test.go @@ -5,8 +5,8 @@ import ( "io" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/suyashkumar/dicom" "github.com/suyashkumar/dicom/pkg/frame" @@ -52,7 +52,9 @@ func TestWriteUnprocessedValueData(t *testing.T) { }, } var buffer bytes.Buffer - require.NoError(t, dicom.Write(io.Writer(&buffer), originDataset)) + if err := dicom.Write(io.Writer(&buffer), originDataset); err != nil { + t.Errorf("write from dataset to DICOM bytes: %v", err) + } rawData := buffer.Bytes() for _, tc := range []struct { @@ -96,21 +98,17 @@ func TestWriteUnprocessedValueData(t *testing.T) { }, }, } { - dataset, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil, dicom.SkipProcessingPixelDataValue()) - require.NoError(t, err) - metadata, err := GetPixelDataMetadata(&dataset) - require.NoError(t, err) - pixelElement, err := dataset.FindElementByTag(tag.PixelData) - require.NoError(t, err) - pixelDataInfo := dicom.MustGetPixelDataInfo(pixelElement.Value) - assert.Equal(t, pixelDataInfo.IntentionallyUnprocessed, true) - assert.Equal(t, pixelDataInfo.IsEncapsulated, false) - // Sanity check if the dataset is suitable for in-place read-write - require.NoError(t, IsSafeForUnprocessedValueDataHandling(metadata, pixelDataInfo.UnprocessedValueData)) + var err error + dataset, metadata, pixelDataInfo := setupUnprocessedPixelData(t, rawData) // in-place write then converts to bytes - require.NoError(t, WriteUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, tc.rowIndex, tc.colIndex, tc.frameIndex, tc.sampleIndex, 100)) + if err = WriteUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, + tc.rowIndex, tc.colIndex, tc.frameIndex, tc.sampleIndex, 100); err != nil { + t.Errorf("WriteUnprocessedValueData: %v", err) + } var buffer2 bytes.Buffer - require.NoError(t, dicom.Write(io.Writer(&buffer2), dataset)) + if err = dicom.Write(io.Writer(&buffer2), dataset); err != nil { + t.Errorf("Write new DICOM obj to file: %v", err) + } outputBytes := buffer2.Bytes() assertPixelDataInfo(t, outputBytes, tc.expectedPixelInfo) } @@ -154,7 +152,9 @@ func TestWriteUnprocessedValueData_BigEndian(t *testing.T) { }, } var buffer bytes.Buffer - require.NoError(t, dicom.Write(io.Writer(&buffer), originDataset)) + if err := dicom.Write(io.Writer(&buffer), originDataset); err != nil { + t.Errorf("write from dataset to DICOM bytes: %v", err) + } rawData := buffer.Bytes() for _, tc := range []struct { @@ -198,31 +198,58 @@ func TestWriteUnprocessedValueData_BigEndian(t *testing.T) { }, }, } { - dataset, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil, dicom.SkipProcessingPixelDataValue()) - require.NoError(t, err) - metadata, err := GetPixelDataMetadata(&dataset) - require.NoError(t, err) - pixelElement, err := dataset.FindElementByTag(tag.PixelData) - require.NoError(t, err) - pixelDataInfo := dicom.MustGetPixelDataInfo(pixelElement.Value) - assert.Equal(t, pixelDataInfo.IntentionallyUnprocessed, true) - assert.Equal(t, pixelDataInfo.IsEncapsulated, false) - // Sanity check if the dataset is suitable for in-place read-write - require.NoError(t, IsSafeForUnprocessedValueDataHandling(metadata, pixelDataInfo.UnprocessedValueData)) + dataset, metadata, pixelDataInfo := setupUnprocessedPixelData(t, rawData) + var err error // in-place write then converts to bytes - require.NoError(t, WriteUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, tc.rowIndex, tc.colIndex, tc.frameIndex, tc.sampleIndex, 100)) + if err = WriteUnprocessedValueData(metadata, pixelDataInfo.UnprocessedValueData, + tc.rowIndex, tc.colIndex, tc.frameIndex, tc.sampleIndex, 100); err != nil { + t.Errorf("WriteUnprocessedValueData: %v", err) + } var buffer2 bytes.Buffer - require.NoError(t, dicom.Write(io.Writer(&buffer2), dataset)) + if err = dicom.Write(io.Writer(&buffer2), dataset); err != nil { + t.Errorf("Write new DICOM obj to file: %v", err) + } outputBytes := buffer2.Bytes() assertPixelDataInfo(t, outputBytes, tc.expectedPixelInfo) } } +func setupUnprocessedPixelData(t testing.TB, rawData []byte) (dicom.Dataset, *PixelDataMetadata, dicom.PixelDataInfo) { + dataset, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil, dicom.SkipProcessingPixelDataValue()) + if err != nil { + t.Errorf("dicom.Parse: %v", err) + } + metadata, err := GetPixelDataMetadata(&dataset) + if err != nil { + t.Errorf("GetPixelDataMetadata: %v", err) + } + pixelElement, err := dataset.FindElementByTag(tag.PixelData) + if err != nil { + t.Errorf("tag.PixelData not found: %v", err) + } + pixelDataInfo := dicom.MustGetPixelDataInfo(pixelElement.Value) + if !pixelDataInfo.IntentionallyUnprocessed || pixelDataInfo.IsEncapsulated { + t.Errorf("unexpected pixelDataInfo: IntentionallyUnprocessed=%t IsEncapsulated=%t", + pixelDataInfo.IntentionallyUnprocessed, pixelDataInfo.IsEncapsulated) + } + // Sanity check if the dataset is suitable for in-place read-write + if err = IsSafeForUnprocessedValueDataHandling(metadata, pixelDataInfo.UnprocessedValueData); err != nil { + t.Errorf("IsSafeForUnprocessedValueDataHandling(%v)", err) + } + return dataset, metadata, pixelDataInfo +} + func assertPixelDataInfo(t testing.TB, rawData []byte, expectedPixelDataInfo dicom.PixelDataInfo) { dataset2, err := dicom.Parse(bytes.NewReader(rawData), int64(len(rawData)), nil) - require.NoError(t, err) + if err != nil { + t.Errorf("read new create dcm file: %v", err) + } pixelElement, err := dataset2.FindElementByTag(tag.PixelData) - require.NoError(t, err) + if err != nil { + t.Errorf("pixel data not found: %v", err) + } pixelDataInfo := dicom.MustGetPixelDataInfo(pixelElement.Value) - require.Equal(t, pixelDataInfo, expectedPixelDataInfo) + if diff := cmp.Diff(pixelDataInfo, expectedPixelDataInfo, cmpopts.EquateErrors()); diff != "" { + t.Errorf("assert pixelDataInfo in dicom file(%v): unexpected diff: %v", pixelDataInfo, diff) + } }