Bagaimana cara membuat dataset DICOM terkompresi JPEG menggunakan pydicom?

14

Saya mencoba membuat gambar DICOM terkompresi JPEG menggunakan pydicom . Bahan sumber yang bagus tentang gambar DICOM berwarna-warni dapat ditemukan di sini , tetapi sebagian besar teori dan C ++. Dalam contoh kode di bawah ini saya membuat ellipsis biru pucat output-raw.dcm(tidak terkompresi) yang terlihat baik seperti ini:

Contoh gambar DICOM

import io
from PIL import Image, ImageDraw
from pydicom.dataset import Dataset
from pydicom.uid import generate_uid, JPEGExtended
from pydicom._storage_sopclass_uids import SecondaryCaptureImageStorage

WIDTH = 100
HEIGHT = 100


def ensure_even(stream):
    # Very important for some viewers
    if len(stream) % 2:
        return stream + b"\x00"
    return stream


def bob_ross_magic():
    image = Image.new("RGB", (WIDTH, HEIGHT), color="red")
    draw = ImageDraw.Draw(image)
    draw.rectangle([10, 10, 90, 90], fill="black")
    draw.ellipse([30, 20, 70, 80], fill="cyan")
    draw.text((11, 11), "Hello", fill=(255, 255, 0))
    return image


ds = Dataset()
ds.is_little_endian = True
ds.is_implicit_VR = True
ds.SOPClassUID = SecondaryCaptureImageStorage
ds.SOPInstanceUID = generate_uid()
ds.fix_meta_info()
ds.Modality = "OT"
ds.SamplesPerPixel = 3
ds.BitsAllocated = 8
ds.BitsStored = 8
ds.HighBit = 7
ds.PixelRepresentation = 0
ds.PhotometricInterpretation = "RGB"
ds.Rows = HEIGHT
ds.Columns = WIDTH

image = bob_ross_magic()
ds.PixelData = ensure_even(image.tobytes())

image.save("output.png")
ds.save_as("output-raw.dcm", write_like_original=False)  # File is OK

#
# Create compressed image
#
output = io.BytesIO()
image.save(output, format="JPEG")

ds.PixelData = ensure_even(output.getvalue())
ds.PhotometricInterpretation = "YBR_FULL_422"
ds.file_meta.TransferSyntaxUID = JPEGExtended

ds.save_as("output-jpeg.dcm", write_like_original=False)  # File is corrupt

Pada akhirnya saya mencoba membuat DICOM terkompresi: Saya mencoba mengatur berbagai sintaks transfer, kompresi dengan PIL, tetapi tidak berhasil. Saya percaya file DICOM yang dihasilkan rusak. Jika saya mengkonversi file DICOM mentah ke JPEG yang dikompres dengan gdcm-tools:

$ gdcmconv -J output-raw.dcm output-jpeg.dcm

Dengan melakukan dcmdumppada file yang dikonversi ini kita bisa melihat struktur yang menarik, yang saya tidak tahu bagaimana cara mereproduksi menggunakan pydicom:

$ dcmdump output-jpeg.dcm

# Dicom-File-Format

# Dicom-Meta-Information-Header
# Used TransferSyntax: Little Endian Explicit
(0002,0000) UL 240                                      #   4, 1 FileMetaInformationGroupLength
(0002,0001) OB 00\01                                    #   2, 1 FileMetaInformationVersion
(0002,0002) UI =SecondaryCaptureImageStorage            #  26, 1 MediaStorageSOPClassUID
(0002,0003) UI [1.2.826.0.1.3680043.8.498.57577581978474188964358168197934098358] #  64, 1 MediaStorageSOPInstanceUID
(0002,0010) UI =JPEGLossless:Non-hierarchical-1stOrderPrediction #  22, 1 TransferSyntaxUID
(0002,0012) UI [1.2.826.0.1.3680043.2.1143.107.104.103.115.2.8.4] #  48, 1 ImplementationClassUID
(0002,0013) SH [GDCM 2.8.4]                             #  10, 1 ImplementationVersionName
(0002,0016) AE [gdcmconv]                               #   8, 1 SourceApplicationEntityTitle

