Skip to content

Commit

Permalink
Merge pull request #6 from okieraised/dev
Browse files Browse the repository at this point in the history
Export/Import annotations as 1D RLE-encoded array
  • Loading branch information
okieraised authored Mar 21, 2023
2 parents 32ef3d7 + 2274e3f commit 2197037
Show file tree
Hide file tree
Showing 6 changed files with 304 additions and 17 deletions.
28 changes: 18 additions & 10 deletions annotation.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import (
)

type Segmentation struct {
nii1Hdr *nifti.Nii1Header
nii2Hdr *nifti.Nii2Header
img *nifti.Nii
outFile string
compression bool
Annotations []SegmentCoordinate
nii1Hdr *nifti.Nii1Header
nii2Hdr *nifti.Nii2Header
img *nifti.Nii
outFile string
compression bool
annotations []SegmentCoordinate
annotationRLE []nifti.SegmentRLE
}

// SegmentCoordinate defines the structure for segmentation coordinate
Expand Down Expand Up @@ -75,7 +76,14 @@ func WithNii2Hdr(hdr *nifti.Nii2Header) SegmentationOption {
// WithAnnotations allows users to specify the annotation coordinates to convert to NIfTI file
func WithAnnotations(annotations []SegmentCoordinate) SegmentationOption {
return func(s *Segmentation) {
s.Annotations = annotations
s.annotations = annotations
}
}

// WithSegmentRLE allows users to specify the annotation as RLE-encoded array to convert to NIfTI file
func WithSegmentRLE(segments []nifti.SegmentRLE) SegmentationOption {
return func(s *Segmentation) {
s.annotationRLE = segments
}
}

Expand Down Expand Up @@ -130,7 +138,7 @@ func (s *Segmentation) AnnotationNiiToJson() error {

// AnnotationJsonToNii converts the annotation coordinates (x,y,z,t) array to a corresponding NIfTI file
func (s *Segmentation) AnnotationJsonToNii() error {
if s.Annotations == nil {
if s.annotations == nil {
return errors.New("no input annotations is specified")
}
if s.nii1Hdr == nil && s.nii2Hdr == nil {
Expand Down Expand Up @@ -176,7 +184,7 @@ func (s *Segmentation) convertSegmentationToNii1() error {

// NIfTI can have multiple annotations on the same file,
// so we have to assign the same pixel value for coordinates with same value
for _, coord := range s.Annotations {
for _, coord := range s.annotations {
var byteCode float64 = 1
_, ok := valMapper[coord.Value]
if !ok {
Expand Down Expand Up @@ -255,7 +263,7 @@ func (s *Segmentation) convertSegmentationToNii2() error {

// NIfTI can have multiple annotations on the same file,
// so we have to assign the same pixel value for coordinates with same value
for _, coord := range s.Annotations {
for _, coord := range s.annotations {
var byteCode float64 = 1
_, ok := valMapper[coord.Value]
if !ok {
Expand Down
51 changes: 51 additions & 0 deletions annotation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gonii

import (
"encoding/json"
"fmt"
"github.com/okieraised/gonii/pkg/nifti"
"github.com/stretchr/testify/assert"
"io/ioutil"
Expand Down Expand Up @@ -90,3 +91,53 @@ func TestNewSegmentation_JsonToNii(t *testing.T) {
err = s.AnnotationJsonToNii()
assert.NoError(err)
}

func TestSegmentation_Annotation(t *testing.T) {
assert := assert.New(t)

filePath := "./test_data/int16.nii.gz"
//filePath = "/home/tripg/workspace/nifti/Arnow^Corie^Shelvey^OM_segmented.nii.gz"
rd, err := NewNiiReader(WithReadImageFile(filePath), WithReadRetainHeader(true))
assert.NoError(err)
err = rd.Parse()
assert.NoError(err)
fmt.Println(rd.GetNiiData().GetVoxels().CountNoneZero())
fmt.Println("Here", rd.GetNiiData().Nx*rd.GetNiiData().Ny, rd.GetNiiData().Ny, rd.GetNiiData().Nt, rd.GetNiiData().Nz)

seg1 := []float64{10657, 7, 215, 7, 9, 11, 211, 11, 6, 13, 209, 13, 5, 13, 2, 7, 200, 13, 4, 25, 197, 15, 3, 26, 196, 15, 3, 26, 179, 7, 10, 15, 3, 27, 176, 11, 8, 15, 3, 27, 175, 13, 7, 15, 3, 31, 171, 13, 7, 15, 3, 33, 168, 15, 6, 15, 4, 33, 167, 15, 7, 13, 5, 33, 167, 15, 7, 13, 6, 33, 166, 15, 8, 11, 9, 7, 2, 22, 166, 15, 10, 7, 20, 22, 166, 15, 38, 21, 166, 15, 40, 19, 8, 7, 152, 13, 45, 15, 6, 11, 150, 13, 45, 15, 5, 13, 150, 11, 47, 13, 6, 13, 24, 8, 120, 7, 49, 33, 21, 12, 99, 7, 69, 32, 20, 14, 96, 11, 69, 30, 20, 14, 95, 13, 74, 24, 19, 16, 94, 13, 73, 25, 19, 16, 93, 15, 72, 25, 19, 16, 93, 15, 72, 25, 18, 17, 93, 15, 72, 25, 18, 17, 93, 15, 72, 25, 18, 17, 93, 15, 50, 7, 15, 25, 18, 17, 93, 15, 48, 11, 13, 25, 18, 16, 94, 15, 47, 13, 13, 24, 18, 16, 95, 13, 48, 13, 13, 25, 17, 16, 95, 13, 47, 15, 13, 24, 18, 15, 96, 11, 47, 16, 15, 22, 18, 15, 98, 7, 48, 18, 6, 7, 4, 19, 17, 16, 153, 33, 1, 21, 16, 16, 152, 56, 16, 15, 153, 56, 16, 15, 152, 58, 15, 15, 89, 7, 56, 58, 15, 15, 87, 11, 1, 7, 45, 60, 13, 16, 86, 22, 42, 61, 13, 15, 87, 23, 41, 61, 12, 16, 86, 24, 40, 62, 12, 15, 87, 25, 39, 63, 11, 15, 87, 25, 39, 63, 11, 15, 87, 25, 39, 64, 10, 15, 87, 25, 37, 66, 10, 15, 87, 25, 36, 67, 9, 16, 87, 25, 35, 68, 9, 15, 89, 24, 34, 69, 9, 15, 89, 23, 34, 70, 9, 15, 90, 22, 34, 70, 9, 15, 92, 7, 1, 11, 34, 71, 9, 15, 102, 7, 36, 72, 8, 15, 145, 72, 8, 14, 146, 73, 6, 15, 142, 77, 6, 14, 141, 79, 5, 15, 140, 80, 5, 15, 140, 80, 5, 15, 139, 81, 5, 15, 139, 81, 5, 15, 139, 80, 6, 15, 139, 81, 5, 15, 139, 20, 1, 61, 5, 13, 140, 20, 1, 61, 4, 14, 134, 89, 2, 14, 95, 7, 31, 91, 2, 13, 94, 11, 28, 92, 1, 15, 92, 13, 27, 92, 1, 15, 92, 13, 26, 93, 1, 15, 91, 15, 25, 93, 1, 15, 91, 15, 25, 93, 1, 15, 91, 15, 25, 92, 2, 15, 91, 15, 25, 92, 2, 15, 91, 15, 25, 91, 4, 13, 92, 15, 25, 108, 92, 15, 26, 106, 94, 13, 27, 104, 96, 13, 28, 104, 96, 11, 30, 103, 98, 7, 32, 103, 138, 102, 140, 100, 141, 99, 105, 7, 29, 99, 103, 11, 28, 97, 103, 13, 27, 97, 103, 13, 28, 101, 97, 15, 27, 109, 2, 19, 68, 15, 27, 140, 58, 15, 27, 144, 54, 15, 27, 146, 52, 15, 27, 147, 51, 15, 27, 147, 51, 15, 27, 82, 1, 65, 51, 13, 29, 80, 9, 58, 51, 13, 29, 80, 8, 59, 52, 11, 30, 79, 9, 59, 54, 7, 31, 80, 9, 59, 63, 7, 22, 80, 9, 59, 61, 11, 19, 82, 8, 59, 60, 13, 18, 83, 7, 58, 61, 13, 18, 83, 7, 58, 60, 18, 14, 84, 6, 15, 2, 7, 12, 21, 61, 20, 12, 84, 6, 15, 30, 10, 63, 21, 11, 84, 6, 15, 103, 21, 11, 84, 6, 15, 103, 23, 10, 83, 6, 15, 103, 25, 5, 86, 6, 15, 103, 26, 2, 88, 6, 15, 104, 25, 1, 88, 7, 15, 104, 114, 7, 15, 105, 113, 7, 15, 105, 113, 7, 15, 105, 113, 8, 13, 106, 113, 8, 13, 106, 113, 7, 13, 40, 8, 60, 112, 7, 13, 38, 12, 58, 111, 7, 15, 34, 16, 58, 22, 1, 87, 7, 15, 32, 18, 60, 7, 1, 11, 2, 86, 8, 15, 28, 23, 69, 7, 4, 73, 4, 7, 10, 15, 24, 27, 81, 72, 21, 15, 12, 9, 1, 29, 81, 72, 21, 15, 10, 41, 81, 72, 21, 15, 1, 50, 81, 77, 17, 65, 81, 79, 15, 65, 81, 80, 14, 65, 82, 79, 14, 64, 83, 80, 13, 64, 84, 79, 13, 63, 87, 59, 3, 15, 13, 61, 95, 52, 4, 15, 13, 57, 99, 50, 4, 17, 13, 54, 103, 41, 10, 19, 13, 52, 105, 42, 8, 20, 13, 48, 108, 50, 1, 19, 14, 44, 112, 70, 14, 42, 113, 70, 15, 30, 125, 68, 17, 19, 136, 20, 1, 7, 2, 36, 19, 15, 140, 19, 11, 36, 19, 15, 140, 17, 13, 36, 19, 15, 140, 15, 15, 36, 19, 15, 140, 15, 16, 34, 20, 15, 141, 13, 17, 34, 20, 15, 141, 13, 18, 32, 20, 16, 142, 13, 19, 20, 1, 7, 22, 15, 143, 13, 26, 13, 30, 15, 142, 15, 26, 11, 31, 15, 142, 15, 28, 7, 33, 15, 142, 15, 68, 15, 142, 15, 68, 15, 142, 15, 68, 15, 142, 15, 68, 15, 142, 15, 68, 16, 142, 13, 69, 16, 142, 13, 69, 16, 142, 13, 69, 16, 141, 15, 68, 16, 141, 15, 68, 16, 141, 15, 68, 16, 141, 16, 67, 15, 142, 16, 66, 16, 142, 17, 65, 16, 142, 17, 65, 16, 143, 16, 65, 16, 143, 16, 65, 16, 143, 16, 65, 16, 143, 16, 65, 16, 142, 17, 66, 15, 142, 16, 67, 14, 143, 16, 68, 13, 143, 16, 69, 11, 144, 16, 71, 7, 146, 16, 224, 16, 224, 16, 224, 15, 225, 15, 226, 13, 227, 13, 228, 11, 231, 7, 2071}
seg2 := []float64{12933, 8, 226, 16, 222, 19, 220, 22, 216, 25, 214, 28, 211, 30, 209, 32, 207, 34, 205, 36, 204, 37, 202, 39, 201, 40, 199, 43, 197, 44, 195, 46, 67, 17, 110, 46, 63, 24, 106, 47, 61, 28, 104, 47, 60, 30, 103, 47, 58, 33, 102, 47, 57, 36, 99, 48, 56, 39, 97, 48, 55, 41, 96, 48, 54, 43, 95, 48, 53, 43, 96, 48, 52, 43, 97, 48, 52, 42, 98, 15, 2, 31, 51, 43, 98, 16, 1, 31, 51, 42, 99, 17, 1, 30, 51, 42, 99, 48, 50, 42, 100, 48, 50, 42, 100, 48, 50, 41, 101, 48, 50, 40, 103, 47, 49, 41, 103, 47, 49, 40, 105, 46, 50, 39, 105, 46, 50, 39, 106, 45, 50, 39, 107, 44, 50, 37, 110, 43, 50, 36, 112, 42, 50, 35, 115, 40, 50, 34, 117, 38, 50, 34, 119, 36, 51, 34, 120, 34, 52, 33, 123, 30, 54, 33, 124, 27, 56, 33, 126, 23, 58, 33, 128, 19, 60, 29, 134, 14, 63, 27, 213, 26, 215, 25, 215, 24, 216, 24, 217, 23, 217, 23, 217, 23, 217, 23, 217, 17, 223, 15, 225, 14, 226, 14, 227, 12, 228, 12, 229, 11, 230, 10, 231, 9, 232, 8, 234, 6, 28016}
zIndex := 32

encoded1 := nifti.SegmentRLE{
EncodedSeg: seg1,
ZIndex: float64(zIndex),
TIndex: 0,
PixVal: 1,
}

encoded2 := nifti.SegmentRLE{
EncodedSeg: seg2,
ZIndex: float64(zIndex),
TIndex: 0,
PixVal: 2,
}

encoded := []nifti.SegmentRLE{encoded1, encoded2}
//encoded = []nifti.SegmentRLE{encoded1}

voxels, err := rd.GetNiiData().GetVoxels().ExportSingleFromRLE(encoded)
assert.NoError(err)

err = rd.GetNiiData().SetVoxelToRawVolume(voxels)
assert.NoError(err)

writer, err := NewNiiWriter("/home/tripg/workspace/int16_seg.nii.gz",
WithWriteNIfTIData(rd.GetNiiData()),
WithWriteCompression(true),
)
err = writer.WriteToFile()
assert.NoError(err)

fmt.Println(rd.GetNiiData().GetVoxels().MapValueOccurrence())
fmt.Println(rd.GetNiiData().IJKOrient, rd.GetNiiData().GetOrientation())
}
12 changes: 6 additions & 6 deletions io.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import (
// NewNiiReader returns a new NIfTI reader
//
// Options:
// - `WithReadInMemory(inMemory bool)` : Read the whole file into memory
// - `WithReadRetainHeader(retainHeader bool)` : Whether to retain the header structure after parsing
// - `WithReadHeaderFile(headerFile string)` : Specify a header file path in case of separate .hdr/.img file
// - `WithReadImageFile(niiFile string)` : Specify an image file path
// - `WithReadImageReader(r *bytes.Reader)` : Specify a header file reader in case of separate .hdr/.img file
// - `WithReadHeaderReader(r *bytes.Reader)` : Specify an image file reader
// - `WithReadInMemory(inMemory bool)` : Read the whole file into memory
// - `WithReadRetainHeader(retainHeader bool)` : Whether to retain the header structure after parsing
// - `WithReadHeaderFile(headerFile string)` : Specify a header file path in case of separate .hdr/.img file
// - `WithReadImageFile(niiFile string)` : Specify an image file path
// - `WithReadImageReader(r *bytes.Reader)` : Specify a header file reader in case of separate .hdr/.img file
// - `WithReadHeaderReader(r *bytes.Reader)` : Specify an image file reader
func NewNiiReader(options ...func(*nifti.NiiReader) error) (nifti.Reader, error) {
// Init new reader
reader := new(nifti.NiiReader)
Expand Down
85 changes: 85 additions & 0 deletions pkg/nifti/annotation_rle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package nifti

type SegmentRLE struct {
EncodedSeg []float64
DecodedSeg []float64
ZIndex float64
TIndex float64
PixVal float64
}

//type SegmentationRLEOption func(s *SegmentRLE)
//
//// WithEncodedSegmentation allows user to specify the RLE-encoded segmentation
//func WithEncodedSegmentation(encodedSeg []float64) SegmentationRLEOption {
// return func(s *SegmentRLE) {
// s.encodedSeg = encodedSeg
// }
//}
//
//// WithDecodedSegmentation allows user to specify the RLE-decoded segmentation
//func WithDecodedSegmentation(decodedSeg []float64) SegmentationRLEOption {
// return func(s *SegmentRLE) {
// s.decodedSeg = decodedSeg
// }
//}
//
//// WithZIndex allows user to specify the z-index of the RLE-encoded segmentation
//func WithZIndex(zIndex float64) SegmentationRLEOption {
// return func(s *SegmentRLE) {
// s.zIndex = zIndex
// }
//}
//
//// WithTIndex allows user to specify the z-index of the RLE-encoded segmentation
//func WithTIndex(tIndex float64) SegmentationRLEOption {
// return func(s *SegmentRLE) {
// s.tIndex = tIndex
// }
//}
//
//// WithPixVal allows user to specify the pixel value of the encoded segment
//func WithPixVal(pixVal float64) SegmentationRLEOption {
// return func(s *SegmentRLE) {
// s.pixVal = pixVal
// }
//}
//
//func NewAnnotationRLE(opts ...SegmentationRLEOption) *SegmentRLE {
// res := &SegmentRLE{}
//
// for _, opt := range opts {
// opt(res)
// }
//
// return res
//}

func (a *SegmentRLE) Decode() {
var deflatedSegment []float64

for idx, segmentLength := range a.EncodedSeg {
var s []float64
s = make([]float64, int(segmentLength))
if idx%2 == 0 {
for i := range s {
s[i] = 0
}
} else {
for i := range s {
s[i] = a.PixVal
}
}
deflatedSegment = append(deflatedSegment, s...)
}
a.DecodedSeg = deflatedSegment
}

func (a *SegmentRLE) Encode() error {
encodedSegment, err := RLEEncode(a.DecodedSeg)
if err != nil {
return err
}
a.EncodedSeg = encodedSegment
return nil
}
59 changes: 59 additions & 0 deletions pkg/nifti/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -721,3 +721,62 @@ func WriteToFile(filePath string, compression bool, dataset []byte) error {
}
return nil
}

func RLEEncode(original []float64) ([]float64, error) {
var rleEncoded []float64

if len(original) == 0 {
return nil, errors.New("array has length zero")
}

//v.voxel = []float64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}

for i := 0; i < len(original); i++ {
var count float64 = 1
if i == 0 && original[i] != 0 {
rleEncoded = append(rleEncoded, 0)
}
for {
if i < len(original)-1 && original[i] == original[i+1] {
count++
i++
} else {
break
}
}
rleEncoded = append(rleEncoded, count)
}

//fmt.Println("rleEncoded", rleEncoded)
//fmt.Println("len(rleEncoded)", len(rleEncoded))

return rleEncoded, nil
}

//func RLEDecode(compressed []float64, val float64) ([]float64, error) {
// var rleEncoded []float64
//
// if len(compressed) == 0 {
// return nil, errors.New("array has length zero")
// }
//
// var original []float64
//
// var s []int
// for idx, segmentLength := range compressed {
// if idx%2 == 0 {
// s = make([]int, segmentLength)
// for i := range s {
// s[i] = 0
// }
// } else {
// s = make([]int, segmentLength)
// for i := range s {
// s[i] = 1
// }
// }
// inflatedSeg = append(inflatedSeg, s...)
// }
//
// return rleEncoded, nil
//}
86 changes: 85 additions & 1 deletion pkg/nifti/voxel.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package nifti

import "github.com/okieraised/gonii/internal/utils"
import (
"errors"
"github.com/okieraised/gonii/internal/utils"
)

// Voxels defines the structure of Voxel values
type Voxels struct {
Expand Down Expand Up @@ -87,3 +90,84 @@ func (v *Voxels) CountNoneZero() (pos, neg, zero int) {
func (v *Voxels) Histogram(bins int) (utils.Histogram, error) {
return utils.Hist(bins, v.voxel)
}

// RLEEncode encodes the 1-D float64 array using the RLE encoding
func (v *Voxels) RLEEncode() ([]float64, error) {
//v.voxel = []float64{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 11, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
return RLEEncode(v.voxel)
}

// MapValueOccurrence maps the occurrence of pixel value as map[float64]int
func (v *Voxels) MapValueOccurrence() map[float64]int {
valMapper := make(map[float64]int)
for _, val := range v.voxel {
valMapper[val] = valMapper[val] + 1
}
return valMapper
}

// ImportAsRLE import the NIfTI image as an array of RLE-encoded segment
func (v *Voxels) ImportAsRLE() ([]SegmentRLE, error) {
valMapper := v.MapValueOccurrence()
var result []SegmentRLE

for z := int64(0); z < v.dimZ; z++ {
for t := int64(0); t < v.dimT; t++ {
sliceData := v.GetSlice(z, t)
for key, _ := range valMapper {
if key == 0 {
continue
}
keyArr := make([]float64, len(sliceData))
for idx, voxVal := range sliceData {
if voxVal == key {
keyArr[idx] = key
}
}
encoded, err := RLEEncode(keyArr)
if err != nil {
return nil, err
}

encodedSegment := SegmentRLE{
EncodedSeg: encoded,
DecodedSeg: sliceData,
ZIndex: float64(z),
TIndex: float64(t),
PixVal: key,
}
result = append(result, encodedSegment)
}
}
}
return result, nil
}

// ExportSingleFromRLE reconstruct a single NIfTI image from input RLE-encoded 1-D segments
func (v *Voxels) ExportSingleFromRLE(segments []SegmentRLE) (*Voxels, error) {

if len(segments) == 0 {
return v, errors.New("segments has length 0")
}

originalLength := make([]float64, len(v.voxel))
for _, segment := range segments {
segment.Decode()
for x := int64(0); x < v.dimX; x++ {
for y := int64(0); y < v.dimY; y++ {
for z := int64(0); z < v.dimZ; z++ {
for t := int64(0); t < v.dimT; t++ {
if z == v.dimZ-1-int64(segment.ZIndex) {
idx := t*v.dimZ*v.dimY*v.dimX + z*v.dimY*v.dimX + y*v.dimX + x
if segment.DecodedSeg[y*v.dimX+x] != 0 {
originalLength[idx] = segment.PixVal
}
}
}
}
}
}
}
v.voxel = originalLength
return v, nil
}

0 comments on commit 2197037

Please sign in to comment.