Writing PET Legacy Enhanced - Trouble writing elements (0020,9170) and (0020,9171)

127 views
Skip to first unread message

Jonathon Nye

unread,
May 16, 2020, 12:38:11 AM5/16/20
to pydicom
Hello,

I am writing our dynamic PET volumes into a Legacy PET Enhanced IOD.

The dataset has multiple nested sequences including,

- (0020,9170) - UnassignedSharedConvertedAttributesSequence
- (0020,9171) - UnassignedPerFrameConvertedAttributesSequence

When reading in this file after writing, pydicom does fine but my checking tools (Offis: dcm2xml and DVTk) show the following,

From Offis 
<element tag="0020,9171" vr="??" vm="1" len="46" name="Unknown Tag &amp; Data" binary="hidden"></element>
<element tag="0020,9170" vr="??" vm="1" len="808" name="Unknown Tag &amp; Data" binary="hidden"></element>

From DVTk
(0020,9171)-++"FEFF00E03000000008003200000000001800421206000000313030303020540000130800000031303030302E302054003013020000000100"
(0020,9170)-++"FEFF00E0200300000800700008000000584341542032 ....

I'm not able to read these data on other imaging analysis platforms (e.g. Matlab, Others). Really stuck here, could use some suggestions.

Code is below.

import numpy as np
import glob, os, time, shutil, uuid
import tempfile
import datetime
import pydicom as pyd
from pydicom.dataset import Dataset, FileDataset
from pydicom.sequence import Sequence
from pydicom.tag import Tag

