Skip to content

Commit

Permalink
Optimize memory usage
Browse files Browse the repository at this point in the history
  • Loading branch information
segmed-lam committed Jul 30, 2024
1 parent ef04346 commit bf55131
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 68 deletions.
5 changes: 1 addition & 4 deletions pkg/codec/codec.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,12 @@ unsigned char * J2K_Decode(unsigned char * dataSrc, size_t length, int width, in
void *vOutBits = NULL;
OPJ_INT32 * ptr;
int adjustR, bitsAllocated;
adjustR = 0;
bitsAllocated = image->comps[0].prec;
if (bitsAllocated == 8) {
BYTE v;
BYTE *newdata = new BYTE[image->x1*image->y1*image->numcomps];
for (unsigned int c=0; c<image->numcomps; c++) {
adjustR =
(image->comps[c].sgnd ? 1 << (bitsAllocated - 1) : 0);
ptr = (OPJ_INT32 *)image->comps[c].data;
for (unsigned int i = 0; i< image->x1*image->y1; i++){
v = (BYTE) (*ptr + adjustR);
Expand All @@ -192,8 +191,6 @@ unsigned char * J2K_Decode(unsigned char * dataSrc, size_t length, int width, in
OPJ_UINT16 v;
OPJ_UINT16 *newdata = new OPJ_UINT16[image->x1*image->y1*image->numcomps];
for (unsigned int c=0; c<image->numcomps; c++) {
adjustR =
(image->comps[c].sgnd ? 1 << (bitsAllocated - 1) : 0);
ptr = (OPJ_INT32 *)image->comps[c].data;
for (unsigned int i = 0; i< image->x1*image->y1; i++){
v = (OPJ_UINT16) (*ptr + adjustR);
Expand Down
150 changes: 92 additions & 58 deletions pkg/codec/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,87 +56,121 @@ func GetNativePixelData(e frame.EncapsulatedFrame) ([]byte, error) {

// GetStdImage returns the converted image in Go standard format
func GetStdImage(e frame.EncapsulatedFrame) (image.Image, error) {
buf := bytes.NewBuffer(e.Data)
r := dicomio.NewReader(bufio.NewReader(buf), binary.LittleEndian, int64(len(e.Data)))
var (
upLeft = image.Point{0, 0}
lowRight = image.Point{e.Cols, e.Rows}
img8 = image.NewRGBA(image.Rectangle{upLeft, lowRight})
img16 = image.NewRGBA64(image.Rectangle{upLeft, lowRight})
samplesPerPixel int
values8 [3]uint8 // red, green, blue
values16 [3]uint16
cols = e.Cols
a = 0xffff
i = 0
upLeft = image.Point{0, 0}
lowRight = image.Point{e.Cols, e.Rows}
img8 = image.NewRGBA(image.Rectangle{upLeft, lowRight})
img16 = image.NewRGBA64(image.Rectangle{upLeft, lowRight})
cols = e.Cols
a = 0xffff
pixelValues = make([]int, e.SamplesPerPixel)
)
if e.BitsAllocated == 8 {
for !r.IsLimitExhausted() {
i++
v, err := r.ReadUInt8()
if err != nil {
return nil, fmt.Errorf("read uint8 failed: %w", err)
decodedBytes, err := GetNativePixelData(e)
if err != nil {
return nil, fmt.Errorf("decode pixeldata: %w", err)
}
buf := bytes.NewBuffer(decodedBytes)
r := dicomio.NewReader(bufio.NewReader(buf), binary.LittleEndian, int64(buf.Len()))
oneByte := e.BitsAllocated == 8
for i := 0; i < e.Cols*e.Rows; i++ {
var clr color.Color
err := readPixelValues(e, *r, pixelValues)
if err != nil {
return nil, fmt.Errorf("read pixel values: %w", err)
}
switch e.SamplesPerPixel {
case 1:
r := pixelValues[0]
if oneByte {
clr = color.Gray{Y: uint8(r)}
} else {
clr = color.Gray16{Y: uint16(r)}
}
values8[samplesPerPixel%3] = v
samplesPerPixel++
if samplesPerPixel < e.SamplesPerPixel {
continue
case 3:
r, g, b := pixelValues[0], pixelValues[1], pixelValues[2]
if oneByte {
clr = color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
} else {
clr = color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
}
var clr color.Color
switch samplesPerPixel {
case 1:
clr = color.Gray{Y: values8[0]}
case 3:
r, g, b := values8[0], values8[1], values8[2]
clr = color.RGBA{r, g, b, uint8(a)}
case 4:
r, g, b, a := pixelValues[0], pixelValues[1], pixelValues[2], pixelValues[3]
if oneByte {
clr = color.RGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
} else {
clr = color.RGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}
}
}
if oneByte {
img8.Set(i%cols, i/cols, clr)
samplesPerPixel = 0
} else {
img16.Set(i%cols, i/cols, clr)
}
}
if oneByte {
return img8, nil
}
for !r.IsLimitExhausted() {
i++
v, err := r.ReadUInt16()
if err != nil {
return nil, fmt.Errorf("read uint16 failed: %w", err)
}
values16[samplesPerPixel%3] = v
samplesPerPixel++
if samplesPerPixel < e.SamplesPerPixel {
continue
return img16, nil
}

func readPixelValues(e frame.EncapsulatedFrame, r dicomio.Reader, pixelValues []int) error {
for sample := 0; sample < e.SamplesPerPixel; sample++ {
if r.IsLimitExhausted() {
break
}
var clr color.Color
switch samplesPerPixel {
case 1:
clr = color.Gray16{Y: values16[0]}
case 3:
r, g, b := values16[0], values16[1], values16[2]
clr = color.RGBA64{r, g, b, uint16(a)}
switch e.BitsAllocated {
case 8:
v, err := r.ReadUInt8()
if err != nil {
return fmt.Errorf("read uint8 failed: %w", err)
}
pixelValues[sample] = int(v)
case 16:
v, err := r.ReadUInt16()
if err != nil {
return fmt.Errorf("read uint16 failed: %w", err)
}
pixelValues[sample] = int(v)
case 32:
v, err := r.ReadUInt32()
if err != nil {
return fmt.Errorf("read uint32 failed: %w", err)
}
pixelValues[sample] = int(v)
default:
return fmt.Errorf("bitsAllocated not supported: %d", e.BitsAllocated)
}
img16.Set(i%cols, i/cols, clr)
samplesPerPixel = 0
}
return img16, nil
return nil
}

// Decode reads a JPEG image from EncapsulatedFrame and returns it as an NativeFrame.
func Decode(e frame.EncapsulatedFrame) (frame.NativeFrame, error) {
img, err := GetStdImage(e)
var nativeData = make([][]int, e.Cols*e.Rows)
decodedBytes, err := GetNativePixelData(e)
if err != nil {
return frame.NativeFrame{}, fmt.Errorf("get Go's std image: %w", err)
return frame.NativeFrame{}, fmt.Errorf("decode pixeldata: %w", err)
}
var nativeData [][]int
buf := bytes.NewBuffer(decodedBytes)
r := dicomio.NewReader(bufio.NewReader(buf), binary.LittleEndian, int64(buf.Len()))
var (
pixelValues = make([]int, e.SamplesPerPixel)
)
for i := 0; i < e.Cols*e.Rows; i++ {
clr := img.At(i%e.Cols, i/e.Cols)
r, g, b, a := clr.RGBA()
err := readPixelValues(e, *r, pixelValues)
if err != nil {
return frame.NativeFrame{}, fmt.Errorf("read pixel values: %w", err)
}
switch e.SamplesPerPixel {
case 1:
nativeData = append(nativeData, []int{int(r)})
r := pixelValues[0]
nativeData[i] = []int{int(r)}
case 3:
nativeData = append(nativeData, []int{int(r), int(g), int(b)})
r, g, b := pixelValues[0], pixelValues[1], pixelValues[2]
nativeData[i] = []int{int(r), int(g), int(b)}
case 4:
nativeData = append(nativeData, []int{int(r), int(g), int(b), int(a)})
r, g, b, a := pixelValues[0], pixelValues[1], pixelValues[2], pixelValues[3]
nativeData[i] = []int{int(r), int(g), int(b), int(a)}
}
}
nf := frame.NativeFrame{
Expand Down
1 change: 0 additions & 1 deletion pkg/codec/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ func assertJPEGDecompressing(t *testing.T, tc testify) {
t.Fatal("failed to decode image")
}

fr.Data = uncompressedBytes
img, err := GetStdImage(fr)
if err != nil {
t.Fatal(err)
Expand Down
6 changes: 3 additions & 3 deletions read.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ func (r *reader) readHeader() ([]*Element, error) {
return metaElems, nil
}

func newEncapsulatedFrame(d *Dataset, data []byte) (*frame.Frame, error) {
func NewEncapsulatedFrame(d *Dataset, data []byte) (*frame.Frame, error) {
e := frame.EncapsulatedFrame{
Data: data,
}
Expand Down Expand Up @@ -312,7 +312,7 @@ func (r *reader) readPixelData(vl uint32, d *Dataset, fc chan<- *frame.Frame) (V
break
}

f, err := newEncapsulatedFrame(d, data)
f, err := NewEncapsulatedFrame(d, data)
if err != nil {
break
}
Expand Down Expand Up @@ -410,7 +410,7 @@ func makeErrorPixelData(d *Dataset, reader io.Reader, vl uint32, fc chan<- *fram
return nil, fmt.Errorf("makeErrorPixelData: read pixelData: %w", err)
}

f, err := newEncapsulatedFrame(d, data)
f, err := NewEncapsulatedFrame(d, data)
if err != nil {
return nil, err
}
Expand Down
8 changes: 6 additions & 2 deletions write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,12 @@ func TestWrite(t *testing.T) {
IsEncapsulated: true,
Frames: []*frame.Frame{
{
Encapsulated: true,
EncapsulatedData: frame.EncapsulatedFrame{Data: []byte{1, 2, 3, 4}},
Encapsulated: true,
EncapsulatedData: frame.EncapsulatedFrame{
Data: []byte{1, 2, 3, 4},
TransferSyntax: uid.ExplicitVRLittleEndian,
BitsAllocated: 8,
},
},
},
}),
Expand Down

0 comments on commit bf55131

Please sign in to comment.