From 82da81924db9872d299a0f27fa0c6623923de7db Mon Sep 17 00:00:00 2001 From: quang_neo Date: Sun, 10 Mar 2024 09:12:28 +1100 Subject: [PATCH] write: fix/support BigEndian PixelData writes, pad odd byte lengths with extra byte (#302) --------- Co-authored-by: quang18 Co-authored-by: Suyash Kumar --- write.go | 14 ++++-- write_test.go | 126 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 107 insertions(+), 33 deletions(-) diff --git a/write.go b/write.go index 88a2b395..d58aca1e 100644 --- a/write.go +++ b/write.go @@ -580,21 +580,22 @@ func writePixelData(w dicomio.Writer, t tag.Tag, value Value, vr string, vl uint 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 { case 8: - if err := binary.Write(buf, binary.LittleEndian, uint8(pixelValue)); err != nil { + if err := binary.Write(buf, bo, uint8(pixelValue)); err != nil { return err } case 16: - if err := binary.Write(buf, binary.LittleEndian, uint16(pixelValue)); err != nil { + if err := binary.Write(buf, bo, uint16(pixelValue)); err != nil { return err } case 32: - if err := binary.Write(buf, binary.LittleEndian, uint32(pixelValue)); err != nil { + if err := binary.Write(buf, bo, uint32(pixelValue)); err != nil { return err } default: @@ -603,6 +604,13 @@ func writePixelData(w dicomio.Writer, t tag.Tag, value Value, vr string, vl uint } } } + // If the byte length is not even, append 1 padding byte to make it even. + // https://dicom.nema.org/medical/dicom/current/output/html/part05.html#sect_8.1.1 + if buf.Len()%2 != 0 { + if err := buf.WriteByte(0); err != nil { + return err + } + } if err := w.WriteBytes(buf.Bytes()); err != nil { return err } diff --git a/write_test.go b/write_test.go index 5c67fd59..2c134c8a 100644 --- a/write_test.go +++ b/write_test.go @@ -442,6 +442,71 @@ func TestWrite(t *testing.T) { }}, expectedError: nil, }, + { + name: "native_PixelData_2samples_2frames_BigEndian", + dataset: Dataset{Elements: []*Element{ + mustNewElement(tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), + mustNewElement(tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), + mustNewElement(tag.TransferSyntaxUID, []string{uid.ExplicitVRBigEndian}), + mustNewElement(tag.Rows, []int{2}), + mustNewElement(tag.Columns, []int{2}), + mustNewElement(tag.BitsAllocated, []int{32}), + mustNewElement(tag.NumberOfFrames, []string{"2"}), + mustNewElement(tag.SamplesPerPixel, []int{2}), + mustNewElement(tag.PixelData, PixelDataInfo{ + IsEncapsulated: false, + Frames: []*frame.Frame{ + { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 32, + Rows: 2, + Cols: 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}}, + }, + }, + }, + }), + }}, + expectedError: nil, + }, + { + name: "native_PixelData_odd_bytes", + dataset: Dataset{Elements: []*Element{ + mustNewElement(tag.MediaStorageSOPClassUID, []string{"1.2.840.10008.5.1.4.1.1.1.2"}), + mustNewElement(tag.MediaStorageSOPInstanceUID, []string{"1.2.3.4.5.6.7"}), + mustNewElement(tag.TransferSyntaxUID, []string{uid.ImplicitVRLittleEndian}), + mustNewElement(tag.Rows, []int{1}), + mustNewElement(tag.Columns, []int{3}), + mustNewElement(tag.BitsAllocated, []int{8}), + mustNewElement(tag.NumberOfFrames, []string{"1"}), + mustNewElement(tag.SamplesPerPixel, []int{1}), + mustNewElement(tag.PixelData, PixelDataInfo{ + IsEncapsulated: false, + Frames: []*frame.Frame{ + { + Encapsulated: false, + NativeData: frame.NativeFrame{ + BitsPerSample: 8, + Rows: 1, + Cols: 3, + Data: [][]int{{1}, {2}, {3}}, + }, + }, + }, + }), + }}, + expectedError: nil, + }, { name: "PixelData with IntentionallyUnprocessed=true", dataset: Dataset{Elements: []*Element{ @@ -500,43 +565,44 @@ func TestWrite(t *testing.T) { t.Fatalf("Unexpected error when creating tempfile: %v", err) } if err = Write(file, tc.dataset, tc.opts...); err != tc.expectedError { - t.Errorf("Write(%v): unexpected error. got: %v, want: %v", tc.dataset, err, tc.expectedError) + t.Fatalf("Write(%v): unexpected error. got: %v, want: %v", tc.dataset, err, tc.expectedError) } file.Close() - + // If we expect an error, we do not need to continue to check the value of the written data, so we continue to the next test case. + if tc.expectedError != nil { + return + } // Read the data back in and check for equality to the tc.dataset: - if tc.expectedError == nil { - f, err := os.Open(file.Name()) - if err != nil { - t.Fatalf("Unexpected error opening file %s: %v", file.Name(), err) - } - info, err := f.Stat() - if err != nil { - t.Fatalf("Unexpected error state file: %s: %v", file.Name(), err) - } + f, err := os.Open(file.Name()) + if err != nil { + t.Fatalf("Unexpected error opening file %s: %v", file.Name(), err) + } + info, err := f.Stat() + if err != nil { + t.Fatalf("Unexpected error state file: %s: %v", file.Name(), err) + } - readDS, err := Parse(f, info.Size(), nil, tc.parseOpts...) - if err != nil { - t.Errorf("Parse of written file, unexpected error: %v", err) - } + readDS, err := Parse(f, info.Size(), nil, tc.parseOpts...) + if err != nil { + t.Errorf("Parse of written file, unexpected error: %v", err) + } - wantElems := append(tc.dataset.Elements, tc.extraElems...) + wantElems := append(tc.dataset.Elements, tc.extraElems...) - cmpOpts := []cmp.Option{ - cmp.AllowUnexported(allValues...), - cmpopts.IgnoreFields(Element{}, "ValueLength"), - cmpopts.IgnoreSliceElements(func(e *Element) bool { return e.Tag == tag.FileMetaInformationGroupLength }), - cmpopts.SortSlices(func(x, y *Element) bool { return x.Tag.Compare(y.Tag) == 1 }), - } - cmpOpts = append(cmpOpts, tc.cmpOpts...) + cmpOpts := []cmp.Option{ + cmp.AllowUnexported(allValues...), + cmpopts.IgnoreFields(Element{}, "ValueLength"), + cmpopts.IgnoreSliceElements(func(e *Element) bool { return e.Tag == tag.FileMetaInformationGroupLength }), + cmpopts.SortSlices(func(x, y *Element) bool { return x.Tag.Compare(y.Tag) == 1 }), + } + cmpOpts = append(cmpOpts, tc.cmpOpts...) - if diff := cmp.Diff( - wantElems, - readDS.Elements, - cmpOpts..., - ); diff != "" { - t.Errorf("Reading back written dataset led to unexpected diff from source data: %s", diff) - } + if diff := cmp.Diff( + wantElems, + readDS.Elements, + cmpOpts..., + ); diff != "" { + t.Errorf("Reading back written dataset led to unexpected diff from source data: %s", diff) } }) }