def writeLegacyPETDICOM(inFilename, 
                        FrameStartTime = None, 
                        FrameDuration = None,
                        StudyDescription = None, 
                        SeriesDescription = None,
                        SeriesNumber = None,
                        HalfLife = None, 
                        RescaleSlope = False,
                        RescaleIntercept = False):
    '''
    

    Parameters
    ----------
    inFilename : TYPE
        DESCRIPTION.
    FrameStartTime : TYPE, optional
        DESCRIPTION. The default is None.
    FrameDuration : TYPE, optional
        DESCRIPTION. The default is None.
    StudyDescription : TYPE, optional
        DESCRIPTION. The default is None.
    SeriesNumber : TYPE, optional
        DESCRIPTION. The default is None.
    HalfLife : TYPE, optional
        DESCRIPTION. The default is None.
    RescaleSlope : TYPE, optional
        DESCRIPTION. The default is None.
    RescaleIntercept : TYPE, optional
        DESCRIPTION. The default is None.

    Returns
    -------
    None.

    '''
    # gather header from first frame to assist with setting data
    hdr = getVol(inFilename)[1]

    suffix = '.dcm'
    
    filename_little_endian = tempfile.NamedTemporaryFile(suffix = suffix).name
    # filename_little_endian = os.path.join(os.getcwd(),str(uuid.uuid4().hex) + suffix)

    # filename_big_endian = tempfile.NamedTemporaryFile(suffix = suffix).name
    
    print("Setting file meta information...")
    # populate required values
    file_meta = Dataset()
    
    # Patient Module
    file_meta.FileMetaInformationGroupLength = 128 #?? is this the preamble size in bytes?
    file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.128.1'
    file_meta.MediaStorageSOPInstanceUID = pyd.uid.generate_uid()
    file_meta.ImplementationClassUID = pyd.uid.generate_uid()
    
    print("Setting dataset values...")
    # Create the FileDataset instance (initially no data elements, but file_meta
    # supplied)
    ds = FileDataset(filename_little_endian, {},
                     file_meta=file_meta, preamble=b"\0" * 128)
    
    # Set the transfer syntax
    ds.file_meta.TransferSyntaxUID = pyd.uid.ImplicitVRLittleEndian
    # ds.is_little_endian = True
    # ds.is_implicit_VR = True
    
    dt = datetime.datetime.now()
    
    ds.SpecificCharacterSet = 'ISO_IR 100'
    ds.ImageType = ['ORIGINAL', 'PRIMARY', 'DYNAMIC', 'NONE']
    ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.128.1'
    ds.SOPInstanceUID = pyd.uid.generate_uid()
    ds.StudyDate = dt.strftime('%Y%m%d')
    ds.SeriesDate = dt.strftime('%Y%m%d')
    ds.ContentDate = dt.strftime('%Y%m%d')
    ds.StudyTime = dt.strftime('%H%M%S')
    ds.SeriesTime = dt.strftime('%H%M%S')
    ds.ContentTime = dt.strftime('%H%M%S')
    ds.Modality = 'PT'
    ds.Manufacturer = 'Unknown'
    ds.InstitutionName = ''
    ds.InstitutionAddress = ''
    ds.ReferringPhysicianName = ''
    ds.StationName = ''
    ds.StudyDescription = hdr['studyDescription'] if StudyDescription is None else StudyDescription #'N-13 Dynamic Stress XCAT Simulation'
    ds.SeriesDescription = hdr['seriesDescription'] if SeriesDescription is None else SeriesDescription
    ds.InstitutionalDepartmentName = ''
    ds.ManufacturerModelsName = 'Unknown'
    ds.PatientName = ''
    ds.PatientID = ''
    ds.PatientAge = '040Y'
    ds.PatientSize = ''
    ds.PatientWeight = '70'
    ds.DeviceSerialNumber = 'Unknown'
    ds.SoftwareVersions = 'Unknown'
    ds.DateOfLastCalibration = ['20130418', '20130121']
    ds.TimeOfLastCalibration = ['123624.875000', '172738.000000']
    ds.ContentQualification = 'PRODUCT'
    ds.CardiacSynchronizationTechnique = 'NONE'
    ds.RespiratoryMotionCompensationTechnique = 'NONE'
    ds.StudyInstanceUID = pyd.uid.generate_uid()
    ds.SeriesInstanceUID = pyd.uid.generate_uid()
    ds.StudyID = ''
    ds.SeriesNumber = '501' if SeriesNumber is None else SeriesNumber 
    ds.InstanceNumber = '1'
    ds.FrameOfReferenceUID = pyd.uid.generate_uid()
    ds.ImageContents = ''
    
    # dimension organization sequence
    DimensionOrganizationSequenceUID = pyd.uid.generate_uid(prefix = None)
    ds.DimensionOrganizationSequence = Sequence([Dataset()])
    ds.DimensionOrganizationSequence[0].DimensionOrganizationUID = DimensionOrganizationSequenceUID
    # end of sequence
    
    # dimention index sequence
    ds.DimensionIndexSequence = Sequence([Dataset(),Dataset(),Dataset()])
    ds.DimensionIndexSequence[0].DimensionOrganizationUID = DimensionOrganizationSequenceUID
    ds.DimensionIndexSequence[0].DimensionIndexPointer = Tag(0x020,0x9056)
    ds.DimensionIndexSequence[0].FunctionalGroupPointer = Tag(0x020,0x9111)
    ds.DimensionIndexSequence[1].DimensionOrganizationUID = DimensionOrganizationSequenceUID
    ds.DimensionIndexSequence[1].DimensionIndexPointer = Tag(0x020,0x9057)
    ds.DimensionIndexSequence[1].FunctionalGroupPointer = Tag(0x020,0x9111)
    ds.DimensionIndexSequence[2].DimensionOrganizationUID = DimensionOrganizationSequenceUID
    ds.DimensionIndexSequence[2].DimensionIndexPointer = Tag(0x020,0x9128)
    ds.DimensionIndexSequence[2].FunctionalGroupPointer = Tag(0x020,0x9111)
    # end of sequence
    
    ds.SamplesPerPixel = 1
    ds.PhotometricInterpretation = 'MONOCHROME2'
    ds.NumberOfFrames = str(hdr['frames'] * hdr['dim'][2])
    ds.Rows = int(hdr['dim'][0])
    ds.Columns = int(hdr['dim'][1])
    ds.BitsAllocated = 16
    ds.BitsStored = 16
    ds.HighBit = 15
    ds.PixelRepresentation = 1
    ds.AquisitionContextSequence = Sequence([Dataset()])
    ds.PresentationLUTShape = 'IDENTITY'
    
    # Shared Functional Groups Sequence
    ds.SharedFunctionalGroupsSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].ReferencedImageSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].DerivationImageSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].PETFrameTypeSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].PETFrameTypeSequence[0].FrameType = ['ORIGINAL', 'PRIMARY', 'DYNAMIC', 'NONE']
    ds.SharedFunctionalGroupsSequence[0].PlaneOrientationSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].PlaneOrientationSequence[0].ImageOrientationPatient = ['1', '0', '0', '0', '1', '0']
    
    #Unassigned Shared Converted Arrtributes Sequence
    ds.SharedFunctionalGroupsSequence[0].add_new([0x020,0x9170], 'SQ', Sequence([Dataset()]))
    # ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].Manufacturer = 'XCAT 2.0'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].CorrectedImage = ['NORM', 'DTIM', 'ATTN', 'SCAT', 'DECY']
    
    # Radiopharmaceutical Information Sequence
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadiopharmaceuticalStartTime = dt.strftime('%H%M%S') 
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclideTotalDose = '481000000'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclideHalfLife = str(hdr['halflife']) if HalfLife is None else HalfLife
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclidePositronFraction = '1.0'
    
     # Radionuclide Code Sequence
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclideCodeSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclideCodeSequence[0].CodeValue = 'C-107A1'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclideCodeSequence[0].CodingSchemeDesignator = 'SNM3'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclideCodeSequence[0].CodeMeaning = 'N^13^[^13^Nitrogen]'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclideCodeSequence[0].MappingResource = 'DCMR'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclideCodeSequence[0].ContextGroupVersion = '20020904000000.000000'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        RadiopharmaceuticalInformationSequence[0].RadionuclideCodeSequence[0].ContextIdentifier = '4020'   
    
    # Unassigned Shared Converted Arrtributes Sequence
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].NumberOfSlices = int(hdr['dim'][2])
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].NumberOfTimeSlices = int(hdr['frames'])
    
    # Patient Orientation Code Sequence
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].PatientOrientationCodeSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].CodeValue = 'F-10450'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].CodingSchemeDesignator = '99SDM'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].CodeMeaning = 'recumbent'  
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].MappingResource = 'DCMR'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].ContextGroupVersion = '20020904000000.000000'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].ContextIdentifier = '19'
        
    # Patient Orientation Modifier Code Sequence
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].PatientOrientationModifierCodeSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].PatientOrientationModifierCodeSequence[0].CodeValue = 'F-10340'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].PatientOrientationModifierCodeSequence[0].CodingSchemeDesignator = '99SDM'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].PatientOrientationModifierCodeSequence[0].CodeMeaning = 'supine'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].PatientOrientationModifierCodeSequence[0].MappingResource = 'DCMR'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].PatientOrientationModifierCodeSequence[0].ContextGroupVersion = '20020904000000.000000'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientOrientationCodeSequence[0].PatientOrientationModifierCodeSequence[0].ContextIdentifier = '20'
    
    # Patient Gantry Relationship Code Sequence
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientGantryRelationshipCodeSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientGantryRelationshipCodeSequence[0].CodeValue = 'F-10470'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientGantryRelationshipCodeSequence[0].CodingSchemeDesignator = '99SDM'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientGantryRelationshipCodeSequence[0].CodeMeaning = 'headfirst'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientGantryRelationshipCodeSequence[0].MappingResource = 'DCMR'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientGantryRelationshipCodeSequence[0].ContextGroupVersion = '20020904000000.000000'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].\
        PatientGantryRelationshipCodeSequence[0].ContextIdentifier = '21'
    
    # Unassigned Shared Converted Arrtributes Sequence
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].SeriesType = ['DYNAMIC', 'IMAGE']
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].Units = 'BQML'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].RandomsCorrectionMethod = 'DLYD'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].AttenuationCorrectionMethod = 'measured,AC_CT_STRESS'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].DecayCorrection = 'START'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].ReconstructionMethod = 'OSEM3D 3i16s'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].ScatterCorrectionMethod = 'Model-based'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].AxialAcceptance = '38'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].AxialMash = ['5', '6']
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].DecayFactor = '1.0'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].DoseCalibrationFactor = '1.0'
    ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence[0].ScatterFractionFactor = '0.45'
    
    # Pixel Measures Sequence
    ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence = Sequence([Dataset()])
    ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence[0].SliceThickness = str(hdr['pixdim'][2])
    ds.SharedFunctionalGroupsSequence[0].PixelMeasuresSequence[0].PixelSpacing = [str(hdr['pixdim'][0]), str(hdr['pixdim'][1])]
    ds.SharedFunctionalGroupsSequence[0].FrameVOILUTSequence = Sequence([Dataset()])
    
    # Create Pixel Array and Sequences for PerFrameFunctionGroupSequence
    Rows, Columns, Slices, Temporal = hdr['dim']
    pixels = np.zeros((Slices*hdr['frames'], Rows, Columns))
    
    # Per Frame Functional Groups Sequence
    ds.PerFrameFunctionalGroupsSequence = Sequence(None)
        
    mx = 0
    if RescaleSlope:
        # get the global maximum
        for gg in range(hdr['frames']):
            vol = getVol(inFilename, frame = gg)[0]
            if vol.max() > mx: mx = vol.max()
        scl_slope = mx / 32700
    else:
        scl_slope = 1.0

    TotSliceNum = 0

    for frameNum in range(hdr['frames']):
        # get volume and put into pixels array
        vol,hdr = getVol(inFilename, frame = frameNum, calibrate = False)
        start = frameNum * Slices
        end = start + Slices

        # flip data in z-direction
        volflipz = np.zeros(vol.shape)
        for jj in range(vol.shape[2]):
            volflipz[:,:,jj] = vol[:,:,-jj]
            

        pixels[start:end,:,:] = np.moveaxis(volflipz,2,0) / scl_slope
  
        for sliceNum in range(Slices):

            dsTmp = Dataset()
            dsTmp.FrameContentSequence = Sequence([Dataset()])
            dsTmp.FrameContentSequence[0].StackID = '1'
            dsTmp.FrameContentSequence[0].InStackPositionNumber = sliceNum + 1 # slice number 0 to 50, int
            dsTmp.FrameContentSequence[0].TemporalPositionIndex = frameNum + 1 # frame number
            dsTmp.FrameContentSequence[0].DimensionIndexValues = [1, sliceNum + 1, frameNum + 1] # [StackID, InStackPosition,TemporalPosition]
            
            dsTmp.PlanePositionSequence = Sequence([Dataset()])
            dsTmp.PlanePositionSequence[0].ImagePositionPatient = ['-156.08316400801', '-293.41428002869', str(-float(hdr['pixdim'][2]) * sliceNum - 100.5)] # last item needs to be index by slice thcikness
            
            dsTmp.UnassignedPerFrameConvertedAttributesSequence = Sequence([Dataset()])
            dsTmp.UnassignedPerFrameConvertedAttributesSequence[0].AcquisitionTime = ''
            dsTmp.UnassignedPerFrameConvertedAttributesSequence[0].ActualFrameDuration = str(int(1000 * hdr['duration'])) if FrameDuration is None else str(int(FrameDuration[frameNum] * 1000)) # in milliseconds
            dsTmp.UnassignedPerFrameConvertedAttributesSequence[0].FrameReferenceTime = str(1000 * hdr['duration']) if FrameStartTime is None else str(FrameStartTime[frameNum] * 1000) # start time in milliseconds ?
            dsTmp.UnassignedPerFrameConvertedAttributesSequence[0].ImageIndex = 1 # 1 to total number of slices for all temporal frames combined
            
            dsTmp.PixelValueTransformationSequence = Sequence([Dataset()])
            dsTmp.PixelValueTransformationSequence[0].RescaleIntercept = str(hdr['scl_inter']) #if not RescaleIntercept else str(RescaleIntercept)
            dsTmp.PixelValueTransformationSequence[0].RescaleSlope = str(hdr['scl_slope']) if not RescaleSlope else str(scl_slope)
            dsTmp.PixelValueTransformationSequence[0].RescaleType = 'US'            

            # add to Per-Frame Functional Groups Sequence
            ds.PerFrameFunctionalGroupsSequence.append(dsTmp)


    # set pixel data and save
    print('Rescaling images by: {}'.format(scl_slope))
    ds.PixelData = pixels.astype(np.int16).tobytes()
    ds.save_as(filename_little_endian, write_like_original = True)
    # copy file to inFilename Directory
    drive, path = os.path.splitdrive(inFilename)
    path, name = os.path.split(path)
    outFilename = os.path.join(drive, path,filename_little_endian.split(os.path.sep)[-1])
    shutil.move(filename_little_endian,outFilename)

