Comment créer un jeu de données DICOM compressé JPEG à l'aide de pydicom?

14

J'essaie de créer une image DICOM compressée JPEG à l'aide de pydicom . Un bon matériel source sur les images DICOM colorées peut être trouvé ici , mais c'est principalement de la théorie et du C ++. Dans l'exemple de code ci-dessous, je crée des points de suspension bleu pâle à l'intérieur output-raw.dcm(non compressés) qui ressemblent bien à ceci:

Exemple d'image 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

À la toute fin, j'essaie de créer des DICOM compressés: j'ai essayé de définir différentes syntaxes de transfert, compressions avec PIL, mais pas de chance. Je crois que le fichier DICOM généré est corrompu. Si je devais convertir le fichier DICOM brut en JPEG compressé avec gdcm-tools:

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

En faisant un dcmdumpsur ce fichier converti, nous pourrions voir une structure intéressante, que je ne sais pas reproduire en utilisant 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

J'ai essayé d'utiliser le module d' encapsulation de pydicom , mais je pense que c'est principalement pour lire des données, pas pour écrire. Quelqu'un d'autre a des idées sur la façon de traiter ce problème, comment créer / encoder ces PixelSequences? J'adorerais créer des DICOM compressés JPEG en Python ordinaire sans exécuter d'outils externes.

mseimys
la source
Pouvez-vous lire l'image compressée JPEG via PyDicom?
norok2
Oui bien sûr je peux le décompresser et le lire. Bien sûr, vous avez besoin de quelques bibliothèques supplémentaires installées, voici les combinaisons possibles: pydicom.github.io/pydicom/stable/image_data_handlers.html
mseimys
Ce cas d'utilisation a-t-il été résolu à chaque fois? J'adorerais voir moi-même de la documentation à ce sujet.
Steven Hart

Réponses:

4

DICOM nécessite que les données de pixels compressées soient encapsulées (voir les tableaux en particulier). Une fois que vous avez vos données d'image compressées, vous pouvez utiliser la méthode encaps.encapsulate () pour créer bytesune utilisation appropriée avec 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
la source
Cela fonctionne, mais uniquement pour le format JPEG à une seule image. Quelqu'un sait comment encoder un JPEG multiframe?
Steven Hart
Chaque image doit être encodée séparément, puis toutes les images encapsulées avecencapsulate([frame1, frame2, ...])
scaramallion
1

Essayer la solution de @scaramallion, avec plus de détails semble fonctionner:

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
la source