# Dicom-Data-Set
# Used TransferSyntax: JPEG Lossless, Non-hierarchical, 1st Order Prediction
...
... ### How to do the magic below?
...
(7fe0,0010) OB (PixelSequence #=2)                      # u/l, 1 PixelData
  (fffe,e000) pi (no value available)                     #   0, 1 Item
  (fffe,e000) pi ff\d8\ff\ee\00\0e\41\64\6f\62\65\00\64\00\00\00\00\00\ff\c3\00\11... # 4492, 1 Item
(fffe,e0dd) na (SequenceDelimitationItem)               #   0, 0 SequenceDelimitationItem

Saya mencoba menggunakan modul encaps pydicom , tapi saya pikir itu sebagian besar untuk membaca data, bukan menulis. Adakah yang punya ide bagaimana menangani masalah ini, bagaimana membuat / menyandikan ini PixelSequence? Ingin sekali membuat DICOM terkompresi JPEG dalam Python polos tanpa menjalankan alat eksternal.

mseimys
sumber
Apakah Anda dapat membaca gambar yang dikompresi JPEG melalui PyDicom?
norok2
Ya tentu saja saya bisa mendekompres dan membacanya. Tentu saja, Anda memerlukan beberapa perpustakaan tambahan yang diinstal, inilah kombinasi yang mungkin: pydicom.github.io/pydicom/stable/image_data_handlers.html
mseimys
Apakah ini kasus penggunaan setiap diselesaikan? Saya ingin melihat beberapa dokumentasi sendiri.
Steven Hart

Jawaban:

4

DICOM membutuhkan Data Pixel terkompresi untuk dienkapsulasi (lihat tabel khususnya). Setelah Anda memiliki data gambar terkompresi, Anda dapat menggunakan metode encaps.encapsulate () untuk membuatnya bytescocok untuk digunakan dengan Pixel Data :

from pydicom.encaps import encapsulate

# encapsulate() requires a list of bytes, one item per frame
ds.PixelData = encapsulate([ensure_even(output.getvalue())])
# Need to set this flag to indicate the Pixel Data is compressed
ds['PixelData'].is_undefined_length = True
ds.PhotometricInterpretation = "YBR_FULL_422"
ds.file_meta.TransferSyntaxUID = JPEGExtended

ds.save_as("output-jpeg.dcm", write_like_original=False)
scaramallion
sumber
Ini berfungsi, tetapi hanya untuk jpeg frame tunggal. Adakah yang tahu cara mengkodekan jreg multiframe?
Steven Hart
Setiap frame harus dikodekan secara terpisah dan kemudian semua frame dienkapsulasi denganencapsulate([frame1, frame2, ...])
scaramallion
1

Mencoba solusi dari @scaramallion, dengan tampilan yang lebih detail:

import numpy as np
from PIL import Image
import io

# set some parameters
num_frames = 4
img_size = 10

# Create a fake RGB dataset
random_image_array = (np.random.random((num_frames, img_size, img_size, 3))*255).astype('uint8')
# Convert to PIL
imlist = []
for i in range(num_frames):   # convert the multiframe image into RGB of single frames (Required for compression)
    imlist.append(Image.fromarray(tmp))

# Save the multipage tiff with jpeg compression
f = io.BytesIO()
        imlist[0].save(f, format='tiff', append_images=imlist[1:], save_all=True, compression='jpeg')
# The BytesIO object cursor is at the end of the object, so I need to tell it to go back to the front
f.seek(0)
img = Image.open(f)

# Get each one of the frames converted to even numbered bytes
img_byte_list = []
for i in range(num_frames):
    try:
        img.seek(i)
        with io.BytesIO() as output:
            img.save(output, format='jpeg')
            img_byte_list.append(output.getvalue())
    except EOFError:
         # Not enough frames in img
         break

ds.PixelData = encapsulate([x for x in img_byte_list])
ds['PixelData'].is_undefined_length = True
ds.is_implicit_VR = False
ds.LossyImageCompression = '01'
ds.LossyImageCompressionRatio = 10 # default jpeg
ds.LossyImageCompressionMethod = 'ISO_10918_1'
ds.file_meta.TransferSyntaxUID = '1.2.840.10008.1.2.4.51'

ds.save_as("output-jpeg.dcm", write_like_original=False)
Steven Hart
sumber