Darcy Mason

unread,
May 16, 2020, 11:59:57 AM5/16/20
to pydicom
Hi Jonathon,

It appears the other libraries are not recognizing those tags as a Sequence (they don't know the VR).  I'm not sure why that would be.

I noticed you have the
ds.is_implicit_VR

commented out.  IIRC, that should be set, even if the TransferSyntaxUID is set.

Another option might be to try explicit VR, then the other readers should definitely know it is a SQ.

If those don't help, please try to reduce the code to the minimal amount to reproduce the problem - perhaps create a dataset with just one of those sequence data elements, with one sequence item in it.

Another thought in reducing the code - it would be much easier to help with it a bit more organized for items and sequences.  E.g. something like:

item0 = Dataset()
item0
.Manufacturer = 'XCAT 2.0'
item0.CorrectedImage = ['NORM', 'DTIM', 'ATTN', 'SCAT', 'DECY']

ds.SharedFunctionalGroupsSequence[0].UnassignedSharedConvertedAttributesSequence = Sequence([item0]) # although the SharedFunctionalGroupsSequence should also be itself handled similarly


Is much easier to follow and more "pythonic" pydicom use.  You could use the "codify" script in pydicom utilities on an existing file to help show good style for these, which will make it much easier to follow.  Run that, then remove most code lines, just leaving the relevant nested sequences causing the problem, and see if that file is accepted by the other libraries.

-Darcy

Jonathon Nye

unread,
May 16, 2020, 3:10:33 PM5/16/20
to pydicom
Hi Darcy,

Thank you for the quick reply.

I've reformatted my code to the minimum elements that will replicate the program I'm seeing. I appreciate your advice on reworking to be more pythonic.

When changing to explicit VR, DVTk is not able to open the file. When setting write_like_original = False, I'm able to read the explicit VR version in DVTk but the results are the same as my original post. Tags (0020,9170) and [0020,9171) are not being recognized as sequences.

The simpler version of code is below. Let me know if you are able to replicate my results.

import os
import tempfile
import datetime
import pydicom as pyd
from pydicom.dataset import Dataset, FileDataset
from pydicom.sequence import
 Sequence

suffix = '.dcm'
    
filename_little_endian = tempfile.NamedTemporaryFile(suffix = suffix).name

print("Setting file meta information...")
# populate required values
file_meta = Dataset()

# Patient Module
file_meta.FileMetaInformationGroupLength = 128 #?? is this the preamble size in bytes?
file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.128.1'
file_meta.MediaStorageSOPInstanceUID = pyd.uid.generate_uid()
file_meta.ImplementationClassUID = pyd.uid.generate_uid()

print("Setting dataset values...")
# Create the FileDataset instance (initially no data elements, but file_meta
# supplied)
ds = FileDataset(filename_little_endian, {},
                 file_meta=file_meta, preamble=b"\0" * 128)

# Set the transfer syntax
ds.file_meta.TransferSyntaxUID = pyd.uid.ImplicitVRLittleEndian
# ds.file_meta.TransferSyntaxUID = pyd.uid.ExplicitVRLittleEndian
ds.is_little_endian = True
ds.is_implicit_VR = True
# ds.is_explicit_VR = True

dt = datetime.datetime.now()

ds.SpecificCharacterSet = 'ISO_IR 100'
ds.ImageType = ['ORIGINAL', 'PRIMARY', 'DYNAMIC', 'NONE']
ds.SOPClassUID = '1.2.840.10008.5.1.4.1.1.128.1'
ds.SOPInstanceUID = pyd.uid.generate_uid()
ds.StudyDate = dt.strftime('%Y%m%d')
ds.SeriesDate = dt.strftime('%Y%m%d')
ds.ContentDate = dt.strftime('%Y%m%d')
ds.StudyTime = dt.strftime('%H%M%S')
ds.SeriesTime = dt.strftime('%H%M%S')
ds.ContentTime = dt.strftime('%H%M%S'
)

Item_0 = Dataset()
Item_0.Manufacturer = 'XCAT 2.0'
Item_0.CorrectedImage = ['NORM', 'DTIM', 'ATTN', 'SCAT', 'DECY']

Group_Items_1 = Dataset()
Group_Items_1.UnassignedSharedConvertedAttributesSequence = Sequence([Item_0])

# add Group_Items_1 to Shared Functional Groups Sequence
ds.SharedFunctionalGroupsSequence = Sequence([Group_Items_1])



# Per Frame Functional Groups Sequence
Item_1 = Dataset()
Item_1.StackID = '1'
Item_1.InStackPositionNumber = 1
Item_1.TemporalPositionIndex = 1
Item_1.DimensionIndexValues = [1, 1, 1]

Item_2 = Dataset()
Item_2.ImagePositionPatient = ['-156.08316400801', '-293.41428002869', -100.5]
    
Item_3 = Dataset()
Item_3.AcquisitionTime = ''
Item_3.ActualFrameDuration = '10000'
Item_3.FrameReferenceTime = '10000'
Item_3.ImageIndex = 1 

Item_4 = Dataset()
Item_4.RescaleIntercept = '0'
Item_4.RescaleSlope = '1'
Item_4.RescaleType = 'US' 

Groups_Items_2 = Dataset()
Groups_Items_2.FrameContentSequence = Sequence([Item_1])
Groups_Items_2.PlanePositionSequence = Sequence([Item_2])
Groups_Items_2.UnassignedPerFrameConvertedAttributesSequence = Sequence([Item_3])
Groups_Items_2.PixelValueTransformationSequence = Sequence([Item_4])
                                                            
# add Group_Items to the Per Functional Groups Sequence
ds.PerFrameFunctionalGroupsSequence = Sequence([Groups_Items_2])  
 
ds.save_as(filename_little_endian, write_like_original = True)

On Saturday, May 16, 2020 at 12:38:11 AM UTC-4, Jonathon Nye wrote:

Jonathon Nye

unread,
May 16, 2020, 4:42:54 PM5/16/20
to pydicom
Dear Darcy,

I ran codify.py and re-saved the output. I was able to verify that all tags were successfully written with DVTk.

Thanks for the hint. Let me work on this for a while and hopefully end up successful.

Thanks again for the moral support!

Jon


On Saturday, May 16, 2020 at 12:38:11 AM UTC-4, Jonathon Nye wrote:
Reply all
Reply to author
Forward
0 new messages