Skip to content

Commit

Permalink
Merge pull request #8 from okieraised/dev
Browse files Browse the repository at this point in the history
New writer to export NIfTI as bytes slice
  • Loading branch information
okieraised authored Mar 22, 2023
2 parents 2197037 + 7da65bf commit fd08cf7
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 36 deletions.
49 changes: 49 additions & 0 deletions annotation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,52 @@ func TestSegmentation_Annotation(t *testing.T) {
fmt.Println(rd.GetNiiData().GetVoxels().MapValueOccurrence())
fmt.Println(rd.GetNiiData().IJKOrient, rd.GetNiiData().GetOrientation())
}

func TestSegmentation_Annotation2(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),
)
b, err := writer.WriteToBytes()
assert.NoError(err)

fmt.Println(len(b))
}
137 changes: 101 additions & 36 deletions pkg/nifti/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,19 @@ type Writer interface {
GetNiiData() *Nii
// GetHeader returns the current NIfTI header
GetHeader() interface{}
// WriteToBytes the NIfTI dataset as byte slice
WriteToBytes() ([]byte, error)
}

// NiiWriter define the NIfTI writer structure.
//
// Parameters:
// - `filePath` : Export file path to write NIfTI image
// - `writeHeaderFile` : Whether to write NIfTI file pair (hdr + img file)
// - `compression` : Whether the NIfTI volume will be compressed. If writeHeaderFile is set to True, both the .hdr and .img files will be compressed
// - `niiData` : Input NIfTI data to write to file
// - `header` : Input NIfTI header to write to file. If nil, the default header will be constructed
// - `version` : Specify the version (NIfTI-1 or NIfTI-2) to export
// - `filePath` : Export file path to write NIfTI image
// - `writeHeaderFile` : Whether to write NIfTI file pair (hdr + img file)
// - `compression` : Whether the NIfTI volume will be compressed. If writeHeaderFile is set to True, both the .hdr and .img files will be compressed
// - `niiData` : Input NIfTI data to write to file
// - `header` : Input NIfTI header to write to file. If nil, the default header will be constructed
// - `version` : Specify the version (NIfTI-1 or NIfTI-2) to export
type NiiWriter struct {
filePath string // Export file path to write NIfTI image
writeHeaderFile bool // Whether to write NIfTI file pair (hdr + img file)
Expand Down Expand Up @@ -63,6 +65,26 @@ func (w *NiiWriter) SetVersion(version int) {
w.version = version
}

func (w *NiiWriter) WriteToBytes() ([]byte, error) {
// Convert image to header
switch w.version {
case NIIVersion1:
err := w.convertImageToNii1Header()
if err != nil {
return nil, err
}
case NIIVersion2:
err := w.convertImageToNii2Header()
if err != nil {
return nil, err
}
default:
return nil, fmt.Errorf("unknown NIfTI version %d", w.version)
}

return w.reconstructDataset()
}

// WriteToFile write the header and image to either a single NIfTI file or a pair of .hdr/.img file
func (w *NiiWriter) WriteToFile() error {
// Convert image to header
Expand Down Expand Up @@ -97,6 +119,44 @@ func (w *NiiWriter) WriteToFile() error {
return nil
}

func (w *NiiWriter) reconstructDataset() ([]byte, error) {
var offset []byte
var offsetFromHeaderToVoxel int

// Need to get the number of bytes between the end of header structure and the start of the image data
switch hdr := w.header.(type) {
case *Nii1Header:
offsetFromHeaderToVoxel = int(hdr.VoxOffset) - int(hdr.SizeofHdr)
case *Nii2Header:
offsetFromHeaderToVoxel = int(hdr.VoxOffset) - int(hdr.SizeofHdr)
default:
return nil, fmt.Errorf("unknown header type")
}

if offsetFromHeaderToVoxel > 0 {
offset = make([]byte, offsetFromHeaderToVoxel, offsetFromHeaderToVoxel)
} else {
offset = make([]byte, DefaultHeaderPadding, DefaultHeaderPadding)
}

// Make a buffer and write the header to it with default system endian
hdrBuf := &bytes.Buffer{}
err := binary.Write(hdrBuf, system.NativeEndian, w.header)
if err != nil {
return nil, err
}

bHeader := hdrBuf.Bytes()
bData := w.niiData.Volume

var dataset []byte
dataset = append(dataset, bHeader...)
dataset = append(dataset, offset...)
dataset = append(dataset, bData...)

return dataset, nil
}

// writePairNii writes the header and NIfTI image Nii as 2 separate files
func (w *NiiWriter) writePairNii() error {
var headerFilePath string
Expand Down Expand Up @@ -185,40 +245,45 @@ func (w *NiiWriter) writePairNii() error {

// writeSingleNii writes the header and NIfTI image Nii to a single NIfTI file
func (w *NiiWriter) writeSingleNii() error {
var offset []byte
var offsetFromHeaderToVoxel int

// Need to get the number of bytes between the end of header structure and the start of the image data
switch hdr := w.header.(type) {
case *Nii1Header:
offsetFromHeaderToVoxel = int(hdr.VoxOffset) - int(hdr.SizeofHdr)
case *Nii2Header:
offsetFromHeaderToVoxel = int(hdr.VoxOffset) - int(hdr.SizeofHdr)
default:
return fmt.Errorf("unknown header type")
}

if offsetFromHeaderToVoxel > 0 {
offset = make([]byte, offsetFromHeaderToVoxel, offsetFromHeaderToVoxel)
} else {
offset = make([]byte, DefaultHeaderPadding, DefaultHeaderPadding)
}

// Make a buffer and write the header to it with default system endian
hdrBuf := &bytes.Buffer{}
err := binary.Write(hdrBuf, system.NativeEndian, w.header)
//var offset []byte
//var offsetFromHeaderToVoxel int
//
//// Need to get the number of bytes between the end of header structure and the start of the image data
//switch hdr := w.header.(type) {
//case *Nii1Header:
// offsetFromHeaderToVoxel = int(hdr.VoxOffset) - int(hdr.SizeofHdr)
//case *Nii2Header:
// offsetFromHeaderToVoxel = int(hdr.VoxOffset) - int(hdr.SizeofHdr)
//default:
// return fmt.Errorf("unknown header type")
//}
//
//if offsetFromHeaderToVoxel > 0 {
// offset = make([]byte, offsetFromHeaderToVoxel, offsetFromHeaderToVoxel)
//} else {
// offset = make([]byte, DefaultHeaderPadding, DefaultHeaderPadding)
//}
//
//// Make a buffer and write the header to it with default system endian
//hdrBuf := &bytes.Buffer{}
//err := binary.Write(hdrBuf, system.NativeEndian, w.header)
//if err != nil {
// return err
//}
//
//bHeader := hdrBuf.Bytes()
//bData := w.niiData.Volume
//
//var dataset []byte
//dataset = append(dataset, bHeader...)
//dataset = append(dataset, offset...)
//dataset = append(dataset, bData...)

dataset, err := w.reconstructDataset()
if err != nil {
return err
}

bHeader := hdrBuf.Bytes()
bData := w.niiData.Volume

var dataset []byte
dataset = append(dataset, bHeader...)
dataset = append(dataset, offset...)
dataset = append(dataset, bData...)

// Check if the user-specified filePath suffix is ending with '.nii' or '.gz'.
// If not, we append '.nii' to the end to signify the file is NIfTI format
if !strings.HasSuffix(w.filePath, NIFTI_EXT) && !strings.HasSuffix(w.filePath, NIFTI_COMPRESSED_EXT) {
Expand Down

0 comments on commit fd08cf7

Please sign in to comment.