Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/Value.IsZero method #196

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 90 additions & 8 deletions element.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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).
Expand All @@ -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
Expand All @@ -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
Expand Down
208 changes: 208 additions & 0 deletions element_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dicom

import (
"encoding/json"
"github.com/suyashkumar/dicom/pkg/frame"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -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)
Expand Down