Source code for toor.ImageReader.DICOM.series
from dateutil.parser import parse
from pydicom.dataset import Dataset, FileDataset
import numpy as np
[docs]
class GeneralSeries(FileDataset):
def __init__(self, filename_or_obj, dataset, ds=None):
super().__init__(filename_or_obj, dataset)
self.ds = ds
self.ds.SeriesDate = ""
self.ds.SeriesTime = ""
self.ds.Modality = "PT"
self.ds.SeriesDescription = "Unknown"
self.ds.PerformingPhysicianName = "Unknown"
self.ds.OperatorsName = "Unknown"
self.ds.AnatomicalOrientationType = 'QUADRUPED'
self.ds.BodyPartExamined = "WHOLEBODY"
self.ds.PatientPosition = "HFS"
self.ds.SeriesInstanceUID = ""
self.ds.SeriesNumber = "1"
self.ds.Laterality = None
self.ds.SmallestPixelValueInSeries = 0
self.ds.LargestPixelValueInSeries = 0
[docs]
def override(self, acquisitionInfo, seriesInfo, imageInfo):
""""""
self.ds.SeriesDate = parse(acquisitionInfo.AcquisitionStartTime).strftime('%Y%m%d')
self.ds.SeriesTime = parse(acquisitionInfo.AcquisitionStartTime).strftime("%H%M%S")
self.ds.SeriesNumber = seriesInfo.seriesNumber
self.ds.SeriesInstanceUID = seriesInfo.seriesInstanceUID
seriesDescription = "Unknown"
if seriesInfo.current_type_of_reconstruction == "DYNAMIC":
seriesDescription = "{} {}".format(seriesInfo.current_type_of_reconstruction, imageInfo.number_cumulative_turns)
elif seriesInfo.current_type_of_reconstruction == "WHOLE BODY":
seriesDescription = "{} {} to {}s".format(seriesInfo.current_type_of_reconstruction,
imageInfo.remove_turns["Init time"], imageInfo.remove_turns["End time"])
elif seriesInfo.current_type_of_reconstruction == "STATIC":
seriesDescription = "{} organ".format(seriesInfo.current_type_of_reconstruction)
self.ds.SeriesDescription = seriesDescription
[docs]
class PETSeries(FileDataset):
def __init__(self, filename_or_obj, dataset, ds=None):
super().__init__(filename_or_obj, dataset)
self.ds = ds
# self.ds.SeriesDate = "" # Override in General Series
# self.ds.SeriesTime = "" # Override in SeriesTime
self.ds.AcquisitionTerminationCondition = "MANU" # if fixed turns Time
self.ds.AcquisitionStartCondition = "MANU"
self.ds.ReconstructionDiameter = "48" # real fov
self.ds.GantryDetectorTilt = "0" # could not be true
self.ds.GantryDetectorSlew = "0" # could be different of 0
self.ds.FieldOfViewShape = "CYLINDRICAL RING"
self.ds.FieldOfViewDimensions = [int(48), int(32*2+31*0.28)]
self.ds.CorrectedImage = ['DECY', 'NORM', 'DCAL', 'RADL','SCAT']
self.ds.EnergyWindowRangeSequence = [Dataset()]
self.ds.EnergyWindowRangeSequence[0].EnergyWindowLowerLimit = 320
self.ds.EnergyWindowRangeSequence[0].EnergyWindowUpperLimit = 720
# self.ds.NumberOfRRIntervals = 0
self.ds.NumberOfTimeSlices = 1
self.ds.NumberOfSlices = 1
self.ds.TypeOfDetectorMotion = "CONTINUOUS"
self.ds.SeriesType = ['STATIC', 'IMAGE']
self.ds.Units = "BQML"
self.ds.CountsSource = "EMISSION"
self.ds.SUVType = "BW"
self.ds.RandomsCorrectionMethod = "None"
self.ds.AttenuationCorrectionMethod = "None"
self.ds.DecayCorrection = "START"
self.ds.ReconstructionMethod = "ListMode - Unknown"
self.ds.ScatterCorrectionMethod = "Dual-energy window"
self.ds.AxialAcceptance = np.degrees(np.arcsin(2.28/60))
self.ds.DetectorElementSize = "2"
self.ds.CoincidenceWindowWidth = "40"
[docs]
def override(self, imageInfo, acquisitionInfo, seriesInfo):
""" """
self.ds.ReconstructionMethod = imageInfo.algorithm
self.ds.SeriesType = [seriesInfo.seriesType, seriesInfo.seriesTypeValue2]
if seriesInfo.seriesType == 'DYNAMIC':
# self.ds.NumberOfTimeSlices = int(imageInfo.dynamicFrames*seriesInfo.volume.shape[2])
self.ds.NumberOfTimeSlices = int(imageInfo.dynamicFrames)
self.ds.NumberOfSlices = int(seriesInfo.volume.shape[2])
else:
self.ds.NumberOfTimeSlices = int(1)
self.ds.NumberOfSlices = int(seriesInfo.volume.shape[2])
self.ds.FrameReferenceTime = 0
self.ds.ReconstructionDiameter = str(imageInfo.fov)
# self.ds.NumberOfSlices = int(acquisitionInfo["Number of turns"]) * int(imageInfo['NumberOfSlices'])
[docs]
class PETIsotope(FileDataset):
def __init__(self, filename_or_obj, dataset, ds=None):
super().__init__(filename_or_obj, dataset)
self.ds = ds
self.ds.RadiopharmaceuticalInformationSequence = [Dataset()]
self.RadioInformationSequence = self.ds.RadiopharmaceuticalInformationSequence[0]
self.RadioInformationSequence.Radiopharmaceutical = "Unknown"
self.RadioInformationSequence.RadiopharmaceuticalRoute = "Unknown"
self.RadioInformationSequence.RadiopharmaceuticalVolume = "0"
self.ds.RadiopharmaceuticalInformationSequence[0].RadionuclideCodeSequence = [Dataset()]
self.RadionuclideCodeSequence = self.ds.RadiopharmaceuticalInformationSequence[0].RadionuclideCodeSequence = [Dataset()]
self.RadionuclideCodeSequence.CodeValue = ""
[docs]
def override(self, acquisitionInfo):
"""DICOM properties """
dt = parse(acquisitionInfo.StartDateTime.split(' ')[1])
# Required, Empty if Unknown (2)
self.RadioInformationSequence.Radiopharmaceutical = acquisitionInfo.Tracer
self.RadioInformationSequence.RadiopharmaceuticalRoute = acquisitionInfo.Route
if acquisitionInfo.VolumeTracer is None:
acquisitionInfo.VolumeTracer = "0"
elif isinstance(acquisitionInfo.VolumeTracer, float):
acquisitionInfo.VolumeTracer = str(acquisitionInfo.VolumeTracer)
elif isinstance(acquisitionInfo.VolumeTracer, int):
acquisitionInfo.VolumeTracer = str(acquisitionInfo.VolumeTracer)
self.RadioInformationSequence.RadiopharmaceuticalVolume = acquisitionInfo.VolumeTracer.replace(",", ".")
if (acquisitionInfo.TotalDose is not None) and (
acquisitionInfo.ResidualActivity is not None):
## ERRADO
self.RadioInformationSequence.RadionuclideTotalDose = \
str(float(acquisitionInfo.TotalDose) - float(acquisitionInfo.ResidualActivity))
self.RadioInformationSequence.RadiopharmaceuticalStartDateTime = dt.strftime("%H%M%S")
self.RadioInformationSequence.RadiopharmaceuticalSpecificActivity = acquisitionInfo.TotalDose #errado
self.RadioInformationSequence.RadionuclidePositronFraction = acquisitionInfo.PositronFraction
self.RadioInformationSequence.RadionuclideHalfLife = acquisitionInfo.HalfLife
self.RadionuclideCodeSequence.CodeValue = acquisitionInfo.BatchCodeValue
self.RadionuclideCodeSequence.CodingSchemeDesignator = acquisitionInfo.BatchCodingSchemeDesignator
self.RadionuclideCodeSequence.CodingSchemeVersion = acquisitionInfo.BatchCodingSchemeVersion
self.RadionuclideCodeSequence.CodeMeaning = acquisitionInfo.BatchCodeMeaning
self.RadionuclideCodeSequence.LongCodeValue = acquisitionInfo.BatchLongCodeValue
self.RadionuclideCodeSequence.URNCodeValue = acquisitionInfo.BatchUrnCodeValue
[docs]
class PETMultiGatedAcquisition(FileDataset):
"""Future Implementation"""