-
-
Notifications
You must be signed in to change notification settings - Fork 154
Utils function for read/write value from unprocessed data #306
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
base: main
Are you sure you want to change the base?
Changes from 5 commits
3055908
8896fd2
434c581
3161ae4
2a226e4
16701fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| package inplace | ||
|
|
||
| import ( | ||
| "encoding/binary" | ||
| "errors" | ||
| "fmt" | ||
| "strconv" | ||
|
|
||
| "github.com/suyashkumar/dicom" | ||
| "github.com/suyashkumar/dicom/pkg/tag" | ||
| ) | ||
|
|
||
| // GetIntFromValue is similar to dicom.MustGetInts but without panic | ||
| func GetIntFromValue(v dicom.Value) (int, error) { | ||
| if v.ValueType() != dicom.Ints { | ||
| return 0, errors.New("value is not ints") | ||
| } | ||
| arr, ok := v.GetValue().([]int) | ||
| if !ok { | ||
| return 0, errors.New("actual value is not ints") | ||
| } | ||
| if len(arr) == 0 { | ||
| return 0, errors.New("empty ints value") | ||
| } | ||
| return arr[0], nil | ||
| } | ||
|
|
||
| // GetStringFromValue is similar to dicom.MustGetStrings but without panic | ||
| func GetStringFromValue(v dicom.Value) (string, error) { | ||
| if v.ValueType() != dicom.Strings { | ||
| return "", errors.New("value is not ints") | ||
| } | ||
| arr, ok := v.GetValue().([]string) | ||
| if !ok { | ||
| return "", errors.New("actual value is not ints") | ||
| } | ||
| if len(arr) == 0 { | ||
| return "", errors.New("empty ints value") | ||
| } | ||
| return arr[0], nil | ||
| } | ||
|
|
||
| // PixelDataMetadata is the metadata for tag.PixelData | ||
| type PixelDataMetadata struct { | ||
| Rows int | ||
| Cols int | ||
| Frames int | ||
| SamplesPerPixel int | ||
| BitsAllocated int | ||
| PlanarConfiguration int | ||
| Bo binary.ByteOrder | ||
| } | ||
|
|
||
| // GetPixelDataMetadata returns the pixel data metadata. | ||
| func GetPixelDataMetadata(ds *dicom.Dataset) (*PixelDataMetadata, error) { | ||
| re := &PixelDataMetadata{} | ||
| rows, err := ds.FindElementByTag(tag.Rows) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("get Rows element: %w", err) | ||
| } | ||
| if re.Rows, err = GetIntFromValue(rows.Value); err != nil { | ||
| return nil, fmt.Errorf("convert Rows element to int: %w", err) | ||
| } | ||
|
|
||
| cols, err := ds.FindElementByTag(tag.Columns) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("get Columns element: %w", err) | ||
| } | ||
| if re.Cols, err = GetIntFromValue(cols.Value); err != nil { | ||
| return nil, fmt.Errorf("convert Columns element to int: %w", err) | ||
| } | ||
|
|
||
| numberOfFrames, err := ds.FindElementByTag(tag.NumberOfFrames) | ||
| if err != nil { | ||
| re.Frames = 1 | ||
| } else { | ||
| var framesStr string | ||
| framesStr, err = GetStringFromValue(numberOfFrames.Value) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("convert NumberOfFrames element to str: %w", err) | ||
| } | ||
| if re.Frames, err = strconv.Atoi(framesStr); err != nil { | ||
| return nil, fmt.Errorf("convert NumberOfFrames to int: %w", err) | ||
| } | ||
| } | ||
|
|
||
| samplesPerPixel, err := ds.FindElementByTag(tag.SamplesPerPixel) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("get SamplesPerPixel element: %w", err) | ||
| } | ||
| if re.SamplesPerPixel, err = GetIntFromValue(samplesPerPixel.Value); err != nil { | ||
| return nil, fmt.Errorf("convert SamplesPerPixel element to int: %w", err) | ||
| } | ||
| bitsAllocated, err := ds.FindElementByTag(tag.BitsAllocated) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("get BitsAllocated element: %w", err) | ||
| } | ||
| if re.BitsAllocated, err = GetIntFromValue(bitsAllocated.Value); err != nil { | ||
| return nil, fmt.Errorf("convert BitsAllocated element to int: %w", err) | ||
| } | ||
| re.Bo, _, err = ds.TransferSyntax() | ||
| if err != nil { | ||
| return nil, fmt.Errorf("get byteOrder: %w", err) | ||
| } | ||
|
|
||
| planarConfElement, err := ds.FindElementByTag(tag.PlanarConfiguration) | ||
| if err != nil { | ||
| re.PlanarConfiguration = 0 | ||
| } else { | ||
| if re.PlanarConfiguration, err = GetIntFromValue(planarConfElement.Value); err != nil { | ||
| return nil, fmt.Errorf("convert Rows element to int: %w", err) | ||
| } | ||
| } | ||
|
|
||
| return re, nil | ||
| } | ||
|
|
||
| // IsSafeForUnprocessedValueDataHandling check if we can support in-place read-write | ||
| // from Pixeldata.UnprocessedValueData | ||
| // This avoids the case that we can not handle it, yet. | ||
| func IsSafeForUnprocessedValueDataHandling(info *PixelDataMetadata, unprocessedValueData []byte) error { | ||
| // https://dicom.innolitics.com/ciods/enhanced-mr-image/enhanced-mr-image/00280006 | ||
| if info.PlanarConfiguration == 1 { | ||
| return fmt.Errorf("unsupported PlanarConfiguration: %d", info.PlanarConfiguration) | ||
| } | ||
| // TODO: support for BitsAllocated == 1 | ||
| switch info.BitsAllocated { | ||
| case 8, 16, 32: | ||
| default: // bitsAllocated = 1 and other cases | ||
| return fmt.Errorf("unsupported bit allocated: %d", info.BitsAllocated) | ||
| } | ||
| pixelsPerFrame := info.Rows * info.Cols | ||
| bytesAllocated := info.BitsAllocated / 8 | ||
| expectedBytes := bytesAllocated * info.SamplesPerPixel * info.Frames * pixelsPerFrame | ||
| // odd number of bytes. | ||
| if expectedBytes%2 == 1 { | ||
| expectedBytes += 1 | ||
| } | ||
| if len(unprocessedValueData) != expectedBytes { | ||
| return errors.New("mismatch data size") | ||
| } | ||
| return nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| // Package inplace contains code for handling UnprocessedValueData | ||
| package inplace | ||
|
|
||
| // ReadUnprocessedValueData read the value of Dicom image directly | ||
| // from the byte array of PixelData.UnprocessedValueData with given frame ID. | ||
| // This ease the memory usage of reading DICOM image. | ||
| func ReadUnprocessedValueData(info *PixelDataMetadata, unprocessedValueData []byte, frameIndex int) ([][]int, error) { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since this returns [][]int, this will still have the same issue of expanding the memory from the smaller int to the 64-bit int right? Is the main place this saves memory in the fact that it only does one frame at a time?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, most of the DICOM with the issues contains multiple frames. But, I think I would change to a new structure of storing data. B/c users need to adapt to new way of read/write pixel data anyway.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed at 16701fa |
||
| pixelsPerFrame := info.Rows * info.Cols | ||
| bytesAllocated := info.BitsAllocated / 8 | ||
| offset := frameIndex * pixelsPerFrame * info.SamplesPerPixel * bytesAllocated | ||
| samplesPerPixel := info.SamplesPerPixel | ||
|
|
||
| re := make([][]int, pixelsPerFrame) | ||
| for i := 0; i < pixelsPerFrame; i++ { | ||
| re[i] = make([]int, samplesPerPixel) | ||
| for j := 0; j < samplesPerPixel; j++ { | ||
| pointOffset := offset + i*info.SamplesPerPixel*bytesAllocated + j*bytesAllocated | ||
| switch bytesAllocated { | ||
| case 1: | ||
| re[i][j] = int(unprocessedValueData[pointOffset]) | ||
| case 2: | ||
| re[i][j] = int(info.Bo.Uint16(unprocessedValueData[pointOffset : pointOffset+bytesAllocated])) | ||
| case 4: | ||
| re[i][j] = int(info.Bo.Uint32(unprocessedValueData[pointOffset : pointOffset+bytesAllocated])) | ||
| } | ||
| } | ||
| } | ||
| return re, nil | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.