diff --git a/element.go b/element.go index 8678b4d7..a770a6b9 100644 --- a/element.go +++ b/element.go @@ -70,6 +70,26 @@ type Value interface { // ValueType returns the underlying ValueType of this Value. This can be used to unpack the underlying data in this // Value. ValueType() ValueType + // IsEmpty returns true if the value is an 'empty' value. The following values are + // considered 'empty': + // + // A []string value where all sub-values are "". + // + // A []int value where all sub-values are 0. + // + // A []float64 value where all sub-values are 0.0. + // + // A []byte value of 0 length. + // + // A SequenceItemValue with no elements, or where all sub-elements are empty. + // + // A []SequenceItemValue with no items or where all items are empty. + // + // A PixelDataInfo value with 0 frames. + // + // Make special note of the []int and []float behavior, as for some tags, 0 is a + // meaningful value. + IsZero() bool // GetValue returns the underlying value that this Value holds. What type is returned here can be determined exactly // from the ValueType() of this Value (see the ValueType godoc). GetValue() interface{} // TODO: rename to Get to read cleaner @@ -181,6 +201,7 @@ type bytesValue struct { func (b *bytesValue) isElementValue() {} func (b *bytesValue) ValueType() ValueType { return Bytes } +func (b *bytesValue) IsZero() bool { return b.value == nil || len(b.value) == 0 } func (b *bytesValue) GetValue() interface{} { return b.value } func (b *bytesValue) String() string { return fmt.Sprintf("%v", b.value) @@ -194,8 +215,19 @@ type stringsValue struct { value []string } -func (s *stringsValue) isElementValue() {} -func (s *stringsValue) ValueType() ValueType { return Strings } +func (s *stringsValue) isElementValue() {} +func (s *stringsValue) ValueType() ValueType { return Strings } +func (s *stringsValue) IsZero() bool { + // If any of our string values are not an empty string, this value is not an + // empty value. + for _, value := range s.value { + if value != "" { + return false + } + } + + return true +} func (s *stringsValue) GetValue() interface{} { return s.value } func (s *stringsValue) String() string { return fmt.Sprintf("%v", s.value) @@ -209,8 +241,18 @@ type intsValue struct { value []int } -func (s *intsValue) isElementValue() {} -func (s *intsValue) ValueType() ValueType { return Ints } +func (s *intsValue) isElementValue() {} +func (s *intsValue) ValueType() ValueType { return Ints } +func (s *intsValue) IsZero() bool { + // If any of our string values are not 0, this value is not an empty value. + for _, value := range s.value { + if value != 0 { + return false + } + } + + return true +} func (s *intsValue) GetValue() interface{} { return s.value } func (s *intsValue) String() string { return fmt.Sprintf("%v", s.value) @@ -224,8 +266,18 @@ type floatsValue struct { value []float64 } -func (s *floatsValue) isElementValue() {} -func (s *floatsValue) ValueType() ValueType { return Floats } +func (s *floatsValue) isElementValue() {} +func (s *floatsValue) ValueType() ValueType { return Floats } +func (s *floatsValue) IsZero() bool { + // If any of our string values are not 0, this value is not an empty value. + for _, value := range s.value { + if value != 0 { + return false + } + } + + return true +} func (s *floatsValue) GetValue() interface{} { return s.value } func (s *floatsValue) String() string { return fmt.Sprintf("%v", s.value) @@ -247,6 +299,21 @@ func (s *SequenceItemValue) isElementValue() {} // to unpack the underlying data in this Value. func (s *SequenceItemValue) ValueType() ValueType { return SequenceItem } +func (s *SequenceItemValue) IsZero() bool { + if s.elements == nil || len(s.elements) == 0 { + return true + } + + // If any of our sub-elements are not empty, this SequenceItemValue is not empty. + for _, element := range s.elements { + if !element.Value.IsZero() { + return false + } + } + + return true +} + // GetValue returns the underlying value that this Value holds. What type is // returned here can be determined exactly from the ValueType() of this Value // (see the ValueType godoc). @@ -268,8 +335,22 @@ type sequencesValue struct { value []*SequenceItemValue } -func (s *sequencesValue) isElementValue() {} -func (s *sequencesValue) ValueType() ValueType { return Sequences } +func (s *sequencesValue) isElementValue() {} +func (s *sequencesValue) ValueType() ValueType { return Sequences } +func (s *sequencesValue) IsZero() bool { + if s.value == nil || len(s.value) == 0 { + return true + } + + // If any of our sequence items are not empty, this SequenceItemValue is not empty. + for _, thisItem := range s.value { + if !thisItem.IsZero() { + return false + } + } + + return true +} func (s *sequencesValue) GetValue() interface{} { return s.value } func (s *sequencesValue) String() string { // TODO: consider adding more sophisticated formatting @@ -293,6 +374,7 @@ type pixelDataValue struct { func (e *pixelDataValue) isElementValue() {} func (e *pixelDataValue) ValueType() ValueType { return PixelData } +func (e *pixelDataValue) IsZero() bool { return len(e.PixelDataInfo.Frames) == 0 } func (e *pixelDataValue) GetValue() interface{} { return e.PixelDataInfo } func (e *pixelDataValue) String() string { // TODO: consider adding more sophisticated formatting diff --git a/element_test.go b/element_test.go index a5d88a41..4980a6e9 100644 --- a/element_test.go +++ b/element_test.go @@ -2,6 +2,7 @@ package dicom import ( "encoding/json" + "github.com/suyashkumar/dicom/pkg/frame" "testing" "github.com/google/go-cmp/cmp" @@ -137,6 +138,213 @@ func TestNewValue(t *testing.T) { } } +func TestValue_IsEmpty(t *testing.T) { + cases := []struct { + name string + value Value + expectedIsEmpty bool + }{ + // STRINGS VALUE + { + name: "strings_none_empty", + value: &stringsValue{value: []string{"a", "b"}}, + expectedIsEmpty: false, + }, + { + name: "strings_one_empty", + value: &stringsValue{value: []string{"a", ""}}, + expectedIsEmpty: false, + }, + { + name: "strings_all_empty", + value: &stringsValue{value: []string{"", ""}}, + expectedIsEmpty: true, + }, + { + name: "strings_single_empty", + value: &stringsValue{value: []string{""}}, + expectedIsEmpty: true, + }, + + // INTS VALUE + { + name: "ints_none_empty", + value: &intsValue{value: []int{1, 2}}, + expectedIsEmpty: false, + }, + { + name: "ints_one_empty", + value: &intsValue{value: []int{0, 2}}, + expectedIsEmpty: false, + }, + { + name: "ints_all_empty", + value: &intsValue{value: []int{0, 0}}, + expectedIsEmpty: true, + }, + { + name: "ints_single_empty", + value: &intsValue{value: []int{0}}, + expectedIsEmpty: true, + }, + + // FLOATS VALUE + { + name: "floats_none_empty", + value: &floatsValue{value: []float64{1, 2}}, + expectedIsEmpty: false, + }, + { + name: "floats_one_empty", + value: &floatsValue{value: []float64{0, 2}}, + expectedIsEmpty: false, + }, + { + name: "floats_all_empty", + value: &floatsValue{value: []float64{0, 0}}, + expectedIsEmpty: true, + }, + { + name: "floats_single_empty", + value: &floatsValue{value: []float64{0}}, + expectedIsEmpty: true, + }, + + // BYTES + { + name: "bytes_not_empty", + value: &bytesValue{value: []byte{0x0, 0x1}}, + expectedIsEmpty: false, + }, + { + name: "bytes_empty", + value: &bytesValue{value: []byte{}}, + expectedIsEmpty: true, + }, + + // PIXEL DATA + { + name: "PixelData_empty", + value: &pixelDataValue{PixelDataInfo{IsEncapsulated: true}}, + expectedIsEmpty: true, + }, + { + name: "PixelData_not_empty", + value: &pixelDataValue{ + PixelDataInfo{ + Frames: []frame.Frame{ + {}, + }, + IsEncapsulated: true, + }, + }, + expectedIsEmpty: false, + }, + + // SEQUENCES + { + name: "sequence_empty", + expectedIsEmpty: true, + value: &sequencesValue{value: []*SequenceItemValue{}}, + }, + { + name: "sequence_empty_sub_elements", + expectedIsEmpty: true, + value: &sequencesValue{value: []*SequenceItemValue{ + { + elements: []*Element{ + { + Tag: tag.PatientName, + ValueRepresentation: tag.VRString, + Value: &stringsValue{ + value: []string{""}, + }, + }, + }, + }, + }}, + }, + { + name: "sequence_multiple_empty_sub_elements", + expectedIsEmpty: true, + value: &sequencesValue{value: []*SequenceItemValue{ + { + elements: []*Element{ + { + Tag: tag.PatientName, + ValueRepresentation: tag.VRString, + Value: &stringsValue{ + value: []string{""}, + }, + }, + { + Tag: tag.SOPInstanceUID, + ValueRepresentation: tag.VRString, + Value: &stringsValue{ + value: []string{""}, + }, + }, + }, + }, + }}, + }, + { + name: "sequence_not_empty", + expectedIsEmpty: false, + value: &sequencesValue{value: []*SequenceItemValue{ + { + elements: []*Element{ + { + Tag: tag.PatientName, + ValueRepresentation: tag.VRString, + Value: &stringsValue{ + value: []string{"Bob"}, + }, + }, + }, + }, + }}, + }, + { + name: "sequence_one_empty_sub_elements", + expectedIsEmpty: false, + value: &sequencesValue{value: []*SequenceItemValue{ + { + elements: []*Element{ + { + Tag: tag.PatientName, + ValueRepresentation: tag.VRString, + Value: &stringsValue{ + value: []string{"bob"}, + }, + }, + { + Tag: tag.SOPInstanceUID, + ValueRepresentation: tag.VRString, + Value: &stringsValue{ + value: []string{""}, + }, + }, + }, + }, + }}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + isEmpty := tc.value.IsZero() + if isEmpty != tc.expectedIsEmpty { + t.Errorf( + "Value.IsZero() returned %v, expected %v", + isEmpty, + tc.expectedIsEmpty, + ) + } + }) + } +} + func TestNewValue_UnexpectedType(t *testing.T) { data := 10 _, err := NewValue(data)